Trim victory-lap docs language

This commit is contained in:
Meta Vens 2026-04-29 11:43:17 +01:00
commit f2b62bc25c
12 changed files with 73 additions and 103 deletions

View file

@ -3,12 +3,11 @@
## Project Purpose
Repixelizer is a standalone Python CLI for forcing fake pixel art onto a real
pixel lattice without turning the solver into a landfill of compensating
heuristics.
pixel lattice with a lean, inspectable solver.
The live machine is intentionally lean: infer a lattice, analyze structure,
optimize one displacement field, sample real source pixels, then write
diagnostics good enough to catch when we are lying to ourselves.
diagnostics that make the result inspectable.
## Canonical State
@ -118,8 +117,11 @@ When the user says to prepare for imminent compaction:
- If the diff grows while understanding shrinks, stop implementation and switch
to diagnosis, mapping, or simplification.
- Keep prose and maps grounded in concrete source paths.
- Do not reintroduce trays, portrait layers, candidate sets, or multi-stage
solver religions unless the design has actually changed and the map says so.
- Keep trays, portrait layers, candidate sets, and multi-stage solver stacks out
of the code unless the design has changed and the map says so.
- Keep documentation focused on the current machine. Avoid victory-lap prose
about removed approaches; put historical context only in evidence,
changelogs, or compact rejected-path notes when it changes future decisions.
- Before handoff or compaction, sync `state/map.yaml`, refresh
`notes/fresh-workspace-handoff.md`, add distilled evidence when future belief
changed, and make the next action explicit.

View file

@ -6,11 +6,10 @@ It is aimed at the annoying middle ground: sprites, emblems, and logos that
look locally pixelated but fall apart the second you ask them to commit to one
lattice like grown-ups.
Instead of pretending this is a resize problem, Repixelizer treats it as
lattice inference plus `phase-field` reconstruction: infer the ruler, preview
the edges, let every output cell search a tiny local window for quieter paint,
relax the whole sheet back into a coherent lattice, then sample once and go
home.
Repixelizer treats fake pixel art as lattice inference plus `phase-field`
reconstruction: infer the ruler, preview the edges, let every output cell
search a tiny local window for quieter paint, relax the whole sheet back into a
coherent lattice, then sample once and go home.
## GUI First
@ -82,8 +81,7 @@ What exists now:
- the `phase-field` reconstruction engine in `src/repixelizer/phase_field.py`
- automatic diagnostics, comparisons, and benchmark runs
- a tuning harness for offline parameter sweeps
- metrics that finally care about visible structure instead of only pleasing the lattice accountant
- `source_structure` is reported alongside `source_fidelity`, because the old metric was happily calling better-looking images worse
- `source_fidelity` and `source_structure` metrics for lattice agreement and visible structure
- a shippable explicit local-search solver config, with tuning notes in `docs/solver-tuning-lessons-2026-04-29.md`
Current weak spots:
@ -169,13 +167,13 @@ That keeps benchmark assets, attribution exports, and tuning runs from polluting
- `tests`: focused regression tests
- `docs/spec.md`: product and technical spec
- `docs/implementation-plan.md`: working roadmap
- `docs/lean-optimizer-algorithm-map.md`: map of the live displacement-field optimizer
- `docs/lean-optimizer-algorithm-map.md`: map of the live phase-field solver
- `examples/corpus/README.md`: local corpus layout and attribution workflow
Core modules:
- `inference.py`: target size inference
- `phase_field.py`: default displacement-field optimizer
- `phase_field.py`: explicit local-search phase-field solver
- `metrics.py`: fidelity, adjacency, and motif metrics
- `benchmark.py`: corpus benchmark runner
- `tuning.py`: black-box hyperparameter search harness

View file

@ -10,8 +10,7 @@ Repixelizer-specific binding:
- `E:\Projects\Heimdall\docs\app-profiles\repixelizer.md`
This local file remains only as a redirect stub so old notes and links do not
pretend the shared access plan still lives inside Repixelizer.
This local file remains as a redirect stub for existing notes and links.
Current repo truth is unchanged:

View file

