verify: ship-design rules + validator, 127/127 stock designs pass; species index order decoded

This commit is contained in:
alex 2026-09-07 16:45:25 -04:00
parent f644b83581
commit daea98feba
3 changed files with 294 additions and 1 deletions

View file

@ -12,7 +12,7 @@ Status flow: `backlog → in-progress → mapped → verified` (or `blocked`).
| `Game::ClientPlayer` / `AIPlayer` | object | verified | high | 100% | 2026-09-07 | ServerPlayer confirmed strict vs real saves (turns 1-3) | | `Game::ClientPlayer` / `AIPlayer` | object | verified | high | 100% | 2026-09-07 | ServerPlayer confirmed strict vs real saves (turns 1-3) |
| `Game::StarSystem` | object | verified | high | 100% | 2026-09-07 | ServerSystem confirmed strict vs real saves; NVO.indi unconditional; haltv bool; cp1252 names | | `Game::StarSystem` | object | verified | high | 100% | 2026-09-07 | ServerSystem confirmed strict vs real saves; NVO.indi unconditional; haltv bool; cp1252 names |
| `Game::Planet` / `DOPlanet` | object | mapped | high | 100% | 2026-09-07 | Planet : Actor is a RENDER actor, not streamed; all colony state is in ServerSystem (+PlayerView in NVs map) | | `Game::Planet` / `DOPlanet` | object | mapped | high | 100% | 2026-09-07 | Planet : Actor is a RENDER actor, not streamed; all colony state is in ServerSystem (+PlayerView in NVs map) |
| `Game::ShipDesignDef` / `DesignList` | object | in-progress | low | 30% | 2026-09-07 | R5 breadth: design/assembly rules + validator derived from section/weapon/turret data, tested vs stock designs in real saves (verify/design-rules/); vft@0x009fef64 leaf | | `Game::ShipDesignDef` / `DesignList` | object | verified | high | 90% | 2026-09-07 | design/assembly rules from data, validator + 32 tests: 127/127 stock designs pass structure/fit/options; DOpts reproduced 197/197; tech gating 121/127 (hidden default riders bypass gating = code rule). verify/design-rules/SHIP_DESIGN_RULES.md; 11 Ghidra questions |
| `Game::FleetLayout` | object | verified | high | 100% | 2026-09-07 | StarFleet/StarShip confirmed strict vs real saves; PrisonerHold PrNSp gated on PrMax>0 | | `Game::FleetLayout` | object | verified | high | 100% | 2026-09-07 | StarFleet/StarShip confirmed strict vs real saves; PrisonerHold PrNSp gated on PrMax>0 |
| Mars entity chain | object | mapped | high | 100% | 2026-09-07 | EntityBase@0x00a36fd0 (10) -> PhysicalEntity@0x00a3710c (12) -> TargetableEntity -> Actor@0x009e2114 (36); mixins RigidBody/Body/NetworkObject/HandleObject/IScriptable | | Mars entity chain | object | mapped | high | 100% | 2026-09-07 | EntityBase@0x00a36fd0 (10) -> PhysicalEntity@0x00a3710c (12) -> TargetableEntity -> Actor@0x009e2114 (36); mixins RigidBody/Body/NetworkObject/HandleObject/IScriptable |
| `Game::GameCombatSim` + `CombatCommand*` | subsystem | backlog | — | 0% | 2026-09-07 | tactical combat (~40 command types) | | `Game::GameCombatSim` + `CombatCommand*` | subsystem | backlog | — | 0% | 2026-09-07 | tactical combat (~40 command types) |

View file

