BuildQueue::ProcessTurn 0x00890d50 disassembled to the next function start (Ghidra says 1230 bytes; the body is 1264 and its end lands inside the epilogue -- rule 17 again). Corrects the recorded prototype: the first stack argument is the StrategyServer S frame, not the system, so the build-completed event goes on the sim and the hull's turn stamp is Frame. THE HEADLINE IS A CORRECTION. Lane E2's open census leaf (shpt[0], one destroyer short on both reference pairs) cannot be closed by S11's build-queue sub-pass: measured over the corpus, both reference saves carry three empty BQ frames, every hbq false, and the one TurnCommands_v5 block empty. The order is created inside the turn by the AI -- PvSav shows 11,900 leaving the treasury before spine phase 0. The blocker moves to game/ai. Also: the per-class built counter has EXACTLY ONE writer in the image (image-wide byte scan); ShipRecords sized by enumeration; the ship/fleet birth chain end to end, correcting lane B5 -- FtFlg 0x400 is set by EVERY fleet born through the create path, not by retreat; one home fleet per system cached at ServerSystem+0x238; and a third indirection class vtable_map.py cannot see (the ship-borne wrapper is dispatched from a stack-built pointer table). 21 addresses in ghidra/addresses.d/lane-b6.json, validated to a scratch path against the merged set (1,077 entries, no duplicate names); two entries dropped as duplicates of lane B5's and recorded as agreements instead.
172 lines
No EOL
14 KiB
JSON
172 lines
No EOL
14 KiB
JSON
{
|
|
"entries": [
|
|
{
|
|
"name": "BuildQueue_ProcessTurn_RealEnd",
|
|
"addr": "0x00891240",
|
|
"convention": "site",
|
|
"prototype": "The byte AFTER Game::BuildQueue::ProcessTurn's last instruction (`ret 0x8` at 0x00891226, then int3 padding). Ghidra reports the function as 1230 bytes from 0x00890d50, i.e. ending at 0x0089121e -- INSIDE the epilogue, before the security cookie check. The body is 1264 bytes. Earned rule 17. Note that this address is ALSO the entry of SystemBuildQueue_AttachBuiltShip, the function's own slot-10 callee, which is why the fleet half of construction looked absent from the pass",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08, instruction stream 0x00890d50-0x00891240)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_off_Owner",
|
|
"offset": "0xc",
|
|
"convention": "field",
|
|
"prototype": "ServerPlayer* -- the player that owns the queue. Read at 0x00890de9 (passed to the ship factory), 0x00890ec9 (passed to the system's post-build hook), 0x00890ef4 (the base of the ShipRecords update) and 0x00890f75 (the build-completed event's owner field)",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_off_OrderList",
|
|
"offset": "0x10",
|
|
"convention": "field",
|
|
"prototype": "std::list<ShipBuildOrder> head sentinel. The pass walks it as `node = *(head); while (node != head) node = *node`, so it is the MSVC circular list. Read at 0x00890d9a and re-read every iteration at 0x00890d9d / 0x00891020",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_off_OrderCount",
|
|
"offset": "0x14",
|
|
"convention": "field",
|
|
"prototype": "int -- the list's element count. Used as the reserve hint for the removal sweep's scratch vector at 0x0089104a and decremented once per unlinked order at 0x008911b0",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ShipBuildOrder_off_ConLeft",
|
|
"offset": "0x18",
|
|
"convention": "field",
|
|
"prototype": "int conleft, measured from the std::list NODE base (node+0x0 next, +0x4 prev, +0x8 the order's own vptr -- the list is polymorphic -- then +0xc desID, +0x10 con, +0x14 sav, +0x18 conleft, +0x1c ordID, +0x20 ShipDesign*). Compared against the remaining points at 0x00890db5, decremented at 0x008910c4, zeroed at 0x0089100c, and it is the removal sweep's predicate at 0x00891070",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ShipBuildOrder_off_Design",
|
|
"offset": "0x20",
|
|
"convention": "field",
|
|
"prototype": "ShipDesign* -- from the same std::list node base as ShipBuildOrder_off_ConLeft. Loaded at 0x00890dbe and is the source of the money cost (+0xc0), the role flags (+0xb8) and the hull class (+0x12c) the completion reads",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_vslot_ChargeMoney",
|
|
"offset": "0x24",
|
|
"convention": "vslot",
|
|
"prototype": "bool (StrategySim* sim, int64 cost) -- vtable slot 9. Called at 0x00890ddc only when design->+0xc0 > 0; a FALSE return SKIPS that order and the pass continues with the next one rather than stopping. Pure in Game::BuildQueue's own vtable (0x00a31328); Game::SystemBuildQueue (0x00a31358) binds 0x00809910 and Game::ShipBuildQueue (0x00a31388) binds 0x0091e480",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_vslot_AttachBuiltShip",
|
|
"offset": "0x28",
|
|
"convention": "vslot",
|
|
"prototype": "void (StrategySim* sim, StarShip* ship) -- vtable slot 10, called at 0x00890e20 immediately after the ship is created. Pure in the base vtable; Game::SystemBuildQueue binds 0x00891240, Game::ShipBuildQueue binds 0x0081c000. This is where join-an-existing-fleet vs create-a-new-one is decided",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "SystemBuildQueue_AttachBuiltShip",
|
|
"addr": "0x00891240",
|
|
"convention": "__thiscall",
|
|
"prototype": "void (SystemBuildQueue* this, StrategySim* sim, StarShip* ship) /* RET 8. Game::SystemBuildQueue vtable slot 10. Routes the newly built hull by design role flags -- design->+0xb8 & 0x80000, design->+0xbc & 0x4, design->+0xb8 & 0x800000, design->+0xb8 & 0x400 each take their own handler -- and FALLS THROUGH at 0x00891328 to `if (ship->FltID == 0) ServerSystem_AttachShipToHomeFleet(sys, ship)`. NOTE the 0x400 tested at 0x00891311 is a DESIGN role bit and is NOT the fleet's FtFlg 0x400 */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ServerSystem_AttachShipToHomeFleet",
|
|
"addr": "0x0074f4d0",
|
|
"convention": "__thiscall",
|
|
"prototype": "void (ServerSystem* this, StarShip* ship) /* The join-or-create step. If this->+0x238 is non-null the cached home fleet is reused; otherwise an id is drawn from the object-id allocator (0x0074f500) and StrategyServer_CreateFleet (lane B5, 0x0085b340) builds a fleet at the system's position with a NULL name override, the result is cached in this->+0x238 (0x0074f513), FtFlg |= 0x20 (0x0074f519), and StarFleet_AddShip links the hull. ONE home fleet per system: every hull built at that system in later turns joins it */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ServerSystem_off_HomeFleet",
|
|
"offset": "0x238",
|
|
"convention": "field",
|
|
"prototype": "StarFleet* -- the cached fleet newly built hulls join. Tested at 0x0074f4d6 and written at 0x0074f513. NEW OFFSET: not in struct-recovery.md's ServerSystem table",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "StarShip_Create",
|
|
"addr": "0x008656f0",
|
|
"convention": "__thiscall",
|
|
"prototype": "StarShip* (ObjectHost* this, int id, ServerPlayer* owner, ShipDesign* design) /* RET 0xc. operator new(0xb0) at 0x0086571c, ctor 0x00861280, then IDMap_Insert at 0x00865754 which is where the object id lands in ship->+0x4. Reached from exactly two places in the image: the build queue (through the 0x004f41a0 thunk) and the trade manager's encounter spawner */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "StarShip_Ctor",
|
|
"addr": "0x00861280",
|
|
"convention": "__thiscall",
|
|
"prototype": "void (StarShip* this, ObjectHost* host, ShipDesign* design, ServerPlayer* owner) /* Zeroes the object through 0x0080c960 (which leaves +0x48, +0x60 and +0xac at -1), then writes +0xc host, +0x14 design (DesID), +0x10 owner (PlrID); allocates the ship-borne BuildQueue into +0x98 when design->+0xb8 & 0x400000, and three Population objects into +0x9c/+0xa0/+0xa4 when design->+0xb8 & 0x4000000; finally 0x00854680 copies the cached design stats -- Range +0x20 from design+0xe8, Health +0x24..+0x30 from design+0xec.., RefCap +0x6c, RepCap +0x70, ConCap +0x68 from design+0xd0. FltID (+0x64) is born NULL and is set by StarFleet_AddShip */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "StarShip_off_TurnBuilt",
|
|
"offset": "0xac",
|
|
"convention": "field",
|
|
"prototype": "int tblt -- the turn the hull was completed. Born -1 in the constructor's default sweep (0x0080c9fb) and overwritten by the build queue at 0x00890e02 with the StrategySim's Frame word. MEASURED: the six hulls the zuul turn-16 -> turn-17 pair adds all carry tblt equal to the NEW turn number, which independently confirms that BeginProcessTurn's Frame increment happens BEFORE the spine, so a phase reading Frame during a turn sees the turn it is producing, not the one it started from",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ShipRecords_BuiltCounterSite",
|
|
"addr": "0x00890ef7",
|
|
"convention": "site",
|
|
"prototype": "`inc DWORD PTR [esi+edx*4+0x1b4]` with esi = the queue's owner and edx = design->+0x12c (the hull class). THE ONLY WRITER OF THE PER-CLASS BUILT COUNTER IN THE WHOLE IMAGE: a byte scan for the indexed-increment form at that displacement over all executable sections returns this one site. A hull that reaches the wire with the counter bumped came through the build queue and through nothing else",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ServerPlayer_off_ShipRecordsBuilt",
|
|
"offset": "0x1b4",
|
|
"convention": "field",
|
|
"prototype": "int built[3] -- the first of Game::ShipRecords' four parallel per-hull-class arrays (built, lost, killed, inService; wire tags srb/srl/srk/sri under the srnc count). SIZED BY ENUMERATION, not by what the code touches: three classes x four arrays x 4 bytes from 0x1b4 lands exactly on 0x1e4, the per-design vector, which is the next thing the same function reads. struct-recovery.md places the ShipRecords sub-object at 0x1b0, so 0x1b0 is its vptr",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ServerPlayer_off_ShipRecordsByDesign",
|
|
"offset": "0x1e4",
|
|
"convention": "field",
|
|
"prototype": "std::vector<{int srd; int src; int srb; int srl; int sri}> at +0x1e4/+0x1e8/+0x1ec, stride 0x14 (the wire's srbd section). The completion scans it linearly for a record whose first word equals the design's object id (0x00890f10), appends one when there is no hit (0x00890f4c) and increments the record's third word (0x00890f61). MEASURED: srd really is the design's save id -- the zuul corpus shows records keyed 608/576/114/816/18/34/130 against build orders naming exactly those desIDs",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "TradeManager_SpawnEncounterSquadron",
|
|
"addr": "0x0088f070",
|
|
"convention": "__thiscall",
|
|
"prototype": "int (ServerTradeManagerImpl* this, int* spec) /* THE SECOND AND ONLY OTHER ROOT THAT CREATES SHIPS. Draws two ids from the object-id allocator, creates a StarFleet through StrategyServer_CreateFleet (lane B5, 0x0085b340) (0x0088f314) and then loops StarShip creation (0x0088f375) + StarFleet_AddShip (0x0088f381). Reached only from 0x008926ce, itself reached only from ServerTradeManagerImpl vtable slot 17 (0x008938a0), which picks its target with an RNG draw. It does NOT touch ModCount, and it does NOT touch ShipRecords -- so an encounter squadron is invisible to the per-class built counter, which is why that counter is a clean discriminator for player-built hulls */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "ShipBorneBuildQueue_ProcessTurn",
|
|
"addr": "0x00789500",
|
|
"convention": "__thiscall",
|
|
"prototype": "void (ShipAction* this, ...) /* The construction-ship wrapper around BuildQueue::ProcessTurn (calls it at 0x00789551 with the ship's own queue at ship->+0x98 and its ConCap at ship->+0x68 as the point budget). REACHABILITY NOTE, and it is a new indirection class for the campaign: this function has ZERO call sites and is in NO vtable. Its address is written into a STACK-BUILT function-pointer table by the ship-action dispatcher (0x007b9c4b `mov eax,0x789500`, stored at 0x007b9c50), alongside five siblings. tools/vtable_map.py cannot see edges of this shape, so `no caller` and `no vtable caller` are BOTH lower bounds */",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_ProcessTurn_Arg0Correction",
|
|
"addr": "0x00752589",
|
|
"convention": "site",
|
|
"prototype": "CORRECTION to addresses.json's BuildQueue_ProcessTurn prototype, which reads `int (BuildQueue* this, ServerSystem* sys, int points)`. The FIRST STACK ARGUMENT IS NOT THE SYSTEM. At the only real call site the pushed value is `[sys+0x10] - 4` (0x0075257c `lea edi,[eax-0x4]` with eax = [esi+0x10], esi = the ServerSystem, ecx = [esi+0xa4] = the queue), i.e. the StrategyServer `S` frame -- the same object lane B5's StrategyServer_CreateFleet takes at S+4, one word higher. Two consequences the old prototype hides: the build-completed event is pushed onto a list at S+0x2b0, NOT onto the system; and the turn stamp written into the new hull at 0x00890e02 is S+0xc, which StrategyServer::Write tags `Frame`. AGREEMENT with lane T section 0 (the two bases four bytes apart) and with lane A2 (S+0xc is Frame, not ModCount)",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
},
|
|
{
|
|
"name": "BuildQueue_off_OwningSystem",
|
|
"offset": "0x8",
|
|
"convention": "field",
|
|
"prototype": "ServerSystem* -- the system a SystemBuildQueue belongs to, read by the slot-10 attach handler at 0x00891246 as `(this->+0x4 == 0) ? this->+0x8 : 0`. Also read by the pass itself at 0x00890f89 for the build-completed event",
|
|
"status": "verified",
|
|
"source": "findings/subsystems/ship-construction.md (lane B6 2026-09-08)"
|
|
}
|
|
]
|
|
} |