Commit graph

279 commits

Author SHA1 Message Date
alex
954f3cec63 CB: the turn-command stream, captured and bound to the autosave it produced
Canonical pair turn2->turn3: the complete block set, the three heap payloads no
previous capture could read (route [272], list-10 [1728], the 24-byte Population
body), the three AI client seeds, and both output autosaves -- byte-identical to the
published oracle AND to this lane's own hooks=off control, so the stream and the save
come from the same run and the instrument did not change the turn it recorded.

Creation turn turn1->turn2: three runs. Pinning the AI client seeds to the values an
earlier run observed made a DIFFERENT process reproduce that run's block -- including
the research pick that varies -- and its autosave byte for byte. The workload three
lanes could not reproduce is reproducible given the seeds.

Two corrections to lane L4's list-23 reading (no trailing int; the body is not
turn-dependent) and one to my own list-5 record, the latter found by lane RB while
consuming this capture.

Format: JSON (raw words are ground truth, decoded is a typing) plus lane RB's own .tcb
grammar with the heap payloads filled in, so RB's reader consumes it unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 19:18:57 -04:00
alex
f0aee296fe board: ratchet hit on L3's trade-route saves - new unmodelled content, not a regression 2026-09-08 19:12:54 -04:00
alex
f18afadf57 board: lane RB - ModCount reaches 24 with zero residual; matched triple; rates memory-vs-wire order defect 2026-09-08 19:09:00 -04:00
alex
ce8b3e66b4 RB: lane CB's real capture replays to the same result, and brings the seeds and the heap payloads
CB's VM146 capture goes through the JSON->tcb adapter and the replayer unchanged and reproduces
this lane's canonical result from a separate run on a different guest: 108 -> 62, closed 46,
regressed 0, ModCount 24. It carries the first AI seeds any capture has held (32/496/512), and
its deep dump closes two gap rows: list 8's route is [272] and list 10's vector is [1728], a
ship the input save already contains.
2026-09-08 19:07:13 -04:00
alex
7a41246450 RB: bind a .tcb capture to the save it was taken on
Replaying a turn's commands against a different board charges the counter happily and produces
a confidently wrong number, because the blocks name player ids that exist in both. The converter
now records the input save and sots_turn warns when they disagree.
2026-09-08 19:05:33 -04:00
alex
a4a1d373f1 RB: replay a recorded turn's commands -- ModCount is reachable, and the rates frame's memory order is not its wire order
sots_turn --turn-commands puts /Sim/ModCount on the original's 24 with zero residual, closing
the one leaf that has been unreachable from a save all campaign. Canonical pair 108 -> 62,
closed 46, regressed 0, fresh build directory.

The .tcb capture format (line-oriented, parser-free, '?' for a field the instrument could not
read, per-client AI seeds), a converter from lane L4's shim dump, and an adapter from lane CB's
JSON capture -- CB's stays the capture of record, .tcb stays the engine's input, and the two
paths produce byte-identical replays.

A falsified prediction paid for itself: the first run regressed two leaves because list 5's
element is decoded in MEMORY order, and the memory order of the rates frame is NOT its wire
order. Memory member 1 is wire member SRsc; six members unread. Lane CB's decoder has the same
defect and should drop its list-5 record.

Two new addresses (the second and third gate-loop heads) in ghidra/addresses.d/lane-rb.json.
2026-09-08 19:01:35 -04:00
alex
af67d31394 resolution: byte-match survives as C-exact/C-set; tie set is a draw support not equal cost; rule 26 on self-consistent controls 2026-09-08 18:38:37 -04:00
alex
9d722815a5 rule 25: commit with a pathspec - the shared index is why staging by path was not enough 2026-09-08 18:37:07 -04:00
alex
ad86555ff2 board: combat multiplayer verified in lockstep; 3370 never binds; lab hazards; fourth commit sweep 2026-09-08 18:33:05 -04:00
lane-l4
f761fb1e1c regenerate the tracked header after lane-l4.json (rule 14: regenerated, never hand-resolved; verified identical to a fresh run of gen_addresses.py) 2026-09-08 18:31:08 -04:00
alex
4ef630b266 L2: record the pgrep -f tap141i0 hazard - it kills the guest, not just tcpdump
QEMU's command line contains ifname=tap141i0, so pgrep -f on the tap name matches
the VM alongside the capture. Stopping the tcpdump that way SIGTERM'd VM141.
Nothing was lost (all artefacts were already off the guest and it restarted with
a clean volume) but it is a plug-pull on an exclusively-held guest. Use
pgrep -x tcpdump.
2026-09-08 18:31:00 -04:00
lane-l4
0c162e5831 L4: the research tie set -- k = 6, named, and the mechanism measured twice
Two processes, same workload. Phase 18 tries three producers; only ONE of the
three AI players reaches the candidate walk at all, which is why the other two
are stable across every run of both lanes -- different code path, not better luck.

