Commit graph

261 commits

Author SHA1 Message Date
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
alex
4c21bbe19e lane T2: re-measure H02 on main 2fd0852 (C3 + B6 merged) -- same +26 / +14 / +12x3, 0 regressed, so the phase is orthogonal to both 2026-09-08 15:04:36 -04:00
alex
f259655026 dashboard refresh 2026-09-08 15:03:10 -04:00
alex
a4cc50043d board: lane B6 - no build orders exist at load, so shpt[0] is Rung B; build queue read; stack-built fptr tables are a third blind spot 2026-09-08 14:58:45 -04:00
lane-b6
5cc66f6fff findings: ship construction -- the build queue read byte for byte, and the missing destroyer is the AI's
BuildQueue::ProcessTurn 0x00890d50 disassembled to the next function start (Ghidra says 1230
bytes; the body is 1264 and its end lands inside the epilogue -- rule 17 again). Corrects the
recorded prototype: the first stack argument is the StrategyServer S frame, not the system, so
the build-completed event goes on the sim and the hull's turn stamp is Frame.

THE HEADLINE IS A CORRECTION. Lane E2's open census leaf (shpt[0], one destroyer short on both
reference pairs) cannot be closed by S11's build-queue sub-pass: measured over the corpus, both
reference saves carry three empty BQ frames, every hbq false, and the one TurnCommands_v5 block
empty. The order is created inside the turn by the AI -- PvSav shows 11,900 leaving the treasury
before spine phase 0. The blocker moves to game/ai.

Also: the per-class built counter has EXACTLY ONE writer in the image (image-wide byte scan);
ShipRecords sized by enumeration; the ship/fleet birth chain end to end, correcting lane B5 --
FtFlg 0x400 is set by EVERY fleet born through the create path, not by retreat; one home fleet
per system cached at ServerSystem+0x238; and a third indirection class vtable_map.py cannot see
(the ship-borne wrapper is dispatched from a stack-built pointer table).

21 addresses in ghidra/addresses.d/lane-b6.json, validated to a scratch path against the merged
set (1,077 entries, no duplicate names); two entries dropped as duplicates of lane B5's and
recorded as agreements instead.
2026-09-08 14:54:03 -04:00
alex
90c665a3be lane T2: record the rule-17 hit -- the caller's real body runs 0x48 bytes past Ghidra's reported end, and a clipped sweep would have found no caller at all 2026-09-08 14:53:40 -04:00
alex
214e00186d roadmap: item 1 done (E1+C3); new item 1b S11 civilian growth, 1c ship repair cost 2026-09-08 14:53:33 -04:00
alex
247285ac28 board: lane C3 output turn path; unspent construction cascades to money; S11 civilian growth is next 2026-09-08 14:53:17 -04:00
alex
49fa5501ba lane T2: the treaty-turn stamp, and Player.Status's real writer
findings/subsystems/treaty-turn-stamp.md. Briefed on the post-combat tail, ranked its
phases by leaves-closed-per-effort against the standalone's 158-leaf residual, and found
that every tail phase which moves a leaf in this corpus is blocked on another lane
(ship construction, the budget) or on the tail's vtable blind spot. Section 5 lists that
ranking with the numbers. The two items below are what a lane holding no VM could close.

1. StrategyServer_StampTreatyTurns 0x007898c0, read as instructions. The diplomacy
   ledger's per-turn stamp over every ordered pair of players holding a treaty, creating
   the entry on demand. Sole caller is ApplyTurnCommands, so it is a HOST step and not a
   tail phase. Three corrections to strategic-turn-internals.md 5.2, which is flagged in
   place: the relation codes are 3=ally / 2=NAP / 1=cease-fire and not the reverse (which
   combat-done-tail.md 2A already implied independently); the stamped value is Frame, not
   ModCount; and the bit is the PlyrIdx field, not the player's vector position -- the
   opposite convention from the shared-vision mask.

   Modelled in sots-engine as host phase H02. Predicted 26 leaves on the reference pair
   and 14 on pair 2 before building; measured exactly 26 and 14, 0 regressed, and +12 on
   each of three further pairs from a different game at turns 2, 15 and 16 that the model
   was never fitted to. 76 closed / 0 regressed over five pairs, 0 RNG words. The rule
   also reproduces the ledger of ten of the eleven corpus saves entry for entry.

2. Player.Status. backlog.md item 6 has it blocked on "the writer between tail 31 and the
   autosave -- watchpoint". There is no such writer. A whole-image scan for immediate
   stores to ServerPlayer+0x164 finds exactly three: ProcessTurn's encounter loop writes
   1, ResumePlaying writes 0 on load (the 4 -> 0 the determinism note recorded), and
   StrategyServer_MarkPlayerTurnEnded 0x00821a40 writes 4 from the three End Turn
   SUBMISSION paths, which run before the turn is processed. Item 6 needs one predicate,
   not a watchpoint -- and an entry probe on that function names the set directly.

   NOT written. The only two saves in the corpus with a non-zero Status agree with
   Species != 4 on all eight players, and that is eight observations on a 1-bit predicate
   that nine of the eleven saves do not exercise at all. Evaluated and reported.

