Lane L3 needed to watch the trade-route and spy-program vectors across a whole game played forward, not one turn of one save. `modcount` and `tshn` arm once on purpose -- their targets are picked from one turn's state and re-picking them would move the measurement -- so this is a third mode rather than a change to either. `cont` puts all four debug slots on the two containers, `_Myfirst` as well as `_Mylast`. Both vectors are default-constructed with all three pointers zero, so the first element writes all three: watching only `_Mylast` cannot separate "allocated for the first time" from "appended to an existing buffer", and those are different events in the model this lane set out to falsify. It re-arms and re-logs on every End Turn, and the canary self-test's counter is therefore read as a delta -- on the arm-once modes the delta is the old value, so their log lines are byte-identical. `ReportContainer` is factored out of `ArmTshnSlots` so both modes emit the same container line. Lane W3's published count=0 is the control every later count is compared against, and a reformatted line would have made that comparison a judgement call. The defect: `Shim_Init` returned before `install_watchpoints` whenever the trace mode was `off`, so `hooks=off watch=on` printed `watch=on` in the banner and armed absolutely nothing -- a config that reports a confident zero, which is the failure method rule 1 exists to catch. The watchpoints are an independent instrument with their own arming detour and no trace records, and a long play session wants them without paying 30-45 s per End Turn for template hooks that measure nothing it is asking about. MinHook is now initialised and the module installed on the `hooks=off` path when `watch=on`. Configs: shim.cfg.l3cont / .l3control differ in exactly one key for rule 19; .l3probe is lane H's hp11 verbatim plus the three watch keys, so the entry counts stay comparable to lane H's empty-container baseline line for line. Gates: clean_room_check OK; host ctest 54/54; CT111 shim cross-build OK, exports 66 names identical. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
134 lines
7.8 KiB
Markdown
134 lines
7.8 KiB
Markdown
# Lane L3 — predictions, written before the build (method rule 2)
|
||
|
||
Lane L3 owns VM144 (`sots-re-win10-144`, `re@192.168.10.144`, a ZFS clone of VM140).
|
||
Target: manufacture a workload in which the trade-route vector and/or the spy-program
|
||
vector is **non-empty**, then measure whether the four RNG-bearing tail callees draw.
|
||
|
||
Instrument: `watch.mode=cont`, a third mode on lane W3's watchpoint module. Same single
|
||
arming detour (`StrategyServer::ApplyAllTurnCommands`, RVA `0x0038f6a0`), same VEH, same
|
||
canary, same hit format. Two differences from `tshn`:
|
||
|
||
1. it arms **all four** debug slots on the two containers — `_Myfirst` and `_Mylast` of
|
||
each — so an allocation (`_Myfirst` moves from 0) and a `push_back` (`_Mylast` moves)
|
||
are separable;
|
||
2. it **re-arms and re-logs every End Turn** instead of only the first. Lane W3 printed
|
||
one count from one turn of one save; this workload is a game played across many turns,
|
||
and the question "did the container grow on turn N" needs a line per turn.
|
||
|
||
---
|
||
|
||
## What the static reading says the workload is (before any run)
|
||
|
||
Read out of `sots.exe`, `TechTree/MasterTechList.tech`, `Data/Strategy/StrategyVars.txt`,
|
||
`Species/*/sections/*.shipsection` and the repo's own catalogs. This is the part three
|
||
previous lanes did not have, and it changes the cost estimate by an order of magnitude.
|
||
|
||
### Trade routes
|
||
|
||
* The gate is the tech **`CCC_FTLEcon`** (id 10026) — it sets `ServerPlayer+0xff` (`CnTrd`)
|
||
unless the player is `RebAI` (`ghidra/addresses.json:1510`, `tech-effects.md:64`).
|
||
* Cost: `CCC_ROOT → CCC_FTLBrdB` (RP 4,000) `→ CCC_FTLEcon` (RP 18,000). **Zuul cannot
|
||
research it** (`Zuul:0` in the availability line) — so the workload must not be Zuul.
|
||
* `StrategyServer_RegisterTradeSystems 0x007adc80` registers, for every owned system whose
|
||
owner has `CnTrd`, `ServerSystem::NumTradeRoutesSupported 0x00819d20` route slots, and
|
||
that function is `ceil(civ/REQ_CIV) + ceil(imp/REQ_IMP)` with a **floor of 1**.
|
||
* **Therefore the population thresholds in `StrategyVars.txt`
|
||
(`TRADE_ROUTE_REQ_CIVPOPULATION 200000000`, `TRADE_ROUTE_REQ_IMPPOPULATION 500000000`)
|
||
are not a hard gate — they scale the count, and the minimum is 1 per owned system.**
|
||
* `TRADE_SECTOR_SIZE 10.0` — routes live inside a trade sector, so the endpoints have to be
|
||
near each other.
|
||
* A **trade station** (`DNStationTrade`, station type 4) adds `STATION_TRADE_NUMROUTES 2`
|
||
and `STATION_BONUS_TRADE_INCOME 0.25`. It is a *bonus*, not a prerequisite.
|
||
|
||
> **Prediction T1.** The premise carried in `tail-probes.md` §3.2 and repeated in the L3
|
||
> brief — "trade income needs trade-station construction plus its tech" — is **wrong about
|
||
> the station**. `CCC_FTLEcon` plus two owned systems inside one trade sector is sufficient
|
||
> to make the trade-route vector non-empty. No trade station is needed.
|
||
>
|
||
> *Falsifier:* a game with `CCC_FTLEcon` researched, ≥2 owned systems within 10 units, and
|
||
> `trade routes … count=0` still logged after several End Turns.
|
||
|
||
> **Prediction T2.** The vector fills during turn processing, not at the moment the tech
|
||
> completes, so the first non-zero count appears on the End Turn **after** the one that
|
||
> finished `CCC_FTLEcon`.
|
||
|
||
### Spy programs
|
||
|
||
Nobody had a click path. The static reading gives one.
|
||
|
||
* `Species/<race>/sections/_Spy.shipsection` — `entity_class "SpyShip"`, `section_type
|
||
mission`, `nodesign 1`, `spy 1`, `cost 20000`, and
|
||
`requires CCC_SpyBm` + `requires IND_SlvgTech`.
|
||
* The **spy tender** — the "ship a spy can dock with" in `Can't build spy: Fleet does not
|
||
have a ship spy can dock with.` — is the section carrying `spytender`. In this image that
|
||
is exactly one section per race: **`CRRepairandSalvage`** (Cruiser Repair & Salvage
|
||
mission section, `requires IND_SlvgTech`, cost 60,000). Nothing in the UI says "tender".
|
||
* Tech cost: `IND_OrbFound → IND_CruisCon` (16,000) `→ IND_SlvgTech` (12,000) for the
|
||
tender and half the spy; `CCC_ROOT → CCC_FTLBrdB` (4,000) `→ CCC_SpyBm` (12,000) for the
|
||
other half. `CCC_FTLBrdB` is shared with the trade chain.
|
||
* The action path is the **`Special` menu on the fleet panel** (`(229,464)` at 1024×768):
|
||
`SHIPACTION_BUILDSPY` / `DEPLOYSPY` / `PICKUPSPY`, backed by `Game::BuildSpyDialog`,
|
||
`Game::DeploySpyDialog`, `Game::PickupSpyDialog` and the `Game::CanBuildSpy` functor.
|
||
* The engine's own refusal strings enumerate the preconditions exactly:
|
||
build — own system, player has ability, fleet has a tender, tender not already carrying;
|
||
deploy — **not your own system**, no enemy fleet present, and
|
||
`Can't deploy spy: Spy requires an asteroid belt to hide.`
|
||
(`SPYSHIP_NEARBY_ASTEROID_RANGE 500.0`; `STARMAP_ASTEROIDBELT_ODDS 25` %).
|
||
|
||
> **Prediction S1.** `ServerSpyManager`'s vector is the spy-**craft** list, and it grows at
|
||
> **Build Spy**, not at Deploy — the `Game::SpyCraft` record has `atto` (attached-to) and
|
||
> `tdep` (turn deployed) fields, which only make sense if the object exists while still
|
||
> docked. If S1 holds, the spy half of this workload needs **no second empire at all**.
|
||
>
|
||
> *Falsifier:* Build Spy succeeds (the fleet shows a docked spy) and the next End Turn still
|
||
> logs `spy programs … count=0`. That would move the growth point to Deploy and make an
|
||
> enemy colony with an asteroid belt mandatory.
|
||
|
||
> **Prediction S2.** The trapped write to the spy vector's `_Mylast` will have an `eip`
|
||
> inside `ServerSpyManager` and an `ebpret` inside the turn-command applier, not inside a UI
|
||
> class — because the debug registers are armed on the **turn thread only**
|
||
> (`ArmCurrentThread(GetCurrentThread())`), and SOTS is lockstep: the UI issues a command
|
||
> and the server applies it. If the write instead happens on the UI thread, **no trap will
|
||
> be taken at all** and the count line is the only evidence. This is the rule-20 trap in
|
||
> this instrument: a zero trap count with a rising count line means *wrong thread*, not
|
||
> *no write*.
|
||
|
||
### Getting there without twenty turns of play
|
||
|
||
The `Custom` game-setup screen (`GAMESETUPFE_BTN_CUSTOM`) exposes
|
||
`GAMESETUP_INITCOLONIES`, `GAMESETUP_INITSAVINGS`, `GAMESETUP_INITTECHS`,
|
||
`GAMESETUP_MONEY_RESEARCHMOD`, `GAMESETUP_MONEY_INCOMEMOD`, `GAMESETUP_SYSTEM_DISTANCE`,
|
||
`GAMESETUP_SYSTEM_SIZE`, `GAMESETUP_SYSTEM_SUITABILITY`. All of it is **stock**: no data
|
||
file is modified, no save is edited, so the resulting save is a legitimate corpus member.
|
||
|
||
> **Prediction W1.** With initial colonies, initial savings, initial techs and the research
|
||
> modifier at maximum on a small close-packed map, both tech chains are reachable inside
|
||
> ~10 End Turns rather than the "tens of 30–60 s End Turns" the brief budgets.
|
||
|
||
---
|
||
|
||
## Instrument predictions
|
||
|
||
> **Prediction I1 (control, rule 19).** `watch=on watch.mode=cont` is byte-neutral: one End
|
||
> Turn from `ref-turn2.sav` reproduces the determinism oracle
|
||
> — `(Autosave EndTurn).sav` `bb4fd9ac89f41e3b…`, `(Autosave).sav` `978041acd168b56e…` —
|
||
> exactly as `watch=off` does. The two configs differ in one key.
|
||
|
||
> **Prediction I2.** On `ref-turn2.sav` the new mode reproduces lane W3's numbers:
|
||
> both managers non-null, all six pointers zero, both counts 0, zero traps. If it does not,
|
||
> the mode is wrong and nothing measured with it later counts.
|
||
|
||
> **Prediction I3.** The canary self-test passes on **every** turn, not just the first —
|
||
> the delta form of the counter is new in this mode and is itself a thing that can be wrong.
|
||
|
||
---
|
||
|
||
## What would make this lane's result worthless
|
||
|
||
* Counting a container from the wrong base. `S` is the `ApplyAllTurnCommands` `this`; every
|
||
`StrategyServer_off_*` is in the **`S+4`** frame (`lane-t.json:172`). A wrong base yields
|
||
a plausible-looking zero.
|
||
* Reporting "0 traps" when the writer ran on another thread (S2).
|
||
* Producing a save whose non-empty container came from a modified data file. Nothing in this
|
||
lane modifies `sots.gob` or any loose data file; if that ever changes it must be recorded
|
||
in the save's provenance line.
|