<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Nivando Soares</title>
    <description>The latest articles on DEV Community by Nivando Soares (@nivandosoares).</description>
    <link>https://dev.to/nivandosoares</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F637659%2F0765b84c-6aa0-407c-93b3-c3b6cfed936a.jpeg</url>
      <title>DEV Community: Nivando Soares</title>
      <link>https://dev.to/nivandosoares</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/nivandosoares"/>
    <language>en</language>
    <item>
      <title>TD2 Port Checkpoint: Native SDL Archaeology Demo Timeline</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Fri, 03 Apr 2026 02:39:13 +0000</pubDate>
      <link>https://dev.to/nivandosoares/td2-port-checkpoint-native-sdl-archaeology-demo-timeline-3bkb</link>
      <guid>https://dev.to/nivandosoares/td2-port-checkpoint-native-sdl-archaeology-demo-timeline-3bkb</guid>
      <description>&lt;h1&gt;
  
  
  TD2 Port Checkpoint: Native SDL Archaeology Demo Timeline
&lt;/h1&gt;

&lt;p&gt;The SDL demo path for the TD2 port is now a real native timeline instead of a&lt;br&gt;
single near-static gameplay seed.&lt;/p&gt;

&lt;p&gt;This checkpoint promotes the default launcher into an archaeology-driven demo&lt;br&gt;
tour built from the raw-state artifacts already solved in the repo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;credits&lt;/li&gt;
&lt;li&gt;attract / Mode 7 windows&lt;/li&gt;
&lt;li&gt;menu anchors&lt;/li&gt;
&lt;li&gt;gameplay anchors&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important constraint is what it does &lt;strong&gt;not&lt;/strong&gt; do:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;it does not use compare mode&lt;/li&gt;
&lt;li&gt;it does not write &lt;code&gt;PPM&lt;/code&gt; or &lt;code&gt;PNG&lt;/code&gt; dumps&lt;/li&gt;
&lt;li&gt;it does not render from screenshot playback&lt;/li&gt;
&lt;li&gt;it does not run a ROM/CPU emulation path&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The default entry point is still:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./port/run_demo.sh
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;But now that launcher boots a manifest-driven native timeline from:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;port/assets/native_demo_archaeology_timeline.txt
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Each step loads a &lt;code&gt;design_pack&lt;/code&gt; or &lt;code&gt;design_pack_range&lt;/code&gt; frame and renders it&lt;br&gt;
through the port's SDL-native SNES-mimetic pipeline from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;raw/vram.bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw/cgram.bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw/oam.bin&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;raw/ppu_state.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Where archaeology already has contiguous frame coverage, the timeline stays&lt;br&gt;
exact. Where the repo only has sparse anchors, the launcher now uses explicit&lt;br&gt;
&lt;code&gt;infer_hold&lt;/code&gt; stretches instead of hiding that gap behind screenshot playback.&lt;/p&gt;

&lt;p&gt;That makes the current capability demo honest:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;exact frames stay exact&lt;/li&gt;
&lt;li&gt;missing spans are labeled as inferred holds&lt;/li&gt;
&lt;li&gt;the demo still moves across the best proven path in the repo&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Another useful cleanup landed under the hood: in demo mode the launcher now&lt;br&gt;
skips optional &lt;code&gt;layers/main_visible.ppm&lt;/code&gt; loading entirely. That means the demo&lt;br&gt;
window is no longer just "compare off" in principle; the reference surface is&lt;br&gt;
out of the runtime path altogether.&lt;/p&gt;

&lt;p&gt;The on-screen overlay now states the proof directly on top of the SDL frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;MESEN OFF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;ROM CPU EMU OFF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;REFERENCE PPM OFF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;PPM PNG DUMP OFF&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;active mode / source / callbacks / output resolution&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The dedicated launcher smoke also changed accordingly. It now verifies that the&lt;br&gt;
default demo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enters timeline mode&lt;/li&gt;
&lt;li&gt;reports the raw-only launcher contract&lt;/li&gt;
&lt;li&gt;advances beyond the first clip under &lt;code&gt;SDL_VIDEODRIVER=dummy&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full port suite still passes after the change:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;make &lt;span class="nt"&gt;-C&lt;/span&gt; port &lt;span class="nb"&gt;test&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  What this checkpoint proves
&lt;/h2&gt;

&lt;p&gt;It proves the current archaeology set is already strong enough to present a&lt;br&gt;
moving SDL-native capability demo without falling back to &lt;code&gt;PPM&lt;/code&gt;/&lt;code&gt;PNG&lt;/code&gt; playback.&lt;/p&gt;

&lt;h2&gt;
  
  
  What it does not prove yet
&lt;/h2&gt;

&lt;p&gt;It does &lt;strong&gt;not&lt;/strong&gt; prove continuous runtime-owned gameplay or a full native&lt;br&gt;
no-input attract loop. The next content-side job is to replace the remaining&lt;br&gt;
inferred holds and sparse gameplay snapshots with denser raw-state captures or&lt;br&gt;
true runtime-owned frame progression.&lt;/p&gt;

</description>
      <category>sdl</category>
      <category>c</category>
      <category>retrodev</category>
      <category>gamedev</category>
    </item>
    <item>
      <title>A measured scanline contract finally beat static composition on a late Test Drive II frame</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Thu, 02 Apr 2026 00:41:59 +0000</pubDate>
      <link>https://dev.to/nivandosoares/a-measured-scanline-contract-finally-beat-static-composition-on-a-late-test-drive-ii-frame-4p8n</link>
      <guid>https://dev.to/nivandosoares/a-measured-scanline-contract-finally-beat-static-composition-on-a-late-test-drive-ii-frame-4p8n</guid>
      <description>&lt;h1&gt;
  
  
  A measured scanline contract finally beat static composition on a late Test Drive II frame
