sots-engine/CMakeLists.txt
alex 39c01422f7 src/app: the standalone -- load a save, run a turn, write a save
`sots_turn` loads a save through the engine's own reader, walks the published
phase order of all three turn drivers, runs what we hold, prints what we do
not, and writes the result back through the engine's own writer.

The phase catalog carries all 32 + 12 + 37 phases whether or not they are
implemented, so an unimplemented phase is a named no-op that appears in the run
log rather than a silent absence. 14 of the 44 turn-driver phases are modelled,
7 commit anything, 2 of the 37 tail phases are modelled.

Modelled but NOT committed is a first-class state. A phase whose formula we hold
and whose inputs we do not is evaluated, reported, and left unwritten unless
--commit-blocked is passed. That distinction was earned: committing phase 31's
player-status restore regressed two leaves that had agreed with the oracle
before the turn, because the phase writes 1 and the file carries 4.

Measured against the game's own post-turn saves, leaves localised by
state_checksum.py with coverage proved by re-serialisation:

  turn1-state -> turn2-state   209 -> 204 diverging, closed 5, regressed 0
  turn2-state -> turn3-state   108 -> 103 diverging, closed 5, regressed 0

Two tests: app_catalog (the tables stay complete and nothing claims to be
verified against a live game) and app_turn (11 saves driven; an untouched load
re-serialises byte-identically, a turn leaves the file re-readable, and no
blocked or stub phase writes anything). Skips cleanly without SOTS_SAVES_DIR.

ctest 38/38, clean-room OK. src/shim untouched. docs/S-standalone.md has the
full gap list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 10:35:45 -04:00

117 lines
6.6 KiB
CMake

