Commit Graph

83 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 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 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 a544809edf aot: persist overlay capture history atomically 2026-07-17 23:56:58 -07: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 411532bd87 Merge remote-tracking branch 'origin/master' into spike/load-time-zero 2026-07-14 23:09:27 -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
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 098b06e047 Merge remote-tracking branch 'origin/master' into feat/reachable-main-discovery-douglas
# Conflicts:
#	docs/config_schema.md
#	recompiler/CMakeLists.txt
#	recompiler/src/main_psx.cpp
2026-07-14 00:09:56 -07:00
Matthew Stanley e7b61c807c recompiler: add guarded config MIPS patches
Add alias-aware, opcode-verified game-owned instruction replacements before discovery and code generation. Validate duplicate IDs and physical sites, preserve guarded overlay variants, and cover parser and generated-code behavior with CTest.

Co-authored-by: douglasjv <vanderveerdouglas@gmail.com>
2026-07-13 21:59:18 -07:00
Matthew Stanley bd705bfcec Add bounded reachable main-EXE discovery
Adapt Douglas's opt-in reachable discovery onto current exact/static dispatch. Validate configured static-analysis bounds before truncation, preserve the whole-image default and canonical page reservations, and leave unresolved indirect or out-of-bound targets to runtime fallback.

Source: https://github.com/douglasjv/mm8

Source-commit: https://github.com/douglasjv/mm8/commit/8415f3a95458c41c7f48fbe36caaf0ee82730720

Co-authored-by: douglasjv <vanderveerdouglas@gmail.com>
2026-07-13 21:56:43 -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 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 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 e9ca97ad11 feat: finish MMX6 widescreen edge handling 2026-07-10 08:28:43 -07:00
Matthew Stanley 79cdb12eed feat: support MMX6 finite-map widescreen 2026-07-10 01:47:29 -07:00
Matthew Stanley d9dae48c8e widescreen: generalize Capcom 2D background hooks for MMX5 2026-07-10 00:57:03 -07:00
NyperYuhgard fe95491ab1 PSXRecomp-Toml Tool 2026-07-09 14:43:05 -05:00
Matthew Stanley 9ed48831b7 launcher: [controller] lock_device — hide the Player controller cards entirely
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>
2026-07-06 19:30:12 -07:00
Matthew Stanley ee09ab9bbb widescreen: generic Ape-Escape-class support + native-wide GL perf + cutscene flicker fix
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>
2026-07-06 19:30:09 -07:00
Matthew Stanley 5abc5f0dca launcher/runtime: per-game [widescreen] offer gate for the Widescreen toggle
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>
2026-07-06 19:30:05 -07:00
Matthew Stanley fe6bd20a0e launcher: config-driven Localization dropdown + 2-column Settings
Adds a general, opt-in language selector to the RmlUi launcher, driven entirely
by game.toml [localization]:

  [localization]
  default = "en"
  languages = [ {code="en", label="English"}, {code="off", label="Default"} ]

- config_loader: parse [localization].default + languages[] into RuntimeConfig;
  add UserSettings.language with settings.toml [localization] parse + write.
- launcher: a "LOCALIZATION" panel in the Settings view (cycle toggle, matching
  the Renderer/Screen-model idiom) shown only when the game declares languages.
  The chosen code persists to settings.toml and rides UserSettings.
- text_xlate: new text_xlate_set_language() re-applies the language after the
  launcher (init runs before it); "off"/"jp"/"" disable APPLY (native game).
- main: seed/read-back the language through the launcher and re-apply on every
  boot path (so a settings.toml choice sticks even when the launcher is skipped).
- Settings view laid out in two columns (VIDEO | LOCALIZATION+AUDIO+CONTROLLER)
  with SYSTEM full-width below, so it uses the horizontal space and fits without
  scrolling; #root gains overflow-y:auto as a safety net.

Only games that declare [localization] get the menu (Tsumu today); byte-identical
for every other title.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-06 15:19:08 -07:00
Matthew Stanley 46d43555e6 runtime: on-the-fly string translation mechanism (capture + apply)
Implements the framework from docs/STRING_TRANSLATION.md: an always-on
capture inventory + a draw-time apply hook, both hung off the psx_dispatch
chokepoint via fntrace_record (no generated-code edits, no BIOS regen,
works in Release).

