27 KiB
SOTS1 data-format parsers — report
Round-two deliverable: stdlib-only Python 3 readers for every textual data
format shipped in sots.gob / sots_local_en.gob, proven against all 1,665
extracted files, with cross-link checks and normalized JSON artifacts.
Location: spicy:/bulk-storage/re-lab/handoff/parsers/ (this file is its sibling).
Input: spicy:/bulk-storage/re-lab/gob-extract/. Python: 3.x, no third-party deps.
python3 parsers/verify.py <gob-extract-dir> <out-dir> # re-runs everything, prints the appendix below
Headline
- 1,595 / 1,595 data files parse (100%); the 70 skipped files are HLSL
shaders (
.fx/.fxh) and prose (Desc<Race>.txt,ChatTrans.txt) — not data. - 12 shipsections are syntactically broken in the shipped archive (11 never
close their outer
{, one has an extra}). They load in-game, so the engine's parser is lenient in exactly two ways: EOF closes all open blocks, and a stray top-level}is ignored.mars_datamirrors that by default and records a warning;strict=Truerejects them. - Cross-links are clean where it matters: 0 dangling weapon→tech, section→tech, tech→section, tech→weapon-file, tech→tech, option→tech, bank→weapon-file, allows→tech, AI/scenario CSV→catalog references.
- Real inconsistencies found (data bugs / engine-behaviour evidence): keys and
identifiers are matched case-insensitively everywhere (25 refs only
resolve that way), 10
_shipsections.txtids point at files that do not exist, 3 NPC weapon name tokens are missing fromStrings.csv, 4Strings.csvkeys are duplicated with a trailing space, and the threeaitech*.csvAI tables are empty templates.
The readers
mars_data.py — the Mars brace-block format
parse(text, typed=True, keep_case=False, strict=False, warnings=None) /
parse_file(path). Tokenizer-based (not line-based), so it handles every
layout the files use. Grammar as observed:
body := (NAME '{' body '}' | NAME value | "bare quoted item")*
value := "quoted" | bareword comment := // to end of line
Returns plain dicts. Repeated keys become lists (get_list(d, k) normalizes);
bare quoted strings inside a block go under "_items"; the file's top level is
itself a body. Barewords that look like C numbers (.5, -.8, 7e+8) or
true/false are typed; quoted strings never are (numplayers "8" stays a str).
Used for: .weapon (207), .shipsection (875), .tech (1), .combat (3),
.def (2), .script (4), and the 24 block-form .txt (scenarios, tutorial,
credits, systemnames, skydefs, ctechvars, shipai).
Quirks handled (all present in shipped files):
| quirk | where | handling |
|---|---|---|
keys case-insensitive: Requires/requires, badge/Badge, Planet_Impact_Effect |
tech, defs, weapons | keys lower-cased by default |
identifier values case-insensitive: WEP_HCLAS vs tech WEP_HCLas, Ind_AstMon, SECTIONNAME_DEHammerHead vs file DEHammerhead, manifest DEWar.SHIPSECTION |
18 weapon + 7 section requires, string keys, manifests |
all cross-links compare .lower() |
block opens on the same line as a pair: turretsize small mount { |
_NPC/_Wreckage_E.shipsection |
tokenizer, not line-based |
block name and brace on one line: weapon { |
MasterTechList.tech (67×) | same |
| outer block never closed (EOF at depth 1) | 11 shipsections (Hiver 6, Liir 4, Morrigi 1) | lenient close + warning |
one } too many |
Human/CrPropaganda.shipsection |
ignored + warning |
// inside quoted strings; backslash Windows paths; "" empty values |
globals.txt, GUI scripts | no escape processing, comment scan is quote-aware |
| bare quoted list items with no key | Data/Strategy/systemnames.txt |
_items list |
scalar and block under the same key: option DRV_PlsmFoc beside option { option A ... } |
153 shipsections (229 occurrences) | both merge into one list; consumers accept str-or-dict members |
repeated turretsize/turretclass inside one bank{} |
19 banks (Zuul DN*, …) | kept as list; verify takes last |
| mixed CRLF/LF, cp1252 bytes | everywhere | decode cp1252 |
No /* */ comments, no =, no escapes, no BOMs were found.
flat_kv.py — KEY value tuning tables and positional rows
parse_kv(text, typed=True, on_dup='last') → {KEY: value}; a value of
several unquoted tokens becomes a list; duplicates(text) lists repeated keys
(none in shipped files). color("r g b") → tuple. parse_rows(text) → list of
token rows for the positional tables (_turrets.txt, _defaultweapons.txt,
damfx*.txt, playercolors.txt, BadgeTable.txt, AvatarTable.txt,
WeaponIconPlacements.txt). Comment stripping is quote-aware
(ENDGAME_FILL_COLOR "0 0 0" // 48 29 2" is a real line).
manifest.py — id manifests and CSVs
parse_manifest(text) → Manifest(entries=[(id, filename)], deleted=[ids], problems=[…]); validates id uniqueness, honours // DELETED - n, and
by_name() folds case. parse_csv(text) drops #/// comment rows and
blank rows, strips cells, honours RFC-4180 quoting (Strings.csv has one
multi-line cell and quoted commas; stock_diplomacy_messages.csv quotes every
cell including its "# species" header). parse_csv_with_header() recovers a
# <a>,<b> schema row when present.
effect_txt.py — Effects/*.effect
Not brace-block, contrary to the round-one write-up. A TXT magic line,
then KEY value lines and KEY + indented BEGIN … END groups. Order is
semantic (PARTICLEDATATYPE n precedes the CREATION/VARIATION/OVERLIFE
curves that belong to it; MODIFIER repeats once per type), so each level is
returned as an ordered [key, value] list; to_dict() gives the unordered view.
415/415 parse; BEGIN/END always balanced.
verify.py
Parses every file with the reader its type calls for, computes key-frequency schema stats per block path, runs the cross-link suite, writes the artifacts, and prints the appendix below. Exit code 0 = no parse failures.
Cross-link results (summary; full detail in appendix and crosslink.json)
| link | refs | dangling | notes |
|---|---|---|---|
weapon requires → tech |
189 (177 weapons; 12 list 2+ techs = AND) | 0 | 18 case-only mismatches; 13 player weapons have no requires (missiles, mirv warheads, boarding shuttles, spyship, wraith) |
shipsection requires → tech |
970 | 0 | 7 case-only (Ind_AstMon, DRN_BTLRdrs) |
shipsection option/optiondef → tech |
3,842 | 0 | |
tech ship{section} → shipsection |
149 | 0 | section names are race-agnostic; matched against the union of race catalogs |
tech weapon{filename} → file |
67 | 0 | |
tech requires → tech or GRP_x |
154 | 0 | 9 groups (group TORPS ↔ requires GRP_TORPS): PRJCTR 4, PD 3, HVYBEAM 3, SPINAL 2, TORPS 12, MINES 7, BIOMISSILE 5, JAMMING 3, SHIELDS 6 |
tech allows edges |
354 | 0 | every edge parses to child RP:n [Race:%…]; 12 roots (*_ROOT) are never a target |
_weapons.txt ↔ Weapons/*.weapon |
123 ids, deleted 36/58/59 | 0 either way | Species/_NPC/weapons/ (84) has no manifest — referenced by path from NPC bank{weapon} (404 refs, 0 dangling) |
_shipsections.txt ↔ files |
885 ids | 10 ids with no file | Tarkas ids 3,4,16,32,43,108,109 (CRAbsorber, CRAIC, CRDeflector, DEAbsorber, DEDeflector, DEDisruptor, CRDisruptor); DEWar in Human 98, Liir 86, Morrigi 39. These are reserved wire ids, consistent with the "never reuse ids" header. |
TECHNAME_/TECHDESC_ |
293 | 0 / 0 | |
SECTIONNAME_/SECTIONDESC_ (267 stems) |
0 / 67 | 62 of the missing DESC are _NPC-only, 5 are hidden player stems (_assaultshuttle, _biomissile, _boardingpod, _nodemissile, _spy) |
|
every @TOKEN in brace files |
378 | 3 | @WEAPON_NPC_REFUGEEDRONE, @WEAPON_SILQUEEN_CANNON, @WEAPON_VNSYSK_CUTTINGBEAM (NPC weapons; would render as raw token) |
_turrets.txt vs weapon/bank (size,class) |
42 rows | 0 / 0 | only when compared case-insensitively (Large, Missile, Standard, COL, PlanetMissile appear) |
_defaultweapons.txt → weapon file |
31 | 0 | |
AI affinity_section/raider_sections → section, weapon_replacements → weapon, affinity_weapon → weaponfamily |
200 / – / 6 / 9 | 0 | |
aitechpri/aitechgrp/aitechmode.csv |
0 rows | – | comment-only templates: AI tech priorities are code-owned |
Scenario *Techs.csv → tech, *FleetTemplates.csv → section |
127 / 87 | 0 |
Schema facts worth knowing (from schema_stats.json)
weapon: 207 instances, 60 distinct scalar keys; behaviour sub-block is one ofbolt(66, always withrangetable),beam(52),torpedo(19, withrangetable),rider(17),missile(13),chainlightning(10),col(9),mine(7),disintegrator(3),grapple,projectedshield,mirv(2 each),nodecannon,siege,mesonprojector,spyship,wraith(1 each). Always present:name weaponclass turretsize turretclass burst_volleys recharge_time.shipsection: 875 instances, 87 distinct keys; alwaysmodel health mass;bank(3,721) →mount(7,375);thruster(1,105);netforcelimits(875);option(1,226 block instances);anim(247).section_typevalues are case-mixed (engine/Engine,command/Command) and 41 sections have none (riders, NPC hulls);section_classhas 16 with none.tech: 293 nodes; keysname(293)allows(354)threat(169)family(155)requires(154)group(45)type(39, alwaysP)option_cost(18)unlock_explicitly(6); sub-blocksship(71)weapon(67)strategy(61).familyis written on only 155 nodes —tech_tree.jsonaddsfamily_inferredfrom the name prefix.Strings.csv: 5,200 data rows → 5,196 keys (4 duplicated via trailing space, identical text). Key prefixes: AIDIP 755, EVENTMSG 415, TECHNAME/TECHDESC 293 each, SECTIONNAME 283, WEAPON 139, TECHBEN 25 …
Artifacts (parsers/out/)
| file | size | content |
|---|---|---|
tech_tree.json |
217 KB | 293 nodes (name, display name/desc, family, family_inferred, type, threat, group, option_cost, requires, TECHBEN inc/dec, sections, weapons, allows) + 354 edges {from, to, rp, pct{Race:%}} + groups |
weapons.json |
345 KB | 207 weapons: full parsed body + stem, file, scope(player/NPC), id, display_name |
shipsections.json |
2.7 MB | 875 sections: full body + race, stem, id, display_name, description, unlocked_by[] |
strings.json |
396 KB | 5,196 token → text |
schema_stats.json |
33 KB | per block path: instance count, key frequencies, sub-block frequencies |
crosslink.json |
27 KB | machine-readable form of the cross-link findings |
tech_tree.dot |
54 KB | Graphviz digraph, nodes coloured by family, edges labelled RP + race % (no graphviz on this box; dot -Tsvg elsewhere) |
report.md |
verbatim verify.py output (appended below) |
Caveat on edges[].pct: a race absent from an allows string has no override
in the data. The default (believed 100%) is engine code, not asserted by the
artifact.
Appendix — verify.py output
Parse results
| reader | file kind | files | parsed | failed |
|---|---|---|---|---|
| mars_data | brace:combat | 3 | 3 | 0 |
| mars_data | brace:def | 2 | 2 | 0 |
| mars_data | brace:script | 4 | 4 | 0 |
| mars_data | brace:shipsection | 875 | 875 | 0 |
| mars_data | brace:tech | 1 | 1 | 0 |
| mars_data | brace:txt | 24 | 24 | 0 |
| mars_data | brace:weapon | 207 | 207 | 0 |
| manifest.parse_csv | csv | 28 | 28 | 0 |
| effect_txt | effect | 415 | 415 | 0 |
| flat_kv.parse_kv | kv | 20 | 20 | 0 |
| manifest.parse_manifest | manifest | 8 | 8 | 0 |
| flat_kv.parse_rows | rows | 8 | 8 | 0 |
Total: 1595 parsed, 0 failed (skipped: 70 HLSL/prose files that are not data).
Lenient recoveries (engine-compatible; strict=True would reject these):
Species/Morrigi/sections/DECommand.shipsection: end of file inside block (depth 1)Species/Liir/sections/CRAbsorber.shipsection: end of file inside block (depth 1)Species/Liir/sections/CRCommand.shipsection: end of file inside block (depth 1)Species/Liir/sections/CRFireControl.shipsection: end of file inside block (depth 1)Species/Liir/sections/CRShield.shipsection: end of file inside block (depth 1)Species/Human/sections/CrPropaganda.shipsection: line 105: stray '}' at top levelSpecies/Hiver/sections/DEAntiMatter.shipsection: end of file inside block (depth 1)Species/Hiver/sections/DEDeflector.shipsection: end of file inside block (depth 1)Species/Hiver/sections/DEDisruptor.shipsection: end of file inside block (depth 1)Species/Hiver/sections/DEJammer.shipsection: end of file inside block (depth 1)Species/Hiver/sections/DEShield.shipsection: end of file inside block (depth 1)Species/Hiver/sections/DESquadronCnc.shipsection: end of file inside block (depth 1)
Schema stats (key frequency per block type)
Full table in schema_stats.json. Block paths with instance counts and the
keys seen in them (count = number of block instances carrying the key):
weapon.weapon (207 instances)
keys: name:207, weaponclass:207, turretsize:207, turretclass:207, burst_volleys:207, recharge_time:207, cost:204, icon_file:204, icon_rect:204, muzzle_sound:203, muzzle_sound_minrange:200, muzzle_effect:190, requires:189, dam_est:179, volley_period:176, rating_frate:176, rating_dam:176, rating_acc:176, rating_range:176, fc_requires_los:173, fc_requires_inrange:173, fc_requires_enemycolony:173, fc_manual_target:173, fc_manual_toggle:173, fc_manual_launch:173, fc_controllable:173, fc_holdsfire:173, range:167, range_planet:167, hpbonus:152, model1:143, muzzle_speed:140, weaponfamily:130, volley_duration:107, solution_tolerance:89, turretmodel1:67, trackspeed_mod:55, model2:43, pinpoint:41, buildup_delay:40, blindfire:37, weapondamagetype:21, munitionsize:15, model3:14, fc_explicit_target:14, fc_exclusive_launch:14, fc_targets_expire:14, turretmodel2:14, target_queue_size:13, compatible_section:10, hidden:9, lock_period:4, buildup_sound:3, buildup_sound_minrange:3, exclusive_species:2, secondary_pd:2, turretmodel3:2, buildup_effect:1, centered_muzzle_effect:1, anim:1
sub-blocks: bolt:66, beam:52, torpedo:19, rider:17, missile:13, chainlightning:10, col:9, mine:7, disintegrator:3, grapple:2, projectedshield:2, mirv:2, nodecannon:1, siege:1, mesonprojector:1, spyship:1, wraith:1
shipsection.shipsection (875 instances)
keys: requires:970, mass:877, model:875, health:875, section_class:859, section_type:834, cost:831, cpoints:824, crew:644, preview_ofs:549, socket_aft:543, socket_fore:487, option:229, entity_class:189, range:182, engine_sound:178, autonomous:178, ftlspeed:168, dam_model:154, engine_idle_sound:133, design_class:126, engine_sound_minrange:125, engine_techera:115, command_cost:111, maintenance_cost:98, nodespeed:75, node_cannon_fling:68, dam_socket_aft:68, dam_socket_fore:67, section_sound:65, section_sound_repeat:65, nodesign:63, section_lod_type:60, view_dist:60, explicit_section:60, defence_platform:49, death_effect:39, tacticalsensorrange:38, scanrange:35, command_quota:34, station:33, death_area_impact_effect:30, death_dam:30, death_damradius:30, explicit_command_section:30, explicit_engine_section:30, refueling_capacity:21, split_traffic_volume:20, repair_capacity:18, aicontrol:17, rebelai_scanrange:17, rebelai_tacticalsensorrange:17, colonizer_pop:12, colonizer_infra:12, colonizer_terra:12, refinery:12, freighter:10, monitor:8, shield_model_offset:7, construction_capacity:7, huge:6, mining_capacity:6, mining_rate:6, spytender:6, propaganda:6, ewar:6, science:6, spy:6, exclude:6, freighterq:5, police:5, tradingpost:5, prisoner_capacity:4, node_bore:3, section_sound_minrange:3, gravboat_bonus:3, gateship:2, ramscoop:2, construction_devourer:1, construction_refresh:1, construction_salvagerate:1, mining_trap:1, colony_trap:1, imperial_population:1, civilian_population:1, protectorate:1, node_missile:1
sub-blocks: bank:3721, option:1226, thruster:1105, netforcelimits:875, anim:247, optiondef:12
tech.tech (293 instances)
keys: allows:354, name:293, threat:169, family:155, requires:154, group:45, type:39, option_cost:18, unlock_explicitly:6
sub-blocks: ship:71, weapon:67, strategy:61
All block paths: combat(3), combat.combatenv(3), combat.endcondition(3), combat.orbit(17), combat.planet(1), combat.summary(3), def(2), def.badge(27), def.light(196), script(4), script.distanceline(1), script.fleetline(1), script.gateline(1), script.hiver(1), script.hiver.dead(1), script.hiver.good(1), script.hiver.ideal(1), script.hiver.ideal_2(1), script.hiver.poor(1), script.human(1), script.human.dead(1), script.human.good(1), script.human.ideal(1), script.human.ideal_2(1), script.human.poor(1), script.liir(1), script.liir.dead(1), script.liir.good(1), script.liir.ideal(1), script.liir.ideal_2(1), script.liir.poor(1), script.morrigi(1), script.morrigi.dead(1), script.morrigi.good(1), script.morrigi.ideal(1), script.morrigi.ideal_2(1), script.morrigi.poor(1), script.moveline(1), script.nodeline(1), script.sprite(11), script.tarkas(1), script.tarkas.dead(1), script.tarkas.good(1), script.tarkas.ideal(1), script.tarkas.ideal_2(1), script.tarkas.poor(1), script.techcolors(1), script.techtreebackground(1), script.techtreemodelinfo(12), script.tradeline(1), script.zuul(1), script.zuul.dead(1), script.zuul.good(1), script.zuul.ideal(1), script.zuul.ideal_2(1), script.zuul.poor(1), shipsection(875), shipsection.shipsection(875), shipsection.shipsection.anim(247), shipsection.shipsection.bank(3721), shipsection.shipsection.bank.mount(7375), shipsection.shipsection.netforcelimits(875), shipsection.shipsection.option(1226), shipsection.shipsection.optiondef(12), shipsection.shipsection.thruster(1105), tech(1), tech.tech(293), tech.tech.ship(71), tech.tech.strategy(61), tech.tech.weapon(67), weapon(207), weapon.weapon(207), weapon.weapon.beam(52), weapon.weapon.bolt(66), weapon.weapon.bolt.rangetable(66), weapon.weapon.chainlightning(10), weapon.weapon.col(9), weapon.weapon.disintegrator(3), weapon.weapon.grapple(2), weapon.weapon.mesonprojector(1), weapon.weapon.mine(7), weapon.weapon.mirv(2), weapon.weapon.missile(13), weapon.weapon.nodecannon(1), weapon.weapon.projectedshield(2), weapon.weapon.rider(17), weapon.weapon.siege(1), weapon.weapon.spyship(1), weapon.weapon.torpedo(19), weapon.weapon.torpedo.rangetable(19), weapon.weapon.wraith(1)
Cross-link results
- weapon
requires-> tech: 189 refs in 177 weapons (12 weapons list 2+ techs), 0 dangling, 18 case-mismatched; 30 weapons have norequires(NPC: 17, player: ['_mis_mirv_warhead.weapon', '_mis_planet_mirv_warhead.weapon', 'bal_grapple.weapon', 'brd_prisonershuttle.weapon', 'brd_shuttle.weapon', 'brd_tarkahunter.weapon', 'mis.weapon', 'mis_defplat.weapon', 'mis_planet.weapon', 'mis_planet_hvy.weapon', 'mis_planet_mirv.weapon', 'spyship.weapon', 'wraith.weapon']).- case:
Weapons/bem_beamer_uv.weaponrequiresWEP_UVBmr(tech isWEP_UvBmr) - case:
Weapons/bem_part.weaponrequiresWEP_prtBm(tech isWEP_PrtBm) - case:
Weapons/bem_part_spinal.weaponrequiresWEP_prtBm(tech isWEP_PrtBm) - case:
Weapons/can_plasma.weaponrequiresWEP_plsmcan(tech isWEP_PlsmCan) - case:
Weapons/hvy_bem_hclas.weaponrequiresWEP_HCLAS(tech isWEP_HCLas) - case:
Weapons/hvy_bem_hclas_free.weaponrequiresWEP_HCLAS(tech isWEP_HCLas) - case:
Weapons/hvy_bem_lancer.weaponrequiresWEP_lancer(tech isWEP_Lancer) - case:
Weapons/hvy_bem_lancer_free.weaponrequiresWEP_lancer(tech isWEP_Lancer) - case:
Weapons/las_green.weaponrequiresWEP_grnlas(tech isWEP_GrnLas) - case:
Weapons/las_uv.weaponrequiresWEP_UVlas(tech isWEP_UvLas) - case:
Weapons/las_xray.weaponrequiresWEP_xrylas(tech isWEP_XryLas) - case:
Weapons/trp_plasma.weaponrequiresWEP_plsmtrp(tech isWEP_PlsmTrp) - case:
Species/_NPC/weapons/Herald_bem_beamer_uv.weaponrequiresWEP_UVBmr(tech isWEP_UvBmr) - case:
Species/_NPC/weapons/Refugee_sml.weaponrequiresWEP_UVBmr(tech isWEP_UvBmr) - case:
Species/_NPC/weapons/SiliciodQueenCannon.weaponrequiresWEP_plsmcan(tech isWEP_PlsmCan) - case:
Species/_NPC/weapons/SwarmerCannon.weaponrequiresWEP_plsmcan(tech isWEP_PlsmCan) - case:
Species/_NPC/weapons/Wreckage_bem_part.weaponrequiresWEP_prtBm(tech isWEP_PrtBm) - case:
Species/_NPC/weapons/Wreckage_las_xray.weaponrequiresWEP_xrylas(tech isWEP_XryLas)
- case:
- shipsection
requires-> tech: 970 refs, 0 dangling, 7 case-mismatched.- case:
Species/Zuul/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon - case:
Species/Tarkas/sections/DNCarrier.shipsectionrequiresDRN_BTLRdrs - case:
Species/Tarkas/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon - case:
Species/Morrigi/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon - case:
Species/Liir/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon - case:
Species/Human/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon - case:
Species/Hiver/sections/_AsteroidMonitor.shipsectionrequiresInd_AstMon
- case:
- shipsection
option{option T}/optiondef-> tech: 3842 refs, 0 dangling. Two forms coexist:option { option A option B }(a mutually-exclusive choice group) and a bare section-leveloption T(229 occurrences in 153 files, e.g.option DRV_PlsmFocon engine sections) -- both merge under the keyoption, so consumers must accept str-or-dict list members. - tech
ship{section}-> shipsection: 149 refs, 0 dangling (matched against the union of all race catalogs, case-insensitive). - tech
weapon{filename}-> file: 67 refs, 0 dangling. - tech
requires-> tech/GRP_: 154 refs, 0 dangling. Groups: {'PRJCTR': 4, 'PD': 3, 'HVYBEAM': 3, 'SPINAL': 2, 'TORPS': 12, 'MINES': 7, 'BIOMISSILE': 5, 'JAMMING': 3, 'SHIELDS': 6}. - tech
allowsedges: 354, 0 point at unknown techs, 0 unparsable. - techs never allowed by anything (roots/orphans): 12: IND_ROOT, EWP_ROOT, SLD_Root, NRG_Root, TRP_ROOT, WHD_ROOT, BAL_ROOT, DRV_ROOT, BIO_ROOT, CCC_ROOT, DRN_ROOT, XNC_ROOT
- duplicate tech names: none
- id manifests <-> files:
Weapons: 123 ids (deleted [36, 58, 59]); listed-but-no-file 0; file-but-unlisted 0Human: 145 ids (deleted none); listed-but-no-file 1; file-but-unlisted 0- MISSING FILE for id 98:
dewar.shipsection
- MISSING FILE for id 98:
Zuul: 122 ids (deleted none); listed-but-no-file 0; file-but-unlisted 0Hiver: 137 ids (deleted none); listed-but-no-file 0; file-but-unlisted 0Tarkas: 139 ids (deleted none); listed-but-no-file 7; file-but-unlisted 0- MISSING FILE for id 3:
crabsorber.shipsection - MISSING FILE for id 4:
craic.shipsection - MISSING FILE for id 16:
crdeflector.shipsection - MISSING FILE for id 109:
crdisruptor.shipsection - MISSING FILE for id 32:
deabsorber.shipsection - MISSING FILE for id 43:
dedeflector.shipsection - MISSING FILE for id 108:
dedisruptor.shipsection
- MISSING FILE for id 3:
Liir: 136 ids (deleted none); listed-but-no-file 1; file-but-unlisted 0- MISSING FILE for id 86:
dewar.shipsection
- MISSING FILE for id 86:
Morrigi: 142 ids (deleted none); listed-but-no-file 1; file-but-unlisted 0- MISSING FILE for id 39:
dewar.shipsection
- MISSING FILE for id 39:
_NPC: 64 ids (deleted none); listed-but-no-file 0; file-but-unlisted 0Species/_NPC/weapons/*.weapon(84 files) have no manifest at all; they are referenced by filename from_NPCshipsectionbank{weapon}lines.
- localization:
Strings.csv: 5200 data rows -> 5196 keys. 4 keys occur twice because one copy carries a trailing space (parse_csv strips cells; the later row wins):SECTIONNAME_CRGravboat: 'Gravboat' then 'Gravboat'SECTIONNAME_CRFusion_Carver: 'Fusion Void Carver' then 'Fusion Void Carver'SECTIONNAME_CRAntimatter_Carver: 'AM Void Carver' then 'AM Void Carver'SECTIONNAME_CRAntimatter_Mastery: 'AM Void Mastery' then 'AM Void Mastery'
- TECHNAME_/TECHDESC_ for 293 techs: 0 / 0 missing. [] []
- SECTIONNAME_/SECTIONDESC_ for 267 distinct section stems: 0 / 67 missing.
- missing SECTIONNAME_: 0 are
_NPC-only stems (never shown in the design UI); player-race stems: 0 [] - missing SECTIONDESC_: 62 are
_NPC-only stems (never shown in the design UI); player-race stems: 5 ['_assaultshuttle', '_biomissile', '_boardingpod', '_nodemissile', '_spy']
- missing SECTIONNAME_: 0 are
- weapon
name @TOKEN: 3 unresolved of 207; 0 weapons carry noname.- UNRESOLVED
Species/_NPC/weapons/Drone_shuttle.weaponname@WEAPON_NPC_REFUGEEDRONE - UNRESOLVED
Species/_NPC/weapons/SiliciodQueenCannon.weaponname@WEAPON_SILQUEEN_CANNON - UNRESOLVED
Species/_NPC/weapons/VNSYSK_Beam.weaponname@WEAPON_VNSYSK_CUTTINGBEAM
- UNRESOLVED
- all
@TOKENrefs in brace-block files: 378 refs, 3 unresolved.- UNRESOLVED
Species/_NPC/weapons/Drone_shuttle.weapon@WEAPON_NPC_REFUGEEDRONE - UNRESOLVED
Species/_NPC/weapons/SiliciodQueenCannon.weapon@WEAPON_SILQUEEN_CANNON - UNRESOLVED
Species/_NPC/weapons/VNSYSK_Beam.weapon@WEAPON_VNSYSK_CUTTINGBEAM
- UNRESOLVED
_turrets.txt(42 rows; size/class values compared case-insensitively -- the data mixesLarge/large,Missile/missile,Standard/standard):- weapon (turretsize,turretclass) pairs with no turret row: none
- section bank (turretsize,turretclass) pairs with no turret row: none
- banks with no turretsize at all (NPC fixed-weapon banks): 2; banks that repeat turretsize/turretclass inside one bank{} (last value taken): 19
- shipsection
bank{weapon <file>}-> file: 404 refs, 0 dangling. _defaultweapons.txt: 31 rows, 0 name a missing weapon file.Data/Strategy/AI/aitechpri.csv: 0 data rows -- the shipped file is a comment-only template (schema documented in its header, no entries); the AI's tech priorities must therefore come from code.Data/Strategy/AI/aitechgrp.csv: 0 data rows -- the shipped file is a comment-only template (schema documented in its header, no entries); the AI's tech priorities must therefore come from code.Data/Strategy/AI/aitechmode.csv: 0 data rows -- the shipped file is a comment-only template (schema documented in its header, no entries); the AI's tech priorities must therefore come from code.AI/affinity_section.csv: 200 rows; unknown sections: noneAI/raider_sections.csv: unknown sections: noneAI/weapon_replacements.csv: 6 rows; unknown weapon stems: noneAI/affinity_weapon.csv: families ['conventionalbeam', 'conventionalmine', 'emitter', 'energycannon', 'gauss', 'heavybeam', 'laser', 'missile', 'torpedo']; not a weaponfamily in any .weapon: none. weaponfamily values in data: {'energycannon': 19, 'heavybeam': 7, 'missile': 14, 'gauss': 27, 'laser': 12, 'conventionalbeam': 21, 'emitter': 9, 'conventionalmine': 6, 'torpedo': 15}Scenarios/HiverInvasion_FleetTemplates.csv: 81 rows; unknown sections: noneScenarios/HiverInvasion_Techs.csv: 67 rows; unknown techs: noneScenarios/UpstartApes_EmpireTechs.csv: 60 rows; unknown techs: noneScenarios/UpstartApes_GiftFleetTemplates.csv: 6 rows; unknown sections: none
Artifacts
tech_tree.json(225 KB)weapons.json(345 KB)shipsections.json(2752 KB)strings.json(396 KB)schema_stats.json(33 KB)crosslink.json(24 KB)tech_tree.dot(54 KB)- tech_tree.json: 293 nodes, 354 edges; weapons.json: 207; shipsections.json: 875; strings.json: 5196 keys