cmake_minimum_required(VERSION 3.25)
project(sots-engine LANGUAGES C CXX)
set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_EXTENSIONS OFF)
# Build id stamped into the shim log banner (tools/build-shim.sh passes git describe + UTC time).
set(SHIM_BUILD_ID "dev" CACHE STRING "Build identifier logged by the shim")
# Provenance of the binary facts: the generated header's own "Source:" line, forwarded verbatim
# so every shim.log records which RE snapshot the RVAs came from.
file(STRINGS include/generated/sots_addresses.h _addr_provenance REGEX "^// Source:")
list(GET _addr_provenance 0 _addr_provenance)
string(REGEX REPLACE "^// " "" SOTS_ADDR_PROVENANCE "${_addr_provenance}")
add_library(sots_addresses INTERFACE)
target_include_directories(sots_addresses INTERFACE ${CMAKE_SOURCE_DIR}/include)
# ---- engine modules (host + cross): pure, oracle-verified code accrues here ----
add_subdirectory(src/mars/parse) # brace-block + .effect readers (lib mars_parse)
add_subdirectory(src/mars/text) # flat-kv, id-manifest, csv (lib mars_text)
add_subdirectory(src/game/sim) # strategic formulas, pure (lib sots_game_sim)
add_subdirectory(src/mars/vfs) # .gob ZIP reader + native override (lib mars_vfs, miniz)
add_subdirectory(src/mars/stream) # Streamable save format + gzip (lib mars_stream)
add_subdirectory(src/mars/rng) # MT19937 (lib mars_rng)
add_subdirectory(src/game/config) # flat KEY/value constants loader (lib sots_game_config)
add_subdirectory(src/game/data) # typed catalogs on mars/parse+text (lib game_data)
add_subdirectory(src/game/effects) # tech effects (TechId table + apply) (lib game_effects)
add_subdirectory(src/game/design) # ship-design rules + derived stats (lib game_design)
add_subdirectory(src/game/events) # player event log + research events (lib sots_game_events)
add_subdirectory(src/app) # the standalone turn driver (lib sots_app, sots_turn)
# ---- shim trace/compare infrastructure (host-testable; linked into binkw32) ----
add_library(shim_trace STATIC
src/shim/trace/sha256.cpp
src/shim/trace/emitter.cpp
src/shim/trace/tracer.cpp
src/shim/trace/selftest.cpp)
target_include_directories(shim_trace PUBLIC ${CMAKE_SOURCE_DIR}/src)
target_compile_options(shim_trace PRIVATE -Wall -Wextra -Werror)
if(MINGW)
target_compile_definitions(shim_trace PUBLIC __USE_MINGW_ANSI_STDIO=1) # C99 %lld/%.17g
endif()
# ---- B1 budget adapter: ServerPlayer snapshot <-> game::sim economy (pure, host-tested) ----
add_library(shim_budget STATIC src/shim/hooks/budget_inputs.cpp)
target_link_libraries(shim_budget PUBLIC shim_trace sots_game_sim)
target_compile_options(shim_budget PRIVATE -Wall -Wextra -Werror)
# ---- B2 tech-effect adapter: ServerPlayer fields <-> game::effects state (pure, host-tested) ----
add_library(shim_techfx STATIC src/shim/hooks/tech_effect_fields.cpp)
target_link_libraries(shim_techfx PUBLIC shim_trace sots_addresses sots_game_effects)
target_compile_options(shim_techfx PRIVATE -Wall -Wextra -Werror)
# ---- B4 colony adapter: ServerSystem snapshot <-> game::sim colony turn (pure, host-tested) ----
add_library(shim_colony STATIC src/shim/hooks/colony_inputs.cpp)
target_link_libraries(shim_colony PUBLIC shim_trace sots_game_sim)
target_compile_options(shim_colony PRIVATE -Wall -Wextra -Werror)
# ---- B4 movement adapter: StarFleet snapshot <-> game::sim movement (pure, host-tested) ----
add_library(shim_movement STATIC src/shim/hooks/movement_inputs.cpp)
target_link_libraries(shim_movement PUBLIC shim_trace sots_game_sim)
target_compile_options(shim_movement PRIVATE -Wall -Wextra -Werror)
# ---- T turn-driver adapter: ServerPlayer snapshot <-> the driver's own writes (pure, host-tested) ----
add_library(shim_player_turn STATIC src/shim/hooks/player_turn_inputs.cpp)
target_link_libraries(shim_player_turn PUBLIC shim_trace)
target_compile_options(shim_player_turn PRIVATE -Wall -Wextra -Werror)
# ---- P events adapter: live Game::EventStorage <-> sots::events model (pure, host-tested) ----
add_library(shim_events STATIC src/shim/hooks/event_inputs.cpp)
target_link_libraries(shim_events PUBLIC shim_trace sots_addresses sots_game_events)
target_compile_options(shim_events PRIVATE -Wall -Wextra -Werror)
if(WIN32)
# ---- shim: proxy binkw32.dll that the original game loads (Phase 2 frontend) ----
add_library(minhook STATIC
third_party/minhook/src/buffer.c
third_party/minhook/src/hook.c
third_party/minhook/src/trampoline.c
third_party/minhook/src/hde/hde32.c)
target_include_directories(minhook PUBLIC third_party/minhook/include)
# ---- hooks: one descriptor per hooked game function (src/shim/hooks/*) ----
add_library(shim_hooks STATIC src/shim/hooks/global_consts.cpp src/shim/hooks/dictionaries.cpp
src/shim/hooks/research.cpp
src/shim/hooks/tech_effects.cpp
src/shim/hooks/compute_budget.cpp
src/shim/hooks/colony_turn.cpp
src/shim/hooks/fleet_movement.cpp
src/shim/hooks/player_turn.cpp)
target_link_libraries(shim_hooks PUBLIC shim_trace sots_addresses sots_game_config sots_game_sim
sots_game_effects mars_rng shim_budget shim_techfx
shim_colony shim_movement shim_events
shim_player_turn)
target_compile_options(shim_hooks PRIVATE -Wall -Wextra -Werror)
add_library(binkw32 SHARED src/shim/main.cpp src/shim/fpu_force.cpp src/shim/binkw32.def)
target_link_libraries(binkw32 PRIVATE minhook sots_addresses shim_trace shim_hooks)
target_compile_definitions(binkw32 PRIVATE
SHIM_BUILD_ID="${SHIM_BUILD_ID}"
SOTS_ADDR_PROVENANCE="${SOTS_ADDR_PROVENANCE}")
# Must be exactly binkw32.dll (no "lib" prefix); export names come solely from the .def.
set_target_properties(binkw32 PROPERTIES PREFIX "" OUTPUT_NAME "binkw32")
else()
# ---- host: engine module tests (ctest) + a header smoke test ----
enable_testing()
add_executable(addr_smoke tests/addr_smoke.cpp)
target_link_libraries(addr_smoke PRIVATE sots_addresses)
add_test(NAME addr_smoke COMMAND addr_smoke)
foreach(_t mars_parse game_config game_data game_design game_sim mars_stream mars_text mars_vfs shim_trace game_effects game_events shim_budget shim_techfx shim_colony shim_movement shim_events shim_player_turn app)
if(EXISTS ${CMAKE_SOURCE_DIR}/tests/${_t}/CMakeLists.txt)
add_subdirectory(tests/${_t})
endif()
endforeach()
endif()