@ -27,3 +27,4 @@ Each links to the finding that raised it. Promoted to backlog or closed by **re-
- **RESOLVED: strict-parse gaps** — 5/8 were reader bugs (ASCII plausibility test applied to string values; empty string == int 0), 3 real: `PrNSp` only when `PrMax>0`; `indi` unconditional in `NVO` nodes (system-level gated by `hindi`); `zdsi/zdst` pairs under `zdsc`. `Key` is an empty string; `tch` int. Summary/CreateParams tag lists confirmed. Reader patch in flight. (from [[schema-gaps-resolved]]) - **RESOLVED: strict-parse gaps** — 5/8 were reader bugs (ASCII plausibility test applied to string values; empty string == int 0), 3 real: `PrNSp` only when `PrMax>0`; `indi` unconditional in `NVO` nodes (system-level gated by `hindi`); `zdsi/zdst` pairs under `zdsc`. `Key` is an empty string; `tch` int. Summary/CreateParams tag lists confirmed. Reader patch in flight. (from [[schema-gaps-resolved]])
- **RESOLVED: save framing** — padding is joint; `"."` = NULL tag (VectorHelper count+elements, Vector3 bodies); empty strings are 4 zero bytes; `CDT` is a tagged frame + opaque `CD` frames. All three real saves parse `--strict` clean. Note: `SvSctOb` IS present in all saves (patch doc §7 was wrong). (from [[SAVE_FORMAT]]) - **RESOLVED: save framing** — padding is joint; `"."` = NULL tag (VectorHelper count+elements, Vector3 bodies); empty strings are 4 zero bytes; `CDT` is a tagged frame + opaque `CD` frames. All three real saves parse `--strict` clean. Note: `SvSctOb` IS present in all saves (patch doc §7 was wrong). (from [[SAVE_FORMAT]])
- **(parked) Combat loader job API** — does tactical-battle loading go through the job helpers `0x8fc160`/`0x8fa5b0` with the `TryEnterCriticalSection→Sleep(1000)` completion poll (`0x0071ea60`)? If yes, that is a plausible many-core stall (lost try-lock = +1 s per poll). End Turn also has a deliberate `Sleep(computed)` ≈0.85 s at `0x00838da9`. (from [[battle-load-profile]]) - **(parked) Combat loader job API** — does tactical-battle loading go through the job helpers `0x8fc160`/`0x8fa5b0` with the `TryEnterCriticalSection→Sleep(1000)` completion poll (`0x0071ea60`)? If yes, that is a plausible many-core stall (lost try-lock = +1 s per poll). End Turn also has a deliberate `Sleep(computed)` ≈0.85 s at `0x00838da9`. (from [[battle-load-profile]])
- **Ship-design code rules (Ghidra, from SHIP_DESIGN_RULES.md §8)** — hidden default rider designs are built WITHOUT tech gating (where?); is section class-equality enforced or merely unoffered; confirm the turret class-accept table (`standard ← {standard,missile,grapple}`, `strafe ← {standard}`; grapple weakest); hull-class tech injection (`IND_CruisCon`/`IND_DreadCon`) + station exemption; option defaults/auto-upgrade and what `option_cost` multiplies; weapon cost per bank vs per mount; mass/speed formulas; `faiDes`/`dWep` meaning. (from [[SHIP_DESIGN_RULES]])

View file

