Commit Graph

101 Commits

Author SHA1 Message Date
Matthew Stanley 64b084e2a7 load: add opt-in event horizon, warm routes, and audio sink 2026-07-14 22:44:31 -07:00
Matthew Stanley 12934ed2c5 data-shards spike: memoized pure-function replay (capture/verify/replay + cycle-faithful credit)
- runtime/src/data_shards.c + include: read-before-write recorder fed from
  the memory chokepoints; byte-verified replay (kernel-bless pattern);
  recorded-cycle credit in slices through psx_advance_cycles +
  psx_check_interrupts_at; purity poisons (MMIO/DMA/thread-switch/budget);
  .dss persistence under the unified cache dir; TCP data_shards cmd.
- emitter: [data_shards] funcs -> psx_datashard_enter at entry +
  psx_datashard_ret at jr-$ra sites (rolls cg-hash).
- obs: phase_hot set=static keyed on new g_psx_last_fn_entry entry stamp
  (current_func dispatch stamp survives post-IRQ resumption and pooled 55%
  of static samples on kernel 0xF40); TCP starv_ring query for the
  always-on PC-sample ring.
- docs/DATA_SHARDS.md: design, soundness model, gates, FMV exclusion.
2026-07-14 11:08:02 -07:00
Matthew Stanley 49c2619542 docs: merge ChatGPT consult into LOAD_TIME_ZERO burn-down
Key merges: event-horizon acceleration + device-batch advancement as
the new Lever-A axis (E2); data-shards temporal-write-visibility flaw
+ verify-only shadow mode gate (E4); never-early+catch-up reclassified
as drive-model correctness, not acceleration (E5); no universally safe
guest-visible CD speedup (risk ladder, per-game traits); state-
transition cache as the only true near-zero, Phase-2 (E10); 12-session
burn-down order with gates and kill criteria.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-14 00:06:10 -07:00
Matthew Stanley cca63dbaf9 docs: LOAD_TIME_ZERO proposal checklist (exploration, no code)
Frame: wall = guest_time x host-cost/guest-sec; turbo makes the window
emulation-bound, so Lever A (host cost: data shards, coverage) is safe
and multiplies with turbo; Lever B (guest CD latency) is the wedge zone;
Lever C (skip guest work) is the proven hardlock class. Burn-down E0-E7
with gates and kill criteria. ChatGPT-consult merge section pending.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-13 23:55:51 -07:00
Matthew Stanley c94fcd5e1a docs: add COMPILING_OVERLAYS how-to (overlay_captures.json -> shards)
User-facing guide for turning overlay_captures.json into native code:
developer-first framing (ship a full gcc cache), the manual gcc cache
build, the --static bake-into-binary path, and the staleness guard.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-11 14:19:16 -07:00
Matthew Stanley fb585541ec opengl: sustain full-rate gameplay and FMV presentation 2026-07-11 14:07:17 -07:00
Matthew Stanley 264f00cee5 widescreen: native-wide flat/phase backdrop gates + FMV silent-hold (Tomba 2 Beach Town)
- [widescreen] nw_flat_backdrop: in the native-wide mirror only, stretch
  untextured flat primitives across the wide output (flat-colour sky/water
  backdrops). Canonical 4:3 framebuffer stays byte-for-byte untouched.
- [widescreen] nw_phase_backdrop: stretch textured prims emitted before the
  frame first shaded 3D primitive (authored 2D backdrop phase), isolating
  the sky from the textured 3D foreground. Both runtime-only, off by default,
  title-opted (draw ordering is title-specific).
- [video] fmv_skip_no_xa_hold (4..3600 vblanks): presentation-side
  fast-forward latch after the last silent MDEC decode, so auto-skip covers a
  preloaded logo authored post-decode wait (Whoopee Camp logo). Guest
  timeline unchanged; default stays the generic 4-vblank hysteresis.
- ws_dbg_stretch correlation modes 7 (all prims) / 8 (all flat prims).
- fmv_state: JSON-escape config_path + report fmv_skip_no_xa_hold.

Validated on Tomba 2: Release build, unattended first-attract captures at
4:3 / 16:9 / 21:9, zero unknown dispatches.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:45:23 -07:00
Matthew Stanley 77d036bfb6 docs: production-friendly contributor docs + declutter root
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>
2026-07-07 18:25:16 -07:00
Matthew Stanley d3034f9fc2 Merge branch 'master' into wt/tsumu
# Conflicts:
#	runtime/src/memory.c
2026-07-06 15:33:41 -07:00
Matthew Stanley 38d1902c2b chore: remove game-specific Kula World content from framework repo
Games live in their own repositories in this project (TombaRecomp,
MegaManX6Recomp, ...); the framework holds only the shared recompiler +
runtime, never a game's config/assets. A contributor PR landed Kula World
(SCES-01000) game files directly in the framework, which is inconsistent
with that layout — that content belongs in the contributor's own game
repo. Remove it:

  - games/kula/{game.toml,seeds.txt}
  - docs/kula-world-findings.md, docs/{MACOS,WINDOWS}_BUILD_KULA.md
  - docs/assets/kula-world-*.png
  - README "This fork — Kula World" + macOS branding sections
  - runtime/CMakeLists.txt kula-runtime target

