Re-ran lane J's image-wide tempering-immediate scan at real instruction
boundaries and audited it site by site. Sixteen functions carry the two masks
inside a decoded instruction, 67 occurrences, and a brute byte scan finds zero
orphans -- recall is complete. Of the sixteen:
* ONE IS A FALSE POSITIVE. 0x008cca30 has no temper chain at all; the four
bytes read as the second mask are the rel32 displacement of a call.
* FOUR ARE RNG ENTRY POINTS, not game code -- the tempering there is the
primitive's own. Two were known; 0x004f7670 and 0x008e6e30 were not.
So the figure is ELEVEN game functions with inlined draws over 28 sites, not
fourteen. Exactly ONE of the eleven is reachable from StrategyServer::ProcessTurn
(0x007aa240, depth 4) and one more from OnAllCombatDone_Tail (lane J's R2). The
other nine are map setup, the lobby, the network layer and two scripted
encounters, all reached through vtable slots with no direct caller -- so their
absence from the turn closure is proved for direct edges only, and that caveat
is stated as loudly as the result.
Both functions lane J handed over are read completely from the instruction
stream:
0x004f7670 (84 B) is Mars::RNG::NextUInt -- ONE WORD, UNCONDITIONAL, no loop,
no branch but the lazy twist. ECX is the generator OBJECT, where NextFloat
and NextInt take the object PLUS FOUR; ProbabilisticJump uses both conventions
0x6b bytes apart. Ghidra's size is right here, which is worth saying.
0x007aa240 (Ghidra says 944; the body is 953 and ends past the reported range)
is the encounter-detection roll. ONE INLINED NextFloat PER (contact, detector)
TRIAL, drawn BEFORE the accept test, so a detector holding neither of two
specific techs still burns a word and can never succeed. The accept test is
`thresh >= r` -- equality accepts -- derived from the fcompp/test ah,5/jp
encoding rather than the mnemonic. The outer repeat-until-no-progress loop
cannot redraw a pair: the "tried" bitset is filled above the back-edge target
and never cleared, so the whole call is bounded by |contacts| x |detectors|.
Three more draw entry points nobody had listed: a float range (one word, and it
NARROWS TWICE), a triangular integer range (at least two words), and a
truncated-normal range whose two draws are BOTH inlined and which costs two
words per attempt with an unbounded attempt count -- and which scales by 2^-32
where NextFloat scales by 1/(2^32-1). Two divisors, one image.
RECONCILIATION, stated honestly. The complete draw-site inventory of the
ProcessTurn closure is 22 sites: 21 entry-point calls plus the one inlined site.
This lane adds two previously-uncounted sources to that list and accounts for
NONE of lane Z's 18-20 words per turn with certainty, because both new sources
are gated and neither has been measured. Lane J's prediction that these two
functions would explain the gap is NOT confirmed. What is now provable is the
negative: there is no twenty-third mechanism, so the 18-20 words are distributed
among exactly these 22 sites. The search space closes; the count does not.
Also corrects, in place: my own Ghidra comment claiming the masks are a Mars
variant of MT19937. They are the textbook masks applied before the shift
instead of after -- (y & 0xff3a58ad) << 7 == (y << 7) & 0x9d2c5680, verified
over 200k words -- so mars::rng was never wrong, but a scan for the textbook
constants finds nothing in this image.
Fragment validated by generating to a scratch path (797 entries, no duplicate
name); the tracked header is untouched while lane Z is in flight. Eight
prototypes, nine plate comments and eight pre-comments written back to Ghidra.
|
||
|---|---|---|
| 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.