Three bodies, one reason: the corpus grew from 22 saves to 43 and started carrying content the shapes did not name. ProjectName read `usnc` as a count of ONE item, on the strength of a comment saying "usnc is 0 in every save available". Game::SpecialProjectNameGen::Write (0x008147e0) says otherwise: each 88-byte record opens with a 32-byte table of per-suffix use counts, `usnc` is the number of NON-ZERO slots, and the loop then writes, for every non-zero slot, its INDEX as `usp` and its count byte -- movzx widened -- as `usc`. Both go through WriteInt, so both are i32 on the wire. Twelve corpus saves have one such slot and were round-tripping exactly 12 bytes short: one i32 item, 4 length + 3 tag + 4 value + 1 pad. `Sprj` is a polymorphic frame and both halves of its mapping are now measured. ServerPlayer::Write emits `SprjT` from the plain member at project+0x3c, and ServerPlayer::Read feeds that value to the factory at 0x008610a0, whose table at 0x008611e8 is 0 BackEngProject / 1 MonitorProject / 2 JewelsProject / 3 TechOfferProject -- and each of those constructors stores its own index back at +0x3c. Only SprjT 0 is exercised by any save; the other three arms are typed from the recovered schema and labelled as the hypotheses they are, and an unknown SprjT falls to rest() so it shows up as opaque coverage rather than being mis-read in silence. FieldTemplate carried its points because no save had ever put one on the wire. Six now do, so Game::FieldTemplate::Point is typed and bound. FTPPosX/PosY/Sqd read 0 in every observed point, so their i32 disk type is still the schema's word and not the corpus's, and the comment says so. All 43 saves now round-trip byte-identically; the wire-schema conformance test binds six new shapes with 0 MISMATCH and every one matching item for item. |
||
|---|---|---|
| cmake | ||
| docs | ||
| include/generated | ||
| src | ||
| tests | ||
| third_party | ||
| tools | ||
| .gitignore | ||
| CMakeLists.txt | ||
| CMakePresets.json | ||
| CONTRIBUTING.md | ||
| LICENSE | ||
| README.md | ||
sots-engine
A from-scratch, functional reimplementation of the engine behind Sword of the Stars (2006). Not a byte-for-byte decompilation: behavior-equivalent code, built up one verified piece at a time (OpenRCT2-style) until the tree can build the whole application on its own.
Bring your own game. This repository contains engine code only. Game data, assets, saves, and
the original binaries are never included; tests and tools read an owner-supplied copy via
$SOTS_DATA_DIR. See CONTRIBUTING.md for the clean-room rules.
Status
Phase 2. M0 done — the shim frontend (src/shim/, a proxy binkw32.dll the original game
loads) builds, deploys, hooks, and logs from a real game launch. Engine code accrues under
src/mars/ and src/game/; each module is oracle-verified against the owner's game data before merge:
mars/parse— brace-block +.effectreaders (1,531/1,531 files agree with the reference)mars/text— flat key/value tables, id manifests, CSV (64/64 files agree)game/sim— strategic formulas (economy, research, colonies, movement) as pure functions; 356 hand-computed checksmars/vfs—.gob(ZIP) archive reader + loose-file override; entry counts and bytes verified againstunzipmars/stream— the game's self-describing save/serialization format (reader, writer, typed shapes) + gzip; three real saves round-trip byte-identicalmars/rng— MT19937 with save-state load/store; layout confirmed against real saves, draw mappings read off the binary (docs/mars-rng.md)game/data— typed catalogs (weapons, ship sections, turrets, id registries, tech tree, strings) with cross-reference checks; 229k values agree with the referencegame/design— ship-design assembly/fit/tech-gating rules and derived stats; validates all 127 stock designs from real savesapp— the standalone:sots_turnloads a save, runs one strategic turn over the published phase order of all three turn drivers, and writes a save. 14 of the 44 turn-driver phases are modelled; every phase that is not appears in the run log as a named no-op. How far it is from the byte-match, and what stands in the way, is indocs/S-standalone.md
Build: cmake --preset host && cmake --build --preset host && ctest --preset host (Linux);
tools/sync-build.sh cross-builds the shim on the lab box and stages it for deployment.
Layout (grows with the work)
src/shim/— binkw32 proxy + hooks + old-vs-new compare harness (frontend #1)src/mars/,src/game/— the engine and game reimplementation (accruing)src/app/— the standalone turn driver (frontend #2): load a save, run a turn, write a saveinclude/generated/sots_addresses.h— binary facts (RVAs/prototypes), generated from the RE repotests/— host tests; real-data tests skip unless$SOTS_DATA_DIRis settools/— build (MinGW i686 cross) and deploy scripts
Planning, findings, and verification evidence are tracked in the private RE repo (sots-re).