Emit a shared <stem>_decls.h (runtime extern prologue, static-inline
unaligned helpers, all func + alias-body forward decls) and distribute
function bodies across N ~40k-line <stem>_full_NN.c shards instead of one
monolithic <stem>_full.c. Overlapping-alias host bodies become externally
linked so shard bucketing has no atomicity constraint. runtime.cmake's
GAME_GENERATED_FULL_C is now multi-value (single path still works, so
other games are unaffected until they regen + glob).
Tomba: SCUS_942.36_full.c 41MB single TU -> 36 shards; compile (-O3)
4m35s -> 51s (-j16, ~5.4x). Generated function bytes verified byte-identical
(6245/6245 functions).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the Ape Escape literals in the recomp-ui launcher block with
config-driven values so every PSX title can consume the shared seam:
- gi.name/gi.region <- game.toml [game] name + new optional region key,
falling back to serial-prefix derivation (SCUS/SLUS/LSP -> USA,
SCES/SLES -> Europe, SCPS/SLPS/SLPM -> Japan; unknown -> no badge)
- gi.num_players <- new optional [game] players key (default 1)
- gi.widescreen_supported <- ws_offered (same source as the RmlUi path)
- Localization parity: language_labels/num_languages from
[runtime].languages, language_index matched from the resolved language,
and the chosen language written back to settings on Launch (mirrors
launcher.cpp's bounds-checked write-back)
config_loader: GameConfig gains region (string) + players (int, default 1),
parsed from [game]; existing configs without the keys are unchanged
(recompiler_patch_test 11/11, defaults probe verified against Ape's toml).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The tier mis-compiled and has been hard-gated off since 2026-06-25; overlay
gaps fall to gcc/tcc shards and the dirty-RAM interpreter, unchanged.
Re-do on master of the removal validated on the stale branch (6da8c02),
reconciled with everything master grew since the fork:
- Delete overlay_sljit.{c,h}, overlay_compile_worker.{c,h}, lib/sljit,
runtime/tests/sljit_*.c, and the runtime.cmake sources/include-dirs.
- Strip the sljit provider/backend from code_provider.{c,h},
overlay_backend.{c,h} ("sljit" in overlay_backend/PSX_OVERLAY_BACKEND now
degrades to auto so stale configs still boot), main.cpp, debug_server.c
(sljit_status/sljit_async/sljit_try TCP commands), overlay_loader.{c,h}
(live policy, JIT-on-miss, persisted-shard cache, async worker glue,
psx_sljit_call/cop2/memx host helpers — reachable only via the removed
cpu->sljit_helpers table), and cpu_state.h (SLJIT_HLP_* enum + the
sljit_helpers[] member; CPUState layout change reshards via the cg tag).
- Master-only entanglement resolved before deletion: 28165d6's Einhander WS
player-X bound emit existed in three paths (gcc code_generator, sljit,
dirty_ram_interp). The gcc shard link contract now forwards
psx_ws_player_x_bound (ABI v13, previous commit), so deleting the sljit
emit path loses no widescreen behavior; ffc6688's game-dispatch block
inside the dead psx_sljit_call helper goes with it.
- Reword comments that described sljit as a live backend (gpu.{c,h},
ws_*_detect.h, cosim_state.c, dirty_ram_interp.c, autocompile.h,
boot_state.h, config_loader.h, compile_overlays.py, and the CPS marker
comments the recompiler emits).
recompiler psxrecomp-game builds clean; game runtimes rebuilt + boot-verified
separately.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Runtime config parsing uses the physical alias key but does not link the recompiler patch implementation. Keep the dependency-free key in the C++17-compatible header and exercise it in the header smoke test.
Avoid exposing std::span through config_loader.h, which is consumed by C++17 game runtimes. Add a dedicated C++17 header compile test alongside the guarded-patch suite.
- psx_cyc.h / psx_cycles.c: _BitScanForward/_BitScanReverse shims for
__builtin_ctz/__builtin_clz under _MSC_VER
- full_function_emitter.cpp / main_psx.cpp: emit a portable .CRT$XCU
init-pointer instead of GCC-only __attribute__((constructor)) in
generated dispatch/game code
- main.cpp: declare cross-language globals extern "C" so MSVC links
them against their C definitions (no-op for GCC/Clang)
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Triage of Tomba2Recomp GH issue #1 exposed three ways a release exe
behaves differently on the build machine than on every user machine:
- DEFAULT_BIOS_PATH baked an ABSOLUTE build-tree path into the binary,
so promoted/release exes silently load the builder's BIOS wherever
that path exists and the clean-install picker flow is never
exercised where releases are validated. runtime.cmake now refuses
absolute values (loud configure warning, converts to the relative
default); dev checkouts keep prompt-free boots via a new
resolve_bios_path rung that walks exe-dir ancestors for
psxrecomp-v4/<relative>, a layout only dev trees have.
- A settings.toml with a TOML syntax error was silently discarded
wholesale (user's renderer choice, BIOS/disc paths - everything),
and the next launcher save then overwrote the file with defaults.
load_user_settings now reports parse_error; the runtime preserves
the broken file as settings.toml.bad and warns via message box +
stderr.
- package_release.ps1 gains guards: fail packaging if the staged exe
contains a drive-letter absolute SCPH1001.BIN path, if the stage
carries BIOS/disc/save/sidecar files (SCPH*.BIN, *.cue, *.iso,
*.mcd, bios.cfg, disc.cfg, settings.toml, keybinds.ini,
overlay_captures.json), or if saves/ is non-empty.
Verified: rebuilt Tomba2 against this tree - configure warning fires
on the absolute arg, strings scan of the exe finds no drive-letter
BIOS path, and a boot with no [bios] setting and no bios.cfg resolves
the BIOS through the psxrecomp-v4 dev rung.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Closes the latent Rule-18 soundness hole: kernel-window static dispatch
hits (RAM [0x500,0x8500), the BIOS's boot-time ROM copy) now require the
live RAM bytes of the function's full reachable extent to byte-match the
ROM source (lazy verify, cached, precisely invalidated on writes); patched
bodies divert to the faithful interpreter. Emitter ships
psx_bios_kernel_bodies[] (499 entries); runtime guard in memory.c; TCP
kernel_bless observability; PSX_KERNEL_BLESS=0 A/B switch.
USER-VALIDATED on Tomba (SCUS-94236): multiple play runs, LLE + HLE boots,
pads/menus/events, title 1.00x / village 0.92x parity; 208-214/219 kernel
functions verified clean, the 5 mismatches (runtime-patched pad-driver
family) faithfully interpreted as before.
NOTE: rolls the codegen hash — regen BIOS+game generated C and recompile
overlay shards with a same-tree recompiler on each title's next fw bump
(the overlay loader fail-closes loudly on tag mismatch).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The generated dispatcher ran kernel-window table hits (RAM [0x500,0x8500),
the BIOS's boot-time copy of ROM 0x1FC10000+) natively with NO check that
the live RAM bytes still match the ROM image the functions were compiled
from — unsound for runtime-patched kernel code (the BIOS installs pad/SIO
handlers INSIDE compiled bodies; live diff: 15 clusters, 348 bytes). It
never misfired only because entry points happened to avoid patched keys.
A static kernel hit now requires psx_kernel_bless_dispatchable(): the
emitter emits psx_bios_kernel_bodies[] (key -> full RAM extent of the code
reachable from it; continuations carry their parent's extent, 499 entries),
and the runtime lazily byte-compares each entry's body against the ROM
source, caches the verdict, and invalidates precisely on writes into the
body (store chokepoint, DMA/EXE range marks, savestate restore). Clean ->
guarded native; patched/unverifiable -> the faithful dirty-RAM interpreter,
exactly as Rule 18 prescribes. This implements the runtime half of the
relocation-manifest contract (docs/RELOCATION_MANIFEST_FORMAT.md).
Measured on Tomba (SCUS-94236): 208-214 of 219 verified functions clean
(the 5 mismatches are the genuinely patched pad-driver family, faithfully
interpreted); millions of guarded native hits per boot; title 1.00x,
village 0.92x (parity — kernel keys were already native, now they are
PROVEN native); LLE and HLE boots + gameplay input verified. Kernel-window
interp measures 1.3% of interp burden (dirty_ram_stats), so this is a
soundness fix, not a speedup; the dominant interp cost is overlay-region
coverage (cg-tag hygiene), tracked separately.
Observability: TCP kernel_bless (entries/clean/mismatch/native_hits/
verifies/invalidations), always-on. PSX_KERNEL_BLESS=0 reverts to
unguarded-table behavior for A/B.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
X4 generates ~60K dispatch entries; GCC lowers the sparse switch to a
multi-megabyte linear compare chain at -O0, making Debug builds unusably
slow. Materialize one sorted table shared by psx_dispatch_game_compiled
and psx_game_is_function_entry and look up via O(log N) binary search,
which is optimizer-independent and avoids emitting the case set twice.
CPS resume PCs ride the same records. Behavior-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Generalizes the Capcom 2D widescreen machinery for the MMX4 engine revision:
- [widescreen.bg2d] count_site accepts the inline slti/sltiu loop compare
(X4/X5 style) alongside MMX6's li; startx_site accepts subu rd,zero,rt
(X4's -(scrollX & 15)); inline-count sites route through the generic
psx_ws_bg2d_* helpers that keep the emitted ring mask and skip MMX6's
title-specific host-side refill state.
- [widescreen.cull] a1_sites now also accept a move rd,a1 (addu encoding):
classifiers that copy the caller margin with no delay-slot nop widen the
copied value instead.
- [widescreen] nw_hud_source_lo/hi: optional GP0 command-source range filter
for nw_hud_corners, so a pure-2D game re-anchors only its dedicated HUD
packet arena and never world sprites. 0/0 preserves legacy behavior.
ws_nw_hud_shift_vertices re-anchors polygon/line prims as one rigid
composite; mono rect/dot/8x8 sites shift too.
All helpers identity at 4:3; unset config keys are byte-identical.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
GTE honors per-instruction lm (bit 10) and sf (bit 19) in the depth-cue and
lighting family (INTPL/DPCS/DPCT/DPCL/NCDS/NCDT/CDP/...) with the faithful
hardware formula and always-on GTE observability rings (gte_ring_dump,
gte_intpl_dump, gte_frame_stats, gte_latch_dump). Fixes Crash Bash
menu-character strobe/flicker and in-game flattening (signed INTPL vertex
tweens were clamped to zero by the hardcoded lm=1).
Also: dispatch no longer shadows runtime-relocated game code in the BIOS
shell window (native-ok byte compare routes matching text to the game path);
stale GPUSTAT-poll VBlank over-delivery fix + present pacing tooling.
Cross-title validation (GTE_LM_FIX.md): Crash Bash (fix target), Ape Escape,
Tomba 2, Mega Man X6 - all rebuilt branch-flavor (regen included) and verified.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Boots Crash Bash (SCUS-94570) past its frame-~144 hang into the attract loop.
Root cause: the Class-A shell-window fix (normalize() maps RAM 0x30000-0x5AFFF ->
shell ROM) collides with games whose code lives in that window, and Crash Bash
RELOCATES a code page onto 0x30000 at runtime. Three interacting gaps sent that
real game code to dead shell ROM -> unknown-dispatch abort:
1. fntrace.c — game_started never latched: it only tripped on a dispatch to
entry_pc (0x2E7B0), but Crash Bash reaches entry via compiled internal flow,
so the latch never armed and the shell-window shadow stayed active. Now also
latches on the first dispatch to native-ok game text (safe pre-load: that RAM
still holds shell bytes that differ from the game image).
2. dirty_ram_interp.c — THE decisive fix. When a game-text address diverged from
the static EXE image (native_ok=0, i.e. runtime-relocated/overlaid code) but
was not "dirty", dirty_ram_dispatch_inner bailed (return 0) and the caller
shadowed it to shell ROM. Now a diverged game-text address is marked a clean
game-text miss so the dirty interpreter executes the live RAM bytes.
3. full_function_emitter.cpp (BIOS dispatch driver) — also route native-ok
game-text shell-window addresses to the game path regardless of the latch.
Likely redundant now that (1) latches correctly; flagged to slim during the
final cross-game validation.
NOT merged/validated against other titles yet (MMX6/Tomba2) — per plan that
happens after Crash is fully running. Verified: Crash Bash runs headless to
6000+ frames and windowed with no dispatch abort; the vblank fix delivers 60/s.
Known remaining issue: character flicker at the game-type menu (Bart's original
report) — under investigation, unrelated to this dispatch fix.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The experiment flag is now the default. A "function" that is >=50%
untranslatable (TODO-opcode) words is a data region discovery mistook for
code (primary-opcode-only validity check); emit it as the compact,
fail-closed psx_unknown_dispatch data stub instead of ~hundreds of silent
/* TODO */ no-op lines. Strictly MORE faithful than the baseline (loud
abort-if-dispatched vs silent no-op), and slashes emission: Tomba
259->42MB, exe 59->24MB, -O2 ~25min->~2.5min.
Smoke-validated by full launch of Tomba (SCUS-94236), Tomba2 (SCUS-94454)
and Ape Escape (SCUS-94423) regenerated with the flag on — all boot and
play, no unknown-dispatch fatal (=> no reachable function misclassified).
Set PSX_EMIT_PROD=0 to restore the full-scan (oracle) emission. Proper
productionization moves the judgment upstream into function_analysis.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Discovery's validity check (function_analysis valid_primary_p3) only checks the
PRIMARY opcode, so data words with a valid primary opcode but a garbage
sub-field (e.g. fake-REGIMM 0x01 with rt=0x0E) slip through as "code". The
generator then can't translate the bogus (opcode, subfield) combo and emits a
silent /* TODO: opcode */ no-op. Nothing is currently emitted as is_data_section
for the games, so these ride as code — Tomba 1's 0x29000..0x88000 data region
becomes ~348K TODO lines / 259MB / ~25min -O2 compile.
This env-flagged (PSX_EMIT_PROD) downstream pass re-emits any function whose
body is >=50% untranslatable (TODO) words as the existing compact, fail-closed
psx_unknown_dispatch data stub — using the generator's OWN judgment of what it
can translate rather than duplicating the implemented-instruction set. Default
OFF => byte-identical to the full-scan baseline (the complete/oracle emission
stays intact).
Measured on Tomba 1 (SCUS-94454), validated by full playthrough (plays great,
no unknown-dispatch fatal => no reachable function misclassified):
generated full.c 259.1 MB -> 39.7 MB (348K TODO -> 1026)
game full.c -O2 ~25 min -> 156 s (~10x)
runtime exe 59.3 MB -> 26.7 MB
EXPERIMENT ONLY. Proper productionization moves this judgment UPSTREAM into
discovery (sub-field-aware validity in function_analysis), fixing it for every
title at discovery time without a post-hoc generate-then-discard pass, and turns
the env var into a real config option. Also apply/measure on Tsumu (261MB) and
MMX4 (215MB).
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
For a title whose controller type is fixed and auto-bound (e.g. Ape Escape =
DualShock analog), exposing a device/mode picker is pointless. New per-game
[controller] lock_device flag hides the Player 1/2 cards in the launcher
entirely (distinct from lock_mode, which only hides the pad-mode segment but
keeps the device dropdown). Plumbed config_loader -> RuntimeConfig ->
GameInfo.lock_device -> launcher model `device_locked` -> rml
`data-if="device_locked == false"` on the players cards-row. Default false, so
every other game is unchanged. The runtime still auto-binds whatever pad/keyboard
is present and presents the game's declared pad type.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Adds full 16:9/21:9 native-wide support for fully-3D titles (Ape Escape,
SCUS-94423) as a generic, opt-in, 4:3-byte-identical framework capability, plus
a native-wide GL compositor perf fix and a cutscene present-mode flicker fix.
Widescreen (generic, per-game opt-in via [widescreen*]):
- Shared screen-extent cull detector (runtime/include/ws_cull_detect.h) used by
all three backends (recompiler, dirty-RAM interp, sljit). Per-game signature
immediates ([widescreen.cull] screen_w_imms/screen_h_imms) — Ape's funnel
tests 0x181/0xF1 on its 368px display, not Tomba's 0x140/0xE0.
- Three funnel idioms widened: unsigned per-vertex sltiu (psx_ws_cull_sltiu),
signed min/max slti right edge + bare-bltz left edge (psx_ws_cull_slti/bltz,
new helpers + SLJIT_HLP appends), and billboard center+/-halfwidth. Explicit
[widescreen.cull] slti_sites for X-only funnels the auto-detector can't
qualify.
- gte_game_mode: GTE-activity gameplay detector for 3D titles with no sprite-tag
helper (gte.cpp notes every RTPS/RTPT). STICKY (>=3 verts, 45-frame hysteresis)
so sparse cutscene frames never flip to a 4:3 pillarbox = fixes intro flicker.
- nw_hud_corners: push outer-third screen-space HUD sprites to the true wide
corners in native-wide (else inset by the reveal).
- nw_backdrop: stretch a full-frame axis-aligned 2D backdrop quad to fill the
wide frame (for titles with a flat sky plane).
- Pattern-scanned interp/sljit widen hooks now gated per-game (auto_screen_x /
auto_backdrop): fixes an ungated auto_backdrop false-positive that rewrote a
live GPR = the ~24s native-wide crash on Ape.
Native-wide GL perf (fixes 16:9 frame drops):
- The wide surface centre equals the canonical 4:3 framebuffer, so wide_blit_center
copies canonical->wide-centre at present (both present paths) and the per-prim
mirror SKIPS prims fully inside the 4:3 frame (mirror_x/geo/batch_center_only).
Only reveal-margin prims re-rasterize. Peak frame time 38.8ms -> 17.5ms, no
vsync misses. Toggle gl_wide_fast; 4:3 untouched (all gated on g_wide_cur).
Runtime-only except the recompiler cull emit (regen needed for cull widening).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A title whose widescreen is unported/unvalidated (MMX4 at bring-up) had no
way to hide the launcher's EXPERIMENTAL Widescreen toggle — ws_eligible was
hardcoded true. Add game.toml [widescreen] offer (default true, runtime-only,
no regen):
- offer=false hides the launcher's Widescreen row and resets a stale
persisted 16:9 aspect back to 4:3 at launcher-model init;
- the runtime also clamps the display aspect to 4:3 after all config
sources (same completeness treatment as the lock_mode clamp), covering
launcher-less builds and hand-edited settings.toml;
- refresh_labels() no longer stomps ws_eligible every refresh — it is set
once from GameInfo.ws_offered at model init.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>