{ "entries": [ { "name": "ServerSystem_off_AFlags", "offset": "0xd4", "convention": "offset", "status": "verified", "prototype": "int AFlags -- the per-player 'has something here' mask, bit = PlyrIdx. Confirmed from ServerSystem::Write 0x00749630 at 0x00749aec (`mov ecx,[edi+0xcc]` with the \"AFlags\" tag at 0xa1f3b0; Write runs on the IStreamable subobject at +0x8, so 0xcc + 8 = 0xd4). Lane E3's +0xd4 is right" }, { "name": "ServerSystem_off_SnLv", "offset": "0x24", "convention": "offset", "status": "verified", "prototype": "int SnLv -- 2 bits per player (level = 0 none / 1 partial / 3 full), NOT SERIALISED. Written once per turn in the sensor phase at 0x0086a789 as `s->SnLv = (s->SnLv & ~(3<<2i)) | (level<<2i)`; read by ServerSystem::IsKnownTo 0x00746390 and RefreshVisibleOwnerIfKnown 0x0075bd70, and shipped to clients by SynchronizePlayer at 0x007c76a4. Because it is absent from every save, any NVO/TShn claim that depends on it is a hypothesis on the corpus alone (rule 6)" }, { "name": "ServerSystem_off_NVO_Myhead", "offset": "0x274", "convention": "offset", "status": "verified", "prototype": "std::map::_Myhead for NVO (the visible-owner record). The map object starts at +0x270 and is 0x10 bytes: allocator +0x0, _Myhead +0x4, _Mysize +0x8, comparator +0xc. From ServerSystem::Write 0x0074a097/0x0074a195 (`[edi+0x26c]`, +8 for the IStreamable subobject) and from the live arming probe, which validated +0x274 on 9 of 28 systems and +0x26c on 0" }, { "name": "ServerSystem_off_NVO_Mysize", "offset": "0x278", "convention": "offset", "status": "verified", "prototype": "std::map _Mysize for NVO. Write 0x0074a07d (`[edi+0x270]`) carries the \"NVO\" tag 0xa1f2c0, so the wire's NVO count is this word. CORRECTS objects/layouts.md, which lists NVO@0x278 as an int field and PID@0x274 as a handle: +0x274 is _Myhead and the \"PID\" tag belongs to the node KEY, not to the object" }, { "name": "ServerSystem_off_NVE_Myhead", "offset": "0x284", "convention": "offset", "status": "verified", "prototype": "std::map _Myhead for NVE (the sighting record). Write 0x0074a1bb. Confirms lane E3's +0x284/+0x288" }, { "name": "ServerSystem_off_NVE_Mysize", "offset": "0x288", "convention": "offset", "status": "verified", "prototype": "std::map _Mysize for NVE. Write 0x0074a1a1, tag \"NVE\"" }, { "name": "ServerSystem_off_NVs_Myhead", "offset": "0x294", "convention": "offset", "status": "verified", "prototype": "std::map _Myhead for NVs (the per-player PlayerView record). Write 0x0074a27d" }, { "name": "ServerSystem_off_NVs_Mysize", "offset": "0x298", "convention": "offset", "status": "verified", "prototype": "std::map _Mysize for NVs. Write 0x0074a263, tag \"NVs\"" }, { "name": "ServerSystem_NVO_node_off_TShn", "offset": "0x12", "convention": "offset", "status": "verified", "prototype": "int16 TShn, offset within the NVO map NODE (not the value). Node is _Left +0x0, _Parent +0x4, _Right +0x8, key(PlyrIdx) +0xc, value +0x10 = {int16 touched +0x10, int16 TShn +0x12, int32 OID +0x14, bool isind +0x18, IndependenceInfo indi +0x1c}, _Color +0x8c, _Isnil +0x8d -- so an NVO node is ~0x8e bytes, unlike NVE's 0x20. From Write 0x0074a0ca (`lea ecx,[ebx+0x12]`, tag \"TShn\") and confirmed live: a 4-byte watchpoint on value+0x0 trapped both halves separately" }, { "name": "ServerSystem_NVO_operator_index", "offset": "0x0075a890", "convention": "thiscall", "status": "verified", "prototype": "OwnRec* (NVO_map* this /* ServerSystem+0x270 */, const int* plyrIdx) // std::map::operator[]; returns node+0x10. Exactly ONE caller in the image: ServerSystem::SetVisibleOwner 0x0075b94b (NVE's operator[] 0x00752730 has three). That single-caller fact is what makes the writer inventory below complete" }, { "name": "ServerSystem_SetVisibleOwner", "offset": "0x0075b880", "convention": "thiscall", "status": "verified", "prototype": "void (ServerSystem* this, ServerPlayer* p, const OwnRec* src) // ret 8. Stamps value.touched = (int16)this->owner(+0x10)->Frame(+0x8) and copies src's TShn/OID/isind/indi verbatim into NVO[p->PlyrIdx]. THE ONLY WRITER OF NVO.TShn IN THE IMAGE. Local gate (converge point 0x0075b983, the inlined IndependenceInfo dtor, so it is a real branch and not a rule-4 artifact): skip the whole write when src->OID == p->id but this->PID(+0x100)->id != p->id, logging \"VisibleOwner: %s sees their self as owner of %s, but they are not.\"" }, { "name": "ServerSystem_SetVisibleOwner_TShnStore", "offset": "0x0075b961", "convention": "offset", "status": "verified", "prototype": "site `mov WORD PTR [eax+0x2],dx` -- the TShn store. Trapped live by a DR watchpoint on Spica's NVO root node: EIP 0x0075b965 (the trap is taken after the store retires), twice per End Turn. The touched-word store is the instruction before it at 0x0075b957 (trap EIP 0x0075b95a)" }, { "name": "ServerSystem_RecordVisibleOwner", "offset": "0x0075bca0", "convention": "thiscall", "status": "verified", "prototype": "void (ServerSystem* this, ServerPlayer* p) // ret 4. Builds an OwnRec with TShn = (int16)server->Frame, OID = this->PID(+0x100)->id, isind/indi from this->indi(+0x1c8), then calls SetVisibleOwner at 0x0075bd45. THIS is where TShn = Frame is decided. Confirmed live: the trapped frame's [ebp+4] was 0x0075bd4a" }, { "name": "ServerSystem_RecordVisibleOwnerNone", "offset": "0x0075bc00", "convention": "thiscall", "status": "mapped", "prototype": "void (ServerSystem* this, ServerPlayer* p) // TShn = Frame, OID = 0 -- 'you now see this system as unowned'. Frame read at 0x0075bc5f" }, { "name": "ServerSystem_ShareVisibleOwner", "offset": "0x0075b9b0", "convention": "thiscall", "status": "mapped", "prototype": "void (ServerSystem* this, ServerPlayer* from, ServerPlayer* to) // the NVO analogue of ShareObservation 0x00754d90: passes the source player's record verbatim, so TShn is NOT refreshed. No corpus save has an alliance, so this path has never executed (rule 6)" }, { "name": "ServerSystem_FindVisibleOwner", "offset": "0x0074d2e0", "convention": "thiscall", "status": "verified", "prototype": "OwnRec* (ServerSystem* this, ServerPlayer* p) // map find on NVO (0x0074d2f2 `lea ecx,[esi+0x270]`, 0x0074d300 `cmp edx,[esi+0x274]`); returns node+0x10 or NULL. Every caller only READS the record" }, { "name": "ServerSystem_GetVisibleOwnerId", "offset": "0x0074f710", "convention": "thiscall", "status": "mapped", "prototype": "int (ServerSystem* this, ServerPlayer* p) // FindVisibleOwner then rec->OID" }, { "name": "ServerSystem_IsKnownTo", "offset": "0x00746390", "convention": "thiscall", "status": "verified", "prototype": "bool (ServerSystem* this, ServerPlayer* p) // THE NVO GATE, and it is looser than IsVisibleTo 0x00743fb0. Whole body: `p && ( (this->AFlags(+0xd4) >> p->PlyrIdx(+0x28)) & 1 || ( p->PlyrIdx < 15 && ((this->SnLv(+0x24) >> 2*p->PlyrIdx) & 3) != 0 && TechTree::HasResearched(p->TechTree(+0xf4), 10000 /* CCC_AdvSens */) ) )`. Byte-decoded at 0x007463ae / 0x007463c0 / 0x007463cb / 0x007463da" }, { "name": "ServerSystem_RefreshVisibleOwnerIfKnown", "offset": "0x0075bd70", "convention": "thiscall", "status": "verified", "prototype": "bool (ServerSystem* this, ServerPlayer* p) // ret 4. IsKnownTo inlined byte-for-byte (0x0075bd88 AFlags, 0x0075bd93 idx<15, 0x0075bd9e SnLv, 0x0075bdb2 HasResearched(0x2710)), then RecordVisibleOwner at 0x0075bdbe. Confirmed live: the trapped frame's [[ebp]+4] was 0x0075bdc3, the return of that call" }, { "name": "SensorPhase_RefreshAllVisibleOwners", "offset": "0x00813700", "convention": "fastcall", "status": "verified", "prototype": "void (StrategyServer_S4* server) // for p in players(+0x50..+0x54) for s in systems(+0x40..+0x44): s->RefreshVisibleOwnerIfKnown(p) at 0x00813737. The player loop is UNFILTERED -- no NPC/species test anywhere on this path. Called from StrategyServer::UpdateSensors 0x0086a8d0 at 0x0086a91d" }, { "name": "SensorPhase_ComputeContactLevel", "offset": "0x00850cf0", "convention": "thiscall", "status": "mapped", "prototype": "int (Sensors* this, ServerPlayer* p, Entity* e) // 3 if IsVisibleTo (AFlags), else 1 if inside p's sensor envelope, else 0 (0x00850d18 / 0x00850e1b). Its result is packed into ServerSystem::SnLv(+0x24) at 0x0086a789" }, { "name": "ServerSystem_ChangeOwner", "offset": "0x0075bde0", "convention": "thiscall", "status": "mapped", "prototype": "void (ServerSystem* this, ServerPlayer* oldOwner, ServerPlayer* newOwner) // Game::ServerSystem vftable 0x00a2044c slot 6; records a visible-owner entry for both parties at 0x0075be77 / 0x0075bf2b" }, { "name": "StrategyServer_OnCommand_SetResearchRate", "offset": "0x00821a80", "convention": "thiscall", "status": "verified", "prototype": "bool (StrategyServer_S4* this, int playerId, const float* rate) // ret 8. Bumps ModCount at 0x00821a84 then sets player->ResRate(+0xbc) = clamp(*rate, 0.0f, 1.0f), logging \"OnCommand: Research rate %f constrained to %f for %s.\" when it clamps. NAMES lane W2's `StrategySim_ModCountBump_unresolved_00821a84`, the handler that fires 4x per End Turn on ref-turn2 -- once per player that has a client" }, { "name": "StrategyServer_OnCommand_SetSystemRates", "offset": "0x0086c3e0", "convention": "thiscall", "status": "verified", "prototype": "bool (StrategyServer_S4* this, int playerId, const SysRatesCmd* cmd) // ret 8. Bumps ModCount at 0x0086c3e6. Strings \"OnCommand: Unable to set system rates for %d(id). System not found.\" (0xa32448) and \"OnCommand: Player setting system rates for system he does not own.\" (0xa32400). NAMES lane W2's `..._unresolved_0086c3e6`" }, { "name": "StrategyServer_OnCommand_TransferShips", "offset": "0x0088bed0", "convention": "thiscall", "status": "verified", "prototype": "int (StrategyServer_S4* this, int playerId, const TransferCmd* cmd, ...) // ret 0xc. Bumps ModCount at 0x0088befe. Strings \"OnCommand: Cannot transfer ships. Player %d(id) not found.\" (0xa33248) and \"TransferShips: Fleet flagged as empty, but still has ships.\". NAMES lane W2's `..._unresolved_0088befe`" }, { "name": "StrategyServer_OnCommand_AddBuildOrder", "offset": "0x00849460", "convention": "thiscall", "status": "mapped", "prototype": "bool (StrategyServer_S4* this, int requesterId, const AddOrderCmd* cmd, int* outReason) // ret 0xc. Bumps ModCount at 0x0084946b. cmd = {int which, id target, id design, int arg}; picks target->+0xa4 (which==0) or +0x98 (which==1) as the BuildQueue, validates with 0x0083ce50, then BuildQueue::AddOrder 0x00845260 push_backs onto the list at queue+0x10. Reached from the Build Screen 0x00656540 via 0x00762fd0, and from the command replay at 0x0089009f. NAMES lane W2's `..._unresolved_0084946b`" }, { "name": "StrategyServer_OnCommand_SetResearchProject", "offset": "0x0088fe0a", "convention": "offset", "status": "verified", "prototype": "site `inc DWORD PTR [ebx+0x4]` -- a SECOND ModCount bump INLINED into ApplyTurnCommandBatch 0x0088f9b0 (at +0x45a), distinct from the one lane W2 found at 0x0089046c. String \"OnCommand: Cannot set research project for player %d(id). Player not found.\" (0xa30ed0). Fires 3x on turn1-state and 0x on turn2-state -- once per AI player that picks a research target on turn 1; the human player set a rate but no project, which is why the reference game's map banner reads \"No Research Project Assigned\"" }, { "name": "StrategyServer_OnCommand_CreateDesign", "offset": "0x00882910", "convention": "thiscall", "status": "verified", "prototype": "bool (StrategyServer_S4* this, int playerId, const DesignCmd* cmd) // ret 8. Bumps ModCount at 0x0088291a. Strings \"OnCommand: Cannot create design for player %d(id), player does not exist.\" (0xa32c40) and \"OnCommand: Unable to create design. ID not specified.\" (0xa32c04). Fires 1x on turn1-state, 0x on turn2-state. ANSWERS LANE AI4'S FALSIFIER: command list 1 emits a design COMMAND, not merely a design object that the build order names" }, { "name": "BuildQueue_AddOrder", "offset": "0x00845260", "convention": "thiscall", "status": "mapped", "prototype": "void (BuildQueue* this, int arg, ShipDesign* design) // builds an order record with five fields pre-set to -1 and push_backs it onto the std::list at this+0x10/+0x14 (\"list too long\" guard at 0x008452e4)" }, { "name": "StrategyServer_CanAddBuildOrder", "offset": "0x0083ce50", "convention": "cdecl", "status": "mapped", "prototype": "bool (ServerPlayer* who, BuildQueue* q, ShipDesign* d, int* outReason) // the 'can this be built' validator; writes a numbered reason (0,1,2,8,...) through arg 4 and calls BankruptcyLevel 0x0080db10" }, { "name": "ServerPlayer_off_ReqCL", "offset": "0xfd", "convention": "offset", "status": "verified", "prototype": "bool ReqCL -- 'this player requires a client'. THE PREDICATE BEHIND Player.Status: a player with ReqCL == 0 never reaches SetupAIPlayers' AI list (gate at 0x007bc25c), so never gets a StrategyClient, so is never the subject of MarkPlayerTurnEnded and keeps the 0 that ResumePlaying wrote. The script binding that creates the four '_NPC' pseudo-players clears it explicitly at 0x00797bcb" }, { "name": "StrategyNetworkServer_AllPlayersTurnEnded", "offset": "0x007cfd00", "convention": "thiscall", "status": "verified", "prototype": "bool (StrategyNetworkServer* this) // vftable slot 3. The game's OWN statement of which players must carry Status == 4: `for p in players: if (!p->ReqCL(+0xfd)) continue; if (p->Elim(+0xf8)) continue; if (p->Status(+0x164) != 4) return false;` -- loop 0x007cffb7..0x007d0002 with edi = 4 loaded at 0x007cffa3" }, { "name": "StrategyNetworkServer_SetupAIPlayers", "offset": "0x007bc1c0", "convention": "thiscall", "status": "verified", "prototype": "void (StrategyNetworkServer* this) // the ReqCL gate at 0x007bc25c decides which players get an AI client at all; AddAIPlayer 0x007bc333 -> 0x007b8ab0" }, { "name": "StrategyServer_OnPlayerEndTurn", "offset": "0x007d9af0", "convention": "thiscall", "status": "verified", "prototype": "void (StrategyServer* this, const SNMPlayerEndTurn* msg) // ONE call per network message, not a loop: handle = msg->+0x4 straight off the wire, MarkPlayerTurnEnded(this+4, handle) at 0x007d9b25 with NO gate before the store. Reached from StrategyNetworkClient::OnMessage 0x00784816. The field tests at 0x007d9b5d/0x007d9ba2/0x007d9bb0 all follow the store and only decide whether to raise a 'waiting on X' event" }, { "name": "StrategyServer_ProcessTurn_StatusOneStore", "offset": "0x007dcc8a", "convention": "offset", "status": "verified", "prototype": "site `mov DWORD PTR [eax+0x164],1` -- the phase-31 Status write lane T2 located. Its gate, decoded at 0x007dcc78, is `if (p->IsAI(+0xf9) == 0 || p->+0xfa != 0)`, over the encounter records at S+0x1e8 (0x74-byte outer, 0x44-byte inner with the ServerPlayer* first). It reads NO species field" }, { "name": "Game_SNMSetPlayerStatus_vftable", "offset": "0x00a2d718", "convention": "offset", "status": "verified", "prototype": "vftable for Game::SNMSetPlayerStatus (factory 0x0080f990, sizeof 0xc: vptr / +4 player INDEX / +8 status). Its handler is the OnMessage+0xa15 store lane W2 found: no gate, no field test -- the value comes verbatim off the wire. The sender is StrategyNetworkServer::Update 0x007cda40 at 0x007cdcf0, which computes `2*(flag!=0) + 2`, i.e. 4 when the player has nothing pending and 2 when it does" } ] }