@ -8,8 +8,7 @@ It is not the authoritative stage-by-stage control-flow map. That lives in
`docs/lean-optimizer-algorithm-map.md`.
If these two notes disagree about what the code does right now, trust the
algorithm map and the source, then fix this plan instead of hand-waving about
intent.
algorithm map and the source, then fix this plan.
## Current machine
@ -38,7 +37,7 @@ The hosted web shell has an auth seam:
- `phase-field` is the only canonical reconstruction path.
- The current explicit solver is fast enough for hosted interaction.
- Compare mode, benchmark mode, diagnostics writing, GUI observer previews, and tuning harnesses exist.
- The repo reports both `source_fidelity` and `source_structure` because lattice-only scoring can slander better-looking output.
- The repo reports both `source_fidelity` and `source_structure` so lattice agreement and visible structure can be inspected separately.
- Hosted landing/app flow is in place and should be the immediate product path.
## Current solver shipping state
@ -86,14 +85,12 @@ Current stance:
- keep automatic inference grounded in autocorrelation
- let autocorr keep a small near-best lag plateau and use cross-axis consensus before collapsing to one lattice family
- keep hosted demo inference on the autocorr path with candidate rerank available when confidence is low
- do not revive the old pixel-walk / change-interval spacing path
- do not revive the projected edge-energy spectral pass
- treat candidate rerank as optional scaffolding, not sacred machinery
- keep candidate rerank as optional support for low-confidence autocorr choices
### 3. Later: retune the phase-field solver deliberately
The next solver pass should begin from the current live config, not from the old
wide-radius geometry.
The next solver pass should begin from the current live config and its narrow
search geometry.
Recommended order:
@ -122,9 +119,6 @@ Knobs still underexplored in the new basin:
- Show the output after every real reconstruction run when doing visual tuning.
- Keep maps updated after code changes.
- Revert or delete machinery that does not visibly earn its keep.
- Do not reintroduce lattice phase, portrait layers, candidate trays, or solver-stage religions casually.
- Keep solver changes inside the one-field / one-signal / one-relaxation model unless the design map changes first.
- If a new term lands, it must belong cleanly in the one-field / one-signal / one-relaxation model.
- If a result looks better and the metrics disagree, fix the metrics rather than worshipping them.
This repo already spent enough time as a Jenga tower of maybe-useful cleverness.
The machine has to deserve every part.
- Treat metrics as evidence about the output, not as a replacement for visual inspection.

View file

