From 7db341f76a1b7d829445a628ec2aad7c7bd9adb1 Mon Sep 17 00:00:00 2001 From: Sylpheed RE agent Date: Tue, 25 Aug 2026 10:04:20 +0000 Subject: [PATCH] re: UnitGroup member field n is a unit count, bounded by the formation FormationSet_S.tbl records are slot lists -- 1 + 8*FrameCount fields, exactly. Resolving every squadron's FormationID and comparing gives sum(n) <= FrameCount holding 1159/1160 across all 28 stages, 0 unresolved, with 539 filling the formation exactly. The single violation is a debug leftover (S20, AI_Test / MessageSet_test, Formation_1_only with n=2) and is recorded. The old 'n is not the _NN suffix of FormationID' observation was right but drew the wrong conclusion: the suffix IS FrameCount, so n=9 against _30 just means 9 units in 9 of 30 slots. Also: FormationID does not hash into its table (0/16). FormationSet carries a name roster record -- no FrameCount, fields are (tag, name, '') with the tags being the record keys -- the same convention as Enumerate_Squadrons. Second occurrence of 'keys are resolved by an in-table roster, not by hashing'. Does not close the 387-vs-300 gap, and the key derivation stays open. --- docs/re/structures/unit-group-table.md | 69 ++++++++++++++++++++++++-- tools/re-capture/freeze_waitobj.sh | 23 ++++++++- 2 files changed, 87 insertions(+), 5 deletions(-) diff --git a/docs/re/structures/unit-group-table.md b/docs/re/structures/unit-group-table.md index 5b4b47c6..96cf1495 100644 --- a/docs/re/structures/unit-group-table.md +++ b/docs/re/structures/unit-group-table.md @@ -62,9 +62,10 @@ Five named fields, always present and always these five: `UN_e007_ADAN_Turret`, `UN_bf001_TCAF_SchlosBase`. * message set β€” the squadron's radio chatter, e.g. `MessageSet_Ellen`, `MessageSet_ADAN_plA`. -* `n` β€” 🟑 **unidentified**; integer 1…30, dominated by 1 (735) and 9 (197). - It is *not* the `_NN` suffix of `FormationID` (`Formation_ADAN_Turret07_30` - pairs with `n = 9`). Plausibly a spawn or wing size, untested. +* `n` β€” βœ… **the number of units this tuple instantiates**, filling slots of the + squadron's formation. Integer 1…30, dominated by 1 (735) and 9 (197). See + *"What `n` counts"* below β€” the old note that it is "not the `_NN` suffix of + `FormationID`" was right but drew the wrong conclusion. * identity β€” 63 distinct: named pilots (`ELLEN`, `SANDRA`, `RAYMOND`, `YOJI`), ship nameplates (`NP_Charon`, `NP_Amalthea`, `NP_Olympus`), carrier tags (`Carrier_ADAN01`), cargo tags (`CARGO_1`), or empty. @@ -120,3 +121,65 @@ missing S17 stage record. positions are not in this file. The next places to look are `Formation_*.tbl` (the formation geometry and possibly its timing) and `EnumSquadron_Test.tbl`, both named by the stage record. + + +## βœ… What `n` counts β€” bounded by the formation, 1159 of 1160 + +`n` is the **number of units the member tuple instantiates**. The evidence is a +hard, falsifiable inequality against a table `UnitGroup` never mentions. + +`FormationSet_S.tbl` is the same IDXD container, and a formation record is a +**slot list**: one named field `FrameCount` plus 8 positional fields per slot β€” +`FrameCount = 4 β†’ 33` fields, `14 β†’ 113`, `30 β†’ 241`, `32 β†’ 257`, i.e. +`1 + 8Β·FrameCount`, exactly. + +Resolving each squadron's `FormationID` to its formation (below) and comparing: + +``` +sum(n) <= FrameCount: HOLDS 1159 VIOLATED 1 unresolved 0 + of the holders, sum(n) == FrameCount exactly: 539 +``` + +Across all 28 stages, **every** `FormationID` resolves and **every** squadron +but one fits its formation, with 46 % filling it exactly. A field unrelated to +formation size would not do that. + +**The one violation is a debug leftover**, recorded rather than swept up: S20 +(a tutorial stage), `Formation_1_only` (`FrameCount = 1`) with `n = 2` β€” +`AIID = AI_Test`, `msg = MessageSet_test`, no identity, unit +`UN_e015_ADAN_Puppy_2`. It is still a literal violation of the invariant. + +### βœ… The formation name's trailing number IS `FrameCount` + +`Formation_ADAN_Turret07_30` β†’ `FrameCount = 30`; `Formation_TCAF_ArrowHead02_32` +β†’ 32; `Formation_4_Bird` β†’ 4 (leading, this family has no suffix). So the old +observation that `n = 9` does not match the `_30` in +`Formation_ADAN_Turret07_30` is correct and **not** evidence against `n` being a +count: 9 units occupy 9 of that formation's 30 slots. + +### βœ… How `FormationID` resolves β€” the same roster trick + +`FormationID` does **not** hash into the table: `name_hash("Formation_4_Bird")` += `0x6286edad`, and the record key is `0x22a5eeed` β€” 0 of 16 resolve that way. +`FormationSet_S02.tbl` has **17 records for 16 formations**, and the extra one is +a **name roster**, exactly like `Enumerate_Squadrons` here: it carries no +`FrameCount` and its fields are `(tag, name, "")` triples whose **tags are the +record keys**. Find it by its missing `FrameCount`, map name β†’ key, done. + +That is the same convention twice in two different tables, which is worth +remembering for the next one: **a table's record keys are resolved by a roster +record inside the table, not by hashing the name.** + +### 🟑 What this does not settle + +* It shows `sum(n)` *fits* the formation, not that each unit is separately + instantiated. "Slots consumed" and "units spawned" are indistinguishable here. +* It does **not** close the 387-vs-~300 gap in + [roster-to-craft-link](../roster-to-craft-link.md). Ξ£`n` over all of Stage 02 + is 387 against 296–300 live craft. That count was measured mid-mission after + kills, and squadrons deploy across phases, so the two are not comparable as + they stand β€” but the earlier note rejected `n = 387` as *the* craft count on + the assumption that everything deploys at once, and that assumption is still + untested. +* The **key derivation** stays ❔. The roster makes it unnecessary, but the tag + is not `name_hash`, so a second hash function is still unidentified. diff --git a/tools/re-capture/freeze_waitobj.sh b/tools/re-capture/freeze_waitobj.sh index 6ab218cf..36f2574d 100755 --- a/tools/re-capture/freeze_waitobj.sh +++ b/tools/re-capture/freeze_waitobj.sh @@ -32,6 +32,15 @@ # emulator survives BETWEEN calls in a turn: # freeze_waitobj.sh boot [fly_s] boot, fly, capture `healthy`, leave running # freeze_waitobj.sh watch [secs] poll for the freeze, capture `frozen` +# freeze_waitobj.sh run [fly_s] boot + healthy + watch, end to end +# +# `run` exists because the whole experiment does not fit one Bash call and a +# `timeout` kills the process group -- it took the emulator down once. Launch it +# detached (`nohup ... &`) and poll the log instead. +# +# SYLPH_INDUCE=1 starts heavy_read.py the moment flight begins, to test whether +# the freeze follows our instrument rather than the mission (see the n=1 entry in +# mission-freeze-resume-spin.md). The control is the same script without it. set -u export HOME=/sylph-home/re SDL_AUDIODRIVER=dummy DISPLAY=:98 export PYTHONPATH=/sylph-home/.local/lib/python3.12/site-packages @@ -111,7 +120,7 @@ if [ "$MODE" = watch ]; then echo "NO FREEZE within ${SECS}s (still animating)"; exit 1 fi -# ---- boot mode ---- +# ---- boot / run mode ---- FLY="${FLY:-150}" "$SD/launch_mission.sh" fly || { echo "BOOT FAILED"; exit 1; } CFG=/tmp/nav-fz.json @@ -123,7 +132,17 @@ for t in 1 2 3; do /tmp/fz-pilot.log 2>&1 & echo "--- pilot flying"; break fi done +if [ "${SYLPH_INDUCE:-0}" = 1 ]; then + nohup python3 "$SD/heavy_read.py" 4000 2 cpu /tmp/heavy.log 2>&1 & + echo "--- INDUCER ON from flight start ($(date +%T))" +else + echo "--- inducer OFF (control run)" +fi echo "--- flying ${FLY}s to the healthy control capture" sleepfor "$FLY"; capture healthy python3 "$SD/waitobj_report.py" healthy -echo "BOOT PHASE DONE -- emulator left running; now: freeze_waitobj.sh watch" +echo "BOOT PHASE DONE -- emulator left running" +if [ "$MODE" = run ]; then + echo "--- watching for the freeze ($(date +%T))" + exec "$0" watch "${WATCH_S:-1500}" +fi