DEV Community

Nivando Soares
Nivando Soares

Posted on

Adding Scripted Input Mutation to the TD2 SDL Runtime

Adding Scripted Input Mutation to the TD2 SDL Runtime

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.

This checkpoint adds the first real input surface on top of those rails.

What changed

There is now a shared input module in the port runtime:

  • td2_input.*

It parses the same window syntax already used by the Mesen-side tooling:

  • frame:buttons
  • start-end:buttons

with button tokens like:

  • a
  • b
  • start
  • up
  • down
  • left
  • right

The SDL runtime now accepts that through:

  • --input-script

So a headless run can do things like:

./port/build/td2_port \
  --scene-dir tools/out/design_lane3_live_race_mid_frame0_native \
  --scheduler-profile gameplay_live_race_mid \
  --input-script '3:a' \
  --headless \
  --frames 1
Enter fullscreen mode Exit fullscreen mode

First promoted mutations

The new layer is intentionally narrow and grounded.

1. JOY1 sample in runtime state

Current active buttons now mirror into state_0960, which the archaeology already treats as the JOY1 sample copied during NMI.

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.

2. Traced no-opponent menu route

The menu_gameplay_entry rail now also recognizes the traced no-opponent corridor:

  • right+down
  • then confirm

When that route is present in the input history, the downstream handoff flips from the default rival baseline:

  • $1C70 = 0
  • $1C76 = 1

to the no-opponent state:

  • $1C70 = 3
  • $1C76 = 0

This is the first input-driven semantic branch promoted directly into the runtime.

Validation

I added a new smoke:

  • ./port/test_input_mutation.sh

Current pass surface:

  • gameplay A -> state_0960 = 0x0080
  • gameplay B -> state_0960 = 0x8000
  • gameplay right+down -> state_0960 = 0x0500
  • menu no-opponent route -> downstream $1C70 = 3 / $1C76 = 0

The full port test suite stayed green after that.

Why this matters

This is not full gameplay input emulation yet. But it is an important boundary crossing.

The runtime now has:

  • contract-fed rails
  • a reusable scripted input surface
  • a first direct input mirror
  • a first traced route mutation that changes semantic state instead of just frame selection

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

Top comments (0)