For that player the candidate stream is ONE entry, {2, 12}, in BOTH runs -- a
category, not a tech. So arrival order in the candidate vector is not the
mechanism (a vector of one has no order), and the three-arm fallback never ran
(both probes zero, both runs). The variation is inside the resolver that turns a
category into a tech, and it produced XNC_TrnsMorr2 (techId 288) in one process
and XNC_TrnsHum2 (techId 282) in the next.

k is nameable from the shipped tech data: XNC_ROOT allows six tier-1 techs at an
identical 2000 RP, one per species, each allowing exactly one tier-2 successor --
the six XNC_Trns<Species>2. Four of the six have been observed across six runs
between lanes L4 and L5, and their costs differ (13000-30000), so the resolver is
not ranking by cost; it takes whichever member of the available set it reaches
first.

Bonus rule-19 result: run R2's autosave is byte-identical to the hooks=off control
of the same workload. Twenty detours installed, same tiebreak, same bytes -- the
only free variable in the whole turn is the tiebreak itself.

Still open and flagged: BIO_GnMod, the one observed value outside that family.
Seven more addresses in ghidra/addresses.d/lane-l4.json; the eighth collided with
lane AI4's cl_SetResearchTarget and was dropped with the agreement recorded.
2026-09-08 18:30:05 -04:00
alex
79245837be board: lane L1 - hive creation draws in BeginProcessTurn; CDiff edges played forward; SnLv verified; VM140 free 2026-09-08 18:26:51 -04:00
alex
940aecad0e board: Rung B lanes - RB replayer paired with CB capture on VM146 2026-09-08 18:25:32 -04:00
alex
4f805e381a L1: CDiff crosses at frame 50, and the AI client seed is fresh in every process
Two results from the same VM session, both of which needed a workload the
corpus cannot supply.

CDiff, played forward. ref-turn2 loaded and 49 End Turns driven through the UI
helper to frame 51, with the entry hook emitting one record per turn:

  50 calls, frames 2..51, EXACTLY TWO STORES
    frame  2  -1 -> 0
    frame 50   0 -> 1     <- the modelled tier transition, at the modelled frame
  and 47 turns between them on which the writer ran and wrote NOTHING

predict_path was computed at entry from the transcribed threshold table on all
fifty turns and agreed with the cdiff region on every one. Frame >= 100 is still
a code read and is not claimed as a measurement.

The AI seed probe (asked for by the coordinator, ranked above CDiff). Hooks on
Mars::RNG::Seed and StrategyApp::RunAI, two launches from turn1-state, load
only. Every AI client seed differs between processes -- net 32, 496 and 512 all
move -- while the record structure is identical and one Seed call with seed=0
produces a byte-identical state in both runs.

So the turn1-state -> turn2 nondeterminism is a SEED effect, not the ordering
effect predicted, and the 'one of three varies' observation is explained by two
of the three empires having a research pick that is robust to the stream. This
falsifies lane AI1's 'every draw from the static generator returns 0', which
that lane had explicitly flagged as arithmetic rather than measurement.

Where the seed comes from is NOT established; the finding names the one hook
that would settle it and the six values it must reproduce.

VM140 left as found: the 8-file save set with its oracle bytes intact.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 18:23:19 -04:00
alex
d1710e9676 standalone on the canonical pair turn2->turn3: 108 -> 63, closed 45, regressed 0 2026-09-08 18:06:30 -04:00
alex
62b738d27a Rung C sharpened to the tie-set claim; re-resolver agent added to the crew 2026-09-08 18:05:01 -04:00
lane-l4
1824dae2f5 L4: credit lane L5 as owner of the turn-1 nondeterminism; add the command-block evidence that localises it to the client 2026-09-08 18:03:28 -04:00
alex
b3299979a7 determinism qualified: oracle holds only where no AI must choose; Rung B = command-stream replay, Rung C = behavioural AI; canonical pair now turn2->turn3 2026-09-08 18:02:58 -04:00
lane-l4
7e6bda3699 L4: the AI's command block, read out of the running game
findings/subsystems/ai-order-capture.md -- two workloads on VM145, every submitted
TurnCommands block dumped at the batch applier, plus sixteen pass-attributed entry
probes on the task bodies and the emission gates.

