Files
vt1200/reference/plan-1-board-model-outcome.md

5.2 KiB

Plan 1 outcome — VT1200 board register model

Plan: docs/superpowers/plans/2026-10-16-vt1200-board-model.md Spec: docs/superpowers/specs/2026-10-16-vt1200-board-model-design.md Commits: acb92d3e44ec..e23dadcfd4de (7 commits) Status: all 4 tasks complete, final whole-branch review clean after one documentation-only fix wave.

This file is the durable record; the SDD workspace was deleted after review, as the process requires, with git history as the record.

What was delivered

Commit What
43f69738f405 board register model extracted into a MAME-free, unit-tested class (vt1200_board.h/.cpp, test_vt1200_board.cpp); driver forwards to it
db2caa555803 fix round 1: switch knob made unambiguous (0x70 raw bits vs position) and non-silent
66fe15367b90 per-window busy state machines
a2f19b2d29a4 the 0x5800000/0x5900000 DMA command/data pair
2d7747a20d48 configurable timings + the timing sweep and its honest findings
8e18d81cef07 fix round 1: verbatim sweep/differential/INTENB evidence into the tracked log
e23dadcfd4de fix wave: documentation corrections (four windows, real callback trap, guessed DMA timing)

Guarantees that hold: no regression — with no VT1200_* env vars set the driver still reproduces the original wedge PC=3FDCE400 samples=199, argued structurally as well as measured; 9 unit tests pass and the model compiles standalone with plain g++.

The experiment's real result (negative, and valuable)

  • The timing sweep is flat: BUSY_W2 in {1,2,4,8} all give total_distinct=59. The reason is structural — tick() advances once per emulated frame, so busy durations of 1/2/4/8 ticks are indistinguishable. Duration is the wrong axis.
  • The critical handshake is isolated to 0x06200000 (WIN2): BUSY_TICKS=0 -> stuck PC=3FDCE400 (5 PCs); BUSY_TICKS=0 BUSY_W2=1 -> 56 PCs at PC=3F208950; BUSY_TICKS=0 BUSY_W3=1 -> still stuck.
  • INTENB never becomes non-zero across 900 frames (INTPEND=0400 throughout), so the level-0 interrupt boundary is still not crossed.

Rulings I made (all of them, in order)

  1. Initialised a git repository in the project. The directory was not under version control and the process needs an isolated tree plus per-task commits. A strict .gitignore excludes generated artifacts (160 MB AVI, RAM dumps, traces, core files, nested clones). Cost if wrong: a .git the user does not want; reversible with rm -rf .git.
  2. Pinned the tick() call site to the scanline callback, guarded on scanline == screen.visible_area().max_y. The plan left it ambiguous and the screen_update alternative is wrong (it fires in small clip regions). Cost if wrong: busy machines advance at the wrong rate; one call site.
  3. Model policy: DeepSeek V4.1 (deepseek/deepseek-flash) preferred over V4, per user guidance; high variant for implementers/reviewers, max for the final review. Cost if wrong: a task or two on a weaker model, with review coverage.
  4. Ran the memory-space investigation as a spike, not a plan, with full Ghidra access — output is an answer plus durable artefacts, not product code. Cost if wrong: one worker's time; deliverables additive and write-isolated.
  5. Resumed Task 1's implementer on a different model for its fix round (V4.1 vs its V4 Pro dispatch), keeping session context. Cost if wrong: none observed — the re-review found no new breakage.
  6. Treated the spike's VRAM finding as inference, not proof, so the video plan's first task is an empirical determination (watchpoint DPYADR), not a static one. Cost if wrong: one wasted probe.
  7. Task 4's flat sweep stands as a successful outcome. No improvement was claimed and none was fabricated; the negative result is the deliverable.

Reviewer findings triaged as acceptable (not fixed, deliberately)

Thirteen minors were raised across the four tasks; the final review triaged them and required only the three documentation corrections (now fixed). The rest were judged acceptable: write-back latches on non-handshake reads; the handshake matching only the low 16 bits; m_busy_ticks default 2 -> 0; assorted test-coverage gaps; the unmasked key in set_window_busy_ticks; the DMA defaults-off gate being untested; a WIN1 read ignoring the register offset (inert while DMA defaults off); and cosmetic diagnostic wording.

Two worth acting on before the next modelling round (the reviewer's words): mask the key in set_window_busy_ticks, and offset-gate the WIN1 DMA read so a WIN1 status bit is not shadowed.

Where the goal stands

This plan deliberately delivered only its slice. INTENB still never leaves zero and no frame has been rendered — the spec's success criteria 2 and 3 remain open, to be met by the two named follow-on plans: video RAM + scanline renderer, and DUART / NVRAM / MAC ROM.

The spike (reference/memory-space-map.md) plus this plan's findings tell the video plan where to start: the framebuffer is not in main RAM, it is set at runtime via DPYADR, and the most-touched unmodelled region is 0x5600000 (~110 accesses on the display/X-init path).