From daea98feba2ce0c998a5b2d6b62aef92c8d19edc Mon Sep 17 00:00:00 2001 From: alex Date: Mon, 7 Sep 2026 16:45:25 -0400 Subject: [PATCH] verify: ship-design rules + validator, 127/127 stock designs pass; species index order decoded --- campaign/board.md | 2 +- campaign/open-questions.md | 1 + verify/design-rules/SHIP_DESIGN_RULES.md | 292 +++++++++++++++++++++++ 3 files changed, 294 insertions(+), 1 deletion(-) create mode 100644 verify/design-rules/SHIP_DESIGN_RULES.md diff --git a/campaign/board.md b/campaign/board.md index 3b756a3..7511c4d 100644 --- a/campaign/board.md +++ b/campaign/board.md @@ -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::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::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 | | 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) | diff --git a/campaign/open-questions.md b/campaign/open-questions.md index dbf07f8..98be33a 100644 --- a/campaign/open-questions.md +++ b/campaign/open-questions.md @@ -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: 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]]) +- **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]]) diff --git a/verify/design-rules/SHIP_DESIGN_RULES.md b/verify/design-rules/SHIP_DESIGN_RULES.md new file mode 100644 index 0000000..468ef17 --- /dev/null +++ b/verify/design-rules/SHIP_DESIGN_RULES.md @@ -0,0 +1,292 @@ +# SOTS1 ship-design rules (data-derived) + +What constrains a valid `ShipDesignDef`, derived from the shipped data +(`Species//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 ""` 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_` entry is satisfied by any researched tech with `group ` (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 `: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`, `XNC_Inc`, +`XNC_Trns2/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?