209 -> 158 (closed 51, regressed 0) and 108 -> 87 (closed 21, regressed 0), identical to the pre-lane baseline, measured on CT111. Falsification hypotheses 1 and 2 refuted by measurement (three empty BQ frames per reference save, every hbq false, the one command block empty); hypothesis 3 refuted by the corpus oracle; hypothesis 4 stands as a labelled hypothesis. construction.h also carries the ship/fleet birth shape as read but NOT implemented, with the reason stated: the newborn hull copies four cached stat words the engine does not yet compute.
162 lines
9.5 KiB
Markdown
162 lines
9.5 KiB
Markdown
# B6 — ship construction: the build queue, and where the missing destroyer actually comes from
|
||
|
||
Lane B6. Branch `wip/build` off `main` `0f1c007`.
|
||
|
||
This file was written and committed **before** the code (earned rule 2). Section 1 is the
|
||
prediction as first written; section 2 is the falsification list; sections 3 onward are filled
|
||
in after the measurement and correct section 1 in place where it was wrong.
|
||
|
||
---
|
||
|
||
## 1. Prediction, written before the build
|
||
|
||
The brief's target is `hist[*]/stats[*]/shpt[0]` on both reference pairs — the archived ship
|
||
census is one destroyer higher than ours, and lane E2 concluded that no phase we run builds a
|
||
ship. That conclusion is right. The inference drawn from it — that `S11`'s build-queue
|
||
sub-pass is what closes it — is **wrong**, and this lane predicts the pass closes **nothing**:
|
||
|
||
> **Both reference pairs: closed 0, regressed 0.**
|
||
>
|
||
> `turn1-state.sav` and `turn2-state.sav` each carry exactly **three** `BQ` frames (one per
|
||
> owned system: players 16, 32 and 576), and **every one of them holds zero orders**. Every
|
||
> `hbq` in both files is `false`, so there is no ship-borne `BQ2` queue either. The only
|
||
> `TurnCommands_v5` block belongs to player 16 and is the byte-identical empty one. So at the
|
||
> moment the standalone loads either save, **the whole game contains no build order at all**,
|
||
> and a faithful build-queue pass has nothing to advance.
|
||
>
|
||
> The destroyer the archive counts is created *inside* the turn: on the reference pair the
|
||
> owner is player 32 ("Fane Lao"), an AI, and the same turn shows `NumDes` 5 → 6 (a design the
|
||
> AI designed during the turn), `FNG/FNGNum` 0 → 1 (a fleet named), `Maint` 0 → 500,
|
||
> `ShipRecs/srb[0]` 0 → 1 and a new `ShipRecs` design record, with `NMnx` 106 → 109 — three
|
||
> ids issued in the order design(107), ship(108), fleet(109). The order that fed the queue was
|
||
> issued by the AI and consumed in the same turn.
|
||
>
|
||
> **`shpt[0]` is blocked on AI order generation (`game/ai`, Rung B), not on S11.** This lane
|
||
> moves the blocker; it does not close the leaf.
|
||
|
||
What this lane does deliver, and what it is worth:
|
||
|
||
* the build-queue advance read byte for byte from `Game::BuildQueue::ProcessTurn`
|
||
(`0x00890d50`, real end `0x00891240` — Ghidra says 1230 bytes, the body runs 1264), which
|
||
**independently confirms** lane B4's `sim::ProcessBuildQueue` and finds **one divergence**
|
||
in it (§4, "What changed in the engine");
|
||
* the `ShipRecords` update the pass performs, whose per-class counter has **exactly one
|
||
writer in the whole image**;
|
||
* the pass wired into the standalone's S11 as its own reported sub-pass;
|
||
* a real oracle for the pass that the campaign already owns and had not noticed:
|
||
`zuul-turn16-noderoute.sav` → `zuul-turn17-rollpending.sav` is a genuine consecutive-turn
|
||
pair (frames 16 and 17) in which **six orders complete and one is partially advanced**
|
||
across two systems (§3, "The corpus oracle").
|
||
|
||
## 2. How this prediction could be wrong, and the symptom of each way
|
||
|
||
1. **A build order exists somewhere I did not look.** The reader types unknown regions as
|
||
`raw`; a queue hidden inside one would not show up in a tag scan. *Symptom:* the standalone
|
||
reports a non-zero order count on a reference pair, and `closed` is not 0.
|
||
2. **The order is created before the spine rather than during it.** If the AI's orders were
|
||
applied at load time by something the standalone also runs, the queue would be non-empty by
|
||
S11 and the pass would build. *Symptom:* same as (1). (The measured `PvSav` says otherwise:
|
||
Fane Lao's savings are 50,000 in the input file and the previous-turn snapshot in the output
|
||
file is 38,100, so 11,900 left the treasury *between the save and phase 0* — the queue-time
|
||
deduction of a build order the file does not contain.)
|
||
3. **The FIFO + stop-at-the-first-short-order model is wrong.** *Symptom:* on the
|
||
`zuul-turn16 → zuul-turn17` pair there is **no** single non-negative integer point total per
|
||
system that turns the observed before-queue into the observed after-queue. This is a real
|
||
test: a per-order point budget, a non-FIFO order, or a "skip and continue" rule instead of
|
||
"stop" each fail it.
|
||
4. **The `points <= 0` gate on the removal sweep is wrong.** Unobservable in this corpus
|
||
and therefore carried as a labelled hypothesis, not a result.
|
||
5. **`shpt` is not the fleet walk.** If the census leaf were fed by `ShipRecs` rather than by
|
||
walking fleets, the fix would be different. Lane E2 already falsifies this: the fleet walk
|
||
reproduces 480/480 archived census leaves.
|
||
|
||
---
|
||
|
||
## 3. Measured
|
||
|
||
Built on CT111 (`/srv/re-lab/build/sots-engine-b6`), report run there with
|
||
`tools/standalone_report.py --binary …`. Host gates run separately, never `&&`-chained.
|
||
|
||
| pair | baseline | after | **closed** | **regressed** |
|
||
|---|---:|---:|---:|---:|
|
||
| `turn1-state.sav` → `turn2-state.sav` | 209 | 158 | **51** | **0** |
|
||
| `turn2-state.sav` → `turn3-state.sav` | 108 | 87 | **21** | **0** |
|
||
|
||
Identical to the pre-lane baseline, which is the prediction holding: **this lane closed 0 and
|
||
regressed 0.** `regressedPaths` is empty on both pairs.
|
||
|
||
The prediction's own numbers, checked one by one:
|
||
|
||
* three `BQ` frames per reference save (systems owned by players 16, 32 and 576), **0 orders in
|
||
all three**, on both sides of both pairs — the standalone now prints this on its own line;
|
||
* every `hbq` in `turn1/2/3-state.sav` is `false` (15, 16 and 17 of them), so **0 ship-borne
|
||
orders**;
|
||
* the four `CDT` ids are `Player.00000016.TurnCommands_v5` plus three `AIAgent` blobs, and the
|
||
one command block is the 36-node empty one.
|
||
|
||
Falsification hypotheses 1 and 2 are therefore both refuted by measurement rather than by
|
||
argument, and hypothesis 3 is refuted by the corpus oracle below. Hypothesis 4 stands as a
|
||
labelled hypothesis; hypothesis 5 was already refuted by lane E2.
|
||
|
||
### What the pass does on the saves that are not the reference pair
|
||
|
||
`sots_turn <save> --phases` now prints a `build queue:` line under S11 on every save:
|
||
|
||
| save | queues | pending orders | demand (points) |
|
||
|---|---:|---:|---:|
|
||
| `turn1/2/3-state.sav` | 3 | 0 | 0 |
|
||
| `human-turn2-orders.sav` | 20 | 0 | 0 |
|
||
| `zuul-turn5-species5.sav` | 4 | 4 | 7,120 |
|
||
| `zuul-turn16-noderoute.sav` | 4 | 7 | 12,099 |
|
||
| `zuul-turn17-orders2.sav` | 4 | 2 | 5,109 |
|
||
| `zuul-turn23-fleet23.sav` | 4 | 1 | 370 |
|
||
|
||
Five of eleven saves exercise the pass the moment points exist. Nothing is committed: an order
|
||
that advanced with no ship behind it leaves a state the game never produces.
|
||
|
||
### The corpus oracle, which the campaign already owned
|
||
|
||
`zuul-turn16-noderoute.sav` (frame 16) → `zuul-turn17-rollpending.sav` (frame 17) is a real
|
||
consecutive-turn pair. Two systems, six completions, one partial advance:
|
||
|
||
| system | owner | before (`desID`:`conleft`) | after | solved points |
|
||
|---|---|---|---|---:|
|
||
| 384 | 16 | 608:1753, 576:1860, 576:1860 | 576:**1291** | **4182** |
|
||
| 80 | 32 | 114:959, 114:1889 ×3 (+816:6974 appended in-turn) | 816:**3818** | **9782** |
|
||
|
||
`test_construction.cpp` does not assume those totals — it **searches** 0..200,000 and asserts a
|
||
solution exists and is **unique**. A per-order budget, a non-FIFO drain, or skip-instead-of-stop
|
||
each make the search come back empty. The completions then reproduce the `ShipRecs` deltas the
|
||
same two files carry (class-0 `srb` 2→4 and 53→57; design 608 2→3; a new record for 576;
|
||
design 114 18→22; design 816 unchanged because its order did not finish).
|
||
|
||
## 4. What changed in the engine
|
||
|
||
* **`game/sim/construction.{h,cpp}`** — `ShipRecords` and the completion bookkeeping. The
|
||
per-class `built` counter's indexed increment has **exactly one writer in the whole image**
|
||
(an image-wide byte scan for the form at that displacement returns one site, inside this
|
||
pass), so a hull on the wire with that counter bumped came through the build queue and
|
||
nothing else — the encounter spawner that also creates ships touches no `ShipRecords`.
|
||
`RunSystemConstruction` wraps the point pass and keeps each completion's design id.
|
||
* **`game/sim/colony.{h,cpp}`** — one corrected rule: with `points <= 0` the original skips the
|
||
**removal sweep** as well, because the entry test branches to the epilogue. Carried as a
|
||
labelled hypothesis; no corpus save can reach the state that shows it, and the workload that
|
||
would is named in the header.
|
||
* **`app/construction_phase.{h,cpp}`** — S11's build-queue sub-pass, reported on its own line.
|
||
* **`app/phase_catalog.cpp`** — S11's blurb no longer calls the build queue the input boundary
|
||
for the census leaf.
|
||
|
||
## 5. What this lane did NOT do, said plainly
|
||
|
||
* **It did not close `shpt[0]`, and no build-queue work can.** The blocker moves to `game/ai`.
|
||
* **It did not model the ship or the fleet at birth in code.** The chain is read end to end and
|
||
written up in the RE repo (`findings/subsystems/ship-construction.md` §4): `StarShip` is 0xb0
|
||
bytes, its id comes from the object-id allocator and lands in `+0x04`, `FltID` is born NULL;
|
||
the hull joins its system's **cached home fleet** (`ServerSystem+0x238`) and a fleet is created
|
||
only when that slot is empty; every fleet born that way gets `FtFlg |= 0x400` — which
|
||
**corrects lane B5**, that bit is not a retreat marker. It is not implemented because the
|
||
newborn hull's stats are copied from design words the engine does not yet compute, and because
|
||
no save exercises the path with an oracle behind it.
|
||
* **The money charge has never fired.** No design in the corpus costs money, so the slot-9
|
||
refusal path is read from the bytes and never observed.
|
||
* **`srl`, `srk`, `sri` carry no model.** Losses and kills are zero in all eleven saves.
|