Commit Graph

150 Commits

Author SHA1 Message Date
Alex Vanderveen 3cb2a60dc3 bump 2026-08-19 22:50:13 -04:00
Alex Vanderveen b7f3642725 psx-dual-link wipeout support 2026-08-19 09:12:06 -04:00
Alex Vanderveen 355605ffc0 psx_link 'shm': two-process serial link — full-speed link play
Each console is a complete single-console runtime in its own process (own
core, own window, own pad); the SIO1 cable is a pair of SPSC rings in a
shared-memory segment, paced by the bounded-skew barrier the fiber dual
mode validated. In-process dual halves the frame rate by construction; the
threads-in-one-process alternative needs ~590 device statics and 66 config
setters made per-machine. Two processes get each console a core for free
and upgrade to two PCs later by swapping the transport.

Design points (psx_link_shm.h):
- LINK-TIME: processes boot at different wall times, so due-cycles run on
  local-cycles-minus-epoch, epoch latched when the pair forms.
- Ops stay pure (psx_link.h determinism contract); wall clock only in the
  barrier poll (peer-death detection -> unplugged-cable semantics).
- Lookahead defaults to ~one PAL frame: below that, each side's ordinary
  pacer idle (clock frozen up to 8 ms/frame) stalls the peer -- measured
  0.57x at a quarter frame, 0.30x at the fiber-tuned 8192. Pair speed is
  min(A,B) by design; menus/races pair at 1.0x, phase-shifted intro FMV
  dips compound and that is correct lockstep behaviour.
- PSX_SIO1_BACKEND=shm PSX_LINK_SHM_ROLE=a|b selects; PSX_LINK_SHM_NAME,
  _LOOKAHEAD, _LATENCY tune. [runtime.link] backend="shm" also accepted.
- Savestate during link play serializes an empty wire (in-flight chars
  lost; handshakes retransmit). Cross-process co-checkpointing is future
  work.

Verified: two instances pair, exchange lines state, hold skew within the
bound, and run at min-of-pair speed with ~3s total barrier wait per 45s of
phase-shifted intro FMV; two UNLINKED instances hold 1.00x simultaneously
(16-core host), isolating all coupling cost to lockstep semantics.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 20:19:33 -04:00
Alex Vanderveen 85d167d559 Merge PR6 psxrecomp head (e27eac7) into feat/rbengine
Conflict resolutions:
- boot_state.h: section-tag collision at 0x11 — master shipped BS_SEC_SIO1
  there (all v5 states + dual-console switch blobs), so the branch's
  BS_SEC_TEXPACK/BS_SEC_MODSET renumber to 0x12/0x13. PR-branch dev states
  are the only casualty.
- boot_state.c: adopt the branch's backpatched true section count (placeholder
  + write_section_raw counter), which subsumes master's constant-minus-
  exclusions arithmetic; keep master's section-exclude guards on CPU/RAM
  writes and the branch's MODSET-first mod-plan guard.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-18 17:33:50 -04:00
Alex Vanderveen 365662d823 chore: sync 2026-08-18 08:47:06 -04:00
tetrisgm 30652ac334 cpu: overclock by scaling the per-instruction cycle charge
The guest runs as native code, so "faster CPU" cannot mean "execute quicker" --
it means CHARGE LESS for the same work. Every device schedules off
psx_cycle_count and the CRTC fires VBlank on a fixed cycle period, so scaling
the per-instruction charge down by N lets the CPU complete N times the work
inside one frame while timers, SPU, CDROM and the refresh rate keep their
real-world rates. Scaling the VBlank period instead would slow everything
EXCEPT the CPU, which is backwards.

Q16 fixed point with a carried remainder, not a divide: this is the hottest path
in the runtime and integer division of a 1-2 cycle instruction cost would floor
to zero and lose all time. The accumulator keeps the total charge exact instead
of drifting by the truncation on every call. Stock (65536) short-circuits, so a
non-overclocked build pays one predictable compare.

[video] cpu_overclock, percent, 100 = stock. Needed because hueponik's
pal100full8 requires >900%.

