cmake_minimum_required(VERSION 3.21)

project(gldoze VERSION 0.1.0 LANGUAGES CXX C)

set(CMAKE_CXX_STANDARD 23)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_EXPORT_COMPILE_COMMANDS ON)

set(GLDOZE_REPO_ROOT "${CMAKE_CURRENT_SOURCE_DIR}")

option(GLDOZE_DEV_WARNINGS "Enable baseline compiler warnings for local dev builds" ON)
option(GLDOZE_BUILD_DEMO "Build the bundled demo application" ON)
option(GLDOZE_BUILD_MAIL "Build GLMail support for embedding in gldoze_desktop" ON)
option(GLDOZE_BUILD_TECHLOBBY "Build the standalone TechLobby application" ON)
option(GLDOZE_BUILD_DESKTOP "Build the optional GLDoze desktop shell" ${UNIX})
option(GLDOZE_BUILD_MESH_SPLITTER "Build the standalone humanoid mesh splitter" ON)
option(GLDOZE_BUILD_ASSET_LIBRARY "Build the standalone GLDoze Asset Library" ON)
if(UNIX AND NOT APPLE)
  set(GLDOZE_ENABLE_X11_DEFAULT ON)
else()
  set(GLDOZE_ENABLE_X11_DEFAULT OFF)
endif()
option(GLDOZE_ENABLE_X11 "Build the X11 desktop platform backend" ${GLDOZE_ENABLE_X11_DEFAULT})
option(GLDOZE_ENABLE_WAYLAND "Build the native wlroots Wayland compositor backend" OFF)
option(GLDOZE_BUILD_BUILTIN_PLUGINS "Build first-party applications as runtime plugins" ON)
option(GLDOZE_MINIMAL_TECHLOBBY "Trim TechLobby-only shell/media dependencies" OFF)
option(GLDOZE_USE_PKGCONFIG_DEPS "Resolve third-party dependencies through pkg-config" ON)
option(GLDOZE_ENABLE_PIPEWIRE "Enable PipeWire audio output where available" ON)
option(GLDOZE_ENABLE_DEBUG_DRAW "Enable Verdant engine debug drawing" ON)
option(GLDOZE_ENABLE_LUAJIT "Enable optional LuaJIT developer-console scripting when available" ON)
option(VERDANT_BUILD_GLDOZE_PLUGIN "Build the Verdant GLDoze plugin" ON)
option(VERDANT_BUILD_STANDALONE "Build the standalone Verdant executable" ON)
set(GLDOZE_CEF_VERSION "150.0.11+gb887805+chromium-150.0.7871.115" CACHE STRING
  "Pinned CEF binary distribution version")
set(CEF_ROOT "" CACHE PATH "Path to the pinned CEF binary distribution")

# A configured CEF distribution is an explicit request to make the browser
# part of the desktop.  Keep CEF optional on machines that do not have the
# pinned distribution, while preventing a valid CEF checkout from silently
# producing a launcher with no Browser entry.
set(GLDOZE_ENABLE_CEF_BROWSER_DEFAULT OFF)
if(CEF_ROOT OR (DEFINED ENV{CEF_ROOT} AND NOT "$ENV{CEF_ROOT}" STREQUAL ""))
  set(GLDOZE_ENABLE_CEF_BROWSER_DEFAULT ON)
endif()
option(GLDOZE_ENABLE_CEF_BROWSER
  "Build the Chromium Embedded Framework browser integration"
  ${GLDOZE_ENABLE_CEF_BROWSER_DEFAULT})

if(GLDOZE_ENABLE_CEF_BROWSER)
  if(NOT UNIX OR APPLE)
    message(FATAL_ERROR "The Stage 1 CEF browser integration currently supports Linux only.")
  endif()
  include(cmake/GLDozeCEF.cmake)
endif()

find_package(Threads REQUIRED)
find_package(OpenGL REQUIRED)
find_package(ZLIB REQUIRED)
find_package(PNG REQUIRED)
if(GLDOZE_USE_PKGCONFIG_DEPS)
  find_package(PkgConfig REQUIRED)
