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
7.8 KiB
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:
- it arms all four debug slots on the two containers —
_Myfirstand_Mylastof each — so an allocation (_Myfirstmoves from 0) and apush_back(_Mylastmoves) are separable; - 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 setsServerPlayer+0xff(CnTrd) unless the player isRebAI(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:0in the availability line) — so the workload must not be Zuul. StrategyServer_RegisterTradeSystems 0x007adc80registers, for every owned system whose owner hasCnTrd,ServerSystem::NumTradeRoutesSupported 0x00819d20route slots, and that function isceil(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) addsSTATION_TRADE_NUMROUTES 2andSTATION_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_FTLEconplus 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_FTLEconresearched, ≥2 owned systems within 10 units, andtrade routes … count=0still 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, andrequires 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 carryingspytender. 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_FTLBrdBis shared with the trade chain. - The action path is the
Specialmenu on the fleet panel ((229,464)at 1024×768):SHIPACTION_BUILDSPY/DEPLOYSPY/PICKUPSPY, backed byGame::BuildSpyDialog,Game::DeploySpyDialog,Game::PickupSpyDialogand theGame::CanBuildSpyfunctor. - 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 — theGame::SpyCraftrecord hasatto(attached-to) andtdep(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
_Mylastwill have aneipinsideServerSpyManagerand anebpretinside 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=contis byte-neutral: one End Turn fromref-turn2.savreproduces the determinism oracle —(Autosave EndTurn).savbb4fd9ac89f41e3b…,(Autosave).sav978041acd168b56e…— exactly aswatch=offdoes. The two configs differ in one key.
Prediction I2. On
ref-turn2.savthe 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.
Sis theApplyAllTurnCommandsthis; everyStrategyServer_off_*is in theS+4frame (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.gobor any loose data file; if that ever changes it must be recorded in the save's provenance line.