&lt;/h1&gt;

&lt;p&gt;I closed a small but important runtime checkpoint on the SNES-mimetic &lt;em&gt;The Duel: Test Drive II&lt;/em&gt; port.&lt;/p&gt;

&lt;p&gt;The question was narrow:&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;For the late gameplay family around frames &lt;code&gt;3250/3400/3550&lt;/code&gt;, should I keep extending the static &lt;code&gt;BG3&lt;/code&gt; top-band composition rule, or go back to stronger measured scanline/state fields?&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Today the answer stopped being speculative.&lt;/p&gt;

&lt;h2&gt;
  
  
  The bounded experiment
&lt;/h2&gt;

&lt;p&gt;I captured one new visible-scanline trace for the promoted late-entry traffic-emergence frame &lt;code&gt;3400&lt;/code&gt; and wired it into the runtime's versioned scanline-contract surface.&lt;/p&gt;

&lt;p&gt;The route was the existing bounded braking variant, so this was not a new archaeology lane, just a tighter test on the current port path.&lt;/p&gt;

&lt;p&gt;The key artifact is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_entry_brake_frame03400_scanline_full/td2_scanline_step_test.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the runtime output promoted from it is:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tools/out/port_gameplay_scanline_runtime_pngs_20260401/live_entry_3400_00000.png&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;Before this checkpoint, frame &lt;code&gt;3400&lt;/code&gt; only used the static late-gameplay composition rule:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;enable &lt;code&gt;BG3&lt;/code&gt; only in the top band&lt;/li&gt;
&lt;li&gt;keep &lt;code&gt;BG3 &amp;gt; BG2&lt;/code&gt; only in that same band&lt;/li&gt;
&lt;li&gt;cutoff &lt;code&gt;79&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That rule was useful, but it still treated the whole late-entry family as basically “composition-only unless proven otherwise”.&lt;/p&gt;

&lt;p&gt;The new scanline-backed &lt;code&gt;3400&lt;/code&gt; render proved otherwise.&lt;/p&gt;

&lt;p&gt;Compared to the earlier composition-only runtime PNG for the same frame:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;pixel delta between the two runtime outputs: &lt;code&gt;9309&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;mismatch against &lt;code&gt;bg_stack_visible_support.png&lt;/code&gt;: &lt;code&gt;15497 -&amp;gt; 7649&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is not a cosmetic shuffle. It is the first late-entry proof that the current measured scanline field family actually pays off on a promoted gameplay bundle.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;This changes the next step for the port.&lt;/p&gt;

&lt;p&gt;The late-entry gameplay family should no longer be treated as one uniform “static composition first” lane:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;3400&lt;/code&gt; is now a positive proof that measured scanline fields can materially improve the runtime output&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3250&lt;/code&gt; stays useful as the counterexample where the current &lt;code&gt;main_layers/bg1/bg2/bg3&lt;/code&gt; field set is still a no-op&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3550&lt;/code&gt; is now the obvious next bounded follow-up&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So the plan is no longer “extend static composition everywhere and maybe revisit scanlines later”.&lt;/p&gt;

&lt;p&gt;The better plan is:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;keep the composition rule where it still earns its keep,&lt;/li&gt;
&lt;li&gt;but promote stronger measured scanline/state consumers where the evidence is already paying off,&lt;/li&gt;
&lt;li&gt;starting from &lt;code&gt;3400&lt;/code&gt; and likely testing &lt;code&gt;3550&lt;/code&gt; next.&lt;/li&gt;
&lt;/ol&gt;

&lt;h2&gt;
  
  
  Port-facing checkpoint
&lt;/h2&gt;

&lt;p&gt;The runtime smoke now validates &lt;code&gt;3400&lt;/code&gt; as a real scanline-backed consumer instead of a composition-only one.&lt;/p&gt;

&lt;p&gt;That gives the SDL port a more honest picture of late gameplay: some of these frames need more than a static horizon-strip rule, and the runtime now has one concrete proof point inside that family.&lt;/p&gt;

</description>
      <category>c</category>
      <category>gamedev</category>
      <category>sdl</category>
      <category>retrocomputing</category>
    </item>
    <item>
      <title>Promoting late-gameplay BG3 composition contracts in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 20:45:56 +0000</pubDate>
      <link>https://dev.to/nivandosoares/promoting-late-gameplay-bg3-composition-contracts-in-the-td2-sdl-port-15gn</link>
      <guid>https://dev.to/nivandosoares/promoting-late-gameplay-bg3-composition-contracts-in-the-td2-sdl-port-15gn</guid>
      <description>&lt;h1&gt;
  
  
  Promoting late-gameplay BG3 composition contracts in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;This checkpoint moved one late-gameplay renderer hypothesis out of tooling and into the runtime.&lt;/p&gt;

