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:
parent
712d184599
commit
4a212e25b4
7 changed files with 884 additions and 1 deletions
694
findings/subsystems/spy-detection-roll.md
Normal file
694
findings/subsystems/spy-detection-roll.md
Normal 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.`
|
||||
61
ghidra/addresses.d/as.json
Normal file
61
ghidra/addresses.d/as.json
Normal 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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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]
|
||||
|
|
|
|||
BIN
verify/results/saves/as-turn14-predeploy.sav
Normal file
BIN
verify/results/saves/as-turn14-predeploy.sav
Normal file
Binary file not shown.
BIN
verify/results/saves/as-turn15-spydeployed.sav
Normal file
BIN
verify/results/saves/as-turn15-spydeployed.sav
Normal file
Binary file not shown.
114
verify/results/shim/as/as-probes8-run.shim.log
Normal file
114
verify/results/shim/as/as-probes8-run.shim.log
Normal 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]
|
||||
BIN
verify/traces/as-probes8-turn15-turn16.jsonl.gz
Normal file
BIN
verify/traces/as-probes8-turn15-turn16.jsonl.gz
Normal file
Binary file not shown.
Loading…
Add table
Reference in a new issue