Enhancement arenas (GPU DMA aperture, Expansion-1 mod memory) were untracked, so Crash widescreen drew every vertex native with no depth. Adds depth stats/clear causes to pgxp, depth to pgxp_tri_ring, input_route_start at_frame, integer number keys in game.toml.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Fixes Ape Escape terrain seams: CPU-built subdivision midpoints and lwl/lwr-copied
grid corners drew native next to precise neighbours. Adds pgxp_tri_ring/pgxp_store_ring.
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A DMA or host store (psx_host_write_*) has no instruction hook, and
validation on read only rejects a shadow whose word changed. A writer
that stores the identical word left the old shadow believed, although
two projections that round to the same integer X/Y carry different
fractions and depth. memory.c now drops the shadow of every RAM or
scratchpad word a DMA or host store touches (pgxp_invalidate_word); the
drop is journaled, so a render pass rolls it back with its other writes.
pgxp_untracked_store_test drives the real memory.c store paths against
the real engine (fails 9 checks without the fix).
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 cases that could still draw a triangle with precise and native corners:
- The rectangle shortcut bypass now needs all four vertices of an
axis-aligned textured quad to be dataflow-precise. A quad with only some
precise corners, such as a CPU-built sprite with one corner copied from
SXY, keeps the shortcut and counts as rect_partial; two triangles would
have mixed precise and native corners. R4's facade quads are fully
precise (rect_partial 0 in every R4 run).
- Under preserve-projection, RTPS now applies the GPU's truncation window
itself (pgxp_ppp_accept): an exact projection outside it is not
shadowed, the vertex keeps the IR path's shadow, which always agrees, and
it draws precise instead of being rejected to native at the GPU
(ppp_window_fallback counts these). The exact math is skipped while the
hooks record nothing (pgxp_active(): speculative passes).
Also:
- The prepare_precise_triangle comment no longer claims a native neighbour
can only disagree by the sub-pixel fraction. Under preserve-projection it
can disagree by up to the window.
- gpu_textured_dot_nw_shift_exec_test links with -dead_strip on Darwin,
as isc_store_test does. GNU --gc-sections kept it from linking on macOS,
so the rectangle cases never ran there. The test now covers no, one and
all four precise corners.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A textured quad whose four packet vertices form an axis-aligned rectangle in
integer screen space and in UV takes the 2D rectangle shortcut
(gr_draw_textured_rect), which draws the native integer position with no
perspective. That is right for a CPU-built sprite, but a GTE-projected world
quad can land on such a rectangle too: a facade seen straight on. Its
neighbours are drawn precise, so at a high internal resolution the shortcut
left a background-coloured seam along the shared edges. On R4's Helter
Skelter start straight it showed as an L-shaped sky line along a building
panel at 4K, with tolerance off, both with and without preserve projection.
When geometry or texture correction is on and any of the quad's vertices
resolves to a dataflow-precise position (pgxp_probe_precise_vertex: the same
decision as pgxp_get_precise_vertex, not counted), the quad is drawn as its
two triangles like any other world quad. A CPU-built sprite has no dataflow
shadow and keeps the shortcut. With PGXP off nothing changes. geom_correction
counts the bypassed quads (rect_bypass).
Tests: gpu_textured_dot_nw_shift_exec_test gains the quad case (PGXP off,
on without a precise vertex, on with one); pgxp_test checks that the probe
does not count; gpu_gp0_history_guard_test's gpu.c stubs gain the two new
pgxp entry points.
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>
psx.enhancement.pgxp did nothing at boot. Its activation armed geometry and
texture correction, then main.cpp's renderer setup, which runs after
activation, applied the [video] baseline and switched both back off. Only the
[video] keys could turn PGXP on.
The activation now records a request (pgxp_mod_request), cleared at every
session start in reset_mod_owned_presentation(). The renderer setup arms PGXP
from the [video] baseline OR the request (pgxp_session.h); the validation env
overrides (PSX_GEOMETRY_CORRECTION, PSX_PERSPECTIVE_TEXTURING,
PSX_PGXP_CPU_MODE) still win. The g_video_* values stay the player's
settings, so the mod never leaks into a launcher seed, and a netplay session,
whose plan is cleared, keeps the [video] keys alone, as before.
No change for a title whose mod is off. pgxp_session_test drives the real
builtin activation and the resolver in main.cpp's order.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Emitter side of the value-propagation engine (ENHANCEMENTS.md G1.10):
- recompiler/src/pgxp_hook_emitter.{h,cpp} (new, in the codegen-hash
surface): ONE hook grammar shared by BOTH emitters, wrapping loads/
stores/COP2 transfers and precision-carrying ALU ops (moves, add/sub,
or-merge, shifts, LUI, HI/LO, muldiv) with PGXP_*() macros. Clobberable
sources are captured into locals before the statement. Precision-
destroying ops (AND/XOR/SLT/...) are deliberately unhooked - validate-
on-read drops their stale shadows for free.
- code_generator.cpp: central append in translate_instruction; LWC2/SWC2
emissions capture the RAW loaded/stored word (a masked GTE register
write must validate against the word as loaded).
- strict_translator.cpp: translate() wraps translate_impl()'s c_code with
the shared grammar; deferred load-delay variants hook the psx_ldd temp.
- BIOS regenerated, fingerprints verified CHANGED (SCPH1001 + OpenBIOS;
42.6k/12.2k macro sites). Base runtime + 33/33 tests green against it.
- Gating: macros expand only under -DPSX_PGXP=1. runtime.cmake grows a
PGXP option on psxrecomp_add_runtime_target (defines PSX_PGXP=1 +
PSX_OVERLAY_FLAVOR=2, _pgxp exe suffix) and PSX_PGXP_VARIANT to build
the sibling target from the same generated tree (framework runtime +
game wrapper). No ABI version bump needed: the appended PGXPHooks
table is NULL-tolerant and the flavor half of the ABI tag rejects any
host/DLL flavor mix.
- Shard caches: flavor folded into the cache tag (cg<V>_<hash>_gc<hash>
_f<flavor>) in overlay_loader.c + compile_overlays.py so base and pgxp
caches never share a directory; compile_overlays.py passes
-DPSX_PGXP=1 when flavor bit 2 is set; overlay preamble forwards
psx_pgxp_* through the table. PSX_OVERLAY_CODEGEN_VER 9 -> 10.
- Env overrides PSX_GEOMETRY_CORRECTION / PSX_PERSPECTIVE_TEXTURING /
PSX_PGXP_CPU_MODE (validation path: free-running headless boots arm
from the first vertex); census grew produced/swc2_stores and the pgxp
verb reports suppress/active gate state.
Validated: PSXRecomp_pgxp.exe boots the recompiled BIOS with hooks armed
(engine active, per-vertex lookups flowing; the BIOS shell has no RTPS
content so produced=0 there is correct - coverage measurement moves to a
3D title in phase 3).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>