cmake_minimum_required(VERSION 3.20)
project(PSXRecomp C CXX)
set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
include(CTest)

# libchdr is needed by exactly one target here: the user-facing `psxrecomp`
# media CLI. It arrives via FetchContent + ExternalProject, which is by far the
# heaviest and most fragile thing in this configure — it needs the network on a
# cold cache, and ExternalProject cannot handle a source path containing a colon
# (a repo directory like "Marvel vs. Capcom: Clash of Super Heroes Recomp"
# truncates to "…/Marvel vs. Capcom.rule" and configure dies).
#
# Turning it off lets a caller that only wants an analysis or emitter binary
# configure this project without any of that. Nothing but the media CLI is
# affected; the emitters, psxrecomp-analyze, and every test are chd-free.
option(PSXRECOMP_ENABLE_CHD
       "Build the psxrecomp media CLI (requires libchdr via FetchContent)" ON)

if(PSXRECOMP_ENABLE_CHD)
    # The Python disc tools (psxrecomp_cli.py verify-disc, tools/prepare_disc.py,
    # tools/new_project_layout/probe_disc.py) read .chd through this same
    # decoder via ctypes, so libchdr is also built as a shared library, placed
    # next to the emitters where tools/psx_chd.py looks for it. BUILD_SHARED_LIBS
    # is scoped to that include: libchdr's codecs are declared STATIC and
    # chdr-static (what psxrecomp links) is unaffected; nothing else in this
    # project turns shared.
    set(_psx_saved_build_shared "${BUILD_SHARED_LIBS}")
    set(BUILD_SHARED_LIBS ON)
    include("${CMAKE_CURRENT_SOURCE_DIR}/../runtime/chd_dependency.cmake")
    set(BUILD_SHARED_LIBS "${_psx_saved_build_shared}")
    unset(_psx_saved_build_shared)
    if(TARGET chdr)
        set_target_properties(chdr PROPERTIES
            RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}"
            LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}")
    endif()
endif()

add_subdirectory(lib/fmt)

# Vendored fmt 9.1.0 enables its `consteval` format-string check on Apple
# clang via the `__cpp_consteval` clause in core.h, even though the same
# block tries to exclude Apple toolchains — and Apple clang 21 then rejects
# fmt's own literal format strings ("call to consteval function is not a
# constant expression"). Force FMT_CONSTEVAL empty to take fmt's
# non-consteval compile-time-check path. Scoped to non-MSVC so Windows is
# untouched.
if(NOT MSVC)
    target_compile_definitions(fmt PUBLIC FMT_CONSTEVAL=)
endif()

# Rabbitizer MIPS decoder
add_library(rabbitizer STATIC)
target_sources(rabbitizer PRIVATE
    lib/rabbitizer/cplusplus/src/analysis/LoPairingInfo.cpp
    lib/rabbitizer/cplusplus/src/analysis/RegistersTracker.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstrId.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstrIdType.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstructionBase.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstructionCpu.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstructionR3000GTE.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstructionR5900.cpp
    lib/rabbitizer/cplusplus/src/instructions/InstructionRsp.cpp
    lib/rabbitizer/src/analysis/RabbitizerLoPairingInfo.c
    lib/rabbitizer/src/analysis/RabbitizerRegistersTracker.c
    lib/rabbitizer/src/analysis/RabbitizerTrackedRegisterState.c
    lib/rabbitizer/src/common/RabbitizerConfig.c
    lib/rabbitizer/src/common/RabbitizerVersion.c
    lib/rabbitizer/src/common/Utils.c
    lib/rabbitizer/src/instructions/RabbitizerInstrCategory.c
    lib/rabbitizer/src/instructions/RabbitizerInstrDescriptor.c
    lib/rabbitizer/src/instructions/RabbitizerInstrId.c
    lib/rabbitizer/src/instructions/RabbitizerInstrIdType.c
    lib/rabbitizer/src/instructions/RabbitizerInstrSuffix.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionCpu/RabbitizerInstructionCpu_OperandType.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_OperandType.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR3000GTE/RabbitizerInstructionR3000GTE_ProcessUniqueId.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_OperandType.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionR5900/RabbitizerInstructionR5900_ProcessUniqueId.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_OperandType.c
    lib/rabbitizer/src/instructions/RabbitizerInstructionRsp/RabbitizerInstructionRsp_ProcessUniqueId.c
    lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction.c
    lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Disassemble.c
    lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Examination.c
    lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_Operand.c
    lib/rabbitizer/src/instructions/RabbitizerInstruction/RabbitizerInstruction_ProcessUniqueId.c
    lib/rabbitizer/src/instructions/RabbitizerRegister.c
    lib/rabbitizer/src/instructions/RabbitizerRegisterDescriptor.c
)
target_include_directories(rabbitizer PRIVATE
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/rabbitizer/tables
)

# PSXRecomp — MIPS→C recompiler tool
set(PSXRECOMP_GAME_SOURCES
    src/ps1_exe_parser.cpp
    src/mips_decoder.cpp
    src/control_flow.cpp
    src/function_analysis.cpp
    src/code_generator.cpp
    src/pgxp_hook_emitter.cpp
    src/bios_address_model.cpp
    src/annotations.cpp
    src/config_loader.cpp
    src/recompiler_patch.cpp
    src/game_dispatch_emitter.cpp
    src/segment_variants.cpp
    src/main_psx.cpp
)

add_executable(psxrecomp-game ${PSXRECOMP_GAME_SOURCES})
target_include_directories(psxrecomp-game PRIVATE
    include
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/fmt/include
    lib/toml11
    lib/ELFIO
)
target_link_libraries(psxrecomp-game PRIVATE fmt rabbitizer)
if(MINGW)
    target_link_options(psxrecomp-game PRIVATE -Wl,--stack,16777216)
elseif(MSVC)
    target_link_options(psxrecomp-game PRIVATE /STACK:16777216)
endif()

# psxrecomp-toml — PS-X EXE → game.toml generator.
# Standalone tool that analyses a PS-X EXE and generates a complete game.toml
# config with auto-detected fields (load_address, entry_pc, text_size,
# stack_base) and optional JAL-target seed generation.
add_executable(psxrecomp-toml
    src/ps1_exe_parser.cpp
    src/main_toml.cpp
)
target_include_directories(psxrecomp-toml PRIVATE
    include
    lib/fmt/include
)
target_link_libraries(psxrecomp-toml PRIVATE fmt)

# psxrecomp — user-facing media-to-source project generator.
# The only libchdr consumer in this project; see PSXRECOMP_ENABLE_CHD above.
if(PSXRECOMP_ENABLE_CHD)
    add_executable(psxrecomp
        src/main_cli.cpp
        src/cli_boot_path.cpp
        src/ps1_exe_parser.cpp
        ../runtime/src/cue_sheet.cpp
        ../runtime/src/iso_reader.cpp
    )
    target_include_directories(psxrecomp PRIVATE
        include
        ../runtime/include
        lib/fmt/include
    )
    target_link_libraries(psxrecomp PRIVATE fmt chdr-static)
    if(MINGW)
        target_link_options(psxrecomp PRIVATE -Wl,--stack,16777216)
    elseif(MSVC)
        target_link_options(psxrecomp PRIVATE /STACK:16777216)
    endif()
endif()

# psxrecomp-analyze — developer-facing static function discovery.
#
# NOT part of the recompilation path. It turns the boundary/CFG/jump-table
# knowledge the emitters already derive into a queryable artifact set for
# modding and decomp work (see docs/FUNCTION_DISCOVERY.md).
#
# The source list is the guarantee: no runtime/ translation unit appears here
# and none may be added. The tool's whole claim is that everything it reports
# was proven from the executable image alone, with no trace, overlay-capture,
# or executed-PC feedback. Linking the runtime would quietly make that false.
add_executable(psxrecomp-analyze
    src/ps1_exe_parser.cpp
    src/mips_decoder.cpp
    src/function_analysis.cpp
    src/analysis_db.cpp
    src/bios_call_names.cpp
    src/analysis_export.cpp
    src/widescreen_scan.cpp
    src/main_analyze.cpp
)
target_include_directories(psxrecomp-analyze PRIVATE
    include
    src
    ../runtime/include   # ws_cull_detect.h — the shared auto_screen_x detector
    lib/fmt/include
    lib/toml11
)
target_link_libraries(psxrecomp-analyze PRIVATE fmt)
if(MINGW)
    target_link_options(psxrecomp-analyze PRIVATE -Wl,--stack,16777216)
elseif(MSVC)
    target_link_options(psxrecomp-analyze PRIVATE /STACK:16777216)
endif()

# ---- baked codegen hash (stale-binary guard) --------------------------------
# Bake the SAME emitter-source hash the runtime folds into its overlay cache
# tag (runtime/runtime.cmake -> hash_codegen.cmake, shared canonical list in
# runtime/codegen_hash_sources.cmake) into THIS binary, exposed via
# `psxrecomp-game --codegen-hash`. compile_overlays.py compares it against the
# hash it stamps on the cache dir (from --runtime-include) and HARD-FAILS on
# mismatch. Closes the 2026-07-01 Tomba pause-menu wedge class: a stale
# recompiler binary emitting shards that get filed under the CURRENT cg tag
# (read tag == write tag, content stale — invisible to the tag alone).
set(PSXRECOMP_CODEGEN_HASH_ROOT ${CMAKE_CURRENT_SOURCE_DIR}/..)
include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/codegen_hash_sources.cmake)
set(_baked_hash_hdr ${CMAKE_CURRENT_BINARY_DIR}/psxrecomp_baked_codegen_hash.h)
# Build-owned, never written into the source tree: runtime.cmake generates the
# same header into its own build dir, and both derive the hash from the shared
# PSXRECOMP_CODEGEN_HASH_SRCS list, so the two copies cannot drift.
set(_runtime_hash_hdr ${CMAKE_CURRENT_BINARY_DIR}/runtime/include/overlay_codegen_hash.h)
add_custom_command(
    OUTPUT  ${_baked_hash_hdr} ${_runtime_hash_hdr}
    COMMAND ${CMAKE_COMMAND} -E make_directory
            ${CMAKE_CURRENT_BINARY_DIR}/runtime/include
    COMMAND ${CMAKE_COMMAND} -DOUT=${_baked_hash_hdr}
            "-DSRCS=${PSXRECOMP_CODEGEN_HASH_SRCS}"
            -P ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/hash_codegen.cmake
    COMMAND ${CMAKE_COMMAND} -DOUT=${_runtime_hash_hdr}
            "-DSRCS=${PSXRECOMP_CODEGEN_HASH_SRCS}"
            -P ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/hash_codegen.cmake
    DEPENDS ${PSXRECOMP_CODEGEN_HASH_SRCS}
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/hash_codegen.cmake
    COMMENT "Hashing codegen sources -> baked and runtime hash headers"
    VERBATIM)
# Overlay generation is usable immediately after a tools-only build, before
# configuring any runtime. Both consumers must have the same cache identity.
add_custom_target(psxrecomp_baked_codegen_hash
    DEPENDS ${_baked_hash_hdr} ${_runtime_hash_hdr})
add_dependencies(psxrecomp-game psxrecomp_baked_codegen_hash)
# Force main_psx.cpp to recompile whenever the hash header content changes
# (same OBJECT_DEPENDS hardening as runtime.cmake — __has_include depfile gap).
set_source_files_properties(src/main_psx.cpp PROPERTIES OBJECT_DEPENDS ${_baked_hash_hdr})
target_include_directories(psxrecomp-game PRIVATE ${CMAKE_CURRENT_BINARY_DIR})

