sots-re/ghidra/addresses.json
lane P 0cd84690af lane P: SetResearched cascade read; 10 new addresses.json entries
Read TechTree::SetResearched (0x00581e10) end to end while wiring the research event
posts into ours (sots-engine wip/post-events). Two things it settles:

- SetResearched invokes the owner callback as vft+0x10(def, (flags>>2)&1), so bit 2 of
  flags IS OnTechResearched's "silent" argument. ProcessResearch passes flags = 2, so
  silent is FALSE and the completion event IS posted. Previously this was only
  inferable from lane R's observation that a completion call moved EvNxID by two.
- The unlock cascade in full: the node's child edges set child state 0->1 and
  child.costRP = min(child.costRP, edge.costRP); then a whole-tree sweep sets state 2
  and stamps turnAvailable from ModCount ONLY when it currently reads -1 (so it is
  sticky, and a node re-entering state 2 is not re-announced), and recurses into any
  zero-cost available node. Both loops test the node itself, not a parent -- there is
  no parent clause anywhere in the function, confirming lane E's correction.

Also records what is and is not known about vector<ObservedTech> at ServerPlayer+0x274:
the on-disk element shape is confirmed against turn3-state.sav (its string holds a tech
name), while sizeof(ObservedTech) and the append call site are still unpinned. The
cheapest route to the stride is now a measurement from the new observed_techs region,
not a search -- find-constant-uses does not index lea displacements.

New entries: ServerPlayer_off_ObservedTechs, TechNode_off_TurnAvailable /
TurnResearched / Order / Children, TechEdge_off_CostRP / ChildDef,
TechTree_off_OrderCounter, TechTree_SetResearched_flag_Force / _flag_Silent.
Generated header regenerated with tools/gen_addresses.py (386 -> 396 entries).
2026-09-08 04:16:59 -04:00

3174 lines
149 KiB
JSON

