Commit graph

8 commits

Author SHA1 Message Date
alex
586171cb72 app: T34 RecordObservedDesigns -- the observed-design list, and the three rules the save cannot show
The tail's phase 34 sweeps every player over every ship of every fleet and
records the design of each ship it can see. Three of the recorder's rules are
invisible from a save and every one of them changes the answer:

  * the lookup key is the DESIGN ID ALONE, not the (design, owner) pair;
  * a re-observation ERASES the record and PUSHES A COPY ON THE BACK rather than
    updating in place, so the list ends in last-observation order and the record's
    first-seen turn survives the move. This is why a turn's output is a
    PERMUTATION of its input and not an append -- which is what made the group
    look like 55 unrelated leaves;
  * the list is CAPPED AT 20 RECORDS PER DESIGN OWNER, counted from the most
    recent end, and the cap is applied after EVERY record call rather than once
    per sweep. On an empire with more than twenty designs in service that thrashes
    inside the one sweep: records are evicted and re-created, and a re-created
    record's first-seen turn is reset.

A fourth rule is a guard on the DESIGN'S OWNER and not on the observer: a design
owned by an NPC that is not a rebel AI is never observed by anyone. Every
observation record in every corpus save agrees -- the NPC factions' 26 designs
appear in nobody's list -- and removing the guard costs 9 leaves on the canonical
pair while costing nothing on the rich turn, so the two pairs test different rules
and both were run.

TWO THINGS ARE NOT MODELLED AND BOTH ARE NAMED IN THE CATALOG TEXT. The gate is a
two-bit-per-player word on the ship that is NOT serialised; this phase stands it
in with ownership, which is the smallest rule correct on every state the corpus
holds, because on both reference pairs no player observes another player's ship.
That is a hypothesis with a cheap falsifier and it is labelled as one. And the
phase's tech and weapon arms are not implemented: the original feeds the design
through three set builders into the observed-tech and observed-weapon lists, and
those builders are not decoded.

Measured, closed and regressed never netted:
  canonical pair turn2 -> turn3   62 -> 61 leaves   1 closed / 0 regressed
  rich turn ad-turn27 -> pinB   1092 -> 1058        36 closed / 2 regressed
The 2 are two `otnF` words at positions whose `odid` also moves, i.e. positions
that now hold a different record; and the 21 design leaves that remain are the
shadow of ship construction, not of this phase -- two designs receive their first
ships during that turn and no phase here builds a ship. Fed the true post-turn
ship list, the same code leaves 1 leaf of 55.

Host build 255/255; host ctest 57/59 with the two pre-existing corpus-writer
failures unchanged (12 of 43 saves, the SpecialProjectNameGen `usp` item, not
this lane's); shim cross-build green; clean-room and shim-config checks green.
2026-09-09 10:15:12 -04:00
alex
fe8ed45de4 merge lane SV: script-object event bus (CMake lists merged into their constructs, rule 22) 2026-09-08 16:48:49 -04:00
alex
2d93404c67 SV: the script-object event bus, and what it writes in a turn
`SvSctOb` is not written by direct calls. Every update goes through an event bus: a
driver notifies the root object with an integer id, the root fans the delivery out to
every child, and each delivery is two steps -- a generic handler that takes the id, then
one event-specific vtable slot that does not. The id -> slot map is a 33-entry jump table
in the image, so which class reacts to which event is recovered and exhaustive rather than
inferred from what the saves happen to show. Five of the 33 rows are not in slot order,
including two the tail sends.

Three handlers write the eight leaves that diverged:

  * the slavers' difficulty tier, on the tail's end-of-turn delivery -- a three-record
    stack table scanned against the frame, boundaries 1/50/100, stored only on a change,
    and at frame 100 and above the scan runs off the end and stores nothing, so the tier
    can never reach 2;
  * the refugees' one-shot latch, on the turn-begin delivery, with a design instantiation
    behind the same latch that nothing here can do;
  * the swarm queen's hives, also at turn begin, registered on the systems carrying the
    SWARM's scenario tag (the queen's constructor stores 3 for that and 10 for its own
    encounter id) and then ticked -- and the tick is the whole explanation of a target
    turn that reads 31 after one turn and 32 after the next. It is not re-rolled; it slips
    forward by one every turn the spawn gates stay shut.

