sots-re/findings/subsystems/tech-effects.md

192 lines
19 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Hard-coded tech effects — Sword of the Stars (2006) `Sword of the Stars.exe`
Ghidra 12 headless on project `sots` (ImageBase 0x00400000). Scripts `TechFx.java`..`TechFx4.java` (copies in
`handoff/scripts/`), raw decompiles in `handoff/tech-decompiles/`. Renames/comments/labels listed in §7 were written
back and saved. Companion: `formula-gaps.md`, `addresses-effects.json`.
## 0. Corrections to `strategic-turn-internals.md` §2.6
* The **116-entry table at 0x00a19718 is NOT the effects table.** It is a plain name list (`g_CombatTechNames`) used by
`IsCombatTechName` (0x006965c0, one caller 0x006b3ed0): returns 1 if a string matches any entry. No index is derived.
* The real key space is the **196-entry `g_TechIdNames` table at 0x009ff9e4** (stride 8: `{const char* name, int}`).
`TechId = 10000 + index` (0xc5 = 197 = "none"). At `MasterTechTree` construction `ResolveTechIds` (0x00581c10)
`_stricmp`s every name against the loaded `.tech` list and stores `TechDef*[196]` at `MasterTechTree+0`. Three helpers
are the whole reader API:
| addr | name (written back) | signature |
|---|---|---|
| 0x0057d5d0 | `MasterTechTree::IsTech` | `bool (TechDef* def, int techId)` — `def == resolved[id-10000]` |
| 0x0057d610 | `MasterTechTree::GetTechDef` | `TechDef* (int techId)` |
| 0x0057d810 | `TechTree::HasResearched` | `bool (int techId)` — per-player node state == 4 |
| 0x00581cc0 | (unnamed) | `bool (const char* name, int techId)` — section tech-list variant |
* The species flag words `ServerPlayer+0x348[7]` are **not hard-coded tech names**: `ServerPlayer::RebuildSpeciesTechFlags`
(0x0082bf10) sets bit k of `flags[sp]` = `HasResearched(SpeciesDef(sp).xncTech[k])` where the per-species XNC tech ids
live in the hard-coded `SpeciesDef` table (§4). So the bits are "xenotech level reached against species sp".
* `strategy{TECHBEN_*}` remains dead (confirmed again: nothing reads the TechDef strategy vector except the one
misspelled `TECHBEN_STRATRANGE` compare).
Reimplementation rule: key on the **tech name → TechId enum** below, apply the callback table (§1) on research
completion (and re-derive the flag words / bitmasks on load), then use the runtime gates in §2/§3.
## 1. `ServerPlayer::OnTechResearched(def, bool silent)` — 0x00891790 (vft slot 4, called by `TechTree::SetResearched`)
Field names are the save-tag names from `struct-recovery.md` §2 (`ConMod[3]`@+0x10c, `SavMod[3]`@+0x118, `OutMod`@+0x124,
`PopMod`@+0x130, `TerraMod`@+0x134, `SuitTol`@+0xb4, `MaxOH`@+0xb8, `MinRate`@+0x140, `pddm`@+0x108, `CstR/E/T`@+0x150,
`PrGtTrf`@+0x148). All constants verified in the disassembly (doubles in `.rdata`). Effects are **additive per research
event and permanent** (no un-research). Confidence HIGH unless marked.
| id | tech | call site | effect |
|---|---|---|---|
| 10000 | CCC_AdvSens | 0x008919f9 | `hadvs = 1` → sensor range × `ADVSENS_SENSORS_MOD` (readers 0x0080b730 system sensors `SENSORMOD[sp] × mod × 4.0`, 0x0081e9d0, 0x008672c0). Also re-derived on load (0x00875fa0). |
| 10001 | IND_Waldo | 0x00891a7f | `ConMod[0..2] −= 0.10`; `OutMod += 0.15` |
| 10002 | IND_CyberInt | 0x00891a27 | `ConMod[0..2] −= 0.05`; `OutMod += 0.20` |
| 10003 | IND_ExpSys | 0x00891a7f | same block as Waldo: `ConMod[0..2] −= 0.10`; `OutMod += 0.15` |
| 10004 | IND_OrbFound | 0x00891ad7 | `SavMod[0..2] −= 0.05` |
| 10005 | IND_OrbDry | 0x00891b1d | `ConMod[1] −= 0.05`; `ConMod[2] −= 0.05` (index 0 untouched → ConMod is per hull class DE/CR/DN, MEDIUM) |
| 10006 | IND_GravCon | 0x00891b43 | `OutMod += 0.30` |
| 10007 | IND_HvyPlat | 0x00891b6b | `OutMod += 0.10` |
| 10008 | IND_AstMine | 0x00891b93 | `AMine = 1` (asteroid mining: `AdjustResources` mode 0, `MRes/ARes2` counted in output; §3.3 of turn notes) |
| 10009 | IND_MsMine | 0x00891bb0 | `MaxOH = max(MaxOH, 0.1)`; `MinRate += 1.0` |
| 10010 | BIO_GnMod | 0x00891c02 | `PopMod += 0.10` |
| 10011 | BIO_AtmoAd | 0x00891c2a | `SuitTol += 0.75`; `PopMod += 0.06`; `TerraMod += 0.35` |
| 10012 | BIO_EnvTail | 0x00891c76 | `PopMod += 0.20`; `TerraMod += 0.45` |
| 10013 | BIO_GrvAdpt | 0x00891c9e | `SuitTol += 1.50`; `PopMod += 0.10`; `TerraMod += 0.35` |
| 10014 | IND_ArcCon | 0x00891cee | `harcc = 1` (carrying cap +1e8 imperial / +2e8 civilian, 0x0080dd30); `PopMod += 0.15`; every owned system whose civilian total == civilian cap is re-evaluated (0x00756040/0x00753510/0x00751b60: morale/civ recalculation, LOW on exact purpose) |
| 10015 | IND_EleNans | 0x00891e42 | `TerraMod += 0.60` |
| 10016 | BIO_TerBac | 0x00891e6a | `TerraMod += 0.45` |
| 10017 | IND_AtProc | 0x00891e92 | `TerraMod += 0.50` |
| 10018 | DRV_TpGate | 0x00891eba | `PrGtTrf = max(PrGtTrf, g_PERGATETRAFFIC_DRV_TpGate)` (storage 0x00b23e2c via PTR 0x00aedfe0) — resolves the open "PERGATETRAFFIC readers" item |
| 10019 | DRV_GatAmp | 0x00891ed3 | `PrGtTrf = max(PrGtTrf, g_PERGATETRAFFIC_DRV_GatAmp)` (0x00b23e30 via 0x00aedfe4) |
| 10020 | DRV_FarCast | 0x00891f07 | `CstR = 10.0; CstE = 2.0; CstT = 1.0` (Morrigi far-casting; used by waypoint type 5 `ProbabilisticJump`) |
| 10021/2/3 | CCC_AI / CCC_AIAdmin / CCC_AIFac | 0x00891f5f | if `AIBn` (AI benefit on): `ApplyAITechBonus(def)` (0x0080e330): `v = aiTable(def).value(+8) × (+1)`; CCC_AI → `ResMod += v`, CCC_AIAdmin → `IncMod(+0x30c) += v`, CCC_AIFac → `OutMod += v`. `SetAIBenefit(false)` (0x008186b0, AI rebellion) subtracts them again for every researched AI tech. The 6-entry value table is at **0x00a17888** (12-byte `{techId, ?, float value}`), values not dumped this round (MEDIUM). |
| 10024 | CCC_AIVrus | 0x00891f8a | every owned system `+0x188 = 1`; if `AIRebellion` object exists → 0x00691030 |
| 10025 | CCC_AISlv | 0x00891fe4 | every owned system `+0x188 = 1`; `SetAIBenefit(true)`; if AIR → 0x00691020. Also: `HasResearched(CCC_AISlv)` **removes the AI-rebellion research risk** (0x00820380: odds = aiTable(def).+4 only while !AISlv && !NPC && no AIR) and gates 0x006d1e30/0x0080dad0 (AI rebellion possible?). |
| (any) | species "node-track" tech (`SpeciesDef+0x74`: CCC_NDTRKHUM for Human, CCC_NDTRKZUL for Zuul, none otherwise) | 0x0089202f | `NPTrk |= 1 << species` (0x0080e410). Readers 0x0080ddb0 `(NPTrk >> p.Species) & 1`, 0x0080dde0 `NPTrk != 0`, client 0x0080ba90/0x008102f0/0x0081bf50 → "can see that species' node-space traffic" (MEDIUM). |
| 10026 | CCC_FtlEcon | 0x00892071 | if `!RebAI`: `CnTrd = 1` (trade allowed; `RegisterTradeSystems` 0x007adc80 only registers routes for owners with CnTrd; other readers 0x00819f00, 0x00833220, 0x0083a690) |
| 10027 | CCC_ComRaid | 0x0089209b | `CnRad = 1` (commerce raiding permission; consumer not isolated — +0x100 offset collides with ServerSystem.owner, LOW) |
| 10028 | DRV_GrvSyn | 0x008920b8 | `hgs = 1` — only synced to clients (0x0077b620, SynchronizePlayer); no server-side reader found. FLOCKDRIVE_GRAVSYN_MOD is applied from the design side (0x006fe0e0/0x006ff370). |
| 10029 | CCC_DatCor | 0x008920d5 | `CnVItl = 1` — readers are client/UI (0x007f4670, 0x007f6d20, 0x007fc7f0, 0x007fed30): view intel on other empires (MEDIUM) |
| 10030 | IND_HrdStrct | 0x008920ef | `pddm *= 0.25`; `OutMod *= 0.90` (multiplicative!) |
| 10031 | DRN_AdvRob | 0x00892126 | `ConMod[0..2] −= 0.05` |
| 10038..10043 | BIO_PLGVAC / RTPLGVAC / BSTVAC / ASPLGVAC / CONNAN / UNIANTI | 0x00892156 (tail) | `mask = GetPlagueCureMask(def)` (0x00537240: bit 0..4, UNIANTI = 0x0f; also matches descendants via 0x0057d220) → `HasVac |= mask; HasImm |= mask;` 0x0078c7e0 clears plague type k on all owned systems and ships ("PLAGUE: Player %d(%s) cured of %s"). |
| — | every completion | tail | `(+0x190,+0x194) = ComputeTechBitmasks()` (0x004d7960; §3), `RebuildSpeciesTechFlags()`, 0x008182c0 → Zuul rip/rend/rad tunnelling params |
| 10032 (+10033) | IND_CruisCon, Zuul only | 0x008921e1 | if `Species == 5`: `SetResearched(GetTechDef(IND_BrdPod), 1,1,0)` — Zuul get Boarding Pods free with Cruiser Construction |
| 10034+10035 | CCC_SpyBm && IND_SlvgTech | 0x00892209 | `cdp = 1` (readers 0x00793d60, 0x007b8460, 0x00809fa0, 0x0086baa0, 0x00878060 — design-capture/observed-design feature, MEDIUM) |
| flag bit 5 (temperance) | XNC_TEMP<sp> | 0x00892241 | for each species with `flags[sp] & 0x20`: every owned system addicted to sp is cured (0x00745e40/0x00743800) → `EVENT_TEMPERANCE` (unless silent) |
| any | — | 0x00891804 | if `!silent`: event `EVENT_RESEARCH_COMPLETE` when `progress/cost ≥ 0.8` else `EVENT_RESEARCH_UNDERBUDGET`; if def == `ResTNm` → clear current research and `ResErrRoll` (running plague-cure roll 0x0088df20 once). |
Where the multipliers are consumed (from `strategic-turn-internals.md`, now with producers known): `OutMod` in
`ComputeOutputFromRates` total (×`p.OutMod`), `PopMod` in growth (`g *= PopMod`), `TerraMod` in terraform points,
`SuitTol` in `HazardMod` (cap) **and** `CalcSuitMod` (money cost, see formula-gaps Q3/Q4), `ConMod[i]` = per-class
construction cost multiplier (readers 0x00817f90 `ConstructionSpend`, 0x0079ac10, 0x00806ed0 …; MEDIUM),
`SavMod[i]` readers 0x00745d60/0x00745dd0/0x00814c80 (maintenance side, LOW), `pddm` (planetary-defence damage
multiplier? readers 0x0075a570/0x0075a6b0/0x0075bde0, LOW), `MaxOH` = max over-harvest slider, `MinRate` = mining
rate, `PrGtTrf` = per-gate traffic capacity vs `GTraf`.
## 2. Other strategic readers (runtime gates by TechId)
| tech | where read | effect | conf |
|---|---|---|---|
| CCC_AdvSens 10000 | 0x00746390, 0x0075bd70, 0x0075cb20 | a player in "partial contact" state (2-bit field, `+0x24`) counts as known/visible when AdvSens is researched | MED |
| CCC_SpyBm 10034, CCC_SPJAM 10112, CCC_FTLBRDB 10108 | 0x00788ff0 (from encounter detection 0x007d7f70) | spy-beam intel on a battle: FTLBRDB → also through cease-fire partners; SpyBm → yes unless the observed side has SPJAM | MED |
| CCC_TUNSENS 10113 | 0x00815ab0 | station type 2 (or flag 0x800) with TUNSENS → tunnel-sensor detection | MED |
| CCC_HYPCOM 10093 | 0x00700920 | without HYPCOM a fleet cannot be re-targeted while in flight to a moving target (waypoint kind 1 → forced stop) | MED |
| IND_TRKSTL 10094 | 0x0079b980 → 0x0079b770 (uses `TRKSTL_REGENERATION_MOD` 0x007870d0) | post-combat hull regeneration for non-NPC owner | HIGH |
| DRV_RIP/REND/RAD 10064/65/66 | 0x006e18e0 (via 0x008182c0 after research) | ServerPlayer+0x308 = {45,15,3} / {65,35,4} / {95,60,5} (Zuul node-bore parameters, highest wins) | MED |
| DRV_FISSN/FUSN/ANTIMAT (0x0057d350), hull 0x0056e4c0, warheads 0x0058fd20 (NUKES, NUKEWHD, GMAWHD, FUSWHD, AMWHD) | 0x0080e490 / 0x0080e4d0 / 0x0080e510 | "tech level" 0..2 / 0..4 for the AI/diplomacy evaluator 0x006d4780 | HIGH |
| WEP_MwMsl 10036, WEP_HvyPmsl 10037 | 0x00535c40 | missile-type availability bits (combat) | HIGH |
| BIO_PLG..NANVIR 10044-48 / vaccines 10038-43 | 0x00535400, 0x00535480, 0x00535ef0, 0x00535f80, 0x00699950 | plague-type ↔ tech maps for `ProcessPlague`, bio-weapon sections and AI research picking | HIGH |
| CCC_TRNSHUM..LIR 10114-117 | 0x0079f050 | scenario/encounter grants N random translation-1 techs (`SetResearched`) | MED |
| IND_STLTHARM 10050, SLD_INTANG 10071 | 0x0081fc30 (StarShip flags), 0x0080b820 | all sections STLTHARM → ship flag 0x100000 (stealth); INTANG clears flag 0x80 | MED |
| DRV_RECFISS 10072 / DRV_PLSMFOC 10051 | 0x0080b850, 0x0080ba30 (+3.0), 0x0081ede0 (+3.0 per section) | +3.0 to a design/ship stat (drive-family bonus; combat side) | LOW |
| IND_CyberInt 10002, DRN_AdvRob 10031, DRN_CMBT 10049 | 0x00586600 (tree build, `mode`) | special-cased during per-species tree construction (scenario "all techs" path) | LOW |
| CCC_ARMCOM/DATSYN/BTLCMP 10074-76 | 0x00695be0, 0x006964a0 | combat command-section level 0/1/2 | HIGH |
| WEP_*LAS 10077/78/82, WEP_*CAN 10079-81 | 0x006907d0, 0x006ad5e0 | combat beam/cannon variants | HIGH |
## 3. Tech bitmasks (non-serialised `ServerPlayer+0x190/+0x194`)
`ComputeTechBitmasks` (0x004d7960): `+0x190` = OR over `g_TechBitmaskTableA` (0x00adf378, 32 pairs) and `+0x194` over
`g_TechBitmaskTableB` (0x00adf478, 29 pairs). Table A bits 0..31: IND_REFCOAT, IND_IMPRFCT, IND_PLYALLOY, IND_MAGLAT,
IND_QRKRES, IND_ADMALY, IND_PREDGUN, SLD_DEF, SLD_ERGAB, SLD_MKONE, SLD_MKTWO, SLD_MKTHREE, SLD_MKFOUR, SLD_CLK,
SLD_IMPCLK, SLD_INTANG, WEP_VRFTECH, WEP_NUKEWHD, WEP_GMAWHD, WEP_FUSWHD, WEP_AMWHD, WEP_NEUTRND, CCC_INTSENS,
CCC_SNSJAM, CCC_QNTCHAF, CCC_CMBTALG, CCC_HOLOTAC, CCC_ADVCNC, CCC_AdvSens, DRV_MCROFUS, DRV_INCTHRST, DRV_SMLFUS.
Table B bits 0..28: DRV_NODE, NODFOC, NODPATH, STRWRP, IMPSTWRP, FLICKER, HYPER, DRV_HYPRFLD, WARP, SLD_MESSHLD,
SLD_GRVSHLD, CCC_AIFRCON, DRV_RIP, REND, RAD, BIO_CONNAN, SLD_DISR, SLD_MAGNI, DRV_QNTCAP, CCC_FCCOM, IND_HRDELEC,
IND_TRKSTL, DRV_VDCTR, VDCRV, VDMSTR, BIO_SMRTNAN, WEP_ACCAMP, WEP_MwMsl, WEP_HvyPmsl. These are the design/section
"option available" masks (consumers on the design side, not traced).
## 4. `SpeciesDef` table (0x00b10a00, 7 × 0x184, `SpeciesDef::InitTable` 0x005453a0) — hard-coded species constants
| off | Human | Hiver | Tarkas | Liir | _NPC | Zuul | Morrigi | used by |
|---|---|---|---|---|---|---|---|---|
| +0x04 drive | 3 | 0 | 1 | 2 | 0 | 3 | 4 | |
| +0x08 | 0.07 | 0.07 | 0.07 | 0.15 | 0.07 | 0.20 | 0.07 | (suit-related, unresolved) |
| +0x0c | 1 | 0.85 | 0.9 | 1.5 | 1 | 0.55 | 1 | |
| +0x10 | 1 | 1.15 | 1.1 | 0.9 | 1 | 1 | 1 | |
| +0x14 | 1 | 1.2 | 1.1 | 0.8 | 1 | 1.7 | 0.85 | |
| +0x18 income factor | 1 | 1 | 1 | 1 | 1 | 1.1 | 0.8 | `TradePointsToMoney` |
| +0x1c | 1 | 1 | 1 | 1 | 1 | 1 | 1.3 | |
| +0x20 | 1 | 1 | 1 | 1 | 1 | 1 | 2.0 | |
| +0x24 cost factor | 1 | 1 | 1 | 1 | 1 | 0.7 | 1 | `TradePointsToMoney` hazard cost |
| +0x28 | 0.9 | 0.75 | 1 | 1.2 | 1 | 0.2 | 0.5 | |
| +0x2c | 0.75 | 0.8 | 0.7 | 0.1 | 1 | 1 | 0.25 | |
| +0x30 | 0 | 0.5 | 0.25 | 0 | 0 | 0.75 | −0.5 | |
| +0x34 | 1 | 1 | 0.85 | 0.95 | 1 | 1 | 1 | |
| +0x38 | 1 | 1 | 1 | 1 | 1 | 0.5 | 1 | |
| +0x4c | 10 | 10 | 10 | 10 | 10 | 0 | 10 | BaseOutput cap (turn notes §3.3) |
| +0x50 | 10 | 10 | 10 | 10 | 10 | 40 | 10 | |
| +0x54 | 1 | 1 | 1 | 1 | 1 | 2 | 1 | |
| +0x58 | SENSORMOD_<sp> from species.txt (NPC 1.0) | | | | | | | sensor range |
| +0x5c | 1 | 1 | 1 | 1 | 1 | **0** | 1 | system bonus eligible (Zuul never get SYSTEMBONUS) |
| +0x74 node-track tech | CCC_NDTRKHUM | none | none | none | none | CCC_NDTRKZUL | none | `NPTrk` |
| +0x78.. XNC ids (order of the 9 fillers) | TRNS1 10114+sp, TRNS2 10120+, TRNS3 10126+, INC 10132+ (Zuul/NPC none), ADCT 10137+, TEMP 10142+, SUB 10147+, ACC 10153+, PROF 10158+ | | | | | | | flag bits 0..8 (§1 "bit0/1/2 slave death" = translation levels?, see note) |
| +0x144 flags | 1 | 0 | 0 | 0 | 0 | 2 | 0 | |
| +0x148 | 0 | 0 | 0 | 0 | 0 | plague-cure mask \| 0x20 | 0 | Zuul start immune/temperate |
Note on the flag-bit semantics used by the colony code (turn notes §3): bits are in filler order, i.e. bit0/1/2 =
translation 1/2/3, bit3 = incorporate, bit4 = addict, bit5 = temperance, bit6 = subjugate, bit7 = accommodate,
bit8 = proliferate. That reassigns the earlier guesses ("bit3 civilian growth", "bit7 hazard immunity", "bit8 may seed
civilians") to Incorporate / Accommodate / Proliferate respectively — consistent with the XNC tech names. The exact
slot order was inferred from the call order in `InitTable` (fillers write `*in_EAX` successive dwords); HIGH on order,
MEDIUM on the +0x78 base (the reader 0x0082bf10 starts at +0x78 and steps 4).
## 5. Techs with NO code effect (pure prerequisites / data-only)
Of the 196 TechIds, **no code reads** ids: 95 IND_SPNLMNT, 96 WEP_HCLAS, 97 WEP_PRTBM, 98 WEP_DSRPTR, 100-107 (NUKMINE,
FUSMINE, DFMSL, GSDRVR, MASDRVR, HVYDRVR, VRFTECH*, PDTECH), 110 CCC_INTSENS*, 171 IND_QRKRES*, 173-175 SLD_MKONE-THREE*,
177 WEP_NEUTRND*, 179-192 (CCC_ADVCNC*, DRV_MCROFUS*, INCTHRST*, SMLFUS*, HYPRFLD*, SLD_MESSHLD*, GRVSHLD*, DISR*,
MAGNI*, DRV_QNTCAP*, CCC_FCCOM*, IND_HRDELEC*, BIO_SMRTNAN*, WEP_ACCAMP*). `*` = present only in a bitmask table (§3).
Everything not in the enum at all (≈ half of the `.tech` files: most WEP_*, SLD_*, DRV_* section techs) has no code
effect beyond `requires`/section availability driven by the data files. Count: **36 ids with a strategic effect
(§1), ~45 with combat/design/AI gates (§2/§3), ~115 pure prerequisites.**
## 6. std::string layout (coordinator request)
`Mars::Stream::WriteString` (0x008b9d70): `CMP [str+0x14], 0x10; JC inline; MOV EAX,[str]` — i.e. `_Myres` at **+0x14**,
buffer pointer / SSO buffer `_Bx` at **+0**. `basic_string::assign` (0x00425550) uses `param_1[4]` = `_Mysize` at
**+0x10** and `param_1[5]` = `_Myres`. Vector-of-string strides are **0x1c** (`FUN_00699bd0` `pbVar5 += 0x1c`,
`MasterTechTree` ctor `local_34 += 7` dwords), so `sizeof == 28` with the allocator/proxy dword at **+0x18**.
**struct-recovery.md §0 is right (`_Bx@0, _Mysize@0x10, _Myres@0x14, _Alval@0x18`); turn-spine.md §1.1 is wrong.**
## 7. Write-back (saved)
Functions renamed + plate comments: `ServerPlayer::OnTechResearched` 0x00891790, `MasterTechTree::IsTech/GetTechDef/
ResolveTechIds` 0x0057d5d0/0x0057d610/0x00581c10, `TechTree::HasResearched` 0x0057d810, `IsCombatTechName` 0x006965c0,
`ServerPlayer::RebuildSpeciesTechFlags` 0x0082bf10, `ServerSystem::HazardMod` 0x00747ae0, `ServerPlayer::
UpdateBankruptcyLimits/SetBankruptcyState/BankruptcyLevel` 0x00818600/0x0080e260/0x0080db10, `ServerPlayer::
ApplyAITechBonus/SetAIBenefit` 0x0080e330/0x008186b0, `TechDef::GetPlagueCureMask` 0x00537240, `ServerPlayer::
SpeciesOfTranslationTech` 0x0080e410, `ComputeTechBitmasks` 0x004d7960, `ServerSystem::TradePointsToMoney/CalcSuitMod/
AccrueSystemBonus/SystemBonusPopTarget` 0x007505b0/0x007484d0/0x0074d4f0/0x0074b5a0, `NodeLine::Step/
BuildStutterSegments` 0x00705510/0x00705280, `DistPointToSegment` 0x008e8eb0, `SpeciesDef::InitTable/Get`
0x005453a0/0x00545cc0, `ServerPlayer::GetIncMod/GetSpeciesCostFactor` 0x0080dd10/0x0080dd20. Labels: `g_TechIdNames`
0x009ff9e4, `g_CombatTechNames` 0x00a19718, `g_SpeciesDefTable` 0x00b10a00, `g_TechBitmaskTableA/B` 0x00adf378/0x00adf478.
## 8. Method / confidence
Pass 1: vtable 0x00a327a4 slot 4 → callback; decompile + annotated disassembly (decompiler drops the pushed TechId,
so every branch was mapped from `PUSH imm; CALL IsTech`). Pass 2: program-wide scan of scalar operands 10000..10196 →
every reader of every id (`tech-decompiles/techfx2/techids.txt`). Pass 3: species table, bitmask tables, money-tail
helpers. Pass 4: producers + write-back. HIGH = read from disassembly with constants; MEDIUM = decompile-level with
an inferred field meaning; LOW = consumer identified but purpose inferred from context only.
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.