cmake_minimum_required(VERSION 3.21)

# --- Version (docs/releasing.md) ------------------------------------------------------------------
# Decided in one place, VERSION.txt: a Semantic Versioning number, "0.1.0", or "0.1.0-rc.1" for a
# pre-release. As in CNA, the numeric part goes to project(VERSION) and the pre-release identifier
# sits beside it; Android's versionName (android/app/build.gradle) and
# tools/release/make_release.sh read the same file. GALAXY_EGGBERT_VERSION_STRING is the one
# spelling the build uses (the generated GalaxyEggbert/Version.hpp, the package's file name).
file(STRINGS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt" GALAXY_EGGBERT_VERSION_STRING LIMIT_COUNT 1)
string(STRIP "${GALAXY_EGGBERT_VERSION_STRING}" GALAXY_EGGBERT_VERSION_STRING)
if(NOT GALAXY_EGGBERT_VERSION_STRING MATCHES "^([0-9]+\\.[0-9]+\\.[0-9]+)(-([0-9A-Za-z.-]+))?$")
    message(FATAL_ERROR "VERSION.txt must hold a Semantic Versioning number such as 0.1.0 or "
                        "0.1.0-rc.1, not \"${GALAXY_EGGBERT_VERSION_STRING}\"")
endif()
set(_ge_version_numeric "${CMAKE_MATCH_1}")
set(GALAXY_EGGBERT_VERSION_PRERELEASE "${CMAKE_MATCH_3}")
set_property(DIRECTORY APPEND PROPERTY CMAKE_CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt")

project(GalaxyEggbert VERSION ${_ge_version_numeric} LANGUAGES C CXX)
message(STATUS "Galaxy Eggbert: version ${GALAXY_EGGBERT_VERSION_STRING}")
set(GALAXY_EGGBERT_GENERATED_INCLUDE_DIR "${CMAKE_CURRENT_BINARY_DIR}/generated/include")
configure_file("${CMAKE_CURRENT_SOURCE_DIR}/cmake/templates/Version.hpp.in"
               "${GALAXY_EGGBERT_GENERATED_INCLUDE_DIR}/GalaxyEggbert/Version.hpp" @ONLY)
include("${CMAKE_CURRENT_SOURCE_DIR}/cmake/BuildStamp.cmake")

include(CTest)

message(STATUS "Project: ${PROJECT_NAME}")
message(STATUS "Platform: ${CMAKE_SYSTEM_NAME}")
message(STATUS "Compiler: ${CMAKE_CXX_COMPILER_ID} ${CMAKE_CXX_COMPILER_VERSION}")

if(NOT WIN32 AND CMAKE_BINARY_DIR MATCHES "build-windows")
    message(WARNING "Building in 'build-windows' directory, but WIN32 is not set. "
            "Make sure you used -DCMAKE_TOOLCHAIN_FILE=cmake/toolchains/mingw-w64.cmake "
            "on a CLEAN build directory.")
endif()

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)

# Engine-independent shared sources (no CNA/cna-engine dependencies).
# Shared by the game target and the engine-agnostic test suite.
set(GE_SHARED_SOURCES
    src/GalaxyEggbert/Worlds/BinaryIO.cpp
    src/GalaxyEggbert/Worlds/BitPacking.cpp
    src/GalaxyEggbert/Worlds/Chunk.cpp
    src/GalaxyEggbert/Worlds/World.cpp
    src/GalaxyEggbert/MoveObjectRecord.cpp
    src/GalaxyEggbert/BigDecorRecord.cpp
    src/GalaxyEggbert/HintZoneRecord.cpp
    src/GalaxyEggbert/HubDoors.cpp
    src/GalaxyEggbert/BlockDefinitionRegistry.cpp
    src/GalaxyEggbert/Game/PlateRotationMetadata.cpp
    src/GalaxyEggbert/Game/Localization.cpp
    src/GalaxyEggbert/Game/TranslationsFr.cpp
    src/GalaxyEggbert/Game/TranslationsDe.cpp
    src/GalaxyEggbert/Game/TranslationsCs.cpp
    src/GalaxyEggbert/Game/TranslationsSk.cpp
    src/GalaxyEggbert/Game/TranslationsEs.cpp
    src/GalaxyEggbert/Game/TranslationsIt.cpp
    src/GalaxyEggbert/Game/TranslationsPl.cpp
    src/GalaxyEggbert/Game/TranslationsPt.cpp
    src/GalaxyEggbert/Game/TranslationsNl.cpp
    src/GalaxyEggbert/Game/TranslationsHu.cpp
    src/GalaxyEggbert/Game/TranslationsRo.cpp
    src/GalaxyEggbert/Game/TranslationsSv.cpp
    src/GalaxyEggbert/Game/TranslationsRu.cpp
    src/GalaxyEggbert/Game/TranslationsUk.cpp
    src/GalaxyEggbert/Game/TranslationsFi.cpp
    src/GalaxyEggbert/Game/TranslationsDa.cpp
    src/GalaxyEggbert/Game/TranslationsNb.cpp
    src/GalaxyEggbert/Game/TranslationsTr.cpp
    src/GalaxyEggbert/Game/TranslationsEl.cpp
)

# =============================================================================
# Direct CNA + cna-engine target — GalaxyEggbertCNA (long-term direction, see CLAUDE.md
# "Current Direction Lock")
#
# ON by default; a plain `cmake -S . -B build` configures the maintained
# playable game, including terrain, objects, gameplay, HUD, sound, menus,
# persistence, and the in-game 3D editor. See CURRENT.md for verified status
# and easy3d.md/plan.md for the original migration analysis and task history (easy-3d
# itself was replaced by cna-engine's strict modules on 2026-10-20).
#
# Build with:
#   cmake -S . -B build-cna -DGALAXY_EGGBERT_BUILD_CNA=ON
#   cmake --build build-cna --target GalaxyEggbertCNA
# =============================================================================

option(GALAXY_EGGBERT_BUILD_CNA "Build the direct CNA + cna-engine Galaxy Eggbert target (actively developed -- see CLAUDE.md)" ON)
option(GALAXY_EGGBERT_ENABLE_CHEATS "Enable typed cheats and the hidden cheat menu" ON)
set_source_files_properties(
    src/GalaxyEggbertCNA/GalaxyEggbertGame.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameDraw.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameScene.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameLoad.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameDemo.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameMissions.cpp
    src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp
    src/GalaxyEggbert/Game/InputPad.cpp
    tools/VerifyGEInputPad.cpp
    PROPERTIES COMPILE_DEFINITIONS
    "GALAXY_EGGBERT_ENABLE_CHEATS=$<BOOL:${GALAXY_EGGBERT_ENABLE_CHEATS}>")

if(GALAXY_EGGBERT_BUILD_CNA)

    # Locate CNA and cna-engine: explicit option > sibling directory.
    set(CNA_HOME "" CACHE PATH
        "Path to the CNA source directory. Defaults to ../cna.")
    if(NOT CNA_HOME)
        set(CNA_HOME "${CMAKE_CURRENT_SOURCE_DIR}/../cna")
    endif()

    set(CNA_ENGINE_HOME "" CACHE PATH
        "Path to the cna-engine source directory. Defaults to ../cna-engine.")
    if(NOT CNA_ENGINE_HOME)
        set(CNA_ENGINE_HOME "${CMAKE_CURRENT_SOURCE_DIR}/../cna-engine")
    endif()

    if(NOT EXISTS "${CNA_HOME}/CMakeLists.txt")
        message(FATAL_ERROR
            "CNA not found at '${CNA_HOME}'. Clone it there or set -DCNA_HOME=<path>.")
    endif()

    if(NOT EXISTS "${CNA_ENGINE_HOME}/CMakeLists.txt")
        message(FATAL_ERROR
            "cna-engine not found at '${CNA_ENGINE_HOME}'. Clone it there or set -DCNA_ENGINE_HOME=<path>.")
    endif()

    message(STATUS "CNA source: ${CNA_HOME}")
    message(STATUS "cna-engine source: ${CNA_ENGINE_HOME}")

    # Prefer the build tree's normal incremental objects over a second copy
    # in ccache. On this workstation ccache occupied 3.7 GiB while only
    # 22.7% of cacheable calls were hits, so misses amplified SSD writes.
    # This is an unforced fresh-build default: callers can still opt in with
    # -DCNA_USE_CCACHE=ON, and an existing cache choice remains untouched.
    set(CNA_USE_CCACHE OFF CACHE BOOL
        "Use ccache for CNA and sharp-runtime builds when available")

    # Use CNA's current public renderer identities directly. Set this before
    # add_subdirectory(CNA_HOME); the unforced cache assignment preserves an
    # explicit command-line selection or an existing build directory's value.
    # Native builds default to OpenGL ES 3, while Emscripten requires WebGL 2.
    if(EMSCRIPTEN)
        set(_ge_default_cna_renderer "WEBGL2")
    else()
        set(_ge_default_cna_renderer "OPENGLES3")
    endif()
    set(CNA_GRAPHICS_RENDERER "${_ge_default_cna_renderer}" CACHE STRING
        "CNA graphics renderer to use; OPENGLES3 (native) and WEBGL2 (Emscripten) select the EasyGL family. See ../cna/cmake/RendererSelection.cmake for the full identity list.")

    # Add CNA first: cna-engine (below) links the CNA module targets this
    # creates instead of building its own copy. Keep CNA's own
    # tests/examples out of this build — we only need the library.
    if(NOT TARGET CNA)
        set(CNA_BUILD_TESTS OFF CACHE BOOL "" FORCE)
        set(CNA_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
        # cna-engine links CNA's static module libraries; a shared CNA would
        # put a second copy of CNA into the same process (cna-engine's
        # upstream-issues.md, UP-006), so CNA is built statically.
        set(CNA_SHARED_LIBRARY OFF CACHE BOOL "" FORCE)
        add_subdirectory("${CNA_HOME}" CNA_dep)
    endif()

    # cna-engine's strict modules (CnaEngine::Core, CnaEngine::Graphics) over
    # the CNA just added: XNA 4.0 types only. Its tests, examples and the
    # modules that need CNA's extensions are not built.
    set(CNA_ENGINE_BUILD_TESTS OFF CACHE BOOL "" FORCE)
    set(CNA_ENGINE_BUILD_EXAMPLES OFF CACHE BOOL "" FORCE)
    set(CNA_ENGINE_WITH_EXTENDED OFF CACHE BOOL "" FORCE)
    set(CNA_ENGINE_WITH_ENHANCED_GRAPHICS OFF CACHE BOOL "" FORCE)
    set(CNA_ENGINE_INSTALL OFF CACHE BOOL "" FORCE)
    set(CNA_ENGINE_PILOT_PROBES OFF CACHE BOOL "" FORCE)
    # A new compiler warning inside the engine must not break this game's
    # native, web or Android build.
    set(CNA_ENGINE_WARNINGS_AS_ERRORS OFF CACHE BOOL "" FORCE)
    if(NOT TARGET CnaEngine::Graphics)
        add_subdirectory("${CNA_ENGINE_HOME}" cna_engine_dep EXCLUDE_FROM_ALL)
    endif()

    if(ANDROID)
        # SDLActivity loads the game as libmain.so and calls its SDL_main (OR-0011).
        add_library(GalaxyEggbertCNA SHARED)
    else()
        add_executable(GalaxyEggbertCNA)
    endif()
    target_sources(GalaxyEggbertCNA PRIVATE
        src/GalaxyEggbertCNA/main.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGame.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameDraw.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameScene.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameLoad.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameDemo.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameMissions.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameUpdate.cpp
        src/GalaxyEggbert/Game/WorldRenderer.cpp
        src/GalaxyEggbert/Game/WorldEffects.cpp
        src/GalaxyEggbert/Game/EffectParticles.cpp
        src/GalaxyEggbert/Game/SurfaceShaders.cpp
        src/GalaxyEggbert/Game/SceneShaders.cpp
        src/GalaxyEggbert/Game/PostProcess.cpp
        src/GalaxyEggbert/Game/UltraFinish.cpp
        src/GalaxyEggbert/Game/WorldRuntime.cpp
        src/GalaxyEggbert/Game/TileAtlas.cpp
        src/GalaxyEggbert/Game/TerrainRenderer.cpp
        src/GalaxyEggbert/Game/TerrainRelief.cpp
        src/GalaxyEggbert/Game/TerrainAnimDivisor.cpp
        src/GalaxyEggbert/Game/DirectionalCubeTiles.cpp
        src/GalaxyEggbert/Game/SwatchUv.cpp
        src/GalaxyEggbert/Game/InnerPillarBoxTiles.cpp
        src/GalaxyEggbert/Game/ThinBarTiles.cpp
        src/GalaxyEggbert/Game/InnerFlatPlateTiles.cpp
        src/GalaxyEggbert/Game/BlupiController.cpp
        src/GalaxyEggbert/Game/CameraShake.cpp
        src/GalaxyEggbert/Game/ChaseCameraYaw.cpp
        src/GalaxyEggbert/Game/ObjectModelTable.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        src/GalaxyEggbert/Game/Sound.cpp
        src/GalaxyEggbert/Game/InteractionSystem.cpp
        src/GalaxyEggbert/Game/Hud.cpp
        src/GalaxyEggbert/Game/TextureScale.cpp
        src/GalaxyEggbert/Game/TrainingHints.cpp
        src/GalaxyEggbert/Game/InputPad.cpp
        src/GalaxyEggbert/Game/QuadBatch.cpp
        src/GalaxyEggbert/Game/Autoplay.cpp
        src/GalaxyEggbert/Game/ResumeLog.cpp
        src/GalaxyEggbert/Game/DemoReplay.cpp
        src/GalaxyEggbert/Game/SaveData.cpp
        src/GalaxyEggbert/Game/UserFolder.cpp
        src/GalaxyEggbert/Editor/WorldEditor.cpp
        src/GalaxyEggbert/Net/NetProtocol.cpp
        src/GalaxyEggbert/Editor/WorldEditorUpdate.cpp
        src/GalaxyEggbert/Editor/EditorDeviceControls.cpp
        src/GalaxyEggbert/Editor/ObjectPathRenderer.cpp
        src/GalaxyEggbert/Editor/VoxelRaycast.cpp
        src/GalaxyEggbert/Editor/EditorHighlightRenderer.cpp
        src/GalaxyEggbert/Editor/EditCommandStack.cpp
        src/GalaxyEggbert/Editor/BoxRegion.cpp
        src/GalaxyEggbert/Editor/Clipboard.cpp
        src/GalaxyEggbert/Editor/PlatformFiles.cpp
        src/GalaxyEggbert/Editor/PaletteCategories.cpp
        src/GalaxyEggbert/Editor/EditorPalette.cpp
        src/GalaxyEggbert/Editor/EditorPaletteLayout.cpp
        src/GalaxyEggbert/Editor/EditorPaletteInput.cpp
        src/GalaxyEggbert/Editor/EditorPaletteRenderer.cpp
        src/GalaxyEggbert/Editor/CustomWorldStorage.cpp
        src/GalaxyEggbert/Editor/EditorBrowserScreen.cpp
        src/GalaxyEggbert/Editor/GalaxyBrowserScreen.cpp
        ${GE_SHARED_SOURCES}
    )

    # The game's file is GalaxyEggbert (GalaxyEggbert.exe, GalaxyEggbert.html on the web; owner,
    # OR-0008, 2026-11-01); the CMake target keeps its name, so `--target GalaxyEggbertCNA` builds it.
    set_target_properties(GalaxyEggbertCNA PROPERTIES OUTPUT_NAME GalaxyEggbert)
    if(ANDROID)
        # Android: libmain.so, the name SDLActivity loads; the assets copied into internal storage
        # before the game starts (src/GalaxyEggbertCNA/AndroidAssets.hpp). SDL3::SDL3 is the
        # imported target CNA's ThirdPartySDL.cmake made (SDL3_DIR is cached by it); find_package
        # only re-imports it into this directory.
        set_target_properties(GalaxyEggbertCNA PROPERTIES OUTPUT_NAME main)
        target_sources(GalaxyEggbertCNA PRIVATE src/GalaxyEggbertCNA/AndroidAssets.cpp)
        if(NOT TARGET SDL3::SDL3)
            find_package(SDL3 CONFIG REQUIRED)
        endif()
        target_link_libraries(GalaxyEggbertCNA PRIVATE SDL3::SDL3 log)
    endif()
    target_include_directories(GalaxyEggbertCNA PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )

    # `CNA` is an INTERFACE umbrella over the framework module libraries plus
    # the selected renderer target (../cna/modules/CMakeLists.txt). Those
    # archives really do reference each other in both directions -- a renderer
    # implements IGraphicsRenderer while calling back into cna_graphics_core/
    # cna_core/cna_math -- but `../cna` now declares those reverse edges on its
    # own targets, so CMake repeats the archives on the final link line by
    # itself and no manual linker group is needed here.
    #
    # Do not spell out a renderer target here: CNA is the single source of
    # truth for whichever public identity CNA_GRAPHICS_RENDERER selected.
    target_link_libraries(GalaxyEggbertCNA PRIVATE CNA SHARP_RUNTIME)

    target_link_libraries(GalaxyEggbertCNA PRIVATE CnaEngine::Graphics)

    # The network game (plans/plan_gemultiplayer.md): CNA's SystemLink sessions where CNA builds
    # networking (not the web build); without it the game has no Multiplayer entry.
    # The screens and the match are built everywhere; NetSession.cpp is a stand-in without it.
    target_sources(GalaxyEggbertCNA PRIVATE
        src/GalaxyEggbertCNA/NetSession.cpp
        src/GalaxyEggbertCNA/GalaxyEggbertGameNet.cpp
        src/GalaxyEggbert/Net/NetReplication.cpp)
    # Not on the web (the owner's Q-019 Q4): CNA now builds CNA::Net under Emscripten too, which put
    # the Multiplayer entry, the network and gamer-services code into the page (its .wasm 8 -> 35 MB).
    if(TARGET CNA::Net AND NOT EMSCRIPTEN)
        target_link_libraries(GalaxyEggbertCNA PRIVATE CNA::Net)
        target_compile_definitions(GalaxyEggbertCNA PRIVATE GALAXY_EGGBERT_NET=1)
    endif()

    # The main menu shows the version (the generated GalaxyEggbert/Version.hpp) and the build's date
    # and time (cmake/BuildStamp.cmake: made again only when the game is linked again). After every
    # source and library of the game above, so the stamp follows them all.
    target_include_directories(GalaxyEggbertCNA PRIVATE "${GALAXY_EGGBERT_GENERATED_INCLUDE_DIR}")
    target_sources(GalaxyEggbertCNA PRIVATE src/GalaxyEggbertCNA/BuildStamp.hpp)
    ge_add_build_stamp(GalaxyEggbertCNA)

    if(TARGET SDL3::SDL3main)
        target_link_libraries(GalaxyEggbertCNA PRIVATE SDL3::SDL3main)
    endif()

    # A Windows build is a directly runnable bundle, not merely an .exe that
    # works while the MinGW/SDL build environment remains on PATH. Keep the
    # GCC/C++ runtime in the executable and use CNA's own helpers for the
    # remaining thread and SDL DLLs. --allow-multiple-definition is the same
    # MinGW static-libstdc++ workaround CNA applies to its own CnaTests:
    # PE/COFF otherwise fails to coalesce std::type_info::operator==.
    if(MINGW)
        target_link_options(GalaxyEggbertCNA PRIVATE
            -static-libgcc -static-libstdc++ -Wl,--allow-multiple-definition)
        cna_copy_mingw_runtime(GalaxyEggbertCNA)
        cna_copy_sdl_runtime(GalaxyEggbertCNA)

        # CNA's generic helper looks for SDL3::SDL3 and its analogous aliases.
        # This repository's Windows prebuilt exposes import libraries through
        # package configuration without making those targets visible here.
        # SDL3_DIR is nevertheless the same resolved package prefix used for
        # linking, so use its bin/ directory as a safe, build-local fallback.
        if(SDL3_DIR)
            get_filename_component(_ge_windows_sdl_prefix "${SDL3_DIR}/../../.." ABSOLUTE)
            foreach(_ge_windows_sdl_dll IN ITEMS SDL3.dll SDL3_image.dll SDL3_mixer.dll)
                set(_ge_windows_sdl_dll_path
                    "${_ge_windows_sdl_prefix}/bin/${_ge_windows_sdl_dll}")
                if(EXISTS "${_ge_windows_sdl_dll_path}")
                add_custom_command(TARGET GalaxyEggbertCNA POST_BUILD
                    COMMAND ${CMAKE_COMMAND} -E copy_if_different
                        "${_ge_windows_sdl_dll_path}"
                        $<TARGET_FILE_DIR:GalaxyEggbertCNA>
                    COMMENT "Copying ${_ge_windows_sdl_dll} next to GalaxyEggbert"
                    VERBATIM)
                endif()
            endforeach()
        endif()
    endif()

    # -------------------------------------------------------------------
    # Runtime asset staging
    #
    # Runtime assets used by GalaxyEggbertCNA are source-tracked here. Stage
    # only changed files after a relink. The old mobile-eggbert Content/
    # copy rewrote ~462 MiB every time, including ~437 MiB of unused 4x
    # artwork; the game only reads this repository's ~22 MiB Content tree.
    # -------------------------------------------------------------------
    function(ge_stage_runtime_directory sourceDirectory destinationName)
        add_custom_command(TARGET GalaxyEggbertCNA POST_BUILD
            COMMAND ${CMAKE_COMMAND}
                "-DGE_COPY_SOURCE=${sourceDirectory}"
                "-DGE_COPY_DESTINATION=$<TARGET_FILE_DIR:GalaxyEggbertCNA>/${destinationName}"
                -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CopyDirectoryIfDifferent.cmake"
            COMMENT "Updating ${destinationName}/ runtime assets (changed files only)"
            VERBATIM)
    endfunction()

    if(NOT EMSCRIPTEN AND NOT ANDROID AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Content")
        ge_stage_runtime_directory("${CMAKE_CURRENT_SOURCE_DIR}/Content" "Content")
    endif()

    # galaxy-eggbert's own hand-authored 3D worlds (plan.md E3D-MIG-058) —
    # committed to this repository and staged next to the executable.
    if(NOT EMSCRIPTEN AND NOT ANDROID AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/worlds3d")
        ge_stage_runtime_directory("${CMAKE_CURRENT_SOURCE_DIR}/worlds3d" "worlds3d")
    endif()

    # galaxy-eggbert's own 3D-specific textures (e.g. grass_top.png, icon 107 —
    # NEXT.md §8 task 3). Same changed-files-only staging as worlds3d/ above.
    if(NOT EMSCRIPTEN AND NOT ANDROID AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/textures3d")
        ge_stage_runtime_directory("${CMAKE_CURRENT_SOURCE_DIR}/textures3d" "textures3d")
    endif()

    # Third-person-mode 3D character model(s) (NEXT.md §3, 2026-07-09) --
    # loaded via CNA's ContentManager, whose RootDirectory is pointed at this
    # directory in LoadContent(). Currently just a temporary
    # placeholder (avatars3d/blupi_placeholder/, see its own README.md) until
    # a real 3D Blupi model exists. Same copy-next-to-binary pattern as
    # worlds3d//textures3d above.
    if(NOT EMSCRIPTEN AND NOT ANDROID AND EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/avatars3d")
        ge_stage_runtime_directory("${CMAKE_CURRENT_SOURCE_DIR}/avatars3d" "avatars3d")
    endif()

    # -------------------------------------------------------------------
    # Emscripten (web) build (2026-07-17, user-requested prototype-for-web-
    # publishing target). Every asset directory above is baked into the
    # .wasm's virtual filesystem at link time via --preload-file instead of
    # copied next to a native binary, since there is no "next to the
    # binary" on the web. Only `Content/icons`, `Content/backgrounds`, and
    # `Content/sounds` are preloaded from this repository's Content/ tree
    # (not the full ~460MB directory) -- confirmed via direct grep that
    # GalaxyEggbertCNA never reads `Content/icons4x/` or
    # `Content/backgrounds4x/` (the 4x-scale variants in mobile-eggbert,
    # ~437MB combined, are genuinely unused here), keeping the web download
    # to a reasonable size (~25MB of assets).
    # -------------------------------------------------------------------
    if(EMSCRIPTEN)
        set_target_properties(GalaxyEggbertCNA PROPERTIES SUFFIX ".html")

        # GalaxyEggbertCNA calls the blocking Game::Run() API.  CNA keeps
        # Asyncify as an explicit executable/event-loop policy for external
        # applications, so linking the framework umbrella alone is not
        # sufficient (without this the first animation-frame wait calls an
        # undefined JavaScript Asyncify object).
        target_link_libraries(GalaxyEggbertCNA PRIVATE CNA::EmscriptenAsyncify)

        # CMake doesn't parse --shell-file out of target_link_options to
        # discover it as a link dependency (it is just a linker-flag string).
        set_target_properties(GalaxyEggbertCNA PROPERTIES
            LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/web/shell-cna.html")

        target_link_options(GalaxyEggbertCNA PRIVATE
            -sALLOW_MEMORY_GROWTH=1
            -sINITIAL_MEMORY=268435456
            -sNO_EXIT_RUNTIME=1
            -sMIN_WEBGL_VERSION=2
            -sMAX_WEBGL_VERSION=2
            -sFULL_ES3=1
            -lidbfs.js
            "SHELL:--shell-file ${CMAKE_CURRENT_SOURCE_DIR}/cmake/web/shell-cna.html"
        )

        foreach(_content_dir Content/icons Content/backgrounds Content/sounds Content/demos)
            if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${_content_dir}")
                target_link_options(GalaxyEggbertCNA PRIVATE
                    "SHELL:--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${_content_dir}@/${_content_dir}")
            else()
                message(WARNING
                    "GalaxyEggbertCNA (web): ${_content_dir}/ not found at "
                    "'${CMAKE_CURRENT_SOURCE_DIR}/${_content_dir}'. The web build will still link, but "
                    "will have no ${_content_dir}/ in its virtual filesystem.")
            endif()
        endforeach()

        foreach(_own_dir worlds3d textures3d avatars3d)
            if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/${_own_dir}")
                target_link_options(GalaxyEggbertCNA PRIVATE
                    "SHELL:--preload-file ${CMAKE_CURRENT_SOURCE_DIR}/${_own_dir}@/${_own_dir}")
            endif()
        endforeach()

        # The music (27 MB) is not packed: it is served beside the page in Content/music/ and the
        # game fetches a track when a world first plays it (GalaxyEggbertGame::UpdateMusic, OR-0014).
        if(EXISTS "${CMAKE_CURRENT_SOURCE_DIR}/Content/music")
            ge_stage_runtime_directory("${CMAKE_CURRENT_SOURCE_DIR}/Content/music" "Content/music")
        endif()
    endif()

    message(STATUS "GalaxyEggbertCNA target enabled (maintained playable game).")

    # Scripted verification for InteractionSystem (2026-07-10, platform
    # lift patrol/crate push/pickup collection) against the real
    # worlds3d/world999.vwr sample world. Needs CNA linked (Sound touches
    # Microsoft::Xna::Framework::Audio types) but not SDL3main --
    # Sound::LoadContent() is never called, so no live audio device is
    # exercised at runtime, only symbols resolved at link time. Run from the
    # repo root (relative default path "worlds3d/world999.vwr"), same
    # quirk as VerifyBlupiMovement.
    add_executable(VerifyInteractionSystem
        tools/VerifyInteractionSystem.cpp
        src/GalaxyEggbert/Game/WorldRuntime.cpp
        src/GalaxyEggbert/Game/Sound.cpp
        src/GalaxyEggbert/Game/InteractionSystem.cpp
        src/GalaxyEggbert/Game/TrainingHints.cpp
        src/GalaxyEggbert/Game/BlupiController.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        src/GalaxyEggbert/Game/Hud.cpp
        src/GalaxyEggbert/Game/TextureScale.cpp
        ${GE_SHARED_SOURCES}
    )
    target_include_directories(VerifyInteractionSystem PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    # Web/Node test wiring: this console tool is only ever run under Node
    # (via ctest, never in a browser -- no .html/SUFFIX set below), so
    # NODERAWFS gives it direct access to the real host filesystem instead
    # of an empty MEMFS, matching native behavior for its relative
    # "worlds3d/world999.vwr" default path with no --preload-file/embed
    # list to maintain.
    if(EMSCRIPTEN)
        target_link_options(VerifyInteractionSystem PRIVATE -sNODERAWFS=1)
    endif()
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32 AND _ge_cna_backend_target)
        target_link_libraries(VerifyInteractionSystem PRIVATE
            -Wl,--start-group CNA ${_ge_cna_backend_target} -Wl,--end-group
            SHARP_RUNTIME)
    else()
        target_link_libraries(VerifyInteractionSystem PRIVATE CNA SHARP_RUNTIME)
    endif()
    target_link_libraries(VerifyInteractionSystem PRIVATE CnaEngine::Graphics)
    if(BUILD_TESTING)
        # WORKING_DIRECTORY = repo root: this tool's default world path
        # ("worlds3d/world999.vwr") is relative, same quirk noted above.
        add_test(NAME VerifyInteractionSystem COMMAND VerifyInteractionSystem
                 WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    endif()

    # The 3D effects' particles (EffectParticles.hpp): stepped by ticks, the same every run and at
    # any frame rate. XNA math and cna-engine's CPU-side types only.
    add_executable(VerifyEffectParticles
        tools/VerifyEffectParticles.cpp
        src/GalaxyEggbert/Game/EffectParticles.cpp
    )
    target_include_directories(VerifyEffectParticles PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32 AND _ge_cna_backend_target)
        target_link_libraries(VerifyEffectParticles PRIVATE
            -Wl,--start-group CNA ${_ge_cna_backend_target} -Wl,--end-group
            SHARP_RUNTIME)
    else()
        target_link_libraries(VerifyEffectParticles PRIVATE CNA SHARP_RUNTIME)
    endif()
    target_link_libraries(VerifyEffectParticles PRIVATE CnaEngine::Graphics)
    if(BUILD_TESTING)
        add_test(NAME VerifyEffectParticles COMMAND VerifyEffectParticles)
    endif()

    # Scripted verification for InputPad (2026-07-13, on-screen D-pad/
    # Jump/Action/Pause + Pause-row hit-testing) -- pure layout/press-state
    # math driven by synthetic MouseState values, no GraphicsDevice/window
    # needed at runtime, but InputPad.cpp's LoadContent()/DrawPlay()/
    # DrawPause() still reference CNA Texture2D/BasicEffect and cna-engine
    # GpuMesh symbols, so this links the same way GalaxyEggbertCNA itself
    # does (CNA + backend + SHARP_RUNTIME + cna-engine).
    add_executable(VerifyGEInputPad
        tools/VerifyGEInputPad.cpp
        src/GalaxyEggbert/Game/InputPad.cpp
        src/GalaxyEggbert/Game/TextureScale.cpp
        src/GalaxyEggbert/Game/QuadBatch.cpp
        src/GalaxyEggbert/Game/Localization.cpp
        src/GalaxyEggbert/Game/TranslationsFr.cpp
        src/GalaxyEggbert/Game/TranslationsDe.cpp
        src/GalaxyEggbert/Game/TranslationsCs.cpp
        src/GalaxyEggbert/Game/TranslationsSk.cpp
        src/GalaxyEggbert/Game/TranslationsEs.cpp
        src/GalaxyEggbert/Game/TranslationsIt.cpp
        src/GalaxyEggbert/Game/TranslationsPl.cpp
        src/GalaxyEggbert/Game/TranslationsPt.cpp
        src/GalaxyEggbert/Game/TranslationsNl.cpp
        src/GalaxyEggbert/Game/TranslationsHu.cpp
        src/GalaxyEggbert/Game/TranslationsRo.cpp
        src/GalaxyEggbert/Game/TranslationsSv.cpp
        src/GalaxyEggbert/Game/TranslationsRu.cpp
        src/GalaxyEggbert/Game/TranslationsUk.cpp
        src/GalaxyEggbert/Game/TranslationsFi.cpp
        src/GalaxyEggbert/Game/TranslationsDa.cpp
        src/GalaxyEggbert/Game/TranslationsNb.cpp
        src/GalaxyEggbert/Game/TranslationsTr.cpp
        src/GalaxyEggbert/Game/TranslationsEl.cpp
    )
    target_include_directories(VerifyGEInputPad PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32 AND _ge_cna_backend_target)
        target_link_libraries(VerifyGEInputPad PRIVATE
            -Wl,--start-group CNA ${_ge_cna_backend_target} -Wl,--end-group
            SHARP_RUNTIME)
    else()
        target_link_libraries(VerifyGEInputPad PRIVATE CNA SHARP_RUNTIME)
    endif()
    target_link_libraries(VerifyGEInputPad PRIVATE CnaEngine::Graphics)
    if(BUILD_TESTING)
        # No file/working-directory dependency -- LoadContent() is
        # deliberately never called (see this tool's own top comment).
        add_test(NAME VerifyGEInputPad COMMAND VerifyGEInputPad)
    endif()

    # Scripted editor verification is split by responsibility so failures
    # identify the broken subsystem instead of disappearing inside one
    # monolithic executable. Compile their shared real-editor support once:
    # repeating it in all four executables created four identical object
    # trees and rewrote them after every common editor change.
    add_library(GEEditorVerifySupport OBJECT
        src/GalaxyEggbert/Editor/WorldEditor.cpp
        src/GalaxyEggbert/Editor/WorldEditorUpdate.cpp
        src/GalaxyEggbert/Editor/EditorDeviceControls.cpp
        src/GalaxyEggbert/Editor/ObjectPathRenderer.cpp
        src/GalaxyEggbert/Editor/VoxelRaycast.cpp
        src/GalaxyEggbert/Editor/EditorHighlightRenderer.cpp
        src/GalaxyEggbert/Editor/EditCommandStack.cpp
        src/GalaxyEggbert/Editor/BoxRegion.cpp
        src/GalaxyEggbert/Editor/Clipboard.cpp
        src/GalaxyEggbert/Editor/PlatformFiles.cpp
        src/GalaxyEggbert/Editor/PaletteCategories.cpp
        src/GalaxyEggbert/Editor/EditorPalette.cpp
        src/GalaxyEggbert/Editor/EditorPaletteLayout.cpp
        src/GalaxyEggbert/Editor/EditorPaletteInput.cpp
        src/GalaxyEggbert/Editor/EditorPaletteRenderer.cpp
        src/GalaxyEggbert/Editor/CustomWorldStorage.cpp
        src/GalaxyEggbert/Editor/EditorBrowserScreen.cpp
        src/GalaxyEggbert/Editor/GalaxyBrowserScreen.cpp
        src/GalaxyEggbert/Game/DemoReplay.cpp
        src/GalaxyEggbert/Game/ResumeLog.cpp
        src/GalaxyEggbert/Game/UserFolder.cpp
        src/GalaxyEggbert/Game/QuadBatch.cpp
        src/GalaxyEggbert/Game/TileAtlas.cpp
        src/GalaxyEggbert/Game/DirectionalCubeTiles.cpp
        src/GalaxyEggbert/Game/TerrainRelief.cpp
        src/GalaxyEggbert/Game/SwatchUv.cpp
        src/GalaxyEggbert/Game/Hud.cpp
        src/GalaxyEggbert/Game/TextureScale.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        ${GE_SHARED_SOURCES}
    )
    target_include_directories(GEEditorVerifySupport PUBLIC
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
    )
    target_link_libraries(GEEditorVerifySupport PRIVATE CNA SHARP_RUNTIME)
    target_link_libraries(GEEditorVerifySupport PRIVATE CnaEngine::Graphics)

    function(ge_add_editor_verify target source)
        add_executable(${target} ${source})
        target_include_directories(${target} PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
            ${CMAKE_CURRENT_SOURCE_DIR}/src
        )
        target_link_libraries(${target} PRIVATE GEEditorVerifySupport)
        if(CMAKE_CXX_COMPILER_ID MATCHES "GNU|Clang" AND NOT WIN32 AND _ge_cna_backend_target)
            target_link_libraries(${target} PRIVATE
                -Wl,--start-group CNA ${_ge_cna_backend_target} -Wl,--end-group
                SHARP_RUNTIME)
        else()
            target_link_libraries(${target} PRIVATE CNA SHARP_RUNTIME)
        endif()
        target_link_libraries(${target} PRIVATE CnaEngine::Graphics)
        if(BUILD_TESTING)
            # Run in a scratch directory of the build tree: the editor tests write customworlds/
            # relative to their working directory, and in the build directory itself that is the
            # player's own folder (a world he exported once made VerifyGEEditorStorage fail).
            file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/verify-run)
            add_test(NAME ${target} COMMAND ${target}
                     WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify-run)
        endif()
    endfunction()

    ge_add_editor_verify(VerifyGEWorldEditor tools/VerifyGEWorldEditor.cpp)
    ge_add_editor_verify(VerifyGEEditorDevices tools/VerifyGEEditorDevices.cpp)
    ge_add_editor_verify(VerifyGEEditorPalette tools/VerifyGEEditorPalette.cpp)
    ge_add_editor_verify(VerifyGEEditorStorage tools/VerifyGEEditorStorage.cpp)
    ge_add_editor_verify(VerifyGEEditorObjects tools/VerifyGEEditorObjects.cpp)
    ge_add_editor_verify(VerifyDefinitionRegistries tools/VerifyDefinitionRegistries.cpp)
    ge_add_editor_verify(VerifyTileFaces tools/VerifyTileFaces.cpp)
    # The static terrain's chunks: a change rebuilds only the chunks within its reach (OR-0074).
    ge_add_editor_verify(VerifyTerrainChunks tools/VerifyTerrainChunks.cpp)
    target_sources(VerifyTerrainChunks PRIVATE
        src/GalaxyEggbert/Game/TerrainRenderer.cpp
        src/GalaxyEggbert/Game/TerrainAnimDivisor.cpp
        src/GalaxyEggbert/Game/InnerPillarBoxTiles.cpp
        src/GalaxyEggbert/Game/ThinBarTiles.cpp
        src/GalaxyEggbert/Game/InnerFlatPlateTiles.cpp)
    target_compile_definitions(VerifyTerrainChunks PRIVATE GE_SOURCE_DIR="${CMAKE_CURRENT_SOURCE_DIR}")
    # The 3D effects' decisions without a GPU (SceneEffectsLogic.hpp): shadows, lights, occlusion.
    ge_add_editor_verify(VerifySceneEffects tools/VerifySceneEffects.cpp)
    ge_add_editor_verify(VerifyUltra tools/VerifyUltra.cpp)

    # -------------------------------------------------------------------
    # Linux runtime bundle (BUILD-010)
    #
    # The game resolves all of its assets relative to its working directory.
    # Install a self-contained directory layout and a launcher that enters
    # that directory, then let CPack emit a portable .tar.gz bundle.  The
    # bundled SDL libraries remove the build-machine-specific SDL RPATH; OS
    # libraries such as the graphics stack and FFmpeg intentionally remain
    # distribution dependencies rather than being copied into the archive.
    # -------------------------------------------------------------------
    if(UNIX AND NOT APPLE AND NOT EMSCRIPTEN AND NOT ANDROID)
        option(GALAXY_EGGBERT_ENABLE_LINUX_BUNDLE
            "Enable CPack's GalaxyEggbertCNA Linux runtime bundle" ON)

        if(GALAXY_EGGBERT_ENABLE_LINUX_BUNDLE)
            set_target_properties(GalaxyEggbertCNA PROPERTIES
                INSTALL_RPATH "$ORIGIN/lib")

            configure_file(
                "${CMAKE_CURRENT_SOURCE_DIR}/packaging/run-galaxy-eggbert.sh.in"
                "${CMAKE_CURRENT_BINARY_DIR}/run-galaxy-eggbert"
                @ONLY)

            install(TARGETS GalaxyEggbertCNA
                RUNTIME DESTINATION "."
                COMPONENT Runtime)
            install(PROGRAMS "${CMAKE_CURRENT_BINARY_DIR}/run-galaxy-eggbert"
                DESTINATION "."
                COMPONENT Runtime)
            install(DIRECTORY Content worlds worlds3d textures3d avatars3d
                DESTINATION "."
                USE_SOURCE_PERMISSIONS
                COMPONENT Runtime)
            install(FILES LICENSE DESTINATION "." COMPONENT Runtime)
            install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/packaging/THIRD_PARTY.txt"
                          "${CMAKE_CURRENT_SOURCE_DIR}/packaging/RELEASE_NOTES.md"
                          "${CMAKE_CURRENT_SOURCE_DIR}/VERSION.txt"
                DESTINATION "." COMPONENT Runtime)
            install(FILES "${CMAKE_CURRENT_SOURCE_DIR}/packaging/README-linux-bundle.md"
                DESTINATION "."
                RENAME "README-Linux.md"
                COMPONENT Runtime)

            # The SDL prebuilt install used by CNA provides license files.
            # Preserve them in the distributed bundle whenever SDL3_DIR points
            # at that standard package-config location.
            if(SDL3_DIR)
                get_filename_component(_ge_sdl_prefix "${SDL3_DIR}/../../.." ABSOLUTE)
                # Copy the versioned shared libraries and their SONAME
                # symlinks. The executable's INSTALL_RPATH resolves these
                # as $ORIGIN/lib after the bundle is extracted.
                install(DIRECTORY "${_ge_sdl_prefix}/lib/"
                    DESTINATION "lib"
                    COMPONENT Runtime
                    FILES_MATCHING PATTERN "libSDL3*.so*")
                if(EXISTS "${_ge_sdl_prefix}/share/licenses")
                    install(DIRECTORY "${_ge_sdl_prefix}/share/licenses/"
                        DESTINATION "licenses/SDL"
                        COMPONENT Runtime)
                endif()
            endif()

            set(CPACK_GENERATOR "TGZ")
            set(CPACK_PACKAGE_NAME "GalaxyEggbert")
            # The version (VERSION.txt) in the archive's name.
            set(CPACK_PACKAGE_FILE_NAME
                "GalaxyEggbert-${GALAXY_EGGBERT_VERSION_STRING}-linux-${CMAKE_SYSTEM_PROCESSOR}")
            set(CPACK_INCLUDE_TOPLEVEL_DIRECTORY ON)
            set(CPACK_COMPONENTS_ALL Runtime)
            # CPack otherwise invokes the root install with no component,
            # which recursively installs library/test artifacts from CNA as
            # well. Package only this project's Runtime component.
            set(CPACK_INSTALL_CMAKE_PROJECTS
                "${CMAKE_BINARY_DIR};${CMAKE_PROJECT_NAME};Runtime;/")
            include(CPack)
        endif()
    endif()

endif() # GALAXY_EGGBERT_BUILD_CNA

# =============================================================================
# Tools (engine-independent — pure C++ data model, no graphics)
# =============================================================================

# ObjectIcons.hpp/.cpp is engine-agnostic (only depends on Def/
# ObjectType.hpp) despite living under src/GalaxyEggbert/Game/ -- linked here
# so the exhibition area can enumerate every ObjectType the renderer has an
# icon for (GetObjIcon), instead of duplicating that list by hand (same
# precedent as VerifyBlupiMovement linking BlupiController.cpp below).
add_executable(GenerateSampleWorld3D
        tools/GenerateSampleWorld3D.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        ${GE_SHARED_SOURCES}
)

target_include_directories(GenerateSampleWorld3D PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# BlupiController.hpp/.cpp are deliberately engine-agnostic (only depend on
# GalaxyEggbert::Worlds) despite living under src/GalaxyEggbert/Game/ — this
# tool links them without needing CNA at all (see plan.md E3D-MIG-060).
add_executable(VerifyBlupiMovement
        tools/VerifyBlupiMovement.cpp
        src/GalaxyEggbert/Game/BlupiController.cpp
        ${GE_SHARED_SOURCES}
)

target_include_directories(VerifyBlupiMovement PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Web/Node test wiring, same rationale as VerifyInteractionSystem above:
# Node-only console tool, NODERAWFS gives it real host filesystem access
# for its relative "worlds3d/world999.vwr" default path.
if(EMSCRIPTEN)
    target_link_options(VerifyBlupiMovement PRIVATE -sNODERAWFS=1)
endif()

# SaveData.hpp/.cpp are deliberately engine-agnostic (only <string>/
# <fstream>) despite living under src/GalaxyEggbert/Game/ -- this tool links
# it without needing CNA at all, same precedent as VerifyBlupiMovement
# above.
add_executable(VerifyGESaveData
        tools/VerifyGESaveData.cpp
        src/GalaxyEggbert/Game/SaveData.cpp
        src/GalaxyEggbert/Game/UserFolder.cpp
        src/GalaxyEggbert/Game/Localization.cpp
        src/GalaxyEggbert/Game/TranslationsFr.cpp
        src/GalaxyEggbert/Game/TranslationsDe.cpp
        src/GalaxyEggbert/Game/TranslationsCs.cpp
        src/GalaxyEggbert/Game/TranslationsSk.cpp
        src/GalaxyEggbert/Game/TranslationsEs.cpp
        src/GalaxyEggbert/Game/TranslationsIt.cpp
        src/GalaxyEggbert/Game/TranslationsPl.cpp
        src/GalaxyEggbert/Game/TranslationsPt.cpp
        src/GalaxyEggbert/Game/TranslationsNl.cpp
        src/GalaxyEggbert/Game/TranslationsHu.cpp
        src/GalaxyEggbert/Game/TranslationsRo.cpp
        src/GalaxyEggbert/Game/TranslationsSv.cpp
        src/GalaxyEggbert/Game/TranslationsRu.cpp
        src/GalaxyEggbert/Game/TranslationsUk.cpp
        src/GalaxyEggbert/Game/TranslationsFi.cpp
        src/GalaxyEggbert/Game/TranslationsDa.cpp
        src/GalaxyEggbert/Game/TranslationsNb.cpp
        src/GalaxyEggbert/Game/TranslationsTr.cpp
        src/GalaxyEggbert/Game/TranslationsEl.cpp
)

target_include_directories(VerifyGESaveData PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Web/Node test wiring: unlike the NODERAWFS tools above, SaveData's own
# kSavePath is "/save/savedata.txt" under __EMSCRIPTEN__ (an absolute IDBFS
# mount point CNA's storage module sets up for browser persistence, see
# SaveData.hpp) -- NODERAWFS would instead map that absolute path onto the
# real host's own "/save" directory, which is wrong here. Reuses a minimal
# pre-js that just creates "/save" in this tool's own in-memory MEMFS (no
# IDBFS/persistence needed -- the test runs and exits within one process).
if(EMSCRIPTEN)
    set_target_properties(VerifyGESaveData PROPERTIES
        LINK_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/cmake/web/pre-test-save-dir.js")
    target_link_options(VerifyGESaveData PRIVATE
        -sFORCE_FILESYSTEM=1
        "SHELL:--pre-js ${CMAKE_CURRENT_SOURCE_DIR}/cmake/web/pre-test-save-dir.js")
endif()

# TerrainAnimDivisor.hpp/.cpp (plan.md TEST-007, 2026-07-14) is a pure
# function of a BlockTypes icon id -- no CNA/graphics dependency at all,
# same engine-independent precedent as VerifyGESaveData/VerifyBlupiMovement
# above.
add_executable(VerifyTerrainAnimDivisor
        tools/VerifyTerrainAnimDivisor.cpp
        src/GalaxyEggbert/Game/TerrainAnimDivisor.cpp
        src/GalaxyEggbert/BlockDefinitionRegistry.cpp
)

target_include_directories(VerifyTerrainAnimDivisor PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# The QA autoplay (Autoplay.hpp/.cpp, milestone F) is engine-independent: its script parser and
# steering are checked against a stand-in for Blupi.
add_executable(VerifyAutoplay
        tools/VerifyAutoplay.cpp
        src/GalaxyEggbert/Game/Autoplay.cpp
        src/GalaxyEggbert/Game/ResumeLog.cpp
        src/GalaxyEggbert/Game/UserFolder.cpp
)

target_include_directories(VerifyAutoplay PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# The network game's messages and lobby (src/GalaxyEggbert/Net/): round trips, damaged and foreign
# packets refused, the lobby's rules. Engine-independent.
add_executable(VerifyNetProtocol
        tools/VerifyNetProtocol.cpp
        src/GalaxyEggbert/Net/NetProtocol.cpp
        src/GalaxyEggbert/Net/NetReplication.cpp
        ${GE_SHARED_SOURCES}
)
target_include_directories(VerifyNetProtocol PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)
add_test(NAME VerifyNetProtocol COMMAND VerifyNetProtocol)

# The texts in every language (Localization.hpp, the owner's Q-023): the UTF-8 glyphs, and a
# translation for every Tr/TrMark/TrFormat literal of the sources (read from the source tree).
add_executable(VerifyLocalization
    tools/VerifyLocalization.cpp
    src/GalaxyEggbert/Game/TrainingHints.cpp
    ${GE_SHARED_SOURCES}
)
target_include_directories(VerifyLocalization PRIVATE
    ${CMAKE_CURRENT_SOURCE_DIR}/include
    ${CMAKE_CURRENT_SOURCE_DIR}/src
)
add_test(NAME VerifyLocalization COMMAND VerifyLocalization ${CMAKE_CURRENT_SOURCE_DIR})

# The network game against broken and hostile packets (Q-022): random and damaged packets through the
# decoder and the world changes it lets through into a world; tools/fuzz_net_sanitized.sh runs it
# under ASan/UBSan. Engine-independent.
add_executable(FuzzNetProtocol
        tools/FuzzNetProtocol.cpp
        src/GalaxyEggbert/Net/NetProtocol.cpp
        src/GalaxyEggbert/Net/NetReplication.cpp
        ${GE_SHARED_SOURCES}
)
target_include_directories(FuzzNetProtocol PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)
add_test(NAME FuzzNetProtocol COMMAND FuzzNetProtocol)

# The main menu's demos (DemoReplay.hpp): their file format and compatibility checks, the idle timer,
# the input that stops a demo, and the order they play in; engine-independent. The shipped demos
# themselves are checked by the VerifyDemoFiles test below (and replayed by tools/verify_demos.sh).
add_executable(VerifyDemoReplay
        tools/VerifyDemoReplay.cpp
        src/GalaxyEggbert/Game/DemoReplay.cpp
        src/GalaxyEggbert/Game/ResumeLog.cpp
        src/GalaxyEggbert/Game/UserFolder.cpp
)

target_include_directories(VerifyDemoReplay PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)
if(EMSCRIPTEN)
    # --shipped reads Content/demos/ and worlds3d/ from the real file system under Node.
    target_link_options(VerifyDemoReplay PRIVATE -sNODERAWFS=1)
endif()

# INFRA-003 (plan.md §7): ObjectIcons.hpp/.cpp is engine-agnostic (only
# depends on Def/ObjectType.hpp, see GenerateSampleWorld3D's own comment
# above) -- a data-integrity regression test for its 34 historical "Fixed"
# transcription bugs, same engine-independent precedent as
# VerifyTerrainAnimDivisor above.
add_executable(VerifyGetObjIcon
        tools/VerifyGetObjIcon.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
)

target_include_directories(VerifyGetObjIcon PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# INFRA-003 follow-up (plan.md §7): the independent programmatic
# reference-doc cross-check REMAKE-ANALYSIS.md's P0-2 actually asked for
# (VerifyGetObjIcon above is a regression lock, not this) -- parses
# mobile-eggbert-reference/08-animations.md's own §3.1/§3.2 tables at
# runtime and black-box-verifies GetObjIcon()'s actual cycle behavior
# against them. Same engine-independent precedent as VerifyGetObjIcon.
add_executable(VerifyObjIconAgainstReferenceDoc
        tools/VerifyObjIconAgainstReferenceDoc.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
)

target_include_directories(VerifyObjIconAgainstReferenceDoc PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# BlockTypes::tileUV() (plan.md TEST-004) is a pure header-only function --
# no CNA/graphics dependency at all, same engine-independent precedent as
# VerifyGESaveData/VerifyBlupiMovement/VerifyTerrainAnimDivisor above.
add_executable(VerifyTileUvBounds
        tools/VerifyTileUvBounds.cpp
)

target_include_directories(VerifyTileUvBounds PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# TerrainLighting.hpp is header-only and engine-agnostic, like LiftVisual.hpp above.
add_executable(VerifyTerrainLighting
        tools/VerifyTerrainLighting.cpp
)

target_include_directories(VerifyTerrainLighting PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# TileCoverage.hpp is header-only and engine-agnostic, like TerrainLighting.hpp above.
add_executable(VerifyTileCoverage
        tools/VerifyTileCoverage.cpp
)

target_include_directories(VerifyTileCoverage PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# INFRA-004 (plan.md §7): UnverifiedRenderMapping.hpp is a header-only data
# table (no CNA/graphics dependency), same engine-independent precedent as
# VerifyTileUvBounds above.
add_executable(VerifyUnverifiedRenderMapping
        tools/VerifyUnverifiedRenderMapping.cpp
)

target_include_directories(VerifyUnverifiedRenderMapping PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
)

# CameraShake.hpp/.cpp (plan.md CAM-008..013) is deliberately
# engine-agnostic (no CNA/graphics dependency) despite living under
# src/GalaxyEggbert/Game/ -- this tool links it without needing CNA at all,
# same precedent as VerifyGESaveData/VerifyTerrainAnimDivisor above.
add_executable(VerifyCameraShake
        tools/VerifyCameraShake.cpp
        src/GalaxyEggbert/Game/CameraShake.cpp
)

target_include_directories(VerifyCameraShake PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# ChaseCameraYaw (the real-Blupi plan's phase E) is engine-agnostic like
# CameraShake: the third-person camera's orbit decisions, checked without CNA.
add_executable(VerifyChaseCameraYaw
        tools/VerifyChaseCameraYaw.cpp
        src/GalaxyEggbert/Game/ChaseCameraYaw.cpp
)

target_include_directories(VerifyChaseCameraYaw PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# ObjectModelTable (plan.md BB3D-010/011): which converted 3D object model draws which sprite
# icon, parsed from avatars3d/objects/*/icons.txt; checked without CNA.
add_executable(VerifyObjectModelTable
        tools/VerifyObjectModelTable.cpp
        src/GalaxyEggbert/Game/ObjectModelTable.cpp
)

target_include_directories(VerifyObjectModelTable PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# WorldRuntime.hpp/.cpp are deliberately engine-agnostic (only
# depend on GalaxyEggbert::Worlds/def, plain floats instead of a CNA/XNA
# Vector3) — this tool links it without needing CNA at all, verifying
# MoveObject: parsing (E3D-MIG-06x prerequisite) against real reference
# world files and the production game parser.
add_executable(VerifyMoveObjectTypesCna
        tools/VerifyMoveObjectTypesCna.cpp
        src/GalaxyEggbert/Game/WorldRuntime.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        ${GE_SHARED_SOURCES}
)

target_include_directories(VerifyMoveObjectTypesCna PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Web/Node test wiring, same rationale as VerifyInteractionSystem above:
# Node-only console tool, NODERAWFS gives it real host filesystem access
# for its relative "../mobile-eggbert/worlds/*.txt" reads.
if(EMSCRIPTEN)
    target_link_options(VerifyMoveObjectTypesCna PRIVATE -sNODERAWFS=1)
endif()

# Same engine-agnostic-parser rationale as VerifyMoveObjectTypesCna above,
# for BigDecor: section parsing (NEXT.md §8 task 3) instead of MoveObject:.
# BlockDefinitionRegistry as CSV for the Python world tools; the CTest keeps
# the committed tools/data/blocks.csv in step with the C++ table.
add_executable(DumpBlockDefinitions
        tools/DumpBlockDefinitions.cpp
        src/GalaxyEggbert/BlockDefinitionRegistry.cpp
)

target_include_directories(DumpBlockDefinitions PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Every shipped world loads, carries its file's mission number and survives
# load -> save -> load (plans/GALAXY_EGGBERT_COMPLETION.md §6).
add_executable(VerifyShippedWorlds
        tools/VerifyShippedWorlds.cpp
        src/GalaxyEggbert/Game/WorldRuntime.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        ${GE_SHARED_SOURCES}
)

target_include_directories(VerifyShippedWorlds PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

if(EMSCRIPTEN)
    target_link_options(VerifyShippedWorlds PRIVATE -sNODERAWFS=1)
endif()

add_executable(VerifyBigDecorParsingCna
        tools/VerifyBigDecorParsingCna.cpp
        src/GalaxyEggbert/Game/WorldRuntime.cpp
        src/GalaxyEggbert/Game/ObjectIcons.cpp
        src/GalaxyEggbert/Game/ObjectDefinitionRegistry.cpp
        ${GE_SHARED_SOURCES}
)

target_include_directories(VerifyBigDecorParsingCna PRIVATE
        ${CMAKE_CURRENT_SOURCE_DIR}/include
        ${CMAKE_CURRENT_SOURCE_DIR}/src
)

# Web/Node test wiring, same rationale as VerifyMoveObjectTypesCna above.
if(EMSCRIPTEN)
    target_link_options(VerifyBigDecorParsingCna PRIVATE -sNODERAWFS=1)
endif()

# plan.md TEST-002/008/009/010: registers these 4 engine-agnostic tools with
# ctest (VerifyInteractionSystem/VerifyGEInputPad are registered next to
# their own add_executable() above, inside the GALAXY_EGGBERT_BUILD_CNA
# block). VerifyBlupiMovement/VerifyMoveObjectTypesCna/VerifyBigDecorParsingCna
# all default to relative paths ("worlds3d/world999.vwr", "../mobile-eggbert/
# worlds/*.txt") that only resolve from the repo root (NEXT.md §7's own
# note) -- WORKING_DIRECTORY makes that explicit instead of relying on
# whatever directory ctest happens to be invoked from. VerifyGESaveData
# needs no such override: it only touches a scratch "savedata.txt" in
# its own cwd and cleans it up itself, so the default build-dir cwd
# keeps the repo root clean.
if(BUILD_TESTING)
    add_test(NAME VerifyBlupiMovement COMMAND VerifyBlupiMovement
             WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    # In its own directory: it creates and removes savedata.txt where it runs, and the build
    # directory's own is the game's save when it is played from there (CLAUDE.md), which every
    # ctest run deleted (2026-11-04).
    file(MAKE_DIRECTORY ${CMAKE_BINARY_DIR}/verify-savedata)
    add_test(NAME VerifyGESaveData COMMAND VerifyGESaveData
             WORKING_DIRECTORY ${CMAKE_BINARY_DIR}/verify-savedata)
    add_test(NAME VerifyMoveObjectTypesCna COMMAND VerifyMoveObjectTypesCna
             WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    add_test(NAME VerifyBigDecorParsingCna COMMAND VerifyBigDecorParsingCna
             WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    # Writes its round-trip copy into the build directory (its default cwd).
    add_test(NAME VerifyShippedWorlds COMMAND VerifyShippedWorlds ${CMAKE_SOURCE_DIR}/worlds3d)
    # The 2x terrain sheet: no cube tile with a line its 1x tile lacks (tools/check_2x_sheets.py; the
    # owner's "thick lines" of 2026-11-10). Skipped (77) without Python's numpy and Pillow.
    find_package(Python3 COMPONENTS Interpreter QUIET)
    if(Python3_Interpreter_FOUND)
        add_test(NAME Verify2xSheets COMMAND ${Python3_EXECUTABLE} ${CMAKE_SOURCE_DIR}/tools/check_2x_sheets.py
                 ${CMAKE_SOURCE_DIR})
        set_tests_properties(Verify2xSheets PROPERTIES SKIP_RETURN_CODE 77)
    endif()
    add_test(NAME VerifyBlockDefinitionsCsv COMMAND DumpBlockDefinitions --check ${CMAKE_SOURCE_DIR}/tools/data/blocks.csv)
    # No file/working-directory dependency -- AnimDivisor() is a pure
    # function of a BlockTypes icon id (plan.md TEST-007).
    add_test(NAME VerifyTerrainAnimDivisor COMMAND VerifyTerrainAnimDivisor)
    add_test(NAME VerifyAutoplay COMMAND VerifyAutoplay)
    add_test(NAME VerifyDemoReplay COMMAND VerifyDemoReplay)
    # Every shipped demo parses with the current simulation revision and matches its world's bytes.
    add_test(NAME VerifyDemoFiles COMMAND VerifyDemoReplay --shipped
             WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    # The shipped demos replayed by the game itself under Xvfb (tools/verify_demos.sh): a simulation
    # change that makes one replay differently fails here; then bump kDemoSimulationRevision and
    # record the demos again (handoff.md, "Main-menu demos"). Runs from the build directory's own
    # verify-demos/, with stand-in player files it checks are left untouched.
    find_program(GE_XVFB_RUN xvfb-run)
    if(GE_XVFB_RUN AND TARGET GalaxyEggbertCNA AND NOT EMSCRIPTEN AND NOT ANDROID AND NOT WIN32)
        add_test(NAME VerifyDemoReplays COMMAND ${CMAKE_SOURCE_DIR}/tools/verify_demos.sh ${CMAKE_BINARY_DIR})
        set_tests_properties(VerifyDemoReplays PROPERTIES ENVIRONMENT "DEMO_RUN=${CMAKE_BINARY_DIR}/verify-demos")
        # A death plays its sound once (lava, saw, spike trap of world 999; tools/verify_death_sounds.sh).
        add_test(NAME VerifyDeathSounds COMMAND ${CMAKE_SOURCE_DIR}/tools/verify_death_sounds.sh ${CMAKE_BINARY_DIR})
        set_tests_properties(VerifyDeathSounds PROPERTIES ENVIRONMENT "DEATH_RUN=${CMAKE_BINARY_DIR}/verify-death-sounds")
    endif()
    # No file/working-directory dependency -- tileUV() is a pure header-only
    # function of a BlockTypes icon id (plan.md TEST-004).
    add_test(NAME VerifyTileUvBounds COMMAND VerifyTileUvBounds)
    add_test(NAME VerifyTerrainLighting COMMAND VerifyTerrainLighting)
    add_test(NAME VerifyTileCoverage COMMAND VerifyTileCoverage)
    # No file/working-directory dependency -- GetObjIcon() is a pure function
    # of (ObjectType, phase) (plan.md INFRA-003).
    add_test(NAME VerifyGetObjIcon COMMAND VerifyGetObjIcon)
    # Reads mobile-eggbert-reference/08-animations.md, a repo-root-relative
    # path -- same WORKING_DIRECTORY reasoning as VerifyBlupiMovement above
    # (plan.md INFRA-003 follow-up).
    add_test(NAME VerifyObjIconAgainstReferenceDoc COMMAND VerifyObjIconAgainstReferenceDoc
             WORKING_DIRECTORY ${CMAKE_SOURCE_DIR})
    # No file/working-directory dependency -- pure data-table integrity check
    # (plan.md INFRA-004).
    add_test(NAME VerifyUnverifiedRenderMapping COMMAND VerifyUnverifiedRenderMapping)
    # No file/working-directory dependency -- CameraShake is a pure
    # tick-based state machine (plan.md CAM-008..013).
    add_test(NAME VerifyCameraShake COMMAND VerifyCameraShake)
    # No file/working-directory dependency -- pure orbit-yaw state machine.
    add_test(NAME VerifyChaseCameraYaw COMMAND VerifyChaseCameraYaw)
    # Reads the shipped avatars3d/objects/ (index.txt, manifests, icons.txt) from the source tree.
    add_test(NAME VerifyObjectModelTable
             COMMAND VerifyObjectModelTable ${CMAKE_CURRENT_SOURCE_DIR}/avatars3d/objects)
endif()

# =============================================================================
# Doxygen
# =============================================================================

find_package(Doxygen)
if(DOXYGEN_FOUND)
    set(DOXYGEN_IN  ${CMAKE_CURRENT_SOURCE_DIR}/Doxyfile)
    set(DOXYGEN_OUT ${CMAKE_CURRENT_BINARY_DIR}/Doxyfile)
    configure_file(${DOXYGEN_IN} ${DOXYGEN_OUT} @ONLY)
    add_custom_target(GalaxyEggbertDocs
            COMMAND ${DOXYGEN_EXECUTABLE} ${DOXYGEN_OUT}
            WORKING_DIRECTORY ${CMAKE_CURRENT_BINARY_DIR}
            COMMENT "Generating API documentation with Doxygen"
            VERBATIM
    )
endif()

# =============================================================================
# Tests  (engine-independent — pure C++ data model, no graphics)
# =============================================================================

if(BUILD_TESTING AND NOT ANDROID AND NOT EMSCRIPTEN)
    include(FetchContent)

    if(NOT TARGET GTest::gtest_main)
        if(NOT TARGET gtest_main)
            FetchContent_Declare(
                    googletest
                    URL https://github.com/google/googletest/archive/refs/tags/v1.14.0.zip
            )
            set(gtest_force_shared_crt ON CACHE BOOL "" FORCE)
            FetchContent_MakeAvailable(googletest)
        endif()

        if(TARGET gtest AND NOT TARGET GTest::gtest)
            add_library(GTest::gtest ALIAS gtest)
        endif()
        if(TARGET gtest_main AND NOT TARGET GTest::gtest_main)
            add_library(GTest::gtest_main ALIAS gtest_main)
        endif()
    endif()

    add_executable(GalaxyEggbertWorldsTests
            tests/GalaxyEggbert/Worlds/BlockTests.cpp
            tests/GalaxyEggbert/Worlds/BitPackingTests.cpp
            tests/GalaxyEggbert/Worlds/ChunkTests.cpp
            tests/GalaxyEggbert/Worlds/WorldTests.cpp
            tests/GalaxyEggbert/Worlds/BlockMetadataTest.cpp
            tests/GalaxyEggbert/MoveObjectRecordTests.cpp
            tests/GalaxyEggbert/BigDecorRecordTests.cpp
            tests/GalaxyEggbert/HintZoneRecordTests.cpp
            tests/GalaxyEggbert/HubDoorsTests.cpp

            src/GalaxyEggbert/Worlds/BinaryIO.cpp
            src/GalaxyEggbert/Worlds/BitPacking.cpp
            src/GalaxyEggbert/Worlds/Chunk.cpp
            src/GalaxyEggbert/Worlds/World.cpp
            src/GalaxyEggbert/MoveObjectRecord.cpp
            src/GalaxyEggbert/BigDecorRecord.cpp
            src/GalaxyEggbert/HintZoneRecord.cpp
            src/GalaxyEggbert/HubDoors.cpp
    )

    target_include_directories(GalaxyEggbertWorldsTests PRIVATE
            ${CMAKE_CURRENT_SOURCE_DIR}/include
    )

    target_link_libraries(GalaxyEggbertWorldsTests PRIVATE GTest::gtest_main)

    include(GoogleTest)
    gtest_discover_tests(GalaxyEggbertWorldsTests)
endif()
