From 5b7e693bd05e9ac10708ba23b5cf146e6f627cce Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 10:06:50 -0400 Subject: [PATCH] Z: defer to lane I's audited inventory -- seven entry points, eleven inlined-draw functions The primitive count went three -> four (this lane) -> seven (lane I) while this measurement was being taken, and not one number in the ledger moved. That is the argument for reading state instead of counting calls, stated where it is now demonstrable rather than merely asserted. --- findings/control-flow/tail-rng-ledger.md | 47 +++++++++++------------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/findings/control-flow/tail-rng-ledger.md b/findings/control-flow/tail-rng-ledger.md index 65ee0ee..1d67655 100644 --- a/findings/control-flow/tail-rng-ledger.md +++ b/findings/control-flow/tail-rng-ledger.md @@ -33,36 +33,31 @@ Three sentences, then the evidence. ## 1. The instrument, and why it is not an RNG hook -Counting draws by hooking the primitives would have undercounted, and the static work this lane -commissioned says by how much. The image has **four** draw entry points, not three: +Counting draws by hooking the primitives would have undercounted, and the campaign now knows by exactly how +much. This lane's scan of the tempering immediates found **four** draw entry points where every prior lane's +primitive set had three, and twelve functions with inlined draws. Lane J then found fourteen inlined-draw +functions; lane I re-ran the scan at real instruction boundaries and audited it site by site, and its +numbers are the ones to use: **seven entry points** and **eleven game functions with inlined draws over 28 +sites**, with a brute byte scan finding zero orphans (`findings/control-flow/inlined-draws.md`). -| VA | primitive | words per call | -|---|---|---| -| 0x0047d830 | `Mars::RNG::NextFloat(&mt)` — `__thiscall`, no stack args, plain `ret` | **exactly 1** | -| 0x004271c0 | `Mars::RNG::NextInt(&mt, uint* pMax)` — `ret 4`, rejection loop, bound re-read each iteration | **1 or more** | -| 0x008e6dd0 | `Mars::RNG::Chance(RNG*, float p)` — `ret 4`; takes the **object** base and does the `add ecx,4` itself | **0 or 1** — see §6.1 | -| **0x004f7670** | **`Mars::RNG::NextUInt(RNG*)`** — plain `ret`, no stack args, 84 B. **In no previous lane's primitive set**, and called from 0x007b6700 inside `ProcessTurn`'s closure | 1 | +So the honest sequence is: three → four (this lane) → seven (lane I). The fourth, +`Mars::RNG::NextUInt` 0x004f7670, is the one this lane's static work surfaced; the fifth, sixth and seventh +— including `Mars_RNG_GaussianRange` 0x008e6e30, the only **unbounded** entry point at two words per +attempt — are lane I's. Exactly **one** of the eleven inlined-draw functions is reachable from +`StrategyServer::ProcessTurn` (0x007aa240, depth 4) and one more from the tail. -(Lane J's scan reaches 0x004f7670 from the other direction and calls it one of the fourteen *inlined-draw* -functions rather than a primitive. Both readings are of the same object and neither is wrong: it is a small -function whose draw is inlined, so it is a draw site that calls no primitive — which is exactly why a -call-graph sweep for `NextFloat`/`NextInt` cannot see it.) +**None of that changes a single number in this document**, and that is the point worth taking away. The +instrument was never built from the primitive set or from the call graph: it reads the generator's **state** +before and after a boundary and reports the difference. An inlined draw, a draw through an entry point +nobody had named, a draw through a vtable — all of them move `left`, and all of them are counted. That is why method rule 16 (*any RNG accounting built from the call graph alone is a +lower bound*) does not apply to it, and why three revisions of the primitive inventory landed underneath +this measurement without disturbing it. -plus **inlined draws**: this lane's scan for the MT tempering immediates found twelve such functions, two of -them reachable from the turn roots (0x007aa240 under `ProcessTurn`, 0x007a7f30 under the combat resolver). -Lane J's independent scan, landed while this run was in flight, found **fourteen** and turned it into method -rule 16 — *inlined draws are invisible to call-graph sweeps, so any RNG accounting built from the call graph -alone is a lower bound.* Take lane J's number over this one; the two scans agree on the shape and the point -is the same either way. A primitive-counting hook would have missed every one of them silently. - -**This instrument is not built from the call graph**, which is why rule 16 does not apply to it: it reads -the generator's state before and after a boundary and reports the difference. An inlined draw, a draw in a -function nobody has named, a draw through a vtable — all of them move `left`, and all of them are counted. - -So the instrument reads **state**, not calls. `Mars::RNG` is +The state it reads: `Mars::RNG` is `{void* vptr; uint32 mt[624]; uint32* next; int32 left}`, `sizeof 0x9cc` — `next` at +0x9c4 is a **pointer** -into the block and `left` at +0x9c8 is the counter, and the three inner primitives are entered with -`ECX = &mt[0] = RNG+4`, which is what lane T's "the generator is entered at `rng+4`" note was seeing. The +into the block and `left` at +0x9c8 is the counter, and the inner primitives are entered with +`ECX = &mt[0] = RNG+4` while the outer helpers take the object base and do the `add ecx,4` themselves — +which is what lane T's "the generator is entered at `rng+4`" note was seeing. The draw is `if (left == 0) Twist(); y = *next++; --left;` — a pre-check against **0**, never −1. The block transform is a pure function, so the blocks a generator visits form a forward-only chain.