STATUS, measured, not assumed: 100% and 300% boot and run. 1000% does NOT reach
a display -- and it is neither hung nor host-bound, frames advance steadily at
~39/s (455 -> 2574 over 54s) while the guest never enables the display. So the
guest's own boot path stalls at that ratio; the mechanism moves time correctly
but something in the boot sequence does not survive the CPU running 10x ahead of
its devices. Not yet isolated, and it is the blocker for pal100full8.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 21:28:23 -07:00
tetrisgm 4210c764b1 present: bezel artwork behind the frame ([video] bezel)
A still image drawn behind the game, filling whatever the letterbox or
pillarbox leaves over -- the trick vertical shmups use on horizontal displays,
where the dead space carries artwork so it reads as designed rather than as a
limitation. MAME calls the same idea bezel artwork.

Drawn across the whole drawable before the game quad, which is simpler and
safer than filling two margin strips: no margin arithmetic, it covers pillarbox
and letterbox alike, and the frame paints over the centre.

The reason this is the right shape and present_edge_fill was not: edge fill
stretches one edge COLUMN of the live frame across the margin. That suits a flat
menu backdrop and turns track geometry into horizontal smears, so it needs to
know whether the current frame is a menu -- a question it answers with
force_4_3, which is only ever set by the widescreen path. A still image has no
such question. It looks the same in a race and in a menu, which is the point.

Loaded after the GL context exists (the first attempt sat next to tex_pack_init,
which runs before the window, so g_gl_active was false and it silently never
loaded). Decoded with stb_image, already linked for the HD texture pack. A
relative path resolves against the disc directory, matching the pack convention,
so artwork can sit beside the disc.

Verified on a 7680-wide maximised window with 4:3 content: margins that were
pure black now carry the image on both sides, with the frame untouched in the
middle.

Known limits: the image is stretched to the drawable, so an aspect that does not
match the window will distort (cover-crop is a small follow-up), and this has
only been exercised in 4:3 pillarbox, not with the widescreen mod active.

For WipEout 3 the team wallpapers by MotorcycleEmptiness
(awesome-wipeout.github.io) suit this exactly; the shipped default is a
placeholder gradient used to verify the path, not final art.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 20:45:54 -07:00
Alex Vanderveen 816fb08fe2 bump + sio 2026-08-17 22:25:49 -04:00
tetrisgm d0ead48928 Merge remote-tracking branch 'origin/feat/rbengine' into feat/ws-cull-widen
# Conflicts:
#	runtime/src/debug_server.c
#	runtime/src/gpu.c
#	runtime/src/gpu_gl_renderer.c
#	runtime/src/tex_pack.cpp
2026-08-17 17:42:40 -07:00
tetrisgm 774b326fca pgxp: declare the depth API and wire [video] pgxp_depth through config
The header contract and config plumbing for the depth work; the implementation
landed in the preceding commits.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-17 17:38:25 -07:00
Alex Vanderveen 165fae0aa1 Fix the pad-hotkey range check rejecting every chord
[hotkeys] rewind_pad / save_state_menu_pad accept the recomp-ui pad encoding,
where 1000 + a button bitmask is a CHORD. The parse guarded on `n < 256`,
which admits single buttons and rejects every chord there is — including the
two values this same file writes back out (1272 = select+r3, 2040 =
select+r1). A saved chord was silently dropped on load and the defaults
quietly reinstated, so the pad hotkeys were never actually configurable.

The bound predates the chord encoding; the runtime's own
normalize_hotkey_pad_binding has handled combos correctly all along.

Replaced with pad_bind_value_ok(), sharing one documented bound with the
encoding it validates. Verified against a live boot with rewind_pad = 1017
(select+cross): parsed has=0 value=1272 before, has=1 value=1017 after.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 18:55:47 -04:00
Alex Vanderveen b423486563 toolchain, texture pack update 2026-08-17 18:55:34 -04:00
Alex Vanderveen a643fab71d Make the FMV present filter a real setting
The reconstruction added in the previous commit was env-only (PSX_FMV_FILTER),
which is fine for trying it out and no good for shipping it. Promote it to
[video] fmv_filter in both game.toml and settings.toml, spelled
"nearest" | "bilinear" | "sharp" | "bicubic" (default bicubic), and push the
resolved value to the renderer instead of having the renderer read getenv.

