# 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 |= bit 0 / 1 / 2 / 3 / 4; cure that plague type on owned systems and ships (outcome) | | 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 `TechIdFromName` on 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 `*_HOME` tuning 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.