@ -34,7 +34,8 @@ These are the pieces that actually matter:
- `disp = pos - uv0_px`: reported displacement field derived from live positions
- `target_rgba`: final output grid after nearest-source sampling
Everything else is support scaffolding.
The remaining helpers load inputs, write diagnostics, or carry this state across
the CLI, GUI, and comparison harness.
## Diagram
@ -61,7 +62,7 @@ flowchart TD
- one lattice relaxation pass to keep spacing coherent
- honest final nearest-source sampling
The current live solver is not projected Adam anymore. The old differentiable loss scaffolding has been removed; `optimize_phase_field(...)` is the explicit NumPy local-search path.
`optimize_phase_field(...)` is the explicit NumPy local-search path.
## Stage 0: The pipeline chooses the ruler
@ -148,13 +149,7 @@ The current signal path is intentionally simple:
- invert it so low-gradient / low-curvature regions become attractive flatness
- sharpen the flatness map with `phase_field_signal_peak_power`
What no longer happens:
- no boundary-context reward
- no wall mask
- no distance transform
- no ring/context/self-penalty signal knobs
- no lattice phase concept
The live signal is the flatness map described above; diagnostic edge preview remains separate from solver steering.
### Metaphor
@ -247,7 +242,7 @@ grid_alignment * phase_field_grid_alignment_weight
- local_signal * phase_field_signal_weight
```
This is feedback for the GUI and logs. It is not a differentiable Adam objective.
This is feedback for the GUI and logs.
### Metaphor
@ -268,7 +263,7 @@ After the loop:
2. Index the original source RGBA.
3. Report `final_disp = pos - uv0_px` and normalized `uv_field`.
The emitted image is made of actual source pixels, not bilinear soup.
The emitted image is made of actual source pixels.
### Metaphor
@ -327,15 +322,7 @@ The important tuning lesson:
## Guardrails
These categories of complexity should not come back casually:
- lattice phase search or explicit lattice phase controls
- per-cell candidate trays that persist across stages
- extra source-derived reference rasters steering the field directly
- multiple sequential solver religions with separate objectives
- cleanup pretending to be the real reconstruction engine
The point of this machine is still one ruler, one position field, one local signal, one lattice relaxation, and one final source sample.
Future solver changes should preserve the current model unless the design map is updated first: one ruler, one position field, one local signal, one lattice relaxation, and one final source sample.
## Documentation boundaries

View file

@ -10,8 +10,7 @@ Shared architecture note:
- `E:\Projects\Heimdall\docs\architecture.md`
This local file remains only as a redirect stub so Repixelizer does not keep a
second copy of the same future auth plan fermenting in a corner.
This local file remains as a redirect stub for existing notes and links.
Current repo truth is unchanged:

View file

@ -1,8 +1,7 @@
# Solver tuning lessons - 2026-04-29
This note records what the tuning pass actually taught us, what it did not
teach us, and how the next pass should be run without constructing another
beautiful little swamp.
This note records what the tuning pass showed, what remains untested, and how
the next tuning pass should be run.
## Shipping state
@ -19,7 +18,7 @@ The current shipped trial config is intentionally usable, not final:
}
```
Everything else in `config/solver_params.json` remains as previously configured.
Other solver parameters remain as configured in `config/solver_params.json`.
The important caveat: this is a motion-geometry win, not a full signal-shape
optimum. It is acceptable for shipping the hosted experiment, but it should not
@ -28,9 +27,8 @@ be treated as the final form of the phase-field solver.
## What the pass proved
The most useful discovery was that the local-search radius was much too large.
The solver was not merely underpowered; it was letting samples inspect too much
neighboring territory. Adjacent samples could chase the same feature, which
looked like feature attraction but behaved like poaching.
At larger radii, adjacent samples could inspect overlapping neighborhoods and
chase the same feature.
Recent focused artificial results, all with:
@ -59,7 +57,7 @@ The model this supports:
- `radius` is the gatekeeper for whether samples solve locally or poach.
- Very small radius eventually starves movement, as seen at `0.16`.
- The useful basin is narrow, roughly `0.20-0.22`, with `0.215` best in the focused pass.
- Once the search window is tight, stronger blend becomes useful because samples can move without wandering into the neighbor's lunch.
- Once the search window is tight, stronger blend becomes useful because samples move within a more local neighborhood.
Blend sweep at `radius=0.215`:
@ -74,7 +72,7 @@ Blend sweep at `radius=0.215`:
The model this supports:
- After radius is tightened, blend must be retuned.
- Low blend was compensating for an over-wide search area.
- Low blend performed best in the wider search regime.
- At the new radius, `0.24` is better than `0.18` on focused metrics.
- We did not yet find the upper blend cliff. `0.24` won the tested set, but values above it still need bracketing later.
@ -96,18 +94,16 @@ not sweep:
- `phase_field_grid_alignment_weight` around the new basin
- `phase_field_local_search_grid_weight` around the new basin
Earlier experiments touched some of these in older regimes, but those results
should not be treated as final because they were gathered before the current
motion geometry was discovered. A signal knob can look useless when the solver
cannot move into the signal correctly. That is exactly how we wasted time
before.
Earlier experiments touched some of these in previous regimes, but those
results should not be treated as final because they were gathered before the
current motion geometry was selected.
The correct summary is:
- Current config is shippable.
- Motion geometry is much better understood.
- Signal shape remains underexplored in the new basin.
- The next tuning pass should start from `radius=0.215, blend=0.24`, not from the old wide-radius config.
- The next tuning pass should start from `radius=0.215, blend=0.24`.
## Current mental model
@ -126,9 +122,7 @@ The important part is the scale separation:
- The blend says how fast the sample moves toward the chosen position.
- The lattice coherence force says how much the grid resists local deformation.
The mistake was treating signal strength as the main issue while the search
window was still too broad. A broad search window turns a good signal into a
poaching contest.
Signal tuning should be evaluated against the current search window.
## Config intents we can currently generate

View file

