# C/C++ tests (ADR 0005). Each area gets one doctest executable; every doctest test case is
# registered with CTest as "<area>.<test case name>", next to platform.smoke. The Python
# tests in this directory run through pytest, not CTest (scripts/test runs both).

maplethief_use_doctest()

# Where tests write files for a human to look at, e.g. golden-image diffs. The presets point
# it at build/test-output/<preset>; the MAPLETHIEF_TEST_OUTPUT_DIR environment variable
# overrides it when a test runs.
set(MAPLETHIEF_TEST_OUTPUT_DIR
    "${CMAKE_BINARY_DIR}/test-output"
    CACHE PATH "Directory for test output files such as golden-image diffs (ADR 0005)")

# One main() for every doctest executable.
add_library(maplethief_doctest_main OBJECT cpp/doctest_main.cpp)
target_link_libraries(maplethief_doctest_main PUBLIC doctest::doctest)
# Let doctest include <ostream> instead of forward-declaring std::basic_ostream itself. Without
# it, MSVC's STL (VS 2026, MSVC 14.51) fails to compile any CHECK that prints a
# std::string_view ("use of undefined type std::basic_ostream"). PUBLIC, so that every test
# translation unit sees the same doctest configuration as the main().
target_compile_definitions(maplethief_doctest_main PUBLIC DOCTEST_CONFIG_USE_STD_HEADERS)
# The tests read their switches with getenv and build fixtures with strcpy and sscanf, which
# MSVC deprecates (C4996, an error under /WX) for its _s variants. The port's own code uses
# those; the tests, never shipped, keep the portable calls.
if(MSVC)
    target_compile_definitions(maplethief_doctest_main PUBLIC _CRT_SECURE_NO_WARNINGS)
endif()
maplethief_target_defaults(maplethief_doctest_main)

# maplethief_doctest_discover(<target> PREFIX <prefix> PROPERTIES <name> <value>...)
#
# Registers each doctest case of <target> as the CTest "<prefix><case name>" after every build,
# with the given properties (a value is one element: no lists). Replaces doctest's own
# doctest_discover_tests, whose CMake-list parsing cut names at ';' and '[' so that the cut
# CTest matched no case and passed without running anything (#171). The listing and the CTest
# script are tools/mtport/doctestctest.py's: every name is a bracket argument, every filter
# selects exactly its case, and MT_DOCTEST_EXPECT_CASES=1 makes doctest_main.cpp fail a run that
# matched no case or more than one. CTest doctest.discovery checks the result.
find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) # the top level's, found later
function(maplethief_doctest_discover target)
    cmake_parse_arguments(PARSE_ARGV 1 arg "" "PREFIX" "PROPERTIES")
    if(NOT arg_PREFIX OR NOT arg_PROPERTIES)
        message(FATAL_ERROR "maplethief_doctest_discover(${target}): PREFIX and PROPERTIES are required")
    endif()
    set(tests_file "${CMAKE_CURRENT_BINARY_DIR}/${target}_ctests.cmake")
    set(include_file "${CMAKE_CURRENT_BINARY_DIR}/${target}_ctest_include.cmake")
    set(props "")
    foreach(value IN LISTS arg_PROPERTIES)
        string(APPEND props " [==[${value}]==]")
    endforeach()
    add_custom_command(
        TARGET ${target}
        POST_BUILD
        BYPRODUCTS "${tests_file}"
        COMMAND "${Python3_EXECUTABLE}" -I -B "${PROJECT_SOURCE_DIR}/tools/mtport/doctestctest.py"
                write --exe "$<TARGET_FILE:${target}>" --prefix "${arg_PREFIX}"
                --workdir "${CMAKE_CURRENT_BINARY_DIR}" --props-fn "_mt_doctest_props_${target}"
                --out "${tests_file}"
        VERBATIM)
    file(WRITE "${include_file}"
         "function(_mt_doctest_props_${target} test)\n"
         "  set_tests_properties(\"\${test}\" PROPERTIES${props})\n"
         "endfunction()\n"
         "if(EXISTS \"${tests_file}\")\n"
         "  include(\"${tests_file}\")\n"
         "else()\n"
         "  add_test(${target}_NOT_BUILT ${target}_NOT_BUILT)\n"
         "endif()\n")
    set_property(DIRECTORY APPEND PROPERTY TEST_INCLUDE_FILES "${include_file}")
endfunction()

# maplethief_add_cpp_test(<target> AREA <area> SOURCES <src>... [LIBRARIES <lib>...])
#
# Builds <target> from the sources plus the shared doctest main, and registers each of its
# test cases with CTest as "<area>.<test case name>".
function(maplethief_add_cpp_test target)
    cmake_parse_arguments(PARSE_ARGV 1 arg "" "AREA" "SOURCES;LIBRARIES")
    if(NOT arg_AREA OR NOT arg_SOURCES)
        message(FATAL_ERROR "maplethief_add_cpp_test(${target}): AREA and SOURCES are required")
    endif()
    add_executable(${target} ${arg_SOURCES})
    target_link_libraries(${target} PRIVATE maplethief_doctest_main ${arg_LIBRARIES})
    maplethief_target_defaults(${target})
    maplethief_doctest_discover(${target} PREFIX "${arg_AREA}." PROPERTIES LABELS cpp TIMEOUT 60)
endfunction()

maplethief_add_cpp_test(
    runtime_test
    AREA runtime
    SOURCES cpp/runtime_test.cpp
            cpp/recomp_headers_test.cpp
            cpp/imports_test.cpp
            cpp/memory_test.cpp
            cpp/diag_test.cpp
            cpp/libc_test.cpp
            cpp/cxxabi_test.cpp
            cpp/hooks_test.cpp
            cpp/widescreen_test.cpp
            cpp/trace_entry_fixture.c
            cpp/guest_trap.cpp
    LIBRARIES runtime)
