From-scratch reimplementation of the Sword of the Stars (2006) engine. Clean-room; engine code only — bring your own game copy. Planning/RE evidence live in sots-re.
Find a file
alex 2d93404c67 SV: the script-object event bus, and what it writes in a turn
`SvSctOb` is not written by direct calls. Every update goes through an event bus: a
driver notifies the root object with an integer id, the root fans the delivery out to
every child, and each delivery is two steps -- a generic handler that takes the id, then
one event-specific vtable slot that does not. The id -> slot map is a 33-entry jump table
in the image, so which class reacts to which event is recovered and exhaustive rather than
inferred from what the saves happen to show. Five of the 33 rows are not in slot order,
including two the tail sends.

Three handlers write the eight leaves that diverged:

  * the slavers' difficulty tier, on the tail's end-of-turn delivery -- a three-record
    stack table scanned against the frame, boundaries 1/50/100, stored only on a change,
    and at frame 100 and above the scan runs off the end and stores nothing, so the tier
    can never reach 2;
  * the refugees' one-shot latch, on the turn-begin delivery, with a design instantiation
    behind the same latch that nothing here can do;
  * the swarm queen's hives, also at turn begin, registered on the systems carrying the
    SWARM's scenario tag (the queen's constructor stores 3 for that and 10 for its own
    encounter id) and then ticked -- and the tick is the whole explanation of a target
    turn that reads 31 after one turn and 32 after the next. It is not re-rolled; it slips
    forward by one every turn the spawn gates stay shut.

New host phase H03 for the turn-begin delivery, run right after the frame counter where
the original sends it, and tail phase T20 implemented. Rules are pure in game/sim.

Measured on CT111, closed and regressed stated separately:

  default                 turn1->turn2  209 -> 126 (was 128)  closed 83, regressed 0
                          turn2->turn3  108 ->  67 (was  69)  closed 41, regressed 0
  --commit-blocked=H03    turn1->turn2  209 -> 124            closed 87, regressed 2
                          turn2->turn3  108 ->  67            closed 41, regressed 0

Registering a hive closes the four leaves that say which systems have hives and that they
have no queens, and opens two carrying a target turn known to be wrong: the original draws
it from the strategic generator inside the turn-begin step, outside both turn drivers, and
neither the two data-file constants nor the generator's position there is settled. That
trade is a flag, not a default.

The prediction in docs/SV-script-objects.md was committed before the build, and P5 was
wrong: it called the second pair a null control, and the second pair is where the slip
rule is tested EXACTLY -- two hives, two target turns, both landing on the oracle with no
draw and no fitting.

Gates run separately: clean-room OK, host ctest 51/51, CT111 shim cross-build clean.
2026-09-08 16:46:25 -04:00
cmake m0: binkw32 proxy shim, minhook, trace hook on Application::Initialize; build/deploy tooling 2026-09-07 17:22:15 -04:00
docs SV: the script-object event bus, and what it writes in a turn 2026-09-08 16:46:25 -04:00
include/generated sync generated header (1138 entries) before G3 merge 2026-09-08 15:54:11 -04:00
src SV: the script-object event bus, and what it writes in a turn 2026-09-08 16:46:25 -04:00
tests SV: the script-object event bus, and what it writes in a turn 2026-09-08 16:46:25 -04:00
third_party mars/vfs: gob ZIP reader + native override, miniz 3.1.2; oracle-verified 8352+2035 entries 2026-09-07 17:40:51 -04:00
tools b4: colony + movement hooks; 22 formula corrections (growth curve has no capacity term, range margin +0.05f, ties-to-even rounding); 3 verified signatures 2026-09-08 00:57:55 -04:00
.gitignore init commit 2026-09-07 17:07:54 -04:00
CMakeLists.txt merge lane W2: watchpoints + multiplayer Tier 0 (CMake list and main.cpp union-resolved to keep both instruments; header regenerated) 2026-09-08 15:28:28 -04:00
CMakePresets.json m0: binkw32 proxy shim, minhook, trace hook on Application::Initialize; build/deploy tooling 2026-09-07 17:22:15 -04:00
CONTRIBUTING.md CONTRIBUTING: note uv tool install cmake as the local workaround 2026-09-08 09:07:47 -04:00
LICENSE init commit 2026-09-07 17:07:54 -04:00
README.md src/app: the standalone -- load a save, run a turn, write a save 2026-09-08 10:35:45 -04:00

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 + .effect readers (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 checks
  • mars/vfs — .gob (ZIP) archive reader + loose-file override; entry counts and bytes verified against unzip
  • mars/stream — the game's self-describing save/serialization format (reader, writer, typed shapes) + gzip; three real saves round-trip byte-identical
  • mars/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 reference
  • game/design — ship-design assembly/fit/tech-gating rules and derived stats; validates all 127 stock designs from real saves
  • app — the standalone: sots_turn loads 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 in docs/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 save
  • include/generated/sots_addresses.h — binary facts (RVAs/prototypes), generated from the RE repo
  • tests/ — host tests; real-data tests skip unless $SOTS_DATA_DIR is set
  • tools/ — build (MinGW i686 cross) and deploy scripts

Planning, findings, and verification evidence are tracked in the private RE repo (sots-re).