sots-engine/docs/game-effects.md

176 lines
9.8 KiB
Markdown
Raw Permalink 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.

# 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. Since B2 it is the **whole** table,
read out of the executable: all 196 names, so `TechIdName` never returns `nullptr` for a
valid id and `TechIdFromName` resolves every tech the code can key on. Enum identifiers are
the data-file names except in the xenotech block, where the role-based names are kept
(`XNC_Temperance_Hiver` = `"XNC_TEMPHVR"`) because `XenoTechId` is built on them.
### 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 (the Zuul slot's data name is `XNC_DOMZUUL`) |
| 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 | **five** |
Ids 10163 / 10164 — the two slots after the proliferate block — are the node-track techs
`CCC_NDTRKHUM` / `CCC_NDTRKZUL`. The NPC race is never a target. Confidence: **high**
throughout since B2 (the family order, the block bases, the species order inside a block
and which families omit the Zuul are all read off the name table). Before B2 the
proliferate block was modelled as six entries, which made
`XenoTechId(Proliferate, Morrigi)` return the Human node-track tech.
### Node-track techs
Seeing a species' node-space traffic is granted by a tech keyed by *name* in the species
table rather than by the effect chain: `CCC_NDTRKHUM` (10163) for Human traffic,
`CCC_NDTRKZUL` (10164) for Zuul traffic, none for the others. `NodeTrackTechName(species)`
exposes the name and both now resolve through `TechIdFromName`. Confidence: high on the
names and ids, 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.
**Everything below is float32.** The modifiers are 4-byte floats in the player object and
each step is `field = (float)((double)field OP k)`, where `k` is the *widened float32*
literal the executable carries -- 0.05 is `(double)0.05f`, not 0.05. `PlayerEconomyState`
therefore holds floats and the table's constants are written with an `f` suffix; see
`docs/B2.md`. `PrGtTrf` is the exception: it is an `int`, raised with an integer max.
| 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 | node-bore parameters {45,15,3} / {65,35,4} / {95,60,5}, highest wins (re-derived in the tail, and the block is absent when none is researched) |
Confidence: **high** on every constant above (each was read with its literal) and, since
B2, **high** on the node-bore rule (the selector tests RAD, then REND, then RIP, and the
first hit wins) and on the AI-benefit re-application. The three AI-bonus values are now
recovered as well: **0.5 each**, and `AiRebellionOdds` carries the same table's odds column
(0.1 for the three AI techs, 0.2 for `CCC_AIFRCON`).
Three of the table's entries are not applied by the completing tech's own branch -- the
game does them in the tail of *every* completion, so `ApplyTechCompletion` does too:
* the two design-option masks (`ComputeDesignOptionMasks`, now available keyed by id);
* the node-bore parameters, re-derived from the whole researched set;
* the capture-designs pair test, which fires on whichever completion first sees both
`CCC_SpyBm` and `IND_SlvgTech` researched -- not only on those two techs' own.
Every completion also rebuilds `speciesFlags[]` (bit k of species sp = the level-k
xenotech for sp is researched), ORs a sticky `translationKnownMask` bit for every non-NPC
species whose level-1 translation 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, runs the tail, 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`). `ApplyTechCompletion` is the same thing **without** the
already-researched guard: that is what the game's callback is, because by the time it runs
the node is already marked researched. A differential hook must use it. `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
- Nothing is left open in the key space: all 196 names are in the table since B2.
- The plague-cure test in the game matches a vaccine tech **or any descendant of it**;
`PlagueCureMask` is exact-id only, because the descendant relation needs the tech tree.
- 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.