diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index dfef859a..85afd75c 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4670,3 +4670,48 @@ 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. + +--- + +## βœ…βœ… 2026-08-27 β€” all 24 settings objects named: `stage\StageParameter_S.tbl` + +**The grep-first rule paid for the whole iteration.** The queued item was the +29-object resource manifest β€” and reading the owning docs first showed it is +**already settled**: `challenge-mission-gate.md` counts exactly 29 stage records +(16 story + 6 tutorial + 6 challenge + `Test`, `StageResource` schema +`0x3c9ae32e`) and `stage-mission-tables.md` documents the six sub-records +including the `Phase_N` block with `MapPath`/`MapMesh`/`AsteroidDefinition`/ +`BackgroundResourceID` β€” the exact four fields I measured. Nothing to add. So the +iteration moved to the open question those docs make answerable. + +βœ… **24/24 named.** Hashing **every string that appears in any IDXD object on the +disc** β€” rather than guessing filenames β€” resolves all of them: +`stage\StageParameter_S01…S16.tbl` (story), `S24…S29` (challenge), +**`StageParameter_Tutorial.tbl` shared by all six tutorials**, and +`StageParameter_Test.tbl`. **16 + 6 + 1 + 1 = 24**, which is why the settings +family is 24 against 29 stage records: the tutorials do not get one table each. + +βœ… **`IsBoss16Enable` confirmed independently.** Predicted last iteration to mark +stage 16; the object carrying it is `0xEECC84F2` = `StageParameter_S16.tbl`. + +⚠️ **The names are NOT `AUTO_SETTINGS`'s.** That record lists +`stage01_settings.tbl` … `challenge06_settings.tbl`, and **none of those 28 +hashes to any of these 24 objects** (19 prefixes tried). Two different naming +schemes for stage-scoped parameters; the pak holds only `StageParameter_*`. + +With names attached, earlier findings acquire stages: **`GravityFactor` is +non-zero in S10 (700), S11 (400), S03 (250), S27 (250)**; the three objects that +zero their score multipliers are **S24, S27, S28**; the object without a +`SplinterCell` record is **`_Test`**. + +πŸ”΄ **Refuted on the way:** TOC order is not stage order. The 24 objects sit in a +separate TOC region (661, 905, 968–1030) from the 29 resource objects (622–771), +and the `IsBoss16Enable` object is 17th in TOC order, not 16th β€” the TOC is +hash-sorted, which is arbitrary with respect to stage. Also, neither the +`StageResource` template (13 fields) nor a real record (19) names the settings +table, so the link is not through the stage record. ⚠️ I first sampled +`res[0]` and got the `_Test` **template** β€” *never conclude from one sample*. + +**Method note:** the win was harvesting *all* disc strings and hashing them, +after guessing filenames had failed twice. The real name resembled nothing I +would have guessed. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 25b72769..a04f3f8c 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -99,8 +99,8 @@ files, which is how the same ground got covered twice. | [`structures/sound-cue-table.md`](structures/sound-cue-table.md) | The cue index in `tables.pak` β€” message id -> cue -> sound id -> `.slb` bank | βœ… CONFIRMED, 1 326/1 338 script message ids bind to a bank; SOUNDS and FILES agree on the same 12 absentees, 0 orphan files | | [`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; the Phase block is 94 fields of which 68 never vary between phases | +| [`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; βœ… all 24 named stage\StageParameter_S.tbl (+ _Tutorial shared by six, + _Test) | +| [`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; all 24 objects named StageParameter_S | | [`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 40941769..669840b5 100644 --- a/docs/re/data/mission-scoring.txt +++ b/docs/re/data/mission-scoring.txt @@ -59,6 +59,32 @@ 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'} +settings objects named from harvested disc strings: 24/24 + 0xe88492fc stage\StageParameter_S01.tbl + 0xe98abbfc stage\StageParameter_S02.tbl + 0xea90e4fc stage\StageParameter_S03.tbl + 0xeb970dfc stage\StageParameter_S04.tbl + 0xec9d36fc stage\StageParameter_S05.tbl + 0xeda35ffc stage\StageParameter_S06.tbl + 0xeea988fc stage\StageParameter_S07.tbl + 0xefafb1fc stage\StageParameter_S08.tbl + 0xf0b5dafc stage\StageParameter_S09.tbl + 0xe8a78ef2 stage\StageParameter_S10.tbl + 0xe9adb7f2 stage\StageParameter_S11.tbl + 0xeab3e0f2 stage\StageParameter_S12.tbl + 0xebba09f2 stage\StageParameter_S13.tbl + 0xecc032f2 stage\StageParameter_S14.tbl + 0xedc65bf2 stage\StageParameter_S15.tbl + 0xeecc84f2 stage\StageParameter_S16.tbl + 0xede957e8 stage\StageParameter_S24.tbl + 0xeeef80e8 stage\StageParameter_S25.tbl + 0xeff5a9e8 stage\StageParameter_S26.tbl + 0xf0fbd2e8 stage\StageParameter_S27.tbl + 0xf1020211 stage\StageParameter_S28.tbl + 0xf2082b11 stage\StageParameter_S29.tbl + 0xd4c05c15 stage\StageParameter_Test.tbl + 0x88720837 stage\StageParameter_Tutorial.tbl + 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'] diff --git a/docs/re/structures/mission-scoring.md b/docs/re/structures/mission-scoring.md index dbade273..30c9c567 100644 --- a/docs/re/structures/mission-scoring.md +++ b/docs/re/structures/mission-scoring.md @@ -67,18 +67,12 @@ difficulties) zero the multipliers, the penalties and `TimeBonus_Maximun` outright: missions that are not scored. Full per-object diffs are in the artefact. -## ❔ Which object is which stage +## βœ… Which object is which stage β€” settled -Not settled. `AUTO_SETTINGS` names 28 files (`stage01…16`, `tutorial01…06`, -`challenge01…06`, tags 1–16/18–23/24–29 β€” tag 17 absent, the cut S17), but -**none of those 28 names resolves to any of the 24 objects** under 19 prefixes -(bare, `stage\`, `Stage\`, `message\`, `settings\`, `2d\`, `ui\`, `system\`, -`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. -**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. +All 24 are named `stage\StageParameter_S.tbl`, plus `_Tutorial` (shared by +all six tutorials) and `_Test`. See +[`stage-settings-table.md`](stage-settings-table.md); that also explains 24 +against the 29 stage records, and confirms the `IsBoss16Enable` object is S16. + +⚠️ The names are **not** `AUTO_SETTINGS`'s `stageNN_settings.tbl`; none of those +28 hashes to any of these objects. diff --git a/docs/re/structures/stage-settings-table.md b/docs/re/structures/stage-settings-table.md index def48f92..fe011c33 100644 --- a/docs/re/structures/stage-settings-table.md +++ b/docs/re/structures/stage-settings-table.md @@ -17,6 +17,40 @@ same rendering fields again. `stage-definition-table.md` / `LodResource` / `MotionResource`. `SplinterCell` and `Difficulty_*` are owned by nothing. +## βœ… Every settings object is named β€” `stage\StageParameter_S.tbl` + +**Settled 2026-08-27, 24/24.** Hashing **every string that appears anywhere in +any IDXD object on the disc** (rather than guessing filenames) resolves all +twenty-four: + +| name | count | +|---|---| +| `stage\StageParameter_S01…S16.tbl` | 16 β€” the story campaign | +| `stage\StageParameter_S24…S29.tbl` | 6 β€” the challenge missions | +| `stage\StageParameter_Tutorial.tbl` | **1, shared by all six tutorials** | +| `stage\StageParameter_Test.tbl` | 1 β€” the developer stage | + +**That is exactly the 24, and it explains 24 against the 29 stage records** +([`challenge-mission-gate.md`](challenge-mission-gate.md) counts 29: +16 story + 6 tutorial + 6 challenge + `Test`): the six tutorials do not get one +table each, they **share a single `_Tutorial` table**, and `_Test` accounts for +the last. 16 + 6 + 1 + 1 = 24. + +βœ… **Independent confirmation of the `IsBoss16Enable` hook.** That field is +present in exactly one object, and the object is `0xEECC84F2` = +**`StageParameter_S16.tbl`** β€” predicted before the naming, confirmed by it. + +⚠️ The names are **not** what `AUTO_SETTINGS` lists. That record names +`stage01_settings.tbl` … `challenge06_settings.tbl`, and **none of those 28 +hashes to any of these 24 objects** under 19 prefixes. Two different naming +schemes for stage-scoped parameters; only `StageParameter_*` is what the pak +actually holds. + +With the names in hand the earlier value findings acquire stages: +**`GravityFactor` is non-zero in S10 (700), S11 (400), S03 (250) and S27 (250)**; +the three objects that zero their score multipliers are **S24, S27 and S28**; and +the one object without a `SplinterCell` record is **`_Test`**. + ## ⚠️ Two different families both use `Phase_1/2/3` Searching `GP_MAIN_GAME_E.pak` for objects containing a `Phase_1` record finds @@ -123,5 +157,4 @@ owns `MapPath`/`MapMesh` in the `Stage_SNN` records; what it does not say is tha 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 d685786a..42c3e890 100644 --- a/tools/re-capture/mission_scoring.py +++ b/tools/re-capture/mission_scoring.py @@ -98,6 +98,32 @@ def main(): except Exception: pass full.sort() + # name every settings object: harvest all IDXD strings and hash them + harvest = set() + for pp in sorted(glob.glob(os.path.join(DAT, '**', '*.pak'), recursive=True)): + for _, s2 in pak_entries(pp): + if s2[:4] != b'IDXD': + continue + try: + for r in U.parse(s2): + for _, nm, v in r['fields']: + harvest.add(v) + except Exception: + pass + want = {h for h, _ in tables} + named = {} + for c in harvest: + if not c or len(c) > 90: + continue + for pre in ('', 'stage\\', 'message\\'): + hh = U.name_hash(pre + c) + if hh in want: + named.setdefault(hh, pre + c) + print() + print('settings objects named from harvested disc strings: %d/%d' % (len(named), len(want))) + for h in sorted(named, key=lambda k: named[k]): + print(' %#010x %s' % (h, named[h])) + print() print('the rest of the settings object (%d objects):' % len(full)) shapes = collections.Counter(tuple(sorted(d)) for _, d in full)