diff --git a/host/psxrecomp_codegen_host.c b/host/psxrecomp_codegen_host.c index 33f3af39..eb178c7e 100644 --- a/host/psxrecomp_codegen_host.c +++ b/host/psxrecomp_codegen_host.c @@ -4353,6 +4353,34 @@ static void host_selfcheck_or_return(const PsxrecompCodegenHostConfig* cfg, printf(",\n \"game_dispatch_present\": %s", game_ok ? "true" : "false"); printf(",\n \"bios_backends_present\": %s", bios_ok ? "true" : "false"); printf(",\n \"sources_missing\": %s", missing ? "true" : "false"); + /* Wave-5 F4: overlay_cache is the switch that makes the runtime initialise the + * overlay loader at all; without it every streamed overlay is interpreted. CI + * asserts this field so a title cannot ship silently without it again. */ + { + char toml_abs[1200]; + int overlay_cache = 0; + if (join_path(toml_abs, sizeof(toml_abs), g_project_root, "game.toml")) { + FILE* tf = fopen(toml_abs, "rb"); + if (tf) { + char line[512]; + while (fgets(line, sizeof(line), tf)) { + const char* p = line; + while (*p == ' ' || *p == '\t') ++p; + if (strncmp(p, "overlay_cache", 13) == 0) { + p += 13; + while (*p == ' ' || *p == '\t') ++p; + if (*p == '=') { + ++p; + while (*p == ' ' || *p == '\t') ++p; + overlay_cache = strncmp(p, "true", 4) == 0; + } + } + } + fclose(tf); + } + } + printf(",\n \"overlay_cache_configured\": %s", overlay_cache ? "true" : "false"); + } printf("\n}\n"); fflush(stdout); exit(missing ? 2 : 0); diff --git a/tools/package_setup_host.sh b/tools/package_setup_host.sh index 7482e9fa..019b9357 100755 --- a/tools/package_setup_host.sh +++ b/tools/package_setup_host.sh @@ -64,6 +64,10 @@ DISPLAY_NAME="" RECOMPILER_BUILD="build-recompiler" VERSION_ENV="RELEASE_VERSION" DISC_HINT="your legally owned game disc" +# Wave-5 F4: a packaged game.toml without `overlay_cache = true` ships a runtime that never +# initialises the overlay loader, so every streamed overlay runs on the interpreter (78 of +# 105 releases measured 2026-09-16). Refuse unless the caller states why. +SHIP_WITHOUT_OVERLAY_CACHE_BECAUSE="" PROJECT_FILES=() PROJECT_DIRS=() RUNTIME_DIRS=() @@ -115,6 +119,7 @@ while [[ $# -gt 0 ]]; do --root) ROOT="${2:?}"; shift 2 ;; --embed-toolchain) EMBED_TOOLCHAIN=1; shift ;; --no-embed-toolchain) EMBED_TOOLCHAIN=0; shift ;; + --ship-without-overlay-cache-because) SHIP_WITHOUT_OVERLAY_CACHE_BECAUSE="${2:?}"; shift 2 ;; *) echo "error: unknown arg: $1" >&2 usage 2 @@ -484,6 +489,17 @@ fi bash "${STAGE_SDK}" "${stage_args[@]}" +if ! grep -qE '^[[:space:]]*overlay_cache[[:space:]]*=[[:space:]]*true' "${STAGE}/game.toml"; then + if [[ -z "${SHIP_WITHOUT_OVERLAY_CACHE_BECAUSE}" ]]; then + echo "error: REFUSING TO PACKAGE: ${STAGE}/game.toml has no '[runtime] overlay_cache = true'." >&2 + echo " The runtime never initialises the overlay loader without it, so every streamed" >&2 + echo " overlay runs on the dirty-RAM interpreter for every player. Add the key, or pass" >&2 + echo " --ship-without-overlay-cache-because '' to record why this title ships without it." >&2 + exit 1 + fi + echo "warning: packaging without overlay_cache = true (reason: ${SHIP_WITHOUT_OVERLAY_CACHE_BECAUSE})" >&2 +fi + cat >"${STAGE}/README-SETUP.txt" <