From 9fa1ee26007624a2ebd89ea9e9794b3d9671deb3 Mon Sep 17 00:00:00 2001 From: alex Date: Tue, 8 Sep 2026 10:14:53 -0400 Subject: [PATCH] Z: the first instrumented battle in this campaign costs zero RNG words A Von Neumann encounter at Gallandro on turn 54 gave the workload the finding said did not exist. Auto-resolved, with P10 committed before the click. P10 predicted a non-zero tail cost and was wrong: res_no_battle flipped to 0 for the first time in 55 turns, the fleet was destroyed, and the generator moved by zero. The bracket residual stayed 0, so combat proper drew nothing either -- all 22 words were inside ProcessTurn, exactly as on a peaceful turn. That is the strong form of lane J's static reading, and it means a reimplementation can model a turn's RNG while modelling nothing about combat. One auto-resolved encounter against an NPC is not combat in general, and 10.2 says so at length. --- findings/control-flow/tail-rng-ledger.md | 71 +++++++++++++++++++----- 1 file changed, 58 insertions(+), 13 deletions(-) diff --git a/findings/control-flow/tail-rng-ledger.md b/findings/control-flow/tail-rng-ledger.md index 1d67655..0e87526 100644 --- a/findings/control-flow/tail-rng-ledger.md +++ b/findings/control-flow/tail-rng-ledger.md @@ -305,19 +305,9 @@ would have said so by construction rather than by anyone noticing. ## 8. What is not settled, listed as loudly as the results -* **The combat resolver has still never run under an instrument.** Every encounter this workload produced - had the no-battle flag set, so `ApplyEncounterResult` was a no-op every time; its measured 0 words says - nothing whatever about combat's RNG cost, and the residual-0 result above holds only for turns with no - battle. - - Lane J read all 7,641 bytes of it in parallel with this run (`combat-resolver.md`; and note **7,641**, not - the 7,499 Ghidra reports — method rule 17). Its conclusion pairs with this one exactly: **the resolver has - no unconditional draw.** All three sites in its subtree are conditional — a node-cannon `NextInt`, an - inlined `NextFloat` per back-engineering candidate, and a `NextInt` per successful roll of that. So lane - J's cheap first prediction is directly testable with this instrument: **a plain fleet battle with no node - cannon and no salvage should cost the same 18–22 words as a peaceful turn.** That is the next run this - hook family should do, and it needs a workload nobody has built yet: a save where two hostile fleets - actually meet. +* ~~The combat resolver has never run under an instrument.~~ **It has now, once — see §10. It cost 0 + words.** What remains unsettled is everything a single auto-resolved encounter cannot speak for; §10.2 + lists it. * **Node-line expiry did not fire.** See §9 for the quantified distance rather than an absence. * **A turn with a genuinely empty encounter vector was not observed** (§4). Both saves produce exactly one sighting encounter on every turn. The tail-runs-every-turn claim is settled; the no-encounters variant is @@ -410,3 +400,58 @@ Compare mode was **not** run on this hook for the same reason: with a prediction `ours` would advance the scratch generator by nothing, diff clean, and prove only that the harness works. The model becomes checkable the first time a line expires, and the descriptor is ready for that day. + +--- + +## 10. A real battle, measured — and it costs nothing + +The turn-54 End Turn of the long run stopped on an **Encounter at Gallandro**: the player's five ships +(3 DE Colonizer, 2 DE Armor) against a **Von Neumann**. That is the workload §8 said did not exist and lane +J's `combat-resolver.md` asked for — every encounter in 54 turns until this one had `res->+0x4` set, making +`ApplyEncounterResult` a whole-function no-op. **Auto Resolve** was chosen (the dialog's four options are +Fight Manually / Auto Resolve / Fight Manually If Opponent Does / Retreat), and the prediction was committed +to `sots-engine/docs/Z-tail-rng.md` §7 with the dialog still on screen and unclicked. + +| turn | `res_no_battle` | `ApplyEncounterResult` words | tail words | `ProcessTurn` words | bracket | residual | +|---|---|---|---|---|---|---| +| 52 | 1 | 0 | 0 | 18 | 18 | 0 | +| 53 | 1 | 0 | 0 | 18 | 18 | 0 | +| 54 | 1 | 0 | 0 | 16 | 16 | 0 | +| **55** | **0** | **0** | **0** | 22 | **22** | **0** | + +**P10 predicted a non-zero tail cost and was wrong.** The first battle this campaign has ever instrumented +moved the strategic generator by **zero words**, and the bracket residual stayed 0 — so combat proper +(`RunCombatRound` / the combat server, which run between `ProcessTurn` and the tail and are hooked by +nobody) drew nothing either. Every one of the turn's 22 words was inside `StrategyServer::ProcessTurn`, just +as on a peaceful turn. + +That is the *strong* form of lane J's reading. Lane J established from the instruction stream that the +resolver has **no unconditional draw** — its three sites are a node-cannon `NextInt`, an inlined `NextFloat` +per back-engineering candidate, and a `NextInt` per successful roll of that. This run shows that on an +ordinary encounter **none of the three fires**, and lane J's own cheap prediction — *a plain fleet battle +should cost the same as a peaceful turn* — holds exactly. + +### 10.1 Why this matters to the standalone + +A reimplementation that models a strategic turn's RNG and **nothing about combat** reproduces the generator +correctly through a battle. Combat's effect on the save is entirely in the state it writes, not in the +generator it advances. That is a much cheaper milestone than "read the 7,641-byte resolver first", and it +was not knowable before this run: the honest prior was lane K's "draw counts are entirely combat-dependent +and unknown". + +### 10.2 What one battle does not settle — and it is a lot + +* **One encounter, auto-resolved.** `Auto Resolve` may not take the same path as a manually fought battle; + the tactical engine has its own `Mars::CombatSim` generator at `sim+0x108` (§7) which nothing here + watches. A manually fought battle is a different experiment and has still never been run. +* **The opponent was a Von Neumann**, an NPC pseudo-player, not a rival empire's war fleet. No node cannon + was present, so R1 could not fire; whether R2's salvage roll was skipped because no candidate had a + non-zero salvage slot, or because the arm was not reached at all, is not distinguishable from a word + count of 0. +* **A cost of 0 is the easiest number to produce by accident.** It is exactly what a hook that compared + nothing would report. The reasons to believe it here are that the same hook reported 18–22 for + `ProcessTurn` on the same turn, that `res_no_battle` flipped to 0 for the first time in 55 turns on + exactly the turn the battle happened, and that the player's fleet was destroyed — the battle demonstrably + occurred. It is still one observation. +* **No `EVENT_*` or state-side check was made.** These hooks declare the generator and nothing else, so + this says the battle was RNG-free and says nothing about whether it was *computed* correctly.