Closes five of lane AI2's open items and corrects two published claims. The stepping order: StrategyServer::ResumePlaying 0x007ddc90 walks the player array in INDEX ORDER and raises SEResumePlaying at each live player; the app callback StrategyApp::OnClientEvent 0x00838e10 delivers it inline for humans and appends it, deduplicated, to the pending queue for AI players. So the AI players are stepped in save player order, each at most once -- computable from a save with no live measurement. AI2's search missed it because 0x00b29f98 is not a pointer to the StrategyApp, it IS the StrategyApp: the enqueue writes the absolute member address 0x00b29fb4 and never materialises the object base. The enqueue also has zero direct callers and no vtable slot -- its address is stored into StrategyServer+0x170 by CreateGame. Lane B6's third blind spot, twice over. The two passes: `pass` is a tier index, not a plan/act switch. 0x006abb2a picks between two per-candidate quota fields -- tier 0 takes +0x10, tier 1 takes +0x14 -- and the hub loops `for (i = 0; i <= pass; ++i)`. Every order-emitting exit is gated pass == 1 (0x006bbd50, 0x006c16c0, 0x006cea50), so pass 0 claims each task's minimum force in priority order and WRITES NOTHING. That halves the ModCount arithmetic. Corrections: - AI2 §4.2: the nine "no order method" tasks are not planners. All nine emit orders at depth 4-9; the depth-4 cut hid it. AITColonize reaches list 7 and AITBuildPoliceShips reaches list 3, which is what their names promise. - AI2 §3: the two priority overrides' flag polarity is inverted. The tunable applies when bit 0 of +0x4 is SET, and "committed" is not a supported name. - AI2 §10.6: the .data invade tunables have no loader. 650 and 750, image constants, exactly one reader each and no writer anywhere. Also: slot 12 named (a preemption permission, 0x006a8d20), slot 13's consumer found (0x00696620, a range budget -- and the Zuul are exempt, a FOURTH independent cross-check on AI2's species reading), slot 11's dispatch located. ServerPlayer+0xf9/+0xfa -- the two bytes that decide whether a player is AI-controlled -- sit in a hole in the serialised layout and are NOT in the save; their writer is unfound and is the biggest remaining hole. Static only; nothing here has run under an instrument. 160 indirect call sites inside the AI closure are unresolved, so reachability is still a lower bound. Four predictions with falsifiers in §7. ghidra/addresses.d/lane-ai3.json: 15 entries, 1,105 -> 1,120, no duplicates, validated to a scratch path. |
||
|---|---|---|
| campaign | ||
| findings | ||
| ghidra | ||
| guides | ||
| notes | ||
| objects | ||
| scripts | ||
| tools | ||
| verify | ||
| .gitignore | ||
| README.md | ||
sots-re
Reverse-engineering worklog for Sword of the Stars (2006, SOTS1) — the 32-bit DX9 original + expansions. The nitty-gritty: static/dynamic analysis notes, Ghidra & ReVa scripts, function/struct maps, D3D9 call traces, decomp progress, findings.
Where this runs
Analysis lab on spicy (PVE, 192.168.3.201):
- CT111
sots-re— Linux workspace: Ghidra + headless ReVa server, radare2/rizin/cutter, binwalk. Hosts the Samba share and this repo's working tree at/srv/re-lab/notes. - VM140
sots-re-win10— Win10 runtime + dynamic analysis (x64dbg, Cheat Engine, RenderDoc/apitrace, DXVK→CPU-Vulkan for GPU-less rendering).
Infra (guests, storage, network, share, ReVa endpoint) is documented from the
system-maintainer POV in trikilli → services/re-lab.md. This repo is everything else.
Layout
findings/— the running findings log (append-only), one file per subsystem.ghidra/— exported scripts, data-type archives, struct definitions.traces/— D3D9 / Win32 API call captures + analysis.scripts/— helper tooling (loaders, extractors, parsers).notes/— session notes, scratch, hypotheses.
Ownership / legality
Game binaries come from the owner's own GOG/Steam copy. RE is for personal
interoperability, bug-fixing, and preservation. Binaries themselves are not committed
here (see .gitignore) — they live on the lab's Samba share /srv/re-lab/samples.
Campaign (how this repo is run)
A 4-agent crew (defined in ~/.claude/agents/re-*.md) runs the exploration:
re-quartermaster (backlog + board) → re-analyst (maps via ReVa) →
re-verifier (proves vs real data; old-vs-new differential once reimpl starts) →
re-scribe (files the note, links it, commits).
campaign/board.md— live status board (start here).- Live tracking = Forgejo issues on
alex/sots-re(labelsstatus/*are the kanban columns;type/*,conf/*).campaign/board.mdis the editable mirror — publish withscripts/forgejo_campaign.py bootstrap(needsFORGEJO_TOKEN). campaign/backlog.md— prioritized target queue.campaign/open-questions.md— unresolved threads.findings/_template.md— the record format every finding follows.findings/{objects,control-flow,subsystems}/— the growing engine map.verify/{parsers,traces,harness,results}/— validation: struct parsers now, golden-trace replay + shim compare-mode for reimplementation.ghidra/— exported scripts + datatype archives.
Approach & north star: findings/00-strategy.md. Binary facts: findings/01-fingerprint.md.
Sibling repo
alex/sots-engine — the from-scratch engine source (clean-room, public-capable). This repo keeps the
evidence + planning for both; binary facts cross over only via ghidra/addresses.json → tools/gen_addresses.py.
guides/re-windows-2000s-howto.md— annotated bibliography + how-to for RE of mid-2000s MSVC/DX9 Windows games, with our-experience call-outs.