PAR: roll parity -- an AI client's per-turn RNG word count is not fixed at any scope

The hypothesis under test was the lockstep discipline: that each run of an AI client consumes a
fixed number of draws regardless of the path it takes, so a reimplementation could keep the
generator aligned with the right COUNT and order of draws while getting the decisions wrong.

It is false, and it fails at four scopes. Measured with a new bracket on
StrategyClient::OnResumePlaying over the per-client generator at +0x134, six runs across VM140 and
VM145, five fresh processes; every unpinned run's autosaves are byte-identical to the published
oracle, so the instrument is behaviour-neutral (rules 19 and 26 both discharged).

  across clients   turn2->turn3: 3 / 0 / 0 words for AI players 32 / 496 / 512; human 0
  across turns     client 32: 3 words on turn 2, 7 on turn 1
  across processes client 512 makes ONE cl_RandRange call on turn 1 -- the research-target
                   tie-break at 0x006a8495, phase 18 -- and it cost 1 word in one process and
                   3 in another, because RNG_NextInt is an unbounded rejection loop
  per site         RNG_Chance costs ZERO words at p<=0 and p>=1

Twenty-one live draw sites in an AI turn, in twelve functions (plus two provably dead ones); two
fired on the reference turn, three on turn 1. Only three are unconditional, and all three only
given that their enclosing function was called. Six of client 32's seven turn-1 words come from
the ship-design composer 0x006ad700, which is also where the only loop-carried draw lives.

Also: cl_RandFloat 0x00579c70, a third cl_* RNG facade, found twice independently. It reaches
RNG_NextFloat by a TAIL JUMP, so no rel32 sweep for the entry points can see it -- which is why
ai-turn-logic.md 5's 'zero NextFloat calls from the AI module' reads as true and is not. All 29
call sites of the three facades are inside the AI band: the cl_* RNG facade is AI-only surface.

Positives for the engine: the AI draws from nothing but its own client's generator (foreign_words
0 on every bracket), the human client draws nothing at all, and the per-turn cost is single digits.
Rung B is unaffected. Rung C needs the decisions.
This commit is contained in:
alex 2026-09-08 19:33:00 -04:00
parent d8162529aa
commit 1893751ffa
16 changed files with 997 additions and 1 deletions

View file