@ -10,7 +10,7 @@ The project exists because generated imagery often creates appealing pixel-like
- noisy highlights and outlines
- fake micro-detail that collapses when downsampled
Repixelizer treats this as lattice inference plus reconstruction rather than a resize problem.
Repixelizer treats this as a lattice-inference and reconstruction problem.
## Goals
@ -108,7 +108,7 @@ The tool must estimate the resolution of the fake lattice being mimicked.
Requirements:
- infer candidate target sizes from autocorrelation hints
- use canonical cell centers for each candidate
- score candidates using coherence-oriented metrics rather than reconstruction alone
- score candidates using coherence-oriented metrics and source-derived periodicity priors
- expose the top-ranked alternatives in diagnostics
- if `--target-size`, `--target-width`, or `--target-height` is provided, skip automatic inference
@ -141,7 +141,7 @@ Requirements:
- relax row and column springs so the sample field stays coherent
- nearest-sample the original source pixels for final output
The current implementation is explicit NumPy local search plus spring relaxation. It is not the old PyTorch autograd / projected-Adam displacement solver.
The current implementation is explicit NumPy local search plus spring relaxation.
### 4. Discrete projection and cleanup
@ -150,9 +150,9 @@ After the phase-field stage, the result must be treated as a true pixel grid.
The discrete stage should:
- preserve source-sampled RGBA values
- snap alpha to an opaque/transparent mask
- leave structural repair to the solver rather than hiding confusion with a cleanup pass
- keep structural repair in the solver
Cleanup is intentionally small in v1. It should not become a second solver wearing a trench coat.
Cleanup is intentionally small in v1.
### 5. Baselines and comparison mode

View file

@ -30,11 +30,11 @@ Do not continue implementation automatically from a rehydrate-only request.
- `phase-field` is the only live reconstruction engine.
- The live pipeline is `source -> lattice inference -> diagnostic edge preview -> fixed lattice centers -> hierarchical flatness signal -> explicit local search + lattice relaxation -> nearest source sample -> cleanup / diagnostics`.
- The current solver is explicit local search, not projected Adam. It starts from fixed lattice centers, adds tiny seeded noise, chooses local preferred positions from a 5x5 candidate window, blends toward those positions, then relaxes lattice springs for row/column coherence.
- The current solver starts from fixed lattice centers, adds tiny seeded noise, chooses local preferred positions from a 5x5 candidate window, blends toward those positions, then relaxes lattice springs for row/column coherence.
- The final image is emitted by nearest-source sampling from final source positions. Bilinear interpolation is not the output path.
- The signal is direct hierarchical flatness: multiscale luminance gradient/Laplacian energy, one normalization, inverted to flatness, then sharpened. No wall mask, no distance transform, no boundary-context reward, no lattice phase.
- `source_structure` exists alongside `source_fidelity` because lattice-only scoring can lie about visible quality.
- Cleanup is alpha snap plus diagnostics only. It is not the real solver.
- The signal is direct hierarchical flatness: multiscale luminance gradient/Laplacian energy, one normalization, inverted to flatness, then sharpened.
- `source_structure` exists alongside `source_fidelity` so visible structure and lattice agreement can be inspected separately.
- Cleanup is alpha snap plus diagnostics.
## Hosted/web state
@ -77,9 +77,8 @@ See `docs/solver-tuning-lessons-2026-04-29.md` before doing more solver tuning.
Ship the hosted experiment path next. The source hygiene pass is complete:
diagnostic analysis stays as a GUI/observer gauge, the solver does not accept
`analysis`, stale GUI event names were removed, `docs/spec.md` matches the live
explicit solver, and `config/solver_params.json` matches the documented
shipping basin.
`analysis`, `docs/spec.md` matches the live explicit solver, and
`config/solver_params.json` matches the documented shipping basin.
## Current priority
@ -94,7 +93,6 @@ combinations, and only then run final full-corpus validation.
- Persistent state is the agent's mind; stale state is bad thought, not harmless clutter.
- Keep the algorithm map grounded in source.
- Do not reintroduce lattice phase, trays, portrait layers, or multi-stage solver religions casually.
- Do not trust a metric win that makes the image look worse.
- Revert or discard tuning changes that do not clearly improve the real outcome.
- Prefer shipping the hosted path over polishing solver knobs right now.

