`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
90 lines
4.3 KiB
C++
90 lines
4.3 KiB
C++
// The strategic turn, as a catalog of named phases.
|
|
//
|
|
// The turn is not one function. It is three drivers running in a fixed order, and the
|
|
// campaign has read all three out of the instruction stream:
|
|
//
|
|
// * `StrategyServer::ProcessTurn` -- 32 phases, the spine
|
|
// * `ServerPlayer::ProcessTurn` -- 12 phases, run once per player from spine phase 13
|
|
// * `StrategyServer::OnAllCombatDone_Tail` -- 37 phases, a SEPARATE driver reached from a
|
|
// different message after combat, and the one the autosave is written from
|
|
//
|
|
// This header is the roadmap and the ledger at once. Every phase of every driver appears
|
|
// here whether or not it is implemented, so a run of the standalone prints the whole turn
|
|
// and marks each phase with what we actually have. A phase that is not implemented is a
|
|
// named no-op that shows up in the output; it is never silently skipped.
|
|
//
|
|
// Nothing in this file describes the original's memory layout. Phase indices are ordinals
|
|
// in a published phase table, not offsets, and no address appears anywhere in src/app.
|
|
#pragma once
|
|
|
|
#include <cstddef>
|
|
|
|
namespace sots::app {
|
|
|
|
// Which driver a phase belongs to.
|
|
enum class Driver : int {
|
|
// The host steps that bracket the drivers: BeginProcessTurn before the spine, and the
|
|
// save writer's own invariants after everything. Not part of the milestone's 44 -- they
|
|
// are not phases of either turn driver -- but they are part of a turn and are listed so
|
|
// the log is the whole sequence.
|
|
Host = 0,
|
|
Strategic = 1, // StrategyServer::ProcessTurn
|
|
Player = 2, // ServerPlayer::ProcessTurn (once per player, from Strategic phase 13)
|
|
Tail = 3, // StrategyServer::OnAllCombatDone_Tail (post-combat, pre-autosave)
|
|
};
|
|
|
|
const char* DriverName(Driver d);
|
|
|
|
// How much of a phase we have, and -- the load-bearing part -- whether the standalone is
|
|
// allowed to COMMIT its result to the save.
|
|
//
|
|
// The distinction between `Blocked` and `Stub` is the whole point of this scaffold. A
|
|
// blocked phase is one whose formula we hold and whose inputs we do not: running it and
|
|
// writing the result would replace a leaf that currently happens to match with a leaf that
|
|
// is confidently wrong. So a blocked phase is evaluated, its result is reported, and it is
|
|
// not written unless the operator asks for it. That keeps the divergence count an honest
|
|
// measure of what we know rather than a measure of how much code we ran.
|
|
enum class PhaseStatus : int {
|
|
Verified = 0, // implemented here AND compared against the live game by an earlier lane
|
|
Implemented = 1, // implemented from an instruction-verified reading; committed
|
|
Partial = 2, // part implemented and committed, part stubbed; the note says which
|
|
Blocked = 3, // formula held, an input is not modelled -> evaluated, reported, NOT committed
|
|
Stub = 4, // named no-op
|
|
};
|
|
|
|
const char* StatusName(PhaseStatus s);
|
|
// One-character glyph for the compact phase listing.
|
|
char StatusGlyph(PhaseStatus s);
|
|
|
|
struct PhaseDesc {
|
|
Driver driver;
|
|
int index; // ordinal in that driver's published phase table
|
|
const char* id; // stable id, e.g. "S11" / "P07" / "T31"
|
|
const char* name; // short identifier-shaped name
|
|
PhaseStatus status;
|
|
const char* note; // what it does, or what is missing -- printed next to the phase
|
|
};
|
|
|
|
// The tables, in execution order.
|
|
const PhaseDesc* HostPhases(std::size_t& count);
|
|
const PhaseDesc* StrategicPhases(std::size_t& count);
|
|
const PhaseDesc* PlayerPhases(std::size_t& count);
|
|
const PhaseDesc* TailPhases(std::size_t& count);
|
|
|
|
// The completion metric's denominator. The campaign's milestone counts the two turn-driver
|
|
// tables -- 32 + 12 = 44 -- and tracks the tail's 37 separately, because the tail is a
|
|
// second driver that has never been executed by anything we own.
|
|
constexpr int kSpinePhaseCount = 44;
|
|
|
|
struct PhaseTally {
|
|
int total = 0, verified = 0, implemented = 0, partial = 0, blocked = 0, stub = 0;
|
|
// "modelled" = anything we run at all, blocked included. "committed" = anything whose
|
|
// result reaches the save.
|
|
int modelled() const { return verified + implemented + partial + blocked; }
|
|
int committed() const { return verified + implemented + partial; }
|
|
};
|
|
|
|
PhaseTally TallySpine();
|
|
PhaseTally TallyTail();
|
|
|
|
} // namespace sots::app
|