#include "cpu_state.h" #include "gte.h" #include "pgxp.h" #include "render_pass_projection.h" #include #include #include #include #include using PSXRecomp::GTE::GTEState; using PSXRecomp::GTE::gte_cfc2; using PSXRecomp::GTE::gte_ctc2; using PSXRecomp::GTE::gte_mfc2; using PSXRecomp::GTE::gte_mtc2; extern "C" void gte_canonicalize_cpu_state(CPUState *cpu); extern "C" void gte_test_set_precise_valid_mask(uint32_t mask); extern "C" uint32_t gte_test_get_precise_valid_mask(void); extern "C" void gte_test_set_timeline_generations(uint32_t precision, uint32_t geometry); extern "C" uint32_t gte_test_get_precision_generation(void); extern "C" uint32_t gte_test_get_geometry_generation(void); extern "C" void gte_precision_tracking_set(int enabled); extern "C" void gte_precision_invalidate_word(uint32_t addr); extern "C" int gte_precision_load_word(uint32_t addr, uint32_t packed, int32_t *x16, int32_t *y16, uint16_t *z); extern "C" void gte_geometry_correction_set(int enabled); extern "C" int gte_geometry_correction_lookup(uint32_t packed, int32_t *x16, int32_t *y16); extern "C" void gte_test_seed_precise_projection(uint32_t index, uint32_t packed, int32_t x16, int32_t y16, uint16_t z); extern "C" void gte_test_get_precise_projection(uint32_t index, uint32_t *packed, int32_t *x16, int32_t *y16, uint16_t *z, uint8_t *valid); extern "C" void gte_test_seed_geometry(uint32_t packed, int32_t x16, int32_t y16); 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) {} static int g_test_shadow_diff = 0; extern "C" int psx_overlay_shadow_diff_active(void) { return g_test_shadow_diff; } extern "C" void psx_ws_note_gte_project(int) {} extern "C" { uint64_t s_frame_count = 0; } uint64_t g_test_cycle = 0; uint32_t g_test_gte_set_calls = 0; uint32_t g_test_gte_last_latency = 0; extern "C" uint32_t psx_gte_cmd_latency(uint32_t cmd) { return 7u + (cmd & 0x3Fu); } extern "C" void psx_gte_set(CPUState *cpu, uint32_t latency) { ++g_test_gte_set_calls; g_test_gte_last_latency = latency; if (cpu->gte_ts_done > g_test_cycle) g_test_cycle = cpu->gte_ts_done; cpu->gte_ts_done = g_test_cycle + latency; } namespace { struct PreciseState { uint32_t packed; int32_t x16; int32_t y16; uint16_t z; uint8_t valid; }; std::array precise_snapshot() { std::array result{}; for (uint32_t i = 0; i < result.size(); ++i) { auto &p = result[i]; gte_test_get_precise_projection(i, &p.packed, &p.x16, &p.y16, &p.z, &p.valid); } return result; } void seed_precise_snapshot() { for (uint32_t i = 0; i < 4; ++i) gte_test_seed_precise_projection(i, 0x01010001u * (i + 1u), 0x10000 + static_cast(i * 31), -0x20000 - static_cast(i * 47), static_cast(0x100u + i)); } bool same_precise(const std::array &lhs, const std::array &rhs) { for (uint32_t i = 0; i < lhs.size(); ++i) { if (lhs[i].packed != rhs[i].packed || lhs[i].x16 != rhs[i].x16 || lhs[i].y16 != rhs[i].y16 || lhs[i].z != rhs[i].z || lhs[i].valid != rhs[i].valid) return false; } return true; } constexpr std::array kEdgeValues = { 0x00000000u, 0x00000001u, 0x00007FFFu, 0x00008000u, 0x0000FFFFu, 0x00010000u, 0x7FFFFFFFu, 0x80000000u, 0xFFFFFFFEu, 0xFFFFFFFFu, }; constexpr std::array kInvalidRegs = {32u, 33u, 63u, 64u, 127u, 255u}; uint64_t g_rng = 0xD1B54A32D192ED03ull; uint32_t random_u32() { g_rng ^= g_rng >> 12; g_rng ^= g_rng << 25; g_rng ^= g_rng >> 27; return static_cast((g_rng * 0x2545F4914F6CDD1Dull) >> 16); } void randomize_gte(CPUState &cpu) { std::memset(&cpu, 0, sizeof(cpu)); for (uint32_t &value : cpu.gte_data) value = random_u32(); for (uint32_t &value : cpu.gte_ctrl) value = random_u32(); } void oracle_load(GTEState >e, const CPUState &cpu) { for (uint8_t reg = 0; reg < 32; ++reg) { if (reg == 15 || reg == 28) continue; gte_mtc2(>e, reg, cpu.gte_data[reg]); } for (uint8_t reg = 0; reg < 32; ++reg) gte_ctc2(>e, reg, cpu.gte_ctrl[reg]); /* Importing emulator state is not a guest CTC2 write. Preserve a computed * FLAG error bit and any existing backing bits exactly like gte_load_data. */ gte.FLAG = cpu.gte_ctrl[31]; } void oracle_export(CPUState &cpu, GTEState >e) { for (uint8_t reg = 0; reg < 32; ++reg) cpu.gte_data[reg] = gte_mfc2(>e, reg); for (uint8_t reg = 0; reg < 32; ++reg) cpu.gte_ctrl[reg] = gte_cfc2(>e, reg); } void oracle_canonicalize(CPUState &cpu) { GTEState gte; oracle_load(gte, cpu); oracle_export(cpu, gte); } uint32_t oracle_read_data(const CPUState &cpu, uint8_t reg) { GTEState gte; oracle_load(gte, cpu); return gte_mfc2(>e, reg); } uint32_t oracle_read_ctrl(const CPUState &cpu, uint8_t reg) { GTEState gte; oracle_load(gte, cpu); return gte_cfc2(>e, reg); } void oracle_write_data(CPUState &cpu, uint8_t reg, uint32_t value) { GTEState gte; oracle_load(gte, cpu); gte_mtc2(>e, reg, value); oracle_export(cpu, gte); } void oracle_write_ctrl(CPUState &cpu, uint8_t reg, uint32_t value) { GTEState gte; oracle_load(gte, cpu); gte_ctc2(>e, reg, value); oracle_export(cpu, gte); } bool same_gte(const CPUState &lhs, const CPUState &rhs) { return std::memcmp(lhs.gte_data, rhs.gte_data, sizeof(lhs.gte_data)) == 0 && std::memcmp(lhs.gte_ctrl, rhs.gte_ctrl, sizeof(lhs.gte_ctrl)) == 0; } int fail_value(const char *phase, unsigned iteration, unsigned reg, uint32_t value, uint32_t expected, uint32_t actual) { std::fprintf(stderr, "FAIL: %s iter=%u reg=%u value=%08X expected=%08X actual=%08X\n", phase, iteration, reg, value, expected, actual); return 1; } int fail_state(const char *phase, unsigned iteration, unsigned reg, uint32_t value, const CPUState &expected, const CPUState &actual) { for (unsigned i = 0; i < 32; ++i) { if (expected.gte_data[i] != actual.gte_data[i]) { std::fprintf(stderr, "FAIL: %s iter=%u source_reg=%u value=%08X data=%u " "expected=%08X actual=%08X\n", phase, iteration, reg, value, i, expected.gte_data[i], actual.gte_data[i]); return 1; } } for (unsigned i = 0; i < 32; ++i) { if (expected.gte_ctrl[i] != actual.gte_ctrl[i]) { std::fprintf(stderr, "FAIL: %s iter=%u source_reg=%u value=%08X ctrl=%u " "expected=%08X actual=%08X\n", phase, iteration, reg, value, i, expected.gte_ctrl[i], actual.gte_ctrl[i]); return 1; } } std::fprintf(stderr, "FAIL: %s reported unequal states without a differing word\n", phase); return 1; } int check_all_reads(const CPUState &expected, CPUState &actual, const char *phase, unsigned iteration) { for (uint8_t reg = 0; reg < 32; ++reg) { const uint32_t expected_data = oracle_read_data(expected, reg); const uint32_t actual_data = gte_read_data(&actual, reg); if (actual_data != expected_data) return fail_value(phase, iteration, reg, 0, expected_data, actual_data); const uint32_t expected_ctrl = oracle_read_ctrl(expected, reg); const uint32_t actual_ctrl = gte_read_ctrl(&actual, reg); if (actual_ctrl != expected_ctrl) return fail_value(phase, iteration, reg, 1, expected_ctrl, actual_ctrl); } return 0; } int test_canonicalizer() { for (unsigned iteration = 0; iteration < 512; ++iteration) { CPUState expected; randomize_gte(expected); CPUState actual = expected; oracle_canonicalize(expected); gte_canonicalize_cpu_state(&actual); if (!same_gte(expected, actual)) return fail_state("canonicalize", iteration, 0, 0, expected, actual); } return 0; } int test_reads() { for (unsigned iteration = 0; iteration < 256; ++iteration) { CPUState state; randomize_gte(state); /* Deliberately use raw randomized backing here. Read helpers must match * the architectural value reconstructed by the old bridge even before * an explicit canonicalization boundary. */ CPUState before = state; if (int rc = check_all_reads(state, state, "raw read", iteration)) return rc; if (!same_gte(before, state)) return fail_state("read mutated state", iteration, 0, 0, before, state); for (uint8_t reg : kInvalidRegs) { const uint32_t actual_data = gte_read_data(&state, reg); const uint32_t actual_ctrl = gte_read_ctrl(&state, reg); if (actual_data != 0u) return fail_value("invalid data read", iteration, reg, 0, 0, actual_data); if (actual_ctrl != 0u) return fail_value("invalid ctrl read", iteration, reg, 0, 0, actual_ctrl); } } return 0; } int test_hardware_register_semantics() { CPUState cpu{}; gte_write_data(&cpu, 1, 0x00008001u); if (gte_read_data(&cpu, 1) != 0xFFFF8001u) return fail_value("VZ sign extension", 0, 1, 0x00008001u, 0xFFFF8001u, gte_read_data(&cpu, 1)); gte_write_data(&cpu, 23, 0xDEADBEEFu); if (gte_read_data(&cpu, 23) != 0xDEADBEEFu) return fail_value("RES1 round trip", 0, 23, 0xDEADBEEFu, 0xDEADBEEFu, gte_read_data(&cpu, 23)); gte_write_ctrl(&cpu, 4, 0x00008002u); if (gte_read_ctrl(&cpu, 4) != 0xFFFF8002u) return fail_value("matrix tail sign extension", 0, 4, 0x00008002u, 0xFFFF8002u, gte_read_ctrl(&cpu, 4)); gte_write_ctrl(&cpu, 26, 0x0000E810u); if (gte_read_ctrl(&cpu, 26) != 0xFFFFE810u) return fail_value("H read sign extension", 0, 26, 0x0000E810u, 0xFFFFE810u, gte_read_ctrl(&cpu, 26)); gte_write_ctrl(&cpu, 31, 0x00800000u); if (gte_read_ctrl(&cpu, 31) != 0x80800000u) return fail_value("FLAG error summary", 0, 31, 0x00800000u, 0x80800000u, gte_read_ctrl(&cpu, 31)); return 0; } int test_writes() { for (unsigned iteration = 0; iteration < 64; ++iteration) { CPUState seed; randomize_gte(seed); /* Valid helper writes must also normalize unrelated backing words. * This preserves the old bridge's behavior when falling back from * committed AOT C that predates helper routing for masked registers. */ for (uint8_t reg = 0; reg < 32; ++reg) { for (uint32_t edge : kEdgeValues) { const uint32_t value = edge ^ (iteration ? random_u32() : 0u); CPUState expected = seed; CPUState actual = seed; oracle_write_data(expected, reg, value); gte_write_data(&actual, reg, value); if (!same_gte(expected, actual)) return fail_state("data write", iteration, reg, value, expected, actual); expected = seed; actual = seed; oracle_write_ctrl(expected, reg, value); gte_write_ctrl(&actual, reg, value); if (!same_gte(expected, actual)) return fail_state("ctrl write", iteration, reg, value, expected, actual); } } CPUState canonical_seed = seed; oracle_canonicalize(canonical_seed); for (uint8_t reg : kInvalidRegs) { CPUState expected = canonical_seed; CPUState actual = canonical_seed; const uint32_t value = random_u32(); oracle_write_data(expected, reg, value); gte_write_data(&actual, reg, value); if (!same_gte(expected, actual)) return fail_state("invalid data write", iteration, reg, value, expected, actual); expected = canonical_seed; actual = canonical_seed; oracle_write_ctrl(expected, reg, value); gte_write_ctrl(&actual, reg, value); if (!same_gte(expected, actual)) return fail_state("invalid ctrl write", iteration, reg, value, expected, actual); } } return 0; } int test_sequence_fuzz() { for (unsigned iteration = 0; iteration < 128; ++iteration) { CPUState expected; randomize_gte(expected); oracle_canonicalize(expected); CPUState actual = expected; for (unsigned step = 0; step < 512; ++step) { const uint32_t selector = random_u32(); const uint8_t reg = (selector & 7u) == 0u ? kInvalidRegs[selector % kInvalidRegs.size()] : static_cast((selector >> 8) & 31u); const uint32_t value = random_u32(); switch ((selector >> 16) & 3u) { case 0: { const uint32_t want = oracle_read_data(expected, reg); const uint32_t got = gte_read_data(&actual, reg); if (want != got) return fail_value("sequence data read", iteration, reg, value, want, got); break; } case 1: { const uint32_t want = oracle_read_ctrl(expected, reg); const uint32_t got = gte_read_ctrl(&actual, reg); if (want != got) return fail_value("sequence ctrl read", iteration, reg, value, want, got); break; } case 2: oracle_write_data(expected, reg, value); gte_write_data(&actual, reg, value); break; case 3: oracle_write_ctrl(expected, reg, value); gte_write_ctrl(&actual, reg, value); break; } if (!same_gte(expected, actual)) return fail_state("sequence state", iteration, reg, value, expected, actual); } } return 0; } static unsigned projection_callback_calls; static int substitute_projection(CPUState *cpu, uint32_t) { ++projection_callback_calls; cpu->gte_ctrl[0] = 0x1000u; cpu->gte_ctrl[1] = 0u; cpu->gte_ctrl[2] = 0x1000u; cpu->gte_ctrl[3] = 0u; cpu->gte_ctrl[4] = 0x1000u; cpu->gte_ctrl[5] = 32u; cpu->gte_ctrl[6] = 16u; cpu->gte_ctrl[7] = 1024u; return 1; } int test_projection_override_restores_transform() { for (uint32_t command : {0x80001u, 0x80030u}) { CPUState actual{}; actual.gte_ctrl[0] = 0x800u; actual.gte_ctrl[2] = 0x800u; actual.gte_ctrl[4] = 0x800u; actual.gte_ctrl[5] = 80u; actual.gte_ctrl[6] = 40u; actual.gte_ctrl[7] = 2048u; actual.gte_ctrl[26] = 256u; actual.gte_data[0] = 0x00100020u; actual.gte_data[1] = 64u; actual.gte_data[2] = 0x00300040u; actual.gte_data[3] = 96u; actual.gte_data[4] = 0x00500060u; actual.gte_data[5] = 128u; CPUState original = actual, expected = actual; substitute_projection(&expected, command); gte_test_execute_reference(&expected, command); projection_callback_calls = 0; g_psx_projection_command = substitute_projection; gte_execute(&actual, command); g_psx_projection_command = nullptr; if (projection_callback_calls != 1 || std::memcmp(actual.gte_ctrl, original.gte_ctrl, 8 * sizeof(uint32_t)) || std::memcmp(actual.gte_data, expected.gte_data, sizeof(actual.gte_data))) { std::fprintf(stderr, "FAIL: projection override leaked transform or changed output (%x)\n", command); return 1; } g_psx_projection_command = substitute_projection; gte_execute(&actual, 0x06u); g_psx_projection_command = nullptr; if (projection_callback_calls != 1) return 1; } return 0; } int test_command_marshaling() { constexpr std::array kFunctions = { 0x01, 0x06, 0x0C, 0x10, 0x11, 0x12, 0x13, 0x14, 0x16, 0x1B, 0x1C, 0x1E, 0x20, 0x28, 0x29, 0x2A, 0x2D, 0x2E, 0x30, 0x3D, 0x3E, 0x3F, }; for (uint8_t function : kFunctions) { for (unsigned iteration = 0; iteration < 96; ++iteration) { CPUState seed; randomize_gte(seed); // deliberately raw/noncanonical legacy state CPUState expected = seed; CPUState actual = seed; const uint32_t cmd = (random_u32() & ~0x3Fu) | function; gte_test_execute_reference(&expected, cmd); gte_execute(&actual, cmd); if (!same_gte(expected, actual)) return fail_state("command marshal", iteration, function, cmd, expected, actual); if (actual.gte_data[15] != actual.gte_data[14] || actual.gte_data[28] != actual.gte_data[29] || actual.gte_data[31] != gte_read_data(&actual, 31)) return fail_value("command canonical aliases", iteration, function, cmd, actual.gte_data[14], actual.gte_data[15]); } } /* MVMVA's matrix/vector/translation dependencies are all selected by the * command word. Exhaust every selector plus sf/lm combination explicitly. */ for (uint32_t mx = 0; mx < 4; ++mx) { for (uint32_t vv = 0; vv < 4; ++vv) { for (uint32_t tv = 0; tv < 4; ++tv) { for (uint32_t sf = 0; sf < 2; ++sf) { for (uint32_t lm = 0; lm < 2; ++lm) { CPUState seed; randomize_gte(seed); CPUState expected = seed; CPUState actual = seed; const uint32_t cmd = 0x12u | (mx << 17) | (vv << 15) | (tv << 13) | (sf << 19) | (lm << 10); gte_test_execute_reference(&expected, cmd); gte_execute(&actual, cmd); if (!same_gte(expected, actual)) return fail_state("MVMVA selectors", mx * 16u + vv * 4u + tv, static_cast(sf * 2u + lm), cmd, expected, actual); } } } } } /* Stateful command streams catch FIFO/alias state that single-command * comparisons can accidentally reinitialize away. */ for (unsigned iteration = 0; iteration < 64; ++iteration) { CPUState expected; randomize_gte(expected); CPUState actual = expected; for (unsigned step = 0; step < 128; ++step) { const uint8_t function = kFunctions[random_u32() % kFunctions.size()]; const uint32_t cmd = (random_u32() & ~0x3Fu) | function; gte_test_execute_reference(&expected, cmd); gte_execute(&actual, cmd); if (!same_gte(expected, actual)) return fail_state("command stream", iteration, function, cmd, expected, actual); } } /* Projection commands update a host-only precise-SXY FIFO. Reset that * global state around each path so the old and direct marshalers can be * compared independently instead of executing twice on one timeline. */ for (uint8_t function : kFunctions) { CPUState seed; randomize_gte(seed); CPUState expected = seed; CPUState actual = seed; const uint32_t cmd = (random_u32() & ~0x3Fu) | function; seed_precise_snapshot(); gte_test_execute_reference(&expected, cmd); const auto expected_precise = precise_snapshot(); seed_precise_snapshot(); gte_execute(&actual, cmd); const auto actual_precise = precise_snapshot(); if (!same_precise(expected_precise, actual_precise)) return fail_value("command precise provenance", 0, function, cmd, 1u, 0u); } return 0; } int test_command_timing_hook() { CPUState cpu{}; g_test_cycle = 100u; cpu.gte_ts_done = 175u; g_test_gte_set_calls = 0; g_test_gte_last_latency = 0; gte_execute(&cpu, 0x06u); if (g_test_gte_set_calls != 1u || g_test_gte_last_latency != 13u || cpu.gte_ts_done != 188u) return fail_value("command timing serialization", 0, 0x06u, 0, 188u, static_cast(cpu.gte_ts_done)); gte_execute(&cpu, 0x30u); if (g_test_gte_set_calls != 2u || g_test_gte_last_latency != 55u || cpu.gte_ts_done != 243u) return fail_value("back-to-back command timing", 0, 0x30u, 0, 243u, static_cast(cpu.gte_ts_done)); return 0; } int test_precise_sxy_invalidation() { CPUState cpu{}; for (uint8_t reg = 0; reg < 32; ++reg) { gte_test_set_precise_valid_mask(0xFu); gte_write_data(&cpu, reg, 0x12345678u); /* The PGXP shadow drops exactly the register(s) the guest wrote: * SXY0/SXY1 clear their own slot; SXY2 and SXYP clear both mirrors * (regs 14+15). The untouched slots stay live — their register * values did not change (SXYP's hardware FIFO shift makes 12/13 * stale in VALUE, which validate-on-read handles at use; liveness * alone is not a correctness claim). */ uint32_t expected = 0xFu; if (reg == 12 || reg == 13) expected &= ~(1u << (reg - 12)); else if (reg == 14 || reg == 15) expected &= ~0xCu; const uint32_t actual = gte_test_get_precise_valid_mask(); if (actual != expected) return fail_value("precise SXY invalidation", 0, reg, 0x12345678u, expected, actual); } for (uint16_t wide_reg = 32; wide_reg <= 255; ++wide_reg) { const uint8_t reg = static_cast(wide_reg); gte_test_set_precise_valid_mask(0xFu); gte_write_data(&cpu, reg, 0x87654321u); if (gte_test_get_precise_valid_mask() != 0xFu) return fail_value("invalid data preserves precise SXY", 0, reg, 0x87654321u, 0xFu, gte_test_get_precise_valid_mask()); } for (uint16_t wide_reg = 0; wide_reg <= 255; ++wide_reg) { const uint8_t reg = static_cast(wide_reg); gte_test_set_precise_valid_mask(0xFu); gte_write_ctrl(&cpu, reg, 0xCAFEBABEu); if (gte_test_get_precise_valid_mask() != 0xFu) return fail_value("ctrl preserves precise SXY", 0, reg, 0xCAFEBABEu, 0xFu, gte_test_get_precise_valid_mask()); } gte_test_set_precise_valid_mask(0xFu); gte_test_set_timeline_generations(41u, 73u); gte_canonicalize_cpu_state(&cpu); if (gte_test_get_precise_valid_mask() != 0u) return fail_value("canonicalize invalidates precise SXY", 0, 0, 0, 0, gte_test_get_precise_valid_mask()); if (gte_test_get_precision_generation() != 42u) return fail_value("canonicalize advances precision generation", 0, 0, 0, 42u, gte_test_get_precision_generation()); if (gte_test_get_geometry_generation() != 74u) return fail_value("canonicalize advances geometry generation", 0, 0, 0, 74u, gte_test_get_geometry_generation()); /* Generation zero is reserved. Wrapping must clear stale generations and * restart at one rather than resurrecting ancient generation-one entries. */ gte_test_set_precise_valid_mask(0xFu); gte_test_set_timeline_generations(0xFFFFFFFFu, 0xFFFFFFFFu); gte_precision_timeline_invalidate(); if (gte_test_get_precise_valid_mask() != 0u || gte_test_get_precision_generation() != 1u || gte_test_get_geometry_generation() != 1u) return fail_value("timeline generation wrap", 0, 0, 0, 1u, gte_test_get_precision_generation()); 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_saturated_nclip_keeps_architectural_result() { CPUState cpu{}; gte_precision_tracking_set(1); g_test_precise_nclip_enabled = 1; cpu.gte_ctrl[26] = 300; const int px[] = {1100, 1200, 1300}, py[] = {100, 200, 400}; for (unsigned i=0;i<3;i++) { uint32_t packed = 1023u | ((uint32_t)py[i] << 16); cpu.gte_data[12+i] = packed; cpu.gte_data[17+i] = 1000; gte_test_seed_precise_projection(i, packed, px[i]*65536, py[i]*65536, 1000); } gte_execute(&cpu, 6); int sign = 0; if (cpu.gte_data[24] != 0 || !gte_nclip_native_wide_sign(0, &sign) || sign != 1 || gte_nclip_native_wide_sign(1, &sign)) return fail_value("saturated winding rescue preserves native zero MAC0",0,6,0,0,cpu.gte_data[24]); cpu.gte_data[17] = 10; gte_execute(&cpu, 6); if (gte_nclip_native_wide_sign(0, &sign) || cpu.gte_data[24] != 0) return fail_value("near depth retains native reject",0,6,0,0,cpu.gte_data[24]); cpu.gte_data[17] = 1000; gte_execute(&cpu, 6); gte_precision_timeline_invalidate(); if (gte_nclip_native_wide_sign(0, &sign)) return fail_value("timeline invalidation clears winding rescue",0,6,0,0,1); gte_test_seed_precise_projection(0, cpu.gte_data[12] ^ 1u, px[0]*65536, py[0]*65536, 1000); gte_execute(&cpu, 6); if (gte_nclip_native_wide_sign(0, &sign)) return fail_value("stale NCLIP projection refuses rescue",0,6,0,0,1); g_test_precise_nclip_enabled = 0; return 0; } /* PGXP precise culling (docs/ENHANCEMENTS.md G1.12): NCLIP's MAC0 takes the * exact sign only while geometry correction and culling are armed, only when * the three SXY shadows are believed, and never in a compared pass. */ int test_pgxp_culling() { gte_precision_tracking_set(1); gte_geometry_correction_set(1); pgxp_set_preserve_projection(1); pgxp_set_tolerance(-1.0f); /* A far road row: the three vertices all round to y = 113 (MAC0 0, the * game culls it); exact y 112.64 / 113.12 / 113.83 (positive area). */ const uint32_t packed[3] = {(113u << 16) | 100u, (113u << 16) | 220u, (113u << 16) | 160u}; const int32_t x16[3] = {100 << 16, 220 << 16, 160 << 16}; const int32_t y16[3] = {(112 << 16) + 41943, (113 << 16) + 7864, (113 << 16) + 54394}; const int64_t cross = ((int64_t)x16[1] - x16[0]) * ((int64_t)y16[2] - y16[0]) - ((int64_t)y16[1] - y16[0]) * ((int64_t)x16[2] - x16[0]); const int32_t area = (int32_t)((uint64_t)(cross + (1ll << 31)) >> 32); auto nclip = [&](const uint32_t (&w)[3], const int32_t (&xs)[3], const int32_t (&ys)[3], uint32_t *flag) { CPUState cpu{}; for (uint32_t i = 0; i < 3; ++i) { cpu.gte_data[12 + i] = w[i]; gte_test_seed_precise_projection(i, w[i], xs[i], ys[i], 100); } gte_execute(&cpu, 0x06u); if (flag) *flag = cpu.gte_ctrl[31]; return static_cast(cpu.gte_data[24]); }; PGXPStats a{}, b{}; pgxp_get_stats(&a); uint32_t flag_off = 0, flag_on = 0; /* Culling off: the guest sees the hardware's 0; the disagreement is * counted (the crack exposure), not corrected. */ pgxp_set_culling(0); if (int32_t m = nclip(packed, x16, y16, &flag_off); m != 0) return fail_value("culling off keeps MAC0", 0, 0x06u, 0, 0u, (uint32_t)m); pgxp_get_stats(&b); if (b.nclip_disagree != a.nclip_disagree + 1 || b.nclip_corrected != a.nclip_corrected) return fail_value("culling off counts the disagreement", 0, 0x06u, 0, 1u, (uint32_t)(b.nclip_disagree - a.nclip_disagree)); /* Culling on: MAC0 is the exact doubled area, rounded (>= 1). */ pgxp_set_culling(1); if (area < 1) return fail_value("test area", 0, 0x06u, 0, 1u, (uint32_t)area); if (int32_t m = nclip(packed, x16, y16, &flag_on); m != area) return fail_value("culling on takes the exact sign", 0, 0x06u, 0, (uint32_t)area, (uint32_t)m); if (flag_on != flag_off) return fail_value("culling keeps FLAG", 0, 0x06u, 0, flag_off, flag_on); pgxp_get_stats(&a); if (a.nclip_corrected != b.nclip_corrected + 1) return fail_value("culling on counts the correction", 0, 0x06u, 0, 1u, (uint32_t)(a.nclip_corrected - b.nclip_corrected)); /* Reversed winding: the exact sign is negative, magnitude at least 1. */ { const uint32_t w[3] = {packed[0], packed[2], packed[1]}; const int32_t xs[3] = {x16[0], x16[2], x16[1]}; const int32_t ys[3] = {y16[0], y16[2], y16[1]}; if (int32_t m = nclip(w, xs, ys, nullptr); m != -area) return fail_value("culling keeps the reversed sign", 0, 0x06u, 0, (uint32_t)-area, (uint32_t)m); } /* A native-positive sliver whose exact winding is reversed: the game * now sees it back-facing. Native (0,0),(10,0),(5,1): MAC0 +10; exact * y2 = -0.25 (inside the window). */ { const uint32_t w[3] = {0u, 10u, (1u << 16) | 5u}; const int32_t xs[3] = {0, 10 << 16, 5 << 16}; const int32_t ys[3] = {0, 0, -(1 << 14)}; pgxp_set_culling(0); if (int32_t m = nclip(w, xs, ys, nullptr); m != 10) return fail_value("native sliver MAC0", 0, 0x06u, 0, 10u, (uint32_t)m); pgxp_set_culling(1); if (int32_t m = nclip(w, xs, ys, nullptr); m != -3) return fail_value("reversed sliver MAC0", 0, 0x06u, 0, (uint32_t)-3, (uint32_t)m); } /* Signs that agree keep the integer MAC0 exactly. */ { const uint32_t w[3] = {0u, 10u, (4u << 16) | 5u}; const int32_t xs[3] = {0, 10 << 16, 5 << 16}; const int32_t ys[3] = {1 << 15, 0, (4 << 16) + (1 << 15)}; if (int32_t m = nclip(w, xs, ys, nullptr); m != 40) return fail_value("agreeing sign keeps MAC0", 0, 0x06u, 0, 40u, (uint32_t)m); } /* A stale shadow (the register word changed under it) fails closed. */ { const uint32_t w[3] = {packed[0], packed[1], packed[2]}; CPUState cpu{}; for (uint32_t i = 0; i < 3; ++i) { cpu.gte_data[12 + i] = w[i]; gte_test_seed_precise_projection(i, w[i] ^ (i == 2 ? 1u : 0u), x16[i], y16[i], 100); } gte_execute(&cpu, 0x06u); if (cpu.gte_data[24] != 0u) return fail_value("stale shadow keeps MAC0", 0, 0x06u, 0, 0u, cpu.gte_data[24]); } /* Compared passes see the hardware result: the overlay shadow diff (its * interpreter pass included) and a speculative pass. */ g_test_shadow_diff = 1; if (int32_t m = nclip(packed, x16, y16, nullptr); m != 0) return fail_value("shadow diff holds culling off", 0, 0x06u, 0, 0u, (uint32_t)m); g_test_shadow_diff = 0; gte_precision_speculative_begin(); { CPUState cpu{}; for (uint32_t i = 0; i < 3; ++i) cpu.gte_data[12 + i] = packed[i]; gte_execute(&cpu, 0x06u); gte_precision_speculative_end(); if (cpu.gte_data[24] != 0u) return fail_value("speculative pass holds culling off", 0, 0x06u, 0, 0u, cpu.gte_data[24]); } /* Geometry correction off: nothing is corrected or counted. */ gte_geometry_correction_set(0); pgxp_get_stats(&a); if (int32_t m = nclip(packed, x16, y16, nullptr); m != 0) return fail_value("geometry off keeps MAC0", 0, 0x06u, 0, 0u, (uint32_t)m); pgxp_get_stats(&b); if (b.nclip_precise != a.nclip_precise || b.nclip_disagree != a.nclip_disagree) return fail_value("geometry off counts nothing", 0, 0x06u, 0, 0u, (uint32_t)(b.nclip_precise - a.nclip_precise)); pgxp_set_culling(0); pgxp_set_preserve_projection(0); return 0; } int test_precision_speculative_transaction() { constexpr uint32_t address = 0x00123450u; constexpr uint32_t packed = 0x00420021u; constexpr int32_t x16 = 0x00123456; constexpr int32_t y16 = -0x00034567; constexpr uint16_t z = 0x4567u; gte_precision_tracking_set(1); gte_geometry_correction_set(1); gte_test_set_timeline_generations(41u, 73u); gte_test_seed_precise_projection(2, packed, x16, y16, z); gte_precision_store_word(address, 14); gte_test_seed_geometry(packed, x16, y16); int32_t got_x = 0, got_y = 0; uint16_t got_z = 0; if (!gte_precision_load_word(address, packed, &got_x, &got_y, &got_z) || got_x != x16 || got_y != y16 || got_z != z) return fail_value("precision seed lookup", 0, 14, packed, z, got_z); if (!gte_geometry_correction_lookup(packed, &got_x, &got_y) || got_x != x16 || got_y != y16) return fail_value("geometry seed lookup", 0, 0, packed, static_cast(x16), static_cast(got_x)); gte_precision_speculative_begin(); gte_precision_speculative_begin(); if (gte_precision_load_word(address, packed, &got_x, &got_y, &got_z) != 0) return fail_value("speculative precision read", 0, 0, packed, 0, 1); if (gte_geometry_correction_lookup(packed, &got_x, &got_y) != 0) return fail_value("speculative geometry read", 0, 0, packed, 0, 1); gte_precision_invalidate_word(address); // suppressed: must not age live entry CPUState cpu{}; gte_write_data(&cpu, 14, 0xDEADBEEFu); // mutates speculative SXY only gte_precision_speculative_end(); if (gte_precision_load_word(address, packed, &got_x, &got_y, &got_z) != 0) return fail_value("nested speculation remains isolated", 0, 0, packed, 0, 1); gte_precision_speculative_end(); if (gte_test_get_precise_valid_mask() != 0x4u) return fail_value("speculative SXY restore", 0, 0, 0, 0x4u, gte_test_get_precise_valid_mask()); if (!gte_precision_load_word(address, packed, &got_x, &got_y, &got_z) || got_x != x16 || got_y != y16 || got_z != z) return fail_value("authoritative precision survives", 0, 0, packed, z, got_z); if (!gte_geometry_correction_lookup(packed, &got_x, &got_y) || got_x != x16 || got_y != y16) return fail_value("authoritative geometry survives", 0, 0, packed, static_cast(x16), static_cast(got_x)); /* A raw savestate/import restore is authoritative even when host polling * reaches it during speculation. Defer its timeline invalidation until the * outer transaction closes, but never restore pre-load provenance. */ gte_precision_speculative_begin(); gte_precision_speculative_begin(); gte_precision_timeline_invalidate(); if (gte_test_get_precision_generation() != 41u || gte_test_get_geometry_generation() != 73u) return fail_value("deferred timeline invalidation", 0, 0, 0, 41u, gte_test_get_precision_generation()); gte_precision_speculative_end(); if (gte_test_get_precision_generation() != 41u) return fail_value("nested timeline remains deferred", 0, 0, 0, 41u, gte_test_get_precision_generation()); gte_precision_speculative_end(); if (gte_test_get_precise_valid_mask() != 0u || gte_test_get_precision_generation() != 42u || gte_test_get_geometry_generation() != 74u || gte_precision_load_word(address, packed, nullptr, nullptr, nullptr) != 0 || gte_geometry_correction_lookup(packed, nullptr, nullptr) != 0) return fail_value("authoritative restore invalidation", 0, 0, 0, 42u, gte_test_get_precision_generation()); /* Exercise each cache's wrap independently and prove stale entries miss, * rather than checking generation counters alone. */ gte_test_set_timeline_generations(0xFFFFFFFFu, 90u); gte_test_seed_precise_projection(2, packed, x16, y16, z); gte_precision_store_word(address, 14); gte_test_seed_geometry(packed, x16, y16); gte_precision_timeline_invalidate(); if (gte_test_get_precision_generation() != 1u || gte_test_get_geometry_generation() != 91u || gte_precision_load_word(address, packed, nullptr, nullptr, nullptr) != 0 || gte_geometry_correction_lookup(packed, nullptr, nullptr) != 0) return fail_value("independent precision wrap", 0, 0, 0, 1u, gte_test_get_precision_generation()); gte_test_set_timeline_generations(120u, 0xFFFFFFFFu); gte_test_seed_precise_projection(2, packed, x16, y16, z); gte_precision_store_word(address, 14); gte_test_seed_geometry(packed, x16, y16); gte_precision_timeline_invalidate(); if (gte_test_get_precision_generation() != 121u || gte_test_get_geometry_generation() != 1u || gte_precision_load_word(address, packed, nullptr, nullptr, nullptr) != 0 || gte_geometry_correction_lookup(packed, nullptr, nullptr) != 0) return fail_value("independent geometry wrap", 0, 0, 0, 1u, gte_test_get_geometry_generation()); return 0; } } // namespace /* Preserve projection precision (docs/ENHANCEMENTS.md G1.11) changes the * host-only SXY shadow and nothing the guest can see: RTPS/RTPT leave exactly * the reference oracle's registers (SXY, MAC0, IR, FLAG, ...) with it on, and * a qualifying vertex's shadow is the exact projection. */ int test_preserve_projection_is_shadow_only() { gte_precision_tracking_set(1); for (uint32_t function : {0x01u, 0x30u}) { for (unsigned iteration = 0; iteration < 256; ++iteration) { CPUState seed; randomize_gte(seed); CPUState expected = seed; CPUState actual = seed; const uint32_t cmd = (random_u32() & ~0x3Fu) | function; gte_test_execute_reference(&expected, cmd); pgxp_set_preserve_projection(1); gte_execute(&actual, cmd); pgxp_set_preserve_projection(0); if (!same_gte(expected, actual)) return fail_state("preserve projection guest state", iteration, function, cmd, expected, actual); } } /* One qualifying vertex with fractional MAC bits: RT not a pure scale, * a translation, H = 300, OFX/OFY = (160.25, 119.5). */ CPUState cpu{}; auto pack = [](int32_t lo, int32_t hi) { return (static_cast(hi & 0xFFFF) << 16) | static_cast(lo & 0xFFFF); }; const int32_t rt[9] = {4100, 3, 0, 0, 4090, 5, 0, 0, 4096}; cpu.gte_ctrl[0] = pack(rt[0], rt[1]); cpu.gte_ctrl[1] = pack(rt[2], rt[3]); cpu.gte_ctrl[2] = pack(rt[4], rt[5]); cpu.gte_ctrl[3] = pack(rt[6], rt[7]); cpu.gte_ctrl[4] = static_cast(rt[8]); const int32_t tr[3] = {7, -3, 40}; cpu.gte_ctrl[5] = static_cast(tr[0]); cpu.gte_ctrl[6] = static_cast(tr[1]); cpu.gte_ctrl[7] = static_cast(tr[2]); const int32_t ofx = (160 << 16) + 0x4000, ofy = (119 << 16) + 0x8000; cpu.gte_ctrl[24] = static_cast(ofx); cpu.gte_ctrl[25] = static_cast(ofy); cpu.gte_ctrl[26] = 300u; const int32_t v[3] = {101, -53, 937}; cpu.gte_data[0] = pack(v[0], v[1]); cpu.gte_data[1] = static_cast(v[2]); const uint32_t rtps = 0x00080001u; /* RTPS, sf=1, lm=0 */ const int64_t mac1 = (int64_t)tr[0] * 4096 + (int64_t)rt[0] * v[0] + (int64_t)rt[1] * v[1] + (int64_t)rt[2] * v[2]; const int64_t mac2 = (int64_t)tr[1] * 4096 + (int64_t)rt[3] * v[0] + (int64_t)rt[4] * v[1] + (int64_t)rt[5] * v[2]; const int64_t mac3 = (int64_t)tr[2] * 4096 + (int64_t)rt[6] * v[0] + (int64_t)rt[7] * v[1] + (int64_t)rt[8] * v[2]; const double ex = ofx / 65536.0 + (double)mac1 * 300.0 / (double)mac3; const double ey = ofy / 65536.0 + (double)mac2 * 300.0 / (double)mac3; CPUState ir_run = cpu; gte_execute(&ir_run, rtps); PreciseState ir{}; gte_test_get_precise_projection(2, &ir.packed, &ir.x16, &ir.y16, &ir.z, &ir.valid); pgxp_set_preserve_projection(1); CPUState ppp_run = cpu; gte_execute(&ppp_run, rtps); pgxp_set_preserve_projection(0); PreciseState ppp{}; gte_test_get_precise_projection(2, &ppp.packed, &ppp.x16, &ppp.y16, &ppp.z, &ppp.valid); if (!same_gte(ir_run, ppp_run)) return fail_state("preserve projection keeps the guest SXY", 0, 1, rtps, ir_run, ppp_run); const uint32_t sxy2 = ppp_run.gte_data[14]; const int32_t native_x = static_cast(sxy2 & 0xFFFFu); const int32_t native_y = static_cast(sxy2 >> 16); if (!ir.valid || ir.packed != sxy2 || (ir.x16 >> 16) != native_x || (ir.y16 >> 16) != native_y) return fail_value("IR-path shadow truncates to SXY2", 0, 14, sxy2, static_cast(native_x), static_cast(ir.x16 >> 16)); if (!ppp.valid || ppp.packed != sxy2 || ppp.z != ir.z) return fail_value("exact-projection shadow keys the same word", 0, 14, sxy2, sxy2, ppp.packed); if (ppp.x16 != (int32_t)std::floor(ex * 65536.0) || ppp.y16 != (int32_t)std::floor(ey * 65536.0)) return fail_value("exact-projection shadow", 0, 14, sxy2, static_cast((int32_t)std::floor(ex * 65536.0)), static_cast(ppp.x16)); if (ppp.x16 == ir.x16 && ppp.y16 == ir.y16) return fail_value("exact projection differs from the IR path", 0, 14, sxy2, 0, 0); /* Divide overflow (H >= 2*SZ3): the guest quotient saturates, so the * shadow stays on the IR path even with the mode on. */ CPUState near_cpu = cpu; near_cpu.gte_data[1] = 100u; /* SZ3 = 140 < H/2 */ pgxp_set_preserve_projection(1); gte_execute(&near_cpu, rtps); pgxp_set_preserve_projection(0); PreciseState sat{}; gte_test_get_precise_projection(2, &sat.packed, &sat.x16, &sat.y16, &sat.z, &sat.valid); const uint32_t near_sxy = near_cpu.gte_data[14]; if (!sat.valid || (sat.x16 >> 16) != static_cast(near_sxy & 0xFFFFu)) return fail_value("divide overflow keeps the IR shadow", 0, 14, near_sxy, near_sxy & 0xFFFFu, static_cast(sat.x16 >> 16)); return 0; } int main() { if (int rc = test_hardware_register_semantics()) return rc; if (int rc = test_canonicalizer()) return rc; if (int rc = test_reads()) return rc; if (int rc = test_writes()) return rc; if (int rc = test_sequence_fuzz()) return rc; if (int rc = test_command_marshaling()) return rc; if (int rc = test_projection_override_restores_transform()) 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_saturated_nclip_keeps_architectural_result()) return rc; if (int rc = test_precision_speculative_transaction()) return rc; if (int rc = test_preserve_projection_is_shadow_only()) return rc; if (int rc = test_pgxp_culling()) return rc; std::puts("PASS: canonical GTE register helpers match GTEState transfer oracle"); return 0; }