&lt;p&gt;The late live-entry bundles at frames &lt;code&gt;3250&lt;/code&gt;, &lt;code&gt;3400&lt;/code&gt;, and &lt;code&gt;3550&lt;/code&gt; already had a strong signal from the cutoff sweep: the missing horizon strip was not explained by missing assets, but by a narrow composition rule. The best candidates were consistent enough to promote:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;frame &lt;code&gt;3250&lt;/code&gt;: enable &lt;code&gt;BG3&lt;/code&gt; in the top &lt;code&gt;79&lt;/code&gt; scanlines and keep &lt;code&gt;BG3 &amp;gt; BG2&lt;/code&gt; there&lt;/li&gt;
&lt;li&gt;frame &lt;code&gt;3400&lt;/code&gt;: same &lt;code&gt;79&lt;/code&gt;-line window&lt;/li&gt;
&lt;li&gt;frame &lt;code&gt;3550&lt;/code&gt;: same rule with a deeper &lt;code&gt;95&lt;/code&gt;-line window&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The runtime now loads those rules from a versioned contract file instead of a hardcoded late-gameplay lookup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rom_analysis/docs/gameplay_composition_contracts.jsonc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;On the implementation side, &lt;code&gt;td2_runtime&lt;/code&gt; resolves the matching profile for the loaded design pack, and &lt;code&gt;td2_ppu&lt;/code&gt; now applies two concrete knobs in the native SDL render path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bg3_enable_top_scanlines&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg3_above_bg2_top_scanlines&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the late-gameplay top-band &lt;code&gt;BG3&lt;/code&gt; rule is now a first-class part of the native renderer rather than a design-only experiment.&lt;/p&gt;

&lt;p&gt;Validation stayed bounded and explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;make -C port test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;python3 tools/build_docs_wiki_report.py --manifest rom_analysis/docs/wiki_doc_index.json --output-dir tools/out/docs_wiki --markdown-bundle-dir tools/out/docs_wiki_markdown_bundle&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The current smoke matrix passed with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;compare lane &lt;code&gt;3/3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;scheduler smoke &lt;code&gt;335&lt;/code&gt; checks&lt;/li&gt;
&lt;li&gt;input mutation smoke &lt;code&gt;200&lt;/code&gt; checks&lt;/li&gt;
&lt;li&gt;live input smoke &lt;code&gt;21&lt;/code&gt; checks&lt;/li&gt;
&lt;li&gt;scanline / composition contract smoke &lt;code&gt;39&lt;/code&gt; checks&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For design review, I also regenerated runtime PNGs for the promoted late-entry anchors so the team can judge the native SDL output directly, not only the sweep candidates.&lt;/p&gt;

&lt;p&gt;The practical result is modest but important: late gameplay is now narrowed past “maybe BG3 matters here” and into one explicit, versioned composition rule that the runtime consumes. The next question is whether that same family needs more measured state beyond this top-band rule, or whether extending the composition contract to more anchors still pays off.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>retro</category>
      <category>sdl</category>
      <category>c</category>
    </item>
    <item>
      <title>Sweeping late-gameplay BG3 cutoffs in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 20:25:01 +0000</pubDate>
      <link>https://dev.to/nivandosoares/sweeping-late-gameplay-bg3-cutoffs-in-the-td2-sdl-port-39f6</link>
      <guid>https://dev.to/nivandosoares/sweeping-late-gameplay-bg3-cutoffs-in-the-td2-sdl-port-39f6</guid>
      <description>&lt;h1&gt;
  
  
  Sweeping late-gameplay BG3 cutoffs in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;The previous checkpoint closed one ambiguity in the late gameplay bundles: raw &lt;code&gt;BG3&lt;/code&gt; was there, and it was visually meaningful. That still left the harder question open: what exact composition rule was missing?&lt;/p&gt;

&lt;p&gt;This turn I promoted a dedicated sweep tool for one very narrow hypothesis:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;BG3&lt;/code&gt; above &lt;code&gt;BG2&lt;/code&gt; in a top band&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;BG3&lt;/code&gt; under &lt;code&gt;BG2&lt;/code&gt; below that band&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The new tool is &lt;code&gt;tools/analyze_gameplay_bg3_cutoff.py&lt;/code&gt;, and it scores tracked bundle compositions against &lt;code&gt;bg_stack_visible_support.png&lt;/code&gt; instead of relying on eyeballing alone.&lt;/p&gt;

&lt;p&gt;I ran it against three promoted late-entry anchors:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;3250&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3400&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;3550&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The result was stronger than I expected.&lt;/p&gt;

&lt;p&gt;Best cutoffs landed at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;3250&lt;/code&gt;: &lt;code&gt;79&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3400&lt;/code&gt;: &lt;code&gt;79&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;3550&lt;/code&gt;: &lt;code&gt;95&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;And the mixed candidate beat the tracked &lt;code&gt;main.png&lt;/code&gt; on all three anchors.&lt;/p&gt;

&lt;p&gt;That matters because it narrows the renderer problem again. The next useful question is not “should we somehow use more BG3?” and it is not “are the assets incomplete?”. The next useful question is whether late gameplay needs a top-band &lt;code&gt;BG3 &amp;gt; BG2&lt;/code&gt; precedence rule, likely with a slightly deeper band on the later collision-heavy &lt;code&gt;3550&lt;/code&gt; phase.&lt;/p&gt;

&lt;p&gt;This is exactly the kind of result I want from the archaeology/tooling loop:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;define one falsifiable renderer rule&lt;/li&gt;
&lt;li&gt;score it against tracked artifacts&lt;/li&gt;
&lt;li&gt;promote PNGs the design team can inspect&lt;/li&gt;
&lt;li&gt;reduce the search space for the native SDL renderer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Promoted artifacts from this checkpoint include:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tools/out/gameplay_bg3_cutoff_sweep_20260401/summary.md&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/gameplay_bg3_cutoff_sweep_20260401/lane3_live_entry_frame03250_bundle_bg3_cutoff_candidate.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/gameplay_bg3_cutoff_sweep_20260401/lane3_live_entry_brake_traffic_frame03400_bundle_bg3_cutoff_candidate.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/gameplay_bg3_cutoff_sweep_20260401/lane3_live_entry_frame03550_bundle_bg3_cutoff_candidate.png&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is straightforward: either promote this as a measured late-gameplay contract surface first, or translate it into a native renderer rule and let the compare lane decide if it survives contact with the real frame stack.&lt;/p&gt;

