From b20609f81853d78ca08f45424dff6a065897e631 Mon Sep 17 00:00:00 2001 From: "Claude (auto)" Date: Thu, 27 Aug 2026 20:17:10 +0000 Subject: [PATCH] re: the GP_MAIN_GAME unnamed block -- 333 of 337 already owned, 4 genuinely new All six language copies carry the identical 337 unnamed IDXD hashes. Partitioned by record-name shape (53 shapes), 333 map onto families the corpus already documents: 131 weapon datasheets, 114 unit datasheets, 64 unit Faces tables, 10 unit message sets, 8 chatter rule tables, 5 enumerations/formations/placement. 114 is exactly the corpus's unit count (43 Craft + 71 Vessel) -- these are the tables the corpus has always worked with, reached by SHAPE because they have no names. Naming them adds nothing. The Enumerate object in each GP_MAIN_GAME_* is EMPTY, zero fields, which is why route 2 named 1283 entries in DefTables and 0 here. The 4 unclassified objects are new -> docs/re/structures/player-tuning-tables.md: the analog stick response curves (8 axes, 11 samples + a named Count = 11, tested 8/8; yaw/roll/throttle are the identity ramp, the shaping is all on pitch and the camera axes; adv_yaw is non-monotone and unexplained), the player craft's flight envelope (Booster, 50 fields -- the player side of the AA_/AV_ pair documented for NPCs -- plus TacticalManeuver, SpecialAttack + three gauge bands, SpecialWeapon, Misc), the Stage 16 boss (identified by Shell_S16Boss_* ids; Guardian HP 65000, Core 42000), and one unidentified Generic naming eff_n0071. New regenerator main_game_unnamed.py, 112-line artefact, byte-identical across two runs; the other twelve artefacts verify unchanged. Co-Authored-By: Claude Opus 5 (1M context) Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE --- docs/re/BACKLOG.md | 50 ++++++++ docs/re/INDEX.md | 1 + docs/re/data/main-game-unnamed.txt | 112 +++++++++++++++++ docs/re/structures/archive-naming.md | 24 ++++ docs/re/structures/player-tuning-tables.md | 81 ++++++++++++ tools/re-capture/main_game_unnamed.py | 136 +++++++++++++++++++++ 6 files changed, 404 insertions(+) create mode 100644 docs/re/data/main-game-unnamed.txt create mode 100644 docs/re/structures/player-tuning-tables.md create mode 100644 tools/re-capture/main_game_unnamed.py diff --git a/docs/re/BACKLOG.md b/docs/re/BACKLOG.md index 18452e0d..e5a3a4f9 100644 --- a/docs/re/BACKLOG.md +++ b/docs/re/BACKLOG.md @@ -4851,3 +4851,53 @@ identical **337 IDXD in each `GP_MAIN_GAME_*`**, 53 shapes led by `Shell+Weapon` `Effect+Explosion+Generic+Maneuver+Mass+SE+Shield+StructureCount` (44) and a chatter table (8) — weapon/unit/chatter families the corpus already documents under other names. Characterising them is the next item. + +--- + +## ✅ 2026-08-27 — the `GP_MAIN_GAME_*` unnamed block: 333 of 337 already owned, 4 genuinely new + +Item (a). New regenerator `tools/re-capture/main_game_unnamed.py` → +`docs/re/data/main-game-unnamed.txt` (112 lines, byte-identical across two runs). + +✅ **Control first:** all six language copies carry the **identical 337 unnamed +IDXD hashes**, so this is one set, not six. + +✅ **By record-name shape (53 shapes), 333 map onto documented families:** 131 +weapon datasheets (`weapon-datasheet-static.md`), **114 unit datasheets**, 64 +unit `Faces` tables (`unit-datasheet-static.md`), 10 unit message sets + 8 +chatter rule tables (`preset-message-rules.md`), 5 enumerations/formations/ +placement (`stage-mission-tables.md`, `weapon-struct-runtime.md`). + +🔑 **114 is exactly the corpus's unit count** (43 `Craft` + 71 `Vessel`) — these +are the tables the corpus already works with, reached **by shape because they +have no names**. Naming them would add nothing. + +⚠️ **The `Enumerate` object in each `GP_MAIN_GAME_*` is EMPTY** — zero fields. +That answers the open sub-question: it declares nothing, which is why route 2 +named 1 283 entries in `DefTables` and 0 here. + +✅✅ **The 4 unclassified objects are new** → new doc +`docs/re/structures/player-tuning-tables.md`: +1. **`955ca077` — the analog stick response curves.** 8 `AnalogRevice_*` records + (yaw/pitch/roll/throttle/adv_yaw/adv_roll/eye_yaw/eye_pitch), each **11 + positional values + a named `Count = 11`** — ⚠️ the `Count`-names-the-group + guess was TESTED and holds **8/8**. **yaw, roll and throttle are the identity + ramp**; the shaping is all on pitch and the camera axes. 🟡 `adv_yaw` peaks at + sample 6 and falls back to 0.208 — not a deflection curve; unexplained. Plus a + `Tweak` record with deadzones (`mov_stick_play 5000`, `eye_stick_play 6000`). +2. **`890e1be4` — the player craft's flight envelope.** `Booster` (50 fields) is + the *player* side of the `AA_`/`AV_` pair `unit-datasheet-static.md` + documents for NPCs, with afterburner twins; `MaximumVelocity 2100`, + `CruisingVelocity 612`. Plus `TacticalManeuver` (the `Turn180`/roll evades + whose morph poses the mesh work already found), `SpecialAttack` + + `SpecialAttackGauge_1/2/3`, `SpecialWeapon`, `Misc`. +3. **`0202269d` — the Stage 16 boss**, identified by its own shell ids + (`Shell_S16Boss_AAGun`/`_Laser`/`_HBeam`); `Guardian` HP 65 000, `Core` 42 000. + Cross-checks the `IsBoss16Enable` hook in `stage-settings-table.md`. +4. 🟡 **`a6c6c79b`** — one `Generic` record, one field named `eff_n0071`, empty + value. Not identified. + +🟡 Not settled: the `Tweak` units are inferred from magnitude, not read from a +consumer; `adv_yaw`'s meaning; object 4. **No loader was located for any of the +three new objects** — this is a data-side characterisation only, so which code +reads the curves is open. diff --git a/docs/re/INDEX.md b/docs/re/INDEX.md index 592554d5..c8e5e9c8 100644 --- a/docs/re/INDEX.md +++ b/docs/re/INDEX.md @@ -103,6 +103,7 @@ files, which is how the same ground got covered twice. | [`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; unnamed entries split by content — three UI paks have ZERO unnamed IDXD (artwork only), DefTables is the only real data gap (1295 LOD/motion tables); **route 2 = the `Enumerate` declaration tables** closes DefTables to 1413/1425 IDXD (99.2 %), 12 left; XPR2 manifests refuted as a naming source | +| [`structures/player-tuning-tables.md`](structures/player-tuning-tables.md) | Player tuning: 8 analog response curves, the 50-field `Booster` flight envelope, special attacks, the S16 boss | ✅ CONFIRMED — the 4 of 337 unnamed main-game objects that belong to no documented family | | [`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 | | [`structures/mission-script-manifest.md`](structures/mission-script-manifest.md) | `Stage\script.tbl`'s 11 non-`MISSION` fields, and the mission dialogue table they name | ✅ CONFIRMED — 1 of 7 values resolves (`message\MissionDialogMessage.tbl`, 200 records), controls in the same sweep | | [`structures/stage-numbering-and-player-craft.md`](structures/stage-numbering-and-player-craft.md) | The disc's stage numbering (S01–S16, S18–S29) and the player's craft per stage | ✅✅ CONFIRMED — two independent files close on 6 + 15 + 1 + 6 = 28 | diff --git a/docs/re/data/main-game-unnamed.txt b/docs/re/data/main-game-unnamed.txt new file mode 100644 index 00000000..08d87897 --- /dev/null +++ b/docs/re/data/main-game-unnamed.txt @@ -0,0 +1,112 @@ +# What the unnamed IDXD objects in GP_MAIN_GAME_* are +# Regenerate: python3 tools/re-capture/main_game_unnamed.py +# See docs/re/structures/archive-naming.md + +## CONTROL are the six language copies the same set? + GP_MAIN_GAME_D.pak 337 IDXD unnamed + GP_MAIN_GAME_E.pak 337 IDXD unnamed + GP_MAIN_GAME_F.pak 337 IDXD unnamed + GP_MAIN_GAME_I.pak 337 IDXD unnamed + GP_MAIN_GAME_J.pak 337 IDXD unnamed + GP_MAIN_GAME_S.pak 337 IDXD unnamed + identical hash sets across all six: True + +## The 337 unnamed objects by FAMILY (53 distinct record-name shapes) + x131 weapon datasheet owned by weapon-datasheet-static.md + x114 unit datasheet owned by unit-datasheet-static.md + x64 unit `Faces` table owned by unit-datasheet-static.md + x10 unit message set owned by preset-message-rules.md + x8 chatter rule table owned by preset-message-rules.md + x4 owned by - + x2 unit enumeration owned by stage-mission-tables.md + x1 asteroid groups owned by unit-datasheet-static.md + x1 formations owned by stage-mission-tables.md + x1 placement / route test owned by stage-mission-tables.md + x1 weapon enumeration owned by weapon-struct-runtime.md + classified: 333 / 337 + +## every shape, with its family + x90 weapon datasheet Shell Weapon + x64 unit `Faces` table Faces Generic + x44 unit datasheet Effect Explosion Generic Maneuver Mass SE Shield StructureCount + x24 weapon datasheet Shell ShellWake Weapon + x12 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x9 weapon datasheet AssortMissileParam Shell ShellWake Weapon + x9 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x8 chatter rule table ATTACK_IS_AVOIDED ATTACK_SUCCEEDED AVOIDED_BY_PLAYER AVOIDED_PLAYER_ATTACK AVOID_ATTACK + x7 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x6 weapon datasheet Shell ShellWake Weapon WhiskMissileParam + x4 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x3 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x3 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x3 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x2 unit datasheet Bridge_000 Bridge_001 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Maneuver Mass NS_Body SE Shield ShieldGene + x2 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Thruster_000 + x2 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 + x2 unit enumeration EnumUnit + x2 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 AnalogRevice_adv_roll AnalogRevice_adv_yaw AnalogRevice_eye_pitch AnalogRevice_eye_yaw A + x1 asteroid groups AsteroidGroup_000 AsteroidGroup_001 AsteroidGroup_002 AsteroidGroup_003 AsteroidGroup_00 + x1 weapon datasheet BezierMissileParam Shell ShellWake Weapon + x1 Booster Misc SpecialAttack SpecialAttackGauge_1 SpecialAttackGauge_2 SpecialAttackGauge_ + x1 unit datasheet Bridge_000 Effect Explosion Generic Hatch_000 Hatch_001 Hatch_002 Hatch_003 Maneuver Mas + x1 unit datasheet Bridge_??? Effect Explosion Generic Hatch_??? Maneuver Mass NS_* SE Shield ShieldGenerat + x1 Core Generic Guardian + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body NS_Body_2 SE Shield StructureCount + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_000 Turre + x1 unit datasheet Effect Explosion Generic Maneuver Mass NS_Body SE Shield StructureCount Turret_001 Turre + x1 unit datasheet Effect Explosion Generic Maneuver Mass SE Shield StructureCount Turret_000 + x1 weapon enumeration EnumWeapon + x1 formations Enumeration Formation_100 Formation_Fleet_01 Formation_Fleet_02 + x1 placement / route test Enumeration Placement_00 Placement_01 Placement_02 RouteTest_00 RouteTest_01 + x1 Generic + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set Generic Message_000 Message_001 Message_002 Message_003 Message_004 Message_005 Message_ + x1 unit message set MessageSet_test + x1 weapon datasheet Shell Weapon WhiskMissileParam + +## The UNCLASSIFIED objects, in full + + object 955ca077 (x1) + AnalogRevice_eye_yaw n=11 Count==n:True monotone=True 0.00 0.00 0.00 0.00 0.03 0.10 0.18 0.29 0.45 0.68 1.00 + AnalogRevice_pitch n=11 Count==n:True monotone=True 0.000 0.003 0.010 0.020 0.040 0.065 0.100 0.155 0.250 0.500 1.000 + AnalogRevice_yaw n=11 Count==n:True monotone=True 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 + AnalogRevice_adv_roll n=11 Count==n:True monotone=True 0.000 0.005 0.032 0.080 0.138 0.231 0.387 0.584 0.792 0.941 1.000 + AnalogRevice_throttle n=11 Count==n:True monotone=True 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 + AnalogRevice_roll n=11 Count==n:True monotone=True 0.0 0.1 0.2 0.3 0.4 0.5 0.6 0.7 0.8 0.9 1.0 + AnalogRevice_eye_pitch n=11 Count==n:True monotone=True 0.00 0.01 0.03 0.06 0.10 0.18 0.28 0.42 0.60 0.80 1.00 + AnalogRevice_adv_yaw n=11 Count==n:True monotone=False 0.000 0.016 0.052 0.143 0.482 0.940 1.000 0.916 0.611 0.382 0.208 + Tweak [('receipt_reverse_s', '20'), ('receipt_after_b', '20'), ('receipt_tgt_atk', '10'), ('receipt_tgt_near', '20'), ('mov_trigger_play', '20'), ('receipt_side_s', '0'), ('minimum_side_s', '0'), ('eye_stick_play', '6000'), ('YawMagForNormal', '2.0'), ('mov_stick_play', '5000'), ('order_cancel_time', '120'), ('receipt_match_spd', '2')] + + object 890e1be4 (x1) + SpecialAttack [('BulletTimeVolumePC', '0.4'), ('FireBirdPower', '1000.0'), ('ChargeMinimum', '10.0'), ('FireBirdPower_Ex', '4000.0'), ('GaugeVelocity', '25.0'), ('ConsumeEnergy', '4.0'), ('FireBirdAccelerationVomume', '200.0'), ('BulletTimeVolumeNPC', '0.1'), ('FireBirdRadius', '100.0f'), ('FireBirdTime', '0.75'), ('GaugeMaximum', '100.0'), ('RapidFire_CoolVolume', '1.5'), ('RapidFire_TimeVolume', '0.75'), ('FireBirdMaxVelocityVomume', '5.0')] + SpecialWeapon [('ShieldDoubler_Time', '15.0'), ('HPRegenerator_RegenSpeed', '100.0'), ('ExtraCartridge_Quantity', '2.0'), ('ShieldDoubler_Count', '5'), ('HPRegenerator_MinimumTime', '1.0'), ('ExtraCartridge_CoolVolume', '1.5'), ('ExtraCartridge_TimeVolume', '0.75')] + TacticalManeuver [('Turn180RequiredTime', '0.5'), ('RollLSlideLength', '350'), ('RollRSlideLength', '350'), ('RollLRequiredTime', '1.0'), ('RollRRequiredTime', '1.0'), ('RollToHorizonTime', '2.0'), ('TurnTargetRequiredTime', '2.0')] + Misc [('EffectPlacementFrameName', 'GN_mnt'), ('HPRegenEffectName', 'eff_s110'), ('ShieldDoublerEffectName_Using', 'eff_i003_f'), ('ShieldDoublerEffectName_Hit', 'eff_i004_f'), ('CollisionEffectName', 'eff_c001')] + SpecialAttackGauge_1 [('AttackTime_Minimum', '3.0'), ('AttackTime_Maximum', '10.0'), ('ChargeMaximum', '33.0')] + SpecialAttackGauge_2 [('AttackTime_Minimum', '0.5'), ('AttackTime_Maximum', '1.0'), ('ChargeMaximum', '66.0')] + SpecialAttackGauge_3 [('AttackTime_Minimum', '1.6'), ('AttackTime_Maximum', '5.0'), ('ChargeMaximum', '100.0')] + Booster [('AA_PitchPlus_Min', '375.0'), ('AA_PitchPlus_Max', '210.0'), ('PowerCutDeceleration', '15.0'), ('AB_AV_Yaw', '16.0'), ('YawDragFactor', '3.0'), ('AV_Roll_Min', '200.0'), ('AV_Roll_Max', '125.0'), ('AV_PitchPlus_Min', '150.0'), ('AB_ConsumeShield', '10.0'), ('AV_PitchPlus_Max', '70.0'), ('MaximumBank_Normal', '60.0'), ('MinimumVelocity', '100.0'), ('MaximumVelocity', '2100.0'), ('AB_ConsumeShield_Begin', '20.0'), ('AB_AA_Roll', '30.0'), ('AccPitchFactor', '30.0'), ('AB_AA_PitchPlus', '15.0'), ('DecPitchFactor', '30.0'), ('AA_PitchMinus_Min', '180.0'), ('AA_PitchMinus_Max', '100.00'), ('PowerCutConsumeShield', '75.0'), ('AB_AV_Roll', '40.0'), ('AB_AV_PitchPlus', '30.0'), ('RollDragFactor', '3.0'), ('AV_PitchMinus_Min', '75.0'), ('ReverseThrust_Vc', '-0.5'), ('AA_AxisMode_Min', '100.0'), ('AV_PitchMinus_Max', '40.0'), ('AA_AxisMode_Max', '20.0'), ('ArterBurner_Vc', '1.5'), ('DragFactorThreshold', '0.5'), ('AV_AxisMode_Min', '35.0'), ('CruisingVelocity', '612.0'), ('AA_Yaw_Min', '120.0'), ('AV_AxisMode_Max', '10.0'), ('AA_Yaw_Max', '65'), ('AV_Yaw_Min', '45.0'), ('AV_Yaw_Max', '25.0'), ('AB_AA_PitchMinus', '10.0'), ('Acceleration', '900'), ('ReverseThrust_Acc', '2.0'), ('Deceleration', '600'), ('ArterBurner_Acc', '2.0'), ('PitchDragFactor', '3.0'), ('SideThrustAcceleration', '1500.0'), ('AB_AV_PitchMinus', '16.0'), ('SideThrustVelocity_Max', '875.0'), ('AB_AA_Yaw', '10.0'), ('AA_Roll_Min', '750.0'), ('AA_Roll_Max', '475.0')] + + object 0202269d (x1) + Guardian [('LaserLifeTime', '5.0'), ('VerticalAccel', '8.0'), ('RotationAccel', '0.12'), ('AAGunShellID', 'Shell_S16Boss_AAGun'), ('HomingLaserPlayerCheckTime', '3.0'), ('GuardInterval', '1000.0'), ('MaxVerticalSpeed', '250.0'), ('MaxRotationSpeed', '72.0'), ('Radius', '2500.0'), ('DamageLevel1', '35000.0'), ('DamageLevel2', '20000.0'), ('LaserShellID', 'Shell_S16Boss_Laser'), ('GuardTime', '10.0'), ('InitHight', '0.0'), ('HP', '65000.0'), ('LaserIntervalTime', '7.0'), ('ChargeTime', '5.0'), ('HomingLaserShellID', 'Shell_S16Boss_HBeam'), ('InitRotation', '180.0')] + Core [('LockOnCancelTime', '3.0'), ('LockOnReleaseInterval', '15.0'), ('DamageLevel1', '25000.0'), ('DamageLevel2', '10000.0'), ('DamageLevel1BombTime', '2.0'), ('DamageLevel2BombTime', '1.0'), ('HP', '42000.0'), ('LockOnReleaseRandomInterval', '5.0'), ('DamageLevel1BombRandTime', '1.0'), ('DamageLevel2BombRandTime', '0.5')] + Generic [] + + object a6c6c79b (x1) + Generic [('eff_n0071', '')] diff --git a/docs/re/structures/archive-naming.md b/docs/re/structures/archive-naming.md index ba120cde..7caeab24 100644 --- a/docs/re/structures/archive-naming.md +++ b/docs/re/structures/archive-naming.md @@ -67,6 +67,30 @@ redo the work. is now iterated **sorted** — a plain `set` gave a different winner for collided hashes on each run and the artefact failed its own byte-identical check. +## ✅ What the `GP_MAIN_GAME_*` unnamed block is — 333 of 337 already owned (2026-08-27) + +Every language copy carries the **identical** 337 unnamed IDXD hashes (6/6). By +record-name shape (53 shapes), classified against the corpus: + +| | objects | owner | +|---|---:|---| +| weapon datasheets (`Shell+Weapon` ± `ShellWake`/missile params) | **131** | `weapon-datasheet-static.md` | +| unit datasheets (`…Maneuver…StructureCount` + turrets/bridges) | **114** | `unit-datasheet-static.md` | +| unit `Faces` tables | 64 | `unit-datasheet-static.md` | +| unit message sets (`Generic+Message_000…`) + `MessageSet_test` | 10 | `preset-message-rules.md` | +| chatter rule tables (`ATTACK_SUCCEEDED…`) | 8 | `preset-message-rules.md` | +| `EnumUnit` ×2, `EnumWeapon`, formations, placement/route-test, asteroid groups | 5 | `stage-mission-tables.md` etc. | +| **unclassified** | **4** | → [`player-tuning-tables.md`](player-tuning-tables.md) | + +🔑 **The 114 unit datasheets are exactly the corpus's 114 units** (43 `Craft` + +71 `Vessel`). These objects were never undiscovered data — they are the tables +the corpus has always worked with, reached **by record shape because they have +no names**. Naming them adds nothing; only the 4 unclassified ones were new. + +⚠️ **The `Enumerate` object in each `GP_MAIN_GAME_*` is EMPTY** — zero fields, +declaring nothing. It is a shipped placeholder, and it is why route 2 named +1 283 entries in `DefTables` and **0** here. + ## ✅✅ ROUTE 2 — the `Enumerate` declaration tables close `DefTables` (2026-08-27) The string harvest could never name `DefTables`, because **those names are not diff --git a/docs/re/structures/player-tuning-tables.md b/docs/re/structures/player-tuning-tables.md new file mode 100644 index 00000000..c0064a68 --- /dev/null +++ b/docs/re/structures/player-tuning-tables.md @@ -0,0 +1,81 @@ +# ✅ The player-tuning tables — analog response curves, booster/flight limits, special attacks + +- **Regenerate:** `python3 tools/re-capture/main_game_unnamed.py` → [`../data/main-game-unnamed.txt`](../data/main-game-unnamed.txt) +- **Where:** `GP_MAIN_GAME_{D,E,F,I,J,S}.pak`, as **unnamed** IDXD objects — identical hash sets in all six language copies. +- **Related:** [[flight-speed-law]], [[flight-controls-runtime]], [[unit-datasheet-static]], [[archive-naming]]. + +Found while characterising the 337 unnamed IDXD objects in each main-game pak +(see `archive-naming.md`). **333 of the 337 belong to families the corpus already +documents.** These are the four that do not. + +## ✅ The analog stick response curves — object `955ca077` + +Eight records, one per input axis, each **11 positional values plus a named +`Count = 11`**. ⚠️ `Count` naming an indexed group is not safe in general — here +it was tested and **holds 8/8**. + +| record | shape | curve | +|---|---|---| +| `AnalogRevice_yaw` | **identity** | `0.0 0.1 … 1.0` | +| `AnalogRevice_roll` | **identity** | `0.0 0.1 … 1.0` | +| `AnalogRevice_throttle` | **identity** | `0.0 0.1 … 1.0` | +| `AnalogRevice_pitch` | strongly eased | `0.000 0.003 0.010 0.020 0.040 0.065 0.100 0.155 0.250 0.500 1.000` | +| `AnalogRevice_eye_pitch` | eased | `0.00 0.01 0.03 0.06 0.10 0.18 0.28 0.42 0.60 0.80 1.00` | +| `AnalogRevice_eye_yaw` | eased, flat dead lead-in | `0.00 0.00 0.00 0.00 0.03 0.10 0.18 0.29 0.45 0.68 1.00` | +| `AnalogRevice_adv_roll` | S-curve | `0.000 0.005 0.032 0.080 0.138 0.231 0.387 0.584 0.792 0.941 1.000` | +| `AnalogRevice_adv_yaw` | **not monotone** | `0.000 0.016 0.052 0.143 0.482 0.940 1.000 0.916 0.611 0.382 0.208` | + +✅ **Three of the eight are the identity ramp** — yaw, roll and throttle are +unshaped, and the shaping the game does apply is all on **pitch and the camera +("eye") axes**. 🟡 `adv_yaw` **rises to 1.0 at sample 6 then falls back to +0.208**, so whatever it indexes, it is *not* a stick-deflection curve like the +other seven; it has the shape of an envelope over something else. Untested. + +The same object carries a `Tweak` record with what read as **deadzones and +input-timing constants** — `mov_stick_play = 5000`, `eye_stick_play = 6000` +(raw stick units), `order_cancel_time = 120`, `YawMagForNormal = 2.0`, and six +`receipt_*` / `minimum_side_s` values. 🟡 The units are inferred from magnitude, +not read from the consumer. + +## ✅ The player craft's flight envelope — object `890e1be4` + +Eight records. The big one is **`Booster`, 50 fields**, and it is the *player* +side of the `AA_`/`AV_` acceleration/velocity pair that +[[unit-datasheet-static]] documents for NPC craft: `AA_Yaw_Min/Max`, +`AV_Yaw_Min/Max`, `AA_Roll_*`, `AV_Roll_*`, `AA_PitchPlus/Minus_*`, +`AV_PitchPlus/Minus_*`, `AA_/AV_AxisMode_*`, each with an afterburner twin +(`AB_AA_Yaw`, `AB_AV_Roll`, …), plus `MinimumVelocity 100`, +`CruisingVelocity 612`, `MaximumVelocity 2100`, `Acceleration 900`, +`Deceleration 600`, `SideThrustAcceleration 1500`, `SideThrustVelocity_Max 875`, +three `*DragFactor 3.0` and `MaximumBank_Normal 60.0`. + +The other seven: + +- `TacticalManeuver` — `Turn180RequiredTime 0.5`, `RollL/RRequiredTime 1.0`, + `RollL/RSlideLength 350`, `RollToHorizonTime 2.0`, `TurnTargetRequiredTime 2.0`. + These name the **evade moves** whose morph poses the mesh work already found as + `_mnv*` / `_turn180` XBG7 variants. +- `SpecialAttack` + `SpecialAttackGauge_1/2/3` — the charge gauge in three bands + (`ChargeMaximum` 33 / 66 / 100, each with its own `AttackTime_Minimum/Maximum`), + and the "FireBird" attack (`FireBirdPower 1000`, `_Ex 4000`, `FireBirdTime + 0.75`, `FireBirdRadius 100.0f` — the game's own stray `f` suffix). + `BulletTimeVolumePC 0.4` / `NPC 0.1` are the slow-motion factors. +- `SpecialWeapon` — `ShieldDoubler_Time/Count`, `HPRegenerator_RegenSpeed/ + MinimumTime`, `ExtraCartridge_Quantity/CoolVolume/TimeVolume`. +- `Misc` — effect names (`eff_s110`, `eff_i003_f`, `eff_i004_f`, `eff_c001`) and + `EffectPlacementFrameName = GN_mnt`. + +## ✅ The Stage 16 boss — object `0202269d` + +Records `Guardian` (19 fields) and `Core` (10). `Guardian` names its three +weapons by shell id — `Shell_S16Boss_AAGun`, `Shell_S16Boss_Laser`, +`Shell_S16Boss_HBeam` — which is what identifies the object: **Stage 16**, the +stage `stage-settings-table.md` singles out via its unique `IsBoss16Enable` +field. `Guardian` HP 65 000 with `DamageLevel1/2` at 35 000 / 20 000; `Core` +HP 42 000 with levels at 25 000 / 10 000 and a lock-on release cycle +(`LockOnReleaseInterval 15.0` ± `LockOnReleaseRandomInterval 5.0`). + +## 🟡 The fourth: object `a6c6c79b` + +One record, `Generic`, one field — the **name** `eff_n0071` with an empty value. +An effect id and nothing else. Not identified. diff --git a/tools/re-capture/main_game_unnamed.py b/tools/re-capture/main_game_unnamed.py new file mode 100644 index 00000000..9755141b --- /dev/null +++ b/tools/re-capture/main_game_unnamed.py @@ -0,0 +1,136 @@ +#!/usr/bin/env python3 +"""What the 337 unnamed IDXD objects in each GP_MAIN_GAME_* pak actually are. + +`archive_naming.py` can name 667 of the 1004 IDXD entries in each language copy +of the main-game pak. This partitions the 337 it cannot by RECORD-NAME SHAPE, +maps each shape to the family the corpus already documents, and dumps in full +the two objects that belong to no documented family. +Regenerates docs/re/data/main-game-unnamed.txt. +""" +import sys, os, glob, re, collections + +HERE = os.path.dirname(os.path.abspath(__file__)) +sys.path.insert(0, HERE) +import unitgroup as U +from unit_substructures import pak_entries + +EXT = r'(prt|t32|rat|tbl|xpr|ttc|ttf|TTC|TTF|bin|col|rgn|prm)' +PRE = ['', '2d\\', 'ui\\', 'hud\\', 'Stage\\', 'stage\\', 'message\\', 'language\\', + 'prt\\', 'view\\'] + [l + '\\' for l in ('eng', 'jpn', 'fra', 'deu', 'ita', 'esp')] + +# shape -> (family, owning doc). Matched on a record name the shape must contain. +FAMILY = [ + ('Maneuver', 'unit datasheet', 'unit-datasheet-static.md'), + ('Weapon', 'weapon datasheet', 'weapon-datasheet-static.md'), + ('Faces', 'unit `Faces` table', 'unit-datasheet-static.md'), + ('Message_000', 'unit message set', 'preset-message-rules.md'), + ('ATTACK_SUCCEEDED', 'chatter rule table', 'preset-message-rules.md'), + ('MessageSet_test', 'unit message set', 'unit-group-table.md'), + ('EnumUnit', 'unit enumeration', 'stage-mission-tables.md'), + ('EnumWeapon', 'weapon enumeration', 'weapon-struct-runtime.md'), + ('AsteroidGroup_000', 'asteroid groups', 'unit-datasheet-static.md'), + ('Formation_Fleet_01', 'formations', 'stage-mission-tables.md'), + ('Placement_00', 'placement / route test', 'stage-mission-tables.md'), + ('Enumerate', 'EMPTY declaration table', 'archive-naming.md'), +] + +def name_map(paks): + names, decl = set(), set() + for pk in paks: + for h, b in pak_entries(pk): + for m in re.finditer((r'[A-Za-z0-9_\\.\-]{4,60}\.' + EXT).encode(), b): + names.add(m.group(0).decode('latin-1')) + if b[:4] == b'IDXD' and b'Enumerate' in b: + try: + recs = U.parse(b) + except Exception: + continue + if len(recs) == 1 and recs[0]['squadron'] == 'Enumerate': + for _t, fn, _v in recs[0]['fields']: + if fn: + decl.add(fn) + NAMES = {} + for n in sorted(names): + for p in PRE: + NAMES.setdefault(U.name_hash(p + n), p + n) + for n in sorted(decl): + NAMES.setdefault(U.name_hash(n + '.tbl'), n + '.tbl') + return NAMES + +def classify(shape): + for key, fam, doc in FAMILY: + if key in shape: + return fam, doc + return None, None + +def main(): + paks = sorted(glob.glob('/work/sylph_extract/**/*.pak', recursive=True)) + NAMES = name_map(paks) + mains = sorted(p for p in paks + if re.match(r'GP_MAIN_GAME_[A-Z]\.pak$', os.path.basename(p))) + + print("# What the unnamed IDXD objects in GP_MAIN_GAME_* are") + print("# Regenerate: python3 tools/re-capture/main_game_unnamed.py") + print("# See docs/re/structures/archive-naming.md") + + print("\n## CONTROL are the six language copies the same set?") + sets = {} + for pk in mains: + e = pak_entries(pk) + un = sorted(h for h, b in e if b[:4] == b'IDXD' and h not in NAMES) + sets[os.path.basename(pk)] = un + print(" %-24s %4d IDXD unnamed" % (os.path.basename(pk), len(un))) + ref = sets[os.path.basename(mains[0])] + print(" identical hash sets across all six: %s" + % all(v == ref for v in sets.values())) + + ents = pak_entries(mains[0]) + un = [(h, b) for h, b in ents if b[:4] == b'IDXD' and h not in NAMES] + sh, samp = collections.Counter(), {} + for h, b in un: + try: + k = tuple(sorted({r['squadron'] for r in U.parse(b)})) + except Exception: + k = ('',) + sh[k] += 1 + samp.setdefault(k, (h, b)) + + print("\n## The %d unnamed objects by FAMILY (%d distinct record-name shapes)" + % (len(un), len(sh))) + fam = collections.Counter() + docs = {} + for k, c in sh.items(): + f, d = classify(k) + fam[f or ''] += c + docs[f or ''] = d or '-' + for f, c in sorted(fam.items(), key=lambda kv: (-kv[1], kv[0])): + print(" x%-4d %-24s owned by %s" % (c, f, docs[f])) + print(" classified: %d / %d" % (sum(v for k, v in fam.items() + if k != ''), len(un))) + + print("\n## every shape, with its family") + for k, c in sorted(sh.items(), key=lambda kv: (-kv[1], kv[0])): + f, _d = classify(k) + print(" x%-4d %-24s %s" % (c, f or '', ' '.join(k)[:88])) + + print("\n## The UNCLASSIFIED objects, in full") + for k, c in sorted(sh.items(), key=lambda kv: (-kv[1], kv[0])): + if classify(k)[0]: + continue + h, b = samp[k] + print("\n object %08x (x%d)" % (h, c)) + for r in U.parse(b): + f = [(n, v) for _t, n, v in r['fields']] + pos = [v for n, v in f if n is None] + if pos and len(pos) >= len(f) - 1: # positional payload + Count + fl = [float(v) for v in pos] + mono = all(fl[i] <= fl[i + 1] for i in range(len(fl) - 1)) + cnt = dict((n, v) for n, v in f if n) + ok = cnt.get('Count') == str(len(pos)) + print(" %-26s n=%2d Count==n:%-5s monotone=%-5s %s" + % (r['squadron'], len(pos), ok, mono, ' '.join(pos))) + else: + print(" %-26s %s" % (r['squadron'], f)) + +if __name__ == "__main__": + main()