Resolve stash conflict in psx_lobby_client by keeping getpeername-based
relay rewrite with the RFC1918-aware path, and land pending rollback
hookup/runtime changes.
Master fixed the embed_spirv.py concurrency race independently and better
(tempfile.TemporaryDirectory cleans up on early-return error paths too), so the
rebase conflict was resolved in master's favour and our mkdtemp version
removed. This branch stays SPU-only.
Records the trap that invalidated the first Tomba 2 measurement: game.toml's
overlay_autocompile_cmd names a recompiler path, and when it is absent
autocompile fails every attempt, no shard is ever built, and the runtime
silently interprets everything. Tomba 2 expects a worktree-relative path the
documented build recipe does not produce; MMX6 hardcodes an absolute path into
the main repo that works only by luck of local layout. Check autocompile_status
before trusting any perf number from a fresh worktree.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The first FMV A/B was run on a fresh worktree whose overlay shard cache was
cold AND whose autocompile could never work: game.toml points at
psxrecomp-v4/recompiler/build-t2/psxrecomp-game.exe while the documented build
recipe builds the recompiler at build-recompiler/ in the worktree root, so it
failed 8/8 with WinError 2 and the runtime silently ran interpreted
(dispatch_native = 0). The '40 fps / ~19 underruns per second' attributed to
master in the previous commit was that broken setup, not master.
Re-measured against the fully warm cache with the pre-SPU exe as a control in
the same worktree: interp_share 0.7813 vs 0.7775, exc_share 0.2386 vs 0.2381,
39.1 vs 38.0-38.4 fps. Identical within noise, so the DSP work is free.
Separately characterises the real pre-existing defect: the FMV is 96% native by
dispatch count but 78% interpreted by wall clock with 24% in exception
handling, even warm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Measured, not asserted:
- BIOS LLE boot proves the reverb engine runs and rings down after the dry
signal stops; noise LFSR observed shifting with correct parity feedback.
- Tomba 2 intro FMV A/B against the exact parent commit shows the added
per-frame DSP work is performance-neutral (min p50 40.0 vs 40.3 fps) and
does not starve the CD bus or the host sink. The FMV's 40 fps and its
~19/s host underruns are PRE-EXISTING on master.
- Records all ten places the documentation ran out as an oracle-comparison
work list, and the two merge prerequisites (rebase onto moved master; bump
recomp-ui to 99558ee or no title compiles).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Closes the four gaps named in issue #103 and lands the separable, verified
parts of PR #102. See docs/internal/SPU_FIDELITY_103.md for the per-piece
disposition and the oracle-verification queue.
SPU DSP (issue #103), clean-room from hardware documentation:
- Reverb: the documented 22050 Hz engine — vLIN/vRIN input, same-side and
crossed different-side IIR reflections, 4-tap comb early echo, both
all-pass filters, vLOUT/vROUT output, and the work-area address advance.
Gated on SPUCNT bit 7 for WRITES only; reads, IRQ checks, address advance
and output always run, as on hardware. Addressing is confined to
[mBASE, 0x80000). Per-voice sends from EON, CD send from SPUCNT bits 0+2.
- Noise: 16-bit LFSR with the documented parity feedback, clocked at the
SPUCNT bits 8-13 rate; NON voices substitute it for their ADPCM sample.
- Volume sweeps: all 24 voice L/R plus main L/R. Bit 15 selects sweep vs
direct; sweep is a live envelope on the same rate machinery as ADSR, and a
guest read of a sweeping register returns the live level.
- Capture buffers: CD L/R and voice 1/3 output written to SPU RAM, so a game
that parks the IRQ address there still gets its interrupt.
- SPU IRQ (I_STAT bit 9): checked at every SPU RAM access class — FIFO, DMA
both directions, ADPCM block fetch, capture writes, reverb work-area
accesses — gated on SPUCNT bit 6, mirrored in SPUSTAT bit 6, acked by
clearing SPUCNT bit 6.
The CD-only fast path in spu_render was removed rather than extended: the
SPU now has per-frame work that must run in every path, and an FMV (XA audio
with zero active voices) is exactly the CD-with-reverb case.
This is a clean-room implementation from the psx-spx/nocash register map and
documented algorithm. Beetle's source was deliberately not consulted: it is
GPL-2.0-or-later and this project is PolyForm Noncommercial. PR #16 was
parked for exactly that reason and PR #13's reverb was separately wrong (it
gated on bit 15 of dAPF1, an address offset, instead of SPUCNT bit 7). The
one place this knowingly deviates from hardware is the 22.05 -> 44.1 kHz
reconstruction filter, which the documentation does not specify; it is
isolated in a single function and flagged for oracle comparison.
CAUSE.IP2 (from PR #102):
IP2 is combinational on R3000A — it mirrors the interrupt line and falls the
instant the guest acks I_STAT or masks the source. It was being OR'd in at
delivery and never cleared, in TWO places (the compiled path and
psx_interpreter.c, the latter missed by PR #102), leaving a phantom pending
interrupt that can spin a kernel dispatcher looping on CAUSE.IP & SR.IM.
psx_irq_refresh_cause_ip2() is now the sole writer of bit 10, refreshed at
raise, I_STAT ack, I_MASK write, HLE context restore and power-on. Verified
against the Beetle oracle's irq.cpp/cpu.cpp rather than asserted, and
covered by test_cause_ip2_combinational.
Mid-dispatch audio pump (from PR #102), with a fix: routed through a gate
that mirrors sdl_audio_update's mute/sink decision. Pumping unconditionally
from the VBlank edge, as submitted, would have pushed real audio during every
turbo-load hard mute and defeated the freeze-in-place mute model.
SPU RAM DMA readback (from PR #102): DMA4 in the SPU->RAM direction wrote
literal zeros; SPU RAM is readable memory and titles carry state through it
across an Exec boundary.
Also fixes an unrelated build race found on the way: tools/embed_spirv.py
wrote its intermediate SPIR-V to <source>.spv, so the two runtime targets
that embed the same shaders raced on one path and one deleted the file the
other was reading.
Co-Authored-By: Alexandros Mandravillis <Alexbeav@live.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A contributor bringing up a new title hit four dead-ends in a row, three of
which were the same missing step wearing different masks: `regen_bios.sh`
builds the BIOS emitter but never configures it, and nothing said so.
- "no recompiler build dir found (set PSXRECOMP_BIOS_BUILD)" pointed at an
env var when a fresh clone has no CMake tree to point it at. Both discovery
failure and an explicit override now name the actual missing step and print
the configure command.
- Setting PSXRECOMP_BIOS_BUILD skipped the CMakeCache.txt check that discovery
applies, so an unconfigured directory reached `cmake --build` and died with
cmake's bare "Error: could not load cache". Validate the override too.
- A CMake configure that FAILS still writes CMakeCache.txt — the cache lands
before the generate step, which never runs after a FATAL_ERROR. So "has a
cache" did not mean "is buildable": the directory left behind by his failed
SDL and BIOS-backend configures had a cache and no build.ninja, and the
build reported only `ninja: error: loading 'build.ninja': GetLastError() =
2`. Read the generator out of the cache, check for its build files, and say
what happened. Discovery skips such a directory and keeps looking rather
than selecting it and dying; an unrecognized generator falls through to
cmake instead of inventing a failure.
All errors now go to stderr (the old one went to stdout).
Docs: the README's framework build block configured the recompiler and jumped
straight to the runtime build, omitting the required BIOS regen between them —
exactly the hole he fell into. It is now the same numbered 1/2/3 sequence
docs/BUILDING.md already used. BUILDING.md gains troubleshooting entries for
the two CMake half-configured-directory errors, the fresh-clone "No recompiled
BIOS backend available", and PSXRECOMP_BIOS_BUILD being resolved relative to
the framework root rather than the shell's cwd.
Verified: both new failure paths and the half-configured-directory case
reproduce and now self-explain; discovery precedence (build-t2 over build),
explicit-override precedence, Ninja/Makefiles/unknown generators, and a CRLF
cache all behave; end-to-end `regen_bios.sh --config bios/OpenBIOS.toml`
still emits (Dispatch entries: 3911).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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>
Root-caused on Tomba 1: overlay_autocompile_cmd's bare 'python' resolved
through PATH to the devkitPro MSYS2/Cygwin python (usr/bin precedes the
Windows install), which segfaults (exit 0x0B00, signal 11) under the
kill-on-close job spawn BEFORE writing a byte — so every compile failed
with '(no output captured)' and nothing pointed at the cause; the title
silently ran interpreted.
At configure time, resolve the command's first token exactly as
cmd.exe /C will (SearchPathA with .exe), print the binding once, and
warn loudly when msys-2.0.dll/cygwin1.dll sits beside the resolved
interpreter, recommending the Windows launcher form 'py -3 ...'.
Also fail loudly up front when the token doesn't resolve at all.
Doc'd in ASYNC_OVERLAY_COMPILE.md. Title game.toml fixes (python ->
py -3) ship separately in each game repo.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Floor ensure-toolchain at 1.0.4 (libxml2 + clang.cfg -fuse-ld=lld) and
prepend pack lib/ to LD_LIBRARY_PATH when activating the toolchain.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep local codegen/FMV cutover/zlib host work while taking upstream
tests, FPS telemetry, and mod-provider wiring.
Co-authored-by: Cursor <cursoragent@cursor.com>
SDL_main.h is a single-header implementation in SDL3. Include it only from the translation unit that owns main so MinGW emits one WinMain instead of one per SDL-using source.
Extract the useful diagnostic from PR #13 into an opt-in, title-agnostic boot
write-range trace with TCP-queryable context.
Co-authored-by: NyperYuhgard <93950153+NyperYuhgard@users.noreply.github.com>
Both targets are defined unconditionally and every build produces them, but
neither had an add_test() entry, so neither could run and neither could fail.
This is the same gap the codegen tests below them had before they were wired
in, and docs/TESTING.md states the rule directly: "an unregistered test cannot
fail, and a test that cannot fail is not a test."
Both were run manually before being registered and both pass. They are
self-contained -- no BIOS profile, no generated C, no psxrecomp-game -- so
unlike the neighbouring tests they need no WORKING_DIRECTORY.
l2_structural_test -> L2 structural: 239/239 ok
reachable_discovery_test -> ALL PASS
ctest -N goes 33 -> 35, so the three places that state the count are updated
to match. Note they said 29, which was already stale before this change.