New host phase H03 for the turn-begin delivery, run right after the frame counter where
the original sends it, and tail phase T20 implemented. Rules are pure in game/sim.

Measured on CT111, closed and regressed stated separately:

  default                 turn1->turn2  209 -> 126 (was 128)  closed 83, regressed 0
                          turn2->turn3  108 ->  67 (was  69)  closed 41, regressed 0
  --commit-blocked=H03    turn1->turn2  209 -> 124            closed 87, regressed 2
                          turn2->turn3  108 ->  67            closed 41, regressed 0

Registering a hive closes the four leaves that say which systems have hives and that they
have no queens, and opens two carrying a target turn known to be wrong: the original draws
it from the strategic generator inside the turn-begin step, outside both turn drivers, and
neither the two data-file constants nor the generator's position there is settled. That
trade is a flag, not a default.

The prediction in docs/SV-script-objects.md was committed before the build, and P5 was
wrong: it called the second pair a null control, and the second pair is where the slip
rule is tested EXACTLY -- two hives, two target turns, both landing on the oracle with no
draw and no fitting.

Gates run separately: clean-room OK, host ctest 51/51, CT111 shim cross-build clean.
2026-09-08 16:46:25 -04:00
alex
26b041106f PL: decompose the /Sim/players residual; S00 PvSav snapshot; T31 recovers its difficulty column from the save; the bankruptcy protection factor is a widened float
Closed 2 on the reference pair and 4 on pair 2, regressed 0, with no operator
input. Measured, both pairs, never netted.

- S00 stamps PvSav from Sav on every live player, before any phase can move it.
  0 closed on pair 1 (a no-op there), 2 on pair 2.
- T31 recovers the per-player difficulty column by recomputing BnkEl from the
  colony state the input save was written from and comparing against the BnkEl
  the save carries. That removes the --ai-player flag as a blocker and turns the
  phase's self-check into a real one: it used to compare its POST-turn result
  against the PRE-turn stored value, so its 6-of-8 only ever covered the six
  players whose limit does not move. The load-time check passes for every live
  player of all eleven corpus saves. T31 Blocked -> Partial; BnkPr still needs
  the tuning constant.
- BANKRUPTCY_PROTECTION_LIMIT_FACTOR is multiplied in as fmul dword ptr, so it
  is a float32 in the image; the engine narrows it now. Zero leaves move on this
  corpus -- all seven of its BnkPr records land where the two constants agree --
  and three hand-written test expectations moved (rule 23).

docs/PL-players-residual.md carries the decomposition, the predictions written
before the build, where they were wrong, and the ranked remainder.
2026-09-08 16:34:40 -04:00
lane-b6
0fcbb69ba5 game/sim + app: ship construction -- the build-queue completion bookkeeping, and S11's sub-pass wired in
game/sim/construction.{h,cpp}: Game::ShipRecords (four per-hull-class arrays plus the
per-design vector, sized by ENUMERATION against the wire, not by what the code touches)
and the completion bookkeeping BuildQueue::ProcessTurn performs -- the per-class built
counter, whose indexed increment has EXACTLY ONE writer in the whole image, and the
find-or-append per-design record keyed by the design's object id. RunSystemConstruction
wraps the point pass and keeps each completion's design id, which the point pass alone
does not report.

game/sim/colony: corrected from the instruction stream -- with points <= 0 the entry test
branches to the epilogue, so the REMOVAL SWEEP IS SKIPPED TOO. Carried as a labelled
hypothesis: no corpus save can reach the state that shows it.

app/construction_phase.{h,cpp}: S11's build-queue sub-pass, reported on its own line
because what blocks it is not what blocks the rest of the colony turn. It is blocked on
the per-system output term for points; it is NOT what the archived ship census waits on.