Headline: our emission model reproduces both real blocks exactly, list for list and
element for element, and both turns land on the measured ModCount of 12. What it did
not have: a list-23 element on every AI turn (the first element ever observed in the
free half of the cost table, and it really is free), client-allocated ids travelling
inside the commands (design 18, fleet 34 -- neither in the input save), and build,
rates and population all naming the same home system.

Two predictions falsified, both usefully: the batch is n = playerCount = 8 with the
four monster factions holding untouched slots, not n = 4; and the fleet order names
the fleet the client has ALREADY created, not the one in the save.

AITRaid's pass-0 question stays open and now says why -- the task never ran on either
board, so the list-16 zero is 'never entered', not 'entered and silent'.

Rule 19: the ref-turn2 control passed byte-identical to the published oracle with all
seventeen detours installed. Separately, the turn-1 workload is NOT reproducible --
three runs, three autosaves, differing in exactly one field: the research target of
the AI player that owns nothing.

ghidra/addresses.d/lane-l4.json: 9 entries (8 IAITask::Execute bodies + the list-16
order method). Raw logs and the two divergent autosaves in verify/results/shim/aiorders/.
2026-09-08 18:01:45 -04:00
alex
a3e6e1d415 L1: hive creation draws inside BeginProcessTurn -- lane Z's zero was the workload
Lane SV recovered the script-object subsystem statically and predicted that
SVSOSwarmQueen::RegisterHives takes one strategic-generator word per new hive
inside StrategyServer::BeginProcessTurn, which runs inside lane Z's autosave
bracket and outside both turn drivers. Nothing had ever been hooked in that
interval. Measured on VM140, and it is right.

  turn 1 -> 2 (hives created)   BeginProcessTurn 2   ProcessTurn 20   residual 2
  turn 2 -> 3 (hives exist)     BeginProcessTurn 0   ProcessTurn 19   residual 0

Two instruments that share no code path agree: the region ledger reads
170 -> 172 across RegisterHives, and the return-address draw-site table gains a
row at 0x00527714 (NextInt, 2 calls / 2 words) which is absent on the next turn.
The draws produce NextQ 30/28, TickHives' slip takes them to 31/29 -- exactly
turn2-state.sav -- and the next turn's slip gives 32/30, exactly turn3-state.sav.

So tail-rng-ledger.md's "all of it inside ProcessTurn, residual exactly zero" is
correct for every turn it measured and false as a statement about the code. The
interval a standalone must reproduce starts at BeginProcessTurn.

Also closed or corrected:
* LO/HI read live rather than fitted: NextQ = frame + 20 + NextInt(10),
  inclusive; the TickHives gates are 10 (a frame floor), 5 and 3, so no queen can
  spawn before frame 11 on any save.
* CDiff's threshold scan re-read independently: the tier can only ever be 0 or 1,
  and "entered and stored nothing" was observed live and distinguished from
  "did not run" (method rule 20).
* SnLv measured for the first time: Spica reads AFlags 0 with SnLv 0x200, so the
  sensor branch is what refreshes it -- nvo-tshn-visible-owner.md §6 goes [H] to
  [V]. ComputeContactLevel's documented "else 1" names one of four return tails;
  the observed non-visible level is 2.
* Rule 19: the oracle reproduced byte for byte with all five new detours live,
  and again with four watchpoints armed on top.
* turn1-state -> turn2 is not deterministic (lane L5 owns this). Two more runs
  here make it four distinct outcomes over six runs, including two DIFFERENT
  hooks=off results, and the strategic generator is provably not what varies.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 17:59:32 -04:00
alex
e4195d47d1 board: lane L5 - reference pair non-deterministic; interest literals verified with a failing control; verified column held at 0 on principle 2026-09-08 17:55:47 -04:00
alex
ea7881acb3 L5: the money chain's float widths, measured live at a boundary
Four float constants in ComputeBudget have a width a C++ port gets wrong by
default, and the corpus exercises the boundary of exactly one. That one is now
measured against the running game, with a control build that must fail and does.

  savings interest   (double)0.01f   boundary: treasury a multiple of 100   IN CORPUS
  debt interest      (double)0.15f   boundary: a negative treasury          not in corpus
  research yield     (double)0.85f   boundary: research money mult of 40k   not in corpus
  the three ResMod   summed in f32   boundary: two of three non-zero        not in corpus

Three constants sitting beside those, in the same expressions, are EXACT doubles
and must not be "corrected" -- so "widen every literal" is its own defect and the
only safe procedure is to read each operand. Same pattern elsewhere in the chain:
ComputeOutputFromRates multiplies by an exact 1.5 then a widened 1.2f two
instructions later, and NormaliseOutputRates holds 1e-4 at BOTH widths.

