board: lane L5 - reference pair non-deterministic; interest literals verified with a failing control; verified column held at 0 on principle
This commit is contained in:
parent
ea7881acb3
commit
e4195d47d1
20 changed files with 895 additions and 2 deletions
File diff suppressed because one or more lines are too long
344
findings/control-flow/hive-creation-rng.md
Normal file
344
findings/control-flow/hive-creation-rng.md
Normal file
|
|
@ -0,0 +1,344 @@
|
|||
# Hive creation draws, inside `BeginProcessTurn` — lane Z's zero was the workload, not the code
|
||||
|
||||
- **Type:** control-flow (live measurement)
|
||||
- **Status:** **verified** — five nested boundary hooks on the running game, reconciled against a
|
||||
return-address draw-site ledger that shares no code path with them
|
||||
- **Confidence:** high on the RNG result (two instruments agree word for word on two consecutive
|
||||
turns, and the *values* the draws produce match two corpus saves exactly); see §7 for what is
|
||||
not settled
|
||||
- **Owner / date:** lane L1 · 2026-09-08 · VM140
|
||||
- **Instrument:** `sots-engine` `wip/l1`, `src/shim/hooks/tail_rng.{h,cpp}` (five new hooks),
|
||||
build `0117495-dirty-20260908T2113Z`, config `shim.cfg.l1hive`
|
||||
- **Predictions, committed before the build:** `sots-engine/docs/L1-predictions.md`
|
||||
- **Corrects:** `findings/control-flow/tail-rng-ledger.md` §0/§2 (qualified, not overturned) and
|
||||
`findings/objects/svsctob-writers.md` §4.3 (one address)
|
||||
|
||||
---
|
||||
|
||||
## 0. The answer
|
||||
|
||||
**`SVSOSwarmQueen::RegisterHives` draws one strategic-generator word per new hive, inside
|
||||
`StrategyServer::BeginProcessTurn`, which runs between the pre-turn autosave and
|
||||
`StrategyServer::ProcessTurn` — outside both turn drivers and before either.** Measured on the End
|
||||
Turn taken from `turn1-state.sav`, where the swarm's two hives are created:
|
||||
|
||||
| turn | `BeginProcessTurn` | `ProcessTurn` | tail | turn total | residual outside the drivers |
|
||||
|---|---|---|---|---|---|
|
||||
| 1 → 2 (**hives created**) | **2** | 20 | 0 | **22** | **2** |
|
||||
| 2 → 3 (hives exist) | **0** | 19 | 0 | **19** | **0** |
|
||||
|
||||
Lane SV predicted this from the instruction stream and could not run it. It is right.
|
||||
|
||||
**Read the two rows' evidence differently, because they are not equally strong.** Row 2 is a closed
|
||||
bracket: `Autosave(endTurn=1)` at word 192, `Autosave(endTurn=0)` at 211, subtotals 0 + 19 + 0,
|
||||
residual 0 by subtraction. Row 1's bracket is **incomplete by construction** — it is the first End
|
||||
Turn after a load, so the pre-turn autosave hook has no server pointer yet and records
|
||||
`words: null`, the same limitation `tail-rng-ledger.md` reports for its turn 3. Its total of 22 comes
|
||||
from the **independent per-call-site ledger** (§2.1), which sums to 22 with nothing unattributed and
|
||||
carries the hive draws as their own row. So row 1's residual is *attributed*, not *subtracted* — and
|
||||
row 2, which is subtracted, is the one that shows the residual returning to 0.
|
||||
|
||||
**What this does to `tail-rng-ledger.md`.** Its headline — *"a strategic turn advances the strategic
|
||||
generator by 18–22 words, all of it inside `StrategyServer::ProcessTurn`, and the residual outside
|
||||
the two turn drivers is exactly zero"* — is **correct for every turn it measured and false as a
|
||||
statement about the code**. All eight of its turns, and the twelve more it added later, were turns on
|
||||
which the hives already existed. On the one turn in this campaign's corpus where they do not, the
|
||||
residual is 2 and it is outside `ProcessTurn`.
|
||||
|
||||
That is the same shape as "the tail draws nothing", which was true of eight turns and false on
|
||||
turn 64. Rule 20's distinction, one level up: **a ledger that closes to zero is a statement about the
|
||||
states that occurred.**
|
||||
|
||||
**What it means for the standalone.** A reimplementation that models `ProcessTurn` and the tail
|
||||
perfectly writes a correct autosave on every turn of the corpus and is **two generator words out of
|
||||
step on the turn the hives appear**, after which every later turn diverges. The interval it must
|
||||
reproduce starts at `BeginProcessTurn`, not at `ProcessTurn`.
|
||||
|
||||
---
|
||||
|
||||
## 1. The instrument, and why it could see this when lane Z's could not
|
||||
|
||||
Lane Z's six hooks bracket `Autosave(endTurn=1) → ProcessTurn → combat → tail → Autosave(endTurn=0)`.
|
||||
`BeginProcessTurn` runs **inside that bracket** and **outside every subtotal**, so a draw there was
|
||||
always visible as *residual* — and lane Z measured the residual as 0, correctly, because on its turns
|
||||
there was nothing to see. Nothing had ever been hooked in that interval.
|
||||
|
||||
This lane adds five nested hooks, each declaring the live `Mars::RNG` at `S+0x16c` as a region whose
|
||||
`describe` reports an absolute word position (lane Z's `RngLedger`):
|
||||
|
||||
```
|
||||
Autosave(endTurn=1) ....................... 192 <- lane Z
|
||||
StrategyServer::BeginProcessTurn ........ NEW <- the interval nobody had hooked
|
||||
SVSOSwarmQueen::OnTurnBegin ........... NEW (evt 0x13, vtable slot +0x60)
|
||||
SVSOSwarmQueen::RegisterHives ....... NEW THE DRAW
|
||||
SVSOSwarmQueen::TickHives ........... NEW the NextQ slip; 0 words
|
||||
StrategyServer::ProcessTurn ............. 19 <- lane Z
|
||||
StrategyServer::OnAllCombatDone_Tail .... 0 <- lane Z
|
||||
SVSOSlaversRefuel::UpdateDifficultyTier NEW (evt 0x14, tail phase 20)
|
||||
Autosave(endTurn=0) ....................... 211
|
||||
```
|
||||
|
||||
Each new hook carries a **model evaluated at entry** — the hive count and the two config constants,
|
||||
the per-hive `NextQ` values and the three gate constants, the whole `CDiff` threshold-table scan — so
|
||||
the record can disagree with the model instead of merely agreeing with itself.
|
||||
|
||||
## 2. The hive-creation turn, measured
|
||||
|
||||
Workload: `turn1-state.sav` (Frame 1, `SvSctOb / EncID 10 / Hives` count **0**, two systems with
|
||||
`EggScio == 3`), pushed to VM140 as `l1-turn1.sav`, loaded, **End Turn**.
|
||||
|
||||
```
|
||||
[d2] SVSOSwarmQueen::RegisterHives words 170 -> 172 DELTA 2
|
||||
scenario_tag = 3 enc_id = 10 hives_in = -1 (empty) systems = 28
|
||||
systems_tagged = 2 predict_new_hives = 2 predict_min_words = 2
|
||||
hive_nextq_lo = {ptr 0x0158e0a0, value 20}
|
||||
hive_nextq_hi = {ptr 0x0158e0a4, value 30}
|
||||
[d2] SVSOSwarmQueen::TickHives words 172 -> 172 DELTA 0
|
||||
hives_in = 2
|
||||
hives = [{sys 0x0e46e788, egg_scio 3, next_q 30, queen 0},
|
||||
{sys 0x0e46dbe8, egg_scio 3, next_q 28, queen 0}]
|
||||
gate_frame_floor = 10 gate_queen_age = 5 spawn_list_cap = 3
|
||||
predict_gate1_fails = true predict_words = 0
|
||||
[d1] SVSOSwarmQueen::OnTurnBegin words 170 -> 172 DELTA 2
|
||||
[d0] StrategyServer::BeginProcessTurn words 170 -> 172 DELTA 2 side[frame] 1 -> 2
|
||||
[d0] StrategyServer::ProcessTurn words 172 -> 192 DELTA 20
|
||||
[d0] OnAllCombatDone_Tail words 192 -> 192 DELTA 0
|
||||
```
|
||||
|
||||
`predict_new_hives` is computed at entry by transcribing the original's own two predicates over the
|
||||
live `Systems` vector — `sys->EggScio(+0x184) == queen->scenarioTag(+0x4)` and "no existing hive
|
||||
references this system". It said **2**; the generator moved **2**. The prediction and the measurement
|
||||
were produced by different code paths and neither was fitted to the other.
|
||||
|
||||
### 2.1 A third instrument agrees, and it is keyed on the return address
|
||||
|
||||
The per-call-site ledger (lane Z's `draw_sites`, seven detoured generator entry points recording
|
||||
`__builtin_return_address(0)`) reported, on the same turn:
|
||||
|
||||
| return VA | entry point | calls | words | site |
|
||||
|---|---|---|---|---|
|
||||
| **0x00527714** | **`RNG_NextInt`** | **2** | **2** | **`RegisterHives` — a site no previous run has ever recorded** |
|
||||
| 0x0058788d | `NextFloat` | 1 | 1 | `TechTree::ProcessResearch`+0x1c8 |
|
||||
| 0x0088df54 | `NextFloat` | 1 | 1 | `ServerPlayer::RollResearchEvent`+0x2f |
|
||||
| 0x0089342b | `Chance` | 8 | 8 | `GenerateTradeRaidEncounters`+0x196 |
|
||||
| 0x00893518 | `Chance` | 8 | 8 | `GenerateTradeRaidEncounters`+0x283 |
|
||||
| 0x005032a2 | `NextFloat` | 1 | 1 | `DetectEncounters` subtree (lane H) |
|
||||
| 0x007929a9 | `NextInt` | 1 | 1 | `DetectEncounters` subtree (lane H) |
|
||||
| 0x008e6e09 | `NextFloat` | 16 | 16 | *inside `Chance`'s own body — the helper-internal double count* |
|
||||
|
||||
38 raw, minus the 16 helper-internal, = **22** = 2 (`BeginProcessTurn`) + 20 (`ProcessTurn`), with
|
||||
nothing left over. On the next turn the same table reads 35 − 16 = **19** and **the 0x00527714 row is
|
||||
absent entirely**.
|
||||
|
||||
0x00527714 is the return address of the `call 0x004271c0` at **0x0052770f**. Lane SV's document puts
|
||||
that call at 0x0052770c; 0x0052770c is the `mov [ebp-0x14],esi` that stores the bound. Corrected in
|
||||
`ghidra/addresses.d/lane-l1.json`.
|
||||
|
||||
### 2.2 The draws produced the historical values, which is the strongest check available
|
||||
|
||||
`NextQ = frame + LO + NextInt(HI − LO)`. Live: `LO = 20`, `HI = 30`, `frame = 2`.
|
||||
|
||||
| | hive on system A | hive on system B |
|
||||
|---|---|---|
|
||||
| after `RegisterHives` (measured) | `next_q = 30` ⇒ draw 8 | `next_q = 28` ⇒ draw 6 |
|
||||
| after `TickHives`' slip, same turn | **31** | **29** |
|
||||
| `turn2-state.sav` (historical) | **31** | **29** |
|
||||
| after the next turn's slip (measured) | **32** | **30** |
|
||||
| `turn3-state.sav` (historical) | **32** | **30** |
|
||||
|
||||
Two draws, two systems, four numbers, all matching saves captured in a different process on a
|
||||
different day. **The generator trajectory is identical to the historical game's**, which is a much
|
||||
stronger statement about instrument neutrality than a word count could be — and it is corroborated by
|
||||
the absolute positions: this run reaches word **192** after turn 2 and **211** after turn 3, and
|
||||
`tail-rng-ledger.md` §2.1 records `211` as the `ProcessTurn` entry position of turn 4 on the same
|
||||
game.
|
||||
|
||||
## 3. `LO` and `HI` — the leaf lane SV declared blocked
|
||||
|
||||
> *"blocked on one MT draw and two data-file constants … behind pointers at 0x00ae0204/0x00ae0208
|
||||
> that no `.text` or `.data` reference initialises in a form this lane could follow. Fitting `LO` and
|
||||
> `HI` from a single two-hive observation would have been fitting, not derivation."*
|
||||
|
||||
Refusing to fit was right, and reading them live costs one `LogF`-shaped line:
|
||||
|
||||
| pointer | value | meaning |
|
||||
|---|---|---|
|
||||
| `*(int*)0x00ae0204` | **20** | `LO` — the low end of the queen-countdown window |
|
||||
| `*(int*)0x00ae0208` | **30** | `HI` — the high end; `RNG_NextInt`'s bound is `HI − LO = 10`, inclusive |
|
||||
| `*(int*)0x00ae0210` | **10** | `TickHives` gate 1: no queen may spawn while `Frame <= 10` |
|
||||
| `*(int*)0x00ae0228` | **5** | `TickHives` gate 3: a minimum age in turns |
|
||||
| `*(int*)0x00ae0220` | **3** | a cap applied inside the (never-executed) spawn arm |
|
||||
|
||||
So a new hive's countdown is `frame + 20 + U{0..10}`, and it then slips +1 per turn for as long as
|
||||
any gate fails. The leaf is closed: a standalone needs these five integers and the two predicates,
|
||||
not the data files.
|
||||
|
||||
## 4. The `NextQ` slip, live
|
||||
|
||||
Lane SV recovered the slip statically — a single `inc [hive+8]` at **0x0052785a** that four
|
||||
gate-failure edges converge on — and reproduced 31/29 → 32/30 with no draw and no fitting. Confirmed:
|
||||
|
||||
* `TickHives` was **entered on both turns** and cost **0 words** both times.
|
||||
* On turn 1→2 it received `next_q` 30 / 28 and the save carries 31 / 29.
|
||||
* On turn 2→3 it received 31 / 29 and the save carries 32 / 30.
|
||||
* `predict_gate1_fails` was **true** on both turns, and now has a number behind it:
|
||||
`Frame <= 10`, so on this save **no queen can spawn before turn 11 whatever the countdown says**.
|
||||
Every hive slips for at least the first ten turns of its life.
|
||||
|
||||
**The spawn arm still has never executed** (rule 6). No hive in any corpus save has a queen, and
|
||||
`queens_in` was empty on both turns. What the gate constants now let anyone state is *why*, and how
|
||||
far away it is, rather than "we never saw it".
|
||||
|
||||
## 5. `CDiff` — both edges, and one of them settled without a workload
|
||||
|
||||
`SVSOSlaversRefuel::UpdateDifficultyTier` 0x00515820 was entered **once per End Turn**, at depth 1
|
||||
inside the tail (event 0x14, through the class's seven-instruction generic handler 0x0051a800), and
|
||||
cost **0 words** both times.
|
||||
|
||||
| turn | `frame` | `cdiff_in` | `predict_path` | region `cdiff` before → after |
|
||||
|---|---|---|---|---|
|
||||
| 1 → 2 | 2 | **−1** | `store` | **−1 → 0** |
|
||||
| 2 → 3 | 3 | 0 | `no_store_unchanged` | 0 → 0 |
|
||||
|
||||
The second row is the point. **"Entered and stored nothing" is now distinguishable from "did not
|
||||
run"** — which is exactly what a save-file comparison cannot do, and why every corpus pair after
|
||||
frame 2 shows `CDiff` motionless without saying whether the writer ran.
|
||||
|
||||
The table itself was re-read from the instruction stream, independently of lane SV, at real
|
||||
instruction boundaries:
|
||||
|
||||
```
|
||||
515868 [ebp-0x34] = 1 [ebp-0x30] = 1 [ebp-0x2c] = 1
|
||||
515877 [ebp-0x28] = 0x32 [ebp-0x24] = 2 [ebp-0x20] = 3
|
||||
515885 [ebp-0x1c] = 0x64 [ebp-0x18] = 2 [ebp-0x14] = 5
|
||||
515893 xor eax,eax ; lea ecx,[ebp-0x34]
|
||||
515898 cmp [ecx],edx ; jg 0x5158c2 ; first threshold GREATER than the frame
|
||||
51589c add eax,ebx ; add ecx,0xc ; cmp eax,3 ; jl 0x515898
|
||||
<falls through to 0x5158a6, which IS THE EPILOGUE -- no store>
|
||||
5158c2 test eax,eax ; jle 0x5158a6 ; index 0 -> no store
|
||||
5158cc cmp [edi+0x38],eax ; je 0x5158a6 ; unchanged -> no store
|
||||
5158d1 mov [edi+0x38],eax ; CDiff = index - 1
|
||||
```
|
||||
|
||||
Only the **threshold** column is ever loaded; the `(1,1) (2,3) (2,5)` payload columns are dead in
|
||||
this function. So:
|
||||
|
||||
| frame | scan index | stored |
|
||||
|---|---|---|
|
||||
| ≤ 0 | 0 | nothing |
|
||||
| 1 … 49 | 1 | `CDiff = 0` |
|
||||
| 50 … 99 | 2 | `CDiff = 1` |
|
||||
| ≥ 100 | walks off the end | **nothing** |
|
||||
|
||||
**`CDiff` can only ever hold 0 or 1. Tier 2 is unreachable at any frame**, and lane SV's reading of
|
||||
that off-by-one is confirmed by a second independent disassembly. The class ctor 0x0051a820 stores
|
||||
`[eax+0x38] = -1`, which is precisely the `CDiff = −1` that `turn1-state.sav` carries and the reason
|
||||
frame ≤ 0 never matters: `Frame` starts at 1 and the tail does not run before the first End Turn.
|
||||
|
||||
What is **not** yet measured is the 50 and 100 crossings live. §7 says what that costs and why it was
|
||||
not bought with the VM time available; the entry hook is in the tree and prints `predict_path` on
|
||||
every turn, so whoever plays a game past frame 50 gets both edges for free.
|
||||
|
||||
## 6. Instrument neutrality (rule 19) — checked, and it needed checking twice
|
||||
|
||||
Five new MinHook detours were installed for this lane, on top of lane Z's six template hooks, seven
|
||||
draw-site detours and twelve entry probes. Lane H's finding is that **one** detour on a clean
|
||||
prologue boundary changed the game's output by four bytes, so this was a real prediction, not a
|
||||
formality.
|
||||
|
||||
**The oracle reproduces, exactly.** Same build, same `shim.cfg.l1hive`, load `ref-turn2.sav`, one
|
||||
End Turn:
|
||||
|
||||
| file | size | sha256 prefix | historical |
|
||||
|---|---|---|---|
|
||||
| `(Autosave EndTurn).sav` | 66,732 | `bb4fd9ac89f41e3b` | **identical** |
|
||||
| `(Autosave).sav` | 67,219 | `978041acd168b56e` | **identical** |
|
||||
|
||||
That run's trace is the second half of the same story: `RegisterHives` entered with the two hives
|
||||
already present and `NextQ` 31 / 29, drew **0**, `BeginProcessTurn` cost **0**, `ProcessTurn` cost
|
||||
**19**, position 192 → 211. So the hooks that measured 2 words on the creation turn measure 0 on a
|
||||
turn that produces a byte-identical historical autosave.
|
||||
|
||||
### 6.1 The creation turn is NOT a byte oracle — and the reason is a live nondeterminism, not the hooks
|
||||
|
||||
Loading `turn1-state.sav` and ending one turn does **not** reproduce `turn2-state.sav`
|
||||
(`ab4ac2d7…`); it produces a 66,739-byte save with a different hash. Reported here rather than
|
||||
buried, because it is the kind of thing that looks like instrument perturbation and is not:
|
||||
|
||||
* The **generator trajectory is identical** to the historical game's — same word positions (192 after
|
||||
turn 2, 211 after turn 3), and the two hive draws produce `NextQ` 31 / 29 and then 32 / 30, exactly
|
||||
the values `turn2-state.sav` and `turn3-state.sav` carry.
|
||||
* The **pre-turn** autosave round-trips byte-identically (`a3f9dc4b…` in, `a3f9dc4b…` out).
|
||||
* The whole difference, at schema level, is **one field on one player**:
|
||||
|
||||
| player | historical `turn2-state.sav` | this run |
|
||||
|---|---|---|
|
||||
| 3 | `ResTNm = BIO_GnMod` | `ResTNm = XNC_TrnsLir2` |
|
||||
|
||||
Players 0, 1 and 2 agree exactly (`""`, `IND_Waldo`, `DRV_PlsFiss`), as do `Status`, `EvNxID`,
|
||||
`NumOwn` and every other field checked. The two `TechTree/St` bytes that also move are that choice's
|
||||
consequence, and `Summary/Checksum` follows.
|
||||
|
||||
The `hooks=off` control was then run from the same input, and it is the reason this section is worth
|
||||
reading. **It produced a third answer.**
|
||||
|
||||
| run | instrument | `(Autosave).sav` | size | player 3's `ResTNm` |
|
||||
|---|---|---|---|---|
|
||||
| the original 2026-09-07 session (live game, never loaded) | phase-2 shim, `hooks=trace` | `ab4ac2d7e2977260` | 66,739 | `BIO_GnMod` |
|
||||
| this lane | full L1 hook set | `62061918176e3441` | 66,739 | `XNC_TrnsLir2` |
|
||||
| this lane, control | **`hooks=off` — nothing installed at all** | `e43ec1d2b443c101` | **66,740** | `XNC_TrnsMorr2` |
|
||||
|
||||
Players 0, 1 and 2 pick identically in all three (`""`, `IND_Waldo`, `DRV_PlsFiss`). Only player 3
|
||||
moves, and it moves **with the instrument removed as well**.
|
||||
|
||||
**So the turn-1 → turn-2 transition of this game is not deterministic across processes, and no hook
|
||||
is responsible.** The three picks are three different tech ids, two of them species-tagged variants
|
||||
of the same node (`XNC_TrnsLir2` / `XNC_TrnsMorr2`), which is the signature of a selection over a
|
||||
container whose order is not fixed — a pointer-keyed map or an unstable tie-break — rather than of a
|
||||
generator draw. The strategic generator's trajectory was **identical to the historical game's** in
|
||||
the instrumented run, so whatever chooses here does not draw from it.
|
||||
|
||||
Two consequences, both of which matter more than this lane:
|
||||
|
||||
* **`turn1-state.sav` must not be used as a determinism-oracle input.** `determinism-oracle.md`
|
||||
establishes byte-identity for `ref-turn2 → turn 3` and was never run on turn 1; that claim is
|
||||
intact and this is a different pair.
|
||||
* **The standalone milestone has a target that the original itself does not always hit.** A
|
||||
reimplementation cannot be held to a byte match on a turn where the original disagrees with itself.
|
||||
Which turns those are is now an open question with one known member.
|
||||
|
||||
None of this touches §0–§5: the RNG numbers were taken on a run whose generator trajectory matches
|
||||
the historical game word for word, and the same hook set reproduces the oracle exactly on the
|
||||
adjacent turn.
|
||||
|
||||
## 7. What this does NOT settle (rule 15)
|
||||
|
||||
* **One hive-creation event, on one save.** `turn1-state.sav` is the only save in the corpus whose
|
||||
swarm queen has zero hives *and* whose next turn creates some; the six Zuul saves sit at zero hives
|
||||
for eighteen turns and never transition. So "one word per new hive" is confirmed for **two** hives
|
||||
created in **one** call and extrapolated beyond that.
|
||||
* **`RNG_NextInt`'s rejection loop never fired.** Bound 10 ⇒ mask 15, so a draw is rejected with
|
||||
probability 5/16 and the *expected* cost is 16/11 ≈ 1.45 words per hive, not 1. Two hives costing
|
||||
exactly 2 is a ~47% outcome. **A reimplementation must model the rejection loop, not the constant.**
|
||||
This run cannot tell the two apart and does not claim to.
|
||||
* **The spawn arm, and everything past it.** No queen has ever spawned under any instrument. Its
|
||||
callees 0x0050dfc0 and 0x004fe810 are unread and may draw.
|
||||
* **`CDiff` at frame 50 and frame 100** — modelled and predicted, not observed (§5).
|
||||
* **`BeginProcessTurn`'s other work is unmodelled.** Its two container walks (three calls per system,
|
||||
one per fleet) and its event post are not declared by anything. On these two turns their cost is
|
||||
zero *by subtraction* — `BeginProcessTurn` and `OnTurnBegin` agree to the word on both turns — but
|
||||
that is a measurement on 28 systems and one player's fleets, not a proof.
|
||||
* **No game-state region is declared by any of these hooks.** They measure the generator and nothing
|
||||
else. A clean run here says the RNG accounting is right and says nothing about whether the turn was
|
||||
computed correctly.
|
||||
* **Event 0x15 and the alliance events remain unreachable** from any save we hold — unchanged from
|
||||
lane SV.
|
||||
|
||||
## 8. Files
|
||||
|
||||
- Traces: `verify/results/shim/l1/trace-et1.jsonl` (End Turn 1, hive creation),
|
||||
`trace-et2.jsonl` (both End Turns; the second is the control turn)
|
||||
- Report tool used to read them: `verify/results/shim/l1/rep.py`
|
||||
- Saves produced: `verify/results/shim/l1/`
|
||||
- Addresses: `ghidra/addresses.d/lane-l1.json` (10 entries)
|
||||
- Instrument + predictions: `sots-engine` `wip/l1`, `docs/L1-predictions.md`
|
||||
|
|
@ -15,6 +15,24 @@ outside the two turn drivers is **exactly zero**. The tail's cost on these turns
|
|||
**latent**: it will bite the first turn a node line expires or a real battle resolves, and our saves reach
|
||||
neither.
|
||||
|
||||
> **QUALIFIED BY LANE L1, 2026-09-08 — the residual is zero for the turns measured, not for the code.**
|
||||
> (`findings/control-flow/hive-creation-rng.md`.) **`StrategyServer::BeginProcessTurn` runs inside this
|
||||
> document's autosave bracket and outside every one of its subtotals, and nothing had ever been hooked
|
||||
> there.** On every turn of both saves measured here the swarm hives already existed, so it cost 0 and the
|
||||
> residual closed. On the one corpus turn that *creates* them — `turn1-state.sav`, Frame 1 → 2 —
|
||||
> `SVSOSwarmQueen::RegisterHives` takes **one `RNG_NextInt` per new hive** at 0x0052770f, and the measured
|
||||
> split is `BeginProcessTurn` **2** + `ProcessTurn` **20** = 22, i.e. a **residual of 2**. The very next
|
||||
> turn it is 0 again (`BeginProcessTurn` 0, `ProcessTurn` 19, bracket 192 → 211), and lane Z's
|
||||
> instrument reproduces this document's own positions exactly.
|
||||
>
|
||||
> Nothing in the tables below is wrong and no number moves. What changes is the scope of the headline:
|
||||
> **"all of it inside `ProcessTurn`" is a statement about eight turns on which no script object created
|
||||
> anything.** §11's per-site table gains one row on such a turn — return address **0x00527714**, entry
|
||||
> point `NextInt`, 2 calls / 2 words — and the same table on the following turn does not carry it.
|
||||
>
|
||||
> For the standalone this changes one thing: **the interval to reproduce starts at `BeginProcessTurn`,
|
||||
> not at `ProcessTurn`.**
|
||||
|
||||
---
|
||||
|
||||
## 0. Where to start if you are building the standalone
|
||||
|
|
|
|||
|
|
@ -264,6 +264,27 @@ the gates open. The gates read config pointers at 0x00ae0210, 0x00ae0228, 0x00ae
|
|||
The `NextQ` **slip** is not blocked and is exact: on `turn2 -> turn3`, where the hives already exist,
|
||||
the modelled rule reproduces both target turns (31 -> 32, 29 -> 30) with no draw and no fitting.
|
||||
|
||||
> **RUN AND CONFIRMED BY LANE L1, 2026-09-08** (`findings/control-flow/hive-creation-rng.md`). Every
|
||||
> claim in §4 and §5 below was put under an instrument on VM140 and held, with two corrections of
|
||||
> detail and one leaf closed:
|
||||
>
|
||||
> * **Hive creation draws, and lane Z's zero is the workload.** On the `turn1-state.sav` End Turn,
|
||||
> `RegisterHives` was entered once inside `BeginProcessTurn` and cost **exactly 2 words** for 2 new
|
||||
> hives; `BeginProcessTurn`'s total was 2 and the residual outside the two drivers was **2**, not 0.
|
||||
> On the next turn all of those are 0. The prediction in §5 was right.
|
||||
> * **The `NextQ` slip is exact, live.** `TickHives` cost 0 words on both turns; the hives left
|
||||
> `RegisterHives` at `NextQ` 30 / 28, the save carries 31 / 29, and the next turn's save carries
|
||||
> 32 / 30 — reproducing `turn2-state.sav` and `turn3-state.sav` from a draw plus an `inc`.
|
||||
> * **`LO` and `HI` are closed, by reading them live rather than fitting them**: `*(int*)0x00ae0204 =
|
||||
> **20**`, `*(int*)0x00ae0208 = **30**`, so `NextQ = frame + 20 + NextInt(10)` (inclusive). The
|
||||
> `TickHives` gates read 10 (a frame floor), 5 and 3 — so no queen can spawn before frame 11 on any
|
||||
> save, which is why the spawn arm has never run.
|
||||
> * **`CDiff`'s two invisible edges are confirmed** — the table scan was re-read independently and
|
||||
> `CDiff` can only ever hold 0 or 1 — and the "stored nothing" case was observed live and
|
||||
> distinguished from "did not run": the writer is entered every turn and stored only on frame 2.
|
||||
> * **One address corrected:** the draw call in §4.3 is at **0x0052770f** (return 0x00527714), not
|
||||
> 0x0052770c, which is the `mov [ebp-0x14],esi` that stores the bound.
|
||||
|
||||
**An RNG claim this lane did not measure.** Lane Z measured a strategic turn at 18–22 generator words,
|
||||
*all* inside `ProcessTurn`, with the residual outside the two turn drivers **exactly zero** — on turns
|
||||
6 and 64, where the swarm hives already existed. Hive creation draws **inside `BeginProcessTurn`**,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,22 @@ Question: "load the same save, press End Turn, and the resulting autosave is byt
|
|||
game processes, with and without an intervening manual save, and also identical to the autosave the
|
||||
*original* (non-loaded, freshly created) game wrote in the earlier session.
|
||||
|
||||
> **SCOPED BY LANE L1, 2026-09-08 — the oracle is a property of THIS PAIR, not of End Turns.**
|
||||
> (`findings/control-flow/hive-creation-rng.md` §6.1.) Every run below starts from the **turn-2**
|
||||
> state. Lane L1 ran the same experiment one turn earlier — load `turn1-state.sav`
|
||||
> (`a3f9dc4b…`, which is this game's turn-1 pre-turn autosave), one End Turn — three times, and got
|
||||
> **three different post-turn autosaves**: `ab4ac2d7…` (the original live session), `62061918…` (full
|
||||
> hook set) and `e43ec1d2…` (**`hooks=off`, nothing installed at all**). The whole difference is one
|
||||
> field: player 3's research target `ResTNm`, which reads `BIO_GnMod` / `XNC_TrnsLir2` /
|
||||
> `XNC_TrnsMorr2` across the three. Players 0–2 pick identically every time, and in the instrumented
|
||||
> run the strategic generator's trajectory matched the historical game word for word.
|
||||
>
|
||||
> So: **the turn-1 → turn-2 transition of this game is not deterministic across processes, and no
|
||||
> hook is responsible.** Nothing below is wrong — but "load the same save, press End Turn, and the
|
||||
> autosave is byte-identical" is verified for `ref-turn2 → turn 3` and **falsified for
|
||||
> `turn1-state → turn 2`**. Anyone building a byte oracle must pick the pair deliberately, and the
|
||||
> pairs in this document plus `verify/results/shim/tailrng/` are the ones that hold.
|
||||
|
||||
## Verdict
|
||||
|
||||
**byte-identical.** No masking is needed for the End Turn oracle when both sides start from the same
|
||||
|
|
|
|||
|
|
@ -218,6 +218,18 @@ with the entire tech tree.
|
|||
first is false. Both reduce to the same one-line model, so nothing downstream is blocked, but the
|
||||
claim "the sensor branch is what refreshes Spica" is **[H]**, not [V]: no measurement of `SnLv`
|
||||
exists. One arming line (`sys+0x24` in slot 1 instead of `_Mysize`) settles it on the next VM run.
|
||||
|
||||
> **SETTLED FOR SPICA BY LANE L1, 2026-09-08** (`findings/subsystems/snlv-measured.md`). The
|
||||
> arming line was added (`watch.mode=snlv`) and run. Spica reads `AFlags = 0x0` with
|
||||
> **`SnLv = 0x00000200`**, i.e. contact level **2** for player index 4 — the player its `NVO`
|
||||
> record is keyed to. The `AFlags` term is false, so the sensor term is the only way `IsKnownTo`
|
||||
> can be true: **"the sensor branch is what refreshes Spica" is now [V]**. Two further facts came
|
||||
> free: level 3 occurs on exactly the eight (system, player) pairs whose `AFlags` bit is set, and
|
||||
> level **2** — never 1 — occurs where it is not, which corrects
|
||||
> `SensorPhase_ComputeContactLevel`'s prototype (it has four return tails, `{0,1}`, `{2,3}`, a
|
||||
> hard 2 and a hard 3; the `else 1` reading names only one of them). **Bismol is still open** —
|
||||
> it is not on this map. The same config on `zuul-turn23-fleet23.sav` answers it in one End Turn,
|
||||
> because the arming sweep prints every system's `SnLv`.
|
||||
- **The 54 "sensor branch" rows are unobserved in one conjunct each.** They are consistent, not
|
||||
proven. The honest statement of the corpus result is: *no record contradicts the model, and the
|
||||
one record that contradicted `AFlags` alone is explained.*
|
||||
|
|
|
|||
141
findings/subsystems/snlv-measured.md
Normal file
141
findings/subsystems/snlv-measured.md
Normal file
|
|
@ -0,0 +1,141 @@
|
|||
# `SnLv` measured — the sensor branch is what refreshes Spica, and the contact level is 2, not 1
|
||||
|
||||
- **Type:** subsystem (live measurement)
|
||||
- **Status:** **verified** for Spica; `nvo-tshn-visible-owner.md` §6's first bullet moves **[H] → [V]**
|
||||
- **Confidence:** high. Hardware data-write watchpoint on the word itself, with the canary self-test
|
||||
passing and the determinism oracle reproduced byte for byte in the same run.
|
||||
- **Owner / date:** lane L1 · 2026-09-08 · VM140
|
||||
- **Instrument:** `src/shim/hooks/watchpoints.cpp`, new `watch.mode=snlv`, config `shim.cfg.l1snlv`,
|
||||
build `0117495-dirty-20260908T2113Z`
|
||||
- **Answers:** `findings/subsystems/nvo-tshn-visible-owner.md` §6 — *"One arming line
|
||||
(`sys+0x24` in slot 1 instead of `_Mysize`) settles it on the next VM run."* It did.
|
||||
|
||||
---
|
||||
|
||||
## 0. The result
|
||||
|
||||
`ServerSystem::IsKnownTo` is `AFlags bit || (PlyrIdx < 15 && ((SnLv >> 2*PlyrIdx) & 3) != 0 &&
|
||||
HasResearched(CCC_AdvSens))`. Lane W3 could not decide whether Spica's `NVO` record is refreshed
|
||||
through the `AFlags` term or the sensor term, because **`SnLv` is not serialised** and had never been
|
||||
measured.
|
||||
|
||||
On `ref-turn2.sav`, at the arming point, **Spica has `AFlags = 0x0` and `SnLv = 0x00000200`**, i.e.
|
||||
contact level **2** for player index 4 — the very player its single `NVO` record is keyed to.
|
||||
|
||||
`AFlags = 0` makes the first term false. The sensor term is therefore the only way `IsKnownTo` can
|
||||
return true for that pair, and the record is refreshed. **The sensor branch is what refreshes Spica**,
|
||||
measured rather than inferred.
|
||||
|
||||
## 1. The whole map, because it costs no debug register
|
||||
|
||||
Printing `SnLv` and its decoded per-player 2-bit fields for every system during the arming sweep is
|
||||
free. All 28 systems of `ref-turn2.sav`, at `ApplyAllTurnCommands` entry on the End Turn from turn 2
|
||||
(only the non-zero rows shown; the other 20 systems read `SnLv = 0`):
|
||||
|
||||
| system | `AFlags` | `SnLv` | non-zero levels |
|
||||
|---|---|---|---|
|
||||
| Hyperion | 0x10 | 0x00000300 | player 4 → **3** |
|
||||
| Gamma Cephei | 0x01 | 0x00000003 | player 0 → **3** |
|
||||
| **Spica** | **0x00** | **0x00000200** | **player 4 → 2** |
|
||||
| Ke'Dolarra | 0x02 | 0x0000000c | player 1 → **3** |
|
||||
| Koa?Vo | 0x80 | 0x0000c000 | player 7 → **3** |
|
||||
| Kaa?Vaalu | 0x10 | 0x00000300 | player 4 → **3** |
|
||||
| Markab | 0x10 | 0x00000300 | player 4 → **3** |
|
||||
| Kea?Pono | 0x10 | 0x00008300 | player 4 → **3**, player 7 → **2** |
|
||||
| Ko'Rorkor | 0x10 | 0x00000300 | player 4 → **3** |
|
||||
|
||||
Two regularities fall out and neither was available from any save:
|
||||
|
||||
1. **Level 3 occurs exactly where the `AFlags` bit is set**, on all eight rows — which is the
|
||||
`IsVisibleTo` early-out of `SensorPhase_ComputeContactLevel` doing what lane W3 read.
|
||||
2. **Level 2 occurs exactly where it is not** — Spica for player 4, Kea'Pono for player 7. So the
|
||||
non-visible contact state on this save is **2**, and **level 1 was never observed**.
|
||||
|
||||
## 2. A correction: `ComputeContactLevel` does not return 1 there
|
||||
|
||||
`ghidra/addresses.d/lane-w3.json` records
|
||||
`SensorPhase_ComputeContactLevel` as *"3 if IsVisibleTo (AFlags), else 1 if inside p's sensor
|
||||
envelope, else 0"*. The function has **four** return tails, read at instruction boundaries:
|
||||
|
||||
```
|
||||
00850d18 mov eax,3 ; IsVisibleTo -> 3
|
||||
00850e2f mov eax,ecx ; ecx = setne on an FPU range compare -> 0 or 1
|
||||
00850e47 lea eax,[edx+2] ; edx = setne on the same compare -> 2 or 3
|
||||
00850e52 mov eax,2 ; -> 2
|
||||
```
|
||||
|
||||
which pair is taken is selected by `cl` and by `[ebx+0x10]`. So the value set is `{0,1,2,3}` and the
|
||||
`{0,1}` pair is one of three tails, not the only alternative to 3. The measurement above lands on a
|
||||
`2` tail. **`IsKnownTo` only tests `!= 0`, so nothing downstream changes** — but the constant in the
|
||||
prototype is wrong and would mislead anyone reimplementing the sensor phase.
|
||||
|
||||
## 3. Who writes it, and how often
|
||||
|
||||
DR1 watched Spica's `sys+0x24` for the whole End Turn: **20 writes**, every one from the same
|
||||
instruction.
|
||||
|
||||
| trap EIP (Ghidra VA) | store site | value written | count |
|
||||
|---|---|---|---|
|
||||
| 0x0086a78c | **0x0086a789** `mov [edi+0x24],edx` | 0x00000200 every time | **20** |
|
||||
|
||||
0x0086a789 is exactly the pack site lane W3 named from static reading — the trap reports the
|
||||
instruction after the store, and 0x0086a789 + 3 = 0x0086a78c. Confirmed to the byte. The value never
|
||||
changed across the turn: the recompute is idempotent on this workload, which is why the *absence* of
|
||||
`SnLv` from the wire costs nothing on these saves and why no save could ever have shown it.
|
||||
|
||||
The enclosing loop at 0x0086a761..0x0086a79b walks `GetGame()->Systems` and calls
|
||||
`ComputeContactLevel` once per system, masking `3 << (2*idx)` out and OR-ing the result in, guarded
|
||||
by `[ebp-0x10] >= 0xf` — the `PlyrIdx < 15` test lane W3 read in `IsKnownTo`, here on the writing
|
||||
side. Twenty full sweeps of the systems vector happen in one End Turn.
|
||||
|
||||
## 4. `TShn` refreshed, in the same run, twice
|
||||
|
||||
DR0 watched Spica's `NVO` record at `root+0x10` (`{int16 touched, int16 TShn}`): **4 writes**, in two
|
||||
pairs, all with a `SetVisibleOwner` frame:
|
||||
|
||||
| seq | trap EIP (VA) | word after the store | reading |
|
||||
|---|---|---|---|
|
||||
| 8 | 0x0075b95a | `0x00020003` | `touched` := 3 (the new `Frame`), `TShn` still 2 |
|
||||
| 9 | 0x0075b965 | `0x00030003` | `TShn` := 3 |
|
||||
| 20 | 0x0075b95a | `0x00030003` | same pair again |
|
||||
| 21 | 0x0075b965 | `0x00030003` | |
|
||||
|
||||
Return addresses 0x0075bd4a / 0x0075bdc3 with 0x0086a806 on the stack put the caller chain in the
|
||||
sensor phase, next to the `SnLv` writer. This confirms three things lane W3 stated as a model:
|
||||
`SetVisibleOwner` stamps `touched` and `TShn` in **two separate stores**; it runs **twice per turn**
|
||||
for one (system, player) pair; and on this turn both are stamped with the new `Frame`.
|
||||
|
||||
## 5. Coverage — what this does NOT settle (rule 15)
|
||||
|
||||
* **Bismol is not on this map.** `nvo-tshn-visible-owner.md` §6's open question — *which* conjunct
|
||||
fails at Bismol — belongs to `zuul-turn23-fleet23.sav`, a different game with a different star
|
||||
list. This run settles the Spica half of that bullet and leaves the Bismol half open. The same
|
||||
config on the Zuul save answers it: the arming sweep prints every system's `SnLv`, so one launch
|
||||
and one End Turn is the whole experiment.
|
||||
* **One system watched, 28 printed.** The 20 `SnLv` writes and the 4 `TShn` writes are Spica's. Every
|
||||
other system's row above is a **read at arm time**, not a watched write.
|
||||
* **`SnLv` never changed value** during the turn, so nothing here says what a *transition* looks like
|
||||
or when a contact is lost.
|
||||
* **Level 1 was never observed.** §2's four-tail reading is static; only 0, 2 and 3 have been seen.
|
||||
* **Per-thread blind spot.** Debug registers are per-thread and the arm is on the turn thread only.
|
||||
* **`ShareVisibleOwner` still has never executed** — no corpus save has an alliance (unchanged).
|
||||
|
||||
## 6. Instrument neutrality (rule 19)
|
||||
|
||||
The armed run **is** the oracle run: `ref-turn2.sav`, one End Turn, four watchpoints live and ~24
|
||||
exception deliveries, plus the five lane-L1 hooks and lane Z's ledger:
|
||||
|
||||
| file | size | sha256 prefix | historical |
|
||||
|---|---|---|---|
|
||||
| `(Autosave EndTurn).sav` | 66,732 | `bb4fd9ac89f41e3b` | **identical** |
|
||||
| `(Autosave).sav` | 67,219 | `978041acd168b56e` | **identical** |
|
||||
|
||||
Canary self-test: `writes=1 traps=1 dr7=0xdddd0055 PASS`.
|
||||
|
||||
## 7. Files
|
||||
|
||||
- Arming log (all 28 systems with `SnLv` decoded, the base probe, the canary):
|
||||
`verify/results/shim/l1/watch-arm.txt`
|
||||
- Hit log: `verify/results/shim/l1/watch-hits.txt`
|
||||
- Instrument: `sots-engine` `wip/l1`, `src/shim/hooks/watchpoints.cpp` (`watch.mode=snlv`),
|
||||
configs `shim.cfg.l1snlv` / `shim.cfg.l1control`
|
||||
84
ghidra/addresses.d/lane-l1.json
Normal file
84
ghidra/addresses.d/lane-l1.json
Normal file
|
|
@ -0,0 +1,84 @@
|
|||
{
|
||||
"entries": [
|
||||
{
|
||||
"name": "SVSOSwarmQueen_RegisterHives_DrawSite",
|
||||
"addr": "0x0052770f",
|
||||
"convention": "site",
|
||||
"prototype": "call RNG_NextInt (0x004271c0) inside SVSOSwarmQueen_RegisterHives, ECX = GetGame()->RNG(+0x16c) + 4, bound = HI - LO passed BY POINTER at [ebp-0x14]; return address 0x00527714, then `add eax,edi` where edi = frame + LO. ONE STRATEGIC-GENERATOR WORD PER NEW HIVE, taken inside StrategyServer::BeginProcessTurn -- outside BOTH turn drivers and before either. Corrects svsctob-writers.md, which put the call at 0x0052770c (that is the `mov [ebp-0x14],esi` storing the bound)",
|
||||
"status": "verified",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08); disassembled from dumps/sots.exe and measured live on turn1-state.sav"
|
||||
},
|
||||
{
|
||||
"name": "SVSOSwarmQueen_TickHives_SlipSite",
|
||||
"addr": "0x0052785a",
|
||||
"convention": "site",
|
||||
"prototype": "`inc DWORD PTR [esi+0x8]` -- the NextQ slip. FOUR gate-failure edges converge here: 0x005277e3 (frame <= *SwarmQueen_GateFrameFloor_ptr), 0x005277fe (queens count >= the per-turn allowance), 0x00527848 (age < *SwarmQueen_GateQueenAge_ptr), 0x0052784f (hive's system handle is 0). Consumes NO generator word on any of them; TickHives calls no RNG primitive anywhere in its body (0x00527770..0x005279a1 read whole)",
|
||||
"status": "verified",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SwarmQueen_HiveNextQOffsetLo_ptr",
|
||||
"addr": "0x00ae0204",
|
||||
"convention": "data",
|
||||
"prototype": "int* -- pointer to the LOW end of the new-hive queen-countdown offset. RegisterHives computes nextQ = frame + *this + RNG_NextInt(*SwarmQueen_HiveNextQOffsetHi_ptr - *this). Behind a pointer, so no .text or .data reference initialises it in a form a static reader can follow; the value is read live by the shim at RegisterHives entry",
|
||||
"status": "mapped",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SwarmQueen_HiveNextQOffsetHi_ptr",
|
||||
"addr": "0x00ae0208",
|
||||
"convention": "data",
|
||||
"prototype": "int* -- pointer to the HIGH end of the new-hive queen-countdown offset. The RNG_NextInt bound is (*this - *SwarmQueen_HiveNextQOffsetLo_ptr) and NextInt is INCLUSIVE of its bound, so the countdown lands in [frame+LO, frame+HI]",
|
||||
"status": "mapped",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SwarmQueen_GateFrameFloor_ptr",
|
||||
"addr": "0x00ae0210",
|
||||
"convention": "data",
|
||||
"prototype": "int* -- TickHives gate 1: `if (GetGame()->Frame(+0xc) <= *this) slip`. No queen can spawn at or below this frame",
|
||||
"status": "mapped",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SwarmQueen_SpawnListCap_ptr",
|
||||
"addr": "0x00ae0220",
|
||||
"convention": "data",
|
||||
"prototype": "int* -- read in TickHives' SPAWN arm (0x005278d8), after a queen has been created: when *this <= 0 the vector at queen+0x30 is trimmed to 0 entries, otherwise to *this. Never exercised on any corpus save (no hive has ever had a queen)",
|
||||
"status": "mapped",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SwarmQueen_GateQueenAge_ptr",
|
||||
"addr": "0x00ae0228",
|
||||
"convention": "data",
|
||||
"prototype": "int* -- TickHives gate 3: an age in turns, compared against `GetGame()->Frame - resolvedQueen->+0xc`; below it the hive slips instead of spawning",
|
||||
"status": "mapped",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SVSOSwarmQueen_off_Hives",
|
||||
"offset": "0x10",
|
||||
"convention": "field",
|
||||
"prototype": "std::vector<HiveInfo> -- {_Myfirst 0x10, _Mylast 0x14, _Myend 0x18, _Alval 0x1c}, element stride 0x10: {void* vptr = 0x009f1a68, StarSystem* sys(+4), int nextQ(+8), Queen* queen(+0xc)}. The WIRE order is HiveID / QueenID / NextQ, which is NOT the member order",
|
||||
"status": "verified",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SVSOSwarmQueen_off_Queens",
|
||||
"offset": "0x20",
|
||||
"convention": "field",
|
||||
"prototype": "std::vector<T> at +0x20..+0x24, element stride 0xc -- TickHives' second gate divides the byte span by 12. Empty on every corpus save",
|
||||
"status": "verified",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08)"
|
||||
},
|
||||
{
|
||||
"name": "SVSOSlaversRefuel_off_CDiff",
|
||||
"offset": "0x38",
|
||||
"convention": "field",
|
||||
"prototype": "int -- the difficulty tier, wire tag \"CDiff\". Set to -1 by the ctor 0x0051a820 and written ONLY by SVSOSlaversRefuel_UpdateDifficultyTier, which stores (scanIndex - 1) and only when it differs. Reachable values are 0 (frame 1..49) and 1 (frame 50..99) ONLY: frame <= 0 exits at index 0 and frame >= 100 walks off the end of the 3-entry threshold table, so BOTH edges store nothing and the tier can never reach 2",
|
||||
"status": "verified",
|
||||
"source": "findings/control-flow/hive-creation-rng.md (lane L1 2026-09-08); threshold table read off the stores at 0x00515868..0x0051588c"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -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 @ 2e935b8, generated 2026-09-08 by tools/gen_addresses.py
|
||||
// Source: sots-re ghidra/addresses.json @ ea7881a, 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>
|
||||
|
|
@ -1913,6 +1913,50 @@ constexpr uint32_t StrategyServer_FinalizeTurnRecords = 0x0038a0e0;
|
|||
constexpr uint32_t ServerSystem_ComputeMaxIncome = 0x003521c0;
|
||||
// note Game::SNMAllCombatDone RTTI vtable, four slots (0x0079e590, 0x0082a100, 0x0082a170, 0x0079e500 -- the middle pair are the network Read/Write). Layout by enumeration from the two stack constructors and from every offset OnAllCombatDone_Tail reads: `struct SNMAllCombatDone { void* vptr; std::vector<EncounterResults> results; }`, 0x10 bytes -- which is why the handler passes msg+4 and not msg. Three construction sites: RunCombatRound 0x007cc847 (stack), the combat server FUN_007cfd00+0x541 = 0x007d0241 (stack; sends it to every player whose +0x44 is 4 or 5, then sets combatServer->+0x60 = 9; NOTE Ghidra sizes FUN_007cfd00 at 384 B but its real body runs to the ret at 0x007d02b9), and the deserialization factory 0x008663b0 (operator new(0x14) -- 4 bytes larger than the enumerated size, UNEXPLAINED) [verified]
|
||||
constexpr uint32_t SNMAllCombatDone_layout = 0x00624758;
|
||||
// site call RNG_NextInt (0x004271c0) inside SVSOSwarmQueen_RegisterHives, ECX = GetGame()->RNG(+0x16c) + 4, bound = HI - LO passed BY POINTER at [ebp-0x14]; return address 0x00527714, then `add eax,edi` where edi = frame + LO. ONE STRATEGIC-GENERATOR WORD PER NEW HIVE, taken inside StrategyServer::BeginProcessTurn -- outside BOTH turn drivers and before either. Corrects svsctob-writers.md, which put the call at 0x0052770c (that is the `mov [ebp-0x14],esi` storing the bound) [verified]
|
||||
constexpr uint32_t SVSOSwarmQueen_RegisterHives_DrawSite = 0x0012770f;
|
||||
// site `inc DWORD PTR [esi+0x8]` -- the NextQ slip. FOUR gate-failure edges converge here: 0x005277e3 (frame <= *SwarmQueen_GateFrameFloor_ptr), 0x005277fe (queens count >= the per-turn allowance), 0x00527848 (age < *SwarmQueen_GateQueenAge_ptr), 0x0052784f (hive's system handle is 0). Consumes NO generator word on any of them; TickHives calls no RNG primitive anywhere in its body (0x00527770..0x005279a1 read whole) [verified]
|
||||
constexpr uint32_t SVSOSwarmQueen_TickHives_SlipSite = 0x0012785a;
|
||||
// data int* -- pointer to the LOW end of the new-hive queen-countdown offset. RegisterHives computes nextQ = frame + *this + RNG_NextInt(*SwarmQueen_HiveNextQOffsetHi_ptr - *this). Behind a pointer, so no .text or .data reference initialises it in a form a static reader can follow; the value is read live by the shim at RegisterHives entry [mapped]
|
||||
constexpr uint32_t SwarmQueen_HiveNextQOffsetLo_ptr = 0x006e0204;
|
||||
// data int* -- pointer to the HIGH end of the new-hive queen-countdown offset. The RNG_NextInt bound is (*this - *SwarmQueen_HiveNextQOffsetLo_ptr) and NextInt is INCLUSIVE of its bound, so the countdown lands in [frame+LO, frame+HI] [mapped]
|
||||
constexpr uint32_t SwarmQueen_HiveNextQOffsetHi_ptr = 0x006e0208;
|
||||
// data int* -- TickHives gate 1: `if (GetGame()->Frame(+0xc) <= *this) slip`. No queen can spawn at or below this frame [mapped]
|
||||
constexpr uint32_t SwarmQueen_GateFrameFloor_ptr = 0x006e0210;
|
||||
// data int* -- read in TickHives' SPAWN arm (0x005278d8), after a queen has been created: when *this <= 0 the vector at queen+0x30 is trimmed to 0 entries, otherwise to *this. Never exercised on any corpus save (no hive has ever had a queen) [mapped]
|
||||
constexpr uint32_t SwarmQueen_SpawnListCap_ptr = 0x006e0220;
|
||||
// data int* -- TickHives gate 3: an age in turns, compared against `GetGame()->Frame - resolvedQueen->+0xc`; below it the hive slips instead of spawning [mapped]
|
||||
constexpr uint32_t SwarmQueen_GateQueenAge_ptr = 0x006e0228;
|
||||
// field std::vector<HiveInfo> -- {_Myfirst 0x10, _Mylast 0x14, _Myend 0x18, _Alval 0x1c}, element stride 0x10: {void* vptr = 0x009f1a68, StarSystem* sys(+4), int nextQ(+8), Queen* queen(+0xc)}. The WIRE order is HiveID / QueenID / NextQ, which is NOT the member order [verified]
|
||||
constexpr uint32_t SVSOSwarmQueen_off_Hives = 0x00000010;
|
||||
// field std::vector<T> at +0x20..+0x24, element stride 0xc -- TickHives' second gate divides the byte span by 12. Empty on every corpus save [verified]
|
||||
constexpr uint32_t SVSOSwarmQueen_off_Queens = 0x00000020;
|
||||
// field int -- the difficulty tier, wire tag "CDiff". Set to -1 by the ctor 0x0051a820 and written ONLY by SVSOSlaversRefuel_UpdateDifficultyTier, which stores (scanIndex - 1) and only when it differs. Reachable values are 0 (frame 1..49) and 1 (frame 50..99) ONLY: frame <= 0 exits at index 0 and frame >= 100 walks off the end of the 3-entry threshold table, so BOTH edges store nothing and the tier can never reach 2 [verified]
|
||||
constexpr uint32_t SVSOSlaversRefuel_off_CDiff = 0x00000038;
|
||||
// thiscall void __thiscall Game::AITColonize::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITColonizeGoal::Execute is the same address. 48 bytes; a forwarder to the parameterised worker 0x0068b280 called as worker(ecx = agent, this, pass, this->+0x8, &this->+0x20, &this->+0x10) with `edi = this->+0xc` passed as an IMPLICIT REGISTER ARGUMENT (whole-program-optimised custom convention; a reimplementation that ports only the stack arguments passes garbage). Reaches the colonize order (list 7) at depth 4 via 0x0068b280 -> 0x006930f0 -> 0x00578ff0 -> 0x00769640 [unverified]
|
||||
constexpr uint32_t AITColonize_Execute = 0x0028b400;
|
||||
// thiscall void __thiscall Game::AITEscortGateInvade::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITEscortGateInvadeGoal::Execute is the same address. 80 bytes; forwards to 0x0068c5d0 as __fastcall(ecx = this->+0xc, edx = agent) plus 8 stack arguments. Reaches list 7 at depth 4 [unverified]
|
||||
constexpr uint32_t AITEscortGateInvade_Execute = 0x0028c7c0;
|
||||
// thiscall void __thiscall Game::AITInvade::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. SHARED BODY: Game::AITInvadeGoal::Execute is the same address. 160 bytes; forwards to 0x0068d460 with 11 arguments, then maintains this->+0x38 and this->+0x39. +0x39 is computed at 0x0068d80f-0x0068d82a as (0x006a6380(agent, this->+0xc) < 2 * 0x006a6260(agent, this->+0xc)) and is what IAITask slot 11 returns for this class. Reaches list 14 at depth 5 [unverified]
|
||||
constexpr uint32_t AITInvade_Execute = 0x0028d7a0;
|
||||
// thiscall void __thiscall Game::AITNodeBore::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Does its setup (0x00685810) and its finaliser (0x0068e090) only when pass == 0, and forwards `pass` to 0x0068a520 in both passes. Reaches list 14 at depth 5 via 0x0068a520 -> 0x006ceef0 -> 0x006c16c0 -> 0x006987e0 -> 0x007634d0. Zuul-flavoured task (the node-bore is a Zuul mechanic), so on a non-Zuul board it may be absent from the task list entirely [unverified]
|
||||
constexpr uint32_t AITNodeBore_Execute = 0x0028e590;
|
||||
// thiscall void __thiscall Game::AITRaid::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. THE ONE TASK WHOSE PASS-0 BEHAVIOUR IS NOT SETTLED: at 0x0068e89e it calls 0x006b76a0 and then at 0x0068e8b8 loops the list-16 order method 0x007635f0(client, fleetId, 1) over the returned fleets, and NEITHER the call nor the loop carries a `pass` guard of its own. AI3 §2.3's argument says the fleet vector at [ebp-0x28] is empty on pass 0 because it is downstream of the same hub, but [ebp-0x28] was never traced to closure. This address exists as an address so that a zero at 0x007635f0 can be read: entered-and-silent and never-entered are opposite answers (method rule 20) [unverified]
|
||||
constexpr uint32_t AITRaid_Execute = 0x0028e670;
|
||||
// thiscall void __thiscall Game::AITAdvanceIdleShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Whole body is pass-1 only: `if (pass != 1) return` at 0x0068f25a. Table priority 0, so it is always the LAST task in the descending-priority sweep -- the AI sweeps up whatever is still idle only after every other task has taken both its minimum and its desired force, which is what the two-tier quota model predicts [unverified]
|
||||
constexpr uint32_t AITAdvanceIdleShips_Execute = 0x0028f230;
|
||||
// thiscall void __thiscall Game::AITBuildDeepScanShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Reaches the BUILD order (list 3) at depth 6 via 0x006ce460 -> 0x006ce360 -> 0x006ce190 -> 0x006bd790 -> 0x006b3bc0 -> 0x00762fd0, the same chain as AITBuildPoliceShips [unverified]
|
||||
constexpr uint32_t AITBuildDeepScanShips_Execute = 0x002901a0;
|
||||
// thiscall void __thiscall Game::AITBuildPoliceShips::Execute(StrategyAIAgent* agent, int pass) -- RET 8, IAITask vtable slot 5. Reaches the BUILD order (list 3) at depth 6, identical chain to AITBuildDeepScanShips. One of the two candidate producers of the single list-3 element the reference AI emits on turn 1 [unverified]
|
||||
constexpr uint32_t AITBuildPoliceShips_Execute = 0x00290380;
|
||||
// thiscall bool __thiscall Game::StrategyClient::OrderList16(int objectId, bool flag) -- RET 8. THE LIST-16 PRODUCER. Opens with `cmp byte [this+0x15c], 0` -- the End-Turn latch StrategyClient::EndTurn 0x00783be0 sets at 0x00783c59 before BuildTurnCommands runs -- so like every other order method it refuses once the turn has been submitted. Element record for list 16 is {i32, bool} (lane Q), matching the (objectId, flag) pair; list 16 is in the PAYING half of the ModCount table (bump at 0x00821e23 in its applier 0x00821e20). Reached from AITRaid::Execute at depth 1. The method is named for the list it fills, NOT for what the flag means: no save has ever carried a list-16 element and nothing has been observed [unverified]
|
||||
constexpr uint32_t StrategyClient_OrderList16 = 0x003635f0;
|
||||
// offset int aidf -- the difficulty level (0..2) stored by ServerPlayer::Read 0x008804d0 at 0x00880fa3, the selector LoadDifficultyRow 0x005a3990 uses to pick the row it copies for this player. VERIFIED LIVE on VM146: read at ComputeBudget entry on every one of the 8 players of turn1-state across 3,895 calls, it is 1 on all eight, which is the difficulty level the whole corpus carries and the level whose AI column holds the 1.1x income modifier [verified]
|
||||
constexpr uint32_t ServerPlayer_off_Aidf = 0x00000368;
|
||||
// offset DifficultyMods* -- a POINTER to the per-player difficulty record, NOT the record inline. This entry exists to correct a lane L5 mistake and to save the next lane the run. addresses.json already records DifficultyMods_Select 0x0059b490 being called as Select(p->diffMods /*+0x36c*/, p), and lane L5 read the 0x1c bytes AT +0x36c expecting {int id; float ai[3]; float other[3]}. They are not that: the first dword is 0x0da0ec20..0x0da13360 on the eight players of turn1-state -- eight distinct heap addresses within 18 KB of each other and of the ServerPlayer allocations themselves (the player object was at 0x0da08600) -- and the floats behind it decode as denormal garbage (1.7e-38, 1.4e-43). So the field is a pointer and the record is one dereference further on. HYPOTHESIS, not verified: this run did not follow the pointer, so it is consistent with +0x36c being a pointer to the DifficultyMods record and equally consistent with its being some other per-player sub-object pointer. What IS established is the thing the reading was for: the difficulty row is reachable from a ServerPlayer, so compute_budget.h's old coverage note ('not reachable from a ServerPlayer, so the two relevant entries are fitted constants') is wrong as written. The fitted pair {3.0, 1.5} / {1.0, 1.0} remains UNVERIFIED against the running game; the next run should dereference this field behind a readability guard and compare the selected triple against those constants [hypothesis]
|
||||
constexpr uint32_t ServerPlayer_off_DiffModsPtr = 0x0000036c;
|
||||
// data double 0x3FEB333340000000 = 0.85000002384185791 = (double)0.85f -- the research-yield factor, multiplied in by ComputeBudget at 0x00863601 as a QWORD operand. THE WIDTH IS THE FINDING, and what makes it a rule-23 case rather than a curiosity is its two NEIGHBOURS in the same product: 1.15 at 0x00a1a4b8 and 0.5 at 0x009e20a0 are stored as EXACT doubles, while this one is a widened float. Three literals, one expression, two widths -- so a C++ port that writes all three as decimals is wrong in exactly one place. The exact-decimal product (money/50 x 1.15 x 0.5 x 0.85) is integral when researchMoney is a multiple of 40,000, and the image's larger constant lands one ABOVE it there: at 40,000 the game gives 391 research points and the decimal gives 390. NOT VISIBLE ON THE CORPUS, and that is now measured rather than argued: the live compare run presented 9 distinct researchMoney values across 3,895 calls and NONE of them is a multiple of 40,000. Read live out of the running process as well, so nothing patched or relocated it [verified]
|
||||
constexpr uint32_t g_ResearchYieldFactor = 0x0062d818;
|
||||
// thiscall double (ServerSystem* sys, double overHarvestRate) // `ret 8`, real end 0x007505a5. Returns 0 when the system has no owner (+0x100) or rbfl (+0x1dc) is non-zero. Otherwise the SUM of three terms -- the over-harvest demand x SpeciesDef+0x50, (TRes + available resources) x StripMineFraction x 0.9, and the population output of groups 0, 1 and 2 -- multiplied in one uninterrupted 80-bit chain by player OutMod, sys.OutMod, player +0x224, RebOutMod, ScOutMod, and finally by ADDICTION_OUTPUT_MOD when the addiction phase is >= 3. SIDE-EFFECT FREE: it and all seven callees were checked for stores to the game state (0x0074a6d0's only writes are through its int64 out-parameters), which is why this and not ComputeOutputFromRates is the compare target -- that one repairs ships in orbit [verified]
|
||||
constexpr uint32_t ServerSystem_ComputeTotalOutput = 0x00350480;
|
||||
// thiscall double (ServerSystem* sys, int groupType, int species, double count) // `ret 0x10`, real end 0x0074b871. THE population -> output term: returns 0 for count <= 0, else max(0, POPTYPE[groupType].outputMod x (stationFactor x 1.8) x moraleMod x (count / 500000)). stationFactor is 1 + stations x STATION_BONUS_IMPERIAL_OUTPUT and applies to groupType 0 of an owned system only (and only while that constant is > 0); moraleMod applies to groupType 1 only. So output points per head are typeOutputMod x 1.8 / 500000 -- exactly 3.6e-6 for an imperial population with no stations [verified]
|
||||
|
|
|
|||
16
verify/results/shim/l1/README.md
Normal file
16
verify/results/shim/l1/README.md
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
# Lane L1 — the turn-begin RNG run, 2026-09-08
|
||||
|
||||
Instrument: `sots-engine` `wip/l1`, build `0117495-dirty-20260908T2113Z`. Findings:
|
||||
`findings/control-flow/hive-creation-rng.md` and `findings/subsystems/snlv-measured.md`.
|
||||
|
||||
| file | what |
|
||||
|---|---|
|
||||
| `trace-et1.jsonl` | `l1-turn1.sav` (= `verify/results/saves/turn1-state.sav`) + **one** End Turn — the hive-creation turn. `SVSOSwarmQueen::RegisterHives` words 170 → 172. |
|
||||
| `trace-et2.jsonl` | the same session after a **second** End Turn; the second bracket is 192 → 211 with a residual of 0. Supersedes `trace-et1.jsonl`, which is kept because it is the record as it stood before the control turn existed. |
|
||||
| `trace-oracle.jsonl` | `ref-turn2.sav` + one End Turn with the same build and config. This run reproduced the determinism oracle byte for byte (`bb4fd9ac…` / `978041ac…`), so it is the rule-19 control for everything in the other two. |
|
||||
| `watch-arm.txt` | the `watch.mode=snlv` arming sweep: all 28 systems with `AFlags`, `SnLv` and the decoded per-player 2-bit contact levels, plus the canary self-test. |
|
||||
| `watch-hits.txt` | 24 hardware watchpoint hits — 20 writes to Spica's `SnLv` (all from 0x0086a789) and 4 to its `NVO` record's `{touched, TShn}` pair. |
|
||||
| `saves/l1-t1t2-post.sav` | post-turn autosave of the hive-creation turn, **full L1 hook set**. |
|
||||
| `saves/l1-t1t2-post-hooksoff.sav` | the same turn with **`hooks=off`, nothing installed**. It differs from the line above AND from the historical `turn2-state.sav`, in one field: player 3's `ResTNm`. See the finding's §6.1 — this pair is why `turn1-state.sav` must not be used as a determinism oracle. |
|
||||
| `saves/l1-t2t3-pre.sav`, `saves/l1-t2t3-post.sav` | the second bracket's two autosaves. |
|
||||
| `rep.py` | the reader used for every table in the finding: prints each record's hook, depth, `rng` word delta and non-pointer arguments. `uv run python3 rep.py trace-et2.jsonl`. |
|
||||
36
verify/results/shim/l1/rep.py
Normal file
36
verify/results/shim/l1/rep.py
Normal file
|
|
@ -0,0 +1,36 @@
|
|||
import json, sys
|
||||
|
||||
SKIP = {'rng','rng_words_in','rng_left_in','server','host','out_name','results',
|
||||
'queen','slavers','server_cached','host_plus_0x54','sites','thresholds'}
|
||||
|
||||
def val(x):
|
||||
if isinstance(x, dict) and 't' in x:
|
||||
v = x.get('v')
|
||||
if x['t'] == 'struct' and isinstance(v, dict):
|
||||
return {k: val(w) for k, w in v.items()}
|
||||
if x['t'] == 'list' and isinstance(v, list):
|
||||
return [val(w) for w in v]
|
||||
return v
|
||||
return x
|
||||
|
||||
for path in sys.argv[1:]:
|
||||
print("="*72); print(path)
|
||||
for ln in open(path):
|
||||
r = json.loads(ln)
|
||||
if 'hook' not in r:
|
||||
print("meta:", r.get('build_id') or list(r)[:6]); continue
|
||||
hook = r['hook']; depth = r.get('depth')
|
||||
side = r.get('side') or {}
|
||||
rng = side.get('rng') or {}
|
||||
b = val(rng.get('before')) or {}; a = val(rng.get('after')) or {}
|
||||
bw, aw = b.get('words'), a.get('words')
|
||||
d = (aw - bw) if isinstance(aw, int) and isinstance(bw, int) else None
|
||||
print(f"[d{depth}] {hook:52s} words {bw} -> {aw} DELTA {d}")
|
||||
for arg in (r.get('args') or []):
|
||||
n = arg.get('n')
|
||||
if n in SKIP: continue
|
||||
print(f" {n} = {json.dumps(val(arg))}")
|
||||
for nm, s in side.items():
|
||||
if nm == 'rng': continue
|
||||
print(f" side[{nm}] {json.dumps(val(s.get('before')))} -> {json.dumps(val(s.get('after')))}")
|
||||
if r.get('err'): print(" ERR", r['err'])
|
||||
BIN
verify/results/shim/l1/saves/l1-t1t2-post-hooksoff.sav
Normal file
BIN
verify/results/shim/l1/saves/l1-t1t2-post-hooksoff.sav
Normal file
Binary file not shown.
BIN
verify/results/shim/l1/saves/l1-t1t2-post.sav
Normal file
BIN
verify/results/shim/l1/saves/l1-t1t2-post.sav
Normal file
Binary file not shown.
BIN
verify/results/shim/l1/saves/l1-t2t3-post.sav
Normal file
BIN
verify/results/shim/l1/saves/l1-t2t3-post.sav
Normal file
Binary file not shown.
BIN
verify/results/shim/l1/saves/l1-t2t3-pre.sav
Normal file
BIN
verify/results/shim/l1/saves/l1-t2t3-pre.sav
Normal file
Binary file not shown.
14
verify/results/shim/l1/trace-et1.jsonl
Normal file
14
verify/results/shim/l1/trace-et1.jsonl
Normal file
File diff suppressed because one or more lines are too long
27
verify/results/shim/l1/trace-et2.jsonl
Normal file
27
verify/results/shim/l1/trace-et2.jsonl
Normal file
File diff suppressed because one or more lines are too long
14
verify/results/shim/l1/trace-oracle.jsonl
Normal file
14
verify/results/shim/l1/trace-oracle.jsonl
Normal file
File diff suppressed because one or more lines are too long
77
verify/results/shim/l1/watch-arm.txt
Normal file
77
verify/results/shim/l1/watch-arm.txt
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
|
||||
17:31:12.313 [tid 3828] watch: VEH=01848448 text=[0x00e81000,0x0145ca00) out=C:\SOTS\shim.watch.txt
|
||||
17:31:12.329 [tid 3828] watch: arm hook StrategyServer::ApplyAllTurnCommands rva=0x0038f6a0 va=0120f6a0 create=MH_OK
|
||||
enable=MH_OK
|
||||
17:34:46.960 [tid 3828] watch: systems vector @0x1f2b20ac first=0x0e5e46c8 last=0x0e5e4738 end=0x0e5e4738 count=28
|
||||
(expect 28 on the reference game)
|
||||
17:34:46.960 [tid 3828] watch: NVO header probe -- ServerSystem+0x274 validated on 9 systems, +0x26c on 0; using
|
||||
+0x274 (pointer delta 0)
|
||||
17:34:46.960 [tid 3828] watch: sys[0] @0x0e5bd0b0 'Caladan' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[1] @0x0e587d80 'Hyperion' AFlags=0x10 SnLv=0x00000300 lv[0..14]=0 0 0 0 3 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=1 NVE=1 root=0x0e5ca6d0 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[2] @0x0e588060 'Tully' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[3] @0x0e588340 'Foo' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[4] @0x0e588620 'Gamma Cephei' AFlags=0x1 SnLv=0x00000003 lv[0..14]=3 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 NVO=1 NVE=1 root=0x1f309038 key=0 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[5] @0x0e588900 'Biter' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[6] @0x0e588be0 'Flinx' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[7] @0x0e588ec0 'Procyon' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[8] @0x0e5891a0 'Kor?Voth' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[9] @0x0e589480 'Ke'Rassak' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[10] @0x0e589760 'Koa?Tuun' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[11] @0x0e5dc6c8 'Spica' AFlags=0x0 SnLv=0x00000200 lv[0..14]=0 0 0 0 2 0 0 0 0 0 0
|
||||
0 0 0 0 NVO=1 NVE=0 root=0x1f309888 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[12] @0x0e5dc9a8 'Junction' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[13] @0x0e5dcc88 'Nemesis' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[14] @0x0e5dcf68 'Keu?Loko' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[15] @0x0e5dd248 'Ke'Dolarra' AFlags=0x2 SnLv=0x0000000c lv[0..14]=0 3 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 NVO=1 NVE=1 root=0x1f3090d0 key=1 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[16] @0x0e5dd528 'Koa?Vo' AFlags=0x80 SnLv=0x0000c000 lv[0..14]=0 0 0 0 0 0 0 3 0 0
|
||||
0 0 0 0 0 NVO=1 NVE=1 root=0x1f3096c0 key=7 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[17] @0x1f2e7890 'Delphi' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[18] @0x1f2e8718 'Kaa?Vaalu' AFlags=0x10 SnLv=0x00000300 lv[0..14]=0 0 0 0 3 0 0 0
|
||||
0 0 0 0 0 0 0 NVO=1 NVE=1 root=0x1f309330 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[19] @0x1f2e6fd8 'Mu Bootis' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[20] @0x1f2e7e60 'Kor?Hakaan' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[21] @0x1f2e72c0 'Serpens' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[22] @0x1f2e8148 'Markab' AFlags=0x10 SnLv=0x00000300 lv[0..14]=0 0 0 0 3 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=1 NVE=1 root=0x1f309920 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[23] @0x1f2e75a8 'Pascal' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[24] @0x1f2e7b78 'Kao?Lolto' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[25] @0x1f2e8430 'Kea?Pono' AFlags=0x10 SnLv=0x00008300 lv[0..14]=0 0 0 0 3 0 0 2 0
|
||||
0 0 0 0 0 0 NVO=2 NVE=1 root=0x1f309168 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: sys[26] @0x1f2e8a00 'Gienah' AFlags=0x0 SnLv=0x00000000 lv[0..14]=0 0 0 0 0 0 0 0 0 0
|
||||
0 0 0 0 0 NVO=0 NVE=0 root=0x00000000 key=0 ok=0
|
||||
17:34:46.960 [tid 3828] watch: sys[27] @0x1f2eea08 'Ko'Rorkor' AFlags=0x10 SnLv=0x00000300 lv[0..14]=0 0 0 0 3 0 0 0
|
||||
0 0 0 0 0 0 0 NVO=1 NVE=1 root=0x1f309ae8 key=4 ok=1
|
||||
17:34:46.960 [tid 3828] watch: trade routes -- manager=0x0e5c0818 (S+4+0x154) vector@mgr+0x3c first=0x00000000
|
||||
last=0x00000000 end=0x00000000 count=0
|
||||
17:34:46.960 [tid 3828] watch: spy programs -- manager=0x1f2c8218 (S+4+0x158) vector@mgr+0x10 first=0x00000000
|
||||
last=0x00000000 end=0x00000000 count=0
|
||||
17:34:46.960 [tid 3828] watch: SELFTEST canary writes=1 traps=1 dr7=0xdddd0055 PASS
|
||||
17:34:46.960 [tid 3828] watch: slot 0 -> 'Spica' NVO root+0x10 {touched:i16,TShn:i16} key=player 4 = 0x1f309898
|
||||
17:34:46.960 [tid 3828] watch: slot 1 -> 'Spica' SnLv (sys+0x24) = 0x00000200 at arm; key player 4 level 2 =
|
||||
0x0e5dc6ec
|
||||
17:34:46.960 [tid 3828] watch: slot 2 -> trade routes vector _Mylast (mgr+0x40, count 0 at arm) = 0x0e5c0858
|
||||
17:34:46.960 [tid 3828] watch: slot 3 -> spy programs vector _Mylast (mgr+0x14, count 0 at arm) = 0x1f2c822c
|
||||
17:34:46.960 [tid 3828] watch: ARMED on tid 3828 dr7=0xdddd0055, S=1f2b2068 (ApplyAllTurnCommands this)
|
||||
|
||||
|
||||
24
verify/results/shim/l1/watch-hits.txt
Normal file
24
verify/results/shim/l1/watch-hits.txt
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
watchhit seq=0 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x00fc5a84,0x014187a6,0x012ea916,0x014052f8
|
||||
watchhit seq=1 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=2 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=3 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=4 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=5 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=6 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=7 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x0125cb9f scan=0x014187a6,0x012ea916,0x014052f8,0x00fa677a
|
||||
watchhit seq=8 mark=1 slot=0 dr6=0xffff0ff1 eip=0x011db95a value=131075(0x00020003) tid=3828 ebpret=0x011dbd4a ebpret2=0x011dbdc3 scan=0x011d29f6,0x014056db,0x011dbd4a,0x012ea806
|
||||
watchhit seq=9 mark=1 slot=0 dr6=0xffff0ff1 eip=0x011db965 value=196611(0x00030003) tid=3828 ebpret=0x011dbd4a ebpret2=0x011dbdc3 scan=0x011d29f6,0x014056db,0x011dbd4a,0x012ea806
|
||||
watchhit seq=10 mark=1 slot=1 dr6=0xffff0ff2 eip=0x0129e78b value=512(0x00000200) tid=3828 ebpret=0x0129e99e ebpret2=0x012ea927 scan=0x0129e99e,0x012ea927,0x014052f8,0x00fa677a
|
||||
watchhit seq=11 mark=1 slot=1 dr6=0xffff0ff2 eip=0x0129e78b value=512(0x00000200) tid=3828 ebpret=0x0129e99e ebpret2=0x012ea927 scan=0x0129e99e,0x012ea927,0x014052f8,0x00fa677a
|
||||
watchhit seq=12 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x00fc5a84,0x014187a6,0x012ea916,0x012e2696
|
||||
watchhit seq=13 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=14 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=15 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=16 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=17 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=18 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=19 mark=1 slot=1 dr6=0xffff0ff2 eip=0x012ea78c value=512(0x00000200) tid=3828 ebpret=0x012ea916 ebpret2=0x01259820 scan=0x014187a6,0x012ea916,0x012e2696,0x0120ae73
|
||||
watchhit seq=20 mark=1 slot=0 dr6=0xffff0ff1 eip=0x011db95a value=196611(0x00030003) tid=3828 ebpret=0x011dbd4a ebpret2=0x011dbdc3 scan=0x011d29f6,0x014056db,0x011dbd4a,0x012ea806
|
||||
watchhit seq=21 mark=1 slot=0 dr6=0xffff0ff1 eip=0x011db965 value=196611(0x00030003) tid=3828 ebpret=0x011dbd4a ebpret2=0x011dbdc3 scan=0x011d29f6,0x014056db,0x011dbd4a,0x012ea806
|
||||
watchhit seq=22 mark=1 slot=1 dr6=0xffff0ff2 eip=0x0129e78b value=512(0x00000200) tid=3828 ebpret=0x0129e99e ebpret2=0x012ea927 scan=0x0129e99e,0x012ea927,0x012e2696,0x0120ae73
|
||||
watchhit seq=23 mark=1 slot=1 dr6=0xffff0ff2 eip=0x0129e78b value=512(0x00000200) tid=3828 ebpret=0x0129e99e ebpret2=0x012ea927 scan=0x0129e99e,0x012ea927,0x012e2696,0x0120ae73
|
||||
Loading…
Add table
Reference in a new issue