tests/game_sim/test_construction.cpp: 65 checks, including a corpus oracle the campaign
already owned and had not noticed -- zuul-turn16-noderoute -> zuul-turn17-rollpending is a
real consecutive-turn pair in which six orders complete and one is partially advanced.
The test SOLVES for the point total rather than assuming it, so a non-FIFO order, a
per-order budget or skip-instead-of-stop each falsify it.
2026-09-08 14:39:20 -04:00
alex
a1910becb0 lane E3: the per-system visibility record, the explored sweep and the system observed-stamp
Three phases, one input. A star system carries four per-player masks and three of
them agree on nearly every system of every save the corpus holds, so a model built
on the wrong one looks right until it does not. The gate is the DERIVED
active-presence mask -- fleet-here OR gate-here OR owner, recomputed on every
arrival and departure -- not the sticky one and not the explored one.

  S29 SystemObservedStamp   the system's own last-observed turn (whole function)
  T17 RebuildPlayerViewTree the per-(system, player) observation record: who saw
                            the system, on what turn, and what encounter was there
  T21 UpdateSurveyAndStats  the explored sweep: seen this turn implies surveyed

game/sim/visibility is pure and knows nothing about save shapes; app/visibility_phase
wires it to them. The mask is READ FROM THE SAVE and never rebuilt: neither reference
pair moves a mask leaf, so the loaded value is the value these phases would see, and
rebuilding it from an unmodelled movement pass would be a change with no evidence.

Measured, closed and regressed reported separately and never netted:

  turn1-state -> turn2-state    209 -> 158   closed 51, regressed 0
  turn2-state -> turn3-state    108 ->  87   closed 21, regressed 0

of which this lane closed 46 and 16 (the rest were already closed at main). The 46
are the brief's 32-leaf target in full -- 8 record counts, 8 player ids, 8 turn
stamps, 8 encounter ids -- plus 8 system stamps and 6 explored masks.

Three further pairs the model was never fitted to, all zero regressions:

  human-turn2 -> human-turn3    353 -> 311   closed 42   (a different game, 21 systems)
  zuul15 -> zuul16              276 -> 264   closed 12
  zuul16 -> zuul17              341 -> 329   closed 12

The corpus's one discriminating row is a host test rather than a comment: a system
whose last visiting fleet has gone carries the sticky and explored bits set, the
active bit clear, and a stamp frozen a turn behind. The test asserts the freeze AND
asserts what the wrong gate would have produced, so a future edit that swaps the
mask fails loudly instead of quietly agreeing with five saves.

Labelled hypothesis, with the workload named in the header: the encounter id is
recovered from the encounter fleet at the system, because the field the original
reads is set once at map generation and is not on the wire. It agrees on all six
encounter fleets in the corpus and no save can separate it -- none kills an
encounter while leaving its system visible.

Not written, deliberately: the colony-ownership stamp that moves beside these.
Its gate is demonstrably NOT the active mask (one system in the corpus has a zero
mask and moves it anyway), the formula is not held, so it is reported, not written.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ARBgSooAfokKUy6wKUKEyZ
2026-09-08 13:38:12 -04:00
Alex
405ba41a1e U: the SetResearched unlock cascade, and the prediction for the run that checks it
Pure module game/sim/techgraph: PrereqsMet (AND of ORs, empty group fails),
SetResearched (stamps, child-cost sweep, sticky turnAvailable, zero-cost
recursion) and the newly-available collector, all read out of 0x00581e10,
0x0057d8e0 and 0x00587cc3.

Wired into the B3 hook in compare mode only, over the scratch node copies:
four more node write-backs, the EVENT_TECHS_UNLOCKED list (still an input,
still nullptr when it could not be computed), the de-duplicating observed-tech
append and the one RNG word RollResearchEvent draws.

docs/U-unlock.md section 4 is the prediction, written before the build was staged.
2026-09-08 06:26:00 -04:00
alex
72ecc74684 game/sim: strategic formulas as pure functions (economy, research, colony, movement), 356 checks 2026-09-07 17:28:45 -04:00