Live result (VM146, turn1-state.sav, three runs): the game pays 499 interest on a
treasury of 50,000 and 380 on 38,100; the exact decimals pay 500 and 381. Every
divergence in the control build lands on a multiple of 100 and nothing else
diverges at all. Coverage stated as distinct states, not calls: 5 distinct
treasuries, 2 on the boundary -- against the earlier green run's 20 distinct
states, none on a boundary.

BANKRUPTCY_PROTECTION_LIMIT_FACTOR read live: 3.29999995 = (float)3.3. Its file
image is zero because the loader fills it at run time, so lane PL-3 could read the
width and had to assume the value. The assumption was right and is now measured.

Falsified: the difficulty-mods record does not sit inline at ServerPlayer+0x36c;
that field is a heap pointer on all eight players. Recorded as a hypothesis with
the measurement, not as a fact.

Second finding, from the rule-19 control: turn1-state -> turn2 is NOT a
deterministic pair. Three runs gave three post-turn autosaves differing in exactly
four leaves -- one Singularity shadow empire's research pick and the derived
checksum. Two of the three runs carried identical hooks, and the un-instrumented
run was a third value, so this is the game and not the instrument. The
determinism oracle stands for ref-turn2 -> turn3 and does not generalise to its
neighbour; no lane should use this pair as a byte-match oracle.
2026-09-08 17:49:35 -04:00
alex
2d6184888f standalone 209 -> 124 (closed 85); rule 24: never reuse a build directory across trees 2026-09-08 17:01:57 -04:00
alex
9075f60f7b gen_addresses: detect same-address-different-name; 13 vocabulary forks resolved across 8 fragments 2026-09-08 16:54:05 -04:00
alex
962cdb6981 tools: live screen wall + contact sheet for the five lab guests
The lab went from one Windows guest to five and there was no way to see what
they were all doing without issuing a QEMU screendump per guest by hand.

- tools/vmwatch.py: always-on HTTP service serving an auto-refreshing wall of
  live guest screens. Runs on spicy as vmwatch.service; browse it at
  http://192.168.3.201:8140/. Click a tile for that guest full size. Guests are
  discovered from /etc/pve/qemu-server by matching sots-re, so clones appear and
  vanish on their own. A stopped, paused or unreachable guest gets a labelled
  placeholder tile carrying the monitor's own error, never a broken image or a
  500. Python 3 stdlib only.
- tools/vmwatch-install.sh: install/update/uninstall the unit on the host.
- tools/vmshot.py: one-shot contact sheet, and --one <id> for a full-size grab.
  Pulls frames from the vmwatch service when it is up (0.5s) and falls back to
  ssh + qm monitor when it is not (4s).
- guides/lab-screen-wall.md: how to use both, and why.

Capture goes over each guest's QMP socket rather than forking qm: qm is a Perl
program, and one fork per guest per tick cost ~90% of a host core and a 728 MB
cgroup peak. Direct QMP is 0.33 CPU-seconds per 88s and 23 MB RSS. QEMU 11 here
dumps PNG natively; the fallback PPM encoder was verified pixel-identical to
QEMU's own on a real framebuffer.

Read-only throughout: screendump does not perturb the guest (method-rule 19),
so reading VM 140's screen is not an experiment and does not take its lock.
2026-09-08 16:53:04 -04:00
alex
2e935b8491 SV: what writes SvSctOb during a turn -- the script-object event bus
Lane W recovered what the type is; this is what moves it. Nothing in the turn calls a
method on a child script object directly. A driver notifies the root with an integer event
id, the root fans the delivery out to every child, and each delivery is a generic handler
plus one event-specific vtable slot chosen from a 33-entry jump table at 0x007a6480. That
table is what proves the hand-written pairs lane K read in the tail are event deliveries
and not ad-hoc calls, and five of its rows are not in slot order.

Six deliveries in a turn, from five functions, with the ids they send. Three handlers
write the eight leaves that diverged: the slavers' difficulty tier (a three-record stack
table against the frame, and at frame >= 100 the scan runs off the end and stores nothing,
so the tier can never reach 2), the refugees' one-shot latch at turn begin, and the swarm
queen's hives -- registered on the systems whose EggScio equals the SWARM's scenario tag,
which the queen's constructor stores at +0x4 while its own encounter id sits at +0x8.

The hive target turn slips forward by one every turn the spawn gates fail. That single
`inc` is the whole explanation of a field that reads 31 after turn 1 and 32 after turn 2,
which no re-roll can produce on two hives at once.

