sots-re/findings/control-flow/trade-raid-rng-gate.md

433 lines
24 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# The tail *does* draw under active contents — the trade-raid gate, decoded and fired
- **Type:** control-flow (static decode + live measurement)
- **Status:** **verified** for the trade half; the spy half is decoded but **not reached** and is
labelled a hypothesis (rule 6)
- **Confidence:** high on the trade half — the callees were entered live, the RNG word delta moved,
and the state that moved it was reached by playing a stock game with no file edited
- **Owner / date:** lane AC · 2026-09-08 · guest **VM141** (`sots-re-win10-b`, 192.168.10.143)
- **Predicts / corrects:** lane W3's open risk (**not** retired — realised), lane L3's
`trade-and-spy-workload.md` §3.5, lane V2's "closure 185 functions, draw-free" reading of the
trade slots
---
## 0. Headline
> **Yes. The strategic-turn tail draws RNG once its containers are *active*.**
> `StrategyServer::OnAllCombatDone_Tail` consumes **1 word per turn** in the state this lane built,
> against **0** on the same guest, same build and same config on the state lane L3 left.
> `TradeManager::Slot13RngCalleeA` — entered **0** times in every measurement any lane had ever
> taken — was entered **1×/turn on both instrumented turns**, and `Slot13RngCalleeB` on one of them.
>
> **Lane W3's open risk is realised, not retired. The RNG ledger and the standalone's generator
> model need extending by one draw site.**
And the condition that does it is **not** the one lane L3 named. Freighters in service do not do it
and a deployed spy would not do it either. The gate is:
> **a player-owned fleet parked exactly on a trade-sector node, whose owner's bit is set in that
> sector's `tscr` mask — which is set by researching `CCC_ComRaid` ("Commerce Raiding").**
That is the trade-raid roll. It had never fired in this campaign because no lane had ever
researched Commerce Raiding, and without it the game refuses the order in as many words:
**`Alpha Fleet cannot raid trade sector.`**
---
## 1. What was measured
Instrument: `shim.cfg.l3probe` verbatim (lane L3's, = lane H's `hp11` + `watch.mode=cont`), shim
build `ac-7e726b9-20260909T0017Z`, cross-built on the dev box. All eleven probes reported
`create=MH_OK enable=MH_OK`; no `COVERAGE:` line was emitted; the watchpoint canary printed
`SELFTEST canary writes=1 traps=1 dr7=0xdddd0055 PASS` on **every** End Turn. These are measured
numbers, not MinHook failures.
### 1.1 Entry counts, active state (turns 21→22 and 22→23 of the AC lineage)
The **baseline column is this lane's own control**, not a quotation: the same shim build, the same
`shim.cfg.l3probe`, the same guest, one End Turn on lane L3's `human-turn15-spyprogram.sav`, run
immediately after the active measurement. It reproduces lane H's and lane L3's published counts
line for line.
| probe | active, turn 1 | active, turn 2 | **AC control** (L3's turn 15) |
|---|---|---|---|
| `ServerSpyManager::vslot13` | 1 | 1 | 1 |
| `ServerSpyManager::vslot14` | 1 | 1 | 1 |
| `ServerTradeManagerImpl::vslot13` | 1 | 1 | 1 |
| `ServerTradeManagerImpl::vslot15` | 1 | 1 | 1 |
| `SpyManager::Slot13RngCallee` | 0 | 0 | 0 |
| **`TradeManager::Slot13RngCalleeA`** | **1** | **1** | **0** |
| **`TradeManager::Slot13RngCalleeB`** | **1** | **0** | **0** |
| `ServerTradeManager::CreateRaidEncounter` | 0 | **1** | 0 |
| `ServerTradeManager::GenerateTradeRaidEncounters` | 1 | 1 | 1 |
| `ServerSpyManager::vslot15` [control] | 1 | 1 | 1 |
| `ServerTradeManagerImpl::vslot14` [control] | 1 | 1 | 1 |
| `EncounterDetect::Run` [control] | NOT INSTALLED | NOT INSTALLED | — |
`Slot13RngCalleeB` firing on turn 1 and not on turn 2 is exactly what the decoded control flow
predicts: `B` is called only when `A` returns true, i.e. only when the raid roll **succeeds**. Two
turns, one success — the roll is doing what a roll does.
### 1.2 The RNG ledger
`side.rng` word deltas. **Control first** (L3's turn 15, no raider, same instrument):
```
ProcessTurn 353 -> 355 delta 2
OnAllCombatDone_Tail 355 -> 355 delta 0
```
**Active** (raider parked on sector 6's node), both turns:
```
ProcessTurn 448 -> 468 delta 20
OnAllCombatDone_Tail 468 -> 469 delta 1
ProcessTurn 469 -> 487 delta 18
OnAllCombatDone_Tail 487 -> 488 delta 1
```
**One word per turn inside the tail**, on a turn where the tail had never drawn a word before. The
draw is `FUN_0047d830(StrategyServer+0x16c + 4)` inside `Slot13RngCalleeA` — one uniform compared
against the raid odds. `Slot13RngCalleeB` and `CreateRaidEncounter` each cost 0 words on the turns
they ran, so the whole of the new consumption is `A`'s single roll.
---
## 2. The gates, decoded from the instruction stream
Read off `sots.exe` (image base `0x00400000`), disassembling to the next function start rather than
to Ghidra's reported size (rule 17).
### 2.1 `Slot13RngCalleeA` / `B` — called from `ServerTradeManagerImpl::vslot13`
Both calls are **direct** `E8 rel32`, not vtable slots:
| callee | called from | at |
|---|---|---|
| `TradeManager::Slot13RngCalleeA` `0x00820ca0` | `ServerTradeManagerImpl::vslot13` `0x0088ef80` | `0x0088f036` |
| `TradeManager::Slot13RngCalleeB` `0x0088b440` | same function | `0x0088f042` |
| `SpyManager::Slot13RngCallee` `0x008408e0` | `ServerSpyManager::vslot13` `0x008877b0` | `0x00887af2` |
`ServerTradeManagerImpl::vslot15` (`0x0082cca0`) and `ServerSpyManager::vslot14` (`0x0088db80`) do
**not** call any of the three. The only data references to the four outer callees are their vtable
slots (`0xa30770`, `0xa30774`, `0xa31ba8`, `0xa31bb0`).
**`vslot13` does not iterate trade routes or trade sectors.** It iterates
`StrategyServer+0x64..+0x68` — the **fleet** vector — and per fleet requires, in order:
```
0088ef95 test ecx,0xfffffffc ; G0 no fleets -> return
0088efb1 je 0x88f047 ; G1 null slot
0088efb9 call 0x6fe320 ; StarFleet::GetLocationAsTradeSector()
0088efc0 je 0x88f047 ; G2 location is not a trade-sector node
0088efc7 call 0x80ed50 ; the raid predicate
0088efd1 je 0x88f047 ; G3
0088efeb..0088f016 ; G4 fleet.Pos == sector.Pos, all three floats
0088f036 call 0x820ca0 ; <-- A
0088f03d je 0x88f047 ; G6 raid roll failed
0088f042 call 0x88b440 ; <-- B
```
**G2** is the one nobody had satisfied. `0x006fe320` reads `StarFleet+0xa0` (`LocID`, resolved to a
pointer) and returns it only if `node+0x14 == 2`. That kind word is written by
`StarMapNode::StarMapNode(int)` `0x00730900`, and enumerating **every** call to it gives exactly
three: `StarSystem` ctor `0x00752ea0` passes **0**, `StarFleet` ctor `0x00706140` passes **1**,
`TradeSector` ctor `0x00858020` passes **2**. So `vslot13` only ever acts on a fleet whose location
is a **trade-sector node**, never a star. Every fleet in every corpus save before this lane was
parked at a star, so G2 rejected all of them — which is why the "23-fleet Zuul save" experiment in
`tail-probes.md` §3.1 could not have worked no matter how many fleets it had.
**G3** is `FUN_0080ed50(fleet)`:
```c
if (!fleet->GetLocationAsTradeSector()) return false;
if (FUN_00813ab0(fleet, 0x80000, 0)) return false; // runtime flag pair +0xb8/+0xbc
ServerPlayer *p = fleet->[0x58]; if (!p) return false;
if (!(sector->[0x90] & (1u << p->[0x28]))) return false; // ** tscr **
if (FUN_0080e9c0(sector, p)) // ** tsct ** bit, or p->[0x5c]==4
if (!FUN_006fe1d0(fleet, 0x800)) return false; // FtFlg & 0x800
return true;
```
`FUN_0080eac0` masks six consecutive words with a low-N-players mask, which is what identifies them
as per-player bitmasks and pins the tags:
```
0080ead7 and [ecx+0x88],edx tssec 0080eae3 and [ecx+0x90],edx tscr
0080eadd and [ecx+0x8c],edx tsct 0080eae9 and [ecx+0x94],edx ptssec …
```
**G4** requires the fleet's `Pos` (`+0x18`) to be **bit-equal** to the sector's — arrived, not in
transit. The `test ah,0x44 / jp` after `fucompp` is the MSVC float-not-equal idiom.
### 2.2 `SpyManager::Slot13RngCallee` — decoded, and much further away than "deployed"
```
0088781d je 0x887e92 ; SpyCraft.deat (+0x10) == 0 -> next spy
00887856 je 0x887c5b ; SpyCraft.sdet (+0x40) == -1 -> DETECTION-ROLL branch, not P
00887867 jl 0x887e92 ; (turn - sdet) < 3 -> next spy
0088787a je 0x887c4f ; system unowned -> sdet = -1
00887891 je 0x887c4f ; system owner == spy's owner -> sdet = -1
00887af2 call 0x8408e0 ; <-- P
```
`P` itself is the **false-flag draw**: `0.25` (`0x009e5ac0`) for an ordinary owner, or `0.75`
(`0x009e5ac4`) plus a uniform pick over the other players when `owner->[0x5c] == 6`; its result is
the name that goes into `EVENT_SPY_DESTROYED`, posted to the *system* owner.
So **deploying a spy is necessary but nowhere near sufficient for `P`**: the spy must be deployed,
then **detected** on an earlier turn, then survive **three more turns** at a system still owned by
someone else. Lane L3's "the next condition is `spyon = 0`" understates the distance by three turns
and a detection roll.
**But the `sdet == -1` arm at `0x00887c5b` is itself an RNG draw** —
`FUN_008e6dd0(StrategyServer+0x16c, spy->sdo)`, gated on `FUN_00743f80(system) & 1` — and it sits
**inline in `vslot13`, not inside `P`**. That is a second, independent way for the tail to draw that
**no entry probe on `P` can see**. It is unreachable today only because `deat == 0` rejects the
docked spy two gates earlier. Rule 20, pointing the other way: instrumenting the entry was necessary
and was not sufficient — a probe on `P` reading 0 does **not** mean the spy half is draw-free.
**This is a hypothesis, not a result** (rule 6): no save in the corpus has a deployed spy, and this
lane did not manage to build one (§5).
---
## 3. `tscr` is the raid mask and `CCC_ComRaid` sets it — measured, before and after
This is the part that turns the decode into a recipe, and it was **not** predicted: this lane's
committed prediction P6 said a player fleet on a sector node would *not* fire the callees, on the
reasoning that `tscr = 252 = 0b11111100` is exactly the six NPC player slots (2–7) and excludes both
real players (0 = `re`, 1 = `The Eternal Empire`). That reading of the *value* was right and the
inference from it was **wrong**, because the mask is not fixed.
| save | `tscr`, all six sectors | player `re` `CnRad` |
|---|---|---|
| `human-turn15-spyprogram.sav` (L3) | **252** = `0b11111100` | absent/false |
| AC turn 19, after `CCC_ComRaid` completes | **253** = `0b11111101` | **true** |
Researching Commerce Raiding sets **bit 0 — the researching player's index — in every trade
sector's `tscr`**, and sets the `ServerPlayer` flag serialised as **`CnRad`**. `CnRad` is to
`CCC_ComRaid` exactly what `CnTrd` (`ServerPlayer+0xff`) is to `CCC_FTLEcon`. The AI, which never
researched it, stays out of the mask: `CnRad` false, bit 1 clear, in the same save.
So `tscr` reads: *the set of players permitted to raid this sector* — the six NPC slots by default
(which is why `TRADE_RAID_ODDS_NPC` and `TRADE_RAID_ODDS_REFUGEE` exist), plus any empire that has
bought its way in with the tech.
Two further tags fall out of the same pair of saves: `tsct` is the set of players **trading through**
the sector (bit 1 on sectors 1, 2 and 5 — the AI's; 0 on sector 6, which is mine), and it is `tsct`
that switches on the extra `FtFlg & 0x800` requirement. Sector 6 has `tsct = 0`, so my raider needed
no flag.
---
## 4. The recipe, end to end
Stock game, stock data, no file edited, starting from lane L3's `human-turn15-spyprogram.sav`.
About 45 minutes of clicking.
1. **Research `CCC_ComRaid`.** `MasterTechList.tech`: `CCC_FTLEcon -> CCC_ComRaid RP:12000
Human:100`. At this game's settings it is **one turn**. In the tech tree it is the node
**directly above FTL Economics in the C3 branch**, and at the zoom the screen opens at it is
drawn *off the top of the viewport*: zoom out two notches with the wheel, then zoom back in one
notch **with the cursor low on the screen** (`wheel 365 560 120 1`), which shifts the camera down
far enough to expose it. Vertical right-drag does **not** pan the tech tree at all — only
horizontal, and only in ~600 px steps, and even then the response is not monotonic in distance.
2. **Toggle Trade View.** Bottom bar circle **(300,714)** opens a six-icon vertical strip; the fifth
icon **(301,626)** is `Toggle Trade View`. Trade sectors then draw as large translucent wireframe
cubes with a padlock/`$` marker at the sector node itself. **Nothing else in the UI shows a trade
sector**, and there is no bar button for trade.
3. **Order a fleet to the sector node.** Select the system, select the fleet row, click **`Move`
(155,464)**, hover the sector marker — the readout reads `Sector 6 / Range 5.0 / ETA 2 Turns` —
then **plain `click`** it. This must be **one `cmd.txt`**; and note the jiggle-click variant
(`jclick`) **silently failed to register the destination** where a plain `click` worked. Confirm
from the fleet panel: it reads `Dest: Sector 6, 2 Turns`. Confirm again from the save — do not
trust the panel: on the first attempt the panel showed nothing and the save showed `LocID 48`
(unmoved).
4. **Two End Turns.** The fleet arrives: `LocID 832` = `TradeID 832` = sector 6, `Pos [0, 0, 8.0]`
bit-identical to the sector's `Pos`.
5. **End Turn with the instrument on.** `Slot13RngCalleeA` fires.
**The refusal is the oracle.** Before the tech, clicking the sector marker with a fleet selected pops
`Alpha Fleet cannot raid trade sector.` (`MOVEWARNING_NO_RAID`; the server-side twin is
`OnCommand: Fleet not able to raid.`). After the tech, the same click is accepted silently. That
single dialog is the cheapest possible test of the whole gate chain.
---
## 5. What this lane could **not** reach: a deployed spy
Reported as an absence, not inferred around.
`ARes2` on `ServerSystem` is the **asteroid-belt** field — its non-zero values in this save (1703,
1948, 2061, 2172, 2531) sit inside `SYSTEM_MIN/MAX_ASTEROID_RESOURCES` 1500–3000, and it is 0
elsewhere. Deploying a spy needs a system that is **not mine**, has **no enemy fleet present**, has
**no spy already**, and **has a belt**. In `human-turn15-spyprogram.sav` only five of 28 systems have
one:
| idx | id | name | `ARes2` | owner |
|---|---|---|---|---|
| 1 | 64 | Ka'Palum | 2061 | unowned |
| 4 | 112 | Regulus | 1703 | unowned |
| 10 | 208 | Kam'Tara | 1948 | unowned, AI fleet parked on it |
| 12 | 240 | Downbelow | 2172 | **mine** |
| 27 | 480 | Hitomi | 2531 | player index **7**, `Independent Colony` |
**No system owned by the AI has an asteroid belt.** The textbook target — an enemy colony with a
belt — does not exist in this game. The only non-own, player-owned belt system is **Hitomi**, held by
the NPC `Independent Colony`; it is 10.1 ly from the tender's berth against a hull range of 9.0, so
it needs a staging hop, and this lane ran out of session before establishing whether it is even
discovered. The `Special` menu at the tender's own system offers only
`Repair / Scuttle / Auto-repair / Flag As Guard` — no `Deploy Spy`, which is consistent with the
gate but does not locate it.
So: **the spy half of "active contents" remains unreached**, and §2.2's prediction that a deployed
spy makes the tail draw through the inline detection roll is a hypothesis for the next lane. The
save to start from is `verify/results/saves/ac-turn22-raider.sav` or L3's turn-15.
**`spies2` is still 0 in all 28 systems** in every save this lane produced, including the turn-22
one. That is unchanged from L3 and is expected: no spy was ever deployed, so the experiment that
distinguishes "`spies2` is not the spy list" from "`spies2` fills only for a deployed spy" was **not
run**. The rule-6 flag on `spies2` / `SysMem` / `mts` stays up. What this lane can add is that the
question is now cheap for whoever reaches a deploy, because `deat` (`SpyCraft+0x10`) is the field
`vslot13` uses as the system handle, and `spies2` living on `ServerSystem` makes the deployed-spy
reading the natural one.
---
## 6. Corrections to earlier findings (rule 11)
**Lane L3, `trade-and-spy-workload.md` §3.5 — "every trade sector has `tsnumflt = 0`, no freighters
in service".** Not true of the save L3 shipped. `human-turn15-spyprogram.sav`, sector `TradeID 816`
(`tsgridID 5`): `tsnumflt 1`, `tsflt 2736` — and fleet 2736 is `Freighters`, `PID 32`, the **AI's**,
with all six member systems AI-owned. The single serialised route record is the AI's too. So across
L3's seven instrumented turns the game **already had a freighter in service on an active route**, and
the trade callees were entered 0 times anyway. **"Freighters in service" was already falsified as the
gate by L3's own data**, before this lane started, and §2.1 now says why: freighters are not in the
predicate at all.
By turn 20 of this lane's lineage the AI had built two more (sector 5 `tsnumflt 2`, sector 2
`tsnumflt 1`) with no effect on any probe.
**Lane V2 — `ServerTradeManagerImpl` vftable slot 13 recorded as "closure 185 functions,
draw-free".** It is not draw-free. It reaches a `NextFloat` at `0x00820ca0` on any turn a permitted
raider is parked on a sector node. The closure was right; "draw-free" was a statement about the
states that occurred (rule 23).
**Lane W3's standing risk — "if either container ever fills, the ledger needs extending".** The risk
was correctly identified and the trigger was wrong: filling the containers changes nothing, and the
ledger needs extending anyway, for a reason that has nothing to do with either container's contents.
**`tsflt` is not a freighter list.** At turn 20, sector 6 reads `tsnumflt 1`, `tsflt 3744` — and 3744
is **my raiding fleet's** id, not a freighter. Whatever the tag is named, it counts fleets present in
the sector.
---
## 7. Controls and coverage (rules 15, 19, 26)
**Guest adoption / instrument-removed control.** `hooks=off watch=off` on `ref-turn2.sav`, one End
Turn, on VM141 with this lane's own shim build:
```
(Autosave EndTurn).sav BB4FD9AC89F41E3BC0DB2AF08B18CE83417521AC4BCEE695FC9FA6CE16E30948
(Autosave).sav 978041ACD168B56ED8EB3F5E42E78D5E70EAE6E6517D75E659A5EB7CA3D60921
```
Both **identical to the published three-run oracle** in
`verify/results/saves/determinism-hashes.txt`. That is agreement with a control already reproduced
in three fresh processes by other lanes on another guest, which is the rule-26 bar; this lane ran it
once here rather than twice, and says so.
**Instrument armed.** All eleven probes `create=MH_OK enable=MH_OK`, no `COVERAGE:` line; watchpoint
canary PASS on every End Turn; container watch read `trade routes count=46 -> 47`,
`spy programs count=1` across the two measured turns.
**Thin coverage, stated plainly.**
- **Two instrumented turns, one raiding fleet, one sector, one ship in the fleet.** The word cost is
1 on both, but 1 fleet × 1 sector is the smallest possible workload and the per-fleet loop means
the cost should scale with *permitted raiders parked on sector nodes*, which this lane did not
vary. Do not fit a constant to two observations (rule 20).
- `Slot13RngCalleeA`'s own body has a **short-circuit that skips the draw entirely**: any ship whose
`design+0x12c > 1` returns true without rolling. My fleet is one `CR Repair and Salvage`, so the
short-circuit was not exercised, and a raid by a different hull could cost **0** words while still
firing `B`. Untested.
- The odds arithmetic (`0.20` vs `0.05` base, `+0.01·nShips`, doubled when a freighter shares the
sector, scaled by a per-ship float) was read statically and **not** validated against measured
success rates: two turns, one success.
- `probes=11` is **not** determinism-neutral (lane H bisected the detour on probe index 8). These
entry counts and word deltas are comparable line-for-line with lane H's and L3's tables, which is
what they are for; they are not a byte-identity claim. `probes=8` still installs indices 0–7, i.e.
all four outer and all three inner callees, and is the configuration to use if a future lane needs
both at once.
- `EncounterDetect::Run [control]` is index 11 and `probes=11` installs 0–10, so it reports
**NOT INSTALLED**, never 0.
---
## 8. Artifacts
| what | where |
|---|---|
| turn-22 save, raider parked on a trade sector, both callees having fired | `verify/results/saves/ac-turn22-raider.sav` |
| turn-20 save, the moment the fleet arrives on the node (pre-measurement) | `verify/results/saves/ac-turn20-raider.sav` |
| probe + ledger trace, two instrumented End Turns | `verify/traces/ac-probe-turn21-turn23.jsonl` |
| shim log and watch hits | `verify/results/shim/ac/ac-probe-run-{shim.log,watch.txt}` |
| predictions, committed before the workload and again after the decode | `sots-engine` `docs/AC-predictions.md` |
| instrument | `sots-engine` `src/shim/shim.cfg.l3probe`, unchanged |
**New content these saves carry** (rule 27 — expect the coverage ratchet to break, and type the
content rather than moving the bar): a `ServerPlayer` with **`CnRad` true**; trade sectors with
**`tscr` = 253** rather than the corpus-universal 252; a `trdmgr` with **three** `rt` route records
rather than one; sectors with `tsnumflt` up to 2 and a `tsflt` entry that is a **warship** fleet id
rather than a freighter; and a `StarFleet` whose **`LocID` is a `TradeSector` id (832)** rather than a
star system id — the first fleet in the corpus located anywhere but a star, which is precisely the
state that makes the tail draw.
---
## 9. VM141 as left, and two things that cost this lane time
**Guest state.** Game stopped. `C:\SOTS\shim.cfg` is a copy of `shim.cfg.l3cont` (`hooks=off`,
`watch=on`, `watch.mode=cont` — the cheap config); the lane's dist is `C:\SOTS\shimdist-ac`
(`BUILD_ID ac-7e726b9-20260909T0017Z`) and `C:\SOTS\binkw32.dll` is that build's proxy, with the
original still at `binkw32_real.dll`. `SavedGames` has been restored from
`C:\SOTS\SavedGames-acstash` and additionally holds **`ac-turn20-raider.sav`** and L3's three saves,
so **every Load-dialog row position recorded by an earlier lane has moved on this guest** — screenshot
the dialog, do not reuse a remembered row. Also left, all harmless: `C:\SOTS\acdata\` (config text
extracted from `sots.gob`) and `C:\SOTS\ui\ac-*.ps1`.
**`click_helper.ps1` gained six verbs**, purely additive, every existing verb byte-for-byte
unchanged; pre-lane backup at `click_helper.ps1.preAC`:
```
drag x1 y1 x2 y2 left-button drag (ported from VM144, lane L3)
rdrag x1 y1 x2 y2 RIGHT-button drag, 30 steps (the only thing that pans the tech tree)
sdrag x1 y1 x2 y2 slow left drag, 40 steps
wheel x y delta [n] n wheel notches
jmove x y move with a real relative mouse_event jiggle (new; ported from lane L2's ui.ps1)
jclick x y click with the same jiggle (new)
```
**The jiggle is needed to *read* the star map and harmful when *committing* to it.** `jmove` is what
makes the map's hover readout update — without it the `Sector 6 / Range 5.0 / ETA 2 Turns` line never
appears and you are clicking blind. But `jclick` on the sector marker **did not register the
destination**, twice, silently; a plain `click` at the same coordinate did, first time. So: `jmove` to
read, plain `click` to commit. Lane L2's note that the map "needs real mouse motion" is right about
motion and wrong if read as "use the jiggle for everything".
**Verify a fleet order from the save, never from the panel.** The first move order left the fleet
panel showing no destination *and* the save showing `LocID 48` — unmoved — through two End Turns,
with no error anywhere. The second attempt showed `Dest: Sector 6, 2 Turns` in the panel and moved.
There is no failure signal; the only reliable check is `Flt.LocID` in the autosave.
**Dev-box build note (not a repo change).** `cmake/toolchain-mingw-i686.cmake` links libgcc and
libstdc++ statically but not **libwinpthread**. On the WSL dev box the i686 mingw is the *posix*
threads model, so `binkw32.dll` comes out importing `libwinpthread-1.dll` and the game dies at load
with `0xC0000135 STATUS_DLL_NOT_FOUND` — no shim log, no banner, just an exit code in
`dxvklog\run.txt`. Adding `-DCMAKE_SHARED_LINKER_FLAGS=-static` fixes it. CT111 does not have this
problem (its mingw uses the win32 model), which is why no lane has hit it before; the toolchain file
was deliberately **not** changed, because that is a shared-infrastructure decision for the integrator
rather than a lane's.