From c5047293415c8265a5097bdede0d5597ff21ad5f Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 10:35:56 -0400 Subject: [PATCH] lane S2: the standalone scaffold, and the measured distance to the byte-match tools/standalone_report.py drives sots-engine's sots_turn over each consecutive-turn save pair and diffs the result against the game's own post-turn save with state_checksum.py, which localises to named leaves and proves its own coverage by re-serialisation. turn1-state -> turn2-state baseline 209 diverging, after 204, closed 5 turn2-state -> turn3-state baseline 108 diverging, after 103, closed 5 regressed 0 on both `regressed` is reported next to `closed` and never netted off. It earned its place immediately: committing the phase-31 player-status restore turned two agreeing leaves into disagreeing ones, because the phase writes 1 and the file carries 4. The stable-system stand-in feeding the colony pass is a labelled hypothesis and it survived a changed workload -- the same 3 ntdev leaves closed on both pairs, six agreements, zero disagreements. Two things deliberately NOT implemented: the TShn/ltis counters (18 leaves, a `+1` would close them, but "+1 across one observed turn" is a hypothesis, not a reading), and the RNG state write-back (an advanced-but-incomplete generator is wrong in a different way from an untouched one). dashboard.py gains section 6, reading verify/results/standalone/status.json: phases modelled/committed per driver, baseline vs after, closed vs regressed, the subsystem breakdown of what still differs, and the RNG gap. Sections 6-8 renumbered to 7-9; the delta footer tracks the two new counts. DASHBOARD_README.md documents every number. findings/control-flow/standalone-scaffold.md has the ranked blocker list. --- campaign/DASHBOARD.md | 49 +- findings/control-flow/standalone-scaffold.md | 124 ++ tools/DASHBOARD_README.md | 29 +- tools/dashboard.py | 79 +- tools/standalone_report.py | 263 +++ verify/results/standalone/report.txt | 88 + verify/results/standalone/status.json | 1964 ++++++++++++++++++ 7 files changed, 2579 insertions(+), 17 deletions(-) create mode 100644 findings/control-flow/standalone-scaffold.md create mode 100644 tools/standalone_report.py create mode 100644 verify/results/standalone/report.txt create mode 100644 verify/results/standalone/status.json diff --git a/campaign/DASHBOARD.md b/campaign/DASHBOARD.md index 9397370..40f45ca 100644 --- a/campaign/DASHBOARD.md +++ b/campaign/DASHBOARD.md @@ -1,6 +1,6 @@ # SotS RE campaign — coverage dashboard -Generated 2026-09-08 14:07 UTC · `sots-re` @ c15a45d,2026-09-08 · `sots-engine` @ bcf4297,2026-09-08 (115 commits) · regenerate with `tools/dashboard.py` +Generated 2026-09-08 14:35 UTC · `sots-re` @ 66fdf0f,2026-09-08 · `sots-engine` @ bcf4297,2026-09-08 (115 commits) · regenerate with `tools/dashboard.py` > **North star:** A functional reimplementation of the engine — behavior-equivalent, NOT byte-for-byte @@ -73,7 +73,39 @@ Board `engine:` rows: verified **18**, mapped 0, in flight 0 (of 18) — verifie | P2-M3 Mars brace-block parser | ⬜ backlog | 0% | Mars::Script pull tokenizer (Open 0x008cd7d0, ReadToken 0x008cd2f0, Next 0x008cd3e0, SkipB | | P2-M4 gobio VFS read | ⬜ backlog | 0% | choke point: bool __cdecl gobio::ReadFile(const char*, IBuffer**) 0x008d5140; FileSystemSe | -## 6. Verification ledger +## 6. Standalone (`src/app`) — distance to the byte-match + +Turn-driver phases: **14/44** modelled (7 committed) `[███░░░░░░░] 32%` + +| | verified | implemented | partial | blocked | stub | +|---|---:|---:|---:|---:|---:| +| turn drivers (44) | 0 | 2 | 5 | 7 | 30 | +| post-combat tail (37) | 0 | 1 | 0 | 1 | 35 | + +Reference pair `turn1-state.sav` → `turn2-state.sav`, leaves localised by `state_checksum.py` (coverage proved by re-serialisation): + +- baseline (a standalone that does nothing): **209** leaves diverge +- after one standalone turn: **204** leaves diverge — closed 5, regressed 0 +- byte match: ❌ not yet `[░░░░░░░░░░] 2%` + +Where the remaining divergence lives: + +| Subsystem | Leaves | +|---|---:| +| `/Sim/players` | 82 | +| `/Sim/systems` | 80 | +| `/Sim/turnstats` | 24 | +| `/Sim/SvSctOb` | 8 | +| `/Sim/DesignIDs[]` | 1 | +| `/Sim/FleetIDs[]` | 1 | +| `/Sim/ModCount` | 1 | +| `/Sim/NMnx` | 1 | + +Generator: 0 word(s) modelled per turn; unattributed per turn: 18-20 (lane Z, in flight). A byte-match is impossible until that closes — the generator state is saved state. + +Detail: `verify/results/standalone/report.txt`. + +## 7. Verification ledger - ✅ Saves strict: 11/11 (strict exit 0, 0 errors, 0 warnings) - ✅ Design rules: 127/127 @@ -82,7 +114,7 @@ Board `engine:` rows: verified **18**, mapped 0, in flight 0 (of 18) — verifie - ✅ M0 evidence present (`verify/results/shim/m0.log`) - ✅ Determinism oracle: verified -## 7. Open questions +## 8. Open questions Open **26** · resolved/parked 11 · backlog items: Now 4, Next 3, Later 2, Breadth queue 7, Parked 1, From the RE how-to 4, Behavioral slice 4 @@ -94,12 +126,13 @@ Most recent open: - SAVE_FORMAT tag corrections (fix Python reader + spec) — real on-disk tags: `otnF` (not `ontF`) in… - Not traced end-to-end — `Species/_NPC/weapons/*.weapon` loading and the `.effect` dictionary entry… -## 8. Delta since previous dashboard +## 9. Delta since previous dashboard -- verified targets: 110 → 119 (+9) · mapped-or-better: 136 → 145 (+9) -- engine LOC: 35,544 → 35,582 (+38) · test files: 94 → 94 (+0) · checks: 3,155 → 3,170 (+15) -- addresses verified: 734 → 777 (+43) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) +- verified targets: 119 → 119 (+0) · mapped-or-better: 145 → 145 (+0) +- engine LOC: 35,582 → 35,582 (+0) · test files: 94 → 94 (+0) · checks: 3,170 → 3,170 (+0) +- addresses verified: 777 → 777 (+0) · recovered layouts: 384 → 384 (+0) · open questions: 26 → 26 (+0) +- standalone leaves closed: n/a · leaves still diverging: n/a --- warnings: board.md: unknown types subsystems; mars-rng.md: no oracle total row parsed; mars-stream.md: no oracle total row parsed; mars-vfs.md: no oracle total row parsed - + diff --git a/findings/control-flow/standalone-scaffold.md b/findings/control-flow/standalone-scaffold.md new file mode 100644 index 0000000..1aedb69 --- /dev/null +++ b/findings/control-flow/standalone-scaffold.md @@ -0,0 +1,124 @@ +# The standalone, and the measured distance to the byte-match + +Lane S2, 2026-09-08. Engine branch `wip/standalone`; full documentation in +`sots-engine/docs/S-standalone.md`. This note records what the lane measured, what it declined +to implement, and the two numbers the campaign should track from here. + +--- + +## 0. The headline + +`sots_turn` loads a real 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 a save +through the engine's own writer. + +``` +turn1-state.sav -> turn2-state.sav (a real End Turn) + baseline (a standalone that does nothing) 209 leaves diverge + after one standalone turn 204 leaves diverge + closed 5, regressed 0 + +turn2-state.sav -> turn3-state.sav + baseline 108 -> 103, closed 5, regressed 0 +``` + +Phases: **14 of 44** turn-driver phases modelled, **7** committing anything; **2 of 37** of the +post-combat tail. Generator words modelled per turn: **0** of the 18–20 consumed. + +Leaves are `verify/state-checksum/state_checksum.py`'s named leaves; every one of the six +saves involved reported `coverage: PROVED` on the same run, so the diff cannot be hiding +anything. + +Regenerate with `tools/standalone_report.py`; outputs land in `verify/results/standalone/` +and the dashboard's new section 6 reads `status.json`. + +## 1. What the scaffold is for + +Everything the milestone needs already existed in pieces — a save reader with 100 % named +coverage, a verified budget roll-up, a verified research slice, a mechanism-verified movement +model, and byte-for-byte maps of both turn drivers. What did not exist was **a place to put +them and a number that says how far they get**. That is the whole of this lane. + +The phase catalog (`src/app/phase_catalog.cpp`) is the roadmap: all 32 + 12 + 37 phases, each +carrying a status and a note. Running `sots_turn --phases` prints the turn, and every +unimplemented phase prints itself. There is no way for a phase to be silently absent, and +`app_catalog` fails the build if a table develops a gap or if anything claims to be `verified` +(which in that table means "compared against the live game" — lane S2 held no VM). + +## 2. The finding that justifies the design: a committed phase can make things worse + +The first version of this lane implemented `StrategyServer::ProcessTurn` phase 31's +player-status restore — `Status = 1` — and committed it. The comparison tool immediately +reported **two regressed leaves** on the `turn2 → turn3` pair: two `Player.Status` words that +**agreed** with the oracle before the turn and disagreed after it. + +The phase writes 1. The post-turn file carries 4. A load resets it to 0. So a writer between +phase 31 and the autosave is unaccounted, and the input save happened to already carry the +right answer. + +This is the same shape as the campaign's oldest lesson in a new place: running more code is +not the same as knowing more. The standalone therefore separates **modelled** from +**committed**, and a phase whose inputs are not modelled is evaluated, reported and *not +written* unless `--commit-blocked` is passed. `regressed` is reported next to `closed` in every +run, never netted off. + +Five phases are blocked behind one unresolved formula (below); `S31` and `T31` are blocked +behind their own; `P11` is blocked behind the event-text table. + +## 3. Two things this lane declined to implement + +**The `TShn` / `ltis` counters.** 18 leaves of the remaining 204 are `TShn` and `ltis` moving +`1 -> 2` on 8–10 systems, on both turn pairs. They look exactly like per-turn counters and a +`+1` would close 18 leaves in ten lines of code. Nothing in the campaign names their writer, so +"+1 per turn across one observed turn" is a hypothesis, not a reading, and rule 6 says label it +as one. They are the **cheapest measured target on the board** and they are named here so the +next lane can close them properly rather than plausibly. + +**The RNG state.** The generator advances during a real turn; the standalone leaves the blob +byte-identical by default. An advanced-but-incomplete state is wrong in a different way from an +untouched one, and the untouched one at least reports the truth. `--commit-rng` is there for +the day lane Z's ledger closes. + +## 4. One hypothesis under test, and it survived a changed workload + +`ProcessColonyTurn` takes `stable` as an input; in the original it is a callee's verdict. The +standalone stands in `owned && !abandoned && !destroyed`, labelled a hypothesis in the code and +printed as one in the run log. + +It drives `ntdev`, which is a named leaf, so it is falsifiable. On `turn1 → turn2` it judged 3 +of 28 systems stable and closed exactly the 3 `ntdev` leaves the oracle moved. On +`turn2 → turn3` — a different turn, a different set of orders — it closed the same 3 again. +**Six agreements, zero disagreements, across two workloads.** Not proof; recorded as such. + +## 5. What is now measurably in the way + +Ordered by what must be solved, not by size. + +| # | blocker | cost in leaves on the reference pair | who can close it | +|---|---|---:|---| +| 1 | **the RNG ledger** — 18–20 words/turn, none modelled, and the generator is saved state | 1 leaf, and it makes a byte-match *arithmetically impossible* | lane Z (in flight); nothing in `src/app` | +| 2 | **the population → base-output term** — one unresolved formula that blocks `P01 P02 P05 P06 T31` | ~10 directly (`Sav`, `BnkPr`, `BnkEl` on 4 players), and it gates 5 of the 44 phases | a formula lane against the live game | +| 3 | **the post-combat tail**, 37 phases, none implemented, and the driver the autosave is written from | 24 (`turnstats`) + the bankruptcy limits + observed designs + player reports | its own milestone | +| 4 | **the per-player system-visibility record** (`nve`) | 32 — one mechanism, eight repetitions | spine phase 24 or tail phase 21 | +| 5 | **the event pipeline** — buckets, ids and localised text | ~20 across the players | needs the string table | +| 6 | **`Summary.Checksum`** — algorithm unknown | 1, and it is the last leaf to fall | — | +| 7 | **the `Player.Status` writer** — phase writes 1, file carries 4 | 4 | small, self-contained | +| 8 | **`ModCount`** — advances 12–44 times a turn from writers across both drivers; we model 2 | 1 | falls out of implementing the other phases | + +## 6. Gates run, separately + +``` +tools/clean_room_check.sh -> clean-room check: OK (with src/app + tests/app staged) +ctest --preset host -> 100% tests passed out of 38 (was 36; +app_catalog, +app_turn) +app_turn with SOTS_SAVES_DIR set -> 11 saves driven, 0 failures +``` + +`src/shim/` was not touched, so no CT111 cross-build was required. + +## 7. What the corpus cannot answer + +The two pairs above are the only true End-Turn transitions we hold. The other nine saves are +single states: `app_turn` drives a turn over each of them and asserts the file survives, but +there is no oracle to diff against. A third and fourth consecutive-turn pair — especially on a +Zuul game, where the research and node-travel paths differ — would make every number in §0 +sturdier for the cost of two End Turns on VM140. diff --git a/tools/DASHBOARD_README.md b/tools/DASHBOARD_README.md index f23c07a..4b6205d 100644 --- a/tools/DASHBOARD_README.md +++ b/tools/DASHBOARD_README.md @@ -82,13 +82,34 @@ verified / all engine rows. In flight = `in-progress`. Board rows whose Target starts with `P2-M`. Glyphs: ✅ verified or mapped, 🔄 in-progress, ⬜ backlog, ⛔ blocked. Notes are truncated to 90 characters. -## 6. Verification ledger +## 6. Standalone progress + +Source: `verify/results/standalone/status.json`, written by `tools/standalone_report.py` (which +drives `sots-engine`'s `sots_turn` over each consecutive-turn save pair and diffs the result +against the game's own post-turn save with `verify/state-checksum/state_checksum.py`). The file +is optional: absent, unreadable, or carrying an unexpected `schema` -> the section renders +"Not measured" plus a footer warning, and the delta row shows `n/a`. + +| Number | Source | +|---|---| +| turn-driver phases modelled / total | `phases.modelled` / `phases.total`. **Total is 44 = the 32 phases of `StrategyServer::ProcessTurn` + the 12 of `ServerPlayer::ProcessTurn`.** The host steps around the drivers are deliberately excluded from the denominator; the 37-phase post-combat tail is a separate row | +| committed | phases whose result is actually written to the save. A `blocked` phase is *modelled* (it runs and reports) but not *committed*, so `committed <= modelled` always | +| status breakdown | `verified` (compared against the live game) / `implemented` / `partial` / `blocked` (formula held, an input is not) / `stub` (named no-op) | +| baseline / after / closed / regressed | `reference.*`. Baseline = leaves that differ between the input save and the oracle, i.e. the distance a standalone that does nothing has to travel. `regressed` is reported next to `closed` and never netted off: a leaf that agreed before the turn and disagrees after it is a phase doing damage | +| byte match | `reference.byteMatch` — the milestone itself. The progress bar next to it is closed/baseline, not a claim about how much is left | +| subsystem table | `reference.subsystems`, the first 8 by leaf count | +| generator | `rng.wordsModelled` and the standing `rng.wordsPerTurnUnattributed` string | + +The reference pair is the first entry of `PAIRS` in `standalone_report.py`; every pair it ran +is in `status.json` under `pairs`, and the readable form is `verify/results/standalone/report.txt`. + +## 7. Verification ledger One line per evidence source: saves strict and design rules (as in section 3), each parsed oracle, `verify/harness/compare/` directory present, `verify/results/shim/m0.log` present, and the board status of the `determinism oracle` row. -## 7. Open questions +## 8. Open questions `campaign/open-questions.md`: every bullet beginning `- **`. A bullet is *resolved/parked* if its bold text starts with `RESOLVED`, `Resolved` or `(parked)`; everything else is open. "Most @@ -96,12 +117,12 @@ recent" = the last five open bullets in file order (the file is append-ordered), first 100 characters. Backlog counts are numbered or bulleted items under each `## ` heading of `campaign/backlog.md`. -## 8. Delta +## 9. Delta The previous `DASHBOARD.md` carries a machine-readable footer comment ``. It is read before the file is overwritten; the section shows old → new (±) for verified targets, mapped-or-better, engine LOC, test files, checks, -addresses verified, recovered layouts and open questions. A dashboard without that comment +addresses verified, recovered layouts, open questions, and the standalone's closed / still-diverging leaf counts. A dashboard without that comment (or the first run) reports "first run". ## Adding a number diff --git a/tools/dashboard.py b/tools/dashboard.py index f0e3694..d56c7b5 100755 --- a/tools/dashboard.py +++ b/tools/dashboard.py @@ -225,6 +225,26 @@ def parse_design_rules(): return None, None +# ---------------------------------------------------------------- standalone +def parse_standalone(path): + """verify/results/standalone/status.json, written by tools/standalone_report.py. + + Absent or malformed -> None, so the section renders as 'not measured' rather than + breaking the dashboard.""" + if not os.path.exists(path): + return None + try: + with open(path, encoding="utf-8") as f: + d = json.load(f) + except (OSError, ValueError) as e: + warn(f"standalone status.json unreadable: {e}") + return None + if d.get("schema") != "sots-standalone-status/1": + warn(f"standalone status.json: unexpected schema {d.get('schema')!r}") + return None + return d + + # ---------------------------------------------------------------- engine SRC_EXT = (".cpp", ".h", ".c") @@ -438,6 +458,53 @@ def render(engine, prev): warn("board.md: no P2-M* rows") L.append("") + # 6 standalone progress -- the north star, measured + sa = parse_standalone(rp("verify", "results", "standalone", "status.json")) + L += ["## 6. Standalone (`src/app`) — distance to the byte-match", ""] + sa_closed = sa_left = 0 + if not sa: + L += ["Not measured. Build `sots-engine`'s host preset and run " + "`tools/standalone_report.py`.", ""] + else: + ph, tp = sa.get("phases") or {}, sa.get("tailPhases") or {} + ref = sa.get("reference") or {} + sa_closed = ref.get("closed") or 0 + sa_left = ref.get("divergingAfterTurn") or 0 + base = ref.get("baselineDiverging") or 0 + if ph: + L += [f"Turn-driver phases: **{ph['modelled']}/{ph['total']}** modelled " + f"({ph['committed']} committed) {bar(ph['modelled'], ph['total'])}", + "", + f"| | verified | implemented | partial | blocked | stub |", + "|---|---:|---:|---:|---:|---:|", + f"| turn drivers (44) | {ph['verified']} | {ph['implemented']} | " + f"{ph['partial']} | {ph['blocked']} | {ph['stub']} |"] + if tp: + L.append(f"| post-combat tail ({tp['total']}) | {tp['verified']} | " + f"{tp['implemented']} | {tp['partial']} | {tp['blocked']} | " + f"{tp['stub']} |") + L.append("") + L += [f"Reference pair `{ref.get('input')}` → `{ref.get('oracle')}`, leaves localised by " + "`state_checksum.py` (coverage proved by re-serialisation):", "", + f"- baseline (a standalone that does nothing): **{base}** leaves diverge", + f"- after one standalone turn: **{sa_left}** leaves diverge " + f"— closed {sa_closed}, regressed {ref.get('regressed')}", + f"- byte match: {'✅ YES' if ref.get('byteMatch') else '❌ not yet'} " + f"{bar(sa_closed, base) if base else ''}", ""] + subs = ref.get("subsystems") or {} + if subs: + L += ["Where the remaining divergence lives:", "", + "| Subsystem | Leaves |", "|---|---:|"] + for k, v in list(subs.items())[:8]: + L.append(f"| `{k}` | {v} |") + L.append("") + rng = sa.get("rng") or {} + L += [f"Generator: {rng.get('wordsModelled')} word(s) modelled per turn; " + f"unattributed per turn: {rng.get('wordsPerTurnUnattributed')}. " + "A byte-match is impossible until that closes — the generator state is saved " + "state.", "", + "Detail: `verify/results/standalone/report.txt`.", ""] + # 7 verification ledger def status_of(target_re): for r in rows: @@ -446,7 +513,7 @@ def render(engine, prev): return "not on board" m0 = os.path.exists(rp("verify", "results", "shim", "m0.log")) harness = os.path.isdir(rp("verify", "harness", "compare")) - L += ["## 6. Verification ledger", "", + L += ["## 7. Verification ledger", "", f"- {'✅' if s_ok else '❌'} Saves strict: {nsaves}/{nsaves} ({s_detail})", f"- {'✅' if d_ok == d_tot and d_ok else '❌'} Design rules: {d_ok}/{d_tot}", "- " + (" · ".join(f"{'✅' if a == f else '❌'} oracle {nm} {a}/{f}" for nm, a, f in oracles) if oracles else "❌ oracle parsers: none parsed"), @@ -457,7 +524,7 @@ def render(engine, prev): # 8 open questions oq, closed = parse_questions(rp("campaign", "open-questions.md")) bl = parse_backlog(rp("campaign", "backlog.md")) - L += ["## 7. Open questions", "", + L += ["## 8. Open questions", "", f"Open **{len(oq)}** · resolved/parked {len(closed)} · backlog items: " + (", ".join(f"{k} {v}" for k, v in bl.items()) if bl else "?"), "", "Most recent open:", ""] for t in oq[-5:][::-1]: @@ -466,8 +533,9 @@ def render(engine, prev): # 9 delta cur = dict(verified=by["verified"], mapped_plus=mapped_plus, targets=total, loc=tot_loc, tests=tot_tf, - checks=tot_ck, addr_verified=a_ver, addr_total=a_total, layouts=len(layouts), open_q=len(oq)) - L += ["## 8. Delta since previous dashboard", ""] + checks=tot_ck, addr_verified=a_ver, addr_total=a_total, layouts=len(layouts), open_q=len(oq), + sa_closed=sa_closed, sa_left=sa_left) + L += ["## 9. Delta since previous dashboard", ""] if prev: def d(k): if k not in prev: @@ -476,7 +544,8 @@ def render(engine, prev): return f"{prev[k]:,} → {cur[k]:,} ({diff:+,})" L += [f"- verified targets: {d('verified')} · mapped-or-better: {d('mapped_plus')}", f"- engine LOC: {d('loc')} · test files: {d('tests')} · checks: {d('checks')}", - f"- addresses verified: {d('addr_verified')} · recovered layouts: {d('layouts')} · open questions: {d('open_q')}", ""] + f"- addresses verified: {d('addr_verified')} · recovered layouts: {d('layouts')} · open questions: {d('open_q')}", + f"- standalone leaves closed: {d('sa_closed')} · leaves still diverging: {d('sa_left')}", ""] else: L += ["- first run (no previous `DASHBOARD.md` metrics found)", ""] diff --git a/tools/standalone_report.py b/tools/standalone_report.py new file mode 100644 index 0000000..ed0fc54 --- /dev/null +++ b/tools/standalone_report.py @@ -0,0 +1,263 @@ +#!/usr/bin/env python3 +"""Measure the standalone against the oracle, and record the distance. + +The milestone is: the standalone loads a save, runs one strategic turn, and writes an +autosave that byte-matches what the original produces from the same state. This tool +measures how far off that is, in the only currency the campaign trusts -- named leaves of +`verify/state-checksum/state_checksum.py`, whose coverage is proved by re-serialisation. + +For each (before, after) pair of real saves it computes three numbers: + + baseline leaves that differ between the INPUT save and the oracle's post-turn save. + This is the distance a standalone that does nothing has to travel. + result leaves that differ between OUR post-turn save and the oracle's. + closed baseline - result, and -- separately -- any leaf we made worse. + +`closed` alone would be a comfortable number, so `regressed` is reported next to it: a leaf +that agreed with the oracle before the turn and disagrees after it is a phase doing damage, +and it is counted and named rather than netted off. + + tools/standalone_report.py # every pair, write the JSON + text report + tools/standalone_report.py --print # also echo the report + tools/standalone_report.py --pair A.sav B.sav # one ad-hoc pair + tools/standalone_report.py --binary PATH # a sots_turn built elsewhere + +Outputs (overwritten): + verify/results/standalone/status.json the completion metric, read by tools/dashboard.py + verify/results/standalone/report.txt the human divergence report +""" +import argparse +import datetime +import json +import os +import shutil +import subprocess +import sys +import tempfile + +RE_ROOT = os.path.abspath(os.path.join(os.path.dirname(os.path.abspath(__file__)), "..")) +CK_DIR = os.path.join(RE_ROOT, "verify", "state-checksum") +SAVE_DIR = os.path.join(RE_ROOT, "verify", "results", "saves") +OUT_DIR = os.path.join(RE_ROOT, "verify", "results", "standalone") + +sys.path.insert(0, CK_DIR) +import state_checksum as ck # noqa: E402 + +# The consecutive-turn pairs the corpus holds. A pair is (input, oracle): the oracle is the +# state the game itself produced by ending a turn on the input. Only the first family is a +# true End-Turn transition of one game; the others are listed so a regression on them is +# still visible, with their nature stated. +PAIRS = [ + ("turn1-state.sav", "turn2-state.sav", "real End Turn"), + ("turn2-state.sav", "turn3-state.sav", "real End Turn"), +] + +DEFAULT_BINARIES = [ + os.path.expanduser("~/sots-engine-wt-standalone/build-host/src/app/sots_turn"), + os.path.expanduser("~/sots-engine/build-host/src/app/sots_turn"), + "/srv/re-lab/build/sots-engine-s2/src/app/sots_turn", +] + + +def find_binary(explicit): + if explicit: + return explicit if os.path.exists(explicit) else None + for p in DEFAULT_BINARIES: + if os.path.exists(p): + return p + return shutil.which("sots_turn") + + +def leaf_paths(a, b, limit=200000): + """The set of leaf paths on which two checksummed saves differ.""" + entries = ck.diff(a.root, b.root, limit=limit) + return {e.path: e for e in entries} + + +def run_pair(binary, src, oracle, note, workdir, keep_saves): + out_sav = os.path.join(workdir, "post-" + os.path.basename(src)) + metric = os.path.join(workdir, "metric-" + os.path.basename(src) + ".json") + cmd = [binary, src, "--out", out_sav, "--metric", metric, "--roundtrip"] + proc = subprocess.run(cmd, capture_output=True, text=True) + row = { + "input": os.path.basename(src), + "oracle": os.path.basename(oracle), + "note": note, + "exit": proc.returncode, + "stdout": proc.stdout.strip().splitlines()[-30:], + } + if proc.returncode != 0: + row["error"] = proc.stderr.strip()[:2000] + return row, [] + + ck_in = ck.checksum_save(src) + ck_or = ck.checksum_save(oracle) + ck_ours = ck.checksum_save(out_sav) + + base = leaf_paths(ck_in, ck_or) + ours = leaf_paths(ck_ours, ck_or) + + closed = sorted(set(base) - set(ours)) + regressed = sorted(set(ours) - set(base)) + remaining = sorted(set(ours) & set(base)) + + row.update({ + "coverage": { + "input": ck_in.coverage.get("ok"), + "oracle": ck_or.coverage.get("ok"), + "ours": ck_ours.coverage.get("ok"), + }, + "roots": {"input": ck_in.digest, "oracle": ck_or.digest, "ours": ck_ours.digest}, + "baselineDiverging": len(base), + "divergingAfterTurn": len(ours), + "closed": len(closed), + "regressed": len(regressed), + "closedPaths": closed, + "regressedPaths": [repr(ours[p]) for p in regressed], + "remainingSample": [repr(ours[p]) for p in remaining[:40]], + "byteMatch": ck_ours.digest == ck_or.digest, + }) + if os.path.exists(metric): + with open(metric) as f: + row["standalone"] = json.load(f) + if keep_saves: + dst = os.path.join(OUT_DIR, os.path.basename(out_sav)) + shutil.copyfile(out_sav, dst) + row["savedTo"] = os.path.relpath(dst, RE_ROOT) + return row, remaining + + +def subsystem_breakdown(remaining): + """Group the remaining divergences by the subsystem they land in.""" + buckets = {} + for p in remaining: + parts = p.strip("/").split("/") + key = "/" + "/".join(parts[:2]) if len(parts) > 1 else "/" + parts[0] + buckets[key] = buckets.get(key, 0) + 1 + return dict(sorted(buckets.items(), key=lambda kv: -kv[1])) + + +def main(argv=None): + ap = argparse.ArgumentParser(description=__doc__, + formatter_class=argparse.RawDescriptionHelpFormatter) + ap.add_argument("--binary", help="path to sots_turn") + ap.add_argument("--pair", nargs=2, metavar=("INPUT", "ORACLE"), + help="one ad-hoc (input, oracle) save pair") + ap.add_argument("--print", dest="echo", action="store_true", help="echo the report") + ap.add_argument("--no-write", action="store_true", help="render only, touch nothing") + ap.add_argument("--keep-saves", action="store_true", + help="copy each post-turn save into verify/results/standalone/") + args = ap.parse_args(argv) + + binary = find_binary(args.binary) + if not binary: + print("standalone_report: sots_turn not found; build sots-engine's host preset first", + file=sys.stderr) + print(" looked in: " + ", ".join(DEFAULT_BINARIES), file=sys.stderr) + return 2 + + pairs = ([(args.pair[0], args.pair[1], "ad-hoc")] if args.pair + else [(os.path.join(SAVE_DIR, a), os.path.join(SAVE_DIR, b), n) + for a, b, n in PAIRS]) + pairs = [(a, b, n) for a, b, n in pairs if os.path.exists(a) and os.path.exists(b)] + if not pairs: + print("standalone_report: no save pairs available, nothing to measure", file=sys.stderr) + return 0 + + if not args.no_write: + os.makedirs(OUT_DIR, exist_ok=True) + + rows, all_remaining = [], [] + with tempfile.TemporaryDirectory() as tmp: + for src, oracle, note in pairs: + row, remaining = run_pair(binary, src, oracle, note, tmp, + args.keep_saves and not args.no_write) + rows.append(row) + if row["input"] == os.path.basename(pairs[0][0]): + all_remaining = remaining + + ok = [r for r in rows if r.get("exit") == 0] + ref = ok[0] if ok else {} + status = { + "schema": "sots-standalone-status/1", + "generated": datetime.datetime.now(datetime.timezone.utc).strftime("%Y-%m-%dT%H:%M:%SZ"), + "binary": binary, + "reference": { + "input": ref.get("input"), + "oracle": ref.get("oracle"), + "baselineDiverging": ref.get("baselineDiverging"), + "divergingAfterTurn": ref.get("divergingAfterTurn"), + "closed": ref.get("closed"), + "regressed": ref.get("regressed"), + "byteMatch": ref.get("byteMatch"), + "subsystems": subsystem_breakdown(all_remaining), + }, + "phases": (ref.get("standalone") or {}).get("spine"), + "tailPhases": (ref.get("standalone") or {}).get("tail"), + "rng": { + "wordsModelled": ((ref.get("standalone") or {}).get("run") or {}).get("rngWords"), + "wordsPerTurnUnattributed": "18-20 (lane Z, in flight)", + }, + "pairs": rows, + } + + lines = [] + w = lines.append + w("# standalone vs the oracle") + w("") + w(f"generated {status['generated']} binary {binary}") + w("") + ph = status["phases"] or {} + tp = status["tailPhases"] or {} + if ph: + w(f"phases: {ph['modelled']}/{ph['total']} of the two turn drivers modelled, " + f"{ph['committed']} committed " + f"(implemented {ph['implemented']}, partial {ph['partial']}, " + f"blocked {ph['blocked']}, stub {ph['stub']})") + if tp: + w(f" {tp['modelled']}/{tp['total']} of the post-combat tail modelled") + w("") + for r in rows: + w(f"## {r['input']} -> {r['oracle']} ({r['note']})") + if r.get("exit"): + w(f" FAILED, exit {r['exit']}: {r.get('error', '')[:400]}") + w("") + continue + w(f" baseline (do nothing) {r['baselineDiverging']:4d} leaves diverge") + w(f" after one standalone turn {r['divergingAfterTurn']:4d} leaves diverge") + w(f" closed {r['closed']}, regressed {r['regressed']}, " + f"byte match: {'YES' if r['byteMatch'] else 'no'}") + w(f" coverage proved on all three saves: {r['coverage']}") + if r["closedPaths"]: + w(" closed:") + for p in r["closedPaths"]: + w(f" + {p}") + if r["regressedPaths"]: + w(" REGRESSED (agreed before the turn, disagrees after):") + for p in r["regressedPaths"]: + w(f" - {p}") + w("") + if all_remaining: + w("## what still differs on the reference pair, by subsystem") + for k, v in subsystem_breakdown(all_remaining).items(): + w(f" {v:4d} {k}") + w("") + w("## first 40 remaining, named") + for p in (ok[0]["remainingSample"] if ok else []): + w(f" {p}") + report = "\n".join(lines) + "\n" + + if not args.no_write: + with open(os.path.join(OUT_DIR, "status.json"), "w") as f: + json.dump(status, f, indent=2) + f.write("\n") + with open(os.path.join(OUT_DIR, "report.txt"), "w") as f: + f.write(report) + print(f"wrote {os.path.relpath(OUT_DIR, RE_ROOT)}/status.json and report.txt") + if args.echo or args.no_write: + print(report) + return 0 + + +if __name__ == "__main__": + sys.exit(main()) diff --git a/verify/results/standalone/report.txt b/verify/results/standalone/report.txt new file mode 100644 index 0000000..3f53294 --- /dev/null +++ b/verify/results/standalone/report.txt @@ -0,0 +1,88 @@ +# standalone vs the oracle + +generated 2026-09-08T14:30:05Z binary /home/alex/sots-engine-wt-standalone/build-host/src/app/sots_turn + +phases: 14/44 of the two turn drivers modelled, 7 committed (implemented 2, partial 5, blocked 7, stub 30) + 2/37 of the post-combat tail modelled + +## turn1-state.sav -> turn2-state.sav (real End Turn) + baseline (do nothing) 209 leaves diverge + after one standalone turn 204 leaves diverge + closed 5, regressed 0, byte match: no + coverage proved on all three saves: {'input': True, 'oracle': True, 'ours': True} + closed: + + /Sim/Frame + + /Sim/systems/Sys[112 "Gamma Cephei"]/ntdev + + /Sim/systems/Sys[288 "Ke'Dolarra"]/ntdev + + /Sim/systems/Sys[304 "Koa’Vo"]/ntdev + + /Summary/Turn + +## turn2-state.sav -> turn3-state.sav (real End Turn) + baseline (do nothing) 108 leaves diverge + after one standalone turn 103 leaves diverge + closed 5, regressed 0, byte match: no + coverage proved on all three saves: {'input': True, 'oracle': True, 'ours': True} + closed: + + /Sim/Frame + + /Sim/systems/Sys[112 "Gamma Cephei"]/ntdev + + /Sim/systems/Sys[288 "Ke'Dolarra"]/ntdev + + /Sim/systems/Sys[304 "Koa’Vo"]/ntdev + + /Summary/Turn + +## what still differs on the reference pair, by subsystem + 82 /Sim/players + 80 /Sim/systems + 24 /Sim/turnstats + 8 /Sim/SvSctOb + 1 /Sim/DesignIDs[] + 1 /Sim/FleetIDs[] + 1 /Sim/ModCount + 1 /Sim/NMnx + 1 /Sim/NumFlts + 1 /Sim/RNG + 1 /Sim/ShipIDs[] + 1 /Sim/cmbtid + 1 /Sim/fleets + 1 /Summary/Checksum + +## first 40 remaining, named + /Sim/DesignIDs[]: removed [], added [18, 1712] (41 -> 43 entries) + /Sim/FleetIDs[]: removed [], added [1744] (6 -> 7 entries) + /Sim/ModCount: 2 -> 12 + /Sim/NMnx: 106 -> 109 + /Sim/NumFlts: 6 -> 7 + /Sim/RNG/.: '' -> '' + /Sim/ShipIDs[]: removed [], added [1728] (15 -> 16 entries) + /Sim/SvSctOb/EncObj[3]/CDiff: -1 -> 0 + /Sim/SvSctOb/EncObj[5]/Hives/.: only-in-A + /Sim/SvSctOb/EncObj[5]/Hives/.[0]: only-in-B + /Sim/SvSctOb/EncObj[5]/Hives/.[1]: only-in-B + /Sim/SvSctOb/EncObj[5]/Hives/.[2]: only-in-B + /Sim/SvSctOb/EncObj[6]/did: only-in-B + /Sim/SvSctOb/EncObj[6]/didc: 0 -> 1 + /Sim/SvSctOb/EncObj[6]/ini: False -> True + /Sim/cmbtid: 1 -> 2 + /Sim/fleets/Flt[1744 "Alpha Fleet"]: only-in-B + /Sim/players/Player[16 "re"]/BnkEl: -1590613 -> -1594593 + /Sim/players/Player[16 "re"]/BnkPr: -787353 -> -789323 + /Sim/players/Player[16 "re"]/Events/EvNxID: 0 -> 2 + /Sim/players/Player[16 "re"]/Events/Events/.: only-in-A + /Sim/players/Player[16 "re"]/Events/Events/.[0]: only-in-B + /Sim/players/Player[16 "re"]/Events/Events/.[EvTurn=2]: only-in-B + /Sim/players/Player[16 "re"]/Sav: 50000 -> 289688 + /Sim/players/Player[16 "re"]/Status: 0 -> 4 + /Sim/players/Player[32 "Fane Lao"]/BnkEl: -1811273 -> -1815833 + /Sim/players/Player[32 "Fane Lao"]/BnkPr: -896580 -> -898837 + /Sim/players/Player[32 "Fane Lao"]/Events/EvNxID: 0 -> 2 + /Sim/players/Player[32 "Fane Lao"]/Events/Events/.: only-in-A + /Sim/players/Player[32 "Fane Lao"]/Events/Events/.[0]: only-in-B + /Sim/players/Player[32 "Fane Lao"]/Events/Events/.[EvTurn=2]: only-in-B + /Sim/players/Player[32 "Fane Lao"]/FNG/FNGNum: 0 -> 1 + /Sim/players/Player[32 "Fane Lao"]/Maint: 0 -> 500 + /Sim/players/Player[32 "Fane Lao"]/NumDes: 5 -> 6 + /Sim/players/Player[32 "Fane Lao"]/PvSav: 50000 -> 38100 + /Sim/players/Player[32 "Fane Lao"]/ResRate: 0.25 -> 0.800000011920929 [1.34218e+07 ulp] + /Sim/players/Player[32 "Fane Lao"]/ResTNm: '' -> 'IND_Waldo' + /Sim/players/Player[32 "Fane Lao"]/Sav: 50000 -> 92651 + /Sim/players/Player[32 "Fane Lao"]/ShipRecs/srb[0]: 0 -> 1 + /Sim/players/Player[32 "Fane Lao"]/ShipRecs/srb[3]: only-in-B diff --git a/verify/results/standalone/status.json b/verify/results/standalone/status.json new file mode 100644 index 0000000..01f3efe --- /dev/null +++ b/verify/results/standalone/status.json @@ -0,0 +1,1964 @@ +{ + "schema": "sots-standalone-status/1", + "generated": "2026-09-08T14:30:05Z", + "binary": "/home/alex/sots-engine-wt-standalone/build-host/src/app/sots_turn", + "reference": { + "input": "turn1-state.sav", + "oracle": "turn2-state.sav", + "baselineDiverging": 209, + "divergingAfterTurn": 204, + "closed": 5, + "regressed": 0, + "byteMatch": false, + "subsystems": { + "/Sim/players": 82, + "/Sim/systems": 80, + "/Sim/turnstats": 24, + "/Sim/SvSctOb": 8, + "/Sim/DesignIDs[]": 1, + "/Sim/FleetIDs[]": 1, + "/Sim/ModCount": 1, + "/Sim/NMnx": 1, + "/Sim/NumFlts": 1, + "/Sim/RNG": 1, + "/Sim/ShipIDs[]": 1, + "/Sim/cmbtid": 1, + "/Sim/fleets": 1, + "/Summary/Checksum": 1 + } + }, + "phases": { + "total": 44, + "verified": 0, + "implemented": 2, + "partial": 5, + "blocked": 7, + "stub": 30, + "modelled": 14, + "committed": 7 + }, + "tailPhases": { + "total": 37, + "verified": 0, + "implemented": 1, + "partial": 0, + "blocked": 1, + "stub": 35, + "modelled": 2, + "committed": 1 + }, + "rng": { + "wordsModelled": 0, + "wordsPerTurnUnattributed": "18-20 (lane Z, in flight)" + }, + "pairs": [ + { + "input": "turn1-state.sav", + "oracle": "turn2-state.sav", + "note": "real End Turn", + "exit": 0, + "stdout": [ + "load: /home/alex/sots-re/verify/results/saves/turn1-state.sav", + " 591376 inflated bytes, 0 error(s), 0 warning(s)", + " turn 1, frame 1, modCount 0, 8 player(s), 28 system(s), 6 fleet(s)", + "roundtrip (untouched): byte-identical (591376 bytes)", + "", + "phases", + " turn drivers (the milestone's denominator): 14 of 44 modelled, 7 committed", + " verified 0 implemented 2 partial 5 blocked 7 stub 30", + " post-combat tail (written to the autosave, tracked separately): 2 of 37 modelled", + " verified 0 implemented 1 partial 0 blocked 1 stub 35", + "", + "this run", + " leaves written 7", + " leaves NOT written by a blocked phase 16", + " generator words consumed 0 (state loaded)", + "", + "wrote /tmp/tmpatg5108q/post-turn1-state.sav (65145 bytes gzipped, 591376 inflated)", + "metric -> /tmp/tmpatg5108q/metric-turn1-state.sav.json" + ], + "coverage": { + "input": true, + "oracle": true, + "ours": true + }, + "roots": { + "input": "ce36f6e5a2e41ae1a90d5b7fefda8f8f", + "oracle": "b0e2448b83ad1da254d526f4f553ff8d", + "ours": "8836ea25910897ec7a9b7d69fcaf6c87" + }, + "baselineDiverging": 209, + "divergingAfterTurn": 204, + "closed": 5, + "regressed": 0, + "closedPaths": [ + "/Sim/Frame", + "/Sim/systems/Sys[112 \"Gamma Cephei\"]/ntdev", + "/Sim/systems/Sys[288 \"Ke'Dolarra\"]/ntdev", + "/Sim/systems/Sys[304 \"Koa\u2019Vo\"]/ntdev", + "/Summary/Turn" + ], + "regressedPaths": [], + "remainingSample": [ + "/Sim/DesignIDs[]: removed [], added [18, 1712] (41 -> 43 entries)", + "/Sim/FleetIDs[]: removed [], added [1744] (6 -> 7 entries)", + "/Sim/ModCount: 2 -> 12", + "/Sim/NMnx: 106 -> 109", + "/Sim/NumFlts: 6 -> 7", + "/Sim/RNG/.: '' -> ''", + "/Sim/ShipIDs[]: removed [], added [1728] (15 -> 16 entries)", + "/Sim/SvSctOb/EncObj[3]/CDiff: -1 -> 0", + "/Sim/SvSctOb/EncObj[5]/Hives/.: only-in-A", + "/Sim/SvSctOb/EncObj[5]/Hives/.[0]: only-in-B", + "/Sim/SvSctOb/EncObj[5]/Hives/.[1]: only-in-B", + "/Sim/SvSctOb/EncObj[5]/Hives/.[2]: only-in-B", + "/Sim/SvSctOb/EncObj[6]/did: only-in-B", + "/Sim/SvSctOb/EncObj[6]/didc: 0 -> 1", + "/Sim/SvSctOb/EncObj[6]/ini: False -> True", + "/Sim/cmbtid: 1 -> 2", + "/Sim/fleets/Flt[1744 \"Alpha Fleet\"]: only-in-B", + "/Sim/players/Player[16 \"re\"]/BnkEl: -1590613 -> -1594593", + "/Sim/players/Player[16 \"re\"]/BnkPr: -787353 -> -789323", + "/Sim/players/Player[16 \"re\"]/Events/EvNxID: 0 -> 2", + "/Sim/players/Player[16 \"re\"]/Events/Events/.: only-in-A", + "/Sim/players/Player[16 \"re\"]/Events/Events/.[0]: only-in-B", + "/Sim/players/Player[16 \"re\"]/Events/Events/.[EvTurn=2]: only-in-B", + "/Sim/players/Player[16 \"re\"]/Sav: 50000 -> 289688", + "/Sim/players/Player[16 \"re\"]/Status: 0 -> 4", + "/Sim/players/Player[32 \"Fane Lao\"]/BnkEl: -1811273 -> -1815833", + "/Sim/players/Player[32 \"Fane Lao\"]/BnkPr: -896580 -> -898837", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/EvNxID: 0 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/Events/.: only-in-A", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/Events/.[0]: only-in-B", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/Events/.[EvTurn=2]: only-in-B", + "/Sim/players/Player[32 \"Fane Lao\"]/FNG/FNGNum: 0 -> 1", + "/Sim/players/Player[32 \"Fane Lao\"]/Maint: 0 -> 500", + "/Sim/players/Player[32 \"Fane Lao\"]/NumDes: 5 -> 6", + "/Sim/players/Player[32 \"Fane Lao\"]/PvSav: 50000 -> 38100", + "/Sim/players/Player[32 \"Fane Lao\"]/ResRate: 0.25 -> 0.800000011920929 [1.34218e+07 ulp]", + "/Sim/players/Player[32 \"Fane Lao\"]/ResTNm: '' -> 'IND_Waldo'", + "/Sim/players/Player[32 \"Fane Lao\"]/Sav: 50000 -> 92651", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/srb[0]: 0 -> 1", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/srb[3]: only-in-B" + ], + "byteMatch": false, + "standalone": { + "schema": "sots-standalone-metric/1", + "input": "/home/alex/sots-re/verify/results/saves/turn1-state.sav", + "output": "/tmp/tmpatg5108q/post-turn1-state.sav", + "spine": { + "total": 44, + "verified": 0, + "implemented": 2, + "partial": 5, + "blocked": 7, + "stub": 30, + "modelled": 14, + "committed": 7 + }, + "tail": { + "total": 37, + "verified": 0, + "implemented": 1, + "partial": 0, + "blocked": 1, + "stub": 35, + "modelled": 2, + "committed": 1 + }, + "run": { + "leafWrites": 7, + "blockedLeafWrites": 16, + "rngWords": 0, + "rngLoaded": true + }, + "phases": [ + { + "driver": "host turn sequence (outside the two turn drivers)", + "id": "H00", + "name": "BeginProcessTurn", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S00", + "name": "SnapshotPreviousTurn", + "status": "partial", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S01", + "name": "SystemPrePassMoraleAndAbandon", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S02", + "name": "TradeManagerTurn", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S03", + "name": "RegisterTradeSystems", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S04", + "name": "RebuildAllianceMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S05", + "name": "BuildShipActionTypeSets", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S06", + "name": "ShipActionsExceptType2", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S07", + "name": "NodeSpaceTravel", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S08", + "name": "FleetMovement", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S09", + "name": "ShipActionsType2", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S10", + "name": "ShipUpkeep", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S11", + "name": "SystemTurn", + "status": "partial", + "ran": 28, + "writes": 3, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S12", + "name": "TradeSliderFinalisation", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S13", + "name": "PlayerTurn", + "status": "partial", + "ran": 8, + "writes": 0, + "blockedWrites": 7, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P01", + "name": "ComputeBudget", + "status": "blocked", + "ran": 8, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P02", + "name": "ApplyNetToSavings", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 3, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P03", + "name": "RecordBudgetDerivedFields", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P04", + "name": "ProcessSpecialProjectsSpend", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P05", + "name": "ProcessResearch", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P06", + "name": "ResearchRefund", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P07", + "name": "ClearTimedResearchAccumulators", + "status": "partial", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P08", + "name": "DecayRebellionOutputModifier", + "status": "implemented", + "ran": 2, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P09", + "name": "AccumulateTimedResearchBonuses", + "status": "implemented", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P10", + "name": "ConsumeResearchRollPending", + "status": "partial", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P11", + "name": "PostNoResearchEvent", + "status": "blocked", + "ran": 4, + "writes": 0, + "blockedWrites": 4, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P12", + "name": "PruneRaidTargets", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S14", + "name": "ProcessMissions", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S15", + "name": "ProcessStations", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S16", + "name": "ProcessDefenceSats", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S17", + "name": "ShipStatCacheRefresh", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S18", + "name": "ShipActionsForceValidate", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S19", + "name": "ProcessAid", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S20", + "name": "ProcessSpecialProjectsServer", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S21", + "name": "ProcessSurrenders", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S22", + "name": "AdvanceAIRebellion", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S23", + "name": "ScriptHookTurnStart", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S24", + "name": "SensorUpdate", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S25", + "name": "ScriptHookPostSensor", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S26", + "name": "RefreshPlayerViews", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S27", + "name": "RecomputePlayerReports", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S28", + "name": "GrantMetSpeciesTechs", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S29", + "name": "SystemTailFixup", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S30", + "name": "BuildTeamPartition", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S31", + "name": "EncounterDetectionAndStatusRestore", + "status": "blocked", + "ran": 8, + "writes": 0, + "blockedWrites": 2, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T00", + "name": "IncrementModCount", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T01", + "name": "ValidateEncounterResultArity", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T02", + "name": "ResolveFirstContact", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T03", + "name": "AnnounceEncounterSightings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T04", + "name": "TallyBattlesFought", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T05", + "name": "UpdateDiplomacyStatsFromCombat", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T06", + "name": "ApplyEncounterResults", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T07", + "name": "ClearEncounters", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T08", + "name": "ScriptHookCombatDone", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T09", + "name": "AdvanceAIRebellionPostCombat", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T10", + "name": "NodeSpaceTravelSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T11", + "name": "DecayNodeLines", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T12", + "name": "DrainColonyLossQueue", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T13", + "name": "UpdateTreasuryMorale", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T14", + "name": "UpdateForeignFleetMorale", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T15", + "name": "ProcessBankruptcy", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T16", + "name": "ResolveArrivedColonizers", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T17", + "name": "RebuildPlayerViewTree", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T18", + "name": "PostFleetWarnings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T19", + "name": "DrainInfraTerraformQueue", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T20", + "name": "ScriptHooksTurnEnd", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T21", + "name": "UpdateSurveyAndSystemStats", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T22", + "name": "TradeSliderFinalisationSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T23", + "name": "TradeManagerEndOfTurnHooks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T24", + "name": "RecomputeMaintenanceAndResearchBonus", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T25", + "name": "SensorUpdateSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T26", + "name": "ScriptHookPostSensorTail", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T27", + "name": "RefreshPlayerViewsSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T28", + "name": "UpdateNodeLineSightingMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T29", + "name": "AbortInvisibleInterceptOrders", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T30", + "name": "RebuildCommunicationMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T31", + "name": "UpdateBankruptcyLimits", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T32", + "name": "PostIncomingFleetWarnings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T33", + "name": "ShipManagerEndOfTurnHooks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T34", + "name": "RecordObservedDesigns", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T35", + "name": "RebuildPlayerReports", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T36", + "name": "FinalizeTurnRecords", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "host turn sequence (outside the two turn drivers)", + "id": "H01", + "name": "SaveWriterInvariants", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + } + ] + } + }, + { + "input": "turn2-state.sav", + "oracle": "turn3-state.sav", + "note": "real End Turn", + "exit": 0, + "stdout": [ + "load: /home/alex/sots-re/verify/results/saves/turn2-state.sav", + " 603360 inflated bytes, 0 error(s), 0 warning(s)", + " turn 2, frame 2, modCount 12, 8 player(s), 28 system(s), 7 fleet(s)", + "roundtrip (untouched): byte-identical (603360 bytes)", + "", + "phases", + " turn drivers (the milestone's denominator): 14 of 44 modelled, 7 committed", + " verified 0 implemented 2 partial 5 blocked 7 stub 30", + " post-combat tail (written to the autosave, tracked separately): 2 of 37 modelled", + " verified 0 implemented 1 partial 0 blocked 1 stub 35", + "", + "this run", + " leaves written 7", + " leaves NOT written by a blocked phase 10", + " generator words consumed 0 (state loaded)", + "", + "wrote /tmp/tmpatg5108q/post-turn2-state.sav (66883 bytes gzipped, 603360 inflated)", + "metric -> /tmp/tmpatg5108q/metric-turn2-state.sav.json" + ], + "coverage": { + "input": true, + "oracle": true, + "ours": true + }, + "roots": { + "input": "b0e2448b83ad1da254d526f4f553ff8d", + "oracle": "7ea50b486c2e9f9f1a8ec5b9646165a8", + "ours": "6523aa25786814380a26f48c046da22b" + }, + "baselineDiverging": 108, + "divergingAfterTurn": 103, + "closed": 5, + "regressed": 0, + "closedPaths": [ + "/Sim/Frame", + "/Sim/systems/Sys[112 \"Gamma Cephei\"]/ntdev", + "/Sim/systems/Sys[288 \"Ke'Dolarra\"]/ntdev", + "/Sim/systems/Sys[304 \"Koa\u2019Vo\"]/ntdev", + "/Summary/Turn" + ], + "regressedPaths": [], + "remainingSample": [ + "/Sim/FleetIDs[]: removed [1744], added [34, 1776] (7 -> 8 entries)", + "/Sim/ModCount: 14 -> 24", + "/Sim/NMnx: 109 -> 111", + "/Sim/NumFlts: 7 -> 8", + "/Sim/RNG/.: '' -> ''", + "/Sim/ShipIDs[]: removed [], added [1760] (16 -> 17 entries)", + "/Sim/SvSctOb/EncObj[5]/Hives/.[1]/NextQ: 31 -> 32", + "/Sim/SvSctOb/EncObj[5]/Hives/.[2]/NextQ: 29 -> 30", + "/Sim/cmbtid: 2 -> 3", + "/Sim/fleets/Flt[1744 \"Alpha Fleet\"]: only-in-A", + "/Sim/fleets/Flt[1776 \"Gamma Fleet\"]: only-in-B", + "/Sim/fleets/Flt[34 \"Beta Fleet\"]: only-in-B", + "/Sim/players/Player[16 \"re\"]/BnkEl: -1594593 -> -1598566", + "/Sim/players/Player[16 \"re\"]/BnkPr: -789323 -> -791290", + "/Sim/players/Player[16 \"re\"]/Events/EvNxID: 2 -> 3", + "/Sim/players/Player[16 \"re\"]/Events/Events/.[0]: 1 -> 2", + "/Sim/players/Player[16 \"re\"]/Events/Events/.[EvTurn=3]: only-in-B", + "/Sim/players/Player[16 \"re\"]/PvSav: 50000 -> 289688", + "/Sim/players/Player[16 \"re\"]/Sav: 289688 -> 532369", + "/Sim/players/Player[32 \"Fane Lao\"]/BnkEl: -1815833 -> -1820206", + "/Sim/players/Player[32 \"Fane Lao\"]/BnkPr: -898837 -> -901002", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/EvNxID: 2 -> 4", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/Events/.[0]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/Events/Events/.[EvTurn=3]: only-in-B", + "/Sim/players/Player[32 \"Fane Lao\"]/FNG/FNGNum: 1 -> 3", + "/Sim/players/Player[32 \"Fane Lao\"]/Maint: 500 -> 1000", + "/Sim/players/Player[32 \"Fane Lao\"]/PvSav: 38100 -> 80751", + "/Sim/players/Player[32 \"Fane Lao\"]/Sav: 92651 -> 135486", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/srb[0]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/srb[3]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/sri[0]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/ShipRecs/sri[3]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/TechTree/TResDone[106]: 2879 -> 5768", + "/Sim/players/Player[32 \"Fane Lao\"]/TechTree/Tbd[106]: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/lboid: 1 -> 2", + "/Sim/players/Player[32 \"Fane Lao\"]/odes/.[1]/otnL: 2 -> 3", + "/Sim/players/Player[496 \"Singularity\"]/dipstats/.[1]/lastally: 2 -> 3", + "/Sim/players/Player[512 \"Singularity\"]/dipstats/.[1]/lastally: 2 -> 3", + "/Sim/players/Player[528 \"Alien Menace\"]/dipstats/.[1]/lastnap: 2 -> 3", + "/Sim/players/Player[528 \"Alien Menace\"]/dipstats/.[2]/lastnap: 2 -> 3" + ], + "byteMatch": false, + "standalone": { + "schema": "sots-standalone-metric/1", + "input": "/home/alex/sots-re/verify/results/saves/turn2-state.sav", + "output": "/tmp/tmpatg5108q/post-turn2-state.sav", + "spine": { + "total": 44, + "verified": 0, + "implemented": 2, + "partial": 5, + "blocked": 7, + "stub": 30, + "modelled": 14, + "committed": 7 + }, + "tail": { + "total": 37, + "verified": 0, + "implemented": 1, + "partial": 0, + "blocked": 1, + "stub": 35, + "modelled": 2, + "committed": 1 + }, + "run": { + "leafWrites": 7, + "blockedLeafWrites": 10, + "rngWords": 0, + "rngLoaded": true + }, + "phases": [ + { + "driver": "host turn sequence (outside the two turn drivers)", + "id": "H00", + "name": "BeginProcessTurn", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S00", + "name": "SnapshotPreviousTurn", + "status": "partial", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S01", + "name": "SystemPrePassMoraleAndAbandon", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S02", + "name": "TradeManagerTurn", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S03", + "name": "RegisterTradeSystems", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S04", + "name": "RebuildAllianceMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S05", + "name": "BuildShipActionTypeSets", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S06", + "name": "ShipActionsExceptType2", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S07", + "name": "NodeSpaceTravel", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S08", + "name": "FleetMovement", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S09", + "name": "ShipActionsType2", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S10", + "name": "ShipUpkeep", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S11", + "name": "SystemTurn", + "status": "partial", + "ran": 28, + "writes": 3, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S12", + "name": "TradeSliderFinalisation", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S13", + "name": "PlayerTurn", + "status": "partial", + "ran": 8, + "writes": 0, + "blockedWrites": 4, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P01", + "name": "ComputeBudget", + "status": "blocked", + "ran": 8, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P02", + "name": "ApplyNetToSavings", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 3, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P03", + "name": "RecordBudgetDerivedFields", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P04", + "name": "ProcessSpecialProjectsSpend", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P05", + "name": "ProcessResearch", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P06", + "name": "ResearchRefund", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P07", + "name": "ClearTimedResearchAccumulators", + "status": "partial", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P08", + "name": "DecayRebellionOutputModifier", + "status": "implemented", + "ran": 2, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P09", + "name": "AccumulateTimedResearchBonuses", + "status": "implemented", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P10", + "name": "ConsumeResearchRollPending", + "status": "partial", + "ran": 2, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P11", + "name": "PostNoResearchEvent", + "status": "blocked", + "ran": 1, + "writes": 0, + "blockedWrites": 1, + "rng": 0 + }, + { + "driver": "ServerPlayer::ProcessTurn", + "id": "P12", + "name": "PruneRaidTargets", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S14", + "name": "ProcessMissions", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S15", + "name": "ProcessStations", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S16", + "name": "ProcessDefenceSats", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S17", + "name": "ShipStatCacheRefresh", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S18", + "name": "ShipActionsForceValidate", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S19", + "name": "ProcessAid", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S20", + "name": "ProcessSpecialProjectsServer", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S21", + "name": "ProcessSurrenders", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S22", + "name": "AdvanceAIRebellion", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S23", + "name": "ScriptHookTurnStart", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S24", + "name": "SensorUpdate", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S25", + "name": "ScriptHookPostSensor", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S26", + "name": "RefreshPlayerViews", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S27", + "name": "RecomputePlayerReports", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S28", + "name": "GrantMetSpeciesTechs", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S29", + "name": "SystemTailFixup", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S30", + "name": "BuildTeamPartition", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::ProcessTurn", + "id": "S31", + "name": "EncounterDetectionAndStatusRestore", + "status": "blocked", + "ran": 8, + "writes": 0, + "blockedWrites": 2, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T00", + "name": "IncrementModCount", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T01", + "name": "ValidateEncounterResultArity", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T02", + "name": "ResolveFirstContact", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T03", + "name": "AnnounceEncounterSightings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T04", + "name": "TallyBattlesFought", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T05", + "name": "UpdateDiplomacyStatsFromCombat", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T06", + "name": "ApplyEncounterResults", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T07", + "name": "ClearEncounters", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T08", + "name": "ScriptHookCombatDone", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T09", + "name": "AdvanceAIRebellionPostCombat", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T10", + "name": "NodeSpaceTravelSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T11", + "name": "DecayNodeLines", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T12", + "name": "DrainColonyLossQueue", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T13", + "name": "UpdateTreasuryMorale", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T14", + "name": "UpdateForeignFleetMorale", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T15", + "name": "ProcessBankruptcy", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T16", + "name": "ResolveArrivedColonizers", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T17", + "name": "RebuildPlayerViewTree", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T18", + "name": "PostFleetWarnings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T19", + "name": "DrainInfraTerraformQueue", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T20", + "name": "ScriptHooksTurnEnd", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T21", + "name": "UpdateSurveyAndSystemStats", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T22", + "name": "TradeSliderFinalisationSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T23", + "name": "TradeManagerEndOfTurnHooks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T24", + "name": "RecomputeMaintenanceAndResearchBonus", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T25", + "name": "SensorUpdateSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T26", + "name": "ScriptHookPostSensorTail", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T27", + "name": "RefreshPlayerViewsSecondPass", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T28", + "name": "UpdateNodeLineSightingMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T29", + "name": "AbortInvisibleInterceptOrders", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T30", + "name": "RebuildCommunicationMasks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T31", + "name": "UpdateBankruptcyLimits", + "status": "blocked", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T32", + "name": "PostIncomingFleetWarnings", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T33", + "name": "ShipManagerEndOfTurnHooks", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T34", + "name": "RecordObservedDesigns", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T35", + "name": "RebuildPlayerReports", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "StrategyServer::OnAllCombatDone_Tail", + "id": "T36", + "name": "FinalizeTurnRecords", + "status": "stub", + "ran": 0, + "writes": 0, + "blockedWrites": 0, + "rng": 0 + }, + { + "driver": "host turn sequence (outside the two turn drivers)", + "id": "H01", + "name": "SaveWriterInvariants", + "status": "implemented", + "ran": 1, + "writes": 1, + "blockedWrites": 0, + "rng": 0 + } + ] + } + } + ] +}