@ -0,0 +1,484 @@
# Roll parity — does an AI client's turn cost a fixed number of RNG words?
- **Type:** subsystem (live measurement + static classification)
- **Status:** **verified — the answer is NO**, at every scope the question can be asked at
- **Confidence:** high on the negative (measured on two boards, four AI clients, five fresh
processes, with the instrument's autosaves byte-identical to the published oracle);
medium on the completeness of the site inventory (§6 says what is not covered)
- **Owner / date:** lane PAR · 2026-09-08 · VM140 (reference) + VM145
- **Instrument:** `sots-engine` `wip/par`, new shim module `src/shim/hooks/ai_rng.cpp`,
configs `shim.cfg.par{ctl,br,pin,orders}`, builds `par-…` … `par4-6ba2c79-20260908T2322Z`
- **Answers:** the coordinator's redirect — *"each run of an AI client consumes a fixed number of
RNG draws regardless of the path it takes"*, the lockstep-discipline hypothesis
- **Corrects:** `ai-turn-logic.md` §5 (zero NextFloat calls from the AI module), §3.1 (the phase
list is not flat), §4.3 (the `cl_*` façade has three RNG members, not two, and ten
`cl_RandRange` call sites, not eight); `ai-task-system.md` §10.1's six direct `NextInt` sites
---
## 0. The verdict, at four scopes
**The hypothesis is false, and it is false in the strongest available way: it fails at the
primitive, at the site, at the client and at the turn.** There is no scope at which an AI client's
word count is fixed.
| scope | claim | measured |
|---|---|---|
| **per turn, across clients** | all AI clients of one turn cost the same | **3 / 0 / 0** on `turn2→turn3`; **7 / 0 / 1** on `turn1→turn2` |
| **per client, across turns** | one client costs the same each turn | client 32: **3** on turn 2, **7** on turn 1 |
| **per client, across processes (path fixed)** | one client's count is a property of the board | client 512, turn 1, **one and the same `cl_RandRange` call** (the research tie-break, `0x006a8495`): **1 word** in one process, **3 words** in another |
| **per site** | a site that is reached always costs the same | `RNG_Chance` costs **0 words** at `p ≤ 0` and `p ≥ 1`; `RNG_NextInt` is an **unbounded rejection loop** |
The two shadow empires (496, 512) own nothing at all — no colonies, no fleets, no systems — and
they still do not agree with each other: on turn 1, 496 spends **0** and 512 spends **1 call
costing 1 or 3 words**. So the count is not even a function of "what the empire owns".
**What this means for `game/ai`, in one line: keeping the generator aligned requires the AI's
*decisions* to be right, not merely its draw count. C-exact does not get cheaper.** §7.
---
## 1. The instrument
`StrategyClient::OnResumePlaying 0x00777480` is an exact bracket around one client's whole turn:
`if (this->+0x12c) agent->vt[1](0x26, ev)` at +0x9d runs the entire AI Process Turn synchronously,
and `this` names the client, its player net id (`+0x148`), its agent (`+0x12c`, non-null only on an
AI client) and its private generator (`+0x134`).
One MinHook detour on that function, plus an **observer registered on lane H's existing seven
draw-site detours** rather than a second set of hooks on the same targets (MinHook allows one hook
per target, and those detours are installed unconditionally whenever `hooks != off`). Two
independent measurements of the same number, per bracket:
* `observed` — the sum over the entry points, with per-return-address attribution;
* `left_delta` — `left` (RNG+0x9c8) read straight off the object at entry and exit.
They are printed together with their difference on **every** bracket, including when it is zero.
Also printed unconditionally: `agent=`, so "this client has no AI" is distinguishable from "the AI
ran and drew nothing" (method rule 20); and `foreign_words`, the count of draws that happened inside
the bracket on some *other* generator.
Two further hooks, on `cl_Chance 0x00578cf0` and `cl_RandRange 0x005798e0`, record
`__builtin_return_address(0)` so a draw is attributed to the **AI call site** and not to the one
address inside the façade where every façade draw would otherwise pile up. `cl_RandFloat 0x00579c70`
is deliberately **not** hooked — §2.
`airng.pin_seed=<hex>` re-seeds the client generator at bracket entry, so two runs start from an
identical generator state. It changes the game's behaviour on purpose; a pinned run's autosave is
never compared against the oracle.
### 1.1 Rule 19 and rule 26, discharged
The instrumented, **unpinned** runs on the canonical pair produce
`(Autosave EndTurn).sav = bb4fd9ac89f41e3b` and `(Autosave).sav = 978041acd168b56e` —
**byte-identical to the published oracle**, which was produced with `hooks=off` in a different
process on a different day. That is a stronger control than a same-key `airng=off` pair, and it was
reproduced in **two fresh process launches** (rule 26). The detour is behaviour-neutral.
### 1.2 The instrument caught its own defect, which is the point of having two measurements
The first bracket containing a `cl_Chance` printed `observed=8` against `left_delta=7`.
`RNG_Chance`'s own body calls `RNG_NextFloat`, and **both are detoured**, so one drawn word was
reported twice — once against the game's call site and once against `0x008e6e09`, the instruction
after Chance's internal call. This is exactly the nested-entry-point double count `draw_sites.h`
warned about for `IntRangeBell`/`GaussianRange` and had never seen. Fixed by rejecting the inner
row's *words* while keeping its *call count* visible, so the nesting stays legible instead of
silently disappearing. `left_delta` was never affected — which is why there are two measurements.
---
## 2. `cl_RandFloat 0x00579c70` — a third façade, found twice, and why nobody saw it
`ai-turn-logic.md` §5 states, from a rel32 scan of the AI band for the seven RNG entry points:
*"Zero calls from the module to `NextFloat`, `Chance`, `NextUInt`, `Twist`, `Seed` or
`GaussianRange`."* The very first live bracket recorded a **`NextFloat` at return address
`0x006ad878`** — inside the AI band, inside the ship-design composer `0x006ad700`.
The mechanism is a **tail jump**:
```
00579c70 mov eax,ds:0xae4808 ; g_CurrentClientIndex
00579c75 fldz
00579c77 mov eax,[eax*4+0xae47e4] ; g_StrategyClients[idx]
00579c7e test eax,eax
00579c80 je 0x579c92 ; no current client -> return 0.0f, NO DRAW
00579c82 mov ecx,[eax+0x134] ; the per-client generator
00579c88 fstp st(0)
00579c8a lea ecx,[ecx+0x4] ; &mt
00579c8d jmp 0x47d830 ; JMP, not CALL -> RNG_NextFloat
```
A scan that follows only `E8` edges to the entry points cannot see it, and a scan that follows only
`E8` edges *from* the AI band sees a call to `0x00579c70`, an ordinary-looking façade. It has
**exactly one caller in the image**, `0x006ad873`.
Because it tail-jumps, the draw-site detour's return address is the **AI call site itself**, which
is why this is the one façade whose consumer shows up directly in a draw-site table — and why
hooking it would have *destroyed* that attribution rather than adding to it. It is left alone.
Independently re-derived by a boundary-accurate image-wide scan that follows `E9` as well as `E8`.
### 2.1 The `cl_*` RNG façade is AI-only surface
Image-wide, at real instruction boundaries:
| façade | call sites | in the AI band 0x00680000–0x006e0000 |
|---|---:|---:|
| `cl_Chance 0x00578cf0` | 18 | **18** |
| `cl_RandRange 0x005798e0` | 10 | **10** |
| `cl_RandFloat 0x00579c70` | 1 | **1** |
**Every call site of all three is in the AI module.** Nothing else in the image draws through them.
(`ai-turn-logic.md` §4.3 says eight `cl_RandRange` sites; there are ten.)
---
## 3. The measurements
Every run: load the save, one End Turn, `hooks=trace` with every template hook off. Autosave hashes
in each row. `words` is `left_delta`, the measurement taken on the generator object.
### 3.1 `turn2-state → turn3-state` — the canonical deterministic pair, VM140
Two fresh process launches, unpinned (each process seeds its AI clients differently — lane L1).
| bracket | player | AI? | run p1 | run p2 |
|---|---|---|---:|---:|
| 1 | 16 `re` (human) | no | 0 | 0 |
| 2 | 32 Fane Lao | yes | **3** | **3** |
| 3 | 496 Singularity | yes | **0** | **0** |
| 4 | 512 Singularity | yes | **0** | **0** |
| 5 | 16 (again, new turn) | no | 0 | 0 |
Both runs: `(Autosave EndTurn).sav bb4fd9ac…`, `(Autosave).sav 978041ac…` — the oracle.
Client 32's three words, both runs, same breakdown:
| site | entry | calls | words |
|---|---|---:|---:|
| `0x006ad878` — `cl_RandFloat` in `AIComposeShipBlueprint 0x006ad700` | NextFloat | 1 | 1 |
| `0x00579915` — `cl_RandRange`'s `NextInt` | NextInt | 2 | 2 |
`residual = 0` and `foreign_words = 0` on every bracket in both runs.
### 3.2 `turn1-state → turn2` — a different board for the same players, VM145
Two fresh process launches, unpinned.
| bracket | player | AI? | run p1 | run p2 |
|---|---|---|---:|---:|
| 1 | 16 (human) | no | 0 | 0 |
| 2 | 32 | yes | **7** | **7** |
| 3 | 496 | yes | **0** | **0** |
| 4 | 512 | yes | **1** | **3** |
| 5 | 16 | no | 0 | 0 |
Client 32's seven words, identical breakdown in both runs: `cl_RandFloat` ×2 = 2, `cl_RandRange`
×4 = 4, `cl_Chance` ×1 = 1.
**Client 512 is the result.** In both runs it made **exactly one `cl_RandRange` call**. That one
call cost **1 word** in the first process and **3 words** in the second. Same board, same save,
same code path, same number of calls — a different number of MT words, because `RNG_NextInt` is a
rejection loop and the two processes seeded that client differently.
### 3.3 Third process, with façade call-site attribution — which sites those words are
A third fresh process on `turn1-state`, with the two hooked façades recording their **AI call
sites** rather than the one address inside the façade:
| player | words | AI call sites (`airngcall`) |
|---|---:|---|
| 32 | 7 | `0x006ad94c` `cl_RandRange` ×2, `0x00691ea0` `cl_RandRange` ×2, `0x006adfca` `cl_Chance` ×1, plus `0x006ad878` `cl_RandFloat` ×2 |
| 496 | 0 | — |
| **512** | **1** | **`0x006a849a` `cl_RandRange` ×1** |
Two things fall out.
**Six of client 32's seven words come from the ship-design composer `0x006ad700`** — sites 13, 14
and 19 of §5. The seventh is a `cl_RandRange` in `0x00691e90`. So on this board the AI's entire
random consumption is *ship design* plus one other pick; nothing in the task system drew at all.
**Client 512's one word is the research-target tie-break.** `0x006a849a` is the return of site 10,
`0x006a8495` in `0x006a8390`, which is reached from **Process Turn phase 18** — the research-target
selection — and which is guarded by `cmp eax,[edi+4] / je return`: *the shortlist is empty or has
one member.* 496 takes that branch and draws nothing; 512 does not and draws once.
That is the mechanism behind lane L5's *"only one of three empires' research pick varies between
runs"* and lane L1's *"all three seeds move but only one empire's pick does"*, and it is now read
off the running game rather than inferred from outcomes: **two of the three empires never reach the
seed-sensitive branch at all.**
### 3.4 Pinning the seed: the same three words, and the oracle bytes unchanged
`airng.pin_seed=5A17C0DE` re-seeds each AI client's generator at bracket entry (`base + seq-1`, so
the three clients do not share a stream). On the canonical pair:
| player | idx in→out | words | sites |
|---|---|---:|---|
| 32 | 0 → 3 | **3** | `cl_RandFloat` ×1, `cl_RandRange` ×2 (`0x006ad94c`, `0x00691ea0`) |
| 496 | 0 → 0 | 0 | — |
| 512 | 0 → 0 | 0 | — |
**And the autosaves are still `bb4fd9ac…` / `978041ac…` — the oracle.** The AI's entire random
stream was replaced with a different one and the turn's output did not move by a byte. So on this
board every one of those three words is a draw *whose result never reaches the save*: two of them
pick among candidates that are equivalent as far as the recorded state is concerned, and one is the
composer's `cl_RandFloat` acceptance test. This is the first direct measurement of that phenomenon —
previous lanes could only observe that a *different* seed sometimes produced a different save.
It is also the sharpest available warning against the wrong conclusion: **"the count did not change
when the seed changed" is not evidence for roll parity.** The count is stable here because the
*path* was stable. §3.2 holds the path fixed and moves the seed, and the count moves.
### 3.5 An observation this lane could not resolve
All three `turn1-state` runs produced the same post-turn autosave, `d59bb9f2fd0eb535`, even though
client 512's stream demonstrably differed between them (1 word, 3 words, 1 word). Lane L5 reports
**three different files** from three runs of this pair on VM146, and `campaign/board.md` records
`turn1-state → turn2` as non-deterministic on that basis.
Both cannot be a complete description. Three identical outcomes from a k = 6 tie set (lane L4) is a
1-in-36 coincidence, so "it is deterministic after all" is not supported either. The honest
statement is that **the draw differs and the outcome did not**, on this guest, three times — and
that whatever maps the drawn word onto the chosen tech is not a plain uniform index into the
shortlist. Flagged, not resolved; it wants the tech id logged next to the draw, which is one line in
a probe on `0x006a8390`.
---
## 4. Why the count moves — the mechanism, read from the instruction stream
### 4.1 The primitives are not fixed-cost
`RNG_NextInt 0x004271c0`, whole body, `ret 4`:
```
4271c9 edi = *bound
4271cb edi |= edi>>1 |= edi>>2 |= edi>>4 |= edi>>8 |= edi>>16 ; mask, computed ONCE
4271f0 LOOP: lazy twist if left==0; y = *next++; left--; temper
42723e and eax,edi
427240 cmp eax,DWORD PTR [ebx] ; re-reads *bound every pass
427242 ja 0x4271f0 ; reject iff (y & mask) > bound -- UNSIGNED
```
One back-edge, **no iteration counter and no bail-out**. `E[words] = (mask+1)/(bound+1)`, in
`[1, 2)`, and **exactly 1 only when `bound+1` is a power of two**. Almost every bound in the AI turn
is a container size minus one, so extra words are the ordinary case, not the exception. Measured:
one call costing 3 words (§3.2).
`RNG_Chance 0x008e6dd0` returns **without a draw** at `p <= 0` and at `p >= 1`; otherwise exactly
one word, no rejection. Two details for a reimplementation: the unit is stored to **float32** before
the comparison, and the accept test is **strict `p > r`** — the opposite convention from the inlined
site in `EncounterDetect_AssignContacts`, where equality accepts. Two conventions in one image.
`RNG_NextFloat 0x0047d830` is exactly one word, always.
**This alone refutes the hypothesis.** A design that drew unconditionally to stay in lockstep would
not use a rejection loop, because a rejection loop's cost is not knowable in advance.
### 4.2 The draws are conditional, and mostly do not fire
Twenty-three draw call sites are reachable inside an AI turn, in twelve AI functions; **two of them
are provably dead code** (`0x006adf5d`, `0x006adf6c`: the block is guarded by `cmp esi,ebx / jle`
with `esi` and `ebx` both zeroed on every inbound edge), leaving **21 live sites**. On the reference
turn, **two** fired. On turn 1, **three**.
Sites and their guards (full table in §5). The shapes that matter:
* **species gates** — Process Turn phase 2 returns immediately unless `ClientPlayer+0x5c == 1`
(Hiver); phase 20's task creation is a seven-way species switch in which **species 4 creates
nothing at all**; the helper `0x00694820` costs 1 word for species 0/4/5/6 and 1-or-2 for
species 1/2/3;
* **empty-container gates** — nearly every `cl_RandRange` sits behind
`cmp begin,end / je skip`. This is the dominant shape;
* **success gates** — the surrender roll at phase 12 gates phases "13" and "14", which are **not
sequential phases at all** but the body of its success arm (a correction to
`ai-turn-logic.md` §3.1). Six further draw sites live in there and only run on a turn where an AI
actually rolls to surrender;
* **loop-carried draws** — `0x006ae575` is one `cl_Chance(0.2f)` **per qualifying empty section
slot** of a ship design, inside a composer that is itself called 1–3 times per design request
inside two outer loops. This is unbounded and it is where most of the variance lives.
### 4.3 The single most legible instance: 496 versus 512 on turn 1
`ai-order-emission.md` §2 establishes that all three AI players set a research target on turn 1.
496 and 512 own nothing — same `Sav = 0`, no colonies, no fleets, no systems. Yet:
* **496 draws zero words.** Its research pick is forced; the shortlist has one member, the
`cl_RandRange` that would break a tie is behind `cmp eax,[edi+4] / je return` and never runs.
* **512 draws exactly one `cl_RandRange`.** Its shortlist has more than one member, so the tie is
broken by a draw.
That is the mechanism behind lane L5's *"one of three empires' research pick varies between runs"*
and lane L1's *"all three seeds move but only one empire's pick does"*, and it is now a read of the
code rather than an inference from outcomes: **two of the three empires never reach the
seed-sensitive branch at all** — precisely the situation method rule 26's corollary warns about.
---
## 5. The draw-site table
Every RNG draw reachable inside `OnResumePlaying` for an AI client. `U` = unconditional once its
enclosing function is entered; `C` = guarded. Phase numbers follow `ai-turn-logic.md` §3.1.
| # | draw VA | enclosing fn | entry | reached from | U/C | guard |
|---:|---|---|---|---|---|---|
| 1 | `0x006c592a` | `0x006c57e0` | `cl_Chance(p)` | phase 2 | **C** | `Species == 1` (Hiver), an agent latch `+0x104`, and an empty-candidate test; `p` is live, so `Chance`'s early-outs apply |
| 2 | `0x006aeeed` | `0x006aee70` | `cl_RandRange` | phases 2, 14 via `0x006b23b0` | **C** | message-list vector empty |
| 3 | `0x006cc57e` | `0x006cb570` | `cl_Chance(clamp01(r))` | phase 7 | **C** | turn delta ≤ 15 returns; `p` clamped to `[0,1]`, so both zero-word early-outs are reachable |
| 4 | `0x006cfc24` | `0x006cf8a0` | `cl_Chance(min(1/T,0.5))` | **phase 12** | **C** | survival outlook false, or `T > 30`, or `p <= 0` |
| 5 | `0x00694830` | `0x00694820` | `cl_Chance(0.75f)` | phase 14 | **U** | first instructions of the function |
| 6–8 | `0x00694865/87/a6` | `0x00694820` | `cl_Chance(0.8f)` | phase 14 | **C** | species jump table: 1 / 3 / 2 respectively; species 0/4/5/6 draw nothing here |
| 9 | `0x006c3447` | `0x006c33e0` | `cl_RandRange` | phase 14 | **C** | `switch(arg)` — only arm 2 reaches it, and `arg` is the return of #5–8 |
| 10 | `0x006a8495` | `0x006a8390` | `cl_RandRange` | **phase 18** (research target) | **U** past 2 gates | `this == 0`, or the shortlist vector is **empty** |
| 11 | `0x006817ae` | `0x00681750` | `cl_RandRange(0,5)` | phase 20 | **C** | the **fallback** arm: only when no category scored |
| 12 | `0x0069086a` | `0x00690860` | `cl_Chance(0.5f)` | phase 20 | **U** | none — 4 instructions, `return chance ? 18 : 19` |
| 13 | `0x006ad873` | `0x006ad700` | **`cl_RandFloat`** | phases 20, 25 | **C** | an "ok" latch cleared by three tech lookups, and request-flag bit `0x40` |
| 14 | `0x006ad947` | `0x006ad700` | `cl_RandRange` | " | **C** | hull slot already filled, or zero candidates |
| 15 | `0x006adf30` | `0x006ad700` | `cl_Chance(0.5f)` | " | **C** | matching-section count ≤ 0 |
| 16 | `0x006adf3f` | `0x006ad700` | `cl_RandRange` | " | **C** | nested under #15's success |
| 17 | `0x006adf5d` | `0x006ad700` | `cl_Chance(0.8f)` | " | **DEAD** | `cmp esi,ebx / jle` with both zero on every inbound edge |
| 18 | `0x006adf6c` | `0x006ad700` | `cl_RandRange` | " | **DEAD** | same block |
| 19 | `0x006adfc5` | `0x006ad700` | `cl_Chance(0.8f)` | " | **U** in its block | both arms above converge before it |
| 20 | `0x006ae413` | `0x006ad700` | `cl_Chance(0.3f)` | " | **C** | flag bit `0x20000`, or a counter ≤ 0 |
| 21 | `0x006ae575` | `0x006ad700` | `cl_Chance(0.2f)` | " | **C, LOOP** | once per qualifying empty section slot |
| 22 | `0x006cce84` | `0x006ccdb0` | `RNG_NextInt` | phases 20, 25 (design names) | **C** | no name-gen object, a **cached** name hit, or an empty name pool |
| 23 | `0x0068bf03` | `0x0068bdc0` | `cl_RandRange(2,5)` | phase 20, **only** via `AITEscortGate::Execute` vtable slot 5 | **C** | a local bool; what sets it was not read |
**Three of twenty-one live sites are unconditional, and all three are unconditional only *given that
their function was called*** — which is itself gated. Not one draw in the AI turn is unconditional
with respect to the turn.
### 5.1 Sites on the same generator that are **outside** the turn
Listed so a later lane does not double-count them:
* **Prepare Turn only** (packet code 2, raised by `RaiseAIPrepareTurn 0x00815f20`, whose two callers
`RunAI` and `CreateGame` are both one-shot): `0x006cfa06`, `0x0069620e`, and **three** draws in
`0x0069dbb0` — `0x0069dbfd`, `0x0069dc29` and **`0x0069dcff`, which lane AI1's six-site list does
not have**. `0x0069dbb0`'s guard `if (agent->+0x36c == 0)` is a true one-shot because the third
draw **re-arms** the field to `turn + 20 + r`, which is never 0. These land at construction and
never again — verified live: none of those return addresses ever appears in a bracket.
* **Net-message paths, not the turn:** `0x0069df9d` (agent vtable slot 3) and `0x006ca11c` (slot 4)
are dispatched only from `StrategyNetworkClient::OnMessage 0x00784640`. *(This also corrects
`ai-turn-logic.md` §2, which lists `0x0069de50` as the `AIObject` packet sink: it reads `[pkt+4]`
as a 0/1/2 selector, while the four real `AIObject` sinks read `[pkt+0]` as the code — and none of
those four draws anything.)*
The load-time cost is large and asymmetric. Measured with the lifetime census on the canonical pair:
the three AI clients arrive at their first turn already **17, 424 and 427** words into their
streams, and the human client's generator is at 0. `g_GlobalRNG 0x00af6e58` shows **3 words drawn,
one per AI client construction** — a second, independent falsification of
`ai-turn-logic.md` §5.1's "every draw from it returns 0", after lane L1's.
---
## 6. What is **not** established
1. **No inlined draw runs in an AI turn — and that is now measured, not assumed.** The
`left_delta` vs `observed` residual was **0 on every bracket of every run**. Statically: an
image-wide scan for the MT tempering immediates at real instruction boundaries reproduces lane
I's 15 functions exactly, and **none is in the AI band**; the three nearest
(`0x006ec720`, `0x006f65f0`, `0x006f7890`) are `StrategyNetworkServer` and sit above
`0x006e0000`. Lane AI1's rule-16 caveat is discharged with the band bounds it asked for.
2. **The site inventory is a lower bound past two indirect hops.** `IAITask::Execute` (slot 5, all
31 classes) and the `AIObject` packet sink (slot 3, all four) were resolved; a virtual call
*inside* a task's `Execute` subtree is unmodelled. Weak evidence that it is close to saturated:
adding all 31 `Execute` roots to the 31 phase roots added exactly **one** new draw function.
3. **Only two boards were measured**, both from the same 2-player-plus-shadows game, and no
**species 5** or **Hiver** AI turn was measured. The species terms in §5 are read from the
instruction stream and are rule-6 hypotheses live. A Hiver AI would exercise sites 1 and 6 and is
the cheapest remaining workload.
4. **The dead-code claim on sites 17–18 is a static read.** No live hit has been observed, but no
run has exercised the enclosing arm heavily either.
5. **`0x0068bdc0`'s `[ebp-0x1]`** — what sets it was not read.
6. **Whether `agent+0x36c` is serialised** in the `StrategyAIAgent::Streamable` block. If it is, the
Prepare-Turn cost differs between a new game and a loaded save and the whole stream is offset.
7. **The number of ship-design requests per turn is unquantified.** The chain
phase 20/25 → `0x006ce190` → `0x006cda40` → `0x006ae620` → `0x006ad700` was confirmed as edges
and two enclosing loops, but the intermediate bodies were not read. Since the composer holds 9 of
the 21 live sites including the only loop-carried one, **this is where the variance is**, and it
is the first thing a follow-up should instrument (entry probe on `0x006ad700` logging `arg[3]`
and `0x006ae620`'s loop counter).
---
## 7. What this means for `sots-engine`
**The cheap version of `game/ai` does not exist.** The redirect's hope was that a reimplementation
could stay in lockstep by reproducing the *count and order* of draws while getting the decisions
wrong. It cannot:
* every draw in the AI turn is behind a branch on game state, so a wrong decision changes **whether**
a draw happens, not only its result;
* `RNG_NextInt`'s rejection loop makes the cost of a *single call* depend on the stream, so even a
correct call sequence cannot be costed in advance — the engine must reproduce the rejection loop
bit-for-bit (it already does; `mars/rng` is closed);
* `RNG_Chance`'s two zero-word early-outs mean a probability computed slightly wrong can change the
word count from 1 to 0 and desynchronise everything after it.
The positives, which are real:
* **the AI draws from nothing but its own client's generator.** `foreign_words = 0` on every
bracket of every run — a live confirmation of what `ai-turn-logic.md` §5 could only assert from a
direct-call scan. The strategic generator and the AI are genuinely independent, so an engine can
get the server's stream exactly right while the AI's is still approximate.
* **the human client draws nothing.** Every human bracket measured 0 words with `agent = 0`. There
is no client-side RNG on the human path at all.
* **the per-turn cost is tiny** — single digits — so the AI's generator is not a large unknown; it
is a small, sharp one.
* **the `cl_*` façade is a clean, complete, AI-only API**: three functions, 29 call sites, all in
the AI band. An engine implements exactly those three and has covered every strategic-AI draw.
The consequence for the roadmap is the one lane L1 already reached from the seed side, now with a
mechanism: **Rung B (replay a recorded command stream) is unaffected — it does not run an AI.
Rung C (behavioural AI) needs the decisions, and the decisions live in the ship-design composer and
the task system**, neither of which is modelled.
---
## 8. Appendix — the *call*-parity question, and what this lane can say about it
This lane was briefed on call parity ("does the AI reach the simulation through the same interface a
human client does?") and redirected mid-flight to roll parity. The redirect is recorded because the
call-parity deliverable is **not complete**: the ~22 `hypothesis` rows in lane Q's list table were
**not** converted by driving the UI-reachable order methods, and no new order method was found. What
follows is only what this lane's own work establishes.
**Parity holds at the wire and apply layers, and it breaks at the call layer** — the reading the
brief proposed, and nothing measured here contradicts it. Two things are added:
1. **A new, clean piece of AI-only call surface: the `cl_*` RNG façade** (§2.1). Three functions —
`cl_Chance 0x00578cf0`, `cl_RandRange 0x005798e0`, `cl_RandFloat 0x00579c70` — with 29 call sites
between them, **all 29 inside the AI module**. Nothing else in the image draws through them. This
sits alongside `cl_SetResearchTarget 0x00578f60` and `cl_EndTurn 0x00579310` (whose `+0x12c` test
makes it AI-only by construction) as part of a coherent AI-only façade over "the current client".
It is not order surface — it emits no `TurnCommands` element — but it *is* interface the engine
must implement separately, and `cl_RandFloat` was not previously known to exist.
2. **A live confirmation that the human client has no client-side RNG at all.** Every human
`OnResumePlaying` bracket measured 0 words with `agent = 0`. So the asymmetry at the call layer is
not merely "the AI calls some methods the UI does not" — the AI has an entire *stateful* facility,
a private seeded generator, that the human path never touches. `StrategyClient+0x134` exists on
the human client too, and stays at word 0 for the whole game.
For `sots-engine` the answer to the original question is therefore: **one order path, two client
APIs.** The order methods and everything downstream of `StrategyClient+0x160` are shared and need
implementing once; the AI-only surface the engine must implement separately is the `cl_*` façade
(three RNG entries plus `cl_SetResearchTarget` and `cl_EndTurn`) and the four order methods with no
UI caller that lane AI1 P4 named (`0x00762ca0`, `0x00763670`, `0x00763720`, `0x00763990`) — with
lane AI2's caveat that its own sample was incomplete still standing, because this lane did not
extend it.
---
## 9. Files
- Instrument: `sots-engine` `wip/par` — `src/shim/hooks/ai_rng.{h,cpp}`, the `draw_sites` observer
hook, `src/shim/shim.cfg.par{ctl,br,pin,orders}`
- Predictions, committed before the build: `sots-engine/docs/PAR-predictions.md`
- Raw brackets and autosave hashes: `verify/results/shim/airng/` — six runs,
`par-{br-p1,br-p2}` (canonical pair, unpinned, two fresh processes),
`par-pin-a1` (canonical pair, pinned), `par-{t1-br-p1,t1-br-p2,t1-br-p3}` (`turn1-state`, three
fresh processes, the third with façade attribution), plus `parse_airng.py`, the reader
- Addresses: `ghidra/addresses.d/lane-par.json`

View file

@ -0,0 +1,21 @@
{
"_note": "Lane PAR 2026-09-08 -- ROLL PARITY: does one run of a strategic AI client consume a fixed number of RNG words regardless of path? Live bracket on StrategyClient::OnResumePlaying over the per-client generator, plus a static classification of every draw site in the AI turn. Two entries: the client player-id offset the bracket needs, and a THIRD cl_* RNG facade nobody had recorded because it reaches the generator by a TAIL JUMP and so leaves no rel32 call edge to any RNG entry point.",
"entries": [
{
"name": "StrategyClient_off_PlayerId",
"offset": "0x00000148",
"convention": "offset",
"prototype": "int -- the owning player's NET id on a Game::StrategyClient (0x708 bytes). Verified from the instruction stream in Game::StrategyApp::RunPendingAITurns 0x00838c60: the pending-AI vector at app+0x1c..+0x20 is walked in index order and each entry `edi` is matched against the client vector at app+0xc..+0x10 with `mov esi,[edx]; cmp DWORD PTR [esi+0x148],edi; je ...` at 0x00838cf0-0x00838cf8, where esi is a StrategyClient* and edi is a player net id. Corroborated by ClientOrder_FleetTask 0x007634d0, which passes `this->+0x148` as the first argument of the local validate 0x00821cf0. LIVE CROSS-CHECK: read out of the running game by lane PAR's OnResumePlaying bracket, it gives 16 / 32 / 496 / 512 on the reference board -- exactly the player ids the lane-L4 aiorders dump prints from the submitted TurnCommands blocks",
"status": "verified",
"source": "findings/subsystems/roll-parity.md -- lane PAR 2026-09-08; instruction-stream read of dumps/sots.exe swept to the next function start (rule 17), plus a live read on VM140 in two fresh processes"
},
{
"name": "cl_RandFloat",
"addr": "0x00579c70",
"convention": "cdecl",
"prototype": "float () -- the THIRD member of the cl_* RNG facade, alongside cl_Chance 0x00578cf0 and cl_RandRange 0x005798e0. Whole 0x22-byte body: `eax = g_StrategyClients[g_CurrentClientIndex]; fldz; if (!eax) ret 0.0f; ecx = eax->+0x134; fstp st(0); lea ecx,[ecx+4]; JMP RNG_NextFloat` -- it reaches the generator by a TAIL JUMP, not a call, which is why an image-wide rel32 scan for the seven RNG entry points does not see it and why ai-turn-logic.md 5's \\\"zero calls from the AI module to NextFloat\\\" reads as true when it is not. Exactly ONE caller in the image, at 0x006ad873 inside the ship-design composer 0x006ad700, where the drawn unit is compared against 0.5. Because of the tail jump the draw-site detour records the return address 0x006ad878 -- the AI call site itself -- so this facade is the one whose consumer is directly visible in a draw-site table. Measured live: it spends exactly one MT word per call, 1 word on the reference turn 2->3 and 2 on turn 1->2",
"status": "verified",
"source": "findings/subsystems/roll-parity.md -- lane PAR 2026-09-08. Found twice independently: from the live draw-site table on VM140 (a NextFloat attributed to 0x006ad878, inside the AI band, which ai-turn-logic.md says cannot happen) and from a boundary-accurate image-wide rel32/rel8 scan that follows E9 tail jumps as well as E8 calls"
}
]
}

View file

@ -1,5 +1,5 @@
// GENERATED — do not edit. Facts about Sword of the Stars.exe (GOG 1.8.1).
// Source: sots-re ghidra/addresses.json @ 940aeca, generated 2026-09-08 by tools/gen_addresses.py
// Source: sots-re ghidra/addresses.json @ d816252, 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>
@ -2083,6 +2083,10 @@ constexpr uint32_t FlightPlan_Waypoint_Set = 0x003006e0;
constexpr uint32_t NodeRoute_Construct = 0x002e1b20;
// cdecl bool (StarFleet* f) // 90 B. FUN_006fe320(f) returns f->LocID(+0xa0) ONLY when the location's kind tag (+0x14) is 2, a DEEP-SPACE POINT -- never a system. Returns true iff any of the three position components differs by exact IEEE comparison (fucompp, test ah,0x44, jp), no epsilon. OrderFleetMove's opening snap is therefore point-only; combat-retreat-pipeline.md 2.5's 'snaps the fleet's position onto its current system' is corrected here -- a fleet parked at a system is never snapped [verified]
constexpr uint32_t StarFleet_PosDiffersFromPointLocation = 0x0040ec50;
// offset int -- the owning player's NET id on a Game::StrategyClient (0x708 bytes). Verified from the instruction stream in Game::StrategyApp::RunPendingAITurns 0x00838c60: the pending-AI vector at app+0x1c..+0x20 is walked in index order and each entry `edi` is matched against the client vector at app+0xc..+0x10 with `mov esi,[edx]; cmp DWORD PTR [esi+0x148],edi; je ...` at 0x00838cf0-0x00838cf8, where esi is a StrategyClient* and edi is a player net id. Corroborated by ClientOrder_FleetTask 0x007634d0, which passes `this->+0x148` as the first argument of the local validate 0x00821cf0. LIVE CROSS-CHECK: read out of the running game by lane PAR's OnResumePlaying bracket, it gives 16 / 32 / 496 / 512 on the reference board -- exactly the player ids the lane-L4 aiorders dump prints from the submitted TurnCommands blocks [verified]
constexpr uint32_t StrategyClient_off_PlayerId = 0x00000148;
// cdecl float () -- the THIRD member of the cl_* RNG facade, alongside cl_Chance 0x00578cf0 and cl_RandRange 0x005798e0. Whole 0x22-byte body: `eax = g_StrategyClients[g_CurrentClientIndex]; fldz; if (!eax) ret 0.0f; ecx = eax->+0x134; fstp st(0); lea ecx,[ecx+4]; JMP RNG_NextFloat` -- it reaches the generator by a TAIL JUMP, not a call, which is why an image-wide rel32 scan for the seven RNG entry points does not see it and why ai-turn-logic.md 5's \"zero calls from the AI module to NextFloat\" reads as true when it is not. Exactly ONE caller in the image, at 0x006ad873 inside the ship-design composer 0x006ad700, where the drawn unit is compared against 0.5. Because of the tail jump the draw-site detour records the return address 0x006ad878 -- the AI call site itself -- so this facade is the one whose consumer is directly visible in a draw-site table. Measured live: it spends exactly one MT word per call, 1 word on the reference turn 2->3 and 2 on turn 1->2 [verified]
constexpr uint32_t cl_RandFloat = 0x00179c70;
// data double 0xBFC3333340000000 = -0.15000000596046448 = (double)(float)-0.15f -- the elimination-limit divisor, loaded by ServerPlayer::UpdateBankruptcyLimits at 0x00818612 with `DD 05` (fld QWORD). It is the negation of g_DebtInterestRate (0x009ed188), and the two are stored separately. Named here because lane N's fix cited the value but not the address [verified]
constexpr uint32_t g_BankruptcyEliminationDivisor = 0x0062ec30;
// data float* -> 0x00b23e28. The config loader's pointer slot for the key BANKRUPTCY_PROTECTION_LIMIT_FACTOR, the same indirection pattern as ADDICTION_INCOME_MOD's (0x00aeca48 -> 0x00aeca44). Read once in the image, by UpdateBankruptcyLimits at 0x0081866e [verified]
@ -2101,6 +2105,10 @@ constexpr uint32_t TurnCommands_WriteBuildOrderList = 0x00422870;
constexpr uint32_t TurnCommands_WriteSystemRatesList = 0x0042e3d0;
// cdecl void __cdecl (Mars::IStream* s, std::list<T>* orders) -- writer for TurnCommands member +0xb8, the COLONIZE list. Per element two WriteInts (node+0x8 then node+0xc): {shipId, w}. Observed once, in zuul-turn17-orders2.sav: three ships 2512/2544/2624, each with w = 1 [verified]
constexpr uint32_t TurnCommands_WriteColonizeList = 0x00422960;
// label the SECOND of the three per-player gate loops inside StrategySim::ApplyTurnCommandBatch. `esi = block+0x20`; it tests the research-boost gate at +0x20 and applies its {spend, fraction} payload through 0x00820560, bumping ModCount inline at 0x008907bc. It runs AFTER all twenty-seven list loops, not with the other gates -- the six prologue gates are split across three loops at three points in the routine, which is why a port that applies them together as a prologue gets the order wrong [verified]
constexpr uint32_t StrategySim_ApplyTurnCommandBatch_GateLoopB = 0x004907b1;
// label the THIRD and last per-player gate loop inside StrategySim::ApplyTurnCommandBatch. `esi = block+0x24`; it tests the group-4 gate at +0x2c and applies its {bool, int} payload through 0x00821b90. It is the final step of the whole batch. Together with the loop-A head at 0x0088fdb0 and the loop-B head at 0x008907b1, and the six inlined ModCount bump sites, this gives nine positions of the thirty-step apply schedule an address-monotonicity check -- the only part of the sequence that can be re-derived rather than inherited from the read of the `add edi, imm` chain [verified]
constexpr uint32_t StrategySim_ApplyTurnCommandBatch_GateLoopC = 0x0049080a;
// thiscall int (Game::SVScriptObject* this, int evt, void* arg) // the script-object event bus. Calls this->vft[0x10](evt, arg) -- the GENERIC handler every object sees -- then `cmp evt,0x20; ja done; jmp dword [evt*4 + SVScriptObject_EventSlotJumpTable]`, which dispatches to ONE event-specific vtable slot with the argument shape that event carries. Every hand-written `vft[0x10](id,0); vft[slot]()` pair in the two turn drivers is this same two-step done on the root object [verified]
constexpr uint32_t SVScriptObject_DispatchEvent = 0x003a60d0;
// data void* [33] // evt (0..0x20) -> the vtable slot SVScriptObject_DispatchEvent calls. Slot byte offsets in evt order: 0x14 0x18 0x1c 0x20 0x24 0x28 0x2c 0x30 0x34 0x38 0x3c 0x40 0x44 0x48 0x4c 0x50 0x54 0x58 0x5c 0x60 0x64 0x6c 0x70 0x74 0x68 0x7c 0x80 0x84 0x78 0x88 0x8c 0x90 0x94. Note 0x15->+0x6c, 0x16->+0x70, 0x17->+0x74, 0x18->+0x68 and 0x1c->+0x78 are NOT in slot order [verified]

View file

@ -0,0 +1,4 @@
post.sav 67219 978041acd168b56e
pre.sav 66732 bb4fd9ac89f41e3b
shim.airng.txt 1289 eadf1cfb7efd7b37
shim.log 11259 1b186f9d4307dc7b

View file

@ -0,0 +1,9 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=011f7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000
airng seq=1 pid=16 agent=0x00000000 rng=0x0e1d9e70 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=2 pid=32 agent=0x33ded8b0 rng=0x34e67480 pin=off:00000000 left=615->612 idx=9->12 left_delta=3 observed=3 calls=3 residual=0 foreign_words=0 foreign_calls=0 sites=2 overflow=0
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=2 zero=0
airng seq=3 pid=496 agent=0x33dea030 rng=0x34ff21c8 pin=off:00000000 left=202->202 idx=422->422 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=4 pid=512 agent=0x33dea3b8 rng=0x0e19d4f0 pin=off:00000000 left=237->237 idx=387->387 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=5 pid=16 agent=0x00000000 rng=0x0e1d9e70 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0

View file

@ -0,0 +1,4 @@
post.sav 67219 978041acd168b56e
pre.sav 66732 bb4fd9ac89f41e3b
shim.airng.txt 11277 bda355cbbf07301c
shim.log 23917 56bbac0f407188e5

View file

@ -0,0 +1,116 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=011f7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000
airng seq=1 pid=16 agent=0x00000000 rng=0x0e303dd8 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airng seq=2 pid=32 agent=0x33b9fa10 rng=0x34fd6ec0 pin=off:00000000 left=613->610 idx=11->14 left_delta=3 observed=3 calls=3 residual=0 foreign_words=0 foreign_calls=0 sites=2 overflow=0 life_in=17 life_out=20
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=2 zero=0
airngen seq=2 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=2 rng=0x34fd6ec0 life_words=20 life_calls=18 <-- this bracket
airngen seq=2 rng=0x35214400 life_words=424 life_calls=295
airngen seq=2 rng=0x0e358bf8 life_words=427 life_calls=295
airngcensus seq=2 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=3
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x35214400 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=156
airngcensus seq=2 rng=0x35214400 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=253
airngcensus seq=2 rng=0x35214400 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=2 rng=0x35214400 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x35214400 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x35214400 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x35214400 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=144
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=268
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e358bf8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x34fd6ec0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=3 pid=496 agent=0x33b9fd98 rng=0x35214400 pin=off:00000000 left=206->206 idx=418->418 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=424 life_out=424
airngen seq=3 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=3 rng=0x34fd6ec0 life_words=20 life_calls=18
airngen seq=3 rng=0x35214400 life_words=424 life_calls=295 <-- this bracket
airngen seq=3 rng=0x0e358bf8 life_words=427 life_calls=295
airngcensus seq=3 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=3
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x35214400 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=156
airngcensus seq=3 rng=0x35214400 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=253
airngcensus seq=3 rng=0x35214400 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=3 rng=0x35214400 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x35214400 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x35214400 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x35214400 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=144
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=268
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e358bf8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x34fd6ec0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=4 pid=512 agent=0x33b9e868 rng=0x0e358bf8 pin=off:00000000 left=203->203 idx=421->421 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=427 life_out=427
airngen seq=4 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=4 rng=0x34fd6ec0 life_words=20 life_calls=18
airngen seq=4 rng=0x35214400 life_words=424 life_calls=295
airngen seq=4 rng=0x0e358bf8 life_words=427 life_calls=295 <-- this bracket
airngcensus seq=4 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=3
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x35214400 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=156
airngcensus seq=4 rng=0x35214400 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=253
airngcensus seq=4 rng=0x35214400 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=4 rng=0x35214400 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x35214400 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x35214400 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x35214400 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=144
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=268
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e358bf8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x34fd6ec0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=5 pid=16 agent=0x00000000 rng=0x0e303dd8 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airngen seq=5 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=5 rng=0x34fd6ec0 life_words=25 life_calls=22
airngen seq=5 rng=0x35214400 life_words=426 life_calls=297
airngen seq=5 rng=0x0e358bf8 life_words=430 life_calls=297
airngen seq=5 rng=0x0e2c9fd8 life_words=35 life_calls=35
airngcensus seq=5 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=7 words=7
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=7 words=7
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=4 words=5
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=3
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x35214400 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=156
airngcensus seq=5 rng=0x35214400 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=253
airngcensus seq=5 rng=0x35214400 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=5 rng=0x35214400 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=5 rng=0x35214400 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x35214400 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x35214400 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=144
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=268
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=6
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=3
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x0e358bf8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x34fd6ec0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x0018788d va=0x0058788d entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=16 words=16
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x0049342b va=0x0089342b entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x00493518 va=0x00893518 entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x001032a2 va=0x005032a2 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e2c9fd8 ret_rva=0x003929a9 va=0x007929a9 entry=NextInt calls=1 words=1

View file

@ -0,0 +1,4 @@
post.sav 67219 978041acd168b56e
pre.sav 66732 bb4fd9ac89f41e3b
shim.airng.txt 11699 17750bab77512434
shim.log 24522 d1b9a7b2a231b188

View file

@ -0,0 +1,121 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=011f7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=5a17c0de
airng: PINNING IS ON -- this run's autosave is EXPECTED to differ from the published oracle and must not be compared against it
airng: facade cl_Chance rva=0x00178cf0 va=00ff8cf0 create=MH_OK enable=MH_OK
airng: facade cl_RandRange rva=0x001798e0 va=00ff98e0 create=MH_OK enable=MH_OK
airng seq=1 pid=16 agent=0x00000000 rng=0x0e1c6020 pin=00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airng seq=2 pid=32 agent=0x33a03cf0 rng=0x1ec14450 pin=5a17c0df left=624->621 idx=0->3 left_delta=3 observed=3 calls=3 residual=0 foreign_words=0 foreign_calls=0 sites=2 overflow=0 life_in=9 life_out=12
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=2 zero=0
airngcall seq=2 pid=32 ret_rva=0x002ad94c va=0x006ad94c facade=cl_RandRange calls=1
airngcall seq=2 pid=32 ret_rva=0x00291ea0 va=0x00691ea0 facade=cl_RandRange calls=1
airngen seq=2 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=2 rng=0x1ec14450 life_words=12 life_calls=12 <-- this bracket
airngen seq=2 rng=0x0e1bece8 life_words=411 life_calls=287
airngen seq=2 rng=0x0e1e8678 life_words=421 life_calls=289
airngcensus seq=2 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=142
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=261
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=4 words=0
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=4 words=4
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=2
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e1bece8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=155
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=257
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x0e1e8678 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x1ec14450 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=3 pid=496 agent=0x33a04b10 rng=0x0e1bece8 pin=5a17c0e0 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=411 life_out=411
airngen seq=3 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=3 rng=0x1ec14450 life_words=12 life_calls=12
airngen seq=3 rng=0x0e1bece8 life_words=411 life_calls=287 <-- this bracket
airngen seq=3 rng=0x0e1e8678 life_words=421 life_calls=289
airngcensus seq=3 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=142
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=261
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=4 words=0
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=4 words=4
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=2
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e1bece8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=155
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=257
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x0e1e8678 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x1ec14450 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=4 pid=512 agent=0x33a04078 rng=0x0e1e8678 pin=5a17c0e1 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=421 life_out=421
airngen seq=4 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=4 rng=0x1ec14450 life_words=12 life_calls=12
airngen seq=4 rng=0x0e1bece8 life_words=411 life_calls=287
airngen seq=4 rng=0x0e1e8678 life_words=421 life_calls=289 <-- this bracket
airngcensus seq=4 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=3
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=142
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=261
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=4 words=0
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=4 words=4
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=2
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e1bece8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=155
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=257
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x0e1e8678 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x1ec14450 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airng seq=5 pid=16 agent=0x00000000 rng=0x0e1c6020 pin=00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airngen seq=5 rng=0x01576e58 life_words=3 life_calls=3
airngen seq=5 rng=0x1ec14450 life_words=15 life_calls=15
airngen seq=5 rng=0x0e1bece8 life_words=414 life_calls=289
airngen seq=5 rng=0x0e1e8678 life_words=423 life_calls=291
airngen seq=5 rng=0x0e1e0860 life_words=19 life_calls=19
airngcensus seq=5 rng=0x01576e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=7 words=0
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=7 words=7
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=4 words=4
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=142
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=261
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=4 words=0
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=4 words=4
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=4
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x0e1bece8 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=155
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=257
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x0e1e8678 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x1ec14450 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x0018788d va=0x0058788d entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=16 words=0
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x0049342b va=0x0089342b entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x00493518 va=0x00893518 entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x001032a2 va=0x005032a2 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0e1e0860 ret_rva=0x003929a9 va=0x007929a9 entry=NextInt calls=1 words=1

View file

@ -0,0 +1,4 @@
post.sav 66746 d59bb9f2fd0eb535
pre.sav 64967 a3f9dc4b49fc669c
shim.airng.txt 1575 8bc9969e2da730d2
shim.log 11620 77d758ee0b197d3a

View file

@ -0,0 +1,12 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=012b7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000
airng seq=1 pid=16 agent=0x00000000 rng=0x0d8a1fb0 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=2 pid=32 agent=0x33373040 rng=0x343f02f0 pin=off:00000000 left=611->604 idx=13->20 left_delta=7 observed=8 calls=8 residual=-1 foreign_words=0 foreign_calls=0 sites=4 overflow=0
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=4 words=4 zero=0
airngsite seq=2 pid=32 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=1 words=1 zero=0
airngsite seq=2 pid=32 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=1 words=1 zero=0
airng seq=3 pid=496 agent=0x33375718 rng=0x0d8de660 pin=off:00000000 left=230->230 idx=394->394 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=4 pid=512 agent=0x33374c80 rng=0x0d8e03e8 pin=off:00000000 left=232->231 idx=392->393 left_delta=1 observed=1 calls=1 residual=0 foreign_words=0 foreign_calls=0 sites=1 overflow=0
airngsite seq=4 pid=512 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1 zero=0
airng seq=5 pid=16 agent=0x00000000 rng=0x0d8a1fb0 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0

View file

@ -0,0 +1,4 @@
post.sav 66746 d59bb9f2fd0eb535
pre.sav 64967 a3f9dc4b49fc669c
shim.airng.txt 1574 7e50a728eb5909bd
shim.log 11619 6e5374ba9d281166

View file

@ -0,0 +1,12 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=012b7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000
airng seq=1 pid=16 agent=0x00000000 rng=0x0d69a9e8 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=2 pid=32 agent=0x32436530 rng=0x3348c720 pin=off:00000000 left=616->609 idx=8->15 left_delta=7 observed=8 calls=8 residual=-1 foreign_words=0 foreign_calls=0 sites=4 overflow=0
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=4 words=4 zero=0
airngsite seq=2 pid=32 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=1 words=1 zero=0
airngsite seq=2 pid=32 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=1 words=1 zero=0
airng seq=3 pid=496 agent=0x32435710 rng=0x0d6c2a40 pin=off:00000000 left=214->214 idx=410->410 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0
airng seq=4 pid=512 agent=0x32435a98 rng=0x0d6a2678 pin=off:00000000 left=236->233 idx=388->391 left_delta=3 observed=3 calls=1 residual=0 foreign_words=0 foreign_calls=0 sites=1 overflow=0
airngsite seq=4 pid=512 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=3 zero=0
airng seq=5 pid=16 agent=0x00000000 rng=0x0d69a9e8 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0

View file

@ -0,0 +1,4 @@
post.sav 66746 d59bb9f2fd0eb535
pre.sav 64967 a3f9dc4b49fc669c
shim.airng.txt 12254 28cdf1f6b83c1764
shim.log 25169 ac316400e2c64133

View file

@ -0,0 +1,127 @@
airng: bracket StrategyClient::OnResumePlaying rva=0x00377480 va=012b7480 create=MH_OK enable=MH_OK
airng: out=C:\SOTS\shim.airng.txt pin_seed=off:00000000
airng: facade cl_Chance rva=0x00178cf0 va=010b8cf0 create=MH_OK enable=MH_OK
airng: facade cl_RandRange rva=0x001798e0 va=010b98e0 create=MH_OK enable=MH_OK
airng seq=1 pid=16 agent=0x00000000 rng=0x1e381880 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airng seq=2 pid=32 agent=0x336e6530 rng=0x346f4fb0 pin=off:00000000 left=614->607 idx=10->17 left_delta=7 observed=7 calls=7 residual=0 foreign_words=0 foreign_calls=0 sites=4 overflow=0 life_in=10 life_out=17
airngsite seq=2 pid=32 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2 zero=0
airngsite seq=2 pid=32 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=4 words=4 zero=0
airngsite seq=2 pid=32 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=1 words=0 zero=0
airngsite seq=2 pid=32 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=1 words=1 zero=0
airngcall seq=2 pid=32 ret_rva=0x002ad94c va=0x006ad94c facade=cl_RandRange calls=2
airngcall seq=2 pid=32 ret_rva=0x00291ea0 va=0x00691ea0 facade=cl_RandRange calls=2
airngcall seq=2 pid=32 ret_rva=0x002adfca va=0x006adfca facade=cl_Chance calls=1
airngen seq=2 rng=0x01636e58 life_words=3 life_calls=3
airngen seq=2 rng=0x346f4fb0 life_words=17 life_calls=15 <-- this bracket
airngen seq=2 rng=0x349a6018 life_words=386 life_calls=286
airngen seq=2 rng=0x1e371498 life_words=391 life_calls=289
airngcensus seq=2 rng=0x01636e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=5 words=7
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x349a6018 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=139
airngcensus seq=2 rng=0x349a6018 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=241
airngcensus seq=2 rng=0x349a6018 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=3 words=0
airngcensus seq=2 rng=0x349a6018 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=3 words=3
airngcensus seq=2 rng=0x349a6018 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x349a6018 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x349a6018 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x1e371498 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=148
airngcensus seq=2 rng=0x1e371498 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=233
airngcensus seq=2 rng=0x1e371498 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=2 rng=0x1e371498 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=2 rng=0x1e371498 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=2
airngcensus seq=2 rng=0x1e371498 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x1e371498 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=2 rng=0x346f4fb0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2
airng seq=3 pid=496 agent=0x336e5710 rng=0x349a6018 pin=off:00000000 left=238->238 idx=386->386 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=386 life_out=386
airngen seq=3 rng=0x01636e58 life_words=3 life_calls=3
airngen seq=3 rng=0x346f4fb0 life_words=17 life_calls=15
airngen seq=3 rng=0x349a6018 life_words=386 life_calls=286 <-- this bracket
airngen seq=3 rng=0x1e371498 life_words=391 life_calls=289
airngcensus seq=3 rng=0x01636e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=5 words=7
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x349a6018 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=139
airngcensus seq=3 rng=0x349a6018 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=241
airngcensus seq=3 rng=0x349a6018 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=3 words=0
airngcensus seq=3 rng=0x349a6018 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=3 words=3
airngcensus seq=3 rng=0x349a6018 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x349a6018 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x349a6018 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x1e371498 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=148
airngcensus seq=3 rng=0x1e371498 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=233
airngcensus seq=3 rng=0x1e371498 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=3 rng=0x1e371498 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=3 rng=0x1e371498 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=2
airngcensus seq=3 rng=0x1e371498 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x1e371498 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=3 rng=0x346f4fb0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2
airng seq=4 pid=512 agent=0x336e6c40 rng=0x1e371498 pin=off:00000000 left=233->232 idx=391->392 left_delta=1 observed=1 calls=1 residual=0 foreign_words=0 foreign_calls=0 sites=1 overflow=0 life_in=391 life_out=392
airngsite seq=4 pid=512 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1 zero=0
airngcall seq=4 pid=512 ret_rva=0x002a849a va=0x006a849a facade=cl_RandRange calls=1
airngen seq=4 rng=0x01636e58 life_words=3 life_calls=3
airngen seq=4 rng=0x346f4fb0 life_words=17 life_calls=15
airngen seq=4 rng=0x349a6018 life_words=386 life_calls=286
airngen seq=4 rng=0x1e371498 life_words=392 life_calls=290 <-- this bracket
airngcensus seq=4 rng=0x01636e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=5 words=7
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x349a6018 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=139
airngcensus seq=4 rng=0x349a6018 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=241
airngcensus seq=4 rng=0x349a6018 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=3 words=0
airngcensus seq=4 rng=0x349a6018 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=3 words=3
airngcensus seq=4 rng=0x349a6018 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x349a6018 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x349a6018 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x1e371498 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=148
airngcensus seq=4 rng=0x1e371498 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=233
airngcensus seq=4 rng=0x1e371498 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=4 rng=0x1e371498 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=4 rng=0x1e371498 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=3
airngcensus seq=4 rng=0x1e371498 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x1e371498 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=1 words=1
airngcensus seq=4 rng=0x346f4fb0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2
airng seq=5 pid=16 agent=0x00000000 rng=0x1e381880 pin=off:00000000 left=624->624 idx=0->0 left_delta=0 observed=0 calls=0 residual=0 foreign_words=0 foreign_calls=0 sites=0 overflow=0 life_in=0 life_out=0
airngen seq=5 rng=0x01636e58 life_words=3 life_calls=3
airngen seq=5 rng=0x346f4fb0 life_words=22 life_calls=18
airngen seq=5 rng=0x349a6018 life_words=389 life_calls=288
airngen seq=5 rng=0x1e371498 life_words=394 life_calls=292
airngen seq=5 rng=0x0d8fd1b8 life_words=22 life_calls=22
airngcensus seq=5 rng=0x01636e58 ret_rva=0x00384f9e va=0x00784f9e entry=NextUInt calls=3 words=3
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=7 words=0
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=7 words=7
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=6 words=10
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x349a6018 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=139
airngcensus seq=5 rng=0x349a6018 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=241
airngcensus seq=5 rng=0x349a6018 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=3 words=0
airngcensus seq=5 rng=0x349a6018 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=3 words=3
airngcensus seq=5 rng=0x349a6018 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=2 words=3
airngcensus seq=5 rng=0x349a6018 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x349a6018 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x1e371498 ret_rva=0x002cd02f va=0x006cd02f entry=NextInt calls=120 words=148
airngcensus seq=5 rng=0x1e371498 ret_rva=0x002cd062 va=0x006cd062 entry=NextInt calls=160 words=233
airngcensus seq=5 rng=0x1e371498 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=6 words=0
airngcensus seq=5 rng=0x1e371498 ret_rva=0x00178d15 va=0x00578d15 entry=Chance calls=6 words=6
airngcensus seq=5 rng=0x1e371498 ret_rva=0x00179915 va=0x00579915 entry=NextInt calls=3 words=4
airngcensus seq=5 rng=0x1e371498 ret_rva=0x0029dc02 va=0x0069dc02 entry=NextInt calls=1 words=1
airngcensus seq=5 rng=0x1e371498 ret_rva=0x0029dc2e va=0x0069dc2e entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x346f4fb0 ret_rva=0x002ad878 va=0x006ad878 entry=NextFloat calls=2 words=2
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x00127714 va=0x00527714 entry=NextInt calls=2 words=2
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x0018788d va=0x0058788d entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x0048df54 va=0x0088df54 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x004e6e09 va=0x008e6e09 entry=NextFloat calls=16 words=0
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x0049342b va=0x0089342b entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x00493518 va=0x00893518 entry=Chance calls=8 words=8
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x001032a2 va=0x005032a2 entry=NextFloat calls=1 words=1
airngcensus seq=5 rng=0x0d8fd1b8 ret_rva=0x003929a9 va=0x007929a9 entry=NextInt calls=1 words=1

View file

@ -0,0 +1,62 @@
#!/usr/bin/env python3
"""Lane PAR: tabulate shim.airng.txt brackets across runs."""
import re, sys, glob, os
# Nested entry points: RNG_Chance's own body calls RNG_NextFloat, and both are hooked, so a
# Chance that drew is recorded TWICE -- once against the game's call site and once against
# 0x008e6e09, the instruction after Chance's internal `call RNG_NextFloat`. The same holds for
# IntRangeBell/GaussianRange (0x008e6d80.., 0x008e6e30..), which this workload never hits.
# `left_delta` is measured on the object and is unaffected; `observed` needs this subtraction.
# This is the double-count draw_sites.h warns about, seen for the first time.
NESTED = [(0x008e6dd0, 0x008e6e30), (0x008e6d80, 0x008e6dd0), (0x008e6e30, 0x008e6ec0)]
def nested(va): return any(lo <= va < hi for lo, hi in NESTED)
SITE = {
0x008e6e09: "(inner) RNG_Chance's own NextFloat -- double-count of the Chance row",
0x00578d15: "cl_Chance->RNG_Chance (facade 0x00578cf0)",
0x006ad878: "cl_RandFloat->NextFloat @AIComposeShipBlueprint 0x006ad700",
0x00579915: "cl_RandRange->NextInt (facade 0x005798e0)",
0x0069086f: "cl_Chance(0.5f) @0x00690860",
0x006cce89: "RNG_NextInt @DesignNameGen 0x006ccdb0",
}
def parse(p):
rows=[]; sites={}; gens=[]; census=[]
for ln in open(p, errors="replace"):
m=re.match(r"airng seq=(\d+) pid=(\d+) agent=0x(\w+) rng=0x(\w+) pin=(\S+) left=(-?\d+)->(-?\d+) idx=(-?\d+)->(-?\d+) left_delta=(\d+) observed=(\d+) calls=(\d+) residual=(-?\d+) foreign_words=(\d+) foreign_calls=(\d+)", ln)
if m:
g=m.groups()
rows.append(dict(seq=int(g[0]),pid=int(g[1]),agent=g[2],rng=g[3],pin=g[4],
left_in=int(g[5]),left_out=int(g[6]),idx_in=int(g[7]),idx_out=int(g[8]),
delta=int(g[9]),obs=int(g[10]),calls=int(g[11]),resid=int(g[12]),
foreign=int(g[13])))
lm=re.search(r"life_in=(\d+) life_out=(\d+)", ln)
if lm: rows[-1]['life_in']=int(lm.group(1)); rows[-1]['life_out']=int(lm.group(2))
continue
m=re.match(r"airngsite seq=(\d+) pid=(\d+) ret_rva=\S+ va=0x(\w+) entry=(\w+) calls=(\d+) words=(\d+)", ln)
if m:
sites.setdefault(int(m.group(1)),[]).append((int(m.group(3),16),m.group(4),int(m.group(5)),int(m.group(6))))
continue
m=re.match(r"airngen seq=(\d+) rng=0x(\w+) life_words=(\d+) life_calls=(\d+)", ln)
if m: gens.append((int(m.group(1)),m.group(2),int(m.group(3)),int(m.group(4))))
m=re.match(r"airngcensus seq=(\d+) rng=0x(\w+) ret_rva=\S+ va=0x(\w+) entry=(\w+) calls=(\d+) words=(\d+)", ln)
if m: census.append((int(m.group(1)),m.group(2),int(m.group(3),16),m.group(4),int(m.group(5)),int(m.group(6))))
return rows,sites,gens,census
for p in sys.argv[1:]:
tag=os.path.basename(os.path.dirname(p))
rows,sites,gens,census=parse(p)
print(f"===== {tag} ({p})")
for r in rows:
life = f" life={r.get('life_in','?')}->{r.get('life_out','?')}" if 'life_in' in r else ""
kind = "HUMAN" if r['agent']=="00000000" else "AI "
print(f" seq={r['seq']} pid={r['pid']:4d} {kind} words={r['delta']:3d} (obs={r['obs']} calls={r['calls']} resid={r['resid']} foreign={r['foreign']}) idx {r['idx_in']}->{r['idx_out']}{life} pin={r['pin']}")
dbl = sum(w for va,_,_,w in sites.get(r['seq'],[]) if nested(va))
if dbl:
print(f" [nested-entry double count: {dbl} word(s); true site sum = {r['obs']-dbl} = left_delta {r['delta']}]")
for va,entry,calls,words in sites.get(r['seq'],[]):
mark = " (nested)" if nested(va) else ""
print(f" 0x{va:08x} {entry:10s} calls={calls} words={words}{mark} {SITE.get(va,'')}")
if gens:
last=max(g[0] for g in gens)
print(" -- generator census at last bracket --")
for seq,rng,w,c in gens:
if seq==last: print(f" rng=0x{rng} lifetime_words={w} calls={c}")