Seven addresses in ghidra/addresses.d/lane-t2.json, validated to a scratch path against
the merged set (1,095 entries, no duplicate). verify/results/standalone/{report,status}
deliberately untouched: lane C3 published into them minutes before this run.
2026-09-08 14:52:44 -04:00
alex
3b99e255c7 lane C3: ComputeOutput on the turn path -- the money ComputeBudget actually sums
`ServerSystem::ComputeOutput` 0x00751fb0 and the whole of `ComputeOutputFromRates`
0x00751bb0, read from the instruction stream to the next function start. This is the
function lane E1 correctly identified as the real blocker on P01/P02/P03/P05/P06 after
its own chain came out 25/25 on BnkEl and did not unblock them.

The shape, and the fact everything turns on: construction points the build queue and
the ship-repair pass do not spend are redistributed over trade / terraform /
infrastructure, and the TRADE share is added to the money channel. Unspent
infrastructure points cascade into the terraform pool and unspent terraform points
cascade into money -- two hops, not one. So a colony with an empty build queue earns
the same money whichever way its sliders point, which is why the turn path's money
sits within one trade point of the projected path on this corpus.

Eleven addresses, five of them dropped as agreeing duplicates. New: the repair pass
0x00751590 (the B1 double-run side effect, and its round robin is provably a `min`),
the construction-point helper 0x00746830, the queue-demand walker 0x008251e0, the
ideal-suitability resolver 0x00745d60, and two helpers with non-standard conventions
that would be silently wrong read as thiscall -- 0x0074c6f0 takes `this` in ESI and
0x007460b0 takes the system in EBX.

Corrections in place: `income-term.md`'s "leftover science points" are leftover SHIP
CONSTRUCTION points (there is no science channel in this function), its
science-cascade bullet moves from inferred to instruction-verified, and
`output-term.md`'s 0x00751fa8 boundary is padding -- the `ret 8` is at 0x00751fa0.

Measured, with the engine change (sots-engine wip/output2 d3ee453):

    turn1-state -> turn2-state    209 -> 157   closed 52  regressed 0   (was 51 / 0)
    turn2-state -> turn3-state    108 ->  86   closed 22  regressed 0   (was 21 / 0)

Closed 1 per pair, regressed 0. Every prediction in the doc's section 4 held; the one
that was off is named and explained. The strongest check is not the leaf count but
the eleven-save self-check in section 5.3, where every difference between the turn
path and the projected path decomposes to the unit into the build queue's points.
2026-09-08 14:50:50 -04:00
alex
6669c4ab1e dashboard refresh 2026-09-08 14:48:59 -04:00
alex
3b4142b566 board: note the self-inflicted build-dir race that mimicked a flaky test 2026-09-08 14:48:58 -04:00
alex
ce4603a380 board: lane AI2 - task selection is a stable sort on a constant priority table; order-method mapping complete; throttle settled 2026-09-08 14:46:12 -04:00
alex
232397aa12 lane AI2: the AI's task selection loop, the priority table, and the order-method -> TurnCommands map
The strategic AI does not search or score. Once a turn (Process Turn phase 20, 0x006cf630) it
rebuilds a candidate task list -- which families it builds at all is a switch on the player's
SPECIES, four arms, and the NPC arm builds nothing -- sorts it by a per-task-type priority, and
walks it twice calling each task's Execute(agent, pass) with pass 0 then 1.

Closed from lane AI1's open list:
  * the selection loop (its item 2) and the whole ordering policy: a 33-entry priority table at
    0x00691f00 plus five named overrides;
  * IAITask's unnamed pure virtuals (item 1) -- seven, not eight: GetTypeId, GetTargetA/B,
    Execute, IsFinished, GetTypeName, Describe;
  * the order-method -> list mapping (item 5): all 27 of lane Q's lists and all six prologue
    gates now have a named producer, and list 14 -- lane Q's "observed but not understood" --
    is the AI's fleet order, two elements per fleet;
  * g_CurrentClientIndex (item 6): a stack pointer with exactly two writers, pushed around the
    whole AI turn by StrategyAIAgent::OnEvent;
  * the think-time throttle (item 7): AIProcessMinTime is a trailing Sleep, not a compute
    budget. Every pending AI player's turn runs back-to-back inside one Update. The clean
    "all AI orders in before the human's End Turn" ordering HOLDS, and Rung B is not at risk;
  * Broadcast -> OnAIPacket, read to the call -- AI1's one inferred hop is now verified.

Corrections: 31 concrete task classes, not 34; 26 order methods, not 21; seven pure virtuals,
not eight. lane-ai1.json's Broadcast and g_CurrentClientIndex entries upgraded in place.

Open and said so: slots 11/12/13 unnamed, the nine goal tasks' bodies unread, the two-pass
meaning inferred, the StrategyApp pending-AI enqueue site not found, nothing run under an
instrument. Four predictions in section 9, P1 being a ModCount prediction.

ghidra/addresses.d/lane-ai2.json: 25 entries, 1031 -> 1056, no duplicates.
2026-09-08 14:43:41 -04:00
alex
8411c7e9c7 board: VM140 holder = lane W2 2026-09-08 14:14:49 -04:00
alex
35ccaf5d94 dashboard refresh 2026-09-08 13:56:49 -04:00
alex
4549f5eb5c board: lane H - oracle reproduced, instrument perturbation found, P2 held so Rung B stands 2026-09-08 13:56:44 -04:00
alex
8b6c946614 rules 19-20: the instrument can perturb the measurement; a count cannot separate did-not-fire from fired-and-found-nothing 2026-09-08 13:56:06 -04:00
alex
132d41c44e roadmap: restore item 2, mark item 4 done, refresh current numbers 2026-09-08 13:53:39 -04:00