endif()
if(GLDOZE_MINIMAL_TECHLOBBY)
  if(GLDOZE_USE_PKGCONFIG_DEPS)
    pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
    pkg_check_modules(SDL3_IMAGE REQUIRED IMPORTED_TARGET sdl3-image)
    if(NOT SDL3_IMAGE_FOUND)
      message(FATAL_ERROR "SDL3_image is required")
    endif()
    pkg_check_modules(SDL3_TTF REQUIRED IMPORTED_TARGET sdl3-ttf)
    if(NOT SDL3_TTF_FOUND)
      message(FATAL_ERROR "SDL3_ttf is required")
    endif()
    set(GLDOZE_ENABLE_RSVG OFF CACHE BOOL "Enable SVG/rsvg image loading" FORCE)
  else()
    find_package(SDL3 CONFIG REQUIRED)
    find_package(SDL3_image CONFIG REQUIRED)
    find_package(SDL3_ttf CONFIG REQUIRED)
    set(GLDOZE_ENABLE_RSVG OFF CACHE BOOL "Enable SVG/rsvg image loading" FORCE)
  endif()
else()
  find_package(LibDataChannel QUIET)
  if(GLDOZE_USE_PKGCONFIG_DEPS)
    pkg_check_modules(LIBDATACHANNEL QUIET IMPORTED_TARGET LibDataChannel)
    if(NOT LIBDATACHANNEL_FOUND)
      pkg_check_modules(LIBDATACHANNEL QUIET IMPORTED_TARGET libdatachannel)
    endif()

    pkg_check_modules(SDL3 REQUIRED IMPORTED_TARGET sdl3)
    pkg_check_modules(SDL3_IMAGE REQUIRED IMPORTED_TARGET sdl3-image)
    if(NOT SDL3_IMAGE_FOUND)
      message(FATAL_ERROR "SDL3_image is required")
    endif()
    pkg_check_modules(SDL3_TTF REQUIRED IMPORTED_TARGET sdl3-ttf)
    if(NOT SDL3_TTF_FOUND)
      message(FATAL_ERROR "SDL3_ttf is required")
    endif()
    pkg_check_modules(AVCODEC REQUIRED IMPORTED_TARGET libavcodec)
    pkg_check_modules(AVFORMAT REQUIRED IMPORTED_TARGET libavformat)
    pkg_check_modules(AVUTIL REQUIRED IMPORTED_TARGET libavutil)
    pkg_check_modules(SWSCALE REQUIRED IMPORTED_TARGET libswscale)
    pkg_check_modules(SWRESAMPLE REQUIRED IMPORTED_TARGET libswresample)
    pkg_check_modules(ARCHIVE REQUIRED IMPORTED_TARGET libarchive)
    pkg_check_modules(RSVG REQUIRED IMPORTED_TARGET librsvg-2.0)
    pkg_check_modules(GIO QUIET IMPORTED_TARGET gio-2.0)
    pkg_check_modules(GLDOZE_LIBSSH2 QUIET IMPORTED_TARGET libssh2)
    if(GLDOZE_ENABLE_PIPEWIRE)
      pkg_check_modules(PIPEWIRE QUIET IMPORTED_TARGET libpipewire-0.3)
      if(NOT PIPEWIRE_FOUND)
        message(STATUS "PipeWire not found: disabling PipeWire-backed audio output")
        set(GLDOZE_ENABLE_PIPEWIRE OFF CACHE BOOL "Enable PipeWire audio output where available" FORCE)
      endif()
    endif()
  else()
    find_package(SDL3 CONFIG REQUIRED)
    find_package(SDL3_image CONFIG REQUIRED)
    find_package(SDL3_ttf CONFIG REQUIRED)
    find_package(FFMPEG REQUIRED COMPONENTS AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
    find_package(LibArchive REQUIRED)
    find_package(unofficial-librsvg CONFIG REQUIRED)
    set(GLDOZE_ENABLE_PIPEWIRE OFF CACHE BOOL "Enable PipeWire audio output where available" FORCE)
    foreach(_ffmpeg_component AVCODEC AVFORMAT AVUTIL SWSCALE SWRESAMPLE)
      if(NOT TARGET FFMPEG::${_ffmpeg_component})
        add_library(FFMPEG::${_ffmpeg_component} INTERFACE IMPORTED)
        target_include_directories(FFMPEG::${_ffmpeg_component} INTERFACE ${FFMPEG_INCLUDE_DIRS})
        target_link_directories(FFMPEG::${_ffmpeg_component} INTERFACE ${FFMPEG_LIBRARY_DIRS})
        target_link_libraries(FFMPEG::${_ffmpeg_component} INTERFACE ${FFMPEG_LIBRARIES})
      endif()
    endforeach()
  endif()
endif()

if(TARGET PkgConfig::RSVG OR TARGET unofficial::librsvg::librsvg)
  set(GLDOZE_ENABLE_RSVG ON CACHE BOOL "Enable SVG/rsvg image loading" FORCE)
endif()

if(TARGET SDL3_image::SDL3_image-static AND NOT TARGET SDL3_image::SDL3_image)
  add_library(SDL3_image::SDL3_image INTERFACE IMPORTED)
  set_target_properties(SDL3_image::SDL3_image PROPERTIES
    INTERFACE_LINK_LIBRARIES SDL3_image::SDL3_image-static
  )
endif()

if(TARGET SDL3_ttf::SDL3_ttf-static AND NOT TARGET SDL3_ttf::SDL3_ttf)
  add_library(SDL3_ttf::SDL3_ttf INTERFACE IMPORTED)
  set_target_properties(SDL3_ttf::SDL3_ttf PROPERTIES
    INTERFACE_LINK_LIBRARIES SDL3_ttf::SDL3_ttf-static
  )
endif()

function(gldoze_strip_link_item target_name item_name)
  if(TARGET "${target_name}")
    get_target_property(_gldoze_link_libs "${target_name}" INTERFACE_LINK_LIBRARIES)
    if(_gldoze_link_libs)
      set(_gldoze_filtered_link_libs "")
      foreach(_gldoze_link_item IN LISTS _gldoze_link_libs)
        set(_gldoze_drop_item OFF)
        if(_gldoze_link_item STREQUAL "${item_name}" OR _gldoze_link_item STREQUAL "$<LINK_ONLY:${item_name}>")
          set(_gldoze_drop_item ON)
        endif()
        if(item_name STREQUAL "m")
          get_filename_component(_gldoze_link_basename "${_gldoze_link_item}" NAME)
          if(_gldoze_link_basename MATCHES "^libm\\.(a|so|dylib)$")
            set(_gldoze_drop_item ON)
          endif()
        endif()
        if(NOT _gldoze_drop_item)
          list(APPEND _gldoze_filtered_link_libs "${_gldoze_link_item}")
        endif()
      endforeach()
      set(_gldoze_link_libs "${_gldoze_filtered_link_libs}")
      set_target_properties("${target_name}" PROPERTIES INTERFACE_LINK_LIBRARIES "${_gldoze_link_libs}")
    endif()
  endif()
endfunction()

if(WIN32)
  gldoze_strip_link_item(SDL3::SDL3-static m)
  gldoze_strip_link_item(SDL3::SDL3 m)
  gldoze_strip_link_item(PkgConfig::SDL3 m)
  gldoze_strip_link_item(PkgConfig::SDL3_IMAGE m)
  gldoze_strip_link_item(PkgConfig::SDL3_TTF m)
endif()

add_subdirectory(framework)

# Shared Verdant implementation.  The legacy native reference remains owned by
# the framework targets below; these targets are the canonical implementation
# used by both host adapters.
add_subdirectory(verdant)

include(CTest)

if(UNIX AND NOT APPLE)
  add_subdirectory(remote)
endif()

if(GLDOZE_BUILD_MESH_SPLITTER)
  add_subdirectory(apps/mesh_splitter)
endif()

if(GLDOZE_BUILD_ASSET_LIBRARY)
  add_subdirectory(apps/asset_library)
endif()

if(BUILD_TESTING AND GLDOZE_BUILD_TECHLOBBY AND UNIX AND NOT APPLE)
	add_executable(lobbyclient_shutdown_test
		tests/lobbyclient_shutdown_test.cpp
		${GLDOZE_REPO_ROOT}/lobbyclient.cpp
		${GLDOZE_REPO_ROOT}/lobbyprotocol.cpp)
	target_include_directories(lobbyclient_shutdown_test PRIVATE
		"${GLDOZE_REPO_ROOT}")
	target_link_libraries(lobbyclient_shutdown_test PRIVATE Threads::Threads)
	target_compile_options(lobbyclient_shutdown_test PRIVATE
		"$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
	add_test(NAME lobbyclient_shutdown COMMAND lobbyclient_shutdown_test)

	add_executable(techlobby_ring_audio_test
		tests/techlobby_ring_audio_test.cpp
		techlobby_ring_audio.cpp)
	target_include_directories(techlobby_ring_audio_test PRIVATE
		"${GLDOZE_REPO_ROOT}")
	target_link_libraries(techlobby_ring_audio_test PRIVATE PkgConfig::SDL3)
	target_compile_options(techlobby_ring_audio_test PRIVATE
		"$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
	add_test(NAME techlobby_ring_audio COMMAND techlobby_ring_audio_test)

  add_executable(battle_rejoin_test
    tests/battle_rejoin_test.cpp)
  target_include_directories(battle_rejoin_test PRIVATE
    "${GLDOZE_REPO_ROOT}")
  add_test(NAME battle_rejoin COMMAND battle_rejoin_test)

  add_executable(contentsyncmanager_sequence_test
    tests/contentsyncmanager_sequence_test.cpp)
  target_link_libraries(contentsyncmanager_sequence_test PRIVATE gldoze_widgets)
  add_test(NAME contentsyncmanager_sequence
    COMMAND contentsyncmanager_sequence_test)
endif()

if(BUILD_TESTING)
  add_executable(jobsystem_main_thread_budget_test
    tests/jobsystem_main_thread_budget_test.cpp jobsystem.cpp)
  target_include_directories(jobsystem_main_thread_budget_test PRIVATE
    ${GLDOZE_REPO_ROOT})
  target_link_libraries(jobsystem_main_thread_budget_test PRIVATE Threads::Threads)
  add_test(NAME jobsystem_main_thread_budget COMMAND jobsystem_main_thread_budget_test)

  add_executable(verdant_core_test verdant/tests/core_test.cpp)
  target_link_libraries(verdant_core_test PRIVATE verdant_core)
  add_test(NAME verdant_core COMMAND verdant_core_test)

  add_executable(verdant_rapid_filesystem_test
    verdant/tests/rapid_filesystem_test.cpp)
  target_link_libraries(verdant_rapid_filesystem_test PRIVATE verdant_core)
  add_test(NAME verdant_rapid_filesystem COMMAND verdant_rapid_filesystem_test)

  add_executable(verdant_app_lifecycle_test verdant/tests/application_lifecycle_test.cpp)
  target_link_libraries(verdant_app_lifecycle_test PRIVATE verdant_app)
  add_test(NAME verdant_app_lifecycle COMMAND verdant_app_lifecycle_test)

  if(VERDANT_BUILD_STANDALONE)
    add_executable(verdant_standalone_host_test
      verdant/tests/standalone_host_test.cpp)
    target_link_libraries(verdant_standalone_host_test
      PRIVATE verdant_standalone_host)
    add_test(NAME verdant_standalone_host
      COMMAND verdant_standalone_host_test)
  endif()

endif()

if(GLDOZE_BUILD_DEMO)
  add_subdirectory(apps/demo)
endif()

if(GLDOZE_BUILD_MAIL)
  add_subdirectory(apps/mail)
endif()

if(GLDOZE_BUILD_TECHLOBBY)
  add_subdirectory(apps/techlobby)
endif()

if(GLDOZE_BUILD_DESKTOP)
  if(GLDOZE_BUILD_BUILTIN_PLUGINS)
    add_subdirectory(apps/plugins/fps_meter)
    add_subdirectory(apps/plugins/color_picker)
    add_subdirectory(apps/plugins/gpu_monitor)
    add_subdirectory(apps/plugins/image_viewer)
    add_subdirectory(apps/plugins/logs)
    add_subdirectory(apps/plugins/remote_systems)
    add_subdirectory(apps/plugins/keys)
    add_subdirectory(apps/plugins/terminal)
    add_subdirectory(apps/plugins/gledit)
    add_subdirectory(apps/plugins/files)
	add_subdirectory(apps/plugins/audio_editor_preview)
	add_subdirectory(apps/plugins/render_test)
	add_subdirectory(apps/plugins/obj_viewer)
	add_subdirectory(apps/plugins/gallery_preview)
	add_subdirectory(apps/plugins/audio_cd_ripper_preview)
	add_subdirectory(apps/plugins/map_preview_test)
	add_subdirectory(apps/plugins/presentations)
	if(VERDANT_BUILD_GLDOZE_PLUGIN)
	  add_subdirectory(apps/plugins/verdant)
	endif()
	add_subdirectory(apps/plugins/gldeck)
	add_subdirectory(apps/plugins/profiler)
  endif()
  add_subdirectory(apps/desktop)
  if(TARGET gldoze AND TARGET gldoze_desktop_support)
    target_link_libraries(gldoze PRIVATE gldoze_desktop_support)
    # The engine contains the recording hooks and GLTop is also part of the
    # demo support archive. Put the concrete desktop implementations after the
    # engine in the final static link order so GNU ld can resolve those hooks.
    target_link_libraries(gldoze PRIVATE
      gldoze_engine
      gldoze_desktop_media
      gldoze_desktop_support)
  endif()
  if(NOT GLDOZE_MINIMAL_TECHLOBBY AND
     TARGET techlobby_app_support AND TARGET gldoze_desktop_support)
    target_link_libraries(techlobby_app_support PUBLIC gldoze_desktop_support)
    target_link_libraries(TechLobbyWin PRIVATE
      gldoze_engine
      gldoze_desktop_media
      gldoze_desktop_support)
    if(TARGET TechLobbyLinux)
      target_link_libraries(TechLobbyLinux PRIVATE
        gldoze_engine
        gldoze_desktop_media
        gldoze_desktop_support)
    endif()
  endif()
endif()

if(BUILD_TESTING AND GLDOZE_BUILD_DEMO)
  add_executable(patrol_commands_test tests/patrol_commands_test.cpp)
  target_link_libraries(patrol_commands_test PRIVATE gldoze_foundation)
  add_test(NAME patrol_commands COMMAND patrol_commands_test)
  add_executable(flamethrower_test tests/flamethrower_test.cpp)
  # springmap_renderer.cpp is part of gldoze_widgets, while its
  # ImmediateBatch implementation is provided by gldoze_engine.
  target_link_libraries(flamethrower_test PRIVATE gldoze_widgets gldoze_engine)
  add_test(NAME flamethrower COMMAND flamethrower_test)
  add_executable(plant_database_test tests/plant_database_test.cpp)
  target_link_libraries(plant_database_test PRIVATE gldoze_foundation)
  # Tests use assert() for their checks. RelWithDebInfo defines NDEBUG, which
  # otherwise turns this test into unchecked code that can dereference an
  # empty result after a failed setup operation.
  target_compile_options(plant_database_test PRIVATE
    "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
  add_test(NAME plant_database COMMAND plant_database_test)
  add_executable(recorder_cursor_test tests/recorder_cursor_test.cpp)
  target_link_libraries(recorder_cursor_test PRIVATE gldoze_foundation)
  add_test(NAME recorder_cursor COMMAND recorder_cursor_test)
endif()

if(BUILD_TESTING AND GLDOZE_BUILD_DESKTOP)
	add_executable(gldeck_playlist_model_test tests/gldeck_playlist_model_test.cpp)
	target_link_libraries(gldeck_playlist_model_test PRIVATE gldoze_gldeck_model)
	target_compile_options(gldeck_playlist_model_test PRIVATE
	  "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
	add_test(NAME gldeck_playlist_model COMMAND gldeck_playlist_model_test)
	add_executable(unicode_layout_test tests/unicode_layout_test.cpp)
	target_include_directories(unicode_layout_test PRIVATE ${GLDOZE_REPO_ROOT})
	target_compile_options(unicode_layout_test PRIVATE
	  "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
	add_test(NAME unicode_layout COMMAND unicode_layout_test)
  add_executable(keyboard_top_level_test tests/keyboard_top_level_test.cpp)
  target_include_directories(keyboard_top_level_test PRIVATE ${GLDOZE_REPO_ROOT})
  if(TARGET PkgConfig::SDL3)
    target_link_libraries(keyboard_top_level_test PRIVATE PkgConfig::SDL3)
  elseif(TARGET SDL3::SDL3)
    target_link_libraries(keyboard_top_level_test PRIVATE SDL3::SDL3)
  endif()
  target_compile_options(keyboard_top_level_test PRIVATE
    "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
  add_test(NAME keyboard_top_level COMMAND keyboard_top_level_test)
  add_executable(context_menu_keyboard_test tests/context_menu_keyboard_test.cpp)
  target_include_directories(context_menu_keyboard_test PRIVATE ${GLDOZE_REPO_ROOT})
  if(TARGET PkgConfig::SDL3)
    target_link_libraries(context_menu_keyboard_test PRIVATE PkgConfig::SDL3)
  elseif(TARGET SDL3::SDL3)
    target_link_libraries(context_menu_keyboard_test PRIVATE SDL3::SDL3)
  endif()
  target_compile_options(context_menu_keyboard_test PRIVATE
    "$<$<CXX_COMPILER_ID:GNU,Clang,AppleClang>:-UNDEBUG>")
  add_test(NAME context_menu_keyboard COMMAND context_menu_keyboard_test)
endif()
if(BUILD_TESTING AND GLDOZE_BUILD_DEMO)
  find_package(Python3 COMPONENTS Interpreter QUIET)
  if(Python3_Interpreter_FOUND)
    add_test(
      NAME window4_regression_preflight
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/window4_regression.py --json
    )
    set_tests_properties(window4_regression_preflight PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME project_session_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/project_session_regression.py --json
    )
    set_tests_properties(project_session_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME ui_contract_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/ui_contract_regression.py
        --build-dir ${CMAKE_BINARY_DIR} --json
    )
    set_tests_properties(ui_contract_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME layout_contract_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/layout_contract_regression.py --json
    )
    set_tests_properties(layout_contract_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME interaction_routing_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/interaction_routing_regression.py --json
    )
    set_tests_properties(interaction_routing_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME widget_state_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/widget_state_regression.py --json
    )
    set_tests_properties(widget_state_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME window_state_fixture_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/window_state_fixture_regression.py --json
    )
    set_tests_properties(window_state_fixture_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    add_test(
      NAME json_atomic_write_regression
      COMMAND ${Python3_EXECUTABLE} ${GLDOZE_REPO_ROOT}/scripts/json_atomic_write_regression.py --json
    )
    set_tests_properties(json_atomic_write_regression PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
    # OpenGL compatibility symbol scanner — must fail until migration complete
    add_test(
      NAME gl_compatibility_check
      COMMAND bash ${GLDOZE_REPO_ROOT}/scripts/gl_compatibility_check.sh
    )
    set_tests_properties(gl_compatibility_check PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
      ENVIRONMENT "GLDOZE_BUILD_DIR=${CMAKE_BINARY_DIR}"
    )
    # Scanner self-test — verifies forbidden/allowed fixture detection
    add_test(
      NAME gl_compatibility_check_selftest
      COMMAND bash ${GLDOZE_REPO_ROOT}/scripts/gl_compatibility_check_selftest.sh
    )
    set_tests_properties(gl_compatibility_check_selftest PROPERTIES
      WORKING_DIRECTORY ${GLDOZE_REPO_ROOT}
    )
  else()
    message(WARNING "Python3 interpreter not found: skipping window4_regression_preflight test")
  endif()
endif()
