psx_mod_allow_direct_shortcut(PSX_MOD_SHORTCUT_*) lets a host shortcut
bound to one controller button act on that button alone. Without it a
one-button binding means Select + button, like the legacy encoding, so a
title can default to a one-button binding safely whether or not its mod is
enabled. While allowed (Rewind: while Rewind is enabled) the runtime claims
the host button: it never reaches P1's guest pad, and a claim made while it
is held lasts until release. Allowances clear at every mod session start.
Multi-button bindings behave as before.
game.toml [controller] direct_shortcut / direct_shortcut_button lets the
title default that shortcut to one button and tells the launcher to capture
it as one button (RECOMP_LAUNCHER_HAS_DIRECT_ASSIST_BIND). No title id is
checked anywhere.
Debug: set_input {"layer":"host", buttons, lx..ry, lt, rt} arms a
virtual P1 gamepad that feeds stage 1 of the offline input path (so sources,
title transforms and trigger values see it) and shortcut polling. A plain
set_input override still wins. Headless runs with the layer armed resolve
P1 through the same path and poll Rewind and the fast-forward latch; the
Rewind pause loop keeps the debug endpoint live and runs without a window.
Binding semantics and the claim latch are pure (psx_hotkey_pad.h) and
covered by psx_hotkey_pad_test.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
[controller] multitap = false in game.toml keeps offline play at two
standalone pads for a title whose game has no multitap support but whose
netplay seats more players (players still sets the netplay seat count).
settings.toml [controller] multitap and the launcher toggle still
override it. Without the key nothing changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reconciled with #470 (PGXP): gpu.c precise vertices take PGXP only under geometry correction and count it, then native-wide edge recovery; the PGXP triangle stat stays PGXP-only. Stubs and tests from both kept; stamps recomputed.
Master moved the textured-dot test stubs into gpu_exec_stubs.inc; the PGXP stubs move there too. pgxp_note_rect_bypass stub takes (int). Stamps recomputed.
1982e7dc included recomp_net/host_relay.h unguarded, so PSX_NETPLAY=OFF builds fail to compile (optional_netplay_auth caught it).
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Hook every GPR-writing ALU op; rebase onto the 11-bit GP0 field. Spider-Man ORs clip flags into SXY2 before storing it.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A renderer that keeps a screen X as SX << 16 tests the right edge against
a lui-held W << 16 and the left edge with bltz on the shifted value. No
explicit [widescreen.cull] kind fits either: bltz_sites compares the whole
register with -margin, and a lui at a screen_x site was a build error.
screen_x_sites now also takes
lui rt, W -> rt = (W + margin) << 16
bltz x, outside -> taken while SX < -margin (x = SX << 16)
Both are the identity at 4:3. The helpers are defined for native code and
for overlay shards; their math is in ws_cull_edge.h. The two prototypes are
written only for a config that lists screen_x_sites, so the generated code
of every other title is unchanged.
A failed start printed "built without recomp-net, or bind/peer invalid" for every cause. A player whose system held the UDP port read first that the build had no netplay.
The library answers a refused bind, a listen address it cannot read and a peer address it cannot use with the same -1. main.cpp now does those steps again (it reads the listen address as the library does, looks up a name, binds without address reuse) and reports the system's answer. The sentence names a cause only when the system does: address in use, access denied (a port range Windows keeps for Hyper-V or WSL, a port below 1024 elsewhere), address not available. Any other error gets the number and the system's text. The peer address is named only when the listen address opened and the peer address cannot be read. The build is named only when it has no netplay.
The sentences are in netplay_exit_reason.c with unit cases; a source guard checks the wiring in main.cpp.
Many PS1 renderers drop a primitive whose depth index falls past the end
of their ordering table. Raising that limit can write past the table, so
a title can now list such guards in game.toml with the register that
carries the index and the last safe value. While a trusted mod has called
psx_mod_set_draw_distance_clamp(1), generated main-EXE code clamps that
register before the guard runs, so far geometry is kept in the farthest
slot instead of popping in later.
- Off by default and reset at every session start, so netplay and every
title that lists no sites run unchanged; with no sites the generated
code and declarations are byte-identical.
- Main executable only: the full instruction word is the guard,
generation fails on a mismatch or a load-delay hazard, captured
overlay code keeps its own code (the sites are not part of the overlay
config identity), and the dirty-RAM interpreter applies the clamp
where the game's text image holds the listed word.
- Compatibility: code_generator.cpp is a codegen-hash input, so the
codegen hash and the overlay-cache tag change for every title, sites
or not. Savestates from earlier builds are refused, and overlay shards
rebuild once on first visit. Generated code and behaviour are
unchanged for a title without sites or with the switch off.
- Tests: draw_distance_codegen_test (config rules, emission, guards,
declarations, overlay identity) and draw_distance_interp_test (store,
switch, interpreted guards); the session-reset guard covers the switch.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
With every drawn vertex precise, a title can still show cracks where the game
culled a face on its integer winding. R4's road emitter runs NCLIP on each
far row and skips rows whose native height rounds to 0. Natively their
neighbours meet at the same integer y. With PGXP they sit at their exact y,
and the skipped row becomes a strip of sky across the road beyond the start
gantry, at 4K and in 2P. tri_mixed cannot see this: the missing face draws
no triangle.
Precise culling (DuckStation's "PGXP culling", minimal footprint):
- While geometry correction is armed, gte_nclip computes the exact
determinant of the three SXY register shadows (pgxp_gte_nclip_precise:
live, current words, the GPU's own truncation agreement and tolerance).
When its sign is nonzero and differs from the integer MAC0's, MAC0 takes
that sign, with the rounded exact doubled area (at least 1) as magnitude.
FLAG keeps the integer overflow bits.
- It changes guest-visible MAC0, so it is a `culling` option of
psx.enhancement.pgxp, false in the builtin, with no [video] key, so netplay
(which clears every mod) never runs it. It needs geometry correction.
It is held off in every pass compared against another execution: the
speculative and replay passes, and the whole overlay shadow diff
(psx_overlay_shadow_diff_active), interpreter pass included.
- Counters whenever geometry correction is armed: nclip_precise,
nclip_disagree (exact sign differs: the culled-face crack exposure),
nclip_corrected. TCP pgxp {"culling":0|1}; env PSX_PGXP_CULLING.
Session arming in one place (src/pgxp_session.cpp, psx_pgxp_session_arm):
main.cpp's renderer setup now calls the same code the test drives. The arming
takes the mod request (reads and clears it), so a session with an empty plan
cannot inherit an earlier session's request even if the reset were skipped.
pgxp_session_test now links the real mod runtime, the builtin plugin and the
builtin manifest, and covers:
- a default-on override;
- a netplay clear, with and without the reset;
- the off switch;
- the options;
- env overrides;
- pgxp_mod_only.
[video] pgxp_mod_only (game.toml, default false): for a title that ships
PGXP through the mod, the mod is the one switch. The [video]
geometry_correction / perspective_texturing / pgxp_cpu_mode values are not
applied, from game.toml or the player's settings.toml. The launcher also
hides its Perspective textures row (has_geometry_precision = 0). That row
showed "off" while a default-on mod corrected textures, and it could keep
texture correction on after the player switched the mod off.
geom_correction also reports texture_correction and the culling switch.
R4, Helter Skelter: 28.7% of NCLIPs disagree in sign. With culling, the
ladder is gone in same-frame 4K captures (1P straight and crest, 2P VS), and
the game emits about 24% more triangles. Its packet buffers peak at 60%.
fp_identity over 12000 frames: stock, PGXP off, and PGXP on with culling off
are IDENTICAL; with culling on, the RAM writes differ from the attract race
on, while cyc/mmio/mc agree throughout.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Two opt-in [video] keys for titles whose build carries the PGXP hooks, both
live-tunable through the TCP pgxp verb. Defaults keep today's behaviour.
pgxp_position_fallback (default true). false skips the position-cache tier
in pgxp_get_precise_vertex and stops gte.cpp filling it. At hook-build
coverage the cache only hands unrelated 3D fractions to CPU-built 2D
polygons (a HUD needle, menus). Beetle and DuckStation leave their vertex
cache off by default.
pgxp_preserve_projection (default false). true makes RTPS/RTPT shadow the
exact projection, from the unshifted MACs and a true divide
(pgxp_project_precise), instead of the GTE's integer-IR one. That removes
the residual wobble of near geometry. Guest SXY, MAC0 and FLAG are
untouched. Only sf=1 vertices with no IR, SZ3 or divide saturation qualify,
and the rest keep the IR path. The exact position can sit a little below the
guest integer or more than a pixel above it, so truncation agreement becomes
the window -4 < precise - native < 5 px, a sanity bound over the IR path's
own error (on R4 every on-screen vertex falls within -1.9 .. +2.6 px). A
packet half at or beyond the GTE saturation limit must still agree exactly.
The tolerance clamp now measures the offset both ways. [widescreen]
precise_nclip reads the same shadows, so in a title that sets both, its
branch sign follows the exact projection too.
Observability: geom_correction reports both switches, ppp_produced, and
geometry-corrected triangles split into all-precise, mixed and native. The
mixed share is the mesh-cracking exposure of G1.1.
Tests: pgxp_test (fallback off, window edges, saturation, two-sided
tolerance, the exact projection and its qualifiers, triangle census);
gte_register_access_test (RTPS/RTPT guest state equals the oracle with the
mode on, and the shadow is the exact projection); video settings test
(defaults, parsing, type check). gpu_gp0_history_guard_test's gpu.c stubs
gain pgxp_note_triangle.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OpenGL textured program read the GP0(E2h) texture window from a
uniform, so every window change ended the open textured batch (batch
reason 5). Titles that tile far textures with per-primitive windows pay
for that in draws: Ridge Racer Type 4's split screen changes the window
about 515 times a game frame and drew about 180 batches, against 17 with
batching on. On the native-wide path every batch that reaches the margins
is drawn again into the wide surface; under heavy load on an M4, 2P with
widescreen at Match display ran at 39-43 guest VBlanks/s and 1P at 50,
against 50-58 and 57 with batching on.
The window now rides in each textured vertex (a_twin, the E2h low 20 bits
as one float) like texpage and CLUT already do, and the shader decodes it
there; the uniform and the window-replay queue's copy of it are gone.
With the default unchanged, batches still split at every window change,
so every vertex carries the batch's window and the image is the same as
before: the new test's oracle passes on the unmodified renderer too, with
the same digests at 1x, 4x, 9x and in the windowed 9x/18x mode.
[video] texture_window_batching = true (game.toml only, default off) lets
primitives with different windows share a batch. The image is the same:
an opaque batch draws its colour in one painter-ordered pass and
semi-transparent primitives are still drawn one per batch. While mask
checking is on the window stays a batch key, because an opaque batch
fixes the stencil for its STP=1 texels after its colour pass.
PSX_GL_TEXWIN_BATCH=0|1 overrides it for a run and the TCP command
gl_texwin_batch switches it live. Software and Vulkan are unchanged.
Tests: gl_scale_invariance_test gains mode "twin" (alternating windows
over opaque, semi-transparent, set-mask, mask-check and bilinear prims,
into the native-wide margins): off and on give the same native VRAM,
frame at S and wide surface, on draws 17 batches instead of 46 and flushes
only for the 5 mask-check window changes. Each run also checks an oracle
that does not depend on the renderer: raw rects through all four windows
into a 4-bit and a 15-bit page must give, pixel for pixel in the native
VRAM and at the centre of each pixel at S, the texel the PS1 window rule
picks from the VRAM as read back. A decode or packing error both settings
share fails it (checked with (v_twin >> 11) and with off_y left out of the
packed window). video_enhancement_settings_test checks the key's default
and opt-in.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A BIOS module's glue.c includes overlay_dispatch_preamble.c.inc and psx_bios_module_glue.c.inc by name. The tcc path compiles against a BOM-stripped copy of the include directory, and that copy held only *.h, so every tcc module build stopped at "include file 'overlay_dispatch_preamble.c.inc' not found" and the runtime fell back to the bundled BIOS.
Copy .inc files into the tcc include copy as well. Game overlay shards were not affected: compile_overlays inlines the preamble text instead of including it.
When straight-line interpretation reaches a clean page, the interpreter
hands the PC back to static dispatch. If that PC is game text whose live
bytes differ from the boot EXE, dispatch refuses the compiled code and
enters the interpreter again, which hands back one instruction later. A
page of such text cost about a thousand dispatch round trips.
This happens during the retail BIOS intro on profiles whose shell runs
interpreted in RAM (the SCPH5552 profile has no shell seeds). In a game
whose boot EXE later owns that RAM, every shell instruction took the
round trip; on one PAL title the intro ran at 17 fields/s on the host
instead of 42.
Straight-line flow now keeps interpreting while the next PC is game text
that psx_game_text_native_ok() refuses, and re-tests every instruction,
so it hands back at the first PC dispatch accepts.
interp_refused_game_text links the production interpreter into a fixture:
from a dirty page into refused text, it hands back once and dispatch runs
the compiled code at the first accepted page. Before this change the
interpreter returned at the first clean page. A control checks that a
clean page outside game text is still handed back at once.
The v3.0 Japanese (SCPH-5500, 09/09/96) and US (SCPH-5501, 11/18/96)
retail BIOS images join SCPH-1001, SCPH-101 and SCPH-5552, so every v3.0
region (J, A, E) has a profile.
Both images match SCPH-1001 v2.2 byte for byte in the kernel,
ROM [0x80, 0x18000). SCPH-5501's reset vector is identical too; SCPH-5500's
differs in 24 bytes. The shell differs in both. So each profile is
SCPH1001.toml with its own identity (name, id, rom, sha256 pin, stem): the
copy windows and install slots are kernel properties and carry over
unchanged. The shell runs through the dirty-RAM interpreter, as for
SCPH5552.
Each image gets its own seed list, derived by the same rule as
phase2_ghidra_seeds_SCPH5552.json: the SCPH-1001 corpus filtered to seeds
whose 64-byte ROM window is identical in both images (534 kept, 569
dropped), plus the two architectural vectors and 6 A0/B0/C0 kernel-table
targets. The files carry only addresses, labels and rationales, plus the
SHA-256 of both ROMs.
- psx_bios_known_images.h: CRC-32 FF3EEB8C (SCPH-5500) and 8D8CB7E4
(SCPH-5501), both 512 KiB. A build that does not link these backends
builds one from the player's own dump as a loadable module, like
SCPH-5552, and the netplay lobby can name the image.
- main_cli.cpp: map SCPH5500 / SCPH5501 dumps to their profiles.
- build_cli.py: package both profiles with the CLI framework.
- docs/BIOS_SELECTION.md: list them among the module-built images.
Preserve current segmented dispatch, active-plan mod hooks and software scale limits. Extend function filters through the active plan, retain the last-store pointer with overlay ABI 25, and keep default config fingerprints stable. Refresh BIOS emitter stamps after byte-identical regeneration and reconcile regression fixtures.