verify: ship-design rules + validator, 127/127 stock designs pass; species index order decoded
This commit is contained in:
parent
f644b83581
commit
daea98feba
3 changed files with 294 additions and 1 deletions
|
|
@ -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) |
|
||||||
|
|
|
||||||
|
|
@ -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]])
|
||||||
|
|
|
||||||
292
verify/design-rules/SHIP_DESIGN_RULES.md
Normal file
292
verify/design-rules/SHIP_DESIGN_RULES.md
Normal 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?
|
||||||
Loading…
Add table
Reference in a new issue