Porting Test Drive II from SNES to PC, Part 19: Splitting the post-1093 attract window with active Mesen traces
The previous checkpoint closed the 986..1093 late-attract block as one continuous callback family.
That was enough to say who owned the window.
It was not enough to say why the continuation after 1093 stopped behaving like one uniform block.
This checkpoint fixes that by adding one more layer between "frame dump" and "render mismatch":
an activity trace built directly from Mesen probe output.
Why this mattered
Before this change, the repo could already answer:
- what the frame looked like
- which callback family was active at selected anchors
- whether bounded write-point probes hit
VRAM,CGRAM, orOAM
What it still could not answer cleanly was:
- whether a late mismatch boundary matched a real behavior change
- whether the continuation after
1093was still one producer shape - whether the next useful split should follow ownership, DMA, or Mode 7 programming
That is the gap the new trace closes.
The new tool
The repo now has a dedicated normalizer:
tools/build_mesen_activity_trace.py
It takes one td2_boot_probe.json plus the optional detailed probe traces:
*_dma_writes.json*_vram_writes.json*_mode7_writes.json
and turns them into one frame-oriented activity report with:
- active main callback
- active IRQ callback
- sampled state tuple
- grouped DMA events
- grouped direct
VRAM/CGRAMwrites - grouped
Mode 7register programming events
The important part is not the JSON format by itself.
The important part is that the late attract lane can now reason about producer behavior with the same granularity it was already using for visual contracts.
The bounded run
I kept the run narrow and targeted to the first ambiguous continuation:
- frames
1094..1117
The exact commands were:
MESEN_RELEASE_DIR=/home/nivando-soares/Mesen2/bin/linux-x64/Release \
MESEN_TIMEOUT_SECONDS=180 \
TD2_BOOT_PROBE_OUTPUT_PREFIX=tools/out/activity_trace_1094_1117/td2_boot_probe \
TD2_BOOT_PROBE_TOTAL_FRAMES=1118 \
TD2_BOOT_PROBE_TRACE_START_FRAME=1094 \
TD2_BOOT_PROBE_TRACE_END_FRAME=1117 \
TD2_BOOT_PROBE_TRACE_DMA=1 \
TD2_BOOT_PROBE_TRACE_VRAM=1 \
TD2_BOOT_PROBE_TRACE_MODE7=1 \
./validation/run_mesen_probe_boot.sh
python3 tools/build_mesen_activity_trace.py \
tools/out/activity_trace_1094_1117/td2_boot_probe.json \
tools/out/activity_trace_1094_1117/activity_trace.json \
--markdown-out tools/out/activity_trace_1094_1117/activity_trace.md
The promoted outputs are:
tools/out/activity_trace_1094_1117/activity_trace.jsontools/out/activity_trace_1094_1117/activity_trace.mdrom_analysis/docs/intro_01_9fe5_activity_trace_1094_1117.md
What the trace showed
The result is sharper than another screenshot comparison would have been.
Across 1094..1117:
- there are no direct
VRAM/CGRAMwrites - the callback switch lands exactly at
1102 - the repeated
OAMDMA continues only through1113 - the late
Mode 7M7A/M7Dprogramming on scanline231disappears after1101
More concretely:
-
1094..1101- active main callback:
01:9FE5 - repeated
00:0700 -> 00:2104544-byteOAMDMA is still present - the late scanline-
231M7A/M7Dpacket is still present
- active main callback:
-
1102..1113- active main callback:
00:8029 - the same repeated
OAMDMA is still present - the late scanline-
231M7A/M7Dpacket is gone
- active main callback:
-
1114..1117- active main callback:
00:8029 - the repeated
OAMDMA is gone too - no direct
VRAM/CGRAMwrites appear here either
- active main callback:
The sampled state tuple stays stable across the 1102 callback switch:
$0204 = 1$0206 = 13$040A = 17$0054 = 128
That matters because it rules out a cheap explanation like "the whole scene state jumped."
It did not.
The behavior changed before the sampled state tuple did.
Why this is useful
This changes the pipeline in a practical way.
Before this checkpoint, the late attract lane mostly had:
- screenshots
main_visible.ppmppu_state.json- tilemap provenance
- coarse producer traces
After this checkpoint, it also has a causal layer:
- which callback family owned each subwindow
- whether
OAMuploads were still happening - whether the late
Mode 7packet was still happening - whether a boundary was real or just visual noise
So the post-1093 continuation no longer has to be treated as one vague tail.
It can now be reasoned about as three concrete subwindows:
1094..11011102..11131114..1117
That is exactly the kind of split the next archaeology step needs.
Validation
This checkpoint used bounded validation only:
python3 -m py_compile tools/build_mesen_activity_trace.py
plus:
- a smoke read against legacy trace payloads
- one fresh headless Mesen run for
1094..1117
That was enough to falsify the tool and confirm the late-window behavior split without turning the checkpoint into another full regression sweep.
What comes next
The next useful step is not to keep restating that the post-1093 block differs.
The next step is to explain the two new boundaries separately:
- why
1102..1113keepsOAMDMA after the callback switch - why
1114..1117stops even that and becomes a no-direct-upload tail
That is a much better starting point for the following renderer and ownership work than "something changes after 1093."
The headline for this checkpoint is simple:
the repo can now map specific game behavior actively through Mesen, and the late attract continuation after 1093 is no longer one block but three behaviorally distinct windows.
Top comments (0)