Commit graph

246 commits

Author SHA1 Message Date
alex
618ccb1c25 shim: research.replace_cascade, and a completion counter that prints in replace mode too
The B3 hook's unlock cascade was compare-mode only, so a replace run left the
completed node unstamped and no tech unlocked -- and its only per-call counter was
gated on the cascade, so a replace run had no instrument of its own saying a
completion had happened at all. Lane CR needs both halves to ask whether
ProcessResearch can be displaced.

research.replace_cascade=on (default off, so the shipped behaviour is unchanged and
still measurable) lets a replace-mode call write the four TechNode words
SetResearched stamps and the tree's completion-order counter. The ServerPlayer half
of the callback stays unmodelled and stays declared: no event is posted, no
ObservedTech element is appended, no tech effect is applied. The pair of settings is
the measurement -- same binary, one config line, and the leaf difference between the
two autosaves is the cascade's own contribution.

The per-call log line now prints in every mode and counts completions from the pass's
own step results, so 'a completion fired' is a statement by the instrument rather
than an inference from the artefact under test.
2026-09-09 09:15:47 -04:00
alex
e7e2bd6f62 ai: the gather order decides fleet names too, and not only ours
Lane BT found it while building the comparison: FtName is an id-attached label
minted by the same pass as the id, and its counter is per player and shared with
the server's fleets. In the original, a run that minted two client fleets named
an unrelated server fleet differently from a run that minted three. So a gather
that visits groups in a different order renames fleets the AI never touched.
2026-09-09 04:13:34 -04:00
alex
45b16ef626 ai: bind the fleet-gather ordering contract where it will be needed
The gather/mint subsystem is not written yet, so this records the requirement at
the emission layer rather than building the subsystem speculatively.

Measured, not guessed: the original's assignment pass walks a container keyed on
fleet->Location, a heap pointer, so its whole observed outcome set on a rich turn
is the two orderings of two heap pointers under LFH randomisation. There is no
original order to match -- it is neither ascending systemId nor ascending minimum
ship id in any process observed -- so we choose one member of its outcome set and
say so. The order must be imposed at the gather, not at assignment: the fleets
already carry their ids when the assignment walk first touches them.
2026-09-09 03:41:32 -04:00
alex
55408a980a shim: aivisit, the fleet-assignment visit-order log (lane BU)
Three function-entry detours, off unless `aivisit=on`:

  * StrategyAIAgent::AssignFleetsAndIssueOrders -- the bracket, recording
    (agent, task, pass, walked vector, its slots in index order);
  * StrategyAIAgent::ClaimShipsOfFleet   -- the head loop, one row per element;
  * StrategyAIAgent::ReleaseShipsOfFleet -- the tail loop, one row per element.

The two per-element targets have exactly ONE caller each in the whole image and
that caller is the third target, so the module's traffic cannot come from
anywhere else in the game and no return-address filter is needed. The element
loop's own body is deliberately not patched: its first instruction is a branch
target, which is the shape rule 19 exists for.

Each element row carries the element address (the hypothesis under test), the
fleet's id, its ship ids and its Location pointer. The ship ids are the join key
to the command block's list 10; the join must not be by position because the
walk can visit more elements than it emits commands for.

Reads of game addresses are probed before every access and laundered through a
register, so a wrong offset logs a zero instead of faulting inside a detour.

Also: shim.cfg.bupin, which is shim.cfg.bppin plus exactly three non-comment
lines, and is marked `# exhaustive` so tools/check_shim_configs.py enforces
that every registered hook is named in it.
2026-09-09 03:10:52 -04:00
alex
dea127d4e1 regenerate addresses header: lanes AZ and BR fragments (1291 entries) 2026-09-09 03:03:17 -04:00
alex
165478592a shim: fix cbpin's wrapped header and six unnamed hooks; add a config check
Lane BP found both defects while copying cbpin for a determinism probe, and its own
first draft reproduced the comment wrap -- which is what a trap looks like. The
header ran on past the comment and left a stray aiseed.values ahead of the real one,
and six registered hooks went unnamed, so under hooks=trace the config ran six more
detours than it claimed.