Corrects combat-done-tail.md: the tail has a FOURTH script-hook site, at 0x007d9820, and
it sends event 0x1c -- the same id ProcessTurn sends, which lane K attributed to that
driver alone.

Two leaves stay blocked, and not on a workload: the refugees' design id needs handle
allocation and data-file instantiation, and the hive target turn needs one MT draw plus
two config constants behind pointers no reference initialises in a form this lane could
follow. Fitting them from a single two-hive observation would have been fitting, not
derivation, so it was not done.

An RNG claim for the next lane to falsify cheaply: lane Z's "residual outside the two turn
drivers is exactly zero" was measured on turns where the hives already existed. Hive
creation draws inside BeginProcessTurn, outside both drivers and before either.

13 addresses in ghidra/addresses.d/lane-sv.json; validated to a scratch path, merges to
1,204 with no duplicate name. A fourteenth was dropped: this lane reached 0x004271c0
independently and would have filed it as RNG_NextIntInclusive, but addresses.json already
carries it as RNG_NextInt with the same convention and four lanes depend on that name. The
merger only detects duplicate NAMES, so a second name for one address would have merged
silently and forked the vocabulary for the campaign's most-used RNG primitive.
2026-09-08 16:46:44 -04:00
alex
d6469bd4c5 board: lane W3 - TShn closed 158/158, Status predicate settled, AI4 prediction confirmed with its falsifier 2026-09-08 16:45:56 -04:00
alex
471d6cb973 W3: NVO.TShn's writer trapped live, its gate named, and 158/158 on the corpus
TShn is refreshed to Frame for every (system, player) that satisfies ServerSystem::IsKnownTo
0x00746390 -- IsVisibleTo OR (2-bit sensor contact at ServerSystem+0x24 AND CCC_AdvSens). Not
AFlags alone, which is why lane E3 could not fit it. The writer chain was confirmed live, frame by
frame, from a hardware watchpoint on Spica's NVO node; the refresh runs twice per End Turn (driver
phase 24 and combat-done phase 25).

Also: the trade and spy containers read out of a live game for the first time (both empty, zero
traps -- the workload confirmation two lanes lacked); Player.Status's predicate named as
ReqCL != 0 && Elim == 0, with Species != 4 shown to be a corpus coincidence; and all ten command
ModCount handlers named, two of them only reachable on a turn-1 workload.

Corrects objects/layouts.md's ServerSystem PID@0x274 row and closes system-visibility-record.md 7.

Runs: build w3tshn-bb81d3d-20260908T2007Z, configs shim.cfg.w3tshn / w3mod / w3control.
Rule 19 control passed -- the armed run reproduced the determinism oracle byte for byte.
2026-09-08 16:41:09 -04:00
alex
4ce7a1e0a7 board: lane PL decomposes /Sim/players - 53 of 54 unmodelled not mismodelled; T31 self-check corrected 2026-09-08 16:40:00 -04:00
alex
a545f33cc7 board: lab expanded to five Windows guests via ZFS clones - the single-VM constraint was shaping the work 2026-09-08 16:37:08 -04:00
alex
91a04377d4 lane PL: decompose the /Sim/players residual by mechanism; the bankruptcy protection factor is a float32
findings/subsystems/players-residual.md is the deliverable: every one of the 54
leaves on the reference pair and the 24 on pair 2 attributed to a named
mechanism, with the rung split. 53 of 54 are pass-through -- the block is
unmodelled, not mismodelled, and only player 32's Sav is a number we compute and
get wrong.

Three corrections to the record:
- T31's self-check compared its post-turn result against the pre-turn stored
  value, so its 6-of-8 covered only the players whose limit never moves.
- the AI difficulty column IS recoverable from the save, by recomputing BnkEl
  under both columns against the value the save carries; 1 AI + 1 non-AI on all
  eleven corpus saves.
- BANKRUPTCY_PROTECTION_LIMIT_FACTOR is read fmul dword ptr, so it is a float32
  in the image; the decimal disagrees at every max income divisible by ten and
  the corpus' seven records land where the two agree (rule 23). formula-gaps Q1
  addendum + ghidra/addresses.d/lane-pl.json (3 data entries, fragment validated
  to a scratch path, no duplicate names).

verify/results/standalone/{status.json,report.txt} refreshed from a build of main
aabd8a3: the committed copy was stale at 131/55, the real number is 128/54.

T34 RecordObservedDesigns is now readable off the corpus and is NOT an intel
pass: a design's creator registers the design in its own odes, its weapons in
owep, and re-stamps otnL on its techs in otch -- odes on build, otch on creation.
Both are hypotheses until a save exists where a player observes someone else's
design.