</description>
      <category>c</category>
      <category>gamedev</category>
      <category>retrogaming</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>Promoting raw BG3 gameplay bundle previews in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 20:10:44 +0000</pubDate>
      <link>https://dev.to/nivandosoares/promoting-raw-bg3-gameplay-bundle-previews-in-the-td2-sdl-port-52c1</link>
      <guid>https://dev.to/nivandosoares/promoting-raw-bg3-gameplay-bundle-previews-in-the-td2-sdl-port-52c1</guid>
      <description>&lt;h1&gt;
  
  
  Promoting raw BG3 gameplay bundle previews in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;Today's checkpoint was small in code size but important in interpretation.&lt;/p&gt;

&lt;p&gt;The late gameplay bundles in the project already had useful &lt;code&gt;BG1&lt;/code&gt;, &lt;code&gt;BG2&lt;/code&gt;, &lt;code&gt;OBJ&lt;/code&gt;, and screenshot-derived support surfaces, but they were still weak on one practical question: when design flagged the sky/horizon side of gameplay, were we looking at a missing asset, or were we looking at a composition problem?&lt;/p&gt;

&lt;p&gt;I closed that ambiguity by extending the gameplay bundle builder to emit first-class &lt;code&gt;BG3&lt;/code&gt; artifacts next to the existing layer outputs:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;bg3.ppm&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg3.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg3_render.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Then I refreshed the promoted late-entry bundles for frames &lt;code&gt;3250&lt;/code&gt;, &lt;code&gt;3400&lt;/code&gt;, and &lt;code&gt;3550&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The useful result is that raw &lt;code&gt;BG3&lt;/code&gt; is clearly populated on these anchors. It carries the sky-to-horizon helper strip much more directly than &lt;code&gt;BG2&lt;/code&gt;, which means the next renderer question is no longer "does this bundle even have usable BG3?". The next question is "which gating/composition rule makes that helper layer visible in the final frame?"&lt;/p&gt;

&lt;p&gt;That sounds subtle, but it matters a lot for a faithful SNES-style port. It keeps the team from wasting time chasing asset extraction that is already good enough, and it gives design a tracked PNG surface for the exact layer that appears to carry the late gameplay horizon helper.&lt;/p&gt;

&lt;p&gt;Promoted artifacts from this checkpoint:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_entry_frame03250_bundle/bg3.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_entry_brake_traffic_frame03400_bundle/bg3.png&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_entry_frame03550_bundle/bg3.png&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The code change itself lives in &lt;code&gt;tools/build_gameplay_frame_bundle.py&lt;/code&gt;, and the lane-3 notes were updated so the new &lt;code&gt;BG3&lt;/code&gt; surfaces are part of the documented review workflow.&lt;/p&gt;

&lt;p&gt;That is the kind of progress I want more often in this port: narrow the uncertainty, promote the artifact, and make the next renderer step smaller and more defensible.&lt;/p&gt;

</description>
      <category>c</category>
      <category>gamedev</category>
      <category>retrogaming</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>Versioning gameplay scanline contracts in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 19:51:47 +0000</pubDate>
      <link>https://dev.to/nivandosoares/versioning-gameplay-scanline-contracts-in-the-td2-sdl-port-5960</link>
      <guid>https://dev.to/nivandosoares/versioning-gameplay-scanline-contracts-in-the-td2-sdl-port-5960</guid>
      <description>&lt;h1&gt;
  
  
  Versioning gameplay scanline contracts in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;This checkpoint moves one of the gameplay renderer shortcuts into a cleaner shape.&lt;/p&gt;

&lt;p&gt;Until now, the SDL runtime loaded the live-race scanline overlay through one scene-specific path. That was enough to prove the horizon fix on the promoted &lt;code&gt;gameplay_live_race_mid&lt;/code&gt; bundle, but it was not a good long-term surface for more gameplay phases.&lt;/p&gt;

&lt;p&gt;So I replaced that hardcoded lookup with a versioned contract:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rom_analysis/docs/gameplay_scanline_contracts.jsonc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The runtime now selects scanline overlays from that contract, and the tracked sources behind it currently are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_race_mid_scanline_full/td2_scanline_step_test.json&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;tools/out/lane3_live_entry_frame03250_scanline_full/td2_scanline_step_test.json&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contract still uses a deliberately narrow field set:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main_layers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg1_hscroll/bg1_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg2_hscroll/bg2_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg3_hscroll/bg3_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That is enough to keep the first solved gameplay consumer intact. The live-race bundle still renders with the restored sky / mountain / roadside split, and that stays protected by the new &lt;code&gt;test_scanline_contract.sh&lt;/code&gt; smoke.&lt;/p&gt;

&lt;p&gt;The more interesting part of this checkpoint is the second gameplay consumer.&lt;/p&gt;

&lt;p&gt;I promoted &lt;code&gt;tools/out/lane3_live_entry_frame03250_bundle/design_pack&lt;/code&gt; onto the same contract-backed surface and captured a full &lt;code&gt;224&lt;/code&gt;-scanline trace for it. That closes a useful architectural question: later gameplay bundles can now opt into the same scanline path without needing another runtime special case.&lt;/p&gt;

