DEV Community

Nivando Soares
Nivando Soares

Posted on

Porting Test Drive II from SNES to PC, Part 5: A real producer trace at frame 986

Porting Test Drive II from SNES to PC, Part 5: A real producer trace at frame 986

The last checkpoint in asmdump did not add a new renderer feature. It added something more useful for the current archaeology lane: a real late-window ownership proof that can be rerun cheaply.

That proof is frame 986.

This matters because the repo had reached an awkward point in the intro work:

  • the bridge-visible path was already strong through the late 01:9FE5 attract window
  • the queue-driven reconstruction around 986 was already close enough to be useful
  • but the producer-side ownership story for later windows was still thin

The project already had a live producer-trace proof on frame 300, but that is an early front-end scene. It is good for validating the probe pipeline. It is not the right place to answer late-attract questions about OAM upload traffic, visible-sprite disappearance, and the unresolved composition gap after the post-Ballistic bootstrap.

The obvious follow-up was the timed-input window around 7051, because that is where a later direct-hit tilemap provenance path had reopened. The problem is that, in the current local environment, the power-on timed-input producer trace for that path still fails before it emits a probe JSON.

That made 7051 a bad place to keep burning retries.

So the next move was to pick a cheaper late-window target with two properties:

  • it already matters to the current blocker
  • it can be reproduced without a fragile delayed-input setup

986 fits both.

Why 986 was the right pivot

By this point, the repo already knew a lot about frame 986:

  • the queue-driven BG/state path was close
  • disabling OBJ dropped the screenshot gap sharply
  • overriding OBJ with clean bridge OAM hit the same practical baseline
  • dedicated OAM delta artifacts already showed that probe OAM and bridge OAM diverged there

That was enough to say "986 is important." It was not enough to say who was still writing what, and when.

The useful question was no longer just:

  • does the reconstructed frame look close?

It was:

  • which producers are still active in the bounded late window around that frame?
  • are we still looking at visible sprite content, or only upload traffic?
  • does the active callback family change?

Those are ownership questions, not screenshot questions.

The concrete checkpoint

The promoted run has three pieces:

  1. Extract a fresh frame bundle and design pack for 986.
  2. Run a bounded live write-point probe over frames 982..986.
  3. Merge that probe into a visual contract.

The exact commands were:

MESEN_RELEASE_DIR=/home/nivando-soares/Mesen2/bin/linux-x64/Release \
make -C tools mesen-design-pack MESEN_FRAME=986

MESEN_RELEASE_DIR=/home/nivando-soares/Mesen2/bin/linux-x64/Release \
MESEN_TIMEOUT_SECONDS=120 \
TD2_BOOT_PROBE_OUTPUT_PREFIX=tools/out/visual_contract_probe_986_live/td2_boot_probe \
TD2_BOOT_PROBE_TOTAL_FRAMES=987 \
TD2_BOOT_PROBE_TRACE_START_FRAME=982 \
TD2_BOOT_PROBE_TRACE_END_FRAME=986 \
TD2_BOOT_PROBE_TRACE_WRITE_POINTS='objsel=00:2101,oamaddl=00:2102,oamaddh=00:2103,oamdata=00:2104,vmaddl=00:2116,vmaddh=00:2117,vmdatal=00:2118,vmdatah=00:2119,cgadd=00:2121,cgdata=00:2122' \
TD2_BOOT_PROBE_WRITE_POINT_MAX_HITS=8192 \
./validation/run_mesen_probe_boot.sh

python3 tools/build_mesen_visual_contract.py \
  tools/out/design_frame986 \
  tools/out/visual_contract_frame986_live_probe.json \
  --probe-json tools/out/visual_contract_probe_986_live/td2_boot_probe.json
Enter fullscreen mode Exit fullscreen mode

The resulting artifacts are now committed:

  • tools/out/design_frame986/design_pack.json
  • tools/out/visual_contract_probe_986_live/td2_boot_probe.json
  • tools/out/visual_contract_frame986_live_probe.json

What the trace actually showed

The bounded live probe recorded 3246 write hits with 0 drops.