Status stays open and is flagged as NOT identifiable: eighteen fields of the
player record split the roster the same way and only two saves carry a non-zero
value. The probe is an entry hook on W2's writer.
2026-09-08 16:36:45 -04:00
alex
ed73b9fe7a board: lane AI4 - ModCount decomposed with zero residual, all EIPs named; reference-game corrections 2026-09-08 16:31:02 -04:00
alex
ae47971316 AI4: what an AI turn emits, and what each command costs in ModCount
Joins the emission side (lanes AI1-AI3) to the counting side (A2, W2). Three
results.

The cost of applying a command is a property of its list, and the boundary is
sharp: lists 1-16 each advance ModCount once per element, lists 17-27 never do.
Four of the six prologue gates bump, one is free, and the sixth has no applier
anywhere in the application path -- its cost is unknown, not zero. The batch
applies the twenty-seven lists in a fixed order that is neither list nor offset
order, with the six gates split across three separate per-player loops at three
different points, and four of the sixteen bumps inlined into the batch rather
than living in a handler, which is why a call-graph sweep under-counts them.

Lane W2's four unnamed handler EIPs are named, and so are its two inlined ones,
and the ten measured command bumps then decompose with zero residual. Four of the
ten are the research-rate gate, one per submitted block, and one of those four is
the human's. Two are list 14, on a turn that moved exactly one fleet -- which
confirms lane AI2's P1 (an AI fleet order deposits two fleet-task elements where
the interface deposits one) from the counter side, at no VM cost.

The reference game is not what the record says. ref-turn2.sav IS turn2-state.sav;
there are THREE AI players, not one, and the two dormant ones do run -- all three
set a research rate and picked a research target on turn 1; and the four
monster-faction players submit no command block at all, which is the first direct
evidence that they have no client rather than an empty task list.

Prediction committed for turn1-state: the same 12, out of a different set of
commands -- 4 rate gates, 3 research targets, and three orders from the one AI
with an empire, which are predicted to be a new design, a build order and a
system-rates command, with NO fleet order on turn 1. The trap multiset it
predicts contains two EIPs W2 has never seen and omits three it did, so it is
cheap to falsify: one save swap on W2's unchanged watchpoint module.

Also: phase 2 of the AI's turn is Hiver-only (a fifth cross-check on the species
reading, and the reason one prologue gate has never been observed set), phases
29-33 are dead because the submit latches the client before it builds the send
buffer, and cl_SetResearchTarget is AI-only surface with exactly one caller.

14 addresses in ghidra/addresses.d/lane-ai4.json; validated to a scratch path,
1,138 -> 1,152, no duplicate names.
2026-09-08 16:27:58 -04:00
alex
c0c685d07f board: lane G3 growth is clamp-decided not capacity-gated; a live-verified module was wrong on widened float literals 2026-09-08 15:57:33 -04:00
alex
63347c7c17 rule 23: a live-verified module can still be wrong - thin coverage is how; float literals are widened floats 2026-09-08 15:57:09 -04:00
alex
d4f9404960 lane G3: civilian growth read and measured; out[6] is not a repair number
findings/subsystems/population-growth.md -- ServerSystem::GrowCivilianPops
0x00754220 read byte for byte, its whole chain, and Ship::RepairCost
0x00815180.

The headline is a correction to the brief's premise. The pass is NOT gated
on imperial carrying capacity. The whole system's civilian delta is clamped
to 20,000,000 -- POPTYPE[1]+0x08, an int64 literal in the executable -- and
on both reference pairs that clamp decides the value: the uncapped delta is
7.5x it and the capacity headroom 25x it. The imperial capacity is pinned
at exactly Size x 1e8 from the corpus alone, by two independent behaviours
of one colony across three turns, with no data files.

Two boundary corrections, both of the rule-17 shape. GrowCivilianPops is a
loop over group types whose back edge lies outside every decompiler `if`,
so it reads as straight-line code if you stop at the first `ret`; its real
end is 0x00754b59. MaxPopGeneric ends at 0x0074a6cd, so the 0x0074a6d0 that
lanes N and E1 both cite for the capacity-surplus pair is a different
function.

A table correction with teeth: InitPopTypeTable never writes the group
ceiling at +0x28/+0x2c. The CRT static initialiser at 0x009abe20 does, to
INT64_MAX, and nothing else touches it -- so the clamp that reads it is
always a no-op. A reader who opens only the obvious initialiser sees zero
there and would cap every carrying capacity in the game at nothing.
Lane N's whole table is otherwise reproduced independently from the
six-register fxch rotation.