&lt;p&gt;But it also closed a stronger negative result.&lt;/p&gt;

&lt;p&gt;When I compared the &lt;code&gt;3250&lt;/code&gt; runtime output with and without the new scanline contract, the result was still:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;0&lt;/code&gt; mismatched pixels&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That means the second gameplay phase is &lt;strong&gt;not&lt;/strong&gt; blocked on trace plumbing anymore. It is blocked on missing renderer inputs beyond the current field set. In other words, scrolls plus &lt;code&gt;main_layers&lt;/code&gt; were enough for the first live-race fix, but they are not enough for the later live-entry phase.&lt;/p&gt;

&lt;p&gt;That is exactly the kind of boundary I wanted from this step: less ad hoc code, one versioned path for gameplay scanlines, and a clearer definition of what the next renderer promotion actually has to solve.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>c</category>
      <category>sdl</category>
      <category>retrodev</category>
    </item>
    <item>
      <title>Restoring gameplay scanline depth in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 19:37:04 +0000</pubDate>
      <link>https://dev.to/nivandosoares/restoring-gameplay-scanline-depth-in-the-td2-sdl-port-1mkg</link>
      <guid>https://dev.to/nivandosoares/restoring-gameplay-scanline-depth-in-the-td2-sdl-port-1mkg</guid>
      <description>&lt;h1&gt;
  
  
  Restoring gameplay scanline depth in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;This checkpoint closed a very specific visual bug in the SDL reimplementation of &lt;em&gt;The Duel: Test Drive II&lt;/em&gt;.&lt;/p&gt;

&lt;p&gt;The promoted gameplay rail &lt;code&gt;gameplay_live_race_mid&lt;/code&gt; was still rendering from a flat end-of-frame &lt;code&gt;ppu_state&lt;/code&gt;, which made the road swallow the shoulders and the horizon. That was good enough for early archaeology, but not for a faithful SNES-mimetic port.&lt;/p&gt;

&lt;p&gt;The fix was to stop treating that gameplay seed as one global presentation state. I attached the measured visible-scanline profile from &lt;code&gt;tools/out/lane3_live_race_mid_scanline_full/td2_scanline_step_test.json&lt;/code&gt; directly to the promoted rail and fed these fields into the native SDL renderer:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;main_layers&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg1_hscroll/bg1_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg2_hscroll/bg2_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;bg3_hscroll/bg3_vscroll&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That immediately restored the first-order depth cues on the live-race frame: the horizon comes back, &lt;code&gt;BG3&lt;/code&gt; only re-enters on the measured strip, and &lt;code&gt;BG2&lt;/code&gt; uses the real per-scanline staircase instead of one global &lt;code&gt;VOFS&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;The useful part is that this did &lt;strong&gt;not&lt;/strong&gt; require inventing a new gameplay heuristic. The scheduler rail stayed intact; the improvement came from consuming the trace we already had in a more faithful way.&lt;/p&gt;

&lt;p&gt;I also kept the guardrails cheap:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;make -C port test&lt;/code&gt; still passes end to end&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;test_scheduler.sh&lt;/code&gt; now proves that the scanline profile is loaded for &lt;code&gt;gameplay_live_race_mid&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the same smoke also checks a few framebuffer pixels so the restored sky/mountain/grass split does not silently regress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;There is still a clear boundary here: only the promoted live-race rail is scanline-aware today. The next step is to move this attachment into versioned contract data and promote another gameplay phase on the same path instead of letting later bundles fall back to flat presentation.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>c</category>
      <category>sdl</category>
      <category>retrodev</category>
    </item>
    <item>
      <title>Promoting the measured 2052..2088 menu window in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 18:41:13 +0000</pubDate>
      <link>https://dev.to/nivandosoares/promoting-the-measured-20522088-menu-window-in-the-td2-sdl-port-17gf</link>
      <guid>https://dev.to/nivandosoares/promoting-the-measured-20522088-menu-window-in-the-td2-sdl-port-17gf</guid>
      <description>&lt;h1&gt;
  
  
  Promoting the measured &lt;code&gt;2052..2088&lt;/code&gt; menu window in the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;This checkpoint closes a useful gap in the SDL runtime: the &lt;code&gt;menu_gameplay_entry&lt;/code&gt; rail no longer jumps from a handful of sampled post-&lt;code&gt;2050&lt;/code&gt; anchors. It now carries the exact measured baseline window from frames &lt;code&gt;2052..2088&lt;/code&gt;, and the default-rival &lt;code&gt;A&lt;/code&gt; lane replays that same window with measured state mutations instead of guessed interpolation.&lt;/p&gt;

&lt;p&gt;The practical change is in the scheduler and mutator surface. The runtime now loads every measured baseline frame in that bounded window from &lt;code&gt;scheduler_rail_contracts.jsonc&lt;/code&gt;, and the &lt;code&gt;A&lt;/code&gt; route mutator applies the traced &lt;code&gt;state_09a2/state_09a8/state_137c&lt;/code&gt; plus &lt;code&gt;dp_0020/dp_0022/dp_0053/dp_0054&lt;/code&gt; values for the same corridor. That makes the menu rail much harder to drift silently: it is no longer “five anchors and some hope”, it is a concrete measured sequence.&lt;/p&gt;

