AZ: predictions for the raid-intercept species word, committed before the build

The brief's gate ('Human or Zuul draws nothing, every other species draws')
is incomplete in two places, found by reading FUN_00820af0 before touching a
guest: species 1 (Hiver) short-circuits too, and the ROUTE OWNER's species is
a gate of its own. The species enum is read from the InitTable jump table as
bytes: 0 Human, 1 Hiver, 2 Tarkas, 3 Liir, 4 _NPC, 5 Zuul, 6 Morrigi.

Corpus counted against the predicate (rule 28 practice 5): 0 of 30. 24 saves
hold a Tarka AI - a drawing species - but tscr is 253 in every one, so the AI
has no Commerce Raiding and never rolls. The workload has to be built.

Tarkas chosen: the tech data gives it a 100% path to CCC_ComRaid via
CCC_FTLEcon, where Hiver has 0% on both edges.
This commit is contained in:
alex 2026-09-09 00:53:00 -04:00
parent e935ec5880
commit 1fecff8af0
2 changed files with 219 additions and 0 deletions

View file

@ -0,0 +1,207 @@
# `z` — the raid interception word, and the species that pays it
- **Type:** control-flow (static decode of the gate + live measurement)
- **Owner / date:** lane AZ · 2026-09-09 · guest **VM141** (`sots-re-win10-b`, 192.168.10.143)
- **Status:** predictions committed; results to follow
- **Decides:** the `z` term of lane AR's `k + s + z` tail model
(`findings/control-flow/raid-target-pick-verdict.md` §4.2) — **never observed through
`Slot13RngCalleeB` by any lane**, because every raid this campaign has run was Human
- **Instrument:** lane Z's return-address ledger (`draw_sites`) + lane H's entry probes at
**`probes=8`**, the byte-neutral set. **Not `probes=11`** (lane H bisected index 8 as not
byte-neutral, and lane AC's turn totals are not calibration data because of it)
---
## 1. Predictions, committed before the build (rule 2)
*Everything in §1 was written and committed before the shim was built, before the workload was
played and before any counter was read. It is not edited after the fact — only annotated with
verdicts below the line.*
### 1.0 What the brief said, and the two ways it is incomplete
My brief — inherited from lane AR's §4.2 and lane AG's §5.2 — states the gate as:
> A **Human or Zuul** raider intercepts and draws nothing. **Every other species draws a word.**
That is **wrong in two places**, and both were found by reading `FUN_00820af0` from the
instruction stream on the host before any guest was touched (rule 18's corollary: static reading
is for explaining, but here it is also for *not wasting a workload*). The function has **three**
short-circuits and they are a disjunction; the campaign has only ever named the third.
```
FUN_00820af0(TradeManager* this, Vector3* out, TradeRoute* rt, StarFleet* fleet) ; 0x00820af0
ebx = registry_lookup(S+0x84, rt->tro) ; the ROUTE OWNER (ServerPlayer)
...
00820bc2 cmp DWORD PTR [ebx+0x5c],0x1 ; ServerPlayer+0x5c is the serialised `Species` tag
00820bc6 je 0x820bf6 ; SC1 -- route owner is species 1
00820bcf call 0x81a2d0 ; StarFleet_GetCrewSpeciesForIntercept(fleet)
00820bd7 cmp eax,0x1
00820bda je 0x820bf6 ; SC2 -- RAIDER's crew species is 1
00820bdd call 0x81a2d0 ; again, same value
00820be3 call 0x545cc0 ; SpeciesDef_Get(k) -> 0x00b10a00 + k*0x184
00820bed call 0x53baf0 ; SpeciesDef_HasInterceptFlag: [ecx+0x144] != 0
00820bf4 je 0x820bf8 ; SC3 -- flag clear: fall through to the draw
00820bf6 mov al,0x1
00820bf8 fldz ; fstp [ebp+0xc] ; frac = 0.0
00820bfd test al,al
00820bff jne 0x820c33 ; short-circuited: NO DRAW
00820c1b call 0x47d830 ; <<< NextFloat, ONE WORD -- this is `z`
```
**Correction 1 — species 1 short-circuits too, and species 1 is Hiver.** `SC2` is a plain
`== 1` test on the raider's own crew species and has nothing to do with `SpeciesDef+0x144`.
It is not mentioned in AR's §4.2 disjunct list as a *species* test at all (AR annotates it
"raider is Species 0 -- no", which is true of AR's state and hides the rule).
**Correction 2 — the route owner's species is a gate too.** `SC1` tests the *route owner*, not
the raider. A route owned by a species-1 empire is intercepted with **no draw whoever raids it**.
My brief does not mention this conjunct at all, and it is a way for this lane's run to come out
zero for a reason that has nothing to do with the raider.
### 1.1 The species enum, named from the image
`SpeciesDef_InitTable 0x005453a0` selects the `Species/%s/...` data-path prefix through
`jmp DWORD PTR [eax*4+0x545b60]`. Reading the seven-entry jump table **as bytes** (not by the
order the case bodies happen to be laid out in, which is a different order and would give the
wrong answer):
| index | jump-table target | string |
|---|---|---|
| **0** | `0x005453f2` | **`Human`** |
| **1** | `0x005453f9` | **`Hiver`** |
| **2** | `0x00545400` | **`Tarkas`** |
| **3** | `0x00545407` | **`Liir`** |
| **4** | `0x0054541c` | **`_NPC`** |
| **5** | `0x0054540e` | **`Zuul`** |
| **6** | `0x00545415` | **`Morrigi`** |
`SpeciesDef_Get 0x00545cc0` bounds-checks `k <= 6`, so **there are exactly seven**, and
`0x00b10a00 + k*0x184` is the element. This independently confirms lane AR's "species 5 = Zuul"
and "species 0 = Human", and it names 1, 2, 3, 4 and 6 for the first time in this campaign.
The `+0x144` flag word is written only at `0x0054562f` (`or [ebx+0x144],2` when the element's
index is **5**) and `0x00545638` (`or [ebx+0x144],1` when it is **0**) — AR's read, reproduced.
### 1.2 The gate as a predicate, and the corpus counted against it (rule 28 practices 1 and 5)
> **`0x00820c1b` draws one word through `Slot13RngCalleeB` iff**
> **(a)** the route's owner is **not species 1 (Hiver)**, **and**
> **(b)** the raider fleet's crew species is **not 1 (Hiver)**, **and**
> **(c)** the raider fleet's crew species is **not 0 (Human) and not 5 (Zuul)**.
>
> Equivalently: the raider's crew species is one of **2 Tarkas, 3 Liir, 4 _NPC, 6 Morrigi**,
> and the route owner is not Hiver.
`StarFleet_GetCrewSpeciesForIntercept 0x0081a2d0` returns the fleet **owner's** `Species`
(`FUN_0071e280(fleet)->+0x5c`) on every path except one: when that species is **4 (`_NPC`)** it
scans the fleet's ships for a `design->+0x14->+0xac..+0xb4` record and returns that record's
`+0x4`. So for a player-owned fleet, crew species == owner species, full stop.
**Corpus count, all 30 saves in `verify/results/saves/`, read with `save_reader.py --json`:**
| species present | saves |
|---|---|
| 0 Human (player 0) + 2 Tarkas (the AI) + 4 `_NPC` | **24** |
| 5 Zuul (both players) + 4 `_NPC` | 6 |
| 1 Hiver, 3 Liir, 6 Morrigi | **0** |
**Corpus count against the predicate: 0 of 30.** And the near miss is instructive: **24 saves
already contain a Tarka empire**, which is a *drawing* species — but it is the **AI**, and
`tscr` is `253` in every one of them (bit 0 set = player 0 permitted, **bit 1 clear** = the AI has
no Commerce Raiding). So the AI never rolls, `A` never fires for it, and no amount of patience on
an existing save reaches `z`. **The "an AI of another species already raids" option my brief asked
me to consider is dead, and the corpus count is what killed it** — the state has to be built.
**And the tech data says which species can even be a raider.** From
`verify/results/data-catalogs/tech_tree.json` (`MasterTechList.tech`, per-race
availability percentages as written):
| edge | rp | Human | Zuul | Hiver | Tarkas | Liir | Morrigi |
|---|---|---|---|---|---|---|---|
| `CCC_FTLBrdB -> CCC_FTLEcon` | 18,000 | 100 | **0** | 100 | 100 | 100 | 100 |
| `CCC_FTLBrdB -> CCC_ComRaid` | 5,000 | 0 | **100** | 0 | 0 | 0 | 0 |
| `CCC_FTLEcon -> CCC_ComRaid` | 12,000 | 100 | 0 | **0** | **100** | **100** | **100** |
So **Hiver can never research Commerce Raiding at all** (0% on both edges) — which makes `SC2`
unreachable for a Hiver *player* through `vslot13`, and leaves it as either a defensive test or
the path for an `_NPC`-owned fleet with a Hiver-crewed design. And the species that can both raid
and draw are exactly **Tarkas, Liir and Morrigi**.
**This lane picks Tarkas (species 2).** Liir is rejected on movement (their early game is a
sublight crawl, and this workload lives or dies on parking a fleet on a distant node), Morrigi on
their fleet-size-dependent drive. Tarkas is a plain node-drive empire with a 100% path to
Commerce Raiding, and it is the species the AI already is in the lineage every other trade lane
has used, so the route-owner side is a known quantity.
### 1.3 The workload
A **new single-player game on VM141** with the species pool cut to **Tarkas only** — lane V's
trick from the Zuul save, which makes *both* players Tarkas and so pins the route owner's species
to 2 as well, satisfying `SC1` by construction rather than by luck. Create-game parameters as
close as I can get to lane AC's `MyGamel3trade` (`NSys 28`, `MaxP 2`, `PCol 10`,
`PSav 9,850,000`, `PTech 15`, `IncM 1.48`, `ResM 1.46`), because that is the setup that put a
human raider on a trade sector by turn 20 and gave the AI four trade routes by turn 22.
Then: research `CCC_FTLBrdB -> CCC_FTLEcon -> CCC_ComRaid` (34,000 RP on this branch), build
**several separate single-ship long-range fleets**, and park them on a trade sector that **sources
a route**. Separate fleets, not one fleet — the roll is per fleet (lane AD) at
`ODDS + 0.01·nShips`, so `m` one-ship fleets roll `m` times and one `m`-ship fleet rolls once.
**Cruisers if their range reaches, destroyers if not.** Cruisers roll at
`(double)0.2f + (double)0.01f = 0.21` per fleet-turn against a destroyer's `0.06` — three and a
half times the signal per fleet-turn — and a raider is **displaced off the node by its own
success** (AR §4.3), so each fleet contributes at most one success per parking and the arm
exhausts itself. That is the structural constraint on this workload and it is why hull choice
matters more than turn count.
**The route set moves between turns and this map is not AD's map.** Lane AR's geometry table is
for the `MyGamel3trade` lineage only. I will re-read `trdmgr` on the state I actually build and
pick the target sector from *that* `trfr` set, preferring a sector that sources **one or two**
routes: `bound = n - 1`, so `n = 1` gives mask 0 and `n = 2` gives mask 1, and in both cases
`RNG_NextInt` **cannot reject** and `0x0088b613` costs exactly one word. At `n >= 3` the rejection
loop is live and `s` stops being one word per success.
### 1.4 The numbered predictions
| # | prediction |
|---|---|
| **P0** | The species enum is `0 Human, 1 Hiver, 2 Tarkas, 3 Liir, 4 _NPC, 5 Zuul, 6 Morrigi`, read from the jump table at `0x00545b60` as bytes. Falsifiable live: the save I build will carry `Species 2` on both `ServerPlayer` records and the game will have been created with **Tarkas** selected in the UI |
| **P1** | `FUN_00820af0` short-circuits on **three** conjuncts, not one. The campaign's published sentence ("Human or Zuul draws nothing, every other species draws") is incomplete: **Hiver raiders draw nothing** (`SC2`), and **a Hiver-owned route is intercepted without a draw whoever raids it** (`SC1`) |
| **P2** | The short-circuit sets `frac = **0.0**`, **not `1.0`** as both lane AG §5.2 and lane AR §4.2 write it. Traced through the x87 stack at `0x00820bf8`–`0x00820c33`: `fldz; fstp [ebp+0xc]` on the short-circuit arm, and `0.0 + (1.0 - 0.0)*NextFloat()` on the draw arm. The interception point is therefore at the route's **source** end for a Human or Zuul raider, not its destination. Word count unaffected; the published sentence is wrong (rule 28 practice 6 — read the polarity before the count) |
| **P3** | Corpus count against the predicate is **0 of 30**, and the "an AI of another species already raids" option is **dead**: 24 saves hold a Tarka AI, but `tscr = 253` in every one, so the AI lacks Commerce Raiding and never rolls |
| **P4** | **`OnAllCombatDone_Tail` costs `k + 2s` words** on this state, where `k` = permitted Tarka raiders parked with `Pos` bit-equal and `s` = successes on a route-sourcing sector. This is `k + s + z` with **`z = s`**, and it is the term no lane has ever observed |
| **P5** | `draw_sites` shows, on a turn with `s` successes: `NextFloat 0x00820e18` `calls == words == k`; `NextInt 0x0088b613` `calls == words == s`; and **`NextFloat 0x00820c1b` `calls == words == s`**. Bracket residual **0** on every turn |
| **P6** | **The `0x00820c1b` row is attributable to `B` and not to `CreateRaidEncounter` exactly when `NextInt 0x008939ee` reads `calls = 0` on the same turn.** Proof, and it is proof-carrying rather than a convention: `FUN_00820af0` has **exactly two `E8` callers in the whole image** — `0x0082cf65` and `0x0088b62c` — verified by an image-wide relative-call scan; `0x0082cf65` sits in `FUN_0082ce00`, whose only caller is `0x00892700` inside `FUN_00892640`, which `CreateRaidEncounter` calls at `0x00893a25`; and `0x008939ee` is on the **straight-line path** from the empty-list gate to `0x00893a25` with no branch around it (`0x008939c9 -> 0x008939ee -> 0x00893a25`, decoded). So `0x008939ee` at zero means the other caller never ran. If it is non-zero on a measured turn I will call that turn's attribution ambiguous and use another turn, not argue |
| **P7** | `B`'s cost on a success is **2** words for a Tarka raider. Lane AG §5.2's "1 (NextInt) + 1 (NextFloat)" is right for a drawing species; lane AR's measured **1** is right for Human and Zuul; AG's committed §3.3 figure of "3 words on a succeeding turn" is right for `k = 1` **and a drawing species**, and was wrong only because the lineage was Human |
| **P8** | The `hooks=off` control **will agree with itself** (two fresh processes, byte-identical autosaves) — I am predicting lane AR's outcome, not lane AD's, **and I am not confident**. AD's variation was confined to one AI player's decision sub-tree on a state ten turns earlier in a *different* game; a resolver is looking at the discrepancy now and neither outcome is safe to assume. If my control varies I will localise it by sub-tree with `state_checksum.py --tree` and state exactly which leaves my measurement depends on and whether *those* reproduce |
| **P9** | With `k` one-cruiser fleets at `p = 0.21` per fleet-turn (`(double)0.2f + (double)0.01f`) I expect the first success within the first two or three instrumented turns. Any zero is reported as a **binomial probability**, never as "unremarkable" (rule 20), and an entry probe on `B` separates "did not fire" from "fired and found nothing" |
| **P10** | **Not settled by this lane, predicted in advance** (rule 15): the freighter doubling of the raid odds; the `design+0x12c > 1` short-circuit; `bestScale` (`0x009f8d48 = 0.33f` when `ship+0x18 & 0x100000`); the rejection loop at `0x0088b613` for a candidate list of three or more; and whether `SC2` is reachable at all for a *player* (the tech data says no) |
### 1.5 The falsifier, stated as the sentence I will have to write
> **If a Tarka raider parked on a route-sourcing sector succeeds, `Slot13RngCalleeB` is entered,
> `NextInt 0x0088b613` fires — and there is still no `NextFloat 0x00820c1b` row — then the species
> flag is not the only thing gating that draw**, and the two conjuncts ahead of it become the live
> suspects. In that case I will report, per rule 28, exactly which conjunct I satisfied and which I
> did not: `SC1` is checkable in the save I built (the route owner's `Species`), `SC2` is checkable
> the same way (the raider's owner's `Species`), and if both check out then `SpeciesDef+0x144` is
> not what `0x0053baf0` is reading and the static decode is retired.
That is a real result and it is the more interesting one, because it would mean two lanes' static
reads of the same twelve-byte function produced a confident wrong answer.
### 1.6 What could go wrong with the *workload*, and what each failure looks like
| way it fails | symptom |
|---|---|
| the Tarka AI never builds a trade route in the turns I can afford | `trdmgr` holds zero `rt` records; no sector sources a route; `A` fires and `B` returns at `G_B4` with 0 words — the exact zero lanes AC and AD measured, for AC's reason not mine |
| no Tarka hull reaches a route-sourcing sector | `A` never fires (`G4` fails, `Pos` not bit-equal); probe 5 reads 0 |
| I cannot get Commerce Raiding in the session | `tscr` stays `252`; `vslot13`'s `G3b` rejects; probe 2 fires and probe 5 reads 0 |
| the target sector sources three or more routes | `0x0088b613` `words > calls` and `s` stops being one word per success — reported, not hidden |
| the encounter query eats a scripted End Turn | detected from the `Done` pixel at (233,673) before every End Turn, per AR §10 |
---
*Results, and the verdicts, follow below. Nothing above this line is edited after the run.*

View file

@ -0,0 +1,12 @@
{
"entries": [
{
"name": "SpeciesDef_NameJumpTable",
"addr": "0x00545b60",
"convention": "data",
"prototype": "void*[7], the switch jump table SpeciesDef_InitTable 0x005453a0 dispatches through at 0x005453eb (`jmp [eax*4+0x545b60]`) to choose the `Species/%s/...` data-path prefix. READ AS BYTES, the seven entries are 0x005453f2 \"Human\", 0x005453f9 \"Hiver\", 0x00545400 \"Tarkas\", 0x00545407 \"Liir\", 0x0054541c \"_NPC\", 0x0054540e \"Zuul\", 0x00545415 \"Morrigi\" -- so the species enum is 0 Human, 1 HIVER, 2 TARKAS, 3 LIIR, 4 _NPC, 5 Zuul, 6 MORRIGI. NOTE the case bodies are NOT laid out in case order (index 4 jumps PAST index 5's body), so reading the disassembly top to bottom gives the wrong enum; only the table bytes settle it. SpeciesDef_Get 0x00545cc0 bounds-checks k <= 6, so there are exactly seven. This confirms lane AR's species 0 = Human / species 5 = Zuul from an independent direction and names 1, 2, 3, 4 and 6 for the first time",
"status": "verified",
"source": "findings/control-flow/raid-intercept-species-word.md (lane AZ 2026-09-09); disassembled from dumps/sots.exe"
}
]
}