output-turn-path.md: out[6] is NOT the ship-repair demand. 0x007460b0 sums
0x0081f8c0, which gates on the design's carried-population bit and computes
GroupIncome over the ship's own Population -- so it is the income of
population carried in slaver and colony hulls, a slot the engine already
had under a name nobody had connected to it. Corrected in place, with
lane-c3.json's three unread stubs superseded by lane-g3.json's read entries.

Ship::RepairCost is unexercised on the corpus and that is a measurement,
not an absence (rule 20): the independent colony keeps a ten-ship fleet in
orbit over Koa'Vo on both reference pairs and its Sav closes exactly with
the demand taken as zero, which it could not do if any hull had a cost.

Engine side: sots-engine wip/growth ed6602e -- reference pair 78 -> 81
closed, 0 regressed; pair 2 36 -> 39 closed, 0 regressed.
2026-09-08 15:53:08 -04:00
alex
3e5117bc05 board: VM140 holder = lane W3 2026-09-08 15:49:45 -04:00
alex
c810c552e5 standalone report from merged main: default 209 -> 131 closed 78; note the with-inputs 127 distinction 2026-09-08 15:49:04 -04:00
alex
1b5553b0aa board: lane EV event posting; two events.md corrections; the withheld-roster control measurement 2026-09-08 15:46:01 -04:00
alex
c224ff2214 lane EV: what a turn actually posts, and two corrections to events.md
Measured over all eleven saves with state_checksum, not derived:

  * the reference pair posts TWO events (turn1->turn2) and THREE (turn2->turn3),
    and only two players in the whole corpus ever hold an event -- the human and
    the one AI empire that owns colonies. The dormant shadow empires pick research
    targets every turn and still post nothing.
  * order within a player is readable off the ids: the build pass posts before the
    research pass. Ids are per player, so no cross-player order is observable.
  * the event type is the EvImg string and it is composed at run time --
    EVENT_ENEMY_INCOMING_Human carries a species suffix, so the type space is not
    an enumeration.

Two corrections in place (rule 11):

  * the turn PostEvent is handed is the FRAME, not ModCount. turn2-state.sav has
    Frame 2 and ModCount 12, and every event it carries is in bucket EvTurn=2. It
    is the post-increment turn: a turn run from a save at turn N posts into N+1.
  * the EVENT_NO_RESEARCH gate: 0x00584e50 is TechTree::CollectResearchedTechs,
    not a ListAvailableTechs, and the middle test is "nothing was researched on
    this turn or later" -- not "no affordable tech". zuul-turn23 exercises it: the
    human posts RESEARCH_COMPLETE on turn 22 with no no-research event that turn,
    then NO_RESEARCH again on turn 23.

ghidra/addresses.d/lane-ev.json records the gate at 0x0089162a with the argument
order re-read from the instruction stream. Validated with tools/gen_addresses.py to
a scratch path (1121 entries, no duplicate); the shared header is NOT regenerated.

tools/standalone_report.py gains --engine-arg (repeatable), so a lane can feed the
standalone the operator inputs a save does not carry -- the data root, the AI
roster, which blocked phases may commit -- instead of hard-coding them. Every run
records what it was given, in the report header and in status.json.
2026-09-08 15:40:07 -04:00
alex
c997c6ebce board: lane W2 - multiplayer verified end to end, ModCount confirmed, rcex closed, Player.Status writer found 2026-09-08 15:34:45 -04:00
alex
2b7758123f rules 21-22: lanes never touch the shared worktree; union-resolve is not concatenation. VM released 2026-09-08 15:34:12 -04:00
alex
e9dec36d77 findings: lane AI3 -- the AI stepping order is save player order, and pass 0 writes nothing
Closes five of lane AI2's open items and corrects two published claims.

The stepping order: StrategyServer::ResumePlaying 0x007ddc90 walks the player
array in INDEX ORDER and raises SEResumePlaying at each live player; the app
callback StrategyApp::OnClientEvent 0x00838e10 delivers it inline for humans
and appends it, deduplicated, to the pending queue for AI players. So the AI
players are stepped in save player order, each at most once -- computable from
a save with no live measurement. AI2's search missed it because 0x00b29f98 is
not a pointer to the StrategyApp, it IS the StrategyApp: the enqueue writes the
absolute member address 0x00b29fb4 and never materialises the object base. The
enqueue also has zero direct callers and no vtable slot -- its address is stored
into StrategyServer+0x170 by CreateGame. Lane B6's third blind spot, twice over.

