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.
This commit is contained in:
alex 2026-09-08 10:06:50 -04:00
parent 9c37635ca3
commit 5b7e693bd0

View file

@ -33,36 +33,31 @@ Three sentences, then the evidence.
## 1. The instrument, and why it is not an RNG hook ## 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 Counting draws by hooking the primitives would have undercounted, and the campaign now knows by exactly how
commissioned says by how much. The image has **four** draw entry points, not three: 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 | 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
| 0x0047d830 | `Mars::RNG::NextFloat(&mt)` — `__thiscall`, no stack args, plain `ret` | **exactly 1** | — including `Mars_RNG_GaussianRange` 0x008e6e30, the only **unbounded** entry point at two words per
| 0x004271c0 | `Mars::RNG::NextInt(&mt, uint* pMax)` — `ret 4`, rejection loop, bound re-read each iteration | **1 or more** | attempt — are lane I's. Exactly **one** of the eleven inlined-draw functions is reachable from
| 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 | `StrategyServer::ProcessTurn` (0x007aa240, depth 4) and one more from the tail.
| **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 |
(Lane J's scan reaches 0x004f7670 from the other direction and calls it one of the fourteen *inlined-draw* **None of that changes a single number in this document**, and that is the point worth taking away. The
functions rather than a primitive. Both readings are of the same object and neither is wrong: it is a small instrument was never built from the primitive set or from the call graph: it reads the generator's **state**
function whose draw is inlined, so it is a draw site that calls no primitive — which is exactly why a before and after a boundary and reports the difference. An inlined draw, a draw through an entry point
call-graph sweep for `NextFloat`/`NextInt` cannot see it.) 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 The state it reads: `Mars::RNG` is
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
`{void* vptr; uint32 mt[624]; uint32* next; int32 left}`, `sizeof 0x9cc` — `next` at +0x9c4 is a **pointer** `{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 into the block and `left` at +0x9c8 is the counter, and the 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 `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. 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. The block transform is a pure function, so the blocks a generator visits form a forward-only chain.