The check enforces the two syntax defects everywhere and exhaustiveness only where a
template opts in with '# exhaustive'. hooks=trace legitimately means 'trace all but
these', so a global exhaustiveness rule would be a rule about a style nobody agreed
to; it matters only for a minimal-hook determinism probe.
2026-09-09 02:01:58 -04:00
alex
989c692c53 rng: model chance(), the only entry point that can cost zero words
Both early-outs return without touching the generator: p <= 0 false, p >= 1
true, otherwise exactly one word compared with a strict <. The zero-cost cases
decide stream alignment wherever a caller's probability climbs -- the spy
counter-mission adds 0.2f per failed turn and stops drawing entirely from the
fifth. A model without the early-outs drifts one word from there on, for ever.

NaN takes neither early-out in the original, so it draws and returns false;
reproduced rather than smoothed over.
2026-09-08 22:32:14 -04:00
alex
35b05ebc18 regenerate addresses header: lane AG's gate-indexed audit fragment (1269 entries) 2026-09-08 22:29:08 -04:00
alex
e62d974601 stream: DomainArchive, the value-axis companion to the coverage ratchet
Coverage is indexed by shape and answers 'does a field name this item'. Two
saves carrying content nobody had modelled scored 100% anyway, because the
novelty landed in fields that were already typed. This asks what the corpus has
ever actually put in each field.

Over 22 saves: 724 fields observed, 490 vary, 234 constant. A third of the
format we call 99.99% typed has been seen holding exactly one value.
2026-09-08 21:57:43 -04:00
alex
0c6d0a3573 shim: launder game-address reads past -Werror=array-bounds in ai_orders
A fresh cross-build directory caught what a reused one had been hiding: gcc reasons
about a reinterpret_cast from uintptr_t as an object of its own when the read is
inlined at a constant offset. Same barrier idiom watchpoints.cpp already uses for
the TIB reads.
2026-09-08 21:50:01 -04:00
alex
68a9fc824d sync header after AC merge 2026-09-08 21:34:48 -04:00
alex
06d2ddfeec AC: address fragment for the trade-raid gate (regenerated header)
Adds lane-ac.json's six entries via tools/gen_addresses.py: the trade-sector
node-kind gate, the StarMapNode kind ctor, the raid predicate and the two
per-player trade-sector masks, plus the spy detection-roll branch as a
hypothesis. Header regenerated, never hand-resolved (rule 14).

Also removes FUN_xxxxxxxx identifiers from docs/AC-predictions.md, which
clean_room_check.sh caught.
2026-09-08 21:31:39 -04:00
alex
b1c61a827a sync header after SD merge 2026-09-08 20:52:40 -04:00
alex
414bcc743e SD: record the two weapon lookups that gate the loop-carried draw
The loop-carried cl_Chance(0.2f) at 0x006ae575 has measured zero on all six
composer calls of all four runs, and a word count cannot say which of its two
gates did it: a null from the UNRESTRICTED weapon lookup skips the whole
point-defence block, a null from the RESTRICTED one short-circuits every
iteration without drawing. Both print zero -- method rule 20 one level below
the level the sub-bracket was built for.

So the chooser 0x006ad2a0 is detoured and its two return values recorded per
composer call, keyed by the return address that distinguishes the call sites
(0x006ae3c6 restricted, 0x006ae3e1 default), and printed as w_alt / w_def.

NOT YET RUN. Every measurement in findings/subsystems/ship-design-composer.md
was taken with the previous build; this hook is the instrument for the next
lane, not for the numbers already published.

Header regenerated from ghidra/addresses.json plus the fragments, never
hand-resolved.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 20:51:17 -04:00
alex
0f75048849 AC: prediction addendum from the static gate decode, before any active-state measurement 2026-09-08 20:35:31 -04:00
alex
dd105da759 SD: a sub-bracket on the ship-design composer 0x006ad700
Extends lane PAR's airng module with a detour on the composer itself, so a
row says what ONE COMPOSER CALL spent rather than what the turn spent.

Three pieces:

  * the detour. The composer's calling convention is neither cdecl nor MSVC
    __fastcall -- two register arguments, two stack arguments, and the CALLER
    cleans -- so it is entered through a hand-written thunk that re-pushes the
    four arguments as cdecl, and the trampoline is re-entered through a second
    thunk that restores ECX/EDX.

  * the sub-bracket. left (RNG+0x9c8) read off the client generator at composer
    entry and exit, and the draws seen in between re-tallied by return address.
    Two independent numbers per row, printed together, because PAR's own
    instrument double-counted a word and only its second measurement caught it.

  * cl_Chance's note is now taken AFTER the trampoline, so a row carries the
    DECISION as well as the call. Site 0x006ae413's 0.3 coin selects the
    fraction that governs the loop-carried draw's trip count, and a call count
    alone cannot say which way it went.