PSX_FMV_FILTER still overrides for a single run without touching the user's
settings, which is what an env knob is actually for.

Also bridges the value to recomp-ui's launcher settings in both directions so
the Display settings row round-trips: recomp-ui stores it 1-based (0 = unset,
so a host predating the field gets the default rather than Nearest), the
config enum is 0-based, and the conversion is confined to two helpers at the
boundary.

Verified end to end: with fmv_filter = "bilinear" in settings.toml the
presented pixels match the bilinear reference exactly (mean |dx| 0.930,
hard-step fraction 0.14%), where the default build produces the bicubic
numbers (1.038 / 0.34%).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-17 16:34:23 -04:00
Alex Vanderveen 6a9002032f memory optimizations for 8mb wipeout, performance in general, turbo++ 2026-08-17 14:32:01 -04:00
tetrisgm fd625f0996 Interpreter parity for cull.widen; raise the GL supersampling ceiling
Two follow-ups to [[widescreen.cull.widen]], both found by testing it.

INTERPRETER PARITY. The widen site type was recompiler-only: the AOT image
evaluated the widened compare while the dirty-RAM interpreter, and any overlay
shard built without the config, still evaluated the vanilla one. The same site
would then cull differently depending on which backend happened to run it.
Adds a runtime site registry mirroring psx_ws_cull_keep_site, wires it from the
config at startup, and consults it from the interpreter's SLT and SLTI paths
before the keep lookup so a config migrated from keep to widen cannot be served
the pinned answer by a stale entry.

GL SUPERSAMPLING CEILING 4x -> 16x. SW_MAX_INTERNAL_SCALE was applied to every
backend, which is the wrong constraint for GL. It exists because the software
path allocates a VRAM-sized hi-res MIRROR costing 1 MB * scale^2 (256 MB at
16x); under GL that mirror stays at 1x (glb_set_scale) and the cost is an FBO
instead. Capping GL at 4x limited it to about 2048x960 internal on hardware
that handles far more.

GL_MAX_INTERNAL_SCALE moves to gpu_render.h (main.cpp needs it to pick the
ceiling before a context exists), main.cpp selects the ceiling per backend, and
the config range becomes 1..16. init_gpu_raster now also clamps to the driver's
real GL_MAX_TEXTURE_SIZE / GL_MAX_RENDERBUFFER_SIZE and says so: previously an
over-large scale reached glTexImage2D, failed silently, and surfaced only as
"GL FBO incomplete" with no hint that the scale caused it.

Measured on WipEout 3 SE (PAL, NVIDIA, Release build + debug tools), sustained
present rate in-race, same track:

    scale  internal     fps
      1    508x256      50.0
      6    3048x1536    49.8
      9    4572x2304    50.2
     16    8128x4096    50.0

Flat across the range - the title is guest-bound, so 8K internal is free on this
hardware. 50 fps is full speed for a PAL disc.
2026-08-17 01:41:58 -07:00
tetrisgm a05ad08651 Add [[widescreen.cull.widen]]: move the cull bound instead of pinning it
[[widescreen.cull.keep]] forces a comparison's verdict while wide. That is
correct for a separately proven binary decision, and config_schema.md already
says as much -- "keep has no queue policy and is appropriate only for a
separately proven binary verdict", and prefer an aspect cone "when the original
predicate is a camera-frustum test".

At a clip-code packer it is actively wrong. Pinning the classifier tells the
clipper that nothing crosses the screen edge, so polygons that do are never
subdivided; they are submitted whole with coordinates outside the GPU's legal
primitive range and the hardware discards the entire primitive. The visible
result is geometry disappearing as the camera moves -- widening the view draws
LESS, not more.

Measured on a WipEout 3 game repo, same track and camera path: ~323 primitives
emitted with the pinned sites active against ~761 with the margin at 0. Live
A/B via the ws_margin debug command also showed inverted ship steering and
broken AI with the pins active, both of which cleared at margin 0 while the
16:9 image was retained.

