Make the repo approachable for outside contributors and an IDE integrator:
- New contributor docs:
- docs/EXECUTION_MODEL.md — semi-technical tour of the static / native-overlay
/ interpreter execution model and how coverage gaps get plugged.
- docs/ARCHITECTURE.md — component-level view (recompiler, runtime, LLE+HLE
BIOS, overlay backend static->gcc->tcc, renderers, oracle model).
- docs/BUILDING.md — accurate dependency table, per-platform prereqs, build
steps, and the submodule + local-junction framework-linking setup.
- CONTRIBUTING.md — distilled core rules, verification expectations, PR flow.
- Declutter root: move ~14 internal dev-logs into docs/internal/ (history
preserved via rename) with an index README; repoint CLAUDE.md references.
- README: correct build recipe, LLE+HLE + overlay-backend summary, links to the
new docs.
- code_provider.c: update the stale backend comment to the real static->gcc->tcc
resolution (dead SLJIT.md reference removed).
- config_schema.md: drop the obsolete "once Phase A2/A3 ships --config" note.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
4.7 KiB
RESOLVED: load=4 boot wedge — faithful guest-cycle pad ACK timing
Branch wt/tomba2-load-accuracy. When PSX_RAM_READ_WAIT_CYCLES=4 (the
Beetle-oracle-accurate single-load value, vs the old DuckStation 6), Tomba 2's
boot deterministically wedged in the BIOS shell. Root-caused and FIXED
2026-06-27. This is the faithful-core cascade exactly as CLAUDE.md predicts:
an accurate CPU cost exposed an inaccurate device timing.
Symptom (was reproducible, deterministic)
- pc=0 / halt;
total_checksfrozen (~134,756,056),current_funcin the BIOS shell (0x1FC2CE28 / 0x1FC2C3E0 — the exact landing varies by a few-cycle observer-effect, axis-7),i_stat0x80,i_mask0x0D. - Wedge mechanism (live-verified registers + disasm): a BIOS-shell GUI/object
builder indexes a 1672-byte-stride table at 0x80110EC8 by a handle
s1(s1*1672 + base) with only a lower-bound (bgez) guard, no upper bound. At the wedges1 = 0x68 = 104→v0 = 0x80110EC8 + 104*1672 = 0x8013B608, a wild pointer; the struct copy corrupts RAM → downstream pc=0. (RAM at the shell funcs was zeroed by the time of the halt — post-mortem RAM is unreliable; the always-on rings are the ground truth.)
CONFIRMED root cause (step A — validated, not hypothesised)
The proximate runaway immediately before the wedge is continuous controller
(pad) polling, NOT memcard enumeration as previously hypothesised. Live
evidence (sio_irq_dump on the wedged runtime): the last 150+ SIO IRQs are
100% source=pad, delay=4, active_device=PAD, mc_state=0 (card idle).
The unfaithfulness (source-confirmed, cross-ref Beetle frontio/sio + axis5 doc
D1/Fix-6): the pad fast-path in sio_write (sio.c:1386) bypassed the
guest-cycle-paced shifter and armed sio_irq_countdown = SIO_IRQ_DELAY_PAD (=4),
which sio_tick(0) decrements once per SIO register access (sio_tick is only
ever called with cycles=0 — see memory.c:389/498/589, interrupts.c:268). So pad
ACK→IRQ7 timing was access-count-paced, not guest-cycle-paced. The card path
(cycle-paced shifter via sio_advance ← psx_advance_cycles) was already
faithful; only the pad path was not.
Causal chain: with the faithful/faster CPU (load=4) the pad ACK IRQ fires at a different guest-cycle phase relative to the cycle-paced timers/VBLANK than it did at load=6, diverging the BIOS pad-detection state machine and ultimately producing the out-of-range handle 104. (The handoff's "memcard save-file enumeration count" attribution was wrong — it is the SIO controller path, a sub-case of the same axis-5 device-timing weakness.)
FIX (step B — faithful, validated)
runtime/src/sio.c, pad fast-path ACK arm: replaced the access-paced
sio_irq_countdown = SIO_IRQ_DELAY_PAD with the guest-cycle-paced ack
scheduler the card path uses:
sio_pending_ack = 1;
sio_ack_remaining = SIO_BAUD_CYCLES_DEFAULT + SIO_ACK_CYCLES_DEFAULT; /* 1088+170 */
sio_pending_ack_irq_en = 1;
g_sio_timing_active = 1;
sio_advance() (driven by psx_advance_cycles() per instruction) now delivers
the pad IRQ after a fixed guest-cycle delay (one byte-shift + ack pulse =
1258 cyc, identical to the card path), independent of how many SIO register
reads the CPU issues while it waits. Runtime-only change — no regen.
Validation
- load=4 boots past the wedge to the intro FMV (screenshot verified, pixels on screen — Rule 5), reaching frame 11,000+ stably (wedge was frame 2189, total_checks frozen). The same FMV the load=6 branch reached.
- Ruler #1 (BIOS EvCB-search [c5c→ca4], native 4500 vs Beetle 4382): native 54 vs Beetle 56, steady −2 = the known unmodeled load ReadFudge gap on the load=4 branch (a listed remaining Stage-2 component), NOT a regression — the SIO change cannot alter CPU instruction cost, and native being lower rules out a spurious mid-window IRQ.
Remaining completeness follow-up (recommended, not faithfulness-blocking)
The pad fast-path is now faithful but is still a separate code path from the card shifter. axis5 Fix-6 / the unfinished "1.0e-e2" (comment at sio.c:1868) specs fully removing the pad fast-path so pad bytes flow through the unified cycle-paced shifter (modeling the byte-shift-in-progress STAT transitions too). That is a path-unification refactor; it needs pad input validation at the title/menu (not just boot-to-FMV) before adopting. Both pad and card ACK timing are already guest-cycle-paced, so there is no remaining fake/access-paced site in the shipping (SIO_MODEL_CYCLE_PACED) config.
Cross-title note: the pad path is shared — regen+smoke Tomba1/MMX6/Ape before any cross-title merge of this branch (Rule -1: breaking other titles is acceptable; they regenerate).