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:
parent
e935ec5880
commit
1fecff8af0
2 changed files with 219 additions and 0 deletions
207
findings/control-flow/raid-intercept-species-word.md
Normal file
207
findings/control-flow/raid-intercept-species-word.md
Normal 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.*
|
||||
12
ghidra/addresses.d/az.json
Normal file
12
ghidra/addresses.d/az.json
Normal 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"
|
||||
}
|
||||
]
|
||||
}
|
||||
Loading…
Add table
Reference in a new issue