diff --git a/docs/superpowers/plans/2026-10-16-env-gate-cleanup.md b/docs/superpowers/plans/2026-10-16-env-gate-cleanup.md new file mode 100644 index 0000000..76cbb58 --- /dev/null +++ b/docs/superpowers/plans/2026-10-16-env-gate-cleanup.md @@ -0,0 +1,98 @@ +# Plan: retire the settled `VT1200_*` env gates + +Status: **planned, not started.** Execute after the current `vt1200.cpp` worker +lands its interrupt-line / palette / test-map work. + +## Why + +The driver grew **33** `VT1200_*` environment gates. They were added deliberately — +one per experiment — so that the no-env baseline stayed byte-identical and a change +could not silently alter behaviour. That discipline paid for itself repeatedly. + +But most of those experiments are now **settled**, and the consequence is that the +*working* configuration is a six-variable incantation: + +```sh +VT1200_VRAM=1 VT1200_BOARD_MODEL=handshake VT1200_BOARD_BUSY_TICKS=2 \ +VT1200_BOARD_SWITCH="0640000D:70:70" VT1200_DUART=1 VT1200_DUART_SEL=1 +``` + +Running `vt1200 vt1200` with no env silently gives a machine that doesn't work — which +is misleading, and already caused one wrong conclusion in this project (a stale +"256 KiB RAM" note led to an incorrect instruction being sent to a worker). **The +correct configuration should be the default.** + +## Triage + +### A. Promote to the default (remove the gate) + +| Gate | Why | +|---|---| +| `VT1200_VRAM` | maps video RAM at bit `0x04000000`; required for the VIDEO MEMORY test to pass | +| `VT1200_DUART` | maps MAME's `SCN2681`s; required — the host-port transmit is ISR-driven and only works with a real DUART | +| `VT1200_DUART_SEL` | fold to **DUART1 always**; expose DUART2 as its own opt-in. `SEL=1` is the working config (mapping both diverts level 0) | +| `VT1200_BOARD_MODEL` | default → `handshake` | +| `VT1200_BOARD_BUSY_TICKS` | default → `2` | + +### B. Delete (completed experiments / superseded probes) + +| Gate | Why | +|---|---| +| `VT1200_CAL`, `VT1200_CAL_VID4M`, `VT1200_CAL_W8` | the memory-space calibration spike — **finished**; its results are in `reference/memory-space-map.md` | +| `VT1200_BOARD_RAM`, `VT1200_BOARD_DEFAULT` | probe variants of the window model | +| `VT1200_BOARD_BUSY_W2`, `VT1200_BOARD_BUSY_W3` | per-window variants of `BUSY_TICKS` — fold into the default | +| `VT1200_BOARD_DMA_TICKS` | probe; not in the working configuration | +| `VT1200_BOARD_SWITCH` | **verify first** — the monitor switch is now served by the DUART's IP4–IP6, so this may be entirely superseded | +| `VT1200_DUART_LB1`, `VT1200_DUART_LB2` | external TX→RX loopback wiring — proven unnecessary: the firmware sets MR2 local loopback itself, and the datasheet holds TxD **high** in local loopback (SCN2681 local-loopback item 3) | +| `VT1200_LOWLOG` | low-RAM logging proxy — a one-off probe, and a confusing extra mapping | +| `VT1200_VRAMLOG` | VRAM access log — one-off | +| `VT1200_STRUCTLOG` | structure dump probe — one-off | + +### C. Keep + +**Instrumentation that earns its place:** `VT1200_DUARTLOG`, `VT1200_BOARD_LOG`, +`VT1200_IRQLOG`, `VT1200_LOG_UNMAPPED` (the catch-all that closed the instrument +cross-check), `VT1200_DPYLOG` + `VT1200_DPYLOG_FRAMES` (pinned the render geometry), +`VT1200_DUMP_FRAME` / `DUMP_RAM` / `DUMP_VRAM` / `DUMP_DIR`, `VT1200_NO_RENDER`. + +**Deliberate behaviour switch:** `VT1200_DUART_IP3` — it changes the *boot path* +(skips level 1). Keep, and **rename** to something self-describing +(e.g. `VT1200_SKIP_LEVEL1`), because `IP3` names a pin rather than an effect. + +**Pending:** `VT1200_DUART1_INT2` — kept until the interrupt-line question is settled, +then deleted or folded. + +Net: **~5 promoted out of existence, ~13 deleted**, leaving roughly 15. + +## The one hard constraint + +**The no-env baseline (`PC=3FDCE400 samples=199`) will legitimately change.** That is +the point — but it must change *deliberately*, never silently, and the project has +been burned twice by a claim that turned out to be a mask. So: + +1. **Equivalence proof per promoted gate.** For each: capture the observable *with the + env set* (frame-dump md5, `progress.lua` sample, console capture via `cap.lua`), + then capture again *after* promotion with **no env at all**. The two must be + **identical**. That demonstrates we changed the *default*, not the *behaviour*. +2. **Deletion proof.** For each deleted gate, show the code it guards is reachable + **only** under that env var — i.e. no default path touches it. Deleting a live path + would be the same class of error as promoting a dead one. +3. **Re-baseline explicitly**, recording the old and new values and why — the baseline + is a change detector, not a specification. + +## The trap to respect while promoting + +`map()` entries are built at **configuration** time. A mapping gated on a pointer set +in `machine_start()` silently registers **nothing**. Promotion means making the +mapping **unconditional** — *not* moving the `getenv()` somewhere it will not be +consulted. + +## Acceptance + +* `vt1200 vt1200` with **no env** boots as far as the env-set configuration did + (frame dumps identical, same self-test progression). +* The promoted set produces byte-identical observables to the old incantation. +* Every deleted gate's code path is gone and nothing references it. +* Regression re-baselined and documented, with the reason. +* `README`/header comment block updated — it currently documents many of these knobs + and would otherwise become a liar.