# NOTE (2026-07-27): the legacy `PSXRecomp` target was REMOVED here.
# It built ${PSXRECOMP_GAME_SOURCES} — byte-for-byte the same source list as
# `psxrecomp-game` above — and was a leftover from before that target was
# introduced. Because Windows and macOS filesystems are case-insensitive, its
# output `PSXRecomp.exe` collided with the user-facing CLI `psxrecomp.exe`
# (the `psxrecomp` target above): both wrote the SAME file on disk and the
# last one linked won. In practice the CLI lost, so `psxrecomp.exe` shipped in
# `psxrecomp-cli-windows-x86_64.zip` (tools/build_cli.py) was actually the game
# recompiler, with a completely different argument grammar than the README
# documents. The bug is invisible on Linux, which is why it survived.
# The `PSXRecomp.exe` users are told to run in README's "Release Package"
# section is a DIFFERENT artifact — the BIOS-only runtime, whose name comes
# from OUTPUT_NAME in runtime/runtime.cmake — and is unaffected by this removal.
# See the collision guard at the bottom of this file.

# psxrecomp-bios — Phase 1a BIOS boot slice recompiler.
# Strict, fail-loud, ingests bios/SCPH1001.BIN as a flat ROM at 0xBFC00000.
# Independent of PSXRecomp's PS1-EXE entry point. Shares mips_decoder +
# ps1_exe_parser sources because mips_decoder.h depends on PS1Executable
# (used only by decode_executable, which psxrecomp-bios does not call).
add_executable(psxrecomp-bios
    src/ps1_exe_parser.cpp
    src/mips_decoder.cpp
    src/strict_translator.cpp
    src/pgxp_hook_emitter.cpp
    src/bios_slice_walker.cpp
    src/bios_address_model.cpp
    src/function_discovery.cpp
    src/full_function_emitter.cpp
    src/config_loader.cpp
    src/recompiler_patch.cpp
    src/main_bios.cpp
)
target_include_directories(psxrecomp-bios PRIVATE
    include
    src
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/fmt/include
    lib/toml11
)
target_link_libraries(psxrecomp-bios PRIVATE fmt rabbitizer)
if(MINGW)
    target_link_options(psxrecomp-bios PRIVATE -Wl,--stack,16777216)
elseif(MSVC)
    target_link_options(psxrecomp-bios PRIVATE /STACK:16777216)
endif()

