8.4 KiB
game/effects — code-defined tech effects
Module: src/game/effects/. Two data tables and a small apply layer over a plain
PlayerEconomyState; no game state, no I/O. Depends on game/sim for Species and
TuningTable.
Build/test: tests/game_effects/build_and_run.sh (plain g++, -Wall -Wextra -Werror), or
-DSOTS_GAME_EFFECTS_TESTS=ON once src/game/effects is added to the root CMake after
src/game/sim.
Confidence legend as in game-sim.md.
The key space (tech_id.h)
The game does not key effects on the .tech files. It carries a fixed list of 196 tech
names; when the master tree is built, every name is matched case-insensitively against
the loaded techs and its position becomes the tech's id, 10000 + index (10197 is the
"none" sentinel). All hard-coded effects, runtime gates and design-option bitmasks read
those ids. A tech absent from the list has no code effect beyond what its data file says
(prerequisites, section/weapon availability).
TechId reproduces that list as an enum with the same numeric values, generated from one
X-macro so the enum and the name table cannot drift:
| entry kind | count | example |
|---|---|---|
| position and data-file name known | 89 | IND_Waldo = 10001 |
| position known, name inferred or role-named | ~55 | BIO_RetroPlague (from its vaccine), WEP_NUKMINE (prefix inferred), XNC_Temperance_Hiver |
| position known, nothing else | ~52 | Unresolved_052 |
TechIdFromName resolves only the 89 confirmed names; everything else comes back as
TechId::None and is treated as "no code effect". TechIdName is nullptr for the
unconfirmed slots so a loader can tell them apart.
Xenotech block
Ids 10114–10163 are the per-species xenotechs, nine families in flag-bit order, each a
block of one tech per target species (XenoTechId(level, species)):
| bit | family | block base | targets |
|---|---|---|---|
| 0 | Translation 1 | 10114 | Human, Hiver, Tarkas, Liir, Zuul, Morrigi |
| 1 | Translation 2 | 10120 | same six |
| 2 | Translation 3 | 10126 | same six |
| 3 | Incorporate | 10132 | five: no Zuul |
| 4 | Addict | 10137 | five |
| 5 | Temperance | 10142 | five |
| 6 | Subjugate | 10147 | six |
| 7 | Accommodate | 10153 | five |
| 8 | Proliferate | 10158 | six |
The NPC race is never a target. Confidence: high on the family order and the block
bases; medium on the compact species order inside a block (enum order minus NPC);
low on which species the 5-entry blocks other than Incorporate omit — Zuul is assumed
for all four. Only CCC_TRNSHUM (10114) and CCC_TRNSLIR (10117) have confirmed data-file
names; the rest of the block is named by role.
Node-track techs
Seeing a species' node-space traffic is granted by a tech keyed by name in the species
table, not by id: CCC_NDTRKHUM for Human traffic, CCC_NDTRKZUL for Zuul traffic, none
for the others. NodeTrackTechName(species) exposes that; ApplyTechEffectByName handles
it. Confidence: high on the names, medium on the reader semantics.
The effects table (tech_effects.h)
EffectsOf(id) returns the list of typed effects ({kind, index, value}) applied when the
tech completes. Effects are additive per research event and permanent. 44 ids carry an
entry; every other id returns an empty list.
| tech | effects |
|---|---|
| CCC_AdvSens | flag AdvancedSensors |
| IND_Waldo, IND_ExpSys | ConMod[0..2] −0.10; OutMod +0.15 |
| IND_CyberInt | ConMod[0..2] −0.05; OutMod +0.20 |
| IND_OrbFound | SavMod[0..2] −0.05 |
| IND_OrbDry | ConMod[1] −0.05; ConMod[2] −0.05 (index 0 untouched — ConMod is per hull class, medium) |
| IND_GravCon | OutMod +0.30 |
| IND_HvyPlat | OutMod +0.10 |
| IND_AstMine | flag AsteroidMining |
| IND_MsMine | MaxOH = max(MaxOH, 0.1); MinRate +1.0 |
| BIO_GnMod | PopMod +0.10 |
| BIO_AtmoAd | SuitTol +0.75; PopMod +0.06; TerraMod +0.35 |
| BIO_EnvTail | PopMod +0.20; TerraMod +0.45 |
| BIO_GrvAdpt | SuitTol +1.50; PopMod +0.10; TerraMod +0.35 |
| IND_ArcCon | flag Arcology; PopMod +0.15; re-evaluate systems whose civilians sit at the cap (outcome) |
| IND_EleNans | TerraMod +0.60 |
| BIO_TerBac | TerraMod +0.45 |
| IND_AtProc | TerraMod +0.50 |
| DRV_TpGate / DRV_GatAmp | PrGtTrf = max(PrGtTrf, PERGATETRAFFIC_DRV_TpGate / _GatAmp) from the tuning table |
| DRV_FarCast | CstR 10, CstE 2, CstT 1 |
| CCC_AI / CCC_AIAdmin / CCC_AIFac | ResMod / IncMod / OutMod += the AI-bonus value for that slot, only while the AI benefit is on |
| CCC_AIVrus | every owned system gets its AI flag (outcome) |
| CCC_AISlv | same, plus the AI benefit is switched back on; AiRebellionPossible becomes false |
| CCC_FtlEcon | flag TradeAllowed, unless the player is the rebel AI |
| CCC_ComRaid | flag CommerceRaiding |
| DRV_GrvSyn | flag GravSynth (client sync only; the drive modifier is design-side) |
| CCC_DatCor | flag ViewIntel |
| IND_HrdStrct | pddm ×0.25; OutMod ×0.90 — multiplicative, so order against the additive OutMod techs matters |
| DRN_AdvRob | ConMod[0..2] −0.05 |
| IND_CruisCon | Zuul only: IND_BrdPod is granted (outcome; the caller researches it) |
| CCC_SpyBm, IND_SlvgTech | flag CaptureDesigns once both are researched |
| BIO_PLGVAC / RTPLGVAC / BSTVAC / ASPLGVAC / CONNAN | HasVac, HasImm |
| BIO_UNIANTI | same with mask 0x0f |
| DRV_RIP / REND / RAD | Zuul node-bore parameters {45,15,3} / {65,35,4} / {95,60,5}, highest wins |
Confidence: high on every constant above (each was read with its literal); medium
on the AI-benefit re-application in SetAiBenefit and on the node-bore "highest wins"
rule; the three AI-bonus values themselves are not recovered — ApplyContext::aiBonus
carries them and defaults to 0.
Every completion also rebuilds speciesFlags[] (bit k of species sp = the level-k
xenotech for sp is researched) and reports, in the outcome, every species whose
temperance bit is held so the caller can cure addiction to it on owned systems.
Where the modifiers are consumed
OutMod → TotalSystemOutput (game/sim); PopMod → PopulationGrowthDelta; TerraMod →
TerraformDelta; SuitTol → HazardModifier and SuitabilityCostMod; ConMod[i] →
per-hull-class construction cost (medium); SavMod[i] → maintenance side (low); pddm →
planetary-defence damage (low); MaxOH / MinRate → mining sliders; PrGtTrf → gate
traffic capacity; the flags → the gates listed in the RE catalog. hazardMod is 1 (skipped)
when speciesFlags[sp] has the accommodate bit or the player is the rebel AI.
Design-option masks
Two 32-bit words of "named tech researched" bits feed the design side; kDesignOptionNamesA
(32 names) and kDesignOptionNamesB (29 names) are the tables and
ComputeDesignOptionMasks(hasResearchedByName) builds the words from a by-name predicate,
because not every name's id is recovered. Consumers are not modelled. Confidence: high on
the tables.
Apply layer
PlayerEconomyState s; // seeded by the caller (species, SuitTol start, ...)
ApplyContext ctx{&tuning, aiBonus};
TechApplyOutcome o = ApplyTechEffect(s, TechId::IND_HrdStrct, ctx);
ApplyTechEffect marks the id researched, applies its effects, rebuilds the species
flags, and returns what the caller must do with real game state: grantedTech,
plagueCuredMask, flagSystemsAI, reevaluateCivilianCaps, temperanceSpeciesMask,
nodeBoreParamsChanged. Applying an invalid or already-researched id is a no-op
(applied == false). ApplyTechEffectByName resolves a data-file name first and also
handles the node-track names. SetAiBenefit(s, on, ctx) adds or withdraws every
researched AI tech's bonus (AI rebellion / AI slave tech). RebuildSpeciesTechFlags is
also the load path.
Not modelled / open
- Values of the three AI-benefit bonuses (a 6-entry table in the executable; not dumped).
- Data-file names for ~107 of the 196 slots (no strategic effect on any of them; the
gaps matter only for
TechIdFromNameon those names). - The events raised on completion (research complete / under budget), the plague-cure
roll, the Zuul starting immunity/temperance flags, and the home-system bonus
initialisation that reads the
*_HOMEtuning keys. - Runtime gates that read ids at use sites (advanced-sensor contact rules, spy-beam intel, tunnel sensors, hyper-com retargeting, hull regeneration, missile/beam/cannon variants, plague-type maps): documented in the RE catalog, to be modelled where those systems are built.