sots-re/findings/objects/save-editor-structs.md
alex 0dff4941bd events: recover the player event-posting API (lane E)
Closes B3's oracle gap and B2's known gap. Both milestones write into the
owner's event list; nothing modelled it, so their clean compares bounded the
economy fields only.

Container: EventStorage embedded at ServerPlayer+0x29c (0x1c bytes), verified by
ServerPlayer::GetEventStorage 0x0080db00 whose whole body is 'lea eax,[ecx+0x29c];
ret'. EvNxID at +0x14 == player+0x2b0, exactly the byte run the harness guard
reports. The list is a vector<TurnEvents{int EvTurn; vector<PlayerEvent>}> --
bucketed by turn, not flat, which the save-editor struct note had wrong.

Entry point: int __thiscall EventStorage::PostEvent(this, std::string BY VALUE,
std::string BY VALUE, void* obj, Vector3* pos, int turn, const char* img,
int act), 0x008862b0, RET 0x4c. 161 call sites in 110 functions: this is the
whole simulation's event API, not a research helper. Dedup is per turn bucket on
message/image/location/position/action but NOT summary; act 0 with no subject and
no position is stored as 2; EvNxID starts at 0 and is promoted to 1 on first post;
PruneOldTurns drops buckets older than turn-50 with an off-by-one that always
leaves one stale bucket behind.

Record: 0x74 bytes, EvEID(+4) EvDsc(+8) EvMsg(+0x24) EvLoc(+0x40) EvPos(+0x44)
EvImg(+0x50) EvAct(+0x6c) EvCID(+0x70) -- confirmed field by field against
turn3-state.sav, which contains the EVENT_RESEARCH_OVERBUDGET record itself.

B3's defect is fully explained: 0x00587b97, reached only from the
completion-roll-FAILED branch, under !wasDone && nowDone && owner.

Three note corrections: EvPos defaults to FLT_MAX, not infinity (writing +inf
changes the save bytes); the save array is turn-bucketed, not flat;
EVENT_TECHS_UNLOCKED has no 'parent researched' clause.

56 entries added to ghidra/addresses.json (header regenerated); 11 prototypes,
13 labels, 12 comments and 2 structs written back to the Ghidra project.
2026-09-08 02:47:11 -04:00

32 KiB
Raw Permalink Blame History

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: framed (has description + BEEFBEEF), body = Int32SaveStruct count then count × T.
  • NonComplexArraySaveStruct: 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>)   <-- SEE NOTE E1
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.

NOTE E1 — events is nested, not flat (corrected 2026-09-08, lane E). The R1 model SimPlayerEventsSaveStruct { Int32 evNxId; ComplexArray<SimPlayerEvent> } reads the array as a flat list of events. Its elements are turn groups. The real shape, read off the three serializers (EventStorage 0x00825cc0, TurnEvents 0x00825bb0/0x00825c40, PlayerEvent 0x00825970) and confirmed against verify/results/saves/turn3-state.sav:

Events (EventStorage, in memory ServerPlayer+0x29c, 0x1c bytes)
  EvNxID : int                                   -- starts at 0, not 1
  Events : n x TurnEvents (0x18 bytes each)
      EvTurn : int
      Events : m x PlayerEvent (0x74 bytes each)
          EvEID(+0x04) EvDsc(+0x08) EvMsg(+0x24) EvImg(+0x50)
          EvLoc(+0x40) EvPos{x,y,z}(+0x44) EvAct(+0x6c) EvCID(+0x70)

save_reader.py already parses this correctly (it falls into the generic tree), so no reader change is needed. Full API in findings/subsystems/events.md.


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 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.