AS: the spy detection roll fires -- 1 word, gated on a usable asteroid belt; spies2 closed

Mars::RNG::Chance at 0x00887c8a, in the BODY of ServerSpyManager::vslot13 (tail
phase 23 call 9), measured at 1 call / 1 word / no_draw_calls=0 / strategic=true
on VM144 at probes=8, with OnAllCombatDone_Tail going 363 -> 364 against 0 on
every turn any lane has measured. The entry probe on SpyManager::Slot13RngCallee
read 0 on the same turn -- that zero was never about the subtree.

Predicate, decoded to save fields: spy.deat != 0 && spy.sdet == -1 &&
sys(deat).ARes2 > 0 && (sys(deat).TerrFl & 1). Corpus count 0 of 22; the failed
conjunct is deat != 0, not the belt -- all 22 corpus saves already had one.

Also: ServerSystem::AddSpy 0x007514c0 writes deat and pushes the spy id into
ServerSystem+0x1cc in the same two instructions, so spies2 is the per-system
DEPLOYED-spy vector; measured non-empty for the first time in the corpus.
And 0x0078c97f, a strategic NextFloat in the SHIPACTION_DEPLOYSPY handler, is in
no existing inventory -- decoded, not measured.
This commit is contained in:
alex 2026-09-09 00:10:49 -04:00
parent 712d184599
commit 4a212e25b4
7 changed files with 884 additions and 1 deletions

View file

