L4: predictions for the live AI order capture, before the module exists
This commit is contained in:
parent
011749571b
commit
45bf085981
1 changed files with 246 additions and 0 deletions
246
docs/L4-predictions.md
Normal file
246
docs/L4-predictions.md
Normal file
|
|
@ -0,0 +1,246 @@
|
||||||
|
# L4 — what the live AI will emit, written before the module was built
|
||||||
|
|
||||||
|
Lane L4, 2026-09-08. Guest **VM145** (`sots-re-win10-145`, 192.168.10.145). Worktree `wip/l4` off
|
||||||
|
`main` `0117495`.
|
||||||
|
|
||||||
|
Rule 2: this file is committed **before** the shim module is written, and it names the falsifier for
|
||||||
|
every claim. Nothing below is a summary of a run; there has been no run.
|
||||||
|
|
||||||
|
The question this lane exists to answer: **does our emission model produce the same commands the
|
||||||
|
game does?** `src/game/ai` models the *shape* of a `TurnCommands` block and the *cost* of each
|
||||||
|
element, and decides nothing. Nobody has ever looked at a real AI block.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. The instrument, and why it is one detour
|
||||||
|
|
||||||
|
`Game::StrategySim::ApplyTurnCommandBatch` **0x0088f9b0** receives `(blocks, n)` as two stack
|
||||||
|
arguments with `ecx = sim` — read straight off the prologue at `0x0088f9db`–`0x0088f9e7`, where
|
||||||
|
`n` is multiplied by `0x1b4` and added to `blocks` to make the end pointer. **At that instant every
|
||||||
|
player's submitted block is complete and in memory**, so one register-transparent entry stub dumps
|
||||||
|
the entire turn's command traffic: six gates and twenty-seven list lengths per player, plus the
|
||||||
|
element bytes.
|
||||||
|
|
||||||
|
That is a strictly better instrument than counting `ModCount` bumps, which is what every previous
|
||||||
|
lane has had. A bump count says *how many* commands; the block says *which lists, how many
|
||||||
|
elements, and what values*.
|
||||||
|
|
||||||
|
Second instrument: sixteen register-transparent entry counters (`probe_entry.cpp`'s pattern, its
|
||||||
|
own table so lane H's set is untouched), on the task bodies and the emission gates. One of them —
|
||||||
|
`StrategyAIAgent::RunTaskList` **0x006b3320** — additionally records its `pass` stack argument into
|
||||||
|
a global, so every later probe hit is **attributed to a pass**. That is what turns AI3's P2 from a
|
||||||
|
count into a measurement.
|
||||||
|
|
||||||
|
**Rule 20 is the whole design.** `AITRaid`'s list-16 emit is the named open item, and a zero at
|
||||||
|
`0x007635f0` means nothing unless `AITRaid::Execute 0x0068e670` is also probed: "never ran" and
|
||||||
|
"ran and emitted nothing" are opposite answers.
|
||||||
|
|
||||||
|
**Rule 19 control.** Both workloads have published oracle bytes. Every configuration is run against
|
||||||
|
them and any configuration that moves a byte has invalidated its own numbers.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 1. Workload W1 — `turn1-state.sav`, one End Turn (the turn 1→2 transition)
|
||||||
|
|
||||||
|
Facts already in the corpus, from `turn1-state.sav` → `turn2-state.sav` (read this session with
|
||||||
|
`verify/save-reader`, not taken from a report):
|
||||||
|
|
||||||
|
| | |
|
||||||
|
|---|---|
|
||||||
|
| players | 8: ids 16, 32, 496, 512 (`Species` 0/2/0/2) and 528/544/560/576 (`Species == 4`) |
|
||||||
|
| player 32 | `HomeSys` **288** = Ke'Dolarra, `PlyrIdx` 1 |
|
||||||
|
| `ModCount` | 0 → 12 |
|
||||||
|
| `ResRate` | 0.25 → **0.8** on 32, 496, 512; unchanged (0.25) on 16 |
|
||||||
|
| `ResTNm` | `''` → `IND_Waldo` (32), `DRV_PlsFiss` (496), `BIO_GnMod` (512); `''` on 16 |
|
||||||
|
| `Status` | 0 → 4 on 16/32/496/512; **0 on all four `Species == 4`** |
|
||||||
|
| player 32 designs | 5 → 6; the new one is `DesID` **18**, `"Honor Lance"` |
|
||||||
|
| `designIds` | gains **18** *and* **1712** — the second belongs to player 528, whose design count also goes 18 → 19 |
|
||||||
|
| `NMnx` | 106 → 109 (ship **1728**, fleet **1744**, and one more) |
|
||||||
|
| new fleet | **1744** `"Alpha Fleet"` at system 288, holding ship 1728 of design 18 |
|
||||||
|
|
||||||
|
### P1 — the block set
|
||||||
|
|
||||||
|
**Prediction: `n == 4`, and the four `block+0x04` player ids are 16, 32, 496, 512 in that order.**
|
||||||
|
|
||||||
|
*Falsified if* `n == 8` — then the `Species == 4` factions **do** submit, they just submit empty
|
||||||
|
blocks, and AI4 §2's "no `StrategyClient` at all" is too strong (it would become "a client that
|
||||||
|
never sets the always-set gate", which is a different and stranger claim). *Falsified differently
|
||||||
|
if* the four ids are 0,1,2,3 — then `block+0x04` is a player **index**, not the save id, and every
|
||||||
|
published reading of that field needs the same correction.
|
||||||
|
|
||||||
|
### P2 — the four blocks, list by list
|
||||||
|
|
||||||
|
| block | gates set | lists non-empty |
|
||||||
|
|---|---|---|
|
||||||
|
| 16 (human) | rate only, value **0.25** | **none** |
|
||||||
|
| 32 (AI) | rate **0.8**, target (`IND_Waldo`) | **1, 3, 5 — one element each** |
|
||||||
|
| 496 (AI) | rate **0.8**, target (`DRV_PlsFiss`) | **none** |
|
||||||
|
| 512 (AI) | rate **0.8**, target (`BIO_GnMod`) | **none** |
|
||||||
|
|
||||||
|
Boost, group-4, the three-float Hiver gate and `CivilianRatios` **clear on all four**.
|
||||||
|
|
||||||
|
That sums to AI4's twelve: 4 rate + 3 target + 3 (player 32's lists) + 2 drivers.
|
||||||
|
|
||||||
|
*Falsified if:* any list outside {1, 3, 5} is non-empty on any block; or a list in {1, 3, 5} carries
|
||||||
|
more than one element; or the human's block carries a list at all; or 496/512 carry a list (they own
|
||||||
|
nothing, so a list element from them would mean a task fired against objects they do not have).
|
||||||
|
|
||||||
|
**The sharpest falsifier is list 8/10/14 non-empty.** Player 32 gets a brand-new fleet (1744) this
|
||||||
|
turn, and a fleet is exactly the thing lists 8/10/14 order. If the AI issues a fleet order on the
|
||||||
|
same turn it creates the fleet, AI4's P2 attribution is wrong by exactly the fleet-order group and
|
||||||
|
the twelve has to be re-derived.
|
||||||
|
|
||||||
|
### P3 — the element values
|
||||||
|
|
||||||
|
* **list 3** (`{i32 ordinal, i32 designId, i32 systemId, i32 w}`): `designId == 18`,
|
||||||
|
`systemId == 288`. The `w` word has never been observed and is not predicted.
|
||||||
|
* **list 5** (`{i32 systemId, OutputRates}`): `systemId == 288`, i.e. **the same system the build
|
||||||
|
order names**.
|
||||||
|
* **list 1** (`ShipDesignDef` frame + `i32`): the element carries the id **18** somewhere in its
|
||||||
|
first 48 bytes.
|
||||||
|
|
||||||
|
*Why 18 matters.* 18 is not a multiple of 16 and does not come from the master `NMnx` counter — the
|
||||||
|
*other* new design that turn, player 528's, got **1712** from that counter, and 528 submits no
|
||||||
|
block. So there are two id spaces, and the prediction is that **a client-issued design carries a
|
||||||
|
small client-local id in the command itself**, while a server-created object gets a master id.
|
||||||
|
*Falsified if* the list-1 element's ids are all ≥ 512 / multiples of 16 — then 18 was assigned
|
||||||
|
server-side when the command was applied and the design command carries no id at all, which is a
|
||||||
|
materially different thing for a reimplementation to emit.
|
||||||
|
|
||||||
|
### P4 — the probes on W1
|
||||||
|
|
||||||
|
| probe | prediction | what a miss means |
|
||||||
|
|---|---|---|
|
||||||
|
| `RunTaskList 0x006b3320` | **6** = 3 AI players × 2 passes | 2 ⇒ only one player's task list runs and 496/512 reach `ResTNm` by some other path; 0 ⇒ the instrument is broken, not the game |
|
||||||
|
| `BuildTurnCommands 0x00783780` | **4**, one per submitting block | ≠ 4 ⇒ P1 is wrong from the other side |
|
||||||
|
| `RequestBuildForTask 0x006cea50` | **> 0 and even-ish** — entered on both passes, emits only on pass 1 | entries only in pass 1 ⇒ the caller gates before it, and AI3 §2.3's "the hub returns an empty list on pass 0" is not the whole mechanism |
|
||||||
|
| `AssignFleetsAndIssueOrders 0x006c16c0`, `IssueRouteForFleets 0x006bbd50` | **entered, both passes, zero list-8/10/14 elements** | entered + elements ⇒ P2 falsified |
|
||||||
|
| `AITRaid::Execute 0x0068e670` | **0** | — |
|
||||||
|
| `ClientOrder 0x007635f0` (list 16) | **0** | — |
|
||||||
|
| the six shared planner bodies (`AITColonize` 0x0068b400, `AITEscortGateInvade` 0x0068c7c0, `AITInvade` 0x0068d7a0, `AITNodeBore` 0x0068e590, `AITBuildPoliceShips` 0x00690380, `AITBuildDeepScanShips` 0x006901a0) | **at least one build-shaped body entered in both passes; at most one produces the single list-3 element** | none entered ⇒ the list-3 element comes from a task nobody has named, and AI3 §5's path table is not the emission path |
|
||||||
|
| `AITAdvanceIdleShips::Execute 0x0068f230` | entered (priority 0, always last) | — |
|
||||||
|
| `IsClaimedByAnotherTask 0x006a8d20` | **> 0** | — |
|
||||||
|
|
||||||
|
**Stated in advance so it cannot be claimed afterwards: W1 almost certainly cannot settle AI3 §2.4.**
|
||||||
|
A turn-1 AI with one colony and no fleet of its own has nothing to raid, so `AITRaid` is predicted
|
||||||
|
not to run at all, and a zero at `0x007635f0` will then be a *non-answer* about pass 0 — exactly the
|
||||||
|
"did not fire" vs "fired and found nothing" distinction rule 20 is about. The AITRaid question needs
|
||||||
|
`AITRaid::Execute` to be entered, and W2 is the better chance.
|
||||||
|
|
||||||
|
**Also stated in advance: this probe set cannot settle AI3's P3.** An entry counter on
|
||||||
|
`IsClaimedByAnotherTask` measures the "called often" half only; the steal branch at `0x006a8ded` is
|
||||||
|
*inside* the function and an entry probe cannot see it. P3 stays open.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 2. Workload W2 — `ref-turn2.sav`, one End Turn (the turn 2→3 transition)
|
||||||
|
|
||||||
|
`ref-turn2.sav` **is** `turn2-state.sav` (same sha256). Corpus facts for 2→3:
|
||||||
|
|
||||||
|
* `ModCount` 12 → 24; `NMnx` 109 → 111 (ship **1760**, fleet **1776** `"Gamma Fleet"`);
|
||||||
|
* fleet **1744** `"Alpha Fleet"` is **gone**, replaced by fleet **34** `"Beta Fleet"`, in transit
|
||||||
|
(`FtTrans 1`), flight plan one waypoint `Wpt 272`, `pnd 288`, ETA 3, origin = Ke'Dolarra's
|
||||||
|
position;
|
||||||
|
* no `ResTNm` change on any player; `ResRate` already 0.8.
|
||||||
|
|
||||||
|
AI4 §2.1 attributes the twelve as 4 rate + list 5 + list 3 + list 10 + **2 × list 14** + list 8 + 2
|
||||||
|
drivers.
|
||||||
|
|
||||||
|
### P5 — the block
|
||||||
|
|
||||||
|
**Prediction:** `n == 4`; blocks 16, 496, 512 carry the rate gate and **nothing else** (no target
|
||||||
|
gate this turn); block 32 carries the rate gate and lists **3 = 1, 5 = 1, 8 = 1, 10 = 1, 14 = 2**,
|
||||||
|
everything else empty.
|
||||||
|
|
||||||
|
### P6 — list 14 is two elements against one fleet, and the second field is the discriminator
|
||||||
|
|
||||||
|
Lane Q read the element as `{i32, i32, bool}`; `human-turn2-orders.sav` (a UI fleet move) carries
|
||||||
|
exactly one, `{1456, 0, true}`. AI2's P1 says the AI's bridge calls the adder twice with mode 0 and
|
||||||
|
mode 1 and the adder keys on `(fleet, mode)`.
|
||||||
|
|
||||||
|
**Prediction: the two list-14 elements are `{F, 0, b}` and `{F, 1, b}` with the same `F`, and `F` is
|
||||||
|
also the fleet id list 8's element names.** That converts AI2's P1 from an inference about bump
|
||||||
|
counts into a read of the values.
|
||||||
|
|
||||||
|
*Falsified if:* both elements carry the same second word (then the adder does not key on mode and
|
||||||
|
the doubling has another cause); or the two fleet ids differ (then it is two orders against two
|
||||||
|
fleets and the "one order costs two elements" rule is wrong); or list 14 has one element (then
|
||||||
|
AI4's two list-14 bumps came from two different fleets and the reference turn moved two).
|
||||||
|
|
||||||
|
### P7 — which fleet id the order names
|
||||||
|
|
||||||
|
**Prediction: `F == 1744`** — the fleet that exists at submit time — and **34 does not appear
|
||||||
|
anywhere in the block.** 34 is created when the order is applied, from the same small-id space as
|
||||||
|
design 18.
|
||||||
|
|
||||||
|
*Falsified if* `F == 34`: then the client creates the fleet object *before* submitting and ships the
|
||||||
|
new id in the command, which would make the small-id space **client-allocated and part of the wire
|
||||||
|
protocol** — a much bigger constraint on a reimplementation than the alternative, because our engine
|
||||||
|
would have to reproduce that counter exactly to get byte-identical saves.
|
||||||
|
|
||||||
|
This is the one prediction I would most like to be wrong, because the falsifier is the more
|
||||||
|
interesting world.
|
||||||
|
|
||||||
|
### P8 — list 10's first word
|
||||||
|
|
||||||
|
Lane AI4 §4.5 refused to name list 10 (`{i32, i32, counted i32}`) on positional evidence alone.
|
||||||
|
|
||||||
|
**Prediction: its first `i32` is `F`, and its counted vector holds ship ids** — on W2 that means the
|
||||||
|
single ship **1728**. If so, "assign these ships to this fleet" is supported by values rather than
|
||||||
|
by adjacency, and the list can be named.
|
||||||
|
|
||||||
|
*Falsified if* the counted vector is empty or holds system ids (W2's route is a single hop to 272,
|
||||||
|
so a `{…, 272}` tail would make it a route-shaped command instead).
|
||||||
|
|
||||||
|
### P9 — AITRaid, the second attempt
|
||||||
|
|
||||||
|
Player 32 now owns a fleet, so `AITRaid` has a candidate. **Prediction: `AITRaid::Execute` is
|
||||||
|
entered on both passes and `ClientOrder 0x007635f0` is entered zero times**, i.e. list 16 stays
|
||||||
|
empty and AI3 §2.4's inference holds — but *for the reason the probe can see*, which is the point.
|
||||||
|
|
||||||
|
*Falsified if* `0x007635f0` is entered with `pass == 0` recorded: then pass 0 **does** emit, AI3's P2
|
||||||
|
is falsified, and every `ModCount` arithmetic in `ai-order-emission.md` that assumes one emitting
|
||||||
|
sweep is off by a factor.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 3. Rule 19 — the controls, and what they are compared against
|
||||||
|
|
||||||
|
Published oracle bytes (sha256 prefixes, `findings/subsystems/running-the-game.md`):
|
||||||
|
|
||||||
|
| workload | `(Autosave EndTurn).sav` | `(Autosave).sav` |
|
||||||
|
|---|---|---|
|
||||||
|
| W1 from `turn1-state.sav` | `a3f9dc4b…` | `ab4ac2d7…` |
|
||||||
|
| W2 from `ref-turn2.sav` | `bb4fd9ac…` | `978041ac…` |
|
||||||
|
|
||||||
|
Three configurations per workload, each a separate launch:
|
||||||
|
|
||||||
|
| run | `shim.cfg` | purpose |
|
||||||
|
|---|---|---|
|
||||||
|
| **C** | `hooks=off` | proves **this clone** reproduces the oracle at all. VM145 is a ZFS clone; nothing has ever checked that it is byte-faithful. If C fails, the lane reports that and stops — every later number would be measured against an unknown baseline. |
|
||||||
|
| **A** | `aiorders=on`, `aiprobes=off` | the block dump alone: one detour |
|
||||||
|
| **B** | `aiorders=on`, `aiprobes=all` | the dump plus sixteen entry probes |
|
||||||
|
|
||||||
|
**Prediction:** C, A and B all reproduce both oracle hashes. **Falsified if** A or B moves a byte —
|
||||||
|
in which case that configuration's numbers are reported as unmeasured, not as results, and the
|
||||||
|
sixteen-probe set is bisected with `aiprobes=N`. Lane H's precedent is explicit that a MinHook
|
||||||
|
detour has changed this game's behaviour once already, and that the probe module is the one it
|
||||||
|
happened to.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 4. What this lane will not do, said now
|
||||||
|
|
||||||
|
1. **It does not close AI3's P3** (§1, P4). An entry probe cannot see a branch inside the callee.
|
||||||
|
2. **It does not test the eleven "free" lists** (AI4 P1's untested half) unless one of them turns up
|
||||||
|
populated, which P2/P5 predict will not happen. If one does, that is the result.
|
||||||
|
3. **It does not settle the Hiver gate** (AI4 P3). The corpus has no Hiver player and this lane is
|
||||||
|
not manufacturing one.
|
||||||
|
4. **It reads element bytes, not element types.** The dump emits a fixed 48-byte window per element
|
||||||
|
plus the same bytes as ints and floats; decoding to lane Q's records happens offline, in the
|
||||||
|
report, where a wrong record is visible as a wrong value rather than being baked into the
|
||||||
|
instrument. Where a list's in-memory element is larger than 48 bytes the dump says so rather than
|
||||||
|
silently truncating.
|
||||||
|
5. **The dump's own coverage check**: for every list it walks the node chain *and* reads `_Mysize`,
|
||||||
|
and logs a `MISMATCH` line if they disagree. A wrong list layout would otherwise print a
|
||||||
|
confident, wrong zero (rule 1).
|
||||||
Loading…
Add table
Reference in a new issue