Skip to content

Recompiler correctness: fail loud, fix delay slots, $ra, jump tables and overlaps - #1

Merged
Dellareti merged 53 commits into
mainfrom
feature/observability-metrics
Aug 13, 2026
Merged

Dellareti merged 53 commits into
mainfrom
feature/observability-metrics

Conversation

@Dellareti

Copy link
Copy Markdown
Member

Static-recompiler correctness work on the Crash Bandicoot bring-up, plus the
observability the branch started as. Suite went 583 → 662.

The change that reframed the rest

recomp_dispatch now aborts on an unmapped target instead of logging five
times and continuing. Every miss used to become a silent no-op: execution
carried on with corrupted state and the symptom surfaced far from its cause.
Failing at the cause turned debugging from divergent to convergent, and three
long-standing defects surfaced within a day of making the switch.

PS1_DISPATCH_PERMISSIVE=1 restores the old behaviour for deliberately
incomplete builds.

Recompiler correctness

  • Delay slot that is also a branch target keeps its label. It was emitted
    inline before the transfer and skipped at its natural position, taking its
    label with it; the post-pass then synthesised a dispatch to an address inside
    the same function. Measured case: 0x8003A264 in func_8003A144.
  • jal writes $ra. None of 1523 direct call sites did. $ra is data as
    much as control flow — callees save it, pass it, jump through it — so a stale
    value travelled into memory and came back as a garbage pointer.
  • Jump-table size comes from the sltiu bound, not a hardcoded 64 entries.
    The cap truncated a 146-entry table whose needed target sat at index 130, and
    over-read past short tables into adjacent data: 20 of 39 tables changed, most
    of them shrinking.
  • Functions no longer overlap the next entry point. Sizes arrive from the
    ELF symbols, from the gap to the next entry, and from --add-func after the
    fact, and the later sources never revisited the earlier ones — so seven
    functions emitted the same bytes twice under two names.

Runtime

  • Root-counter ticks advance from VSync. Crash arms RCnt2 with target 0x1000
    and registers a handler that increments ticks_elapsed, the delta-time its
    physics and waits are written against. That handler never fires here, so the
    word was frozen: measured 69 logic frames in 40s against 1261 with the correct
    rate — the game ran ~18× slow. Address and rate come from the game TOML's
    [timing] block, so no game address enters the runtime.
  • ADPCM sign-extension and End-flag handling. The low nibble was
    sign-extended wrongly, corrupting every other decoded sample; the End flag
    failed to jump to the repeat address in the common End+Mute case.

PsyQ HLE audit

Ten HLEs verified against cited sources with covering tests: SetDrawMode,
GetTPage, _dws/LoadImage, PutDispEnv, DrawOTag, GetClut,
SetDefDrawEnv, PutDrawEnv, _addque, _addque2. _addque2 was audited by
disassembling the game binary and returned the wrong value. PutDispEnv and
SetDefDrawEnv now honour the PAL branch instead of assuming NTSC.

FillRect also stopped treating a zero size as a full 512 lines, which had been
wiping the texture pages every frame.

Notes

  • feature/observability-metrics outgrew its name; the observability work is
    only the first third of it.
  • The fatal-dispatch default is a behaviour change: a build with missing
    functions now stops instead of limping.

Dellareti added 30 commits July 26, 2026 19:56
@Dellareti
Dellareti merged commit 7df7a28 into main Aug 13, 2026
2 checks passed
@Dellareti
Dellareti deleted the feature/observability-metrics branch September 20, 2026 23:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant