Skip to content

v0.5.0 - Gamepad input + dispatch audit + auto-discovery codegen

Choose a tag to compare

@mstan mstan released this 20 May 22:34
· 73 commits to master since this release

Production release (Release x64 build, not debug).

What's new

SDL_GameController gamepad input

Plug-and-play support for Xbox controllers (XInput backend on Windows) and HID gamepads (PS / Switch Pro / generic). Keyboard still works simultaneously — both layered via OR.

Default Xbox mapping:

Input Genesis action
D-pad / left stick D-pad (8000 axis deadzone)
A / Y (face) Genesis A
B (face) Genesis B
X (face) Genesis C
Start (Menu) Start
Back (View) Turbo (parallel to Tab)
LB Quicksave slot 1 (parallel to Shift+F1)
RB Quickload slot 1 (parallel to F1)

Recompiler: auto-discovered JMP-table dispatch

The recompiler now recognises two JMP (PC,Dn.W) dispatch patterns directly from the JMP base bytes, without needing per-site disasm seeds:

  • Duff's-device (8+ consecutive identical 2-byte opcodes from base): emits an in-function switch + goto label_XXXXXX or promotes targets to function entries depending on whether they're in the parent function or land past the next adjacent function.
  • Offset table (dc.w (target - base) rows pointing to interior labels): same — in-function switch when local, function-entry promotion when cross-function.

Eliminated all four INTERIOR_UNRESOLVED dispatch sites the dispatch audit was flagging in Sonic 1:

  • Deform_SLZ at $00647C — Star Light Zone background deformation Duff's device.
  • Elev_Types at $010BE6 — elevator object state dispatcher.
  • Circ_Types at $010DFC — circular-platform state dispatcher.
  • SStom_Move at $00BAD2 — sub-stomper object handler.

Before this release these sites silently failed at runtime (the dispatch lookup couldn't find the interior PC, function returned short, scroll/object handling did nothing). Now they all dispatch correctly via either an in-function switch or call_by_address.

Build-time dispatch audit

Every recompiler run now writes generated/sonic_dispatch_audit.log next to the generated C. Every JMP (PC,Dn.W) and JSR/BSR (PC,...) site is classified:

  • static_target / in_function_switch / dynamic_register — safe.
  • fallback_hybrid/function_entry, bra_w_trampoline, offset_table — runtime resolution via call_by_address, safe.
  • fallback_hybrid/INTERIOR_UNRESOLVED — the silent-failure class the CPZ-scroll fix surfaced. Sonic 1 now has zero of these.

If a future recompiler change introduces a new INTERIOR_UNRESOLVED site, it shows up in the audit log at build time instead of as a user-visible bug.

Observability: interior-label dispatch miss logging

Runner-side counterpart to the audit. interior_label_misses.log now captures every silent dispatch failure with addr / in_func / frame, so any latent silent-failure class shows up at runtime rather than hiding.

Under the hood

  • New runner/gamepad.{h,c} — SDL_GameController integration.
  • recompiler/src/code_generator.c — seed_pc_idx_dispatch_targets auto-discovery helper, in-function switch emit for Duff's-device + offset-table patterns, dispatch audit.
  • recompiler/src/game_config.{h,c} — new extra_seeds field for disasm local-label PCs (Sonic 2 only; Sonic 1 has no .lst checked in).
  • tests/tools/gen_disasm_labels.py — captures asm68k anonymous labels (+:, -:).

Usage

Drop a legally-acquired Sonic 1 ROM (sonic.bin or .md) anywhere Windows can find it; the exe will prompt for one on first launch.

SonicTheHedgehogRecomp.exe <rom.bin>                  # normal play
SonicTheHedgehogRecomp.exe <rom.bin> --turbo          # no-wait (for captures)
SonicTheHedgehogRecomp.exe <rom.bin> --wav out.wav    # audio capture