The engine has modelled the event log since lane E, but the standalone never wrote any of it into the save it produces. This wires the two together for the one event on the reference pair the standalone can compute, and corrects the condition. What a turn actually posts, measured over all eleven saves: two events on turn1 -> turn2 and three on turn2 -> turn3, and only two players in the whole corpus ever hold an event at all. Order is readable off the ids -- the build pass posts before the research pass. See docs/EV-events.md section 1. The condition had two of the original's three tests. The missing one is "no tech finished on this turn or later", and it is what keeps the event off the turn a tech lands; zuul-turn23 exercises it. The third input, whether the player holds a target at the moment of the check, is not on the wire -- three of the four real players acquire one during the turn, which is AI research selection -- so the operator's --ai-player roster stands in for it as a stated hypothesis and the phase stays blocked without it. No prose in the engine: the record's text is resolved through a caller-supplied lookup over the operator's own installed string table, and a run without a data root posts nothing rather than writing a record it cannot fill. Measured (CT111 host build, real data root), closed and regressed never netted: turn1 -> turn2 209 -> 127 closed 82 (+4 this lane), regressed 0 turn2 -> turn3 108 -> 69 closed 39 (+3 this lane), regressed 0 The posted record agrees with the oracle on all eight of its fields. Controls: with the roster withheld the phase over-fires and regresses 16 leaves on pair 1; with the string table withheld it posts nothing and regresses none. Gates run separately: tools/clean_room_check.sh OK; host ctest 50/50; CT111 shim cross-build OK (exports 66 names identical to binkw32.dll, staged in /srv/re-lab/shim/dist-ev); CT111 host ctest 50/50. |
||
|---|---|---|
| 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).