# Released Windows CLI archives must run without a separate MinGW/MSYS2
# install. Prefer llvm-mingw (+ -static) in CI so emitters do not need
# libgcc/libstdc++ harvested from MSYS2. Keep opt-in for local debug builds.
option(PSXRECOMP_STATIC_CLI "Statically link Windows runtime into CLI tools" OFF)
if(PSXRECOMP_STATIC_CLI AND WIN32 AND (MINGW OR CMAKE_CXX_COMPILER_ID MATCHES "Clang"))
    set(_cli_targets psxrecomp-game psxrecomp-toml psxrecomp-bios psxrecomp-analyze)
    if(PSXRECOMP_ENABLE_CHD)
        list(APPEND _cli_targets psxrecomp)
    endif()
    if(TARGET chdr)
        # The Python tools load this with ctypes from a plain python.exe, with
        # no toolchain bin/ on PATH, so it must not pull libgcc or libwinpthread.
        list(APPEND _cli_targets chdr)
    endif()
    foreach(_cli_target IN LISTS _cli_targets)
        if(CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
            target_link_options(${_cli_target} PRIVATE
                -static -static-libgcc -static-libstdc++)
        else()
            # llvm-mingw / Clang: fully static CRT + libc++ / libunwind / pthread.
            target_link_options(${_cli_target} PRIVATE -static)
        endif()
    endforeach()
endif()

# BIOS address-model equivalence test — sweeps every 4-byte address through
# the profile-driven BiosAddressModel and compares against verbatim copies of
# the pre-refactor hardcoded SCPH1001 helpers; also exercises the from_config
# invariants. Run from the framework root: recompiler/build/bios_address_model_test
add_executable(bios_address_model_test
    tests/bios_address_model_test.cpp
    src/bios_address_model.cpp
    src/config_loader.cpp
    src/ps1_exe_parser.cpp
)
target_include_directories(bios_address_model_test PRIVATE
    include
    src
    lib/fmt/include
    lib/toml11
)
target_link_libraries(bios_address_model_test PRIVATE fmt)

# L1 decoder conformance test — compares MipsDecoder against Rabbitizer
# oracle for every instruction in SCPH1001.BIN code regions.
add_executable(l1_decoder_test
    tests/l1_decoder_test.cpp
    src/mips_decoder.cpp
    src/ps1_exe_parser.cpp
)
target_include_directories(l1_decoder_test PRIVATE
    include
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/fmt/include
)
target_link_libraries(l1_decoder_test PRIVATE fmt rabbitizer)

# L2 structural codegen test — verifies StrictTranslator output patterns
# for each MIPS instruction form.
add_executable(l2_structural_test
    tests/l2_structural_test.cpp
    src/mips_decoder.cpp
    src/ps1_exe_parser.cpp
    src/strict_translator.cpp
    src/pgxp_hook_emitter.cpp
)
target_include_directories(l2_structural_test PRIVATE
    include
    src
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/fmt/include
)
target_link_libraries(l2_structural_test PRIVATE fmt rabbitizer)

# Emitter directive-line guard — pins the predicate that keeps same-line
# appenders off preprocessor-directive lines (the PR #171 hazard class).
add_executable(emitter_directive_line_test
    tests/emitter_directive_line_test.cpp
    src/pgxp_hook_emitter.cpp
)
target_include_directories(emitter_directive_line_test PRIVATE
    include
    src
    lib/rabbitizer/include
    lib/rabbitizer/cplusplus/include
    lib/fmt/include
)
target_link_libraries(emitter_directive_line_test PRIVATE fmt rabbitizer)

if(BUILD_TESTING)
    add_executable(control_flow_metadata_test
        tests/control_flow_metadata_test.cpp
        src/control_flow.cpp)
    target_include_directories(control_flow_metadata_test PRIVATE include)
    target_link_libraries(control_flow_metadata_test PRIVATE fmt)
    add_test(NAME control_flow_metadata_test COMMAND control_flow_metadata_test)
    if(PSXRECOMP_ENABLE_CHD)
        add_executable(disc_identity_test ../runtime/tests/test_disc_identity.cpp
            ../runtime/src/disc_identity.cpp ../runtime/src/disc_path.cpp
            ../runtime/src/iso_reader.cpp ../runtime/src/cue_sheet.cpp
            ../runtime/src/psx_sha256.c ../runtime/src/crc32.c)
        target_include_directories(disc_identity_test PRIVATE ../runtime/include include)
        target_compile_options(disc_identity_test PRIVATE
            $<$<CXX_COMPILER_ID:MSVC>:/UNDEBUG>
            $<$<NOT:$<CXX_COMPILER_ID:MSVC>>:-UNDEBUG>)
        target_link_libraries(disc_identity_test PRIVATE chdr-static)
        include("${CMAKE_CURRENT_SOURCE_DIR}/../cmake/psx_sha256_file.cmake")
        psxrecomp_sha256_file(disc_identity_test)
        add_test(NAME disc_identity_test COMMAND disc_identity_test)
    endif()
    add_executable(host_path_test tests/host_path_test.cpp)
    target_include_directories(host_path_test PRIVATE include)
    add_test(NAME host_path_test COMMAND host_path_test)

    # PSXRTI3 route format and the record -> release replay round trip (T101).
    # BIOS-free, so they also run here where CI configures this tree.
    add_executable(input_route_v3_file_test ../runtime/tests/test_input_route_v3_file.c)
    target_include_directories(input_route_v3_file_test PRIVATE ../runtime/include)
    add_test(NAME input_route_v3_file_test COMMAND input_route_v3_file_test)
    if(Python3_Interpreter_FOUND)
        add_test(NAME public_commit_guard COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../tools/test_public_commits.py)
        foreach(_product IN ITEMS diagnostic release)
            set(_target input_route_session_${_product})
            add_executable(${_target} ../runtime/tests/test_input_route_session.c
                ../runtime/src/input_route_session.c ../runtime/src/disc_digest_cache.c
            ../runtime/src/psx_sha256.c)
            target_include_directories(${_target} PRIVATE ../runtime/include)
            target_compile_definitions(${_target} PRIVATE
                "PSX_FRAMEWORK_PIN=\"0123456789abcdef0123456789abcdef01234567\""
                $<$<STREQUAL:${_product},release>:PSX_NO_DEBUG_TOOLS=1>)
        endforeach()
        # PS1B-404: the release replay drives the route observer for pictures.
        target_sources(input_route_session_release PRIVATE
            ../runtime/src/input_route_observer.c)
        add_test(NAME input_route_session_test COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_input_route_session.py
            $<TARGET_FILE:input_route_session_diagnostic>
            $<TARGET_FILE:input_route_session_release>)
    endif()

    # The source GPU/SPU headers must stay valid C++ (main.cpp includes them).
    # Compiling succeeds or the build fails; the test only runs the result.
    foreach(_std IN ITEMS 17 20)
        add_executable(source_gpu_headers_cxx${_std}_test
            ../runtime/tests/test_source_gpu_headers_cxx.cpp)
        target_include_directories(source_gpu_headers_cxx${_std}_test PRIVATE ../runtime/include)
        set_target_properties(source_gpu_headers_cxx${_std}_test PROPERTIES
            CXX_STANDARD ${_std} CXX_STANDARD_REQUIRED ON CXX_EXTENSIONS OFF)
        # GCC rejects C-only constructs outright; Clang accepts them as extensions.
        if(CMAKE_CXX_COMPILER_ID MATCHES "Clang")
            target_compile_options(source_gpu_headers_cxx${_std}_test PRIVATE -Werror=c11-extensions)
        endif()
        add_test(NAME source_gpu_headers_cxx${_std}_test COMMAND source_gpu_headers_cxx${_std}_test)
    endforeach()

    if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
        foreach(_mode IN ITEMS O0 O2)
            add_executable(tas_cdrom_subq_position_${_mode}
                ../runtime/tests/test_cdrom_subq_position.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_cdrom_subq_position_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_cdrom_subq_position_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_cdrom_subq_position_${_mode} PRIVATE m)
            add_test(NAME tas_cdrom_subq_position_${_mode} COMMAND tas_cdrom_subq_position_${_mode})
            add_executable(tas_cdrom_paused_subq_cycle_${_mode}
                ../runtime/tests/test_cdrom_paused_subq_cycle.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_cdrom_paused_subq_cycle_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_cdrom_paused_subq_cycle_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_cdrom_paused_subq_cycle_${_mode} PRIVATE m)
            add_test(NAME tas_cdrom_paused_subq_cycle_${_mode} COMMAND tas_cdrom_paused_subq_cycle_${_mode})
            if(PSXRECOMP_ENABLE_CHD)
                foreach(_disc_test IN ITEMS source_subq sbi)
                    if(_disc_test STREQUAL "source_subq")
                        set(_disc_source ../runtime/tests/test_iso_reader_source_subq.cpp)
                    else()
                        set(_disc_source ../runtime/tests/test_iso_reader_sbi.cpp)
                    endif()
                    add_executable(tas_iso_${_disc_test}_${_mode}
                        ${_disc_source}
                        ../runtime/src/iso_reader.cpp ../runtime/src/cue_sheet.cpp)
                    target_include_directories(tas_iso_${_disc_test}_${_mode} PRIVATE ../runtime/include include)
                    target_compile_options(tas_iso_${_disc_test}_${_mode} PRIVATE -${_mode} -UNDEBUG)
                    target_link_libraries(tas_iso_${_disc_test}_${_mode} PRIVATE chdr-static)
                endforeach()
                add_test(NAME tas_iso_source_subq_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_iso_reader_source_subq_contracts.py
                    $<TARGET_FILE:tas_iso_source_subq_${_mode}>)
                file(MAKE_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sbi-${_mode})
                add_test(NAME tas_iso_sbi_${_mode} COMMAND tas_iso_sbi_${_mode})
                set_tests_properties(tas_iso_sbi_${_mode} PROPERTIES WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}/sbi-${_mode})
            endif()
            add_executable(tas_gpu_sprite_blend_${_mode}
                ../runtime/tests/test_source_gpu_sprite_blend.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c)
            target_include_directories(tas_gpu_sprite_blend_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_sprite_blend_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_sprite_blend_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_sprite_blend_${_mode} COMMAND tas_gpu_sprite_blend_${_mode})
            add_executable(tas_gpu_sprite_fixed_${_mode}
                ../runtime/tests/test_source_gpu_sprite_fixed.c)
            target_include_directories(tas_gpu_sprite_fixed_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_sprite_fixed_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_sprite_fixed_${_mode} COMMAND tas_gpu_sprite_fixed_${_mode})
            add_executable(tas_gpu_textured_triangle_${_mode}
                ../runtime/tests/test_source_gpu_textured_triangle.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_textured_triangle_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_textured_triangle_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_textured_triangle_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_textured_triangle_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_textured_triangle_contracts.py
                $<TARGET_FILE:tas_gpu_textured_triangle_${_mode}>)
            add_executable(tas_gpu_polygon_order_${_mode}
                ../runtime/tests/test_source_gpu_polygon_order.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_polygon_order_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_polygon_order_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_polygon_order_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_polygon_order_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_polygon_order_contracts.py
                $<TARGET_FILE:tas_gpu_polygon_order_${_mode}>)
            add_executable(tas_gpu_shaded_texture_family_${_mode}
                ../runtime/tests/test_source_gpu_shaded_texture_family.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_shaded_texture_family_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_shaded_texture_family_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_shaded_texture_family_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_shaded_texture_family_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_shaded_texture_family_contracts.py
                $<TARGET_FILE:tas_gpu_shaded_texture_family_${_mode}>)
            add_executable(tas_gpu_shaded_triangle_${_mode}
                ../runtime/tests/test_source_gpu_shaded_triangle.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_shaded_triangle_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_shaded_triangle_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_shaded_triangle_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_shaded_triangle_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_shaded_triangle_contracts.py
                $<TARGET_FILE:tas_gpu_shaded_triangle_${_mode}>)
            add_executable(tas_gpu_shaded_quad_${_mode}
                ../runtime/tests/test_source_gpu_shaded_quad.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_shaded_quad_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_shaded_quad_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_shaded_quad_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_shaded_quad_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_shaded_quad_contracts.py
                $<TARGET_FILE:tas_gpu_shaded_quad_${_mode}>)
            add_executable(tas_gpu_polygon_wrap_${_mode}
                ../runtime/tests/test_source_gpu_polygon_wrap.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_polygon_wrap_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_polygon_wrap_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_polygon_wrap_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_polygon_wrap_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_polygon_wrap_contracts.py
                $<TARGET_FILE:tas_gpu_polygon_wrap_${_mode}>)
            # Disabled (PS1B-167): the harness expects the pre-T172 out-of-range reject;
            # hardware truncates vertex+offset to signed 11-bit (nocash GP0(E5h) note).
            # Rewrite harness + fixtures to the documented rule: follow-up PS1B-171.
            set_tests_properties(tas_gpu_polygon_wrap_${_mode} PROPERTIES DISABLED TRUE)
            add_executable(tas_gpu_flat_blend_${_mode}
                ../runtime/tests/test_source_gpu_flat_blend.c
                ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c)
            target_include_directories(tas_gpu_flat_blend_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_flat_blend_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_flat_blend_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_flat_blend_${_mode} COMMAND tas_gpu_flat_blend_${_mode})
            # PS1B-180: the SCPH550x 480i logo quad draws with field skipping.
            add_executable(tas_gpu_interlace_draw_${_mode}
                ../runtime/tests/test_source_gpu_interlace_draw.c)
            target_include_directories(tas_gpu_interlace_draw_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_interlace_draw_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_interlace_draw_${_mode} COMMAND tas_gpu_interlace_draw_${_mode})
            # PS1B-180 (c)/(d): GPUSTAT.28 is "write FIFO empty" during A0h/C0h.
            add_executable(tas_gpu_ready_transfer_${_mode}
                ../runtime/tests/test_source_gpu_ready_transfer.c)
            target_include_directories(tas_gpu_ready_transfer_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_ready_transfer_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_ready_transfer_${_mode} COMMAND tas_gpu_ready_transfer_${_mode})
            # PS1B-182: oracle GPU logs replayed through the timing table. Skips
            # (77) when the logs (lab share, not committed) are absent.
            add_executable(tas_gpu_oracle_replay_${_mode}
                ../runtime/tests/test_source_gpu_oracle_replay.c)
            target_include_directories(tas_gpu_oracle_replay_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_oracle_replay_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_oracle_replay_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_oracle_replay.py
                $<TARGET_FILE:tas_gpu_oracle_replay_${_mode}>)
            # Both oracle drivers outgrew CTest's 1500 s default once the log set
            # gained megamanx4-route03. Measured on Eagle 2026-09-26: replay O2
            # 1477 s, timing O2 2736 s under concurrent builds. 5400 s covers O0
            # and a loaded host.
            set_tests_properties(tas_gpu_oracle_replay_${_mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 5400)
            # PS1B-182: scheduling (when queued commands start), replayed from the
            # same share logs; skips (77) without them.
            add_executable(tas_gpu_oracle_timing_${_mode}
                ../runtime/tests/test_source_gpu_oracle_timing.c)
            target_include_directories(tas_gpu_oracle_timing_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_oracle_timing_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_oracle_timing_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_oracle_replay.py
                $<TARGET_FILE:tas_gpu_oracle_timing_${_mode}>)
            set_tests_properties(tas_gpu_oracle_timing_${_mode} PROPERTIES SKIP_RETURN_CODE 77 TIMEOUT 5400)
            add_executable(tas_gpu_service_start_${_mode}
                ../runtime/tests/test_source_gpu_service_start.c)
            target_include_directories(tas_gpu_service_start_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_service_start_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_service_start_${_mode} COMMAND tas_gpu_service_start_${_mode})
            # PS1B-182: authored oracle micro-fixtures (no retail data), committed.
            add_executable(tas_gpu_oracle_fixtures_${_mode}
                ../runtime/tests/test_source_gpu_oracle_fixtures.c)
            target_include_directories(tas_gpu_oracle_fixtures_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_oracle_fixtures_${_mode} PRIVATE -${_mode} -UNDEBUG)
            add_test(NAME tas_gpu_oracle_fixtures_${_mode} COMMAND tas_gpu_oracle_fixtures_${_mode}
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/source_gpu_oracle_micro_fixtures.tsv
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/source_gpu_oracle_micro_fixtures_gp1.tsv)
            add_executable(tas_gpu_line_${_mode}
                ../runtime/tests/test_source_gpu_line.c ../runtime/src/gpu_sw_renderer.c ../runtime/tests/source_gpu_raster_stub.c
                ../runtime/src/gpu_vram_dirty.c ../runtime/src/psx_sha256.c)
            target_include_directories(tas_gpu_line_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_line_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_line_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_line_${_mode} COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_line_contracts.py
                $<TARGET_FILE:tas_gpu_line_${_mode}>)
            # PS1B-423: the rows of the source-profile raster spec (triangle, line,
            # copy, sprite) against a model in other terms; whole VRAM after every draw.
            add_executable(tas_gpu_raster_rows_${_mode}
                ../runtime/tests/test_source_gpu_raster_rows.c ../runtime/src/gpu_sw_renderer.c
                ../runtime/tests/source_gpu_raster_stub.c ../runtime/src/gpu_vram_dirty.c)
            target_include_directories(tas_gpu_raster_rows_${_mode} PRIVATE ../runtime/include)
            target_compile_options(tas_gpu_raster_rows_${_mode} PRIVATE -${_mode} -UNDEBUG)
            target_link_libraries(tas_gpu_raster_rows_${_mode} PRIVATE m)
            add_test(NAME tas_gpu_raster_rows_${_mode} COMMAND tas_gpu_raster_rows_${_mode})
        endforeach()
    endif()

    # TAS checkpoint state I/O and the v7 identity dimensions. Both drivers compile their own
    # C at O0 and O2 with -Werror, so they take no CMake target.
    if(Python3_EXECUTABLE AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
        add_test(NAME tas_stateio_manifest COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_tas_stateio.py
            --cc ${CMAKE_C_COMPILER})
        add_test(NAME tas_stateio_identity COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_stateio_identity.py
            --cc ${CMAKE_C_COMPILER})
    endif()

    add_executable(mdec_source_fifo_rows_test ../runtime/tests/test_mdec_source_fifo_rows.c)
    target_include_directories(mdec_source_fifo_rows_test PRIVATE ../runtime/include)
    add_test(NAME mdec_source_fifo_rows_test COMMAND mdec_source_fifo_rows_test)

    add_executable(source_cpu_block_bound_test
        ../runtime/tests/test_source_cpu_block_bound.c
        ../runtime/src/psx_icache.c)
    target_include_directories(source_cpu_block_bound_test PRIVATE ../runtime/include)
    target_compile_definitions(source_cpu_block_bound_test PRIVATE PSX_ENABLE_BLOCK_CYCLES=1)
    if(MSVC)
        target_compile_options(source_cpu_block_bound_test PRIVATE /UNDEBUG)
    else()
        target_compile_options(source_cpu_block_bound_test PRIVATE -UNDEBUG)
    endif()
    add_test(NAME source_cpu_block_bound_test COMMAND source_cpu_block_bound_test)
    add_test(NAME emitter_directive_line_test
             COMMAND emitter_directive_line_test)
    add_executable(guest_tty_test
        ../runtime/tests/test_guest_tty.c
        ../runtime/src/guest_tty.c)
    target_include_directories(guest_tty_test PRIVATE ../runtime/include)
    add_test(NAME guest_tty_test COMMAND guest_tty_test)

    # MDEC status bit 29 stays set until a decode's output is down to the last
    # 32-word OutFIFO load (Beetle InCommand); libpress DecDCTin spins on it.
    add_executable(mdec_busy_drain_test
        ../runtime/tests/test_mdec_busy_drain.c
        ../runtime/src/mdec.c)
    target_include_directories(mdec_busy_drain_test PRIVATE ../runtime/include)
    add_test(NAME mdec_busy_drain_test COMMAND mdec_busy_drain_test)

    add_executable(debug_trace_ranges_test
        ../runtime/tests/test_debug_trace_ranges.c
        ../runtime/src/debug_trace_ranges.c
    )
    target_include_directories(debug_trace_ranges_test PRIVATE
        ../runtime/include)
    add_test(NAME debug_trace_ranges_test COMMAND debug_trace_ranges_test)

    add_executable(display_scanout_test
        ../runtime/tests/test_display_scanout.c)
    target_include_directories(display_scanout_test PRIVATE ../runtime/include)
    add_test(NAME display_scanout_test COMMAND display_scanout_test)

    # Pure header arithmetic for the OpenGL internal-resolution clamp.
    add_executable(gl_scale_limits_test
        ../runtime/tests/test_gl_scale_limits.c)
    target_include_directories(gl_scale_limits_test PRIVATE ../runtime/include)
    add_test(NAME gl_scale_limits_test COMMAND gl_scale_limits_test)

    # Internal resolution presets -> integer scale (pure header).
    add_executable(internal_resolution_test
        ../runtime/tests/test_internal_resolution.cpp)
    target_include_directories(internal_resolution_test PRIVATE ../runtime/include)
    add_test(NAME internal_resolution_test COMMAND internal_resolution_test)

    add_executable(cli_boot_path_test
        tests/cli_boot_path_test.cpp
        src/cli_boot_path.cpp
    )
    target_include_directories(cli_boot_path_test PRIVATE src)
    add_test(NAME cli_boot_path_test COMMAND cli_boot_path_test)

    add_executable(ps1_exe_padding_test
        tests/ps1_exe_padding_test.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(ps1_exe_padding_test PRIVATE src include)
    target_link_libraries(ps1_exe_padding_test PRIVATE fmt)
    add_test(NAME ps1_exe_padding_test COMMAND ps1_exe_padding_test)

    add_executable(recompiler_patch_test
        tests/recompiler_patch_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(recompiler_patch_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(recompiler_patch_test PRIVATE fmt rabbitizer)
    add_test(NAME recompiler_patch_test COMMAND recompiler_patch_test)

    add_executable(full_function_emitter_test
        tests/full_function_emitter_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/strict_translator.cpp
        src/pgxp_hook_emitter.cpp
        src/bios_address_model.cpp
        src/function_discovery.cpp
        src/full_function_emitter.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(full_function_emitter_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(full_function_emitter_test PRIVATE fmt rabbitizer)
    add_test(NAME full_function_emitter_test COMMAND full_function_emitter_test)

    add_executable(signed_x_bound_codegen_test
        tests/signed_x_bound_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(signed_x_bound_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(signed_x_bound_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME signed_x_bound_codegen_test
             COMMAND signed_x_bound_codegen_test)

    add_executable(mod_function_replacement_codegen_test
        tests/mod_function_replacement_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(mod_function_replacement_codegen_test PRIVATE
        include src lib/rabbitizer/include lib/rabbitizer/cplusplus/include
        lib/fmt/include lib/toml11 lib/ELFIO)
    target_link_libraries(mod_function_replacement_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME mod_function_replacement_codegen_test
             COMMAND mod_function_replacement_codegen_test)
    set_tests_properties(mod_function_replacement_codegen_test PROPERTIES
        ENVIRONMENT "PSX_CPS=1")

    add_executable(ws_cull_edge_codegen_test
        tests/ws_cull_edge_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(ws_cull_edge_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(ws_cull_edge_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME ws_cull_edge_codegen_test
             COMMAND ws_cull_edge_codegen_test)

    # [widescreen.cull] screen_x_sites on `lui` and `bltz` (a screen X kept in
    # the high half of a register): emitted code for hand-written instruction
    # words, the main-EXE guard, and the shared math in ws_cull_edge.h.
    add_executable(ws_cull_screen_x_hi_codegen_test
        tests/ws_cull_screen_x_hi_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(ws_cull_screen_x_hi_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(ws_cull_screen_x_hi_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME ws_cull_screen_x_hi_codegen_test
             COMMAND ws_cull_screen_x_hi_codegen_test)

    # [[draw_distance.clamp]]: config rules, the emitted main-EXE clamp and
    # its guards, the shared declaration and the unchanged overlay identity.
    add_executable(draw_distance_codegen_test
        tests/draw_distance_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(draw_distance_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(draw_distance_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME draw_distance_codegen_test
             COMMAND draw_distance_codegen_test)

    # [[mod_immediate.sites]]: config rules, the per-selector switch emitted
    # for loads, stores, ALU immediates and delay slots, the guards and the
    # overlay-cache identity.
    add_executable(mod_immediate_codegen_test
        tests/mod_immediate_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(mod_immediate_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(mod_immediate_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME mod_immediate_codegen_test
             COMMAND mod_immediate_codegen_test)

    # The same explicit cull kinds in the dirty-RAM interpreter: gpu.c's
    # sorted, capped site store and real interpreted bgez/bltz/branch-keep/
    # clip-edge instructions at margin 0 and at a forced margin. The test
    # #includes gpu.c and dirty_ram_interp.c; link-time dead-code removal
    # keeps the stub set small (dead_strip is Apple ld's --gc-sections).
    if(NOT MSVC)
        add_executable(ws_cull_edge_interp_test
            ../runtime/tests/test_ws_cull_edge_interp.c
            ../runtime/tests/interpreter_opcode_fixture_support.c
            ../runtime/src/psx_icache.c
            ../runtime/src/ws_aspect_cone_math.c)
        target_include_directories(ws_cull_edge_interp_test PRIVATE
            ../runtime/include
            ../runtime/src)
        target_compile_options(ws_cull_edge_interp_test PRIVATE
            -O2 -flto -ffunction-sections -fdata-sections)
        if(APPLE)
            target_link_options(ws_cull_edge_interp_test PRIVATE
                -flto -Wl,-dead_strip)
        else()
            target_link_options(ws_cull_edge_interp_test PRIVATE
                -flto -Wl,--gc-sections)
        endif()
        if(NOT WIN32)
            target_link_libraries(ws_cull_edge_interp_test PRIVATE m)
        endif()
        add_test(NAME ws_cull_edge_interp_test
                 COMMAND ws_cull_edge_interp_test
                         ${CMAKE_CURRENT_BINARY_DIR}/ws_cull_edge_interp_stderr.txt)
    endif()

    # [[draw_distance.clamp]] in the dirty-RAM interpreter: draw_distance.c's
    # store and switch and real interpreted guard instructions. The test
    # #includes draw_distance.c and dirty_ram_interp.c (as above).
    if(NOT MSVC)
        add_executable(draw_distance_interp_test
            ../runtime/tests/test_draw_distance_interp.c
            ../runtime/tests/interpreter_opcode_fixture_support.c
            ../runtime/src/psx_icache.c
            ../runtime/src/ws_aspect_cone_math.c)
        target_include_directories(draw_distance_interp_test PRIVATE
            ../runtime/include
            ../runtime/src)
        target_compile_options(draw_distance_interp_test PRIVATE
            -O2 -flto -ffunction-sections -fdata-sections)
        if(APPLE)
            target_link_options(draw_distance_interp_test PRIVATE
                -flto -Wl,-dead_strip)
        else()
            target_link_options(draw_distance_interp_test PRIVATE
                -flto -Wl,--gc-sections)
        endif()
        if(NOT WIN32)
            target_link_libraries(draw_distance_interp_test PRIVATE m)
        endif()
        add_test(NAME draw_distance_interp_test
                 COMMAND draw_distance_interp_test
                         ${CMAKE_CURRENT_BINARY_DIR}/draw_distance_interp_stderr.txt)
    endif()

    # [[mod_immediate.sites]] runtime store, selectors and word substitution
    # (mod_immediate.c). The interpreter call site is pinned by
    # runtime/tests/test_interp_mod_immediate_guards.py.
    add_executable(mod_immediate_unit_test
        ../runtime/tests/test_mod_immediate.c)
    target_include_directories(mod_immediate_unit_test PRIVATE
        ../runtime/include
        ../runtime/src)
    add_test(NAME mod_immediate_unit_test
             COMMAND mod_immediate_unit_test
                     ${CMAKE_CURRENT_BINARY_DIR}/mod_immediate_unit_stderr.txt)

    # Uncached (KSEG1) code charges a fetch before every instruction; cached
    # code keeps the line-leader rule (test_uncached_fetch_charge.py checks
    # the BIOS emitter on OpenBIOS and the cycle model).
    add_executable(uncached_fetch_codegen_test
        tests/uncached_fetch_codegen_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(uncached_fetch_codegen_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(uncached_fetch_codegen_test PRIVATE fmt rabbitizer)
    add_test(NAME uncached_fetch_codegen_test
             COMMAND uncached_fetch_codegen_test)


    # Every guest-visible PC the game/overlay emitter and the BIOS translator
    # bake goes through runtime_pc() (docs/SEGMENT_AWARE_CODE.md §5.2): links,
    # fetch tags, IRQ resume PCs, CPS exits/continuation keys, store-PC stamps,
    # EPCs. KSEG0 output is unchanged; identity keys keep the compile address.
    add_executable(emitter_runtime_pc_test
        tests/emitter_runtime_pc_test.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/game_dispatch_emitter.cpp
        src/strict_translator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(emitter_runtime_pc_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(emitter_runtime_pc_test PRIVATE fmt rabbitizer)
    add_test(NAME emitter_runtime_pc_test
             COMMAND emitter_runtime_pc_test)
    # The BIOS translator latches PSX_CPS at start-up; this run covers its
    # legacy (non-CPS) syscall form.
    add_test(NAME emitter_runtime_pc_test_legacy
             COMMAND emitter_runtime_pc_test)
    set_tests_properties(emitter_runtime_pc_test_legacy PROPERTIES
                         ENVIRONMENT "PSX_CPS=0")

    # Segment variants (docs/SEGMENT_AWARE_CODE.md §5.4): the planner's
    # direct-edge closure per segment, the segment view and the variant's
    # codegen config.
    add_executable(segment_variants_test
        tests/segment_variants_test.cpp
        src/segment_variants.cpp
        src/ps1_exe_parser.cpp
        src/mips_decoder.cpp
        src/control_flow.cpp
        src/function_analysis.cpp
        src/code_generator.cpp
        src/pgxp_hook_emitter.cpp
        src/annotations.cpp
        src/bios_address_model.cpp
        src/config_loader.cpp
        src/recompiler_patch.cpp
    )
    target_include_directories(segment_variants_test PRIVATE
        include
        src
        lib/rabbitizer/include
        lib/rabbitizer/cplusplus/include
        lib/fmt/include
        lib/toml11
        lib/ELFIO
    )
    target_link_libraries(segment_variants_test PRIVATE fmt rabbitizer)
    add_test(NAME segment_variants_test COMMAND segment_variants_test)

    add_executable(recompiler_patch_cxx17_header_test
        tests/recompiler_patch_cxx17_header_test.cpp
    )
    target_include_directories(recompiler_patch_cxx17_header_test PRIVATE src)
    set_target_properties(recompiler_patch_cxx17_header_test PROPERTIES
        CXX_STANDARD 17
        CXX_STANDARD_REQUIRED ON
    )
    add_test(NAME recompiler_patch_cxx17_header_test
             COMMAND recompiler_patch_cxx17_header_test)

    # [video] geometry_correction / perspective_texturing opt-in plumbing.
    add_executable(video_enhancement_settings_test
        tests/video_enhancement_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(video_enhancement_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(video_enhancement_settings_test PRIVATE fmt)
    add_test(NAME video_enhancement_settings_test
             COMMAND video_enhancement_settings_test)

    # [video] rewind opt-in plumbing (default off + settings round trip).
    add_executable(rewind_settings_test
        tests/rewind_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(rewind_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(rewind_settings_test PRIVATE fmt)
    add_test(NAME rewind_settings_test COMMAND rewind_settings_test)
    add_executable(set_config_loader_test
        tests/set_config_loader_test.cpp src/config_loader.cpp src/ps1_exe_parser.cpp)
    target_include_directories(set_config_loader_test PRIVATE
        include src lib/fmt/include lib/toml11)
    target_link_libraries(set_config_loader_test PRIVATE fmt)
    add_test(NAME set_config_loader_test COMMAND set_config_loader_test)
    add_executable(jogcon_ffb_settings_test
        tests/jogcon_ffb_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(jogcon_ffb_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(jogcon_ffb_settings_test PRIVATE fmt)
    add_test(NAME jogcon_ffb_settings_test COMMAND jogcon_ffb_settings_test)

    add_executable(select_shortcuts_config_test
        tests/select_shortcuts_config_test.cpp src/config_loader.cpp src/ps1_exe_parser.cpp)
    target_include_directories(select_shortcuts_config_test PRIVATE
        include src lib/fmt/include lib/toml11)
    target_link_libraries(select_shortcuts_config_test PRIVATE fmt)
    add_test(NAME select_shortcuts_config_test COMMAND select_shortcuts_config_test)
    add_executable(required_disc_config_test
        tests/required_disc_config_test.cpp src/config_loader.cpp src/ps1_exe_parser.cpp)
    target_include_directories(required_disc_config_test PRIVATE
        include src lib/fmt/include lib/toml11)
    target_link_libraries(required_disc_config_test PRIVATE fmt)
    add_test(NAME required_disc_config_test COMMAND required_disc_config_test)

    # Launcher-saved paths stay relative inside a portable game folder.
    add_executable(portable_settings_paths_test
        tests/portable_settings_paths_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(portable_settings_paths_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(portable_settings_paths_test PRIVATE fmt)
    add_test(NAME portable_settings_paths_test COMMAND portable_settings_paths_test)

    add_executable(boot_keys_settings_test
        tests/boot_keys_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(boot_keys_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(boot_keys_settings_test PRIVATE fmt)
    add_test(NAME boot_keys_settings_test COMMAND boot_keys_settings_test)

    # The TAS runner writes a settings.toml of its own; its two boot keys must
    # survive the loader (PS1B-394). The TAS tooling is stripped from shipped
    # setup packages, so the probe and the test exist only where it does.
    if(Python3_Interpreter_FOUND AND EXISTS
       "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/test_run_native_settings.py")
        add_executable(settings_boot_keys_probe
            tests/settings_boot_keys_probe.cpp
            src/config_loader.cpp
            src/ps1_exe_parser.cpp
        )
        target_include_directories(settings_boot_keys_probe PRIVATE
            include
            src
            lib/fmt/include
            lib/toml11
        )
        target_link_libraries(settings_boot_keys_probe PRIVATE fmt)
        add_test(NAME tas_runner_settings_format
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/test_run_native_settings.py"
                --probe $<TARGET_FILE:settings_boot_keys_probe>)
    endif()


    # PS1 Mouse settings.toml keys (PS1B-279).
    add_executable(mouse_settings_test
        tests/mouse_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(mouse_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(mouse_settings_test PRIVATE fmt)
    add_test(NAME mouse_settings_test COMMAND mouse_settings_test)

    # neGcon pad type in settings.toml (PS1B-304).
    add_executable(negcon_settings_test
        tests/negcon_settings_test.cpp
        src/config_loader.cpp
        src/ps1_exe_parser.cpp
    )
    target_include_directories(negcon_settings_test PRIVATE
        include
        src
        lib/fmt/include
        lib/toml11
    )
    target_link_libraries(negcon_settings_test PRIVATE fmt)
    add_test(NAME negcon_settings_test COMMAND negcon_settings_test)

    # Needs bios/SCPH1001.toml — run from the framework root.
    add_test(NAME bios_address_model_test
             COMMAND bios_address_model_test
             WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

    # These targets were already built by every test build, but without
    # add_test() entries ctest could neither run nor fail them.
    add_test(NAME l2_structural_test COMMAND l2_structural_test)
    add_test(NAME reachable_discovery_test COMMAND reachable_discovery_test)

    find_package(Python3 COMPONENTS Interpreter)
    if(Python3_Interpreter_FOUND AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
        add_test(NAME source_bus_generated_cpu_test COMMAND ${Python3_EXECUTABLE}
            ${CMAKE_CURRENT_SOURCE_DIR}/tests/source_bus_codegen_probe.py
            --recompiler $<TARGET_FILE:psxrecomp-game> --cc ${CMAKE_C_COMPILER})
        set_tests_properties(source_bus_generated_cpu_test PROPERTIES TIMEOUT 180)
    endif()
    if(Python3_Interpreter_FOUND AND CMAKE_GENERATOR STREQUAL "Ninja")
        add_test(NAME runtime_ipo_configuration
            COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_runtime_ipo.py
                --cmake ${CMAKE_COMMAND} --cc ${CMAKE_C_COMPILER}
                --cxx ${CMAKE_CXX_COMPILER} --ninja ${CMAKE_MAKE_PROGRAM})
    endif()
    if(Python3_Interpreter_FOUND AND NOT CMAKE_CROSSCOMPILING AND
       CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_CXX_COMPILER_ID STREQUAL "GNU")
        if(CMAKE_GENERATOR STREQUAL "Ninja")
            set(_psx_hot_pgo_ninja "${CMAKE_MAKE_PROGRAM}")
        else()
            find_program(_psx_hot_pgo_ninja NAMES ninja ninja.exe)
        endif()
        if(_psx_hot_pgo_ninja)
            set(PSX_RUNTIME_HOT_PGO_TEST_SCRATCH_ROOT
                "${CMAKE_CURRENT_BINARY_DIR}/pgo" CACHE PATH
                "Retained PGO fixture runs; use a short path on Windows")
            set(_psx_hot_pgo_tool_cmake "${CMAKE_COMMAND}")
            set(_psx_hot_pgo_tool_gcc "${CMAKE_C_COMPILER}")
            set(_psx_hot_pgo_tool_gxx "${CMAKE_CXX_COMPILER}")
            set(_psx_hot_pgo_tool_ninja "${_psx_hot_pgo_ninja}")
            set(_psx_hot_pgo_tools "{}")
            foreach(_tool IN ITEMS cmake gcc gxx ninja)
                string(REPLACE "\\" "\\\\" _value "${_psx_hot_pgo_tool_${_tool}}")
                string(REPLACE "\"" "\\\"" _value "${_value}")
                string(JSON _psx_hot_pgo_tools SET "${_psx_hot_pgo_tools}" "${_tool}" "\"${_value}\"")
            endforeach()
            set(_psx_hot_pgo_tools_file "${CMAKE_CURRENT_BINARY_DIR}/runtime-hot-pgo-tools.json")
            file(GENERATE OUTPUT "${_psx_hot_pgo_tools_file}" CONTENT "${_psx_hot_pgo_tools}\n")
            add_test(NAME runtime_hot_pgo_contract
                COMMAND "${Python3_EXECUTABLE}"
                    "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_runtime_hot_pgo.py"
                    --tools "${_psx_hot_pgo_tools_file}"
                    --scratch-parent "${PSX_RUNTIME_HOT_PGO_TEST_SCRATCH_ROOT}")
            set_tests_properties(runtime_hot_pgo_contract PROPERTIES
                RUN_SERIAL TRUE PROCESSORS 2 LABELS "compiler;pgo")
        endif()
    endif()
    # MFC0/CFC0 read COP0 from before their own cycle charge: the production
    # interpreter decoder with cycle charging on, a device event that raises
    # CAUSE.IP2 on the read's own cycle, other seams stubbed to abort()
    # (runtime/tests/fixture_link.py needs nm and GNU ld diagnostics).
    if(Python3_Interpreter_FOUND AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
        add_test(NAME interp_cop0_read_sample
            COMMAND ${Python3_EXECUTABLE}
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_interp_cop0_read_sample.py"
                --cc ${CMAKE_C_COMPILER})
    endif()
    if(Python3_Interpreter_FOUND)
        add_test(NAME optional_netplay_auth
            COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_optional_netplay_auth.py
                --cxx ${CMAKE_CXX_COMPILER})
        add_test(NAME bios_emitter_stamps
            COMMAND ${Python3_EXECUTABLE}
                ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_emitter_stamps.py
                --cmake ${CMAKE_COMMAND})
        add_test(NAME pgo_rebuild_flow
            COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_pgo_rebuild_flow.py)
        add_test(
            NAME cli_project_packaging
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_cli_project_packaging.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        add_test(NAME no_overlapping_bodies
            COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_no_overlapping_bodies.py
                --recompiler $<TARGET_FILE:psxrecomp-game>)

        # tools/bios_module_build.py end to end: recompile the bundled OpenBIOS
        # image with the psxrecomp-bios built here, compile it as a loadable
        # BIOS module, and read the descriptor back (psx_bios_module.h).
        # Skips itself without a C compiler on PATH.
        add_test(
            NAME bios_module_build
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_module_build.py
                    --emitter $<TARGET_FILE:psxrecomp-bios>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # The tcc build of a BIOS module sees a BOM-stripped copy of the
        # include directory. That copy must hold the .c.inc fragments the
        # module's glue.c includes, or the tcc build stops at "not found".
        # Pure unit test: no compiler, no built target.
        add_test(
            NAME tcc_module_include_dir
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_tcc_module_include_dir.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # tools/coverage_vault.py's history compaction and streaming JSON
        # reader. Pure unit test — loads the module by path, needs no built
        # target and no generated BIOS. It sat in tools/ with no add_test()
        # entry anywhere, which is exactly the orphan class
        # runtime/check_test_registration.cmake exists to prevent; moving it
        # under tests/ puts it inside that guard's glob.
        add_test(
            NAME coverage_vault
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_coverage_vault.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME enrich_overlay_captures
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_enrich_overlay_captures.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # A walk root is a hard cap: a root inside another function splits it
        # and still passes every audit. The classifier's no-split guard, the
        # image-local enrichment proof, the recompiler's own guard on explicit
        # roots, and the root policy's place in the cache namespace
        # (beads-eio.3.177).
        add_test(
            NAME overlay_no_split_guard
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_no_split_guard.py
                    --cmake ${CMAKE_COMMAND}
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # Cross-image call evidence (main EXE, other captured images) roots
        # an overlay function only where the overlay's own bytes prove the
        # boundary (beads-eio.3.191, category a).
        add_test(
            NAME overlay_cross_image_calls
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_cross_image_calls.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # The runtime exe name must come from CMake, not be re-derived.
        add_test(
            NAME runtime_exe_name
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_runtime_exe_name.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # Bad input (game.toml errors, main-EXE [widescreen.cull] sites with
        # the wrong opcode, an overlay the emitter must refuse) must end
        # psxrecomp-game with exit status 1 and a message, never abort();
        # the test requires exactly 1.
        add_test(
            NAME recompiler_error_exit
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_recompiler_error_exit.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --project-root ${CMAKE_CURRENT_SOURCE_DIR}/..
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # These two invoke psxrecomp-game, whose BIOS-profile resolution
        # probes fixed working-directory-relative paths (bios/<name>.toml,
        # then psxrecomp/bios/<name>.toml) -- run them from the framework
        # root so the shipped profiles are found.
        add_test(
            NAME overlay_cross_page_delay_codegen
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_cross_page_delay_codegen.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The overlay capture format's trailing delay-slot guard word: legal
        # to READ (a branch at the run's last word emits its slot from it),
        # illegal to ANALYSE (the word after it does not exist). Also pins
        # that the mandatory-delay-slot throw still fires on an untagged
        # image, and that no range manifest claims a byte past the image end.
        # Bead beads-eio.3.100.
        add_test(
            NAME overlay_guard_word_analysis_bound
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_guard_word_analysis_bound.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Request half of the same contract: no seed, alias or fragment demand
        # may name the guard word (classifier, fragment jobs, hosted selection,
        # fragment batches), and the recompiler warns when it drops a seed
        # past Analysis End. Bead beads-eio.3.190 (AC3 fragment no-func-ids).
        add_test(
            NAME overlay_guard_word_requests
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_guard_word_requests.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Producer half of the same contract: the capture writer declares its
        # guard bytes and compile_overlays puts them in the PS-EXE tag the
        # recompiler reads.
        add_test(
            NAME overlay_guard_bytes_declaration
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_guard_bytes_declaration.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The pipeline reads mod manifests with tomllib (Python 3.11+). An
        # older interpreter keeps the test registered but DISABLED, so it is
        # reported as not run instead of silently missing from `ctest -N`.
        add_test(
            NAME aot_overlay_methods
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_aot_overlay_pipeline.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME counted_relocation_discovery
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_counted_relocation_discovery.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # psxrecomp_cli.py generate builds the static overlay shard a title's
        # aot/overlays.json declares (static_output) and reports every outcome.
        # The pipeline it drives needs Python 3.11+, so the CLI refuses older
        # interpreters and so does this test.
        add_test(
            NAME cli_generate_aot_static
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_cli_generate_aot_static.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # psxrecomp_cli.py rebuild: the pre-build mtime clamp works without
        # lutimes (Windows), --cmake-extra wins over the shipping
        # PSX_DEBUG_TOOLS=OFF default, and the build runs the configuring cmake.
        add_test(
            NAME cli_rebuild_host
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_cli_rebuild_host.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # update_disc_set.py writes in-project discs relative with '/' so one
        # game.toml resolves on every host.
        add_test(
            NAME update_disc_set_paths
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_update_disc_set_paths.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # runtime/overlay_static_sources.cmake: a declared-but-absent shard is
        # reported at configure, a profile/CMake disagreement is an error, and
        # hash_codegen.cmake's source-list mode matches psxrecomp-game's hash.
        add_test(
            NAME overlay_static_sources_cmake
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_static_sources_cmake.py
                    "--cmake=${CMAKE_COMMAND}"
                    "--generator=${CMAKE_GENERATOR}"
                    "--make-program=${CMAKE_MAKE_PROGRAM}"
                    "--recompiler=$<TARGET_FILE:psxrecomp-game>"
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        if(Python3_VERSION VERSION_LESS "3.11")
            set_tests_properties(aot_overlay_methods cli_generate_aot_static
                                 overlay_static_sources_cmake PROPERTIES DISABLED TRUE)
        endif()
        # --static must walk the whole of compile_overlays.main() and exit 0:
        # a DLL-only reconciliation at its tail once read an unbound cache_dir
        # and failed every static run after its output was written.
        add_test(
            NAME compile_overlays_static_tail
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_compile_overlays_static_tail.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The word after a `jr $ra` is as often a pointer table as a frameless
        # function; a dispatch entry there must prove a CFG before it may be a
        # walk root, or the walk runs through the table to the image end.
        add_test(
            NAME frameless_dispatch_root_cfg_proof
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_frameless_dispatch_root_cfg_proof.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME fmv_cycle_batch_codegen
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_fmv_cycle_batch_codegen.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME kuseg_dispatch_lookup
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_kuseg_dispatch_lookup.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Gap ledger for docs/SEGMENT_AWARE_CODE.md: a synthetic KUSEG-linked
        # EXE, checked against a Beetle fetch-model transcription.
        add_test(
            NAME source_page_crc_table
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_source_page_crc_table.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Overlay shards keyed by segment (docs/SEGMENT_AWARE_CODE.md §5.7):
        # capture views, per-segment cache directories and manifests, the real
        # compile_overlays.py driver and the real overlay loader.
        add_test(
            NAME split_piece_handoff_guards
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_split_piece_handoff_guards.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # A shard compiled from resident text patched only at a control-flow
        # word (the 17e9bbaa0 fixture) is published, and the real loader plus
        # memory.c's exact-range text guard still never run stale native code
        # for it. The harness is built with GCC-style flags.
        if(CMAKE_C_COMPILER_ID MATCHES "GNU|Clang")
            add_test(
                NAME overlay_resident_patch_runtime
                COMMAND ${Python3_EXECUTABLE} -B
                        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_overlay_resident_patch_runtime.py
                        --recompiler $<TARGET_FILE:psxrecomp-game>
                        --compiler ${CMAKE_C_COMPILER}
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        endif()
        # BIOS segment variants (docs/SEGMENT_AWARE_CODE.md §5.4): OpenBIOS
        # with segment-qualified seeds, and SCPH-1001's KSEG1 kernel entry.
        add_test(
            NAME bios_segment_variants
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_segment_variants.py
                    --bios-recompiler $<TARGET_FILE:psxrecomp-bios>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME overlay_shim_compile_contract
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_shim_compile.py)
        add_test(
            NAME lwlr_load_delay_forward
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_lwlr_load_delay_forward.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME zero_run_compaction
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_zero_run_compaction.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME dirty_text_continuation_guards
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_dirty_text_continuation_guards.py)
        add_test(
            NAME dirty_text_admission_guards
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_dirty_text_admission_guards.py)
        add_test(
            NAME bios_backend_constant_codegen
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_backend_constant_codegen.py
                    --recompiler $<TARGET_FILE:psxrecomp-bios>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # tools/bios_emitter_fingerprint.sh takes its file list from the
        # psxrecomp-bios target above. CMake hands the test its own list of
        # that target's sources, so the script's reading of this file is held
        # to the authority in both directions; the test also checks the
        # verdicts of runtime/bios_stale_check.cmake (PS1B-131).
        add_test(
            NAME bios_vector_stub_shapes
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_vector_stub_shapes.py
                    --recompiler $<TARGET_FILE:psxrecomp-bios>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Compiled BIOS fetch charges == the interpreter's (psx_icache.c) ==
        # Beetle, per instruction, on OpenBIOS: KSEG1 ROM code pays every fetch.
        add_test(
            NAME bios_emitter_fingerprint
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_bios_emitter_fingerprint.py
                    # MinGW's generated application-manifest resource object
                    # is linked metadata, not a BIOS emitter source file.
                    --target-sources "$<JOIN:$<FILTER:$<TARGET_PROPERTY:psxrecomp-bios,SOURCES>,EXCLUDE,[.]rc[.]obj$>,$<COMMA>>"
                    --cmake ${CMAKE_COMMAND}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME stall_report
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_stall_report.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME prepare_disc_boot_case
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_prepare_disc_boot_case.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME package_setup_bios_hint
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_package_setup_bios_hint.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME package_setup_cmake_comments
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_package_setup_cmake_comments.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # A set whose discs boot different programs, set up as one folder
        # (PS1B-333): the set file, the disc check, the join, the start
        # scripts, the host project, and the unchanged single-program path.
        add_test(
            NAME program_set
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_program_set.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME package_setup_program_set
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_package_setup_program_set.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The packager's zip step as its own entry. The run above accepts a
        # shell without zip, so a host without one passed with the zip never
        # made. This one checks the zip (its name, its members against the
        # staged files) where a zip tool exists, and is reported as skipped,
        # by name, where none does (PS1B-397).
        add_test(
            NAME package_setup_program_set_zip
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_package_setup_program_set.py
                    --zip-part
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        set_tests_properties(package_setup_program_set_zip PROPERTIES SKIP_RETURN_CODE 77)
        # The check the packager runs on a staged setup program: start it as a
        # double-click does and require that it reaches its launcher (PS1B-365).
        add_test(
            NAME setup_host_plain_start
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_setup_host_plain_start.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME setup_package_payload_filter
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_setup_package_payload_filter.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The bash the two gate tests below run a script with must not be a
        # WSL launcher. On a host whose PATH puts System32 before Git it was,
        # and both failed with exit 127 for no fault of the tree (PS1B-271).
        add_test(
            NAME host_bash
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_host_bash.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME setup_private_path_gate
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_setup_private_path_gate.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The same gate over the framework tree the setup packager stages, so
        # a private path in a shipped file fails here and not in a release
        # build (PS1B-216).
        add_test(
            NAME setup_staged_tree_private_paths
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_setup_staged_tree_private_paths.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The DuckStation oracle patch must hold added lines only: a context
        # line or a removed line is a line of upstream's source (PS1B-216).
        # Keep this focused gate beside the complete oracle suite below.
        add_test(
            NAME duckstation_oracle_patch_form
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_duckstation_oracle.py
                    TestOraclePatch
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # PS1B-476/375: these source-owned tool checks must execute in the
        # recompiler gate. A missing compiler or CHD reader must not turn a
        # skipped case into a green gate.
        set(_psx_tools_tests "")
        foreach(_t IN ITEMS
                menu_scan cursor_writers gpu_frame pad_trace pad_delivery
                aligned_lzss_banks static_fragment_variant_keys
                mips_tagged_relocations overlay_widescreen_callbacks
                duckstation_oracle)
            add_test(NAME tools_${_t}
                COMMAND ${Python3_EXECUTABLE} -B -m unittest discover
                    -s ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests
                    -p test_${_t}.py -v
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
            list(APPEND _psx_tools_tests tools_${_t})
        endforeach()
        if(PSXRECOMP_ENABLE_CHD AND TARGET chdr)
            add_test(NAME tools_psx_chd
                COMMAND ${Python3_EXECUTABLE} -B -m unittest discover
                    -s ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests
                    -p test_psx_chd.py -v
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
            set_tests_properties(tools_psx_chd PROPERTIES
                ENVIRONMENT "PSXRECOMP_LIBCHDR=$<TARGET_FILE:chdr>")
            list(APPEND _psx_tools_tests tools_psx_chd)
            set(_psx_tools_registration_args "")
        else()
            set(_psx_tools_registration_args --without-chd)
        endif()
        set_tests_properties(${_psx_tools_tests} PROPERTIES
            FAIL_REGULAR_EXPRESSION "skipped;Ran 0 tests")
        # Clean gate hosts need not install optional image tools. Decode and
        # diff checks still run; only NumPy/Pillow rendering cases may skip.
        set_tests_properties(tools_gpu_frame PROPERTIES
            FAIL_REGULAR_EXPRESSION "Ran 0 tests")
        add_test(NAME tools_ctest_registration
            COMMAND ${Python3_EXECUTABLE} -B
                ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_ctest_tools_registration.py
                --build-tree ${CMAKE_BINARY_DIR} --ctest ${CMAKE_CTEST_COMMAND}
                ${_psx_tools_registration_args})
        set_tests_properties(tools_ctest_registration PROPERTIES
            SKIP_RETURN_CODE 77)
        add_test(
            NAME app_icon_rc_spaces
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_app_icon_rc_spaces.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME cli_diagnostics
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cli_diagnostics.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Pruning build intermediates after setup deleted tcc's own library from
        # the staged overlay toolchain (PS1B-333).
        add_test(
            NAME cli_prune_keeps_product
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cli_prune_keeps_product.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # A product that setup builds ends with the licenses/ folder a build
        # host's product has, and the step that writes it never reads its own
        # output, wherever the product folder is (PS1B-333).
        add_test(
            NAME setup_product_notices
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_setup_product_notices.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # The tool behind source_tree_snapshot and source_tree_unchanged (see
        # runtime/source_tree_check.cmake) must itself fail on a dirty tree.
        add_test(
            NAME source_tree_clean_tool
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_source_tree_clean.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Configures throwaway projects against runtime/psx_release_channel.cmake:
        # a release-package build must name its channel.
        add_test(
            NAME cli_diagnostic_only
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cli_diagnostic_only.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME cli_python_floor
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cli_python_floor.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME codegen_hash_build_tree
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_codegen_hash_build_tree.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        add_test(
            NAME bundle_mingw_dll_preference
            COMMAND ${Python3_EXECUTABLE} -B
                    ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_bundle_mingw_dll_preference.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # ---- codegen tests that were written but never registered -----------
        # These existed in recompiler/tests/ with no add_test() entry, so they
        # had no way to run and no way to fail. Each was executed manually
        # before being wired here; all pass. They drive psxrecomp-game, so they
        # run from the framework root for BIOS-profile resolution, exactly like
        # the two above.
        foreach(_t IN ITEMS
                aot_base_recovery
                bios_resident_shard
                computed_stride_jump_codegen
                msvc_cps_codegen
                overlay_guard_codegen
                overlay_store_barrier_codegen
                reachable_discovery_codegen)
            add_test(
                NAME ${_t}
                COMMAND ${Python3_EXECUTABLE} -B
                        ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_${_t}.py
                        --recompiler $<TARGET_FILE:psxrecomp-game>
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        endforeach()

        # test_aot_overlay_discovery.py had no add_test() entry at all. It runs
        # far enough to exercise real discovery, then fails in
        # check_real_batched_fragment_publication: that check reaches
        # MOD.compile_fragment_batch, which spawns psxrecomp-game with cwd set
        # to its own TemporaryDirectory, so BIOS-profile resolution (which
        # probes bios/SCPH1001.toml relative to the working directory) finds
        # nothing and returns "recompiler-error: no BIOS profile found". Every
        # other subprocess in the same file passes cwd=str(ROOT) explicitly, so
        # this looks like a missed cwd rather than a product defect — but it
        # needs an owner, and DISABLED keeps it counted until then.
        add_test(
            NAME aot_overlay_discovery
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_aot_overlay_discovery.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        # Original-disc extraction's normal-mode discovery resolves its BIOS
        # profile from --project-root, not the cwd, and a failed recompiler run
        # aborts extraction instead of silently shipping fewer native pairs.
        add_test(
            NAME extract_discovery_project_root
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_extract_discovery_project_root.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

        # ---- runtime source-invariant guards --------------------------------
        # These live in runtime/tests/ but only READ runtime source, so they
        # need neither a generated BIOS nor a built runtime. Registering them
        # here (as dirty_text_continuation_guards already was) means they run
        # from a plain recompiler build, which is the only tree a newcomer can
        # configure before regenerating a BIOS. Each was run manually first;
        # all pass.
        #
        foreach(_t IN ITEMS
                bios_selection_guards
                capture_history
                cli_retail_bios_profile
                codegen_host_bios_stems
                default_renderer_guards
                mod_owned_display_controls
                fmv_quiet_guards
                netplay_early_input_idle_wiring
                fps_telemetry_opt_in
                frame_interpolation_display_rate
                frame_interpolation_flip_source
                hybrid_dev_any_input
                launcher_pad_mode_wiring
                launcher_serial_status_wiring
                negcon_twist_wiring
                replay_disc_identity_wiring
                netplay_chd_disc_rule_wiring
                run_report_cdrom_counters
                start_refusal_wiring
                netplay_lan_identity_wiring
                netplay_overlay_pin_wiring
                netplay_start_failure_wiring
                jalr_link_register_guards
                gl_internal_scale_guards
                launcher_vulkan_option
                mod_controller_override
                mod_load_acceleration
                native_wide_line_batching
                overlay_candidate_capacity
                release_zip
                overlay_decodable_fallback
                overlay_export_declarations
                interp_report_wiring
                bios_phase_report_wiring
                overlay_init_guard
                overlay_segment_gate
                recomp_ui_root_override
                render_pass_guards
                pal_cadence_host_refresh
                sdl3_main_single_include
                spu_sample_scheduler_default
                vk_build_default
                vk_color_self_barrier
                vk_command_buffer_batching
                vk_draw_area_batch_boundary
                vk_mailbox_policy
                vk_present_timing
                vk_present_wait_stage
                vk_staging_cache
                wide_overlay_band_scissor)
            add_test(
                NAME ${_t}
                COMMAND ${Python3_EXECUTABLE} -B
                        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_${_t}.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
        endforeach()

        # ---- known-failing guards: DISABLED, not de-registered ---------------
        # (overlay_pair_dedup_runtime was the third of these. It failed to link:
        # its harness had no psx_cpu_step_boundary_* definitions after the
        # loader began to hand them to shards. The harness has them now, the
        # overlay ABI gate test shares it, and the test is in the list above.)
        # These were previously left out of the list above with a comment
        # explaining that they FAIL and that wiring a red check would recreate
        # the problem that took CI off PRs. That reasoning is right; the remedy
        # was not. De-registering is precisely how a test stops being counted,
        # and it is the mechanism that let 16 C/C++ tests rot in runtime/tests/.
        #
        # ctest's DISABLED property is the intended tool: the test stays
        # registered, stays visible in `ctest -N`, and is reported as Disabled
        # rather than silently absent — without turning the run red. Delete the
        # matching set_tests_properties line to re-enable one once it is fixed.
        #
        #   interpreter_perf_guards    psx_devices_mmio_sync's recompute-deadline
        #                              branch never clears g_psx_cycle_fast_limit,
        #                              and the guard still names the pre-rename
        #                              s_next_service_cycle
        #   runtime_perf_diag_guards   asserts a substring no longer present in
        #                              the runtime source
        foreach(_t IN ITEMS
                interpreter_perf_guards
                runtime_perf_diag_guards)
            add_test(
                NAME ${_t}
                COMMAND ${Python3_EXECUTABLE} -B
                        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_${_t}.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
            set_tests_properties(${_t} PROPERTIES DISABLED TRUE)
        endforeach()
    endif()
endif()

# Reachable main-EXE discovery test: parser-bound validation plus exact-entry
# analysis behavior for direct, indirect, seeded, and out-of-bound targets.
add_executable(reachable_discovery_test
    tests/reachable_discovery_test.cpp
    src/function_analysis.cpp
    src/ps1_exe_parser.cpp
)
target_include_directories(reachable_discovery_test PRIVATE
    include
    lib/fmt/include
)
target_link_libraries(reachable_discovery_test PRIVATE fmt)

# Straight-line interpretation keeps going through game text that dispatch
# refuses (live bytes differ from the boot EXE) instead of handing each
# instruction back: the production interpreter linked into a fixture, other
# seams stubbed to abort() (runtime/tests/fixture_link.py needs nm and GNU ld).
if(BUILD_TESTING AND Python3_Interpreter_FOUND AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
    add_test(NAME interp_refused_game_text
        COMMAND ${Python3_EXECUTABLE}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_interp_refused_game_text.py"
            --cc ${CMAKE_C_COMPILER})
endif()

# GTE deadline ownership includes pending CPU charges from both host sinks.
if(BUILD_TESTING AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
    foreach(_mode IN ITEMS O0 O2)
        set(_target "tas_gte_deferred_${_mode}")
        add_executable(${_target}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_gte_deferred.c"
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/src/psx_cycles.c")
        target_include_directories(${_target} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/include")
        target_compile_definitions(${_target} PRIVATE PSX_ENABLE_BLOCK_CYCLES=1)
        target_compile_options(${_target} PRIVATE -${_mode} -UNDEBUG -ffunction-sections -fdata-sections)
        target_link_options(${_target} PRIVATE -Wl,--gc-sections)
        add_test(NAME ${_target} COMMAND ${_target})
    endforeach()
    # PS1B-185: MULT/DIV/GTE latencies against oracle fixture F1-F3, and the
    # recompilers' inline DIV literal against PSX_DIV_LATENCY.
    add_executable(muldiv_latency_fixture
        "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_muldiv_latency_fixture.c"
        "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/src/psx_cycles.c")
    target_include_directories(muldiv_latency_fixture PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/include")
    target_compile_definitions(muldiv_latency_fixture PRIVATE PSX_ENABLE_BLOCK_CYCLES=1)
    target_compile_options(muldiv_latency_fixture PRIVATE -O2 -UNDEBUG -ffunction-sections -fdata-sections)
    target_link_options(muldiv_latency_fixture PRIVATE -Wl,--gc-sections)
    add_test(NAME muldiv_latency_fixture COMMAND muldiv_latency_fixture
        "${CMAKE_CURRENT_SOURCE_DIR}/src/code_generator.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/src/strict_translator.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/src/dirty_ram_interp.c"
        "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/include/source_cpu_block_bound.h")
    # PS1B-185: MFHI/MFLO deadline wait, load give-back and second read against oracle fixtures F5 and F6.
    foreach(_mode IN ITEMS O0 O2)
        set(_target "muldiv_deferred_${_mode}")
        add_executable(${_target}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_muldiv_deferred.c"
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/src/psx_cycles.c")
        target_include_directories(${_target} PRIVATE "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/include")
        target_compile_definitions(${_target} PRIVATE PSX_ENABLE_BLOCK_CYCLES=1)
        target_compile_options(${_target} PRIVATE -${_mode} -UNDEBUG -ffunction-sections -fdata-sections)
        target_link_options(${_target} PRIVATE -Wl,--gc-sections)
        add_test(NAME ${_target} COMMAND ${_target})
    endforeach()
endif()

# ChangeThread to the running thread continues after the SYSCALL: the
# production traps.c linked into a fixture, other seams stubbed to abort()
# (runtime/tests/fixture_link.py needs nm and GNU ld diagnostics).
if(BUILD_TESTING AND Python3_Interpreter_FOUND AND CMAKE_C_COMPILER_ID STREQUAL "GNU")
    add_test(NAME syscall_change_thread_self
        COMMAND ${Python3_EXECUTABLE}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_syscall_change_thread_self.py"
            --cc ${CMAKE_C_COMPILER})
    # CloseTh(current); ChangeTh(other) still saves the outgoing registers.
    add_test(NAME changethread_in_handler_test
        COMMAND ${Python3_EXECUTABLE}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_changethread_in_handler.py"
            --cc ${CMAKE_C_COMPILER})
    add_test(NAME syscall_closed_thread_context
        COMMAND ${Python3_EXECUTABLE}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_syscall_closed_thread_context.py"
            --cc ${CMAKE_C_COMPILER})
endif()

# Presentation capture ring: distinct-presentation dedupe and bounded slots.
if(BUILD_TESTING)
    add_executable(present_image_ring_test
        ../runtime/tests/test_present_image_ring.c
        ../runtime/src/present_image_ring.c)
    target_include_directories(present_image_ring_test PRIVATE ../runtime/include)
    add_test(NAME present_image_ring_test COMMAND present_image_ring_test)
endif()

# ---- test-registration completeness guard -----------------------------------
if(BUILD_TESTING)
    add_executable(fast_forward_policy_test ../runtime/tests/test_fast_forward.c)
    target_include_directories(fast_forward_policy_test PRIVATE ../runtime/include)
    if(MSVC)
        target_compile_options(fast_forward_policy_test PRIVATE /UNDEBUG)
    else()
        target_compile_options(fast_forward_policy_test PRIVATE -UNDEBUG)
    endif()
    if(NOT WIN32)
        target_link_libraries(fast_forward_policy_test PRIVATE m)
    endif()
    add_test(NAME fast_forward_policy_test COMMAND fast_forward_policy_test)
    if(Python3_Interpreter_FOUND)
        # The runtime regression above and this tools-only unit use different
        # authored fixtures; keep both, with unique CTest names.
        add_test(NAME overlay_export_declarations_tools
            COMMAND ${Python3_EXECUTABLE} -I
                ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_export_declarations.py)
    endif()
endif()
if(BUILD_TESTING AND Python3_Interpreter_FOUND AND NOT MSVC)
    add_test(NAME gpu_upload_history
        COMMAND ${Python3_EXECUTABLE} -B
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_gpu_upload_history.py"
            --cc ${CMAKE_C_COMPILER})
endif()
# The TAS replay tooling is developer infrastructure and is stripped from
# shipped setup packages, so a source tree may legitimately not have it.
if(BUILD_TESTING AND EXISTS
   "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/tests/CMakeLists.txt")
    add_subdirectory("${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/tests"
                     "${CMAKE_CURRENT_BINARY_DIR}/tas-models")
    if(Python3_Interpreter_FOUND)
        # The Python driver compiles test_scheduler_precise_escape.c and
        # test_source_gpu_pending_deadline.c at O0/O2.
        add_test(NAME tas_scheduler_precise_escape
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_scheduler_precise_escape.py"
                --cc ${CMAKE_C_COMPILER})
        add_test(NAME tas_bk2_intake
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/test_bk2_intake.py")
        add_test(NAME tas_replay_recipe
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/test_tekken3.py")
        add_test(NAME tas_pepsiman_admission
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../tools/tasreplays/test_pepsiman.py")
        # The Python driver compiles test_source_ram_page_probe.c at O0/O2.
        add_test(NAME tas_ram_long_route
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_ram_page_probe.py"
                --cc ${CMAKE_C_COMPILER})
        add_test(NAME tas_cpu_long_route
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_cpu_return_probe.py"
                --cc ${CMAKE_C_COMPILER})
        # The probes and the checkpoint manifest create their files through
        # psx_file_create_new.h, which needs no exclusive-create flag from the C
        # runtime, so these tests run on msvcrt (MSYS2 MINGW64) too (PS1B-206).
        # The driver compiles test_file_create_new.c at O0/O2 and fails when a
        # source file opens a stream with that flag again.
        add_test(NAME file_create_new
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_file_create_new.py"
                --cc ${CMAKE_C_COMPILER})
        # tas_stateio_manifest and tas_stateio_identity are registered with the
        # foundation above. The Python driver compiles test_input_route_raster_wire.c at O0/O2.
        add_test(NAME tas_raster_wire
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_input_route_raster_wire.py"
                --cc ${CMAKE_C_COMPILER})
        # The Python driver links the four comparison-profile section codecs with
        # auto-derived link stubs and round-trips per-field sentinels (O0/O2).
        add_test(NAME tas_section_wire_sentinels
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_boot_state_section_wire.py"
                --cc ${CMAKE_C_COMPILER})
        add_test(NAME tas_cpu_state_wire
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cpu_state_wire.py"
                --cc ${CMAKE_C_COMPILER})
        add_test(NAME tas_gpu_service_path
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_source_gpu_service_path.py"
                --cc ${CMAKE_C_COMPILER})
        # The Python driver compiles test_boot_state_replace.c with its own TU.
        add_test(NAME tas_state_replace
            COMMAND ${Python3_EXECUTABLE} -B
                "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_boot_state_replace.py"
                --cc ${CMAKE_C_COMPILER})
    endif()
endif()

# UTF-8 process code page on Windows for every shipped tool (issue #371): they
# take disc/BIOS/output paths from argv and hand them to fopen/Win32 calls.
include(${CMAKE_CURRENT_SOURCE_DIR}/../cmake/psx_windows_manifest.cmake)
foreach(_psx_tool psxrecomp-game psxrecomp-toml psxrecomp psxrecomp-analyze psxrecomp-bios)
    if(TARGET ${_psx_tool})
        psxrecomp_windows_manifest(${_psx_tool})
    endif()
endforeach()

# Reads both CMakeLists.txt files as text, so it catches an orphan in either
# project from whichever one is being configured. Placed before the collision
# guard, which must stay last because it needs every target declared; this one
# inspects files on disk and has no such ordering requirement.
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/restored_authored_tests.cmake")
if(BUILD_TESTING AND Python3_Interpreter_FOUND)
    # The authored helper is registered by the restored module, above.
    set_tests_properties(windows_rebuild_helper PROPERTIES
        SKIP_REGULAR_EXPRESSION "^SKIP:"
        TIMEOUT 90)
endif()
include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/check_test_registration.cmake)
psxrecomp_check_all_tests_registered()

# ---- output-name collision guard (must stay LAST) ---------------------------
# Fails configure if any two targets here would write the same file on a
# case-insensitive filesystem. See the module header for the shipped-bug story.
include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/check_target_name_collisions.cmake)
psxrecomp_check_output_name_collisions()

if(BUILD_TESTING)
    add_test(NAME load_delay_l1 COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_load_delay_l1.py
        --cc ${CMAKE_C_COMPILER} --recompiler $<TARGET_FILE:psxrecomp-game>
        --bios-emitter $<TARGET_FILE:full_function_emitter_test>)
    add_test(NAME mfc0_event_sample COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_mfc0_event_sample.py
        --cc ${CMAKE_C_COMPILER})
    # PS1B-324 exception-return fixtures. Also registered in runtime/, so the
    # tree that TAS setup builds runs them too. GCC only, as there: the driver
    # compiles with -Wall -Wextra -Werror.
    if(CMAKE_C_COMPILER_ID STREQUAL "GNU")
        add_test(NAME rfe_irq_context_test COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_rfe_irq_context.py
            --cc ${CMAKE_C_COMPILER})
        add_test(NAME rfe_syscall_depth_test COMMAND ${Python3_EXECUTABLE} -B
            ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_rfe_syscall_depth.py
            --cc ${CMAKE_C_COMPILER})
        # PS1B-417: the same guest fixture with a handler that calls ChangeThread.

    endif()
endif()

if(Python3_EXECUTABLE)
    add_test(NAME load_delay_l1_clean_matrix COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_load_delay_l1_matrix.py)
endif()
if(BUILD_TESTING)
    add_test(NAME load_delay_overlay_transport COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_load_delay_overlay.py
        --recompiler $<TARGET_FILE:psxrecomp-game>)
    add_test(NAME load_delay_l1_precise_matrix COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_load_delay_l1_matrix.py --precise)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND AND NOT MSVC)
    add_test(NAME orphan_jump_target_codegen
        COMMAND ${Python3_EXECUTABLE} -B
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_orphan_jump_target_codegen.py"
            --cc ${CMAKE_C_COMPILER}
            --bios-emitter $<TARGET_FILE:full_function_emitter_test>)
endif()

if(BUILD_TESTING)
    add_test(NAME cpu_return_load_view COMMAND ${Python3_EXECUTABLE} -B
        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_cpu_return_load_view.py --cc ${CMAKE_C_COMPILER})
endif()

# ---- source tree unchanged by the suite (after the last add_test) -----------
# Adds source_tree_snapshot and source_tree_unchanged around every test above.
# See the module header for the stray folder this exists to catch. OPTIONAL:
# a tree staged without the module still configures.
include(${CMAKE_CURRENT_SOURCE_DIR}/../runtime/source_tree_check.cmake OPTIONAL)
if(COMMAND psxrecomp_add_source_tree_check)
    psxrecomp_add_source_tree_check()
endif()

# Accepted modern regressions beside the retained replay regressions.
if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
            NAME segment_aware_codegen
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_segment_aware_codegen.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --bios-recompiler $<TARGET_FILE:psxrecomp-bios>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME overlay_segment_keys
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_overlay_segment_keys.py
                    --recompiler $<TARGET_FILE:psxrecomp-game>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME uncached_fetch_charge
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/tests/test_uncached_fetch_charge.py
                    --bios-recompiler $<TARGET_FILE:psxrecomp-bios>
                    --compiler ${CMAKE_C_COMPILER}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME playtest_diagnostics_defaults
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_playtest_diagnostics_defaults.py
                    --cmake ${CMAKE_COMMAND}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME release_channel_cmake
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_release_channel_cmake.py
                    --cmake ${CMAKE_COMMAND}
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME compile_overlays_static_split
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/test_compile_overlays_static_split.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME aot_overlay_spike_coverage_report
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tools/aot_overlay_spike/test_coverage_report.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(
            NAME disc_inventory
            COMMAND ${Python3_EXECUTABLE}
                    ${CMAKE_CURRENT_SOURCE_DIR}/../tests/test_disc_inventory.py
            WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)

add_test(NAME dma_gpu_list_cpu_hold
        COMMAND ${Python3_EXECUTABLE}
            "${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_dma_gpu_list_cpu_hold.py"
            --cc ${CMAKE_C_COMPILER})
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME aligned_lzss_banks
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_aligned_lzss_banks.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME bpe_container
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_bpe_container.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME audit_playtest_diagnostics_package
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_audit_playtest_diagnostics_package.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME package_title_release
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_package_title_release.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME bundled_release_layout
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_bundled_release_layout.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME collect_game_misses
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_collect_game_misses.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME cursor_writers
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_cursor_writers.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME disc_companion
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_disc_companion.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME duckstation_oracle
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_duckstation_oracle.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME fp_identity
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_fp_identity.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME gpu_frame
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_gpu_frame.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME menu_scan
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_menu_scan.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME migrate_probe_refresh
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_migrate_probe_refresh.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME mips_tagged_relocations
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_mips_tagged_relocations.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME overlay_widescreen_callbacks
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_overlay_widescreen_callbacks.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME pad_delivery
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_pad_delivery.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME pad_trace
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_pad_trace.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME psx_chd
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_psx_chd.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME sbi_registry
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_sbi_registry.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME static_fragment_variant_keys
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../tools/tests/test_static_fragment_variant_keys.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()

if(BUILD_TESTING AND Python3_Interpreter_FOUND)
add_test(
                NAME overlay_pair_dedup_runtime
                COMMAND ${Python3_EXECUTABLE}
                        ${CMAKE_CURRENT_SOURCE_DIR}/../runtime/tests/test_overlay_pair_dedup_runtime.py
                WORKING_DIRECTORY ${CMAKE_CURRENT_SOURCE_DIR}/..)
endif()
