findings/objects/turncommands-block.md -- why lane W's 44-item recovery and the
save's 35/38/41/61/123 items disagreed, in mechanism terms, plus the per-list
element records read off all 22 distinct helper writers.
Three separate causes, only one of which was a branch:
* layouts.md is OFFSET-SORTED and Game::TurnCommands is one of the 89 classes
whose offset order is not write order, because each gate bool sits after the
payload it gates. Align saves against streams.json, never layouts.md.
* the writer's tail is 27 std::list members, each written by its own helper as
WriteInt(size) then size element records. The linear recovery keeps one item
per CALL SITE, guesses its kind from an element field and drops the count.
44 - 17 members = 27 = the list count.
* 8 prologue items + 27 zero counts = the 35-item empty block, which is why
every earlier save was bit-identical here.
Corrects lane O's section 11 in place: the fleet-move element is
{fleetId, nHops, nHops x systemId} -- a counted route vector, not a fixed
{fleetId, 1, destSystemId, 0}. Only the three-item reading makes the item totals
close (a four-item element needs 26 lists on human-turn2 and 28 on zuul-turn15;
the writer has 27). Also records that neither noderoute save carries a fleet
move, so the node-route UI does not queue through that list.
22 of the 27 lists are labelled hypotheses: read off the instruction stream, but
no save exercises them and no element value has ever been observed. List 14 is
observed but not understood -- issuing one fleet move also queues
{sameFleetId, 0, true} there.
save_reader.py deliberately left alone, with the reasoning written down: it reads
CD generically for the AIAgent bodies too, state_checksum's digest tree is built
from the generic tree so typing CD would add no named leaf to any diff, dispatch
by CDT ordinal would need new machinery in the oracle, and rule 8 is pointed at
exactly this move -- a layout mirrored into both readers from one reading is not
two checks. coverage: PROVED, 0 error 0 warn on all eleven saves, unchanged.
ghidra/addresses.d/lane-q.json: 6 entries (the writer, its Read pair, and the
four observed list helpers). Validated by generating to a scratch path; the
tracked header was not regenerated.
52 lines
4.5 KiB
JSON
52 lines
4.5 KiB
JSON
{
|
|
"entries": [
|
|
{
|
|
"name": "TurnCommands_Write",
|
|
"addr": "0x00842540",
|
|
"convention": "thiscall",
|
|
"prototype": "void __thiscall Game::TurnCommands::Write(Mars::IStream* s) -- the writer for the `Player.<id>.TurnCommands_v5` custom-data block (CDT id -> one CD frame). 764 bytes, no loops of its own. Two halves. (1) A PROLOGUE of six flag-gated groups, each a WriteBool on a member followed, only when that bool is set, by the group's payload; write order is NOT offset order, which is why the offset-sorted layout view cannot be aligned to the wire: bool@0x0c gates f32@0x08 (research rate); bool@0x14 gates i32@0x10 (research target tech); bool@0x20 gates i32@0x18 + f32@0x1c (research boost spend + fraction); bool@0x2c gates bool@0x24 + i32@0x28; bool@0x3c gates f32@0x30,0x34,0x38; bool@0x6c gates a StreamableHelper<Game::CivilianRatios> frame on the member at 0x40. i32@0x04 (player id) is written first and unconditionally. (2) TWENTY-SEVEN std::list<T> members at 0x70..0x1a8, stride 0x0c ({_Myhead, _Mysize, _Alval}), each passed to its own free-function writer as helper(stream, &list). Every one is written unconditionally, so an empty list still costs one zero int: 8 prologue items + 27 zero counts = the 35-item block every no-orders save carries",
|
|
"status": "verified",
|
|
"source": "lane Q 2026-09-08, instruction-stream read of dumps/sots.exe; item counts reproduce all 11 saves in verify/results/saves exactly (35/38/41/61/123 items)"
|
|
},
|
|
{
|
|
"name": "TurnCommands_Read",
|
|
"addr": "0x00892ae0",
|
|
"convention": "thiscall",
|
|
"prototype": "void __thiscall Game::TurnCommands::Read(Mars::IStream* s) -- the reader paired with TurnCommands_Write (0x00842540). 1543 bytes; not decompiled by lane Q, listed so the pair is on the record",
|
|
"status": "mapped",
|
|
"source": "lane Q 2026-09-08, IStreamable vftable 0x00a25e00 slot pair"
|
|
},
|
|
{
|
|
"name": "TurnCommands_WriteFleetMoveList",
|
|
"addr": "0x0083e550",
|
|
"convention": "cdecl",
|
|
"prototype": "void __cdecl (Mars::IStream* s, std::list<T>* moves) -- writer for TurnCommands member +0xc4, the FLEET MOVE list. Per element: WriteInt(fleetId) then a NESTED counted int vector -- n = (v._Mylast - v._Myfirst) >> 2 written with WriteInt, then n ints. So a fleet move on the wire is {fleetId, hopCount, hopCount x systemId}, NOT a fixed {fleetId, 1, destSystemId, 0} quadruple: a multi-hop route is longer. Observed single-hop in human-turn2-orders.sav {1456, 1, [128]} and zuul-turn15-orders.sav {688, 1, [432]}",
|
|
"status": "verified",
|
|
"source": "lane Q 2026-09-08, instruction-stream read; corrects SAVE_FORMAT.md section 11's fleet-move element"
|
|
},
|
|
{
|
|
"name": "TurnCommands_WriteBuildOrderList",
|
|
"addr": "0x00822870",
|
|
"convention": "cdecl",
|
|
"prototype": "void __cdecl (Mars::IStream* s, std::list<T>* orders) -- writer for TurnCommands member +0x88, the BUILD ORDER list. Per element four WriteInts taken in DESCENDING member order (node+0x14, +0x10, +0xc, +0x8), so the wire order is {ordinal, designId, systemId, w}. Observed in zuul-turn15-orders.sav (5 orders, designs 608/576, system 384) and zuul-turn17-orders2.sav (20 orders, ordinals 6..25)",
|
|
"status": "verified",
|
|
"source": "lane Q 2026-09-08, instruction-stream read + UI cross-check from lane O's saves"
|
|
},
|
|
{
|
|
"name": "TurnCommands_WriteSystemRatesList",
|
|
"addr": "0x0082e3d0",
|
|
"convention": "cdecl",
|
|
"prototype": "void __cdecl (Mars::IStream* s, std::list<T>* cmds) -- writer for TurnCommands member +0xa0, the PLANETARY-BUDGET list. Per element WriteInt(systemId) then a StreamableHelper<Game::StarSystem::OutputRates> frame (helper vftable 0x00a1f884), which is the only NAMED sub-frame anywhere in the block (SRs SRt SRsc SRtf SRi SRoh SRnr). Observed once, in zuul-turn17-orders2.sav: system 384 with SRsc = 1.0",
|
|
"status": "verified",
|
|
"source": "lane Q 2026-09-08, instruction-stream read + RTTI vftable resolution"
|
|
},
|
|
{
|
|
"name": "TurnCommands_WriteColonizeList",
|
|
"addr": "0x00822960",
|
|
"convention": "cdecl",
|
|
"prototype": "void __cdecl (Mars::IStream* s, std::list<T>* orders) -- writer for TurnCommands member +0xb8, the COLONIZE list. Per element two WriteInts (node+0x8 then node+0xc): {shipId, w}. Observed once, in zuul-turn17-orders2.sav: three ships 2512/2544/2624, each with w = 1",
|
|
"status": "verified",
|
|
"source": "lane Q 2026-09-08, instruction-stream read + UI cross-check"
|
|
}
|
|
]
|
|
}
|