Porting Test Drive II from SNES to PC, Part 6: Extending late-window ownership through frame 994
The previous checkpoint proved something important at frame 986: the repo could now generate a real late-window live producer trace, not just an early proof at frame 300 and not just a blocked timed-input experiment at 7051.
That immediately raised the next question:
was 986 only a cheap local win, or does the same ownership story hold as the late attract overlay comes back on screen?
The natural follow-up frames were 990 and 994.
They were already good archaeology targets for three reasons:
- both frames already had queue-side and bridge-visible artifacts
- both sat inside the same late
01:9FE5attract family -
994was the point where the remaining screenshot gap was still visible, even though the committed OAM variants had already converged
So this checkpoint was not about adding a new renderer feature. It was about extending the ownership proof far enough to say something useful about the remaining gap.
The concrete checkpoint
The workflow stayed the same as the 986 proof:
- extract a fresh raw frame bundle
- build a design pack
- run a bounded write-point trace around the frame
- merge the probe into a visual contract
For 990, the exact commands were:
MESEN_RELEASE_DIR=/home/nivando-soares/Mesen2/bin/linux-x64/Release \
make -C tools mesen-design-pack MESEN_FRAME=990
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_990_live/td2_boot_probe \
TD2_BOOT_PROBE_TOTAL_FRAMES=991 \
TD2_BOOT_PROBE_TRACE_START_FRAME=986 \
TD2_BOOT_PROBE_TRACE_END_FRAME=990 \
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_frame990 \
tools/out/visual_contract_frame990_live_probe.json \
--probe-json tools/out/visual_contract_probe_990_live/td2_boot_probe.json
For 994, I ended up recording a small but real reproducibility detail. The first extractor attempt timed out when I launched it in parallel with the live probe. Running the extractor by itself with a larger frame timeout closed the frame cleanly:
MESEN_RELEASE_DIR=/home/nivando-soares/Mesen2/bin/linux-x64/Release \
./tools/run_mesen_ppu_extract.sh \
--rom ./game.smc \
--frame 994 \
--frame-timeout-seconds 120 \
--out-dir ./tools/out/mesen_frame994
python3 tools/build_mesen_design_pack.py \
tools/out/mesen_frame994 \
tools/out/design_frame994 \
--clean-out
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_994_live/td2_boot_probe \
TD2_BOOT_PROBE_TOTAL_FRAMES=995 \
TD2_BOOT_PROBE_TRACE_START_FRAME=990 \
TD2_BOOT_PROBE_TRACE_END_FRAME=994 \
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_frame994 \
tools/out/visual_contract_frame994_live_probe.json \
--probe-json tools/out/visual_contract_probe_994_live/td2_boot_probe.json
Those four JSON artifacts are now committed in the repo:
tools/out/visual_contract_probe_990_live/td2_boot_probe.jsontools/out/visual_contract_frame990_live_probe.jsontools/out/visual_contract_probe_994_live/td2_boot_probe.jsontools/out/visual_contract_frame994_live_probe.json
What changed between 990 and 994
The most useful result is that the ownership story did not fork.
Both windows still sit under the same callback pair:
- active main callback:
01:9FE5 - active IRQ callback:
00:835F
The raw numbers are:
- frame
990-
3762write hits -
0drops - exact trace window
986..990 -
2730OAM writes across986..990 -
1032VRAM writes across986/988/989/990 -
5visible sprites
-
- frame
994-
4020write hits -
0drops - exact trace window
990..994 -
2730OAM writes across990..994 -
1290VRAM writes across990..994 -
19visible sprites
-
That is a clean late-window reading:
- OAM upload volume stays flat
- VRAM traffic grows
- the visible overlay expands from
5sprites to19 - the callback family does not change
That is a much stronger statement than “the screenshot is still off.”
It says the repo now knows that the late 986 -> 994 edge is still one continuous ownership surface, not a mystery fork where different code suddenly takes over.
The validation numbers that matter
The new frame dumps were checked against both the local screenshot-backed captures and the already-committed bridge-object scenes.
For frame 990:
python3 tools/compare_frames.py \
tools/out/intro_loop_frame_00990_frame.png \
tools/out/mesen_frame990/main_visible.ppm \
--diff-out tools/out/mesen_frame990_vs_intro990_diff.ppm
python3 tools/compare_frames.py \
tools/out/mesen_frame990/main_visible.ppm \
tools/out/bank1_bootstrap_queue_990_bridgeobj.ppm \
--diff-out tools/out/mesen_frame990_vs_bridgeobj990_diff.ppm
Results:
-
1516mismatched pixels (2.643694%) against the local screenshot -
2mismatched pixels (0.003488%) against the committed bridge-object scene
For frame 994:
python3 tools/compare_frames.py \
tools/out/intro_loop_frame_00994_frame.png \
tools/out/mesen_frame994/main_visible.ppm \
--diff-out tools/out/mesen_frame994_vs_intro994_diff.ppm
python3 tools/compare_frames.py \
tools/out/mesen_frame994/main_visible.ppm \
tools/out/bank1_bootstrap_queue_994_bridgeobj.ppm \
--diff-out tools/out/mesen_frame994_vs_bridgeobj994_diff.ppm
Results:
-
2622mismatched pixels (4.572405%) against the local screenshot -
96mismatched pixels (0.167411%) against the committed bridge-object scene
That split is the important part.
994 is still not screenshot-accurate. But the repo can now say something stricter about why:
- it is not blocked on missing late-window producer ownership
- it is not blocked on a separate committed OAM fork
- it is downstream of that
In other words, the remaining 994 gap is no longer an “I still do not know who is writing this” problem.
Why this was the right move instead of going back to 7051
The timed-input 7051 path is still useful. It is just still a bad place to spend retries in the current environment.
Two bounded power-on attempts had already exited 255 before producing a probe JSON. The right response to that was not to keep asking the same broken path to tell a new story.
The right response was:
- take the cheaper late-window proof that already matters
- keep the lane moving
- return to
7051only with a better starting surface
That is exactly what 990 and 994 accomplished.
This checkpoint turned the previous 986 proof from an isolated anchor into a short continuous ownership strip:
986990994
That strip is enough to make the next decision obvious.
What comes next
The next target is not another blind 7051 retry.
The next target is 998.
That is where the direct bridge-extracted 01:9FE5 continuation already begins, so extending the same live ownership surface there should say more than another timed-input failure would.
The headline for this checkpoint is simple:
the repo now has live late-window producer-trace-backed visual contracts not only at 986, but also at 990 and 994, and that is enough to show that the remaining 994 gap is downstream of ownership, not blocked by it.
Top comments (0)