Files
Sylpheed/docs/re/structures/player-tuning-tables.md
Claude (auto) 909408f00d re: the object is PlayerParams, and sub_822F9498 is ITS loader -- not the unit loader
Resolving every string sub_822F9498 references, in code order, gives 90, and they
are exactly the 890e1be4 object's schema in its record order: Misc (5 effect
names), SpecialAttack (+17), TacticalManeuver (+7), SpecialWeapon (+8), Booster
(+50).  It never names Generic, Maneuver, Explosion, Shield or StructureCount, and
the string Maneuver has 0 xrefs.  One call site, from sub_821A6CF0, which is
itself called once and references the literal PlayerParams.

CORRECTION to unit-datasheet-static.md, which calls sub_822F9498 the
unit-definition loader.  It loads one object: the player parameter table.  Its
AA_/AV_ interleave still stands as a struct layout, but the struct is
PlayerParams's Booster record, not each unit's Maneuver.  What loads the 114 unit
Maneuver records is now an open question -- it uses none of these strings.

Ranking all 114 units by how many of Booster's 50 values they reproduce: the top
five are exactly the five _Player units, all at 39/50; the sixth drops to 13/50;
none matches 50/50.  The five players agree with each other on all 50 -- the three
player ships share one flight model.  Booster stands alone on 10 fields; the
eleventh, AA_Yaw_Max, is 65.0 vs 65, formatting not a value (last iteration's
"11 differ" over-counted).

Still open, and sharper: nothing selects Booster.  PlayerParams is loaded once,
unconditionally, no branch -- yet the measured flight tracks the unit Maneuver
(1.25/1.20/1.28) and misses Booster badly.  Both cannot govern the same craft; the
next test is a runtime watch.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PMRJjbxLqZtsb5Vb7KunPE
2026-08-27 20:48:56 +00:00

9.6 KiB
Raw Blame History

The player-tuning tables — analog response curves, booster/flight limits, special attacks

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 constantsmov_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, CORRECTED

My label from the first pass was wrong twice over. Kept, with the measurements that refuted it, because both errors are instructive.

Error 1 — Booster is not a new schema. Its 50 field names are a strict subset of the unit Maneuver record: 50/50 shared, zero Booster-only. (The first check compared against Generic and scored 0/50, which looked like a brand-new structure — the wrong record of a multi-record object is not a control.) CruisingVelocity appears in exactly 115 records disc-wide: 114 Maneuver + this one Booster.

Error 2 — it is not the profile the player actually flies. flight-speed-law measured the Delta Saber at ~125 / ~420 / ~1 530 (min / cruise / max). Against the two candidate tables:

min cruise max ratios measured ÷ table
unit Maneuver (5 units carry it) 100 350 1 200 1.25 / 1.20 / 1.28 — consistent
Booster 100 612 2 100 1.25 / 0.69 / 0.73 — not

The flight matches the unit Maneuver record, at one consistent scale. Booster is a second, faster profile — and the way it differs is the finding: 39 of its 50 values are identical to the player unit's Maneuver, and of the 11 that move, the three velocities are scaled by exactly ×1.75 (350 → 612, i.e. 612.5 rounded; 1 200 → 2 100; SideThrustVelocity_Max 500 → 875) and two accelerations by ×1.5 (Acceleration 600 → 900, SideThrustAcceleration 1 000 → 1 500). The rest: Deceleration 500 → 600, PowerCutDeceleration 10 → 15, PowerCutConsumeShield 20 → 75, AB_ConsumeShield_Begin 50 → 20, ArterBurner_Vc 2.0 → 1.5.

🟡 What that profile is for — an upgrade, a difficulty tier, a boost mode — is not settled. Nothing selects it on the disc that I could find.

The object's other records stand as described: TacticalManeuver (Turn180RequiredTime 0.5, RollL/RRequiredTime 1.0, RollL/RSlideLength 350, RollToHorizonTime 2.0), SpecialAttack + SpecialAttackGauge_1/2/3 (ChargeMaximum 33 / 66 / 100), SpecialWeapon, Misc.

