lane O: 7 purpose-built saves - issued turn commands, node routes, research-roll-pending

Produces the game states several board rows were blocked on, and reads each
one back out of the save before claiming it.

- CD/TurnCommands_v5 decoded: three saves with issued orders (research target,
  research boost, build orders, system rates, colonize, fleet move). Layout in
  SAVE_FORMAT.md section 11; ids cross-checked against the same file's Sys/Flt.
- Waypoint type 3 (NodeRoute) now has 16 instances in one save and the player's
  own fleet is a mover in two. Type 2 is shown NOT to be produced by a Move
  order for either node-drive race (Zuul and Human both emit type 3).
- research_roll_pending true at load in two saves; the ProcessTurn gate constant
  at 0x00a2c788 is a float 0.5, so the flag survives into ProcessResearch only
  while progress/cost <= 0.5 at the start of the turn.
- Budget tail: construction and a large maintenance are live; Nexp is empty in
  all 11 saves and the 1.8 UI has no expense slider, so slot 12 looks dead.

VM140 restored to the original 8-file SavedGames set (autosaves byte-identical)
and left at the main menu; VM140 row set FREE.
This commit is contained in:
alex 2026-09-08 08:35:13 -04:00
parent 648e9aae77
commit 9de77f7e5e
10 changed files with 73 additions and 8 deletions

File diff suppressed because one or more lines are too long

View file

@ -0,0 +1,12 @@
{
"entries": [
{
"name": "ResearchRollProgressThreshold",
"addr": "0x00a2c788",
"convention": "data",
"prototype": "const float 0.5f -- the progress-ratio threshold in ServerPlayer::ProcessTurn's `ResT != NULL && ResErrRoll != 0 && CONST < progressRatio` gate (events.md §3, window 0x008915ec-0x00891624). Read out of dumps/sots.exe: .rdata bytes at 0x00a2c788 are 00 00 00 3f (float 0.5); the following word 0x00a2c78c is float 100.0, so this is a float32, NOT the double an 8-byte read would suggest (that reads as 5.28e13). CONSEQUENCE, measured on the live game: ResErrRoll survives into ProcessResearch only while progress/cost <= 0.5 at the START of the turn, so the OnTechResearched draw (0x0088df20) can fire only when a single turn supplies more than half the target tech's remaining cost. See the board's `research_roll_pending save` row",
"status": "verified",
"source": "lane O 2026-09-08, PE section walk of dumps/sots.exe + turn-by-turn observation on VM140 (verify/results/saves/zuul-turn1[5-7]-*.sav)"
}
]
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

Binary file not shown.

View file

@ -268,3 +268,56 @@ resyncs / 0 hint-failures; the info count drops from 257/259/259 to 197 on turn1
60/62/62 `otnF` "read positionally" notes are gone, and every remaining info is a `"."`
item. `test_save_reader.py::TagNameCorrectionsTest` pins each correction and fails a
frame that uses the old spelling.
## 11. `CD` `Player.<id>.TurnCommands_v5` — the issued-order block (lane O, 2026-09-08)
Until now every save in the corpus carried the **same** empty `TurnCommands_v5` frame, so the
block looked structureless. `state_checksum --tree --depth 1` makes that concrete: `CD[0]` is
`3df7d93164fb1d7d`, **35 leaves / 122 B**, byte-for-byte identical in `turn1/2/3-state.sav`,
`zuul-turn5-species5.sav` and `human-turn3-noderoute.sav`. Saves made **after issuing orders and
before End Turn** grow it (all five below are `--strict` clean and `coverage: PROVED`):
| save | CD[0] | orders issued that turn |
|---|---|---|
| any no-orders save | 35 leaves / 122 B | — |
| `human-turn2-orders.sav` | 41 / 143 B | 1 fleet move |
| `zuul-turn15-orders.sav` | 61 / 226 B | research target + research boost + 5 build + 1 fleet move |
| `zuul-turn17-orders2.sav` | 129 / 498 B | 20 build + 1 system-rates + 1 colonize (3 ships) |
Body, as read off `zuul-turn15-orders.sav` / `zuul-turn17-orders2.sav` (all items `"."`-tagged,
so this is a positional record, not a named one):
```
int playerId (16 = the human player's PID; matches CDT's "Player.00000016.…")
bool ? (True in every sample, orders or not)
float researchRate (0.25 default -> 0.97 after the empire Savings/Research slider)
bool hasResearchTargetCmd ; if set, int techId follows (191 = WEP_GrnLas)
bool hasResearchBoostCmd ; if set, int money follows (216383 -- exactly the
Imperial Savings delta the boost produced)
float ? (0.9992… when a research command is present, absent otherwise)
bool x3 = False (three further command-present flags, never seen set)
int x2 = 0
int nBuildOrders ; nBuildOrders x { int ordinal; int designId; int systemId; int 0 }
(ordinal is the running build-queue index, not 0-based per turn;
designId 608 = DE Colonizer, 576 = DE Armor; systemId 384 = the
home system that owns the queue)
int 0
int nSystemRateCmds ; n x { int systemId; frame { float SRs; float SRt; float SRsc;
float SRtf; float SRi; float SRoh; int SRnr } }
(SRsc = 1.0 is the Planetary Budget slider pushed fully to
Construction; this is the only NAMED sub-frame in the block)
int 0
int nColonizeOrders ; n x { int shipId; int 1 }
int nFleetMoveOrders ; n x { int fleetId; int 1; int destSystemId; int 0 }
… trailing zero words (the unused command lists) to the frame END
```
Cross-checks that pin the decode: `384`/`432` resolve to `Sys` **Gallandro**/**Octans** in the
same file, `688` to the `Flt` the UI showed as *Alpha Fleet* with `Dest: Octans`, and `216383` is
the exact `Imperial Savings` drop the Boost Research panel caused. **The commands are pending, not
applied**: in `zuul-turn15-orders.sav` the `ServerPlayer` still reads `ResRate 0.25`, `ResTNm ''`,
`Sav 2,902,722` while the block already carries rate 0.97 and the boost — the block is the
client→server queue that `ProcessTurn` drains.
Not observed (the flags/lists that stayed zero in every sample): the three unset command-present
bools, and every list beyond the five above.