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.
29 lines
1.3 KiB
C++
29 lines
1.3 KiB
C++
// Run reporting: the phase log a human reads, and the machine-readable completion metric.
|
|
#pragma once
|
|
|
|
#include <cstdio>
|
|
#include <string>
|
|
|
|
#include "app/turn.h"
|
|
|
|
namespace sots::app {
|
|
|
|
// The phase table with this run's numbers, in execution order. Every phase appears --
|
|
// including the ones that did nothing -- because the point of the listing is the gap.
|
|
void PrintPhaseLog(std::FILE* out, const TurnResult& r, bool verbose);
|
|
|
|
// A one-screen summary: how many phases of each driver we hold, and how much this run moved.
|
|
void PrintSummary(std::FILE* out, const TurnResult& r);
|
|
|
|
// The recorded command stream, command by command, in the order the original applies them,
|
|
// with each one's disposition and -- for the ones we decline -- the named subsystem it needs.
|
|
// That list of reasons IS the remaining work, so it is printed in full rather than summarised.
|
|
void PrintCommandReplay(std::FILE* out, const TurnResult& r);
|
|
|
|
// The completion metric, as JSON, for the campaign dashboard. Written to `path`; the
|
|
// divergence numbers are filled in by the comparison tool that runs the checksum diff, so
|
|
// this file carries only what the standalone itself knows.
|
|
bool WriteMetricJson(const std::string& path, const TurnResult& r, const std::string& inputName,
|
|
const std::string& outputName);
|
|
|
|
} // namespace sots::app
|