&lt;p&gt;The probe data justified that promotion. Inside &lt;code&gt;2054..2088&lt;/code&gt;, the no-input lane is stable enough to treat as exact contract data, while the &lt;code&gt;A&lt;/code&gt; lane forms a real staircase: &lt;code&gt;dp_0053/dp_0054&lt;/code&gt; advance frame by frame, &lt;code&gt;dp_0020&lt;/code&gt; only flips on selected even frames, and &lt;code&gt;state_09a8&lt;/code&gt; only flips on part of the odd-frame cadence. That is exactly the kind of bounded, deterministic window that is worth promoting verbatim before trying to generalize anything.&lt;/p&gt;

&lt;p&gt;Validation stayed clean after the expansion:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;scheduler smoke: &lt;code&gt;320/320&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;input mutation smoke: &lt;code&gt;200/200&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;compare lane: &lt;code&gt;3/3&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;regression lane: &lt;code&gt;2/2&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For design review, the runtime is still emitting PNG alongside the native PPM dumps, and this checkpoint also materialized a local paired &lt;code&gt;A/B&lt;/code&gt; PNG pack for frames &lt;code&gt;2054&lt;/code&gt;, &lt;code&gt;2066&lt;/code&gt;, &lt;code&gt;2083&lt;/code&gt;, and &lt;code&gt;2088&lt;/code&gt; so the team can track the corridor visually as the runtime evolves.&lt;/p&gt;

&lt;p&gt;The next gate is straightforward now: stop hand-feeding this through &lt;code&gt;--input-script&lt;/code&gt; only and wire live SDL keyboard/controller input into the same mutator path. The measured menu window is doing its job; the right next move is to make the runtime consume real input on top of it.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>c</category>
      <category>sdl</category>
      <category>reverseengineering</category>
    </item>
    <item>
      <title>Measured post-2050 input anchors and PNG review artifacts for the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:53:14 +0000</pubDate>
      <link>https://dev.to/nivandosoares/measured-post-2050-input-anchors-and-png-review-artifacts-for-the-td2-sdl-port-5dk9</link>
      <guid>https://dev.to/nivandosoares/measured-post-2050-input-anchors-and-png-review-artifacts-for-the-td2-sdl-port-5dk9</guid>
      <description>&lt;h1&gt;
  
  
  Measured post-2050 input anchors and PNG review artifacts for the TD2 SDL port
&lt;/h1&gt;

&lt;p&gt;The current TD2 SDL runtime already had scripted input and a first menu handoff mutator, but it still flattened the post-&lt;code&gt;2050&lt;/code&gt; default-rival corridor into a mostly generic rail.&lt;/p&gt;

&lt;p&gt;This checkpoint moves one step deeper into a SNES-mimetic path:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;promoted exact no-input scheduler anchors at frames &lt;code&gt;2052&lt;/code&gt;, &lt;code&gt;2053&lt;/code&gt;, &lt;code&gt;2083&lt;/code&gt;, &lt;code&gt;2104&lt;/code&gt;, and &lt;code&gt;2125&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;overlaid the traced default-rival &lt;code&gt;A&lt;/code&gt; route on top of those anchors&lt;/li&gt;
&lt;li&gt;carried measured fields instead of heuristics: &lt;code&gt;state_09a2&lt;/code&gt;, &lt;code&gt;state_09a8&lt;/code&gt;, &lt;code&gt;state_137c&lt;/code&gt;, &lt;code&gt;dp_0020&lt;/code&gt;, &lt;code&gt;dp_0022&lt;/code&gt;, &lt;code&gt;dp_0053&lt;/code&gt;, and &lt;code&gt;dp_0054&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;taught the runtime dump path to emit PNG siblings for every PPM artifact, including compare outputs&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The useful practical effect is twofold.&lt;/p&gt;

&lt;p&gt;First, the port now has a grounded post-&lt;code&gt;2050&lt;/code&gt; mutation surface that is tied directly to probe evidence instead of broad guessed gameplay logic. Second, the same runtime artifacts that validate the port are now immediately usable by design review without manual conversion.&lt;/p&gt;

&lt;p&gt;Validation stayed bounded and green:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;make -C port test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;PNG sanity check against the existing PPM dump path: &lt;code&gt;0&lt;/code&gt; mismatched pixels on the frame-&lt;code&gt;300&lt;/code&gt; smoke&lt;/li&gt;
&lt;li&gt;local review pack exported for the new anchor frames under &lt;code&gt;tools/out/port_input_mutation_anchor_pngs_20260401/&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is still not a full continuous gameplay model. The next step is to densify these exact anchors into short windows, especially around the &lt;code&gt;2054..2088&lt;/code&gt; dashboard/radar divergence, and then feed live SDL input into the same mutator surface.&lt;/p&gt;

</description>
      <category>retro</category>
      <category>gamedev</category>
      <category>c</category>
      <category>emulation</category>
    </item>
    <item>
      <title>Adding Scripted Input Mutation to the TD2 SDL Runtime</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 17:29:29 +0000</pubDate>
      <link>https://dev.to/nivandosoares/adding-scripted-input-mutation-to-the-td2-sdl-runtime-3l8b</link>
      <guid>https://dev.to/nivandosoares/adding-scripted-input-mutation-to-the-td2-sdl-runtime-3l8b</guid>
      <description>&lt;h1&gt;
  
  
  Adding Scripted Input Mutation to the TD2 SDL Runtime
&lt;/h1&gt;

&lt;p&gt;The last checkpoint externalized the non-intro scheduler rails into versioned contracts. That closed the hardcoded-anchor problem, but the runtime was still fundamentally input-blind.&lt;/p&gt;