The two passes: `pass` is a tier index, not a plan/act switch. 0x006abb2a picks
between two per-candidate quota fields -- tier 0 takes +0x10, tier 1 takes +0x14
-- and the hub loops `for (i = 0; i <= pass; ++i)`. Every order-emitting exit is
gated pass == 1 (0x006bbd50, 0x006c16c0, 0x006cea50), so pass 0 claims each
task's minimum force in priority order and WRITES NOTHING. That halves the
ModCount arithmetic.

Corrections:
- AI2 §4.2: the nine "no order method" tasks are not planners. All nine emit
  orders at depth 4-9; the depth-4 cut hid it. AITColonize reaches list 7 and
  AITBuildPoliceShips reaches list 3, which is what their names promise.
- AI2 §3: the two priority overrides' flag polarity is inverted. The tunable
  applies when bit 0 of +0x4 is SET, and "committed" is not a supported name.
- AI2 §10.6: the .data invade tunables have no loader. 650 and 750, image
  constants, exactly one reader each and no writer anywhere.

Also: slot 12 named (a preemption permission, 0x006a8d20), slot 13's consumer
found (0x00696620, a range budget -- and the Zuul are exempt, a FOURTH
independent cross-check on AI2's species reading), slot 11's dispatch located.
ServerPlayer+0xf9/+0xfa -- the two bytes that decide whether a player is
AI-controlled -- sit in a hole in the serialised layout and are NOT in the save;
their writer is unfound and is the biggest remaining hole.

Static only; nothing here has run under an instrument. 160 indirect call sites
inside the AI closure are unresolved, so reachability is still a lower bound.
Four predictions with falsifiers in §7.

ghidra/addresses.d/lane-ai3.json: 15 entries, 1,105 -> 1,120, no duplicates,
validated to a scratch path.
2026-09-08 15:29:53 -04:00
alex
b77a6116e2 lane W2: VM housekeeping notes for the multiplayer recipe (SOTSUI2 left registered, SOTSB staged not registered, -ExecutionPolicy Bypass gotcha) 2026-09-08 15:26:42 -04:00
alex
cb1ea723e1 lane W2: the three queue items not reached, with the reason and the cheapest next step for each
TShn: the brief's address was wrong -- player+0x274 is observed_techs; the TShn map is
ServerSystem+0x274. The real obstacle is that a std::map's nodes are heap-allocated, so there is
no fixed leaf to arm; arm the map header to trap the insertion instead.

rcex: closed from the corpus, no VM time needed (see rcex-explained.md).

Trade/spy workload: not attempted, and lane H's reasons still hold. What is new is that the
watchpoint module is the instrument for confirming the workload BEFORE spending a turn on it --
both containers are one add from the arming point, and for the spy vector the trapped return
addresses would name the UI nobody has found.
2026-09-08 15:25:53 -04:00
alex
92ad44efb0 lane W2: multiplayer Tier 0 played end to end with no server; ModCount/Frame/Status watchpoints; rcex explained
Multiplayer: two clients on one guest, joined by typed IP and by LAN browse, launched a
2-player game and played two turns in lockstep. 44,319 packets captured outside the guest,
ZERO to any GameSpy port. Availability check fails open exactly as lane G2 read it; Join
Manually is enabled with every gamespy.com name dead, falsifying G2's own caveat (a). The
shipped MOTD is fetched live from kerberos-productions.com and tells players to host in LAN
mode -- the developer's own statement of the same finding.

Watchpoints: a new shim module arms DR0-DR3 from StrategyServer::ApplyAllTurnCommands.
ModCount takes exactly 12 writes per End Turn (lane A2's prediction, confirmed twice, both
predicted addresses exact); Frame takes exactly one, from BeginProcessTurn+0x2a, settling the
ModCount-vs-Frame naming in A2's favour. The Player.Status writer between tail phase 31 and
the autosave EXISTS and is StrategyNetworkClient::OnMessage+0xa15 -- correcting lane T2.
Rule 19 control: the armed run reproduced the determinism oracle byte for byte.

rcex: closed from the corpus alone. It is sixteen 4-bit per-player counters; nibble p is set
to 1 on the turn the system enters player p's AFlags and ticked to 0 the next turn. 7/7
across two different games.
2026-09-08 15:23:52 -04:00
alex
3fe74bc6a5 standalone report from merged main: 209 -> 131 leaves, closed 78 regressed 0 2026-09-08 15:10:17 -04:00
alex
449daeb621 roadmap: item 6 premise refuted - no writer exists, it needs a predicate not a watchpoint 2026-09-08 15:07:02 -04:00
alex
7574623f73 board: lane T2 treaty stamps close 26; roadmap item 6 refuted - no writer exists; tail ranked and unactionable 2026-09-08 15:06:46 -04:00