Those hits split into only two active domains in this window:

  • OAM: 2730 writes across frames 982..986
  • VRAM: 516 writes at frames 984 and 986

The dominant callsites are not deep gameplay-side logic. They are IRQ/NMI-side helpers:

  • OAM traffic is dominated by 00:824F and 00:8257
  • VRAM traffic is dominated by 00:81E5 and 00:81F2

All sampled hits in this window still run under active main callback 01:9FE5.

Just as useful is what did not show up:

  • no CGRAM writes
  • no OBJSEL writes

That turns 986 into a cleaner contract surface than it looked from screenshots alone.

The surprising but useful part

The fresh 986 design pack reports:

  • bgMode = 7
  • mainScreenLayers = 0x11
  • active visible BG layer: bg1
  • visible sprite count: 0

That is the useful late-window fact.

By frame 986, the visible overlay is already gone, but OAM upload traffic is still active.

That sounds contradictory if you are still thinking in screenshot terms. It makes sense if you treat OAM traffic and visible sprite composition as separate questions.

That separation is exactly why the repo had already moved toward visual contracts that split:

  • BG state
  • OBJ/OAM state
  • producer-side ownership

The 986 proof validates that split. A frame can have:

  • no visible sprites
  • active OAM writes
  • stable callback continuity

and all three facts matter at the same time.

Validation against the existing late-window model

The new extraction path for 986 was checked two ways.

First, against the local screenshot-backed frame:

python3 tools/compare_frames.py \
  tools/out/intro_loop_frame_00986_frame.png \
  tools/out/mesen_frame986/main_visible.ppm \
  --diff-out tools/out/mesen_frame986_vs_intro986_diff.ppm
Enter fullscreen mode Exit fullscreen mode

That compare lands at 267 mismatched pixels (0.465611%).

Second, against the already committed bridgeoverride scene:

python3 tools/compare_frames.py \
  tools/out/mesen_frame986/main_visible.ppm \
  tools/out/bank1_bootstrap_queue_986_bridgeoverride.ppm \
  --diff-out tools/out/mesen_frame986_vs_bridgeoverride986_diff.ppm
Enter fullscreen mode Exit fullscreen mode

That compare lands at 2 mismatched pixels (0.003488%).

So the new cheap extractor path and the committed bridgeoverride scene are practically on the same surface. That is exactly what you want from a proof target.

Queue-driven late-window frame 986 bridge scene

One small schema fix that mattered immediately

This checkpoint also fixed a small but real contract bug.

td2_boot_probe.json already contained the write hits, but it did not preserve trace_start_frame and trace_end_frame in the main payload. That meant a merged visual contract could tell you:

  • which domains were active
  • which frames were touched inside each domain

but not the exact requested trace window that produced the capture.

The probe now writes those fields directly, and the merged contract now carries:

  • producerTrace.traceWindow.startFrame
  • producerTrace.traceWindow.endFrame

For the 986 proof, that means the contract explicitly records 982..986, not just the derived per-domain spans.

That is a small change, but it makes the artifact more trustworthy as a handoff surface.

Why this checkpoint is better than another blind 7051 retry

The earlier 7051 follow-up was still the right experiment to try. It just was not the right experiment to keep retrying in the same environment.

Two bounded power-on timed-input attempts still exited 255 before producing a probe JSON. After that, the right move was not persistence for its own sake. The right move was to switch to a target that could still advance the lane.

986 did exactly that:

  • it produced a real later-window ownership trace
  • it tightened the contract format itself
  • it gave the lane a better anchor for the already-documented 986/990/994 late-attract composition boundary

That is forward progress, not a workaround.

What comes next

The next step is not to go back immediately to 7051.

The next step is to extend the same live ownership path forward into:

  • 990
  • 994

Those windows already have queue artifacts, OAM delta reports, and bridge-visible comparisons. They are the natural continuation of the same question.

7051 should come back only when there is a better starting surface for it, likely a reusable late-intro seed or savestate. That is a different problem.

For now, the important result is simpler:

the repo no longer has only an early producer-trace proof and a blocked later timed-input path. It now has a real late-window live ownership contract at 986, and that is enough to keep the archaeology lane moving.

Top comments (0)