From 64ca4c3dbaa306a3d8d3daedc2bc6bb9beee39ea Mon Sep 17 00:00:00 2001 From: Claudio Varandas Date: Wed, 22 Jul 2026 20:07:21 +0100 Subject: [PATCH] recompiler: [widescreen.cull] bltz_sites (explicit left-edge funnel widen) Left-edge counterpart to slti_sites: emit psx_ws_cull_bltz at configured bltz PCs. detect_cull_bltz_sites only classifies left-edge bltz for auto_screen_x-qualified functions, so an X-only funnel wired via explicit slti_sites had no left-edge widen. bltz_sites closes that gap. Identity at 4:3; regen-class. --- recompiler/include/code_generator.h | 5 +++++ recompiler/src/code_generator.cpp | 13 +++++++++---- recompiler/src/config_loader.cpp | 3 +++ recompiler/src/config_loader.h | 7 +++++++ recompiler/src/main_psx.cpp | 4 ++++ 5 files changed, 28 insertions(+), 4 deletions(-) diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index 0fd90af0..f68075bf 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -99,6 +99,11 @@ struct CodeGenConfig { // the auto-detector cannot qualify (X-only test, no height compare). std::set ws_cull_slti_sites; + // `bltz rs, reject` emitted through psx_ws_cull_bltz — the explicit + // LEFT-edge counterpart to ws_cull_slti_sites ([widescreen.cull] + // bltz_sites), for X-only funnels the auto-detector cannot qualify. + std::set ws_cull_bltz_sites; + // Explicit horizontal low-edge widen sites ([widescreen.cull] // negsub_sites): `subu rd, zero, rs` becomes `-rs - x_margin`. // Identity at 4:3; empty by default. diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 515718b6..950861d7 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -721,10 +721,15 @@ std::string CodeGenerator::generate_branch_condition(uint32_t instr, uint32_t ad if (opcode == 0x01) { uint32_t regimm_op = (instr >> 16) & 0x1F; if ((regimm_op & 0x01u) == 0x00u) { // bltz family (incl. bltzal + undefined mirrors) - // Classified LEFT-edge funnel bltz (auto_screen_x, signed idioms): - // reject only past the revealed margin. Identity at 4:3 (margin 0). - if (regimm_op == 0x00 && ws_cull_bltz_pcs_.count(addr)) - return fmt::format("psx_ws_cull_bltz({}) /* ws auto screen-x cull (left edge) */", + // LEFT-edge funnel bltz: reject only past the revealed margin. + // Identity at 4:3 (margin 0). Two sources: (a) auto_screen_x's + // detect_cull_bltz_sites classification (ws_cull_bltz_pcs_), and + // (b) explicit [widescreen.cull] bltz_sites — the left-edge + // counterpart to slti_sites, for X-only funnels auto_screen_x can't + // qualify (whose bltz would otherwise never be widened). + if (regimm_op == 0x00 && + (ws_cull_bltz_pcs_.count(addr) || config_.ws_cull_bltz_sites.count(addr))) + return fmt::format("psx_ws_cull_bltz({}) /* ws cull (left edge) */", reg_name(rs)); return fmt::format("(int32_t){} < 0", reg_name(rs)); } else { // bgez family (incl. bgezal + undefined mirrors) diff --git a/recompiler/src/config_loader.cpp b/recompiler/src/config_loader.cpp index 94ed4bcf..12aacbd2 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -1066,6 +1066,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_cull_bias_sites, ws_cull_range_sites, ws_cull_a1_sites; std::vector ws_cull_screen_x_sites; std::vector ws_cull_slti_sites; + std::vector ws_cull_bltz_sites; std::vector ws_cull_negsub_sites; std::vector ws_cull_vxrange_sites; std::vector ws_cull_depth_sites; @@ -1093,6 +1094,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { load_sites("a1_sites", ws_cull_a1_sites); load_sites("screen_x_sites", ws_cull_screen_x_sites); load_sites("slti_sites", ws_cull_slti_sites); + load_sites("bltz_sites", ws_cull_bltz_sites); load_sites("negsub_sites", ws_cull_negsub_sites); load_sites("vxrange_sites", ws_cull_vxrange_sites); load_sites("depth_sites", ws_cull_depth_sites); @@ -1250,6 +1252,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_a1_sites*/ ws_cull_a1_sites, /*ws_cull_screen_x_sites*/ ws_cull_screen_x_sites, /*ws_cull_slti_sites*/ ws_cull_slti_sites, + /*ws_cull_bltz_sites*/ ws_cull_bltz_sites, /*ws_cull_negsub_sites*/ ws_cull_negsub_sites, /*ws_cull_vxrange_sites*/ ws_cull_vxrange_sites, /*ws_cull_depth_sites*/ ws_cull_depth_sites, diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index c1922e32..cfb52ddf 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -534,6 +534,13 @@ struct GameConfig { // auto-detector cannot qualify (e.g. an X-only test with no height compare // in the same function — Ape Escape 0x8004AB64). Empty by default; regen. std::vector ws_cull_slti_sites; + // [widescreen.cull] bltz_sites — explicit signed LEFT-edge widen sites + // (`bltz rs, reject` -> psx_ws_cull_bltz), the counterpart to slti_sites. + // detect_cull_bltz_sites only classifies left-edge bltz for functions + // auto_screen_x qualified, so an X-only funnel wired through explicit + // slti_sites has no left-edge widen without this. Empty by default; + // identity at 4:3; regen required. + std::vector ws_cull_bltz_sites; // Horizontal low-edge form `subu rd,zero,rs` -> `-rs-x_margin`. // Empty by default; configured sites require regenerated native code. std::vector ws_cull_negsub_sites; diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index ec13733f..bf244937 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -156,6 +156,7 @@ int main(int argc, char** argv) { std::set ws_cull_bias, ws_cull_range, ws_cull_a1; // [widescreen.cull] std::set ws_cull_screen_x; // [widescreen.cull] screen_x_sites std::set ws_cull_slti; // [widescreen.cull] slti_sites + std::set ws_cull_bltz; // [widescreen.cull] bltz_sites std::set ws_cull_negsub; // [widescreen.cull] negsub_sites std::set ws_cull_vxrange; // [widescreen.cull] vxrange_sites std::set ws_cull_depth; // [widescreen.cull] depth_sites @@ -202,6 +203,7 @@ int main(int argc, char** argv) { ws_cull_a1.insert(cfg.ws_cull_a1_sites.begin(), cfg.ws_cull_a1_sites.end()); ws_cull_screen_x.insert(cfg.ws_cull_screen_x_sites.begin(), cfg.ws_cull_screen_x_sites.end()); ws_cull_slti.insert(cfg.ws_cull_slti_sites.begin(), cfg.ws_cull_slti_sites.end()); + ws_cull_bltz.insert(cfg.ws_cull_bltz_sites.begin(), cfg.ws_cull_bltz_sites.end()); ws_cull_negsub.insert(cfg.ws_cull_negsub_sites.begin(), cfg.ws_cull_negsub_sites.end()); ws_cull_vxrange.insert(cfg.ws_cull_vxrange_sites.begin(), cfg.ws_cull_vxrange_sites.end()); ws_cull_depth.insert(cfg.ws_cull_depth_sites.begin(), cfg.ws_cull_depth_sites.end()); @@ -280,6 +282,7 @@ int main(int argc, char** argv) { ws_cull_a1.insert(wscfg.ws_cull_a1_sites.begin(), wscfg.ws_cull_a1_sites.end()); ws_cull_screen_x.insert(wscfg.ws_cull_screen_x_sites.begin(), wscfg.ws_cull_screen_x_sites.end()); ws_cull_slti.insert(wscfg.ws_cull_slti_sites.begin(), wscfg.ws_cull_slti_sites.end()); + ws_cull_bltz.insert(wscfg.ws_cull_bltz_sites.begin(), wscfg.ws_cull_bltz_sites.end()); ws_cull_negsub.insert(wscfg.ws_cull_negsub_sites.begin(), wscfg.ws_cull_negsub_sites.end()); ws_cull_vxrange.insert(wscfg.ws_cull_vxrange_sites.begin(), wscfg.ws_cull_vxrange_sites.end()); ws_cull_depth.insert(wscfg.ws_cull_depth_sites.begin(), wscfg.ws_cull_depth_sites.end()); @@ -1053,6 +1056,7 @@ int main(int argc, char** argv) { codegen_config.ws_cull_a1_sites = ws_cull_a1; codegen_config.ws_cull_screen_x_sites = ws_cull_screen_x; codegen_config.ws_cull_slti_sites = ws_cull_slti; + codegen_config.ws_cull_bltz_sites = ws_cull_bltz; codegen_config.ws_cull_negsub_sites = ws_cull_negsub; codegen_config.ws_cull_vxrange_sites = ws_cull_vxrange; codegen_config.ws_cull_depth_sites = ws_cull_depth;