Adds toolchain_ready and toolchain_note to the selfcheck JSON: the
same pack + system-compiler judgement as wizard page 0, without the cache
healing, so scripts and maintainers can check a machine headlessly (the
macOS Command Line Tools case above had no headless signal at all).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Browsers and Explorer name a repeat download "…-windows-x64 (1)". In
such an install folder the deferred rebuild helper
(recomp_deferred_rebuild.cmd) died after the build with
"\…was unexpected at this time": cmd expands %GEN_MARKER% / %EXE_FINAL%
when it parses a ( ) block, and the ")" in the path closed the block
early, so the freshly built game never started.
Move the two path-bearing error messages out of parenthesized blocks
(if ... goto labels). Also launch the helper as cmd.exe /C ""<path>"", the
documented form that survives cmd's quote stripping when the path holds
special characters.
No emulation or runtime behaviour changes; Windows setup-host only.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setup host's toolchain health check requires <pack>/bin/clang and
ld.lld and links with -fuse-ld=lld. The macOS pack
(cmake-clang-v1-macos-universal) ships CMake, Ninja, ccache and Python only
and relies on the system compiler, so on every Mac the check failed: the
wizard deleted latest/, renamed the version folder to .broken, a fresh
download ended in "Extracted toolchain but cmake is missing or will not
run", and page 0 could never be passed, with or without Xcode installed.
On macOS a pack without clang is now the expected layout. Readiness
separately checks Apple's Command Line Tools (xcode-select -p, then a tiny
/usr/bin/clang compile+link). When they are missing, the wizard shows a
repair note telling the player to run xcode-select --install, and the
downloaded pack is left in place. Windows and Linux are unchanged.
No emulation or runtime behaviour changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The setup host located itself with realpath("/proc/self/exe"), which only
exists on Linux. On macOS it failed, so a host launched from Finder (cwd =
$HOME) could not find its project tree: Generate & rebuild never appeared
and forward-to-built-game never fired. Take the path from dyld's
_NSGetExecutablePath there (as recomp-ui's launcher_model.c already does);
Linux and Windows are unchanged.
The netplay rollback "binary path" diagnostic had the same /proc read and
logged "(unknown)" on macOS; it uses the same source now.
No emulation or runtime behaviour changes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
cli_fail_msg replaced whatever the CLI said on exit 3 with a flat
"Disc verification failed (wrong dump)." With the CHD reader, the CLI's
own line can be "... is a CHD and the CHD reader (libchdr) is not built
for this kit ... this does not mean your dump is bad", and the wizard then
contradicted it. cli_tail_note already captures that line; show it, and
fall back to the old text only when the CLI said nothing.
Carried over from #369, which was closed in favour of this change.
78 of 105 published releases lack the key, and the runtime never initialises
the overlay loader without it. The packager now refuses a staged game.toml
without it unless --ship-without-overlay-cache-because states why, and the
setup host's self-check reports overlay_cache_configured so CI can assert it.
(cherry picked from commit a030f4843d8e55eafed4cb42eafc6d74bb89c086)
Focused port of the fork's fix/setup-host-bios-deadlock feature (10 commits) onto current upstream master, per the 2026-09-10 decision to port rather than rebase (the fork line and upstream share no merge base).
Content: the codegen host accepts any recompiled BIOS stem instead of only OpenBIOS/SCPH1001 (psx_bios_known_images.h); the runtime checks the setup host before validating a chosen BIOS and the first-run loop breaker reports a cause-specific reason; the CLI decodes subprocess output as UTF-8; --setup-selfcheck verifies a headless setup; the rbengine preflight names the recursive init. Two port conflicts resolved: the new CTest registrations in recompiler/CMakeLists.txt are kept (the names exist only in this change), and runtime.cmake takes the new recomp-ui guidance text. Cubic P3 finding fixed: the sibling PSX_NETPLAY FATAL_ERROR now also names --recursive.
Evidence: python syntax checks pass for psxrecomp_cli.py and the new tests; runtime/tests/test_codegen_host_bios_stems.py runs or skips only on a missing RECOMP_UI_ROOT. No full runtime build or retail route is claimed here. Developed with AI assistance (Cline, 2026-09-10).
The launcher and studio ship as Retro Launcher and Retro Studio now,
from the RetroPortingToolKit org, so the old product name is gone from
display text, documentation and release asset filenames.
The split is by case, and it is not cosmetic. CamelCase RetComM was
always the product; lowercase retcomm and RETCOMM_ are identifiers --
the C++ namespace, the binaries, the data directories, the CMake and
environment variables, the Cloudflare service names, the readme markers.
Renaming those would orphan existing installs for no benefit, so they
are untouched: 1101 lowercase retcomm and 205 RETCOMM_ still stand in
the launcher alone.
Four things are deliberately still spelled RetComM, and three of them
would have been real breakage:
- RetComM-Data is CamelCase but it is a directory on disk --
<exe_dir>\RetComM-Data\{runtime,config,data} for a portable install.
Renaming it strands every portable user's config, toolchains and
catalog cache.
- TechnicallyComputers/RetComM-Launcher in readme_has_launcher() is the
historical half of a both-slugs test. Renaming it makes it a duplicate
of the line above and sends every port built before the transfer back
to reporting its launcher section missing.
- _LEGACY_LAUNCHER_RE matched "^## RetComM Launcher". A sweep renamed it,
which would have silently stopped it matching every README already out
there; it now accepts both headings.
- The portable-stub lookup gained the pre-rename filenames back as
fallbacks, because old zips stay on the releases page and a rollback
downloads one.
Release assets become Retro-Launcher-* / Retro-Studio-*. That is safe
for installed clients: pick_launcher_asset() gates on *linux*/*windows*/
*macos* plus extension and arch, not on the product name, and the
Windows portable stub is identified by its RCM1 trailer rather than its
filename.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013DpaQqVguHFpBuCmdoFYWY
recomp-net, retcomm-rbengine (now rbengine), retcomm-toolchains (now
RetroPorting-Toolchains), the catalog and the launcher moved from
TechnicallyComputers to the RetroPortingToolKit org. GitHub still
redirects the old repo URLs, so nothing is broken today — but this repo
is what every port inherits its .gitmodules from, so the old owner
propagates into each new clone until it is fixed here.
.gitmodules now names the new URLs. The toolchain repo is updated in the
CI action default, the codegen host, fetch_toolchain.sh, toolchain_pack
and package_setup_host, and the scaffolder's recomp-net / rbengine
defaults follow.
Two things the mechanical sweep would have missed:
- render_launcher_block() held a second copy of the launcher slug split
across two string literals, so a search for the full slug did not see
it and every regenerated README kept the dead owner. It is now derived
from LAUNCHER_REPO, which is the only place the slug lives.
- readme_has_launcher() matched one exact slug. Pointed at the new one
alone it would report every port built before the transfer as missing
its launcher section, and append a second copy to any README carrying
neither the markers nor a "## RetComM Launcher" heading. It now
accepts both.
The scaffolder's default GitHub owner for new ports becomes
RetroPortingToolKit. Existing ports are unaffected — they carry their
own owner in the README and remote.
psxrecomp, snesrecomp and recomp-ui did not move and still point at
mstan. Submodule paths (lib/recomp-net, lib/retcomm-rbengine) are
unchanged: they are pinned by every port and are not repo slugs.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018xfKkiEkh87zL1EpE5Ty55
Follow-ups to the marker-skew hardening, closing the remaining legs of
the generate+build loop class:
- Loop breaker: host_prepare_generate stamps a successful generate into
.psxrecomp_last_generate at the project root. When apply() is about to
reopen the wizard because sources are missing and the stamp is recent,
the wizard's prepare note becomes a diagnostic naming exactly which
gate is unmet (the expected *_dispatch.c and/or the BIOS backends) and
what generated/ actually contains, instead of silently asking for
another round. The stamp is dropped once sources are visible so a much
later manual wipe of generated/ reads as a fresh first run.
- Preflight: generate now refuses up front when the project root sits
under the Windows temp directory (Explorer's run-from-inside-the-ZIP
extraction, which evaporates) or is not writable, with instructions,
instead of failing downstream in ways that present as the same loop.
- CI lint: tools/ci/check_boot_exe.sh cross-checks the three baked
copies of the boot-EXE name (game.toml boot_exe/exe, CMakeLists
GEN_MARKER, codegen_setup.c gen_marker_relpath) and the setup-release
template gains a fail-fast "Lint boot-EXE name consistency" step, so a
skew fails the maintainer's release instead of the player's install.
op_emit_ci_workflow's step-name drift detection flags older titles
automatically. Script is bash-3.2-clean for macOS runners; verified
against fixture repos (consistent / skewed / boot_exe override /
single-source) and real ports - where it immediately caught
Tomba2Recomp's placeholder gen_marker_relpath ("SLUS_01234" vs
SCUS_944.54), a live instance of the reported loop.
Both gitignore templates learn the new sidecar names.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfPHUBEYiSCwaGiwRc8ea7
A boot-exe name skew between game.toml, codegen_setup.c's
gen_marker_relpath, and CMakeLists' GEN_MARKER used to loop the setup
wizard forever with every stage green: generate accepted any
*_dispatch.c and reported ok, the rebuild silently produced another
setup host (REQUIRE_GAME_C defaults OFF and nothing set it), and the
Windows deferred helper relaunched it straight back into first-run
setup. Reported by self-build users on ports whose three baked
@BOOT_EXE@ copies disagree.
Make each stage assert what the next one gates on:
- cmd_rebuild now configures with -DPSXRECOMP_REQUIRE_GAME_C=ON and the
canonical -DPSXRECOMP_FORCE_SETUP_HOST=OFF (the legacy BPE_ alias only
maps ON). Defaults precede --cmake-extra so a deliberate override
still wins via cmake's last-one-wins.
- generate errors when the produced dispatch name differs from the
expected marker, naming both; the setup host passes
--gen-marker <basename of gen_marker_relpath> so generate checks the
exact name the launcher and CMake will later demand.
- The Windows deferred helper re-resolves the exe from
psxrecomp_exe_name-<target>.txt after the build (the pre-build guess
goes stale on a title rename) and verifies marker + exe exist before
relaunching, pausing with a report-to-maintainer message otherwise.
Relaunch/heal and both failure gates exercised under Wine cmd.
Multi-disc titles keep a single boot EXE (update_disc_set.py only
maintains discs/disc_serials), so the strict name check is safe there.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KfPHUBEYiSCwaGiwRc8ea7
Records the wizard's disc picks where the runtime actually looks, so a
standalone-wizard install reaches the same end state as a RetComM build.
The gap this closes. The runtime's hot-swap roster is built from
game.toml [game] discs (game_discs = gc.discs). disc.cfg is the
mounted-image cache and read_cached_path() takes only its first line via
std::getline. So the previous commit -- which taught the wizard to ASK
for every disc and write them all to disc.cfg -- recorded discs 2..N
where nothing reads them for the roster. The player located three images
and still got a one-disc roster.
Nothing in the standalone path wrote [game] discs. The tooling that does
lives in retcomm-studio, which is why the RetComM path produced a working
3-disc config and the wizard could not.
tools/new_project_layout/probe_disc.py synced forward
tools/new_project_layout/verify_disc_set.py vendored (new here)
Both from retcomm-studio @ a2b71ed9 (MIT, (c) Alex Vanderveen; psxrecomp
is PolyForm-NC, so this is permissive-into-stricter and the notice is
recorded in THIRD_PARTY_ATTRIBUTION.md). psxrecomp's probe_disc.py was a
stale fork predating multi-disc support: 90 lines behind, with its only
8 unique lines being older revisions of the same functions, so the sync
drops nothing psxrecomp-specific. A single-disc project still emits
`disc = "..."` byte-identically to before.
update_disc_set.py is new, and is the piece neither repo had. probe_disc
--write-game-toml renders a COMPLETE game.toml, which is right when
scaffolding and destructive afterwards -- it would discard a live
project's [video], [controller], [widescreen], [netplay] and its
comments. This probes each located image, verifies them as one set, and
then edits ONLY disc / discs / disc_serials / required_disc_fps. It skips
any key whose value it already agrees with, so it does not reflow a
hand-formatted array and a re-run is a no-op. It refuses to write at all
when verify_disc_set rejects the set: images that are not one program are
a mistake to surface, not a config to record.
Generate runs it before invoking the CLI (the CLI reads game.toml).
Failure is reported and non-fatal -- Generate needs only the boot image,
and refusing to build because disc 3 could not be probed is worse than
building with a single-disc roster. Absent script (older SDK checkout) or
fewer than two located discs are both early returns, so single-disc
titles are untouched.
Verified against Final Fantasy VII's three real images:
- from a single-disc game.toml, produces discs + disc_serials
(SCUS-94163/64/65) + required_disc_fps identical to the values the
RetComM path had already produced in that repo;
- all 114 remaining lines of config and comments byte-for-byte intact;
- a second run changes nothing.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWiDNcWuNtDWHE7Pnhq4jp
The runtime launcher has built a multi-disc roster from game.toml
[game] discs for a while. The SETUP host never did -- it did not read
that key at all -- so on a first run the wizard could not know a title
was a multi-disc set and asked for a single image. The other discs were
discovered missing much later, mid-game, at the first swap.
Read [game] discs in psxrecomp_codegen_host_apply and publish it as
gi.discs / gi.num_discs, matching the runtime's rule that a roster only
appears when there is more than one image. A single-disc title publishes
nothing and its wizard is byte-for-byte the page it was before.
The reader is ~60 lines rather than a TOML dependency: this host shells
out to Python for every real parse, and one array of strings does not
justify linking a parser. Only [game] discs is recognised; anything else
is skipped rather than guessed at. Relative entries are absolutised
against the project root, because the launcher existence-checks what we
publish and its cwd is not necessarily that root -- a relative path would
otherwise render as "missing" on a perfectly good install.
Implements persist_setup_discs (recomp-ui, appended additively to
RecompLauncherCGameInfo). disc.cfg becomes one path per line in disc
order. That is additive in the direction that matters: a reader taking
only the first line still gets disc 1, exactly what it got before. A slot
the player has not located is written as an EMPTY line rather than
skipped, so line N is always disc N and filling one in later cannot
renumber the others. Nothing located at all leaves any existing disc.cfg
alone instead of replacing it with blank lines.
The BIOS half delegates to host_persist_setup with rom_path NULL, which
runs its bios.cfg logic and skips its single-path disc write, so the two
entry points cannot drift on BIOS handling.
Verified against Final Fantasy VII's real 3-disc game.toml by driving
psxrecomp_codegen_host_apply directly: num_discs = 3, persist_setup_discs
set, all three absolute paths in disc order.
Requires the matching recomp-ui change to be visible; without it the
roster is published to a launcher that ignores the field, which is inert
rather than broken.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UWiDNcWuNtDWHE7Pnhq4jp
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
The cache was accepted forever once any cmake ran: default min version was
empty, so a Deck (or any host) that cached a pre-sysroot pack (<=1.0.13)
never re-downloaded and kept compiling against host headers — absent on
stock SteamOS — with objects that mis-link after a pack upgrade
(__isoc23_strtoul).
Both resolvers now default the floor to 1.0.14, the first pack with the
Linux build sysroot. This is a framework floor, not a per-title pin:
RETCOMM_TOOLCHAIN_MIN_VERSION still overrides in either direction, and
"0" / "off" / "none" disables it (pre-floor behavior).
- tools/toolchain_pack.py: DEFAULT_MIN_VERSION in default_min_version();
the ensure_toolchain stale-probe passes min_version="0" so its log line
still sees below-floor packs.
- host: toolchain_min_version() mirrors the same default and disable
tokens; below-floor caches now fail host_portable_cmake_ready and the
wizard downloads /releases/latest.
Verified: Python — floor/override/disable semantics, real v1.0.10 pack
rejected and v1.0.14 accepted, an isolated cache holding only v1.0.10 no
longer resolves (visible again with floor disabled), full cache resolves
to v1.0.14. C — same matrix via a unit harness incl. manifest-less dirs;
both platform branches syntax-checked.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vc7zPEnfUFMhMtJzUEruyb
(cherry picked from commit 00f0a9a9bb)
Stock SteamOS (Steam Deck) has no /usr/include and no GL dev symlinks, so
the wizard rebuild died at recomp-ui's find_package(OpenGL REQUIRED) with
"Could NOT find OpenGL (missing: OPENGL_INCLUDE_DIR)" — reported to the
user only as "psxrecomp rebuild failed (exit 1)" (recomp-ui issue #34).
The cmake-clang-v1 >=1.0.14 pack already bundles a jammy sysroot with GL
headers and a legacy libGL.so stub, but only clang.cfg used it; CMake's
find_* still searched the host.
- psxrecomp_cli: when the active pack carries sysroot/usr/include (Linux),
pass -DCMAKE_SYSROOT=<pack>/sysroot -DOpenGL_GL_PREFERENCE=LEGACY to both
the game and the emitters configure. LEGACY is required: the sysroot has
legacy libGL.so + GL/gl.h but no GLVND dev files (glx.h/libOpenGL.so/
libGLX.so), so the GLVND path cannot complete and OpenGL::GL would never
be created. Skipped when the caller already sets CMAKE_SYSROOT.
- codegen host: run_cli_posix/run_cli_win now route the child's stderr
into the progress pipe and keep a last-lines capture (CliTail); on
failure err_msg carries the most recent error-looking line — JSON
progress rows contribute their message, raw rows (compiler output,
Python tracebacks) contribute as-is. Exit-3 disc-verify text unchanged.
Verified against a stock-SteamOS simulation (bwrap: tmpfs over
/usr/include, GL dev symlinks removed) using the shipped v1.0.14 pack:
configure previously failed exactly as on the Deck; with the sysroot args
FindOpenGL resolves from the pack, the build links, and the binary runs.
CliTail paths exercised with real compact-JSON rows and raw stderr.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Vc7zPEnfUFMhMtJzUEruyb
(cherry picked from commit 0bb45442f8)
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>
Probe cmake --version; if the cache or latest pointer is unusable, clear
the bad pointer and stamp, then re-download (or install from zip) so
Generate & rebuild can recover without manual cleanup.
Co-authored-by: Cursor <cursoragent@cursor.com>
Install into versioned <tag>/ with latest as a pointer (not extract-into-
latest), honor RETCOMM_DATA_HOME/TOOLCHAIN_CACHE, and search both XDG and
~/.local/share so install.sh packs resolve during ensure/rebuild.
Co-authored-by: Cursor <cursoragent@cursor.com>
Codegen host hands g_wizard_bios (or OpenBIOS clear) to the CLI with
--disc/--bios. CLI skips SCPH1001 emit when the backend already exists.
Co-authored-by: Cursor <cursoragent@cursor.com>
Relative bios.cfg / settings picks like bios/SCPH1001.BIN broke after
rebuild when cwd was build/; launcher verify also ignored exe-anchored
resolve. Absolutize sidecars, hydrate from bios.cfg, and resolve in
ae_bios_verify.
Co-authored-by: Cursor <cursoragent@cursor.com>
Keep wizard BIOS memory + sidecar writes to project root/build, and restore
NULL bios_path leave-untouched persist semantics from the sidecar PR.
Co-authored-by: Cursor <cursoragent@cursor.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>
Setup-host cwd and the rebuilt binary directory often differ on Windows;
resolve --bios from those sidecars and chdir to the project root before
exec on POSIX so relaunch sees the same picks.
Co-authored-by: Cursor <cursoragent@cursor.com>