From 748e29f1b96fa2109645a26a1e10dd6a4f033804 Mon Sep 17 00:00:00 2001 From: Matthew Stanley <1379tech@gmail.com> Date: Sat, 13 Jun 2026 00:22:44 -0700 Subject: [PATCH] widescreen(8C): backdrop screenX squash on native + interp paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The 16:9 edge pop-in is the parallax 2D BACKDROP layer (ocean/cloud/mountain/ grass), drawn by overlay actor handlers (FUN_801216bc family) that compute screenX = (worldX - camX) >> parallax in pure integer math and store it to the object's +0x12 field WITHOUT the GTE — so the GTE X-squash that gives 3D the wider 16:9 FOV never reaches them and far pieces sit past the 320px edge, clipped (the blue void / half-rectangles at the edges). See TombaRecomp ISSUES.md #8C for the full root-cause trail. Fix = psx_ws_backdrop_x() (gpu.c): squash screenX around screen-centre by the same ws factor as the GTE, identity at 4:3. Applied on BOTH execution paths because overlay code runs interpreted when no cache DLL is loaded (the common case in dev — see #8G): - Native: recompiler emits write_half(.., psx_ws_backdrop_x(rt)) at the [widescreen.backdrop] x_sites store (config_loader + code_generator + main_psx --ws-config so the overlay compile gets the sites). Forwarded into overlay DLLs via OverlayCallbacks {ws_backdrop_x, ws_x_margin, ws_sprite_tag} (overlay ABI v2->v3 as the cache discriminator); compile_overlays.py glue + --ws-config passthrough. - Interp: dirty_ram_interp SH hook keyed by psx_ws_is_backdrop_site(pc); the runtime registers the x_sites from game.toml (main.cpp). Verified in data: backdrop slot screenX squashes 371 -> 318 (pulled on-screen). PARTIAL: visible culling persists -> remaining cause is the backdrop spawn/ despawn window (separate from screenX clip); see #8C "REMAINING CAUSE". Also added (earlier this session): ws_census draw ring, ws_margin override, gpu_state ws fields. Co-Authored-By: Claude Opus 4.8 (1M context) --- recompiler/include/code_generator.h | 7 +++++ recompiler/src/code_generator.cpp | 22 +++++++++++++- recompiler/src/config_loader.cpp | 14 +++++++++ recompiler/src/config_loader.h | 13 ++++++++ recompiler/src/main_psx.cpp | 34 +++++++++++++++++++++ runtime/include/gpu.h | 15 +++++++++ runtime/include/overlay_api.h | 20 ++++++++++-- runtime/src/dirty_ram_interp.c | 11 ++++++- runtime/src/gpu.c | 47 +++++++++++++++++++++++++++++ runtime/src/main.cpp | 7 +++++ runtime/src/overlay_loader.c | 10 ++++++ tools/compile_overlays.py | 19 +++++++++++- 12 files changed, 214 insertions(+), 5 deletions(-) diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index 88d584cb..bb9e60e2 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -51,6 +51,13 @@ struct CodeGenConfig { std::set ws_cull_range_sites; std::set ws_cull_a1_sites; + // Widescreen backdrop screenX squash ([widescreen.backdrop] x_sites). Each + // address is a `sh rt,off(base)` storing a parallax 2D backdrop layer's + // final screen-X; emitted as write_half(base+off, psx_ws_backdrop_x(rt)) + // so the un-GTE'd backdrop is squashed around screen centre for the 16:9 + // FOV (identity at 4:3). Overlay-resident; see config_loader.h. Empty=default. + std::set ws_backdrop_x_sites; + CodeGenConfig() : emit_comments(true) , emit_line_numbers(true) diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 3410d7ac..4abdca8c 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -717,6 +717,25 @@ std::string CodeGenerator::translate_instruction(uint32_t addr, uint32_t instr) return fmt::format("cpu->gpr[5] = cpu->gpr[5] + psx_ws_x_margin();" " /* ws cull a1 bias */{}", comment); } + // Widescreen backdrop screenX squash ([widescreen.backdrop] x_sites). The + // site is the `sh rt,off(base)` storing a parallax 2D backdrop layer's + // final screen-X; squash the stored value around the screen centre so the + // un-GTE'd backdrop tracks the 16:9 FOV (psx_ws_backdrop_x is identity at + // 4:3). Instruction type is verified; a mismatch is a loud build error. + if (config_.ws_backdrop_x_sites.count(addr)) { + if (opcode != 0x29) { // sh + fmt::print(stderr, "ERROR: [widescreen.backdrop] x site 0x{:08X} is not " + "sh (opcode 0x{:02X})\n", addr, opcode); + std::exit(1); + } + uint32_t rs = get_rs(instr), rt = get_rt(instr); + int16_t offset = get_imm16(instr); + if (offset == 0) + return fmt::format("cpu->write_half({}, (uint16_t)psx_ws_backdrop_x((int16_t){}));{}", + reg_name(rs), reg_name(rt), comment); + return fmt::format("cpu->write_half({} + {}, (uint16_t)psx_ws_backdrop_x((int16_t){}));{}", + reg_name(rs), offset, reg_name(rt), comment); + } // SPECIAL opcode (0x00) if (opcode == 0x00) { @@ -1672,7 +1691,8 @@ std::string CodeGenerator::generate_file( ss << "#include \"psx_runtime.h\"\n\n"; ss << "extern void debug_server_log_call_entry(uint32_t func_addr);\n"; ss << "extern void psx_ws_sprite_tag(CPUState* cpu); /* widescreen prim tag (gpu.c) */\n"; - ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n\n"; + ss << "extern int psx_ws_x_margin(void); /* widescreen cull-margin term (gpu.c) */\n"; + ss << "extern int psx_ws_backdrop_x(int x); /* widescreen backdrop screenX squash (gpu.c) */\n\n"; // Emit reference implementations for unaligned memory helpers. // These implement the MIPS lwl/lwr/swl/swr semantics. diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index d3d9223b..9a89df40 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -514,6 +514,19 @@ GameConfig load_game_config(const fs::path& config_path_in) { } } + // Optional [widescreen.backdrop] block — parallax 2D backdrop screenX squash. + std::vector ws_backdrop_x_sites; + if (cfg.contains("widescreen")) { + const toml::value& ws = toml::find(cfg, "widescreen"); + if (ws.contains("backdrop")) { + const toml::value& bd = toml::find(ws, "backdrop"); + if (bd.contains("x_sites")) + for (const auto& a : toml::find>(bd, "x_sites")) + ws_backdrop_x_sites.push_back( + parse_hex(a, "widescreen.backdrop.x_sites")); + } + } + return GameConfig{ /*config_path*/ config_path, /*project_root*/ root, @@ -540,6 +553,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_bias_sites*/ ws_cull_bias_sites, /*ws_cull_range_sites*/ ws_cull_range_sites, /*ws_cull_a1_sites*/ ws_cull_a1_sites, + /*ws_backdrop_x_sites*/ ws_backdrop_x_sites, }; } diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 21fedeb8..a83c4599 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -246,6 +246,19 @@ struct GameConfig { std::vector ws_cull_bias_sites; std::vector ws_cull_range_sites; std::vector ws_cull_a1_sites; + + // Backdrop screen-X squash ([widescreen.backdrop] x_sites). The parallax + // 2D backdrop layer (ocean/cloud/mountain/grass — overlay actor handlers) + // computes screenX = (worldX - camX) >> parallax in pure integer math and + // stores it to the object's screen-X field WITHOUT the GTE, so the GTE + // X-squash that gives 3D the wider 16:9 FOV never reaches it; far pieces + // sit past the 320px edge and are clipped (the edge "void"/pop-in). Each + // x_site is a `sh rt,off(base)` storing the FINAL screenX; we emit it as + // `write_half(base+off, psx_ws_backdrop_x(rt))` so the value is squashed + // around screen centre (identity at 4:3). These addresses live in OVERLAY + // code, so the overlay compile must see this config (--ws-config). A regen + // is required; empty by default. + std::vector ws_backdrop_x_sites; }; // UserSettings — the launcher-written, user-editable override layer. diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 2f1a565a..01ade332 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -61,6 +61,12 @@ int main(int argc, char** argv) { // If --config is provided, all paths come from the TOML and other // CLI flags are ignored. Positional form below stays for back-compat. std::filesystem::path config_path; + // --ws-config : load ONLY the [widescreen] site lists from this TOML + // without treating it as the full game config. Used by compile_overlays.py + // so overlay compilation gets the widescreen emits (sprite-tag / cull / + // backdrop) whose addresses live in overlay code — the overlay path is + // positional (no --config), so this is the channel for those sites. + std::filesystem::path ws_config_path; for (int i = 1; i < argc; ++i) { std::string a = argv[i]; if (a == "--config" && i + 1 < argc) { @@ -72,6 +78,14 @@ int main(int argc, char** argv) { break; } } + for (int i = 1; i < argc; ++i) { + std::string a = argv[i]; + if (a == "--ws-config" && i + 1 < argc) { ws_config_path = argv[i + 1]; break; } + if (a.rfind("--ws-config=", 0) == 0) { + ws_config_path = a.substr(std::string("--ws-config=").size()); + break; + } + } std::filesystem::path exe_path; std::string extra_funcs_storage; // lifetime anchor for the .c_str() below @@ -80,6 +94,7 @@ int main(int argc, char** argv) { bool overlay_mode = false; std::set ws_tag_funcs; // [widescreen] sprite_tag_funcs std::set ws_cull_bias, ws_cull_range, ws_cull_a1; // [widescreen.cull] + std::set ws_backdrop_x; // [widescreen.backdrop] x_sites std::filesystem::path out_dir = "generated"; if (!config_path.empty()) { @@ -93,6 +108,7 @@ int main(int argc, char** argv) { ws_cull_bias.insert(cfg.ws_cull_bias_sites.begin(), cfg.ws_cull_bias_sites.end()); ws_cull_range.insert(cfg.ws_cull_range_sites.begin(), cfg.ws_cull_range_sites.end()); ws_cull_a1.insert(cfg.ws_cull_a1_sites.begin(), cfg.ws_cull_a1_sites.end()); + ws_backdrop_x.insert(cfg.ws_backdrop_x_sites.begin(), cfg.ws_backdrop_x_sites.end()); fmt::print("config: {}\n", config_path.string()); fmt::print(" exe = {}\n", exe_path.string()); fmt::print(" seeds = {}\n", extra_funcs_storage); @@ -131,6 +147,23 @@ int main(int argc, char** argv) { } } + // --ws-config: merge the [widescreen] site lists from a side config WITHOUT + // adopting its exe/paths. The overlay path is positional (no --config), so + // this is how overlay compilation receives the widescreen emits whose + // addresses live in overlay code (backdrop screenX, and any sprite-tag / + // cull sites that resolve there). No-op when --config already provided the + // sites (same TOML) — std::set insert dedupes. + if (!ws_config_path.empty()) { + const auto wscfg = PSXRecompV4::load_game_config(ws_config_path); + ws_tag_funcs.insert(wscfg.ws_sprite_tag_funcs.begin(), wscfg.ws_sprite_tag_funcs.end()); + ws_cull_bias.insert(wscfg.ws_cull_bias_sites.begin(), wscfg.ws_cull_bias_sites.end()); + ws_cull_range.insert(wscfg.ws_cull_range_sites.begin(), wscfg.ws_cull_range_sites.end()); + ws_cull_a1.insert(wscfg.ws_cull_a1_sites.begin(), wscfg.ws_cull_a1_sites.end()); + ws_backdrop_x.insert(wscfg.ws_backdrop_x_sites.begin(), wscfg.ws_backdrop_x_sites.end()); + fmt::print("ws-config: {} (backdrop_x sites={})\n", + ws_config_path.string(), ws_backdrop_x.size()); + } + // Parse the PS1-EXE file std::string error_msg; fmt::print("Parsing PS1-EXE: {}\n", exe_path.string()); @@ -623,6 +656,7 @@ int main(int argc, char** argv) { codegen_config.ws_cull_bias_sites = ws_cull_bias; codegen_config.ws_cull_range_sites = ws_cull_range; codegen_config.ws_cull_a1_sites = ws_cull_a1; + codegen_config.ws_backdrop_x_sites = ws_backdrop_x; // Load per-game annotations: annotations/_annotations.csv // Silently skipped if the file doesn't exist. diff --git a/runtime/include/gpu.h b/runtime/include/gpu.h index 65e991b3..8d3910ac 100644 --- a/runtime/include/gpu.h +++ b/runtime/include/gpu.h @@ -103,6 +103,21 @@ int gpu_ws_present_native_43(void); * cull immediates by the recompiler ([widescreen.cull]); 0 unless stretching. */ int psx_ws_x_margin(void); +/* Backdrop screen-X correction ([widescreen.backdrop] x_sites). The parallax + * 2D backdrop layer computes screen-X without the GTE, so it misses the + * widescreen squash; the recompiler emits this on each backdrop handler's + * final screenX store to squash it around the screen centre (identity at 4:3). + * Pulls far backdrop pieces in from past the 320px edge to cover the 16:9 FOV. */ +int psx_ws_backdrop_x(int x); + +/* Backdrop store-site registry: the runtime registers the [widescreen.backdrop] + * x_sites here (from game.toml) so the dirty-RAM interpreter applies the same + * psx_ws_backdrop_x() squash at those `sh` PCs that the recompiler emits into + * native cache DLLs — overlay code frequently runs interpreted, where the emit + * can't reach. PCs are masked to the physical (KUSEG) range. */ +void psx_ws_set_backdrop_sites(const uint32_t* pcs, int n); +int psx_ws_is_backdrop_site(uint32_t pc); + /* Live widescreen state for diagnostics (TCP gpu_state). All pointers * optional. last_tag_frame/cur_frame let the caller see game_mode freshness. */ typedef struct { diff --git a/runtime/include/overlay_api.h b/runtime/include/overlay_api.h index 256875c0..849f7a95 100644 --- a/runtime/include/overlay_api.h +++ b/runtime/include/overlay_api.h @@ -16,8 +16,15 @@ * * v2: dispatch call-contract (Bug D family) — DLL call sites carry (ra, sp) * contract checks and share the runtime's bail state via the appended - * callback pointers below. */ -#define PSX_OVERLAY_ABI_VERSION 2 + * callback pointers below. + * v3: widescreen backdrop screenX squash ([widescreen.backdrop] x_sites) — + * overlay backdrop handlers now emit psx_ws_backdrop_x() at their screenX + * store. This is an EMIT-content change (not a struct-ABI change), but the + * version is the cache discriminator: bumping it forces the loader to + * reject pre-backdrop DLLs so autocompile regenerates them with the squash. + * (The deferred follow-up is to auto-derive this tag from a hash of the + * widescreen config + emit headers; until then it's a manual bump.) */ +#define PSX_OVERLAY_ABI_VERSION 3 /* Codegen flavor of the recompiled output the overlays + runtime were built * against. Overlays are keyed in the cache by guest-bytes CRC, which is @@ -66,6 +73,15 @@ typedef struct { int *call_bail_flag; uint64_t *bail_first; uint64_t *bail_resolved; + /* Widescreen hooks (ABI v3). The recompiler can emit psx_ws_* calls into + * OVERLAY code (backdrop screenX squash; and, for other games, sprite-tag + * or cull sites that resolve into overlays). These compute against the + * host's live widescreen state (gpu.c), so the DLL forwards to the runtime + * through these. Appended LAST (struct grows back-compatibly); may be NULL + * on a host that predates them — the DLL glue falls back to identity. */ + int (*ws_backdrop_x)(int x); + int (*ws_x_margin)(void); + void (*ws_sprite_tag)(CPUState *cpu); } OverlayCallbacks; #ifdef __cplusplus diff --git a/runtime/src/dirty_ram_interp.c b/runtime/src/dirty_ram_interp.c index 2c81d1f2..9b30beab 100644 --- a/runtime/src/dirty_ram_interp.c +++ b/runtime/src/dirty_ram_interp.c @@ -28,6 +28,7 @@ #include "debug_server.h" #include "interrupts.h" #include "psx_cycles.h" +#include "gpu.h" /* psx_ws_is_backdrop_site / psx_ws_backdrop_x (interp hook) */ #include #include @@ -795,7 +796,15 @@ static int exec_one(CPUState *cpu, uint32_t pc, uint32_t *next_pc_out) { } case 0x29: { /* SH */ uint32_t addr = cpu->gpr[rs] + (uint32_t)simm; - cpu->write_half(addr, (uint16_t)cpu->gpr[rt]); + uint16_t val = (uint16_t)cpu->gpr[rt]; + /* Widescreen backdrop screenX squash on the interpreter path: mirrors + * the recompiler emit at [widescreen.backdrop] x_sites. Overlay code + * (the parallax backdrop handlers) runs interpreted when no cache DLL + * is loaded, so without this the squash never happens. Identity at 4:3 + * (psx_ws_backdrop_x gates on ws_active). */ + if (psx_ws_is_backdrop_site(pc)) + val = (uint16_t)psx_ws_backdrop_x((int16_t)val); + cpu->write_half(addr, val); return 0; } case 0x2A: { /* SWL */ diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 4a39b68f..22702d66 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -82,6 +82,10 @@ extern int mdec_recently_active(uint32_t within_frames); /* mdec.c */ static int ws_configured(void) { return ws_xnum != ws_xden; } +/* Forward decls: defined later but used by psx_ws_backdrop_x above them. */ +static int32_t ws_scale_about(int32_t x, int32_t ax); +static int32_t ws_disp_w(void); + /* Gameplay vs full-2D screen. Character/billboard prims tag (psx_ws_sprite_tag) * within the last couple of frames => the actor render funnel is running => * gameplay. A menu/title/save screen draws no tagged prims. The 2-frame window @@ -135,6 +139,49 @@ int psx_ws_x_margin(void) { return (160 * (ws_xden - ws_xnum) + ws_xnum / 2) / ws_xnum; } +/* Widescreen backdrop screen-X correction ([widescreen.backdrop] x_sites). + * The parallax 2D backdrop layer (ocean/cloud/mountain/grass — overlay actor + * handlers e.g. 0x801216BC) computes its screen-X in pure integer math + * (screenX = (worldX - camX) >> parallax) and NEVER goes through the GTE, so + * the GTE X-squash (gte_set_display_aspect) that gives 3D the wider 16:9 FOV + * does not touch it. The recompiler emits this on each handler's final + * screenX store so the backdrop is squashed by the SAME factor around the + * screen centre: a far piece whose 4:3 screenX sat past the 320px edge (and + * was GPU-clipped → the blue void / half-rectangles at the edges) is pulled + * in to cover the revealed FOV. Identity at 4:3 / boot / FMV / full-2D (the + * exact ws_active() predicate the GTE squash uses), so one build serves both. + * x is the int16 screenX the handler was about to store. */ +int psx_ws_backdrop_x(int x) { + if (!ws_active()) return (int16_t)x; + int32_t cx = ws_disp_w() / 2; /* screen centre (=160 @ 320) */ + return ws_scale_about((int16_t)x, cx); +} + +/* Backdrop store-site registry. The [widescreen.backdrop] x_sites are emitted + * into native cache-DLL code by the recompiler, but overlay code very often + * runs INTERPRETED (no DLL loaded), where the emit can't reach. So the runtime + * also registers the same site PCs here and the dirty-RAM interpreter applies + * psx_ws_backdrop_x() at those `sh` PCs — same transform, both paths. Tiny set; + * a linear scan per matching SH is negligible. */ +#define WS_BACKDROP_SITES_MAX 16 +static uint32_t ws_backdrop_sites[WS_BACKDROP_SITES_MAX]; +static int ws_backdrop_site_n = 0; + +void psx_ws_set_backdrop_sites(const uint32_t* pcs, int n) { + ws_backdrop_site_n = 0; + if (!pcs) return; + for (int i = 0; i < n && ws_backdrop_site_n < WS_BACKDROP_SITES_MAX; i++) + ws_backdrop_sites[ws_backdrop_site_n++] = pcs[i] & 0x1FFFFFFFu; +} + +int psx_ws_is_backdrop_site(uint32_t pc) { + if (!ws_backdrop_site_n) return 0; + pc &= 0x1FFFFFFFu; + for (int i = 0; i < ws_backdrop_site_n; i++) + if (ws_backdrop_sites[i] == pc) return 1; + return 0; +} + /* Snapshot live widescreen state for the TCP gpu_state diagnostic. Mirrors * the predicates above so a probe can see, mid-gameplay, whether the squash * and the cull-margin are actually engaged (8C). */ diff --git a/runtime/src/main.cpp b/runtime/src/main.cpp index bf44ae18..039c64db 100644 --- a/runtime/src/main.cpp +++ b/runtime/src/main.cpp @@ -1474,6 +1474,13 @@ int main(int argc, char** argv) { g_video_aspect_den = gc.runtime.video_aspect_den; g_ws_anchor_addr = gc.ws_sprite_anchor_addr; g_ws_hud_sprt = gc.ws_hud_sprt_squash; + /* Register the [widescreen.backdrop] store PCs so the dirty-RAM + * interpreter applies the backdrop screenX squash on the interp + * path (overlay backdrop handlers run interpreted when no cache + * DLL is loaded — the recompiler emit only covers native). */ + if (!gc.ws_backdrop_x_sites.empty()) + psx_ws_set_backdrop_sites(gc.ws_backdrop_x_sites.data(), + (int)gc.ws_backdrop_x_sites.size()); g_audio_spu_hq = gc.runtime.audio_spu_hq; g_auto_skip_fmv = gc.runtime.video_auto_skip_fmv ? 1 : 0; { const char *e = std::getenv("PSX_GL_FORCE_CPU_PRESENT"); diff --git a/runtime/src/overlay_loader.c b/runtime/src/overlay_loader.c index 2cd5faad..a5826ba6 100644 --- a/runtime/src/overlay_loader.c +++ b/runtime/src/overlay_loader.c @@ -413,6 +413,16 @@ static void init_callbacks(void) { s_callbacks.call_bail_flag = &g_psx_call_bail; s_callbacks.bail_first = &g_psx_bail_first; s_callbacks.bail_resolved = &g_psx_bail_resolved; + /* Widescreen hooks (ABI v3): overlay-emitted psx_ws_* calls forward to + * the runtime's live widescreen state (gpu.c). */ + { + extern int psx_ws_backdrop_x(int x); + extern int psx_ws_x_margin(void); + extern void psx_ws_sprite_tag(CPUState *cpu); + s_callbacks.ws_backdrop_x = psx_ws_backdrop_x; + s_callbacks.ws_x_margin = psx_ws_x_margin; + s_callbacks.ws_sprite_tag = psx_ws_sprite_tag; + } } /* ---- DLL loading and export enumeration -------------------------------- */ diff --git a/tools/compile_overlays.py b/tools/compile_overlays.py index 6907ef41..a6322ca3 100644 --- a/tools/compile_overlays.py +++ b/tools/compile_overlays.py @@ -792,6 +792,18 @@ void debug_server_log_call_entry(uint32_t func_addr) { void psx_restore_state_escape(void) { if (g_cbs.psx_restore_state_escape) g_cbs.psx_restore_state_escape(); } +/* Widescreen hooks (ABI v3): forward to the runtime's live widescreen state. + * Identity fallback if the host predates the callbacks (NULL) — so a v3 DLL + * stays correct (4:3) on an older host. */ +int psx_ws_backdrop_x(int x) { + return g_cbs.ws_backdrop_x ? g_cbs.ws_backdrop_x(x) : (int)(short)x; +} +int psx_ws_x_margin(void) { + return g_cbs.ws_x_margin ? g_cbs.ws_x_margin() : 0; +} +void psx_ws_sprite_tag(CPUState *cpu) { + if (g_cbs.ws_sprite_tag) g_cbs.ws_sprite_tag(cpu); +} /* ----------------------------------------------------------------------- */ """ @@ -1195,7 +1207,12 @@ def main(): cmd = [args.recompiler, psx_path, '--seeds', seeds_path, '--out-dir', out_dir_tmp, - '--overlay'] + '--overlay', + # Forward the [widescreen] site lists so overlay-resident + # emits (backdrop screenX squash, and any sprite-tag/cull + # sites that resolve into overlay code) are applied. --ws-config + # only adopts the widescreen lists, not the game's exe/paths. + '--ws-config', os.path.abspath(args.game_toml)] print(f' recompile: {args.recompiler} ...') toml_dir = os.path.dirname(os.path.abspath(args.game_toml)) r = subprocess.run(cmd, capture_output=True, text=True,