The general framework improvements those commits also carried are KEPT
(they benefit every game): KUSEG-addressed PS-X EXE header acceptance,
dirty-RAM interpreter interrupt-poll-at-entry, main-RAM 4x mirror /
GetID-from-serial / CD-status / CD-version fixes, and the Linux/macOS
POSIX ports. Source comments citing Kula as the motivating case are left
in place as provenance.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 14:53:47 -07:00
Matthew Stanley 60801820fd cleanup: genericize hardcoded dev-machine paths in tracked docs/tools
Replace absolute F:/C: paths with repo-relative (../TombaRecomp, bios/, beetle-psx/)
or placeholders (<desktop>/<psxref>/<nesrecomp>/<psxrecomp-v4>) across accuracy docs,
WIDESCREEN/SHADOW/RECURSION_BUG/config_schema/ISSUES/README, and tools/*.py|.ps1.
Removed a personal C:\Users\Matthew\Desktop path (ISSUES.md). Left CLAUDE.md/PLAN.md
sibling-project + version-history paths intact (intentional layout documentation).
2026-07-06 11:51:36 -07:00
Matthew Stanley 9b54441a8a repo cleanup: drop 16 stale handoff/overnight/resolved-bug docs; untrack scratch scripts + test binaries
Deleted dated session-scratch (HANDOFF*, OVERNIGHT*, SUMMARY*) and resolved-bug
writeups (memcard audit, Tomba2 attract/FMV/Whoopee, codex analysis, load4 wedge
superseded). KEPT docs still cited by canonical docs/source (RECURSION_BUG,
WEDGE_load4, CASE_A_AOT_GAP) and all current subsystem-design docs.
Untracked (gitignored, kept on disk): tools/_options_session/ (80 scratch scripts
w/ hardcoded paths) + runtime/tests/*.exe (build artifacts; .c sources stay).
Genericized sibling-project absolute paths in bios_hle.h + crash_trace.c; fixed
README link CURRENT_STATE->FAITHFUL_TIMING_PLAN.
2026-07-06 11:34:35 -07:00
Matthew Stanley 13d84d2f7b Merge branch 'pr9-kula' into merge-pr9
# Conflicts:
#	runtime/src/beetle_libretro.cpp
#	runtime/src/dirty_ram_interp.c
#	runtime/src/memory.c
2026-07-06 10:59:27 -07:00
Matthew Stanley c6d12d76cd docs: Appendix Z — full 37-entry vram_patch coverage + palette-family notes
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:51:23 -07:00
Matthew Stanley d78e7a784f text_xlate: [[vram_patch]] pre-rendered-strip layer + GP0-ring builder attribution
Two interlocking pieces from the Tsumu HUD/menu-label investigation:

- GP0 ring builder attribution (gpu.h/gpu.c/debug_server.c): op-0x80
  entries now carry a bounded guest-stack unwind (bld[6]) + copy-time
  $sp (csp); gpu_frame_dump emits func/ra per entry and csp+bld[] for
  copies. This cracked the HUD compose: whole label strips are
  MoveImaged from the glyph sheet (src 834/852,352 -> HUD cache y=32).

- [[vram_patch]] (text_xlate.cpp/.h + gpu.c hook): third translation
  layer for text that exists only as pre-rendered pixels in an
  uploaded texture asset (Tsumu HUD labels, menus, prompts live in
  the glyph-sheet image; the 144-glyph font has no katakana at all).
  On every GP0 0xA0 upload completion the configured rects are
  verified halfword-for-halfword against expected JP pixels and only
  then rewritten with the target-language pixels via the renderer
  facade (software/GL/supersample coherent). Re-applies on every
  re-upload; verify-fail leaves foreign assets untouched. Debug:
  xlate stats gains vram_patches/vram_applied; new "vpatch"
  force-apply subcommand for config iteration without rebuild.

Verified on-screen: HUD "STAGE: 1-1 / BATTERY: 100", pause menu
(CONTINUE/RESTART/GIVE UP/CHANGE CONTROLS, TARGET BATT), title-menu
strips. docs/STRING_TRANSLATION.md Appendix Z documents the model,
mechanism, authoring recipe, and remaining strip backlog.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-06 01:41:42 -07:00
Matthew Stanley 4806096cde docs: HUD reachability blockers (empty saves, static demo HUD)
Records the LOAD-GAME attempt outcome and why the HUD can't be finished
headless: both memcards are empty (0 used blocks) so LOAD GAME reaches no
stage; NEW GAME still hits the intractable name grid; and the attract
demo's HUD is composed once at stage-load and only displayed (never
per-frame-drawn as glyph quads in the full 554-3754 GP0 capture; its
values are static so there is no dynamic draw to intercept). The drawer
(0x8005443C) + font-tile scheme are cracked; capturing the HUD label's
own glyph quads needs real gameplay (dynamic HUD) reachable only via a
real save or driving past the name grid on an interactive build. No hook
shipped (no discriminator captured -> would risk non-HUD quads; Rule 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:30:16 -07:00
Matthew Stanley 7627978b8c docs: pin the per-glyph text drawer (0x8005443C) for the HUD remap
Live GP0-ring capture (on the name-entry screen, same per-glyph drawer
class as the HUD) cracked the mechanism for the eventual HUD index-remap:

- Drawer PC 0x8005443C draws one textured 0x2C quad per glyph via direct
  GP0, building prims in game RAM ~0x80092xxx; U,V into texpage 0x1E
  selects the font tile (CLUT 0x7C10).
- Texpage 0x1E holds hiragana AND Latin (the name-entry grid draws A-Z
  through this same drawer), so the Latin tile U,Vs the remap needs are
  readable straight off the grid's letter quads.
- Remaining blocker: catch the HUD's own stage-load compose to get its
  katakana source prims, then rewrite their U,V to Latin tiles (or hook
  0x8005443C to remap HUD-position glyph quads). Gated only on reaching a
  fresh stage-load (blind grid-confirm navigation didn't land headless; a
  reachable build clears it). No fragile partial shipped (Rule 5).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:12:32 -07:00
Matthew Stanley 4c19677b9e docs: CLEAR!! already English + HUD render model (GP0-ring evidence)
Corrects and refines the Bucket-1/3 findings with live GPU evidence:

- CLEAR!! banner: ALREADY renders in stylized Latin ("CLEAR!!") — verified
  on-screen in a gameplay stage. Not Japanese; no translation needed. The
  earlier "resister/baked-sprite" assumption was wrong.

- In-game HUD (ステージ/バッテリー): pinned the render model via the always-on
  GP0 ring (gpu_frame_dump). The game composites — ~6 draw prims/frame,
  rebuilding only the animated foreground (character/blocks, ~8 textured 0x2C
  quads at screen y>=31, via BIOS DMA from an OT at ~0x138xxx). The HUD (screen
  y~8) is composed ONCE at stage-load, not per-frame, and has no static SJIS
  source (RAM-scan proof). Remapping it needs catching that stage-load compose
  (a fresh stage load while observing) and a scoped blit/tile intercept — not a
  source-patch. NOT shipped: no clean pixel-verified remap this pass, and a
  blanket VRAM font swap would corrupt katakana elsewhere (Rule 5, no hacks).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 18:07:42 -07:00
Matthew Stanley 938fc26f17 docs: per-glyph coverage pass — shipped labels + composed-HUD/CLEAR resisters
Records the outcome of the per-glyph translation pass (Buckets 1-3):

- SHIPPED: stage/level/tutorial names (source-patch, f00631e/31ab3c2);
  name-entry prompt -> "YOUR NAME?" (source-patch, 31ba92d); name-entry
  Latin input confirmed NATIVE (grid already draws the font's A-Z/0-9).

- RESISTERS (not shipped, no fake): the in-game HUD (ステージ/バッテリー)
  and the CLEAR!! banner are composed per-glyph at runtime with NO static
  source address. Proven: a live 2 MB RAM scan finds ステージ/バッテリー
  only inside static message sentences (never a standalone HUD label,
  no ステージ: template); クリア/CLEAR/CLEAR exist nowhere as a
  standalone banner; and every disc graphic TIM was extracted+rendered —
  the only baked text is the already-English TSUMU light logo. So the
  source-patch (which needs a static address) cannot reach them; they
  require the scoped per-glyph sprite-draw index-remap hook (Appendix Y),
  a separate larger effort. Includes the full disc asset map.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 17:49:17 -07:00
Matthew Stanley b0f8a8518c docs: per-glyph text investigation (font index map, label storage, hook design)
Records the concrete findings for the deferred per-glyph HUD/menu/title
localization: the font glyph-index map (Latin A-Z=73-98, 0-9=105-114;
fullwidth Latin glyphs present, no injection needed), the static LE-SJIS
label storage in fixed-width space-padded slots (level names 0x80070755+,
tutorial/mode labels 0x80070300+), and the cleanest hook design (load-time
source patch: rewrite a label's katakana/hiragana SJIS to fullwidth-Latin
so the existing per-glyph routine draws English).

Not shipped: the verification bar is a rendered-English screenshot, and the
target screens (mode/level-select, in-game HUD) are unreachable in the
headless build — the attract never auto-plays the demo, and reaching them
needs completing the name-entry glyph grid (blind grid nav to the "End"
cell did not land). Committing a RAM-patch without that screenshot would be
unverified coverage. Remaining resisters (HUD runtime counter; title/CLEAR
sprites) documented.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 15:29:56 -07:00
Matthew Stanley d24d660774 docs: coverage status - 138-entry message table + reader limitation
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 13:52:32 -07:00
Matthew Stanley c72e36d10e docs: record Phase-0 empirical findings for string translation
Fold the hard-won runtime findings into STRING_TRANSLATION.md: the
message-table text is little-endian 16-bit Shift-JIS (EXE menu labels are
big-endian); FE-FF/FF-FF/30-FC control framing; message text is
string-pointer-drawn (interceptable) but HUD/title/menu are per-glyph
sprites (need a separate glyph-index hook); struct-embedded NUL records
crash if replaced (apply gated to 0xFFFF-framed messages by default);
capture quality gate; verification status; open items for full coverage.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 12:19:53 -07:00
Matthew Stanley bf4482b4c8 docs: spec on-the-fly string translation / localization framework
Implementation-ready design for capturing PSX source strings from memory
and substituting translated bytes on the fly, so JP-only titles (Tsumu
Light SLPS-02253 first) can be played in English or any language.

Adapts the author's proven n64recomp prior art (Pocket Monsters Stadium
JP): always-on capture ring keyed by FNV-1a64 content hash, a per-title
multilingual table, and a draw-time apply hook that repoints the string
arg at guest scratch so the original renderer draws the replacement (no
regen, unbounded length, dynamic args preserved).

Grounds the PSX design in real findings: Tsumu text is Shift-JIS (2-byte
fullwidth + 1-byte halfwidth kana) with 0xFFFF-framed message records;
in-EXE text block ~0x80070900-0x80072000 via pointer table at 0x80071474;
menu labels at 0x80010C5C/0x80010C94; font already has fullwidth Latin
(English renders with zero glyph injection). Hooks onto the psx_dispatch
chokepoint (fntrace_record census + g_psx_bios_hle_hook precedent), so no
per-function codegen and works in Release.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 11:04:47 -07:00
ai-tdd-labs 6e711e0f2d macos: build & run Kula World natively on Apple Silicon
The Windows->Linux POSIX port left the recompiler and runtime almost
portable; two small platform gaps remained on macOS (AppleClang, arm64),
both guarded so Windows/Linux are unaffected:

- runtime/src/autocompile.c: add #include <stdlib.h> for getenv. glibc and
  windows.h leak that declaration transitively; the macOS SDK does not, so
  AppleClang errored on the implicit declaration.
- recompiler/src/main_bios.cpp: the C-compiler probe used ">NUL" (Windows
  null device). On POSIX that misdirects the redirect and drops a stray
  "NUL" file in the cwd; now #ifdef'd to /dev/null.

The ucontext fiber backend already carried the _XOPEN_SOURCE/_DARWIN_C_SOURCE
guards it needs; SDL2 (Homebrew) selects the Metal backend automatically.

Verified end to end on macOS 15.5 / Apple Silicon: BIOS -> KULA WORLD title
-> menu -> 1 Player -> a 3D level that rolls under control.

Adds docs/MACOS_BUILD_KULA.md and a macOS gameplay screenshot; README notes
the port. Ignores the local dist/ .app bundle (recompiled-BIOS derivative).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-03 09:41:37 +02:00
Claude b8673b0ad0 docs: Windows 11 build/run handoff for Kula World
Step-by-step instructions for an AI agent to reproduce the working Kula
World recomp on Windows: prerequisites (MSYS2 mingw64 + SDL2), clone the
branch, user-supplied BIOS/disc, extract the boot EXE, build the
recompiler, generate BIOS + game C, build the kula-runtime target, run,
controls, and an honest verified/unverified status list.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-03 07:03:11 +00:00
Claude a6ee29a4ea readme: Kula World bring-up on Linux — title screen + the 5 fixes
Adds this fork's Kula World (SCES-01000) end-to-end target to the README with
a title-screen screenshot and a summary of the five runtime bugs fixed during
bring-up (four of them game-agnostic framework improvements): the dirty-RAM
interpreter interrupt-latency fix, the main-RAM mirror, GetID region
derivation, CD status bits, and the SCPH-1001 CD controller version.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-03 06:07:31 +00:00
Claude 590c236933 dirty_ram_interp: poll interrupts at interpreter entry — fixes Kula World wedge (title screen renders)
ROOT CAUSE of the Kula World black-screen wedge: a guest wait loop (libcd's
CdSync spin) that lives entirely in dirty RAM re-enters the dirty-RAM
interpreter once per short (~7-insn) block. The interpreter's only interrupt
poll was gated by the PER-INVOCATION (insns_executed & 0xFFF) counter, which
short blocks never reach — and when the loop exits to dispatch each iteration
that path isn't reached at all. So the interpreter ran millions of
instructions with psx_check_interrupts never called, while SR (IEc+IM2) and
Cause (IP2) fully satisfied the MIPS interrupt-take condition and i_stat had
CDROM+VBLANK pending. The CD IRQ that sets libcd's wait-flag was never
serviced; the loop spun ~14s per cycle until a rare timeout re-dispatched.

Fix: poll interrupts at the interpreter's per-invocation entry, gated by a
global invocation counter (every 64 entries), cpu->pc set for a coherent
resume — the interpreter analogue of a static block-leader poll.

RESULT: Kula World now boots to its title screen and menu. Diagnosed with the
self-stop trap + gdb sampling of SR/Cause/i_stat + the interrupt gate globals
(in_exception, total_checks) + g_dirty_ram_insns_run, which isolated 'CPU
spinning in the interpreter without polling interrupts'. General fix: any
dirty-RAM spin-loop now yields to interrupts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-03 06:02:35 +00:00
Claude 7c8298b12e docs: round 7 — remaining Kula wedge is unserviced CD IRQ (libcd CdSync never completes)
With the boot fixed, live state during the game's retry loop shows the CDROM
interrupt is raised (i_stat bit2) AND enabled (i_mask bit2) and the CPU takes
exceptions (VBLANK cleared each frame), yet the CDROM bit is never cleared and
the response FIFO never drained (irq_flag=0x03, response_read=0). The game's
libcd CD interrupt handler in the BIOS IntRP chain is not servicing the CD
IRQ, so CdSync never completes. Distinct from the CD-response-value bugs fixed
today; a BIOS-IRQ-chain / libcd-handler matter. Documents the next step and
the full session tally in docs/kula-world-findings.md.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 23:13:55 +00:00
Claude a3f57ec570 cdrom: Test 0x20 reports SCPH-1001 CD version (0x94..), removing spurious boot ReadTOC
The shell CD-init (ROM 0x1DF50) sets kernel flag [0xA000DFFC]=1 when the CD
controller version high byte (Test sub-cmd 0x20) is >= 0x95; the boot CD-open
(0xBFC0D570) then issues a ReadTOC gated on that flag. We returned the
PSone-era 0x97 0x01 0x10 0xC2 (copied from beetle, which hardcodes it) — wrong
for the SCPH-1001 BIOS, whose 1994 sub-CPU reports 0x94 0x09 0x19 0xC0. With
the correct value the flag stays clear, the spurious frame-890 ReadTOC is
gone, and the boot command stream matches the oracle exactly. Found via
self-stop trap + fntrace-all + gdb watchpoint on [0xDFFC] + capstone tracing
the flag setter and its version check.

Also adds a symmetric self-stop trap to psx-beetle's CD-command hook so the
oracle can be frozen at the same boot point for state diffs.

Honest status: this closes the boot-flow divergence but the game still wedges
downstream in its OWN CD-init retry (GetStat/Init loop from game pc 0x63A14);
that is a separate issue, documented in docs/kula-world-findings.md round 6.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 23:10:31 +00:00
Claude 536ad0f886 debug: PSX_FNTRACE_ALL boot trace; root mechanism of Kula bug 3 found
Adds PSX_FNTRACE_ALL=1 (records every dispatch from power-on) and documents
the full mechanistic chain of the wedge (docs/kula-world-findings.md round 5):

The frame-890 ReadTOC that breaks the game's CD reads is gated by kernel flag
[0xA000DFFC]: the CD-open function 0xBFC0D570 does 'beqz [0xDFFC], skip;
jal ReadTOC'. On ours [0xDFFC]=1 -> ReadTOC -> wedge; on the oracle it stays
0 -> reads continue. The flag is set to 1 by func_1FC1DF50 (a CD control
routine) called from shell code at ra=0x80036024. The shells run the same
verbatim bytes but diverge early (ours 0x35000-0x39000 issuing Test/ReadTOC,
oracle 0x30D00-0x33200 issuing Init/GetTN), so a branch input differs. Traced
via self-stop trap + fntrace-all + gdb watchpoint on [0xDFFC] + capstone.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 20:48:04 +00:00
Claude 698e100816 oracle: add guest PC to CD-command trace hook
Extends psxrecomp_cdcmd_cb_t (and the beetle_cdcmd_trace ring + cdrom_cmd_dump
JSON) with the guest PC at dispatch, read via PS_CPU::GetRegister(GSREG_PC).
Lets the boot-command-stream oracle-diff localize the first divergent
command's call site (kernel vs shell vs game) instead of only the command
byte — the next step in isolating the early-boot branch divergence behind
Kula World bug 3. Builds clean; behavior-inert when no listener is attached.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 20:24:50 +00:00
Claude c0ea49832a cdrom: fix 0x1F801800 status bits per oracle (spurious ADPBUSY, missing BUSYSTS)
Bit 2 is ADPBUSY (XA-ADPCM playback in progress) and must idle at 0; we
raised it unconditionally under a mistaken 'ADPCM empty' reading. Bit 7
(BUSYSTS: command written, first response not yet ready) was never raised;
now mapped to the queued-command window, mirroring beetle's
PendingCommandCounter/phase busy window. Verified against beetle cdc.cpp
Read(A==0), which sets bit 2 never and bit 7 during command latency.

Boot-diff verification shows this does NOT change our boot command stream
(the early-boot branch divergence vs the oracle persists — documented in
docs/kula-world-findings.md round 4, with the next sharp step: add guest
PC to the oracle's cdcmd hook and diff the first divergent call site).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 20:18:24 +00:00
Claude 9fee0b5dc0 cdrom: PSX_CD_TRAP_CMD self-stop trap; boot flows diverge from power-on
New debug tool: PSX_CD_TRAP_CMD=<byte> [PSX_CD_TRAP_NTH=<n>] SIGSTOPs the
process the instant the Nth matching CD command dispatches, so a debugger
attaches to the exact moment with zero run-speed cost (a gdb conditional
breakpoint on exec_command slows the emu ~100x and never reaches the wedge).

Used it to pin Kula bug 3 one level deeper: the pre-wedge ReadTOC at frame
~890 is issued by the BIOS BOOT FLOW itself (guest stack shows only
Main/StartKernel-chain ROM addresses, zero game frames) — the boot's
post-EXE-load disc re-check. And the full command-history dump shows our
boot diverges from the oracle's from the VERY FIRST command: the oracle's
kernel opens with Init/Init/GetTN/GetID and never issues Test or ReadTOC;
ours opens with the shell's Test[20]/GetID/ReadTOC check and re-checks
after the EXE load. Same SCPH1001 bytes, different path — an early-boot
branch divergence (recompiled-BIOS or hardware-sim input), documented in
docs/kula-world-findings.md for the first-divergence session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 20:05:05 +00:00
Claude 8861d3e963 docs: sharpen Kula bug 3 — spurious BIOS disc re-identify at frame 890
Full oracle command trace confirms it NEVER issues ReadTOC; ours does, from
the BIOS (Pause->ReadTOC->GetID) at frame 890, with clean pre-read stat
(0x22, no error/shell bits). Same BIOS bytes on both, so our runtime reaching
the disc-identify path is a state-driven BRANCH divergence, not a CD-sim
error: likely a kernel 'disc already identified' flag left unset, or a
recompiler-level game-logic divergence. Next: instruction-level
first-divergence hunt around frame 890.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 19:11:35 +00:00
Claude 99a4002ffb oracle: CD-command trace hook + live-diff localizes Kula World bug 3
Adds a CD-command trace to psx-beetle (beetle_cdcmd_trace_hook.patch:
callback in cdc.cpp's command dispatch, wired through beetle_libretro.cpp
into cdrom_cmd_dump / cdrom_cmd_reset on the oracle debug server). This is
the two-process oracle-diff the setup is built for, now working on Linux.

Live-diff result (docs/kula-world-findings.md, bug 3): the oracle's game
reads level data with a clean Setloc->SeekL->Setmode->ReadN->Pause per
sector (SeekL x18, ReadN x22, GetlocL x89). Our runtime's game, in the
wedge, issues NEITHER SeekL NOR ReadN — only GetStat/Init/Setloc/Pause,
an error/retry branch the oracle never enters.

Crucial: our runtime DID deliver 307 sectors during early boot (ReadN +
data delivery work). Reads only stop at frame ~890 — the BIOS mid-game
region revalidation (ReadTOC+GetID). The region fix made that PASS, but
the post-ReadTOC CD state still stops the game resuming reads. Prime
suspect: our ReadTOC (case 0x1E) is a bare stat+COMPLETE stub that does
not model drive repositioning / the track table the game reads to
recompute its seek. Documented for the next session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 19:05:47 +00:00
Claude 74b3c36779 docs: refine Kula World bug 3 — CD responses are correct, retry loop points at timing/event
The live trace shows the game's CD-init state machine (GetStat/Init then
Setloc/Pause, ~2.5s cadence) receives CORRECT responses at every step —
stat=0x02 (healthy idle drive), INT3 ACK + INT2 COMPLETE both fire and the
game's ISR processes them — yet it retries forever. So bug 3 is NOT a
wrong-response-value bug like bug 2; the game is waiting on a timing or
event-completion condition its init check never sees satisfied. Documents
two next-session paths: (1) rebuild psx-beetle with a CD-command hook and
live-diff the boot->demo sequence, (2) find the game's init-complete branch
condition and check our INT2-COMPLETE delivery timing against it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 18:50:53 +00:00
Claude 3c54c646f6 docs: Kula World bring-up findings (mirror + region fixed, demo-load wedge open)
Records the two fixed bugs (RAM mirror, GetID region) and characterizes
the open third wedge: the game's own CD driver loops Setloc(PVD)/Pause
without ever reading the directory, so the demo level never loads and
the screen stays black. The oracle loads it, so the divergence is
concrete and localized for the next oracle-diff session.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 18:28:27 +00:00
Claude 21e57a82cc docs+patch: psx-beetle oracle on Linux (pinned beetle-psx base + SIO trace hook)
beetle_libretro.cpp targets the C++ beetle-psx tree; upstream master has
been converted to plain C and no longer compiles against it. Documented
the working recipe: pin beetle-psx at 5759277b (the wtrace patch's base,
located via the patch's own blob hash), apply beetle_wtrace_hook.patch,
and apply the new beetle_sio_trace_hook.patch — FrontIO::SetSIOTraceCallback
(fires once per completed SIO byte exchange), a hook beetle_libretro.cpp
required that was missing from the committed patch set.

Verified on Linux/Xvfb: psx-beetle boots Kula World to demo gameplay and
serves ping/screenshot over the shared TCP debug protocol, alongside
kula-runtime — the two-process oracle setup from CLAUDE.md §16 now works
in this environment. Runtime-created per-game .mcr files are gitignored.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01BdjxoRB1f9VQsNzZayCgGS
2026-07-02 17:08:19 +00:00
Matthew Stanley 77a1f8bd6b fix(recompiler): overlay in-function jal targets become alias entries (B/C)
Completeness layer for the Tomba 2 FMV-freeze fix (the discovery/ownership side
of "containment is not ownership, ownership is not entry legality"). In overlay
discovery (main_psx.cpp --overlay), after walking the seed roots, every DIRECT
CALL (jal) target that lands strictly INSIDE an already-discovered function is
registered as an ALIAS ENTRY of that host via the existing alias machinery
(materialize_alias_groups). So when a host's discovered range absorbs a separate
routine (e.g. the crt0 `jal 0x50B08; break` falling through into the init-array
at 0x89788), the host's entry-switch now routes a dispatch of that PC to the
correct absorbed interior block and runs it NATIVE — instead of depending on the
PC having been captured, or falling closed to the interpreter.

Derived purely from static jal evidence (jal encodes a code target, so it never
mints code from data); does not depend on execution capture. Preferred over an
explicit range-clip because the absorbed code IS the right code — it just needed
a routable entry. The static two-phase FunctionDiscovery already enforces
hard_cap = next-entry, so only the overlay path needed this.

VERIFIED: regenerated cache (codegen cg4_42de29a8) emits `case 0x80089788u` in
func_800896E0; cps_probe for 0x89788 drops 1021 -> 0 (now main-chain native, not
the broken CPS find-by-range default); freeze stays gone, 60fps sustained. Pairs
with the fail-closed entry guard (bd60381), which remains the safety net for any
genuinely-unrouted interior PC.

Framework-wide: regression-test Tomba 1 / MMX6 / Ape before any master merge.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:23:15 -07:00
Matthew Stanley 2b5c32d564 docs(tomba2): FMV freeze FIXED + verified (fail-closed entry guard)
Record the implemented fix and verification at the top of the freeze writeup:
overlay CPS entry-switch fails closed on a foreign interior PC -> sanctioned
dirty-RAM interpreter (ABI v6). Verified native passes the freeze (frame
1823 -> 18001+, 60fps, i_mask=0x0D, reval_crc_miss=0). Notes the optional B/C
completeness layer (clip overlay ownership range in the capture-driven overlay
discovery path) and that the static two-phase discovery already enforces
hard_cap=next-entry.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:23:15 -07:00
Matthew Stanley ff714d1ee1 diag(overlay): confirm Tomba 2 FMV-freeze root cause (break range pollution)
Adds overlay_cps_probe (debug cmd) to record what the CPS interior-continuation
dispatch decides for a watched PC. Used it to CONFIRM the root cause:

func_80050B08 calls jal 0x89788 (init-array). 0x89788 isn't a registered entry,
so overlay_find_by_range(0x89788) runs -- 6 loaded candidates have a range
containing it -- and returns func_800896E0 (the crt0 bootstrap), whose range
wrongly spans 0x89788. 0x89788 is not a case in its entry-switch, so default
re-runs the bootstrap from the top: InitHeap/A0 spin, VBLANK-masked deadlock.

func_800896E0's range covers 0x89788 because function_discovery.cpp:493-499
treats BREAK as fall-through; the bootstrap's "jal 0x50B08; break" (break guards
the main-returned case) falls through at 0x89784 into the separate function at
0x89788, absorbing it. So the early break-no-op finding WAS the root -- damage is
at discovery/range time, not runtime break execution. Interp is immune (executes
0x89788 bytes directly).

Fix options + recommendation (clip function range at next entry / don't cross a
function boundary on break fall-through) documented in
docs/TOMBA2_NATIVE_FMV_FREEZE.md. Diagnostic only; no fix applied yet (needs
regression on Tomba1/MMX6/Ape). Workaround: overlay_native_block 0x80050B08.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:22:37 -07:00
Matthew Stanley 0acc6f2dbb feat(overlay): per-function native-disable for divergence bisection
Adds a phys-keyed blocklist in overlay_loader.c that forces a named overlay
function through the sanctioned dirty-RAM interpreter (it still RUNS — not
skip/stub/HLE), as if s_native_exec were off but for that one function only.
Exposed as the `overlay_native_block` debug command (add/clear/report). This is
the localization tool for native<->interp divergences (e.g. the Tomba 2 FMV
freeze): binary-search which compiled overlay function's native execution flips
the bug, without rebuilding or losing candidate visibility. Runtime/tool-only.

Used it to localize the Tomba 2 end-of-Whoopee-Camp FMV freeze to
func_80050B08's OWN native body (blocking it passes the freeze with callees
still native; all 33 jal targets + continuations verified clean). Narrowed to
the main-loop region with a strong interior-entry suspect at 0x80050CE8.
Findings in docs/TOMBA2_NATIVE_FMV_FREEZE.md.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:22:36 -07:00
Matthew Stanley e8ded2f306 docs(tomba2): root-cause the native-overlay FMV freeze (frame ~1823)
Whoopee-Camp-end freeze with the warm overlay cache is a DETERMINISTIC
codegen divergence, not a timing race -- proven via the sandboxed
native<->interp shadow-compare (overlay_diff/shadow_dump): both engines
run from identical CPU+RAM state, no IRQ/timing, and native still diverges.

First divergence (overlay_shadow_detail) is fn 0x800896E0: native t1=0xE4
(=0x39<<2, the A0 dispatcher`s sll t1,t1,2) / t0=0xBFC01E24 (A0_table[0x39],
InitHeap) vs interp t1=0x39 / t0=0x80000000. Native wrongly routes through
the A0:0x39 dispatch path interp doesn`t take -> re-inits every spin-loop
iteration -> endless A0-vector dispatch with VBLANK masked -> deadlock.

Also documents a separate latent CLASS bug: the recompiler does not model
the R3000A load-delay slot (strict_translator.cpp:830) while the dirty
interp does; 50 load-delay hazards found in this game`s overlays (0
control-flow) -- not this freeze, but a real divergence class to class-fix.

Same ~frame-1828 hang a prior session believed fixed by 47f3f15; it was
masked by interp fallback (audit-failed region), the clean warm cache
re-exposes it. Exact diverging instruction + recompiler fix still pending.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:21:50 -07:00
Matthew Stanley 8e60a419a9 docs(overlay-cache v2): interior-entry fragments IMPLEMENTED + runtime-stable
P1+P2 done (c5efd70). Decoupled post-loop fragment pass; validated by probe
(clean mid-entry island for 0x80083094), full run (34/47 executed orphans at
0x38000 incl 0x80083094 as isolated shards, despite that region's normal compile
audit-failing), and a fresh runtime run (20 DLLs / 264 native / crc_miss=0 — no
divergence). Open: clean good-variant warm for end-to-end FMV/title speed (messy
accumulated captures audit-fail the region, losing hot NORMAL funcs); optionally
extend isolated fragments to executed normal funcs; P3 host recovery optional.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:21:50 -07:00
Matthew Stanley a8a0b0da88 docs(overlay-cache v2): ChatGPT-validated plan — interior-entry "island" fragment
Architecture review verdict for the FMV-driver coverage gap: DON'T recover the
host. The regression was mixing speculative recovered-host roots into the trusted
all-roots region DLL (shared failure domain). Fix = three isolated build classes
(REGION_TRUSTED / RECOVERED_HOST_SPECULATIVE / INTERIOR_ENTRY_FRAGMENT); for the
FMV, compile an interior-entry ISLAND that enters at the executed PC 0x80083094
over a worklist-discovered CFG (not linear-to-boundary), its own DLL, keyed by
covered ranges + covered-byte hash. Mid-function start is safe iff codegen makes
no function-entry assumptions (no synthesized frame; out-of-island branches are
dispatcher exits, not audit failures). Phased plan recorded.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:21:49 -07:00
Matthew Stanley 17caf25dd5 docs(overlay-cache v2): host-recovery attempt reverted — needs compile isolation
Attempted orphan-interior host recovery (walk back to the host start, root it so
the FMV driver 0x80083094 compiles). It REGRESSES: adding the recovered host as a
root to the region's shared seed list (even gated to executed/resident orphans)
makes the recompiler emit C that fails the strict generated-C audit, and because
all roots in a region compile into ONE DLL, one failure skips the whole region ->
the previously-good 0x38000 region stopped compiling. Reverted to clean
checkpoint. Robust path = isolate each recovered host (+ its interior aliases)
into its OWN compile unit / DLL so a bad recovery fails alone (loader already
content-matches per function across DLLs), or audit-validate each host before
including it. Dedicated effort; recorded for next session.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:21:49 -07:00
Matthew Stanley e6bcdb57a3 docs(overlay-cache v2): session outcome — native stabilized; FMV-driver discovery wall
Records the three fixes that made native overlay execution stable through boot
(region_start load fix bb62b20, alias-rollup 47f3f15, timer write8 6004ced) and
the remaining FMV-speed blocker: the hot FMV driver 0x80083094 is a
DISPATCH_INTERIOR orphan (no discovered host -> not compilable -> interp-only).
Plus the next-step plan: recover the host by walking backward from the interior
PC to a callable-start signature, root it, and let the interior become an
alias-entry into it.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:21:49 -07:00
Matthew Stanley aab66e6699 docs(overlay-cache v2): record region_start fix + alias-keying divergence finding
The slowness root cause (region_start walkback overshoot) is fixed (bb62b20):
native funcs 14 -> 790. Remaining blocker = a native-vs-interp divergence at
0x80051FA4 that hangs the boot when native runs.

0x80051FA4 is a CPS CONTINUATION point inside host 0x80051F80, and the recompiler
emits BOTH a full host func_80051F80 AND a separate epilogue-only alias body
func_80051FA4 -- with the SAME .ranges range (R 80051F80 34) and SAME code_crc
(5CE0B05E). So two candidates cover identical bytes and a continuation to 0x51FA4
can resolve to the epilogue-only alias instead of the full host. Hypothesis:
aliases/interior entries should roll up to (dedup into) the parent host shard.
Recorded for the recompiler fix + ChatGPT architecture review.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:16:18 -07:00
Matthew Stanley f89dadc22e docs: overlay cache v2 rearchitecture spec
Audit of the current overlay-cache subsystem (loader, capture, autocompile,
compile tool) plus the design target. Key finding: the correctness model is
sound (dispatch re-validates live RAM vs per-function code_crc every call, so
no stale execution) -- the problems are efficiency: whole-region-CRC build
keying causes redundant DLLs, a per-dispatch full CRC32 taxes warm code, and
capture/dlopen/JIT run on the main thread. Phased plan P1-P5.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:16:17 -07:00
Matthew Stanley 1a65b5c92a docs: CORRECTION — CD streaming is NOT the immediate blocker (directory traversal works)
The fixed CD command trace shows single-sector data reads (PVD16->root22->subdir373) all work; the
game stalls AFTER the directory read at the FILE-LOAD DECISION, issuing no further CD command. The
CD-audio/XA streaming is still missing (oracle DMA3-streams, recomp doesn't) but the game never
reaches it, so it's downstream, not the gate. Corrected blocker: a state the game tests after the
dir read diverges (no SetLoc+ReadN for the next file). Supersedes the premature 'CD streaming root
cause' conclusion.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:16:15 -07:00