{
"_schema": "facts about Sword of the Stars.exe (GOG 1.8.1, build 19833). rva = file address - 0x00400000. Runtime address = GetModuleHandle(NULL) + rva (exe is ASLR-relocated). Prototypes marked 'unverified' are best current reading; a Ghidra round confirms them.",
"image_base": "0x00400000",
"entries": [
{
"name": "Mars_Application_Initialize",
"addr": "0x008a0e50",
"convention": "thiscall",
"prototype": "bool (Application* this, AppStartup* startup) /* ONE stack arg, RET 4, returns AL; EDX unused. startup = {HINSTANCE hInst; std::vector<std::string> args; ...; int nShow; const char* wndClass; int iconId} built in WinMain */",
"status": "verified",
"source": "handoff/loader-prototypes.md#application"
},
{
"name": "Mars_Application_Run",
"addr": "0x0089f5b0",
"convention": "thiscall",
"prototype": "void (Application* this) /* no stack args, plain RET, EDX not an input, EAX = last PumpMessages result (ignore) */",
"status": "verified",
"source": "handoff/loader-prototypes.md#application"
},
{
"name": "Process_PinAffinity",
"addr": "0x0089ee70",
"convention": "cdecl",
"prototype": "void (int coreIndex)",
"status": "unverified",
"source": "findings/objects/ghidra-recon.md"
},
{
"name": "CfgVar_RegisterKey",
"addr": "0x008b76a0",
"convention": "thiscall",
"prototype": "GlobalConst* (GlobalConst* this, void* storage, const char* key, ParseFn parser, const char* file) // RET 0x10",
"status": "verified",
"source": "findings/subsystems/loader-prototypes.md"
},
{
"name": "DemoApp_OnTick",
"addr": "0x0089a640",
"convention": "thiscall",
"prototype": "bool (DemoApp* this) /* no stack args; returns 0 when quitting (this+0x1b2), else 1 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#application"
},
{
"name": "StrategyClient_EndTurn",
"addr": "0x00783be0",
"convention": "thiscall",
"prototype": "void (StrategyClient* this)",
"status": "unverified",
"source": "findings/control-flow/turn-spine.md"
},
{
"name": "StrategyServer_BeginProcessTurn",
"addr": "0x007d98e0",
"convention": "thiscall",
"prototype": "void (StrategyServer* this)",
"status": "unverified",
"source": "findings/control-flow/turn-spine.md"
},
{
"name": "StrategyServer_ProcessTurn",
"addr": "0x007dc6c0",
"convention": "thiscall",
"prototype": "void (StrategyServer* this)",
"status": "unverified",
"source": "findings/control-flow/turn-spine.md"
},
{
"name": "ServerPlayer_ComputeBudget",
"addr": "0x00863030",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, Budget* out, bool projected) /* Budget = { int slot[22]; std::vector<{Tech* node, int points}> researchAlloc (3 words @+0x58); int overBudget @+0x64 }. The array is 22 ints, not 25: the three words after slot 21 are the allocation vector and grow by one 8-byte element exactly when the player has a research target. Slots: 0 Sav, 1 systemIncome+, 2 trade, 3 shipCarriedPop, 4 secondaryManager, 5 savingsInterest, 6 bonusIncome, 7 systemIncome-, 8 maintenance, 9 researchMoneyKept (written only when ResT is set), 10 debtInterest, 11 construction, 12 expenses, 13 researchMoneyGiven, 14 savingsGiven, 15 available, 16 researchMoney, 17 researchPoints, 18 TRA, 19 researchPointsGiven, 20 TRP, 21 totalResearchPoints */",
"status": "verified-by-trace",
"source": "handoff/formula-gaps.md#q5 + sots-engine docs/B1.md (live trace, 4623 calls)"
},
{
"name": "TechTree_ProcessResearch",
"addr": "0x005876c0",
"convention": "thiscall",
"prototype": "void (TechTree* this, Mars::RNG* rng, std::vector<{TechDef* target, int points}>* alloc, int* overbudget) /* args confirmed at the single call site: rng = StrategyServer+0x16c object, passed as the RNG *object* and re-based to &mt with lea ecx,[rng+4] before each NextFloat. Loop: node = this->nodes[*(int*)entry.target]; lo/hi from Cost x 50/150 /100 (32-bit imul, signed /100); spend = min(points, hi-progress) with NO clamp at 0; *overbudget += points-spend; odds and roll are both narrowed to float32 before the compare; decay loop hits every state-2 node with progress != 0; the trailing unlock-event loop makes no RNG draw and writes no node */",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "ServerSystem_Read",
"addr": "0x0075d4b0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this /*+8 IStreamable*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "ServerSystem_Write",
"addr": "0x00749630",
"convention": "thiscall",
"prototype": "void (ServerSystem* this /*+8*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "ServerPlayer_Read",
"addr": "0x008804d0",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this /*+0x3a0*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "ServerPlayer_Write",
"addr": "0x008563e0",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this /*+0x3a0*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StarFleet_Read",
"addr": "0x00702470",
"convention": "thiscall",
"prototype": "void (StarFleet* this /*+8*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StarFleet_Write",
"addr": "0x00701070",
"convention": "thiscall",
"prototype": "void (StarFleet* this /*+8*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StarShip_Read",
"addr": "0x00853fa0",
"convention": "thiscall",
"prototype": "void (StarShip* this /*+8*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StarShip_Write",
"addr": "0x008291f0",
"convention": "thiscall",
"prototype": "void (StarShip* this /*+8*/, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StrategyServer_Read",
"addr": "0x007d27a0",
"convention": "thiscall",
"prototype": "void (StrategyServer* this, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "StrategyServer_Write",
"addr": "0x0079fa70",
"convention": "thiscall",
"prototype": "void (StrategyServer* this, Stream* s)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "Stream_WriteNetworkObjectId",
"addr": "0x00816490",
"convention": "thiscall",
"prototype": "void (Stream* this, const char* tag, NetworkObject* obj)",
"status": "unverified",
"source": "findings/objects/struct-recovery.md"
},
{
"name": "RNG_Twist",
"addr": "0x00426e00",
"convention": "thiscall",
"prototype": "void (uint32_t* mtBlock) // ECX = &mt = RNG object + 4 (NOT the object). N=624 M=397 MATRIX_A 0x9908b0df, 227/396 split; ends with left(&mt+0x9c4) = 624 and next(&mt+0x9c0) = &mt[0]. RET 0",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_Seed",
"addr": "0x0049fdf0",
"convention": "thiscall",
"prototype": "RNG* (RNG* this, uint32_t seed) // RET 4; writes vftable 0x009e9aec, seeds from &mt = this+4: mt[0]=seed, mt[i]=0x6c078965*(mt[i-1]^(mt[i-1]>>30))+i for i=1..623, then Twist(&mt). OBJECT layout: {vftable @+0; uint32 mt[624] @+4 .. +0x9c3; uint32* next @+0x9c4; int left @+0x9c8} = 0x9cc bytes. Beware: NextFloat/NextInt/Twist take &mt, so *their* this+0x9c0/+0x9c4 are next/left",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_NextFloat",
"addr": "0x0047d830",
"convention": "thiscall",
"prototype": "float (uint32_t* mtBlock) // ECX = &mt = RNG object + 4. Lazy twist (if left==0 Twist()), y = *next++, left--, standard MT tempering, then value = (double)(uint32)y * 1/(2^32-1) -- the multiplier at 0x009e61b0 is 0x3df0000000001000 = 1/4294967295.0, NOT 2^-32; the unsigned fix-up at 0x009e61b8 is +2^32 after a signed fild. Left in st(0) at the current x87 precision and narrowed by the caller; range [0,1] INCLUSIVE",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_NextInt",
"addr": "0x004271c0",
"convention": "thiscall",
"prototype": "uint32_t (uint32_t* mtBlock, const uint32_t* n) // ECX = &mt; the bound is passed BY POINTER, RET 4. mask = smallest 2^k-1 >= *n via the or/shift cascade; draws (lazy twist + tempering) until (y & mask) <= *n -- note the loop condition is `ja`, so the result is uniform on [0, *n] INCLUSIVE, not [0, n)",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_size",
"offset": "0x9cc",
"convention": "offset",
"prototype": "sizeof(Mars::RNG) -- {vftable @+0; uint32 mt[624] @+4; uint32* next @+0x9c4; int left @+0x9c8}",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_off_State",
"offset": "0x4",
"convention": "offset",
"prototype": "uint32 mt[624]; also the pointer Twist/NextFloat/NextInt receive in ECX",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_off_Next",
"offset": "0x9c4",
"convention": "offset",
"prototype": "uint32* next; always &mt[624 - left], and recomputed from left on load, so `left` alone pins the stream position",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "RNG_off_Left",
"offset": "0x9c8",
"convention": "offset",
"prototype": "int left; words still unread in the current block",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechDef_off_TechId",
"offset": "0x0",
"convention": "offset",
"prototype": "int tech id -- the index into TechTree_off_Nodes",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "ResearchAlloc_stride",
"offset": "0x8",
"convention": "offset",
"prototype": "sizeof({TechDef* target, int points}) -- the element of the vector ProcessResearch walks",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "Mars_Application_MountModules",
"addr": "0x008a0a20",
"convention": "cdecl",
"prototype": "void (void) /* sots.ini [Modules] Mount<N> -> gobio::Init */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "Game_GetDefaultMounts",
"addr": "0x008991c0",
"convention": "cdecl",
"prototype": "void (int* count, const char*** table) /* count 3 @0x00b2d514, table @0x00a35e98 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "GlobalConst_ctor",
"addr": "0x008b76a0",
"convention": "thiscall",
"prototype": "GlobalConst* (GlobalConst* this, void* storage, const char* key, GlobalConstParseFn parser, const char* file) /* RET 0x10; static-init stub call. GlobalConst 0x10 bytes: {+0 void* storage (int/float/float[4]), +4 const char* key, +8 parser, +c const char* file} */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_Register",
"addr": "0x008b7610",
"convention": "cdecl",
"prototype": "void (GlobalConst* g)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_ParseInt",
"addr": "0x008b7000",
"convention": "cdecl",
"prototype": "void (int* storage, const char* text)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_ParseFloat",
"addr": "0x008b7020",
"convention": "cdecl",
"prototype": "void (float* storage, const char* text)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_ParseFloatScaled",
"addr": "0x008b70e0",
"convention": "cdecl",
"prototype": "void (float* storage, const char* text) /* sscanf(%f) into *storage, then *storage = (float)((double)*storage * *(double*)0x00af5210) */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_ParseColour",
"addr": "0x008b7110",
"convention": "cdecl",
"prototype": "void (float rgba[4], const char* text) /* '%d %d %d %d', defaults 255, /255, clamp 0..1 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConst_ParseRect",
"addr": "0x008b7040",
"convention": "cdecl",
"prototype": "void (int rect[4], const char* text) /* '%d %d %d %d' (x y w h); 2 keys (COMBATSETUP_CARD_*_RECT); layout inferred from the M1 trace, int vs float unconfirmed */",
"status": "verified-by-trace",
"source": "sots-engine docs/M1.md"
},
{
"name": "GlobalConst_ParseVec3",
"addr": "0x008b7080",
"convention": "cdecl",
"prototype": "void (float xyz[3], const char* text) /* '%f %f %f'; 5 keys (HIVER_SPAWN_*_GATE_HEIGHT / _SHIP_OFFSET ...); inferred from the M1 trace */",
"status": "verified-by-trace",
"source": "sots-engine docs/M1.md"
},
{
"name": "GlobalConst_ParseString",
"addr": "0x008b7670",
"convention": "cdecl",
"prototype": "void (std::string* storage, const char* text) /* *storage = text; MSVC2010 std::string 0x18 bytes {+0 char buf[16] | char* ptr when capacity > 15, +0x10 size, +0x14 capacity}; 146 keys (*_NAME, *_SOUND, *_TEXTURENAME, DERELICT_SECTION_nn ...); seen in the M1 trace */",
"status": "verified-by-trace",
"source": "sots-engine docs/M1.md"
},
{
"name": "GlobalConsts_LoadAll",
"addr": "0x008b76d0",
"convention": "cdecl",
"prototype": "void (void) /* called once from Application::Initialize; sets g_GlobalConstsLoaded */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "GlobalConsts_LoadFile",
"addr": "0x008b73c0",
"convention": "cdecl",
"prototype": "void (const char* file, GlobalConstMap* consts) /* consts = std::map<const char*,GlobalConst*,stricmp> of the keys registered for this file; MSVC2010 layout: map {+4 head node, +8 size}; node {+0 left, +4 parent, +8 right, +0xc const char* key, +0x10 GlobalConst*, +0x14 color, +0x15 isnil}; head: isnil=1, parent=root. Consumed keys are erased from the map. Hook this for the file-level read */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "g_GlobalConstsLoaded",
"addr": "0x00b2d740",
"convention": "data",
"prototype": "bool",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "g_GlobalConstRegistry",
"addr": "0x00b2d744",
"convention": "data",
"prototype": "std::map<const char*,GlobalConst*,stricmp_less> /* +4 (0x00b2d748) = header node pointer */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "g_GlobalConstFloatScale",
"addr": "0x00af5210",
"convention": "data",
"prototype": "double /* pi/180 as a DOUBLE (0x3f91df46a2529d39); GlobalConst_ParseFloatScaled does float word *= this in x87 and stores float */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m1"
},
{
"name": "Script_ctor",
"addr": "0x008cd700",
"convention": "thiscall",
"prototype": "Script* (Script* this) /* sizeof(Script) = 0x28 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_dtor",
"addr": "0x008cd720",
"convention": "thiscall",
"prototype": "void (Script* this)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_Open",
"addr": "0x008cd7d0",
"convention": "thiscall",
"prototype": "bool (Script* this, const char* path) /* gobio::ReadFile into this->buf */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_Close",
"addr": "0x008cd6c0",
"convention": "thiscall",
"prototype": "void (Script* this)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_ReadToken",
"addr": "0x008cd2f0",
"convention": "thiscall",
"prototype": "int (Script* this, char* out, uint outMax) /* 0 token, 1 no buffer/past end, 2 reached end */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_Next",
"addr": "0x008cd3e0",
"convention": "thiscall",
"prototype": "int (Script* this, ScriptToken* tok) /* tok: {int type; char key[1024]; char value[1024]}; type 1 kv, 2 block open, 3 '}' */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_SkipBlock",
"addr": "0x008cd4b0",
"convention": "thiscall",
"prototype": "int (Script* this, int depth) /* 0 ok, nonzero EOF */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Script_ScanToken",
"addr": "0x008cd1f0",
"convention": "custom",
"prototype": "/* EAX=cursor, ECX=&quotedFlag, EDX=out; stack: end, outEnd, &len — internal, do not hook */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "ConfigParser_ParseFile",
"addr": "0x008cd820",
"convention": "cdecl",
"prototype": "void (IConfigCallback* cb, const char* path, const char* blockName, bool* found) /* cb->vft[1](Script*) per matching block */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "RefCounted_Release",
"addr": "0x008a7230",
"convention": "fastcall",
"prototype": "int (IBuffer* obj) /* ECX=obj; --refcount, vft[0](1) at 0 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "WeaponDictionary_Init",
"addr": "0x0059a4c0",
"convention": "thiscall",
"prototype": "void (WeaponDictionary* this) /* reads Weapons/_weapons.txt manifest */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "WeaponDictionary_LoadWeapon",
"addr": "0x0059a230",
"convention": "thiscall",
"prototype": "void (WeaponDictionary* this, const char* path, int id, WeaponDef** out)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "WeaponDictionary_FindByName",
"addr": "0x00590a10",
"convention": "thiscall",
"prototype": "WeaponDef* (WeaponDictionary* this, const char* name) /* _stricmp over def->name @+0x40 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "WeaponDictionary_FindByNameSorted",
"addr": "0x00591e30",
"convention": "thiscall",
"prototype": "WeaponDef* (WeaponDictionary* this, const char* name) /* 'Weapon not found: \"%s\" - Was it added to the index file?' */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "WeaponDef_ctor",
"addr": "0x00598a20",
"convention": "thiscall",
"prototype": "WeaponDef* (WeaponDef* this, int index, int id, const char* path) /* sizeof 0x278 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "WeaponDef_ParseScript",
"addr": "0x00599070",
"convention": "thiscall",
"prototype": "bool (WeaponDef* this, Script* script, int* rc, TechTree* tree)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "SectionDictionary_ctor",
"addr": "0x00576f40",
"convention": "thiscall",
"prototype": "SectionDictionary* (SectionDictionary* this, TechTree* tree) /* reads Species/<Race>/_shipsections.txt for species 0..6 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "SectionDictionary_LoadSection",
"addr": "0x00576cd0",
"convention": "thiscall",
"prototype": "SectionDef* (SectionDictionary* this, const char* path, int species, int id)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "SectionDef_ctor",
"addr": "0x00574020",
"convention": "thiscall",
"prototype": "SectionDef* (SectionDef* this, int index, int species, int id) /* sizeof 0x3d8 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "SectionDef_ParseScript",
"addr": "0x005744e0",
"convention": "thiscall",
"prototype": "bool (SectionDef* this, Script* script, int* rc, TechTree* tree)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "Species_GetDirName",
"addr": "0x00545ec0",
"convention": "cdecl",
"prototype": "const char* (uint species)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "MasterTechTree_ctor",
"addr": "0x0058b870",
"convention": "thiscall",
"prototype": "MasterTechTree* (MasterTechTree* this) /* TechTree/MasterTechList.tech */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "MasterTechTree_LoadTechFile",
"addr": "0x0058b770",
"convention": "thiscall",
"prototype": "void (MasterTechTree* this, const char* path, int flag)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "MasterTechTree_ParseTech",
"addr": "0x0058b050",
"convention": "thiscall",
"prototype": "void (MasterTechTree* this, Script* script, int flag) /* one tech{} block */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m3"
},
{
"name": "DemoApp_LoadGameData",
"addr": "0x00899280",
"convention": "thiscall",
"prototype": "void (DemoApp* this) /* MasterTechTree -> +0x110, WeaponDictionary -> +0x118, SectionDictionary -> +0x114 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "DemoApp_EnsureGameData",
"addr": "0x00899870",
"convention": "thiscall",
"prototype": "void (DemoApp* this) /* once, from OnTick when this+0x10c is set */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m2"
},
{
"name": "gobio_Init",
"addr": "0x008d7010",
"convention": "cdecl",
"prototype": "void (std::vector<std::string>* mountPaths)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_FileSystemSet_ctor",
"addr": "0x008d6d60",
"convention": "thiscall",
"prototype": "FileSystemSet* (FileSystemSet* this, std::vector<std::string>* mounts)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_FileSystemSet_Open",
"addr": "0x008d5060",
"convention": "thiscall",
"prototype": "bool (FileSystemSet* this, const char* path)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_FileSystemSet_ListFiles",
"addr": "0x008d7090",
"convention": "thiscall",
"prototype": "void (FileSystemSet* this, const char* pattern, std::vector<std::string>* out)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_ReadFile",
"addr": "0x008d5140",
"convention": "cdecl",
"prototype": "bool (const char* path, IBuffer** out) /* whole file by relative path; THE hook point for M4 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Open",
"addr": "0x008d50d0",
"convention": "cdecl",
"prototype": "bool (const char* path)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Close",
"addr": "0x008d4d20",
"convention": "cdecl",
"prototype": "void (void)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Size",
"addr": "0x008d4d50",
"convention": "cdecl",
"prototype": "uint (void)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Read",
"addr": "0x008d4d80",
"convention": "cdecl",
"prototype": "uint (void* dst, uint n)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Exists",
"addr": "0x008d50f0",
"convention": "cdecl",
"prototype": "bool (const char* path)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_ListFiles",
"addr": "0x008d71d0",
"convention": "cdecl",
"prototype": "void (const char* pattern, std::vector<std::string>* out)",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Buffer_Create",
"addr": "0x008d4c90",
"convention": "custom",
"prototype": "/* size in EBX, IBuffer** out on stack — internal, do not hook */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Buffer_Data",
"addr": "0x00682d50",
"convention": "thiscall",
"prototype": "void* (IBuffer* this) /* vft[1] and vft[2]; = this+8 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_Buffer_Size",
"addr": "0x008d4c80",
"convention": "thiscall",
"prototype": "uint (IBuffer* this) /* vft[3]; = this+0xc */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_NativeFileSystem_ctor",
"addr": "0x008d57a0",
"convention": "thiscall",
"prototype": "NativeFileSystem* (NativeFileSystem* this, const char* root) /* sizeof 0x28; vtable 0x00a398ac */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_NativeFileSystem_Open",
"addr": "0x008d4ef0",
"convention": "thiscall",
"prototype": "bool (NativeFileSystem* this, const char* path) /* vft[1] fopen(root+path,'rb') */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_NativeFileSystem_Read",
"addr": "0x008d4c30",
"convention": "thiscall",
"prototype": "uint (NativeFileSystem* this, void* dst, uint n) /* vft[4] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_ZipFileSystem_ctor",
"addr": "0x008d6c40",
"convention": "thiscall",
"prototype": "ZipFileSystem* (ZipFileSystem* this, const char* zipPath) /* sizeof 0x34; vtable 0x00a39934 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_ZipFileSystem_Open",
"addr": "0x008d56a0",
"convention": "thiscall",
"prototype": "bool (ZipFileSystem* this, const char* path) /* vft[1]; case- and slash-insensitive */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "gobio_ZipFileSystem_Read",
"addr": "0x008d4b40",
"convention": "thiscall",
"prototype": "uint (ZipFileSystem* this, void* dst, uint n) /* vft[4] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "g_gobio",
"addr": "0x00b2e270",
"convention": "data",
"prototype": "FileSystemSet* /* -> 0x00b2e274 after gobio::Init */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "g_DefaultMountTable",
"addr": "0x00a35e98",
"convention": "data",
"prototype": "const char*[3] /* used when sots.ini has no [Modules] Mount<N>; contains sots_local_en.gob, sots.gob */",
"status": "verified",
"source": "handoff/loader-prototypes.md#m4"
},
{
"name": "Stream_OpenFile",
"addr": "0x008cfcb0",
"convention": "cdecl",
"prototype": "Stream* (const char* path) /* sniffs 'TXT' magic, returns a TextFileStream (0x420 bytes) opened for read */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ctor",
"addr": "0x0091e460",
"convention": "thiscall",
"prototype": "TextFileStream* (TextFileStream* this) /* vtable 0x00a428c0 */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_Open",
"addr": "0x008cfb90",
"convention": "thiscall",
"prototype": "bool (TextFileStream* this, const char* path, int mode /*1*/)",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "FileStream_ReadLine",
"addr": "0x008cfa30",
"convention": "thiscall",
"prototype": "uint (Stream* this, char* out, int max)",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_MatchLabel",
"addr": "0x0091e760",
"convention": "thiscall",
"prototype": "const char* (TextFileStream* this, const char* line, const char* label) /* NULL on END/mismatch */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ReadString",
"addr": "0x0091ec90",
"convention": "thiscall",
"prototype": "bool (TextFileStream* this, const char* label, char* out, int outMax) /* vft[1] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ReadBool",
"addr": "0x0091eba0",
"convention": "thiscall",
"prototype": "bool (TextFileStream* this, const char* label, bool* out) /* vft[2] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ReadFloat",
"addr": "0x0091eb30",
"convention": "thiscall",
"prototype": "bool (TextFileStream* this, const char* label, float* out) /* vft[3] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ReadInt",
"addr": "0x0091eab0",
"convention": "thiscall",
"prototype": "bool (TextFileStream* this, const char* label, int* out, int dflt) /* vft[4] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "TextFileStream_ReadNested",
"addr": "0x0091e8e0",
"convention": "thiscall",
"prototype": "void (TextFileStream* this, const char* label, IStreamable* obj /*NULL=skip*/) /* vft[5]; BEGIN..END */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "ParticleSystem_Read",
"addr": "0x008db650",
"convention": "thiscall",
"prototype": "void (ParticleSystem* this, Stream* s) /* .effect body */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "ParticleSystem_Write",
"addr": "0x008dad60",
"convention": "thiscall",
"prototype": "void (ParticleSystem* this, Stream* s)",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "EffectDictionary_Load",
"addr": "0x008b42b0",
"convention": "thiscall",
"prototype": "void* (EffectDictionary* this, const char* name) /* 'effects/' + name; Stream::OpenFile; 0x1c0-byte effect */",
"status": "unverified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "Stream_ReadString",
"addr": "0x008b9d90",
"convention": "cdecl",
"prototype": "void (Stream* s, const char* label, std::string* out, int flag) /* wrapper over vft[1] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "Stream_ReadInt",
"addr": "0x008b9d20",
"convention": "cdecl",
"prototype": "void (Stream* s, const char* label, int* out, int dflt) /* wrapper over vft[4] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "Stream_ReadBool",
"addr": "0x008b9c00",
"convention": "cdecl",
"prototype": "void (Stream* s, const char* label, bool* out) /* wrapper over vft[2] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "Stream_ReadFloat",
"addr": "0x008b9bc0",
"convention": "cdecl",
"prototype": "void (Stream* s, const char* label, float* out, int flag) /* wrapper over vft[3] */",
"status": "verified",
"source": "handoff/loader-prototypes.md#effect"
},
{
"name": "ServerPlayer_OnTechResearched",
"addr": "0x00891790",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, TechDef* def, bool silent) /* vft slot 4 of 0x00a327a4; hard-coded tech effects */",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "MasterTechTree_IsTech",
"addr": "0x0057d5d0",
"convention": "thiscall",
"prototype": "bool (MasterTechTree* this, TechDef* def, int techId) /* techId = 10000+index into g_TechIdNames; 0xc5 = none */",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "MasterTechTree_GetTechDef",
"addr": "0x0057d610",
"convention": "thiscall",
"prototype": "TechDef* (MasterTechTree* this, int techId)",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "TechTree_HasResearched",
"addr": "0x0057d810",
"convention": "thiscall",
"prototype": "bool (TechTree* this, int techId)",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "TechTree_Cost",
"addr": "0x0057da00",
"convention": "thiscall",
"prototype": "int (TechTree* this, TechNode* node) // RET 4. node==0 -> INT_MAX; node->costRP(+0x18)==INT_MAX -> INT_MAX; costRP<=0 or this->owner(+0xc)==0 or mult<=0 -> 0; else max(1, _ftol2((float)TechCostMult(owner, node->def) * costRP)). Reads only (no RNG, no writes), so a reimplementation may call it on a scratch tree whose +0xc is the real owner",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "ServerPlayer_TechCostMult",
"addr": "0x0080db50",
"convention": "thiscall",
"prototype": "float (ServerPlayer* this, TechDef* def) // 1.0 - 0.25 per applicable species research-bonus tech owned; read-only",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechTree_SetResearched",
"addr": "0x00581e10",
"convention": "thiscall",
"prototype": "void (TechTree* this, TechDef* def, int flags) // state 4 + turn/order stamps + owner callback + child unlock cascade. Makes no direct RNG draw; the owner callback is not audited, so a compare that runs it is out of scope",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechTree_SetResearched_flag_Force",
"offset": "0x2",
"convention": "constant",
"prototype": "flags bit 1 of SetResearched(def, flags): skip the PrereqsMet test and complete unconditionally. TechTree::ProcessResearch passes flags = 2",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechTree_SetResearched_flag_Silent",
"offset": "0x4",
"convention": "constant",
"prototype": "flags bit 2 of SetResearched(def, flags): the owner callback is invoked as vft+0x10(def, (flags>>2)&1), so this bit IS OnTechResearched's `silent` argument. ProcessResearch passes flags = 2, so silent = FALSE and the completion event IS posted",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechTree_off_Owner",
"offset": "0xc",
"convention": "offset",
"prototype": "ServerPlayer* owner (0 for a tree with no player)",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechTree_off_Nodes",
"offset": "0x10",
"convention": "offset",
"prototype": "std::vector<TechNode*> indexed by tech id (MSVC2010: 3 words {first@+0x10, last@+0x14, end@+0x18}); entries may be NULL",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechTree_off_OrderCounter",
"offset": "0x20",
"convention": "offset",
"prototype": "int completion-order counter; SetResearched stamps node+0x28 from it and post-increments it. Harness-audit row 9",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechNode_size",
"offset": "0x34",
"convention": "offset",
"prototype": "sizeof(TechNode) -- the ctor's operator new argument",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechNode_off_Def",
"offset": "0x0",
"convention": "offset",
"prototype": "TechDef* def; *(int*)def is the tech id used to index TechTree_off_Nodes",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechNode_off_State",
"offset": "0x14",
"convention": "offset",
"prototype": "int state (0 hidden, 1 parent researched, 2 available, 3 available AND selected as the current research target, 4 researched); ctor writes 0. The decay sweep tests == 2, so a state-3 node never decays -- confirmed by the B3 live trace",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5 + B3 live trace 2026-09-08"
},
{
"name": "TechNode_off_CostRP",
"offset": "0x18",
"convention": "offset",
"prototype": "int costRP, INT_MAX = no researched parent yet; ctor writes 0x7fffffff",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechNode_off_Progress",
"offset": "0x1c",
"convention": "offset",
"prototype": "int progress in RP; the only node word ProcessResearch itself writes besides the flag",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "TechNode_off_TurnAvailable",
"offset": "0x20",
"convention": "offset",
"prototype": "int turnAvailable; SetResearched's second sweep stamps it with the server ModCount ONLY when it currently reads -1 (a sticky first-availability stamp). EVENT_TECHS_UNLOCKED collects nodes with state==2 && turnAvailable==currentTurn",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechNode_off_TurnResearched",
"offset": "0x24",
"convention": "offset",
"prototype": "int turnResearched; SetResearched writes the server ModCount",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechNode_off_Order",
"offset": "0x28",
"convention": "offset",
"prototype": "int order; SetResearched writes the tree's completion-order counter, then bumps it",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechNode_off_Children",
"offset": "0x04",
"convention": "offset",
"prototype": "std::vector<TechEdge*> children (3 words at +0x04/+0x08/+0x0c)",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechEdge_off_CostRP",
"offset": "0x1c",
"convention": "offset",
"prototype": "int RP cost carried by the edge; SetResearched sets child.costRP = min(child.costRP, edge.costRP)",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechEdge_off_ChildDef",
"offset": "0x40",
"convention": "offset",
"prototype": "TechDef* the edge's child tech; SetResearched indexes tree->nodes by childDef->[0]",
"status": "verified",
"source": "lane P own disassembly pass 2026-09-08 (reva_call get-decompilation): TechTree::SetResearched 0x00581e10"
},
{
"name": "TechNode_off_Flag",
"offset": "0x2c",
"convention": "offset",
"prototype": "int flag (1 default from the ctor, 0 completed below 80% of cost, 2 over-budget event raised)",
"status": "verified",
"source": "B3 own disassembly pass 2026-09-08 (objdump/pei-i386): 0x005876c0, 0x0047d830, 0x00426e00, 0x0049fdf0, 0x004271c0, 0x0057da00, 0x008914a5"
},
{
"name": "MasterTechTree_ResolveTechIds",
"addr": "0x00581c10",
"convention": "thiscall",
"prototype": "void (MasterTechTree* this) /* fills TechDef*[196] at this+0 from g_TechIdNames */",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "IsCombatTechName",
"addr": "0x006965c0",
"convention": "cdecl",
"prototype": "int (std::string* name) /* membership in g_CombatTechNames (116) */",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "ServerPlayer_RebuildSpeciesTechFlags",
"addr": "0x0082bf10",
"convention": "fastcall",
"prototype": "void (ServerPlayer* this) /* flags[7] at +0x348 from SpeciesDef xnc tech ids */",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "ServerPlayer_ApplyAITechBonus",
"addr": "0x0080e330",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, TechDef* def)",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "ServerPlayer_SetAIBenefit",
"addr": "0x008186b0",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, bool on)",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "TechDef_GetPlagueCureMask",
"addr": "0x00537240",
"convention": "cdecl",
"prototype": "bool (TechDef* def, uint* maskOut)",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "ComputeTechBitmasks",
"addr": "0x004d7960",
"convention": "cdecl",
"prototype": "uint* (uint out[2], TechTree* tree) /* EBX-passed tree in caller; tables 0x00adf378 / 0x00adf478 */",
"status": "unverified",
"source": "handoff/tech-effects.md#3"
},
{
"name": "SpeciesDef_Get",
"addr": "0x00545cc0",
"convention": "cdecl",
"prototype": "SpeciesDef* (uint species) /* 0x00b10a00 + i*0x184 */",
"status": "verified",
"source": "handoff/tech-effects.md#4"
},
{
"name": "SpeciesDef_InitTable",
"addr": "0x005453a0",
"convention": "cdecl",
"prototype": "void (void)",
"status": "verified",
"source": "handoff/tech-effects.md#4"
},
{
"name": "ServerPlayer_UpdateBankruptcyLimits",
"addr": "0x00818600",
"convention": "fastcall",
"prototype": "void (ServerPlayer* this)",
"status": "verified",
"source": "handoff/formula-gaps.md#q1"
},
{
"name": "ServerPlayer_SetBankruptcyState",
"addr": "0x0080e260",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, int level)",
"status": "verified",
"source": "handoff/formula-gaps.md#q1"
},
{
"name": "ServerPlayer_BankruptcyLevel",
"addr": "0x0080db10",
"convention": "fastcall",
"prototype": "int (ServerPlayer* this)",
"status": "verified",
"source": "handoff/formula-gaps.md#q1"
},
{
"name": "ServerSystem_HazardMod",
"addr": "0x00747ae0",
"convention": "cdecl",
"prototype": "double (double suit, double ideal, double tol) /* clamp01(1-|suit-ideal|/(tol+0.1)) */",
"status": "verified",
"source": "handoff/formula-gaps.md#q2"
},
{
"name": "ServerSystem_TradePointsToMoney",
"addr": "0x007505b0",
"convention": "thiscall",
"prototype": "double (ServerSystem* this, double tradePoints) /* RET 8 */",
"status": "verified",
"source": "handoff/formula-gaps.md#q3"
},
{
"name": "ServerSystem_CalcSuitMod",
"addr": "0x007484d0",
"convention": "thiscall",
"prototype": "double (ServerSystem* this, int species)",
"status": "verified",
"source": "handoff/formula-gaps.md#q3"
},
{
"name": "ServerSystem_AccrueSystemBonus",
"addr": "0x0074d4f0",
"convention": "fastcall",
"prototype": "void (ServerSystem* this)",
"status": "verified",
"source": "handoff/formula-gaps.md#q4"
},
{
"name": "ServerSystem_SystemBonusPopTarget",
"addr": "0x0074b5a0",
"convention": "thiscall",
"prototype": "int (ServerSystem* this, float frac)",
"status": "verified",
"source": "handoff/formula-gaps.md#q4"
},
{
"name": "g_TechIdNames",
"addr": "0x009ff9e4",
"convention": "data",
"prototype": "struct { const char* name; int x; }[196] /* TechId = 10000+i */",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "g_CombatTechNames",
"addr": "0x00a19718",
"convention": "data",
"prototype": "const char*[116]",
"status": "verified",
"source": "handoff/tech-effects.md#0"
},
{
"name": "g_SpeciesDefTable",
"addr": "0x00b10a00",
"convention": "data",
"prototype": "SpeciesDef[7] (0x184 B each, .bss, filled by SpeciesDef_InitTable)",
"status": "verified",
"source": "handoff/tech-effects.md#4"
},
{
"name": "g_TechBitmaskTableA",
"addr": "0x00adf378",
"convention": "data",
"prototype": "struct { int techId; uint bit; }[32]",
"status": "verified",
"source": "handoff/tech-effects.md#3"
},
{
"name": "g_TechBitmaskTableB",
"addr": "0x00adf478",
"convention": "data",
"prototype": "struct { int techId; uint bit; }[29]",
"status": "verified",
"source": "handoff/tech-effects.md#3"
},
{
"name": "g_AITechValueTable",
"addr": "0x00a17888",
"convention": "data",
"prototype": "struct { int techId; float rebellionOdds; float bonus; }[6] /* rows: CCC_AI/CCC_AIAdmin/CCC_AIFac odds 0.1f bonus 0.5f, CCC_AIFRCON odds 0.2f bonus 0, CCC_AIVrus/CCC_AISlv odds 0 bonus 0 */",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "g_PERGATETRAFFIC_DRV_TpGate",
"addr": "0x00b23e2c",
"convention": "data",
"prototype": "int (config storage; PTR slot 0x00aedfe0)",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "g_PERGATETRAFFIC_DRV_GatAmp",
"addr": "0x00b23e30",
"convention": "data",
"prototype": "int (config storage; PTR slot 0x00aedfe4)",
"status": "verified",
"source": "handoff/tech-effects.md#1"
},
{
"name": "ServerPlayer_off_PlyrIdx",
"offset": "0x28",
"convention": "offset",
"prototype": "int PlyrIdx",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_OwnedSystems",
"offset": "0x30",
"convention": "offset",
"prototype": "std::vector<ServerSystem*> owned systems (MSVC2010 release layout: 3 words {first@+0, last@+4, end@+8}, handle ids, 4 B stride); save tag NumOwn/OwnId",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Species",
"offset": "0x5c",
"convention": "offset",
"prototype": "int Species (0 Human .. 6 Morrigi)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_ResRate",
"offset": "0xbc",
"convention": "offset",
"prototype": "float ResRate (research share of available money, 0..1)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_ResMod",
"offset": "0xc0",
"convention": "offset",
"prototype": "float ResMod",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_ResScl",
"offset": "0xc4",
"convention": "offset",
"prototype": "float ResScl",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_TRM",
"offset": "0xd0",
"convention": "offset",
"prototype": "float TRM (timed research multiplier bonuses)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_TRA",
"offset": "0xd4",
"convention": "offset",
"prototype": "int TRA (per-turn research-point contribution)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_TRP",
"offset": "0xd8",
"convention": "offset",
"prototype": "int TRP (per-turn research-point contribution)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Elim",
"offset": "0xf8",
"convention": "offset",
"prototype": "bool Elim",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_IsAI",
"offset": "0xf9",
"convention": "offset",
"prototype": "bool isAI /* gate ComputeBudget reads for the construction slot and for picking the AI difficulty-mod row */",
"status": "verified",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_NPC",
"offset": "0xfb",
"convention": "offset",
"prototype": "bool NPC",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_RebAI",
"offset": "0xfc",
"convention": "offset",
"prototype": "bool RebAI",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Maint",
"offset": "0x15c",
"convention": "offset",
"prototype": "int Maint (raw fleet upkeep, before the difficulty divisor)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_shrm",
"offset": "0x160",
"convention": "offset",
"prototype": "float shrm (shared research modifier)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Nexp",
"offset": "0x204",
"convention": "offset",
"prototype": "std::vector<ExpenseEntry> expense sliders (3 words; entry 16 B {int xid, int xmin, int xmax, float xper}); save tag Nexp",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_SetupIncomeMult",
"offset": "0x228",
"convention": "offset",
"prototype": "float income multiplier from the game-setup handicap block; ComputeBudget bonus slot = ftol((this - 1.0) x net)",
"status": "verified",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_SetupResearchMult",
"offset": "0x22c",
"convention": "offset",
"prototype": "float research multiplier from the game-setup handicap block; a factor of the research-points slot",
"status": "verified",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Sav",
"offset": "0x284",
"convention": "offset",
"prototype": "int Sav (treasury)",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_ResearchTarget",
"offset": "0x294",
"convention": "offset",
"prototype": "Tech* current research target (ResT); NULL = none",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_ObservedTechs",
"offset": "0x274",
"convention": "offset",
"prototype": "std::vector<ObservedTech> otch (3 words {first,last,end}); save tag otch, element {int otnF, otnL, odet; string otch; int owith}. All three words move on every tech completion (a realloc) -- observed live by both the ProcessResearch and the OnTechResearched player guards. NOT PINNED: the append call site and sizeof(ObservedTech); the byte span the observed_techs region reports is what will measure the stride",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/golden-trace-recapture.md (player guard, both completion calls)"
},
{
"name": "ServerPlayer_off_IncMod",
"offset": "0x30c",
"convention": "offset",
"prototype": "float IncMod",
"status": "verified-by-save",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_Aid",
"offset": "0x310",
"convention": "offset",
"prototype": "std::vector<PlayerAid> (3 words; entry 0x18 B, {+0x8 int researchPercent, +0xc int researchActive, +0x10 int savings, +0x14 int savingsActive})",
"status": "verified",
"source": "findings/objects/struct-recovery.md#2 (Read/Write serializers) + findings/subsystems/strategic-turn-internals.md#1.3"
},
{
"name": "ServerPlayer_off_SuitTol",
"offset": "0xb4",
"convention": "offset",
"prototype": "float SuitTol -- raised by the two adaptation techs; also caps the hazard money cost",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_MaxOH",
"offset": "0xb8",
"convention": "offset",
"prototype": "float MaxOH -- max over-harvest slider, raised by a float32 max() against 0.1f",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_TechTree",
"offset": "0xf4",
"convention": "offset",
"prototype": "TechTree* the player's own tech tree; its +4 is the MasterTechTree the IsTech chain uses",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_AIBn",
"offset": "0xfe",
"convention": "offset",
"prototype": "bool AIBn -- AI benefit active; gates ApplyAITechBonus and flips the bonus sign",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_CnTrd",
"offset": "0xff",
"convention": "offset",
"prototype": "bool CnTrd -- trade routes allowed (set by CCC_FtlEcon unless RebAI)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_CnRad",
"offset": "0x100",
"convention": "offset",
"prototype": "bool CnRad -- commerce raiding allowed (CCC_ComRaid)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_CnVItl",
"offset": "0x101",
"convention": "offset",
"prototype": "bool CnVItl -- may view other empires' intel (CCC_DatCor)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_hgs",
"offset": "0x102",
"convention": "offset",
"prototype": "bool hgs -- gravitic-syncing drive researched (DRV_GrvSyn); client-synced only",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_hadvs",
"offset": "0x103",
"convention": "offset",
"prototype": "bool hadvs -- advanced sensors (CCC_AdvSens)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_harcc",
"offset": "0x104",
"convention": "offset",
"prototype": "bool harcc -- arcologies (IND_ArcCon)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_pddm",
"offset": "0x108",
"convention": "offset",
"prototype": "float pddm -- multiplied by 0.25 by IND_HrdStrct",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_ConMod",
"offset": "0x10c",
"convention": "offset",
"prototype": "float ConMod[3] -- construction cost per hull class; techs subtract from all three, IND_OrbDry only from [1] and [2]",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_SavMod",
"offset": "0x118",
"convention": "offset",
"prototype": "float SavMod[3] -- IND_OrbFound subtracts 0.05f from all three",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_OutMod",
"offset": "0x124",
"convention": "offset",
"prototype": "float OutMod -- industrial output multiplier; several techs add, IND_HrdStrct multiplies by 0.9f",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_PopMod",
"offset": "0x130",
"convention": "offset",
"prototype": "float PopMod",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_TerraMod",
"offset": "0x134",
"convention": "offset",
"prototype": "float TerraMod",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_AMine",
"offset": "0x138",
"convention": "offset",
"prototype": "bool AMine -- asteroid mining (IND_AstMine)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_MinRate",
"offset": "0x140",
"convention": "offset",
"prototype": "float MinRate -- mining rate; IND_MsMine adds 1.0",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_PrGtTrf",
"offset": "0x148",
"convention": "offset",
"prototype": "int PrGtTrf -- per-gate traffic capacity; the two gate techs raise it with a SIGNED INTEGER max against a config int, not a float compare",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_CstR",
"offset": "0x150",
"convention": "offset",
"prototype": "float CstR, CstE, CstT at +0x150/+0x154/+0x158 -- set to 10.0f / 2.0f / 1.0f (FLD1) by DRV_FarCast",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_TechMaskA",
"offset": "0x190",
"convention": "offset",
"prototype": "uint32 design-option mask A; +0x194 is mask B. Rewritten wholesale by ComputeTechBitmasks on every completion; not serialised",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_TranslationKnown",
"offset": "0x1a4",
"convention": "offset",
"prototype": "uint32 sticky 'level-1 translation researched for species sp' bit mask, one bit per species, NPC (4) excluded. Second pass of RebuildSpeciesTechFlags; only ever ORed",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_HasVac",
"offset": "0x288",
"convention": "offset",
"prototype": "uint32 HasVac; +0x28c HasImm. Both get |= the plague-cure mask of the completing tech",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_NPTrk",
"offset": "0x290",
"convention": "offset",
"prototype": "uint32 NPTrk -- bit per species whose node-space traffic is visible; set by that species' node-track tech",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_NodeBore",
"offset": "0x308",
"convention": "offset",
"prototype": "int (*)[3] -- pointer to a separately allocated 3-word node-bore parameter block, NULL while no bore drive is researched (the updater allocates and frees it)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_CaptureDesigns",
"offset": "0x330",
"convention": "offset",
"prototype": "bool cdp -- set once CCC_SpyBm and IND_SlvgTech are both researched; the test is in the completion tail, not in either tech's branch",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_SpeciesTechFlags",
"offset": "0x348",
"convention": "offset",
"prototype": "uint32 flags[7] xenotech bits per target species, followed by a count word 7 at +0x364 (0x20 bytes assigned as a unit)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_ResearchRollPending",
"offset": "0x3b4",
"convention": "offset",
"prototype": "bool -- a pending plague-cure roll; run and cleared when the completing tech is the current research target",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_AIRebellion",
"offset": "0x3b8",
"convention": "offset",
"prototype": "AIRebellion* -- non-null while an AI rebellion object exists; the two AI techs notify it",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "TechTree_off_Master",
"offset": "0x4",
"convention": "offset",
"prototype": "MasterTechTree* -- the `this` MasterTechTree::IsTech / GetTechDef are called on",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_UpdateNodeBoreParams",
"addr": "0x008182c0",
"convention": "fastcall",
"prototype": "void (ServerPlayer* this) -- re-selects the node-bore parameters from the researched set and stores them in the +0x308 block, allocating it on first use and freeing it when no bore drive is researched",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "SelectNodeBoreParams",
"addr": "0x006e18e0",
"convention": "cdecl",
"prototype": "bool (int out[3], ServerPlayer* p) -- highest researched bore drive wins: DRV_RAD {95,60,5}, else DRV_REND {65,35,4}, else DRV_RIP {45,15,3}; false (and out left at {INT_MAX,INT_MAX,0}) when none. No species gate",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_SpeciesOfTranslationTech",
"addr": "0x0080e410",
"convention": "custom",
"prototype": "int (TechDef* def in EBX) -- index of the species whose SpeciesDef+0x74 node-track tech this def is, or -1",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "AITechRow",
"addr": "0x00690f70",
"convention": "cdecl",
"prototype": "struct {int techId; float rebellionOdds; float bonus;}* (TechDef* def) -- linear search of g_AITechValueTable, NULL when the def is none of them",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "g_AITechValueCount",
"addr": "0x00aea2ec",
"convention": "data",
"prototype": "int -- number of rows in g_AITechValueTable (6)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::OnTechResearched 0x00891790, RebuildSpeciesTechFlags 0x0082bf10, ApplyAITechBonus 0x0080e330, SetAIBenefit 0x008186b0, UpdateNodeBoreParams 0x008182c0, SelectNodeBoreParams 0x006e18e0, GetPlagueCureMask 0x00537240"
},
{
"name": "ServerPlayer_off_ServerLink",
"offset": "0x8",
"convention": "offset",
"prototype": "void* -- the StrategyServer subobject pointer; StrategyServer = *(void**)(player+8) - 4. Its +0x16c is the Mars::RNG* the strategic sim draws from, and its +8 the event manager",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::RollResearchEvent 0x0088df20, ResearchEventOdds 0x00820380, ServerPlayer::OnTechResearched 0x00891790"
},
{
"name": "StrategyServer_off_RNG",
"offset": "0x16c",
"convention": "offset",
"prototype": "Mars::RNG* -- the strategic generator (same object TechTree::ProcessResearch is handed)",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::RollResearchEvent 0x0088df20, ResearchEventOdds 0x00820380, ServerPlayer::OnTechResearched 0x00891790"
},
{
"name": "ServerPlayer_RollResearchEvent",
"addr": "0x0088df20",
"convention": "fastcall",
"prototype": "void (ServerPlayer* this) -- odds = ResearchEventOdds(this, this->ResT); draws EXACTLY ONE NextFloat from StrategyServer's generator, unconditionally, then fires 0x00889d60 when odds > roll. Called from OnTechResearched when the completing def is the current research target and the pending-roll byte at +0x3b4 is set. This is the extra RNG draw B3 observed on a completion",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::RollResearchEvent 0x0088df20, ResearchEventOdds 0x00820380, ServerPlayer::OnTechResearched 0x00891790"
},
{
"name": "ServerPlayer_ResearchEventOdds",
"addr": "0x00820380",
"convention": "thiscall",
"prototype": "float (ServerPlayer* this, TechDef* def) -- 0 when def is null; a plague-family path via 0x00535480, else the AI-rebellion path via AITechRow (odds column) gated on !NPC. Read-only, makes no draw",
"status": "verified",
"source": "B2 own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386): ServerPlayer::RollResearchEvent 0x0088df20, ResearchEventOdds 0x00820380, ServerPlayer::OnTechResearched 0x00891790"
},
{
"name": "ServerSystem_ProcessTurn",
"addr": "0x007598e0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* NO stack arguments -- plain RET, nothing reads [ebp+8]; Ghidra's decompile shows a spurious second parameter. Body order: unowned Infra decay -> ApplyInfraBonus -> ApplyPopBonus -> independent pop drift -> IsStable/ntdev -> AccrueSystemBonus -> ProcessPlague -> ProcessBuildQueue -> imperial growth -> civilian growth -> AdjustResources(-out[2]) -> TRes=0 -> RefuelInOrbit(1) -> Bats2 tick -> rcex tick -> haltv[0..2]=false -> ProcessSlaves -> ProcessRebellion -> addiction sweep. Consumes no RNG itself; ProcessPlague / civilian growth / ProcessSlaves are draw-free to depth 1, and ProcessRebellion is the only consumer */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_MoveFleet",
"addr": "0x007d9ee0",
"convention": "thiscall",
"prototype": "bool (StrategyServer* this, StarFleet* fleet, float dt) /* RET 8; dt is a 4-byte float pushed with fstp DWORD [esp]; returns AL, true only when the fleet ends exactly on a waypoint destination at the deepest recursion level. range = MinRange(fleet, +0.05f) -- ADDED, not subtracted; out of range with MinRange(0)==0 zeroes the RANGE, not the step; move = min(min(range, step), distance) with no floor at 0; recursion iff fraction < (double)0.9999f with dt' = float32((1-fraction)*dt) */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_ProcessFleetMovement",
"addr": "0x007da9a0",
"convention": "thiscall",
"prototype": "void (StrategyServer* this) /* no stack args, plain RET. Pursuit schedule, not a departing/in-transit split: classify every fleet whose current waypoint targets a fleet by owner relation (0 = pursuer, else follower); pass1 prey dt 0.5, pass2 pursuers dt 0.5 (an arrival retires the pair), pass3 remaining prey dt 0.5, pass4 everything unscheduled dt 1.0 (an uncaught pursuer gets 0.5), pass5 followers dt 1.0. Then FPdpos, gate traffic, OnFleetArrived, and clears flag 0x100 on every fleet. Makes no RNG draw of its own */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_MinRange",
"addr": "0x006ff6a0",
"convention": "thiscall",
"prototype": "float (StarFleet* this, float bias) /* RET 4; min over ships of ship->Range seeded with FLT_MAX (an empty fleet is unconstrained), then + bias, narrowed to float32. No ship is skipped -- range-exempt tankers still clamp the fleet */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_HasAllFlags",
"addr": "0x006fe1d0",
"convention": "thiscall",
"prototype": "bool (StarFleet* this, uint32 mask) /* (flags & mask) == mask, flags at fleet+0x10c */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_SetFlag",
"addr": "0x006fe1a0",
"convention": "thiscall",
"prototype": "void (StarFleet* this, uint32 mask, bool on) /* the only two writers of fleet+0x10c in the image */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_ResolveWaypoint",
"addr": "0x00701390",
"convention": "thiscall",
"prototype": "void* (StarFleet* this) /* no args; NULL when the waypoint vector is empty or the front waypoint's target id does not resolve in the entity hash at fleet->galaxy(+0x10)+0x80 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "IsGateTransitWaypoint",
"addr": "0x0056e6e0",
"convention": "cdecl",
"prototype": "bool (int waypointType) /* 7-entry jump table: true only for 4 and 5; false default */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "IsNodeWaypoint",
"addr": "0x0056e720",
"convention": "cdecl",
"prototype": "bool (int waypointType) /* 7-entry jump table: true only for 3. NOTE the node-LINE case of the movement switch is type 2, which this does NOT accept */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ProbabilisticJump",
"addr": "0x007b6700",
"convention": "thiscall",
"prototype": "void (StrategyServer* this, StarFleet* fleet, void* dest) /* waypoint type 5. v = float32(NextFloat() * player->CstE); arrives iff !(v > player->CstT) (equality arrives); on a miss the fleet is placed at dest + randomUnitVector * v -- scattered AROUND the destination by v, not advanced a fraction along the vector -- which costs a second raw draw. 1 draw on success, 2 on a miss */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "NodeLine_Step",
"addr": "0x00705510",
"convention": "cdecl",
"prototype": "void (Vector3* posOut, bool* arrivedOut, float nodespeed, float dt, vector<StarSystem*>* systems, const Vector3* from, const Vector3* to) /* builds the stutter segments, reverses them and pops from the back (so ascending by start); plain nodespeed between spheres, a constant per-segment speed inside one; arrived = (time < dt) || |along - length| < FLT_EPSILON, and on arrival the destination is copied verbatim */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "NodeLine_BuildStutterSegments",
"addr": "0x00705280",
"convention": "cdecl",
"prototype": "void (vector<StutterSegment>* out, vector<StarSystem*>* systems, const Vector3* from, const Vector3* to) /* chord parameters scaled to world distance and clamped to [0, length]; a chord with |start-end| <= 0.01f is dropped; std::sort ascending by start; then ONE forward pass over adjacent pairs sets BOTH boundaries of an overlap to end_i + 0.5*(end_i - start_{i+1}) -- the mirror of the midpoint, pushed forward past both chords. Nothing is dropped or clipped back, so a swallowed chord comes out inverted */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "SegmentSphereIntersect",
"addr": "0x008a64f0",
"convention": "cdecl",
"prototype": "bool (float* tNear, float* tFar, const Sphere4* s, const Vector3* from, const Vector3* to) /* wraps the quadratic solver 0x008a62c0; rejects a sphere the segment does not reach and reports an open end as -FLT_MAX / +FLT_MAX */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "DistPointToSegment",
"addr": "0x008e8eb0",
"convention": "cdecl",
"prototype": "float (const Vector3* p, const Vector3* a, const Vector3* b) /* projection parameter clamped to [0,1]. NOTE 0x008c8eb0 is a varargs formatter, not this */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_MaxPop",
"addr": "0x0074ab20",
"convention": "thiscall",
"prototype": "int (ServerSystem* this, ServerPlayer* p, int flag) /* RET 8 -- (player, flag), NOT (species, groupType). Species is derived from p->Species and the group type is hard-coded to 0, which makes the cross-species and INDSYS branches dead in this specialisation. Returns the int64 result clamped to [0, INT32_MAX], low dword only */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_MaxPopGeneric",
"addr": "0x0074a4a0",
"convention": "thiscall",
"prototype": "int64 (ServerSystem* this, int groupType, int species, ServerPlayer* p, float* suitOverride) /* RET 0x10 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_IsStable",
"addr": "0x0074ad90",
"convention": "thiscall",
"prototype": "bool (ServerSystem* this) /* false without an owner, with Infra < 1.0, or with Suit != IdealSuit */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ApplyInfraBonus",
"addr": "0x00746780",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* returns early unless ibon > 0 and Infra < 1; resets ntdev to 0 when the system is not the owner's home system; applied = min(ibon, float32(1 - Infra)); Infra becomes EXACTLY 1.0f when applied == the remainder, else float32(Infra + applied); ibon -= applied */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ApplyPopBonus",
"addr": "0x0074b510",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* nothing unless pbon > 0; an unowned system drops the whole pool; returns when Pop >= MaxPop; resets ntdev for a non-home system; Pop += min(cap - Pop, pbon); pbon -= same. Pop and pbon are int32 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ApplyInfraDelta",
"addr": "0x00748270",
"convention": "thiscall",
"prototype": "void (ServerSystem* this, float delta) /* no-op once Infra >= 1; clamps to 1 and re-normalises the system's own output rates when it lands there */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ApplySuitDelta",
"addr": "0x007481c0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this, float delta) /* clamps at the ideal from whichever side it approached, so suitability never overshoots; re-normalises the rates when it lands exactly on the ideal */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ComputeOutputFromRates",
"addr": "0x00751bb0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this, int out[12], float rates[7]) /* RET 8. `out` is 12 dwords of MIXED type: 0 total (truncated), 1 strip-mined resources, 2 resources consumed, 3 money, 7 gross construction, 8 points to the queue, 9 points SPENT on repairs, 10 float32 infra delta, 11 float32 suitability delta. `rates` is copied to the stack first, so the caller's struct is untouched. NOT side-effect free: it repairs damaged ships in orbit via 0x00751590(points, estimateOnly=0) */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_NormaliseOutputRates",
"addr": "0x00747390",
"convention": "cdecl",
"prototype": "void (float rates[7], ServerSystem* sys, float* pinned) /* `pinned` defaults to &rates[0] (trade) and every call site passes NULL or that. Suppress terraform at the ideal (exact ==) and infra when float32(ibon+Infra) >= 1; zero any channel <= (double)1e-4f; clamp ONLY the pinned channel to [0,1]; sum the other three in float32; an exactly-zero sum seeds them with 1e-4f (honouring the suppressions); each becomes (r/sum)*(1-pinned) */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ProcessBuildQueue",
"addr": "0x00752500",
"convention": "thiscall",
"prototype": "void (ServerSystem* this, int* resourcesConsumedOut) /* computes the turn's output vector, writes out[2] (resources consumed) through the pointer, applies out[11] via ApplySuitDelta and out[10] via ApplyInfraDelta, and feeds out[8] to BuildQueue::ProcessTurn. Increments TnsOH while SRoh > 0 && out[1] > 0 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "BuildQueue_ProcessTurn",
"addr": "0x00890d50",
"convention": "thiscall",
"prototype": "int (BuildQueue* this, ServerSystem* sys, int points) /* RET 8; `points` is BY VALUE and the leftover is the RETURN value. An order needing more than what is left absorbs everything and stops the pass; a design that costs money asks this->vft[9](sys, (int64)cost) and a refusal SKIPS that order and continues rather than stopping. Removal is a separate sweep afterwards that unlinks every order with conleft <= 0 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_AdjustResources",
"addr": "0x00745f30",
"convention": "thiscall",
"prototype": "void (ServerSystem* this, int delta, int mode) /* RET 8; ProcessTurn passes -out[2] with mode 0 when the owner has AMine, else mode 3. Mode 3 takes it all from Res; mode 0 splits it proportionally over Res / ARes2 / MRes */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_PopGrowthDelta",
"addr": "0x00748100",
"convention": "thiscall",
"prototype": "int64 (ServerSystem* this, int groupType, int species) /* RET 8; 0 without an owner or when haltv[groupType] is set */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "PopGrowthFraction",
"addr": "0x00536fb0",
"convention": "thiscall",
"prototype": "float (ServerPlayer* p, int groupType, int species, float suit, float factor) /* the growth curve. base = 1 - clamp01(min(|ideal - clamp(suit,0,20)|, SuitTol) / SuitTol) -- there is NO pop/capacity term anywhere; g = clamp01(pow(base, clamp(POPULATION_GROWTH_EXP, 0.01f, 1000))) then x MOD x PopMod x factor x groupdef[+4], each gated on a strict > 0 and each stored back to a float32 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_SlaveDeathRate",
"addr": "0x0074b110",
"convention": "thiscall",
"prototype": "float (ServerSystem* this, int species) /* RET 4; 1.0 when unowned. mod = (bit0 ? 0.8f : 1) - 0.2*bit1 - 0.2*bit2, no clamp; rate = ((|Ideal-Suit| x BYHAZARD + DEATH_RATE) + SRs x BYOUTPUT) x mod, every step narrowed to float32 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ProcessSlaves",
"addr": "0x007537b0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* the worst plague's rate is ADDED to the death rate; SLAVES_MIN/MAX_DEATHS are each disabled by ANY negative value; deaths are clamped into [0, adjusted slave count]. Consumes no RNG */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ProcessPlague",
"addr": "0x00756a90",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* consumes no RNG, at depth 1 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_ProcessRebellion",
"addr": "0x007583b0",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* the ONLY RNG consumer in a colony turn, and its draw count is data-dependent: one RandChance per iteration of a 64-bit rebel counter (0x0074fbe0), a short-circuiting per-species roll loop (0x00753c60), one outcome roll (0x00756350) and one 0.2f continuation roll */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_GrowCivilianPops",
"addr": "0x00754220",
"convention": "thiscall",
"prototype": "void (ServerSystem* this) /* consumes no RNG, at depth 1 */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "RNG_Chance",
"addr": "0x008e6dd0",
"convention": "stdcall",
"prototype": "bool (RNG* this /*ecx, the object*/, float p) /* p <= 0 -> false and p >= 1 -> true, both WITHOUT a draw; otherwise exactly one NextFloat and `r < p` */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_AddMoraleEvent",
"addr": "0x00839d60",
"convention": "thiscall",
"prototype": "void (ServerPlayer* this, MoraleEvent* ev, ServerSystem* sys) /* the actual apply. 0x00752a10 is only the MoraleEvent constructor; the per-species delta is carried in ev->deltas[species] (int[7] at ev+0x18), not as an argument */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "MoraleEvent_ctor",
"addr": "0x00752a10",
"convention": "thiscall",
"prototype": "MoraleEvent* (MoraleEvent* this) /* 0x50 bytes: {vptr, ints (+0x10 = event id), vptr2 @+0x14, int deltas[7] @+0x18, std::string name @+0x34} */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Owner",
"offset": "0x00000010",
"convention": "offset",
"prototype": "StrategyServer* owner (the RAW base; the RNG accessor uses owner-4)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Idx",
"offset": "0x0000005c",
"convention": "offset",
"prototype": "int Idx",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Size",
"offset": "0x00000060",
"convention": "offset",
"prototype": "int Size (1..10); the capacity base is Size * 100000000 as an exact int64 product",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Suit",
"offset": "0x00000064",
"convention": "offset",
"prototype": "float Suit",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Res",
"offset": "0x00000068",
"convention": "offset",
"prototype": "int Res",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_ARes2",
"offset": "0x0000006c",
"convention": "offset",
"prototype": "int ARes2",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_MRes",
"offset": "0x00000070",
"convention": "offset",
"prototype": "int MRes",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_TRes",
"offset": "0x00000074",
"convention": "offset",
"prototype": "int TRes -- zeroed every turn by ProcessTurn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_haltv",
"offset": "0x00000078",
"convention": "offset",
"prototype": "bool haltv[3] -- growth halt per group; cleared every turn by ProcessTurn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_OutMod",
"offset": "0x0000007c",
"convention": "offset",
"prototype": "float OutMod",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_TAcq",
"offset": "0x00000080",
"convention": "offset",
"prototype": "int TAcq (turn acquired)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Rates",
"offset": "0x00000088",
"convention": "offset",
"prototype": "StarSystem::OutputRates (0x1c): float SRt, SRsc, SRtf, SRi, SRoh, SRs; int SRnr",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_SRoh",
"offset": "0x00000098",
"convention": "offset",
"prototype": "float SRoh -- the over-harvest slider (Rates + 0x10)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_BuildQueue",
"offset": "0x000000a4",
"convention": "offset",
"prototype": "BuildQueue*",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Name",
"offset": "0x000000a8",
"convention": "offset",
"prototype": "std::string Name (0x1c, MSVC SSO; capacity word at +0xbc)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Abdn",
"offset": "0x000000c4",
"convention": "offset",
"prototype": "bool Abdn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Bats2",
"offset": "0x000000f0",
"convention": "offset",
"prototype": "int64 Bats2 -- player i's 4-bit battle-recent counter at bits [4i, 4i+4), i < 15",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Rcex",
"offset": "0x000000f8",
"convention": "offset",
"prototype": "int64 rcex -- the same shape for the explored/recon-recent counter",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_PID",
"offset": "0x00000100",
"convention": "offset",
"prototype": "ServerPlayer* PID (null = unowned)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Morale",
"offset": "0x0000011c",
"convention": "offset",
"prototype": "Morale cm (0x20: vptr + int[7])",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Fleets",
"offset": "0x0000016c",
"convention": "offset",
"prototype": "std::vector<StarFleet*>",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Pop",
"offset": "0x0000018c",
"convention": "offset",
"prototype": "int Pop (imperial)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Infra",
"offset": "0x00000190",
"convention": "offset",
"prototype": "float Infra",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_pbon",
"offset": "0x00000194",
"convention": "offset",
"prototype": "int pbon -- the pending population bonus (int32, not a float)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_ibon",
"offset": "0x00000198",
"convention": "offset",
"prototype": "float ibon -- the pending infrastructure bonus",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Pop2",
"offset": "0x000001a0",
"convention": "offset",
"prototype": "Population Pop2 (civilians): {vptr, vector<PopulationGroup> @+4}, 24-byte entries {?, int type @+4, int species @+8, int64 count @+0x10}",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_pbon2",
"offset": "0x000001b4",
"convention": "offset",
"prototype": "Population pbon2 -- the civilian analogue of pbon",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_Indi",
"offset": "0x000001c8",
"convention": "offset",
"prototype": "IndependenceInfo* indi (int indsp at +4)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_rbfl",
"offset": "0x000001dc",
"convention": "offset",
"prototype": "int rbfl -- rebelling-species bitmask",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_adt",
"offset": "0x000001e4",
"convention": "offset",
"prototype": "int adt[7] -- per-species addiction start turn; 0 means never addicted",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_BatsMask",
"offset": "0x000002a0",
"convention": "offset",
"prototype": "uint32 -- bit i is cleared when player i's Bats2 counter reaches 0",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_RcexMask",
"offset": "0x000002a4",
"convention": "offset",
"prototype": "uint32 -- the same for rcex",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_TnsOH",
"offset": "0x000002b8",
"convention": "offset",
"prototype": "int TnsOH (turns over-harvesting)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_ntdev",
"offset": "0x000002c4",
"convention": "offset",
"prototype": "int ntdev -- turns developing; ++ when IsStable, reset to 0 otherwise AND by either bonus-apply helper on a non-home system. This is the second SYSTEMBONUS_MINTURNS gate, not rbtn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerSystem_off_rbtn",
"offset": "0x000002cc",
"convention": "offset",
"prototype": "int rbtn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_ModCount",
"offset": "0x00000008",
"convention": "offset",
"prototype": "int ModCount (the turn counter), relative to the RAW server base a ServerSystem's +0x10 points at",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_Players",
"offset": "0x00000050",
"convention": "offset",
"prototype": "std::vector<ServerPlayer*> (begin @+0x50, end @+0x54); numPlayers = (end-begin)>>2 /* TWO BASES: every StrategyServer_off_* here is relative to the RAW base a ServerSystem's owner word (+0x10) points at. The class's own methods receive a base FOUR BYTES LOWER in ECX (0x007437f0 does owner-4), so a hook on MoveFleet or ProcessFleetMovement must add 4 to `this` before applying these */",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_Fleets",
"offset": "0x00000060",
"convention": "offset",
"prototype": "std::vector<StarFleet*> (begin @+0x60, end @+0x64) -- ALL fleets of ALL players, one flat global list. B4 live: the earlier 0x64 was the Ghidra-base number transcribed as a raw-base one; it made ProcessFleetMovement enumerate the vector's spare capacity instead of its elements",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_EntityHash",
"offset": "0x00000080",
"convention": "offset",
"prototype": "16-bucket hash of entity id -> object, hashed by (key & 0xF); 0x008b9240 is the lookup",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_RNGPtr",
"offset": "0x00000168",
"convention": "offset",
"prototype": "Mars::RNG* relative to the RAW base (== the -4-adjusted base's +0x16c)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_ArrivedSet",
"offset": "0x00000200",
"convention": "offset",
"prototype": "std::set<pair<FleetId,LocationId>> cleared at the head of ProcessFleetMovement",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StrategyServer_off_InMotionSet",
"offset": "0x00000210",
"convention": "offset",
"prototype": "std::set<pair<FleetId,LocationId>> that MoveFleet fills for fleets still in motion; OnFleetArrived takes the set difference",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Id",
"offset": "0x00000004",
"convention": "offset",
"prototype": "int id (also the entity-hash key)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Galaxy",
"offset": "0x00000010",
"convention": "offset",
"prototype": "the object whose +0x80 holds the entity hash the waypoint target is resolved in",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Pos",
"offset": "0x00000018",
"convention": "offset",
"prototype": "Vector3 Pos",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_PrvPos",
"offset": "0x0000004c",
"convention": "offset",
"prototype": "Vector3 PrvPos -- set to the ENTRY position when the fleet moved",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_PID",
"offset": "0x00000058",
"convention": "offset",
"prototype": "ServerPlayer* owner",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Location",
"offset": "0x000000a0",
"convention": "offset",
"prototype": "Location*; kind at Location+0x14 (0 system, 1 fleet, 2 point)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Ships",
"offset": "0x000000a4",
"convention": "offset",
"prototype": "std::vector<StarShip*> (begin @+0xa4, end @+0xa8)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_GateTraffic",
"offset": "0x000000c0",
"convention": "offset",
"prototype": "int16 -- SIGNED, summed into the owner's gate traffic",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Waypoints",
"offset": "0x000000c8",
"convention": "offset",
"prototype": "std::vector<Waypoint> _Myfirst (proxy @+0xc4, last @+0xcc, end @+0xd0)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Speed",
"offset": "0x000000d8",
"convention": "offset",
"prototype": "float FPsp2 -- the fleet's strategic speed",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_DestPos",
"offset": "0x000000ec",
"convention": "offset",
"prototype": "Vector3 -- set after every pass to the current waypoint target's position. The notes called this FPogn2; by the FlightPlan layout it is FPdpos",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarFleet_off_Flags",
"offset": "0x0000010c",
"convention": "offset",
"prototype": "uint32; bit 0x100 = held this turn (cleared for every fleet at the end of ProcessFleetMovement), bit 0x2 cleared in the destination pass, bit 0x1 set by MoveFleet when the fleet moved",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "Waypoint_stride",
"offset": "0x0000001c",
"convention": "offset",
"prototype": "sizeof(Waypoint) -- pinned by the divide-by-28 reciprocal multiply in the iterator arithmetic",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "Waypoint_off_Target",
"offset": "0x00000004",
"convention": "offset",
"prototype": "int -- the destination entity id",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "Waypoint_off_Type",
"offset": "0x00000008",
"convention": "offset",
"prototype": "int -- 2 node line, 3 node route, 4 gate teleport, 5 probabilistic jump, otherwise a straight run",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarShip_off_Flags",
"offset": "0x00000018",
"convention": "offset",
"prototype": "uint64 flag pair; bit 0x1000 exempts the ship from movement fuel and marks it a tanker",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarShip_off_Range",
"offset": "0x00000020",
"convention": "offset",
"prototype": "float Range -- remaining strategic range",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "StarShip_off_MaxRange",
"offset": "0x00000078",
"convention": "offset",
"prototype": "float -- the cap the refuel helper clamps Range to",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_off_HomeSystem",
"offset": "0x0000002c",
"convention": "offset",
"prototype": "ServerSystem* -- the bonus-apply helpers skip the ntdev reset for this system",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_off_GateTraffic",
"offset": "0x0000014c",
"convention": "offset",
"prototype": "int GTraf -- assigned (not accumulated) once per turn",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_off_CstE",
"offset": "0x00000154",
"convention": "offset",
"prototype": "float CstE -- the probabilistic jump's efficiency",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_off_CstT",
"offset": "0x00000158",
"convention": "offset",
"prototype": "float CstT -- the probabilistic jump's threshold",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_SYSTEMBONUS_MINTURNS",
"addr": "0x00aeca10",
"convention": "data",
"prototype": "int** -- the GlobalConst pointer slot; the storage word is *slot",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_SYSTEMBONUS_POPBONUS",
"addr": "0x00aeca18",
"convention": "data",
"prototype": "float** -- pointer slot",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_SYSTEMBONUS_INFRABONUS",
"addr": "0x00aeca20",
"convention": "data",
"prototype": "float** -- pointer slot",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_SYSTEMBONUS_POPBONUS_INC",
"addr": "0x00aeca38",
"convention": "data",
"prototype": "float** -- pointer slot",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_SYSTEMBONUS_INFRABONUS_INC",
"addr": "0x00aeca40",
"convention": "data",
"prototype": "float** -- pointer slot",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_ADDICTION_PHASE2_START",
"addr": "0x00aeca58",
"convention": "data",
"prototype": "int** -- pointer slot (image default 10)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_ADDICTION_PHASE3_START",
"addr": "0x00aeca60",
"convention": "data",
"prototype": "int** -- pointer slot (image default 15)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_IndependentPopDriftRate",
"addr": "0x00aeca80",
"convention": "data",
"prototype": "float -- 0.05f, a hard-coded literal in .data, NOT a GlobalConst; both ServerSystem::ProcessTurn calls to the independent pop-drift helper pass it",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_STUTTER_SYSTEM_INFLUENCE_RADIUS",
"addr": "0x00aebc44",
"convention": "data",
"prototype": "float** -- pointer slot; storage 0x00b212cc (shipped value 2)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_STUTTER_MIN_SPEED",
"addr": "0x00aebc48",
"convention": "data",
"prototype": "float** -- pointer slot; storage 0x00b212d0 (shipped value 0.33)",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "g_ptr_STUTTER_MAX_SPEED",
"addr": "0x00aebc4c",
"convention": "data",
"prototype": "float** -- pointer slot; storage 0x00b212d4 (shipped value 0.33). NOTE min == max in the shipped data, so the stutter ramp collapses to a constant 0.33x inside any influence sphere",
"status": "verified",
"source": "handoff/b4-colony-movement.md"
},
{
"name": "ServerPlayer_GetEventStorage",
"addr": "0x0080db00",
"convention": "thiscall",
"prototype": "EventStorage* (ServerPlayer* this) /* whole body: lea eax,[ecx+0x29c]; ret. No stack args, plain RET */",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "ServerPlayer_GetServer",
"addr": "0x0080e320",
"convention": "thiscall",
"prototype": "void* (ServerPlayer* this) /* eax = [this+8] ? [this+8]-4 : 0. StrategyServer primary base; turn = *(int*)(result+0x0c) */",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_PostEvent",
"addr": "0x008862b0",
"convention": "thiscall",
"prototype": "int (EventStorage* this, std::string summary /*BY VALUE 0x1c -> EvDsc*/, std::string message /*BY VALUE 0x1c -> EvMsg*/, void* obj, Vector3* pos, int turn, const char* img, int act) RET 0x4c. Returns the event id (a duplicate's id if one already exists in the turn bucket). Callee frees both by-value string buffers. img==NULL -> \"\". act==0 && obj==NULL && pos==NULL -> stored EvAct becomes 2. EvLoc = obj ? obj[+4] : 0; EvPos = obj ? obj[+0x18..0x20] : pos ? *pos : FLT_MAX triple. 161 call sites in 113 functions: this is the whole simulation's event API",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_GetOrCreateTurnBucket",
"addr": "0x00885380",
"convention": "thiscall",
"prototype": "TurnEvents* (EventStorage* this, int turn) RET 4. Linear scan with NO early exit, so it returns the LAST bucket whose EvTurn == turn; otherwise appends a new bucket (ctor 0x00884cb0, vtable 0x00a0f07c) and sets its EvTurn",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_FindDuplicate",
"addr": "0x00825d40",
"convention": "thiscall",
"prototype": "PlayerEvent* (EventStorage* this, TurnEvents* bucket, PlayerEvent* candidate) RET 8. NULL bucket -> 0. Match requires EvAct, EvLoc, all three EvPos floats (fucompp), EvMsg and EvImg to be equal. EvDsc is NOT compared",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_PruneOldTurns",
"addr": "0x00879eb0",
"convention": "thiscall",
"prototype": "void (EventStorage* this, int turn) RET 4. Cutoff = turn - 0x32 (50), a code constant. Shifts from the LAST bucket of the leading run with EvTurn < cutoff, so it erases n-1 of n leading stale buckets: one stale bucket always survives and a single leading stale bucket is never removed",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_Read",
"addr": "0x00825cc0",
"convention": "thiscall",
"prototype": "void (EventStorage* this, IStreamable* s) RET 4. Order: EvNxID (int, tag 0x00a2bd90), then nested collection \"Events\" (tag 0x00a2bda0, descriptor vtable 0x00a2da8c) over this+4",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TurnEvents_Write",
"addr": "0x00825bb0",
"convention": "thiscall",
"prototype": "void (TurnEvents* this, IStreamable* s) RET 4. Order: EvTurn (int, tag 0x00a2bd98), then nested collection \"Events\" (descriptor vtable 0x00a2da7c) over this+8",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TurnEvents_Read",
"addr": "0x00825c40",
"convention": "thiscall",
"prototype": "void (TurnEvents* this, IStreamable* s) RET 4. Same field order as TurnEvents_Write",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_ctor",
"addr": "0x0084ee30",
"convention": "thiscall",
"prototype": "PlayerEvent* (PlayerEvent* this) RET 0. vptr=0x00a21958; EvEID=EvLoc=EvAct=EvCID=0; the three std::strings = \"\" (0x009e100c); EvPos = the Vector3 global at 0x00af0dc8 = {FLT_MAX, FLT_MAX, FLT_MAX} (0x7f7fffff x3, NOT infinity)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_Serialize",
"addr": "0x00825970",
"convention": "thiscall",
"prototype": "void (PlayerEvent* this, IStreamable* s) RET 4. vftable slot 1. Field order on the wire: EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvImg(+0x50) EvLoc(+0x40) EvPos(+0x44) EvAct(+0x6c) EvCID(+0x70)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "ProcessResearch_PostEventOverbudget",
"addr": "0x00587b97",
"convention": "thiscall",
"prototype": "call site: EventStorage::PostEvent for EVENT_RESEARCH_OVERBUDGET. Guard at 0x005879e9: !wasDone && nowDone && owner, inside the completion-roll-FAILED branch (chance < draw). Sets TechNode.flag(+0x2c)=2 at 0x00587ba3. EvAct=1, obj=NULL, pos=NULL",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "ProcessResearch_PostEventTechsUnlocked",
"addr": "0x00587ff4",
"convention": "thiscall",
"prototype": "call site: EventStorage::PostEvent for EVENT_TECHS_UNLOCKED, once after the per-node loop, if any node has state==2 and turnAvailable==currentTurn. EvAct=1, obj=NULL, pos=NULL",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "OnTechResearched_PostEventComplete",
"addr": "0x008919b5",
"convention": "thiscall",
"prototype": "call site: EventStorage::PostEvent for EVENT_RESEARCH_COMPLETE / _UNDERBUDGET. Guarded by !silent ([ebp+0xc]==0). Message is _snprintf'd (0x008c8eb0) into a 0x100-byte buffer, so >255 chars truncate. EvAct=1",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "OnTechResearched_PostEventTemperance",
"addr": "0x00892427",
"convention": "thiscall",
"prototype": "call site: EventStorage::PostEvent for EVENT_TEMPERANCE. Guarded by !silent AND by the 'a system was cured' local at [ebp-0x189]. Pushed act=0 with obj=pos=NULL, so the STORED EvAct is 2",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "ServerPlayer_ProcessTurn_PostEventNoResearch",
"addr": "0x0089168c",
"convention": "thiscall",
"prototype": "call site: EventStorage::PostEvent for EVENT_NO_RESEARCH. Condition at 0x0089162a: ResT(+0x294)==NULL && ListAvailableTechs(0x00584e50, turn, INT_MAX, 1) returned empty && TechTree 0x0057da90 != 0. EvAct=1",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TechTree_ProcessResearch_LogFormat",
"addr": "0x00a007a8",
"convention": "data",
"prototype": "const char* \"Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\\n\" -- log line on the completion-roll-SUCCEEDED branch, 0x00587977",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "ServerPlayer_off_Events",
"offset": "0x29c",
"convention": "offset",
"prototype": "EventStorage ServerPlayer::Events -- embedded, size 0x1c. Confirmed by ServerPlayer_GetEventStorage",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_off_Events",
"offset": "0x04",
"convention": "offset",
"prototype": "std::vector<TurnEvents> _Myfirst (element stride 0x18; _Mylast +0x08, _Myend +0x0c, _Alval +0x10)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_off_EvNxID",
"offset": "0x14",
"convention": "offset",
"prototype": "int EvNxID -- next event id. Starts at 0; PostEvent promotes 0->1 on the first post, then post-increments. ServerPlayer+0x2b0",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_sizeof",
"offset": "0x1c",
"convention": "offset",
"prototype": "sizeof(EventStorage)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TurnEvents_off_EvTurn",
"offset": "0x04",
"convention": "offset",
"prototype": "int EvTurn (after the vptr at +0)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TurnEvents_off_Events",
"offset": "0x08",
"convention": "offset",
"prototype": "std::vector<PlayerEvent> _Myfirst (stride 0x74; _Mylast +0x0c, _Myend +0x10, _Alval +0x14)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "TurnEvents_sizeof",
"offset": "0x18",
"convention": "offset",
"prototype": "sizeof(TurnEvents); the outer vector's stride, from the /24 divide at 0x008853b3",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvEID",
"offset": "0x04",
"convention": "offset",
"prototype": "int EvEID (after the vptr at +0)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvDsc",
"offset": "0x08",
"convention": "offset",
"prototype": "std::string EvDsc (summary / title), 0x1c bytes",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvMsg",
"offset": "0x24",
"convention": "offset",
"prototype": "std::string EvMsg (body), 0x1c bytes",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvLoc",
"offset": "0x40",
"convention": "offset",
"prototype": "int EvLoc (object id, or 0)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvPos",
"offset": "0x44",
"convention": "offset",
"prototype": "float[3] EvPos (default FLT_MAX x3)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvImg",
"offset": "0x50",
"convention": "offset",
"prototype": "std::string EvImg (event-type name, e.g. \"EVENT_RESEARCH_OVERBUDGET\"), 0x1c bytes",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvAct",
"offset": "0x6c",
"convention": "offset",
"prototype": "int EvAct",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_off_EvCID",
"offset": "0x70",
"convention": "offset",
"prototype": "int EvCID -- ctor sets 0 and PostEvent never writes it",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "PlayerEvent_sizeof",
"offset": "0x74",
"convention": "offset",
"prototype": "sizeof(PlayerEvent) = 116. Two independent confirmations: the /116 divide at 0x00825d5f and PostEvent's 'mov [_Mylast-0x70], id' writing EvEID at element+4",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "EventStorage_PruneWindowTurns",
"offset": "0x32",
"convention": "offset",
"prototype": "prune cutoff = turn - 50; a code constant at 0x00879ec3, not config",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_vft_PlayerEvent",
"addr": "0x00a21958",
"convention": "data",
"prototype": "void** -- PlayerEvent vftable. slot0 dtor 0x007694d0, slot1 Serialize 0x00825970, slot2 0x00825ab0. RTTI locator 0x00a80a7c",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_vft_TurnEvents",
"addr": "0x00a0f07c",
"convention": "data",
"prototype": "void** -- TurnEvents vftable (bucket ctor 0x00884cb0); slot0 is the virtual dtor the pruner calls",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_EventTagTable",
"addr": "0x00a2bd88",
"convention": "data",
"prototype": "const char[12][8] -- stride 8: EvEID EvNxID EvTurn Events EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_Vector3_Invalid",
"addr": "0x00af0dc8",
"convention": "data",
"prototype": "float[3] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} = FLT_MAX. PlayerEvent's default EvPos. NOT infinity",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_RESEARCH_OVERBUDGET",
"addr": "0x00a0078c",
"convention": "data",
"prototype": "const char* \"EVENT_RESEARCH_OVERBUDGET\" -- EvImg pushed at 0x00587b24",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_RESEARCH_COMPLETE",
"addr": "0x00a33368",
"convention": "data",
"prototype": "const char* \"EVENT_RESEARCH_COMPLETE\" -- EvImg for progress/cost >= 0.8",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_RESEARCH_UNDERBUDGET",
"addr": "0x00a33380",
"convention": "data",
"prototype": "const char* \"EVENT_RESEARCH_UNDERBUDGET\" -- EvImg for progress/cost < 0.8",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_TEMPERANCE",
"addr": "0x00a33340",
"convention": "data",
"prototype": "const char* \"EVENT_TEMPERANCE\" -- EvImg at 0x008923ae; posted with act=0 so the stored EvAct is 2",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_TECHS_UNLOCKED",
"addr": "0x00a00774",
"convention": "data",
"prototype": "const char* \"EVENT_TECHS_UNLOCKED\" (length 0x14 pushed at 0x00587eb6)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_str_EVENT_NO_RESEARCH",
"addr": "0x00a3332c",
"convention": "data",
"prototype": "const char* \"EVENT_NO_RESEARCH\" -- EvImg at 0x0089168c",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTSUM_RESEARCH_OVERBUDGET",
"addr": "0x00ae48e4",
"convention": "data",
"prototype": "const char** -> slot 0x00ae48e0; Strings.csv key EVENTSUM_RESEARCH_OVERBUDGET = \"Research Over Budget\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTMSG_RESEARCH_OVERBUDGET",
"addr": "0x00ae48ec",
"convention": "data",
"prototype": "const char** -> slot 0x00ae48e8; key EVENTMSG_RESEARCH_OVERBUDGET = \"Research for %s has gone overbudget.\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTSUM_UNLOCKEDTECHS",
"addr": "0x00ae48f4",
"convention": "data",
"prototype": "const char** -> slot 0x00ae48f0; key EVENTSUM_UNLOCKEDTECHS = \"New Technologies Available\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTMSG_UNLOCKEDTECHS",
"addr": "0x00ae48fc",
"convention": "data",
"prototype": "const char** -> slot 0x00ae48f8; key EVENTMSG_UNLOCKEDTECHS",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTSUM_RESEARCH_COMPLETE",
"addr": "0x00af09ec",
"convention": "data",
"prototype": "const char** -> slot 0x00af09e8; key EVENTSUM_RESEARCH_COMPLETE = \"Research Complete\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTMSG_RESEARCH_COMPLETE",
"addr": "0x00af09f4",
"convention": "data",
"prototype": "const char** -> slot 0x00af09f0; key EVENTMSG_RESEARCH_COMPLETE = \"Tech %s has been acquired\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTSUM_RESEARCH_UNDERBUDGET",
"addr": "0x00af09fc",
"convention": "data",
"prototype": "const char** -> slot 0x00af09f8; key EVENTSUM_RESEARCH_UNDERBUDGET = \"Research Breakthrough!\"",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTMSG_RESEARCH_UNDERBUDGET",
"addr": "0x00af0a04",
"convention": "data",
"prototype": "const char** -> slot 0x00af0a00; key EVENTMSG_RESEARCH_UNDERBUDGET",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTSUM_ADDICTION_TEMPERENCE",
"addr": "0x00af0a8c",
"convention": "data",
"prototype": "const char** -> slot 0x00af0a88; key EVENTSUM_ADDICTION_TEMPERENCE (shipped misspelling)",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_ptr_EVENTMSG_ADDICTION_TEMPERENCE",
"addr": "0x00af0a94",
"convention": "data",
"prototype": "const char** -> slot 0x00af0a90; key EVENTMSG_ADDICTION_TEMPERENCE",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_dbl_ResearchUnderbudgetThreshold",
"addr": "0x009e20c8",
"convention": "data",
"prototype": "double 0.800000011920929 = (double)0.8f. progress/cost >= this -> EVENT_RESEARCH_COMPLETE, else EVENT_RESEARCH_UNDERBUDGET; the same constant gates TechNode.flag=0 in ProcessResearch",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "g_dbl_ResearchRollBias",
"addr": "0x009e1e68",
"convention": "data",
"prototype": "double 0.0 -- the research completion draw is NextFloat()*(1.0-this)+this, so it is a plain NextFloat()",
"status": "verified",
"source": "E own disassembly pass 2026-09-08 (ReVa read-memory + capstone x86-32): EventStorage::PostEvent 0x008862b0, GetOrCreateTurnBucket 0x00885380, FindDuplicate 0x00825d40, PruneOldTurns 0x00879eb0, PlayerEvent ctor 0x0084ee30 / Serialize 0x00825970; layout cross-checked against verify/results/saves/turn3-state.sav. findings/subsystems/events.md"
},
{
"name": "Mars_Vec3_Normalize",
"addr": "0x00422520",
"convention": "cdecl",
"prototype": "float (Vec3* out, const Vec3* in) /* normalises in-place-capable (MoveFleet passes the same pointer twice) and RETURNS THE LENGTH. FOUR float32 narrowings in this order: sumsq = float32(x*x+y*y+z*z) (products/adds stay in the x87 53-bit registers, only the sum is stored to a dword and reloaded); len = float32(sqrt(sumsq)); inv = float32(1.0/len) -- a RECIPROCAL, stored to a dword and reloaded, then MULTIPLIED through, NOT three divides; out.c = float32(in.c * inv). Zero branch: !(len > 2^-23, the float at 0x009e1ef8) => out = {0,0,0} and it returns with an EMPTY x87 stack, i.e. no return value at all (original bug, only reachable on a zero-length vector). 123 callers. */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08 (ReVa read-memory + objdump -b binary -m i386); confirmed bit-for-bit against 8 live MoveFleet legs"
},
{
"name": "Mars_Vec3_Length",
"addr": "0x004224b0",
"convention": "cdecl",
"prototype": "float (const Vec3* v) /* float32(sqrt(float32(x*x+y*y+z*z))) -- the same two narrowings as the first half of Mars_Vec3_Normalize */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08"
},
{
"name": "Mars_Vec3_LengthSquared",
"addr": "0x004224f0",
"convention": "cdecl",
"prototype": "float (const Vec3* v) /* float32(x*x+y*y+z*z), one narrowing on the sum */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08"
},
{
"name": "Mars_Vec3_NormaliseEpsilon",
"addr": "0x009e1ef8",
"convention": "data",
"prototype": "const float = 0x34000000 = 2^-23 = 1.1920928955078125e-07 /* Mars_Vec3_Normalize zeroes the direction when !(len > this) */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08"
},
{
"name": "StrategyServer_MoveFleet_straight_leg",
"addr": "0x007da0f2",
"convention": "site",
"prototype": "site inside StrategyServer::MoveFleet /* the straight-run leg. Each delta dest.c - fleet.pos.c is computed on the x87 stack and STORED BACK TO A FLOAT32 SLOT before Mars_Vec3_Normalize(&v, &v) is called on it in place; that single call returns the leg DISTANCE and leaves the float32 unit direction in the same slots. The distance is never recomputed. */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08"
},
{
"name": "StrategyServer_MoveFleet_position_update",
"addr": "0x007da2ac",
"convention": "site",
"prototype": "site inside StrategyServer::MoveFleet /* the move != distance branch: exactly two roundings per component, tmp.c = float32(dir.c * move) then pos.c = float32(pos.c + tmp.c). `move` is reloaded from a float32 slot. The sibling branch (move == distance, an EXACT float compare) copies the destination's three words verbatim with mov, so an arrival never steps onto its destination. */",
"status": "verified",
"source": "lane M own disassembly pass 2026-09-08"
}
]
}