The row also carries the model's prediction beside the measurement: N, the
per-section bank counts and the PointDefence flags are read off the design the
call produced, L is computed from them, and the row says HOLDS, WRONG, or
GATED-OR-WRONG when the prediction is positive and the measurement is zero --
which is the weapon-lookup gate, and is not counted as a confirmation.

Header regenerated from ghidra/addresses.json plus the fragments, never
hand-resolved; the six new lane-sd entries do not change any existing constant
and the module uses none of them by name.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 20:30:22 -04:00
alex
e2296c5270 sync header after ID merge 2026-09-08 20:27:40 -04:00
alex
bd48e80e6e merge lane ID: one allocator partitioned by node nibble; techId is the stricmp-sorted index 2026-09-08 20:27:40 -04:00
alex
6a214f00f6 regenerate addresses: lane ID's 11 IDMap / StrategySim / tech-table entries
Generated by sots-re tools/gen_addresses.py from sots-re 975c19a. No source change in
this repo; the entries name the single object-id allocator (IDMap::Initialize and the
per-node counter accessors), the two functions that give a client sim its network node
index, StrategySim::CreateDesign's mint-or-honour branch, and the eight instructions in
MasterTechTree's constructor that make the wire techId a sorted index.
2026-09-08 20:25:29 -04:00
alex
1d3f8116c3 merge lane WS: 14 new wire bindings; corpus coverage 99.50% -> 99.9949%, all 20 saves above the ratchet 2026-09-08 20:16:30 -04:00
alex
7f04bcc88b lane WS: pay the ratchet debt -- type the five new bodies, do not move the bar
The corpus went 11 -> 19 saves and named coverage fell to 97.6% against a 99.99
bar. Rule 27 says type the content. Typed, all from the recovered wire schema
plus the records the new trade/spy saves finally carry:

  Game::CombatPlayerReport  the <rest:prep> tail -- ncls run of six per ship
                            class, nsec run of two per section, ndam, srep
  Game::CombatShipReport    srep elements (caps2 is an i64; dami/damt f32)
  Game::TacReport           TRnc is a COUNT and TRships/TRsats/TRshipsL are one
                            loop body, INTERLEAVED on the wire -- not three
                            trailing runs. Two lanes could not settle this
                            because TRnc was 0 in every save until tonight.
  Game::TacReportEvents     TRby / TRto
  Game::FleetLayout         Lay: a FieldTemplate frame then a count and its ids
  Game::TradeRoute          rt -- a container write with NO count word, read as
                            an uncounted run keyed on the tag
  Game::ServerTradeSector::FreighterWarning   fwarn elements
  Game::SpyCraft            spy elements
  Game::WeaponGroups        Dwg, and Game::GunBankSelection under it

Corrections found on the way, both silent until a shape was bound to the table:

  * Game::CombatReport: auto and cdst are BOOLs, dur/cdt/cdi are FLOATS. All
    five were ints here. Four are 0/1 everywhere so the bytes never moved; cdt
    is not, and was being read as 1070805848 instead of 1.598.
  * Game::CombatWeaponReport: the damage quartet is flat and dami/damt are
    floats. The old shape reached them through obj_flex, modelling a nested
    `dams` frame the binary does not write. That also made CoverageArchive
    charge one phantom typed item per weapon report -- exactly 8/13/21/32 on the
    four affected saves -- so the pre-fix coverage figures were slightly
    optimistic as well as too low.

FTPnts stays carried: it is the one item the recovery itself marks unresolved,
its count is 0 in every save, and element framing is a property of the helper --
the SysMem / mts / nalat trap. The workload that settles it is a save with a
stored fleet tactical formation.

Named coverage, per save, before -> after:
  human-turn5-traderoutes   99.0003% -> 99.9951%
  human-turn8-traderoutes   98.6349% -> 99.9955%
  human-turn11-spytechs     97.6382% -> 99.9959%
  human-turn15-spyprogram   96.9412% -> 99.9964%
  the other 16 saves        unchanged, 99.9940-99.9953%
  corpus                    99.5021% -> 99.9949%