The object is PlayerParams, and sub_822F9498 is ITS loader — not the unit loader

Resolving every string sub_822F9498 references, in code order, gives 90 of them, and they are exactly this object's schema, in this object's record order:

CollisionEffectName … EffectPlacementFrameName      (Misc, 5)
SpecialAttack      + 17 fields
TacticalManeuver   +  7
SpecialWeapon      +  8
Booster            + 50

It never names Generic, Maneuver, Explosion, Shield or StructureCount; the string Maneuver exists once in the image and has 0 xrefs. The function is called exactly once, from sub_821A6CF0 — which is itself called once and whose four strings include the literal PlayerParams.

🔴 Correction to unit-datasheet-static, which calls sub_822F9498 "the unit-definition loader". It reads one object, the player parameter table. Its AA_/AV_ interleave finding (AV_ at X, AA_ at X+8, five axes) still stands as a struct layout — but the struct is PlayerParams's Booster record, not each unit's Maneuver. Whatever loads the 114 unit Maneuver records does not use these name strings at all, and has not been found.

🟡 name_hash/tag_hash of PlayerParams under 7 prefixes × 6 spellings (84 combinations) hits the object's key 0x890E1BE4 0 times — the string names the table to the code, not the pak entry.

⚠️ The loader names 8 SpecialWeapon fields; the disc values 7. HPRegenerator_Quantity is named and never valued — the usual pattern.

All five player craft fly identically; Booster differs on ten fields

Ranking all 114 units by how many of the 50 Booster values they reproduce: the top five are exactly the five _Player unitsf001_T, two f001_T_Tt, f002_W, f004_A — all at 39/50, and the sixth-best unit drops to 13/50. No unit matches 50/50.

And the five players agree with each other on all 50 fields: the three player ships share one flight model. Booster stands alone on 10 of them (the eleventh, AA_Yaw_Max, is 65.0 vs 65 — a formatting difference, not a value; my earlier "11 differ" over-counted).

🟡 So what selects Booster? Nothing does, and that is the problem

PlayerParams is loaded once, unconditionally, at startup: one call site, one caller, no branch. So Booster is not a profile the game chooses — it is the only flight table this path loads.

🟡 That is in tension with the measurements and I have not resolved it. flight-speed-law clocked the player at ~125 / ~420 / ~1 530, which tracks the unit Maneuver 100 / 350 / 1 200 at a flat ~1.25 and misses Booster's 100 / 612 / 2 100 badly — and the RT-held run reached ~1 530, not anything near 2 100. Both tables cannot govern the same craft. Cheapest next test is a runtime one: watch which of the two constant sets reaches the live flight struct, and re-measure with the afterburner held.

🔴 BLOCKED: the analog-curve block is referenced by nothing

The AnalogRevice_* / Tweak schema is a second contiguous block at 0x820A119C0x820A1378, in the object's own order:

ControlTweakName
mov_stick_play  mov_trigger_play  eye_stick_play  receipt_after_b
receipt_reverse_s  receipt_tgt_near  receipt_tgt_atk  receipt_side_s
minimum_side_s  receipt_match_spd  order_cancel_time  YawMagForNormal
AnalogRevice_adv_roll … AnalogRevice_throttle          (8 curve names)

0 of its 28 strings has a code xref, and no instruction anywhere in the disassembly names a 0x820A1xxx operand. Control: the neighbouring block that sub_822F9498 reads scores 36/36. So this is a real absence, not a query mistake — but its cause is unexplained, and the reader is 🔴 unreachable this way.

⚠️ The twin-string-block trap, in the flesh. mov_stick_play and eye_stick_play each exist twice; only the second copy (0x820AA630, 0x820AA654) is referenced, by sub_822AE628 — which also references ControlTweak, Camera, Rendering, BackGround, WeaponChangeFrame and the DOF/bloom field names. And ControlTweak records exist only in GP_HANGAR_ARSENAL.pak (×6), while Tweak exists only in the six main-game paks (×1 each). sub_822AE628 reads the hangar's control table. Taking it for ours would have been a clean, wrong answer.

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.