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) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
This commit is contained in:
@@ -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.
|
||||
|
||||
@@ -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 |
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
87
docs/re/structures/stage-settings-table.md
Normal file
87
docs/re/structures/stage-settings-table.md
Normal file
@@ -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.
|
||||
@@ -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()):
|
||||
|
||||
Reference in New Issue
Block a user