sots-engine/include/generated/sots_addresses.h

839 lines
84 KiB
C++

// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
// Source: sots-re ghidra/addresses.json @ 3768f24, generated 2026-09-08 by tools/gen_addresses.py
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
#pragma once
#include <cstdint>
namespace sots::addr {
constexpr uint32_t IMAGE_BASE = 0x00400000;
// thiscall 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 */ [verified]
constexpr uint32_t Mars_Application_Initialize = 0x004a0e50;
// thiscall void (Application* this) /* no stack args, plain RET, EDX not an input, EAX = last PumpMessages result (ignore) */ [verified]
constexpr uint32_t Mars_Application_Run = 0x0049f5b0;
// cdecl void (int coreIndex) [unverified]
constexpr uint32_t Process_PinAffinity = 0x0049ee70;
// thiscall GlobalConst* (GlobalConst* this, void* storage, const char* key, ParseFn parser, const char* file) // RET 0x10 [verified]
constexpr uint32_t CfgVar_RegisterKey = 0x004b76a0;
// thiscall bool (DemoApp* this) /* no stack args; returns 0 when quitting (this+0x1b2), else 1 */ [verified]
constexpr uint32_t DemoApp_OnTick = 0x0049a640;
// thiscall void (StrategyClient* this) [unverified]
constexpr uint32_t StrategyClient_EndTurn = 0x00383be0;
// thiscall void (StrategyServer* this) [unverified]
constexpr uint32_t StrategyServer_BeginProcessTurn = 0x003d98e0;
// thiscall void (StrategyServer* this) [unverified]
constexpr uint32_t StrategyServer_ProcessTurn = 0x003dc6c0;
// thiscall 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 */ [verified-by-trace]
constexpr uint32_t ServerPlayer_ComputeBudget = 0x00463030;
// thiscall 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 */ [verified]
constexpr uint32_t TechTree_ProcessResearch = 0x001876c0;
// thiscall void (ServerSystem* this /*+8 IStreamable*/, Stream* s) [verified-by-save]
constexpr uint32_t ServerSystem_Read = 0x0035d4b0;
// thiscall void (ServerSystem* this /*+8*/, Stream* s) [verified-by-save]
constexpr uint32_t ServerSystem_Write = 0x00349630;
// thiscall void (ServerPlayer* this /*+0x3a0*/, Stream* s) [verified-by-save]
constexpr uint32_t ServerPlayer_Read = 0x004804d0;
// thiscall void (ServerPlayer* this /*+0x3a0*/, Stream* s) [verified-by-save]
constexpr uint32_t ServerPlayer_Write = 0x004563e0;
// thiscall void (StarFleet* this /*+8*/, Stream* s) [verified-by-save]
constexpr uint32_t StarFleet_Read = 0x00302470;
// thiscall void (StarFleet* this /*+8*/, Stream* s) [verified-by-save]
constexpr uint32_t StarFleet_Write = 0x00301070;
// thiscall void (StarShip* this /*+8*/, Stream* s) [verified-by-save]
constexpr uint32_t StarShip_Read = 0x00453fa0;
// thiscall void (StarShip* this /*+8*/, Stream* s) [verified-by-save]
constexpr uint32_t StarShip_Write = 0x004291f0;
// thiscall void (StrategyServer* this, Stream* s) [verified-by-save]
constexpr uint32_t StrategyServer_Read = 0x003d27a0;
// thiscall void (StrategyServer* this, Stream* s) [verified-by-save]
constexpr uint32_t StrategyServer_Write = 0x0039fa70;
// thiscall void (Stream* this, const char* tag, NetworkObject* obj) [unverified]
constexpr uint32_t Stream_WriteNetworkObjectId = 0x00416490;
// thiscall 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 [verified]
constexpr uint32_t RNG_Twist = 0x00026e00;
// thiscall 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 [verified]
constexpr uint32_t RNG_Seed = 0x0009fdf0;
// thiscall 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 [verified]
constexpr uint32_t RNG_NextFloat = 0x0007d830;
// thiscall 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) [verified]
constexpr uint32_t RNG_NextInt = 0x000271c0;
// offset sizeof(Mars::RNG) -- {vftable @+0; uint32 mt[624] @+4; uint32* next @+0x9c4; int left @+0x9c8} [verified]
constexpr uint32_t RNG_size = 0x000009cc;
// offset uint32 mt[624]; also the pointer Twist/NextFloat/NextInt receive in ECX [verified]
constexpr uint32_t RNG_off_State = 0x00000004;
// offset uint32* next; always &mt[624 - left], and recomputed from left on load, so `left` alone pins the stream position [verified]
constexpr uint32_t RNG_off_Next = 0x000009c4;
// offset int left; words still unread in the current block [verified]
constexpr uint32_t RNG_off_Left = 0x000009c8;
// offset int tech id -- the index into TechTree_off_Nodes [verified]
constexpr uint32_t TechDef_off_TechId = 0x00000000;
// offset sizeof({TechDef* target, int points}) -- the element of the vector ProcessResearch walks [verified]
constexpr uint32_t ResearchAlloc_stride = 0x00000008;
// cdecl void (void) /* sots.ini [Modules] Mount<N> -> gobio::Init */ [verified]
constexpr uint32_t Mars_Application_MountModules = 0x004a0a20;
// cdecl void (int* count, const char*** table) /* count 3 @0x00b2d514, table @0x00a35e98 */ [verified]
constexpr uint32_t Game_GetDefaultMounts = 0x004991c0;
// thiscall 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} */ [verified]
constexpr uint32_t GlobalConst_ctor = 0x004b76a0;
// cdecl void (GlobalConst* g) [verified]
constexpr uint32_t GlobalConst_Register = 0x004b7610;
// cdecl void (int* storage, const char* text) [verified]
constexpr uint32_t GlobalConst_ParseInt = 0x004b7000;
// cdecl void (float* storage, const char* text) [verified]
constexpr uint32_t GlobalConst_ParseFloat = 0x004b7020;
// cdecl void (float* storage, const char* text) /* sscanf(%f) into *storage, then *storage = (float)((double)*storage * *(double*)0x00af5210) */ [verified]
constexpr uint32_t GlobalConst_ParseFloatScaled = 0x004b70e0;
// cdecl void (float rgba[4], const char* text) /* '%d %d %d %d', defaults 255, /255, clamp 0..1 */ [verified]
constexpr uint32_t GlobalConst_ParseColour = 0x004b7110;
// cdecl 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 */ [verified-by-trace]
constexpr uint32_t GlobalConst_ParseRect = 0x004b7040;
// cdecl void (float xyz[3], const char* text) /* '%f %f %f'; 5 keys (HIVER_SPAWN_*_GATE_HEIGHT / _SHIP_OFFSET ...); inferred from the M1 trace */ [verified-by-trace]
constexpr uint32_t GlobalConst_ParseVec3 = 0x004b7080;
// cdecl void (std::string* storage, const char* text) /* *storage = text; MSVC std::string is 0x1c bytes {+0 char buf[16] | char* ptr when capacity > 15, +0x10 _Mysize, +0x14 _Myres, +0x18 _Alval (empty allocator, never read/written)} -- see std_string_sizeof; 146 keys (*_NAME, *_SOUND, *_TEXTURENAME, DERELICT_SECTION_nn ...); seen in the M1 trace */ [verified-by-trace]
constexpr uint32_t GlobalConst_ParseString = 0x004b7670;
// cdecl void (void) /* called once from Application::Initialize; sets g_GlobalConstsLoaded */ [verified]
constexpr uint32_t GlobalConsts_LoadAll = 0x004b76d0;
// cdecl 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 */ [verified]
constexpr uint32_t GlobalConsts_LoadFile = 0x004b73c0;
// data bool [verified]
constexpr uint32_t g_GlobalConstsLoaded = 0x0072d740;
// data std::map<const char*,GlobalConst*,stricmp_less> /* +4 (0x00b2d748) = header node pointer */ [verified]
constexpr uint32_t g_GlobalConstRegistry = 0x0072d744;
// data double /* pi/180 as a DOUBLE (0x3f91df46a2529d39); GlobalConst_ParseFloatScaled does float word *= this in x87 and stores float */ [verified]
constexpr uint32_t g_GlobalConstFloatScale = 0x006f5210;
// thiscall Script* (Script* this) /* sizeof(Script) = 0x28 */ [verified]
constexpr uint32_t Script_ctor = 0x004cd700;
// thiscall void (Script* this) [verified]
constexpr uint32_t Script_dtor = 0x004cd720;
// thiscall bool (Script* this, const char* path) /* gobio::ReadFile into this->buf */ [verified]
constexpr uint32_t Script_Open = 0x004cd7d0;
// thiscall void (Script* this) [verified]
constexpr uint32_t Script_Close = 0x004cd6c0;
// thiscall int (Script* this, char* out, uint outMax) /* 0 token, 1 no buffer/past end, 2 reached end */ [verified]
constexpr uint32_t Script_ReadToken = 0x004cd2f0;
// thiscall int (Script* this, ScriptToken* tok) /* tok: {int type; char key[1024]; char value[1024]}; type 1 kv, 2 block open, 3 '}' */ [verified]
constexpr uint32_t Script_Next = 0x004cd3e0;
// thiscall int (Script* this, int depth) /* 0 ok, nonzero EOF */ [verified]
constexpr uint32_t Script_SkipBlock = 0x004cd4b0;
// custom /* EAX=cursor, ECX=&quotedFlag, EDX=out; stack: end, outEnd, &len — internal, do not hook */ [verified]
constexpr uint32_t Script_ScanToken = 0x004cd1f0;
// cdecl void (IConfigCallback* cb, const char* path, const char* blockName, bool* found) /* cb->vft[1](Script*) per matching block */ [verified]
constexpr uint32_t ConfigParser_ParseFile = 0x004cd820;
// fastcall int (IBuffer* obj) /* ECX=obj; --refcount, vft[0](1) at 0 */ [verified]
constexpr uint32_t RefCounted_Release = 0x004a7230;
// thiscall void (WeaponDictionary* this) /* reads Weapons/_weapons.txt manifest */ [verified]
constexpr uint32_t WeaponDictionary_Init = 0x0019a4c0;
// thiscall void (WeaponDictionary* this, const char* path, int id, WeaponDef** out) [verified]
constexpr uint32_t WeaponDictionary_LoadWeapon = 0x0019a230;
// thiscall WeaponDef* (WeaponDictionary* this, const char* name) /* _stricmp over def->name @+0x40 */ [verified]
constexpr uint32_t WeaponDictionary_FindByName = 0x00190a10;
// thiscall WeaponDef* (WeaponDictionary* this, const char* name) /* 'Weapon not found: "%s" - Was it added to the index file?' */ [verified]
constexpr uint32_t WeaponDictionary_FindByNameSorted = 0x00191e30;
// thiscall WeaponDef* (WeaponDef* this, int index, int id, const char* path) /* sizeof 0x278 */ [verified]
constexpr uint32_t WeaponDef_ctor = 0x00198a20;
// thiscall bool (WeaponDef* this, Script* script, int* rc, TechTree* tree) [verified]
constexpr uint32_t WeaponDef_ParseScript = 0x00199070;
// thiscall SectionDictionary* (SectionDictionary* this, TechTree* tree) /* reads Species/<Race>/_shipsections.txt for species 0..6 */ [verified]
constexpr uint32_t SectionDictionary_ctor = 0x00176f40;
// thiscall SectionDef* (SectionDictionary* this, const char* path, int species, int id) [verified]
constexpr uint32_t SectionDictionary_LoadSection = 0x00176cd0;
// thiscall SectionDef* (SectionDef* this, int index, int species, int id) /* sizeof 0x3d8 */ [verified]
constexpr uint32_t SectionDef_ctor = 0x00174020;
// thiscall bool (SectionDef* this, Script* script, int* rc, TechTree* tree) [verified]
constexpr uint32_t SectionDef_ParseScript = 0x001744e0;
// cdecl const char* (uint species) [verified]
constexpr uint32_t Species_GetDirName = 0x00145ec0;
// thiscall MasterTechTree* (MasterTechTree* this) /* TechTree/MasterTechList.tech */ [verified]
constexpr uint32_t MasterTechTree_ctor = 0x0018b870;
// thiscall void (MasterTechTree* this, const char* path, int flag) [verified]
constexpr uint32_t MasterTechTree_LoadTechFile = 0x0018b770;
// thiscall void (MasterTechTree* this, Script* script, int flag) /* one tech{} block */ [verified]
constexpr uint32_t MasterTechTree_ParseTech = 0x0018b050;
// thiscall void (DemoApp* this) /* MasterTechTree -> +0x110, WeaponDictionary -> +0x118, SectionDictionary -> +0x114 */ [verified]
constexpr uint32_t DemoApp_LoadGameData = 0x00499280;
// thiscall void (DemoApp* this) /* once, from OnTick when this+0x10c is set */ [verified]
constexpr uint32_t DemoApp_EnsureGameData = 0x00499870;
// cdecl void (std::vector<std::string>* mountPaths) [verified]
constexpr uint32_t gobio_Init = 0x004d7010;
// thiscall FileSystemSet* (FileSystemSet* this, std::vector<std::string>* mounts) [verified]
constexpr uint32_t gobio_FileSystemSet_ctor = 0x004d6d60;
// thiscall bool (FileSystemSet* this, const char* path) [verified]
constexpr uint32_t gobio_FileSystemSet_Open = 0x004d5060;
// thiscall void (FileSystemSet* this, const char* pattern, std::vector<std::string>* out) [verified]
constexpr uint32_t gobio_FileSystemSet_ListFiles = 0x004d7090;
// cdecl bool (const char* path, IBuffer** out) /* whole file by relative path; THE hook point for M4 */ [verified]
constexpr uint32_t gobio_ReadFile = 0x004d5140;
// cdecl bool (const char* path) [verified]
constexpr uint32_t gobio_Open = 0x004d50d0;
// cdecl void (void) [verified]
constexpr uint32_t gobio_Close = 0x004d4d20;
// cdecl uint (void) [verified]
constexpr uint32_t gobio_Size = 0x004d4d50;
// cdecl uint (void* dst, uint n) [verified]
constexpr uint32_t gobio_Read = 0x004d4d80;
// cdecl bool (const char* path) [verified]
constexpr uint32_t gobio_Exists = 0x004d50f0;
// cdecl void (const char* pattern, std::vector<std::string>* out) [verified]
constexpr uint32_t gobio_ListFiles = 0x004d71d0;
// custom /* size in EBX, IBuffer** out on stack — internal, do not hook */ [verified]
constexpr uint32_t gobio_Buffer_Create = 0x004d4c90;
// thiscall void* (IBuffer* this) /* vft[1] and vft[2]; = this+8 */ [verified]
constexpr uint32_t gobio_Buffer_Data = 0x00282d50;
// thiscall uint (IBuffer* this) /* vft[3]; = this+0xc */ [verified]
constexpr uint32_t gobio_Buffer_Size = 0x004d4c80;
// thiscall NativeFileSystem* (NativeFileSystem* this, const char* root) /* sizeof 0x28; vtable 0x00a398ac */ [verified]
constexpr uint32_t gobio_NativeFileSystem_ctor = 0x004d57a0;
// thiscall bool (NativeFileSystem* this, const char* path) /* vft[1] fopen(root+path,'rb') */ [verified]
constexpr uint32_t gobio_NativeFileSystem_Open = 0x004d4ef0;
// thiscall uint (NativeFileSystem* this, void* dst, uint n) /* vft[4] */ [verified]
constexpr uint32_t gobio_NativeFileSystem_Read = 0x004d4c30;
// thiscall ZipFileSystem* (ZipFileSystem* this, const char* zipPath) /* sizeof 0x34; vtable 0x00a39934 */ [verified]
constexpr uint32_t gobio_ZipFileSystem_ctor = 0x004d6c40;
// thiscall bool (ZipFileSystem* this, const char* path) /* vft[1]; case- and slash-insensitive */ [verified]
constexpr uint32_t gobio_ZipFileSystem_Open = 0x004d56a0;
// thiscall uint (ZipFileSystem* this, void* dst, uint n) /* vft[4] */ [verified]
constexpr uint32_t gobio_ZipFileSystem_Read = 0x004d4b40;
// data FileSystemSet* /* -> 0x00b2e274 after gobio::Init */ [verified]
constexpr uint32_t g_gobio = 0x0072e270;
// data const char*[3] /* used when sots.ini has no [Modules] Mount<N>; contains sots_local_en.gob, sots.gob */ [verified]
constexpr uint32_t g_DefaultMountTable = 0x00635e98;
// cdecl Stream* (const char* path) /* sniffs 'TXT' magic, returns a TextFileStream (0x420 bytes) opened for read */ [verified]
constexpr uint32_t Stream_OpenFile = 0x004cfcb0;
// thiscall TextFileStream* (TextFileStream* this) /* vtable 0x00a428c0 */ [verified]
constexpr uint32_t TextFileStream_ctor = 0x0051e460;
// thiscall bool (TextFileStream* this, const char* path, int mode /*1*/) [verified]
constexpr uint32_t TextFileStream_Open = 0x004cfb90;
// thiscall uint (Stream* this, char* out, int max) [verified]
constexpr uint32_t FileStream_ReadLine = 0x004cfa30;
// thiscall const char* (TextFileStream* this, const char* line, const char* label) /* NULL on END/mismatch */ [verified]
constexpr uint32_t TextFileStream_MatchLabel = 0x0051e760;
// thiscall bool (TextFileStream* this, const char* label, char* out, int outMax) /* vft[1] */ [verified]
constexpr uint32_t TextFileStream_ReadString = 0x0051ec90;
// thiscall bool (TextFileStream* this, const char* label, bool* out) /* vft[2] */ [verified]
constexpr uint32_t TextFileStream_ReadBool = 0x0051eba0;
// thiscall bool (TextFileStream* this, const char* label, float* out) /* vft[3] */ [verified]
constexpr uint32_t TextFileStream_ReadFloat = 0x0051eb30;
// thiscall bool (TextFileStream* this, const char* label, int* out, int dflt) /* vft[4] */ [verified]
constexpr uint32_t TextFileStream_ReadInt = 0x0051eab0;
// thiscall void (TextFileStream* this, const char* label, IStreamable* obj /*NULL=skip*/) /* vft[5]; BEGIN..END */ [verified]
constexpr uint32_t TextFileStream_ReadNested = 0x0051e8e0;
// thiscall void (ParticleSystem* this, Stream* s) /* .effect body */ [verified]
constexpr uint32_t ParticleSystem_Read = 0x004db650;
// thiscall void (ParticleSystem* this, Stream* s) [verified]
constexpr uint32_t ParticleSystem_Write = 0x004dad60;
// thiscall void* (EffectDictionary* this, const char* name) /* 'effects/' + name; Stream::OpenFile; 0x1c0-byte effect */ [unverified]
constexpr uint32_t EffectDictionary_Load = 0x004b42b0;
// cdecl void (Stream* s, const char* label, std::string* out, int flag) /* wrapper over vft[1] */ [verified]
constexpr uint32_t Stream_ReadString = 0x004b9d90;
// cdecl void (Stream* s, const char* label, int* out, int dflt) /* wrapper over vft[4] */ [verified]
constexpr uint32_t Stream_ReadInt = 0x004b9d20;
// cdecl void (Stream* s, const char* label, bool* out) /* wrapper over vft[2] */ [verified]
constexpr uint32_t Stream_ReadBool = 0x004b9c00;
// cdecl void (Stream* s, const char* label, float* out, int flag) /* wrapper over vft[3] */ [verified]
constexpr uint32_t Stream_ReadFloat = 0x004b9bc0;
// thiscall void (ServerPlayer* this, TechDef* def, bool silent) /* vft slot 4 of 0x00a327a4; hard-coded tech effects */ [verified]
constexpr uint32_t ServerPlayer_OnTechResearched = 0x00491790;
// thiscall bool (MasterTechTree* this, TechDef* def, int techId) /* techId = 10000+index into g_TechIdNames; 0xc5 = none */ [verified]
constexpr uint32_t MasterTechTree_IsTech = 0x0017d5d0;
// thiscall TechDef* (MasterTechTree* this, int techId) [verified]
constexpr uint32_t MasterTechTree_GetTechDef = 0x0017d610;
// thiscall bool (TechTree* this, int techId) [verified]
constexpr uint32_t TechTree_HasResearched = 0x0017d810;
// thiscall 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 [verified]
constexpr uint32_t TechTree_Cost = 0x0017da00;
// thiscall float (ServerPlayer* this, TechDef* def) // 1.0 - 0.25 per applicable species research-bonus tech owned; read-only [verified]
constexpr uint32_t ServerPlayer_TechCostMult = 0x0040db50;
// thiscall 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 [verified]
constexpr uint32_t TechTree_SetResearched = 0x00181e10;
// constant flags bit 1 of SetResearched(def, flags): skip the PrereqsMet test and complete unconditionally. TechTree::ProcessResearch passes flags = 2 [verified]
constexpr uint32_t TechTree_SetResearched_flag_Force = 0x00000002;
// constant 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 [verified]
constexpr uint32_t TechTree_SetResearched_flag_Silent = 0x00000004;
// offset ServerPlayer* owner (0 for a tree with no player) [verified]
constexpr uint32_t TechTree_off_Owner = 0x0000000c;
// offset std::vector<TechNode*> indexed by tech id (MSVC2010: 3 words {first@+0x10, last@+0x14, end@+0x18}); entries may be NULL [verified]
constexpr uint32_t TechTree_off_Nodes = 0x00000010;
// offset int completion-order counter; SetResearched stamps node+0x28 from it and post-increments it. Harness-audit row 9 [verified]
constexpr uint32_t TechTree_off_OrderCounter = 0x00000020;
// offset sizeof(TechNode) -- the ctor's operator new argument [verified]
constexpr uint32_t TechNode_size = 0x00000034;
// offset TechDef* def; *(int*)def is the tech id used to index TechTree_off_Nodes [verified]
constexpr uint32_t TechNode_off_Def = 0x00000000;
// offset 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 [verified]
constexpr uint32_t TechNode_off_State = 0x00000014;
// offset int costRP, INT_MAX = no researched parent yet; ctor writes 0x7fffffff [verified]
constexpr uint32_t TechNode_off_CostRP = 0x00000018;
// offset int progress in RP; the only node word ProcessResearch itself writes besides the flag [verified]
constexpr uint32_t TechNode_off_Progress = 0x0000001c;
// offset 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 [verified]
constexpr uint32_t TechNode_off_TurnAvailable = 0x00000020;
// offset int turnResearched; SetResearched writes the server ModCount [verified]
constexpr uint32_t TechNode_off_TurnResearched = 0x00000024;
// offset int order; SetResearched writes the tree's completion-order counter, then bumps it [verified]
constexpr uint32_t TechNode_off_Order = 0x00000028;
// offset std::vector<TechEdge*> children (3 words at +0x04/+0x08/+0x0c) [verified]
constexpr uint32_t TechNode_off_Children = 0x00000004;
// offset int RP cost carried by the edge; SetResearched sets child.costRP = min(child.costRP, edge.costRP) [verified]
constexpr uint32_t TechEdge_off_CostRP = 0x0000001c;
// offset TechDef* the edge's child tech; SetResearched indexes tree->nodes by childDef->[0] [verified]
constexpr uint32_t TechEdge_off_ChildDef = 0x00000040;
// offset int flag (1 default from the ctor, 0 completed below 80% of cost, 2 over-budget event raised) [verified]
constexpr uint32_t TechNode_off_Flag = 0x0000002c;
// thiscall void (MasterTechTree* this) /* fills TechDef*[196] at this+0 from g_TechIdNames */ [verified]
constexpr uint32_t MasterTechTree_ResolveTechIds = 0x00181c10;
// cdecl int (std::string* name) /* membership in g_CombatTechNames (116) */ [verified]
constexpr uint32_t IsCombatTechName = 0x002965c0;
// fastcall void (ServerPlayer* this) /* flags[7] at +0x348 from SpeciesDef xnc tech ids */ [verified]
constexpr uint32_t ServerPlayer_RebuildSpeciesTechFlags = 0x0042bf10;
// thiscall void (ServerPlayer* this, TechDef* def) [verified]
constexpr uint32_t ServerPlayer_ApplyAITechBonus = 0x0040e330;
// thiscall void (ServerPlayer* this, bool on) [verified]
constexpr uint32_t ServerPlayer_SetAIBenefit = 0x004186b0;
// cdecl bool (TechDef* def, uint* maskOut) [verified]
constexpr uint32_t TechDef_GetPlagueCureMask = 0x00137240;
// cdecl uint* (uint out[2], TechTree* tree) /* EBX-passed tree in caller; tables 0x00adf378 / 0x00adf478 */ [unverified]
constexpr uint32_t ComputeTechBitmasks = 0x000d7960;
// cdecl SpeciesDef* (uint species) /* 0x00b10a00 + i*0x184 */ [verified]
constexpr uint32_t SpeciesDef_Get = 0x00145cc0;
// cdecl void (void) [verified]
constexpr uint32_t SpeciesDef_InitTable = 0x001453a0;
// fastcall void (ServerPlayer* this) [verified]
constexpr uint32_t ServerPlayer_UpdateBankruptcyLimits = 0x00418600;
// thiscall void (ServerPlayer* this, int level) [verified]
constexpr uint32_t ServerPlayer_SetBankruptcyState = 0x0040e260;
// fastcall int (ServerPlayer* this) [verified]
constexpr uint32_t ServerPlayer_BankruptcyLevel = 0x0040db10;
// cdecl double (double suit, double ideal, double tol) /* clamp01(1-|suit-ideal|/(tol+0.1)) */ [verified]
constexpr uint32_t ServerSystem_HazardMod = 0x00347ae0;
// thiscall double (ServerSystem* this, double tradePoints) /* RET 8 */ [verified]
constexpr uint32_t ServerSystem_TradePointsToMoney = 0x003505b0;
// thiscall double (ServerSystem* this, int species) [verified]
constexpr uint32_t ServerSystem_CalcSuitMod = 0x003484d0;
// fastcall void (ServerSystem* this) [verified]
constexpr uint32_t ServerSystem_AccrueSystemBonus = 0x0034d4f0;
// thiscall int (ServerSystem* this, float frac) [verified]
constexpr uint32_t ServerSystem_SystemBonusPopTarget = 0x0034b5a0;
// data struct { const char* name; int x; }[196] /* TechId = 10000+i */ [verified]
constexpr uint32_t g_TechIdNames = 0x005ff9e4;
// data const char*[116] [verified]
constexpr uint32_t g_CombatTechNames = 0x00619718;
// data SpeciesDef[7] (0x184 B each, .bss, filled by SpeciesDef_InitTable) [verified]
constexpr uint32_t g_SpeciesDefTable = 0x00710a00;
// data struct { int techId; uint bit; }[32] [verified]
constexpr uint32_t g_TechBitmaskTableA = 0x006df378;
// data struct { int techId; uint bit; }[29] [verified]
constexpr uint32_t g_TechBitmaskTableB = 0x006df478;
// data 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 */ [verified]
constexpr uint32_t g_AITechValueTable = 0x00617888;
// data int (config storage; PTR slot 0x00aedfe0) [verified]
constexpr uint32_t g_PERGATETRAFFIC_DRV_TpGate = 0x00723e2c;
// data int (config storage; PTR slot 0x00aedfe4) [verified]
constexpr uint32_t g_PERGATETRAFFIC_DRV_GatAmp = 0x00723e30;
// offset int PlyrIdx [verified-by-save]
constexpr uint32_t ServerPlayer_off_PlyrIdx = 0x00000028;
// offset 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 [verified-by-save]
constexpr uint32_t ServerPlayer_off_OwnedSystems = 0x00000030;
// offset int Species (0 Human .. 6 Morrigi) [verified-by-save]
constexpr uint32_t ServerPlayer_off_Species = 0x0000005c;
// offset float ResRate (research share of available money, 0..1) [verified-by-save]
constexpr uint32_t ServerPlayer_off_ResRate = 0x000000bc;
// offset float ResMod [verified-by-save]
constexpr uint32_t ServerPlayer_off_ResMod = 0x000000c0;
// offset float ResScl [verified-by-save]
constexpr uint32_t ServerPlayer_off_ResScl = 0x000000c4;
// offset float TRM (timed research multiplier bonuses) [verified-by-save]
constexpr uint32_t ServerPlayer_off_TRM = 0x000000d0;
// offset int TRA (per-turn research-point contribution) [verified-by-save]
constexpr uint32_t ServerPlayer_off_TRA = 0x000000d4;
// offset int TRP (per-turn research-point contribution) [verified-by-save]
constexpr uint32_t ServerPlayer_off_TRP = 0x000000d8;
// offset bool Elim [verified-by-save]
constexpr uint32_t ServerPlayer_off_Elim = 0x000000f8;
// offset bool isAI /* gate ComputeBudget reads for the construction slot and for picking the AI difficulty-mod row */ [verified]
constexpr uint32_t ServerPlayer_off_IsAI = 0x000000f9;
// offset bool NPC [verified-by-save]
constexpr uint32_t ServerPlayer_off_NPC = 0x000000fb;
// offset bool RebAI [verified-by-save]
constexpr uint32_t ServerPlayer_off_RebAI = 0x000000fc;
// offset int Maint (raw fleet upkeep, before the difficulty divisor) [verified-by-save]
constexpr uint32_t ServerPlayer_off_Maint = 0x0000015c;
// offset float shrm (shared research modifier) [verified-by-save]
constexpr uint32_t ServerPlayer_off_shrm = 0x00000160;
// offset std::vector<ExpenseEntry> expense sliders (3 words; entry 16 B {int xid, int xmin, int xmax, float xper}); save tag Nexp [verified-by-save]
constexpr uint32_t ServerPlayer_off_Nexp = 0x00000204;
// offset float income multiplier from the game-setup handicap block; ComputeBudget bonus slot = ftol((this - 1.0) x net) [verified]
constexpr uint32_t ServerPlayer_off_SetupIncomeMult = 0x00000228;
// offset float research multiplier from the game-setup handicap block; a factor of the research-points slot [verified]
constexpr uint32_t ServerPlayer_off_SetupResearchMult = 0x0000022c;
// offset int Sav (treasury) [verified-by-save]
constexpr uint32_t ServerPlayer_off_Sav = 0x00000284;
// offset Tech* current research target (ResT); NULL = none [verified-by-save]
constexpr uint32_t ServerPlayer_off_ResearchTarget = 0x00000294;
// offset std::vector<ObservedTech> otch -- 3 words {_Myfirst@0x274,_Mylast@0x278,_Myend@0x27c}; save tag otch. sizeof(ObservedTech) = 0x2c (44), PINNED three ways: magic divide 0x2e8ba2e9 sar 3 (=/44) in vector_ObservedTech_assign 0x0087239f, imul reg,reg,0x2c at 0x0087243a / 0x007b735b, and the linear search stride `add edi,0x2c` at 0x007ba257. Append site = RecordObservedTech+0xdf (0x007ba27f): lea ecx,[player+0x274]; call vector_ObservedTech_push_back 0x007b7320. All three words move because push_back reallocs through vector_44B_grow 0x007b5820. [verified]
constexpr uint32_t ServerPlayer_off_ObservedTechs = 0x00000274;
// offset float IncMod [verified-by-save]
constexpr uint32_t ServerPlayer_off_IncMod = 0x0000030c;
// offset std::vector<PlayerAid> (3 words; entry 0x18 B, {+0x8 int researchPercent, +0xc int researchActive, +0x10 int savings, +0x14 int savingsActive}) [verified]
constexpr uint32_t ServerPlayer_off_Aid = 0x00000310;
// offset float SuitTol -- raised by the two adaptation techs; also caps the hazard money cost [verified]
constexpr uint32_t ServerPlayer_off_SuitTol = 0x000000b4;
// offset float MaxOH -- max over-harvest slider, raised by a float32 max() against 0.1f [verified]
constexpr uint32_t ServerPlayer_off_MaxOH = 0x000000b8;
// offset TechTree* the player's own tech tree; its +4 is the MasterTechTree the IsTech chain uses [verified]
constexpr uint32_t ServerPlayer_off_TechTree = 0x000000f4;
// offset bool AIBn -- AI benefit active; gates ApplyAITechBonus and flips the bonus sign [verified]
constexpr uint32_t ServerPlayer_off_AIBn = 0x000000fe;
// offset bool CnTrd -- trade routes allowed (set by CCC_FtlEcon unless RebAI) [verified]
constexpr uint32_t ServerPlayer_off_CnTrd = 0x000000ff;
// offset bool CnRad -- commerce raiding allowed (CCC_ComRaid) [verified]
constexpr uint32_t ServerPlayer_off_CnRad = 0x00000100;
// offset bool CnVItl -- may view other empires' intel (CCC_DatCor) [verified]
constexpr uint32_t ServerPlayer_off_CnVItl = 0x00000101;
// offset bool hgs -- gravitic-syncing drive researched (DRV_GrvSyn); client-synced only [verified]
constexpr uint32_t ServerPlayer_off_hgs = 0x00000102;
// offset bool hadvs -- advanced sensors (CCC_AdvSens) [verified]
constexpr uint32_t ServerPlayer_off_hadvs = 0x00000103;
// offset bool harcc -- arcologies (IND_ArcCon) [verified]
constexpr uint32_t ServerPlayer_off_harcc = 0x00000104;
// offset float pddm -- multiplied by 0.25 by IND_HrdStrct [verified]
constexpr uint32_t ServerPlayer_off_pddm = 0x00000108;
// offset float ConMod[3] -- construction cost per hull class; techs subtract from all three, IND_OrbDry only from [1] and [2] [verified]
constexpr uint32_t ServerPlayer_off_ConMod = 0x0000010c;
// offset float SavMod[3] -- IND_OrbFound subtracts 0.05f from all three [verified]
constexpr uint32_t ServerPlayer_off_SavMod = 0x00000118;
// offset float OutMod -- industrial output multiplier; several techs add, IND_HrdStrct multiplies by 0.9f [verified]
constexpr uint32_t ServerPlayer_off_OutMod = 0x00000124;
// offset float PopMod [verified]
constexpr uint32_t ServerPlayer_off_PopMod = 0x00000130;
// offset float TerraMod [verified]
constexpr uint32_t ServerPlayer_off_TerraMod = 0x00000134;
// offset bool AMine -- asteroid mining (IND_AstMine) [verified]
constexpr uint32_t ServerPlayer_off_AMine = 0x00000138;
// offset float MinRate -- mining rate; IND_MsMine adds 1.0 [verified]
constexpr uint32_t ServerPlayer_off_MinRate = 0x00000140;
// offset 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 [verified]
constexpr uint32_t ServerPlayer_off_PrGtTrf = 0x00000148;
// offset float CstR, CstE, CstT at +0x150/+0x154/+0x158 -- set to 10.0f / 2.0f / 1.0f (FLD1) by DRV_FarCast [verified]
constexpr uint32_t ServerPlayer_off_CstR = 0x00000150;
// offset uint32 design-option mask A; +0x194 is mask B. Rewritten wholesale by ComputeTechBitmasks on every completion; not serialised [verified]
constexpr uint32_t ServerPlayer_off_TechMaskA = 0x00000190;
// offset 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 [verified]
constexpr uint32_t ServerPlayer_off_TranslationKnown = 0x000001a4;
// offset uint32 HasVac; +0x28c HasImm. Both get |= the plague-cure mask of the completing tech [verified]
constexpr uint32_t ServerPlayer_off_HasVac = 0x00000288;
// offset uint32 NPTrk -- bit per species whose node-space traffic is visible; set by that species' node-track tech [verified]
constexpr uint32_t ServerPlayer_off_NPTrk = 0x00000290;
// offset 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) [verified]
constexpr uint32_t ServerPlayer_off_NodeBore = 0x00000308;
// offset 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 [verified]
constexpr uint32_t ServerPlayer_off_CaptureDesigns = 0x00000330;
// offset uint32 flags[7] xenotech bits per target species, followed by a count word 7 at +0x364 (0x20 bytes assigned as a unit) [verified]
constexpr uint32_t ServerPlayer_off_SpeciesTechFlags = 0x00000348;
// offset bool -- a pending plague-cure roll; run and cleared when the completing tech is the current research target [verified]
constexpr uint32_t ServerPlayer_off_ResearchRollPending = 0x000003b4;
// offset AIRebellion* -- non-null while an AI rebellion object exists; the two AI techs notify it [verified]
constexpr uint32_t ServerPlayer_off_AIRebellion = 0x000003b8;
// offset MasterTechTree* -- the `this` MasterTechTree::IsTech / GetTechDef are called on [verified]
constexpr uint32_t TechTree_off_Master = 0x00000004;
// fastcall 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 [verified]
constexpr uint32_t ServerPlayer_UpdateNodeBoreParams = 0x004182c0;
// cdecl 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 [verified]
constexpr uint32_t SelectNodeBoreParams = 0x002e18e0;
// custom int (TechDef* def in EBX) -- index of the species whose SpeciesDef+0x74 node-track tech this def is, or -1 [verified]
constexpr uint32_t ServerPlayer_SpeciesOfTranslationTech = 0x0040e410;
// cdecl struct {int techId; float rebellionOdds; float bonus;}* (TechDef* def) -- linear search of g_AITechValueTable, NULL when the def is none of them [verified]
constexpr uint32_t AITechRow = 0x00290f70;
// data int -- number of rows in g_AITechValueTable (6) [verified]
constexpr uint32_t g_AITechValueCount = 0x006ea2ec;
// offset 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 [verified]
constexpr uint32_t ServerPlayer_off_ServerLink = 0x00000008;
// offset Mars::RNG* -- the strategic generator (same object TechTree::ProcessResearch is handed) [verified]
constexpr uint32_t StrategyServer_off_RNG = 0x0000016c;
// fastcall 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 [verified]
constexpr uint32_t ServerPlayer_RollResearchEvent = 0x0048df20;
// thiscall 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 [verified]
constexpr uint32_t ServerPlayer_ResearchEventOdds = 0x00420380;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ProcessTurn = 0x003598e0;
// thiscall 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) */ [verified]
constexpr uint32_t StrategyServer_MoveFleet = 0x003d9ee0;
// thiscall 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 */ [verified]
constexpr uint32_t StrategyServer_ProcessFleetMovement = 0x003da9a0;
// thiscall 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 */ [verified]
constexpr uint32_t StarFleet_MinRange = 0x002ff6a0;
// thiscall bool (StarFleet* this, uint32 mask) /* (flags & mask) == mask, flags at fleet+0x10c */ [verified]
constexpr uint32_t StarFleet_HasAllFlags = 0x002fe1d0;
// thiscall void (StarFleet* this, uint32 mask, bool on) /* the only two writers of fleet+0x10c in the image */ [verified]
constexpr uint32_t StarFleet_SetFlag = 0x002fe1a0;
// thiscall 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 */ [verified]
constexpr uint32_t StarFleet_ResolveWaypoint = 0x00301390;
// cdecl bool (int waypointType) /* 7-entry jump table: true only for 4 and 5; false default */ [verified]
constexpr uint32_t IsGateTransitWaypoint = 0x0016e6e0;
// cdecl 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 */ [verified]
constexpr uint32_t IsNodeWaypoint = 0x0016e720;
// thiscall 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 */ [verified]
constexpr uint32_t ProbabilisticJump = 0x003b6700;
// cdecl 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 */ [verified]
constexpr uint32_t NodeLine_Step = 0x00305510;
// cdecl 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 */ [verified]
constexpr uint32_t NodeLine_BuildStutterSegments = 0x00305280;
// cdecl 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 */ [verified]
constexpr uint32_t SegmentSphereIntersect = 0x004a64f0;
// cdecl float (const Vector3* p, const Vector3* a, const Vector3* b) /* projection parameter clamped to [0,1]. NOTE 0x008c8eb0 is a varargs formatter, not this */ [verified]
constexpr uint32_t DistPointToSegment = 0x004e8eb0;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_MaxPop = 0x0034ab20;
// thiscall int64 (ServerSystem* this, int groupType, int species, ServerPlayer* p, float* suitOverride) /* RET 0x10 */ [verified]
constexpr uint32_t ServerSystem_MaxPopGeneric = 0x0034a4a0;
// thiscall bool (ServerSystem* this) /* false without an owner, with Infra < 1.0, or with Suit != IdealSuit */ [verified]
constexpr uint32_t ServerSystem_IsStable = 0x0034ad90;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ApplyInfraBonus = 0x00346780;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ApplyPopBonus = 0x0034b510;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ApplyInfraDelta = 0x00348270;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ApplySuitDelta = 0x003481c0;
// thiscall 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) */ [verified]
constexpr uint32_t ServerSystem_ComputeOutputFromRates = 0x00351bb0;
// cdecl 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) */ [verified]
constexpr uint32_t ServerSystem_NormaliseOutputRates = 0x00347390;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ProcessBuildQueue = 0x00352500;
// thiscall 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 */ [verified]
constexpr uint32_t BuildQueue_ProcessTurn = 0x00490d50;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_AdjustResources = 0x00345f30;
// thiscall int64 (ServerSystem* this, int groupType, int species) /* RET 8; 0 without an owner or when haltv[groupType] is set */ [verified]
constexpr uint32_t ServerSystem_PopGrowthDelta = 0x00348100;
// thiscall 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 */ [verified]
constexpr uint32_t PopGrowthFraction = 0x00136fb0;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_SlaveDeathRate = 0x0034b110;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ProcessSlaves = 0x003537b0;
// thiscall void (ServerSystem* this) /* consumes no RNG, at depth 1 */ [verified]
constexpr uint32_t ServerSystem_ProcessPlague = 0x00356a90;
// thiscall 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 */ [verified]
constexpr uint32_t ServerSystem_ProcessRebellion = 0x003583b0;
// thiscall void (ServerSystem* this) /* consumes no RNG, at depth 1 */ [verified]
constexpr uint32_t ServerSystem_GrowCivilianPops = 0x00354220;
// stdcall 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` */ [verified]
constexpr uint32_t RNG_Chance = 0x004e6dd0;
// thiscall 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 */ [verified]
constexpr uint32_t ServerPlayer_AddMoraleEvent = 0x00439d60;
// thiscall MoraleEvent* (MoraleEvent* this) /* 0x50 bytes: {vptr, ints (+0x10 = event id), vptr2 @+0x14, int deltas[7] @+0x18, std::string name @+0x34} */ [verified]
constexpr uint32_t MoraleEvent_ctor = 0x00352a10;
// offset StrategyServer* owner (the RAW base; the RNG accessor uses owner-4) [verified]
constexpr uint32_t ServerSystem_off_Owner = 0x00000010;
// offset int Idx [verified]
constexpr uint32_t ServerSystem_off_Idx = 0x0000005c;
// offset int Size (1..10); the capacity base is Size * 100000000 as an exact int64 product [verified]
constexpr uint32_t ServerSystem_off_Size = 0x00000060;
// offset float Suit [verified]
constexpr uint32_t ServerSystem_off_Suit = 0x00000064;
// offset int Res [verified]
constexpr uint32_t ServerSystem_off_Res = 0x00000068;
// offset int ARes2 [verified]
constexpr uint32_t ServerSystem_off_ARes2 = 0x0000006c;
// offset int MRes [verified]
constexpr uint32_t ServerSystem_off_MRes = 0x00000070;
// offset int TRes -- zeroed every turn by ProcessTurn [verified]
constexpr uint32_t ServerSystem_off_TRes = 0x00000074;
// offset bool haltv[3] -- growth halt per group; cleared every turn by ProcessTurn [verified]
constexpr uint32_t ServerSystem_off_haltv = 0x00000078;
// offset float OutMod [verified]
constexpr uint32_t ServerSystem_off_OutMod = 0x0000007c;
// offset int TAcq (turn acquired) [verified]
constexpr uint32_t ServerSystem_off_TAcq = 0x00000080;
// offset StarSystem::OutputRates (0x1c): float SRt, SRsc, SRtf, SRi, SRoh, SRs; int SRnr [verified]
constexpr uint32_t ServerSystem_off_Rates = 0x00000088;
// offset float SRoh -- the over-harvest slider (Rates + 0x10) [verified]
constexpr uint32_t ServerSystem_off_SRoh = 0x00000098;
// offset BuildQueue* [verified]
constexpr uint32_t ServerSystem_off_BuildQueue = 0x000000a4;
// offset std::string Name (0x1c, MSVC SSO; capacity word at +0xbc) [verified]
constexpr uint32_t ServerSystem_off_Name = 0x000000a8;
// offset bool Abdn [verified]
constexpr uint32_t ServerSystem_off_Abdn = 0x000000c4;
// offset int64 Bats2 -- player i's 4-bit battle-recent counter at bits [4i, 4i+4), i < 15 [verified]
constexpr uint32_t ServerSystem_off_Bats2 = 0x000000f0;
// offset int64 rcex -- the same shape for the explored/recon-recent counter [verified]
constexpr uint32_t ServerSystem_off_Rcex = 0x000000f8;
// offset ServerPlayer* PID (null = unowned) [verified]
constexpr uint32_t ServerSystem_off_PID = 0x00000100;
// offset Morale cm (0x20: vptr + int[7]) [verified]
constexpr uint32_t ServerSystem_off_Morale = 0x0000011c;
// offset std::vector<StarFleet*> [verified]
constexpr uint32_t ServerSystem_off_Fleets = 0x0000016c;
// offset int Pop (imperial) [verified]
constexpr uint32_t ServerSystem_off_Pop = 0x0000018c;
// offset float Infra [verified]
constexpr uint32_t ServerSystem_off_Infra = 0x00000190;
// offset int pbon -- the pending population bonus (int32, not a float) [verified]
constexpr uint32_t ServerSystem_off_pbon = 0x00000194;
// offset float ibon -- the pending infrastructure bonus [verified]
constexpr uint32_t ServerSystem_off_ibon = 0x00000198;
// offset Population Pop2 (civilians): {vptr, vector<PopulationGroup> @+4}, 24-byte entries {?, int type @+4, int species @+8, int64 count @+0x10} [verified]
constexpr uint32_t ServerSystem_off_Pop2 = 0x000001a0;
// offset Population pbon2 -- the civilian analogue of pbon [verified]
constexpr uint32_t ServerSystem_off_pbon2 = 0x000001b4;
// offset IndependenceInfo* indi (int indsp at +4) [verified]
constexpr uint32_t ServerSystem_off_Indi = 0x000001c8;
// offset int rbfl -- rebelling-species bitmask [verified]
constexpr uint32_t ServerSystem_off_rbfl = 0x000001dc;
// offset int adt[7] -- per-species addiction start turn; 0 means never addicted [verified]
constexpr uint32_t ServerSystem_off_adt = 0x000001e4;
// offset uint32 -- bit i is cleared when player i's Bats2 counter reaches 0 [verified]
constexpr uint32_t ServerSystem_off_BatsMask = 0x000002a0;
// offset uint32 -- the same for rcex [verified]
constexpr uint32_t ServerSystem_off_RcexMask = 0x000002a4;
// offset int TnsOH (turns over-harvesting) [verified]
constexpr uint32_t ServerSystem_off_TnsOH = 0x000002b8;
// offset 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 [verified]
constexpr uint32_t ServerSystem_off_ntdev = 0x000002c4;
// offset int rbtn [verified]
constexpr uint32_t ServerSystem_off_rbtn = 0x000002cc;
// offset int ModCount (the turn counter), relative to the RAW server base a ServerSystem's +0x10 points at [verified]
constexpr uint32_t StrategyServer_off_ModCount = 0x00000008;
// offset 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 */ [verified]
constexpr uint32_t StrategyServer_off_Players = 0x00000050;
// offset 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 [verified]
constexpr uint32_t StrategyServer_off_Fleets = 0x00000060;
// offset 16-bucket hash of entity id -> object, hashed by (key & 0xF); 0x008b9240 is the lookup [verified]
constexpr uint32_t StrategyServer_off_EntityHash = 0x00000080;
// offset Mars::RNG* relative to the RAW base (== the -4-adjusted base's +0x16c) [verified]
constexpr uint32_t StrategyServer_off_RNGPtr = 0x00000168;
// offset std::set<pair<FleetId,LocationId>> cleared at the head of ProcessFleetMovement [verified]
constexpr uint32_t StrategyServer_off_ArrivedSet = 0x00000200;
// offset std::set<pair<FleetId,LocationId>> that MoveFleet fills for fleets still in motion; OnFleetArrived takes the set difference [verified]
constexpr uint32_t StrategyServer_off_InMotionSet = 0x00000210;
// offset int id (also the entity-hash key) [verified]
constexpr uint32_t StarFleet_off_Id = 0x00000004;
// offset the object whose +0x80 holds the entity hash the waypoint target is resolved in [verified]
constexpr uint32_t StarFleet_off_Galaxy = 0x00000010;
// offset Vector3 Pos [verified]
constexpr uint32_t StarFleet_off_Pos = 0x00000018;
// offset Vector3 PrvPos -- set to the ENTRY position when the fleet moved [verified]
constexpr uint32_t StarFleet_off_PrvPos = 0x0000004c;
// offset ServerPlayer* owner [verified]
constexpr uint32_t StarFleet_off_PID = 0x00000058;
// offset Location*; kind at Location+0x14 (0 system, 1 fleet, 2 point) [verified]
constexpr uint32_t StarFleet_off_Location = 0x000000a0;
// offset std::vector<StarShip*> (begin @+0xa4, end @+0xa8) [verified]
constexpr uint32_t StarFleet_off_Ships = 0x000000a4;
// offset int16 -- SIGNED, summed into the owner's gate traffic [verified]
constexpr uint32_t StarFleet_off_GateTraffic = 0x000000c0;
// offset std::vector<Waypoint> _Myfirst (proxy @+0xc4, last @+0xcc, end @+0xd0) [verified]
constexpr uint32_t StarFleet_off_Waypoints = 0x000000c8;
// offset float FPsp2 -- the fleet's strategic speed [verified]
constexpr uint32_t StarFleet_off_Speed = 0x000000d8;
// offset Vector3 -- set after every pass to the current waypoint target's position. The notes called this FPogn2; by the FlightPlan layout it is FPdpos [verified]
constexpr uint32_t StarFleet_off_DestPos = 0x000000ec;
// offset 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 [verified]
constexpr uint32_t StarFleet_off_Flags = 0x0000010c;
// offset sizeof(Waypoint) -- pinned by the divide-by-28 reciprocal multiply in the iterator arithmetic [verified]
constexpr uint32_t Waypoint_stride = 0x0000001c;
// offset int -- the destination entity id [verified]
constexpr uint32_t Waypoint_off_Target = 0x00000004;
// offset int -- 2 node line, 3 node route, 4 gate teleport, 5 probabilistic jump, otherwise a straight run [verified]
constexpr uint32_t Waypoint_off_Type = 0x00000008;
// offset uint64 flag pair; bit 0x1000 exempts the ship from movement fuel and marks it a tanker [verified]
constexpr uint32_t StarShip_off_Flags = 0x00000018;
// offset float Range -- remaining strategic range [verified]
constexpr uint32_t StarShip_off_Range = 0x00000020;
// offset float -- the cap the refuel helper clamps Range to [verified]
constexpr uint32_t StarShip_off_MaxRange = 0x00000078;
// offset ServerSystem* -- the bonus-apply helpers skip the ntdev reset for this system [verified]
constexpr uint32_t ServerPlayer_off_HomeSystem = 0x0000002c;
// offset int GTraf -- assigned (not accumulated) once per turn [verified]
constexpr uint32_t ServerPlayer_off_GateTraffic = 0x0000014c;
// offset float CstE -- the probabilistic jump's efficiency [verified]
constexpr uint32_t ServerPlayer_off_CstE = 0x00000154;
// offset float CstT -- the probabilistic jump's threshold [verified]
constexpr uint32_t ServerPlayer_off_CstT = 0x00000158;
// data int** -- the GlobalConst pointer slot; the storage word is *slot [verified]
constexpr uint32_t g_ptr_SYSTEMBONUS_MINTURNS = 0x006eca10;
// data float** -- pointer slot [verified]
constexpr uint32_t g_ptr_SYSTEMBONUS_POPBONUS = 0x006eca18;
// data float** -- pointer slot [verified]
constexpr uint32_t g_ptr_SYSTEMBONUS_INFRABONUS = 0x006eca20;
// data float** -- pointer slot [verified]
constexpr uint32_t g_ptr_SYSTEMBONUS_POPBONUS_INC = 0x006eca38;
// data float** -- pointer slot [verified]
constexpr uint32_t g_ptr_SYSTEMBONUS_INFRABONUS_INC = 0x006eca40;
// data int** -- pointer slot (image default 10) [verified]
constexpr uint32_t g_ptr_ADDICTION_PHASE2_START = 0x006eca58;
// data int** -- pointer slot (image default 15) [verified]
constexpr uint32_t g_ptr_ADDICTION_PHASE3_START = 0x006eca60;
// data float -- 0.05f, a hard-coded literal in .data, NOT a GlobalConst; both ServerSystem::ProcessTurn calls to the independent pop-drift helper pass it [verified]
constexpr uint32_t g_IndependentPopDriftRate = 0x006eca80;
// data float** -- pointer slot; storage 0x00b212cc (shipped value 2) [verified]
constexpr uint32_t g_ptr_STUTTER_SYSTEM_INFLUENCE_RADIUS = 0x006ebc44;
// data float** -- pointer slot; storage 0x00b212d0 (shipped value 0.33) [verified]
constexpr uint32_t g_ptr_STUTTER_MIN_SPEED = 0x006ebc48;
// data 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 [verified]
constexpr uint32_t g_ptr_STUTTER_MAX_SPEED = 0x006ebc4c;
// thiscall EventStorage* (ServerPlayer* this) /* whole body: lea eax,[ecx+0x29c]; ret. No stack args, plain RET */ [verified]
constexpr uint32_t ServerPlayer_GetEventStorage = 0x0040db00;
// thiscall void* (ServerPlayer* this) /* eax = [this+8] ? [this+8]-4 : 0. StrategyServer primary base; turn = *(int*)(result+0x0c) */ [verified]
constexpr uint32_t ServerPlayer_GetServer = 0x0040e320;
// thiscall 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 [verified]
constexpr uint32_t EventStorage_PostEvent = 0x004862b0;
// thiscall 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 [verified]
constexpr uint32_t EventStorage_GetOrCreateTurnBucket = 0x00485380;
// thiscall 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 [verified]
constexpr uint32_t EventStorage_FindDuplicate = 0x00425d40;
// thiscall 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 [verified]
constexpr uint32_t EventStorage_PruneOldTurns = 0x00479eb0;
// thiscall void (EventStorage* this, IStreamable* s) RET 4. Order: EvNxID (int, tag 0x00a2bd90), then nested collection "Events" (tag 0x00a2bda0, descriptor vtable 0x00a2da8c) over this+4 [verified]
constexpr uint32_t EventStorage_Read = 0x00425cc0;
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Order: EvTurn (int, tag 0x00a2bd98), then nested collection "Events" (descriptor vtable 0x00a2da7c) over this+8 [verified]
constexpr uint32_t TurnEvents_Write = 0x00425bb0;
// thiscall void (TurnEvents* this, IStreamable* s) RET 4. Same field order as TurnEvents_Write [verified]
constexpr uint32_t TurnEvents_Read = 0x00425c40;
// thiscall 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) [verified]
constexpr uint32_t PlayerEvent_ctor = 0x0044ee30;
// thiscall 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) [verified]
constexpr uint32_t PlayerEvent_Serialize = 0x00425970;
// thiscall 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 [verified]
constexpr uint32_t ProcessResearch_PostEventOverbudget = 0x00187b97;
// thiscall 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 [verified]
constexpr uint32_t ProcessResearch_PostEventTechsUnlocked = 0x00187ff4;
// thiscall 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 [verified]
constexpr uint32_t OnTechResearched_PostEventComplete = 0x004919b5;
// thiscall 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 [verified]
constexpr uint32_t OnTechResearched_PostEventTemperance = 0x00492427;
// thiscall 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 [verified]
constexpr uint32_t ServerPlayer_ProcessTurn_PostEventNoResearch = 0x0049168c;
// data const char* "Research completed at %d of %d (%.1f%%). (Odds: %.2f, Roll: %.2f)\n" -- log line on the completion-roll-SUCCEEDED branch, 0x00587977 [verified]
constexpr uint32_t TechTree_ProcessResearch_LogFormat = 0x006007a8;
// offset EventStorage ServerPlayer::Events -- embedded, size 0x1c. Confirmed by ServerPlayer_GetEventStorage [verified]
constexpr uint32_t ServerPlayer_off_Events = 0x0000029c;
// offset std::vector<TurnEvents> _Myfirst (element stride 0x18; _Mylast +0x08, _Myend +0x0c, _Alval +0x10) [verified]
constexpr uint32_t EventStorage_off_Events = 0x00000004;
// offset int EvNxID -- next event id. Starts at 0; PostEvent promotes 0->1 on the first post, then post-increments. ServerPlayer+0x2b0 [verified]
constexpr uint32_t EventStorage_off_EvNxID = 0x00000014;
// offset sizeof(EventStorage) [verified]
constexpr uint32_t EventStorage_sizeof = 0x0000001c;
// offset int EvTurn (after the vptr at +0) [verified]
constexpr uint32_t TurnEvents_off_EvTurn = 0x00000004;
// offset std::vector<PlayerEvent> _Myfirst (stride 0x74; _Mylast +0x0c, _Myend +0x10, _Alval +0x14) [verified]
constexpr uint32_t TurnEvents_off_Events = 0x00000008;
// offset sizeof(TurnEvents); the outer vector's stride, from the /24 divide at 0x008853b3 [verified]
constexpr uint32_t TurnEvents_sizeof = 0x00000018;
// offset int EvEID (after the vptr at +0) [verified]
constexpr uint32_t PlayerEvent_off_EvEID = 0x00000004;
// offset std::string EvDsc (summary / title), 0x1c bytes [verified]
constexpr uint32_t PlayerEvent_off_EvDsc = 0x00000008;
// offset std::string EvMsg (body), 0x1c bytes [verified]
constexpr uint32_t PlayerEvent_off_EvMsg = 0x00000024;
// offset int EvLoc (object id, or 0) [verified]
constexpr uint32_t PlayerEvent_off_EvLoc = 0x00000040;
// offset float[3] EvPos (default FLT_MAX x3) [verified]
constexpr uint32_t PlayerEvent_off_EvPos = 0x00000044;
// offset std::string EvImg (event-type name, e.g. "EVENT_RESEARCH_OVERBUDGET"), 0x1c bytes [verified]
constexpr uint32_t PlayerEvent_off_EvImg = 0x00000050;
// offset int EvAct [verified]
constexpr uint32_t PlayerEvent_off_EvAct = 0x0000006c;
// offset int EvCID -- ctor sets 0 and PostEvent never writes it [verified]
constexpr uint32_t PlayerEvent_off_EvCID = 0x00000070;
// offset sizeof(PlayerEvent) = 116. Two independent confirmations: the /116 divide at 0x00825d5f and PostEvent's 'mov [_Mylast-0x70], id' writing EvEID at element+4 [verified]
constexpr uint32_t PlayerEvent_sizeof = 0x00000074;
// offset prune cutoff = turn - 50; a code constant at 0x00879ec3, not config [verified]
constexpr uint32_t EventStorage_PruneWindowTurns = 0x00000032;
// data void** -- PlayerEvent vftable. slot0 dtor 0x007694d0, slot1 Serialize 0x00825970, slot2 0x00825ab0. RTTI locator 0x00a80a7c [verified]
constexpr uint32_t g_vft_PlayerEvent = 0x00621958;
// data void** -- TurnEvents vftable (bucket ctor 0x00884cb0); slot0 is the virtual dtor the pruner calls [verified]
constexpr uint32_t g_vft_TurnEvents = 0x0060f07c;
// data const char[12][8] -- stride 8: EvEID EvNxID EvTurn Events EvPos EvLoc EvMsg EvImg EvDsc EvCID EvAct sasc [verified]
constexpr uint32_t g_EventTagTable = 0x0062bd88;
// data float[3] = {0x7f7fffff, 0x7f7fffff, 0x7f7fffff} = FLT_MAX. PlayerEvent's default EvPos. NOT infinity [verified]
constexpr uint32_t g_Vector3_Invalid = 0x006f0dc8;
// data const char* "EVENT_RESEARCH_OVERBUDGET" -- EvImg pushed at 0x00587b24 [verified]
constexpr uint32_t g_str_EVENT_RESEARCH_OVERBUDGET = 0x0060078c;
// data const char* "EVENT_RESEARCH_COMPLETE" -- EvImg for progress/cost >= 0.8 [verified]
constexpr uint32_t g_str_EVENT_RESEARCH_COMPLETE = 0x00633368;
// data const char* "EVENT_RESEARCH_UNDERBUDGET" -- EvImg for progress/cost < 0.8 [verified]
constexpr uint32_t g_str_EVENT_RESEARCH_UNDERBUDGET = 0x00633380;
// data const char* "EVENT_TEMPERANCE" -- EvImg at 0x008923ae; posted with act=0 so the stored EvAct is 2 [verified]
constexpr uint32_t g_str_EVENT_TEMPERANCE = 0x00633340;
// data const char* "EVENT_TECHS_UNLOCKED" (length 0x14 pushed at 0x00587eb6) [verified]
constexpr uint32_t g_str_EVENT_TECHS_UNLOCKED = 0x00600774;
// data const char* "EVENT_NO_RESEARCH" -- EvImg at 0x0089168c [verified]
constexpr uint32_t g_str_EVENT_NO_RESEARCH = 0x0063332c;
// data const char** -> slot 0x00ae48e0; Strings.csv key EVENTSUM_RESEARCH_OVERBUDGET = "Research Over Budget" [verified]
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_OVERBUDGET = 0x006e48e4;
// data const char** -> slot 0x00ae48e8; key EVENTMSG_RESEARCH_OVERBUDGET = "Research for %s has gone overbudget." [verified]
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_OVERBUDGET = 0x006e48ec;
// data const char** -> slot 0x00ae48f0; key EVENTSUM_UNLOCKEDTECHS = "New Technologies Available" [verified]
constexpr uint32_t g_ptr_EVENTSUM_UNLOCKEDTECHS = 0x006e48f4;
// data const char** -> slot 0x00ae48f8; key EVENTMSG_UNLOCKEDTECHS [verified]
constexpr uint32_t g_ptr_EVENTMSG_UNLOCKEDTECHS = 0x006e48fc;
// data const char** -> slot 0x00af09e8; key EVENTSUM_RESEARCH_COMPLETE = "Research Complete" [verified]
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_COMPLETE = 0x006f09ec;
// data const char** -> slot 0x00af09f0; key EVENTMSG_RESEARCH_COMPLETE = "Tech %s has been acquired" [verified]
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_COMPLETE = 0x006f09f4;
// data const char** -> slot 0x00af09f8; key EVENTSUM_RESEARCH_UNDERBUDGET = "Research Breakthrough!" [verified]
constexpr uint32_t g_ptr_EVENTSUM_RESEARCH_UNDERBUDGET = 0x006f09fc;
// data const char** -> slot 0x00af0a00; key EVENTMSG_RESEARCH_UNDERBUDGET [verified]
constexpr uint32_t g_ptr_EVENTMSG_RESEARCH_UNDERBUDGET = 0x006f0a04;
// data const char** -> slot 0x00af0a88; key EVENTSUM_ADDICTION_TEMPERENCE (shipped misspelling) [verified]
constexpr uint32_t g_ptr_EVENTSUM_ADDICTION_TEMPERENCE = 0x006f0a8c;
// data const char** -> slot 0x00af0a90; key EVENTMSG_ADDICTION_TEMPERENCE [verified]
constexpr uint32_t g_ptr_EVENTMSG_ADDICTION_TEMPERENCE = 0x006f0a94;
// data 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 [verified]
constexpr uint32_t g_dbl_ResearchUnderbudgetThreshold = 0x005e20c8;
// data double 0.0 -- the research completion draw is NextFloat()*(1.0-this)+this, so it is a plain NextFloat() [verified]
constexpr uint32_t g_dbl_ResearchRollBias = 0x005e1e68;
// cdecl 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. */ [verified]
constexpr uint32_t Mars_Vec3_Normalize = 0x00022520;
// cdecl 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 */ [verified]
constexpr uint32_t Mars_Vec3_Length = 0x000224b0;
// cdecl float (const Vec3* v) /* float32(x*x+y*y+z*z), one narrowing on the sum */ [verified]
constexpr uint32_t Mars_Vec3_LengthSquared = 0x000224f0;
// data const float = 0x34000000 = 2^-23 = 1.1920928955078125e-07 /* Mars_Vec3_Normalize zeroes the direction when !(len > this) */ [verified]
constexpr uint32_t Mars_Vec3_NormaliseEpsilon = 0x005e1ef8;
// site 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. */ [verified]
constexpr uint32_t StrategyServer_MoveFleet_straight_leg = 0x003da0f2;
// site 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. */ [verified]
constexpr uint32_t StrategyServer_MoveFleet_position_update = 0x003da2ac;
// constant sizeof(Game::ObservedTech) = 0x2c (44 bytes). Confirmed independently by (a) the compiler's magic division by 44 (mov eax,0x2e8ba2e9; imul; sar edx,3) at 0x0087239f and 0x007b7339, (b) imul reg,reg,0x2c at 0x0087243a, 0x00872468, 0x007b735b, and (c) the iterator advance `add edi,0x2c` in RecordObservedTech's linear search at 0x007ba257. FULL MEMBER MAP, from ObservedTech_Write 0x00817cf0 / ObservedTech_Read 0x00817c40 (lane S 2026-09-08): +0x00 vptr 0x00a2439c; +0x04 uint16 otnF; +0x06 uint16 otnL; +0x08 bool odet (1 byte, +3 pad); +0x0c std::string otch (0x1c, so _Mysize at +0x1c, _Myres at +0x20, _Alval at +0x24); +0x28 int owith. 4 + 2 + 2 + 4 + 0x1c + 4 = 0x2c exactly, no padding slack and no unaccounted field. [verified]
constexpr uint32_t ObservedTech_sizeof = 0x0000002c;
// data Game::ObservedTech vftable. RTTI COL 0x00a81c78 -> type descriptor 0x00aeede4 = '.?AVObservedTech@Game@@'. Written to element+0x00 by ObservedTech_ctor 0x008562a0 -- so ObservedTech is polymorphic and its first word is the vptr, not a data field. [verified]
constexpr uint32_t ObservedTech_vftable = 0x0062439c;
// offset std::string (save tag `otch`, the tech name) at ObservedTech+0x0c, 0x1c bytes, spanning +0x0c..+0x27. MSVC layout relative to the string object: _Bx[16] @+0x00, _Mysize @+0x10, _Myres @+0x14, _Alval @+0x18 -- i.e. ObservedTech+0x1c is the length, +0x20 the capacity, +0x24 the empty-allocator word (never read or written by anything). Evidence: ObservedTech_ctor 0x008562a0 writes [elem+0x0c]=0, [elem+0x1c]=0, [elem+0x20]=0xf; RecordObservedTech's search reads [elem+0x1c] as the length and calls compare with this=elem+0x0c; the post-append assign uses lea ecx,[_Mylast-0x20]. CORRECTION (lane S 2026-09-08): an earlier revision called this string 0x18 bytes and listed +0x24 as an unaccounted data field. It is not one -- three independent whole-object enumerations skip +0x24 entirely: ObservedTech_ctor 0x008562a0, ObservedTech_copy_ctor 0x0079a184, and ObservedTech_Write 0x00817cf0 (which serialises +0x04,+0x06,+0x08,+0x0c,+0x28 and nothing else). sizeof(std::string)=0x1c holds binary-wide; see std_string_sizeof. [verified]
constexpr uint32_t ObservedTech_off_Name = 0x0000000c;
// thiscall void (this, ServerPlayer* observer, ?, std::string* techName) -- appends to observer->otch. Linear-searches observer->otch (ServerPlayer+0x274) with stride 0x2c comparing each element's name string; if not found, default-constructs an ObservedTech on the stack (0x008562a0) and push_backs it (0x007b7320 @0x007ba288), then writes otnF (+0x04) and otnL (+0x06), both 16-bit, from the same source word param_1+0xc -- i.e. first-observed turn == last-observed turn on the first sighting, which is what the tag names now confirm. DIRECT CALLEE of ServerPlayer::OnTechResearched 0x00891790; also called from 0x007be228, 0x007be4e1, 0x007be535. This is the append lane P could not find. DE-DUPLICATING: appends only when no existing element carries that tech name, so a reimplementation must not naively push_back on re-observation. [verified]
constexpr uint32_t RecordObservedTech = 0x003ba1a0;
// thiscall void (std::vector<ObservedTech>* this, ObservedTech* value) RET 4. Stride 0x2c. Calls vector_44B_grow 0x007b5820 when _Mylast==_Myend -- the realloc that moves all three vector words. Exactly one caller (RecordObservedTech), so this instantiation is not COMDAT-ambiguous. [verified]
constexpr uint32_t vector_ObservedTech_push_back = 0x003b7320;
// thiscall std::vector<ObservedTech>& operator=(const std::vector<ObservedTech>&) RET 4. Both callers pass ServerPlayer+0x274 (0x00878091 in the settings copy-out, 0x00892507 in the copy-in). [verified]
constexpr uint32_t vector_ObservedTech_assign = 0x00472380;
// thiscall Game::ObservedTech* (ObservedTech* this) -- default ctor. Writes exactly: vptr 0x00a2439c at +0x00; dword 0 at +0x04 (otnF+otnL zeroed together); BYTE 0 at +0x08 (`mov [esi+0x8],bl`, 0x008562f4 -- odet is a bool, not an int); the empty string at +0x0c (_Buf[0]=0, _Mysize=0, _Myres=0xf, then assign("") 0x00425550); dword 0 at +0x28 (owith). It never touches +0x24 -- that word is the string's _Alval. [verified]
constexpr uint32_t ObservedTech_ctor = 0x004562a0;
// thiscall void Game::ObservedTech::Write(Mars::Stream* s) RET 4. Vftable 0x00a2439c slot [2]. Serialises the whole object, in order and with nothing else: `otnF` = movzx word [this+0x04] via stream vft+0x24 (int); `otnL` = movzx word [this+0x06] via vft+0x24; `odet` = WriteBool 0x008b9c20 (&this[+0x08]); `otch` = WriteString 0x008b9d70 (&this[+0x0c]); `owith` = [this+0x28] via vft+0x24. THIS IS THE MEMBER MAP: there is no field at +0x24. Tag pointers 0x00a2b38c/0x00a2b384/0x00a2b36c/0x00a2b3e8/0x00a2b364. [verified]
constexpr uint32_t ObservedTech_Write = 0x00417cf0;
// thiscall void Game::ObservedTech::Read(Mars::Stream* s) RET 4. Vftable 0x00a2439c slot [1]. Mirror of ObservedTech_Write: `otnF`/`otnL` through stream vft+0x10 (int-by-ref) stored back as 16-bit (`mov word [ebx],ax`) at +0x04/+0x06, `odet` = ReadBool 0x008b9c00 (&this[+0x08]), `otch` = ReadString 0x008b9d90 (&this[+0x0c]), `owith` at +0x28 (reached as `add edi,0x28`). [verified]
constexpr uint32_t ObservedTech_Read = 0x00417c40;
// thiscall ObservedTech* (ObservedTech* this, int flags) RET 4. Vftable 0x00a2439c slot [0], scalar deleting dtor. Inlines ~basic_string on the name at +0x0c (`cmp [esi+0x20],0x10` -> operator delete [esi+0x0c], then _Tidy: [esi+0x20]=0xf, [esi+0x1c]=0, byte [esi+0x0c]=0), restores the base vptr 0x009e22bc, and frees `this` when flags&1. The string is the ONLY member needing destruction -- confirming there is no second string or owned pointer in the element. [verified]
constexpr uint32_t ObservedTech_dtor = 0x00393610;
// site site inside the vector<ObservedTech> uninitialised-copy helper FUN_0079a150(&_Alval, dest, src). The inlined copy constructor writes vptr 0x00a2439c, `mov word [dst+0x04],[src+0x04]`, `mov word [dst+0x06],[src+0x06]`, `mov byte [dst+0x08],[src+0x08]`, the string at +0x0c (via basic_string::assign 0x00425430), and `mov [dst+0x28],[src+0x28]`. Nothing is copied at +0x24 -- second independent proof that +0x24 belongs to the 0x1c string, not to a data member. [verified]
constexpr uint32_t ObservedTech_copy_ctor = 0x0039a184;
// offset uint16 otnF -- turn the tech was first observed. Widened to int on disk by stream vft+0x24. Written together with otnL from one source word at RecordObservedTech 0x007ba2b0. [verified]
constexpr uint32_t ObservedTech_off_TurnFirst = 0x00000004;
// offset uint16 otnL -- turn the tech was last observed. Widened to int on disk. Written at RecordObservedTech 0x007ba2c6 from the same source word as otnF. [verified]
constexpr uint32_t ObservedTech_off_TurnLast = 0x00000006;
// offset bool odet -- ONE BYTE (ctor `mov [esi+0x8],bl` 0x008562f4; copy-ctor `mov byte` 0x0079a1a0), serialised through WriteBool/ReadBool, so on disk it is 1 byte + 3 NUL joint padding. Note the save reader types `odet` as int; for a 4-character tag a bool item and an int item are the same 12 bytes and the value is identical, so that is benign, not a parse error. [verified]
constexpr uint32_t ObservedTech_off_Detected = 0x00000008;
// offset int owith -- last member of the element; serialised through stream vft+0x24 with tag 0x00a2b364. +0x28..+0x2b is the tail of the 0x2c-byte element, which is why sizeof is 0x2c with no padding slack. [verified]
constexpr uint32_t ObservedTech_off_With = 0x00000028;
// thiscall void Game::ObservedWeapon::Write(Mars::Stream* s) RET 4. Byte-for-byte the same shape as ObservedTech_Write with tag `owep` (0x00a2b3f0) in place of `otch`: otnF u16 @+0x04, otnL u16 @+0x06, odet bool @+0x08, owep std::string (0x1c) @+0x0c, owith int @+0x28. Reader is 0x00817b10. Independent second instance of the same 0x2c element shape. [verified]
constexpr uint32_t ObservedWeapon_Write = 0x00417bc0;
// constant sizeof(std::basic_string<char>) = 0x1c (28) binary-wide, ONE layout only: _Bx (16-byte SSO union, char[16] or char*) @+0x00, _Mysize @+0x10, _Myres @+0x14, _Alval (empty allocator) @+0x18. The allocator word is trailing, never read and never written -- the same allocator-last shape as this build's std::vector {_Myfirst,_Mylast,_Myend,_Alval} = 0x10. Evidence: (a) Stream::WriteString 0x008b9d76 `cmp [str+0x14],0x10` / `mov eax,[str]` and basic_string::assign 0x00425550 (_Mysize +0x10, _Myres +0x14) fix the field offsets; (b) the vector<std::string> scan loop FUN_00699bd0 advances `add esi,0x1c` (0x00699c29) while doing the SSO test at [esi+0x14]/[esi] -- element base == string base, so the stride IS the sizeof; (c) PostEvent 0x008862b0 takes two by-value strings at [ebp+0x08] and [ebp+0x24] and RET 0x4c = 2*0x1c + 5*4; (d) a whole-binary sweep of the Stream string helpers recovered 65 std::string members off a non-stack base across all serializers -- ZERO have any sibling member inside (N, N+0x1c), and 51 of the 52 that have a next member have it at exactly N+0x1c (the one exception, StrategyServer KeyPath @+0x134, is +0x20 on the Write side and +0x1c on the Read side, i.e. the writer skips a member). There is no 0x18 instantiation, no empty-base variant and no game-local string class. [verified]
constexpr uint32_t std_string_sizeof = 0x0000001c;
// thiscall vector<T>::_Reserve/grow for a 44-byte element type. Shared with FUN_0086dec0, so it may be a COMDAT-folded body -- do NOT assume it is ObservedTech-specific. [mapped]
constexpr uint32_t vector_44B_grow = 0x003b5820;
} // namespace sots::addr