@ -0,0 +1,694 @@
# The spy detection roll — decoded, its gate turned into a save predicate, and measured
- **Type:** subsystem (static decode + live measurement)
- **Owner / date:** lane AS · 2026-09-09 · guest **VM144** (`sots-re-win10-144`, `re@192.168.10.144`)
- **Instrument:** lane Z's `draw_sites` return-address ledger at **`probes=8`**, shim build
`as-c172c99-20260909T0205Z`, dist `C:\SOTS\shimdist-as`
- **Predictions, committed before the build:** `sots-engine` `docs/AS-predictions.md`
(commit `c172c99`, addendum `61875a4`)
- **Answers:** the resolution `2026-09-09-tail-draws.md` §8 item 2 (the spy half of the tail gate)
- **Corrects:** the resolution's "the detection roll is inline in `vslot13`" (it is a direct call);
lane L3 §6's "`spies2` is not the spy list"; board row 399's V2 misquote is *not* touched here
*(Sections 1–3 were written and committed before any measurement. Sections 4 onward are the
measurement.)*
---
## 0. Headline
> **The detection roll fires, it costs one word, and its gate is an asteroid belt.**
>
> `Mars::RNG::Chance` at **`0x00887c8a`** — in the body of `ServerSpyManager::vslot13`, phase 23
> call 9 of 9 of `OnAllCombatDone_Tail` — rolls once per deployed, undetected spy per turn, on the
> strategic generator, at a probability `sdo` that starts at **0.0084 as measured** and accumulates
> by the same amount every turn. `OnAllCombatDone_Tail` went **363 → 364**: one word, against 0 on
> every turn any lane has ever measured.
>
> The predicate is `spy.deat != 0 && spy.sdet == -1 && sys(deat).ARes2 > 0 && (sys(deat).TerrFl & 1)`.
> **Corpus count: 0 of 22.** The failed conjunct is **`deat != 0`** — every spy this campaign has
> ever built was still docked to its tender. Not trade routes, not freighters, not `spyon`, and not
> the asteroid belt: **all 22 corpus saves already had a usable belt somewhere**, and two of them
> already had four AI-owned ones.
>
> And the polarity is the opposite of the intuitive one: **when the belt is gone the roll does not
> happen and the spy is detected anyway.** A zero at this site is two different results.
---
## 1. The chain, decoded from the instruction stream
Program `sots` / "Sword of the Stars.exe", ImageBase `0x00400000`, all addresses VAs. Every body
below was disassembled from its start **to the next function start** (rule 17); no Ghidra size was
used. ReVa/CT111's MCP endpoint was down for this session (`CONNECTION_CLOSED`), so nothing was read
in the decompiler — this is all instruction stream, which for these bodies is a feature.
`Game::ServerSpyManager::vslot13` = **`0x008877b0`** (lane V2's name), dispatched at `0x007d9811`
from `OnAllCombatDone_Tail` on receiver `S+0x15c` slot 13. Body `0x008877b0 .. 0x00887f30`.
Throughout: `ebx = 0`, `edi = this` (the manager), `esi` = the current `Game::SpyCraft`. The loop is
over the vector at `this+0x10 .. this+0x14` — the same vector lane L3 watched grow to 1 at
**Build Spy**.
```
0088781d je 0x887e92 G1 spy.deat (+0x10) == 0 -> next spy
0088782d call 0x8b9240 sysA = registry(server+0x84, spy.deat) -> [ebp-0xbc]
00887845 call 0x8b9240 ownerO = registry(server+0x84, spy.sown) -> [ebp-0xc0]
00887856 je 0x887c5b G2 spy.sdet (+0x40) == -1 -> BRANCH D (detection)
00887867 jl 0x887e92 (server.turn - sdet) < 3 -> next spy
0088787a je 0x887c4f sysA.PID == 0 -> sdet := -1, next spy
00887891 je 0x887c4f sysA.PID == ownerO -> sdet := -1, next spy
00887af2 call 0x8408e0 <-- P, the false-flag draw (three sites inside)
```
**Branch D — the detection roll:**
```
00887c5b push eax ; ownerO
00887c62 push [ebp-0xbc] ; sysA
00887c63 push esi ; the spy
00887c66 call 0x81f570 D1 SpyCraft_AccumulateDetectionOdds -> writes spy.sdo (+0x3c)
00887c6b mov ecx,[ebp-0xbc]
00887c71 call 0x743f80 D2 t = ServerSystem_BeltUsableFlags(sysA)
00887c76 test al,1
00887c78 je 0x887c97 (t & 1) == 0 -> SKIP THE ROLL, straight to D4
00887c7a mov ecx,[edi+8] ; the StrategyServer
00887c7d fld [esi+0x3c] ; spy.sdo
00887c80 mov ecx,[ecx+0x16c] ; THE STRATEGIC GENERATOR
00887c86 push ecx
00887c87 fstp [esp]
00887c8a call 0x8e6dd0 D3 Mars::RNG::Chance(strategic, sdo) <-- THE DETECTION ROLL
return address 0x00887c8f
00887c8f test al,al
00887c91 je 0x887e92 roll failed -> next spy, still undetected
00887c97 mov edx,[edi+8]
00887c9a mov eax,[edx+0xc] ; the current turn
00887c9d mov [esi+0x40],eax D4 spy.sdet := turn (DETECTED; then the event strings)
```
### 1.1 It is a direct call, not an inlined draw — the instrument advice was right for the wrong reason
The resolution that created this lane says the roll is *inline* in `vslot13`. It is not. The bytes
are `e8 41 f1 05 00` — a plain `E8 rel32` to the `Chance` **entry point** `0x008e6dd0`. Nothing
resembling rule 16's inlined tempering sequence is present.
What *is* true, and is the whole reason `draw_sites` is the right instrument, is that the call sits
in the **caller** rather than in `SpyManager::Slot13RngCallee 0x008408e0`. An entry probe on
`0x008408e0` therefore reads zero whether or not the roll fires — but that is instrument *choice*,
not inlining, and the distinction matters because an inlined draw would be invisible to
`draw_sites` too, and this one is not. Lane AG read this independently and reached the same
conclusion; the correction is recorded in both places.
### 1.2 The gate is an asteroid belt, and it is sixteen bytes
```
00743f80 cmp dword ptr [ecx + 0x6c], 0
00743f84 mov eax, dword ptr [ecx + 0x19c]
00743f8a jg 0x743f8f
00743f8c and eax, 0xfffffffe ; clear bit 0 when [ecx+0x6c] <= 0
00743f8f ret
```
On `Game::ServerSystem` (`objects/layouts.md`): **`+0x6c` = `ARes2`**, **`+0x19c` = `TerrFl`**,
`+0x100` = `PID` (which `ServerSystem_GetOwner 0x007437e0`, used three times above, returns). So
> **bit 0 of the return = `(TerrFl & 1) AND (ARes2 > 0)` — "this system still has a usable
> asteroid belt".**
`ARes2` is already the field lane AC identified as the asteroid-belt resource (values 1500–3000,
matching `SYSTEM_MIN/MAX_ASTEROID_RESOURCES`). `TerrFl` bit 0 is its flag; §2 checks that reading
against the corpus, which is the only place a static reading of an unnamed bit can be checked
without a debugger.
**The polarity, stated because everyone's intuition gets it backwards.** The branch is a `je` that
jumps **past** the roll. Belt usable ⇒ roll. Belt gone ⇒ **no draw at all, and `sdet := turn`
unconditionally.** The spy hides in the belt; if the belt is mined out it has nowhere to hide and is
spotted for free. So at this site, a `draw_sites` row with `calls = 0` and a `sdet` that has moved
is a *positive* result about a different arm, and must never be reported as "the roll did not fire".
### 1.3 `sdo` — where the probability comes from, and why the roll should cost a word
`0x0081f570` (`SpyCraft_AccumulateDetectionOdds(spy, sys, spyOwner)`, `__stdcall`, `ret 0xc`, 512
bytes) is called **unconditionally** on branch D, immediately before the gate, and writes
`spy.sdo (+0x3c)`:
* if `sys.PID == 0` (unowned): `fldz; fstp [esi+0x3c]` — **`sdo := 0.0` and return**. A spy at an
unowned system rolls at p = 0, which costs no word.
* otherwise it walks the system's fleet vector accumulating three counters (`[ebp-4]`, `[ebp-8]`,
`[ebp-0xc]`) and computes, with every literal read as the **four bytes in the image** (rule 23 —
these are widened floats, not decimals):
```
p = 0.01f (0x009e31c0)
p += (countA > 0) ? 0.01f : 0 (0x009e3e14)
p += (countB > 0) ? 0.02f : 0 (0x00a1d048)
p += 0.001 * countC (0x009e23c0)
p *= (spyOwner->[0x5c] == 5) ? 0.75f (0x009e5ac4)
: (spyOwner->[0x5c] == 6) ? 0.5f (0x00a2c788)
: 1.0f
p *= 0.7 (0x009e5df8)
sdo = min(sdo + p, 1.0) (0x009e1ef0)
```
Two properties matter more than the arithmetic. It **accumulates** onto the previous `sdo`, and it
is **clamped at 1.0**. `Mars::RNG::Chance` costs **0 words at `p <= 0` and at `p >= 1`** (board row
367). So:
* a fresh deploy at a quiet foreign system gives `sdo = 0.7 × 0.01 = 0.007` — strictly inside
(0, 1), so the roll costs **exactly one word**;
* `sdo` climbs by ~0.007/turn, so a spy left in place for ~143 turns reaches `p >= 1`, at which
point the site **costs nothing and always succeeds**. That is a second way a zero at this site is
not a negative, and it is the same shape as lane AG's `cmo` early-out.
*(Measured afterwards: **0.0084**, i.e. `0.7 × (0.01 + 0.001 × 2)` — `countC = 2` rather than the
0 assumed here. The one-word conclusion is unaffected and the form of the expression reproduced
exactly. §4.2a.)*
### 1.4 What a deploy actually writes — and one draw site nobody had
`ServerSpyManager::DeploySpy` = **`0x00887410`**, vftable `0x00a3073c` slot 7, zero direct call
sites, reached only from the `SHIPACTION_DEPLOYSPY` handler `0x0078c930` through
`[[dispatcher+0x15c]+0x1c]` — the stack-built ship-action function-pointer table that lane B6
identified as a distinct indirection class. On the success path it writes:
```
spy->Reset() (0x00838070)
spy->sdo (+0x3c) := 0.0f
spy->sdet (+0x40) := -1
spy->cbh (+0x18) := <the float argument>
spy->tdep (+0x14) := server->[+0xc] ; the current turn
spy->atto (+0xc) := 0 ; carrierShip->[+0xa8] := 0 ; detach from the tender
ServerSystem_AddSpy(system, spy) at 0x008874d4
```
and `AddSpy` is where `deat` is born (§1.5). Because the deploy leaves `sdet == -1` and `sdo == 0`,
**the detection branch is entered on the very next `OnAllCombatDone_Tail`** — which is the same End
Turn that applied the order, since `ApplyAllTurnCommands` runs before the tail.
The float argument is computed in the handler, and it is a draw:
```
0078c975 mov ecx,[esi+0x16c] ; esi = StrategyServer -- THE STRATEGIC GENERATOR
0078c97c add ecx,4 ; the +4 sub-object, same one the trade-raid roll uses
0078c97f call 0x47d830 ; Mars::RNG::NextFloat <-- 1 word, ret 0x0078c984
0078c984 fld qword [0x009e21b0] ; 6.283185482025146 == 2*pi
cbh = NextFloat() * 2*pi
```
> **`0x0078c97f` is a strategic-generator draw site in no existing inventory.** It is not one of
> lane V2's eight, and it is not in lane I's 22, because it hangs off the ship-action table and no
> call-graph sweep reaches it. It fires **once per Deploy Spy order applied**, inside
> `ApplyAllTurnCommands` — so it lands in the `ProcessTurn` bracket, not the tail's. Any gate-indexed
> audit (lane AG) should carry it, and its predicate is "a `SHIPACTION_DEPLOYSPY` command in the
> turn's command stream", which is a *stream* predicate rather than a save predicate — a category
> the audit does not yet have a column for.
### 1.5 `deat` and `spies2` are written by the same two instructions — `spies2` is closed
`ServerSystem::AddSpy` = **`0x007514c0`**, 48 bytes, complete:
```
007514c3 mov edx,[ebp+8] ; the spy
007514c6 test edx,edx / je ret
007514ca test ecx,ecx / jne
007514ce xor eax,eax ; this == NULL -> store 0
007514d2 mov eax,[ecx+4] ; the system's own registry handle
007514d5 mov [edx+0x10],eax ; *** spy->deat = system handle ***
007514d8 add edx,4 ; &spy->sid
007514db push edx
007514dc add ecx,0x1cc ; &system->spies2
007514e2 call 0x59f1a0 ; push_back (lane AI3's wrapper)
```
and its exact mirror `ServerSystem::RemoveSpy` **`0x0074f550`** sets `spy->deat = 0` and erases
`spy->sid` from the same `+0x1cc` vector. `ServerSystem +0x1cc` is `spies2` in `objects/layouts.md`.
`AddSpy` has exactly one caller (`0x008874d4`, inside `DeploySpy`) and no vtable slot, so **this is
the only way `deat` ever becomes non-zero at runtime.**
> **`spies2` is the per-system vector of DEPLOYED spy ids**, and `deat` is its reciprocal
> back-pointer. They are written by the same function, two instructions apart.
>
> - Lane L3 §6: *"`spies2` is therefore not the spy list"* — **half right and worth correcting in
> place.** It is not the spy *manager's craft* list; it is the *system's deployed-spy* list. L3's
> inference from "this save has a spy and `spies2` is still empty" was sound about what `spies2`
> is not, and the alternative it offered second ("or it only fills for a deployed spy") is the
> right one.
> - Lane AC's P4 (*"fills only for a deployed spy"*) is **confirmed, and now from the writer rather
> than by elimination.**
>
> This retires one third of lane W's rule-6 flag on `spies2` / `SysMem` / `mts` **statically**, and
> §5 exercises it live.
### 1.6 `vslot14`'s roll is a different gate — and it excludes independent colonies
For the record, because target choice depends on it. `ServerSpyManager::vslot14` `0x0088db80`
(phase 33) loops the same vector and requires `spy.deat != 0`, `sys.PID != 0`, and
**`sys.indi (+0x1c8) == 0`** (`ServerSystem_IsIndependent 0x00743fa0`) — *not* an independent
colony — plus `spy.cm (+0x24)` in 1..4, before its `Chance` at `0x0088dc43`. Lane AG has decoded
that site fully; the consequence here is only that **an AI-empire target exercises strictly more
than an Independent Colony**, which is why this lane went looking for an AI-owned belt rather than
taking the easy NPC one.
---
## 2. The corpus, counted before measuring (rule 28b)
`verify/save-reader/save_reader.py --json` over all **22** saves in `verify/results/saves/`.
| | count |
|---|---|
| saves with any `spymgr.spy` record | **3** (L3's turn-15, AC's turn-20 and turn-22) |
| of those, `deat != 0` | **0** |
| saves satisfying the full detection-roll predicate | **0 of 22** |
| **the failed conjunct** | **`spy.deat != 0`** |
The integrator's value-domain census (`value-domain-census.md`) reaches the same 0 independently and
more strongly: `deat`, `sdet`, `sdo`, `cbh`, `cm`, `cmo` are all in the 234 fields that have only
ever held one value, so the count is 0 **by construction**.
**Two things fell out of the same pass, and the first is the load-bearing one.**
* **`ARes2 > 0` and `TerrFl & 1` agree on every one of 616 system records** (22 saves × 28 systems),
with no exception in either direction. That is a save-side confirmation of §1.2's reading of an
unnamed flag bit, obtained without a debugger, over a sample large enough to be worth something.
It got stronger on the state this lane built (§4): there, `TerrFl` also takes the values **2** and
**3**, and `ARes2 > 0` still coincides exactly with `TerrFl & 1` — so bit 1 is a second,
independent terrain feature and bit 0 is not merely "TerrFl is non-zero".
* **The belt was never the missing conjunct.** **All 22** corpus saves have at least one system
with a usable belt (5 to 15 of 28), and **two of them — `human-turn2-orders.sav` and
`human-turn3-noderoute.sav` — already have four AI-owned belt systems.** Lane AC's map had none,
and the campaign generalised from it. Those two saves are `PTech 0 / ResM 1.0`, so the spy tech
chain is tens of turns away and they were not a usable shortcut; but "no AI colony can have a
belt" was a fact about lane L3's one map, exactly the rule-28 practice-3 trap.
---
## 3. Predictions
Committed to `sots-engine` `docs/AS-predictions.md` at `c172c99` before the build, with the
addendum at `61875a4` before the measurement. Summarised here; the falsification table is in that
file.
| | prediction |
|---|---|
| **P1** | an AI-owned belt system exists within ≤ 5 map regenerations at lane L3's custom settings |
| **P2** | the predicate of §2; corpus count 0 of 22; failed conjunct `deat != 0` |
| **P3** | the roll fires: one `draw_sites` row at `ret_rva 0x00887c8f`, `Chance`, `calls = 1`, `strategic = true` |
| **P4** | it costs **1** word; `no_draw_calls = 0` |
| **P5** | the tail bracket goes 0 → **1**; the trade half contributes 0 (`tscr` 252, no fleet at a sector node) |
| **P6** | `sdet` most likely stays −1 (p ≈ 0.993); **`sdo` moves off its corpus-constant 0 to ≈ 0.007**, and that is the save-side confirmation |
| **P7** | `spies2` non-empty in the target system only |
| **P8** | the `hooks=off` pair reproduces byte-identically in two fresh processes |
| **P9** | `probes=8` is byte-neutral on this workload too |
---
## 4. The workload — built from a cold main menu in one session
Shim build **`as-c172c99-20260909T0205Z`**, cross-built on CT111 in this lane's own directory
`/srv/re-lab/build/sots-engine-as` (`rm -rf build-host build-shim` before configure — rule 24),
staged to `/srv/re-lab/shim/dist-as`, deployed to `C:\SOTS\shimdist-as` on VM144. Exports checked
identical to the real `binkw32.dll` (66 names).
**Guest adoption check, first, before anything else.** `ref-turn2.sav`, one End Turn, `hooks=off`,
this lane's own build:
```
(Autosave EndTurn).sav bb4fd9ac89f41e3bc0db2af08b18ce83417521ac4bcee695fc9fa6ce16e30948 66,732
(Autosave).sav 978041acd168b56ed8eb3f5e42e78d5e70eae6e6517d75e659a5eb7ca3d60921 67,219
```
Both **identical to the published oracle** in `verify/results/saves/determinism-hashes.txt`. The
binary and the guest are sane.
### 4.1 The map — zero regenerations were needed
New custom game at lane L3's settings (28 stars, 5 ly, 2 players Human vs Tarkas, treasury
9,850,000, **10 initial colonies, 15 initial technologies**, econ 148 %, research 146 %), species
set explicitly to Human on the launch card (it defaults to Random — L3's note holds).
**The first map generated had two AI-owned belt systems.** The brief expected several
regenerations; P1's estimate of ≤ 5 was never tested because none were needed.
| SysID | name | PID | `ARes2` | `TerrFl` | note |
|---|---|---|---|---|---|
| **400** | **Kepler** | **32 (AI)** | **2435** | **1** | the target — 3.5 ly from my colony Midway |
| 224 | Delta Pavonis | 32 (AI) | 1537 | 1 | the spare |
| 272 | Solaris | 0 | 2773 | 1 | unowned — `sdo` would be forced to 0 here (§1.3) |
| 160 / 480 | Piscium / Pascal | 16 (me) | 2448 / 2522 | 1 | mine |
| 336 | Copernicus | 0 | 2359 | **3** | bit 1 also set |
| 128 | Downbelow | 0 | 1965 | 1 | |
| 384 / 448 / 464 | Nunki / Mu Bootis / Jinx | — | **0** | **2** | **bit 1 set, bit 0 clear, no belt** |
That last row is the one that matters for §1.2. In the whole 22-save corpus `TerrFl` had only ever
been 0 or 1, so "bit 0" and "non-zero" were indistinguishable. This map has three systems at
`TerrFl = 2` with `ARes2 = 0` and one at `TerrFl = 3` with `ARes2 = 2359`. **`ARes2 > 0` still
coincides exactly with `TerrFl & 1`, and now it is bit 0 specifically, not "TerrFl is set".**
### 4.2 The path to a deployed spy, with what each turn cost
The four techs came faster than lane L3's recipe suggested because `CCC_FTLBrdB` was in the
15-technology roll again (it appears to be common) and because the top-of-screen
**Savings ↔ Research slider at its right end turns a 2-turn tech into a 1-turn tech.**
| turn | what |
|---|---|
| 2 | new game; research `CCC_SpyBm` (`CCC_FTLBrdB` already researched from the initial roll) |
| 4 | `CCC_SpyBm` done; research `IND_OrbFound` |
| 6 | done; research `IND_CruisCon` (2 turns → 1 with the research slider at max) |
| 7 | done; research `IND_SlvgTech` |
| 8 | **all four techs**; design a Cruiser with the `Repair and Salvage` mission section; queue it |
| 9 | the tender exists; fleet panel's `Special` → `Build Spy` → `Select All` → `OK` |
| 10 | **the spy craft exists**: `nspy 1`, `deat 0`, `sdet -1`, `sdo 0`, `atto` = the tender — the corpus state |
| 10 | `Move` the fleet to Kepler (`Dest: Kepler, 4 Turns`) |
| 14 | **fleet arrived**: `Flt.LocID = 400`, read from the autosave, not the panel |
| 14 | `Special` → **`Deploy Spy`** — the menu entry is present, which is itself the belt gate answering |
| 15 | order applied. **`deat = 400`, `sdet = -1`, `sdo = 0.0084`, `spies2 = [1]` at Kepler** |
Two things about turn 15 are worth stating separately, because they are results rather than steps.
**(a) `sdo = 0.0084`, and that is §1.3's formula evaluated, not fitted.** The prediction was 0.007
for a quiet system (`0.7 × 0.01`). The measured value is `0.7 × 0.012 = 0.0084`, i.e. `countC = 2`
in the third term. So the *form* — a 0.7 multiplier over a 0.01 base plus 0.001 per unit of a
counter — reproduces exactly, at a value no run of this campaign has ever produced, on a state
nobody had built. The identity of `countC` is not established (it is 2 at a system holding one
foreign colony and my one-ship fleet); the constant and the shape are.
**(b) `spies2` filled, in exactly the system the writer says.** Reading all 28 systems of the
turn-15 autosave: `spies2` is `count = 0` in 27 of them and `count = 1, element = 1` at **Kepler**,
where `1` is the spy's `sid`. This is the **first non-empty `spies2` in the corpus** and it matches
`ServerSystem::AddSpy` instruction for instruction (§1.5).
---
## 5. The measurement
All three runs load the **same file**, `MyGameas1spydep.sav`
(sha256 `854a10fa1ea602f0f7909f9bf9154942e56ea5fa1db4cce47d6d32bf53952e08`, 76,705 B, turn 15,
`--strict` clean, 0 errors / 0 warnings), in a **freshly launched process**, and press End Turn
**once**. Repo copy: `verify/results/saves/as-turn15-spydeployed.sav`.
### 5.1 The oracle pair (rule 26) — and what it says honestly
| run | config | `(Autosave EndTurn).sav` (the input, re-saved) | `(Autosave).sav` (post-turn) |
|---|---|---|---|
| **A** | `hooks=off` | `98e45d3745b91450…` 76,706 | `262f8bda97c9511d…` 77,685 |
| **B** | `hooks=off`, fresh process | `98e45d3745b91450…` **identical** | `e34775a757e6fb3d…` 77,649 **differs** |
| **M** | `probes=8` | `98e45d3745b91450…` **identical** | `262f8bda97c9511d…` **identical to A** |
**The pre-turn save is byte-identical in all three**, which proves the input state — including any
pending order — was the same in every run. The post-turn saves are not.
**A vs B differs in exactly 22 leaves, and every one of them is inside one AI player.**
`state_checksum --no-audit` localises them:
```
/Summary/Checksum 985948837 -> 985948925
/Sim/turnstats/history/hist[1]/stats[15]/tch 46 -> 45
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/St[62] 2 -> 3
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TResDone[62] 0 -> 22469
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/St[64] 4 -> 2
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TResDone[64] 22469 -> 0
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TAcq[64] 16 -> -1
/Sim/players/Player[32 "Revenge Fleet"]/TechTree/TiAcq[64] 45 -> -1
/Sim/players/Player[32 "Revenge Fleet"]/ResTNm '' -> 'DRV_NodFoc'
… plus that player's Events/EvNxID/otch bookkeeping for the same pick
```
In A the AI completed `Overthrusting`; in B it is part-way through `DRV_NodFoc`. **Nothing else
moved** — not one leaf of my empire, not `deat`, `sdet`, `sdo`, `spies2`, `trdmgr` or any fleet.
That is precisely the mechanism `2026-09-08-ai-seed-per-process.md` established: each AI
`StrategyClient`'s generator is seeded with a word that differs in every process, so its research
pick is a per-process outcome. Board row 326 recorded the same signature on
`turn1-state → turn2` — *"one shadow empire's research pick plus the derived checksum"*.
**So the control agrees with itself modulo the known non-deterministic leaves, which is the bar the
brief set — and it is a weaker bar than byte-identity, which must be said plainly.** What is
established is: on this input, in two fresh un-instrumented processes, every leaf outside one AI
player's research bookkeeping reproduced exactly. What is **not** established is byte-identity, and
this workload cannot establish it until the client seeds are pinned (the resolution's Rung C).
**And the instrumented run M came out byte-identical to A.** Read carefully, that is:
* strong for the thing that matters — `probes=8` changed **no leaf** of the sim, the spy, or my
empire, on the same input, which is the byte-neutrality claim P9 makes;
* and, for the AI-pick leaf specifically, a **coincidence of size 1/k** (rule 26's corollary): M
happened to draw the same AI seed outcome as A. It is not evidence about the instrument in that
one leaf class, and I am not treating it as such.
### 5.2 The instrument was armed, and one probe reading zero is the point
`shim.log`: seven draw-site detours `create=MH_OK enable=MH_OK`; `probe: installing 8 of 12`; all
eight `MH_OK`; **no `COVERAGE:` line**. `draw_site_overflow = 0`.
| probe | entries this turn |
|---|---|
| `Game::ServerSpyManager::vslot13` `0x008877b0` | **1** |
| `Game::ServerSpyManager::vslot14` `0x0088db80` | 1 |
| `Game::ServerTradeManagerImpl::vslot13` `0x0088ef80` | 1 |
| `Game::ServerTradeManagerImpl::vslot15` `0x0082cca0` | 1 |
| **`Game::SpyManager::Slot13RngCallee` `0x008408e0`** | **0** |
| `Game::TradeManager::Slot13RngCalleeA` `0x00820ca0` | 0 |
| `Game::TradeManager::Slot13RngCalleeB` `0x0088b440` | 0 |
| `Game::ServerTradeManager::CreateRaidEncounter` `0x008938a0` | 0 |
| indices 8–11 (`GenerateTradeRaidEncounters`, three controls) | **NOT INSTALLED** — `probes=8` installs 0–7 |
> **That table is the lane's methodological point in one line.** The detection roll fired, and the
> entry probe on `SpyManager::Slot13RngCallee` read **0** on the same turn. Four lanes read that
> zero and concluded the spy subtree was draw-free. It was never a statement about the subtree; it
> was a statement about which function the call sits in.
### 5.3 The detection roll — it fires, and it costs one word
`draw_sites`, the post-turn autosave marker, the row verbatim:
```
entry = Chance
ret_rva = 0x0048_7c8f (VA 0x00887c8f — the instruction after `call 0x8e6dd0` at 0x00887c8a)
calls = 1
words = 1
no_draw_calls = 0
strategic = true
```
and the same site in the per-call-site report, with the owner resolved:
```
* STRAT Chance call 0x00887c8a <ServerSpyManager::vslot13>+0x4da calls=1 words=1
```
The boundary ledger for the same turn:
```
BeginProcessTurn 345 -> 345 0
ProcessTurn 345 -> 363 18
OnAllCombatDone_Tail 363 -> 364 1
Autosave 364 -> 364 0
```
> **`OnAllCombatDone_Tail` costs 1 word, against 0 on every turn any lane has ever measured, and the
> whole of it is the detection roll.**
Turn total 19 words; the per-site sum after removing helper-internal rows is **19**; **residual 0**.
The tail's one word is the only site in the tail that fired: no row at `0x00820e18` or `0x0088b613`
(trade 13 — `tscr` is 252 in this game and no fleet stands on a sector node, so lane AC's predicate
says 0 and it is 0), none at `0x0082cdb8` (trade 15), none at `0x0088dc43` (spy 14 — `cm = 0`, lane
AG's failed conjunct), and none of `P`'s three.
**Predictions, scored.** P1 held trivially (0 regenerations, not ≤ 5). **P2 held.** **P3 held
exactly.** **P4 held exactly** — one word, `no_draw_calls = 0`, and the reasoning behind it (four
widened-float literals plus `Chance`'s cost at interior *p*) was written down before the run.
**P5 held** — 0 → 1. **P6 held in both halves**: `sdet` stayed −1 (the roll failed, as a 1.68 %
roll should), and `sdo` moved from its corpus-constant 0 to **0.0084** on the deploy turn and to
**0.0168** after the measured turn — an increment of exactly 0.0084 both times, which is the
accumulator running. **P7 held.** **P8 held only modulo the AI-seed leaves** (§5.1). **P9 held.**
### 5.4 The second instrument agreed — which is what makes this more than one number
The coordinator asked for a save-visible confirmation independent of the bracket. There are two:
* **`sdo` moved and by the right amount.** 0 → 0.0084 → 0.0168. Only branch D writes `sdo`, and it
writes it immediately before the gate, so a moving `sdo` proves the branch ran; the *equal*
increments prove the accumulator, not a one-off.
* **`spies2` filled in exactly one system.** That is the deploy half rather than the roll, but it
confirms the `deat` reading the whole predicate rests on.
`sdet` did **not** move, and per P6 that was the predicted outcome, not a failure.
---
## 6. Coverage — what this did not touch, said as loudly as what it did
* **One measured turn, one spy, one target system, one fleet of one ship.** The cost is 1 word on
that turn. The loop is over the spy vector, so the cost should be one word per deployed,
undetected spy at a system with a usable belt — **that is a reading, not a measurement.** Rule 20:
do not fit a constant to one observation. A second spy at a second belt system separates
per-spy from per-turn and costs one more deploy.
* **The `sdo` arithmetic is validated only in its first two terms.** `countA`, `countB` and the
species multiplier were 0/0/1.0 here, and `countC = 2` is inferred from the value, not observed.
A Zuul or Liir owner (`+0x5c` = 5 or 6) would exercise the 0.75/0.5 branches; a system with enemy
ships present would exercise `countA`/`countB`.
* **The auto-detect arm was never taken.** `(TerrFl & 1) == 0` at the target has not been reached —
it would need a belt to be mined out from under a deployed spy. So §1.2's polarity claim rests on
the instruction stream plus the deploy-time refusal, not on a measured firing. It is the single
most load-bearing unmeasured branch in this document.
* **`P` (`0x008408e0`) and its three draws stay unfired**, and the failed conjunct is now `sdet`,
not `deat`. From this save the recipe is arithmetic rather than a guess: `sdo` grows 0.0084/turn,
so `P(detected)` is 12 % after 5 more End Turns, **38 % after 10, 65 % after 15, 85 % after 20** —
then `P` runs 3 turns after `sdet` is stamped, provided the system is still foreign-owned.
About 20 End Turns from `as-turn15-spydeployed.sav`, no clicking.
* **`0x0078c97f` was decoded and not measured** (§6.1).
* **`probes=11` was not used anywhere in this lane**, and no number here is comparable to lane AC's
turn totals, which were taken at that configuration.
* **No `Guard` region is declared by any hook in this family**, the same gap lanes Z and H reported:
`undeclared = 0` in this trace is vacuous.
* **One control run was lost to the harness and is reported rather than hidden.** A third
`hooks=off` run mis-detected the post-turn End Turn button as "turn still running" and clicked
End Turn three times, advancing the line to turn 18. It was discarded; nothing in §5 uses it. The
defect was in this lane's own click automation (the brightness test had the wrong polarity: after
a completed turn the button is *brighter*, not dimmer), which is rule 19 pointing at the operator
rather than at the detour. Its lesson is worth carrying: **verify the turn number changed, not a
button's colour.**
### 6.1 A draw site nobody has — the deploy's own `NextFloat`
Decoded here and **not measured**, so it is a rule-6 hypothesis with a hook site attached:
```
0078c975 mov ecx,[esi+0x16c] ; esi = StrategyServer -- the STRATEGIC generator
0078c97c add ecx,4
0078c97f call 0x0047d830 ; Mars::RNG::NextFloat -- 1 word, return address 0x0078c984
0078c984 fld qword [0x009e21b0] ; 6.283185482025146 == 2*pi
spy.cbh = NextFloat() * 2*pi
0078c9bb call edx ; -> ServerSpyManager::DeploySpy, vtable slot 7
```
inside the `SHIPACTION_DEPLOYSPY` handler `0x0078c930`, which is entry 9 of the stack-built
ship-action function-pointer table (lane B6's indirection class — no direct callers, no vtable
slot, invisible to every call-graph sweep this campaign has run).
It fires **once per Deploy Spy order applied**, inside `ApplyAllTurnCommands`, so it lands in the
**`ProcessTurn`** bracket, not the tail's. Its predicate is not a predicate on save fields at all —
it is *"a `SHIPACTION_DEPLOYSPY` command in this turn's command stream"*, a **stream** predicate,
which lane AG's gate-indexed audit has no column for and should grow one.
To measure it costs one run: load `verify/results/saves/as-turn14-predeploy.sav` under `probes=8`,
select Kepler, `Special → Deploy Spy → <ship> → OK`, End Turn, and read `draw_sites` at
`ret_rva 0x0078c984`. This lane attempted it and lost the run to the map UI (Kepler and Midway sit
within four pixels of each other at the zoom the map opens at, and Midway wins the hit test), then
chose to report the site honestly rather than rush a fourth process. Prediction, committed here for
whoever takes it: **one call, one word, `strategic = true`, in the `ProcessTurn` bracket**, and the
same turn's tail still costs exactly 1 — the deploy and the first detection roll happen on the same
End Turn, because `ApplyAllTurnCommands` runs before `OnAllCombatDone_Tail`.
---
## 7. VM144 as left
**Restored and verified by screenshot at the main menu**, profile `re`, 2026-09-09 00:03 local.
* `C:\SOTS\binkw32.dll` restored to lane L3's build from `C:\SOTS\shimdist-l3\binkw32.dll`
(sha256 `479B8614D2417603…`, byte-identical to what this lane found in place).
* `C:\SOTS\shim.cfg` restored from `C:\SOTS\ui\preAS-shim.cfg` (L3's `shim.cfg.l3probe`).
* The three autosaves restored **byte-identical** from `C:\SOTS\ui\preAS-SavedGames`
(`1985E6F4…` / `24B2E072…` / `5EC80C1E…`).
* `C:\SOTS\shim.trace.jsonl` removed.
**`SavedGames` is now 15 files, not 13**, so **every Load-dialog row position has moved again** —
screenshot the dialog, do not reuse a remembered row. The two additions are this lane's, and they
are worth leaving because they are the only deployed-spy states in existence:
| file | turn | why it is worth keeping |
|---|---|---|
| `MyGameas1predeploy.sav` | 14 | fleet + tender + docked spy standing **at** an AI belt colony, one click from a deploy — the input for §6.1 |
| `MyGameas1spydep.sav` | 15 | **the deployed spy**: `deat 400`, `sdet -1`, `sdo 0.0084`, `spies2 = [1]` at Kepler |
At the 15-file set, verified by screenshot this session: `MyGameas1predeploy` is at **(400, 347)**
and `MyGameas1spydep` at **(400, 376)**; `OK` is unmoved at (682, 624).
Also left in place, all harmless: `C:\SOTS\shimdist-as\` (build
`as-c172c99-20260909T0205Z`, 64 files), `C:\SOTS\ui\asgo.ps1`, `C:\SOTS\ui\ashash.ps1`,
`C:\SOTS\ui\preAS-shim.cfg`, `C:\SOTS\ui\preAS-SavedGames\` (13 files),
`C:\SOTS\ui\asfetch.sav` / `asgrab.jsonl` / `asgrab.log` (scratch). **`click_helper.ps1` was not
touched** — this lane needed no new verbs, which is the first VM lane in a while that did not.
### 7.1 Guest notes worth carrying forward
* **The map's hover readout works with a plain `move`** on VM144 — lane AC's `jmove` jiggle was not
needed here. Enter Move mode first; the readout renders at the hovered star, not at the cursor.
* **Finding a named system on the star map is solvable arithmetically instead of by hunting.**
Hover four systems, read their names, and least-squares-fit an affine map from the save's `Pos`
vectors to screen coordinates; the residuals came out ≤ 8 px on 5 points, which is enough to click
a specific star first time. The script is in this lane's scratch notes and the method is the
reusable part. Its failure mode is systems that project within a few pixels of each other
(Kepler and Midway), where the nearer one wins the hit test.
* **The End Turn button is BRIGHTER after a turn completes, not dimmer.** Any "is the turn still
running?" test built on that button's colour has the polarity backwards. Poll the **autosave
mtime** and the **turn number in the ticker**, never the button.
* Accepting the AI's non-aggression offer (turn 6 here) stopped the combat encounters that were
interrupting every third End Turn, and cost nothing this lane needed.
---
## 8. Corrections to earlier findings (rule 11)
* **`2026-09-09-tail-draws.md` §0.7 and §7, and lane AC §2.2: "the detection roll is inline in
`vslot13`".** It is a direct `E8` call to the `Chance` entry point sitting in `vslot13`'s body.
The instrument advice that followed from it is right; the stated reason is not. §1.1.
* **Lane L3 §6: "`spies2` is therefore not the spy list".** Half right. It is not the spy manager's
craft list; it is the **system's deployed-spy id vector**, written by `ServerSystem::AddSpy` in
the same two instructions that set `deat`. L3's own second alternative was the correct one. §1.5.
* **Lane AC §5: "the textbook target — an enemy colony with a belt — does not exist in this game."**
True of that map and read across the campaign as if it were true of the game. Two corpus saves
already had four AI-owned belt systems, and the first map this lane generated had two. §2.
* **Lane V2 §3.1's spy-13 row** listed `0x00887c8a` correctly and is **vindicated**, like its
trade-13 row before it. Its "never observed firing" note is now discharged for this one site.
* **Ghidra's size for `0x008877b0` is 1,869 bytes; the next function starts at `0x00887f30`, i.e.
1,920.** The detection roll at `0x00887c8a` is inside both, but the 51-byte shortfall is rule 17
again, on the very function this lane exists to read.
---
## 9. Artifacts
| what | where |
|---|---|
| **the deployed-spy save** — first `deat != 0` and first non-empty `spies2` in the corpus | `verify/results/saves/as-turn15-spydeployed.sav` (76,705 B, `--strict` 0 errors / 0 warnings, sha256 `854a10fa1ea602f0…`) |
| the pre-deploy save, one click from a deploy at an AI belt colony | `verify/results/saves/as-turn14-predeploy.sav` (75,908 B, sha256 `53986f8511cbf992…`) |
| the instrumented trace (`draw_sites` + `probe_entries` + the boundary ledger) | `verify/traces/as-probes8-turn15-turn16.jsonl.gz` |
| shim log for that run (probe and detour install status) | `verify/results/shim/as/as-probes8-run.shim.log` |
| addresses this lane mints | `ghidra/addresses.d/as.json` (7 entries; 6 `verified`, 1 `mapped`) |
| predictions, committed before the build | `sots-engine` `docs/AS-predictions.md` (`c172c99`, addendum `61875a4`) |
| instrument | `sots-engine` `src/shim/shim.cfg.hp8` and `shim.cfg.hoff`, **both unchanged** — this lane wrote no engine code |
### 9.1 The hashes, for the record
```
input MyGameas1spydep.sav 854a10fa1ea602f0f7909f9bf9154942e56ea5fa1db4cce47d6d32bf53952e08
A/B/M (Autosave EndTurn).sav 98e45d3745b914506fc4c409ad5a5ad0938a2003ab4c845421a99a8065149c3a
A (Autosave).sav 262f8bda97c9511d8d2b41e6a8a0582d6f5364c2b01b1caf99e639d0e4f53ebe
B (Autosave).sav e34775a757e6fb3df647d7a589e8b1819b90bea094196a9e3cc819e51ed63257
M (Autosave).sav 262f8bda97c9511d8d2b41e6a8a0582d6f5364c2b01b1caf99e639d0e4f53ebe
```
**This is not a calibration pair for the standalone**, and it must not be entered in
`determinism-hashes.txt` as one. A ≠ B, in the AI-research-pick leaf class, so the turn is
reproducible only once the client seeds are pinned. What it *is* is a reproducible **input** and a
turn whose every non-AI leaf reproduces across three processes and two configurations.
---
## Proposed `campaign/board.md` rows
New row:
```
| THE SPY DETECTION ROLL FIRES - 1 word, gated on an asteroid belt; `spies2` and `deat` closed | verify | verified | high | 100% | 2026-09-09 | **Lane AS, VM144, build `as-c172c99-20260909T0205Z`, `probes=8` (never 11).** `Mars::RNG::Chance` at **0x00887c8a**, in the BODY of `ServerSpyManager::vslot13` (tail phase 23 call 9), fired **1 call / 1 word / no_draw_calls=0 / strategic=true** at `ret_rva 0x00887c8f`; `OnAllCombatDone_Tail` went **363 -> 364**, against 0 on every turn any lane has ever measured. Turn total 19, per-site sum 19, **residual 0**. THE ENTRY PROBE ON `SpyManager::Slot13RngCallee 0x008408e0` READ **0 ON THE SAME TURN** - that zero was never a statement about the subtree, only about which function the call sits in (rule 28 practice 4, demonstrated rather than argued). **CORRECTION: the roll is NOT inline** (the resolution and AC §2.2 both say so) - it is a plain `E8` to the `Chance` entry point in the caller. **THE PREDICATE**, decoded to save fields: `spy.deat != 0 && spy.sdet == -1 && sys(deat).ARes2 > 0 && (sys(deat).TerrFl & 1)`; **corpus count 0 of 22, failed conjunct `deat != 0`**. `ServerSystem_BeltUsableFlags 0x00743f80` (16 bytes) returns `TerrFl` with bit 0 cleared when `ARes2 <= 0`; `ARes2>0` and `TerrFl&1` agree on all 616 corpus system records AND on the new map, which also has `TerrFl = 2` and `3`, so bit 0 is the belt specifically. **POLARITY: belt gone => NO DRAW AND THE SPY IS DETECTED ANYWAY** (`je` past the roll to `sdet := turn`) - a zero here is two different results. Cost: `sdo` starts at 0, `SpyCraft_AccumulateDetectionOdds 0x0081f570` ACCUMULATES `0.7*(0.01 + [cA]0.01 + [cB]0.02 + 0.001*cC) * species(1.0/0.75/0.5)` and CLAMPS AT 1.0, so `Chance` costs 1 word until sdo reaches 1 and 0 thereafter. Measured `sdo` 0 -> 0.0084 -> 0.0168 (= 0.7*0.012 exactly, a value no run had produced). **`spies2` IS CLOSED**: `ServerSystem::AddSpy 0x007514c0` sets `spy.deat = system handle` and pushes `spy.sid` into `ServerSystem+0x1cc` in the same two instructions - `spies2` is the per-system DEPLOYED-spy id vector; L3's "not the spy list" was half right, AC's P4 was right. Measured: `count=1 element=1` at Kepler, 0 in the other 27. **NEW DRAW SITE NOBODY HAS**: `0x0078c97f` NextFloat in the SHIPACTION_DEPLOYSPY handler `0x0078c930` (`cbh = NextFloat()*2pi`), strategic generator, in the ProcessTurn bracket - decoded, NOT measured, and its predicate is on the COMMAND STREAM not the save, a column the gate-indexed audit lacks. Oracle: input byte-identical in 3 processes; A vs B differ in **22 leaves, all inside one AI player's research pick** (the known per-process client seed, resolution 2026-09-08) - so this is NOT a calibration pair and must not enter `determinism-hashes.txt`. `probes=8` run came out byte-identical to control A. Saves: `as-turn15-spydeployed.sav`, `as-turn14-predeploy.sav`; findings `findings/subsystems/spy-detection-roll.md` |
```
Edits to existing rows:
- **Row 62** (guest holders) — `VM144 = FREE (lane AS released 2026-09-09; restored to L3's build + shim.cfg + the three pre-AS autosaves byte-identical, main menu verified by screenshot). SavedGames is now 15 FILES - AS added MyGameas1predeploy (400,347) and MyGameas1spydep (400,376), so ROW POSITIONS HAVE MOVED AGAIN; screenshot the dialog. AS left C:\SOTS\shimdist-as + ui\as{go,hash}.ps1 + ui\preAS-*; click_helper.ps1 untouched.`
- **Row 207** (V2's eight sites) — append: `FIRED 2026-09-09: 0x00887c8a (spy 13), lane AS, 1 word, behind a deployed spy at a foreign colony with a usable asteroid belt. V2 vindicated a second time. Five of the eight still unfired: P's three (0x00840929 / 0x008409c7 / 0x00840a3c, gate now `sdet != -1` and turn-sdet>=3, ~20 End Turns from as-turn15-spydeployed.sav), 0x0088dc43 (spy 14, gate `cm in 1..4`, lane AG), 0x0082cdb8 (trade 15). AND THE INVENTORY IS SHORT ONE: 0x0078c97f in the SHIPACTION_DEPLOYSPY handler draws a NextFloat on the strategic generator and is in no sweep, because it hangs off the stack-built ship-action table.`
- **Row 373 / the `spies2` rule-6 flag** — `CLOSED 2026-09-09 by lane AS, from the writer and then live. spies2 (ServerSystem+0x1cc) is the per-system vector of DEPLOYED spy ids: ServerSystem::AddSpy 0x007514c0 sets SpyCraft+0x10 (`deat`) to the system handle and push_backs SpyCraft+0x4 (`sid`) into +0x1cc; RemoveSpy 0x0074f550 is the exact mirror. Measured non-empty for the first time in the corpus: count=1, element=1, in the deploy system only. `SysMem` and `mts` are untouched and their flags stay up.`
- **Row 186 / `tail-rng-ledger.md`** — append to the existing qualification: `THIRD site outside the firing-indexed table has now fired: the spy detection roll (tail T23c9, lane AS). The pattern is now three for three - hives, the raid roll, the detection roll - and each fired on the first state built for it. The gate-indexed audit is the fix, not a longer table.`

View file

@ -0,0 +1,61 @@
{
"_note": "Lane AS (2026-09-09). The spy-deployment and detection-roll chain, read from the instruction stream of dumps/sots.exe (disassembled to the NEXT FUNCTION START, rule 17 -- Ghidra's reported ends are not used anywhere below). Every entry marked `verified` was read instruction by instruction and its operand offsets cross-checked against objects/layouts.md for Game::ServerSystem; entries marked `mapped` were reached but not read. Two of these -- ServerSystem_AddSpy and ServerSystem_RemoveSpy -- also settle the campaign's long-standing `spies2` rule-6 flag: they are the only writers of both SpyCraft+0x10 (`deat`) and ServerSystem+0x1cc (`spies2`), and they write them together. Evidence: findings/subsystems/spy-detection-roll.md. NOT minted here because another fragment already has them: ServerSystem_GetOwner 0x007437e0 (lane B5) and ServerSystem_IsIndependent 0x00743fa0 (lane AG), both of which this chain calls.",
"entries": [
{
"name": "ServerSystem_AddSpy",
"addr": "0x007514c0",
"convention": "__thiscall",
"prototype": "void (Game::ServerSystem* this, Game::SpyCraft* spy) /* 48 B, complete: `if (!spy) return; spy->deat(+0x10) = this ? this->[+4] : 0; push_back(&this->spies2(+0x1cc), &spy->sid(+0x4))` via lane AI3's 0x0059f1a0. THE ONLY WRITER THAT EVER MAKES `deat` NON-ZERO AT RUNTIME (the other stores to SpyCraft+0x10 are the zero-init in CreateSpyCraft, the deserializer, and three field copies). Exactly one caller, 0x008874d4 inside ServerSpyManager_DeploySpy, and no vtable slot. THIS SETTLES `spies2`: ServerSystem+0x1cc is the per-system vector of DEPLOYED spy ids, written in the same two instructions as `deat`, which is why it is 0 in every save whose only spy is docked to its tender */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "ServerSystem_RemoveSpy",
"addr": "0x0074f550",
"convention": "__thiscall",
"prototype": "void (Game::ServerSystem* this, Game::SpyCraft* spy) /* 48 B, complete: `if (!spy) return; spy->deat(+0x10) = 0; erase(&this->spies2(+0x1cc), &spy->sid(+0x4))` via 0x0059ec00. The exact mirror of ServerSystem_AddSpy; the two together are the whole life cycle of `deat` and of `spies2` */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "ServerSpyManager_DeploySpy",
"addr": "0x00887410",
"convention": "__thiscall",
"prototype": "bool (Game::ServerSpyManager* this, int spyHandle, float cbh) /* Game::ServerSpyManager vftable 0x00a3073c SLOT 7; zero direct call sites -- reached only from the SHIPACTION_DEPLOYSPY handler 0x0078c930 through [[dispatcher+0x15c]+0x1c]. Read at the field writes: spy->Reset() (0x00838070), then sdo(+0x3c) := 0.0f, sdet(+0x40) := -1, cbh(+0x18) := arg, tdep(+0x14) := server->[+0xc] (the current turn), atto(+0xc) := 0 and carrierShip->[+0xa8] := 0 (detach), then ServerSystem_AddSpy(system, spy) at 0x008874d4, then a player message built from system->Name (+0xa8, capacity at +0xbc -- which is what proves the receiver is a ServerSystem). CONSEQUENCE FOR THE TAIL: a spy is deployed with sdet == -1 and sdo == 0, so ServerSpyManager_vslot13's detection branch is entered on the very next OnAllCombatDone_Tail */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "ServerSystem_BeltUsableFlags",
"addr": "0x00743f80",
"convention": "__thiscall",
"prototype": "int (Game::ServerSystem* this) /* 16 B, complete: `eax = this->TerrFl(+0x19c); if (this->ARes2(+0x6c) <= 0) eax &= ~1; return eax`. BIT 0 OF THE RETURN IS \"this system still has a usable asteroid belt\" -- TerrFl bit 0 masked by the belt's remaining resources. Corroborated from the save side without a debugger: ARes2 > 0 and TerrFl & 1 agree on all 616 system records of the 22-save corpus and on all 28 of the state this lane built, where TerrFl also takes the values 2 and 3, so bit 1 is a second, independent terrain feature. Called at 0x00887c71 (the detection roll's gate) and at 0x0083d15d inside the shared placement validator 0x0083ce50, where a clear bit 0 sets refusal bit 0x1000000 */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "SpyCraft_AccumulateDetectionOdds",
"addr": "0x0081f570",
"convention": "__stdcall",
"prototype": "void (Game::SpyCraft* spy, Game::ServerSystem* sys, Game::ServerPlayer* spyOwner) /* 512 B, `ret 0xc`. Writes spy->sdo (+0x3c) and is called unconditionally at 0x00887c66, immediately before the detection roll's gate. EARLY OUT: if sys->PID == 0 (unowned) it stores 0.0f and returns, so a spy at an unowned system rolls at p = 0 and costs no word. Otherwise it walks the system's fleet vector accumulating three counters and computes, with every literal read as the four bytes in the image (rule 23): p = 0.01f + (cA>0 ? 0.01f : 0) + (cB>0 ? 0.02f : 0) + 0.001*cC, then p *= (spyOwner->[+0x5c]==5 ? 0.75f : spyOwner->[+0x5c]==6 ? 0.5f : 1.0f), then p *= 0.7, then sdo = min(sdo + p, 1.0) -- IT ACCUMULATES onto the old value and is CLAMPED AT 1.0. So a quiet foreign target gives sdo = 0.007 on the first turn and ~0.007/turn after, and a spy left in place long enough reaches p >= 1, at which point RNG_Chance costs 0 words and always succeeds */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "ServerSpyManager_DetectionRoll_DrawSite",
"addr": "0x00887c8a",
"convention": "site",
"prototype": "/* THE SPY DETECTION ROLL. A plain E8 rel32 `call Mars_RNG_Chance 0x008e6dd0` in the BODY of ServerSpyManager_vslot13 0x008877b0 -- NOT an inlined draw (the campaign carried `inline` for a day; the draw is a normal entry-point call, it simply sits in the caller rather than in SpyManager_Slot13RngCallee 0x008408e0, which is why an entry probe on that callee reads zero either way). Generator: `mov ecx,[server+0x16c]` at 0x00887c80 -- the STRATEGIC generator. Probability: `fld [spy+0x3c]` -- sdo. Return address for lane Z's draw_sites ledger: 0x00887c8f. Gates, in order: spy.deat != 0, spy.sdet == -1, and ServerSystem_BeltUsableFlags(sys(deat)) & 1. WHEN THAT LAST BIT IS CLEAR THE ROLL IS SKIPPED AND THE SPY IS DETECTED UNCONDITIONALLY (`je 0x887c97`, which stores sdet := turn) -- a zero here is therefore not always a negative */",
"status": "verified",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
},
{
"name": "SpyCraft_ResetMission",
"addr": "0x00838070",
"convention": "__thiscall",
"prototype": "void (Game::SpyCraft* this) /* Called by ServerSpyManager_DeploySpy at 0x0088749e and by ServerSpyManager_vslot14 at 0x0088dc11 when the target system changes hands. NOT READ: only its position in those two chains and the fields its callers write immediately afterwards (sdo := 0, sdet := -1) are established here */",
"status": "mapped",
"source": "findings/subsystems/spy-detection-roll.md (lane AS 2026-09-09)"
}
]
}

View file

@ -1,5 +1,5 @@
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
// Source: sots-re ghidra/addresses.json @ 48db3cc, generated 2026-09-08 by tools/gen_addresses.py
// Source: sots-re ghidra/addresses.json @ 712d184, generated 2026-09-08 by tools/gen_addresses.py
// Runtime address = (uintptr_t)GetModuleHandle(NULL) + RVA (the exe is ASLR-relocated).
#pragma once
#include <cstdint>
@ -1281,6 +1281,20 @@ constexpr uint32_t RaidEncounter_Helper848e50 = 0x00448e50;
constexpr uint32_t RaidEncounter_Helper848e50_DrawSite = 0x00448fd9;
// thiscall Called only from TradeManager_BuildRaidEncounter at 0x00892700; calls TradeManager_ComputeRaidInterceptPoint at 0x0082cf65, which is where the NextFloat at 0x00820c1b is spent on this path. Body not read [mapped]
constexpr uint32_t RaidEncounter_Helper82ce00 = 0x0042ce00;
// __thiscall void (Game::ServerSystem* this, Game::SpyCraft* spy) /* 48 B, complete: `if (!spy) return; spy->deat(+0x10) = this ? this->[+4] : 0; push_back(&this->spies2(+0x1cc), &spy->sid(+0x4))` via lane AI3's 0x0059f1a0. THE ONLY WRITER THAT EVER MAKES `deat` NON-ZERO AT RUNTIME (the other stores to SpyCraft+0x10 are the zero-init in CreateSpyCraft, the deserializer, and three field copies). Exactly one caller, 0x008874d4 inside ServerSpyManager_DeploySpy, and no vtable slot. THIS SETTLES `spies2`: ServerSystem+0x1cc is the per-system vector of DEPLOYED spy ids, written in the same two instructions as `deat`, which is why it is 0 in every save whose only spy is docked to its tender */ [verified]
constexpr uint32_t ServerSystem_AddSpy = 0x003514c0;
// __thiscall void (Game::ServerSystem* this, Game::SpyCraft* spy) /* 48 B, complete: `if (!spy) return; spy->deat(+0x10) = 0; erase(&this->spies2(+0x1cc), &spy->sid(+0x4))` via 0x0059ec00. The exact mirror of ServerSystem_AddSpy; the two together are the whole life cycle of `deat` and of `spies2` */ [verified]
constexpr uint32_t ServerSystem_RemoveSpy = 0x0034f550;
// __thiscall bool (Game::ServerSpyManager* this, int spyHandle, float cbh) /* Game::ServerSpyManager vftable 0x00a3073c SLOT 7; zero direct call sites -- reached only from the SHIPACTION_DEPLOYSPY handler 0x0078c930 through [[dispatcher+0x15c]+0x1c]. Read at the field writes: spy->Reset() (0x00838070), then sdo(+0x3c) := 0.0f, sdet(+0x40) := -1, cbh(+0x18) := arg, tdep(+0x14) := server->[+0xc] (the current turn), atto(+0xc) := 0 and carrierShip->[+0xa8] := 0 (detach), then ServerSystem_AddSpy(system, spy) at 0x008874d4, then a player message built from system->Name (+0xa8, capacity at +0xbc -- which is what proves the receiver is a ServerSystem). CONSEQUENCE FOR THE TAIL: a spy is deployed with sdet == -1 and sdo == 0, so ServerSpyManager_vslot13's detection branch is entered on the very next OnAllCombatDone_Tail */ [verified]
constexpr uint32_t ServerSpyManager_DeploySpy = 0x00487410;
// __thiscall int (Game::ServerSystem* this) /* 16 B, complete: `eax = this->TerrFl(+0x19c); if (this->ARes2(+0x6c) <= 0) eax &= ~1; return eax`. BIT 0 OF THE RETURN IS "this system still has a usable asteroid belt" -- TerrFl bit 0 masked by the belt's remaining resources. Corroborated from the save side without a debugger: ARes2 > 0 and TerrFl & 1 agree on all 616 system records of the 22-save corpus and on all 28 of the state this lane built, where TerrFl also takes the values 2 and 3, so bit 1 is a second, independent terrain feature. Called at 0x00887c71 (the detection roll's gate) and at 0x0083d15d inside the shared placement validator 0x0083ce50, where a clear bit 0 sets refusal bit 0x1000000 */ [verified]
constexpr uint32_t ServerSystem_BeltUsableFlags = 0x00343f80;
// __stdcall void (Game::SpyCraft* spy, Game::ServerSystem* sys, Game::ServerPlayer* spyOwner) /* 512 B, `ret 0xc`. Writes spy->sdo (+0x3c) and is called unconditionally at 0x00887c66, immediately before the detection roll's gate. EARLY OUT: if sys->PID == 0 (unowned) it stores 0.0f and returns, so a spy at an unowned system rolls at p = 0 and costs no word. Otherwise it walks the system's fleet vector accumulating three counters and computes, with every literal read as the four bytes in the image (rule 23): p = 0.01f + (cA>0 ? 0.01f : 0) + (cB>0 ? 0.02f : 0) + 0.001*cC, then p *= (spyOwner->[+0x5c]==5 ? 0.75f : spyOwner->[+0x5c]==6 ? 0.5f : 1.0f), then p *= 0.7, then sdo = min(sdo + p, 1.0) -- IT ACCUMULATES onto the old value and is CLAMPED AT 1.0. So a quiet foreign target gives sdo = 0.007 on the first turn and ~0.007/turn after, and a spy left in place long enough reaches p >= 1, at which point RNG_Chance costs 0 words and always succeeds */ [verified]
constexpr uint32_t SpyCraft_AccumulateDetectionOdds = 0x0041f570;
// site /* THE SPY DETECTION ROLL. A plain E8 rel32 `call Mars_RNG_Chance 0x008e6dd0` in the BODY of ServerSpyManager_vslot13 0x008877b0 -- NOT an inlined draw (the campaign carried `inline` for a day; the draw is a normal entry-point call, it simply sits in the caller rather than in SpyManager_Slot13RngCallee 0x008408e0, which is why an entry probe on that callee reads zero either way). Generator: `mov ecx,[server+0x16c]` at 0x00887c80 -- the STRATEGIC generator. Probability: `fld [spy+0x3c]` -- sdo. Return address for lane Z's draw_sites ledger: 0x00887c8f. Gates, in order: spy.deat != 0, spy.sdet == -1, and ServerSystem_BeltUsableFlags(sys(deat)) & 1. WHEN THAT LAST BIT IS CLEAR THE ROLL IS SKIPPED AND THE SPY IS DETECTED UNCONDITIONALLY (`je 0x887c97`, which stores sdet := turn) -- a zero here is therefore not always a negative */ [verified]
constexpr uint32_t ServerSpyManager_DetectionRoll_DrawSite = 0x00487c8a;
// __thiscall void (Game::SpyCraft* this) /* Called by ServerSpyManager_DeploySpy at 0x0088749e and by ServerSpyManager_vslot14 at 0x0088dc11 when the target system changes hands. NOT READ: only its position in those two chains and the fields its callers write immediately afterwards (sdo := 0, sdet := -1) are established here */ [mapped]
constexpr uint32_t SpyCraft_ResetMission = 0x00438070;
// thiscall void (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) // the body of every `Player.<id>.AIAgent` CD block. 36 wire items, NO conditionals: the only `if` the decompiler shows around `lnat` is an inlined std::vector destructor whose operator delete is marked noreturn, and both paths converge at 0x006c72e8. The agent object is *(this+4) [verified]
constexpr uint32_t Game_StrategyAIAgent_Streamable_Write = 0x002c6f00;
// thiscall bool (Game::StrategyAIAgent::Streamable* this, Mars::Stream* s) [verified]

Binary file not shown.

Binary file not shown.

View file

@ -0,0 +1,114 @@
23:43:10.838 [tid 7184] ==== sots-engine shim (binkw32 proxy) build as-c172c99-20260909T0205Z ====
23:43:10.838 [tid 7184] exe: C:\SOTS\Sword of the Stars.exe
23:43:10.838 [tid 7184] exe base=0x006b0000 (link-time image base 0x00400000, ASLR delta +2818048) pid=3060 shim=72b70000
23:43:10.838 [tid 7184] addresses: Source: sots-re ghidra/addresses.json @ bdaa26f, generated 2026-09-08 by tools/gen_addresses.py
23:43:10.854 [tid 7184] config: hooks=trace
23:43:10.854 [tid 7184] config: hook.Shim::SelfTest::Fill=off
23:43:10.854 [tid 7184] config: hook.Mars::GlobalConsts::LoadFile=off
23:43:10.854 [tid 7184] config: hook.Game::WeaponDictionary::Init=off
23:43:10.854 [tid 7184] config: hook.Game::SectionDictionary::SectionDictionary=off
23:43:10.854 [tid 7184] config: hook.Game::ServerPlayer::ComputeBudget=off
23:43:10.854 [tid 7184] config: hook.Game::TechTree::ProcessResearch=off
23:43:10.854 [tid 7184] config: hook.Game::ServerPlayer::OnTechResearched=off
23:43:10.854 [tid 7184] config: hook.Game::ServerSystem::ProcessTurn=off
23:43:10.854 [tid 7184] config: hook.Game::ServerPlayer::ProcessTurn=off
23:43:10.854 [tid 7184] config: hook.Game::ServerSystem::GroupOutput=off
23:43:10.854 [tid 7184] config: hook.Game::ServerSystem::ComputeTotalOutput=off
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::MoveFleet=off
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::ProcessFleetMovement=off
23:43:10.854 [tid 7184] config: hook.Game::StrategyHost::Autosave=trace
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::ProcessTurn=trace
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::OnAllCombatDone_Tail=trace
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::ApplyEncounterResult=trace
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::NodeLineDecay=trace
23:43:10.854 [tid 7184] config: hook.Game::StrategyServer::ProcessNodeSpaceTravel=trace
23:43:10.854 [tid 7184] config: hook.Game::EncounterDetect::AssignContacts=trace
23:43:10.854 [tid 7184] config: hook.Game::EncounterDetect::ProcessTeamRecord=trace
23:43:10.854 [tid 7184] config: fpu.sample_turn=off
23:43:10.854 [tid 7184] config: fpu.sample_ticks=off
23:43:10.854 [tid 7184] config: trace.inline_max=64
23:43:10.854 [tid 7184] config: trace.path=C:\SOTS\shim.trace.jsonl
23:43:10.854 [tid 7184] config: trace.flush=always
23:43:10.854 [tid 7184] config: probes=8 -> 8 lane-H entry probes
23:43:10.947 [tid 7184] trace: C:\SOTS\shim.trace.jsonl (default mode trace, inline_max 64, flush always)
23:43:10.947 [tid 7184] hook: Mars_Application_Initialize rva=0x004a0e50 -> va=00b50e50
23:43:10.947 [tid 7184] hook: MH_Initialize -> MH_OK
23:43:10.947 [tid 7184] hook: MH_CreateHook -> MH_OK (trampoline=01930fe0)
23:43:10.947 [tid 7184] hook: MH_EnableHook -> MH_OK
23:43:10.947 [tid 7184] cfg: GlobalConsts hook ready (scale constant 0.017453292519943295)
23:43:10.947 [tid 7184] hook: Mars::GlobalConsts::LoadFile rva=0x004b73c0 mode=off (not installed)
23:43:10.947 [tid 7184] dict: dictionaries hook ready (crt new=74c4232b delete=74c40174)
23:43:10.947 [tid 7184] hook: Game::WeaponDictionary::Init rva=0x0019a4c0 mode=off (not installed)
23:43:10.947 [tid 7184] hook: Game::SectionDictionary::SectionDictionary rva=0x00176f40 mode=off (not installed)
23:43:10.963 [tid 7184] research: ProcessResearch hook ready (Cost=0082da00, node=0x34, rng=0x9cc, fpu_cw=0x027f)
23:43:10.963 [tid 7184] hook: Game::TechTree::ProcessResearch rva=0x001876c0 mode=off (not installed)
23:43:10.963 [tid 7184] techfx: OnTechResearched hook ready (regions=15, gate=0/0, fpu_cw=0x027f)
23:43:10.963 [tid 7184] hook: Game::ServerPlayer::OnTechResearched rva=0x00491790 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::ServerPlayer::ComputeBudget rva=0x00463030 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::ServerSystem::ProcessTurn rva=0x003598e0 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::ServerSystem::GroupOutput rva=0x0034b7a0 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::ServerSystem::ComputeTotalOutput rva=0x00350480 mode=off (not installed)
23:43:10.963 [tid 7184] player_turn: ServerPlayer::ProcessTurn hook armed (ratio helper at 0082e950)
23:43:10.963 [tid 7184] hook: Game::ServerPlayer::ProcessTurn rva=0x00491340 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::StrategyServer::MoveFleet rva=0x003d9ee0 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::StrategyServer::ProcessFleetMovement rva=0x003da9a0 mode=off (not installed)
23:43:10.963 [tid 7184] hook: Game::StrategyHost::Autosave rva=0x00495210 -> va=00b45210 MH_CreateHook -> MH_OK (trampoline=01930fc0)
23:43:10.979 [tid 7184] hook: Game::StrategyHost::Autosave MH_EnableHook -> MH_OK mode=trace
23:43:10.979 [tid 7184] hook: Game::StrategyServer::ProcessTurn rva=0x003dc6c0 -> va=00a8c6c0 MH_CreateHook -> MH_OK (trampoline=01930fa0)
23:43:10.994 [tid 7184] hook: Game::StrategyServer::ProcessTurn MH_EnableHook -> MH_OK mode=trace
23:43:10.994 [tid 7184] hook: Game::StrategyServer::OnAllCombatDone_Tail rva=0x003d92a0 -> va=00a892a0 MH_CreateHook -> MH_OK (trampoline=01930f80)
23:43:11.010 [tid 7184] hook: Game::StrategyServer::OnAllCombatDone_Tail MH_EnableHook -> MH_OK mode=trace
23:43:11.010 [tid 7184] hook: Game::StrategyServer::ApplyEncounterResult rva=0x003d8920 -> va=00a88920 MH_CreateHook -> MH_OK (trampoline=01930f60)
23:43:11.026 [tid 7184] hook: Game::StrategyServer::ApplyEncounterResult MH_EnableHook -> MH_OK mode=trace
23:43:11.026 [tid 7184] hook: Game::StrategyServer::NodeLineDecay rva=0x003ae010 -> va=00a5e010 MH_CreateHook -> MH_OK (trampoline=01930f40)
23:43:11.041 [tid 7184] hook: Game::StrategyServer::NodeLineDecay MH_EnableHook -> MH_OK mode=trace
23:43:11.041 [tid 7184] hook: Game::StrategyServer::ProcessNodeSpaceTravel rva=0x003a0e20 -> va=00a50e20 MH_CreateHook -> MH_OK (trampoline=01930f20)
23:43:11.057 [tid 7184] hook: Game::StrategyServer::ProcessNodeSpaceTravel MH_EnableHook -> MH_OK mode=trace
23:43:11.057 [tid 7184] hook: Game::EncounterDetect::AssignContacts rva=0x003aa240 -> va=00a5a240 MH_CreateHook -> MH_OK (trampoline=01930f00)
23:43:11.072 [tid 7184] hook: Game::EncounterDetect::AssignContacts MH_EnableHook -> MH_OK mode=trace
23:43:11.072 [tid 7184] hook: Game::EncounterDetect::ProcessTeamRecord rva=0x003ca640 -> va=00a7a640 MH_CreateHook -> MH_OK (trampoline=01930ee0)
23:43:11.088 [tid 7184] hook: Game::EncounterDetect::ProcessTeamRecord MH_EnableHook -> MH_OK mode=trace
23:43:11.088 [tid 7184] hook: Game::StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=00a898e0 MH_CreateHook -> MH_OK (trampoline=01930ec0)
23:43:11.104 [tid 7184] hook: Game::StrategyServer::BeginProcessTurn MH_EnableHook -> MH_OK mode=trace
23:43:11.104 [tid 7184] hook: Game::SVSOSwarmQueen::OnTurnBegin rva=0x00129930 -> va=007d9930 MH_CreateHook -> MH_OK (trampoline=01930ea0)
23:43:11.135 [tid 7184] hook: Game::SVSOSwarmQueen::OnTurnBegin MH_EnableHook -> MH_OK mode=trace
23:43:11.135 [tid 7184] hook: Game::SVSOSwarmQueen::RegisterHives rva=0x00127630 -> va=007d7630 MH_CreateHook -> MH_OK (trampoline=01930e80)
23:43:11.151 [tid 7184] hook: Game::SVSOSwarmQueen::RegisterHives MH_EnableHook -> MH_OK mode=trace
23:43:11.151 [tid 7184] hook: Game::SVSOSwarmQueen::TickHives rva=0x00127770 -> va=007d7770 MH_CreateHook -> MH_OK (trampoline=01930e60)
23:43:11.166 [tid 7184] hook: Game::SVSOSwarmQueen::TickHives MH_EnableHook -> MH_OK mode=trace
23:43:11.166 [tid 7184] hook: Game::SVSOSlaversRefuel::UpdateDifficultyTier rva=0x00115820 -> va=007c5820 MH_CreateHook -> MH_OK (trampoline=01930e40)
23:43:11.182 [tid 7184] hook: Game::SVSOSlaversRefuel::UpdateDifficultyTier MH_EnableHook -> MH_OK mode=trace
23:43:11.182 [tid 7184] hook: Mars::RNG::Seed rva=0x0009fdf0 -> va=0074fdf0 MH_CreateHook -> MH_OK (trampoline=01930e20)
23:43:11.197 [tid 7184] hook: Mars::RNG::Seed MH_EnableHook -> MH_OK mode=trace
23:43:11.197 [tid 7184] hook: Game::StrategyApp::RunAI rva=0x004706f0 -> va=00b206f0 MH_CreateHook -> MH_OK (trampoline=01930e00)
23:43:11.213 [tid 7184] hook: Game::StrategyApp::RunAI MH_EnableHook -> MH_OK mode=trace
23:43:11.229 [tid 7184] drawsite: Mars::RNG::NextFloat rva=0x0007d830 -> va=0072d830 create=MH_OK enable=MH_OK
23:43:11.244 [tid 7184] drawsite: Mars::RNG::NextInt rva=0x000271c0 -> va=006d71c0 create=MH_OK enable=MH_OK
23:43:11.260 [tid 7184] drawsite: Mars::RNG::Chance rva=0x004e6dd0 -> va=00b96dd0 create=MH_OK enable=MH_OK
23:43:11.276 [tid 7184] drawsite: Mars::RNG::NextUInt rva=0x000f7670 -> va=007a7670 create=MH_OK enable=MH_OK
23:43:11.291 [tid 7184] drawsite: Mars::RNG::FloatRange rva=0x0007d8a0 -> va=0072d8a0 create=MH_OK enable=MH_OK
23:43:11.307 [tid 7184] drawsite: Mars::RNG::IntRangeBell rva=0x004e6d80 -> va=00b96d80 create=MH_OK enable=MH_OK
23:43:11.322 [tid 7184] drawsite: Mars::RNG::GaussianRange rva=0x004e6e30 -> va=00b96e30 create=MH_OK enable=MH_OK
23:43:11.322 [tid 7184] probe: installing 8 of 12 (probes= in shim.cfg)
23:43:11.338 [tid 7184] probe: Game::ServerSpyManager::vslot13 rva=0x004877b0 -> va=00b377b0 create=MH_OK enable=MH_OK
23:43:11.354 [tid 7184] probe: Game::ServerSpyManager::vslot14 rva=0x0048db80 -> va=00b3db80 create=MH_OK enable=MH_OK
23:43:11.369 [tid 7184] probe: Game::ServerTradeManagerImpl::vslot13 rva=0x0048ef80 -> va=00b3ef80 create=MH_OK enable=MH_OK
23:43:11.385 [tid 7184] probe: Game::ServerTradeManagerImpl::vslot15 rva=0x0042cca0 -> va=00adcca0 create=MH_OK enable=MH_OK
23:43:11.401 [tid 7184] probe: Game::SpyManager::Slot13RngCallee rva=0x004408e0 -> va=00af08e0 create=MH_OK enable=MH_OK
23:43:11.416 [tid 7184] probe: Game::TradeManager::Slot13RngCalleeA rva=0x00420ca0 -> va=00ad0ca0 create=MH_OK enable=MH_OK
23:43:11.447 [tid 7184] probe: Game::TradeManager::Slot13RngCalleeB rva=0x0048b440 -> va=00b3b440 create=MH_OK enable=MH_OK
23:43:11.463 [tid 7184] probe: Game::ServerTradeManager::CreateRaidEncounter rva=0x004938a0 -> va=00b438a0 create=MH_OK enable=MH_OK
23:43:11.463 [tid 7184] watch: disabled (watch=off)
23:43:11.463 [tid 7184] aiorders: disabled (aiorders=off)
23:43:11.463 [tid 7184] fpu: module init, entry cw=0x027f 53bit-double/nearest; force=off value=0x0000 sample_ticks=off
23:43:11.463 [tid 7184] fpu: sample_turn=off (off releases StrategyServer::ProcessTurn for another hook)
23:43:11.463 [tid 7184] fpu: StrategyClient::EndTurn rva=0x00383be0 -> va=00a33be0 MH_CreateHook -> MH_OK (trampoline=01930c00)
23:43:11.479 [tid 7184] fpu: StrategyClient::EndTurn MH_EnableHook -> MH_OK
23:43:11.479 [tid 7184] fpu: StrategyServer::BeginProcessTurn rva=0x003d98e0 -> va=00a898e0 MH_CreateHook -> MH_ERROR_ALREADY_CREATED (trampoline=00000000)
23:43:11.479 [tid 7184] fpu: StrategyServer::ProcessTurn rva=0x003dc6c0 not installed (sampler off)
23:43:11.479 [tid 7184] fpu: DemoApp::OnTick rva=0x0049a640 not installed (sampler off)
23:43:11.479 [tid 7184] selftest: Shim::SelfTest::Fill mode=off checksum=075ef0c3 records=0
23:43:11.479 [tid 7184] Application::Initialize called (this=01968128)
23:48:37.590 [tid 7184] fpu: sample at StrategyClient::EndTurn (this=1e031680): cw=0x127f 53bit-double/nearest [no fpu.force configured]
23:48:40.778 [tid 7184] fpu: sample at StrategyClient::EndTurn (this=32a91610): cw=0x127f 53bit-double/nearest [no fpu.force configured]
23:48:40.778 [tid 7184] fpu: sample at StrategyClient::EndTurn (this=32a96d70): cw=0x127f 53bit-double/nearest [no fpu.force configured]

Binary file not shown.