# libc_test.cpp reaches the runtime's internal HLE headers (runtime/src/hle/format.h, hle.h).
target_include_directories(runtime_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")

# The guest that crashes on purpose (ADR 0030): diag_test.cpp runs it as a child process to
# see the crash reporter's output for faults that kill the process. ENABLE_EXPORTS: dladdr
# finds its f_XXXXXXXX function from the host PC, as in the game executable.
add_executable(crash_child cpp/crash_child.cpp)
target_link_libraries(crash_child PRIVATE runtime)
set_target_properties(crash_child PROPERTIES ENABLE_EXPORTS ON)
maplethief_target_defaults(crash_child)
add_dependencies(runtime_test crash_child)
target_compile_definitions(runtime_test PRIVATE MT_CRASH_CHILD="$<TARGET_FILE:crash_child>")

# The real image in guest memory (ADR 0025): maps work/slices/MapleThief.armv7 when
# scripts/bootstrap has written it, and is reported as skipped otherwise (always in CI).
add_executable(memory_real_test cpp/memory_real_test.cpp cpp/guest_trap.cpp)
target_link_libraries(memory_real_test PRIVATE runtime)
target_compile_definitions(
    memory_real_test PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7")
maplethief_target_defaults(memory_real_test)
add_test(NAME runtime.memory_real_image COMMAND memory_real_test)
set_tests_properties(runtime.memory_real_image PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE
                                                          77 TIMEOUT 60)

# The Objective-C runtime (ADR 0026) defines its own mt_trap / mt_dispatch test doubles, so it
# is a separate executable. The offline service stubs (#52) are host classes of that runtime and
# are tested with it; stubs_test.cpp also tests their JSON code (runtime/src/stubs/json.h). So are
# CoreFoundation (#48), Foundation (#49) and UIKit (#54).
maplethief_add_cpp_test(
    objc_test
    AREA objc
    SOURCES cpp/objc_test.cpp
            cpp/objc_services_test.cpp
            cpp/stubs_test.cpp
            cpp/cf_test.cpp
            cpp/cg_test.cpp
            cpp/foundation_test.cpp
            cpp/saves_test.cpp
            cpp/snapshot_test.cpp
            cpp/uikit_test.cpp
            cpp/host_surface_test.cpp
            cpp/gles_test.cpp
            cpp/gles_fixed_test.cpp
            cpp/audio_test.cpp
            cpp/extaudiofile_test.cpp
            cpp/mp3_test.cpp
            cpp/audio_pack_test.cpp
            cpp/texpack_test.cpp
            cpp/textpack_test.cpp
    LIBRARIES runtime maplethief_golden maplethief::dr_mp3)
# stubs_test.cpp (#52; #156: the offline cash shop's store, fake receipt server and counters on
# a synthetic guest), cf_test.cpp (#48), cg_test.cpp (#51), foundation_test.cpp (#49),
# saves_test.cpp (#78: atomic save files, a child process killed while it saves),
# snapshot_test.cpp (#202: save states' file, refusals and in-process restore, synthetic),
# uikit_test.cpp (#54), gles_test.cpp (#117), gles_fixed_test.cpp (#56), audio_test.cpp (#61,
# OpenAL through the mixer, sample-exact on synthetic PCM), extaudiofile_test.cpp (#62, WAV
# decoding on synthetic files), texpack_test.cpp (#153, texture packs and dumps on synthetic
# textures) and mp3_test.cpp (#63, AVAudioPlayer on the synthetic MP3s of
# tests/cpp/data/audio/, decoded by the runtime's dr_mp3, whose header it includes for the
# reference decode) and audio_pack_test.cpp (#188, audio packs of generated tones and those
# MP3s, sample for sample through the mixer; corrupt and oversize files) and textpack_test.cpp
# (#189, text packs on synthetic packs and tables) reach the runtime's
# internal headers (runtime/src/); cg_test.cpp, uikit_test.cpp and gles_fixed_test.cpp check
# golden images of synthetic drawings; host_surface_test.cpp reads the committed host-surface
# table.
target_include_directories(objc_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    objc_test PRIVATE MT_GOLDEN_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/golden/data"
                      MT_HOST_SURFACE_DOC="${PROJECT_SOURCE_DIR}/docs/re/host-surface.md"
                      MT_AUDIO_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/cpp/data/audio")
# The GL pipeline's goldens are exact on every CPU (ADR 0039, 0040): no contraction there either;
# nor in the mixer's expected samples (ADR 0045).
maplethief_strict_fp(cpp/gles_fixed_test.cpp cpp/audio_test.cpp cpp/mp3_test.cpp
                     cpp/audio_pack_test.cpp)

# mt_gfx, the rendering backend (#55, ADR 0039): rasterization rules and golden images of
# synthetic scenes, exact on every platform. It also builds the frontend's prototype scene
# (platform/src/gfx_prototype.cpp, no SDL in it) to compare it with its golden image, and reaches
# the runtime's internal gfx header (runtime/src/gfx/gfx_internal.h).
maplethief_add_cpp_test(
    gfx_test
    AREA gfx
    SOURCES cpp/gfx_test.cpp "${PROJECT_SOURCE_DIR}/platform/src/gfx_prototype.cpp"
    LIBRARIES runtime maplethief_golden)
target_include_directories(gfx_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src"
                                            "${PROJECT_SOURCE_DIR}/platform/src")
target_compile_definitions(gfx_test PRIVATE MT_GOLDEN_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/golden/data")
# The scenes' vertices are computed in floats: the same on every CPU only without contraction.
maplethief_strict_fp(cpp/gfx_test.cpp "${PROJECT_SOURCE_DIR}/platform/src/gfx_prototype.cpp")

# mt_gfx's GPU backend (#120, ADR 0060) against the software rasterizer: gfx_test's golden scenes
# on the GPU within ADR 0060's tolerance, internal scale 4, the sync points, batching and a busy
# frame's GPU time, the present filters (#88) and GPU presentation (#130). Local only: without a GPU device (CI, the Linux container) and on GitHub
# Actions every case prints "gfx_gpu: skipped", which CTest reports as skipped. One label only:
# maplethief_doctest_discover flattens a list value into the property arguments.
add_executable(gfx_gpu_test cpp/gfx_gpu_test.cpp)
target_link_libraries(gfx_gpu_test PRIVATE maplethief_doctest_main maplethief_gfx_gpu
                                           maplethief_golden maplethief_frontend)
# The present stage's filters (#88) are drawn through the frontend's Presenter and PresentPass.
target_sources(gfx_gpu_test PRIVATE "${PROJECT_SOURCE_DIR}/platform/src/gfx_prototype.cpp")
target_include_directories(gfx_gpu_test PRIVATE "${PROJECT_SOURCE_DIR}/platform/src")
target_compile_definitions(gfx_gpu_test PRIVATE MT_GOLDEN_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/golden/data")
maplethief_strict_fp(cpp/gfx_gpu_test.cpp)
maplethief_target_defaults(gfx_gpu_test)
maplethief_doctest_discover(
    gfx_gpu_test
    PREFIX "gfx_gpu."
    PROPERTIES LABELS gpu TIMEOUT 120 SKIP_REGULAR_EXPRESSION "gfx_gpu: skipped")

# The GPU shaders' committed header is up to date with its HLSL sources (#120, ADR 0060):
# scripts/build-shaders --check, without the container.
find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) # the top level's, found later
add_test(NAME shaders.stamps
         COMMAND "${Python3_EXECUTABLE}" -I -B "${PROJECT_SOURCE_DIR}/tools/mtport/shaderbuild.py"
                 check --repo "${PROJECT_SOURCE_DIR}")
set_tests_properties(shaders.stamps PROPERTIES TIMEOUT 30)

# Every doctest case has exactly one CTest, whose filter selects that case (#171): ctest's own
# test list against each doctest executable's --list-test-cases.
add_test(NAME doctest.discovery
         COMMAND "${Python3_EXECUTABLE}" -I -B "${PROJECT_SOURCE_DIR}/tools/mtport/doctestctest.py"
                 check --ctest "${CMAKE_CTEST_COMMAND}" --build-dir "${CMAKE_BINARY_DIR}")
set_tests_properties(doctest.discovery PROPERTIES LABELS cpp TIMEOUT 60)

# The frontend (#58, ADR 0042): the fit rectangle, the armv7 slice of the bundle's executable,
# the bundle's precedence, and the presentation chain on a synthetic guest (GL -> the present
# hook -> the compositor -> an SDL texture -> a dummy-driver window, read back). Scaling (#59,
# ADR 0043, scaling_test.cpp): every mode's placement, the mouse inverse, settings.ini, and
# read-backs at 1080p, 1440p and a Retina-sized drawable. The audio output (#61, ADR 0045,
# audio_out_test.cpp): the capture's frames and bytes, late callbacks, the dummy-driver device
# stream. Touch input (#65, ADR 0047, input_test.cpp): SDL mouse and finger events pushed on a
# dummy-driver window, through Input into a synthetic main menu and key pad. Gamepads and
# actions (#67, ADR 0048, gamepad_test.cpp): SDL3 virtual joysticks of the Xbox 360, PS4 and
# PS5 (mapped) kinds through Gamepads and Actions, and the runtime's action delivery into a
# synthetic EAGLView, TouchKeyPadView and SkyCanvas. The keyboard (#68, keyboard_test.cpp):
# synthetic SDL key events through Keyboard and Actions, against docs/controls/keyboard.md's
# table. The key pad's hiding (#71, ADR 0052, keypad_hide_test.cpp): the modes, every input
# source, a hide waiting for a touch to end, the fade, hide_keypad, the opacity on the pad's
# panel 0. Controller navigation (#69, ADR 0051, focus_test.cpp): spatial navigation, the
# highlight, Focus on scripted targets, and mt_uikit_controls on a synthetic view tree. The
# bindings file and the remap overlay (#70, ADR 0054, bindings_test.cpp, remap_test.cpp):
# parsing, validation, the round trip, the overrides in Actions, the overlay driven by
# synthetic events, the glyph sets on virtual Xbox 360, PS5 and Switch Pro pads, and a
# capture of the overlay per set, by hash, in the test output directory. Rumble (#72, ADR
# 0053, rumble_test.cpp): virtual joysticks whose motors record SDL's calls, the game
# events of a synthetic hero (mt_observe_entry) and the vibrate import (through the
# runtime's internal audio header). The safe area (#73, ADR 0055, safe_area_test.cpp): the
# placement inside a phone's safe area in every mode, both landscape orientations, densities
# 1-3, the touch inverse, the focus highlight, and a read-back with forced insets. Frame
# telemetry and the frame-time overlay (#137, ADR 0071, frame_stats_test.cpp): FrameStats over a
# synthetic real-time run (the exit summary, the split, the vblanks, the CSV), F3 and the
# overlay on a dummy-driver window, never in a run that writes images. The settings registry
# and the presets (#144, ADR 0070, presets_test.cpp): each preset sets exactly its column, one
# change makes it custom, Assists off in every column, the preset line in the file, and the
# first-launch picker by keys, pad, mouse and touch (its loop on a dummy-driver window) with its
# drawing checked by hash. The offline cash shop's pricing (#156, ADR 0072, cashshop_test.cpp):
# the level curve, the escalation, the floors, the counters file and the price line's text
# (the running shop on a synthetic guest is stubs_test.cpp's). Display-locked pacing (#80, ADR
# 0075, pacing_test.cpp): the plan for 60/120/144 Hz and the rest, the paced clock on a fake
# host clock, and the game's tick gate on it at 60, 120, 59.94 and 60.06 Hz. The save tools
# (#148, ADR 0078, save_tools_test.cpp): export and import round trips, hostile sets refused
# with nothing written, the import's backup and roll-back, backups rotating at N, the
# SaveQuestData hook, zips, portable mode. The desktop conveniences (#145, ADR 0077,
# desktop_test.cpp): the four settings rows, the hotkeys, the cursor, the wheel, window.ini,
# screenshots, and fullscreen and vsync live on a dummy-driver window with the mouse still
# mapping. The local leaderboard and achievements list (#154, ADR 0084,
# local_game_center_test.cpp). Menu repeat and the popup confirm guard (#147,
# menu_input_test.cpp): the timing, the panels, a busy queue, two devices, the guard's window
# and its held-from-the-field rule, the popup reader. Button prompts and the focus markers
# (#146, ADR 0051's addendum, prompts_test.cpp): the prompt table and the glyph's side, the
# control shown, the device in use, the popup menu's list cursor in a synthetic guest memory,
# the glyphs switching live on virtual Xbox 360, PS5 and Switch Pro pads (by hash), and the
# prompts' layer on a software renderer. The customisable key pad (#159, ADR 0086,
# keypad_layout_test.cpp): the layout's text, its clamping, the hit mask's point and the GL
# move, both hooks on synthetic calls, the views of a synthetic pad and the editor's touches,
# keys and pad. The border (#157, ADR 0085, border_test.cpp): color byte-identical to before,
# the blur and the art at 16:9 and 21:9 in every mode, hostile PNGs refused. Game speed and
# fast-forward (#151, ADR 0087, game_speed_test.cpp): the Assists rows, the speed table patched
# in a synthetic guest, the fast_forward action never sent to the guest, the clocks' rate
# continuous at every toggle and the tick gate at 2x and 3x. Autosave (#150,
# autosave_test.cpp): every guard on a fake guest, the 16 no-save maps, the setting row, once
# per leave. The colour-blind filter (#158, ADR 0093, color_filter_test.cpp): the matrices,
# the CPU way against the float reference, off byte-identical, live, the CPU path read back;
# the picture adjustments in the same step (#187, ADR 0093's addendum) likewise.
# Frame interpolation (#87, ADR 0091, frame_interp_test.cpp): the camera's scroll,
# repeated tiles, animation frames, snaps, the replay byte for byte at alpha 1 on the software
# rasterizer, and the presents between ticks at 60 Hz. The minimap (#176, ADR 0098,
# minimap_test.cpp): a synthetic guest's map read back, hostile values refused, the layout in a
# bar or over the picture, the dots, a golden image of a synthetic grid, off drawing nothing.
# The EXP percentage (#191, ADR 0109, exp_percent_test.cpp): a synthetic hero and hero.dat read
# back, hostile values refused, the text, the box at every scale, the pixels, the layer.
# The key pad's button labels (ADR 0101, hud_labels_test.cpp): the keys' table, each key's
# control per device and after a remap, the places in the original layout, widescreen's edges
# and narrow and custom layouts, the glyph size, the fade, a golden image of a synthetic pad,
# the layer on a software renderer. UI scaling (ADR 0103, ui_scaling_test.cpp): the picture, the mapping, the gear and the
# window text over 2304 window sizes, densities, modes, widescreen, render scales and safe
# areas; the window text's scale and boxes; the notice and the price line drawn; the veil.
# The regression run's replay and checkpoints (#91, ADR 0104, replay_test.cpp): the file's round
# trip and every malformed line refused, SDL's pad names, the cursor, the frame hash, checkpoint
# lines, digests and the first difference, the plan, the expects. The controls card (#193, ADR
# 0054's addendum, controls_card_test.cpp): the lists from the live bindings, unbound actions,
# the device and glyph set following virtual pads, the touch card's layout, the picker's second
# step, and the card per device against synthetic goldens. The key pad's haptics (#194, ADR
# 0055's addendum, keypad_haptics_test.cpp): a recording sink's taps per new key, none for holds,
# off-mask reads, a popup or a hidden pad, the wrapper on synthetic mask reads, the iOS-only row.
# The gamepad pointer (#185, ADR 0107, pad_pointer_test.cpp): the stick's dead zone, curve and
# acceleration, the clamp (narrow, widescreen), touchpad points, the tap's one touch pair, R3
# shared with fast-forward, the hides, off changing nothing, `hold NAME`.
# The main thread's priority (#206, ADR 0120, thread_priority_test.cpp): the opt-out, the
# descriptions, the start-up line, a real raise on a thread of its own (or its refusal).
# The first-launch IPA import (#182, ipa_import_test.cpp): synthetic IPAs refused with each
# reason, unsafe entries writing nothing, an interrupted import leaving no game/, a re-import,
# the bundle lookup's order, and the import screen's input and loop on a dummy-driver window.
# Reaches
# the runtime's internal UIKit and GL headers to set up the guest's window and GL calls.
maplethief_add_cpp_test(
    platform_test
    AREA platform
    SOURCES cpp/platform_test.cpp cpp/scaling_test.cpp cpp/clock_test.cpp cpp/audio_out_test.cpp
            cpp/input_test.cpp cpp/gamepad_test.cpp cpp/keyboard_test.cpp
            cpp/keypad_hide_test.cpp cpp/focus_test.cpp cpp/lifecycle_test.cpp cpp/rumble_test.cpp
            cpp/safe_area_test.cpp cpp/bindings_test.cpp cpp/remap_test.cpp
            cpp/frame_stats_test.cpp cpp/presets_test.cpp cpp/render_scale_test.cpp
            cpp/cashshop_test.cpp cpp/pacing_test.cpp cpp/save_tools_test.cpp
            cpp/desktop_test.cpp cpp/local_game_center_test.cpp cpp/menu_input_test.cpp
            cpp/prompts_test.cpp cpp/keypad_layout_test.cpp cpp/border_test.cpp
            cpp/settings_menu_test.cpp cpp/game_speed_test.cpp cpp/autosave_test.cpp
            cpp/color_filter_test.cpp
            cpp/frame_interp_test.cpp
            cpp/assists_test.cpp
            cpp/scene_skip_test.cpp
            cpp/minimap_test.cpp
            cpp/exp_percent_test.cpp
            cpp/auto_run_test.cpp
            cpp/hud_labels_test.cpp
            cpp/list_keys_test.cpp
            cpp/disconnect_test.cpp
            cpp/controls_card_test.cpp
            cpp/ui_scaling_test.cpp
            cpp/replay_test.cpp
            cpp/pad_pointer_test.cpp
            cpp/file_dialog_test.cpp
            cpp/keypad_haptics_test.cpp
            cpp/port_text_test.cpp
            cpp/thread_priority_test.cpp
            cpp/ipa_import_test.cpp
            cpp/save_state_input_test.cpp
            cpp/quick_state_test.cpp
    LIBRARIES maplethief_frontend runtime SDL3::SDL3 maplethief_golden)
# The audio output's expected capture samples round as the capture does (ADR 0045).
maplethief_strict_fp(cpp/audio_out_test.cpp)
target_include_directories(platform_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(platform_test PRIVATE MT_REPO_DIR="${PROJECT_SOURCE_DIR}"
                                                 MT_TEST_OUTPUT_DIR="${MAPLETHIEF_TEST_OUTPUT_DIR}")
# The minimap's golden image of a synthetic grid (#176, minimap_test.cpp) and the key pad's
# labels' (ADR 0101, hud_labels_test.cpp).
target_compile_definitions(platform_test PRIVATE MT_GOLDEN_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/golden/data")

# The first-launch IPA import on the real IPA (#182, ipa_import.h): original/MapleThief-1.5.ipa
# imported into a fresh data directory, every file compared with work/ipa's extraction.
# Skipped (exit 77) without them (always in CI). The synthetic cases are ipa_import_test.cpp's.
add_executable(ipa_import_real_test cpp/ipa_import_real_test.cpp)
target_link_libraries(ipa_import_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_compile_definitions(ipa_import_real_test PRIVATE MT_REPO_DIR="${PROJECT_SOURCE_DIR}")
maplethief_target_defaults(ipa_import_real_test)
add_test(NAME platform.ipa_import_real COMMAND ipa_import_real_test)
set_tests_properties(platform.ipa_import_real PROPERTIES LABELS "cpp;real_assets"
                                                         SKIP_RETURN_CODE 77 TIMEOUT 120)

# The save checks in C (mt_saves_check) and in Python (tools/mtport/savecheck.py) on the same
# synthetic fixtures from savedata.py, as folders and deflated zips, through `MapleThief
# --check-saves`; then each fixture imported through `--import-saves` (refused ones change
# nothing) and exported back with `--export-saves` to a folder and a zip, byte for byte (#148,
# ADR 0078). Each run has its own MAPLETHIEF_DATA_DIR, never the player's.
foreach(what IN ITEMS parity cli)
    set(_name check_parity)
    if(what STREQUAL "cli")
        set(_name cli_round_trip)
    endif()
    add_test(NAME saves.${_name}
             COMMAND "${Python3_EXECUTABLE}" -B -m mtport.savecheck ${what} --program
                     "$<TARGET_FILE:MapleThief>"
             WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools")
    set_tests_properties(saves.${_name} PROPERTIES TIMEOUT 300 ENVIRONMENT
                                                   "SDL_VIDEO_DRIVER=dummy")
endforeach()

# Saves out of an iPhone or iPod backup (#149, docs/formats/ios-backup.md): synthetic backups
# of both manifest formats (Manifest.db and Manifest.mbdb) through `python3 -m mtport.iosbackup
# import` and the program's `--import-saves`, which leaves Documents/ byte for byte as the
# backup's saves; encrypted backups and a broken save are refused with Documents/ unchanged.
add_test(NAME saves.ios_backup_import
         COMMAND "${Python3_EXECUTABLE}" -B -m mtport.iosbackup selftest --program
                 "$<TARGET_FILE:MapleThief>"
         WORKING_DIRECTORY "${PROJECT_SOURCE_DIR}/tools")
set_tests_properties(saves.ios_backup_import PROPERTIES TIMEOUT 300 ENVIRONMENT
                                                        "SDL_VIDEO_DRIVER=dummy")

# CoreFoundation on the real bundle and slice (#48, ADR 0032): every bundle file through the
# game's resource calls, every CFString literal; skipped without work/ipa (always in CI).
add_executable(cf_real_test cpp/cf_real_test.cpp cpp/guest_trap.cpp)
target_link_libraries(cf_real_test PRIVATE runtime)
target_include_directories(cf_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    cf_real_test PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
                         MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
maplethief_target_defaults(cf_real_test)
add_test(NAME cf.real_bundle COMMAND cf_real_test)
set_tests_properties(cf.real_bundle PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                               TIMEOUT 120)

# CoreGraphics / UIKit drawing on the real bundle (#51, ADR 0036): every shipped PNG (CgBI
# included) and every PNG of the png/**/*.dat packs through UIImage; skipped without work/ipa
# (always in CI).
add_executable(cg_real_test cpp/cg_real_test.cpp cpp/guest_trap.cpp)
target_link_libraries(cg_real_test PRIVATE runtime)
target_include_directories(cg_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    cg_real_test PRIVATE MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
maplethief_target_defaults(cg_real_test)
add_test(NAME cg.real_bundle_images COMMAND cg_real_test)
set_tests_properties(cg.real_bundle_images PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE
                                                      77 TIMEOUT 120)

# AVAudioPlayer's MP3 music on the real bundle (#63, ADR 0046): all 18 MP3s open with the
# frame walk's length, stream to their end, loop past it, and the menu track mixes exactly across
# its loop point; on macOS, every file and the synthetic fixtures also against Apple's own decoder
# (ExtAudioFile). Skipped without work/ipa (always in CI).
add_executable(mp3_real_test cpp/mp3_real_test.cpp)
target_link_libraries(mp3_real_test PRIVATE runtime)
if(APPLE)
    target_link_libraries(mp3_real_test PRIVATE "-framework AudioToolbox"
                                                "-framework CoreFoundation")
endif()
target_include_directories(mp3_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    mp3_real_test PRIVATE MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app"
                          MT_AUDIO_DATA_DIR="${PROJECT_SOURCE_DIR}/tests/cpp/data/audio")
maplethief_target_defaults(mp3_real_test)
add_test(NAME audio.real_mp3 COMMAND mp3_real_test)
set_tests_properties(audio.real_mp3 PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                               TIMEOUT 300)

# Apple's CoreGraphics as the reference for the texture loader's premultiplied context (#57,
# ADR 0041), macOS only: synthetic images always (CI), every shipped PNG when work/ipa is there.
if(APPLE)
    add_executable(cg_apple_test cpp/cg_apple_test.cpp cpp/guest_trap.cpp)
    target_link_libraries(cg_apple_test PRIVATE runtime "-framework CoreFoundation"
                                                "-framework CoreGraphics" "-framework ImageIO")
    target_include_directories(cg_apple_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
    target_compile_definitions(
        cg_apple_test PRIVATE MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
    maplethief_target_defaults(cg_apple_test)
    add_test(NAME cg.apple_reference COMMAND cg_apple_test)
    set_tests_properties(cg.apple_reference PROPERTIES LABELS cpp TIMEOUT 300)
endif()

# Foundation on the real bundle (#49, ADR 0035): every binary plist parses, every localized
# string answers through NSBundle; skipped without work/ipa (always in CI).
add_executable(foundation_real_test cpp/foundation_real_test.cpp cpp/guest_trap.cpp)
target_link_libraries(foundation_real_test PRIVATE runtime)
target_include_directories(foundation_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    foundation_real_test
    PRIVATE MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
maplethief_target_defaults(foundation_real_test)
add_test(NAME foundation.real_bundle COMMAND foundation_real_test)
set_tests_properties(foundation.real_bundle PROPERTIES LABELS "cpp;real_assets"
                                                       SKIP_RETURN_CODE 77 TIMEOUT 120)

# ExtAudioFile on the real bundle (#62, ADR 0045): every shipped WAV through the game's
# MyGetOpenALAudioData calls and alBufferData, against a strict reference reader and the
# generated table of docs/re/audio.md; skipped without work/ipa (always in CI).
add_executable(audio_real_test cpp/audio_real_test.cpp cpp/guest_trap.cpp)
target_link_libraries(audio_real_test PRIVATE runtime)
target_include_directories(audio_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    audio_real_test PRIVATE MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app"
                            MT_AUDIO_DOC="${PROJECT_SOURCE_DIR}/docs/re/audio.md")
maplethief_target_defaults(audio_real_test)
add_test(NAME audio.real_wavs COMMAND audio_real_test)
set_tests_properties(audio.real_wavs PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                                TIMEOUT 120)

# The ObjC runtime running recompiled methods of the real image (#47, ADR 0029). Only when the
# recompiler's output is there (target maplethief_gen, which also compiles
# runtime/src/recomp/dispatch.c; never in CI); skipped without the slice.
if(TARGET maplethief_gen)
    add_executable(objc_real_test cpp/objc_real_test.cpp cpp/guest_trap.cpp)
    target_link_libraries(objc_real_test PRIVATE maplethief_gen runtime)
    target_compile_definitions(
        objc_real_test PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7")
    maplethief_target_defaults(objc_real_test)
    add_test(NAME objc.real_image_app_delegate COMMAND objc_real_test)
    set_tests_properties(objc.real_image_app_delegate PROPERTIES LABELS "cpp;real_assets"
                                                                 SKIP_RETURN_CODE 77 TIMEOUT 60)

    # The offline stubs (#52, ADR 0034) under the game's recompiled GameCenterManager and
    # InAppPurchaseManager.
    add_executable(stubs_real_test cpp/stubs_real_test.cpp cpp/guest_trap.cpp)
    target_link_libraries(stubs_real_test PRIVATE maplethief_gen runtime)
    # Foundation's host API (runtime/src/foundation/foundation.h) makes the test's strings.
    target_include_directories(stubs_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
    target_compile_definitions(
        stubs_real_test PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7")
    maplethief_target_defaults(stubs_real_test)
    add_test(NAME stubs.real_image_game_center_and_store COMMAND stubs_real_test)
    set_tests_properties(stubs.real_image_game_center_and_store
                         PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 60)
endif()

# GCD (ADR 0033): queues, worker threads, groups, timers, dispatch_once. It defines its own
# mt_trap / mt_dispatch test doubles, so it is a separate executable.
maplethief_add_cpp_test(
    gcd_test
    AREA gcd
    SOURCES cpp/gcd_test.cpp
    LIBRARIES runtime)

# Process start-up (ADR 0027) on a synthetic Mach-O. It links the runtime's own mt_trap and
# defines an mt_dispatch test double, so it is a separate executable.
maplethief_add_cpp_test(
    boot_test
    AREA boot
    SOURCES cpp/boot_test.cpp
    LIBRARIES runtime)

# The real boot (ADR 0027), launch (#54, ADR 0037) and first GL frame (#117, ADR 0038): the
# generated program (maplethief_gen) and the runtime start the armv7 slice, load MainWindow.nib
# and run the main run loop: runtime.boot_real until the first GL frame (M4's exit criterion),
# runtime.launch_real for a few seconds of the main menu with taps. Needs work/gen (python -m
# recomp all), the slice and work/ipa; without them the test is built without the generated code
# and reports itself skipped (CI). Its renders of the
# screen go to the test output directory (build/, never committed).
add_executable(boot_real_test cpp/boot_real_test.cpp)
target_link_libraries(boot_real_test PRIVATE runtime maplethief_golden)
target_include_directories(boot_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    boot_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(boot_real_test PRIVATE maplethief_gen)
    target_compile_definitions(boot_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(boot_real_test)
add_test(NAME runtime.boot_real COMMAND boot_real_test first-frame)
set_tests_properties(runtime.boot_real PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                                  TIMEOUT 120)
add_test(NAME runtime.launch_real COMMAND boot_real_test launch)
set_tests_properties(runtime.launch_real PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                                    TIMEOUT 120 RUN_SERIAL TRUE)

# The gamepad in the real game (#67, ADR 0048): the game boots fresh with the virtual clock
# (MapleThief --fixed-step 1), and the frontend's own input layer (Input, Actions, Gamepads) is
# driven from the turn hook with SDL3 virtual joysticks: input.gamepad_play walks, jumps,
# attacks, uses a skill and drinks potions with an Xbox 360, a PS4 and a PS5 pad hot-plugged in
# turn and checks the hero in guest memory; input.gamepad_hold holds the d-pad and attack once
# as key pad touches and once as the pad, on the same turns, and the hero's per-tick x, y and
# state must be identical (platform/gamepad_test.cmake). Skipped (77) without the generated
# code, the slice or the bundle. Output to the test output directory (build/, never committed).
add_executable(gamepad_real_test cpp/gamepad_real_test.cpp)
target_link_libraries(gamepad_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_include_directories(gamepad_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    gamepad_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(gamepad_real_test PRIVATE maplethief_gen)
    target_compile_definitions(gamepad_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(gamepad_real_test)
add_test(NAME input.gamepad_play COMMAND gamepad_real_test play
                                         "${MAPLETHIEF_TEST_OUTPUT_DIR}/gamepad_play")
set_tests_properties(input.gamepad_play PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                                   TIMEOUT 300 RUN_SERIAL TRUE)
add_test(
    NAME input.gamepad_hold
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:gamepad_real_test>"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/gamepad_hold" -P
            "${PROJECT_SOURCE_DIR}/platform/gamepad_test.cmake")
set_tests_properties(input.gamepad_hold PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION
                                                   "skipped:" TIMEOUT 300 RUN_SERIAL TRUE)
# The key pad's hiding (#71, ADR 0052): the same program's keypad run (auto, always and never on
# one input stream; the first map hidden vs shown differs only inside the key pad's rects) and
# its hold run with the pad hidden, whose hero and GL frames must match the shown runs'
# (platform/keypad_hide_test.cmake).
add_test(
    NAME input.keypad_hide
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:gamepad_real_test>"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/input_keypad_hide" -P
            "${PROJECT_SOURCE_DIR}/platform/keypad_hide_test.cmake")
set_tests_properties(input.keypad_hide PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION
                                                  "skipped:" TIMEOUT 900 RUN_SERIAL TRUE)
# The customisable key pad (#159, ADR 0086): the same program's hold run with keypad_layout
# original, with a custom layout touched where its keys are drawn (the same hero, tick for
# tick) and where they were (nothing), the screen and the GL frame differing only inside the
# groups' old and new rects (platform/keypad_layout_test.cmake).
add_test(
    NAME input.keypad_layout
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:gamepad_real_test>"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/input_keypad_layout" -P
            "${PROJECT_SOURCE_DIR}/platform/keypad_layout_test.cmake")
set_tests_properties(input.keypad_layout PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION
                                                    "skipped:" TIMEOUT 900 RUN_SERIAL TRUE)
# Rumble in the real game (#72, ADR 0053): the same boot to the first map with a virtual pad
# whose motors record SDL's calls; a hit through CHero::TakeDamage and a level-up through
# CHero::setExp, with rumble off (no call) and on (one call each, at the strength).
add_test(NAME input.rumble COMMAND gamepad_real_test rumble
                                   "${MAPLETHIEF_TEST_OUTPUT_DIR}/rumble")
set_tests_properties(input.rumble PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                             TIMEOUT 300 RUN_SERIAL TRUE)
# The disconnect pause in the real game (#184): the same boot to the first map, the pad's stick
# walking right, the pad unplugged mid-walk; the turn held while the pause is open (the guest's
# tick and the hero unchanged), a key closing it, the hero standing afterwards.
add_test(NAME input.gamepad_disconnect COMMAND gamepad_real_test disconnect
                                               "${MAPLETHIEF_TEST_OUTPUT_DIR}/gamepad_disconnect")
set_tests_properties(input.gamepad_disconnect PROPERTIES LABELS "cpp;real_assets"
                                                         SKIP_RETURN_CODE 77 TIMEOUT 300
                                                         RUN_SERIAL TRUE)

# Guest function hooks in the real game (#143, ADR 0069): the main menu's NGB portal items sent
# OK through CStateMainMenu::OnKeyPressed with the OpenPortalURLAndExit hook on (the Enhanced
# preset's fix_original_bugs, #155: the game goes on) and off (Original: exit(1), as on the device). Skipped (77) without the generated code, the slice or the
# bundle. Output to the test output directory.
add_executable(hooks_real_test cpp/hooks_real_test.cpp)
target_link_libraries(hooks_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_compile_definitions(
    hooks_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(hooks_real_test PRIVATE maplethief_gen)
    target_compile_definitions(hooks_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(hooks_real_test)
foreach(what on off)
    add_test(NAME runtime.hooks_portal_${what}
             COMMAND hooks_real_test ${what} "${MAPLETHIEF_TEST_OUTPUT_DIR}/hooks_portal_${what}")
    set_tests_properties(runtime.hooks_portal_${what} PROPERTIES LABELS "cpp;real_assets"
                         SKIP_RETURN_CODE 77 TIMEOUT 300 RUN_SERIAL TRUE)
endforeach()

# The keyboard in the real game (#68, ADR 0048's addendum): as the gamepad's, from the turn hook
# with synthetic SDL key events (and the held keys' repeats) through Input, Keyboard and Actions:
# input.keyboard_play plays the first map by keys and checks the hero in guest memory;
# input.keyboard_hold holds Left, Z and Right once as key pad touches and once as keys, on the
# same turns, and the per-tick hero must be identical; input.keyboard_menus goes through the main
# menu and the character screens by keys alone (platform/keyboard_test.cmake). Skipped (77)
# without the generated code, the slice or the bundle. Output to the test output directory.
add_executable(keyboard_real_test cpp/keyboard_real_test.cpp)
target_link_libraries(keyboard_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_include_directories(keyboard_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    keyboard_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(keyboard_real_test PRIVATE maplethief_gen)
    target_compile_definitions(keyboard_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(keyboard_real_test)
add_test(NAME input.keyboard_play COMMAND keyboard_real_test play
                                          "${MAPLETHIEF_TEST_OUTPUT_DIR}/keyboard_play")
set_tests_properties(input.keyboard_play PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77
                                                    TIMEOUT 300 RUN_SERIAL TRUE)
foreach(what hold menus)
    add_test(
        NAME input.keyboard_${what}
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:keyboard_real_test>" -DWHAT=${what}
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/keyboard_${what}" -P
                "${PROJECT_SOURCE_DIR}/platform/keyboard_test.cmake")
    set_tests_properties(input.keyboard_${what} PROPERTIES LABELS "cpp;real_assets"
                         SKIP_REGULAR_EXPRESSION "skipped:" TIMEOUT 300 RUN_SERIAL TRUE)
endforeach()

# Remapped bindings in the real game (#70, ADR 0054): a bindings.ini moves attack to V and the
# pad's right stick click; in the first map the new controls attack and the old ones do not,
# the remap overlay binds quick slot 1 to Q and saves, and a restart on the same data directory
# keeps both (platform/remap_test.cmake). Skipped without the generated code, slice or bundle.
add_executable(remap_real_test cpp/remap_real_test.cpp)
target_link_libraries(remap_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_include_directories(remap_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    remap_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(remap_real_test PRIVATE maplethief_gen)
    target_compile_definitions(remap_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(remap_real_test)
add_test(
    NAME input.remap_play
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:remap_real_test>"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/remap_play" -P
            "${PROJECT_SOURCE_DIR}/platform/remap_test.cmake")
set_tests_properties(input.remap_play PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION
                                                 "skipped:" TIMEOUT 600 RUN_SERIAL TRUE)
# MapleThief --bindings FILE --save-bindings (#70): a bad line reported by file and line, the
# rest applied, the file written back whole (platform/remap_test.cmake, MODE=flags).
add_test(
    NAME input.bindings_flags
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=flags
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/bindings_flags" -P
            "${PROJECT_SOURCE_DIR}/platform/remap_test.cmake")
set_tests_properties(input.bindings_flags PROPERTIES LABELS "real_assets" SKIP_REGULAR_EXPRESSION
                                                     "skipped:" TIMEOUT 120)

# Controller navigation in the real game (#69, ADR 0051): every menu, screen and popup done with
# a virtual Xbox 360 pad only (no taps), through the frontend's Gamepads, Actions and Focus:
# input.menu_main (the main menu, Help, About and its links, Game Center, More Games, Play),
# input.menu_terms (the Korean terms and the 2-page help), input.menu_early (A on Play at 0.05 s,
# before the engine's main menu: dropped, #129; then Play), input.menu_characters (create,
# select and delete, platform/menu_nav_test.cmake running the flows on one data directory) and
# input.menu_game (NPC chat, the popup menu and its shortcut panel, the shops) and
# input.menu_shops (#127: Mr. Wang's shop, a purchase, Charge, the cash shop's tiles, a quest
# offer refused and accepted) on the post-tutorial save its fixture input.menu_shops_save makes
# by the pad alone (the tutorial, then Elder's Advice); input.menu_pointer (#185, ADR 0107: the
# gamepad pointer, steered by the right stick's axis events, taps the Korean terms, About and
# Help, and swipes Help's two pages). Checks the uikit trace's button actions,
# the save files and guest memory. Skipped (77) without the generated code, the slice or the
# bundle. Output, saves included, to the test output directory (build/, never committed).
add_executable(menu_nav_real_test cpp/menu_nav_real_test.cpp)
target_link_libraries(menu_nav_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_include_directories(menu_nav_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    menu_nav_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(menu_nav_real_test PRIVATE maplethief_gen)
    target_compile_definitions(menu_nav_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(menu_nav_real_test)
foreach(flow main terms early characters game pointer)
    add_test(
        NAME input.menu_${flow}
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:menu_nav_real_test>" "-DFLOW=${flow}"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_${flow}" -P
                "${PROJECT_SOURCE_DIR}/platform/menu_nav_test.cmake")
    set_tests_properties(input.menu_${flow} PROPERTIES LABELS "cpp;real_assets"
                                                       SKIP_REGULAR_EXPRESSION "skipped:"
                                                       TIMEOUT 600 RUN_SERIAL TRUE)
endforeach()
add_test(
    NAME input.menu_shops_save
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:menu_nav_real_test>" "-DFLOW=shops_save"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shops_save" -P
            "${PROJECT_SOURCE_DIR}/platform/menu_nav_test.cmake")
set_tests_properties(
    input.menu_shops_save PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION "skipped:"
                                     TIMEOUT 1200 RUN_SERIAL TRUE FIXTURES_SETUP menu_shops_save)
add_test(
    NAME input.menu_shops
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:menu_nav_real_test>" "-DFLOW=shops"
            "-DSEED=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shops_save/data"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shops" -P
            "${PROJECT_SOURCE_DIR}/platform/menu_nav_test.cmake")
set_tests_properties(
    input.menu_shops PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION "skipped:"
                                TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED menu_shops_save)
# input.menu_shop_scroll (ADR 0102): Mr. Wang's Buy and Sell lists by the d-pad, item by item,
# the drawn list scrolled so the cursor's row stays in view; the amount set by the d-pad (and
# held with menu_repeat, ADR 0114) and two bought; the Sell list paged with LT (ADR 0114); the
# original's stuck cursor with the hook off. On the same save.
add_test(
    NAME input.menu_shop_scroll
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:menu_nav_real_test>" "-DFLOW=shop_scroll"
            "-DSEED=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shops_save/data"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shop_scroll" -P
            "${PROJECT_SOURCE_DIR}/platform/menu_nav_test.cmake")
set_tests_properties(
    input.menu_shop_scroll PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION "skipped:"
                                      TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED menu_shops_save)
# input.menu_popup_grids (ADR 0102): the popup menu's item grid by the d-pad, cell by cell as
# drawn (the original's down with the hooks off: six cells on), up from its top row to the tab
# bar; then the equipment grid, filled with 30 items in a copy of the same save at test time
# (equip_save), through its hook and paged with LT (ADR 0114).
add_test(
    NAME input.menu_popup_grids
    COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:menu_nav_real_test>" "-DFLOW=popup_grids"
            "-DSEED=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_shops_save/data"
            "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/menu_popup_grids" -P
            "${PROJECT_SOURCE_DIR}/platform/menu_nav_test.cmake")
set_tests_properties(
    input.menu_popup_grids PROPERTIES LABELS "cpp;real_assets" SKIP_REGULAR_EXPRESSION "skipped:"
                                      TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED menu_shops_save)

# The first maps for 15 game-minutes (#76, docs/testing/first-map-run.md): a fresh install
# played by a bot with a virtual Xbox 360 pad (tests/cpp/game_driver.h, the same driver as
# input.menu_*): the character created by Play, the intro, the tutorial maps 2.8 and 2.9,
# Henesys, the map screen, then the hunting field 2.3 until 18,000 game ticks of play. Fails on
# a trap, a crash or an exception, anything unimplemented, GL misuse, an error line in the log,
# a stalled or black screen, or unless the hero walked, jumped, attacked, defeated mobs, picked
# up drops, took hits and gained a level by fighting. A summary line and a shot per game-minute,
# the log and the data directory go to the test output directory (build/, never committed).
# Skipped (77) without the generated code, the slice or the bundle.
add_executable(play_real_test cpp/play_real_test.cpp)
target_link_libraries(play_real_test PRIVATE maplethief_frontend runtime SDL3::SDL3)
target_include_directories(play_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    play_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app")
if(TARGET maplethief_gen)
    target_link_libraries(play_real_test PRIVATE maplethief_gen)
    target_compile_definitions(play_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(play_real_test)
# The regression run (#91, ADR 0104, docs/testing/regression-replay.md): mtport.regress plays
# the recorded replay tests/regress/title_to_combat.replay (the title, Play, the intro, the first
# map, the tutorial to the snail) with play_real_test regress, twice: every expect holds (the
# first map reached, a mob defeated) and both runs give one digest of their checkpoints (the
# screen's and the audio's hashes, the game's state). Skipped (77) without the generated code or
# the game files. scripts/regress runs it with --repeat, a preset and baselines.
add_test(
    NAME play.regress
    COMMAND
        "${Python3_EXECUTABLE}" -B -m mtport.regress --binary "$<TARGET_FILE:play_real_test>"
        --replay "${PROJECT_SOURCE_DIR}/tests/regress/title_to_combat.replay" --repeat 2 --out
        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_regress")
set_tests_properties(
    play.regress PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
                            ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")
# Save states in process (#203, ADR 0105 and its addendum, docs/re/save-states.md): the replay
# with a save at a safe point on the first map (2.8) and one on the hunting field (2.9, the
# snail fight); 200 ticks on from each, then the restore in the same process and the same 200
# ticks again: the same frame and audio hashes and state at every tick. Replaces the fork
# prototype (#190).
add_test(NAME play.save_state COMMAND play_real_test save_state
                                      "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save_state")
set_tests_properties(
    play.save_state
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_REPLAY=${PROJECT_SOURCE_DIR}/tests/regress/title_to_combat.replay"
)
# The save-state census (#202, ADR 0105): the same replay to the same safe point, where
# mt_snapshot_safe holds and every live host state type has a serializer (#203: any without one
# fails by name); a full and a partial state are saved there in memory and read back.
add_test(NAME play.save_state_census COMMAND play_real_test save_state_census
                                             "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save_state_census")
set_tests_properties(
    play.save_state_census
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_REPLAY=${PROJECT_SOURCE_DIR}/tests/regress/title_to_combat.replay"
)
# Quick-save and quick-load (#204, quick_state.h): the same replay with save_states on and the
# frontend's own path: F5 through the keyboard's bindings at the first safe turn on 2.9, the state
# written to the run's data directory (states/slot0.state), 200 ticks, then F9: the same 200
# ticks' checkpoints again; the game's save files (theif*, quest*) byte for byte before and after
# each key.
add_test(NAME play.quick_state COMMAND play_real_test quick_state
                                       "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_quick_state")
set_tests_properties(
    play.quick_state
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_REPLAY=${PROJECT_SOURCE_DIR}/tests/regress/title_to_combat.replay"
)
add_test(NAME play.first_map COMMAND play_real_test first_map
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_first_map")
set_tests_properties(
    play.first_map
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
# Save, quit, relaunch (#78, ADR 0074): play.save_quit plays the tutorial on a fresh data
# directory, saves in Henesys and records the hero's state and the save files; its process
# ends. play.save_relaunch, a second process on the same data directory, checks the files are
# byte for byte as saved, loads slot 0 and compares the hero's state with the record, then
# saves again and compares the bytes. TICKS 1: no play time is asked for.
add_test(NAME play.save_quit COMMAND play_real_test save_quit
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save" 1)
add_test(NAME play.save_relaunch COMMAND play_real_test save_relaunch
                                         "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save" 1)
set_tests_properties(
    play.save_quit play.save_relaunch
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
set_tests_properties(play.save_quit PROPERTIES FIXTURES_SETUP play_save)
set_tests_properties(play.save_relaunch PROPERTIES FIXTURES_REQUIRED play_save)
# game_speed (#151, ADR 0087): the speed table patched before the boot (33 ms), then live
# changes through 65, 50, 40, 25 and game; each speed's tick rate within 1 % on the virtual
# clock, and the game's config keeps its own Speed byte.
add_test(NAME play.game_speed COMMAND play_real_test game_speed
                                      "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_game_speed" 1)
set_tests_properties(
    play.game_speed
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
# Autosave (#150, docs/re/autosave.md), with autosave = on_quit set by the test: on a new game,
# a minimise in the intro's scene and a quit in the tutorial's 2.8 save nothing
# (play.autosave_scene); slot 0 of play.save_quit's save, moved to each of the 16 maps where the
# game refuses to save, saves nothing at a quit (play.autosave_map_<area>_<map>); in Henesys,
# the popup menu passes, the "Do you want to save?" popup does not, and a quit after a potion
# and a step saves (play.autosave_quit); a second process loads that progress
# (play.autosave_reload). TICKS 1: no play time is asked for.
set(_autosave_env "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
add_test(NAME play.autosave_scene COMMAND play_real_test autosave_scene
                                          "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_autosave_scene" 1)
set_tests_properties(
    play.autosave_scene PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600
                                   RUN_SERIAL TRUE ENVIRONMENT "${_autosave_env}")
foreach(
    _map
    2.8 2.9 3.8 4.17 7.4 9.5 10.5 2.6 2.7 6.1 6.2 9.9 9.10 0.5 4.18 6.3)
    string(REPLACE "." "_" _name "${_map}")
    add_test(
        NAME play.autosave_map_${_name}
        COMMAND play_real_test autosave_map
                "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_autosave_map_${_name}" 1)
    set_tests_properties(
        play.autosave_map_${_name}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
                   FIXTURES_REQUIRED play_save
                   ENVIRONMENT
                   "${_autosave_env};MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save;MT_AUTOSAVE_MAP=${_map}")
endforeach()
add_test(NAME play.autosave_quit COMMAND play_real_test autosave_quit
                                         "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_autosave" 1)
add_test(NAME play.autosave_reload COMMAND play_real_test autosave_reload
                                           "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_autosave" 1)
set_tests_properties(play.autosave_quit play.autosave_reload
                     PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE)
set_tests_properties(
    play.autosave_quit
    PROPERTIES FIXTURES_REQUIRED play_save FIXTURES_SETUP play_autosave
               ENVIRONMENT "${_autosave_env};MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save")
# The exit to the main menu (#179): Yes on the popup menu's exit (popup 14) autosaves slot 0
# before the main menu (play.autosave_exit); with autosave = off it saves nothing, as the game
# (play.autosave_exit_off).
foreach(_mode IN ITEMS on off)
    set(_name play.autosave_exit)
    if(_mode STREQUAL "off")
        set(_name play.autosave_exit_off)
    endif()
    add_test(NAME ${_name} COMMAND play_real_test autosave_exit
                                   "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_autosave_exit_${_mode}" 1)
    set_tests_properties(
        ${_name}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
                   FIXTURES_REQUIRED play_save
                   ENVIRONMENT
                   "${_autosave_env};MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save;MT_AUTOSAVE_MODE=${_mode}")
endforeach()
# The reload is a relaunch on play.autosave_quit's data directory: no MT_FROM_SAVE.
set_tests_properties(play.autosave_reload PROPERTIES FIXTURES_REQUIRED play_autosave ENVIRONMENT
                                                                               "${_autosave_env}")
# The scene skip (#174, scene_skip.h, ADR 0096, docs/re/qol.md#cutscene-skip): a scene watched
# (A through its speech boxes) and the same scene with skip_intros = on (no input) leave the same
# guest state (map, hero, items, quests, mobs, the scene's end). The skipped run compares with the
# watched run's scene_state.txt (MT_SKIP_WATCHED). The intro from a new game; Balog's entrance
# (scene 14) from play.save_quit's save moved to 10.5, as play.autosave_map_10_5.
foreach(_pair IN ITEMS intro boss)
    if(_pair STREQUAL "intro")
        set(_prefix skip_intros)
        set(_env "${_autosave_env}")
        set(_needs)
    else()
        set(_prefix skip_intros_boss)
        set(_env "${_autosave_env};MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save;MT_AUTOSAVE_MAP=10.5")
        set(_needs play_save)
    endif()
    add_test(NAME play.${_prefix}_watched
             COMMAND play_real_test ${_prefix}_watched
                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_${_prefix}_watched" 1)
    add_test(NAME play.${_prefix}_skipped
             COMMAND play_real_test ${_prefix}_skipped
                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_${_prefix}_skipped" 1)
    set_tests_properties(
        play.${_prefix}_watched play.${_prefix}_skipped
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE)
    set_tests_properties(play.${_prefix}_watched PROPERTIES FIXTURES_SETUP play_${_prefix}
                                                            ENVIRONMENT "${_env}")
    if(_needs)
        set_tests_properties(play.${_prefix}_watched PROPERTIES FIXTURES_REQUIRED "${_needs}")
    endif()
    set_tests_properties(
        play.${_prefix}_skipped
        PROPERTIES FIXTURES_REQUIRED "play_${_prefix};${_needs}"
                   ENVIRONMENT
                   "${_env};MT_SKIP_WATCHED=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_${_prefix}_watched")
endforeach()
# Screen shake (#158, docs/re/screen-shake.md): the intro's three shakes (amplitude up to 15)
# drawn at 100, 50 and 0 percent; the offsets stay within the scaled amplitude and the game's
# rand() state after the intro is the same whatever the percent (printed by each run).
foreach(_pct 100 50 0)
    add_test(NAME play.screen_shake_${_pct}
             COMMAND play_real_test screen_shake
                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_screen_shake_${_pct}" 1)
    set_tests_properties(
        play.screen_shake_${_pct}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
                   ENVIRONMENT "${_autosave_env};MT_SCREEN_SHAKE=${_pct}")
endforeach()
# Flashes (#186, docs/re/flashes.md): the tutorial's first level-up, measured from the level-up
# to 60 ticks after its dialog closes. play.flashes_original runs without the hooks and has the
# full-screen white and black frames; at 100 percent every frame is byte for byte the original's,
# at 50 and 0 there is no full-screen white or black frame. The ticks and the rand() state are
# the original's in every run.
add_test(NAME play.flashes_original
         COMMAND play_real_test flashes "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_flashes_original" 1)
set_tests_properties(
    play.flashes_original
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               FIXTURES_SETUP play_flashes ENVIRONMENT "${_autosave_env}")
foreach(_pct 100 50 0)
    add_test(NAME play.flashes_${_pct}
             COMMAND play_real_test flashes "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_flashes_${_pct}" 1)
    set_tests_properties(
        play.flashes_${_pct}
        PROPERTIES
            LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
            FIXTURES_REQUIRED play_flashes
            ENVIRONMENT
            "${_autosave_env};MT_FLASHES=${_pct};MT_FLASHES_BASE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_flashes_original"
    )
endforeach()
# A config with speed byte 3 (#78): the game boots on it and ticks every 40 ms (speed 2), not
# on every poll (the unchecked table read gave -1); the file is not changed.
add_test(NAME play.config_speed COMMAND play_real_test config_speed
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_config_speed" 1)
set_tests_properties(
    play.config_speed
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The offline cash shop (#156, ADR 0072): play.cash_shop_meso buys EXP Explosion 40% and a
# POW! Package for meso after the tutorial (the meso down by the price line's quote, the grant,
# the receipt lists empty again, theif0 saved), is refused SP Marble for lack of meso, and sees
# the revive dearer at the next fall; play.cash_shop_off makes the same taps with cash_shop = off
# (ADR 0034: every payment cancelled, nothing changes). TICKS 1: no play time is asked for.
foreach(mode cash_shop_meso cash_shop_off)
    add_test(NAME play.${mode} COMMAND play_real_test ${mode}
                                       "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_${mode}" 1)
    set_tests_properties(
        play.${mode}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
                   ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
endforeach()
# The popup confirm guard (#147, menu_input.h): after the tutorial, the pad's A mashed through a
# fall answers the death popup with confirm_guard off and leaves it open with it on, as does a
# button held from the field; a level-up on 2.3 opens no popup. TICKS 1: no play time asked for.
add_test(NAME play.confirm_guard COMMAND play_real_test confirm_guard
                                         "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_confirm_guard" 1)
set_tests_properties(
    play.confirm_guard
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# Quality of life (#90, assists.h, ADR 0092), each from a new game on the tutorial's map 2.8:
# auto_potion off drinks nothing at a fifth of max HP, = 50 drinks quick slot 1's Red Potion
# once and none in the popup menu (play.auto_potion); X held 3 s attacks once with
# turbo_attack off and again and again with it on, never in the popup menu
# (play.turbo_attack); the title menu animates with ui_animations = normal and fast and not at
# all with instant (play.ui_animations_<value>). TICKS 1: no play time is asked for.
foreach(_mode IN ITEMS auto_potion turbo_attack ui_animations_normal ui_animations_fast
                       ui_animations_instant)
    add_test(NAME play.${_mode} COMMAND play_real_test ${_mode}
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_${_mode}" 1)
    set_tests_properties(
        play.${_mode}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
                   ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
endforeach()
# Auto-loot (#175, assists.h, docs/re/qol.md#auto-loot): after the tutorial, on 2.3, every pick-up
# is in contact with auto_loot off; turned on, a drop out of contact is picked up with the hero
# standing, the hunt picks drops up across a gap no wider than the reach, and with both
# inventories full the drops in reach are refused and stay. TICKS 1: no play time is asked for.
add_test(NAME play.auto_loot COMMAND play_real_test auto_loot
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_auto_loot" 1)
set_tests_properties(
    play.auto_loot
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# Fast text (#173, docs/re/qol.md): through the intro with A on the first tick of each box, every
# NPC dialogue page and speech box shows its whole text at once, never changes while it shows,
# and the next box follows the press within the input's latency (play.chat_text).
add_test(NAME play.chat_text COMMAND play_real_test chat_text
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chat_text" 1)
set_tests_properties(
    play.chat_text
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The HUD extras (#191, ADR 0109, docs/re/hud-extras.md): first_map's route to 2.3 with
# exp_percent = on and a hunt until a Red Potion and a level: the figure equal to the hero's
# fields and hero.dat in every field turn; the game's own mob HP bar after each hit and its own
# pick-up lines, the evidence for the two overlays the research dropped (play.hud_extras).
add_test(NAME play.hud_extras COMMAND play_real_test hud_extras
                                      "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_hud_extras")
set_tests_properties(
    play.hud_extras
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The game's rules traced (#214, docs/re/mechanics.md): a hunt on 2.3 until 60 kills, each
# mob HP loss in the basic attack's damage range, each hero HP loss in a mob's contact range,
# each kill's EXP and drops as mob.dat and the drop rules give them (play.mechanics).
add_test(NAME play.mechanics COMMAND play_real_test mechanics
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_mechanics")
set_tests_properties(
    play.mechanics
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1800 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# Text packs (#189, ADR 0108): a pack made at run time in the test output directory from your
# own copy (Hénésys in the dialogue, Pötion in item_etc), through the intro and the item tab:
# whole UTF-8 box lines, the names drawn, no EUC-KR misreading (play.text_pack).
add_test(NAME play.text_pack COMMAND play_real_test text_pack
                                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_text_pack" 1)
set_tests_properties(
    play.text_pack
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The minimap (#176, minimap.h, ADR 0098): first_map's route to 2.3 with minimap = overlay and
# 600 ticks of the hunt there; the guest's grid equal to map/23.att, the hero's dot following
# the hero (play.minimap). Shots (minimap_NN.png) stay in the test output directory.
add_test(NAME play.minimap COMMAND play_real_test minimap
                                   "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_minimap")
set_tests_properties(
    play.minimap
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
# The minimap's corner (ADR 0098's addendum): the same route with widescreen (568 points), the
# key pad at the view's edges and minimap = overlay; the box at the visible view's top left in
# every wide field turn, the game pad composited alone (the moved compass, Bag, Cash, the keys)
# never under it, and taps on the moved compass and on the box open the map screen
# (play.minimap_wide). Shots (minimap_wide_NN.png) stay in the test output directory.
add_test(NAME play.minimap_wide COMMAND play_real_test minimap_wide
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_minimap_wide")
set_tests_properties(
    play.minimap_wide
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The key pad's button labels (ADR 0101, hud_labels.h): widescreen_pad's way to Henesys with
# widescreen on; with the key pad at the view's edges, narrow and a custom layout, each label's
# key is where the compositor draws that key's view, and every label (pad and keyboard) sits a
# point or two beside it, inside the picture, over no key (play.hud_labels). Shots
# (labels_*.png) stay in the test output directory.
add_test(NAME play.hud_labels COMMAND play_real_test hud_labels
                                      "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_hud_labels")
set_tests_properties(
    play.hud_labels
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# Controller extras (#172, assists.h, ADR 0094), from a new game on map 2.8: L3 latches nothing
# with auto_run off; with it on, the latched walk crosses the map with nothing held, ends on the
# other direction and in the popup menu; hold_toggle = walk walks from one tap to the next.
add_test(NAME play.auto_run COMMAND play_real_test auto_run
                                    "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_auto_run" 1)
set_tests_properties(
    play.auto_run
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               ENVIRONMENT "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# The settings menu's Game options and Import saves (#89, ADR 0076): from play.save_quit's save,
# the menu's import is allowed on the main menu (no key pad panel) and refused in the field, and
# GameOptionsOpener opens the popup menu on its option tab (CDataMgr +0xB0 = 5) in the field.
add_test(NAME play.game_options COMMAND play_real_test game_options
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_game_options" 1)
set_tests_properties(
    play.game_options
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 1200 RUN_SERIAL TRUE
               FIXTURES_REQUIRED play_save
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save")

# Chapter 1's quests (#77, docs/testing/chapter-1-quests.md): the same bot plays chapter 1's
# quests in parts, each going on from the save the part before it made (MT_FROM_SAVE: that
# part's output directory, a CTest fixture), so a part can be run again on its own once the
# parts before it have run. Each part checks the quest states it completes in guest memory.
# Part d (Perion's main quests) joins the list with #160.
set(_chapter_1_parts a b c)
set(_previous "")
foreach(part IN LISTS _chapter_1_parts)
    add_test(NAME play.chapter_1_${part}
             COMMAND play_real_test chapter_1_${part}
                     "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_${part}")
    set(_env "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")
    if(_previous)
        list(APPEND _env "MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_${_previous}")
        set_tests_properties(play.chapter_1_${part} PROPERTIES FIXTURES_REQUIRED
                                                               play_chapter_1_${_previous})
    endif()
    set_tests_properties(
        play.chapter_1_${part}
        PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
                   FIXTURES_SETUP play_chapter_1_${part} ENVIRONMENT "${_env}")
    set(_previous ${part})
endforeach()
# The popup menu's skill, quest and option tabs by the d-pad (#164, prompts.h): from part c's
# save, each list shows and the port's frame sits on its cursor, in each tab's window shot; A
# opens each quest's page and B closes it (#167, the quest page fix).
add_test(NAME play.popup_menu_tabs
         COMMAND play_real_test popup_menu_tabs "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_popup_menu_tabs")
set_tests_properties(
    play.popup_menu_tabs
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               FIXTURES_REQUIRED play_chapter_1_c
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_c")
# The item and equipment tabs opened by the d-pad's down: from part c's save, each list shows
# (the focus's touch on the tab, focus.h), each in a shot (down_items.png, down_equipment.png).
add_test(NAME play.popup_menu_down
         COMMAND play_real_test popup_menu_down "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_popup_menu_down")
set_tests_properties(
    play.popup_menu_down
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               FIXTURES_REQUIRED play_chapter_1_c
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_c")
# LB / RB through the popup menu's tabs and back (focus.h): from part a's save, each tab opens
# with its list shown, the item and equipment tabs by the focus's touch (shoulder_*.png).
add_test(NAME play.popup_menu_shoulders
         COMMAND play_real_test popup_menu_shoulders
                 "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_popup_menu_shoulders")
set_tests_properties(
    play.popup_menu_shoulders
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               FIXTURES_REQUIRED play_chapter_1_a
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_a")
# The skill list scrolled by the d-pad (#166, prompts.h): part c's save made a tier-4 Assassin
# (13 skills), down to the last skill and back up, the cursor's row framed on screen at each
# step (window shots scroll_*.png); a touch drag moves the list as with the hooks off.
add_test(NAME play.popup_menu_scroll
         COMMAND play_real_test popup_menu_scroll
                 "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_popup_menu_scroll")
set_tests_properties(
    play.popup_menu_scroll
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 600 RUN_SERIAL TRUE
               FIXTURES_REQUIRED play_chapter_1_c
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_FROM_SAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_chapter_1_c")
# Texture packs (#153, ADR 0081): play.texture_dump plays first_map's flow (TICKS 1200: a
# game-minute on the field) with --dump-textures' dump; play.texture_pack_build upscales it 2x
# with python3 -m mtport.texpack; play.texture_pack plays the same flow (the same uploads, on
# the virtual clock) with that pack and fails unless it replaced 100% of the uploads and of the
# UIKit images loaded by name. Dump and pack are game art: they stay in the test output
# directory (build/, never committed).
add_test(NAME play.texture_dump COMMAND play_real_test texture_dump
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_texture_dump" 1200)
add_test(
    NAME play.texture_pack_build
    COMMAND
        "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m mtport.texpack
        build "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_texture_dump/dump"
        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_texture_pack_src" --scale 2 --skip-without-dump)
add_test(NAME play.texture_pack COMMAND play_real_test texture_pack
                                        "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_texture_pack" 1200)
set_tests_properties(
    play.texture_dump play.texture_pack
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_TEXTURE_PACK=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_texture_pack_src")
set_tests_properties(
    play.texture_pack_build
    PROPERTIES LABELS "real_assets" TIMEOUT 3600 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
               ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")
set_tests_properties(play.texture_dump PROPERTIES FIXTURES_SETUP play_texture_dump)
set_tests_properties(play.texture_pack_build PROPERTIES FIXTURES_REQUIRED play_texture_dump
                                                        FIXTURES_SETUP play_texture_pack)
set_tests_properties(play.texture_pack PROPERTIES FIXTURES_REQUIRED play_texture_pack)
# Widescreen (#86, ADR 0090): play.widescreen_off plays first_map's flow (TICKS 2400) and
# writes the game mode, the hero's and every mob's state at each game tick; play.widescreen_on
# plays it again with the 568-point GL layer (the field wide on Henesys 2.0 and 2.3, 3:2 on the
# tutorial's 30-tile maps 2.8 and 2.9) and fails unless every line equals the off run's.
add_test(NAME play.widescreen_off COMMAND play_real_test widescreen_off
                                          "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_off")
add_test(NAME play.widescreen_on COMMAND play_real_test widescreen_on
                                         "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_on")
set_tests_properties(
    play.widescreen_off play.widescreen_on
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_WIDESCREEN_BASE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_off")
set_tests_properties(play.widescreen_off PROPERTIES FIXTURES_SETUP play_widescreen)
set_tests_properties(play.widescreen_on PROPERTIES FIXTURES_REQUIRED play_widescreen)
# Widescreen with the touch key pad (#168): play.widescreen_pad_off holds the key pad's left,
# attack and right keys by touch on the field 2.3 and writes the hero at each tick;
# play.widescreen_pad_on does it with the field 568 points wide and the key pad's groups at the
# view's edges, touching where they are drawn now (partly in the wings), and fails unless every
# tick equals the off run's. It also writes the wide window pictures with the pad (pad_*.png).
add_test(NAME play.widescreen_pad_off COMMAND play_real_test widescreen_pad_off
                                              "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_pad_off")
add_test(NAME play.widescreen_pad_on COMMAND play_real_test widescreen_pad_on
                                             "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_pad_on")
set_tests_properties(
    play.widescreen_pad_off play.widescreen_pad_on
    PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
               ENVIRONMENT
               "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en;MT_WIDESCREEN_PAD_BASE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_pad_off")
set_tests_properties(play.widescreen_pad_off PROPERTIES FIXTURES_SETUP play_widescreen_pad)
set_tests_properties(play.widescreen_pad_on PROPERTIES FIXTURES_REQUIRED play_widescreen_pad)
# Widescreen's wings through the present pass (#170): play.widescreen_filters walks into 2.3 with
# the field 568 points wide and the key pad at the edges, and draws the real frame through the
# Presenter on an offscreen SDL GPU renderer with each filter, the colour-blind filter off and on:
# check_readback over the whole wide picture, the window byte for byte the frame drawn as one
# picture (filters_*.png). The real video driver: SDL has no GPU device under the dummy one;
# skipped without a GPU device.
add_test(NAME play.widescreen_filters COMMAND play_real_test widescreen_filters
                                              "${MAPLETHIEF_TEST_OUTPUT_DIR}/play_widescreen_filters")
set_tests_properties(
    play.widescreen_filters
    PROPERTIES LABELS "cpp;real_assets;gpu" SKIP_RETURN_CODE 77 TIMEOUT 3600 RUN_SERIAL TRUE
               SKIP_REGULAR_EXPRESSION "widescreen_filters: skipped"
               ENVIRONMENT "SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_LANGUAGE=en")

# M6's exit criteria through the game's own sound code (#124, ADR 0045, 0046): the real game
# boots to its main menu, then the test calls CMSoundPlayer::Play for every id 0-43
# (audio.game_sounds: each effect's exact length, each track's loop) and CStateGame::PlayMapBGM
# for every shipped map (audio.game_map_music: against docs/re/audio.md's generated maps
# table), pulling the mix itself; audio.game_gapless_music plays every track with the gapless
# option on (#125, ADR 0062: at most one MPEG frame of silence at each loop point). Skipped (77)
# without the generated code, the slice or the bundle.
add_executable(game_audio_real_test cpp/game_audio_real_test.cpp)
target_link_libraries(game_audio_real_test PRIVATE runtime)
if(APPLE) # Apple's decode of the MP3s, the loop points' reference (apple_decode.h)
    target_link_libraries(game_audio_real_test PRIVATE "-framework AudioToolbox"
                                                       "-framework CoreFoundation")
endif()
target_include_directories(game_audio_real_test PRIVATE "${PROJECT_SOURCE_DIR}/runtime/src")
target_compile_definitions(
    game_audio_real_test
    PRIVATE MT_ARMV7_SLICE="${PROJECT_SOURCE_DIR}/work/slices/MapleThief.armv7"
            MT_BUNDLE_DIR="${PROJECT_SOURCE_DIR}/work/ipa/Payload/MapleThief.app"
            MT_AUDIO_DOC="${PROJECT_SOURCE_DIR}/docs/re/audio.md")
if(TARGET maplethief_gen)
    target_link_libraries(game_audio_real_test PRIVATE maplethief_gen)
    target_compile_definitions(game_audio_real_test PRIVATE MT_HAVE_GENERATED_CODE=1)
endif()
maplethief_target_defaults(game_audio_real_test)
add_test(NAME audio.game_sounds COMMAND game_audio_real_test sounds)
add_test(NAME audio.game_map_music COMMAND game_audio_real_test maps)
add_test(NAME audio.game_gapless_music COMMAND game_audio_real_test gapless)
set_tests_properties(audio.game_sounds audio.game_map_music audio.game_gapless_music
                     PROPERTIES LABELS "cpp;real_assets" SKIP_RETURN_CODE 77 TIMEOUT 300)

# The frontend running the real game (#58, ADR 0042): MapleThief, linked with maplethief_gen,
# boots the game from work/ipa headless (SDL's dummy video and audio drivers), taps Play on the
# main menu at 3 s (a scripted tap) and runs for 150 window frames, into the first map. The last
# frame is read
# back and must show the composited screen, not black (exit status 3 otherwise). The read-backs
# (the first frame, the first that is not black, the first with a GL frame that is not black,
# the last) go to the test output directory (build/, never committed), and so does the data
# directory, emptied first so that Play starts a new game. Only with generated code, so CI never
# registers it.
if(TARGET maplethief_gen)
    set(_game_boot_out "${MAPLETHIEF_TEST_OUTPUT_DIR}/game_boot")
    add_test(NAME platform.game_boot_clean COMMAND "${CMAKE_COMMAND}" -E rm -rf
                                                   "${_game_boot_out}")
    set_tests_properties(platform.game_boot_clean PROPERTIES FIXTURES_SETUP game_boot_dir)
    add_test(NAME platform.game_boot COMMAND MapleThief --frames 150 --tap 3,106,165 --capture
                                             "${_game_boot_out}")
    set_tests_properties(
        platform.game_boot
        PROPERTIES LABELS "real_assets" TIMEOUT 120 RUN_SERIAL TRUE FIXTURES_REQUIRED game_boot_dir
                   ENVIRONMENT
                   "SDL_VIDEO_DRIVER=dummy;SDL_AUDIO_DRIVER=dummy;MAPLETHIEF_DATA_DIR=${_game_boot_out}/data"
    )

    # The deterministic audio capture (#61, ADR 0045): the same scripted --fixed-step run twice,
    # each with --audio-capture, must write the same WAV byte for byte, and a third with
    # --audio-mono on one whose channels are equal in every frame (#195)
    # (platform/audio_capture_test.cmake). The captures and data directories go to the test
    # output directory (build/, never committed).
    add_test(
        NAME platform.audio_capture
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/audio_capture"
                "-DPYTHON=${Python3_EXECUTABLE}" -P
                "${PROJECT_SOURCE_DIR}/platform/audio_capture_test.cmake")
    set_tests_properties(platform.audio_capture PROPERTIES LABELS "real_assets" TIMEOUT 300
                                                           RUN_SERIAL TRUE)

    # Audio packs (#188, ADR 0106): the same run without a pack, with a pack for a track it
    # never plays and with a corrupt file give the same capture; a generated tone in place of
    # the main menu's track plays (platform/audio_pack_test.cmake). The tones are written at
    # run time into the test output directory.
    add_test(
        NAME platform.audio_pack
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/audio_pack"
                "-DPYTHON=${Python3_EXECUTABLE}" -P
                "${PROJECT_SOURCE_DIR}/platform/audio_pack_test.cmake")
    set_tests_properties(platform.audio_pack PROPERTIES LABELS "real_assets" TIMEOUT 300
                                                        RUN_SERIAL TRUE)

    # --seconds S (#124, the soak's time limit): the game ends by itself at 2 s of virtual time
    # (--fixed-step) and after 3 s of real time with the device stream on SDL's dummy driver,
    # logging the audio output's numbers (platform/seconds_test.cmake).
    add_test(
        NAME platform.game_seconds
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=game
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_seconds" -P
                "${PROJECT_SOURCE_DIR}/platform/seconds_test.cmake")
    set_tests_properties(platform.game_seconds PROPERTIES LABELS "real_assets" TIMEOUT 120
                                                          RUN_SERIAL TRUE)

    # Game speed and fast-forward (#151, ADR 0087): real-time runs on the dummy drivers with
    # fast_forward x2 and x3 held (paced clock) and x2 with pacing = wall (launch clock), and
    # game_speed 25: the switch is logged and the exit clock line's tick rate is near the
    # option's (platform/game_speed_test.cmake; the 1 % is play.game_speed's).
    add_test(
        NAME platform.game_speed
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=game
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_speed" -P
                "${PROJECT_SOURCE_DIR}/platform/game_speed_test.cmake")
    set_tests_properties(platform.game_speed PROPERTIES LABELS "real_assets" TIMEOUT 300
                                                        RUN_SERIAL TRUE)

    # Frame telemetry and the frame-time overlay (#137, ADR 0071): a real-time run's exit log
    # has the tick intervals, the split and the pacing, and --perf-csv a row per window frame;
    # a scripted fixed-step run writes the same shot, read-backs and WAV with --perf-overlay on
    # and --perf-csv as without (platform/perf_test.cmake).
    add_test(
        NAME platform.perf_telemetry
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/perf_telemetry" -P
                "${PROJECT_SOURCE_DIR}/platform/perf_test.cmake")
    set_tests_properties(platform.perf_telemetry PROPERTIES LABELS "real_assets" TIMEOUT 300
                                                            RUN_SERIAL TRUE)

    # The app lifecycle (#128, ADR 0063): a scripted minimise and restore (fixed-step, twice:
    # the same capture) shows Clet_Pause (the music stops, silence and no present while
    # minimised), Clet_Resume (the music again), a tap on Play after it and the quit's
    # applicationWillTerminate:; SDL's iOS home-gesture sequence and a real-time minimise send
    # each message once, in order (platform/lifecycle_test.cmake). Logs, captures and data
    # directories go to the test output directory (build/, never committed).
    add_test(
        NAME platform.game_lifecycle
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=game
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_lifecycle" -P
                "${PROJECT_SOURCE_DIR}/platform/lifecycle_test.cmake")
    set_tests_properties(platform.game_lifecycle PROPERTIES LABELS "real_assets" TIMEOUT 600
                                                            RUN_SERIAL TRUE)

    # Autosave in the frontend (#150, docs/re/autosave.md): with --autosave on_quit, a scripted
    # minimise and quit in Henesys (play.save_quit's save) each save slot 0 before their
    # lifecycle message; without it nothing is saved; at the main menu and in the intro's scene
    # the guard is logged and nothing is saved (platform/autosave_test.cmake). Data directories
    # and logs go to the test output directory (build/, never committed).
    add_test(
        NAME platform.game_autosave
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DSAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_autosave" -P
                "${PROJECT_SOURCE_DIR}/platform/autosave_test.cmake")
    set_tests_properties(
        platform.game_autosave
        PROPERTIES LABELS "real_assets" TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED play_save
                   SKIP_REGULAR_EXPRESSION "skipped:")

    # Restoring a backup from the settings menu (#192, save_menu.h): save, play on, save again,
    # restore the first backup through the menu's Backups list at the title (refused in the
    # field), reload: the hero is back at the first save's level, map and place
    # (platform/restore_backup_test.cmake). Data directories and logs go to the test output
    # directory (build/, never committed).
    add_test(
        NAME platform.game_restore_backup
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DSAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_restore_backup" -P
                "${PROJECT_SOURCE_DIR}/platform/restore_backup_test.cmake")
    set_tests_properties(
        platform.game_restore_backup
        PROPERTIES LABELS "real_assets" TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED play_save
                   SKIP_REGULAR_EXPRESSION "skipped:")

    # Frame interpolation (#87, ADR 0091): the same 60 s fixed-step walk in Henesys (play.save_quit's
    # save) with frame_rate = original and smooth has the same tick count and the same audio
    # capture, and smooth interpolated most ticks (platform/frame_rate_test.cmake). Logs, WAVs and
    # data directories go to the test output directory (build/, never committed).
    add_test(
        NAME platform.frame_rate
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>"
                "-DSAVE=${MAPLETHIEF_TEST_OUTPUT_DIR}/play_save"
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/frame_rate" -P
                "${PROJECT_SOURCE_DIR}/platform/frame_rate_test.cmake")
    set_tests_properties(
        platform.frame_rate
        PROPERTIES LABELS "real_assets" TIMEOUT 600 RUN_SERIAL TRUE FIXTURES_REQUIRED play_save
                   SKIP_REGULAR_EXPRESSION "skipped:")

    # The desktop conveniences (#145, ADR 0077): pause_unfocused pauses on a scripted focus loss
    # (--unfocus) as a minimise does and the default does not; language = ko is
    # MAPLETHIEF_LANGUAGE=ko (the same shot) and the variable overrides it; fullscreen and vsync
    # at start; no scripted run writes window.ini or a screenshot
    # (platform/desktop_test.cmake). Logs, shots and data directories go to the test output
    # directory (build/, never committed).
    add_test(
        NAME platform.game_desktop
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=game
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/game_desktop" -P
                "${PROJECT_SOURCE_DIR}/platform/desktop_test.cmake")
    set_tests_properties(platform.game_desktop PROPERTIES LABELS "real_assets" TIMEOUT 600
                                                          RUN_SERIAL TRUE)

    # Mouse clicks into the game (#65, ADR 0047): --tap clicks (the left mouse button through
    # the frontend's Input, as a real click) through the device goldens' fresh run up to the key
    # pad's cash button; the uikit trace must show -[MainMenuView clickPlay:] and the key pad's
    # clickCashButton: / releaseCashButton: (platform/input_test.cmake). Log and data directory
    # go to the test output directory (build/, never committed).
    add_test(
        NAME platform.mouse_clicks
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=game
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/mouse_clicks" -P
                "${PROJECT_SOURCE_DIR}/platform/input_test.cmake")
    set_tests_properties(platform.mouse_clicks PROPERTIES LABELS "real_assets" TIMEOUT 600
                                                          RUN_SERIAL TRUE)

    # A tap on Play before the engine's main menu (#122, #129): --tap 0.1,106,165 under
    # --fixed-step 1 still ends the game with the device's exception (exit status 5); the
    # controller focus's guard leaves touches alone (platform/input_test.cmake).
    add_test(
        NAME platform.early_tap
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=early_tap
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/early_tap" -P
                "${PROJECT_SOURCE_DIR}/platform/input_test.cmake")
    set_tests_properties(platform.early_tap PROPERTIES LABELS "real_assets" TIMEOUT 120
                                                       RUN_SERIAL TRUE)
    # The same tap with --fix-original-bugs on (#155): the early Play guard hook drops it, and
    # Play at 4.5 s starts a new game (platform/input_test.cmake).
    add_test(
        NAME platform.early_tap_fixed
        COMMAND "${CMAKE_COMMAND}" "-DPROGRAM=$<TARGET_FILE:MapleThief>" -DMODE=early_tap_fixed
                "-DOUT=${MAPLETHIEF_TEST_OUTPUT_DIR}/early_tap_fixed" -P
                "${PROJECT_SOURCE_DIR}/platform/input_test.cmake")
    set_tests_properties(platform.early_tap_fixed PROPERTIES LABELS "real_assets" TIMEOUT 180
                                                             RUN_SERIAL TRUE)

    # The title for a minute (#74): mtport.titlerun drives MapleThief --fixed-step 16.667
    # --seconds 60 headless from a fresh data directory, and fails unless the run ends cleanly
    # at 60 s with no crash, trap or GL misuse, the unimplemented report reads "none", the
    # frame loop presents GL frames for the whole minute, the slide-in's shots all differ and
    # the still title's shots (2.5 s to 60 s) are identical and not black. Log, report, shots
    # and data directory go to the test output directory (build/, never committed).
    find_package(Python3 3.10 REQUIRED COMPONENTS Interpreter) # the top level's, found later
    add_test(
        NAME platform.title_60s
        COMMAND
            "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m
            mtport.titlerun soak --binary "$<TARGET_FILE:MapleThief>" --out
            "${MAPLETHIEF_TEST_OUTPUT_DIR}/title_60s")
    set_tests_properties(
        platform.title_60s
        PROPERTIES LABELS "real_assets" TIMEOUT 900 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
                   ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")

    # The menus before the game (#75): mtport.menuflows clicks MapleThief --fixed-step headless
    # through one flow each (help and about; the Korean terms and the help swipe; the online
    # buttons; the online buttons hidden and the local leaderboard (#154); a new game; New,
    # choose + Start and Delete on the character select) and checks the uikit trace (actions,
    # openURL: refused, the leaderboard dismissed), the save files in the data directory and the
    # shots by hash (a closed screen leaves the menu as it was; the loading screen shows). Skipped (77) when the game cannot start. Logs, shots and data
    # directories go to the test output directory (build/, never committed).
    foreach(_flow help_about terms online offline_ui new_game create select delete)
        add_test(
            NAME menus.${_flow}
            COMMAND
                "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m
                mtport.menuflows --binary "$<TARGET_FILE:MapleThief>" --flow ${_flow} --out
                "${MAPLETHIEF_TEST_OUTPUT_DIR}/menus")
        set_tests_properties(
            menus.${_flow}
            PROPERTIES LABELS "real_assets" TIMEOUT 900 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
                       ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")
    endforeach()

    # The device golden screens (#60, ADR 0044): mtport.devicegolden drives MapleThief
    # --fixed-step headless through the runs of tests/golden/device_screens.json (fresh install,
    # then a second launch on its saves) and compares each screen with our recorded render
    # (work/golden/ours/, exact; MAPLETHIEF_UPDATE_GOLDEN=1 records it) and with the device
    # screenshot (work/golden/device/, normalised, masks and tolerance from the list). A missing
    # golden skips its case; the test is skipped (77) when the game cannot start or every case
    # was skipped. Shots, diffs and the runs' data directories go to the test output directory.
    cmake_path(GET MAPLETHIEF_GEN_DIR PARENT_PATH _device_golden_work)
    cmake_path(ABSOLUTE_PATH _device_golden_work BASE_DIRECTORY "${PROJECT_SOURCE_DIR}" NORMALIZE)
    add_test(
        NAME platform.device_golden
        COMMAND
            "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m
            mtport.devicegolden --binary "$<TARGET_FILE:MapleThief>" --screens
            "${PROJECT_SOURCE_DIR}/tests/golden/device_screens.json" --golden-dir
            "${_device_golden_work}/golden" --out "${MAPLETHIEF_TEST_OUTPUT_DIR}/device_golden")
    set_tests_properties(
        platform.device_golden
        PROPERTIES LABELS "real_assets" TIMEOUT 1800 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
                   ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")

    # The key pad's hiding through the same capture path (#71, ADR 0052): mtport.keypadhide runs
    # the device golden list's fresh run with --hide-keypad never and always (--shot, --shot-gl):
    # the GL frames are identical, the screens differ only inside the key pad's rects, the shown
    # run matches the device golden's render. Skipped (77) when the game cannot start.
    add_test(
        NAME platform.keypad_hide
        COMMAND
            "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m
            mtport.keypadhide --binary "$<TARGET_FILE:MapleThief>" --screens
            "${PROJECT_SOURCE_DIR}/tests/golden/device_screens.json" --golden-dir
            "${_device_golden_work}/golden" --out
            "${MAPLETHIEF_TEST_OUTPUT_DIR}/platform_keypad_hide")
    set_tests_properties(
        platform.keypad_hide
        PROPERTIES LABELS "real_assets" TIMEOUT 1800 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
                   ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")

    # The GPU backend against the software rasterizer on the game's frames (#120, ADR 0060):
    # mtport.gpucompare runs the fresh run with --gfx-backend software and gpu, at render scale 1
    # and 4 (--shot, --shot-gl): every GL frame (and the screens at 1x) within ADR 0060's
    # tolerance, and the GPU time per frame at 4x under 2 ms. The GPU runs open a window (SDL GPU
    # has no device under the dummy video driver). Skipped (77) without the game or a GPU.
    add_test(
        NAME platform.gpu_compare
        COMMAND
            "${Python3_EXECUTABLE}" -X "pycache_prefix=${CMAKE_BINARY_DIR}/pycache" -m
            mtport.gpucompare --binary "$<TARGET_FILE:MapleThief>" --screens
            "${PROJECT_SOURCE_DIR}/tests/golden/device_screens.json" --out
            "${MAPLETHIEF_TEST_OUTPUT_DIR}/gpu_compare")
    set_tests_properties(
        platform.gpu_compare
        PROPERTIES LABELS "real_assets;gpu" TIMEOUT 3600 RUN_SERIAL TRUE SKIP_RETURN_CODE 77
                   ENVIRONMENT "PYTHONPATH=${PROJECT_SOURCE_DIR}/tools")
endif()

add_subdirectory(golden)
