From 6699f543bca6bcc90bf296b5e44a73ae3ae4f99f Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 19:10:10 -0400 Subject: [PATCH] watch.mode=cont: both containers, both ends, re-armed every turn; and hooks=off no longer disarms the watchpoints 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) Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ --- docs/L3-predictions.md | 134 +++++++++++++++++++++++++++++++++ docs/W3-watchpoints.md | 29 +++++++ src/shim/hooks/watchpoints.cpp | 100 +++++++++++++++++++----- src/shim/main.cpp | 19 ++++- src/shim/shim.cfg.l3cont | 13 ++++ src/shim/shim.cfg.l3control | 13 ++++ src/shim/shim.cfg.l3probe | 40 ++++++++++ 7 files changed, 328 insertions(+), 20 deletions(-) create mode 100644 docs/L3-predictions.md create mode 100644 src/shim/shim.cfg.l3cont create mode 100644 src/shim/shim.cfg.l3control create mode 100644 src/shim/shim.cfg.l3probe diff --git a/docs/L3-predictions.md b/docs/L3-predictions.md new file mode 100644 index 0000000..d30dcc4 --- /dev/null +++ b/docs/L3-predictions.md @@ -0,0 +1,134 @@ +# 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//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. diff --git a/docs/W3-watchpoints.md b/docs/W3-watchpoints.md index 509baf7..d257408 100644 --- a/docs/W3-watchpoints.md +++ b/docs/W3-watchpoints.md @@ -68,3 +68,32 @@ The second run (turn-1 workload, `watch.mode=modcount`) has **no oracle of its o everything else — the canary, the VEH, the flusher, the hit format — is unchanged. Keep the canary self-test and keep the `ref-turn2` oracle control: together they cost about two minutes and they are what makes the numbers evidence rather than output. + +## Mode `cont` (lane L3) + +A third `watch.mode`, added when lane L3 needed to watch the trade-route and spy-program +containers across a whole game played forward rather than one turn of one save. + +* All four debug slots go on the two containers: trade `_Myfirst` (`mgr+0x3c`), trade `_Mylast` + (`mgr+0x40`), spy `_Myfirst` (`mgr+0x10`), spy `_Mylast` (`mgr+0x14`). `_Myfirst` is armed as + well as `_Mylast` because these vectors are default-constructed with all three pointers zero: + the first element writes all three, and watching only `_Mylast` cannot tell "allocated for the + first time" from "appended to an existing buffer". Those are different events in the model the + lane was trying to falsify. +* It **re-arms and re-logs on every End Turn**. `modcount` and `tshn` arm once on purpose - their + targets are chosen from one turn's state and re-picking them would move the measurement. `cont` + has no such target selection, the two manager pointers are stable for the life of the server, + and the per-turn count line is the signal. +* The canary self-test therefore runs every turn too, and its counter is read as a **delta**. On + the arm-once modes the delta is the old value, so their log lines are unchanged. + +`ReportContainer` is factored out of `ArmTshnSlots` so both modes emit a byte-identical container +line; lane W3's published counts are the control every later count is compared against. + +**`hooks=off watch=on` used to arm nothing.** `Shim_Init` returned before `install_watchpoints` +whenever the trace mode was `off`, so that config printed `watch=on` in the banner and then +measured a confident zero - the exact failure mode method rule 1 exists to catch. The watchpoints +are an independent instrument (one arming detour of their own, 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. `main.cpp` now initialises MinHook and installs the watchpoint module on the +`hooks=off` path when `watch=on`. diff --git a/src/shim/hooks/watchpoints.cpp b/src/shim/hooks/watchpoints.cpp index 9300c9e..d570c3d 100644 --- a/src/shim/hooks/watchpoints.cpp +++ b/src/shim/hooks/watchpoints.cpp @@ -20,8 +20,13 @@ int g_players = 2; char g_outPath[MAX_PATH] = {}; // Which four addresses the single arming point computes. `modcount` is lane W2's set and is the -// default so its run stays reproducible byte for byte; `tshn` is lane W3's. -enum class Mode { ModCount, Tshn }; +// default so its run stays reproducible byte for byte; `tshn` is lane W3's; `cont` is lane L3's. +// +// `cont` differs from the other two in one further respect: it re-arms and re-logs on EVERY End +// Turn rather than only the first. The other two modes measure one turn of one save, where arming +// once is right. L3's workload is a game played forward across many turns and the question is +// "did the container grow on turn N", which needs a line per turn. +enum class Mode { ModCount, Tshn, Cont }; Mode g_mode = Mode::ModCount; // ---- what is being watched ------------------------------------------------------------------ @@ -284,6 +289,62 @@ constexpr std::uint32_t kTradeMgrOffVec = 0x3c; // _Myfirst; _Mylast at +0 constexpr std::uint32_t kServerOffSpyMgr = 0x158; // -> ServerSpyManager* constexpr std::uint32_t kSpyMgrOffVec = 0x10; // _Myfirst; _Mylast at +0x14 +// One container report line. Factored out of lane W3's arming function so lane L3's mode prints +// the identical line -- the two modes' numbers have to be comparable word for word, and W3's +// published counts are the control this campaign compares every later count against. +struct ContInfo { + std::uintptr_t mgr; // 0 when the manager pointer is null or unreadable + int count; // element count, or -1 when the offset does not name a std::vector +}; + +ContInfo ReportContainer(std::uintptr_t S, std::uint32_t mgrOff, std::uint32_t vecOff, + const char* what) { + const std::uintptr_t slot = S + 4 + mgrOff; + const std::uintptr_t mgr = Readable(slot, 4) ? U32(slot) : 0; + std::uint32_t vf = 0, vl = 0, ve = 0; + int n = -1; + if (Readable(mgr, vecOff + 12)) n = VectorCount(mgr + vecOff, &vf, &vl, &ve, 4); + LogF("watch: %s -- manager=0x%08x (S+4+0x%x) vector@mgr+0x%x first=0x%08x last=0x%08x " + "end=0x%08x count=%d", + what, static_cast(mgr), mgrOff, vecOff, vf, vl, ve, n); + return {mgr, n}; +} + +// Lane L3's four slots: both ends of both containers. +// +// `_Mylast` alone catches every push_back, which is what lane W3 armed. `_Myfirst` is armed too +// because these vectors are default-constructed -- all three pointers zero -- so the FIRST element +// writes all three, and watching only `_Mylast` cannot tell "the vector was allocated for the +// first time" from "an element was appended to an existing buffer". They are different events in +// the model this lane is trying to falsify, so they get different slots. +// +// The canary self-test then borrows slot 3 for one write and gives it back, exactly as in the +// other two modes. +void ArmContSlots(std::uintptr_t S) { + const ContInfo trade = ReportContainer(S, kServerOffTradeMgr, kTradeMgrOffVec, "trade routes"); + const ContInfo spy = ReportContainer(S, kServerOffSpyMgr, kSpyMgrOffVec, "spy programs"); + + const struct { + const ContInfo* c; + std::uint32_t vecOff; + const char* what; + } want[4] = { + {&trade, kTradeMgrOffVec, "trade routes _Myfirst"}, + {&trade, kTradeMgrOffVec + 4, "trade routes _Mylast"}, + {&spy, kSpyMgrOffVec, "spy programs _Myfirst"}, + {&spy, kSpyMgrOffVec + 4, "spy programs _Mylast"}, + }; + for (int i = 0; i < 4; ++i) { + if (want[i].c->mgr) { + g_watchAddr[i] = want[i].c->mgr + want[i].vecOff; + std::snprintf(g_slotName[i], sizeof g_slotName[i], "%s (mgr+0x%x, count %d at arm)", + want[i].what, want[i].vecOff, want[i].c->count); + } else { + std::snprintf(g_slotName[i], sizeof g_slotName[i], "%s (manager null)", want[i].what); + } + } +} + // Lane W3's four slots. Slot 0 is the whole point: the `TShn` word of an NVO record on a system // whose AFlags is zero. Slot 1 is its map's _Mysize, which is what makes slot 0's silence mean // something -- if the node were freed and reallocated, slot 0 would be watching dead memory and @@ -361,21 +422,12 @@ void ArmTshnSlots(std::uintptr_t S) { {kServerOffSpyMgr, kSpyMgrOffVec, "spy programs"}, }; for (int i = 0; i < 2; ++i) { - const std::uintptr_t slot = S + 4 + cont[i].mgrOff; - const std::uintptr_t mgr = Readable(slot, 4) ? U32(slot) : 0; - std::uint32_t vf = 0, vl = 0, ve = 0; - int n = -1; - if (Readable(mgr, cont[i].vecOff + 12)) - n = VectorCount(mgr + cont[i].vecOff, &vf, &vl, &ve, 4); - LogF("watch: %s -- manager=0x%08x (S+4+0x%x) vector@mgr+0x%x first=0x%08x last=0x%08x " - "end=0x%08x count=%d", - cont[i].what, static_cast(mgr), cont[i].mgrOff, cont[i].vecOff, vf, vl, ve, - n); - if (mgr) { - g_watchAddr[2 + i] = mgr + cont[i].vecOff + 4; + const ContInfo ci = ReportContainer(S, cont[i].mgrOff, cont[i].vecOff, cont[i].what); + if (ci.mgr) { + g_watchAddr[2 + i] = ci.mgr + cont[i].vecOff + 4; std::snprintf(g_slotName[2 + i], sizeof g_slotName[2 + i], "%s vector _Mylast (mgr+0x%x, count %d at arm)", cont[i].what, - cont[i].vecOff + 4, n); + cont[i].vecOff + 4, ci.count); } else { std::snprintf(g_slotName[2 + i], sizeof g_slotName[2 + i], "%s (manager null)", cont[i].what); @@ -401,17 +453,23 @@ extern "C" void WatchApplyAllDetour(); extern "C" void WatchOnApplyAll(void* self) { InterlockedIncrement(&g_mark); - if (g_armed) { + // modcount/tshn arm once and never again -- their targets are chosen from one turn's state and + // re-picking them would move the measurement. `cont` re-arms every turn: the two manager + // pointers are stable for the life of the server, and the count line is the per-turn signal. + if (g_armed && g_mode != Mode::Cont) { watch_flush(g_log); return; } + if (g_armed) watch_flush(g_log); const std::uintptr_t S = reinterpret_cast(self); g_watchAddr[0] = 0; g_watchAddr[1] = 0; g_watchAddr[2] = 0; g_watchAddr[3] = 0; - if (g_mode == Mode::Tshn) { + if (g_mode == Mode::Cont) { + ArmContSlots(S); + } else if (g_mode == Mode::Tshn) { ArmTshnSlots(S); } else { @@ -449,6 +507,9 @@ extern "C" void WatchOnApplyAll(void* self) { const std::uintptr_t saved3 = g_watchAddr[3]; g_watchAddr[3] = reinterpret_cast(const_cast(&g_canary)); std::uint32_t dr7 = 0; + // The counter is cumulative and `cont` mode runs this test once per End Turn, so take the + // delta. On the arm-once modes `before` is 0 and the printed value is unchanged. + const LONG canaryBefore = g_canaryHits; g_canaryArmed = true; if (!ArmCurrentThread(g_watchAddr, 4, &dr7)) { LogF("watch: ArmCurrentThread FAILED (err %lu) -- NOTHING IS ARMED", GetLastError()); @@ -457,7 +518,7 @@ extern "C" void WatchOnApplyAll(void* self) { return; } g_canary = 0x5a5a5a5a; - const LONG canaryHits = g_canaryHits; + const LONG canaryHits = g_canaryHits - canaryBefore; g_canaryArmed = false; LogF("watch: SELFTEST canary writes=1 traps=%ld dr7=0x%08x %s", canaryHits, dr7, canaryHits == 1 ? "PASS" : "FAIL -- every count below is unmeasured, not zero"); @@ -501,7 +562,8 @@ bool watch_apply_config(const char* key, const char* value, std::string* err) { if (std::strcmp(key, "watch.mode") == 0) { if (std::strcmp(value, "modcount") == 0) g_mode = Mode::ModCount; else if (std::strcmp(value, "tshn") == 0) g_mode = Mode::Tshn; - else if (err) *err = "expected modcount|tshn"; + else if (std::strcmp(value, "cont") == 0) g_mode = Mode::Cont; + else if (err) *err = "expected modcount|tshn|cont"; return true; } if (std::strcmp(key, "watch.out") == 0) { diff --git a/src/shim/main.cpp b/src/shim/main.cpp index be0ac29..3d838da 100644 --- a/src/shim/main.cpp +++ b/src/shim/main.cpp @@ -326,7 +326,24 @@ void Shim_Init(HMODULE self) { const Config cfg = ReadConfig(); if (!cfg.hooks) { - Log("hook: disabled by config"); + // `hooks=off` turns off the TRACE hooks. It must not turn off the watchpoint module. + // Those are two independent instruments: the watchpoints install one arming detour of + // their own and write no trace records, and lane L3 wears them for a whole game played + // forward, where the template hooks' 30-45 s per End Turn is pure cost and measures + // nothing being asked about. Before this, `hooks=off watch=on` silently armed nothing -- + // a config that reports "watch=on" in the banner and then measures a confident zero, + // which is exactly the failure method rule 1 exists to catch. + if (shim::hooks::watch_enabled()) { + MH_STATUS st = MH_Initialize(); + Log("hook: trace hooks disabled by config; MH_Initialize (watch only) -> %s", + MH_StatusToString(st)); + if (st == MH_OK) { + g_minhookUp = true; + shim::hooks::install_watchpoints(exeBase, g_dir, &ShimLogLine); + } + } else { + Log("hook: disabled by config"); + } return; } diff --git a/src/shim/shim.cfg.l3cont b/src/shim/shim.cfg.l3cont new file mode 100644 index 0000000..1acd182 --- /dev/null +++ b/src/shim/shim.cfg.l3cont @@ -0,0 +1,13 @@ +# Lane L3: the watchpoint module in `cont` mode -- all four debug slots on the two ends of the +# trade-route and spy-program vectors, re-armed and re-counted on EVERY End Turn. +# +# `hooks=off` on purpose. This config is worn for a whole game played forward, not for one turn of +# one save: the template hooks cost 30-45 s per End Turn and measure nothing this lane is asking +# about. The watchpoint module installs independently of the trace mode. +# +# Identical to shim.cfg.l3control except for the single key `watch=`, so the pair is a real +# rule-19 control. +hooks=off +watch=on +watch.mode=cont +watch.out=C:\SOTS\shim.watch.txt diff --git a/src/shim/shim.cfg.l3control b/src/shim/shim.cfg.l3control new file mode 100644 index 0000000..85bab6b --- /dev/null +++ b/src/shim/shim.cfg.l3control @@ -0,0 +1,13 @@ +# Lane L3 CONTROL: the same build as shim.cfg.l3cont with the watchpoints disarmed. The two files +# differ in exactly one key (`watch=`), which is what makes the pair a rule-19 control. +# +# `hooks=off` on purpose. This config is worn for a whole game played forward, not for one turn of +# one save: the template hooks cost 30-45 s per End Turn and measure nothing this lane is asking +# about. The watchpoint module installs independently of the trace mode. +# +# Identical to shim.cfg.l3control except for the single key `watch=`, so the pair is a real +# rule-19 control. +hooks=off +watch=off +watch.mode=cont +watch.out=C:\SOTS\shim.watch.txt diff --git a/src/shim/shim.cfg.l3probe b/src/shim/shim.cfg.l3probe new file mode 100644 index 0000000..7e727d7 --- /dev/null +++ b/src/shim/shim.cfg.l3probe @@ -0,0 +1,40 @@ +# Lane L3: lane H's shim.cfg.hp11 verbatim -- the same eleven register-transparent entry probes and +# the same six-hook RNG ledger -- plus the watchpoint module in cont mode. The point is to run the +# IDENTICAL instrument lane H ran on an empty-container turn against a turn whose trade-route +# vector is NON-empty, so the two entry counts are comparable line for line. +hook.Shim::SelfTest::Fill=off +hook.Mars::GlobalConsts::LoadFile=off +hook.Game::WeaponDictionary::Init=off +hook.Game::SectionDictionary::SectionDictionary=off +hook.Game::ServerPlayer::ComputeBudget=off +hook.Game::TechTree::ProcessResearch=off +hook.Game::ServerPlayer::OnTechResearched=off +hook.Game::ServerSystem::ProcessTurn=off +hook.Game::ServerPlayer::ProcessTurn=off +hook.Game::ServerSystem::GroupOutput=off +hook.Game::ServerSystem::ComputeTotalOutput=off +hook.Game::StrategyServer::MoveFleet=off +hook.Game::StrategyServer::ProcessFleetMovement=off + +hook.Game::StrategyHost::Autosave=trace +hook.Game::StrategyServer::ProcessTurn=trace +hook.Game::StrategyServer::OnAllCombatDone_Tail=trace +hook.Game::StrategyServer::ApplyEncounterResult=trace +hook.Game::StrategyServer::NodeLineDecay=trace +hook.Game::StrategyServer::ProcessNodeSpaceTravel=trace +hook.Game::EncounterDetect::AssignContacts=trace +hook.Game::EncounterDetect::ProcessTeamRecord=trace + +fpu.sample_turn=off +fpu.sample_ticks=off + +# The generator is 0x9cc bytes and every record carries it twice. Inlining it as hex would make +# each record ~5 kB of state nobody reads. +trace.inline_max=64 +trace.path=C:\SOTS\shim.trace.jsonl +trace.flush=always +probes=11 + +watch=on +watch.mode=cont +watch.out=C:\SOTS\shim.watch.txt