&lt;p&gt;This checkpoint adds the first real input surface on top of those rails.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;There is now a shared input module in the port runtime:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;td2_input.*&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It parses the same window syntax already used by the Mesen-side tooling:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;frame:buttons&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;start-end:buttons&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;with button tokens like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;a&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;b&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;start&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;up&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;left&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;right&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The SDL runtime now accepts that through:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;--input-script&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So a headless run can do things like:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;./port/build/td2_port &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scene-dir&lt;/span&gt; tools/out/design_lane3_live_race_mid_frame0_native &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--scheduler-profile&lt;/span&gt; gameplay_live_race_mid &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--input-script&lt;/span&gt; &lt;span class="s1"&gt;'3:a'&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--headless&lt;/span&gt; &lt;span class="se"&gt;\&lt;/span&gt;
  &lt;span class="nt"&gt;--frames&lt;/span&gt; 1
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h2&gt;
  
  
  First promoted mutations
&lt;/h2&gt;

&lt;p&gt;The new layer is intentionally narrow and grounded.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. &lt;code&gt;JOY1&lt;/code&gt; sample in runtime state
&lt;/h3&gt;

&lt;p&gt;Current active buttons now mirror into &lt;code&gt;state_0960&lt;/code&gt;, which the archaeology already treats as the &lt;code&gt;JOY1&lt;/code&gt; sample copied during NMI.&lt;/p&gt;

&lt;p&gt;That means the runtime is no longer just replaying callback/state rows. It now has a direct input-facing field that changes under scripted input.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Traced no-opponent menu route
&lt;/h3&gt;

&lt;p&gt;The &lt;code&gt;menu_gameplay_entry&lt;/code&gt; rail now also recognizes the traced no-opponent corridor:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;right+down&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;then confirm&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;When that route is present in the input history, the downstream handoff flips from the default rival baseline:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$1C70 = 0&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$1C76 = 1&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;to the no-opponent state:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;$1C70 = 3&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;$1C76 = 0&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This is the first input-driven semantic branch promoted directly into the runtime.&lt;/p&gt;

&lt;h2&gt;
  
  
  Validation
&lt;/h2&gt;

&lt;p&gt;I added a new smoke:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;./port/test_input_mutation.sh&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Current pass surface:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;gameplay &lt;code&gt;A&lt;/code&gt; -&amp;gt; &lt;code&gt;state_0960 = 0x0080&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;gameplay &lt;code&gt;B&lt;/code&gt; -&amp;gt; &lt;code&gt;state_0960 = 0x8000&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;gameplay &lt;code&gt;right+down&lt;/code&gt; -&amp;gt; &lt;code&gt;state_0960 = 0x0500&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;menu no-opponent route -&amp;gt; downstream &lt;code&gt;$1C70 = 3 / $1C76 = 0&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The full port test suite stayed green after that.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;This is not full gameplay input emulation yet. But it is an important boundary crossing.&lt;/p&gt;

&lt;p&gt;The runtime now has:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;contract-fed rails&lt;/li&gt;
&lt;li&gt;a reusable scripted input surface&lt;/li&gt;
&lt;li&gt;a first direct input mirror&lt;/li&gt;
&lt;li&gt;a first traced route mutation that changes semantic state instead of just frame selection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is to extend that layer into the post-&lt;code&gt;2050&lt;/code&gt; gameplay deltas that are already narrowed in the archaeology, especially fields like &lt;code&gt;state_09a2&lt;/code&gt;, &lt;code&gt;state_09a8&lt;/code&gt;, &lt;code&gt;dp_0053&lt;/code&gt;, and &lt;code&gt;dp_0054&lt;/code&gt;.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>sdl</category>
      <category>c</category>
      <category>retrocomputing</category>
    </item>
    <item>
      <title>Contract-Fed Scheduler Rails for the TD2 SDL Port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 15:29:44 +0000</pubDate>
      <link>https://dev.to/nivandosoares/contract-fed-scheduler-rails-for-the-td2-sdl-port-1kk5</link>
      <guid>https://dev.to/nivandosoares/contract-fed-scheduler-rails-for-the-td2-sdl-port-1kk5</guid>
      <description>&lt;h1&gt;
  
  
  Contract-Fed Scheduler Rails for the TD2 SDL Port
&lt;/h1&gt;

&lt;p&gt;This checkpoint closes an important bootstrap gap in the SDL port.&lt;/p&gt;

&lt;p&gt;The runtime already had three promoted scheduler rails:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intro_noinput&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;menu_gameplay_entry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gameplay_live_race_mid&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;But two of them were still encoded as hardcoded C branches. That was good enough to prove the path, but not good enough to treat the rails as reusable contracts.&lt;/p&gt;

&lt;h2&gt;
  
  
  What changed
&lt;/h2&gt;

&lt;p&gt;The non-intro rails now load from a shared contract file:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;rom_analysis/docs/scheduler_rail_contracts.jsonc&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That file carries the validated frame windows for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the menu handoff corridor from &lt;code&gt;1500 -&amp;gt; 2050&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;the reproducible gameplay seed over &lt;code&gt;0 -&amp;gt; 11&lt;/code&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;So &lt;code&gt;td2_scheduler&lt;/code&gt; no longer needs embedded anchor tables for menu/gameplay playback. The runtime still uses the callback model for intro, but menu and gameplay now come from versioned contract data.&lt;/p&gt;

