From 26b7728ceb21caa7be15f18939d34da56ade24a4 Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 18:35:05 +0000 Subject: [PATCH] re: the rest of the stage-settings object -- cameras, player limits, difficulty 53 objects in GP_MAIN_GAME_E carry a Phase_1 record, in TWO families: 29 are the resource manifest the corpus already owns (Phase_N = 4 fields) and 24 are the settings table (Phase_N = 31-90 fields). That answers the 24-vs-28 puzzle left open by the scoring entry -- the 29 is a different table, not the settings. Camera: three chase rigs in metres, 13 of 14 fields identical in every stage -- Nose (0, 4.5, 7), Near (0, 10, 40), Far (0, 15, 80), FOV 0.92; only CameraFar varies, once. Player: BulletLimit 512 / HomingLimit 256 / LaserLimit 32 and the three 0.30 axis adjustments are constant, GravityFactor is non-zero in 4 of 24 stages, and IsBoss16Enable appears in exactly ONE object -- the first per-stage handle for a family whose filenames do not resolve. Difficulty_Easy/Normal/Hard is a SECOND difficulty record (8 damage and guidance multipliers), separate from Score_*. New doc structures/stage-settings-table.md; mission_scoring.py extended. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 48 ++++++++++++ docs/re/INDEX.md | 1 + docs/re/data/mission-scoring.txt | 65 ++++++++++++++++ docs/re/structures/mission-scoring.md | 8 +- docs/re/structures/stage-settings-table.md | 87 ++++++++++++++++++++++ tools/re-capture/mission_scoring.py | 34 +++++++++ 6 files changed, 241 insertions(+), 2 deletions(-) create mode 100644 docs/re/structures/stage-settings-table.md diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index de3f458..89d9225 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4575,3 +4575,51 @@ not assume the missing four are the tutorials without opening them. positive last iteration are the same offsets that made it findable — but only after switching from *offset* to *name*. Read what a loader names, not where it stores. + +--- + +## ✅ 2026-08-27 — the rest of the stage-settings object: cameras, player limits, difficulty + +Continues the scoring entry. New doc +[`structures/stage-settings-table.md`](structures/stage-settings-table.md); +`mission_scoring.py` extended; artefact `data/mission-scoring.txt` grows by the +whole-object census. **Still twelve artefacts.** + +**Docs checked first.** `isl-condition-builtins.md` owns `Camera`, +`ControlTweak`, `Rendering` **as records in `GP_HANGAR_ARSENAL.pak`** — the +game-wide defaults. **What it does not say** is that `Camera` recurs *per stage* +in the `GP_MAIN_GAME_*` packs. `stage-definition-table.md` / +`challenge-mission-gate.md` own `StageResource`; `stage-mission-tables.md` owns +`LodResource`/`MotionResource`. `SplinterCell` and `Difficulty_*` are unowned. + +⚠️ **Two families share the `Phase_1/2/3` record names.** 53 objects in +`GP_MAIN_GAME_E.pak` contain a `Phase_1`: **29** are the resource manifest +(`StageResource`/`LodResource`/`MotionResource`, `Phase_N` = 4 named fields) and +**24** are the settings table (`Camera`/`Player`/`SplinterCell`/`Difficulty_*`/ +`Score_*`, `Phase_N` = 31–90 fields). Counting without partitioning would have +merged them — *a record name is a file-local role*, again. + +✅ **This answers the 24-vs-28 puzzle** left open last iteration: the settings +family is 24; the **29** — matching `AUTO_SETTINGS`'s 1…29 tag range — is a +different table. + +✅ **`Camera`: three chase rigs, and 13 of 14 fields are identical in every +stage.** Nose (0, 4.5, 7.0), Near (0, 10, 40), Far (0, 15, 80), all FOV 0.92; +`CameraNear` 1.0, `CameraFar` 3 000 000.0 with a single stage at 500 000.0. +World unit is 1 m, so these are metres — directly portable. + +✅ **`Player`:** `BulletLimit` 512, `HomingLimit` 256, `LaserLimit` 32, +`Pitch/Roll/YawAdjustment` 0.30, `AirDragFactor` 1.0 — all constant across 24. +**`GravityFactor` is non-zero in 4 of 24 stages** (250/400/700), and +**`IsBoss16Enable` appears in exactly ONE object**, which identifies it as stage +16 — the first per-stage handle for a family whose filenames do not resolve. + +✅ **`Difficulty_Easy/Normal/Hard` is a SECOND difficulty record**, 8 damage and +guidance multipliers, separate from `Score_*`. So difficulty is expressed twice +in one object: once for scoring, once for damage. + +🟡 **Not settled:** the `Phase_1/2/3` block of the settings family (31–90 fields +— post-processing, fog, DOF, unsharp mask, supply squadrons/movies, +`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. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index e4e1d00..7470a48 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -100,6 +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/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 c8e2df5..4bcb8d4 100644 --- a/docs/re/data/mission-scoring.txt +++ b/docs/re/data/mission-scoring.txt @@ -59,6 +59,71 @@ objects whose Normal record differs from the commonest Normal record: 0xf1020211 {'CraftScore_Adjustment': '0.0', 'FFPenalty_Craft_Basis': '0.0', 'FFPenalty_Craft_Maximum': '0', 'FFPenalty_Vessel_Basis': '0.0', 'FFPenalty_Zessel_Maximum': '0', 'KillBonus_Maximum': '0', 'LostWingmanPenalty': '0', 'MainMissionBonus': '0', 'MainMissionCount': '1', 'OtherScore_Adjustment': '0.0', 'RankScore_A': '8276', 'RankScore_B': '6897', 'RankScore_C': '4138', 'RankScore_D': '690', 'RankScore_S': '9999', 'TimeBonus_Basis_Maximum': '1200', 'TimeBonus_Basis_Minimum': '330', 'TimeBonus_Maximun': '10000', 'VesselScore_Adjustment': '0.0'} 0xf2082b11 {'FriendlyVesselDamageBonus': '1000', 'KillBonus_Maximum': '0', 'MainMissionBonus': '0', 'MainMissionCount': '1', 'RankScore_A': '100001', 'RankScore_B': '80001', 'RankScore_C': '65001', 'RankScore_D': '50001', 'RankScore_S': '120001', 'TimeBonus_Maximun': '0'} +the rest of the settings object (24 objects): + record-name sets: 2 + x23 ['Camera', 'Difficulty_Easy', 'Difficulty_Hard', 'Difficulty_Normal', 'Phase_1', 'Phase_2', 'Phase_3', 'Player', 'Score_Easy', 'Score_Hard', 'Score_Normal', 'SplinterCell'] + x1 ['Camera', 'Difficulty_Easy', 'Difficulty_Hard', 'Difficulty_Normal', 'Phase_1', 'Phase_2', 'Phase_3', 'Player', 'Score_Easy', 'Score_Hard', 'Score_Normal'] + + Camera — present in 24/24 objects, 14 fields + CameraFar 2 distinct 3000000.0=23 500000.0=1 + CameraNear 1 distinct 1.0=24 + FarCameraFOV 1 distinct 0.92=24 + FarCameraPosX 1 distinct 0.0=24 + FarCameraPosY 1 distinct 15.0=24 + FarCameraPosZ 1 distinct 80.0=24 + NearCameraFOV 1 distinct 0.92=24 + NearCameraPosX 1 distinct 0.0=24 + NearCameraPosY 1 distinct 10.0=24 + NearCameraPosZ 1 distinct 40.0=24 + NoseCameraFOV 1 distinct 0.92=24 + NoseCameraPosX 1 distinct 0.0=24 + NoseCameraPosY 1 distinct 4.5=24 + NoseCameraPosZ 1 distinct 7.0=24 + + Player — present in 24/24 objects, 9 fields + AirDragFactor 1 distinct 1.0=24 + BulletLimit 1 distinct 512=24 + GravityFactor 4 distinct 0.0=20 250.0=2 700.0=1 400.0=1 + HomingLimit 1 distinct 256=24 + IsBoss16Enable 1 distinct Yes=1 + LaserLimit 1 distinct 32=24 + PitchAdjustment 1 distinct 0.30=24 + RollAdjustment 1 distinct 0.30=24 + YawAdjustment 1 distinct 0.30=24 + + SplinterCell — present in 23/24 objects, 1 fields + Count 1 distinct 5=23 + + Difficulty_Easy — present in 24/24 objects, 8 fields + AntiPlayerMissileGuidanceAdjustment 2 distinct 0.5=23 1.0=1 + CollisionDamageRatio 2 distinct 0.8=23 0.0=1 + DamageAdjustment 3 distinct 2.0=22 1.5=1 1.0=1 + FriendlyFireAdjustment 2 distinct 0.0=23 1.0=1 + PlayerDamageAdjustment 3 distinct 0.5=22 0.75=1 1.0=1 + PlayerMissileGuidanceAdjustment 2 distinct 2.0=23 1.0=1 + PlayerShieldDamageAdjustment 2 distinct 0.5=23 1.0=1 + ShieldDamageAdjustment 2 distinct 2.0=23 1.0=1 + + Difficulty_Normal — present in 24/24 objects, 8 fields + AntiPlayerMissileGuidanceAdjustment 1 distinct 1.0=24 + CollisionDamageRatio 2 distinct 0.8=23 0.0=1 + DamageAdjustment 1 distinct 1.0=24 + FriendlyFireAdjustment 2 distinct 0.5=23 1.0=1 + PlayerDamageAdjustment 1 distinct 1.0=24 + PlayerMissileGuidanceAdjustment 1 distinct 1.0=24 + PlayerShieldDamageAdjustment 1 distinct 1.0=24 + ShieldDamageAdjustment 1 distinct 1.0=24 + + Difficulty_Hard — present in 24/24 objects, 8 fields + AntiPlayerMissileGuidanceAdjustment 2 distinct 1.25=23 1.0=1 + CollisionDamageRatio 2 distinct 0.8=23 0.0=1 + DamageAdjustment 1 distinct 1.0=24 + FriendlyFireAdjustment 1 distinct 1.0=24 + PlayerDamageAdjustment 1 distinct 1.0=24 + PlayerMissileGuidanceAdjustment 2 distinct 0.75=23 1.0=1 + PlayerShieldDamageAdjustment 1 distinct 1.0=24 + ShieldDamageAdjustment 2 distinct 0.5=23 1.0=1 + the commonest Normal record: CraftScore_Adjustment 1.0 FFPenalty_Craft_Basis 1.0 diff --git a/docs/re/structures/mission-scoring.md b/docs/re/structures/mission-scoring.md index 79b2bb0..dbade27 100644 --- a/docs/re/structures/mission-scoring.md +++ b/docs/re/structures/mission-scoring.md @@ -76,5 +76,9 @@ Not settled. `AUTO_SETTINGS` names 28 files (`stage01…16`, `tutorial01…06`, `dat\`, `prt\`, `view\`, `language\`, `eng\`, `script\`, `Script\`, `mission\`, `Mission\`, `param\`, `Param\`). So either the settings files are loaded outside the pak namespace, or the scoring objects are keyed by something else entirely. -**24 objects against 28 declared files is itself unexplained** — do not assume -the missing four are the tutorials without checking. +**The 24-vs-28 puzzle is answered** by +[`stage-settings-table.md`](stage-settings-table.md): the same pack holds a +*second* family of **29** objects that also uses `Phase_1/2/3` — the resource +manifest — so the settings family really is 24 and the 29 is a different table. +The only per-stage handle found so far is `IsBoss16Enable`, present in exactly +one object. diff --git a/docs/re/structures/stage-settings-table.md b/docs/re/structures/stage-settings-table.md new file mode 100644 index 0000000..2be2fbc --- /dev/null +++ b/docs/re/structures/stage-settings-table.md @@ -0,0 +1,87 @@ +# The per-stage settings table — cameras, player limits, difficulty + +**✅ Settled 2026-08-27** (static; regenerator +`tools/re-capture/mission_scoring.py`, artefact +[`data/mission-scoring.txt`](../data/mission-scoring.txt) — the same object the +scoring block lives in, see [`mission-scoring.md`](mission-scoring.md)). + +Loader: `sub_8230D1F8`, whose 122 field names identify it. + +**Docs checked.** [`isl-condition-builtins.md`](isl-condition-builtins.md) owns +`Camera`, `ControlTweak` and `Rendering` **as records in +`GP_HANGAR_ARSENAL.pak`** — the game-wide defaults (`NoseCameraFOV 0.92`, +`BloomScale 0.5`, …). **What it does not say** is that `Camera` recurs **per +stage** in the `GP_MAIN_GAME_*` packs, or that the per-phase records carry the +same rendering fields again. `stage-definition-table.md` / +`challenge-mission-gate.md` own `StageResource`; `stage-mission-tables.md` owns +`LodResource` / `MotionResource`. `SplinterCell` and `Difficulty_*` are owned by +nothing. + +## ⚠️ Two different families both use `Phase_1/2/3` + +Searching `GP_MAIN_GAME_E.pak` for objects containing a `Phase_1` record finds +**53**, in two shapes — a textbook case of *a record name being a file-local +role*: + +| objects | record names | `Phase_N` named fields | +|---|---|---| +| **29** | `StageResource`, `LodResource`, `MotionResource`, `Phase_1/2/3` | **4** | +| **24** | `Camera`, `Player`, `SplinterCell`, `Difficulty_{Easy,Normal,Hard}`, `Score_{Easy,Normal,Hard}`, `Phase_1/2/3` | **31–90** | + +The 29 are the **resource manifest** the corpus already owns. The 24 are the +**settings** table this document describes. Counting `Phase_1` records without +partitioning first would have merged them. + +That also answers the count left open by `mission-scoring.md`: the settings +family really is **24**, and the **29** — matching `AUTO_SETTINGS`'s tag range +1…29 — is a *different* family. 23 of the 24 carry `SplinterCell`; one does not. + +## `Camera` — three chase rigs, in metres + +Present in 24/24. Thirteen of its fourteen fields are **identical in every +stage**: + +``` +NoseCamera Pos (0.0, 4.5, 7.0) FOV 0.92 +NearCamera Pos (0.0, 10.0, 40.0) FOV 0.92 +FarCamera Pos (0.0, 15.0, 80.0) FOV 0.92 +CameraNear 1.0 CameraFar 3000000.0 (one stage: 500000.0) +``` + +World unit is 1 metre (corpus), so these are metres behind and above the ship, +and the near/far planes are 1 m / 3000 km. Only `CameraFar` varies, and only once. + +## `Player` — projectile pools and gravity + +``` +BulletLimit 512 HomingLimit 256 LaserLimit 32 (constant, 24/24) +PitchAdjustment / RollAdjustment / YawAdjustment 0.30 (constant) +AirDragFactor 1.0 (constant) +GravityFactor 0.0 in 20 stages; 250.0, 400.0, 700.0 in three others +IsBoss16Enable Yes — in exactly ONE object +``` + +Two findings here. **`GravityFactor` is non-zero in 4 of 24 stages** — those +missions pull the player. And **`IsBoss16Enable` appears in exactly one object**, +which identifies that object as **stage 16**; it is the only per-stage handle +found so far for a family whose filenames do not resolve. + +## `Difficulty_Easy` / `_Normal` / `_Hard` — a second difficulty record + +Eight fields, separate from the `Score_*` records: `DamageAdjustment`, +`ShieldDamageAdjustment`, `FriendlyFireAdjustment`, `PlayerDamageAdjustment`, +`PlayerShieldDamageAdjustment`, `PlayerMissileGuidanceAdjustment`, +`AntiPlayerMissileGuidanceAdjustment`, `CollisionDamageRatio`. On `Normal` +almost everything is `1.0`, with `CollisionDamageRatio 0.8` and +`FriendlyFireAdjustment 0.5` (each with one stage that differs). So difficulty +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 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. +* 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 a3a092e..ebe5cf3 100644 --- a/tools/re-capture/mission_scoring.py +++ b/tools/re-capture/mission_scoring.py @@ -86,6 +86,40 @@ def main(): if t['Score_Normal'][f] != dict(base)[f]} print(' %#010x %s' % (h, diff)) + # the rest of the settings object: Camera / Player / SplinterCell / Difficulty_* + OTHER = ('Camera', 'Player', 'SplinterCell', + 'Difficulty_Easy', 'Difficulty_Normal', 'Difficulty_Hard') + full = [] + for h, s2 in pak_entries(P): + if s2[:4] != b'IDXD' or KEY.encode() not in s2: + continue + try: + full.append((h, {r['squadron']: r for r in U.parse(s2)})) + except Exception: + pass + full.sort() + print() + print('the rest of the settings object (%d objects):' % len(full)) + shapes = collections.Counter(tuple(sorted(d)) for _, d in full) + print(' record-name sets: %d' % len(shapes)) + for t, c in shapes.most_common(): + print(' x%-3d %s' % (c, list(t))) + for rn in OTHER: + vals = collections.defaultdict(collections.Counter) + n = 0 + for _, d in full: + if rn not in d: + continue + n += 1 + for k, v in U.named(d[rn]).items(): + vals[k][v] += 1 + print() + print(' %s — present in %d/%d objects, %d fields' % (rn, n, len(full), len(vals))) + for k in sorted(vals): + c = vals[k] + print(' %-36s %2d distinct %s' % ( + k, len(c), ' '.join('%s=%d' % (a, b) for a, b in c.most_common(5))[:70])) + print() print('the commonest Normal record:') for k, v in sorted(dict(base).items()):