sots-re/findings/objects/save-editor-structs.md

552 lines
31 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Sword of the Stars 1 (SOTS1) — Save-File Struct Reference (Community RE)
Harvested from two community save-game editors for cross-checking against binary-recovered
serializable types. SOTS1 uses a self-describing "Streamable" serialization system: **field
order + type matter**, and most values are preceded by their own name string.
Target game version: **SOTS1 v1.8+** (both editors target the 1.8.x line; repo2 says "1.80 +19").
> **Tag-name note (2026-09-07):** this document preserves the community editors' (R1) field spellings. The real on-disk tags, byte-confirmed on our saves, differ in a few places: `otnF` (not `ontF`) in Odes/Owep/Otch, `nextid` (not `nextId`) in NdGr2, Design = `FAIDes/DHide/DWep/DName`, and `ords`/`wpts`/`paths` are real tags. See `verify/save-reader/SAVE_FORMAT.md` §10.
## Sources
- **[R1] BardezAnAvatar/Sots.Sots1.SavedGameEditor** — C#. A *complete, ordered, typed*
Streamable parser. Migrated from SourceForge `sots-sge`. This is the authoritative layout
evidence (read/write methods reproduce exact on-disk order). Key files under
`Bardez.Project.SwordOfTheStars.DataStructures/`:
- `BaseSaveStructures.cs` (primitives + framing), `SharedSaveStructures.cs` (coords/colors),
`SaveGameDataStructure.cs` (top level), `SummarySaveStructures.cs`,
`CreateParametersSaveStructures.cs`, `SimulationSaveStructures.cs` (11,974 lines — the bulk),
`CdTableSaveStructures.cs` (combat/AI table). IO: `Bardez.Project.SwordOfTheStars.IO/{Gzip,SaveFileIO}.cs`.
- **[R2] ghbplayer/SOTSedit** — C#/WPF. A *name-tag scanner* (does NOT model layout; it searches
the decompressed blob for length-prefixed field-name strings and reads the value that follows).
Value comes from `Parse.cs` + the field catalog `SOTSEdit.cfg` (friendly-name ⇄ serialized-name
⇄ type mapping) and race/semantic hacks. Written 2011 "to learn C#"; author calls the parser weak.
Both editors independently confirm the same primitives, framing markers, race IDs, and color IDs.
---
## 1. File format & serialization mechanics
### 1.1 Container
- **Whole `.sav` file = gzip stream.** Decompress first (`GZipStream`, standard gzip). [R1 Gzip.cs, R2 gzip.cs]
- Editors work on the **decompressed** byte stream (R1 names its test artifacts `*.sav.inflate.dat`).
- No separate magic/version header is decoded by either editor beyond the top-level Summary block;
version is implied by scenario/field presence, not a numeric version field. Endianness: **little-endian**
throughout (`BitConverter` on x86).
### 1.2 Decompressed top-level order [R1 SaveGameData.ReadFromStream]
```
SaveGameData:
1. SummarySaveStruct summary
2. CreateParametersSaveStruct createParams
3. SimSaveStruct sim <-- the giant one (players/systems/fleets/etc.)
4. CdTable cdTable <-- combat / AI state table
```
### 1.3 Primitive encodings [R1 BaseSaveStructures.cs]
Text encoding is **windows-1252** (R1) / ASCII (R2).
- **StringStruct** (raw string): `Int32 length` + `length` bytes (no NUL terminator counted).
R2 calls this a "BStr": 4-byte little-endian length prefix + ASCII bytes.
- **Padding rule (critical):** every *basic* value struct is **NUL-padded to a 4-byte boundary**.
`PaddingSize = 4`. Padding is computed over `(sizeof(Int32 desc-length) + description.Length + valueBytes)`.
- **Named value fields** (`BasicSaveStruct` subclasses) are each laid out as:
`StringStruct description` (a field-name tag, often the non-descriptive `"."`) → then the value →
then NUL padding to 4 bytes. Concrete leaf types:
| Struct | Payload after description tag |
|---|---|
| `Int32SaveStruct` | 4-byte Int32 |
| `Int64SaveStruct` | 8-byte Int64 |
| `FloatSaveStruct` | 4-byte IEEE Single |
| `BooleanSaveStruct` | 1 byte (0/1), padded to 4 |
| `StringSaveStruct` | nested StringStruct (len+bytes) |
| `ByteArraySaveStruct` | raw bytes (length externally known) |
So the on-disk shape of a named scalar is: `[len][name-ascii][pad] [value] [pad]`. R2 exploits exactly
this: it locates a field by searching for `[len][name]` and reads the value immediately after.
### 1.4 Complex-struct framing (the "BEEFBEEF" envelope) [R1 ComplexSaveStruct]
Every **complex** structure is framed:
```
StringStruct description (NUL-padded to 4)
UInt32 0xBEEFBEEF (begin marker)
... body (ordered child fields) ...
UInt32 0x41104110 (end marker = bitwise NOT of 0xBEEFBEEF)
```
`0xBEEFBEEF` / `~0xBEEFBEEF (0x41104110)` bracket every complex object — a reliable resync/validation
signature when scanning the binary. (`ISotsStructure` leaf types are NOT framed; only `ComplexSaveStruct`.)
### 1.5 Array conventions [R1 BaseSaveStructures.cs]
- **ComplexArraySaveStruct<T>**: framed (has description + BEEFBEEF), body = `Int32SaveStruct count`
then `count` × T.
- **NonComplexArraySaveStruct<T>**: NOT framed; body = `Int32SaveStruct count` then `count` × T.
(Distinguishing which arrays are framed vs. not is itself layout evidence — see per-struct notes.)
### 1.6 Conditional & polymorphic reads (watch for these in the binary)
- **Optional-by-flag:** a boolean/int gate precedes an optional sub-object.
- `SimPlayerColorSaveStruct`: `Int32 colorIndex`; **iff `colorIndex == -1`**, an `RgbColorInt32`
(custom RGB) follows. Otherwise palette index only.
- `SimPlayerDesignDw2SaveStruct` (weapon slot): `Boolean bId`; if true → `Int32 wId`, else →
`StringSaveStruct wfn` (weapon full resource path); then `Int32 dId`.
- `SimFleetShipDetails`: `Boolean hbq` gates `bq`; `Boolean hsp` gates `sp`. Fleet flight-plan/lay
gated by `hfPlan` / `hLay` booleans.
- `SimSystemDetailNvo.isInd` gates independent-colony sub-block; `SimSystemDetailsIndi.hindi`,
`SimSystemDetailsVonNeumann.vnh` similar boolean gates.
- **Polymorphism by string tag:** `SimSvSctObXscn` reads `StringSaveStruct xcsn`, then switches:
`"crowdefs"`, `"gmtrigger"`, `"traps"`, `"indsys"`/default → different body subclass.
- **Polymorphism by fixed position:** `SimScSctObEncObjArray` (grand-menace/encounter objects) reads a
count then dispatches subclass **by index 0..8** in fixed order:
`0 Infest, 1 Dsn, 2 AsteroidMonitor, 3 TD, 4 WD, 5 Hives, 6 Rsuc, 7 Dfts, 8 Ini2`.
### 1.7 Write-time quirks worth knowing (R2)
- R2 edits in place and cannot safely change string length (it truncates/space-pads to the original
length). R1 rewrites the whole stream and re-pads. If the binary stores string lengths, the game
reads them dynamically (R1 proves round-trip works when re-padded).
- R2 planet OID/PID hack: on-disk **`OID = PID * 16`** (R2 divides by 16 to show a "PlayerID").
i.e. the raw owner id field is the player index shifted left 4 bits.
---
## 2. Enums / ID tables (agreed by both editors)
### 2.1 Species / race ID [R2 Parse.cs addRace(); R1 PlayerSlot.FxSp comment]
| ID | Species |
|---|---|
| 0 | Human |
| 1 | Hiver |
| 2 | Tarka(s) |
| 3 | Liir |
| 4 | `_NPC` / AI-rebellion / grand-menace player (R1: "??? AI Rebellion") |
| 5 | Zuul |
| 6 | Morrigi |
### 2.2 Player color ID (palette index) [R1 PlayerSlot.FxCrId & SimPlayerColor]
`01 Red, 02 Yellow, 03 Blue, 04 Pink/Magenta, 05 Orange, 06 Green, 07 Aqua, 08 Gray,
09 Dark Green, 10 Purple`. Value **-1 ⇒ custom RGB triplet follows** (see §1.6).
### 2.3 Difficulty [R1 PlayerSettings.Difficulty]
`0 Easy, 1 Normal, 2 Difficult`.
### 2.4 Sentinel values seen in fields
`0x7FFFFFFF (Int32.MaxValue)` used as "tag"/unset (PlayerSlot.tag);
`team = -1 (0xFFFFFFFF)` = no team; R2: value `-1` = field absent in this save.
No named C# enums exist for tech IDs / weapon families — techs and weapons are **string resource
names** (e.g. tech `tNm`, weapon `wfn`), not numeric enums. Weapon *family* enumeration lives only in
the CdAi combat block as `aiSitWepFams` (opaque int set).
---
## 3. Summary block [R1 SummarySaveStructures.cs] (complex)
### SummarySaveStruct (fields in on-disk order)
1. `StringSaveStruct gameName`
2. `Int32 turn`
3. `Int32 numSys` (system count)
4. `Int32 checkSum`
5. `ComplexArray<PlayerSlotWrapper> players`
6. `SessionSaveStruct session`
7. `Int32 mapShape`
8. `Int32 incMod` (income modifier)
9. `Int32 resMod` (research modifier)
10. `Boolean alliances`
11. `Boolean teams`
12. `Boolean encounters`
13. `StringSaveStruct scenario`
### PlayerSlotWrapper (complex): `{ PlayerSlotSaveStruct slot; Int32 rank; }`
### PlayerSlotSaveStruct (complex) — new-game slot definition, ordered:
`Boolean isPlay, isDead, isReq, isRec, isFxNm` → `String fxNm` (fixed name) →
`Boolean isFxSp` → `Int32 fxSp` (species, §2.1) → `Boolean isFxCr` →
`NestedInt32 fxCrId` (color, §2.2) → `Boolean isFxBd` → `String fxBd` (badge) →
`Boolean isFxAv` → `String fxAv` (avatar) → `Int32 tag` (often 0x7FFFFFFF) →
`Int32 pwd` (password) → `Int32 team` (-1=none) → `PlayerSettingsSaveStruct settings`.
### PlayerSettingsSaveStruct (complex):
`Int32 initialTreasury, initialColonies, initialTechnologies, difficulty (§2.3)`.
### SessionSaveStruct (complex) → `TmrsSaveStruct tmrs`:
`Int32 tstl (0x7F7FFFFF), tctl (0x42700000), tqtl (0x7F7FFFFF), tqtle (0)` — timer limits (float bit-patterns stored as int).
---
## 4. CreateParameters block [R1 CreateParametersSaveStructures.cs] (complex)
### CreateParametersSaveStruct (ordered):
`String name; Int32 id; Int32 rSeed (random seed); Int32 aid; String key;`
`MapPSaveStruct mapP;` `Int32 mapS; Int32 mapF; Int32 nSys;` `Float rEnc (random-encounter rate);`
`Int32 sDist;` `Float sSize; Float sRes;` `Int32 sSuit; Int32 maxP; Int32 aSpec;`
`Boolean bAlly; Int32 nTeam; Boolean tmgrp;`
`Int32 pSav (start savings); Int32 pCol (start colonies); Int32 pTech (start techs);`
`Float incM; Float resM; ScrpSaveStruct scrp;`
### MapPSaveStruct (complex) — initial map/galaxy generation:
1. `Int32 unknown1`
2. `ComplexArray<PlanetSaveStruct> planetArray`
3. `NonComplexArray<ComplexArray<Int32>> players` (per-player int arrays; "non-complex array of players")
4. `ComplexArray<MapPNpc> npcArray` (≈ players − 1; independents/NPCs)
### PlanetSaveStruct (complex) — initial star node geometry:
`SpatialCoordinate coordinates (x,y,z floats)`, `Int32 unknown1..4`
(values seen: `0x7FFFFFFF`, `0x7F7FFFFF`). NOTE: this is the *map-generation* planet record; the
*live* planet/colony state lives in `SimSystemDetailsSaveStruct` (§8).
### MapPNpc (complex): `Int32 unknown1, unknown2`. ### ScrpSaveStruct (complex): `Int32 spc`.
### Shared value types [R1 SharedSaveStructures.cs]
- `SpatialCoordinateSaveStruct` (complex): `Float x, y, z`.
- `RgbColorFloat` (leaf): `Float r,g,b`. `RgbaColorFloat` (leaf): `RgbColorFloat rgb; Float a`.
- `RgbColorInt32` (leaf): `Int32 r,g,b`.
---
## 5. Simulation block — top level [R1 SimulationSaveStructures.cs]
### SimSaveStruct (complex) — the master world state, ordered:
```
String keyPath
Int32 nMsz, nMlc, nMnx (next-id / size counters)
NonComplexArray<Int32> playerIds, designIds, systemIds, fleetIds, shipIds, tradeIds
Int32 modCount, frame, gameId
AttributeSaveStruct attribute
RngSaveStruct rng (ByteArray unknownData ~2500 bytes: RNG state)
String gameName
Int32 map, incMod, resMod, enAl, enTm, gOTurn
NestedInt32 gOWinPly
Int32 npcm, npco, npci, npcv, npca, szad, rsad, suad
ComplexArray<ResearchSaveStruct> sprjs (shared/special research projects)
Float randEncAdjustment
Int32 cmbtid (next combat id)
ComplexArray<TurnPly> turnstats (per-turn per-player history)
NonComplexArray<SimCrepSaveStruct> creps (combat reports)
Int32 ninv, allExc1, allExc2, allExcCF
NonComplexArray<SimPlayerSaveStruct> players <-- EMPIRES (§6)
SimSpeciesArraySaveStruct species (galaxy species list)
NonComplexArray<SimSystemSaveStruct> systems <-- STAR SYSTEMS (§8)
SimNodeGrid2 ndgr2 (node-line / warp graph)
SimTradeManager trdmgr (§9)
SimSpyManager spymgr (Int32 xsid, nspy)
NonComplexArray<SimFleet> flt <-- FLEETS (§10)
NonComplexArray<Int32> acts
SimSvSctOb svSctOb (scenario/encounter objects, §11)
Int32 zdsc, zdsi, zdst (Zuul/system-destroyer counters)
```
### Small shared sim types
- `SimPopGSaveStruct` (complex): `Int32 popT; Int32 popS; Int64 popC` (pop type / species / civ count).
- `ResearchSaveStruct` (leaf): `NonComplexArray<ResearchOptionalSaveStruct> us; String nm; String ntg`.
`ResearchOptionalSaveStruct`: `Int32 usc, usp`.
- `TurnPly` (leaf): `Int32 ply; PlyHistSaveStruct hist`.
- `PlyHistSaveStruct` (complex): `Int32 ply; PlyHistStatsSaveStruct[] stats`.
- `PlyHistStatsSaveStruct` (complex): `Int64 pop; ComplexArray<PlyHistStatsSacq> sacq, slost;`
`Int32 trn, almem, inc, tdinc, sav, col, bat, tch; NonComplexArray<PlyHistClsSaveStruct> cls`.
- `PlyHistStatsSacq` (complex): `Int32 set, ses, seop, senp; NonComplexArray<Int32> seo`.
- `PlyHistClsSaveStruct` (leaf): `Int32 cls, shpt, shpl, shpk, satt, satl, satk` (ship/sat built/lost/killed by class).
---
## 6. Player / Empire [R1 SimulationSaveStructures.cs]
### SimPlayerSaveStruct (leaf wrapper): `Int32 playerId; SimPlayerDetailsSaveStruct details`.
### SimPlayerDetailsSaveStruct (complex) — the empire record, on-disk order:
```
SimPlayerTechTree techTree <-- TECH TREE (§7)
Int32 homeSystem, playerIndex
String playerName
Int32 species (§2.1)
SimPlayerColorSaveStruct colorId (palette idx or -1 + RGB, §1.6/§2.2)
String badge, avatar
Int32 team, sav (savings)
Float idealSuit, suitTolerance, maxOH
Float resRate, resModifier, resScl (research)
Int32 trm, trp, tra
Float outMod, rebOutMod, scOutMod, incMod, popMod, terraMod (economy multipliers)
Boolean aMine; Float minPure, minRate; Int32 ngts, prGtTrf, gTraf
Int32 cstR, cstE, cstT, maint, shrm, status, elim
Boolean npc, rebAi, reqCL
SimPlayerTeamSaveStruct teamStruct (Int32 alid, al, na, cf)
Int32 hasVac, hasImm, npTrak, hasDisc, hasDiscSp, hasDiscCl, hasEnc, hasEng
SimPlayerEventsSaveStruct events (Int32 evNxId; ComplexArray<SimPlayerEvent>)
NestedInt32 fngNum
Int32 pvSav, pvMA, aibN
Boolean cnTrd, cnRad, hgs, hadvs, harcc, cnVItl
Float pddm
Int32 bankWrn, bankTrn, bankPr, bankEl
SimPlayerShipRecsEventsSaveStruct shipRecs
Int32 nextPrjId, plcy, pswd, lret, nmeid
Boolean cdp
SimPlayerSpySaveStruct spy2 (Int32 defc2, rtc, evc, ttc)
SimPlayerCivrSaveStruct civR (Float smx; ComplexArray<SimPlayerCivrSpeSpVa {Int32 sp, va2}>)
Int32 aidf
Boolean srn; Int32 srcTo, lboid, lcid2
String resTnm
Boolean resErrRoll
SimPlayerModsSaveStruct conMods (3× SimPlayerConModSaveStruct {Float conMod, savMod})
NonComplexArray<Int32> ownerIds
NonComplexArray<SimPlayerDesignEntrySaveStruct> designs <-- SHIP DESIGNS (§7.2)
NonComplexArray<SimPlayerDesignEntrySaveStruct> droneDesigns
NonComplexArray<SimPlayerNote> notes (Int32 ntSys; String ntTxt; Int32 ntTrn)
NonComplexArray<SimPlayerPr> pr (Float prm; Int32 prbt)
Boolean hasAiRebellion, cta
NestedInt32 aienf
NonComplexArray<SimPlayerDetailsSpecialProjectT> nSprj
Int32 nexp, nWeapXcl
ComplexArray<SimPlayerDetailsOjv> ovjs (objectives: Int32 id; Bool cmp; Int32 spr; String dsc; Int32 nid)
ComplexArray<SimPlayerDipStat> dipStats (diplomacy; see below)
ComplexArray<SimPlayerComm> comms (Int32 msgt; SimPlayerCommMsg msg)
ComplexArray<SimPlayerPrepSaveStruct> preps
ComplexArray<SimPlayerOdesSaveStruct> odes
ComplexArray<SimPlayerOwepSaveStruct> owep
ComplexArray<SimPlayerOtchSaveStruct> otch
NestedInt32 aid
Int32 ndeflay, rdtc, tnc
```
- `SimPlayerDipStat` (complex): `Int32 other; SimPlayerDipStatDetail nap, ally, cf; Int32 deadhome`.
`SimPlayerDipStatDetail` (leaf): `Int32 last_, last_bty, bkn_, bty_`.
- `SimPlayerCommMsg` (complex): `Int32 cid2, snd, rcp, exp, sent, rcpt, sys`.
- `SimPlayerPrepSaveStruct` (complex): `Int32 oid, pid, flds, sav, home, ncol, mpwr, mcls, mmsl, nshp, nsat`.
- `SimPlayerOdes/Owep/Otch` (complex, "old design/weapon/tech" build history):
`Odes {Int32 ontF, otnL, odid, opid}`, `Owep {Int32 ontF, otnL, odet; String owep; Int32 owith}`,
`Otch {Int32 ontF, otnL, odet; String otch; Int32 owith}`.
- `SimPlayerDetailsSpecialProjectT` (leaf): `Int32 sprjT; SimPlayerDetailsSpecialProjectDetails sprj`.
`...Details` (complex): `Int32 stp; SpecialProjectSpi spi; tail (polymorphic AsMon | Tech)`.
`...Spi` (complex): `Int32 sPid, sts; Float cst; Int32 mxC; String name; Int32 trns`.
tail `AsMon`: `Int32 rDn, sys, rMn, rMx; Float rMd`; tail `Tech`: `Int32 rDn; Float aOdd, aInc; String tch; Int32 rCst`.
---
## 7. Tech tree & ship designs
### 7.1 Tech tree [R1]
- `SimPlayerTechTree` (complex): `NonComplexArray<SimPlayerTechTreeBranch> tree; NonComplexArray<SimPlayerTechTreeTech> techs`.
- `SimPlayerTechTreeBranch` (leaf): `String tNm` (tech name); `NonComplexArray<String> branches` (child tech names).
- `SimPlayerTechTreeTech` (leaf), ordered:
`String tNm` (tech name) → `Int32 st` → `Int32 tResCost` (research cost) →
`Int32 tResDone` (progress) → `Int32 tAcq` (turn acquired) → `Int32 tiAcq` (turns-to-acquire) →
`Int32 tbd` → `Boolean tfc` → `Int32 tUnlck` (unlocked flag).
**Techs are identified by string name, not a numeric enum.**
### 7.2 Ship designs [R1]
- `SimPlayerDesignEntrySaveStruct` (leaf): `Int32 designId; SimPlayerDesignSaveStruct design`.
- `SimPlayerDesignSaveStruct` (complex), ordered:
`Boolean faiDes; Boolean dHide; Int32 dWep; String dName;` `SimPlayerDesignSectionArray sections;`
`Int32 dtc; NonComplexArray<SimPlayerDesignDwg> dwgv`.
- `SimPlayerDesignSectionArray` (leaf): `Int32 count` + `count` × `SimPlayerDesignSectionEntrySaveStruct`.
- `SimPlayerDesignSectionEntrySaveStruct` (complex): `SimPlayerDesignSectionSaveStruct dsec` (Int32 unknown1, unknown2);
`ComplexArray<SimPlayerDesignUnknown1SaveStruct> dgbnk2;` `ComplexArray<String> dOpts`.
- `SimPlayerDesignUnknown1SaveStruct` (complex) → `SimPlayerDesignDw2SaveStruct dw2` (weapon slot,
conditional — see §1.6): `Boolean bId; (bId? Int32 wId : String wfn); Int32 dId`.
- `SimPlayerDesignDwg` (complex): `NonComplexArray<SimPlayerDesignGng> wgng`;
`SimPlayerDesignGng` (leaf): `Int32 wgid; SimPlayerDesignDwgWgb wgb` (complex: `Int32 unknown1..3`).
---
## 8. Star systems & planets [R1]
- `SimSystemSaveStruct` (leaf): `Int32 sysId; SimSystemDetailsSaveStruct details`.
- **`SimSystemDetailsSaveStruct` (complex)** — the live system+colony record, on-disk order:
```
SpatialCoordinate pos
RgbaColorFloat starColor
Int32 idx
Int32 size (1-10)
Float suit (climate hazard)
Int32 res, aRes, mRes (resources / asteroid / extra)
Boolean noRebAi
Int32 tRes, pop
ComplexArray<SimPopGSaveStruct> popG
Float infra
Int32 pvPop; ComplexArray<SimPopG> pvPopG; Float pvInfra, pvSuit; Int32 pvRes, pvARes2, pvMRes; Bool pvNoRebAi (previous-turn snapshot)
SimSystemDetailRtsSaveStruct rts (Float sRs, sRt, sRsc, sRtf, sRi, sRoh, sRnr — IO allocations)
Int32 abdn; Boolean dstyd; Int32 tnsOh
Float outMod, repCur, repMax
Int32 ntdev, pbon
ComplexArray<SimPopG> pbon2
Float ibon
Int32 ltis, rbfl, rbtn, rbfr, rbwn, hsrg
NonComplexArray<SimSystemDetailHalt> halt (Int32 haltt; Bool haltv)
SimSystemDetailsVonNeumann vnm (Bool vnh gate → details: Bool vnd, vnex3, vnpex3)
String name
SimSystemDetailFlags1 flags1 (Int32 vFlags, eFlags, aFlags, fFlags, gFlags)
Int64 bats2 (recent battles; larger=more recent)
Int64 rcex
SimSystemDetailFlags2 flags2 (Int32 mnRFlags, rfRFlags, clkFlags)
Int32 eggScio, terrFl, tAcq, tfAcq, tDst
ComplexArray<SimPopG> dcs; Int32 dsu
ComplexArray<SimSystemDetailCm> cm, pvcm (SimSystemDetailCm: Int32 msp, mv)
ComplexArray<SimSystemDetailCme2> cme2 (Int32 mid, mtrT, mn, mtp; ComplexArray<Cm> mfx; String mdsc)
ComplexArray<SimSystemDetailSpy> spies
Int32 pid (owner player id), defF, defSf
SimSystemDetailBq bq (ComplexArray<SimSystemDetailBqOrd> ords; Ord: Int32 desId, con, conleft, sav, ordId)
NonComplexArray<SimSystemDetailAdct> adct (Int32 ads, adt)
Int32 numPlgs2
NonComplexArray<Int32> flts, gfs, snF, mnF (fleets / gates / stations / monitors present)
NonComplexArray<SimSystemDetailNvo> nvos (colonies; see below)
NonComplexArray<SimSystemDetailVe> nve (Int32 ePid, ets, eid)
NonComplexArray<SimSystemDetailVs> nvs (Int32 pid; SimSystemDetailVsPView pview)
SimSystemDetailsIndi indi (Bool hindi gate → indsp, SimPlayerColor indcl, String indnm/indav/indba)
```
- `SimSystemDetailNvo` (leaf): `Int32 pid, tShn, oId; Boolean isInd; SimSystemDetailNvoIndi indi`
(independent-colony sub-block gated by `isInd`).
- `SimSystemDetailVsPView` (complex — per-player *seen* snapshot of a colony): `Int32 vTrn, pop;`
`ComplexArray<SimPopG> pop2; Int32 infra; Float suit; Int32 res, aRes2, mRes; Bool noRebAi;`
`Int32 pbon; ComplexArray<SimPopG> pbon2; Float ibon; Int32 terrFl; Bool footer`.
### 8.1 R2 ⇄ R1 cross-map for planet/colony fields (verifier gold)
R2 finds these serialized tags anywhere in the "Planets" region (between markers `NumSys`…`NdGr2`).
They correspond to fields inside R1's `SimSystemDetailsSaveStruct` / `...VsPView`:
| R2 serialized tag | type | meaning | R1 field |
|---|---|---|---|
| `Idx` | int | planet/system id | `idx` |
| `Name` | string | name | `name` |
| `Size` | int | 1-10 | `size` |
| `Suit` | float | climate hazard | `suit` |
| `Res` / `ARes2` / `MRes` | int | resources | `res` / `aRes` / `mRes` |
| `Infra` | float | infrastructure | `infra` |
| `ibon` | float | infra bonus | `ibon` |
| `Pop` | int | imperial pop | `pop` |
| `pbon` | int | imperial pop bonus | `pbon` |
| `PopC` | long | civilian pop | (SimPopG `popC` Int64) |
| `OID` | int | owner id (**= PID×16**) | `pid` (owner) |
| `PID` | int | derived player id | (OID/16) |
| `SRt/SRsc/SRtf/SRi/SRoh` | int | IO trade/ship/terraform/infra/overharvest | `rts.sRt/sRsc/sRtf/sRi/sRoh` |
| `Abdn` | short | abandon order | `abdn` |
| `Dstyd` | short | star annihilated | `dstyd` |
| `ltis` | short | last-time-seen | `ltis` |
| `VFlags/EFlags/AFlags/FFlags/GFlags` | int | state flags | `flags1.*` |
| `Bats2` | int | recent combat | `bats2` (Int64 in R1) |
| `nadct` | int | addicted (1=yes) | (in `adct` array) |
| `NumFlts/NumGFs/NumSnF/NumMnF` | int | fleets/gates/stations/monitors | `flts/gfs/snF/mnF` counts |
Note the **type disagreements** (verifier flags): R2 reads `Abdn`, `Dstyd`, `ltis` as **short (Int16)**
while R1 models them as framed `Int32SaveStruct`; R2 reads `Bats2` as int while R1 uses Int64. R2's
name-scan reads the value bytes directly after the tag+pad, so R2's width is the more literal
on-value-bytes claim for those specific fields; treat as "value is small, low bytes are the datum."
---
## 9. Trade & node grid [R1]
- `SimNodeGrid2` (complex): `ComplexArray<SimNodeGridPath> paths; Int32 nextId`.
`SimNodeGridPath` (complex): `Int32 npt, npid, npfr(from), npto(to), npctm, npcby, npdtn, npdtf, npenp, npuse, nptf`.
- `SimTradeManager` (complex): `NonComplexArray<SimTradeSector> tradeSectors; Float sctSize; List<SimTradeSectorRt> rt`.
`SimTradeSector` (leaf): `Int32 tradeId; SimTradeSectorTradeSaveStruct trade`.
`SimTradeSectorTradeSaveStruct` (complex): `SpatialCoordinate pos; Int32 tradeSectorGridId;`
`SimTradeSectorTradeCtrSaveStruct tsctr(3 floats); Int32 tssec, tsct, tscr, ptssec, ptsct, ptscr;`
`ComplexArray<...Fwarn {Int32 pId, ntrns}> fwarn; NonComplexArray<Int32> systems, tsflt`.
`SimTradeSectorRt` (complex): `Int32 tro, trfow, trfr, trfrs, trtow, trto, trtos, trtc`.
---
## 10. Fleets & ships [R1]
- `SimFleet` (leaf): `Int32 fltId; SimFleetDetails flt`.
- **`SimFleetDetails` (complex)**, ordered:
`SpatialCoordinate pos; Int32 pId, locId; FlightPlanContainer fplan (Bool hfPlan gate);`
`String ftName; Int32 ftTrans; SimFleetOrigin ftOrig (Int32 ×3);`
`Int32 ftFlag, ftae, ftpae, ftEnc, ftMs, perm; SpatialCoordinate prvPos;`
`LayContainer lay (Bool hLay gate); NonComplexArray<SimFleetShip> ships`.
- `SimFleetDetailsFlightPlan` (complex): `ComplexArray<Wpt> wpts; Float fPsp2; Int32 fPeta2;`
`Fpogn2 (3 floats); SpatialCoordinate fPdpos; Int32 pnd`. `Wpt`: `Int32 wpt, tp; Nrt {Int32 nrp,nrf,nrt}`.
- `SimFleetShip` (leaf): `Int32 shipId; SimFleetShipDetails ship`.
- **`SimFleetShipDetails` (complex)**, ordered:
`Int32 desId (design id), fltId, plrId; Float range; SimFleetShipHealth health (3 floats: command/mission/drive);`
`Int32 conCap, refCap, repCap, mineCap, plg, act; Boolean dep, atq; Int32 encId;`
`SimFleetShipPrish prish (Int32 prMax; NonComplexArray<Int32> prSp); Int32 lct, tsd, atsp, tblt;`
`Boolean hbq; SimFleetShipDetailsBq bq (gated by hbq);`
`Boolean hsp; SimFleetShipDetailsSp sp (gated by hsp; two SpPop pop/pPop);`
`NonComplexArray<SimFleetShipDetailsTh> th (Float th, thm)`.
`SimFleetShipDetailsBqOrd`: `Int32 desId, con, conleft, sav, ordId` (same shape as system BqOrd).
---
## 11. Combat reports & scenario/encounter objects [R1]
- `SimCrepSaveStruct` (combat report, complex): `Int32 cid, trn; SpatialCoordinate pos; Int32 sid, auto, dur, cow, cdst, cpk, cpt, cdt, cdi;`
`ComplexArray<SimCrepPrepSaveStruct> prep; ComplexArray<SimCrepWrepSaveStruct> wrep`.
`SimCrepPrepSaveStruct`: `Int32 plr; Bool ai; Int32 ally, status, mxeng, mxcls, mxmsl;`
`NonComplexArray<Cls> cls; NonComplexArray<Sec> sec; Int32 ndam; ComplexArray<Srep> srep`.
`SimCrepPrepSrepSaveStruct`: `String name; Int32 did, cls; Int64 caps2; Int32 nshp, nfld, nlst, dtak; SimDamsSaveStruct dams`.
`SimDamsSaveStruct`: `Int32 dams, damp, dami, damt`. `SimCrepWrepSaveStruct`: `String wep; SimDams dams`.
- `SimSvSctOb` (complex): `ScnObjStruct scn; NonComplexArray<SimSvSctObXscn> xscn; SimScSctObEncObjArray encObjs`.
`SimSvSctObXscn` = polymorphic-by-string (§1.6): `traps` (ComplexArray<TrapDetails {Int32 sys,pid,trenc,trgenc}>),
`gmtrigger` (Int32 gmch), `crowdefs` (Int32 sys; NonComplexArray<Int32> dsys,des; Float drad), `indsys`/default (empty).
- **`SimScSctObEncObjArray`** = grand-menace/encounter table, dispatched **by fixed index 0-8** (§1.6).
Each subclass carries that menace's state, e.g.:
- `EncInfest` (Hiver infestation): `NonComplexArray<Asg> asg; ComplexArray<Infest> infests; Int32 deshive, deslarva`.
- `EncHives`: `Int32 qDesignId; ComplexArray<Hive {Int32 hiveId,queenId,nextQ}> hives; ComplexArray<Queen {Int32 queenId,qDstId}> queens; ComplexArray<NestedInt32> sysMem`.
- `EncAsteroidMonitor`, `EncTD`, `EncWD`, `EncRsuc`, `EncDfts` (Von Neumann; large), `EncIni2`.
---
## 12. CdTable — combat / AI persistence block [R1 CdTableSaveStructures.cs]
### CdTable (leaf, top of the 4th file-section): `ComplexArray<String> cdt; CdPlayer cdplayer; CdAi[] cdai`.
### CdPlayer (complex) — **entirely reverse-unlabeled** (fields named `unknown1..35`); shape is known:
`Int32 unknown1(=16); Bool unknown2; Float unknown3; Bool unknown4; Int32 unknown4p5; Bool unknown5..8;`
`Int32 unknown9,10; NonComplexArray<CdPlayerUnknown11Item {Int32 unknownId, const1, const2, value1}> unknown11;`
`Int32 unknown12..14; NonComplexArray<Int32> unknown15,16; NonComplexArray<Unknown17Item{Int32 ×2}> unknown17;`
`NonComplexArray<Int32> unknown18; Int32 unknown19..21;`
`NonComplexArray<Unknown22Item{Int32×2,Bool}> unknown22; Int32 unknown23..35`.
### CdAi (complex) — per-AI-player planner state, ordered:
`AttributeSaveStruct aiAttr; NestedInt32 aiTurnPris; CdAiSit aiSit; NestedInt32 aiPlyHat;`
`ComplexArray<CdAiPrsUnknown {Int32 pid,trn}> prs2; Int32 dsh, nbStab, nmBlst; CdAiAidng aidng;`
`Int32 aiHivJ, sdFlT; NestedInt32 nalat; Int32 lnat, lat;`
`NonComplexArray<CdAiSys> aiSys; NonComplexArray<CdAiCmbr> cmbR; NonComplexArray<CdAiCl {Int32 clTn,clSyId,clPlId}> cl;`
`NonComplexArray<CdAiPrv {Int32 nPrvId; Float nPrvVa}> prv; NonComplexArray<Int32> tecs; Int32 fct;`
`ComplexArray<CdAiApr {Int32 sid,tn0,tn1}> apr`.
- `CdAiSit` (complex): `NestedInt32 aiSitSecs; NestedInt32 aiSitWepFams` (**weapon-family set** lives here — opaque ints).
- `CdAiCmbr` (complex): `Int32 crTrnK; Bool crPce; NestedInt32 crSys; CdAiCmbrCrplSv2 crplSv2`.
`...CrplSv2`: `Int32 rpBon, rpBonT, savBonus; Bool maintHf; ComplexArray<TacReport> tacReports`.
`TacReport`: `TrStruct trBy, trTo; TacReportDamage damageStruct; NonComplexArray<TacReportShips> ships`.
`TrStruct`: `Int32 treHd, treHi, treD, treDp, treDi, treDt, treB`.
`TacReportDamage` (leaf): `Int32 tRid, tRal, tRbal, tRlas, tRmis, tRmin, tRnrg, tRbio, tRbrd; Bool tRsld, tRsldd, tRsldc, tRsldi, tRsldr`
— **damage-by-weapon-family breakdown**: bal(listic)/las(er)/mis(sile)/min(e)/nrg(=energy)/bio/brd(=boarding); sld=shields.
`TacReportShips` (leaf): `Int32 tRships, tRsldr, tRshipL`.
---
## 13. Coverage gaps & contradictions
**Coverage (what's decoded):**
- R1 decodes essentially the *entire* file top-to-bottom: summary, create-params/map-gen, full sim
(players, tech, designs, systems/colonies incl. per-player fog-of-war snapshots, fleets, ships,
trade, node grid, combat reports, grand-menace/encounter objects) and the CdTable AI block. This is
the most complete community model and the primary Rosetta source.
- R2 decodes only Summary, Player Settings, Players, Species, and Planets (colony) fields, by tag
search — but adds *friendly semantics* and confirms the value-bytes width of several planet fields.
**Known-unknown fields (labelled `unknown*` in R1 — do NOT treat names as authoritative):**
- All of `CdPlayer` (`unknown1..35`) and `CdAiAidngDnId`, `SimFleetOrigin`, `SimFleetDetailsFlightPlanFpogn2`,
`SimTradeSectorTradeCtr`, `SimPlayerDesignDwgWgb`, `SimSystemDetailNvoIndcl*` bodies.
- `SimFleetShipHealth` three floats guessed as command/mission/drive.
- `PlanetSaveStruct.unknown1..4` (map-gen) unexplained.
**Not decoded / thin:**
- Tactical/real-time combat geometry: only *reports/summaries* are stored (SimCrep*, CdAiCmbr TacReport).
Per-ship in-battle positions/velocities are not in these editors (likely not in the sim save at all).
- `SimSystemDetailSpy` body is empty in R1 (marked "needs to be populated"); spy detail unresolved.
- RNG state (`RngSaveStruct.unknownData`) is an opaque ~2500-byte blob.
- R1 source comments flag `SimSystemDetailSpiesArray`, `SimSvSctObXscnXsc` and
`SimScSctObEncObjDetails` as incomplete/"wrong" in places — verify encounter bodies against binary.
**Contradictions between R1 and R2 (reconcile against binary):**
1. **Field widths on planet flags:** R2 reads `Abdn`, `Dstyd`, `ltis` as Int16 and `Bats2` as Int32;
R1 models `abdn/ltis` as Int32 and `bats2` as Int64. → The datum is small; check the true stored
width in the binary struct.
2. **PID vs OID:** R2 asserts `OID = PID*16` (owner id is player index << 4); R1 stores a single `pid`
owner field and does not model the ×16 relationship. → Confirm whether the binary owner field is a
raw index or a shifted/tagged handle.
3. **`_NPC` species id 4:** R2 names it `_NPC`; R1 comment guesses "AI Rebellion". Same numeric id 4,
different label — likely a shared "non-player/rogue" species slot.
4. R2 treats Players and PlayerSettings as separate flat tab regions bounded by marker strings
(`HomeSys`…`ISsp`, `Slot`…`Session`); R1 shows these are actually nested (settings inside the
Summary PlayerSlot, live player data inside SimPlayerDetails). R2's region boundaries
(`Summary`,`Slot`,`Session`,`HomeSys`,`ISsp`,`NumSys`,`NdGr2`,`PlayerIDs`,`DesignIDs`) are useful
**section-marker strings** to locate blocks in the raw binary.
**High-value binary-scan signatures:**
`0xBEEFBEEF` / `0x41104110` complex-struct brackets; length-prefixed ASCII field-name tags
(`[int32 len][name]`) preceding every named scalar; section marker strings above.