A fresh Final Fantasy 7 scaffold failed to build with SDL3/SDL.h file not
found on every translation unit that includes psx_sdl.h, even though CMake
had reported "using prebuilt/system SDL3".
cmake-clang-v1 compiles against its own sysroot and never searches the host
/usr/include:
#include <...> search starts here:
.../v1.0.14/sysroot/usr/include/...
End of search list.
so a host package can be found and still be unusable. Two paths hit that.
SDL3: the toolchain ships a compatible SDL3 under deps/lib/cmake/SDL3, and
runtime.cmake already prefers it — but only via _PSX_TOOLCHAIN_PREFIX_HINTS,
which was built purely from RETCOMM_TOOLCHAIN_DIR and friends. The setup
wizard passes -DCMAKE_C_COMPILER=<toolchain>/bin/cc without exporting any of
them, so the hint list was empty and find_package fell through to
/usr/lib/cmake/SDL3. The compiler's own location is the hint that cannot be
forgotten, so it is now derived from CMAKE_C_COMPILER as well.
Vulkan: find_path landed on /usr/include, and CMake drops that directory from
include lists (adding it explicitly would disturb the system header order), so
the -I never reached the compiler at all. The block already knows how to build
gpu_vk_renderer.c as an inert stub when Vulkan is absent; it just never got
there, because "found" was decided by a path existing rather than by the header
compiling.
Both now verify before they believe, via a _psx_header_compiles() helper that
actually compiles the header. It takes LIBRARIES for an imported target and
INCLUDES for a bare directory, and the distinction is load-bearing: the first
attempt read INTERFACE_INCLUDE_DIRECTORIES off SDL3::SDL3, which is NOTFOUND
because SDL3 carries its headers on a transitive SDL3::Headers target, so the
check rejected a perfectly good SDL3. Asking through CMAKE_REQUIRED_LIBRARIES
resolves the interface transitively and discriminates correctly: the
toolchain's SDL3 passes, the host's SDL3 under this compiler fails.
Vulkan falls back to the stub it already had. SDL3 fails loudly instead,
naming the toolchain copy to point SDL3_DIR at — silently switching to
FetchContent there would collide with the SDL3::SDL3 target find_package has
already defined, and a wrong SDL is worse than a stopped configure.
Verified on the Final Fantasy 7 project that surfaced this: from a clean build
directory, with no overrides and with every toolchain environment variable
unset, configure selects the toolchain's SDL3, reports the Vulkan stub with the
reason, and psx-runtime builds and links (321/321).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017AwqKPQFnK3x7xQbK4PQKj
The runtime's filename was derived three times from three copies of the same
window title. runtime.cmake sets OUTPUT_NAME from
MAKE_C_IDENTIFIER(WINDOW_TITLE); psxrecomp_cli.py rebuilt it from --exe-name;
and the in-runtime self-compiler rebuilt it again from
codegen_setup.exe_basename. The rule is identical in all three, so they agree
right up until a game is renamed and only one copy is refreshed. After that the
build links <new>.exe while both consumers look for <old>.exe, and the failure
is reported as
build succeeded but binary missing: ...\<old>.exe
Build failed. Fix the errors above, then rebuild manually.
on a build that had no errors at all, with the executable sitting in the build
directory under the name CMake chose. Seen on Revelations: Persona, where CMake
emitted Revelations__Persona_Recompiled.exe and the setup host wanted
Revelations_Persona__Recompiled.exe — same algorithm, colon in a different
place in each side's copy of the title.
runtime.cmake now writes the OUTPUT_NAME it actually set to
<build-dir>/psxrecomp_exe_name-<target>.txt, and both consumers prefer that
over their own derivation. Written at configure time with file(WRITE) rather
than file(GENERATE): _psxrt_exe_name is a plain variable carrying no generator
expression and does not vary per configuration, so one fixed filename is
correct on multi-config generators too. The marker holds the name only, never a
path — the consumers already know their build directory and only ever got the
basename wrong.
A build tree without the marker still falls back to exe_basename, so older
trees and foreign build directories behave exactly as before.
The error messages were misleading in both directions and are now specific: a
name disagreement says the build succeeded and names both sides, while a
genuinely absent binary lists the executables that are present. The PGO
training path resolved the binary the same guessing way and now shares the
resolver.
Adds test_runtime_exe_name.py, which pins all four behaviours against the real
Revelations: Persona names, and relaxes the GAME_PROJECT_SETUP.md note that
previously just warned people to keep the two values in sync by hand.
Verified: the resolver against a real Ninja build tree whose OUTPUT_NAME and
marker were produced by the same two lines runtime.cmake uses, including the
exact stale-name case that failed in the wild; test_cli_project_packaging still
passes; the self-compiler change compiles clean. The runtime.cmake edit itself
was exercised in an isolated reproduction rather than a full runtime configure
— this checkout has no BIOS dump or generated game C to build one.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017AwqKPQFnK3x7xQbK4PQKj
Three changes, one fix and two pieces of visibility.
1. ws_ui_group_assign gains a third join: consecutive in the ordering-table
walk AND touching. WipEout 3's speed/shield readout ends at x=288 and the
meter bar it belongs to starts at x=306 with a different CLUT, so neither
shared columns nor a matching key could join them. The readout stood alone,
its bbox midpoint 256.5 landed in the middle third against a screen centre of
254, and it stayed pinned near centre while its own meters went to the right
edge -- a 2 px gap in 4:3 opening to 159 framebuffer pixels at 32:9.
No anchor threshold can fix that: the genuinely centred "pit ... check"
message sits FARTHER RIGHT (midpoint 300.5) than the mis-anchored readout, so
any threshold reaching the readout drags the message with it. Submission order
is the only signal that separates them, and the array already carries it.
Simulated against seven captures spanning three aspects and two code states,
this forms three unions and changes exactly one anchor: [225,288] centre
becomes [225,486] right. The other two unions ([208,240]+[240,361],
[364,447]+[450,473]) are within an anchor. Confirmed in-race at 32:9: 7 runs,
lap readout left, speed readout and meters together on the right.
2. ws_ui_groups reports why primitives were NOT admitted to the partition.
Each gate in ws_ui_prepass_add is a silent return, so a primitive left at its
raw 4:3 X while its neighbours are squashed -- which is how a HUD mark ends up
stranded mid-screen -- was indistinguishable from one never drawn. In-race at
32:9: opcode 367, not_axis 410, everything else 0.
3. geom_correction reports perspective-UV arming with a real denominator.
perspective_triangles could previously only be compared against gp0_draw,
which counts untextured primitives that are correctly never armed, so it read
as a coverage figure without being one. texcorr.attempts counts exactly the
textured triangles reaching the predicate. In-race at 32:9: 199599 submitted,
96.15% armed, 3.85% rejected for a missing per-vertex Z, 0% for correction
disabled or a missing packet address.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
(cherry picked from commit 572b9210d8)
Two defects that only bite a MinGW C++17 build, both still live on master.
runtime.cmake declared only `c_std_11` on the runtime target, leaving the C++
dialect to whatever the parent project happened to set. A game CMakeLists that
omits CMAKE_CXX_STANDARD therefore compiled mod_packages.cpp as a pre-17
dialect. State the runtime's real requirement instead of inheriting it.
mod_packages.cpp captured a structured binding (`for (const auto& [id, ...])`
with `id` used inside a lambda). That is a C++20 extension; clang accepts it
silently, MinGW/GCC at C++17 rejects it. Bind the map entry to ordinary locals
before the lambda.
Cherry-picked from ae9432a3 on feat/rbengine-bak, minus that commit's unrelated
lib/recomp-net submodule bump. Verified by building psx-runtime on Linux.
psxrecomp_add_game_runtime() (added in #110) is unusable with
PSX_RECOMP_UI=OFF and real generated game C linked — a documented,
supported config ("PSX_RECOMP_UI OFF for headless/generated builds",
BUILDING.md) that nothing currently exercises. Found while bringing up
a new title with recomp-ui intentionally left out initially.
Two independent breaks in that combination:
1. main.cpp calls psx_game_codegen_forward_if_built() from main()
whenever PSX_HAS_GAME_CODEGEN is set (i.e. any time real game C is
linked), but only *declares* it under the narrower RECOMP_LAUNCHER
guard. Any no-launcher build with generated game C fails with
"use of undeclared identifier". Introduced a precise
PSX_HAS_CODEGEN_SETUP_HOST macro (set only when a codegen-setup
host is actually linked) and re-guarded both the declaration and
the call site on it instead of the mismatched pair of guards.
2. psxrecomp_codegen_host.c was unconditionally added to every game
target's extras, but it unconditionally #includes recomp_launcher.h
from the recomp-ui submodule. Any --no-recomp-ui build fails with
"recomp_launcher.h file not found". Gated its inclusion on
PSX_RECOMP_UI.
Verification:
- Built and ran a new title (headless: PSX_RECOMP_UI=OFF, real
generated game C, no launcher) end to end: OpenBIOS -> game entry ->
continuous gameplay, clean atexit shutdown, no crash.
- Regression-checked the one existing title that actually calls
psxrecomp_add_game_runtime() (TombaRecomp; every other shipped title
calls the lower-level psxrecomp_add_runtime_target directly and
never reaches this code): built TombaRecomp's real production config
(PSX_RECOMP_UI=ON, ENABLE_SETUP_WIZARD, recomp-ui launcher, mod
catalog, setup-host/no-disc mode) against this branch via
-DPSXRECOMP_ROOT override, no submodule surgery. Clean build, ran to
the launcher with no regressions. Both changes are conditionally
gated on flags Tomba always sets the opposite way, so this is
expected and was confirmed empirically, not just by inspection.
Not covered by this PR: CMAKE_CXX_STANDARD 17 is also never inherited
by a game repo that include()s runtime.cmake directly (as opposed to
add_subdirectory()-ing runtime/CMakeLists.txt), which silently
compiles text_xlate.cpp's std::filesystem usage against whatever the
host compiler's default standard is. Six of seven other title repos
work around this by setting it themselves; happy to send a follow-up
if that's wanted here instead of per-repo.
main.cpp includes iphlpapi.h and calls GetAdaptersAddresses for the
launcher's netplay local-address discovery, but no CMake file links the
library. MinGW builds link regardless; MSVC does not link iphlpapi
implicitly, so every MSVC runtime build fails at link with an
unresolved GetAdaptersAddresses.
The dirty-RAM interpreter reused a suffix-aware validity check when resuming generated code after a patched call site. If an entry prologue had been overwritten but its resume suffix still matched, execution could enter stale AOT code and fall through to another generated piece without redispatch.
Emit a whole-entry predicate for straight-line handoffs, retain suffix checks for explicit guest transfers, and fail closed for older generated dispatchers until regeneration.
Validated with the MGS PAL 6,875-frame route, manual codec-to-heliport gameplay, focused continuation-guard tests, and a current-upstream recompiler build.
psx.enhancement.pgxp (game_id \"*\") in the builtin catalog: one Mods-page
toggle that arms sub-pixel vertex precision + perspective-correct
texturing with the user-validated defaults (tolerance clamp 0.5px), plus
a cpu-mode option. Default off = the faithful floor (G1.9). Fully
effective on PGXP-variant binaries; reduced-but-safe coverage elsewhere
(the always-emitted swc2 tier + interpreters, validate-on-read).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Titles that call psxrecomp_add_runtime_target directly (Ape Escape)
never went through the game wrapper, so the variant only existed for
wrapper callers. The clone now replays ARGN recursively at the end of
the target builder itself under PSX_PGXP_VARIANT, which covers every
caller; the wrapper folds an existing clone into its tail config and
the framework CMakeLists drops its now-redundant explicit block.
First real-title census (Ape Escape, worktree build vs this framework,
memory-mode only, boot->title->attract): 79-90% dataflow hits per
10s window vs the 5.2% position-cache ceiling, 0 value mismatches,
0 truncation rejects, every dataflow hit carrying depth; residual
native is 2D HUD/sprite content that must stay native. The G1.9
census gate is passed; visual screenshot_hires A/B requires a windowed
run (headless has no hi-res mirror - scale:1 fallback).
Co-Authored-By: Claude Fable 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>
Keep recomp-net at occupied_mask (d65918f); master's pin was an ancestor.
Brings in rewind/save-state controls from master.
Co-authored-by: Cursor <cursoragent@cursor.com>
Wire lib/retcomm-rbengine beside recomp-net and replace MotK in-tree sched,
hash_confirm, input hist, snap ring, and rb_post with thin PSX shims so the
ecosystem can share invent/pacing policy outside each title facade.
Co-authored-by: Cursor <cursoragent@cursor.com>
Pass generated shard paths via a SOURCES_FILE so cmake -P is not killed by
CreateProcess's ~8191-char limit, and keep has_bios on for OpenBIOS-only
setup SDKs so the first-run wizard still offers SCPH1001.
Co-authored-by: Cursor <cursoragent@cursor.com>
083959df added runtime/src/nd_intro_ot.c + nd_intro_ot.h, and 02b08dae /
later commits call psx_nd_note_wood_batch_ot(), psx_nd_wood_batch_ot(),
psx_nd_note_sibling_ot_hint() and psx_nd_sibling_ot_hint() from gte.cpp and
debug_server.c. runtime.cmake keeps an explicit source list (no globbing), so
the TU was never compiled and every link failed with undefined references.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>