Commit Graph

181 Commits

Author SHA1 Message Date
Matthew Stanley 647b81752f runtime: tri-state fullscreen (windowed/borderless/exclusive)
The shared recomp-ui launcher already exposes fullscreen as a universal
tri-state (RecompLauncherCSettings.fullscreen: 0 off / 1 borderless /
2 exclusive), but the PSX runtime collapsed it to a bool everywhere and
only ever used SDL_WINDOW_FULLSCREEN_DESKTOP. Widen the runtime to carry
the full tri-state end to end, matching the SNES/Genesis engines:

- config_loader: UserSettings::fullscreen is now int (0/1/2) instead of
  bool. settings.toml persists it as a bare int; loading still accepts
  the old bool form (true -> 1) for back-compat with settings.toml files
  written before this change, then clamps to [0,2].
- main.cpp: removed every `!= 0` / `? 1 : 0` collapse between
  UserSettings, the seed struct, and RecompLauncherCSettings, so 0/1/2
  round-trips unchanged through the launcher and back into settings.toml.
- main.cpp: win_flags now maps 2 -> SDL_WINDOW_FULLSCREEN (exclusive
  mode change) and 1 -> SDL_WINDOW_FULLSCREEN_DESKTOP (borderless), via
  a small psx_fullscreen_flag_for_mode() helper shared with the hotkey.
- main.cpp: the Alt+Enter / Cmd+Ctrl+F hotkey now detects "currently
  fullscreen" by testing the SDL_WINDOW_FULLSCREEN bit (set in both
  SDL_WINDOW_FULLSCREEN and SDL_WINDOW_FULLSCREEN_DESKTOP) instead of
  only the DESKTOP flag, and toggles between windowed and the
  CONFIGURED tri-state mode rather than hardcoding DESKTOP.