This adds the widening alternative:

    [[widescreen.cull.widen]]
    address  = "0x8017032C"
    expected = "0x28850000"
    mode     = "imm_lower"

Modes name which operand carries the bound and which way it travels:
  imm_upper  SLTI/SLTIU  coord <  imm + m
  imm_lower  SLTI/SLTIU  coord <  imm - m
  bound_rt   SLT/SLTU    rs    <  rt  + m
  bound_rs   SLT/SLTU    rs + m <  rt

Every mode reduces to the vanilla comparison at margin 0, so 4:3 output stays
bit-for-bit identical -- the same guarantee keep sites give. The immediate modes
reuse the existing psx_ws_cull_slti / psx_ws_cull_slti_lower helpers; the
register-bound modes need psx_ws_cull_slt_widen, added here.

The parser rejects a mode that does not match the instruction shape (an
immediate mode on a register compare would widen an operand that does not
exist), rejects duplicate addresses, and full-word guards the site as keep does
so an overlay variant at the same VA is left alone. Widen sites contribute to
overlay cache identity, otherwise migrating a site from keep to widen would
silently reuse the pinned overlay.

No game config uses this yet; migrating a title's sites is a separate change
that needs each site's operand roles established first.
2026-08-17 00:42:30 -07:00
Alex Vanderveen 84ea64d613 texture pack support 2026-08-16 21:55:51 -04:00
Alex Vanderveen 76945d3a65 8mb ram patches + toolchain windows hotfix 2026-08-16 21:19:05 -04:00
Matthew Stanley bdd7ade6e8 Merge remote-tracking branch 'origin/master' into codex/netplay-local-viewport 2026-08-15 21:40:56 -07:00
Matthew Stanley b2eeb0f866 Add opt-in netplay local viewport extraction 2026-08-15 20:51:23 -07:00
Matthew Stanley 7993f412c0 Add opt-in controller device defaults 2026-08-15 20:09:37 -07:00
Matthew Stanley 200b5bf003 Add opt-in widescreen culling keep hooks 2026-08-15 19:42:43 -07:00
Matthew Stanley 2ba74f3b4b pgxp: land clean-room value-propagation engine (phases 0+1, issue #92)
The G1.2/G1.3 mechanism: per-word RAM/scratchpad + per-GPR + per-GTE-reg
shadows carrying sub-pixel projections BY PROVENANCE, validated against
the actual guest word on every read (never guessed from rounded position
- the measured 93.1%-ambiguous G1.4 dead end). Clean-room from psx-spx +
public design docs; vendored duckstation (CC BY-NC-ND) and beetle-psx
(GPL) trees remain black-box oracles only - no code ported.

- runtime/src/pgxp.cpp + pgxp.h: engine, per-vertex GetPreciseVertex
  (dataflow -> ambiguity-gated position cache -> native) with truncation
  agreement + tolerance safeguards; stats; test accessors.
- pgxp_hooks.h: 5-entry hook funnel; PGXP_*() macros no-op unless
  -DPSX_PGXP=1 (codegen-time gate over ONE generated tree); appended
  PGXPHooks* table member in OverlayCallbacks (ABI bump ships with the
  Phase-2 emitter change).
- Both interpreters wired: loads/stores/COP2 + precision-carrying
  arithmetic (moves, add/sub, or-merge, 16-bit shifts, LUI, HI/LO).
- gte.cpp: RTPS feeds the GTE reg shadows; retired s_precise_sxy FIFO,
  hashed s_precision_store, and plain-store invalidation (redundant
  under validate-on-read; store hot path gets 3 calls back).
- gpu.c: prepare_precise_triangle rewritten per-vertex; perspective UVs
  share the same provenance and compose per triangle.
- [video] pgxp_cpu_mode / pgxp_tolerance keys; TCP pgxp verb live-
  toggles geometry/texture/cpu_mode/tolerance; geom_correction census
  grew the pgxp dataflow counters (G1.9 gate metric).
- tests: new pgxp_test (registered); gte_register_access_test updated
  to precise invalidation contract. 33/33 enabled tests green.

Also fixed while landing (both broke a fresh master tree):
- runtime.cmake: once-only psx_game_version.txt stamp (multi-target
  file(GENERATE) collision broke every fresh single-config configure).
- test_overlay_capture_retry: SDL3 rot (SDL_SetMainReady gone, inverted
  SDL_Init check) -> psx_sdl_init.

Tracked as beads-eio.3.48; postmortem + this landing: ENHANCEMENTS.md
G1-G1.10.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-15 18:34:47 -07:00
Matthew Stanley cab7d00f9a Fix PSX launcher hotkeys and audio persistence 2026-08-14 15:22:29 -07:00
Matthew Stanley cee9b97239 Add PSX rewind and save-state controls 2026-08-12 11:12:24 -07:00
Alex Vanderveen 554476457f rewind support 2026-08-12 03:51:50 -04:00
Alex Vanderveen 1c070dfe40 chore: bump recomp-net + retcomm-rbengine 2026-08-12 03:25:31 -04:00
Matthew Stanley f0cfa92afa config: retire the legacy generic turbo_loads switch — load acceleration is mod-only
Load acceleration moved to the Mods catalog in 125d84d
(psx.enhancement.fast-loading "Fast Loading (host pacing)" and
psx.enhancement.cd-speed, both `game_id = "*"` so they ship with every
title, both default-off), and recomp-ui deliberately stopped drawing a
generic Turbo loads row. But the legacy switch was left live here with
`offer_turbo_loads` defaulting TRUE, so any title that had not explicitly
migrated still got turbo forced on — with no UI to turn it back off.

That shipped to players. MegaManX6Recomp v1.0.4 and v1.0.5 both carry
`[runtime] turbo_loads = true` in the game.toml inside the release zip
(verified by extracting the artifacts). Two paths forced it on:

  * game.toml [runtime] turbo_loads set g_turbo_loads_enabled directly;
  * a stale [video] turbo_loads in settings.toml was restored, and the
    seed write persisted it — so one run latched the value permanently
    and no later game.toml change could undo it.

Reported as MegaManX6Recomp#14: the WARNING screen self-advances and load
spinners run fast, with Turbo confirmed off in Mods. Tomba 2 hit the same
defect (fixed per-title), and MMX5 is also unmigrated. Third occurrence of
one config default, so fix the class instead of the titles.

Both keys are still PARSED, so existing configs load without error, but
neither is honoured:

  * offer_turbo_loads defaults false and turbo_loads_offered becomes a
    constexpr false, matching how ws_offered / skip_fmv_offered /
    frame_interpolation_offered already model mod-owned features. No title
    can offer the generic switch, so forgetting to migrate is no longer
    punished.
  * game.toml turbo_loads logs a deprecation line naming the Fast Loading
    mod and enables nothing.
  * settings.toml [video] turbo_loads is neither restored nor written back
    out, so the stale row is dropped on the first save — same write-only
    latch reasoning already documented for fast_boot, which bit harder here
    because there was no UI control at all.

Also fixes a related hole found while auditing: the in-game Settings apply
path wrote both g_turbo_loads_enabled and g_auto_skip_fmv from a launcher
snapshot taken BEFORE mod_runtime_activate_plugins() ran, so the first
in-game Apply silently turned a player's enabled Fast Loading or Tweaks FMV
feature back off. Both are now gated on their offered flags.

The supported surfaces are unchanged: the Fast Loading / CD Speed mod
plugins still enable acceleration (verified: "mod selected 8x load
acceleration"), and the `turbo_loads` TCP debug command still toggles it
for development.

Verified against MMX6 built on this framework: with game.toml
`turbo_loads = true` AND `offer_turbo_loads = true` AND a stale
settings.toml `turbo_loads = true`, both deprecation lines fire and the
debug server reports enabled=0, turbo_frames=0. With the keys removed and
no mods enabled, it boots silently with enabled=0.

test_mod_load_acceleration.py is updated to pin the new contract; it also
had a stale assertion on the old 2..16 multiplier range, which is now
pinned to the symbolic PSX_MOD_LOAD_ACCEL_MAX bound. The guard-test suite
goes from 9 pre-existing failures to 8 (no new failures; this one fixed).

Refs: beads-eio.3.26, MegaManX6Recomp#14

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-10 18:52:16 -07:00
Matthew Stanley 20a8f4ad08 input: make Hybrid a mod-only mode, not a selectable one
Hybrid kept reappearing in titles that never meant to ship it, because it was
the ZERO value of PadMode and therefore the default everywhere: PlayerInput,
p1_mode/p2_mode, ctrl_locked_*, and default_p1/p2_mode all initialised to it.
A game then had to opt OUT with [controller] allow_hybrid = false, and every
shipping title did exactly that or pinned a mode - so no title actually ran it.

Hybrid now has exactly one route: a trusted game plugin calling
psx_mod_set_controller_mode_override(), which is how Tomba's hybrid controller
mod already requests it. Everything that made it reachable by accident is gone:

  - defaults are ANALOG (PlayerInput, p1/p2, ctrl_locked_*, default_p1/p2)
  - [controller] allow_hybrid is removed, along with the startup clamp that
    existed only to undo the bad default
  - game.toml default_mode/p1_mode/p2_mode = hybrid now THROWS, naming the
    mod API as the only route, rather than being silently coerced
  - a stale settings.toml hybrid migrates to analog via
    pad_mode_from_settings_string(); a user should not fail to launch over a
    value they never typed
  - pad_mode_to_string() never writes hybrid back

Deliberately KEPT, because the mod runs on it: PAD_MODE_HYBRID,
PSX_MOD_CONTROLLER_HYBRID, psx_mod_set_controller_mode_override(),
hybrid_stick_active/hybrid_dpad_active, PlayerInput::hybrid_analog and
psx_keybinds_dpad_active.

Paired with the recomp-ui change that drops the Hybrid segment and the
allow_hybrid field from RecompLauncherCGameInfo - the two must land together.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-09 19:06:13 -07:00
Alex Vanderveen 4cdccf58c1 multitap enhancements 2026-08-07 10:19:12 -04:00
Alex Vanderveen 94cb3807a9 Merge origin/master into integrate/game-codegen-hooks-into-rollback
Keep rollback/netplay multiplayer routing, dual-raster GL present, and TOC
fingerprints while taking master's CHD support, mod load acceleration,
adaptive widescreen, and geometry/perspective APIs.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-08-06 22:50:38 -04:00
Alex Vanderveen 4073e2311c rollback + fmv + connectivity 2026-08-06 20:34:50 -04:00
Alex Vanderveen 652be7ba02 rollback FMV patches and overlay 2026-08-06 00:52:14 -04:00
Matthew Stanley c8b692eda2 feat(video): opt-in sub-pixel vertex precision + perspective-correct textures
Makes the parked projection/perspective work reachable and gets it into the
renderers people actually use. Addresses issue #92 (PGXP-style geometry and
texture correction).

The GTE side cache, SWC2 RAM provenance, GP0 triangle preparation and the
software-renderer consumption path already landed in 2ceaf5a ("park projection
and perspective correction", from kem0x PR #14), but were unreachable: the
generic setters gte_geometry_correction_set() / gpu_texture_correction_set()
had no production caller, and the corrected vertices went straight to
sw_set_precise_triangle(), bypassing the renderer vtable — while the default
renderer is OpenGL.

This adds:

- [video] geometry_correction and [video] perspective_texturing, in game.toml
  and settings.toml, both defaulting false. Independent knobs; the launcher
  seed marks them present so a save round-trips a hand-edited key instead of
  dropping it. Startup names the active pair, and notes when geometry
  correction is on below supersampling 2 (where it cannot be seen).
- gr_set_precise_triangle() / gr_set_perspective_triangle() on the
  GpuRenderBackend vtable, so gpu.c hands the correction to whichever backend
  is live rather than to the software rasterizer directly. NULL entries no-op.
- OpenGL and Vulkan consumption. Positions are already float end-to-end, so
  sub-pixel vertices drop in; the readback dirty rect widens by one pixel to
  cover them. Perspective UVs use a per-vertex a_q weight and clip coordinates
  pre-multiplied by w = 1/q, letting the hardware's own perspective divide
  interpolate a smooth UV varying beside the affine noperspective one. a_q == 0
  — the default, and what every prim gets with the feature off — makes w
  exactly 1.0 and selects the affine varying, so the faithful path is the
  pre-feature pipeline unchanged.

The PS1-visible GTE SXY FIFO stays integer either way: guest-side screen-bounds
culling and SXY readback are untouched, and this remains visual-only.

Also fixes a pre-existing concurrent-build race found while validating: every
runtime target runs embed_spirv.py over the same shaders, and each wrote
"<shader>.spv" next to the source, so under -j N one process deleted the file
another had just written (FileNotFoundError on a shader that compiled fine).
The intermediates now go to a per-invocation temp dir.

Parked-work provenance retained; see ENHANCEMENTS.md G1.

Co-authored-by: Kareem Olim <kareemolim@gmail.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-08-05 14:02:58 -07:00
Matthew Stanley 67757efac4 feat(runtime): add CHD discs and mod-owned loading modes 2026-08-03 22:32:17 -07:00
Matthew Stanley b2af4e89c9 feat(mods): add trusted game enhancement hooks 2026-08-03 15:17:39 -07:00
Alex Vanderveen ba276624b9 patch bad merge 2026-08-03 05:26:37 -04:00
Matthew Stanley 0cfa9fe0a8 runtime: add PaRappa timing debug controls 2026-08-01 16:11:11 -07:00
Matt Stanley cb0c00f397 Merge pull request #86 from luisg0c/fix/persist-dir-host-independent-path-check
fix(config): validate overlay_capture_persist_dir independent of host path grammar
2026-07-31 15:40:21 -07:00
Matthew Stanley 987c6eeced Revert "Merge pull request #87 from mstan/feat/ui-5p"
This reverts commit e01347cbca, reversing
changes made to 60cb60eee6.
2026-07-31 15:37:57 -07:00
Alex Vanderveen 070fc805c4 Merge origin/master into feat/ui-5p.
Keep 5p/ICE netplay host + lobby client; take master's cycle batching,
SDL3 defaults, and dropped vendored lib/recomp-ui.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-28 20:17:14 -04:00
Matthew Stanley 9aa5466da7 Add per-game audio buffer target 2026-07-28 12:23:06 -07:00
Matthew Stanley a292a7c136 Add activation-only widescreen cull margin 2026-07-28 11:08:03 -07:00
luisg0c 4ced15faf4 fix(config): validate overlay_capture_persist_dir independent of host path grammar
std::filesystem answers by the *host* path grammar, so this guard meant
different things on different hosts.

Under POSIX, "C:/outside" is a relative path whose first component is
literally "C:", a backslash is an ordinary filename character, and
"\\server\share" is a single relative component -- so Windows-authored
absolute paths and backslash ".." traversals were accepted. Under Windows,
a rooted "/outside" has no root name, so operator/ drops the project root's
relative path and the result lands at the drive root; and a drive-relative
"C:foo" is not absolute at all, yet replaces the whole path when the project
sits on another drive.

Scan the raw config bytes under both separator conventions instead, and
reject an embedded NUL. TOML forbids a raw NUL byte but allows the \u0000
escape that produces one, and the runtime hands this value on as a C string
(runtime/src/main.cpp:5236-5241 -> overlay_capture.c:230), so
"..\u0000keep" validated as a four-byte component and then truncated to ".."
at the point of use, escaping the project. Validating what the consumer
actually sees closes that gap.

recompiler/src/config_loader.h carries the contract next to the field and
still described the old, narrower rule; docs/AOT_OVERLAY_PLAN.md carries the
only prose statement of it. Both now match what is enforced. Note the doc
claims identical accept/reject across hosts, not identical meaning: a value
like "nested\dir" is accepted everywhere but still names different things.

The registered recompiler_patch_test assertion "parser rejects absolute
capture history directory" failed on POSIX before this; the suite is now
33/33 on macOS arm64.
2026-07-28 10:48:37 -03:00
Matthew Stanley e4e2aa7a7d runtime: add per-game radial stick response 2026-07-28 00:20:45 -07:00
Matthew Stanley 5ec343ab4f fix(bios): make the boot-skip flag mean the same thing on every BIOS
`bios_hle` (default on) is the player-facing "skip the BIOS and go
straight to the game" flag, and it did nothing under the bundled
OpenBIOS: choosing OpenBIOS sat the player in the boot animation while
the identical build on retail SCPH-1001 went straight to the game.

Root cause was ordering, not policy. The tier has two axes with
different per-image requirements:

  * boot-skip     needs only `shell_entry_phys`, works under pure LLE
                  (nothing is synthesized — the shell call just returns
                  immediately), so any image exporting it can skip
  * kernel-call    needs `deliver_event_ret`, the kernel's own
                  DeliverEvent `$ra`, or a delivered callback returns to
                  the wrong PC

main.cpp correctly refused the call axis on an image without
`deliver_event_ret` by assigning `bios_hle = false` — and then derived
`boot_skip` from that already-mutated flag. OpenBIOS omits
`deliver_event_ret` (B0 semantics unvalidated) but DOES export
`shell_entry_phys`, so an available skip was cancelled by an unrelated
refusal, and one flag meant two things depending on a BIOS detail no
player can see.

Both axes are now decided by one pure, dependency-free function,
`psx_bios_hle_plan()` (runtime/src/bios_hle_plan.c): the boot decision
reads the REQUESTED `bios_hle`, each axis is gated on only the anchor it
needs, and refusals are reported at startup instead of downgrading
silently. `psx_bios_hle_configure()` clamps `boot_skip` to
`shell_entry_phys != 0` as a backstop so the banner and `hle_dump`
cannot overstate what can fire. The main.cpp local is renamed
`bios_hle_requested` and is no longer written back from the plan — the
`session_reboot` path re-enters that block, so folding a refusal into
the request would have made a netplay rematch differ from first launch.

Also in this pass:

* A BIOS profile's `[runtime]` block was parsed into
  `BiosConfig::runtime` and read by NOTHING, and bios/OpenBIOS.toml
  carried an inert `bios_hle = false` there that read as the reason HLE
  was off on OpenBIOS. It was not. `load_bios_config` now REJECTS
  `[runtime]` in a BIOS profile: a profile states facts about an image,
  never preferences.
* `psx_icache_fastpath_test` had been unbuildable ("redefinition of
  `psx_advance_cycles`" — the test's counting stub vs the header's
  `static inline`), which blocked every runtime test registered after
  it. It now builds with `PSX_OVERLAY_DLL_BUILD=1`, the seam
  psx_cycles.h already provides for a TU supplying its own advance.

Tests: new `bios_hle_plan_test` pins the matrix, including a 64-case
sweep asserting the boot decision is independent of
`deliver_event_ret` and the call decision independent of
`shell_entry_phys`. Runtime ctest 15/15, recompiler ctest 30/30.

Validated on Ape Escape (SCUS-94423) built against this framework with
both backends linked, headless, all four cases:

  OpenBIOS default   bios_boot=skip to game; route=2 fire at vec 0x30000
                     ra=0xBFC06EA8 (the continuation OpenBIOS.toml
                     documents); in-game on screen
  retail default     bios_boot=skip to game; route=2 at vec 0x30000
                     ra=0xBFC0702C; in-game on screen; differs only in
                     bios_backend=HLE (LLE fallback)
  PSX_BIOS_HLE=0     bios_boot=real intro; ring empty (no hook)
  KEEP_INTRO=1       bios_boot=real intro; ring empty

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-07-27 16:36:48 -07:00
Matthew Stanley b699089d46 runtime: let games move display options into mods 2026-07-27 16:16:26 -07:00
Matthew Stanley 63d8f88dc5 runtime: group front-layer UI for widescreen 2026-07-27 15:54:23 -07:00
Matthew Stanley 2696c391b8 feat: add aspect-aware world participation 2026-07-27 10:48:41 -07:00
Matthew Stanley fa408aae50 Merge trusted mod plugin support 2026-07-26 21:05:03 -07:00