diff --git a/AGENTS.md b/AGENTS.md index 01988f2..855d2ff 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -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. diff --git a/README.md b/README.md index e038654..52a484f 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/gamecult-hosted-access-architecture.md b/docs/gamecult-hosted-access-architecture.md index 1bb1639..ba98e8b 100644 --- a/docs/gamecult-hosted-access-architecture.md +++ b/docs/gamecult-hosted-access-architecture.md @@ -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: diff --git a/docs/implementation-plan.md b/docs/implementation-plan.md index 0e8db8d..e1e1953 100644 --- a/docs/implementation-plan.md +++ b/docs/implementation-plan.md @@ -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. diff --git a/docs/lean-optimizer-algorithm-map.md b/docs/lean-optimizer-algorithm-map.md index 1b99bc6..406efea 100644 --- a/docs/lean-optimizer-algorithm-map.md +++ b/docs/lean-optimizer-algorithm-map.md @@ -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 diff --git a/docs/repixelizer-hosted-access-profile.md b/docs/repixelizer-hosted-access-profile.md index ffc2c1f..4ac6cfd 100644 --- a/docs/repixelizer-hosted-access-profile.md +++ b/docs/repixelizer-hosted-access-profile.md @@ -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: diff --git a/docs/solver-tuning-lessons-2026-04-29.md b/docs/solver-tuning-lessons-2026-04-29.md index 60cfd57..68cc172 100644 --- a/docs/solver-tuning-lessons-2026-04-29.md +++ b/docs/solver-tuning-lessons-2026-04-29.md @@ -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 diff --git a/docs/spec.md b/docs/spec.md index e77eea9..50ce0dd 100644 --- a/docs/spec.md +++ b/docs/spec.md @@ -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 diff --git a/notes/fresh-workspace-handoff.md b/notes/fresh-workspace-handoff.md index eab928b..038972a 100644 --- a/notes/fresh-workspace-handoff.md +++ b/notes/fresh-workspace-handoff.md @@ -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. diff --git a/state/evidence.jsonl b/state/evidence.jsonl index 59aa98f..f689475 100644 --- a/state/evidence.jsonl +++ b/state/evidence.jsonl @@ -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."} diff --git a/state/map.yaml b/state/map.yaml index f5da615..9afa771 100644 --- a/state/map.yaml +++ b/state/map.yaml @@ -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. diff --git a/state/scratch.md b/state/scratch.md index f246820..480fcf8 100644 --- a/state/scratch.md +++ b/state/scratch.md @@ -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`.