All 20 saves are above the ratchet; the only opaque items left anywhere are the
two of the RNG blob. 14 new shapes bound to the wire schema, every one a full
match: 100 shapes, 966 items, 0 MISMATCH. ctest 58/58 over all 20 saves, both
round trips byte-identical, 0 errors and 0 warnings.
2026-09-08 20:14:18 -04:00
alex
7e726b9074 AC: predictions before the active-contents workload
Written before the game was started. Records one correction to lane L3 up front:
the turn-15 save already has a trade sector with tsnumflt=1 (the AI's), so 'any
freighter anywhere' is already falsified as the gate for the trade callees.
2026-09-08 20:14:14 -04:00
alex
b67362b999 SD: predictions for the ship-design composer, written before the build
The cost model for 0x006ad700 read from the instruction stream: nine live draw
sites, an exit taxonomy saying what each of the eight bail-outs has already
drawn, and a closed form for the loop-carried draw's trip count

  f  = 1.00 / 0.75 / 0.50 by request flags, hull size and one 0.3 coin
  M  = (int)(N * f)
  D' = max(1, (N + 1) / M)
  L  = #{ qualifying mounts j : PointDefence section, or j mod D' == 0 }

with the falsifiable corollary that the 0.3 coin can only move the word count
at N in {1,2,3,5}, and that N=1 with f<1 enters the loop and draws nothing.

Also predicts client 32's seven turn-1 words as two composer calls -- a
costOnly=1 price query (A+B+C) and a costOnly=0 build (A+B+C+F) -- which is
read statically off 0x006cda40's two push sites, not fitted to the number.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 20:08:40 -04:00
alex
16b1e65fa3 merge lane L3: trade/spy workload (watchpoint modes unioned: modcount|tshn|snlv|cont) 2026-09-08 19:48:07 -04:00
alex
c36b560cf0 merge lane L3 (header regenerated) 2026-09-08 19:47:40 -04:00
alex
f99488af9f merge lane PAR (header regenerated, not hand-resolved) 2026-09-08 19:38:32 -04:00
alex
e1995e6b11 PAR: roll parity -- the AI's per-turn RNG word count is NOT fixed, at any scope
New shim module `ai_rng`: a bracket on StrategyClient::OnResumePlaying over the per-client
generator at +0x134, with two independent measurements per bracket (an observer on the existing
seven draw-site detours, and `left` read straight off the object) plus AI-call-site attribution
through the cl_* facades and an optional seed pin.

Measured on VM140/VM145, six runs, five fresh processes, unpinned autosaves byte-identical to the
published oracle:

  turn2->turn3   human 0, AI 32 -> 3 words, AI 496 -> 0, AI 512 -> 0
  turn1->turn2   human 0, AI 32 -> 7 words, AI 496 -> 0, AI 512 -> 1 word ... or 3

The last row is the answer: 512 makes ONE cl_RandRange call (the research-target tie-break at
0x006a8495, phase 18) and that one call cost 1 word in one process and 3 in another, because
RNG_NextInt is an unbounded rejection loop. The count is not fixed across clients, across turns,
or across processes with the path held fixed.

Also found: cl_RandFloat 0x00579c70, a third cl_* RNG facade that reaches RNG_NextFloat by a TAIL
JUMP and so leaves no rel32 edge for a call-graph sweep -- which is why ai-turn-logic.md 5's "zero
NextFloat calls from the AI module" reads as true when it is not.

The instrument caught its own defect: RNG_Chance calls RNG_NextFloat and both are detoured, so a
drawn word was reported twice. left_delta was never affected, which is the point of having two
measurements.

Gates run separately on a fresh build directory: clean_room_check OK, host ctest 55/55, CT111 shim
cross-build OK.
2026-09-08 19:32:19 -04:00
alex
9fcf220bae merge lane CB: capture with payloads and seeds (header regenerated, not hand-resolved) 2026-09-08 19:27:40 -04:00
alex
cd17280f01 CB: record the third pinned run
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 19:26:33 -04:00
alex
cd92076b39 CB: the engine-side record of the capture instrument and what it measured
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 19:22:16 -04:00
alex
c2eba45850 CB: drop the list-5 rates record rather than ship a wrong one
Lane RB consumed the capture and found the record was wrong: the element is dumped in
memory order and its memory field order is not its wire order. Memory member 1 is wire
member SRsc; six members are unread. RB's first replay regressed two leaves on exactly
that.

The values now ship as rates_memory_order_{u32,f32} with wire: null, and the .tcb emits
the seven '?' tokens RB's own converter emits, so a replayer refuses rather than guesses.
A missing field is honest; a mislabelled one propagates -- and a capture is meant to
outlive whatever adapter happened to read it.

Also records the outcomes against every prediction, including the one the consumer
falsified.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 19:10:28 -04:00
alex
6699f543bc 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) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 19:10:10 -04:00
alex
e2fb08f55f merge lane RB: --turn-commands replay (game_ai test list union-resolved; header regenerated) 2026-09-08 19:08:34 -04:00
alex
f438d2f9a7 RB: refuse to replay a capture silently against the wrong board
A capture belongs to one save. Replaying one turn's commands against another turn's board is
not an error the arithmetic can see -- the blocks name player ids that exist in both, so the
counter is charged happily and the number is confidently wrong. The capture now records which
save it was taken on and the CLI says so when they disagree, and says so too when the capture
does not record it at all.
2026-09-08 19:05:29 -04:00
alex
c3d95dd44f CB: the seed capture and its predictions -- the seeds are part of the stream
Lane L1 found each AI client's generator takes a fresh per-process word, so the AI is
MT19937 from one word per client and game/ai is a function of (save, seed). A capture
without the seeds records the answer without the input.