- text_xlate.{h,cpp}: EncodingProfile abstraction (shift_jis profile:
  SJIS record reader with NUL/0xFFFF terminators + halfwidth-kana + control
  framing; UTF-8 -> fullwidth-SJIS transcoder so English renders with the
  font's existing fullwidth Latin, zero glyph injection). Capture inventory
  (FNV-1a64 keyed) queryable over TCP; apply repoints the string-arg reg at
  guest-stack scratch (KV-gated: only known records are swapped, stray
  pointers untouched; own scratch is skipped to avoid recapture).
- fntrace.c: call text_xlate_on_dispatch() at the dispatch chokepoint.
- config_loader: [localization].language (+ [runtime].language alias);
  env PSX_LANG overrides. Loads translations/*.toml per project.
- main.cpp: text_xlate_init() at game-config load.
- debug_server: "xlate" command (stats|dump|todo|reload) — observability
  over TCP, no log files.

Verified: builds (Release), Tsumu boots headless, capture live
(distinct records accumulating), apply disabled until a table is present.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-05 11:42:07 -07:00
Matthew Stanley 95ca9d80e5 runtime: fmv_skip_no_xa detection key + host_thread_probe tool
- [video] fmv_skip_no_xa: broaden FMV detection to MDEC activity alone.
  Silent RAM-preloaded movies never stream XA, so the MDEC+XA detector
  missed them and no skip mechanism was even attempted. Per-game opt-in.
- tools/host_thread_probe.py: suspend/sample RIP+RSP of every host
  thread of a live or wedged runtime, stack-scan return addresses into
  the main exe, addr2line-symbolize (RelWithDebInfo). Diagnosed the
  fatal-halt-serve loop in minutes; use whenever a runtime 'freezes'.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 22:42:26 -07:00
Matthew Stanley 582aeccdac bios: HLE as a first-class swappable tier (gbarecomp model) + HLE boot shell-skip
USER PIVOT 2026-07-02 (CLAUDE.md §0 amendment included): psxrecomp now
carries an opt-in High-Level Emulation backend alongside LLE. LLE (the
recompiled BIOS) stays the default, the reference implementation, and the
correctness/timing oracle; every service HLE does not implement falls
through to LLE, so HLE is never load-bearing beyond what it covers.

Mechanism: full_function_emitter.cpp emits a null-by-default hook
g_psx_bios_hle_hook consulted at the top of every psx_dispatch_impl
iteration, on the pre-normalize physical address, BEFORE any backend
(game image / dirty-RAM / static table) claims the target. A handled
call resumes at $ra through the trampoline's existing return/tail
contract. NULL (default) = pure LLE, dispatch-identical. Regenerating
BIOS+game images is required (emitter change).

v1 call-HLE (runtime/src/bios_hle.c): the B0 event family — DeliverEvent
(incl. callback dispatch with the kernel-true $ra 0x1720), OpenEvent,
CloseEvent, TestEvent, EnableEvent, DisableEvent — each ground-truthed
instruction-by-instruction against the SCPH1001 kernel disassembly
(0xBFC11644..0xBFC11A84) and operating on the REAL guest EvCB table
([0x120]/[0x124], stride 0x1C). WaitEvent/threads/pads/card/A0/C0 fall
through to LLE. Cycle charges are documented approximations; LLE remains
the timing oracle. No-stubs stands.

Boot HLE: with the tier on (and no keep-intro opt-out) the hook one-shot
intercepts the first dispatch of the BIOS shell entry (RAM 0x30000 —
LoadRunShell's indirect call always routes through the dispatcher), so
BIOS Main() proceeds straight to SYSTEM.CNF + game EXE load with kernel
state built entirely by the real recompiled kernel init, unpaced (boot
turbo) until game handoff. This REPLACES the fast_boot snapshot
capture/restore; fast_boot=true now aliases the boot-skip alone.

Selection: [runtime] bios_hle / bios_hle_keep_intro (+ settings.toml
bios_hle mirror), PSX_BIOS_HLE / PSX_BIOS_HLE_KEEP_INTRO env overrides,
startup banner bios_backend=/bios_boot=. The PSX_HLE_SCHEDULER spike is
folded in as the tier's standing subsystem replacement: default from
[runtime] hle_scheduler via psx_hle_scheduler_set_default, env wins.

Observability: always-on 16K-entry HLE ring (route LLE-fallthrough /
HLE / boot-skip per record) + hle_dump TCP command (TCP_COMMANDS.md).

VALIDATED on Tomba (build-wt): LLE pass byte-identical behavior (real
intro, card check, save/load OK); HLE pass boots shell-less, TestEvent/
DeliverEvent route HLE, ChangeTh/ReturnFromException/OpenTh/CloseTh and
the card stack fall through to LLE, save/load OK (user-driven).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 16:27:33 -07:00
Matthew Stanley 8ab24c5e62 wip(tomba2): ABI v8 check_interrupts_at + emitter charge-cycle work (pre-rebase snapshot)
In-progress investigation work snapshotted before rebasing onto the
tcc-refactored psxrecomp master. Includes overlay ABI v8
(check_interrupts_at resume-PC callback for async RFE recovery) and
code_generator/full_function_emitter delay-slot cycle accounting.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:26:04 -07:00
Matthew Stanley 84696570d0 feat(launcher): [controller] lock_mode — hide pad-mode selector, force one mode
For a game that supports exactly one pad type, the launcher's 3-way pad-mode
selector (Hybrid | Analog | D-Pad) offers modes that don't work. New
game.toml [controller] lock_mode = true hides the whole selector and forces
every port to default_mode, so the player can't pick a broken mode.

Tomba 2 is the first user: its driver only works as a plain digital pad
(the DualShock config-mode handshake emulation is incomplete — see sio.c
pad_supports_config / commit 1141f62), so analog/hybrid re-trigger the
phantom-input bug. lock_mode supersedes allow_hybrid (which only hid Hybrid).

Plumbing mirrors allow_hybrid: config_loader (controller_lock_mode) -> main.cpp
(ginfo.lock_mode/locked_mode) -> launcher (m.mode_selectable + force p1/p2 mode,
overriding stale settings.toml) -> launcher.rml (data-if="mode_selectable" on
both .seg selectors). Verified: compiles in a PSX_LAUNCHER=ON build (build-t2-launcher).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 23:16:17 -07:00
Matthew Stanley c2a3f6ad79 overlay: add tcc tier (toolchain-free fallback) replacing sljit; extract overlay_backend
Compiler-neutral tier-selection policy extracted from the mis-named
overlay_sljit.{c,h} into NEW overlay_backend.{c,h} (OverlayBackend enum +
resolve/name/active). overlay_sljit.{c,h} is now just the deprecated emitter.

New tcc tier. gcc and tcc run the IDENTICAL pipeline (recompiler -> C ->
compiler -> DLL -> loader); only the compiler binary differs, so both map to the
autocompile-spawn provider (code_provider.c) and the COMMAND is chosen by the
resolved backend (main.cpp). compile_overlays.py gains --compiler gcc|tcc: tcc
writes a separate tcc/ cache namespace, BOM-strips the C + a copy of the include
headers (tcc 0.9.27 can't skip a UTF-8 BOM), and uses tcc's built-in linker.

Backends (overlay_backend): auto (gcc if a gcc TOOLCHAIN is present, else tcc --
was sljit), gcc, tcc, auto-no-gcc (dev/test: force tcc even with gcc present, to
exercise the toolchain-less user path), sljit (DEPRECATED, gated off, lowest).
Priority: static > gcc shard > tcc shard > sljit(off) > interp. The loader scans
gcc/ then tcc/ and dedups by region+crc (filename) so a gcc shard always wins a
tie.

Self-contained tcc: with no explicit overlay_autocompile_cmd_tcc, the runtime
CONSTRUCTS the tcc command from a toolchain bundled beside the exe
(<exe>/overlay_toolchain/: embedded python + tcc + recompiler + compile_overlays.py
+ runtime headers) -- no system python or gcc needed. config_loader gains
overlay_autocompile_cmd_tcc (a dev override). autocompile cmd buffers enlarged for
the constructed path-heavy command.

Compilation stays OFF the dispatch/render thread (off-process autocompile
subprocess, same model as gcc); the main thread only spawns it and dlopens the
finished DLL.

Validated: MMX6 with overlay_backend=auto-no-gcc fills the tcc/ namespace live
from the bundled toolchain and plays through save-load + gameplay -- the exact
path the sljit tier softlocked on. The gcc path is untouched.

Runtime-only, no regen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-25 13:10:57 -07:00
Matthew Stanley c35c04f310 launcher: add "Skip PSX BIOS" toggle (default off) for fast_boot
Surfaces the boot-snapshot feature as a launcher setting instead of a
game.toml-only opt-in. Mirrors the turbo_loads toggle end-to-end: UserSettings
field (has_fast_boot/fast_boot, default false), config_loader read/write,
main.cpp apply/seed/seed-apply, launcher.cpp model field/bind/toggle/io-write.
Reuses the existing [runtime] fast_boot key as the persisted seed; nothing
renamed. A hint (reusing .ws-hint, shown only when On) notes the first boot
still loads to build the cache and is instant after. Launcher/config only; no regen.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-24 23:39:09 -07:00
Matthew Stanley e0c819cf6b Merge Ape Escape forward-port into master
Validated CD-model rewrite (boot-EXE SeekL wedge fixed via deferred response
presentation), recompiler pre-prologue function discovery, dma/iso/memory
forward-port, and MMIO-READ (rtrace) instrumentation. Functional on Tomba
(in-game), Mega Man X6 (in-game), and Ape Escape (3D title/gameplay), 0
dispatch misses each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 22:41:26 -07:00
Matthew Stanley 01b4d91e8a WIP: forward-port Ape Escape framework (dev/ape-escape) onto master
Comprehensive 3-way merge of the dev/ape-escape branch into master for
the isolated wt/ape-escape worktree. Brought in every complementary ape
change automatically (dma.c, memory.c interrupt byte/half lanes,
function_analysis discovery, main_bios vector seeds, iso_reader
iso_sector_count, config_loader, crash-trace fields, headers). Resolved
the 13 conflicts by rule:

  - CPS dispatch / recompiler (code_generator, full_function_emitter,
    dirty_ram_interp, traps, main_psx) -> KEEP MASTER (evolved system).
  - CD model (cdrom.c) -> take ape (the model that booted Ape).
  - interrupts.c/.h -> reverted to MASTER (ape's interrupt-delivery is
    dispatch-coupled; the merged Frankenstein stormed).
  - sio/debug_server/freeze_heartbeat/main.cpp -> keep master (evolved).
  - crash_trace.c -> master (ape freeze-dump fns omitted to avoid the
    debug-ring cascade).

STATUS: does NOT boot yet. ape-cdrom + master-interrupts derails to
PC=0 at frame ~2092 (insta-close); ape-cdrom + ape-interrupts storms
(297M exception re-entries) at frame 2077. Root finding: ape's CD
anti-storm is coupled to ape's interrupt-delivery + call-depth + EPC
model, which master replaced with CPS continuation-passing, so the CD
subsystem does not forward-port as pure hardware simulation. Preserved
as the reference state for a design consult on bridging the two models.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:55:23 -07:00
Matthew Stanley 1965cae59d launcher: Fullscreen on launch + Skip Launcher on Boot (parity with SMW)
Adds two launcher controls requested for Tomba (issue #6):

- Fullscreen on launch: new [video] fullscreen setting, cloned through the full
  turbo_loads pipeline (UserSettings field + parse/save, launcher model/bind/
  toggle/commit, main.cpp seed/read), applied as SDL_WINDOW_FULLSCREEN_DESKTOP at
  game-window creation. The in-game F11 / Alt+Enter hotkey is unchanged.

- Skip Launcher on Boot: a footer switch (bottom-left) that boots straight into
  the game on subsequent launches, with a (first-in-this-launcher) confirmation
  modal explaining the --launcher escape hatch. The runtime skip_launcher round-
  trip + --launcher / PSX_NO_LAUNCHER already existed; this wires the UI to it.

Also drops the redundant footer Settings/Back links (the top bar already has
them). Mirrors SuperMarioWorldRecomp's feature.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 15:23:41 -07:00
Matthew Stanley af303f3454 runtime: Tomba "Hybrid" legacy pad-config compatibility (per-game opt-in)
The launcher Hybrid controller mode (built first, for Tomba, to mimic Tomba:
Special Edition) flips the emulated pad's reported type 0x41<->0x73 on
d-pad<->stick. After the real DualShock config state machine was added for MMX6
(98aa688), that flip regressed every game's libpad re-detect into a 1-frame
"pad unplugged": Tomba unpauses its menu / drops input, MMX6 phantom-dashes.

The modern config SM is correct and required by MMX6, so instead of weakening it
globally, restore the pre-98aa688 "config commands always answer 0xF3" behaviour
as a per-game opt-in: [controller] legacy_pad_config (default OFF). Only Tomba
sets it; with the flag off the modern pad path is byte-for-byte unchanged, so no
other title is affected. A/B verified live: legacy = clean Tomba flip / MMX6
input; modern = Tomba disconnect + MMX6 dash.

Explicitly LEGACY: a correct findpad/re-detect refactor would make the flip
benign for every game with no per-game branch (real hardware tolerates unlimited
analog-button presses); when that lands this whole feature set is to be removed.
Full rationale in sio.c (g_pad_legacy_cfg).

Also includes the deferred coherent pad-type apply (pad_type_req, applied only at
an idle/non-config bus boundary so a flip can't split a poll/handshake) and a
`pad_cfg` debug command + tools/padcfg.py + tools/sio_flip_trace.py used to A/B
the config SM live.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:43:34 -07:00
Matthew Stanley c7939b712c launcher: add per-game [controller] allow_hybrid; drop turbo-loads subtext
allow_hybrid (default true) lets a game hide the "Hybrid" pad mode from the
launcher's segmented selector, leaving Analog | D-Pad. Parsed in config_loader,
threaded through GameInfo into the launcher data model, and gated in launcher.rml
with data-if="allow_hybrid" (mirroring ws_eligible). When hidden, a port that
would default to Hybrid is clamped to Analog so the selection stays valid. Games
that hard-require a DualShock (e.g. Mega Man X6) set [controller] allow_hybrid =
false; Tomba and others are unaffected.

Also remove the "much faster loading, audio plays through" hint under the Turbo
loads toggle — the On/Off control is self-explanatory.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 13:30:05 -07:00
Matthew Stanley bb1d990f1b widescreen: MMX6 BG tile-ring freshness fix — kill the reveal-margin staircase
The 16:9 BG widen draws LEFT extra tile columns each side, but the engine's
streamer (FUN_800273e4 -> FUN_800274a0) only refreshes one column per side per
frame at the leading edge. On scene entry / fast scroll the inner reveal columns
held stale tiles from a prior scrollY -> real-but-misplaced tiles spilling
up-and-left (the "staircase"). Census confirmed this is NOT the BG packet-budget
overflow (674-816 prims/frame, well under the 1000-tile cap).

Fix (render window == refresh window): clone FUN_800274a0's inner metatile->ring
fill into mmx6_fill_column() and re-stream every column of the widened window for
all 3 layers each frame (psx_ws_mmx6_bg_refill_all), triggered once/frame from the
existing count_site hook (mmx6_bg_refill_tick, frame-gated). Uses each layer's own
scroll, omits the engine's scrollX wrap-bookkeeping writes (those drive the engine's
own incremental streamer), skips map-loop seams; off-map columns fill tile 0 (the
renderer skips them). Validated byte-exact against the engine's own streaming
(0 mismatches / 1113 cells x 3 frames; debug cmd mmx6_freshfix {validate:1}). Gated
native-wide <=384w so 4:3 and the 512 hi-res title stay byte-identical.

Also removes the 2-col reveal cap (WS_FULL2D_MAX_REVEAL): it never addressed the
staircase and cost screen real-estate; the full 53px/4-col reveal is restored.

Parked, gated-off experiments ride along (inert, default off): host-side reveal
(Attempt A: gpu_ws_mmx6_emit_reveal + sw_wide_emit_tile) and BG packet-buffer
relocation (Attempt B: [widescreen.bg2d] bufbase/cap recompiler hooks). These target
the separate dense-stage budget-overflow concern, not this fix.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-21 11:44:54 -07:00
Matthew Stanley 510d75f70b widescreen: MMX6 stale-margin fix — widen the BG tile-ring streamer
The BG renderer widen (a8f37c1) draws extra tile columns on both sides, but the
engine's 64-column tile ring is only refreshed at its leading edge: the streamer
(FUN_800273e4) re-streams ONE column per side per frame at world-X scrollX-16
(left) and scrollX+16+width (right). So the columns the widened renderer reaches
were never populated — they showed empty (black margin) or stale (old tiles as
the camera scrolled), the "stale margins" the user saw in motion.

Add two more [widescreen.bg2d] sites (stream_left_site / stream_right_site) that
push the streamer's left/right leading-edge world-X out by LEFT*16 via gpu.c
psx_ws_mmx6_bg_stream_{left,right}, so the incremental streamer keeps the ring
valid across the widened window as the camera scrolls. Same number of streams
(the single leading-edge target just moves out LEFT columns) — no extra cost.
Identity at 4:3 / 512 hi-res.

Verified: the previously-black left reveal margin now fills with coherent
adjacent stage; blackL/blackR = 0 across scenes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 12:40:16 -07:00
Matthew Stanley a8f37c1f76 widescreen: MMX6 2D true-FOV — full_2d opt-in + [widescreen.bg2d] BG widen
Pure-2D sprite games (Mega Man X6) render a true 4:3 field of view with no
overscan to reveal, so 16:9 requires making the engine draw more. Two parts:

- [widescreen] full_2d opts a pure-2D game into the native-wide present path
  (it never emits the 3D sprite-tag hook the gameplay detector keys on, so
  every in-game frame was misclassified as a 4:3 menu and pillarboxed).

- [widescreen.bg2d] is a gen-time hook on the per-layer background tile
  renderer (MMX6 FUN_800270d0) at its column-count / start-tile-column /
  start-screen-x instructions, routed through gpu.c psx_ws_mmx6_bg_{cols,
  startcol,startx}. It draws LEFT=ceil(nw_offset/16) extra 16px columns on
  BOTH sides so the loop fills the 16:9 reveal margins with real adjacent
  stage, existing columns staying pixel-aligned with objects. Identity at
  4:3 and in the engine's own 512 hi-res mode (so the title is untouched).

Tooling: wide_full debug command + sw_wide_dump_full dump the entire
native-wide compositor surface (both vertical-double-buffer y-bands and both
reveal margins) for diagnosis.

Known follow-ups: object screen-X cull widen (actors in the margins);
top/bottom flicker + stale-margin refresh on the vertical double-buffer.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-20 11:57:32 -07:00
Matthew Stanley 9932dabc4c launcher: add Turbo loads toggle (Settings, default off)
Exposes turbo-through-loads as a launcher Settings option instead of a
game.toml-only knob. Turbo runs the guest unpaced + mutes audio while the CD
data stream is active (faster loads, but a brief audio cut/fade). Now defaults
OFF so loads play at natural 60fps with continuous audio (disc_speed=instant
already keeps data delivery fast); users opt into turbo via the launcher.

Wired through the full settings chain, mirroring auto_skip_fmv: UserSettings
turbo_loads/has_turbo_loads, settings.toml read+write, runtime override/seed/
apply over game.toml [runtime] turbo_loads, launcher data-model + toggle event,
and the RML row. Runtime/launcher only — no regen, codegen unaffected.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 10:34:01 -07:00
Matthew Stanley dc7f5c52f9 fmv-skip: end movies via the game's own frame-count teardown ([video] fmv_skip_*)
New universal FMV auto-skip: when auto_skip_fmv is on and a movie is detected
(MDEC+XA), write the CURRENT movie's per-movie frame-total down to fmv_skip_end_total
so the player's MDEC loop (Tomba FUN_8001efe8: teardown when streamed frame# >=
total-3) ends it on the next frame — a NATURAL end that reaches EVERY movie,
including ones whose caller never polls the skip button (Whoopee Camp logo, opening,
cutscene movies). Only the active movie's table entry is touched (via the movie-id
byte). Config: [video] fmv_skip_total_table / fmv_skip_movie_id / fmv_skip_end_total
(0 table => START-injection fallback). Present-suppress + audio mute (existing) hide
the 1-2 transition frames. Runtime-only, no regen. USER-VALIDATED on Tomba.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 19:21:31 -07:00
Matthew Stanley 905b52da57 game_options: restore-time value-range validation + format version
A persisted option value outside its declared [min,max] is now REJECTED at load
(the game's own default is used instead), so a corrupt/stale options file can no
longer inject an out-of-range value into a config global the game may use as a
jump-table index (wild jump = softlock). Ranges declared per option in
game_options.toml (min/max; absent => no validation, e.g. signed screen offsets).
Adds a format_version header (newer-than-known files are rejected wholesale).
Restore stays name-keyed (offset-drift-safe). Runtime-only, no regen.
Validated: corrupt values rejected (defaults applied); valid file restores intact.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 18:47:27 -07:00
Matthew Stanley 0ac63b655c vulkan: 3rd renderer backend (WIP) — emitter, GPU-direct present, full-frame readback
Vulkan backend alongside software + OpenGL: geometry/textured-prim
pipelines, GPU-authoritative VRAM image, deferred-submit + drain-at-present
perf model, GPU-direct present (vk_renderer_present_vram), and a
full-VRAM-pack readback path (ensure_cpu rect_adds the whole VRAM rect
before pack_flush so screenshots/guest VRAM reads reflect live content,
not just the incremental pack-dirty rect — readback-only, no per-frame cost).

Runtime plumbing: --debug-port / --renderer / --window-title CLI overrides
(multi-instance fleet), latency_ring, SPIR-V shaders + embed_spirv.py,
config_loader renderer selection, debug_server screenshot wiring.

All on feat/vulkan-renderer; snapshot in backup/vulkan-wip-20260617.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 14:56:12 -07:00