View file

@ -20,3 +20,4 @@
{"ts": "2026-04-29T10:09:47+00:00", "type": "verification", "status": "ok", "note": "Post-cleanup verification passed: AST parse succeeded across source/script/tool/test Python files and targeted stale-string scans found no removed solver params, fake cleanup hooks, old guidance observer names, or solver-analysis call sites."}
{"ts": "2026-04-29T10:20:00+00:00", "type": "process", "status": "accepted", "note": "When warned about imminent compaction, immediately write hot context from memory to a GUID-named scratch file before reading files or running commands. The trapdoor eats tidy workflows."}
{"ts": "2026-04-29T10:23:52+00:00", "type": "verification", "status": "ok", "note": "Source hygiene audit read all 60 source files under src/scripts/tools/tests, removed stale GUI lattice_search event handling and outdated spec text, kept PhaseFieldSourceAnalysis only as a diagnostic gauge, aligned config/solver_params.json to the documented shipping basin, and passed the full pytest suite: 89 passed."}
{"ts": "2026-04-29T10:41:17+00:00", "type": "docs", "status": "accepted", "note": "Documentation hygiene rule accepted: project-facing docs should describe the live machine and current tradeoffs directly, not take victory laps about removed approaches. Historical contrast belongs only in evidence, changelogs, postmortems, or compact rejected-path notes when it changes future decisions."}

View file

@ -5,7 +5,7 @@ project:
premise: >
Recover fake pixel art by inferring a real lattice and moving one explicit
source-position field under a simple local flatness signal plus lattice
coherence, instead of piling up compensating solver machinery.
coherence.
objective:
primary: >
@ -21,7 +21,7 @@ constraints:
- Prefer one chosen ruler, one live position field, one direct signal, and nearest-source final sampling.
- Use visible structure and focused fixtures, not just lattice proxy metrics.
- Keep compare mode, diagnostics, tuning, and GUI observer surfaces working.
- Avoid reviving trays, portrait layers, lattice phase, or multi-stage solver religions.
- Keep solver changes inside the one-ruler, one-position-field, one-signal model unless the design map changes first.
- Treat hosted convenience as the product path; do not prioritize native desktop packaging unless it clearly beats hosted access on revenue, support burden, or strategy.
invariants:
@ -39,7 +39,7 @@ active_subgoals:
- Ship the hosted experiment so Repixelizer unblocks Heimdall and StreamPixels.
- Keep the current usable solver config stable unless launch exposes a blocking quality regression.
- Keep inference, compare mode, diagnostics, tuning, and GUI observer events working after solver changes.
- Preserve `source_structure` as a real objective signal instead of over-trusting lattice-only fidelity.
- Preserve `source_structure` as an objective signal alongside lattice fidelity.
- Keep Heimdall-backed hosted auth isolated to the web layer so future provider or session work does not contaminate self-host or solver code.
accepted_design:
@ -53,9 +53,9 @@ accepted_design:
- `src/repixelizer/access.py` owns the hosted access seam: trusted-header ingress still works, `GC_ACCESS_MODE=heimdall` verifies Heimdall Ed25519 access tokens locally, and auth attempts are adopted into local httpOnly cookie sessions.
- The solver starts from fixed lattice centers, adds tiny seeded noise, follows a direct hierarchical flatness signal, enforces local lattice coherence through spring relaxation, then rounds to real source pixels for final output.
- Cleanup is local and secondary; it does not define the core reconstruction model.
- `source_fidelity` is useful but incomplete; `source_structure` exists because visible structure can improve while lattice-only scoring complains.
- `source_fidelity` and `source_structure` separate lattice agreement from visible structure.
- Character glint/symbol cases, artificial focused cases, and the badge fixture remain useful reality checks for later solver tuning.
- The algorithm map and forward plan must stay separate from scratch and evidence so the notes do not congeal into one giant lying blob.
- Keep the algorithm map, forward plan, scratch, and evidence separate.
- The open-source distribution story is self-hosting; the paid offer is hosted convenience, not a desktop-app paywall.
landed_baseline:
@ -68,15 +68,15 @@ landed_baseline:
rejected_paths:
- name: tray_optimizer_sprawl
reason: the old tray-based optimizer added machinery without preserving the current lean model
- name: metric_worship
reason: lattice-only fidelity can call visibly better outputs worse
- name: multi_stage_solver_religion
reason: stacked stages and compensators hide confusion instead of fixing the one-field model
reason: persistent per-cell trays do not belong in the current one-field solver model
- name: lattice_only_metric_selection
reason: lattice fidelity alone does not cover visible structure
- name: multi_stage_solver_stack
reason: sequential solver stages do not match the current one-field model
- name: projected_edge_spectrum_prior
reason: the projected edge-energy spectral pass floor-hugged to 2px / 626-family aliases on the dense landscape fixture and did not justify its complexity
reason: projected edge-energy spectral sizing was unreliable on the dense landscape fixture
- name: lattice_phase
reason: lattice phase controls and search were removed; the live solver starts from fixed centers and moves the position field directly
reason: the live solver starts from fixed centers and moves the position field directly
current_status:
summary: >
@ -94,10 +94,8 @@ current_status:
`GC_ACCESS_*` turns the seam on. The solver is shipping with a usable but
not final tuned config: local search radius `0.215`, blend `0.24`, move
weight `0.09`, grid alignment `4.75`, signal energy power `1.4`, and signal
peak power `3.25`, now aligned in `config/solver_params.json` and the docs.
The source hygiene audit removed stale solver-analysis wiring, old GUI event
names, fake cleanup prose, and outdated spec text while keeping diagnostic
edge analysis as an observer/GUI gauge. The tuning pass proved that wide
peak power `3.25`, aligned in `config/solver_params.json` and the docs.
Diagnostic edge analysis remains an observer/GUI gauge. The tuning pass proved that wide
local-search windows caused neighbor feature poaching and that tighter radius
plus stronger blend is the current motion-geometry basin. It did not fully
retune signal knobs around that basin.

