b2 static findings: tech effects corrections, full g_TechIdNames dump; contract 176->211

This commit is contained in:
alex 2026-09-08 00:01:04 -04:00
parent ead46e2608
commit db9b561964
3 changed files with 439 additions and 6 deletions

View file

@ -190,3 +190,86 @@ an inferred field meaning; LOW = consumer identified but purpose inferred from c
Not done: values of the 6-entry AI tech table (0x00a17888), the `+0x224/+0x228/+0x22c` player multipliers' producer
(0x0077b620 copies them from a 0x50-byte per-player options record `+0x48/+0x4c/+0x50` — likely the game-setup
difficulty/handicap block, MEDIUM), and the design-side consumers of the §3 masks.
---
## 9. B2 corrections (2026-09-08) — read from the instruction stream, ReVa + `objdump -b binary -m i386`
Nine things this note had wrong, missing, or at lower confidence than the binary supports.
Where the two disagree, the binary wins.
1. **Every modifier is a float32 and every constant is a widened float literal.** The shape is
`fld dword [field]; fadd/fmul qword [k]; fstp dword [field]`. The doubles at the `.rdata`
addresses listed in §1 are *float32 values widened to double*, not the decimals: 0x009e5060 =
0x3FA99999A0000000 = `(double)0.05f`, 0x009e2318 = `(double)0.1f`, 0x009ed188 = `(double)0.15f`,
0x009e20d8 = `(double)0.2f`, 0x009e5df0 = `(double)0.3f`, 0x009f15b0 = `(double)0.35f`,
0x00a33358 = `(double)0.45f`, 0x009e5068 = `(double)0.6f`, 0x00a33360 = `(double)0.06f`,
0x009e23a0 = `(double)0.9f`. Exact: 0x009e5d70 = 0.25, 0x009e20a0 = 0.5, 0x009e8d28 = 0.75,
0x009e90b8 = 1.5, 0x009e1ef0 = 1.0. `MaxOH`'s 0x009e22f0 is a **float32** 0.1f loaded with
`fld dword` and passed to `std::max<float>`. `DRV_FarCast` writes float32 10.0 (0x00a2bed0),
2.0 (0x009e5840) and `FLD1`. A reimplementation must round through float32 at *every* step.
2. **`g_AITechValueTable` (0x00a17888) is dumped.** Six 12-byte rows `{int techId, float odds,
float value}`, count at 0x00aea2ec = 6:
`{10021,0.1,0.5} {10022,0.1,0.5} {10023,0.1,0.5} {10083,0.2,0.0} {10024,0,0} {10025,0,0}`.
So the CCC_AI / AIAdmin / AIFac bonus is **0.5** into ResMod / IncMod(+0x30c) / OutMod, and the
AI-rebellion odds column is 0.1 for those three and 0.2 for **CCC_AIFRCON (10083)** — which is
in the table for its odds only; `ApplyAITechBonus` has no branch for it. `ApplyAITechBonus`
multiplies the value by `AIBn ? 1.0f : (float)[0x009e1fac] = -1.0f` and narrows the product to
float32 before adding.
3. **`PrGtTrf` (+0x148) is an int.** `mov ecx,[PTR]; mov edx,[esi+0x148]; cmp edx,[ecx]; jl` — a
signed integer max against an integer config word, not a float compare. §1's "PrGtTrf =
max(...)" is right, the type was not stated.
4. **`ServerPlayer+0x308` is a pointer, not three inline words.** `ServerPlayer::
UpdateNodeBoreParams` (0x008182c0, called unconditionally in the tail) builds a local 3-word
struct via `SelectNodeBoreParams` (0x006e18e0); on success it `operator new(0xc)`s the block if
+0x308 is null and copies the three words in; on failure it `operator delete`s the block and
nulls +0x308. `SelectNodeBoreParams(out, player)` tests **RAD (0x2752) first, then REND
(0x2751), then RIP (0x2750)**, first hit wins — "highest wins" confirmed, HIGH not MEDIUM —
returning {95,60,5} / {65,35,4} / {45,15,3}, and false with out = {INT_MAX, INT_MAX, 0} when
none. **There is no species check**: these are not Zuul-gated in code.
5. **`RebuildSpeciesTechFlags` (0x0082bf10) has an undocumented second pass.** After filling
`flags[7]` at +0x348 (and writing the count word 7 at +0x364 — the 0x20 bytes are assigned as a
unit), it loops species 0..6 and, for each `sp != 4` (_NPC) whose `flags[sp] & 1` (translation
level 1) is set, ORs `1 << sp` into **`ServerPlayer+0x1a4`**. Sticky: only ever ORed, never
cleared, so it survives the first pass clearing a flag. Not previously recorded.
6. **Three tail steps are not effects of their tech.** `cdp` is a tail check —
`if (!cdp) { if HasResearched(10034) && HasResearched(10035) then cdp = 1 }` — evaluated on
every completion, not only on those two techs'. So are the +0x190/+0x194 masks and the
node-bore selection. §1's table reads as if they belong to the completing tech.
7. **The callback has no already-researched guard**, because `SetResearched` sets node state 4
before invoking it.
8. **`*(int*)TechDef` is NOT the TechId.** `MasterTechTree::IsTech(def, id)` (0x0057d5d0) does
`if (id == 0xc5) fail; idx = id - 10000; if ((unsigned)idx > 0xc3) fail; return def &&
master[0][idx] && def == master[0][idx]`. `TechTree::HasResearched(id)` (0x0057d810) does the
same map through `this+4` (the MasterTechTree) to reach a `TechDef`, and *then* uses that def's
first word as the index into `this+0x10` (the node vector), testing `node->state == 4`. So the
def's first word is a node index in a larger key space, the TechId key space is exactly
10000..10195, and 197 is the "none" sentinel special-cased before the subtraction. `TechTree+4`
= `MasterTechTree*` is worth adding to the object map.
9. **`g_TechIdNames` (0x009ff9e4) is dumped in full.** 196 × `{const char* name, int}`; the
second word is `10001 + index` and is not the id (the id is `10000 + index`, per `IsTech`).
The names, in position order, are now in `sots-engine`'s `src/game/effects/tech_id.h`. Anchors
all agree: 0 CCC_AdvSens (0x2710), 30 IND_HrdStrct (0x272e), 38..43 the six vaccines
(0x2736..0x273b), 64..66 DRV_RIP/REND/RAD (0x2750..0x2752). Consequences for §4's filler-order
note: the block species order is **Human, Hiver, Tarkas, Liir, [Zuul,] Morrigi**; the families
that omit the Zuul are Incorporate (132-136), Addict (137-141), Temperance (142-146),
Accommodate (153-157) **and Proliferate (158-162)** — proliferate is five entries, not six.
Index 130, the Zuul slot of the level-3 translation block, is `XNC_DOMZUUL`. **Ids 163 and 164
are `CCC_NDTRKHUM` and `CCC_NDTRKZUL`**, the node-track techs of §1 — previously unlocated.
§5's "no code reads them" list can now be stated by name rather than by index.
10. **`g_TechBitmaskTableA/B` are `{int techId, uint bit}`.** Joined with §3's bit-ordered name
lists every id matches the name table above, so §3's two lists are confirmed and are keyed by
id, not by name: A = 10092, 10170, 10091, 10090, 10171, 10169, 10084, 10111, 10172,
10173-10176, 10088, 10073, 10071, 10106, 10166, 10167, 10099, 10168, 10177, 10110, 10109,
10178, 10085, 10086, 10179, 10000, 10180, 10181, 10182; B = 10056, 10057, 10058, 10061,
10062, 10063, 10059, 10183, 10060, 10184, 10185, 10083, 10064, 10065, 10066, 10042, 10186,
10187, 10188, 10189, 10190, 10094, 10067, 10068, 10069, 10191, 10192, 10036, 10037.
Also confirmed unchanged: the chain is a strict if/else-if over `push 0x2710`..`push 0x272f`
(10000..10031) with no gaps and at most one branch taken; the tail then tests 0x2730/0x2731
(CruisCon → GetTechDef(BrdPod) → SetResearched(def,1,1,0), gated on `Species == 5`) and
0x2732/0x2733 via HasResearched. `TechDef::GetPlagueCureMask` loops i = 0..5 over ids
10038..10042 with a NULL at i == 5, returning `1 << i` on the first hit, then falls through to
10043 returning 0x0f — and the per-id test is `0x0057d220(def, target)`, i.e. **def is the target
or a descendant of it**, not an exact match. Every magnitude in §1 is confirmed.

View file

@ -1214,9 +1214,9 @@
"name": "g_AITechValueTable",
"addr": "0x00a17888",
"convention": "data",
"prototype": "struct { int techId; int a; float value; }[6] /* values not dumped */",
"status": "unverified",
"source": "handoff/tech-effects.md#1"
"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",
@ -1409,6 +1409,286 @@
"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"
}
]
}
}

View file

@ -1,5 +1,5 @@
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
// Source: sots-re ghidra/addresses.json @ 1e7428d, generated 2026-09-07 by tools/gen_addresses.py
// Source: sots-re ghidra/addresses.json @ ead46e2, 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>
@ -309,7 +309,7 @@ constexpr uint32_t g_SpeciesDefTable = 0x00710a00;
constexpr uint32_t g_TechBitmaskTableA = 0x006df378;
// data struct { int techId; uint bit; }[29] [verified]
constexpr uint32_t g_TechBitmaskTableB = 0x006df478;
// data struct { int techId; int a; float value; }[6] /* values not dumped */ [unverified]
// 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;
@ -359,5 +359,75 @@ constexpr uint32_t ServerPlayer_off_ResearchTarget = 0x00000294;
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;
} // namespace sots::addr