- launcher.cpp/launcher.rml (the legacy RmlUi launcher, still built
  whenever PSX_LAUNCHER is on): fullscreen is now a 3-way cycle button
  (Off/Borderless/Exclusive) following the same cycle_*/*_label pattern
  as the existing CRT/texture-filter controls, instead of an On/Off
  toggle.

Validated with g++ -fsyntax-only (mingw64) against config_loader.cpp
standalone, and main.cpp with no launcher flags, PSX_LAUNCHER only,
RECOMP_LAUNCHER only (against the real recomp-ui RecompLauncherCSettings
header), and both together - all clean. launcher.cpp (RmlUi) was
reviewed by exact pattern-match against the file's existing cycle_crt/
crt_label code (lib/RmlUi isn't checked out in this worktree, so it
could not be compiled).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 22:34:58 -07:00
Matthew Stanley e38daa3415 fix: mirror widescreen range sites in interpreter
Register configured range_sites with the dirty-RAM interpreter so overlay-resident classifiers use the same two-margin widening already emitted by native codegen.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-20 19:39:36 -07:00
Matthew Stanley d742a62391 fix: default generated games to OpenGL renderer 2026-07-20 17:34:12 -07:00
Matthew Stanley 8f78ddbd89 integrate interpreter hotpath and AOT sharding 2026-07-20 15:58:43 -07:00
Matthew Stanley fa626bdb4c feat: add aspect-scaled depth cull sites
Add opt-in slti/sltiu far-bound scaling with matched native and interpreter semantics, correct immediate sign extension, and an overlay ABI forwarder for live aspect state.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-20 12:14:18 -07:00
Matthew Stanley 288926111f feat: add masked-u16 widescreen cull sites
Add a shared native/interpreter helper for sltiu classifiers operating on ANDI-masked 16-bit screen X values, preserving MIPS immediate sign extension and overlay linkage.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-20 12:11:58 -07:00
Matthew Stanley 5ded80e61e Merge master (AOT overlay generation) into perf/interpreter-hotpath
Integration branch: the interpreter-perf series (interpreter fallback +
additive shard autocompile + always-on production observability + the
fail-closed alias-body emitter fix) rebased-by-merge onto master's AOT
overlay generation (play-free extraction, bounded indirect discovery,
AOT recall, shadow-diff redesign).

Semantic-union highlights:
- gte.cpp: HEAD's direct-field marshaling fast paths kept; master's
  s_gte_replay_sandbox guards grafted onto every affected site.
- psx_icache.c: HEAD's global tag array + fetch_miss split kept;
  master's transactional shadow record/replay retargeted onto it.
- overlay_capture.c: HEAD's commit/retry/wait-pending pipeline kept;
  master's durable history + autocapture APIs wired into it
  (persist_history_snapshot fed from the immutable .d contribution).
- overlay_loader.c: master's interp-record/native-replay shadow-diff
  redesign kept as base with HEAD's scheduler-bail hardening grafted
  (new SHDIFF_SCHED divergence kind); strict manifest v2 parsing keeps
  master's P pair-id lines; always-on native-call ring preserved.
- main.cpp shutdown calls BOTH overlay_autocapture_shutdown() and
  overlay_capture_wait_pending() (the latter drains the preserve-write
  FIFO the former does not).
- recompiler: master's generate_alias_group refactor retains the
  fail-closed alias-body re-entry defaults from c85f1d1.

Validated on this tree: recompiler + runtime + tests build; all six
runtime unit tests PASS; interpreter perf guards PASS; recompiler
patch/structural tests PASS (l1 decoder oracle noise = 376 pre-existing
on both parents).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-20 00:43:41 -07:00
Matthew Stanley bdeb106e5c feat: add negsub widescreen cull sites
Add an opt-in horizontal low-edge transform for subu rd,zero,rt classifiers, with matching native and dirty-RAM interpreter behavior and guarded overlay variants.

Co-authored-by: Opok <novelasevilliousesp@gmail.com>
2026-07-19 23:25:03 -07:00
Matthew Stanley eb3474c8ac observability: PSX_DEBUG_SERVER=1 opts the TCP listener into production builds
The debug server is compiled into every build (recorders no-op under
PSX_NO_DEBUG_TOOLS; emu-thread pump sites early-out on a null check), so
starting the listener observes the REAL production hot path instead of the
instrumented build whose shifted IRQ-delivery timing diverges from it.
Loud init banners (opt-in, listening, socket/bind failure). Also adds
PSX_EVENT_STEP_CONSERVATIVE=1 env opt-in so the conservative device-step
diagnostic is reachable without TCP. Releases set neither: no port opens.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 19:42:19 -07:00
Matthew Stanley e02b7b3455 Close publication lifecycle: shutdown join, prepare retry, loud caps, canonical paths
Audit fixes for the async shard-publication pipeline (45a2dda):

- autocompile_shutdown(): stops the pipeline, kills the compiler tree via a
  kill-on-close job object (assigned while the child is SUSPENDED so cmd.exe
  cannot spawn grandchildren outside it), and JOINS the watcher + preparer —
  never abandoning a thread inside the Windows loader lock at ExitProcess.
  Wired first into shutdown_runtime(). Crash exits reap the tree via the job.
- Failed off-thread prepares retry (3 attempts, 100 ms) on the worker before
  the artifact silently reverts to a synchronous emu-thread first-map; retry
  and giveup counters exposed in autocompile_status_json.
- Capacity caps fail closed and LOUD: cand_register shouts once at CAND_CAP;
  load_one_dll_prepared refuses at MAX_LOADED_DLLS before registering, so
  dll_already_loaded can never lose track and double-register on rescan.
- Publication confinement is filesystem-resolved: published_path_valid
  canonicalizes the cache root and the pipe path (GetFinalPathNameByHandle /
  realpath), prefix-checks canonical forms, and the worker maps the canonical
  string while holding a no-delete-share guard handle (TOCTOU). `..`/junction
  spellings that passed the lexical check are now rejected — proven by the
  new overlay_path_canon_test boundary test.
- test_autocompile_publication gains a shutdown-mid-run scenario asserting
  prepared == committed + discarded with nothing queued; perf guards updated
  to require validate -> guard-handle -> canonical first-map ordering.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-19 16:19:57 -07:00
Matthew Stanley 05f6ef3e02 Optimize GTE fallback and isolate interpreter perf runs 2026-07-19 14:23:36 -07:00
Matthew Stanley 7376e6b042 Harden additive overlay shards and speed interpreter fetch 2026-07-19 12:11:56 -07:00
Matthew Stanley 3949854981 Optimize interpreter fallback and additive overlay autocompile 2026-07-18 08:32:42 -07:00
Matthew Stanley a544809edf aot: persist overlay capture history atomically 2026-07-17 23:56:58 -07:00
NyperYuhgard adba58b6cf REINSERT CD PANIC BUTTON 2026-07-17 03:54:27 -05:00
Matthew Stanley d2006e02a3 recomp-ui launcher: generalize the RECOMP_LAUNCHER seam for all titles
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>
2026-07-16 19:27:22 -07:00
Matthew Stanley a18f911f8b apeescape/recomp-ui: real disc-verify + memcard-inspect host callbacks (identify_disc/memcard_summary_path); seed memcard slots + enable flags 2026-07-16 16:06:08 -07:00
Matthew Stanley edb003a4cc recomp-ui launcher: wire deep PSX settings surface via launcher_profile
Replace manual gi identity/caps setup with launcher_profile_apply("psx",
&gi), which bundles theme/platform/rom_noun/pad-mode caps + all has_*
deep-settings flags as one unit. Ape Escape now overrides only what the
profile can't know (name, region, pad-mode lock state, aspect mask,
disc-verify off).

Map PSXRecompV4::UserSettings <-> RecompLauncherCSettings both ways for
the deep fields the profile now exposes: window_width, renderer,
supersampling, antialiasing, texture_filter, screen_kind,
frame_interpolation(+fps), spu_hq, auto_skip_fmv, turbo_loads, and
bios_path (string, snprintf-guarded). Fix the pre-existing
texture_filter write-back, which read the legacy ls.linear_filter bool
even though has_texture_filter=1 makes the launcher edit ls.texture_filter
directly — it now reads the right field.

Verified via build-recompui + LNG_SCRIPT screenshots: Settings now shows
the full PSX surface (Window size/Renderer/Supersampling/Aspect/Texture
filtering/Antialiasing/Screen model/Frame interpolation/Skip FMVs/Turbo
loads/Fullscreen, SPU high-quality/Analog deadzone, and a SYSTEM/BIOS
row with Browse), not the old pad-mode+aspect-only surface.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:37:31 -07:00
Matthew Stanley 53fee8fbd6 recomp-ui launcher: wire pad-mode + aspect-ratio capabilities
Extends the RECOMP_LAUNCHER block in runtime/src/main.cpp to pass the
new RecompLauncherCSettings.pad_mode[2]/aspect_index and
RecompLauncherCGameInfo.pad_mode_supported/pad_mode_selectable/
allow_hybrid/locked_pad_mode/lock_device/aspect_mask fields, sourced
from the same GameConfig-derived locals (ctrl_lock_mode,
ctrl_allow_hybrid, ctrl_lock_device, p1_mode, ws_offered,
ws_ultrawide_offered) the existing RmlUi launcher path already uses.

- ls.pad_mode[0/1] seeded from seed.p1_mode/p2_mode; ls.aspect_index
  derived from seed.aspect_num/aspect_den (4:3/16:9/21:9).
- gi.pad_mode_supported = 1 (PSX always has pad modes);
  gi.pad_mode_selectable = !ctrl_lock_mode; gi.allow_hybrid =
  ctrl_allow_hybrid; gi.locked_pad_mode = p1_mode; gi.lock_device =
  ctrl_lock_device; gi.aspect_mask = 0x1 | (ws_offered<<1) |
  (ws_ultrawide_offered<<2).
- On LAUNCH, ls.pad_mode[]/aspect_index are copied back into
  seed.p1_mode/p2_mode/aspect_num/aspect_den so the launcher's choice
  persists to settings.toml exactly like the RmlUi path.

Verified against the real Ape Escape game.toml: aspect_mask=0x7
(4:3+16:9+21:9, offer_ultrawide=true) and pad_mode=[1,1] (analog,
matching default_mode="analog") both flow through correctly. The pad-
mode selector itself renders hidden/locked to Analog in the built
launcher, because Ape Escape's [controller] block sets lock_mode =
true (intentional: it's a dual-analog-only title) -- gi.pad_mode_selectable
correctly evaluates to 0 for this game, same as the RmlUi path's
ginfo.lock_mode already did.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 11:25:25 -07:00
Matthew Stanley 97c3506467 psxrecomp: alternate recomp-ui (Dear ImGui) launcher path behind RECOMP_LAUNCHER
When built with the recomp-ui submodule (RECOMP_LAUNCHER defined), run the shared
recomp_launcher_run_window() instead of the RmlUi psx_launcher::run(); RmlUi stays
the default when the define is absent. Maps UserSettings <-> RecompLauncherCSettings.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-16 10:23:03 -07:00
Matthew Stanley 5b7e69b441 Remove the sljit Tier-2 in-process JIT backend
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>
2026-07-15 22:22:38 -07:00
Matthew Stanley 411532bd87 Merge remote-tracking branch 'origin/master' into spike/load-time-zero 2026-07-14 23:09:27 -07:00
Matthew Stanley 6b069bedb8 feat: park duplicate-frame presentation smoothing
Adapted from https://github.com/mstan/psxrecomp/commit/ec889d6e5956fc4f34acfee41d87f30b24123b7f

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
2026-07-14 23:07:14 -07:00
douglasjv 28165d6421 Expand Einhander player bounds for native widescreen 2026-07-14 23:06:05 -07:00
douglasjv 9a8cdb6772 Add typed native-wide textured edge expansion 2026-07-14 23:04:55 -07:00
Matthew Stanley 44b895e331 Merge remote-tracking branch 'origin/master' into spike/load-time-zero 2026-07-14 22:48:36 -07:00
Matthew Stanley 64b084e2a7 load: add opt-in event horizon, warm routes, and audio sink 2026-07-14 22:44:31 -07:00
Codex b156770bbd Configure the runtime OpenGL 3.3 core context
Co-authored-by: douglasjv <vanderveerdouglas@gmail.com>
2026-07-14 15:59:39 -07:00
Matthew Stanley 7d1fadb07c runtime: report simulated-vblank FPS telemetry
Adapted from NyperYuhgard's PR #13.

Co-authored-by: NyperYuhgard <93950153+NyperYuhgard@users.noreply.github.com>
2026-07-14 10:19:50 -07:00
Matthew Stanley faaacc555d fix: keep explicit BIOS precedence over stray arguments 2026-07-14 00:15:47 -07:00
Martin Penkava 05d4f35bd6 build: MSVC portability for runtime and generated code
- 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>
2026-07-13 21:47:40 -07:00
Matthew Stanley dde268dc0f input: decouple analog stick from D-pad in analog pad mode
The default controller map folds the left stick onto the D-pad bits
(up=dpup,lefty- … right=dpright,leftx+) so the stick acts as a D-pad
for digital-only games. In ANALOG pad mode that fold is wrong: the
stick already drives the analog axes, and on a real DualShock the
stick and D-pad are independent. A dual-analog title that uses the
D-pad as its own control therefore saw phantom D-pad presses from
every stick movement — and constant presses from stick centre drift.

Ape Escape is the concrete victim: it is locked to analog, its D-pad
left/right rotates the camera, so walking with the left stick (or a
drifting Series-X stick at rest, past the 12%/4000 deadzone) spun the
camera with no D-pad input. Reported by VGEsoterica.

Fix in the framework (class fix, all analog titles inherit): resolve
the effective pad type before building the button word, and when the
pad presents as analog this frame, suppress the LEFTX/LEFTY/RIGHTX/
RIGHTY axis sources as button contributors. Trigger axes (L2/R2) and
button sources are unaffected; digital mode keeps the stick->D-pad
fold (its only outlet there). The physical D-pad still drives the
D-pad bits, so Ape's camera rotate works from the D-pad as intended.
Stick/axis output is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-13 12:54:28 -07:00
Matthew Stanley d53742310d release hygiene: no baked build-machine paths, loud malformed-settings warning
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>
2026-07-12 18:52:37 -07:00
Matthew Stanley fb585541ec opengl: sustain full-rate gameplay and FMV presentation 2026-07-11 14:07:17 -07:00
Matthew Stanley eefea1295f Merge codex/ape-widescreen-framework: scope curved backdrop projection sites (Ape widescreen) 2026-07-10 16:33:00 -07:00
Matthew Stanley 244474a147 widescreen: scope curved backdrop projection sites 2026-07-10 15:51:07 -07:00
Matthew Stanley a0818de0b4 Merge feat/ws-2d-scene-pillarbox: 2D-only scenes pillarbox in native-wide; present ring; full-pad input injection
Tomba 16:9: rooms, sky falls, and task-card sequences presented stretched
because native-wide's 2D-backdrop stretch fires wholesale in scenes whose
background phase never ends. Such scenes now present native 4:3 from the
faithful canonical buffer, keyed on DEEP UNTAGGED POLYGON OVERHANG (>=24px
past the canonical window, >=4 polys, sustained 2 frames) — the only world
signal that survived adversarial scenes (shaded prims, GTE volume, any-poly
and shallow overhang all falsified by census evidence; lineage in gpu.c).
Scoped to sprite-tag titles; full_2d (MMX6) and gte_game_mode (Ape) paths
unchanged. Plus: native-wide never stretches a canonical-width game frame
(fallback pillarboxes); always-on present-classification ring (TCP
present_ring) across all backends; debug input injection drives the full
pad model (hybrid d-pad drop + stick axes); savestate TCP honesty; census
tagged column + ws_hud_mode A/B (HUD-corners deferred: ENHANCEMENTS.md W2).
USER-VALIDATED on Tomba (SCUS-94236).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:41:52 -07:00
Matthew Stanley b85f10959b widescreen: pillarbox 2D-only scenes in native-wide; HUD corner re-anchor for tag titles; present ring
2D-only-scene classifier (fixes room/fall/title-card stretch, Tomba):
- Native-wide's 2D-backdrop stretch fires wholesale in scenes whose
  background phase never ends (room interiors, sky-only falls), stretching
  the whole frame. Such scenes now present native 4:3 from the faithful
  canonical buffer (gpu_ws_present_native_43).
- The world signal is DEEP UNTAGGED POLYGON OVERHANG: polys whose raw SX
  extent crosses >=24px past the canonical window, >=4 per frame, sustained
  2+ consecutive frames. Lineage of rejected signals (each defeated by a
  real scene, census-evidenced): shaded-prim presence (title-card letter
  tiles are gouraud), GTE projection volume (task-jingle projects
  world-scale counts; title screen idles at 1680 verts), any-poly overhang
  (card letters slide in from off-screen), shallow overhang (jingle
  sparkles poke <=20px). Outdoor terrain measures 55 deep-overhang
  polys/frame vs 0-1 for every observed 2D scene/effect.
- Scoped to sprite-tag titles: full_2d (MMX6) and gte_game_mode (Ape)
  classification is unchanged.

Native-wide present invariant:
- A game frame that cannot present wide (compositor fallback) pillarboxes
  4:3 instead of stretching the canonical read across the wide window
  (all backends; squash mode still stretches by design).

HUD corner re-anchoring for sprite-tag titles ([widescreen] nw_hud_corners):
- Poly/line prims never shift (they are the world / tagged billboards);
  rect-family prims shift only when UNTAGGED — the proven hud_sprt_squash
  discriminator — so enabling nw_hud_corners is safe without a packet
  arena range.

Observability (always-on, institutional):
- present_ring: per-present classification ring (path, present width,
  game_mode, native_43, wide fallback, tag age, gte_verts, ovh_prims)
  for every backend; TCP "present_ring". Answers "why did this frame
  stretch" after the fact.
- gpu_state ws block gains last_3d_frame, gte_verts, last_world3d_frame,
  ovh_prims, last_ovh_frame; GTE vert counting is now always-on.

Input injection drives the full pad model:
- set_input/press now resolve pad type like real sampling: injected d-pad
  drops a hybrid pad to digital (movement works), optional lx/ly/rx/ry
  stick override (0..255), type via the coherent request channel.
  Previously buttons-only: menus reacted, walking never did.

Savestate honesty:
- savestate_request_save/load return staged/refused; the TCP handler
  reports ok:false instead of silently acking loads it cannot perform
  (LLE host-fiber runs). Torn-restore reliability audit remains open.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 15:23:44 -07:00
Matthew Stanley e8943945e1 Merge codex/tomba2-whoopee-beach-fix: native-wide flat/phase backdrop gates + FMV silent-hold
Tomba 2 widescreen enablers: [widescreen] nw_flat_backdrop /
nw_phase_backdrop native-wide mirror gates (canonical 4:3 untouched),
[video] fmv_skip_no_xa_hold for silent preloaded-logo auto-skip,
ws_dbg_stretch modes 7/8, fmv_state JSON escaping.

Conflicts (config_loader.h/.cpp, main.cpp) were both-sides-adds vs the
MMX4/5 clear_reveal work - resolved keeping both feature sets.
Compile + boot sanity: Ape build-release rebuilt and boots.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 13:48:56 -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 e5a958f86a Arm the text-image guard from the disc image; require it for the alternate game-start latch
The dirty-RAM text-image guard (685f38d) only armed when the loose local
boot-EXE file next to game.toml existed - found via find_project_root's
repo-marker walk. End-user release installs have neither a repo checkout
nor the EXE file (it is copyrighted game data), so the guard silently
never armed. Without it, fntrace's alternate game-start latch
(psx_game_address_in_text && dirty_ram_text_native_ok) cannot fire for
titles whose BIOS handoff enters text inside the shell-window shadow
(RAM 0x30000-0x5AFFF -> shell ROM until game-start): the shadow never
lifts, execution runs dead shell ROM bytes over real game text, and the
boot dies in a jump to PC=0 (Ape Escape v0.0.2/v0.0.3 release zips,
frame 14, both HLE and LLE boots; in-repo runs armed invisibly and
masked the bug during validation).

Fix, class-level for every title:
- main.cpp: defer guard arming until the disc path is resolved and add a
  second source - extract the boot EXE from the mounted disc image via
  ISOReader (basename of game.toml's exe field, SYSTEM.CNF BOOT token
  fallback). The disc is the same bytes the BIOS loads, i.e. the true
  reference; every install has one. Local EXE file still wins for dev
  checkouts. A loud WARNING prints when neither source arms (previously
  silent, which is how the broken releases shipped green).
- fntrace.c: the alternate game-start latch now requires a registered
  image (dirty_ram_text_image_registered). Its safety argument - "RAM
  still holds shell bytes that differ from the game image, so it cannot
  fire prematurely" - only holds when an image exists; with no image,
  native_ok degrades to !dirty and untouched text pages pass it,
  latching game-start before the EXE loads and clearing the dirty
  baseline mid-load. Without an image, only the exact entry_pc dispatch
  may latch (the pre-685f38d conservative behavior).

Verified: v0.0.3 zip contents + rebuilt exe in an isolated directory
(no repo above it) boots to the Ape Escape intro on both HLE and LLE
paths, guard armed from disc SCUS_944.23; in-repo runs still arm from
the local EXE.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 12:54:00 -07:00
Matthew Stanley 29b1fd6246 input: first-class L3/R3 (stick click) binds across the keybind ecosystem
The controller map had no l3/r3 rows at all, so pad bits 1/2 could never
be produced by a gamepad — DualShock-only games (Ape Escape) were
unplayable. Keyboard slots existed but shipped unbound.

- main.cpp: PAD_L3/PAD_R3 bits; controller_map 14->16 entries; defaults
  l3=leftstick, r3=rightstick; input.ini template + doc comment updated.
- psx_keybinds.c: keyboard defaults L3=T, R3=Y (continue the Q/W/E/R
  shoulder row). Launcher Controls page picks them up automatically.
- beetle_main.cpp: same T/Y keys on the oracle so psx-beetle stays
  drivable for L3/R3 games.
- tools/package_release.ps1: shipped input.ini template gains l3/r3.
- README: keyboard + controller map tables.

Wire is unchanged: sio.c transmits the button word verbatim, matching
Beetle's dualshock.cpp. Validated in-game on Ape Escape (user-confirmed
R3/L3 inputs on a real pad).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 11:09:33 -07:00
Matthew Stanley 40b476c4e4 widescreen: finish MMX5 edge handling 2026-07-10 10:07:40 -07:00
Matthew Stanley 57ce271990 Merge branch 'master' into feat/mmx5-widescreen-fw
# Conflicts:
#	recompiler/src/config_loader.cpp
#	recompiler/src/config_loader.h
#	recompiler/src/main_psx.cpp
#	runtime/src/gpu.c
#	runtime/src/main.cpp
2026-07-10 08:52:29 -07:00
Matthew Stanley e4145d7008 Merge feat/crashbash-fw-fixes: GTE lm/sf depth-cue/lighting fix + dispatch shell-window + VBlank pacing
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>
2026-07-10 08:43:38 -07:00
Matthew Stanley c3f4aab028 Merge feat/mmx6-widescreen: MMX6 true-wide support
# Conflicts:
#	runtime/launcher/launcher.cpp
#	runtime/launcher/launcher.h
2026-07-10 08:41:36 -07:00
Matthew Stanley e9ca97ad11 feat: finish MMX6 widescreen edge handling 2026-07-10 08:28:43 -07:00
Matthew Stanley 54df1ecf7b Merge feat/audio-quality-pin: measured audio-quality campaign (MMX5 complaint)
Always-on audio tap rings + SPU event ring (runtime + psx-beetle oracle),
audio_stats/audio_wav/audio_events debug commands, offline A/B toolchain.

Fixes, all measured against the Beetle oracle:
- clock-domain DRC bridge for SPU output (pull-mode, polyphase resampler)
- guest-cycle sample budget (768 cyc/sample exact; was 60.00Hz assumption)
- frame pacer bounded catch-up (was forgiving all >1-period debt)
- PI fill servo with anti-windup (was P-only with standing deficit)
- voice loop-address register is LIVE state (X5 stuck-voice +11dB fix)
- CDC decode-volume matrix (X5 music fades)

MMX5: underruns ~360 -> 1 per 150s; level -3.8 dBFS clipping -> -15.5
vs oracle -14.1. Cross-title gate: Tomba built+booted, bridge fill
stable at target, user ear-validated both titles.
2026-07-10 08:13:17 -07:00
Matthew Stanley 79cdb12eed feat: support MMX6 finite-map widescreen 2026-07-10 01:47:29 -07:00
Matthew Stanley 49e758e4c9 audio: guest-cycle sample budget + pacer catch-up + PI bridge servo
Three producer/delivery fixes, each measured on MMX5 attract (port 4520
audio_stats, always-on rings):

1. Guest-cycle sample budget (main.cpp): the SPU pump produced 44100/60
   samples per host present — assuming a 60.00 Hz vblank against the real
   NTSC 59.94 Hz and quantizing to presents. Production measured 43950/s
   vs the device's 44100/s. Now the budget derives from psx_cycle_count:
   exactly 768 guest cycles per output frame (33.8688 MHz / 44100), so
   audio tracks guest time like every other device.

2. Bounded pacer catch-up (frame_pacing.c): frame_pacer_wait re-anchored
   (forgave all debt) whenever a frame ran >1 period late, permanently
   slipping the timeline on every heavy frame — measured 59.80 Hz average
   against the 59.94 target. Debt up to 8 periods is now repaid by running
   unpaced, so transient stalls no longer starve the audio ring; only
   sustained sub-realtime forgives.

3. PI bridge servo (recomp_audio_drc.h): the P-only fill servo needed a
   standing fill deficit of corr/kp to hold any steady correction (0.3%
   skew = 7.5 ms of permanently eaten margin -> jitter spikes hit the dry
   floor). Added an anti-windup integral term that holds steady clock skew
   at zero fill error, and raised target/ring 50/150 -> 80/250 ms for
   emulated-machine jitter (tens of ms, far beyond host-driver jitter).
   Also: bridge dry-pulls during an intentional turbo mute are resynced
   past, not reported as gaps (the mute IS the silence).

Measured, MMX5 attract, 150 s steady windows:
  legacy push baseline:   ~360 underrun events / 150 s (queue to 0 = gap)
  bridge as merged:       ~90 underrun events-equivalent / 150 s
  this commit:            1 underrun event / 150 s, fill ~55-73 ms,
                          production 43981/s, corr -0.5% (absorbs the
                          guest's residual ~0.27% sub-realtime pace).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-10 01:14:36 -07:00