Gyro: damage fan-out FUN_004b2980 -- the cockpit hit-BOUNCE is live (task #56)

Re-disassembled the unexported gap 0x4b2980-0x4b2d8b byte-exact (capstone over
section_dump.txt) and reconstructed Gyroscope::ApplyDamageResponse:
- zero-damage + Collision(type 0) no-op; hit direction = Damage::damageForce
  or a RANDOM horizontal fallback when ~zero (per-component sign roll then
  value roll -- binary-legal until senders fill damageForce)
- direction rotated by the yaw-only torso-twist frame (placeRot=(0,twist,0)
  -> euler->matrix, the FUN_00408744 raw row-dot reproduced verbatim)
- per-type scaling amount / damageMultiplier[type] * damageResponse[type]
  .{trans,pitchRoll,yaw,vibration}; Explosive alone multiplies burstCount;
  each clamped at 1.3f
- four kicks: impulse(trans, dir), torque(pitchRoll, dir), impulse(vibration,
  vibrationDirection@0x390) and verticalImpulse(yaw, dir)

Layout corrections (byte-verified): gyro+0x390 is ONE Vector3D vibration axis
init (0,1,0) -- was mis-split as scalars animationOffset/Scale/Phase; mech
+0x5c4 is a FLOAT rumble-period countdown (gyroRumbleTimer) -- was mis-typed
int clipLoadGuard.

Call sites wired (binary-gated):
- take-damage hub: mech.cpp TakeDamageMessageHandler, FIRST action (@0x4a0264
  in hub FUN_004a0230) -- an invalid-zone hit still bounces
- crushable-icon crunch: mech4.cpp, torque 0.4 along the bounce delta-v + up
  impulse 0.2 (@4aa81e/@4aa86c)
- firing recoil: projweap.cpp FireWeapon, damage>3 -> impulse (0,0.6,-1.5)
  x damage/16 (@4bc136-4bc19c) via the new Mech::GetGyroSubsystem accessor

DEFERRED (recorded in KB with the byte recipe): the alternate-gait engage
jolt + 0.4s rumble (@4aa158-4aa365) -- its gates are [T3]-flagged and it
mutates the #49/#50-stabilized gait machine.

Verified live: [gyro-dmg] fires per hit with correct type scaling; eyePosition
shows the damped bounce (ramp to ~0.015u, oscillating Y, decay to zero); kill
chain un-regressed; no NaN.

Also recorded in KB: the OFFSET-COCKPIT verification -- Thor's authored
jointeye tranx=+1.13 matches its canopy X-center exactly AND our in-game eye
reproduces it to the hundredth (the placement model is correct per-mech); and
the playable roster is 8 models (strider/raptor/firstrtr/blkjack have assets
but no model resource -- BT_FORCE_MODEL with those names = NULL-resource AV,
cdb-verified, not a code bug).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-11 07:36:25 -05:00
co-authored by Claude Fable 5
parent cb85517ede
commit 946323b696
10 changed files with 283 additions and 25 deletions
+43 -7
View File
@@ -92,13 +92,49 @@ inverse(eyeWorld)`. No LookAt anywhere.
relationship is off by ~0.6u for that group by some OTHER mechanism (candidates: a joint in the
siteeyepoint parent chain we don't pose, per-mech skeleton rest differences, or the canopy
authored for a view state we don't replicate). OPEN.
- **Hit-bounce: plumbing live, trigger missing.** The integrators + ApplyDamageImpulse/Torque/
Vertical hooks are faithful and ready, but the binary's damage→gyro fan-out **FUN_004b2980**
(0x4b2980-0x4b2d8b, called from the damage hub @0x4a02fb; also direct crunch kicks
@0x4aa254/0x4aa288/…) is an unexported gap not yet reconstructed — nothing kicks the gyro yet.
Also pending: the mech+0x3F0 overspeed sway model (bytes 0x4aade8-0x4aaf14; we feed swayBias=0),
and the per-frame `EyepointRotation.pitch = torso pitch` writer (FUN_004b66b4 @0x4aafab) + the
mapper glance-look states (FUN_004afd10) — separate fidelity items.
- **Hit-bounce: LIVE and verified [T2] (2026-07-11).** The damage→gyro fan-out **FUN_004b2980**
was re-disassembled byte-exact from the raw image (capstone; scratchpad/dis_4b2980.py) and
reconstructed as `Gyroscope::ApplyDamageResponse` (gyro.cpp): zero-damage + Collision(0) no-op;
hit direction = `Damage::damageForce` or a RANDOM horizontal fallback when ~zero (our senders
don't fill damageForce yet — the fallback is binary-legal; directional fidelity = fill it later);
rotated by the yaw-only torso-twist frame; per-type scaling `amount / damageMultiplier[type] *
damageResponse[type].{trans,pitchRoll,yaw,vibration}` (Explosive alone multiplies burstCount);
clamp 1.3; four kicks — impulse(trans, dir), torque(pitchRoll, dir), impulse(vibration,
**vibrationDirection@0x390 = (0,1,0)** — the "animationOffset/Scale/Phase scalars" were this
unit vector all along), verticalImpulse(yaw, dir). Call sites wired: the take-damage hub
(mech.cpp TakeDamageMessageHandler, FIRST action, binary @0x4a0264), the crushable-icon crunch
(mech4.cpp, torque 0.4 along Δv + up-impulse 0.2, @4aa81e/@4aa86c), the firing recoil
(projweap.cpp FireWeapon: damage>3 → impulse (0,0.6,1.5) × damage/16, @4bc136-4bc19c).
Verified live: `[gyro-dmg]` fires per hit with correct scaling, eyePosition shows the damped
oscillation (ramp to ~0.015u, Y oscillating, decay to 0), no NaN, kill chain un-regressed.
- **Deferred gyro tail:** (a) the alternate-gait engage JOLT + 0.4s engaged-gait RUMBLE
(@4aa158-4aa365 — full byte recipe in the wf_6880e605 synthesis; **flagged [T3] on the gate
naming** and it mutates the gait state machine tasks #49/#50 stabilized — do NOT wire without
re-verifying the gate semantics); (b) the mech+0x3F0 overspeed sway model (swayBias fed 0);
(c) the per-frame `EyepointRotation.pitch = torso pitch` writer (FUN_004b66b4 @0x4aafab) + the
mapper glance-look states (FUN_004afd10); (d) senders filling `Damage::damageForce` (beam:
impactmuzzle; projectile: velocity) for directional bounce fidelity; (e) FireWeapon's heat add
(pre-existing separate gap). `gyroRumbleTimer@0x5c4` was re-typed from the mis-named
`int clipLoadGuard` (binary uses it as a float rumble countdown).
## Offset cockpits — the placement-model verification [T1/T2]
Some mechs have laterally OFFSET cockpits, and the data + our port agree exactly (2026-07-11):
`[jointeye]` rest translation per SKL vs the `_COP` canopy X-center — **thor: tranx=+1.13, canopy
center +1.13 (exact)**; loki 0.25/0.15; owens +0.19/+0.16; the other five ≈0/≈0. Our in-game
measured eye (`[EYECHK]`) reproduces the Thor offset to the hundredth (lateral 1.13 from the mech
origin). The eye chain (baseOffset + parent-segment DCS composition) is therefore CORRECT per-mech;
the still-blocked cockpit views are a canopy-RENDER question, not a camera one.
## The playable roster is 8, not 12 [T1]
`BTL4.RES` contains exactly 8 mech model resources (avatar, bhk1/blkhawk, loki, madcat, owens,
sunder, thor, vulture — each with `<name>`, variants `<pfx>1/2`, and a `<pfx>dead`). Strider /
Raptor / Firestarter / Blackjack have `_COP` meshes, SKLs and gauge configs but NO model resource
(cut/other-version leftovers); forcing them via `BT_FORCE_MODEL` feeds NULL to
`ResourceDescription::Lock` → AV in `CreatePlayerVehicle` (cdb-verified). Valid `BT_FORCE_MODEL`
names: `avatar bhk1 loki madcat owens sunder thor vulture` (+ variants ava1/lok1/lok2/mad1/mad2/
own1/snd1/thr1/vul1/blkhawk).
## Diagnostics / env gates
+10
View File
@@ -178,6 +178,16 @@ BT_PUNCH, …) are catalogued in [[rendering]]. Warp visuals: [[translocation-wa
per-frame Execute (spin about local Z + submit, NO texcoord/ramp work) = **`FUN_00453dc4`**
(`reference/decomp/all/part_007.c`), using axis-rotation helper `FUN_0040998c` (axis idx 2 = Z).
**Gyro damage fan-out** [T1, re-disassembled from raw bytes] (full story: [[cockpit-view]]):
**`FUN_004b2980`** (0x4b2980-0x4b2d8b, unexported gap) = `Gyroscope::ApplyDamageResponse` — args =
`(Gyroscope*, Damage BY VALUE 12 dwords)`, caller cleans 0x34; constants: dir-eps 1e-4, sign 0.5f
(@0x4b2d84), clamp 1.3f (@0x4b2d88); `vibrationDirection` = gyro+0x390 (0,1,0). Call sites: the
take-damage hub @0x4a02fb (hub = FUN_004a0230, gyro kick FIRST); crushable crunch @4aa81e (torque
0.4)/@4aa86c (up 0.2); gait jolt/rumble @4aa254/@4aa288/@4aa342 (DEFERRED [T3 gates]); firing
recoil @4bc136-4bc19c (damage>3 → (0,0.6,1.5) × damage/16; `gyroRumbleTimer` = mech+0x5c4 float,
ex-"clipLoadGuard"). Byte tooling: `scratchpad/dis_4b2980.py` / `dis_range.py` over
`reference/decomp/section_dump.txt`.
**Cockpit-eye / punch addresses** [T1] (full story: [[cockpit-view]]): eye ctor
**`FUN_004579a8`** (part_007.c:9274 = DPLEyeRenderable; offset = segment `GetBaseOffset`
segment+0x74, parent = parent-segment DCS; caller part_014.c:5525-66 gates on `"siteeyepoint"`);
+15 -8
View File
@@ -75,8 +75,10 @@ authentic path scoped.
world through the openings, verified vs gameplay footage; shows by DEFAULT now
(`BT_HIDE_COCKPIT=1` hides). NOTE the old claim here ("canopy windows are punch texels") was
WRONG — the shell has no texture; the openings are geometry. STILL OPEN: the non-Madcat mechs'
views are blocked because the resting eye sits ~0.6u inside the canopy wall — the missing
gyro eye-joint steady offset (task #56, [[cockpit-view]] §gyro). (b) the **3D marker
views render dark/blocked — NOT an eye-placement bug (disproven: the gyro equilibrium is zero,
and the per-mech eye placement is verified EXACT against the SKL jointeye offsets, incl. the
Thor's authored +1.13 lateral offset cockpit — [[cockpit-view]] §offset-cockpits). It is a
canopy-RENDER question (how those shells read from inside). (b) the **3D marker
chain** ([0x31c]/[0x320] dpl objects + the PNAME1-8.bgf player-name meshes over the locked
target — matters for MP). (c) the compass rotation SIGN is derived-not-verified [T3] (turn
left → the stem should swing right; flip the sin sign in Draw if footage disagrees).
@@ -92,12 +94,17 @@ authentic path scoped.
Performance). Layout locked (sizeof==0x3D0); runtime clean (joints type 5, finite state, no
NaN). Empirical: spring targets are SYMMETRIC → eye equilibrium (0,0,0) → the gyro is a
±0.1-0.15u BOUNCE mechanism, NOT a steady offset (the earlier hypothesis is disproven).
REMAINING (task #56 tail): (a) the damage→gyro fan-out **FUN_004b2980** (0x4b2980-0x4b2d8b,
unexported gap; called @0x4a02fb + direct crunch kicks) — without it no hit-bounce fires;
(b) the mech+0x3F0 overspeed sway model (swayBias fed 0); (c) the per-frame
`EyepointRotation.pitch = torso pitch` writer (FUN_004b66b4) + mapper glance-look states
(FUN_004afd10 — yaw/pitch from mech+0x564..0x570). Also: `deathAnimationLatched`/`legResetLatch`
were never ctor-initialised (0xCDCDCDCD gate bug, fixed in mech.cpp ctor). See [[cockpit-view]].
**FAN-OUT LANDED (2026-07-11):** FUN_004b2980 re-disassembled from raw bytes and reconstructed
(`Gyroscope::ApplyDamageResponse`) + wired at the take-damage hub, the crushable crunch and the
firing recoil — the hit-BOUNCE is live and verified (damped eye oscillation, no NaN).
REMAINING (task #56 tail): (a) the alternate-gait engage jolt + engaged-gait rumble
(@4aa158-4aa365 — [T3] gate naming, mutates the #49/#50-stabilized gait machine; byte recipe in
the wf_6880e605 synthesis); (b) the mech+0x3F0 overspeed sway model (swayBias fed 0); (c) the
per-frame `EyepointRotation.pitch = torso pitch` writer (FUN_004b66b4) + mapper glance-look
states (FUN_004afd10 — yaw/pitch from mech+0x564..0x570); (d) senders filling
`Damage::damageForce` for directional bounce (random-fallback is binary-legal meanwhile).
Also: `deathAnimationLatched`/`legResetLatch` were never ctor-initialised (0xCDCDCDCD gate bug,
fixed in mech.cpp ctor). See [[cockpit-view]].
- **MechControlsMapper look/eyepoint commit** — reconstructed but its offsets collide with declared
members; arbitrate before enabling (no port consumer yet). The fire-trigger-through-mapper is
still the `gBTWeaponTrigger` bring-up.