- aiseed=log|pin on StrategyApp::RunAI: one detour that reads the fourth stack argument
  where the callee reads it, and in pin mode replaces it. Both modes log observed AND
  used, so a pinned run says so in its own capture.
- aiorders.words= widens the element window past list 1's 48-byte horizon.
- aiorders.deep= follows the heap: the route vector, the counted vector and the
  Population body were ABSENT from every capture so far, not empty.
- tools/turncommands_capture.py (in sots-re) does the typing offline, so a corrected
  element record costs a re-parse rather than a VM run.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 18:59:24 -04:00
alex
98257b9e82 RB: sots_turn --turn-commands replays a recorded command stream, and ModCount closes
The milestone needs a second input. The AI is a client, not part of the sim: it decides once,
on one machine, and its decisions reach the server as commands. A save carries the board and
half the input, which is why our turn wrote ModCount 14 where the original writes 24 -- the
missing ten ARE the turn's command stream.

  * `game/ai/apply_order` -- the thirty-step schedule the original drains a batch in: twenty-
    seven per-LIST steps (every player's elements of one list before the next list starts) with
    three per-PLAYER gate loops spliced in at step 10, 29 and 30. Neither list order nor member
    order, and both facts are asserted so a port that sorted cannot pass.
  * `game/ai/command_capture` -- a `.tcb` recorded turn: gates, list lengths, elements in wire
    order, per-client seeds, and `?` for a field the instrument could not read. An element count
    that disagrees with its declaration is REJECTED, because a counter quietly one short is
    indistinguishable from a turn that issued one fewer command.
  * `app/command_replay` -- applies it before the drivers, where the End-Turn dispatcher does.
    Every command is CHARGED; only the ones whose subsystem we hold are APPLIED; the rest are
    declined with the named gap, or marked incomplete when the capture itself lacks the payload.
  * `--turn-commands`, `--replay-count-only`, `--replay-recorded-names`, `--ai-seed`.

Measured on a fresh build directory, canonical pair turn2-state -> turn3-state:
  108 -> 62, closed 46, regressed 0  (was 108 -> 63, closed 45) -- /Sim/ModCount now reads the
  original's 24, decomposed as 2 drivers + 4 research-rate gates + build + rates + list 10 +
  two list-14 + fleet move, with the list-23 population element free.

turn1-state replayed against the SAME run's autosave closes 7 (ModCount and all six research
leaves); against the historical turn2-state it closes 6 and leaves player 512's research pick
diverging -- which is correct, because that recording is from a process that picked differently.

One prediction was falsified and it paid for itself: the first run regressed two leaves because
the rates element's MEMORY field order is not its wire order. The converter no longer claims a
mapping it cannot support.