View file

@ -5,21 +5,21 @@ Ship the hosted experiment path. Source hygiene is complete unless a fresh,
concrete stale signature appears.
## Completed in this cleanup pass
- Removed unused old solver hyperparams from `src/repixelizer/params.py`.
- Removed unused solver hyperparams from `src/repixelizer/params.py`.
- Removed stale tuning-campaign knobs from `tools/solver_tuning_campaign.py`.
- Removed `analysis` / `edge_map` from the live phase-field solver path.
- Simplified `cleanup_pixels(...)` to alpha snap plus zero heatmap; the old iterative cleanup path was unreachable.
- Simplified `cleanup_pixels(...)` to alpha snap plus zero heatmap.
- Updated tests that still passed `analysis` into the solver/candidate-selection path.
- Renamed the live observer/GUI flatness artifact from `guidance` to `signal`.
- Fixed the signal payload rename bug where the observer still copied stale `guidance`.
- Reworded docs/state from `edge scout` / solver `guidance` to diagnostic edge preview plus hierarchical flatness signal.
- Updated GUI event listeners from removed `lattice_search_*` events to the live `lattice_inference_*` events.
- Updated GUI event listeners to the live `lattice_inference_*` events.
- Reconciled `config/solver_params.json` with the documented shipping basin: `grid=4.75`, `radius=0.215`, `blend=0.24`, `move=0.09`, `energy=1.4`, `peak=3.25`.
- Updated `docs/spec.md` so it describes the live explicit local-search solver and alpha snapping, not the removed autograd/projected-Adam solver.
- Updated `docs/spec.md` so it describes the live explicit local-search solver and alpha snapping.
## Verification already run
- AST parse across `src`, `scripts`, `tools`, and `tests`: passed.
- Targeted stale-string scan for removed solver params, fake cleanup hooks, old guidance names, and solver-analysis call sites: clean.
- Targeted stale-string scan for solver params, cleanup hooks, guidance names, and solver-analysis call sites: clean.
- Full source audit read 60 source files under `src`, `scripts`, `tools`, and `tests`; the only remaining `analysis=analysis` hit is the intentional `RunResult` diagnostic field.
- Focused suite passed: `tests/test_pipeline.py tests/test_gui.py tests/test_inference.py tests/test_phase_field.py`.
- Full suite passed: `89 passed`.