@ -0,0 +1,292 @@
# SOTS1 ship-design rules (data-derived)
What constrains a valid `ShipDesignDef`, derived from the shipped data
(`Species/<Race>/sections/*.shipsection` 875, `Weapons/*.weapon` 207,
`Weapons/_turrets.txt` 42 rows, `Weapons/_defaultweapons.txt` 31 rows,
`TechTree/MasterTechList.tech` 293 nodes) and checked against every design in
three real saves (127 designs, 197 section instances, 24 player records) plus
the scenario fleet templates (87 section triples). `design_rules.py` is the
executable form; `test_design_rules.py --report` prints the ground-truth run.
Rule ids below match `Violation.rule`. "Evidence" is what in the data or saves
forces the rule; "Only code can decide" marks the residual ambiguity.
```
verify/design-rules/
design_rules.py Catalog / validate / derive_stats / applied_techs / design_from_save
stock_designs.py .sav -> stock_designs.json (designs + researched techs per player)
stock_designs.json 127 designs from turn1/2/3-state.sav in Design form
test_design_rules.py 32 tests (unit rules, catalog self-consistency, saves, scenarios)
data/_turrets.txt, data/_defaultweapons.txt, data/*_FleetTemplates.csv (copied game data)
```
## 1. The design record (what a design *is*)
From the save (`Player.designs[]` / `Player.legacyDesigns[]`, fully decoded in
`stock_designs.py`):
```
Des { faiDes bool, dHide bool, dWep int, dName string,
sections[5] : DSec { DSec { int species_idx, int section_id },
DGbnk2 { int n_banks, n x ( DW2 { bool bID, wid int | wfn string, int did } ) },
DOpts { int n, n x tech-name string } } }
```
* **Five section slots, three used**: slot 0 = command, 1 = mission, 2 = engine;
slots 3-4 are absent in every design (reserved). An empty slot is
`(species 0, section 0)`; ids in `_shipsections.txt` start at 1.
* `species_idx` indexes the save's species table `Human, Hiver, Tarkas, Liir,
_NPC, Zuul, Morrigi` (`SPECIES_INDEX`); `section_id` is the race's
`_shipsections.txt` id. Every design in the saves uses one species for all
its sections, but the record allows otherwise and NPC players do use other
races' catalogs (the `Independent Colony` builds a *Tarkas* `DEDefencePlatform`).
* **One weapon per bank** (`DW2` per `bank{}` block, in file order; all mounts of
a bank share it). `bID true` + `wid` = player weapon id from `_weapons.txt`;
`bID false` + `wfn` = weapon file path (NPC fixed banks). `did` = design id
of the rider design carried by a rider weapon (0 everywhere in these saves).
* `DOpts` = the techs the section instance is built with, exactly
`applied_techs()`: hull-class tech, then `requires`, then the chosen option
of each option group (section 5). Verified equal on all 197 instances.
* `dHide` marks engine-generated hidden designs (`Default Assault Shuttle`,
`Default Herald Defender`); `dWep 1` accompanies them (default-weapon fill);
`faiDes` is set on the one design the player made in the designer
(`Honor Lance`, turn 2) -- meaning still open.
## 2. Structure rules (A*)
| id | rule | evidence |
|---|---|---|
| A2 | the mission slot is always filled | every design in saves and templates has a mission section; standalone hulls sit in the mission slot with command/engine empty |
| A3 | mission section **with** `socket_fore`/`socket_aft` = hull ship: command **and** engine required. Mission section **without** sockets = standalone (platforms, stations' monitor, freighters, police, spy, gravboat, tricksters, node bore, wraith, devourer, construction, propaganda, terrorize): command and engine must be empty | 57-61 hull vs 16-21 standalone mission sections per race; `Light Defense Platform` = `[0,0] DEDefencePlatform [0,0]`; every 3-section design's mission section has both sockets |
| A4 | section_type must match the slot (`command`/`mission`/`engine`, case-insensitive). Typeless sections (`_AssaultShuttle`, `_Drone`, `_Biomissile`, `_BoardingPod`, `_NodeMissile`, ...) only appear in the mission slot of hidden rider designs | 164+30 command, 502 mission, 108+30 engine; hidden `Default Assault Shuttle` = `[0,0] _AssaultShuttle [0,0]` |
| A5 | every section comes from one species catalog (the design's race) | per-race `sections/` dirs; `DSec.species_idx`; the same stem differs per race (Human `DEFission` requires `DRV_Node`, Tarkas `DRV_Hyper`, Zuul nothing) |
| A6 | all sections share `section_class` (destroyer/cruiser/dreadnought) | all 127 designs and 87 templates are class-homogeneous; DE/CR/DN name prefixes. **Only code can decide** whether the engine checks this or merely never offers a mix (socket node names are shared across classes, so geometry alone would not stop DE+CR) |
| A7 | `exclude` list: a section may not be combined with a listed stem | Hiver `DERamScoop exclude {DEFission DEPulseFission DELongRangeFission}`, `CRRamscoop` likewise (the only two) -- ramscoop is a *command* section that forbids fission engines |
| A8 | `explicit_command_section`/`explicit_engine_section` on a mission section pin its partners; sections flagged `explicit_section true` are usable only that way | the six `DNStation{Command,Habitat,Repair,Science,Sensor,Trade}` triples (`_Fore`/`_Aft`, `design_class station`) |
| A9 | `nodesign 1` = not offered by the designer; **not** an engine rule | `DNGuard` (nodesign, "special flagship used by hungry children scenario") is a scenario fleet mission section; NPC designs use nodesign sections throughout -> reported as `warn` |
**Sockets are geometry, not a compatibility key.** `socket_aft` on a command
section and `socket_fore` on a mission section are mesh node names used to
snap the models; the names do *not* have to match. Stock `Colonizer` =
`DECommand (socket_aft CommandNode)` + `DEColonizer (socket_fore MissionNode01,
socket_aft MissionNode)` + `DEFission (socket_fore EngineNode)`. Node names
vary in case (`COmmandNode`, `Commandnode`, `fore`/`Fore`) and per class/race
(`Fore/Aft`, `Command/Engine`, `MissionNode01/02`, `EngineNode01`). The only
assembly information sockets carry is *presence* (rule A3).
## 3. Banks and weapon fit (B*)
Hardpoints: `bank { turretclass C turretsize S mount { node ... azimuth/inclination } ... }`.
3,721 banks / 7,375 mounts; player-race bank population:
| bank size/class | banks | sections carrying it |
|---|---|---|
| small standard | 1533 | almost everything |
| medium standard | 975 | |
| large standard | 333 | |
| large beam / freebeam | 94 / 14 | *BattleBridge, *Blazer, *Barrage, *Assault, DNFlagship, DNGuard, DNWar |
| large torpedo | 67 | DETorpedo, CRAssault, CRBarrage, DNAssault, DNBarrage, *TorpedoPlatform |
| large dronerider | 59 | *Drone, *DronePlatform, CRMining/Refinery/RepairandSalvage, CRStrikeForceCNC, DNArmadaCNC, DNFlagship, DNSupport |
| large missile / medium missile | 27 / 10 | CRBarrage, DNBarrage, *DefencePlatform, CRDronePlatform, CRTorpedoPlatform, DNBioWar, DNImpactor |
| large col | 25 | CRCOL, DNCOL |
| large assaultshuttlerider | 23 | DEAssaultShuttle, CRAssaultShuttle, CRAssault, DNAssault |
| large projector | 23 | CRProjector, DNProjector |
| large planetmissile | 20 | DNDefencePlatform, DNTorpedoPlatform, _AsteroidMonitor |
| large boardingpod | 18 | CRBoarding, CRMining, CRRefinery, CRRepairandSalvage, CRStrikeForceCNC, CRTerrorize, DNArmadaCNC, DNDevourer, DNFlagship |
| large missilerider | 15 | *Biowar |
| small/medium strafe | 7 / 14 | DEStrafe, CRStrafe, _Drone, _DroneHeavy |
| large railcannon 13, mine 12, spinal 6, siege 6, spyship 6, nodecannon 4, prisonershuttlerider 5, nodemissilerider 1, tarkahunter 1, wraith 1 | | |
| id | rule | evidence |
|---|---|---|
| B1 | the design carries exactly one weapon entry per `bank{}` block, in file order | `DGbnk2.n == len(bank{})` for all 197 instances (Human `DEArmor` 3, `DEDefencePlatform` 5, Tarkas `DEFission` 2, `_AsteroidMonitor` 26 ...) |
| B2 | no bank is left empty | every bank in every save design has a `DW2`; `_defaultweapons.txt` exists to fill banks "in the absence of tech and player information" |
| B3 | a bank with `weapon "<file>"` in data (404 NPC banks) is bound to that weapon | NPC designs store the same file in `wfn` |
| B4 | **fit** = class compatibility **and** a `_turrets.txt` row for `(bank size, weapon size, class)` | see below |
| B5 | weapon must exist; NPC-scope weapons (`Species/_NPC/weapons`, no manifest id) only on NPC hulls | player weapons are referenced by id, NPC ones by path |
| B6 | `exclusive_species zuul` (bal_grapple, bal_disruptorwhip) | data field |
| B7 | rider weapons (`compatible_section`) need that rider section in the race catalog: brd_shuttle -> `_AssaultShuttle`, brd_shuttleadv -> `_AssaultShuttle_Advanced`, brd_prisonershuttle -> `_ZuulAssaultShuttle` (Zuul only), brd_drone/col_flock -> `_Drone`/`_DroneHeavy`, brd_tarkahunter -> `_HunterShip_*` (Tarkas only); `did` names the rider design | data field; DNCarrier's `tarkahunter` bank exists only in the Tarkas catalog |
| B8 | `hidden 1/true` weapons (mis_planet*, `_mis_*_warhead`, Silicoid NPC guns) are engine-only | data field |
### B4 in detail -- `_turrets.txt`
`size weapon-size class health track-speed az% inc% "model"` (42 rows):
```
standard/strafe/grapple: small<-{tiny,small} medium<-{small,medium} large<-{small,medium,large}
missile: medium<-medium large<-{medium,large}
planetmissile: large<-medium
beam freebeam spinal torpedo mine missilerider assaultshuttlerider prisonershuttlerider
nodemissilerider siege projector boardingpod wraith nodecannon dronerider col spyship
railcannon tarkahunter: large<-large only
```
Weapon side: every weapon has `turretsize` (tiny 4 / small 16 / medium 24 /
large 82 player weapons) and `turretclass` (23 distinct). Both tables match
only when compared case-insensitively (`Large`, `Missile`, `COL`).
Class compatibility (`BANK_CLASS_ACCEPTS`) -- the part the turret table alone
does not settle:
* **standard bank accepts missile-class weapons.** Stock `Armor` puts `mis`
(medium / *missile*) in `DEArmor`'s third bank, which is `medium standard`
(Human and Tarkas); `DEDefencePlatform` too. The missile row
`(medium, medium, missile) turret_m1missile.x` supplies the turret model, i.e.
the row lookup is keyed by the *weapon's* class first.
* **strafe bank accepts standard-class weapons.** No weapon is `turretclass
strafe`; the strafe rows mirror the standard rows model-for-model;
`_defaultweapons.txt` fills `strafe small/medium/large` with `las_red`
(standard); the NPC `_HeraldDefender` strafe bank carries a standard-class
beamer. Row lookup falls back to the *bank's* class.
* **grapple weapons go in standard banks** (assumption). `bal_grapple` /
`bal_disruptorwhip` are `turretclass grapple`, no section has a grapple bank,
`_turrets.txt` and `_defaultweapons.txt` have grapple rows for medium/large.
Only code can decide; the alternative (grapple banks exist in unshipped data)
leaves the two Zuul weapons unmountable.
* Everything else: exact class match. Every other class has its own banks
and weapons, and the stock/scenario/NPC data never crosses them.
Consequences the catalog self-tests confirm: every bank of every player-race
section accepts at least one player weapon, and every player weapon fits some
bank. PD (`tiny`) weapons fit **only** small standard/strafe banks; a large
standard bank takes 54 player weapons (small, medium, large standard + missiles
+ grapples); missile banks take missiles only; `planetmissile` banks take the
four planet missiles (`mis_defplat` is the only non-hidden one).
Repeated `turretsize`/`turretclass` inside one `bank{}` (19 banks: Zuul/Human/
Morrigi DN*, DEFusion, CRDeepScan, CRBlazer...) -- the validator takes the
last value; the engine's choice (first/last) is code.
## 4. Tech gating (C*)
| id | rule | evidence |
|---|---|---|
| C1 | every `requires` of every section must be researched; a `GRP_<name>` entry is satisfied by any researched tech with `group <name>` (case-insensitive: `GRP_Torps`, `GRP_HvyBeam`, `GRP_Shields` appear) | 970 refs; Human `DEFission requires DRV_Fissn DRV_Node`, both `St==4` on turn 1; groups PRJCTR/PD/HVYBEAM/SPINAL/TORPS/MINES/BIOMISSILE/JAMMING/SHIELDS |
| C2 | every weapon's `requires` (AND of 1-2 techs) must be researched | 189 refs; `bal_gauss requires WEP_GsDrvr` (researched), `mis` has none |
| C3 | chosen options must be researched | the Human/Tarkas turn-1 designs choose no options: none of IND_PlyAlloy... is researched; NPC players (all 293 techs) choose one per group |
| C4 | **hull-class tech**: cruiser sections need `IND_CruisCon`, dreadnought sections `IND_DreadCon`; `design_class station` sections are exempt | `DOpts` carries `IND_CruisCon`/`IND_DreadCon` first for every CR/DN section (`_SwarmHive`, `_Wreckage_*`, `_VNMMoonbase`, `_CRRipper*`, `_SilicoidMother`) and omits it for `_AsteroidMonitor`/`_AsteroidMonitor2` (station). Both techs grant `TECHBEN_HULLSIZE`. Destroyers need nothing |
| C5 | race availability: a tech whose every incoming `allows` edge writes `<Race>:0` can never be researched by that race, so sections/weapons requiring it are unbuildable for it | `DRV_Node` Human-only, `DRV_Hyper` Tarkas-only, `DRV_StrWrp` Liir-only; the per-race catalogs already encode this (Tarkas `DEFission requires DRV_Hyper`) |
Not a gate: the tech tree's `ship { section X }` lists (149 refs, 71 techs) are
the "new section unlocked" UI notice, **not** the buildability rule -- for 139
of the ~150 stems they differ from the sections' own `requires` (e.g.
`IND_CruisCon` lists no sections; `DRV_Fusn` lists `DEfusion` while the section
requires `DRV_Fusn` **and** a race drive tech).
Starting techs (save `TechTree`, `St == 4` on turn 1): all 12 `*_ROOT`,
`DRV_Fissn`, the race drive (`DRV_Node`/`DRV_Hyper`), `WEP_GsDrvr`,
`WEP_RedLas`, `WEP_Nukes`, `CCC_FTLCom`, `CCC_Trns<Race>`, `XNC_Inc<Race>`,
`XNC_Trns<Race>2/3` -- 22 techs. Every stock starting design validates
against exactly this set. (`St` values seen: 0 locked, 1 unavailable-to-race,
2 researchable, 3 in progress, 4 researched.)
Scenario fleet templates are **granted**, not designed: `HiverInvasion` gives
DN designs while `HiverInvasion_Techs.csv` lacks `IND_DreadCon`. They pass
the structural rules (A*, B1/B4) for every race that carries the sections.
## 5. Options (D*)
`option { option A option B ... }` = a mutually exclusive group (1,226 blocks);
a bare `option T` (229) is a one-member group; `optiondef { option SLD_MkOne
... SLD_GrvShld }` (12 shield sections) is the shield-level group. Signatures:
| count | groups |
|---|---|
| 323 | armor {PlyAlloy MagLat QrkRes AdmAly StlthArm} + coating {RefCoat ImpRfCt} |
| 170 | none |
| 98 | {PlyAlloy} + {RefCoat} |
| 47 | armor without StlthArm + coating |
| 32 | {DRV_PlsmFoc} + armor + coating (fusion engines) |
| 28 | armor + coating + {DRV_RecFiss} (fission engines) |
| 12 | {PlyAlloy} {RefCoat} {BIO_SpndAni} (colonizers) ; 12 | {PlyAlloy} {RefCoat} {SLD_Intang} (cloaking) |
| id | rule | evidence |
|---|---|---|
| D1 | a chosen option must be a member of one of the section's groups; at most one per group | no tech appears in two groups of one section; NPC `DOpts` hold exactly one per group |
| D2 | `applied_techs = [hull-class tech] + requires + chosen options (group order)` is what the save stores | 197/197 instances match |
NPC auto-designs pick the **first** member of each group (`IND_PlyAlloy`,
`IND_RefCoat`; `_CRRipperCommand` has groups `{IND_MagLat}` `{IND_RefCoat}`
and picks those). What the player designer picks by default, and whether a
better-known option is auto-upgraded, is code. Option techs carry
`option_cost` (RefCoat 1.2, ImpRfCt 1.5, PlyAlloy 1.2, MagLat 1.4, QrkRes 1.6,
AdmAly 1.8, StlthArm 1.5, RecFiss 1.2, PlsmFoc 1.4, SpndAni 1.4, MkTwo 1.4,
MkThree 1.8, MkFour 2.2, ErgAb/MesShld/GrvShld 2.5, Intang 2.0, HrdElec 1.5)
-- read as a section-cost multiplier (assumption, section 6).
## 6. Derived stats (`derive_stats`)
| stat | derivation | status |
|---|---|---|
| mass | sum of section `mass` | data; whether weapons add mass is code (weapons have no mass field) |
| cost | sum of section `cost` x product of chosen options' `option_cost` + sum of weapon `cost` | option multiplier and per-bank vs per-mount weapon cost are **assumptions**; both weapon sums are reported |
| health | per section (`ShipHealth` in the save is 3 floats) | sum reported only as a total |
| crew | sum of `crew` (destroyers have none) | data |
| cpoints | sum (`cpoints`, build-point/time figure) | meaning is code |
| FTL | engine `ftlspeed`, `nodespeed`, `engine_techera` (fission/fusion/antimatter) | per-race semantics: Human node drive `ftlspeed 0.2 nodespeed 4`, Tarkas hyperdrive `ftlspeed 2.0 nodespeed 0`, Zuul `0.2/3` -- the strategic-speed formula is code, tuned by `TECHBEN_STRATSPD/STRATRNGE` |
| tactical speed/turn | engine section `netforcelimits {force_*, torque_*, speed, rotspeed}` reported; every section has one | integrator is code |
| command | command section `command_cost` (0/1), mission `command_quota` (20-76 on CnC/station sections), `maintenance_cost` | data |
| capacities | `refueling_capacity repair_capacity construction_capacity mining_* prisoner_capacity colonizer_pop/infra/terra scanrange tacticalsensorrange range` and boolean roles (`refinery freighter police tradingpost monitor spy science propaganda ewar gateship ramscoop node_bore gravboat_bonus protectorate huge defence_platform`) | data, copied through |
| turrets | one per mount; `_turrets.txt` row gives the turret model, health, track speed, azimuth/inclination constraint scale | data |
Duplicate scalar keys in shipped sections (`mass [1800, 1000]` on Liir
`DEAntimatterImpSW` and Tarkas `DEShapedHyperfieldAntimatter`; `crew` on four
Zuul sections) -- last value taken; engine behaviour unknown.
## 7. Ground-truth results
`python3 test_design_rules.py --report`:
* 127/127 designs from `turn1/2/3-state.sav` pass A*/B*/D* (structure, bank
count, weapon fit, options); 197/197 section instances reproduce `DOpts`.
* Tech gating against each player's researched techs: 121/127 pass. The 6
failures are the same design three times over two players: the hidden
`Default Assault Shuttle` of the two **Tarkas** players (`_AssaultShuttle`
requires `DRN_AdvFrm`, unresearched on turn 1; the Human version has no
`requires`). **Code rule not visible in data**: default rider designs are
created for every race at game start without tech gating. The validator
reports gating on `hidden` designs as `warn`.
* 87 scenario template triples x every race carrying them: all structurally
valid (they are class-homogeneous, command/mission/engine typed, sockets
present); `DERamScoop`/`CRRamScoop` appear as command sections with
`DEFusion`/`CRFusion` engines, consistent with their `exclude` lists.
* Stock starting designs per race (Human and Tarkas observed): `Armor`
(DECommand + DEArmor + DEFission, gauss everywhere, `mis` in the medium
bank), `Tanker` (DETanker), `Colonizer` (DEColonizer), `Extended Range`
(DEExtendedRange), `Light Defense Platform` (DEDefencePlatform alone,
4x gauss + `mis`), hidden `Default Assault Shuttle`. The `Independent
Colony` NPC has a Tarkas `Defence Platform` with `las_red` x4 + `mis`.
## 8. Open questions for the Ghidra side
1. **Class-equality (A6)** -- enforced in `ShipDesignDef` validation, or only
by the designer never offering a mix? Look for a compare of
`section_class` across the three slots.
2. **Turret-class compatibility (B4)** -- confirm the accept table
`standard <- {standard, missile, grapple}`, `strafe <- {standard}` and
the row-lookup order (weapon class first, bank class second). The grapple
case is the weakest. Also whether `tiny` really is small-only.
3. **Hull-class tech (C4)** -- confirm `IND_CruisCon`/`IND_DreadCon` are
injected from `section_class`, the `design_class station` exemption, and
whether the check happens at design time or build time.
4. **Hidden default designs** -- where the engine builds `Default Assault
Shuttle`/`Default Herald Defender` and why `requires` is skipped
(probably: designs are only validated when the player saves one; hidden
designs are constructed directly).
5. **Option semantics** -- default pick per group, auto-upgrade when a better
option is researched, and what `option_cost` multiplies (section cost?
whole design? also build time?).
6. **Cost/mass/speed formulas** -- weapon cost per bank or per mount; whether
weapons/turrets add mass; how `netforcelimits` + mass become tactical
speed; how `ftlspeed`/`nodespeed` + `TECHBEN_STRATSPD` become strategic
speed for each drive family.
7. **Missiles and `WEP_Nukes`** -- `mis.weapon` has no `requires`, yet every
race starts with `WEP_Nukes`; is missile availability gated in code?
8. **`faiDes`, `dWep`** -- meaning of the two design flags (`faiDes` true on
the player-made `Honor Lance`; `dWep 1` on hidden defaults).
9. **Empty banks (B2)** -- can a design be saved with an unfilled bank, or does
the designer always fill from `_defaultweapons.txt`?
10. **Duplicate-key resolution** -- first or last value for repeated
`mass`/`crew`/`turretsize` keys (affects two engine sections' mass).
11. **Mixed-species designs** -- the record allows a different species per
slot; does anything forbid it for players?