Compare commits
No commits in common. "132d41c44e14055dd7d99bbed07a43a339bca253" and "3f014a42eb1e44fa7364fdb6172ab6af9e27aa94" have entirely different histories.
132d41c44e
...
3f014a42eb
15 changed files with 4 additions and 838 deletions
|
|
@ -5,7 +5,7 @@ Groomed 2026-09-08. Replaces the phase-0/phase-2 backlog, all of which shipped.
|
||||||
code.** RE findings are inputs to it, and the measure of progress is how much of the game runs in
|
code.** RE findings are inputs to it, and the measure of progress is how much of the game runs in
|
||||||
our engine, verified against the original. Every number here is measured; the source is named.
|
our engine, verified against the original. Every number here is measured; the source is named.
|
||||||
|
|
||||||
## 1. The engine today (`sots-engine` main, ~33k LOC, ctest 46/46, 1,031 addresses, clean-room, MIT)
|
## 1. The engine today (`sots-engine` main `0592104`, 31,980 LOC, ctest 45/45, clean-room, MIT)
|
||||||
|
|
||||||
| module | LOC | what it is | verification |
|
| module | LOC | what it is | verification |
|
||||||
|---|---:|---|---|
|
|---|---:|---|---|
|
||||||
|
|
@ -17,12 +17,12 @@ our engine, verified against the original. Every number here is measured; the so
|
||||||
| `game/effects`, `game/events` | 1,531 | tech effects, `EventStorage` (turn-bucketed, dedup) | live: research slice 35 calls / 0 div |
|
| `game/effects`, `game/events` | 1,531 | tech effects, `EventStorage` (turn-bucketed, dedup) | live: research slice 35 calls / 0 div |
|
||||||
| `game/sim` | 3,382 | economy (`ComputeBudget`, output term), research + unlock cascade, colony, movement | live: budget 4,437 / output 24,357 / research 35 / movement 45 calls — all 0 div on declared regions |
|
| `game/sim` | 3,382 | economy (`ComputeBudget`, output term), research + unlock cascade, colony, movement | live: budget 4,437 / output 24,357 / research 35 / movement 45 calls — all 0 div on declared regions |
|
||||||
| `game/nav`, `game/combat` | 1,163 | route classifier + failure bits; retreat **planner** | offline vs 58 waypoints / 46 plans, 0 failures; retreat never instrumented |
|
| `game/nav`, `game/combat` | 1,163 | route classifier + failure bits; retreat **planner** | offline vs 58 waypoints / 46 plans, 0 failures; retreat never instrumented |
|
||||||
| `app` (`sots_turn`) | 1,754 | **the standalone**: load save → walk all three drivers' phase order → write save; divergence + completion metric | 11/11 saves run; ref pair **209 → 158** leaves, closed 51 / regressed 0 |
|
| `app` (`sots_turn`) | 1,754 | **the standalone**: load save → walk all three drivers' phase order → write save; divergence + completion metric | 11/11 saves run; ref pair **209 → 204** leaves, closed 5 / regressed 0 |
|
||||||
| `shim` | 769 + hooks | proxy `binkw32.dll` + MinHook trace/compare/replace harness — **scaffolding, to be displaced** | it is the instrument, not the product |
|
| `shim` | 769 + hooks | proxy `binkw32.dll` + MinHook trace/compare/replace harness — **scaffolding, to be displaced** | it is the instrument, not the product |
|
||||||
|
|
||||||
What the engine can do end to end today: read every shipped asset and every save byte-for-byte,
|
What the engine can do end to end today: read every shipped asset and every save byte-for-byte,
|
||||||
run the economy/research/movement/output pieces to the original's numbers, and step a turn with
|
run the economy/research/movement/output pieces to the original's numbers, and step a turn with
|
||||||
**16 of 44** driver phases and **5 of 37** tail phases modelled (10 committed). It cannot yet
|
**14 of 44** driver phases and **2 of 37** tail phases modelled (8 committed). It cannot yet
|
||||||
reproduce a turn.
|
reproduce a turn.
|
||||||
|
|
||||||
## 2. The milestone, restated
|
## 2. The milestone, restated
|
||||||
|
|
@ -45,9 +45,8 @@ turn, after the autosave**. `Summary.Checksum` is fed by `ModCount`. So, two run
|
||||||
| # | engine deliverable | module | RE input (done unless noted) | closes |
|
| # | engine deliverable | module | RE input (done unless noted) | closes |
|
||||||
|---|---|---|---|---|
|
|---|---|---|---|---|
|
||||||
| 1 | ~~income chain~~ **DONE, and it did not unblock the phases** — `TradePointsToMoney` + difficulty ×1.1 landed at **25/25** on the `BnkEl` oracle (lane E1). But `ComputeBudget`'s **turn path** calls `ComputeOutput` with the system's own `Rts` sliders, **not** `ComputeMaxIncome` — a strictly larger function where the repair pass and the unspent-industry/terraform cascades are live. **The new item 1 is `ComputeOutput` on the turn path.** | `game/sim` | lane E1; `findings/subsystems/income-term.md` | P01/P02/P03/P05/P06, ~82 leaves, 2 RNG words |
|
| 1 | ~~income chain~~ **DONE, and it did not unblock the phases** — `TradePointsToMoney` + difficulty ×1.1 landed at **25/25** on the `BnkEl` oracle (lane E1). But `ComputeBudget`'s **turn path** calls `ComputeOutput` with the system's own `Rts` sliders, **not** `ComputeMaxIncome` — a strictly larger function where the repair pass and the unspent-industry/terraform cascades are live. **The new item 1 is `ComputeOutput` on the turn path.** | `game/sim` | lane E1; `findings/subsystems/income-term.md` | P01/P02/P03/P05/P06, ~82 leaves, 2 RNG words |
|
||||||
| 2 | ~~wire `ShipCensus` + alliance mask into `BuildTurnRecord`~~ **DONE** (lane E2). T36 still blocked on two named things: the budget (item 1) and **ship construction** — a new blocker found by E2's *falsified* prediction (one census leaf short by exactly one destroyer on both pairs; no phase we run builds a ship). | `app` | lanes D2/A2 | +24 turnstats leaves when item 1 lands |
|
|
||||||
| 3 | **encounter-detection draws** in the generator model | `game/sim` + `app` | lane I bound; **lane H measuring now** | last 2 RNG words |
|
| 3 | **encounter-detection draws** in the generator model | `game/sim` + `app` | lane I bound; **lane H measuring now** | last 2 RNG words |
|
||||||
| 4 | ~~`nve` visibility record~~ **DONE** (lane E3): **closed 51, regressed 0** on the reference pair; validated on three pairs it was never fitted to — 128 leaves across five pairs, 0 regressed. The gate is `AFlags`, not `VFlags`/`EFlags`, and all three agree on nearly every corpus system so the wrong one *looks right*. Remainder: `TShn` (10/pair, gate proven NOT `AFlags`, watchpoint specified), `rcex` (6/pair, unexplained). | `game/sim` | lane E3 | **51 leaves closed** |
|
| 4 | **`nve` visibility record** | `game/sim` | one mechanism ×8, unread | 32 leaves |
|
||||||
| 5 | **post events into the save's turn bucket** (P11 + tail event phases) | `app` + `game/events` | model exists; wiring only | events subtree |
|
| 5 | **post events into the save's turn bucket** (P11 + tail event phases) | `app` + `game/events` | model exists; wiring only | events subtree |
|
||||||
| 6 | **`Player.Status` restore** (S31) | `app` | needs the writer between tail 31 and autosave — **watchpoint** | S31 regression |
|
| 6 | **`Player.Status` restore** (S31) | `app` | needs the writer between tail 31 and autosave — **watchpoint** | S31 regression |
|
||||||
| 7 | **tail phases** — bankruptcy decision half, turn results outbox swap, per-player sync | `game/sim` + `app` | lane K map; 34/37 stubs | tail subtree |
|
| 7 | **tail phases** — bankruptcy decision half, turn results outbox swap, per-player sync | `game/sim` + `app` | lane K map; 34/37 stubs | tail subtree |
|
||||||
|
|
|
||||||
|
|
@ -1,438 +0,0 @@
|
||||||
# Five probes on the running game — and a harness defect the probes found in themselves
|
|
||||||
|
|
||||||
Lane H, 2026-09-08. Program `sots` / "Sword of the Stars.exe", ImageBase 0x00400000, all addresses VAs.
|
|
||||||
Engine worktree `wip/probes`, predictions in `sots-engine/docs/H-probes.md`, **committed before the build**
|
|
||||||
(method rule 2). VM140 held for the duration; released at the end.
|
|
||||||
|
|
||||||
**The headline, in order of what depends on what.**
|
|
||||||
|
|
||||||
1. **The determinism oracle reproduces exactly.** `bb4fd9ac…` and `978041ac…`, byte for byte, after
|
|
||||||
everything this session changed. §1.
|
|
||||||
2. **A hook in this lane's own instrument changed the game's output**, and it was caught by comparing
|
|
||||||
against a save lane Z had already produced. Bisected to **one address**. Every measurement below
|
|
||||||
was then re-taken on a configuration proved byte-neutral against that save. §2.
|
|
||||||
3. The four tail callees that can draw are **entered on every single turn** and gated *inside*; the
|
|
||||||
three inner functions that hold the draws are entered **zero** times. "Never observed firing" was
|
|
||||||
about a gate, not about reachability. §3.
|
|
||||||
4. `CreateRaidEncounter` **is called** — twice on one measured turn — and **draws nothing**. The two
|
|
||||||
hypotheses a word count could not separate are separated: the candidate list is empty. §4.
|
|
||||||
5. The Zuul prediction holds exactly: **7 calls and 7 words per site, 14 not 16.** The raid loop
|
|
||||||
iterates the player vector. §5.
|
|
||||||
6. `EncounterDetect::AssignContacts` cannot fire on our corpus for a reason nobody had measured: its
|
|
||||||
caller is never called, because `EncounterDetect_Run` receives an **empty** record vector. The
|
|
||||||
2-word detection residual is somewhere else entirely, and §6 names the function it is in.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 1. The oracle, regenerated — and it reproduces
|
|
||||||
|
|
||||||
Lane N's End Turns overwrote the three autosave *contents* on VM140 and said so. The file set was
|
|
||||||
never disturbed, `ref-turn2.sav` still hashed `ab4ac2d7e2977260…` (checked before the run), and all 11
|
|
||||||
curated saves were in the repo, so nothing was lost — but the reference pair had to be remade.
|
|
||||||
|
|
||||||
Load `ref-turn2.sav`, one End Turn, build `recap-7584bad-20260908T0615Z`, `hooks=trace`:
|
|
||||||
|
|
||||||
| file | size | sha256 | historical value |
|
|
||||||
|---|---|---|---|
|
|
||||||
| `(Autosave EndTurn).sav` | 66,732 | `bb4fd9ac89f41e3bc0db2af08b18ce83417521ac4bcee695fc9fa6ce16e30948` | **identical** |
|
|
||||||
| `(Autosave).sav` | 67,219 | `978041acd168b56ed8eb3f5e42e78d5e70eae6e6517d75e659a5eb7ca3d60921` | **identical** |
|
|
||||||
|
|
||||||
**Both reproduce.** The turn-2 state on screen (Imperial Savings 289,688) and the turn-3 state
|
|
||||||
(532,369) match `determinism-oracle.md` as well.
|
|
||||||
|
|
||||||
This is worth more than a repeat. The process the oracle now runs in is not the one it was measured
|
|
||||||
in: nineteen template hooks and seven entry-point detours installed instead of one asm stub, an x87
|
|
||||||
control-word module, eight save files where there were four, and a Zuul game played to turn 64 in the
|
|
||||||
same directory in between. **The oracle is revalidated across all of that**, which is what every
|
|
||||||
Phase-2 comparison downstream of it needed.
|
|
||||||
|
|
||||||
Files: `verify/results/shim/probes/oracle-restore/h-oracle-{endturn,autosave}.sav`.
|
|
||||||
|
|
||||||
**And the same two files were put back on the VM at the end**, so the three autosaves once again carry
|
|
||||||
the documented oracle bytes. That is a restore of bytes this lane produced by an actual run today, not
|
|
||||||
a substitution of an older snapshot — the distinction lane N was right to insist on.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 2. The instrument perturbed the game. Bisected to one address.
|
|
||||||
|
|
||||||
This section is first among the probes because it invalidates a run and because nothing in this
|
|
||||||
campaign has ever tested it.
|
|
||||||
|
|
||||||
### 2.1 How it was caught
|
|
||||||
|
|
||||||
Three End Turns from `ref-turn2` under the full lane-H build gave per-turn word counts of
|
|
||||||
**19, 18, 19**. Lane Z's per-site table for the same three turns of the same save reads **19, 18, 20**.
|
|
||||||
One word apart on the third turn — the kind of difference that is easy to explain away as workload.
|
|
||||||
|
|
||||||
It was not explainable, because the **input was byte-identical**. The run's `(Autosave EndTurn).sav`
|
|
||||||
(the turn-4 pre-turn state) hashed `8648957fecf5041233f9add4c8173f78981f026f133ecdbecdb89ddd10917048`,
|
|
||||||
which is exactly lane Z's `z2-endturn.sav`. Same bytes in; different bytes out:
|
|
||||||
|
|
||||||
| | `(Autosave).sav` after the turn-4 → turn-5 End Turn | size |
|
|
||||||
|---|---|---|
|
|
||||||
| lane Z (`z2-autosave.sav`, and `z-t5-autosave.sav` from a second lane-Z run) | `b2124798470d85ea…` | 68,267 |
|
|
||||||
| lane H, full build | `058656787877cbee…` | 68,263 |
|
|
||||||
|
|
||||||
Both sides are reproducible: lane Z's value appears in two of its runs, and lane H's appeared in two
|
|
||||||
of its own (three consecutive End Turns, and a fresh load of `z2-endturn.sav` followed by one End
|
|
||||||
Turn). So this is not noise in either direction.
|
|
||||||
|
|
||||||
### 2.2 The bisect
|
|
||||||
|
|
||||||
`z2-endturn.sav` was pushed to the VM and the same single End Turn run five more times, changing one
|
|
||||||
thing each time. A `probes=` key was added to `shim.cfg` for exactly this purpose — an instrument that
|
|
||||||
can change what it measures has to be switchable, or the question cannot be asked at all.
|
|
||||||
|
|
||||||
| configuration | `(Autosave).sav` | verdict |
|
|
||||||
|---|---|---|
|
|
||||||
| `hooks=off` — the shim installs **nothing** | `b2124798…` | the un-instrumented game |
|
|
||||||
| all template hooks + 7 draw-site detours, `probes=0` | `b2124798…` | **neutral** |
|
|
||||||
| + probes 0–3 (the four phase-23/33 vtable callees) | `b2124798…` | neutral |
|
|
||||||
| + probes 4–7 (three inner callees, `CreateRaidEncounter`) | `b2124798…` | neutral |
|
|
||||||
| + probe 8 (`ServerTradeManager_GenerateTradeRaidEncounters` 0x00893290) | **`058656…`** | **perturbs** |
|
|
||||||
|
|
||||||
Two things fall out, and the first is the more important:
|
|
||||||
|
|
||||||
* **The un-instrumented game agrees with lane Z, not with lane H.** So lane Z's ledger — 7 draw-site
|
|
||||||
detours and 6 nested template hooks — **is behaviour-neutral on this pair**, and its published
|
|
||||||
numbers stand. This is the first time any lane has checked that, and it came out the right way.
|
|
||||||
* **Hooking `GenerateTradeRaidEncounters` at 0x00893290 with a MinHook detour changes the turn.**
|
|
||||||
Specifically it *suppresses* the `ServerPlayer::RollResearchEvent` draw at 0x0088df4f: the clean run
|
|
||||||
spends 20 words with that site firing once, the perturbed run spends 19 with it not firing at all.
|
|
||||||
|
|
||||||
### 2.3 What the mechanism is not
|
|
||||||
|
|
||||||
The obvious relocation hazard is **absent**. The bytes at 0x00893290 are
|
|
||||||
|
|
||||||
```
|
|
||||||
55 push ebp
|
|
||||||
8b ec mov ebp,esp
|
|
||||||
6a ff push -1 <- the 5-byte patch boundary falls exactly here
|
|
||||||
68 37 c3 99 00 push 0x99c337 ; SEH scope table
|
|
||||||
64 a1 00 00 00 00 mov eax,fs:[0] ; ... standard MSVC __try + /GS prologue
|
|
||||||
```
|
|
||||||
|
|
||||||
so MinHook's five bytes land on a clean instruction boundary, the relocated prologue is three
|
|
||||||
position-independent instructions, and there is no branch target inside the patched range. A mangled
|
|
||||||
prologue is therefore **not** the explanation, which makes this more interesting rather than less.
|
|
||||||
|
|
||||||
**The mechanism is undetermined and is recorded as an open item, not guessed at.** What is odd, and is
|
|
||||||
the thing to chase: `RollResearchEvent` runs *earlier in the turn* than trade-raid generation, so
|
|
||||||
hooking 0x00893290 changed a decision taken before the hooked function was reached. The cheapest next
|
|
||||||
probe is a compare-mode hook on `RollResearchEvent` in both configurations, reading the threshold and
|
|
||||||
the progress value it tests, on the same input save.
|
|
||||||
|
|
||||||
### 2.4 What this costs, and what it does not
|
|
||||||
|
|
||||||
* Every number in §§3–6 was re-taken at `probes=8`, the largest configuration proved byte-identical
|
|
||||||
to the un-instrumented game on this save. Nothing below rests on the perturbed run.
|
|
||||||
* The perturbed run's trace is kept — `verify/traces/h-probes-human-perturbed-t3-5.jsonl.gz`, report
|
|
||||||
`verify/results/shim/probes/human-t3-5-perturbed.txt` — labelled as such, because it is the evidence
|
|
||||||
for §2 and because its probe *entry counts* agree with the clean runs on every row.
|
|
||||||
* **The general lesson is a method rule waiting to be written.** A behavioural harness has to be
|
|
||||||
checked against the oracle, not just against itself. It cost one save comparison to find this; it
|
|
||||||
would have cost a published wrong number to find it later. The `probes=` key exists so the check is
|
|
||||||
cheap to repeat: any lane adding detours should run one End Turn from `z2-endturn.sav` and confirm
|
|
||||||
`b2124798470d85ea…` before trusting its own measurements.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 3. The four tail callees — reached on every turn, gated inside
|
|
||||||
|
|
||||||
Lane V2 closed lane K's tier-4 blind spot: four of the eleven phase-23/33 vtable targets on
|
|
||||||
`StrategyServer+0x158`/`+0x15c` reach a draw on the strategic generator, at eight instructions. Lane Z
|
|
||||||
measured 0 tail words on eight turns. Between those two facts sat a question a word count cannot
|
|
||||||
answer — *not reached*, or *reached and gated*?
|
|
||||||
|
|
||||||
Register-transparent asm stubs (`pushfl; pushal; push idx; call; popal; popfl; jmp trampoline`) count
|
|
||||||
entries without assuming a calling convention. Two saves, three measured turns:
|
|
||||||
|
|
||||||
| address | what | Human t5 | Zuul t24 | Zuul t25 |
|
|
||||||
|---|---|---|---|---|
|
|
||||||
| 0x008877b0 | `ServerSpyManager` slot 13 | **1** | **1** | **1** |
|
|
||||||
| 0x0088db80 | `ServerSpyManager` slot 14 | **1** | **1** | **1** |
|
|
||||||
| 0x0088ef80 | `ServerTradeManagerImpl` slot 13 | **1** | **1** | **1** |
|
|
||||||
| 0x0082cca0 | `ServerTradeManagerImpl` slot 15 | **1** | **1** | **1** |
|
|
||||||
| 0x008408e0 | `SpyManager_Slot13RngCallee` | 0 | 0 | 0 |
|
|
||||||
| 0x00820ca0 | `TradeManager_Slot13RngCalleeA` | 0 | 0 | 0 |
|
|
||||||
| 0x0088b440 | `TradeManager_Slot13RngCalleeB` | 0 | 0 | 0 |
|
|
||||||
|
|
||||||
**P2a and P2c both hold.** The phase-23/33 dispatch block is unconditional, exactly as lane V2 read
|
|
||||||
it; and the gate that keeps the draws from firing is **inside each outer body, above the inner call**.
|
|
||||||
No draw-site row appeared at any of the eight return addresses on any turn, and the tail's bracket
|
|
||||||
cost stayed 0 — reproducing lane Z's number under an instrument that can now say *why*.
|
|
||||||
|
|
||||||
So the correct statement is no longer "the tail draws nothing" and not even "the tail draws nothing on
|
|
||||||
these turns". It is: **the tail's four draw-bearing callees run on every turn and return before
|
|
||||||
reaching their draws.** That is a much stronger negative, and it converts the open question from
|
|
||||||
"which turns reach these functions" to "what does the gate in each body test".
|
|
||||||
|
|
||||||
### 3.1 Three of the four gates are an empty container; the fourth is not, and it was wrong
|
|
||||||
|
|
||||||
Read from the bodies (each confirmed a real function start with exactly one caller):
|
|
||||||
|
|
||||||
| callee | the loop it drives | empty when |
|
|
||||||
|---|---|---|
|
|
||||||
| `ServerTradeManagerImpl` slot 15 | vector at `tradeManager+0x3c .. +0x40` | no trade routes |
|
|
||||||
| `ServerSpyManager` slot 14 | vector at `spyManager+0x10 .. +0x14` | no spy programs |
|
|
||||||
| `ServerSpyManager` slot 13 | 1,869 bytes, same manager, not fully read | — |
|
|
||||||
| **`ServerTradeManagerImpl` slot 13** | **`GetServer()+0x64 .. +0x68` — the FLEETS vector** | **no fleets** |
|
|
||||||
|
|
||||||
That last row produced a competing prediction, **P2b-alt**, committed after the bodies were read and
|
|
||||||
before any run: on a save with fleets, slot 13's loop body runs, `TradeManager_Slot13RngCalleeA`
|
|
||||||
fires, and the tail cost is non-zero.
|
|
||||||
|
|
||||||
**P2b-alt is falsified, and cleanly.** `zuul-turn23-fleet23.sav` is named for its 23 fleets; slot 13
|
|
||||||
was entered on both its turns and 0x00820ca0 was entered **zero** times. So the fleet loop is not the
|
|
||||||
gate — the per-fleet test chain that precedes the call at 0x0088f036 is, and it rejects every fleet on
|
|
||||||
a 23-fleet save. That is a narrower and more useful place to look than "the loop never runs", and it is
|
|
||||||
the kind of wrong prediction that pays for itself: had P2b been the only one written down, its
|
|
||||||
confirmation would have been read as "no trade routes, no spies" and the fleet loop would have gone
|
|
||||||
unexamined.
|
|
||||||
|
|
||||||
### 3.2 What is still a hypothesis (rule 6)
|
|
||||||
|
|
||||||
**Whether any of the eight draw sites ever fires** is still unexercised. The workload that would
|
|
||||||
settle it — a game with live trade routes and a live spy program — was **not built**, and it is worth
|
|
||||||
saying exactly why rather than vaguely:
|
|
||||||
|
|
||||||
* SotS 1 trade income requires trade-station construction and the tech behind it; from `ref-turn2`
|
|
||||||
(turn 2, 1 colony, 0 techs) that is tens of End Turns at 30–60 s each plus a Build/Research click
|
|
||||||
path per turn. The newest Zuul save in the corpus (turn 23) still has **no** trade routes: slot 15's
|
|
||||||
loop body did not execute on it.
|
|
||||||
* The corresponding spy state is worse — no lane has yet identified which UI produces an entry in the
|
|
||||||
`spyManager+0x10` vector, so there is no click path to write down, only a container to watch.
|
|
||||||
* What this lane did instead was cheaper and, for the map, better: it established that the containers
|
|
||||||
are the gate for two of the four and that the fourth is gated per-fleet. A future lane that builds
|
|
||||||
the workload now knows which two vectors to watch fill, and can confirm the workload took *before*
|
|
||||||
spending a turn measuring.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 4. `CreateRaidEncounter` — the two hypotheses, separated
|
|
||||||
|
|
||||||
A successful trade-raid roll calls `ServerTradeManagerImpl` slot 17, `CreateRaidEncounter` 0x008938a0,
|
|
||||||
which draws a `NextInt` at 0x008939ee to pick a target but returns at 0x0089391c **without drawing**
|
|
||||||
when the candidate vector is empty. It cost 0 words on all three of lane Z's turns, which lane Z read
|
|
||||||
as consistent with either "no roll succeeded" or "the candidate list is empty".
|
|
||||||
|
|
||||||
**A correction to the prior first (rule 11).** Lane Z wrote that no roll succeeding is "≈11% on the
|
|
||||||
defaults, so three quiet turns in a row is unremarkable". 11% is the probability of a quiet *turn*:
|
|
||||||
`0.8^8 × 0.95^8 = 0.1113`. Three consecutive is `0.00138`, about **1 in 720**. The two hypotheses were
|
|
||||||
never close to equally likely, and the expected number of successes per turn is `8 × 0.2 + 8 × 0.05 = 2.0`.
|
|
||||||
|
|
||||||
**Measured:**
|
|
||||||
|
|
||||||
| turn | `CreateRaidEncounter` entries | words drawn |
|
|
||||||
|---|---|---|
|
|
||||||
| Human t4 → t5 | 0 | 0 |
|
|
||||||
| Zuul t23 → t24 | **2** | **0** |
|
|
||||||
| Zuul t24 → t25 | **1** | **0** |
|
|
||||||
| Human t3/t4/t5, perturbed run | 0 / 1 / 0 | 0 |
|
|
||||||
|
|
||||||
**Settled: rolls succeed at about the predicted rate, and the candidate list is empty every time.**
|
|
||||||
Four successful rolls were observed across five measured turns and not one of them reached the
|
|
||||||
`NextInt` at 0x008939ee — no draw-site row at return address 0x008939f3 on any turn.
|
|
||||||
|
|
||||||
For the standalone this is the useful form: on a corpus like ours the cost of a successful raid roll
|
|
||||||
is **0 further words**, and the row lane Y flagged as "the one that will bite" does not bite yet. It
|
|
||||||
will bite the first turn a raid candidate exists, and the candidate list is built by 0x0083b110 — a
|
|
||||||
function no lane has read.
|
|
||||||
|
|
||||||
**One caveat this lane found and must state.** `CreateRaidEncounter` is slot 17 and is reached by
|
|
||||||
virtual dispatch, and `GenerateTradeRaidEncounters` is **not its only caller**: `0x007d5150`, the
|
|
||||||
other subtree of `DetectEncounters` (see §6), also dispatches slot 17 —
|
|
||||||
`(**(code **)(**(int **)(S + 0x158) + 0x44))(out, player, 1, records)` — on the branch where its own
|
|
||||||
per-player roll returns 0x12. So an entry count of 2 does not prove two *trade-raid* successes; it
|
|
||||||
proves two slot-17 calls from one of two known sites. Separating them needs the return address, which
|
|
||||||
an entry counter deliberately does not capture. `tail-rng-ledger.md` §11.1's "called from
|
|
||||||
`GenerateTradeRaidEncounters` at 0x0089345e / 0x00893548 / 0x00893603" is incomplete as a list of
|
|
||||||
callers.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 5. The Zuul word count — 14, exactly as predicted
|
|
||||||
|
|
||||||
Lane Z's free prediction, restated by lane Y as a consequence of its `trade_raid.cpp` model: the two
|
|
||||||
`Chance` sites in `GenerateTradeRaidEncounters` cost one word per entry of `StrategyServer::Players`,
|
|
||||||
which holds 8 on the Human saves and 7 on the Zuul ones. Lane N ran a Zuul End Turn with the RNG hooks
|
|
||||||
off, so it could not answer.
|
|
||||||
|
|
||||||
`zuul-turn23-fleet23.sav`, two End Turns, `probes=8`:
|
|
||||||
|
|
||||||
| site | Human t5 | **Zuul t24** | **Zuul t25** |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 0x00893426 `Chance` (`TRADE_RAID_ODDS_PLAYER`) | 8 calls / 8 words | **7 / 7** | **7 / 7** |
|
|
||||||
| 0x00893513 `Chance` (`TRADE_RAID_ODDS_NPC`) | 8 calls / 8 words | **7 / 7** | **7 / 7** |
|
|
||||||
| trade-raid block total | 16 | **14** | **14** |
|
|
||||||
| turn total (attributed = measured) | 20 | **18** | **18** |
|
|
||||||
|
|
||||||
**P5 holds in full**, and it is a prediction that survived a changed workload rather than a repeat of a
|
|
||||||
recording: 7 is a number no earlier run produced. The loop iterates the server's player vector and
|
|
||||||
nothing else — not the `TeamRecord` vector it is handed, not a species list, not a fixed NPC count.
|
|
||||||
Lane Y's `trade_raid.cpp`, which counts `NumPlrs`, is right.
|
|
||||||
|
|
||||||
The Zuul turn's remaining 4 words are 2 detection (§6) and **2 research** — both `ProcessResearch`
|
|
||||||
sites (0x00587888 and 0x005878bb) fired on both turns, which is the second research site's first
|
|
||||||
appearance in a site table.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 6. `DetectEncounters` — the residual is not the `AssignContacts` family, and here is where it is
|
|
||||||
|
|
||||||
Lane Y's standalone leaves a 2-word residual per turn, attributed by lane Z to 0x0050329d
|
|
||||||
(`NextFloat`, depth 4) and 0x007929a4 (`NextInt`, depth 3), and refused to model it: *"fitting 2/turn
|
|
||||||
to three observations is not a model."* Lane I bounds the containing family at
|
|
||||||
`|contacts| × |detectors|` and showed `EncounterDetect_AssignContacts` draws one inlined `NextFloat`
|
|
||||||
per (contact, detector) trial. Lane Z found `AssignContacts` never ran.
|
|
||||||
|
|
||||||
`EncounterDetect_ProcessTeamRecord` 0x007ca640 — its only caller — was hooked, with lane I's gate
|
|
||||||
predicate, contact count and detector count recomputed from the record at hook entry.
|
|
||||||
|
|
||||||
**It never ran either. On any turn, on either save.** The hook installed (`MH_OK` for create and
|
|
||||||
enable, logged), was registered in the trace's meta line, and emitted **zero records** across five
|
|
||||||
End Turns, while the entry probe on `EncounterDetect_Run` recorded **one call per turn, every turn**.
|
|
||||||
|
|
||||||
`EncounterDetect_Run` calls `ProcessTeamRecord` once per 0x74-byte record. One call, zero records
|
|
||||||
means the vector it receives is **empty**. So:
|
|
||||||
|
|
||||||
* **`AssignContacts` cannot fire on our corpus, and the reason is one level higher than anyone
|
|
||||||
thought.** It is not that the `+0xfc` gate fails on the records — there are no records. Lane I's
|
|
||||||
`|contacts| × |detectors|` bound is correct and simply has nothing to bound yet.
|
|
||||||
* **P3b is falsified**, exactly as its falsification clause anticipated, and the depth reasoning that
|
|
||||||
produced it was wrong about the *path*, not the depth.
|
|
||||||
|
|
||||||
### 6.1 Where the two words actually are
|
|
||||||
|
|
||||||
A call-graph walk from both residual sites upward lands in the same place, and it is not
|
|
||||||
`EncounterDetect_Run`:
|
|
||||||
|
|
||||||
```
|
|
||||||
StrategyServer::DetectEncounters 0x007d7f70 (the last phase of ProcessTurn)
|
|
||||||
├─ 0x007d8469 GenerateTradeRaidEncounters [virtual] 16 words (14 on Zuul) §5
|
|
||||||
├─ 0x007d8470 EncounterDetect_Run 0x007cb080 0 words -- empty record vector
|
|
||||||
└─ 0x007d860b 0x007d5150 ** the 2 words **
|
|
||||||
├─ 0x007d52b3 0x0078cc70 -> 0x0078ccb1 0x00503200 -> NextFloat 0x0050329d 1 word
|
|
||||||
└─ 0x007d5311 / 0x007d5338 0x00792750 -> NextInt 0x007929a4 1 word
|
|
||||||
```
|
|
||||||
|
|
||||||
Every one of those edges is a direct `call` with a **single** incoming reference, so the attribution
|
|
||||||
is not a guess: 0x00503200 has exactly one caller (0x0078cc70), which has exactly one caller
|
|
||||||
(0x007d5150); 0x00792750 has two call sites, both inside 0x007d5150; and 0x007d5150 has exactly one
|
|
||||||
caller, `DetectEncounters` at 0x007d860b.
|
|
||||||
|
|
||||||
**The whole detection residual is inside one 744-byte function with one call site.** That is a far
|
|
||||||
better handle than a family bound over two counts nobody can observe, and it makes the residual a
|
|
||||||
tractable modelling target rather than a fitted constant. From its body:
|
|
||||||
|
|
||||||
* it first builds a bitmask over the same `records` vector (`entry[0]->+0xf9 == 0` sets bit
|
|
||||||
`entry[0]->+0x28`) and returns immediately if the mask is 0;
|
|
||||||
* it then loops the **player vector** at `+0x54/+0x58`, skipping players whose mask bit is set or
|
|
||||||
whose `+0xf9` is non-zero, builds a per-player candidate vector (0x00795870), and for each
|
|
||||||
non-empty one calls 0x0078cc70 — **that is the `NextFloat`** — whose non-zero return leads on, and
|
|
||||||
whose return value of **0x12 dispatches slot 17, `CreateRaidEncounter`** (§4's caveat);
|
|
||||||
* the measured cost is 1 call and 1 word per site per turn on 8-player and 7-player saves alike, so
|
|
||||||
only one player reaches the draw per turn on both — the per-player gate, not the loop bound, is what
|
|
||||||
sets the count.
|
|
||||||
|
|
||||||
That last observation is the one to hand lane Y: **the 2 words are not `2 × |players|` and not a
|
|
||||||
constant by luck — they are one per player that passes a gate, and on both corpus saves exactly one
|
|
||||||
player passes.** A model that assumes 2/turn will be right on our saves and wrong on a busier one, and
|
|
||||||
now there is a named function to read to find out when.
|
|
||||||
|
|
||||||
### 6.2 The hook that measured nothing, reported as loudly as the ones that did (rule 15)
|
|
||||||
|
|
||||||
`EncounterDetectProcessTeamRecordHook` compared nothing, because it never ran. Its coverage notes, its
|
|
||||||
gate predicate, its contact/detector classifier and its byte-vs-dword cross-check on `+0xfc` are all
|
|
||||||
**unexercised**. They are kept in the tree because the moment a save produces a non-empty record
|
|
||||||
vector they become the check that matters — but nothing in this report rests on them, and the
|
|
||||||
`fc_byte_vs_dword_disagreements` counter has never returned a value. Lane I's classification rules are
|
|
||||||
therefore still exactly as verified as they were: from the instruction stream, and from nowhere else.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 7. Coverage — what these runs did not touch
|
|
||||||
|
|
||||||
* **Five End Turns on two saves.** Human `ref-turn2` turn 4 → 5 (once clean, three times in the
|
|
||||||
perturbed run) and Zuul turn 23 → 25. That is a thin workload measured precisely.
|
|
||||||
* **No trade routes, no spy programs, no manufactured workload for either.** §3.2 says what it would
|
|
||||||
take. Until then, every statement about the eight tail draw sites is "they are not reached on a game
|
|
||||||
with none of either", and the four callees' gates are located but not read.
|
|
||||||
* **`ServerSpyManager` slot 13 (1,869 bytes) was not read.** Its loop and its gate are unexamined; it
|
|
||||||
is grouped with slot 14 above only by manager, not by evidence.
|
|
||||||
* **The probe counters cannot say which caller.** They record entries, not return addresses — a
|
|
||||||
deliberate cost of being calling-convention-agnostic — and §4 shows that costing something real.
|
|
||||||
* **The perturbation's mechanism is unknown**, and it is the largest open item this lane produced.
|
|
||||||
A second, independent instance may already exist and be undetected in some other lane's build; the
|
|
||||||
`z2-endturn.sav` → `b2124798470d85ea…` check is cheap enough that every future VM lane should run it.
|
|
||||||
* **No `Guard` region is declared by any hook in this family**, the same deliberate gap lane Z
|
|
||||||
reported: these hooks measure the generator and claim nothing about game state. `undeclared = 0` in
|
|
||||||
these traces is vacuous and must be read that way.
|
|
||||||
* **`probes=8` is proved neutral on one save and one turn.** It is not proved neutral in general, and
|
|
||||||
the Zuul numbers in §3 and §5 inherit that limit — they were taken on a configuration validated
|
|
||||||
against a Human turn, not a Zuul one.
|
|
||||||
|
|
||||||
## 8. Corrections to earlier findings
|
|
||||||
|
|
||||||
* `tail-rng-ledger.md` §11.1: the caller list for `CreateRaidEncounter` is incomplete — 0x007d5150
|
|
||||||
dispatches slot 17 as well (§4).
|
|
||||||
* `tail-rng-ledger.md` §11.1: "three quiet turns in a row is unremarkable" — it is a 1-in-720 event,
|
|
||||||
and the measurement shows the rolls do succeed (§4).
|
|
||||||
* `tail-rng-ledger.md` §11.3 and lane Y's residual table: the 2-word detection residual is **not** the
|
|
||||||
`AssignContacts` family and is not blocked on the `+0xfc` gate; `EncounterDetect_Run` receives an
|
|
||||||
empty vector, and the words are in 0x007d5150's subtree (§6).
|
|
||||||
* Lane V2's per-slot notes: "never observed firing" is confirmed for the draws and **wrong as a
|
|
||||||
statement about the callees** — all four run on every turn (§3).
|
|
||||||
* `combat-done-tail.md` tier 4 / lane V2: the trade slot-13 loop is over the **fleets** vector, and a
|
|
||||||
23-fleet save still reaches no draw (§3.1).
|
|
||||||
|
|
||||||
## 9. Files
|
|
||||||
|
|
||||||
* Predictions (pre-registered): `sots-engine/docs/H-probes.md`.
|
|
||||||
* Traces: `verify/traces/h-probes-human-t5.jsonl.gz`, `h-probes-zuul-t24-25.jsonl.gz`,
|
|
||||||
`h-probes-human-perturbed-t3-5.jsonl.gz`.
|
|
||||||
* Reports: `verify/results/shim/probes/{human-t5,zuul-t24-25,human-t3-5-perturbed}.txt`;
|
|
||||||
tool `tools/probe_report.py`.
|
|
||||||
* Oracle pair: `verify/results/shim/probes/oracle-restore/h-oracle-{endturn,autosave}.sav`.
|
|
||||||
* Addresses: `ghidra/addresses.d/lane-h.json` (3 entries, all `mapped`).
|
|
||||||
* Shim: `sots-engine/src/shim/hooks/probe_entry.{h,cpp}`, the `probes=` config key, and
|
|
||||||
`EncounterDetectProcessTeamRecordHook` in `tail_rng.{h,cpp}`; configs `shim.cfg.hprobes*`,
|
|
||||||
`shim.cfg.hp{0,4,8,9,10,11}`, `shim.cfg.hoff`.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 10. Priority insert from lane AI1 — run before the VM was released
|
|
||||||
|
|
||||||
Lane AI1 asked for its P1–P3 on the same VM session. Results are written up where they belong, in
|
|
||||||
`findings/subsystems/ai-turn-logic.md` §8.1; in brief:
|
|
||||||
|
|
||||||
* **P2 held — and it is the roadmap-level one.** `ref-turn2.sav` loaded in **two separate process
|
|
||||||
launches** (pids 3400 and 2028, killed and relaunched between), shim at `hooks=off` so nothing is
|
|
||||||
installed, one End Turn each: both autosaves byte-identical to each other and to the 2026-09-07
|
|
||||||
oracle. Byte-identity subsumes `ModCount` and `Summary.Checksum`. **The AI's turn is reproducible
|
|
||||||
from a save alone**, so `backlog.md` §2's Rung B stays as written. Seven fresh processes across two
|
|
||||||
saves agreed today in total, counting the `z2-endturn.sav` bisect runs of §2.
|
|
||||||
* **P1 could not be answered as specified.** `session.log` after a full load + End Turn of a game with
|
|
||||||
an AI player is 22 lines and contains no `RunAI:`, no `random seed` and no `AI Process Turn` line;
|
|
||||||
the game's stdout carries the same 22 and nothing more. The level-2 messages are not emitted by this
|
|
||||||
build at its default log level, so the "one log read, no build" route does not exist. A suggestive
|
|
||||||
counter-indication to the all-zero-seed prediction is recorded there as a hypothesis, with the
|
|
||||||
decisive test named (record the `this` pointer beside the return address in `draw_sites.cpp`).
|
|
||||||
* **P3 not run** — no `ModCount` watchpoint was armed and no debugger session was held.
|
|
||||||
|
|
||||||
## 11. VM140 released
|
|
||||||
|
|
||||||
Left at the **main menu**, verified by screenshot, build `recap-7584bad-20260908T0615Z`,
|
|
||||||
`shim.cfg` = the recap trace config, `SavedGames\` back to the documented **8-file** set
|
|
||||||
(`ref-turn2` still at (400,436), `zuul-turn5` at (400,465)) with the three autosaves carrying the
|
|
||||||
oracle bytes `bb4fd9ac…` / `978041ac…` / `978041ac…`. Lane H left `C:\SOTS\shimdist-h` and
|
|
||||||
`C:\SOTS\ui\h{deploy,release}.ps1` in place, plus `C:\SOTS\ui\h-oracle-{endturn,autosave}.sav` and
|
|
||||||
`ai-run{1,2}.log`.
|
|
||||||
|
|
||||||
**One VM note worth carrying forward.** The board's advice to screenshot before clicking is right but
|
|
||||||
under-specified: the *intro* screens are bright white, so a brightness threshold fires on them and the
|
|
||||||
click batch lands nowhere. The reliable detector is **redness in the main menu's Exit button** —
|
|
||||||
crop `100x14+462+674`, require `mean.r > 0.2` and `mean.g < 0.15` (the menu reads 0.327 / 0.036; the
|
|
||||||
white intro reads 1.0 / 1.0; a black screen reads 0 / 0). With that, every one of this lane's ten
|
|
||||||
launches reached the menu on the first attempt, at t+48…54 s.
|
|
||||||
|
|
@ -593,16 +593,6 @@ word**. On these three turns it cost 0, which is consistent either with no roll
|
||||||
image defaults, so three quiet turns in a row is unremarkable) or with an empty candidate list every time.
|
image defaults, so three quiet turns in a row is unremarkable) or with an empty candidate list every time.
|
||||||
A word count cannot separate those, and separating them is the cheapest experiment left on this path.
|
A word count cannot separate those, and separating them is the cheapest experiment left on this path.
|
||||||
|
|
||||||
> **SETTLED BY LANE H, 2026-09-08 — and two corrections to this paragraph**
|
|
||||||
> (`findings/control-flow/tail-probes.md` §4). An entry counter on 0x008938a0 recorded **2 calls on
|
|
||||||
> Zuul turn 24 and 1 on turn 25, all drawing 0 words**: the rolls *do* succeed and **the candidate
|
|
||||||
> list is empty**. A successful raid roll costs 0 further words on our corpus.
|
|
||||||
> (a) "three quiet turns in a row is unremarkable" understates it by three orders of magnitude —
|
|
||||||
> 11% is the probability of a quiet *turn* (`0.8^8 × 0.95^8`), so three in a row is 0.14%, about
|
|
||||||
> 1 in 720. (b) The caller list below is **incomplete**: `0x007d5150`, the other subtree of
|
|
||||||
> `DetectEncounters`, also dispatches slot 17, so an entry count of 2 is two slot-17 calls from one
|
|
||||||
> of two known sites, not two trade-raid successes.
|
|
||||||
|
|
||||||
### 11.2 Two bookkeeping corrections the raw numbers need
|
### 11.2 Two bookkeeping corrections the raw numbers need
|
||||||
|
|
||||||
The shim's raw totals are 35 / 34 / 36, not 19 / 18 / 20, and both differences are accounting rather than
|
The shim's raw totals are 35 / 34 / 36, not 19 / 18 / 20, and both differences are accounting rather than
|
||||||
|
|
@ -629,29 +619,9 @@ one object; the site instrument had to be told.
|
||||||
which is consistent with the sums reconciling exactly. **The inlined-draw path is therefore still
|
which is consistent with the sums reconciling exactly. **The inlined-draw path is therefore still
|
||||||
unexercised**, and if it fires on some other save the site sum will fall short of the bracket by exactly
|
unexercised**, and if it fires on some other save the site sum will fall short of the bracket by exactly
|
||||||
its cost — which is how it will announce itself.
|
its cost — which is how it will announce itself.
|
||||||
|
|
||||||
> **CORRECTED BY LANE H, 2026-09-08** (`tail-probes.md` §6). The gate is not the reason. Lane H
|
|
||||||
> hooked `EncounterDetect_ProcessTeamRecord` itself: it recorded **zero calls** over five End Turns
|
|
||||||
> on two saves, while an entry probe on `EncounterDetect_Run` recorded **one call per turn**. Run
|
|
||||||
> calls ProcessTeamRecord once per record, so **the record vector it receives is empty** — the
|
|
||||||
> `+0xfc` gate is never evaluated because there is nothing to evaluate it on. Lane I's
|
|
||||||
> `|contacts| × |detectors|` bound is untouched and still has nothing to bound.
|
|
||||||
>
|
|
||||||
> The same lane found where the two detection words really are, and it is not this subtree:
|
|
||||||
> `DetectEncounters` → **0x007d5150** (one call site, at 0x007d860b) → 0x0078cc70 → 0x00503200
|
|
||||||
> (`NextFloat` 0x0050329d) and → 0x00792750 (`NextInt` 0x007929a4). Every edge on that path has a
|
|
||||||
> single caller. It is one word per player that passes a per-player gate, and on both corpus saves
|
|
||||||
> exactly one player passes — so "2 per turn" is a coincidence of these saves, not a constant.
|
|
||||||
* Three turns of one save. The two `Chance` sites fired 8/8 every turn with no variation, so nothing here
|
* Three turns of one save. The two `Chance` sites fired 8/8 every turn with no variation, so nothing here
|
||||||
says what makes them fire *fewer* times, and `FUN_00893290`'s own gating is unmeasured.
|
says what makes them fire *fewer* times, and `FUN_00893290`'s own gating is unmeasured.
|
||||||
|
|
||||||
> **LANE H, 2026-09-08.** The free prediction at the end of §11.3 was run and **held exactly**: on
|
|
||||||
> `zuul-turn23-fleet23` (7-entry player vector) both sites read **7 calls / 7 words**, for **14**
|
|
||||||
> instead of 16, on two consecutive turns. The loop iterates `StrategyServer::Players` and nothing
|
|
||||||
> else. Lane H also found that **hooking 0x00893290 itself perturbs the game** (`tail-probes.md`
|
|
||||||
> §2) — but the *un-instrumented* game reproduces this document's `z2-autosave.sav` byte for byte,
|
|
||||||
> so the lane-Z instrument is behaviour-neutral and every number here stands.
|
|
||||||
|
|
||||||
**A free prediction, for whoever runs this next.** If the 8 calls per site are one per entry of the server's
|
**A free prediction, for whoever runs this next.** If the 8 calls per site are one per entry of the server's
|
||||||
player vector, then on any **Zuul** save — where that vector holds **7**, not 8 (§8) — the two sites should
|
player vector, then on any **Zuul** save — where that vector holds **7**, not 8 (§8) — the two sites should
|
||||||
cost **14** words per turn instead of 16, and the turn total should drop by 2 for the same activity. The
|
cost **14** words per turn instead of 16, and the turn total should drop by 2 for the same activity. The
|
||||||
|
|
|
||||||
|
|
@ -551,64 +551,6 @@ that never populates from the UI but does populate in an AI game is one of these
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## 8.1 Results — lane H, VM140, 2026-09-08
|
|
||||||
|
|
||||||
Run by lane H at lane AI1's request while it held VM140. Full lane-H report:
|
|
||||||
`findings/control-flow/tail-probes.md`.
|
|
||||||
|
|
||||||
### P2 — **HELD. Rung B stays as written.**
|
|
||||||
|
|
||||||
`ref-turn2.sav` (2 players: human `re` Morrigi, AI `Fane Lao`) loaded in **two separate process
|
|
||||||
launches** — the game was killed and relaunched through the `SOTS` scheduled task between them, so
|
|
||||||
different pids (`3400`, then `2028`) and a fresh address space each time — with the shim at
|
|
||||||
**`hooks=off`**, i.e. the proxy DLL forwards Bink exports and installs **nothing**. One End Turn in
|
|
||||||
each.
|
|
||||||
|
|
||||||
| run | pid | `(Autosave EndTurn).sav` | `(Autosave).sav` |
|
|
||||||
|---|---|---|---|
|
|
||||||
| 1 | 3400 | `bb4fd9ac89f41e3b…` 66,732 B | `978041acd168b56e…` 67,219 B |
|
|
||||||
| 2 | 2028 | `bb4fd9ac89f41e3b…` 66,732 B | `978041acd168b56e…` 67,219 B |
|
|
||||||
|
|
||||||
**Byte-identical**, and equal to the 2026-09-07 oracle values from a third process on a different day.
|
|
||||||
Byte-identity subsumes the specific fields P2 named: `ModCount` and `Summary.Checksum` are inside the
|
|
||||||
compared bytes, and `Summary.Checksum` is derived from the whole payload, so a single differing AI
|
|
||||||
decision anywhere would have moved it.
|
|
||||||
|
|
||||||
**So the AI's turn is reproducible from a save alone, in a fresh process.** `backlog.md` §2's Rung B
|
|
||||||
is a real target: implementing the AI and byte-matching is reachable, and `--turn-commands` injection
|
|
||||||
does not have to become the primary path.
|
|
||||||
|
|
||||||
Corroborating evidence from the same day, same method, different save and different instrument
|
|
||||||
configurations — five further fresh-process launches of `z2-endturn.sav` (`hooks=off`, and four shim
|
|
||||||
configurations) all produced `(Autosave).sav` = `b2124798470d85ea…`, matching two lane-Z runs from a
|
|
||||||
different build. Seven fresh processes, two saves, one answer.
|
|
||||||
|
|
||||||
### P1 — **not answerable from the log at the default level.**
|
|
||||||
|
|
||||||
`session.log` after a full load + End Turn of a 2-player game with one AI is **22 lines** and contains
|
|
||||||
no `RunAI:` line, no `random seed` line and no `AI Process Turn` line; `dxvklog\run.txt` (the game's
|
|
||||||
stdout) carries the same 22 lines and nothing more. So the message exists in the image but is **not
|
|
||||||
emitted at the log level this build runs at** — P1's "one log read, no build" route does not exist as
|
|
||||||
described. Whatever gates level-2 logging has not been found; a `-log` / config switch was not
|
|
||||||
searched for, and that is the cheap next step before anyone builds a hook for this.
|
|
||||||
|
|
||||||
**One suggestive observation, offered as a hypothesis and not as a result (rule 6).** Lane H's
|
|
||||||
per-call-site RNG instrument separates draws by generator instance. On every measured turn the
|
|
||||||
non-strategic rows include `FUN_005798e0`+0x30, a `NextInt` that lane Z attributed to a
|
|
||||||
`StrategyClient`'s generator at `client+0x134` — and it repeatedly consumes **more words than calls**
|
|
||||||
(3 calls / 5 words, 3 / 4, 3 / 3 across turns and saves). Extra words in a bounded `NextInt` mean its
|
|
||||||
**rejection loop ran**, which requires successive draws to differ — impossible for an all-zero MT
|
|
||||||
state, where every temper returns 0. If that generator is an *AI* client's, P1 is false. **It is not
|
|
||||||
established that it is**: the human player has a `StrategyClient` too, and lane H did not determine
|
|
||||||
which instance those rows belong to. The decisive version of this test is cheap now that the
|
|
||||||
machinery exists: record the `this` pointer alongside the return address in `draw_sites.cpp` and
|
|
||||||
compare it against each client's `+0x134`.
|
|
||||||
|
|
||||||
### P3 — **not run.** No `ModCount` watchpoint was armed; lane H held no debugger session and lane A2's
|
|
||||||
DR0 spec was not set up on this VM. Nothing was measured for or against it.
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 9. Ranked plan for `sots-engine/src/game/ai`
|
## 9. Ranked plan for `sots-engine/src/game/ai`
|
||||||
|
|
||||||
Ordered by *evidence available now* over *cost*, not by architectural tidiness.
|
Ordered by *evidence available now* over *cost*, not by architectural tidiness.
|
||||||
|
|
|
||||||
|
|
@ -17,29 +17,6 @@ resets a per-player `Status` int (4 → 0) on load, which also moves the derived
|
||||||
See "Round-trip stability" below for the canonicalization rule if a harness ever compares a loaded
|
See "Round-trip stability" below for the canonicalization rule if a harness ever compares a loaded
|
||||||
`(Autosave).sav` against a re-save of it.
|
`(Autosave).sav` against a re-save of it.
|
||||||
|
|
||||||
## Revalidated 2026-09-08 (lane H)
|
|
||||||
|
|
||||||
Lane N's End Turns overwrote the three autosave *contents* on VM140 (the file set was untouched).
|
|
||||||
Lane H regenerated the pair by the documented procedure — load `ref-turn2.sav` (`ab4ac2d7…`, verified
|
|
||||||
first), one End Turn, build `recap-7584bad-20260908T0615Z`, `hooks=trace` — and **both hashes below
|
|
||||||
reproduced exactly**: `(Autosave EndTurn).sav` 66,732 B `bb4fd9ac89f41e3b…`, `(Autosave).sav`
|
|
||||||
67,219 B `978041acd168b56e…`.
|
|
||||||
|
|
||||||
That is a stronger result than a repeat, because the process is no longer the one this document
|
|
||||||
measured: nineteen template hooks and seven RNG entry-point detours are now installed instead of a
|
|
||||||
single asm stub, an x87 control-word module is present, and `SavedGames\` holds eight files rather
|
|
||||||
than four. The oracle survives all of it. Copies kept at
|
|
||||||
`verify/results/shim/probes/oracle-restore/h-oracle-{endturn,autosave}.sav`, and the same bytes were
|
|
||||||
put back on the VM as the three autosaves.
|
|
||||||
|
|
||||||
**Related, and it belongs next to the oracle:** lane H found that a MinHook detour on
|
|
||||||
**0x00893290** changes the game's output — same input save, `(Autosave).sav` 4 bytes different and
|
|
||||||
one generator word short (`findings/control-flow/tail-probes.md` §2). The un-instrumented game and
|
|
||||||
the lane-Z instrument agree; that one hook does not. **A behavioural harness must be checked against
|
|
||||||
the oracle, not only against itself.** The cheap standing check is: load
|
|
||||||
`verify/results/shim/tailrng/z2-endturn.sav`, one End Turn, expect
|
|
||||||
`(Autosave).sav` = `b2124798470d85ea86b0e9e4abfd9e23f3be73caa1115dd7614021be76305939`.
|
|
||||||
|
|
||||||
## Environment
|
## Environment
|
||||||
|
|
||||||
* VM140 `sots-re-win10`, `C:\SOTS\Sword of the Stars.exe` GOG v1.8.1 (`ver.1.8.1 Wed Dec 13 03:38:31 2017`),
|
* VM140 `sots-re-win10`, `C:\SOTS\Sword of the Stars.exe` GOG v1.8.1 (`ver.1.8.1 Wed Dec 13 03:38:31 2017`),
|
||||||
|
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
{
|
|
||||||
"entries": [
|
|
||||||
{
|
|
||||||
"name": "SpyManager_Slot13RngCallee",
|
|
||||||
"addr": "0x008408e0",
|
|
||||||
"convention": "thiscall",
|
|
||||||
"prototype": "int (void* this, int, int) // the ONE callee through which Game::ServerSpyManager vftable 0x00a3073c slot 13 (0x008877b0) reaches the strategic generator. Exactly ONE incoming reference in the image: an unconditional call at 0x00887af2 inside that slot. Real body 0x008408e0..0x00840a59, 370 bytes. Contains three of lane V2's eight draw sites -- Chance 0x00840929, NextInt 0x008409c7, Chance 0x00840a3c -- all loading the generator as [reg+0x16c]. Named for the verified relationship (which slot reaches it, and that it draws), NOT for any semantics: its body was not read. Lane H hooks it with a register-transparent entry counter so that 'the spy path never fired' can be split into 'slot 13 was not entered' and 'slot 13 was entered and gated above this call'",
|
|
||||||
"status": "mapped",
|
|
||||||
"source": "findings/control-flow/tail-probes.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TradeManager_Slot13RngCalleeA",
|
|
||||||
"addr": "0x00820ca0",
|
|
||||||
"convention": "thiscall",
|
|
||||||
"prototype": "uint (void* fleet, char) // first of the two callees through which Game::ServerTradeManagerImpl vftable 0x00a31b74 slot 13 (0x0088ef80) reaches the strategic generator. Exactly ONE incoming reference: an unconditional call at 0x0088f036. Real body 0x00820ca0..0x00820e53, 436 bytes, containing lane V2's NextFloat site 0x00820e18. Its own body loops the vector at arg+0xa4/+0xa8 testing per-element flags, and its boolean result is what gates the call to TradeManager_Slot13RngCalleeB one instruction later. THE CALLER'S LOOP IS OVER THE FLEETS VECTOR at GetServer()+0x64/+0x68, not over a trade-route list -- which is why 'the tail draws nothing because we have no trade routes' does not explain this slot",
|
|
||||||
"status": "mapped",
|
|
||||||
"source": "findings/control-flow/tail-probes.md"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "TradeManager_Slot13RngCalleeB",
|
|
||||||
"addr": "0x0088b440",
|
|
||||||
"convention": "thiscall",
|
|
||||||
"prototype": "void (void* this, int) // second of the two callees through which Game::ServerTradeManagerImpl slot 13 (0x0088ef80) reaches the strategic generator, called at 0x0088f042 ONLY when TradeManager_Slot13RngCalleeA returned non-zero. Exactly ONE incoming reference. Real body 0x0088b440..0x0088b976, 1323 bytes, containing lane V2's NextInt site 0x0088b613 (`mov ecx,[ecx+0x16c]; add ecx,4`). The two form a two-stage gate: A draws first and decides whether B runs, so a run where A fires and B does not is a MEASUREMENT, not a gap",
|
|
||||||
"status": "mapped",
|
|
||||||
"source": "findings/control-flow/tail-probes.md"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Lane H: the entry-counter table and the encounter-detection records from a probe trace.
|
|
||||||
|
|
||||||
Two things the word-count reports cannot say, printed side by side with the words so they are read
|
|
||||||
together:
|
|
||||||
|
|
||||||
* `probe_entries` -- how many times control ENTERED each of the tail callees lane V2 proved can
|
|
||||||
reach the strategic generator, and the two trade-raid functions. A row with `calls > 0` and no
|
|
||||||
matching draw-site row is "reached and gated"; a row with `calls == 0` is "not reached". Eight
|
|
||||||
turns of `tail words = 0` could not distinguish those, and they have opposite consequences.
|
|
||||||
`installed=false` is printed as **NOT INSTALLED**, never as a zero (method rule 1).
|
|
||||||
|
|
||||||
* `ProcessTeamRecord` -- the gate predicate, contact count and detector count recomputed from the
|
|
||||||
record at hook entry, beside the words the call actually cost. When the gate is false, the
|
|
||||||
counts are a prediction of a call that will not happen; when it is true, `AssignContacts` fires
|
|
||||||
and prints the callee's own numbers, and the two can be compared.
|
|
||||||
|
|
||||||
uv run python3 tools/probe_report.py <trace.jsonl>
|
|
||||||
"""
|
|
||||||
import json
|
|
||||||
import sys
|
|
||||||
|
|
||||||
|
|
||||||
def val(tv):
|
|
||||||
return None if tv is None else tv.get("v")
|
|
||||||
|
|
||||||
|
|
||||||
def sv(struct_tv, key):
|
|
||||||
if struct_tv is None or struct_tv.get("t") != "struct":
|
|
||||||
return None
|
|
||||||
return val(struct_tv["v"].get(key))
|
|
||||||
|
|
||||||
|
|
||||||
def words_of(rec):
|
|
||||||
rng = rec.get("side", {}).get("rng", {})
|
|
||||||
wb, wa = sv(rng.get("before"), "words"), sv(rng.get("after"), "words")
|
|
||||||
return (wa - wb) if (wa is not None and wb is not None) else None
|
|
||||||
|
|
||||||
|
|
||||||
turn_no = 0
|
|
||||||
for line in open(sys.argv[1]):
|
|
||||||
d = json.loads(line)
|
|
||||||
if "hook" not in d:
|
|
||||||
continue
|
|
||||||
hook = d["hook"]
|
|
||||||
args = {a.get("n"): a for a in d.get("args", []) if a.get("n")}
|
|
||||||
|
|
||||||
if hook.endswith("ProcessTeamRecord") or hook.endswith("AssignContacts"):
|
|
||||||
w = words_of(d)
|
|
||||||
short = hook.split("::")[-1]
|
|
||||||
if short == "ProcessTeamRecord":
|
|
||||||
flags = [
|
|
||||||
(sv(e, "fb"), sv(e, "fc"), sv(e, "fc_dword"))
|
|
||||||
for e in (val(args.get("entry_flags")) or [])
|
|
||||||
]
|
|
||||||
print(
|
|
||||||
f" [{short}] call {d['call_id']} depth {d.get('depth')}: "
|
|
||||||
f"entries={val(args.get('entries'))} gate={val(args.get('gate'))} "
|
|
||||||
f"contacts={val(args.get('pred_contacts'))} "
|
|
||||||
f"detectors={val(args.get('pred_detectors'))} "
|
|
||||||
f"neither={val(args.get('pred_neither'))} "
|
|
||||||
f"max_trials={val(args.get('pred_max_trials'))} "
|
|
||||||
f"fc_byte_vs_dword_disagreements={val(args.get('fc_byte_vs_dword_disagreements'))} "
|
|
||||||
f"WORDS={w}"
|
|
||||||
)
|
|
||||||
if flags:
|
|
||||||
print(" entry flags (fb, fc, fc_dword): " + ", ".join(
|
|
||||||
f"({a},{b},0x{c:x})" for a, b, c in flags))
|
|
||||||
else:
|
|
||||||
print(
|
|
||||||
f" [{short}] call {d['call_id']} depth {d.get('depth')}: "
|
|
||||||
f"detectors={val(args.get('detectors'))} contacts={val(args.get('contacts'))} "
|
|
||||||
f"max_trials={val(args.get('max_trials'))} WORDS={w}"
|
|
||||||
)
|
|
||||||
continue
|
|
||||||
|
|
||||||
if not hook.endswith("Autosave"):
|
|
||||||
continue
|
|
||||||
if val(args.get("end_turn")) is not False:
|
|
||||||
continue
|
|
||||||
turn_no += 1
|
|
||||||
probes = val(args.get("probe_entries"))
|
|
||||||
print(f"\n=== post-turn autosave #{turn_no} (call {d['call_id']}) ===")
|
|
||||||
if probes is None:
|
|
||||||
print(" probe_entries: ABSENT -- this trace is from a build without lane H's counters")
|
|
||||||
continue
|
|
||||||
for p in probes:
|
|
||||||
name = sv(p, "name")
|
|
||||||
calls = sv(p, "calls")
|
|
||||||
total = sv(p, "calls_since_launch")
|
|
||||||
inst = sv(p, "installed")
|
|
||||||
state = f"calls={calls:<4} since_launch={total}" if inst else "**NOT INSTALLED**"
|
|
||||||
print(f" 0x{sv(p, 'rva') + 0x400000:08x} {name:<52} {state}")
|
|
||||||
|
|
@ -1,78 +0,0 @@
|
||||||
== human turns 3-5, ALL 12 probes = PERTURBED RUN, build h-probes-20260908T1636Z ==
|
|
||||||
turn: strategic 35 words / 35 calls; other generators 10 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x00587888 ProcessResearch+0x1c8 calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 8 words= 8
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 8 words= 8
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 16 words= 16 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 5
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 3 words= 3
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 19
|
|
||||||
turn: strategic 34 words / 34 calls; other generators 8 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 8 words= 8
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 8 words= 8
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 16 words= 16 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 3
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 3 words= 3
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 18
|
|
||||||
turn: strategic 35 words / 35 calls; other generators 9 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x00587888 ProcessResearch+0x1c8 calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 8 words= 8
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 8 words= 8
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 16 words= 16 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 4
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 3 words= 3
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 19
|
|
||||||
|
|
||||||
=== post-turn autosave #1 (call 3925) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=1
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=1
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=1
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=1
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=0 since_launch=0
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters calls=1 since_launch=1
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] calls=1 since_launch=1
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] calls=1 since_launch=1
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] calls=1 since_launch=1
|
|
||||||
|
|
||||||
=== post-turn autosave #2 (call 11130) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=2
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=2
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=2
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=2
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=1 since_launch=1
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters calls=1 since_launch=2
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] calls=1 since_launch=2
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] calls=1 since_launch=2
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] calls=1 since_launch=2
|
|
||||||
|
|
||||||
=== post-turn autosave #3 (call 18233) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=3
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=3
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=3
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=3
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=0 since_launch=1
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters calls=1 since_launch=3
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] calls=1 since_launch=3
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] calls=1 since_launch=3
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] calls=1 since_launch=3
|
|
||||||
|
|
@ -1,28 +0,0 @@
|
||||||
== human turn 4->5, probes=8 (behaviour-neutral), build h-probes2-20260908T1658Z ==
|
|
||||||
turn: strategic 36 words / 36 calls; other generators 10 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x00587888 ProcessResearch+0x1c8 calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x0088df4f FUN_0088df20+0x2f calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 8 words= 8
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 8 words= 8
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 16 words= 16 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 5
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 3 words= 3
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 20
|
|
||||||
|
|
||||||
=== post-turn autosave #1 (call 7) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=1
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=1
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=1
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=1
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=0 since_launch=0
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters **NOT INSTALLED**
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] **NOT INSTALLED**
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] **NOT INSTALLED**
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] **NOT INSTALLED**
|
|
||||||
Binary file not shown.
Binary file not shown.
|
|
@ -1,57 +0,0 @@
|
||||||
== zuul turn 23->25, probes=8, build h-probes2-20260908T1658Z ==
|
|
||||||
turn: strategic 32 words / 32 calls; other generators 10 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x00587888 ProcessResearch+0x1c8 calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x005878bb ProcessResearch+0x1fb calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 7 words= 7
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 7 words= 7
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 14 words= 14 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 5
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 2 words= 2
|
|
||||||
other NextFloat call 0x006ad873 FUN_006ad700+0x173 calls= 1 words= 1
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 18
|
|
||||||
turn: strategic 32 words / 32 calls; other generators 9 words / 8 calls; overflow 0
|
|
||||||
* STRAT NextFloat call 0x0050329d FUN_00503200+0x9d calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x00587888 ProcessResearch+0x1c8 calls= 1 words= 1
|
|
||||||
* STRAT NextFloat call 0x005878bb ProcessResearch+0x1fb calls= 1 words= 1
|
|
||||||
* STRAT NextInt call 0x007929a4 FUN_00792750+0x254 calls= 1 words= 1
|
|
||||||
* STRAT Chance call 0x00893426 FUN_00893290+0x196 calls= 7 words= 7
|
|
||||||
* STRAT Chance call 0x00893513 FUN_00893290+0x283 calls= 7 words= 7
|
|
||||||
STRAT NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 14 words= 14 [internal to Chance: already counted on that row]
|
|
||||||
other Chance call 0x00578d10 FUN_00578cf0+0x20 calls= 1 words= 1
|
|
||||||
other NextInt call 0x00579910 FUN_005798e0+0x30 calls= 3 words= 4
|
|
||||||
other NextInt call 0x0069dc29 FUN_0069dbb0+0x79 calls= 2 words= 2
|
|
||||||
other NextFloat call 0x006ad873 FUN_006ad700+0x173 calls= 1 words= 1
|
|
||||||
other NextFloat call 0x008e6e04 FUN_008e6dd0+0x34 calls= 1 words= 1 [internal to Chance: already counted on that row]
|
|
||||||
=> attributed strategic words: 18
|
|
||||||
|
|
||||||
=== post-turn autosave #1 (call 7) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=1
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=1
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=1
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=1
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=2 since_launch=2
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters **NOT INSTALLED**
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] **NOT INSTALLED**
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] **NOT INSTALLED**
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] **NOT INSTALLED**
|
|
||||||
|
|
||||||
=== post-turn autosave #2 (call 15) ===
|
|
||||||
0x008877b0 Game::ServerSpyManager::vslot13 calls=1 since_launch=2
|
|
||||||
0x0088db80 Game::ServerSpyManager::vslot14 calls=1 since_launch=2
|
|
||||||
0x0088ef80 Game::ServerTradeManagerImpl::vslot13 calls=1 since_launch=2
|
|
||||||
0x0082cca0 Game::ServerTradeManagerImpl::vslot15 calls=1 since_launch=2
|
|
||||||
0x008408e0 Game::SpyManager::Slot13RngCallee calls=0 since_launch=0
|
|
||||||
0x00820ca0 Game::TradeManager::Slot13RngCalleeA calls=0 since_launch=0
|
|
||||||
0x0088b440 Game::TradeManager::Slot13RngCalleeB calls=0 since_launch=0
|
|
||||||
0x008938a0 Game::ServerTradeManager::CreateRaidEncounter calls=1 since_launch=3
|
|
||||||
0x00893290 Game::ServerTradeManager::GenerateTradeRaidEncounters **NOT INSTALLED**
|
|
||||||
0x00887f30 Game::ServerSpyManager::vslot15 [control] **NOT INSTALLED**
|
|
||||||
0x008590d0 Game::ServerTradeManagerImpl::vslot14 [control] **NOT INSTALLED**
|
|
||||||
0x007cb080 Game::EncounterDetect::Run [control] **NOT INSTALLED**
|
|
||||||
Binary file not shown.
Binary file not shown.
Binary file not shown.
Loading…
Add table
Reference in a new issue