&lt;p&gt;I also tightened the scheduler smoke so it proves more than field values. It now asserts:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;scheduler.contract_loaded&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;segment_count&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;scheduler_contract&lt;/code&gt; as the live state source&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;That pushes the smoke from &lt;code&gt;156&lt;/code&gt; checks to &lt;code&gt;175&lt;/code&gt;, while keeping the same three rails green.&lt;/p&gt;

&lt;h2&gt;
  
  
  Wiki automation fix
&lt;/h2&gt;

&lt;p&gt;The second part of the checkpoint is operational, but it matters.&lt;/p&gt;

&lt;p&gt;The repo already had a post-push wrapper that rebuilt the curated docs wiki. The old safety rule was to skip the wiki auto-commit whenever the main worktree had unrelated dirty files. That avoided mixing work, but it also meant the wiki was often left stale.&lt;/p&gt;

&lt;p&gt;The wrapper now rebuilds the wiki in a temporary isolated &lt;code&gt;git worktree&lt;/code&gt; at the pushed checkpoint. If the generated wiki changes, the follow-up refresh commit is created and pushed from there.&lt;/p&gt;

&lt;p&gt;That means:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;unrelated local dirty files no longer block wiki publication&lt;/li&gt;
&lt;li&gt;the wiki refresh commit stays isolated&lt;/li&gt;
&lt;li&gt;local generated wiki output can be cleaned back to the pushed state afterward&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Why this matters
&lt;/h2&gt;

&lt;p&gt;This is still bootstrap infrastructure, not full gameplay execution. But it moves the port in the right direction:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;runtime rails are becoming data-backed instead of branch-backed&lt;/li&gt;
&lt;li&gt;validation is proving source and handoff shape, not just static values&lt;/li&gt;
&lt;li&gt;the docs/wiki side is finally reliable enough to keep up with the checkpoint flow&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The next step is not more rail hardcoding. The next step is mutating &lt;code&gt;menu_gameplay_entry&lt;/code&gt; and &lt;code&gt;gameplay_live_race_mid&lt;/code&gt; under real input on top of these contracts.&lt;/p&gt;

</description>
      <category>gamedev</category>
      <category>sdl</category>
      <category>c</category>
      <category>retrocomputing</category>
    </item>
    <item>
      <title>From seeded state to scheduler rails in the TD2 SDL port</title>
      <dc:creator>Nivando Soares</dc:creator>
      <pubDate>Wed, 01 Apr 2026 14:56:55 +0000</pubDate>
      <link>https://dev.to/nivandosoares/from-seeded-state-to-scheduler-rails-in-the-td2-sdl-port-5g0l</link>
      <guid>https://dev.to/nivandosoares/from-seeded-state-to-scheduler-rails-in-the-td2-sdl-port-5g0l</guid>
      <description>&lt;h1&gt;
  
  
  From Seeded State To Scheduler Rails In The TD2 SDL Port
&lt;/h1&gt;

&lt;p&gt;The latest checkpoint on the Test Drive II SNES port closes an important architectural gap.&lt;/p&gt;

&lt;p&gt;Up to this point, the SDL runtime could load design packs, seed SNES-like state, render natively from &lt;code&gt;VRAM/CGRAM/OAM/PPU&lt;/code&gt;, and compare itself against trusted extracted frames. That was a good bootstrap, but it still depended on a seeded callback shadow for the semantic side.&lt;/p&gt;

&lt;p&gt;This checkpoint promotes a minimal callback scheduler into the runtime.&lt;/p&gt;

&lt;p&gt;What changed:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;the runtime now resolves scheduler profiles instead of seeding callback state only once at init&lt;/li&gt;
&lt;li&gt;the first three promoted rails are now executable in the SDL port:

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;intro_noinput&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;menu_gameplay_entry&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;gameplay_live_race_mid&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;


&lt;/li&gt;

&lt;li&gt;the intro rail now carries the validated handoff from &lt;code&gt;01:9FE5&lt;/code&gt; into &lt;code&gt;00:8029&lt;/code&gt; through the &lt;code&gt;986 -&amp;gt; 1117&lt;/code&gt; window&lt;/li&gt;

&lt;li&gt;the menu rail now covers the input-driven corridor from &lt;code&gt;1500 -&amp;gt; 2050&lt;/code&gt;
&lt;/li&gt;

&lt;li&gt;the gameplay rail now covers the reproducible live-race seed over the promoted &lt;code&gt;3 -&amp;gt; 11&lt;/code&gt; window&lt;/li&gt;

&lt;/ul&gt;

&lt;p&gt;I also fixed a practical blocker in the content pipeline: tracked investigation bundles without &lt;code&gt;layers/main_visible.ppm&lt;/code&gt; can now load directly in the runtime as long as compare is not requested. That matters because the most useful menu/gameplay design packs are often raw-state bundles first and compare goldens later.&lt;/p&gt;

&lt;p&gt;Validation for this checkpoint is intentionally cheap and explicit:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;&lt;code&gt;make -C port test&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;regression smoke still passes&lt;/li&gt;
&lt;li&gt;compare lane still passes on the promoted intro fixtures&lt;/li&gt;
&lt;li&gt;intro callback model smoke passes&lt;/li&gt;
&lt;li&gt;new scheduler smoke passes across intro, menu, and gameplay rails&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The important part is not that the scheduler is complete. It is that the port is no longer locked into intro-only seeded playback. The same runtime loop now has a viable proving path on menu and gameplay surfaces, which is where the next input and ownership work should happen.&lt;/p&gt;

</description>
      <category>c</category>
      <category>sdl</category>
      <category>reverseengineering</category>
      <category>gamedev</category>
    </item>
  </channel>
</rss>
