The milestone needs a second input. The AI is a client, not part of the sim: it decides once,
on one machine, and its decisions reach the server as commands. A save carries the board and
half the input, which is why our turn wrote ModCount 14 where the original writes 24 -- the
missing ten ARE the turn's command stream.
* `game/ai/apply_order` -- the thirty-step schedule the original drains a batch in: twenty-
seven per-LIST steps (every player's elements of one list before the next list starts) with
three per-PLAYER gate loops spliced in at step 10, 29 and 30. Neither list order nor member
order, and both facts are asserted so a port that sorted cannot pass.
* `game/ai/command_capture` -- a `.tcb` recorded turn: gates, list lengths, elements in wire
order, per-client seeds, and `?` for a field the instrument could not read. An element count
that disagrees with its declaration is REJECTED, because a counter quietly one short is
indistinguishable from a turn that issued one fewer command.
* `app/command_replay` -- applies it before the drivers, where the End-Turn dispatcher does.
Every command is CHARGED; only the ones whose subsystem we hold are APPLIED; the rest are
declined with the named gap, or marked incomplete when the capture itself lacks the payload.
* `--turn-commands`, `--replay-count-only`, `--replay-recorded-names`, `--ai-seed`.
Measured on a fresh build directory, canonical pair turn2-state -> turn3-state:
108 -> 62, closed 46, regressed 0 (was 108 -> 63, closed 45) -- /Sim/ModCount now reads the
original's 24, decomposed as 2 drivers + 4 research-rate gates + build + rates + list 10 +
two list-14 + fleet move, with the list-23 population element free.
turn1-state replayed against the SAME run's autosave closes 7 (ModCount and all six research
leaves); against the historical turn2-state it closes 6 and leaves player 512's research pick
diverging -- which is correct, because that recording is from a process that picked differently.
One prediction was falsified and it paid for itself: the first run regressed two leaves because
the rates element's MEMORY field order is not its wire order. The converter no longer claims a
mapping it cannot support.
Two new addresses (the second and third gate-loop heads) via ghidra/addresses.d/lane-rb.json;
header regenerated, never hand-resolved.
35 lines
1.3 KiB
CMake
35 lines
1.3 KiB
CMake
# src/app — the standalone: load a save, run one strategic turn, write a save.
|
|
# Included from the root CMakeLists.txt via add_subdirectory(src/app).
|
|
#
|
|
# The turn logic is a library so the test can drive it in process; `sots_turn` is the CLI.
|
|
|
|
add_library(sots_app STATIC
|
|
alliance.cpp
|
|
phase_catalog.cpp
|
|
trade_raid.cpp
|
|
treaty.cpp
|
|
turn_record.cpp
|
|
construction_phase.cpp
|
|
event_phase.cpp
|
|
growth_phase.cpp
|
|
script_phase.cpp
|
|
visibility_phase.cpp
|
|
command_replay.cpp
|
|
turn.cpp
|
|
report.cpp)
|
|
target_include_directories(sots_app PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/..)
|
|
# game_design (and game_data under it) is here for one phase only: the tail's turn record
|
|
# needs a design's hull size and defence-platform flag, which are recomputed from the section
|
|
# catalog and are nowhere on the wire. No game data is embedded; the root is supplied at run
|
|
# time and its absence costs exactly six fields.
|
|
target_link_libraries(sots_app PUBLIC mars_stream mars_rng sots_game_sim sots_game_events sots_game_ai game_design)
|
|
target_compile_features(sots_app PUBLIC cxx_std_17)
|
|
if(NOT MSVC)
|
|
target_compile_options(sots_app PRIVATE -Wall -Wextra -Werror)
|
|
endif()
|
|
|
|
add_executable(sots_turn main.cpp)
|
|
target_link_libraries(sots_turn PRIVATE sots_app)
|
|
if(NOT MSVC)
|
|
target_compile_options(sots_turn PRIVATE -Wall -Wextra -Werror)
|
|
endif()
|