From 54e36349a431806e38e1f8c1b72e8e38c148ae84 Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 18:50:27 +0000 Subject: [PATCH] re: the Phase_1/2/3 block of the settings family -- mostly per stage, not per phase 72 records, 94 distinct field names, 31 in every one (post-processing core, five Fog*, three ScreenColor*, SpaceSize, five Supply*, UnderCommandSquadron, the first BGOperate slot). Optional families sit in clean tiers: Nebura_* and ColorLayer* in 69/72, DOF_*/UnsharpMask_*/ExposureKey_* in 15/72, FinalPass* in 3/72. REFUTED: BGOperateFrameCount is NOT the number of BGOperateFrameName_i slots -- 36 of 72. What holds is Count <= slots, 72/72: a fixed slot array with a live count, the same shape as MessageCount under the 32-slot clamp. 68 of the fields common to all three phases NEVER differ in any of the 24 objects. The phase block is a per-stage environment block copied three times; what a phase change is actually for is the backdrop animation (BGOperate*) and the supply/command squadron assignment. MapPath is not in this family -- its 87 records per pack are 29 x 3, the resource manifest's Phase_N. Also fixed a non-determinism the verify loop caught: most_common() over a set iteration ordered ties differently per run; now sorted by (-count, name). Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 47 ++++++++++++++++++++ docs/re/INDEX.md | 2 +- docs/re/data/mission-scoring.txt | 35 +++++++++++++++ docs/re/structures/stage-settings-table.md | 50 +++++++++++++++++++--- tools/re-capture/mission_scoring.py | 50 +++++++++++++++++++++- 5 files changed, 177 insertions(+), 7 deletions(-) diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 89d92256..dfef859a 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4623,3 +4623,50 @@ in one object: once for scoring, once for damage. `UnderCommandSquadron`, `MapPath`) is the largest remaining piece and was not read. Which object is which stage, beyond `IsBoss16Enable`. `SplinterCell Count 5` is a name and a value only. + +--- + +## βœ…πŸ”΄ 2026-08-27 β€” the `Phase_1/2/3` block of the settings family + +The largest remaining piece of the stage-settings table, now read. +`stage-settings-table.md` extended; `mission_scoring.py` extended; artefact +`data/mission-scoring.txt` grows. Still twelve artefacts. + +**Docs checked first.** `stage-definition-table.md` owns `MapPath`/`MapMesh` β€” in +the **`Stage_SNN`** records, naming `.rgn`/`.col` MiscBin objects; `regn-map-grid.md` +and `stage-mission-tables.md` also carry `MapPath`. `UnderCommandSquadron`, +`BGOperateFrameName`, `SupplyMovie*`, `FinalPassVinetting`, `Nebura_*` and +`ScreenColor*` are owned by **nothing**. + +βœ… **72 records, 94 distinct field names, 31 in every one** β€” the post-processing +core, five `Fog*`, three `ScreenColor*`, `SpaceSize`, five `Supply*`, +`UnderCommandSquadron`, and the first `BGOperate` slot. Optional families sit in +clean tiers: `Nebura_*` (12) + `ColorLayer*` + `BGRotateScale` in **69/72**; +`DOF_*` (7), `UnsharpMask_*` (7), `ExposureKey_*` (4) in **15/72**; +`FinalPass*` (11) in **3/72**. + +πŸ”΄ **Refuted: `BGOperateFrameCount` is NOT the number of `BGOperateFrameName_i` +slots β€” 36 of 72.** I expected the `CountΒ·k` pattern that pinned the chatter pairs +and the cutscene pages, and it does not hold. What holds is +**`Count ≀ slots` in 72/72**: the slot array is fixed (1, 4 or 5 present) and +`Count` says how many are live β€” the same shape as `MessageCount` under the +32-slot clamp. Recorded as a refutation because the strong form is the one a port +would naively implement. + +βœ… **68 of the fields common to all three phases NEVER differ, in any of the 24 +objects.** The whole `Nebura_*`, `DOF_*`, `UnsharpMask_*`, `FinalPass*` families, +most fog/colour settings, `SupplySquadron2` and `SupplyRange` are **per stage, +duplicated into all three phase records**. What moves β€” never in more than 6 of 24 +β€” is `BGOperateFrameName_0` (6), `SupplySquadron1` (5), then `Brightness`, +`BrightPass*`, `ColorLayerA`, `BGOperateFrameCount`, `UnderCommandSquadron`, +`FirstAdaptedLuminance`, `BGOperateFrameName_2` (3 each). **The phase block is a +per-stage environment block copied three times**, and what a phase change is +actually for is the backdrop animation and the supply/command squadron. + +⚠️ **`MapPath` is not in this family.** Its 87 records per pack are 29 Γ— 3 β€” the +**resource** manifest's `Phase_N`, not the settings'. Arithmetic, not assumption: +the settings family is 24 objects, the resource family 29. + +🟑 Still open: which settings object is which stage beyond `IsBoss16Enable`; +`SplinterCell Count 5`; what `SpaceSize`, `GlareType` and `Nebura_*` mean +numerically. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 7470a48b..25b72769 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -100,7 +100,7 @@ files, which is how the same ground got covered twice. | [`structures/cutscene-message-table.md`](structures/cutscene-message-table.md) | Cutscene dialogue β€” speaker, portrait, on-screen seconds, audio cue per page | βœ… CONFIRMED, field count = 9Β·PageCount+2 for all 7 PageCounts, 1 252/1 252 caption keys match, 138 ids close both ways | | [`structures/preset-message-rules.md`](structures/preset-message-rules.md) | The reactive-chatter rule table β€” 64 events, the (message, Yes/No) pair payload, the `Sperkers` roster | βœ… CONFIRMED, the seven fields read off `sub_82213980`, phase tables merge non-additively (224 variants discarded); 8 firing sites share one entry, Probability is a per-cent roll against rand*100.0; Yes = one-shot line; queue is Priority-ordered and capped at 128; the cooldown is Interval + Fluctuation*rand held in the lingering queue node; the 14 player-facing events are voiced by the player set alone (13/15 Katana tables, 0/129 others) (three are seconds Γ—60, the Yes/No element is a u32 mask, hence the 32 clamp); MessageCountΒ·2 == positional count in 9 216/9 216; five naming routes union to 144/144 (six names predicted from the speaker roster before hashing, control 0/4); 2 388/2 405 message ids bind to a cue | | [`structures/mission-scoring.md`](structures/mission-scoring.md) | Mission scoring and the S/A/B/C/D rank thresholds β€” 24 tables x 3 difficulties | βœ… CONFIRMED, one 22-field schema over 72 records; difficulty moves 10 of 22 fields and never the RankScore thresholds; πŸ”΄ which object is which stage unresolved (0/28 names under 19 prefixes) | -| [`structures/stage-settings-table.md`](structures/stage-settings-table.md) | The per-stage settings object β€” Camera rigs, Player limits, Difficulty_* damage record | βœ… CONFIRMED, 24 settings objects vs 29 resource objects both using Phase_1/2/3; Camera is 13/14 fields constant; GravityFactor non-zero in 4 stages; IsBoss16Enable identifies one object | +| [`structures/stage-settings-table.md`](structures/stage-settings-table.md) | The per-stage settings object β€” Camera rigs, Player limits, Difficulty_* damage record | βœ… CONFIRMED, 24 settings objects vs 29 resource objects both using Phase_1/2/3; Camera is 13/14 fields constant; GravityFactor non-zero in 4 stages; IsBoss16Enable identifies one object; the Phase block is 94 fields of which 68 never vary between phases | | [`structures/isl-mission-timer.md`](structures/isl-mission-timer.md) | The mission timer β€” two sequential countdowns, not a limit and a warning | βœ… CONFIRMED β€” read from `sub_822639B8`; the threshold reading is | | [`structures/archive-naming.md`](structures/archive-naming.md) | Which archives the disc can name β€” 100% for menu paks, **0.0%** for the six 2D paks and `GP_READY_ROOM` | βœ… CONFIRMED β€” 6027 names Γ— 16 prefixes, bimodal | | [`structures/hud-config.md`](structures/hud-config.md) | The in-game HUD configuration β€” 16 records, 419 asset paths, and a per-stage `ResourceTable` | βœ… CONFIRMED β€” 0 of 419 config paths resolve as pak entries | diff --git a/docs/re/data/mission-scoring.txt b/docs/re/data/mission-scoring.txt index 4bcb8d4d..40941769 100644 --- a/docs/re/data/mission-scoring.txt +++ b/docs/re/data/mission-scoring.txt @@ -124,6 +124,41 @@ the rest of the settings object (24 objects): PlayerShieldDamageAdjustment 1 distinct 1.0=24 ShieldDamageAdjustment 2 distinct 0.5=23 1.0=1 +the Phase_1/2/3 block (72 records over 24 objects): + distinct field names : 94 + present in EVERY record : 31 + BGOperateFrameCount BGOperateFrameName_0 BGOperateScale_0 BloomScale BrightPassOffset BrightPassThreshold Brightness Contrast ExposureKeyValue FirstAdaptedLuminance FogLinear FogMaxAmount FogMaxDistance FogMinAmount FogMinDistance GlareType Hue LuminanceMax LuminanceMin Saturation ScreenColorB ScreenColorG ScreenColorR SpaceSize StarScale SupplyMovie1ID SupplyMovie2ID SupplyRange SupplySquadron1 SupplySquadron2 UnderCommandSquadron + field-count histogram : {31: 3, 58: 28, 59: 24, 61: 1, 62: 1, 77: 6, 78: 6, 90: 3} + BGOperateFrameCount == slots: 36/72 (REFUTED) + BGOperateFrameCount <= slots: 72/72 + slot histogram {1: 3, 4: 67, 5: 2} ; Count histogram {1: 9, 2: 14, 3: 16, 4: 31, 5: 2} + fields that DIFFER between phases (of 24 objects): + BGOperateFrameName_0 6 + SupplySquadron1 5 + BGOperateFrameCount 3 + BGOperateFrameName_2 3 + BrightPassOffset 3 + BrightPassThreshold 3 + Brightness 3 + ColorLayerA 3 + FirstAdaptedLuminance 3 + UnderCommandSquadron 3 + BGOperateScale_2 2 + BloomScale 2 + ColorLayerG 2 + ColorLayerR 2 + Contrast 2 + GlareType 2 + LuminanceMax 2 + StarScale 2 + SupplyMovie1ID 2 + BGRotateScale_2 1 + FogMaxAmount 1 + SpaceSize 1 + SupplyMovie2ID 1 + fields that NEVER differ between phases: 68 + AfterimageScale BGAnimation_0 BGAnimation_3 BGOperateFrameName_1 BGOperateFrameName_3 BGOperateScale_0 BGOperateScale_1 BGOperateScale_3 BGRotateScale_0 BGRotateScale_1 BGRotateScale_3 CCDLuminance ColorLayerB ContrailFirstCullingOff DOF_Enable DOF_FarAmount DOF_FarClip DOF_FarFocal DOF_NearAmount DOF_NearClip DOF_NearFocal ExposureKeyValue ExposureKey_BeginTime ExposureKey_BeginValue ExposureKey_EndTime ExposureKey_EndValue FinalPassBB FinalPassBG FinalPassBR FinalPassDB FinalPassDG FinalPassDR FinalPassDesaturation FinalPassToneRatio FinalPassType FinalPassVinetting FinalPassVinettingSharpness FogLinear FogMaxDistance FogMinAmount FogMinDistance Hue LuminanceMin Nebura_Count Nebura_EmitterRadius Nebura_EmitterRadiusJitter Nebura_MiddleAmount Nebura_MiddleFar Nebura_MiddleNear Nebura_NearAmount Nebura_NearClip Nebura_ResourceName Nebura_Size Nebura_SpeedLimit Nebura_SpeedScale Saturation ScreenColorB ScreenColorG ScreenColorR SupplyRange SupplySquadron2 UnsharpMask_B UnsharpMask_Far UnsharpMask_G UnsharpMask_Luminance UnsharpMask_Near UnsharpMask_Offset UnsharpMask_R + the commonest Normal record: CraftScore_Adjustment 1.0 FFPenalty_Craft_Basis 1.0 diff --git a/docs/re/structures/stage-settings-table.md b/docs/re/structures/stage-settings-table.md index 2be2fbc6..def48f92 100644 --- a/docs/re/structures/stage-settings-table.md +++ b/docs/re/structures/stage-settings-table.md @@ -77,11 +77,51 @@ almost everything is `1.0`, with `CollisionDamageRatio 0.8` and is expressed **twice** in the same object β€” once for *scoring* (`Score_{Easy,Normal,Hard}`) and once for *damage* (`Difficulty_*`). -## 🟑 Not settled +## βœ… The `Phase_1/2/3` block β€” mostly per stage, not per phase -* The `Phase_1/2/3` block of the settings family (31–90 named fields: - post-processing, fog, DOF, unsharp mask, supply squadrons and movies, - `UnderCommandSquadron`, `MapPath`) is **not read here** β€” it is the largest - remaining piece of this table. +**Settled 2026-08-27.** 72 records (24 objects Γ— 3), **94 distinct field names**, +of which **31 appear in every one**: the post-processing core +(`ExposureKeyValue`, `LuminanceMin/Max`, `FirstAdaptedLuminance`, +`BrightPassThreshold/Offset`, `BloomScale`, `StarScale`, `GlareType`, +`Brightness`, `Contrast`, `Saturation`, `Hue`), the five `Fog*` fields, the three +`ScreenColor*`, `SpaceSize`, the five `Supply*` (`SupplySquadron1/2`, +`SupplyMovie1/2ID`, `SupplyRange`), `UnderCommandSquadron`, and the first +`BGOperate` slot. Optional families come in fixed frequency tiers: +`Nebura_*` (12 fields) and `ColorLayer*`/`BGRotateScale`/`AfterimageScale` in +**69/72**, `DOF_*` (7) and `UnsharpMask_*` (7) and the four `ExposureKey_*` in +**15/72**, `FinalPass*` (11) in **3/72**. + +### πŸ”΄ Refuted: `BGOperateFrameCount` is not the number of name slots + +The obvious reading β€” a `Count` naming its indexed group, the pattern that pinned +the chatter pairs and the cutscene pages β€” **fails here: 36 of 72**. What holds is +the weaker statement, **`BGOperateFrameCount ≀ slot count` in 72/72**: the slot +array is fixed (1, 4 or 5 `BGOperateFrameName_i` present) and `Count` says how +many are live, exactly as `MessageCount` sits under the 32-slot clamp in +[`preset-message-rules.md`](preset-message-rules.md). `Count` runs 1…5. + +### βœ… What actually changes between phases + +Of the fields common to all three phase records of an object, **68 never differ +in any of the 24 objects**. The entire `Nebura_*`, `DOF_*`, `UnsharpMask_*` and +`FinalPass*` families, most of the fog and colour settings, `SupplySquadron2` and +`SupplyRange` are **per stage, duplicated into all three phases**. + +The short list that *does* move β€” and never in more than 6 of 24 objects β€” is: +`BGOperateFrameName_0` (6), `SupplySquadron1` (5), then `Brightness`, +`BrightPassOffset`/`Threshold`, `ColorLayerA`, `BGOperateFrameCount`, +`UnderCommandSquadron`, `FirstAdaptedLuminance`, `BGOperateFrameName_2` (3 each), +and a tail of bloom/glare/contrast/colour knobs at 2. + +So the phase block is best read as **a per-stage environment block copied three +times**, with the backdrop animation (`BGOperate*`) and the supply/command +squadron assignment as the parts a phase change is actually for. + +⚠️ **`MapPath` is not in this family.** Its 87 records per pack are 29 Γ— 3 β€” the +**resource** manifest's `Phase_N`, not the settings'. `stage-definition-table.md` +owns `MapPath`/`MapMesh` in the `Stage_SNN` records; what it does not say is that +the name recurs in the resource manifest's per-phase records. + +## 🟑 Not settled * Which object is which stage, beyond the single `IsBoss16Enable` hook. * `SplinterCell Count 5` β€” name and value only. diff --git a/tools/re-capture/mission_scoring.py b/tools/re-capture/mission_scoring.py index ebe5cf3f..d685786a 100644 --- a/tools/re-capture/mission_scoring.py +++ b/tools/re-capture/mission_scoring.py @@ -7,7 +7,7 @@ language pack, each holding Score_Easy / Score_Normal / Score_Hard with one Run: python3 mission_scoring.py > ../../docs/re/data/mission-scoring.txt """ -import sys, os, glob, collections +import sys, os, re, glob, collections sys.path.insert(0, os.path.dirname(os.path.abspath(__file__))) from unit_substructures import pak_entries import unitgroup as U @@ -120,6 +120,54 @@ def main(): print(' %-36s %2d distinct %s' % ( k, len(c), ' '.join('%s=%d' % (a, b) for a, b in c.most_common(5))[:70])) + # the Phase_1/2/3 block of the settings family + prec = [(h, i, U.named(d['Phase_%d' % i])) for h, d in full for i in (1, 2, 3) + if 'Phase_%d' % i in d] + allf = collections.Counter() + for _, _, n in prec: + for f in n: + allf[f] += 1 + core = sorted(f for f, c in allf.items() if c == len(prec)) + print() + print('the Phase_1/2/3 block (%d records over %d objects):' % (len(prec), len(full))) + print(' distinct field names : %d' % len(allf)) + print(' present in EVERY record : %d' % len(core)) + print(' %s' % ' '.join(core)) + print(' field-count histogram : %s' % dict(sorted( + collections.Counter(len(n) for _, _, n in prec).items()))) + + le = 0 + slots = collections.Counter() + cnts = collections.Counter() + for _, _, n in prec: + c = int(n['BGOperateFrameCount']) + k = sum(1 for f in n if re.fullmatch(r'BGOperateFrameName_\d+', f)) + slots[k] += 1 + cnts[c] += 1 + le += (c <= k) + eq = sum(1 for _, _, n in prec + if int(n['BGOperateFrameCount']) + == sum(1 for f in n if re.fullmatch(r'BGOperateFrameName_\d+', f))) + print(' BGOperateFrameCount == slots: %d/%d (REFUTED)' % (eq, len(prec))) + print(' BGOperateFrameCount <= slots: %d/%d' % (le, len(prec))) + print(' slot histogram %s ; Count histogram %s' % ( + dict(sorted(slots.items())), dict(sorted(cnts.items())))) + + moved = collections.Counter() + same = collections.Counter() + for h, d in full: + ns = [U.named(d['Phase_%d' % i]) for i in (1, 2, 3) if 'Phase_%d' % i in d] + if len(ns) != 3: + continue + for f in set(ns[0]) & set(ns[1]) & set(ns[2]): + (moved if len({n[f] for n in ns}) > 1 else same)[f] += 1 + never = sorted(f for f in same if f not in moved) + print(' fields that DIFFER between phases (of %d objects):' % len(full)) + for f, c in sorted(moved.items(), key=lambda kv: (-kv[1], kv[0])): + print(' %-28s %2d' % (f, c)) + print(' fields that NEVER differ between phases: %d' % len(never)) + print(' %s' % ' '.join(never)) + print() print('the commonest Normal record:') for k, v in sorted(dict(base).items()):