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>
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)
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
Smart App Control blocks unsigned executables outright. The packager now
runs tools/ci/sign_windows.sh over the staged tree (host exe, DLLs,
emitters) with SHA-256 + RFC 3161 timestamp; no certificate in the
environment means a notice and an unsigned package as before, while a
configured certificate that fails to sign is fatal. The workflow template
passes WINDOWS_SIGN_PFX_BASE64 / WINDOWS_SIGN_PFX_PASSWORD to the package
step. Docs cover the secrets and the limit: the game exe the player builds
locally after Generate is unsigned by nature.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01V1dT6h1ZshMVUUVKTPxsCD
Brings the packaging gates and Project Studio audit for allowlist drift:
- template stages src/, mods/ and the generated symbol headers
- package_setup_host.sh refuses to zip a stage whose CMakeLists.txt names
an unstaged path, or whose project sources include one
- Studio Migrate gains packager_stages_sources / packager_stages_optional
and op_sync_packager_project_dirs
Both branches touch tools/package_setup_host.sh and the packaging template,
so this is merged here rather than left to race on master.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1TpXRJve6WvwyZbSJAbCL
FF7 v0.1.3 shipped src/ff7_mods.c (the previous commit's fix) and then died
at the first object file:
src/ff7_mods.c:20:10: fatal error: '../psx_symbols.h' file not found
psx_symbols.h is a tracked 82 KB header that game-owned C in src/ includes by
relative path. Nothing in CMakeLists.txt names it, so the CMakeLists gate
added in the previous commit could not see it — it is a compile input of the
shipped tree that only the source knows about.
Two changes:
1. The template stages psx_symbols.h and psx_symbols_overlays.h when present.
2. package_setup_host.sh gains a second pre-zip gate: every relative #include
in the project's own staged sources must resolve inside the stage. Only
project-owned paths (PROJECT_FILES / PROJECT_DIRS) are scanned — framework
trees resolve their headers through -I and are not ours to police.
The include check accepts three resolutions: relative to the including file,
relative to the stage root, and finally any file of that basename anywhere in
the stage. That last one covers -I resolution out of the staged framework
trees (a bare "mod_plugins.h" comes from psxrecomp/runtime/include). It is
deliberately loose: a false pass only forfeits a warning, while a false
failure would block a good release.
Verified against the real v0.1.3 tree: the stage as shipped fails naming
exactly ../psx_symbols.h, and passes once the header is staged. mod_plugins.h
was a false positive in a minimal stage and is correctly cleared once the
framework tree is present.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1TpXRJve6WvwyZbSJAbCL
Final Fantasy VII shipped a release zip whose CMakeLists.txt compiles
src/ff7_mods.c, but the zip had no src/ at all. Installing it died at
configure with "Cannot find source file" and "No SOURCES given to target:
psx-runtime". The commit that added the widescreen mod plugin updated
CMakeLists.txt but not the packaging allowlist, and nothing checked.
The allowlist in the generated scripts/package_setup_release.sh is a closed
list that has to be kept in sync with CMakeLists.txt by hand, so this is a
class problem, not a one-off: of 23 game repos with a packaging script, all
23 carry mods/ and only 3 stage it; 7 carry src/ and only 2 stage it. Those
3 are hand-patched copies of this same fix, which is the tell.
Two changes:
1. templates/package_setup_release.sh.in stages src/ and mods/ when the
project has them, using the existing EXTRA_PROJECT idiom (copy_proj hard
-fails on a missing path, so they cannot be added unconditionally).
src/ holds game-owned C that CMakeLists compiles in; mods/preloaded/
packages holds the markers those plugins select themselves through, so
without it a mod is built and never enabled.
2. package_setup_host.sh now refuses to zip a stage that cannot configure
itself: every ${CMAKE_CURRENT_SOURCE_DIR}/<rel> the staged CMakeLists.txt
names must exist in the stage. Paths used only inside if(EXISTS "...")
are optional by construction and are skipped, as are globs and paths
built from cmake variables. This turns a silent omission that surfaces on
a user's machine into a release-time failure.
Verified against the real FF7 CMakeLists: the stage as shipped fails naming
exactly src/ff7_mods.c; with src/ staged it passes; the guarded
mods/preloaded/packages reference correctly does not fail a project without
mods/.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01L1TpXRJve6WvwyZbSJAbCL
Mods under active development had nowhere to live: a package was either in
the catalog and therefore in every release, or not built at all. Widescreen is
the immediate case -- it engages, but it is unvalidated as gameplay and FF7's
pre-rendered field backdrops still cull at the 4:3 bound, so it is not
something to put in front of players yet.
A package now declares its own maturity with channel = "developer". It still
ships with ordinary local builds and local exports, so it can be tested
exactly as a player would see it, and release packaging drops it.
The exclusion defaults from $CI -- on under any CI provider, off locally.
That is deliberate: a per-workflow opt-in is precisely the shape that left 21
of 23 ports shipping no mods at all, so this must not need 23 release
workflows edited to take effect. --exclude-dev-mods / --include-dev-mods and
EXCLUDE_DEV_MODS=0|1 override in either direction, and Studio's
`build export --exclude-dev-mods` reproduces what a release contains.
Applied in all three packagers (setup-host, Windows, macOS). Each prunes the
whole PACKAGE directory and then asserts no developer manifest survived --
including the SOURCE catalog under mods/preloaded, since the setup-host zip
rebuilds on the player's machine and would otherwise re-stage the package the
release just removed.
A manifest is never rewritten during packaging: editing a package while
packaging it would change content meant to be verifiable, which is why the
marker is package-level rather than per-feature.
Verified by packaging: local keeps 5 catalogs, CI=true drops to 4 with no
ff7.enhancements entry anywhere in the zip, and EXCLUDE_DEV_MODS=0 under
CI=true restores it.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194TYJAqgyG9a9PJe9uUcgQ
A built title stages its mod catalog next to the exe, but nothing put it in
the shipped package. Every release went out with an empty Mods page.
The packager already had --runtime-dir mods; it was opt-in and RUNTIME_DIRS
defaulted to empty, so each title had to pass it from its own generated
wrapper. An audit of the workspace found 21 of 23 ports never did, and the
two that did used the -optional variant that warns instead of failing. A
capability that exists, defaults off, and fails silently.
- package_setup_host.sh stages mods/ by DEFAULT and REQUIRES it. runtime.cmake
guarantees <exe>/mods for every non-oracle target, so a missing one means
broken build wiring and should fail the release rather than quietly ship an
empty catalog. --no-mods opts out a title with none; an explicit
--runtime-dir-optional mods still wins. It also ships the SOURCE catalog,
because the setup-host zip rebuilds on the player's machine and that rebuild
re-runs POST_BUILD staging from the source tree -- without it a rebuilt exe
silently drops every game-owned mod and keeps only framework builtins.
- package_release.ps1 (Windows/MinGW) had no mod handling at all. It now
stages the catalog before the stray-file scan, so the existing gate covers
it, strips the build machine's mods/state.toml, and asserts at least one
manifest survived -- matching the file's own assert-rather-than-trust idiom.
- package_release_macos.sh shipped ONE package where the player should have
seen five. It took the catalog from mods/preloaded "never the build tree",
but framework builtins (PGXP, fast loading, CD speed, bezel) live in
psxrecomp/mods/builtin and only ever appear next to the exe. The real hazard
was never the build tree, it was state.toml inside it -- so take the build
tree and delete that one file. --mods-src still overrides.
- The wrapper template gains --project-dir src so future titles ship their
game-owned trusted-plugin sources; CMakeLists.txt references them
unconditionally, so the setup-host rebuild cannot configure without them.
Studio could build a title but never bundle one: the only route to a
distributable was install-CI, commit, push, wait. That is how an empty Mods
page survived unnoticed -- nobody produced a package locally to look inside.
buildops.export_release() runs the SAME wrapper CI runs, so a local zip and a
released one are the same artifact by construction; exposed as
`build export` on the CLI and an "Export release zip" button in the GUI.
Artifact tags default to the host and match the CI matrix.
Verified by packaging, not by reading: default produces 5 catalogs with no
state.toml; --no-mods reproduces the old behaviour at 0.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0194TYJAqgyG9a9PJe9uUcgQ