diff --git a/recompiler/include/code_generator.h b/recompiler/include/code_generator.h index 5754e74d..e8b7f43a 100644 --- a/recompiler/include/code_generator.h +++ b/recompiler/include/code_generator.h @@ -150,6 +150,10 @@ struct CodeGenConfig { // widescreen reveals extra world. 4:3 keeps the original branch predicate. std::set ws_cull_nclip_keep_sites; + // Exact `bltz MAC0, reject`-style NCLIP/backface sites that consume the + // validated precise sign while wide, without changing guest-visible MAC0. + std::set ws_cull_nclip_exact_sites; + // Exact branch PCs whose reject target is suppressed while widescreen // reveals extra world. 4:3 keeps the original branch predicate. std::set ws_cull_branch_keep_sites; diff --git a/recompiler/src/code_generator.cpp b/recompiler/src/code_generator.cpp index 7ae7015a..0a1ca43e 100644 --- a/recompiler/src/code_generator.cpp +++ b/recompiler/src/code_generator.cpp @@ -784,6 +784,9 @@ std::string CodeGenerator::generate_branch_condition(uint32_t instr, uint32_t ad if (regimm_op == 0x00 && config_.ws_cull_nclip_keep_sites.count(addr)) return fmt::format("psx_ws_x_margin() > 0 ? 0 : ((int32_t){} < 0) /* ws nclip keep */", reg_name(rs)); + if (regimm_op == 0x00 && config_.ws_cull_nclip_exact_sites.count(addr)) + return fmt::format("psx_ws_x_margin() > 0 ? gte_nclip_precise_bltz((int32_t){}) : ((int32_t){} < 0) /* ws exact nclip */", + reg_name(rs), reg_name(rs)); return keep_branch_if_wide( 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 cb4aec55..56221feb 100644 --- a/recompiler/src/config_loader.cpp +++ b/recompiler/src/config_loader.cpp @@ -73,6 +73,7 @@ uint32_t overlay_codegen_config_hash(const GameConfig& c) { h.words("cull_plane_nx", c.ws_cull_plane_nx_sites); h.words("cull_xclip_load", c.ws_cull_xclip_load_sites); h.words("cull_nclip_keep", c.ws_cull_nclip_keep_sites); + h.words("cull_nclip_exact", c.ws_cull_nclip_exact_sites); h.words("cull_branch_keep", c.ws_cull_branch_keep_sites); h.words("cull_w_imms", c.ws_cull_w_imms); h.words("cull_h_imms", c.ws_cull_h_imms); @@ -1697,6 +1698,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { std::vector ws_cull_plane_nx_sites; std::vector ws_cull_xclip_load_sites; std::vector ws_cull_nclip_keep_sites; + std::vector ws_cull_nclip_exact_sites; std::vector ws_cull_branch_keep_sites; std::vector ws_cull_keep_sites; std::vector ws_cull_widen_sites; @@ -1733,6 +1735,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { load_sites("plane_nx_sites", ws_cull_plane_nx_sites); load_sites("xclip_load_sites", ws_cull_xclip_load_sites); load_sites("nclip_keep_sites", ws_cull_nclip_keep_sites); + load_sites("nclip_exact_sites", ws_cull_nclip_exact_sites); load_sites("branch_keep_sites", ws_cull_branch_keep_sites); if (cull.contains("keep")) { std::set seen; @@ -2214,6 +2217,7 @@ GameConfig load_game_config(const fs::path& config_path_in) { /*ws_cull_plane_nx_sites*/ ws_cull_plane_nx_sites, /*ws_cull_xclip_load_sites*/ ws_cull_xclip_load_sites, /*ws_cull_nclip_keep_sites*/ ws_cull_nclip_keep_sites, + /*ws_cull_nclip_exact_sites*/ ws_cull_nclip_exact_sites, /*ws_cull_branch_keep_sites*/ ws_cull_branch_keep_sites, /*ws_cull_keep_sites*/ ws_cull_keep_sites, /*ws_cull_widen_sites*/ ws_cull_widen_sites, diff --git a/recompiler/src/config_loader.h b/recompiler/src/config_loader.h index 0363f8c8..55372f91 100644 --- a/recompiler/src/config_loader.h +++ b/recompiler/src/config_loader.h @@ -999,6 +999,9 @@ struct GameConfig { // not-taken only while widescreen reveals extra world. This is deliberately // separate from bltz_sites, whose helper adjusts screen-X edge thresholds. std::vector ws_cull_nclip_keep_sites; + // Exact NCLIP/backface branch sites that use the validated tracked sign + // only while wide. Guest MAC0 remains native and 4:3 remains bit-exact. + std::vector ws_cull_nclip_exact_sites; // Exact branch PCs whose reject path is forced not-taken only while // widescreen reveals extra world. Use only after screenshot-validated // evidence that the target is a visibility reject. diff --git a/recompiler/src/main_psx.cpp b/recompiler/src/main_psx.cpp index 89aaf420..20120adf 100644 --- a/recompiler/src/main_psx.cpp +++ b/recompiler/src/main_psx.cpp @@ -210,6 +210,7 @@ int main(int argc, char** argv) { std::set ws_cull_plane_nx; // [widescreen.cull] plane_nx_sites std::set ws_cull_xclip_load; // [widescreen.cull] xclip_load_sites std::set ws_cull_nclip_keep; // [widescreen.cull] nclip_keep_sites + std::set ws_cull_nclip_exact; // [widescreen.cull] nclip_exact_sites std::set ws_cull_branch_keep; // [widescreen.cull] branch_keep_sites std::vector ws_cull_keep; std::vector ws_cull_widen; @@ -274,6 +275,7 @@ int main(int argc, char** argv) { ws_cull_plane_nx.insert(cfg.ws_cull_plane_nx_sites.begin(), cfg.ws_cull_plane_nx_sites.end()); ws_cull_xclip_load.insert(cfg.ws_cull_xclip_load_sites.begin(), cfg.ws_cull_xclip_load_sites.end()); ws_cull_nclip_keep.insert(cfg.ws_cull_nclip_keep_sites.begin(), cfg.ws_cull_nclip_keep_sites.end()); + ws_cull_nclip_exact.insert(cfg.ws_cull_nclip_exact_sites.begin(), cfg.ws_cull_nclip_exact_sites.end()); ws_cull_branch_keep.insert(cfg.ws_cull_branch_keep_sites.begin(), cfg.ws_cull_branch_keep_sites.end()); ws_cull_keep = cfg.ws_cull_keep_sites; ws_cull_widen = cfg.ws_cull_widen_sites; @@ -370,6 +372,7 @@ int main(int argc, char** argv) { ws_cull_plane_nx.insert(wscfg.ws_cull_plane_nx_sites.begin(), wscfg.ws_cull_plane_nx_sites.end()); ws_cull_xclip_load.insert(wscfg.ws_cull_xclip_load_sites.begin(), wscfg.ws_cull_xclip_load_sites.end()); ws_cull_nclip_keep.insert(wscfg.ws_cull_nclip_keep_sites.begin(), wscfg.ws_cull_nclip_keep_sites.end()); + ws_cull_nclip_exact.insert(wscfg.ws_cull_nclip_exact_sites.begin(), wscfg.ws_cull_nclip_exact_sites.end()); ws_cull_branch_keep.insert(wscfg.ws_cull_branch_keep_sites.begin(), wscfg.ws_cull_branch_keep_sites.end()); if (ws_cull_keep.empty()) ws_cull_keep = wscfg.ws_cull_keep_sites; if (ws_cull_widen.empty()) ws_cull_widen = wscfg.ws_cull_widen_sites; @@ -1235,6 +1238,7 @@ int main(int argc, char** argv) { codegen_config.ws_cull_plane_nx_sites = ws_cull_plane_nx; codegen_config.ws_cull_xclip_load_sites = ws_cull_xclip_load; codegen_config.ws_cull_nclip_keep_sites = ws_cull_nclip_keep; + codegen_config.ws_cull_nclip_exact_sites = ws_cull_nclip_exact; codegen_config.ws_cull_branch_keep_sites = ws_cull_branch_keep; codegen_config.ws_cull_keep_sites = ws_cull_keep; codegen_config.ws_cull_widen_sites = ws_cull_widen; diff --git a/recompiler/tests/recompiler_patch_test.cpp b/recompiler/tests/recompiler_patch_test.cpp index 6c04c885..ab644eae 100644 --- a/recompiler/tests/recompiler_patch_test.cpp +++ b/recompiler/tests/recompiler_patch_test.cpp @@ -245,6 +245,15 @@ branch_keep_sites = ["0x80012340"] std::vector{0x80012340u}, "parser preserves branch keep sites"); + const auto nclip_exact = write_config(root, "nclip-exact", R"toml( +[widescreen.cull] +nclip_exact_sites = ["0x80012340"] +)toml"); + const auto nclip_exact_config = PSXRecompV4::load_game_config(nclip_exact); + check(nclip_exact_config.ws_cull_nclip_exact_sites == + std::vector{0x80012340u}, + "parser preserves exact NCLIP branch sites"); + const auto vxrange = write_config(root, "vxrange", R"toml( [widescreen.cull] vxrange_sites = ["0x80012340"] @@ -729,6 +738,16 @@ void codegen_tests() { branch_keep.find("ws branch keep") != std::string::npos, "codegen emits guarded branch keep predicate"); + PSXRecomp::CodeGenConfig nclip_exact_config; + nclip_exact_config.ws_cull_nclip_exact_sites.insert(0x80010000u); + const std::string nclip_exact = generate_first_instruction( + 0x04400002u, {}, false, nclip_exact_config); // bltz v0,+2 + check(nclip_exact.find( + "gte_nclip_precise_bltz((int32_t)cpu->gpr[2])") != + std::string::npos && + nclip_exact.find("ws exact nclip") != std::string::npos, + "codegen emits title-scoped exact NCLIP predicate"); + PSXRecomp::CodeGenConfig plane_nx_config; plane_nx_config.ws_cull_plane_nx_sites.insert(0x80010000u); const std::string plane_nx = generate_first_instruction( diff --git a/runtime/include/cpu_state.h b/runtime/include/cpu_state.h index 6595d2d0..f16a02c6 100644 --- a/runtime/include/cpu_state.h +++ b/runtime/include/cpu_state.h @@ -220,11 +220,15 @@ extern uint32_t gte_geometry_correction_hits(void); extern void gte_geometry_correction_stats(uint32_t *lookups, uint32_t *hits, uint32_t *miss_unrecorded, uint32_t *miss_ambiguous); -/* Exact-sign NCLIP coverage: precise = all three SXY shadows live and - * word-validated; fallback = integer sign decided (the widescreen thin-face - * flicker regime); corrected = sign disagreements actually fixed. */ +/* Exact NCLIP audit coverage: precise = all three SXY shadows are coherent and + * word-validated; fallback = native integer-only; disagreements counts cases + * where sub-pixel and guest-visible integer signs differ. MAC0 stays native. */ extern void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks, - uint64_t *corrected); + uint64_t *disagreements); +/* Title-scoped widescreen cull consumer. Returns the exact tracked NCLIP sign + * only when it belongs to the supplied native MAC0; otherwise preserves the + * native comparison. This does not change guest-visible GTE state. */ +extern int gte_nclip_precise_bltz(int32_t native_mac0); /* pc=0 escape journal (traps.c). Every runtime site that publishes a null PC * (the "continue the native chain below" convention) or rescues one records diff --git a/runtime/include/pgxp.h b/runtime/include/pgxp.h index db419e91..bb4fd78d 100644 --- a/runtime/include/pgxp.h +++ b/runtime/include/pgxp.h @@ -58,6 +58,11 @@ void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3, uint32_t packed); /* Current SXY FIFO shadow (index 0..3 selects GTE data regs 12..15). * Returns nonzero when the shadow is live and carries X/Y precision. */ int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16); +/* Validate-on-read variant: check!=0 additionally requires the shadow's packed + * word to equal expect (the live guest SXY word). Sign consumers must use this + * form so stale FIFO shadows fail closed to native integer behavior. */ +int pgxp_get_gte_sxy_checked(uint32_t index, uint32_t expect, int check, + int32_t *x16, int32_t *y16); /* Guest write to a GTE data register outside gte_execute (MTC2/CTC2 handled * by psx_pgxp_cop2; this is for direct gte_write_data paths): reg 15 performs diff --git a/runtime/src/gpu.c b/runtime/src/gpu.c index 72068b21..7e653862 100644 --- a/runtime/src/gpu.c +++ b/runtime/src/gpu.c @@ -221,7 +221,13 @@ static int ws_gameplay_state_value_count = 0; * this many consecutive frames (save/options/memory-card) — reverts to 4:3. */ #define WS_GTE_GAME_MODE_HYSTERESIS 45u void gpu_ws_set_gte_game_mode(int on) { ws_gte_game_mode_cfg = on ? 1 : 0; } -void gpu_ws_set_precise_nclip(int on) { ws_precise_nclip_cfg = on ? 1 : 0; } +void gpu_pgxp_rederive_enable(void); +void gpu_ws_set_precise_nclip(int on) { + ws_precise_nclip_cfg = on ? 1 : 0; + /* Exact NCLIP consumes PGXP transport shadows even when every visual + * PGXP correction is disabled. Re-derive the internal transport arm. */ + gpu_pgxp_rederive_enable(); +} int gpu_ws_precise_nclip_enabled(void) { return ws_precise_nclip_cfg && ws_active(); } void gpu_ws_set_gameplay_state_gate(uint32_t addr, const uint32_t *values, int nvalues) { @@ -3448,12 +3454,17 @@ static int s_texture_correction_enabled = 0; extern int gte_precision_load_word(uint32_t addr, uint32_t packed, int32_t *x16, int32_t *y16, uint16_t *z); +/* Arm the PGXP dataflow transport for any consumer. Exact NCLIP is an internal + * sign source only; this does not enable geometry or texture correction. */ +void gpu_pgxp_rederive_enable(void) { + pgxp_set_enabled(s_texture_correction_enabled || + gte_geometry_correction_enabled() || + ws_precise_nclip_cfg); +} + void gpu_texture_correction_set(int enabled) { s_texture_correction_enabled = enabled ? 1 : 0; - /* The PGXP dataflow engine feeds BOTH corrections; arm it while either - * is on (geometry correction is toggled in gte.cpp, so re-derive here). */ - pgxp_set_enabled(s_texture_correction_enabled || - gte_geometry_correction_enabled()); + gpu_pgxp_rederive_enable(); } int gpu_texture_correction_enabled(void) { diff --git a/runtime/src/gte.cpp b/runtime/src/gte.cpp index 4ef73575..d33c17ac 100644 --- a/runtime/src/gte.cpp +++ b/runtime/src/gte.cpp @@ -312,6 +312,8 @@ static inline int64_t geom_slot(uint32_t packed) { return (int64_t)(y + GEOM_BIAS) * GEOM_AXIS + (x + GEOM_BIAS); } +extern "C" void gpu_pgxp_rederive_enable(void); + extern "C" void gte_geometry_correction_set(int enabled) { s_geom_enabled = enabled ? 1 : 0; s_geom_hits = 0; @@ -324,6 +326,7 @@ extern "C" void gte_geometry_correction_set(int enabled) { if (!s_geom_cache) s_geom_enabled = 0; /* fail closed: stay faithful */ } gte_geom_generation_advance(); + gpu_pgxp_rederive_enable(); } /* Lookup census for the debug server: attempted, hit, and the two miss classes. @@ -888,9 +891,15 @@ void gte_rtps_internal(GTEState* gte, int16_t* V, bool setMac0, uint32_t instr) int64_t sx = sx16 >> 16; int64_t sy = sy16 >> 16; gte->push_sxy(sx, sy); - if (!s_gte_replay_sandbox) - pgxp_gte_push_sxy((int32_t)sx16, (int32_t)sy16, gte->SZ[3], + if (!s_gte_replay_sandbox) { + /* Bound the 16.16 transport so an extreme projection cannot wrap an + * int32 and masquerade as a valid on-screen shadow. */ + const int64_t kLim = (int64_t)4096 << 16; + int64_t cx16 = sx16 < -kLim ? -kLim : (sx16 > kLim - 1 ? kLim - 1 : sx16); + int64_t cy16 = sy16 < -kLim ? -kLim : (sy16 > kLim - 1 ? kLim - 1 : sy16); + pgxp_gte_push_sxy((int32_t)cx16, (int32_t)cy16, gte->SZ[3], (uint32_t)gte->SXY[2]); + } geom_note((uint32_t)gte->SXY[2], sx16, sy16); // Step 5: Depth cueing (MAC0/IR0) — only for last vertex of RTPT or RTPS @@ -921,6 +930,24 @@ void gte_rtpt(GTEState* gte, uint32_t instr) { // --------------------------------------------------------------------------- // NCLIP (0x06) — Normal Clipping (2D cross product for backface culling) // --------------------------------------------------------------------------- +static uint64_t s_nclip_precise_hits = 0; +static uint64_t s_nclip_fallbacks = 0; +static uint64_t s_nclip_disagreements = 0; +static int32_t s_nclip_last_native = 0; +static int8_t s_nclip_last_precise_sign = 0; +static bool s_nclip_last_precise_valid = false; +extern "C" void gte_nclip_precise_stats(uint64_t *hits, uint64_t *fallbacks, + uint64_t *disagreements) { + if (hits) *hits = s_nclip_precise_hits; + if (fallbacks) *fallbacks = s_nclip_fallbacks; + if (disagreements) *disagreements = s_nclip_disagreements; +} +extern "C" int gte_nclip_precise_bltz(int32_t native_mac0) { + if (!s_nclip_last_precise_valid || native_mac0 != s_nclip_last_native) + return native_mac0 < 0; + return s_nclip_last_precise_sign < 0; +} + void gte_nclip(GTEState* gte, uint32_t instr) { gte->FLAG = 0; int32_t sx0 = static_cast(gte->SXY[0] & 0xFFFF); @@ -929,23 +956,36 @@ void gte_nclip(GTEState* gte, uint32_t instr) { int32_t sy1 = static_cast(gte->SXY[1] >> 16); int32_t sx2 = static_cast(gte->SXY[2] & 0xFFFF); int32_t sy2 = static_cast(gte->SXY[2] >> 16); - int32_t px0 = 0, py0 = 0, px1 = 0, py1 = 0, px2 = 0, py2 = 0; - if (gpu_ws_precise_nclip_enabled() && - pgxp_get_gte_sxy(0, &px0, &py0) && - pgxp_get_gte_sxy(1, &px1, &py1) && - pgxp_get_gte_sxy(2, &px2, &py2)) { - sx0 = px0 >> 16; - sy0 = py0 >> 16; - sx1 = px1 >> 16; - sy1 = py1 >> 16; - sx2 = px2 >> 16; - sy2 = py2 >> 16; - } int64_t mac0 = (int64_t)sx0 * (sy1 - sy2) + (int64_t)sx1 * (sy2 - sy0) + (int64_t)sx2 * (sy0 - sy1); gte->check_mac0_overflow(mac0); - gte->MAC0 = static_cast(mac0); + int32_t out = static_cast(mac0); + s_nclip_last_native = out; + s_nclip_last_precise_valid = false; + /* Compute an exact 16.16 determinant for configured branch consumers, but + * preserve native guest-visible MAC0 for every architectural reader. */ + int32_t px0, py0, px1, py1, px2, py2; + if (gpu_ws_precise_nclip_enabled() && + !s_gte_replay_sandbox && s_speculative_depth == 0 && + pgxp_get_gte_sxy_checked(0, gte->SXY[0], 1, &px0, &py0) && + pgxp_get_gte_sxy_checked(1, gte->SXY[1], 1, &px1, &py1) && + pgxp_get_gte_sxy_checked(2, gte->SXY[2], 1, &px2, &py2)) { + s_nclip_precise_hits++; + const int64_t dx10 = (int64_t)px1 - px0; + const int64_t dy10 = (int64_t)py1 - py0; + const int64_t dx20 = (int64_t)px2 - px0; + const int64_t dy20 = (int64_t)py2 - py0; + const int64_t cross = dx10 * dy20 - dy10 * dx20; + s_nclip_last_precise_sign = cross < 0 ? -1 : (cross > 0 ? 1 : 0); + s_nclip_last_precise_valid = true; + if ((cross > 0 && out <= 0) || (cross < 0 && out >= 0)) + s_nclip_disagreements++; + } else if (gpu_ws_precise_nclip_enabled() && + !s_gte_replay_sandbox && s_speculative_depth == 0) { + s_nclip_fallbacks++; + } + gte->MAC0 = out; gte->set_error_flag(); } @@ -1543,11 +1583,13 @@ extern "C" int gte_replay_side_effects_begin(void) { if (s_gte_replay_sandbox) return 0; s_gte_replay_saved_caller_ra = s_gte_caller_ra; s_gte_replay_sandbox = 1; + pgxp_suppress_begin(); return 1; } extern "C" void gte_replay_side_effects_end(void) { using namespace PSXRecomp::GTE; if (!s_gte_replay_sandbox) return; + pgxp_suppress_end(); s_gte_caller_ra = s_gte_replay_saved_caller_ra; s_gte_replay_sandbox = 0; } diff --git a/runtime/src/pgxp.cpp b/runtime/src/pgxp.cpp index 8c6bc86d..f0b09588 100644 --- a/runtime/src/pgxp.cpp +++ b/runtime/src/pgxp.cpp @@ -104,6 +104,7 @@ extern "C" void pgxp_invalidate_all(void) { } extern "C" void pgxp_set_enabled(int enabled) { + int resized = 0; if (enabled) { /* Cover the REAL guest RAM, not the stock 2 MB. With the 8 MB mod the * extension is real memory, and a 2 MB-wrapped shadow aliased every @@ -129,11 +130,15 @@ extern "C" void pgxp_set_enabled(int enabled) { std::free(s_ram); s_ram = fresh; s_ram_words = want; + resized = 1; } else if (!s_ram) { enabled = 0; /* fail closed: stay faithful */ } } } + /* Re-applying configuration must not invalidate every live shadow. */ + if (s_enabled == (enabled ? 1 : 0) && !resized) + return; s_enabled = enabled ? 1 : 0; pgxp_invalidate_all(); recompute_active(); @@ -724,10 +729,17 @@ extern "C" void pgxp_gte_push_sxy(int32_t x16, int32_t y16, uint16_t sz3, } extern "C" int pgxp_get_gte_sxy(uint32_t index, int32_t *x16, int32_t *y16) { + return pgxp_get_gte_sxy_checked(index, 0u, 0, x16, y16); +} + +extern "C" int pgxp_get_gte_sxy_checked(uint32_t index, uint32_t expect, + int check, int32_t *x16, int32_t *y16) { if (index >= 4) return 0; const PGXPValue *pv = &s_gte[12 + index]; if (!pv_live(pv) || (pv->flags & PGXP_F_VXY) != PGXP_F_VXY) return 0; + if (check && pv->value != expect) + return 0; if (x16) *x16 = pv->x16; if (y16) *y16 = pv->y16; return 1; diff --git a/runtime/tests/test_gte_register_access.cpp b/runtime/tests/test_gte_register_access.cpp index 4de0b17a..872d9a67 100644 --- a/runtime/tests/test_gte_register_access.cpp +++ b/runtime/tests/test_gte_register_access.cpp @@ -44,6 +44,12 @@ extern "C" void gte_test_execute_reference(CPUState *cpu, uint32_t cmd); /* gte.cpp runtime dependencies that are irrelevant to register-transfer tests. */ extern "C" int gpu_ws_present_native_43(void) { return 0; } +static int g_test_precise_nclip_enabled; +extern "C" int gpu_ws_precise_nclip_enabled(void) { + return g_test_precise_nclip_enabled; +} +extern "C" void gpu_pgxp_rederive_enable(void) {} +extern "C" uint32_t memory_get_ram_bytes(void) { return 2u * 1024u * 1024u; } extern "C" void psx_ws_note_gte_project(int) {} extern "C" { uint64_t s_frame_count = 0; @@ -549,6 +555,49 @@ int test_precise_sxy_invalidation() { return 0; } +int test_precise_nclip_is_title_scoped() { + CPUState cpu{}; + gte_precision_tracking_set(1); + g_test_precise_nclip_enabled = 1; + + /* Native determinant is +1. The validated 16.16 positions have a negative + * exact determinant, so only the title-scoped branch helper may see it. */ + const uint32_t packed[3] = {0x00000001u, 0xFFFFFFFEu, 0xFFFFFFFFu}; + const int32_t x16[3] = {101245, -109694, -34340}; + const int32_t y16[3] = {19509, -59658, -20365}; + for (uint32_t i = 0; i < 3; ++i) { + cpu.gte_data[12 + i] = packed[i]; + gte_test_seed_precise_projection(i, packed[i], x16[i], y16[i], 100); + } + uint64_t hit0 = 0, fallback0 = 0, disagree0 = 0; + gte_nclip_precise_stats(&hit0, &fallback0, &disagree0); + gte_execute(&cpu, 0x06u); + uint64_t hit1 = 0, fallback1 = 0, disagree1 = 0; + gte_nclip_precise_stats(&hit1, &fallback1, &disagree1); + if (cpu.gte_data[24] != 1u || hit1 != hit0 + 1u || + fallback1 != fallback0 || disagree1 != disagree0 + 1u) + return fail_value("precise NCLIP preserves guest MAC0", 0, 0x06u, + 0, 1u, cpu.gte_data[24]); + if (!gte_nclip_precise_bltz(1) || gte_nclip_precise_bltz(2)) + return fail_value("title-scoped precise NCLIP predicate", 0, 0x06u, + 0, 1u, 0u); + + /* A stale packed-word shadow must fail closed to the native sign and count + * as a fallback, never as a precise hit. */ + gte_test_seed_precise_projection(0, packed[0] ^ 1u, + x16[0], y16[0], 100); + gte_execute(&cpu, 0x06u); + uint64_t hit2 = 0, fallback2 = 0, disagree2 = 0; + gte_nclip_precise_stats(&hit2, &fallback2, &disagree2); + g_test_precise_nclip_enabled = 0; + if (cpu.gte_data[24] != 1u || hit2 != hit1 || + fallback2 != fallback1 + 1u || disagree2 != disagree1 || + gte_nclip_precise_bltz(1)) + return fail_value("stale precise NCLIP falls back natively", 0, 0x06u, + 0, 1u, cpu.gte_data[24]); + return 0; +} + int test_precision_speculative_transaction() { constexpr uint32_t address = 0x00123450u; constexpr uint32_t packed = 0x00420021u; @@ -661,6 +710,7 @@ int main() { if (int rc = test_command_marshaling()) return rc; if (int rc = test_command_timing_hook()) return rc; if (int rc = test_precise_sxy_invalidation()) return rc; + if (int rc = test_precise_nclip_is_title_scoped()) return rc; if (int rc = test_precision_speculative_transaction()) return rc; std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle"); return 0;