Two new addresses (the second and third gate-loop heads) via ghidra/addresses.d/lane-rb.json;
header regenerated, never hand-resolved.
2026-09-08 18:57:46 -04:00
alex
1e6474b31c RB: predictions for the recorded-command replay, before the module exists
The headline is one number: our turn writes ModCount 14 where the oracle writes 24, and the
missing ten are the turn's command stream. P1 decomposes the ten and names what each near
miss would mean; P5 predicts the secondary pair closes five leaves and must NOT close the
sixth, because the recording is from a run that diverged there.
2026-09-08 18:33:17 -04:00
alex
6ba2c796fc merge lane L2: combat multiplayer verified in lockstep with zero GameSpy; 3370 never binds 2026-09-08 18:32:19 -04:00
alex
7c0c395bea sync header after L4 merge 2026-09-08 18:32:19 -04:00
alex
4457249b88 CB: predictions and the capture format, before the instrument changes
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 18:30:54 -04:00
alex
c67374d2e3 CB: take lane L4's ai_orders instrument as the base for the command-stream capture
The capture lane needs the block dump L4 built; branching off main without it
would mean writing the same detour twice. Header regenerated from sots-re
(rule 14), not hand-resolved: 1,217 entries.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 18:28:18 -04:00
lane-l4
c842d44d62 L4: engine doc for the research-selection capture 2026-09-08 18:26:59 -04:00
alex
4f25f1e8c0 merge lane L1 (header regenerated from addresses.json + fragments, not hand-resolved) 2026-09-08 18:25:53 -04:00
alex
d89b290988 sync header before L1 merge 2026-09-08 18:25:49 -04:00
alex
fa53e02b53 L1: probe the AI client seed across two processes -- it is fresh every time
Two hooks, Mars::RNG::Seed 0x0049fdf0 and StrategyApp::RunAI 0x008706f0, and a
config that turns everything else off. Two launches from the same save, load
only -- the AI clients are constructed on load, so no End Turn is needed.

Result: every AI client's generator seed differs between processes (net 32,
496 and 512 all move), while the record structure is byte-for-byte the same
shape and one Seed call with seed=0 produces an identical state in both runs.
So the turn1-state -> turn2 nondeterminism is a SEED effect, not the ordering
effect that was predicted, and lane AI1's 'every draw from the static generator
returns 0' is falsified by measurement.

The prediction said the opposite and is left in docs/L1-predictions.md with its
outcome underneath.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 18:22:14 -04:00
lane-l4
ca0de05e07 L4: research-selection capture -- candidate stream in arrival order, and the fallback probes that separate a tie from a rotation 2026-09-08 18:15:28 -04:00
lane-l4
ec8d841dba L4: predictions for the research-selection tie set, before the instrument 2026-09-08 18:06:31 -04:00
lane-l4
c769394b57 L4: credit lane L5 for the turn-1 nondeterminism, and record what the block dump adds to it 2026-09-08 18:03:18 -04:00
lane-l4
4a5c218a35 L4: read the AI's command block out of the running game
New shim module src/shim/hooks/ai_orders.{h,cpp}: one register-transparent entry
stub on StrategySim::ApplyTurnCommandBatch dumps every submitted TurnCommands block
(six gates, 27 list lengths, element bytes) at the point where all of them are
complete in memory; sixteen entry probes, with RunTaskList's stub recording the
pass so every later hit is pass-attributed.

Two workloads on VM145, one End Turn each. The rule-19 control passed with all
seventeen detours installed: both autosaves byte-identical to the published oracle.

What the AI actually emits, and three things no reading had produced:
  - a list-23 element on EVERY turn, the first element ever observed in the free
    half of the cost table -- and both turns still cost the measured 12;
  - the ids in AI commands are client-allocated and travel in the command (design
    18, fleet 34; neither exists in the input save);
  - pass 0 emits nothing, measured from element counts rather than inferred.

tests/game_ai/test_live_blocks.cpp rebuilds both captured blocks through the public
OrderClient API and asserts the list profile, element values, gate counts and
ModCount total: 44 checks. Kept separate from test_orders.cpp, which stays the
record of what static reading predicted.

Gates: clean_room_check OK, host ctest 55/55, CT111 shim cross-build exit 0.
2026-09-08 18:01:19 -04:00
alex
7444c3b85c merge lane L5: interest-literal boundary verified live with a failing control; two more widened-float defects; reference pair shown non-deterministic 2026-09-08 17:51:30 -04:00