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

19 KiB
Raw Blame History

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) _stricmps 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
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
— 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 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_ 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.