Commit Graph

28 Commits

Author SHA1 Message Date
Shokunin ff570512ed docs: ENHANCEMENTS G1.11/G1.12, PGXP as a title's default, with R4 numbers
G1.11: the hook-flavor runtime, the mod armed at session start, dataflow
only, exact projection, and the rectangle shortcut. It covers what tri_mixed
can and cannot see, and the R4 census after every change:
- 1P: 99.91% dataflow, 0 of 389k drawn triangles mixed;
- menus, attract and 2P VS: every truncation reject is a vertex at the
  saturation limit.
The wobble table drops the preserve-projection row, which is 0 by
construction.

G1.12: precise culling (the culled-row crack, the rule, why it is mod-only,
packet headroom, identity, cost under R4's default stack) and
[video] pgxp_mod_only.

TCP_COMMANDS: the new geom_correction fields and the pgxp verb's culling
knob. MOD_PACKAGES: the session arming takes the request; how a title
ships PGXP on. GAME_PROJECT_SETUP: pointer to pgxp_mod_only.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 06:08:55 -07:00
Shokunin 2ddb2e0ec4 Let a title build its one runtime as the PGXP hook flavor
psxrecomp_add_game_runtime gains a PGXP option and forwards it to
psxrecomp_add_runtime_target, which already supports a PGXP primary
(-DPSX_PGXP=1, overlay flavor 2, published flavor file, no _pgxp suffix and no
clone). Until now a title could only get the hook flavor through
PSX_PGXP_VARIANT's side-by-side clone, or by leaving a bare PGXP in the
forwarded tail, where an earlier multi-value argument such as
CODEGEN_SETUP_SOURCES or GEN_FULL_GLOB swallowed it as a file name.

The generated C already carries the PGXP_*() hook sites, so a title's
committed generated/ does not change. Default unchanged: titles that do not
pass PGXP build the base flavor as before.

game_runtime_pgxp_test runs the real function body under cmake -P with the
runtime target stubbed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-01 02:56:05 -07:00
Alex Vanderveen f180a32ec1 bios: commit the OpenBIOS and SCPH-1001 backends; releases link both
Decision (Alex, 2026-09-30): the recompiled BIOS carries the same risk as
the recompiled game, which titles already commit, so the retail backend is
compiled in rather than built on the player's machine. The retail image is
still never shipped: the runtime needs its data at run time and accepts only
the exact image the backend was built from, so the player supplies their
own dump as before.

- generated/{OpenBIOS,SCPH1001}_{full,dispatch}.c and their .emitter.sha
  stamps are tracked (.gitignore un-ignores exactly those); regenerated with
  the current emitter.
- bios_emitter_fingerprint.sh no longer hashes the ROM image: the profile's
  SHA-256 pin stands for it and the emitter refuses any other image, and CI
  has no dump, so hashing it made every CI configure read "stale".
- runtime.cmake: the staleness check covers every LINKED stem and
  -DPSXRECOMP_BIOS_STALE_FATAL=ON makes a stale stamp fail configure.
- game-release.yml: no CI-side OpenBIOS emission (generate_openbios.sh is
  gone); configure with BIOS_STALE_FATAL and assert both backends linked.
  check_generated.sh asserts the submodule carries both backends tracked
  and no dump is tracked anywhere.
- The player-side module build stays for a known image a build does not
  link (SCPH-5552).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 20:27:41 -04:00
Alex Vanderveen 874df9b5bb release: ship bundled, compiled games from committed generated/ C
Retire the setup-host release model (CI wiped generated/ and shipped
emitters + sources + a wizard so every player regenerated and rebuilt the
title). Titles now commit generated/ and CI ships the compiled game.

- docs/ci/templates/game-release.yml replaces setup-release.yml: gate on
  committed game C (tools/ci/check_generated.sh), emit the OpenBIOS backend
  in CI (tools/ci/generate_openbios.sh), configure with
  PSXRECOMP_REQUIRE_GAME_C=ON and assert the full path was taken, package
  with tools/package_game_release.sh, verify the zip is a game not a kit.
- tools/package_game_release.sh: exe, assets, bundled OpenBIOS, configs, mod
  catalog + overlay toolchain via release_stage.py, notices; refuses sources,
  emitters, generated C, BIOS dumps. clear_generated.sh removed;
  package_setup_host.sh marked retired.
- Scaffold: gitignore templates track generated/, package_release.sh.in
  wrapper, setup_project.{sh,ps1} commit generated/ after Generate.
- Project Studio: audit flags ignored/untracked generated/, retail BIOS C
  and setup-host workflows; merge_gitignore un-ignores generated/; ops emit
  the bundled packager + workflow; generate_ci uses the new template.
- Docs: docs/ci/BUNDLED_RELEASES.md replaces HOST_ONLY_RELEASES.md.
- Tests: tools/tests/test_bundled_release_layout.py; fix pre-existing
  false positives in the packager-surface and overlay-static tests.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-30 17:11:14 -04:00
Alex Vanderveen 833c591720 scaffold: setup_project writes a starter .recomp.json (recomp.fyi spec)
New projects describe themselves to community lists (recomp.board et al.)
via .recomp.json at the repo root: game, system PS1, type recomp,
toolchain psxrecomp, status exploring, and original region + serial.

write_recomp_json.py takes the serial from disc_probe.json and derives
region from the Sony serial prefix, falling back to --region (which
defaults to USA and would mislabel PAL discs). It only fills missing
fields, so re-running it never clobbers a maintainer's edits.

Both gitignore templates ignored /*.json at the root, which would have
silently dropped the file from the initial commit; un-ignore it.

Closes #406

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 00:07:26 -04:00
Alex Vanderveen 922e8c5a07 Rename the product from RetComM to Retro
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
2026-09-09 17:32:19 -04:00
Alex Vanderveen 3b5bf48305 Follow the RetroPortingToolKit transfer in submodule URLs and tooling
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
2026-09-09 00:39:14 -04:00
Alex Vanderveen d371d74421 tools: generate_ci -- the release workflow for an existing project, and nothing else
sh psxrecomp/tools/generate_ci.sh            # in the project
    powershell -File psxrecomp\tools\generate_ci.ps1
    ... [root] [--check] [--force] [--dry-run] [--zip-prefix X] [--title X]

Project Studio already knows how to emit the setup-host release workflow
(emit_ci_workflow, behind `apply` and `git install-ci`), but reaching it
meant a migration plan or a commit-and-push. This is the single-purpose
face of that operation: it resolves the template from the project's own
psxrecomp submodule, takes the zip prefix from
scripts/package_setup_release.sh so CI uploads the zips the packager
built, writes .github/workflows/release.yml, and stops.

--check reports missing / placeholder / stale (step names the template
has and the file lacks) with exit 1, so a fleet can be audited without
writing anything; only --force overwrites. The .sh and .ps1 are thin
launchers over the one Python implementation.

Verified read-only against two ports: ApeEscapeRecomp reports stale
(four emitter/ccache steps missing), TombaRecomp reports current.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0173EhspvAdRQc9DZdDr97Hg
2026-09-05 18:53:24 -04:00
Matthew Stanley e6ce5006b4 docs: move root and stale docs into docs/ and docs/internal/
The repository root carried seven markdown files that were not onboarding
material, plus two that had been superseded by the code they described. This
sorts them: reader-facing docs to docs/, working notes to docs/internal/, and
the two superseded ones merged into the docs that replaced them.

Moved to docs/:            WIDESCREEN.md, ENHANCEMENTS.md, TCP_COMMANDS.md
Moved to docs/internal/:   PRINCIPLES.md, ISSUES.md, DEBUG.md, GTE_LM_FIX.md,
                           and from docs/: bios-hardening.md, overlay-status.md,
                           overlay-plan.md, overlay-recompilation-design.md,
                           overlay-discovery.md, TOMBA2_EXC_CONSULT_RESPONSE.md,
                           CASE_A_AOT_GAP.md, audit_inventory.md
Moved to docs/internal/upstream/: UPSTREAM_2026-08-20.md

The root keeps only what belongs there: README.md, CONTRIBUTING.md, LICENSE,
CLAUDE.md, THIRD_PARTY_ATTRIBUTION.md, RELEASE_NOTES.md, the dotfiles and
psxrecomp_cli.py.

MERGES

SPLITGEN_MIGRATION.md folded into docs/BUILDING.md as "Split generated C". Its
migration instructions were stale in a way that mattered: it told every game
repo to hand-edit its CMakeLists and add a file(GLOB ...) over
<serial>_full*.c, but runtime/runtime.cmake now does that itself. GEN_FULL_GLOB
is a multi-value argument of psxrecomp_add_game_runtime(); the loop at
runtime.cmake:1985 makes each pattern absolute against CMAKE_CURRENT_SOURCE_DIR,
expands it with file(GLOB), and concatenates the hits into
GAME_GENERATED_FULL_C. So a title names the pattern and gets however many shards
a regeneration produced. The rewritten section describes that, the two-step
fallback when the globs match nothing (GEN_FULL_FALLBACK, else <serial>_full.c
derived from the dispatch marker by substituting _dispatch.c), and the one
caveat the code actually has: that file(GLOB) is plain, not CONFIGURE_DEPENDS,
so a regeneration which changes the shard COUNT needs a re-configure to be seen.

psxrecomp-toml-readme.md folded into docs/GAME_PROJECT_SETUP.md as "EXE-only
autofill (psxrecomp-toml)", placed after "Disc autofill (probe_disc.py)" and
before "Project Studio" so the two autofill paths sit together. Its documented
flags were checked against the usage text in recompiler/src/main_toml.cpp:28-42
and against the argument parser at :150-165 -- all seven options match with no
drift, so the flag list is reproduced as-is. Added a pointer that probe_disc.py
is preferable when a cue exists, since that tool does the same EXE-header work
plus disc identity and netplay gates.

FUNCTIONAL FIXES

tools/gen_tcp_commands.py wrote to REPO/"TCP_COMMANDS.md". After the move that
path does not exist, so the generator would have created a new stray root file
and --check would have failed against it forever. Retargeted to
REPO/"docs"/"TCP_COMMANDS.md", along with the seven docstring and message
strings that name the path.

Running it surfaced two further problems, both fixed here:

  * The doc was genuinely stale -- pre-existing drift, not introduced by this
    commit (the moved file is byte-identical to origin/master, and no
    debug-server command table is touched here). Four commands registered since
    the last regeneration were missing from the index -- cdrom_timing_dump,
    savestate_status, ws_ui_groups, xprobe_watch -- and scanline had gained a
    backend. Regenerated: 307 -> 311 commands, and the change is confined to the
    AUTOGEN block (the hand-written head and tail are byte-identical to master).
    tools/gen_tcp_commands.py --check now exits 0.
  * DOC.write_text() has no newline argument, so Python translated every newline
    to os.linesep. On Windows that rewrote all 585 lines of the file to CRLF and
    turned a four-line index update into a whole-file diff -- measured: 589 CRLF
    / 0 LF after a run, against 0 CRLF / 585 LF in the repo. Now written through
    an explicit newline="" handle, verified to leave the file at 0 CRLF.

runtime/include/debug_server.h:9 and runtime/src/debug_server.c:8 cited "TCP.md
and DEBUG.md". TCP.md has never existed in this repo under that name; both now
name docs/TCP_COMMANDS.md and docs/internal/DEBUG.md.

LINK AND REFERENCE FIXES

Every inbound reference to a moved file was located by grepping each basename
across the whole tracked tree, not just the markdown. Markdown hrefs in
README.md, CONTRIBUTING.md, docs/ARCHITECTURE.md, docs/COMPILING_OVERLAYS.md,
docs/AOT_OVERLAY_PLAN.md, docs/RELOCATION_MANIFEST_FORMAT.md,
docs/config_schema.md, docs/OVERLAY_XREF.md, docs/TCP_COMMANDS.md and several
docs/internal/ files now point at the new locations; where two files became
siblings under docs/internal/ the path was shortened rather than lengthened
(bios-hardening.md's reference to overlay-discovery.md, for instance).
docs/COMPILING_OVERLAYS.md's "in this folder" phrasing was wrong once two of the
three files it listed moved, so it now names internal/ explicitly.

24 source-comment mentions of ENHANCEMENTS.md sections G1.x across 19 files
(recompiler, runtime, cmake) were retargeted to docs/ENHANCEMENTS.md with the
section numbers untouched. docs/SHADOW_ENHANCEMENTS.md is a DIFFERENT file and
was deliberately excluded.

20 rationale strings citing docs/CASE_A_AOT_GAP.md across the four
recompiler/seeds/*.json files were updated. Done as a byte-level substring
replacement rather than a JSON round-trip: two of those files are stored with
CRLF endings and two with LF, and re-serializing them reformatted 5,700 lines.
The byte-level version changes exactly 20 lines and each file still parses.

References that look similar but point elsewhere were deliberately left alone,
and are worth naming so a future pass does not "fix" them:

  * "recomp-template PRINCIPLES.md" / "NES recomp template PRINCIPLES.md" in
    CLAUDE.md:43, docs/SHADOW_ENHANCEMENTS.md:28,
    docs/internal/HLE_SCHEDULER_CARVEOUT_PLAN.md:5,
    runtime/src/debug_server.c:4934 and tools/parity_diff.py:8 are a different
    repository's file. Only the five that mean this repo's PRINCIPLES.md were
    updated.
  * Every remaining bare ISSUES.md mention is a GAME repo's tracker
    (MegaManX6Recomp 2/7/8/9, TombaRecomp 6), not this repo's.
  * docs/disc-speed.md:4,54,56 and docs/internal/bios-hardening.md:5 name
    overlay-discovery as a BRANCH, not a file, so no path applies.

INDEXES

README.md's "New here?" table gains rows for docs/WIDESCREEN.md,
docs/ENHANCEMENTS.md and docs/TCP_COMMANDS.md. docs/internal/README.md gains
sections indexing all fourteen documents that moved in, plus an "Upstream
contributor reviews" section describing upstream/ and the new sweep file, plus a
"Removed" bullet for CYCLE_TIMING_ARCH.md (deleted in the previous commit)
pointing at FAITHFUL_TIMING_PLAN.md, CYCLE_MODEL_BEETLE.md and
PRECISE_IRQ_SLICE.md -- following the SLJIT_PERSIST_CACHE.md precedent already
in that file.

docs/ENHANCEMENTS.md:7 claimed framework bugs live in "game repos' ISSUES.md
until a framework tracker exists". A framework tracker does exist; it now says
so and names the Beads epic.

VERIFICATION

A link checker was written to scan every tracked *.md for relative links,
resolving each against the tree being scanned. Baseline at origin/master: 129
files, 137 relative links, 3 broken. After both commits: 127 files, 159 relative
links, 3 broken -- the SAME three, all pre-existing and untouched here:

    docs/DATA_SHARDS.md -> INSTANT_LOADS_PARKED.md   (never existed)
    docs/DATA_SHARDS.md -> INSTANT_LOADS_SPIKE2.md   (never existed)
    docs/NETPLAY.md     -> ../lib/recomp-net/README.md  (inside a submodule)

Zero new broken links. The checker's own coverage was checked against a raw grep
for markdown link openers: the 3-link difference is C++ lambdas inside code
blocks, correctly not treated as links.

Also verified: py_compile over all 113 tracked tools/*.py plus psxrecomp_cli.py;
all four seed JSONs still parse; the test-registration guard still reports 123
test files all registered; recompiler/tests/test_coverage_vault.py still passes;
and no file in this commit had its line endings flipped (checked mechanically
across all 50 modified files, after two files were caught and redone).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-03 07:39:07 -07:00
Matthew Stanley 57648745a3 mods: the framework stages the title's catalog too (5 titles could not package)
4cc04be3 ("rebump") renamed the staged mod catalog from <exe>/mods/packages to
<exe>/mods/bundled so that a rebuild could wipe build output without deleting
the player's own installed mods. runtime.cmake followed the rename. The shared
release module followed it (release_overlay_stage.ps1's Add-ModCatalog reads
mods/bundled). docs/MOD_PACKAGES.md already described the destination as "the
framework's mods/builtin/packages PLUS the title's mods/preloaded/packages".

No title CMakeLists followed, because the second half of that sentence was
implemented five times, once per repository, as a hand-written

    add_custom_command(TARGET psx-runtime POST_BUILD
        COMMAND ${CMAKE_COMMAND} -E copy_directory
            "${<TITLE>_PRELOADED_MODS}" "$<TARGET_FILE_DIR:psx-runtime>/mods")

in ApeEscapeRecomp, Tomba2Recomp and MegaManX4/5/6Recomp. That names the
destination as a STRING. There is no symbol, no header, no link dependency --
so renaming a staged output directory breaks nothing a configure, compile or
link step can see, and the diffstat of the commit that does it looks cosmetic.
The five titles went on staging into a directory neither the launcher nor any
packager reads, and discovery was deferred to whichever title next ran a
release packager, in a different repository, on a later day.

MEASURED, on real build output. Framework origin/master 22fbbfca plus
Tomba2Recomp origin/master cdf00af, both unmodified, both built:

  build-dir mods/
    bundled/psx.enhancement.cd-speed          <- framework, correct
    bundled/psx.enhancement.fast-loading
    bundled/psx.enhancement.pgxp
    bundled/psx.presentation.bezel
    packages/tomba2.debug.debug-menu          <- title, stranded
    packages/tomba2.enhancement.skip-fmvs
    packages/tomba2.enhancement.widescreen
    packages/tomba2.experimental.interpolated-frame-rate

and Add-ModCatalog run against that tree, from this commit's parent:

  Mod catalog is missing package(s) the sources define:
  tomba2.debug.debug-menu, tomba2.enhancement.skip-fmvs,
  tomba2.enhancement.widescreen, tomba2.experimental.interpolated-frame-rate.
  They exist in the source tree but did not reach ...\mods\bundled, so the
  release would ship a Mods page the dev build does not have.

Shipped releases are NOT affected. Tomba 2 v0.0.9 pins 04d9184b, which is an
ancestor of 4cc04be3 and contains zero occurrences of migrate_legacy_root and
bundled_root; its artifact is a self-consistent mods/packages layout. This is a
forward break only.

THE FIX PUTS THE LAYOUT IN ONE PLACE.

psxrecomp_add_runtime_target() gains a one-value keyword PRELOADED_MODS_DIR.
A title declares WHERE its packages live and never HOW they are laid out:

    set(APE_PRELOADED_MODS "${CMAKE_CURRENT_SOURCE_DIR}/mods/preloaded")
    psxrecomp_add_runtime_target(psx-runtime
        ...
        PRELOADED_MODS_DIR "${APE_PRELOADED_MODS}")

A keyword argument rather than a positional one or a variable read out of the
caller's scope, for three reasons. It is what the other fourteen one-value
arguments already are, so it needs no new convention and reads the same as the
LAUNCHER_BOXART line next to it. A pre-set variable would be invisible at the
call site and would silently do nothing if set after the call -- the same
"fails quietly in the caller's repository" shape as the bug being fixed. And it
is PER TARGET, which a global variable cannot be: Tomba 2 has two runtime
targets needing two different catalogs (mods/preloaded, mods/preloaded_ita).
psxrecomp_v4_add_runtime_target and psxrecomp_add_game_runtime both forward
their remaining arguments, so it reaches through both wrappers unchanged.

_psxrt_stage_mod_catalog() now emits, in one POST_BUILD command sequence per
runtime target: wipe mods/bundled; remove exactly the ids it is about to stage
from any pre-existing mods/packages; copy the framework builtins; copy the
title's packages; copy <dir>/README.md to mods/README.md; verify. Consequences
worth stating:

  * mods/README.md previously reached mods/ only as a side effect of copying
    the whole mods/preloaded tree. Now that only packages/ is staged it is
    copied on purpose, or it would silently disappear. Checked per title: all
    five carry mods/preloaded/README.md and none carries a root mods/README.md,
    so mods/preloaded/README.md is the only source there ever was.
  * a title may still OVERRIDE a framework builtin at the same id and version,
    because the framework copy runs first and the title copy overwrites it.
    Tomba 2's Italian catalog relies on exactly this, shipping localized psx.*
    manifests. The id LIST is deduplicated for the assertion and the reported
    count while the copies keep both, or the build claims to stage 11 packages
    for a catalog that holds 7.
  * COSIM targets stage nothing. They have no launcher and no Mods page, and
    they land in the same output directory as the real runtime, so letting one
    stage had it wipe and re-stage the runtime's catalog with only the
    framework half whenever -DPSX_BUILD_COSIM=ON built it last.

TWO GUARDS, because the failure mode being closed is silence.

Configure time: a project with packages under mods/preloaded/packages that does
not declare PRELOADED_MODS_DIR is a FATAL_ERROR naming the packages and the
argument to add. That is exactly the missed-title state and it is detectable
before any build runs. An empty packages/ is fine (the scaffold creates one),
and PRELOADED_MODS_DIR NONE is the explicit opt-out.

Build time: runtime/psx_check_mod_catalog.cmake fails the build if a declared
package did not reach mods/bundled, or if any package this build stages turned
up under mods/packages -- which after this change can only mean a CMakeLists
still stages its own catalog by hand. It runs as the LAST POST_BUILD step, via
cmake_language(DEFER), because POST_BUILD commands execute in registration
order and the framework block registers BEFORE anything a title adds after its
psxrecomp_add_runtime_target() call. Verified by inspecting build.ninja: the
deferred command lands after a title-registered POST_BUILD, not before it.

The deferred pass takes NO arguments and reads its work list out of global
properties. cmake_language(DEFER CALL <fn> "${var}") does not carry a
function-local variable through -- measured with cmake 4.2.2, the callee
receives an empty argument and an add_custom_command built from it fails at
directory end with "CMakeLists.txt:DEFERRED". Recorded so the next reader does
not re-derive it.

Ids the build does NOT stage are deliberately left alone in mods/packages. On a
self-compiling setup release that directory can hold packages the player
installed under the pre-split layout, and mod_packages.cpp's
migrate_legacy_root() relocates those into mods/installed on the next launch.
Removing only the ids this build stages is also what lets a build directory
made before the split be migrated silently, so the guard's "an id turned up in
mods/packages" report has no false positives.

The same script is registered as the ctest psx_staged_mod_catalog_test in every
project that stages a catalog, with REQUIRE_STAGED=0 so a tree where the
runtime was never built reports a skip rather than a spurious failure. Where
two runtime targets share an output directory it accepts either target's
complete catalog, since after a full build mods/bundled holds whichever linked
last -- which is correct for the packager, as it builds exactly one target per
variant, and each target's own POST_BUILD guard already verified its catalog at
stage time.

TESTS. runtime/tests/test_mod_catalog_layout.py exercises the guard against
on-disk fixtures: a reproduction of the broken state as it actually appeared
(bundled/ holding the four psx.* ids, packages/ holding the four game ids), a
declared package simply absent, a package directory with no manifest.toml, a
FOREIGN legacy package that must NOT fail the build, the require-staged split
between the two callers, and the shared-output-directory sibling case. It also
pins two source invariants in runtime.cmake so the layout cannot drift back
into per-title copies. Registered in runtime/CMakeLists.txt; the
test-registration guard reports 123 test file(s), all registered.

Framework ctest: 59/59 pass, 2 pre-existing DISABLED.

Also updated so the next title does not repeat this: the project scaffold
template now passes PRELOADED_MODS_DIR (it staged no catalog at all before, so
a scaffolded project with mods/preloaded/packages shipped none), and
docs/MOD_PACKAGES.md gains a "How bundled/ gets staged" section written at
titles.

Bead beads-eio.3.101. The five title-side patches must land AFTER this commit:
they reference PRELOADED_MODS_DIR, which does not exist before it.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
2026-09-02 16:37:19 -07:00
Alex Vanderveen cc448e20a1 build: let CMake own the runtime executable name
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
2026-08-28 23:48:56 -04:00
Alex Vanderveen d5b5a029fe hotfix ci template 2026-08-12 21:53:26 -04:00
Alex Vanderveen 2c1942b569 project migration tools 2026-08-10 20:44:55 -04:00
Alex Vanderveen 272c843ba3 update CI workflow for fast update packages that reuse local c 2026-08-10 00:29:02 -04:00
Alex Vanderveen 4fb1715425 update new project scripts 2026-08-08 15:12:35 -04:00
Alex Vanderveen c31eb12dc1 setup wizard and launcher support 2026-08-08 14:10:49 -04:00
Alex Vanderveen 346e627f3e toolchain + CI + wizard hardening 2026-08-07 14:33:43 -04:00
Alex Vanderveen 4aa919a06f setup wizard hardening 2026-08-07 14:06:23 -04:00
Alex Vanderveen 6a62730141 CI hardening 2026-08-07 12:57:28 -04:00
Alex Vanderveen 33c9901497 buiild with ICE 2026-08-07 03:56:32 -04:00
Alex Vanderveen 7940137d0d bump setup script 2026-08-07 03:42:37 -04:00
Alex Vanderveen 03a187374c GH & CI patch automation 2026-08-07 03:25:11 -04:00
Alex Vanderveen bea16e3ad2 -DPSXRECOMP_ALLOW_NO_BIOS=ON 2026-08-07 03:24:34 -04:00
Alex Vanderveen 09d94214af GH reorder 2026-08-07 03:08:40 -04:00
Alex Vanderveen a79619462e update script 2026-08-07 02:04:55 -04:00
Alex Vanderveen 480fcad652 new build wizard 2026-08-07 01:43:04 -04:00
Alex Vanderveen 4073e2311c rollback + fmv + connectivity 2026-08-06 20:34:50 -04:00
Alex Vanderveen 4ddd2d01b9 rollback patches 2026-08-01 14:13:58 -04:00