Gait: the AUTHENTIC walk lean -- interior/exterior clip sets, level cockpit + leaning mechs (task #59)
The MadCat (and every leg-mech) leans -8deg into a walk / -11deg into a run --
an authored jointhip (hingex) pose in the EXTERIOR gait clips that the walk/run
CYCLE clips never rebind, so it HOLDS. The 1995 game keeps a separate INTERIOR
('i'-suffix) clip set for the mech you PILOT: those shake jointshakey (cockpit
rattle) and OMIT jointhip, so your own view stays level while everyone else sees
you lean. The port had this entirely dead -- the local cockpit mech pitched
-8deg into the ground (user-reported "staring at the ground").
ROOT CAUSE (two stacked bugs):
- The authentic ctor clip-set gate (@part_012.c:10308-10320) was reconstructed
as no-op stubs: LoadLowDetailBody/LoadHighDetailBody MISLABELED the
FUN_004a80d4/86c8 GAIT-CLIP loader addresses as a "body LOD" pair, so the gate
did nothing and a separate unconditional LoadLocomotionClips always loaded
EXTERIOR clips.
- LoadLocomotionClipsExt (the interior loader) was a stub aliased to the
exterior loader.
FIX:
- Reconstructed the real 4-char INTERIOR loader (byte-exact vs @004a86c8; the
'i'-suffix table dumped from the exe @0x10d74d: swri/wwri/wwli/...; all 18
base clips confirmed present in BTL4.RES for mad/blh/ava).
- Fixed the ctor gate: getenv("L4VIEWEXT") || (instanceFlags&0xC)==4 -> exterior
(replicant / forced external view); else -> interior (local cockpit master).
Removed the mislabeling no-op stubs.
- PORT ADAPTATION (MaintainViewClipSet, per-frame in PerformAndWatch): the port
never sets the replicant COPY bit (all MP mechs build as local masters --
heat-sim/scoring/torso-watcher-connect all run on both, by design), so the
ctor gate lands everyone on interior. Pick the set by VIEWPOINT instead: the
mech you pilot keeps interior (level cockpit), every other mech flips once to
exterior (the lean). Reloads only on a viewpoint-status change; the sets
share stride data so the swap is seamless. Model pointers stashed at ctor.
VERIFIED live (BT_HIP_LOG probe): your walking mech writes jointhip=0 (level);
the peer's replica in the other pod's view leans at exactly -8.0/-11.1deg --
the authored clip values. A/B control: L4VIEWEXT=1 forces the walking master
back to 74 sustained -8/-11 writes.
KB: locomotion.md ("NO walk lean" audit conclusion CORRECTED -- the lean is
authored + sustained + clip-set-specific), asset-formats.md (.ANI parses
pitch/yaw/roll for all joint types; KeyJointPos translation; cycle clips carry
no pitch), open-questions.md (item closed).
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
02cdfd6576
commit
2e9c78d604
@@ -20,9 +20,16 @@ spec is `docs/ASSET_PIPELINE.md`. Full list: `docs/PROGRESS_LOG.md §5`.
|
||||
Object=part.bgf, dzone=, tranx/y/z, pitch/yaw/roll`. Build a **DCS tree**; neutral pose ≈
|
||||
translations (pyr≈0). [T1]
|
||||
- **`.ANI`** — INI keyframe animation. `[HEADER] framecount/framerate(30)/skeletonfile`; `[frameN]`
|
||||
joint→`rx ry rz` radians (balltranslate→translate); `[RootTranslation]` per-keyframe `x y z`
|
||||
where **`.z` = forward root SPEED** (units/s). Walk anims have no baked root translation — forward
|
||||
motion is animation-driven from `.z` ([[locomotion]]). [T1]
|
||||
joint→**`pitch yaw roll` radians for EVERY joint type** (ANIMTOOL.cpp:140-142 parses the triple as
|
||||
EulerAngles unconditionally — the old "balltranslate→translate" note was wrong [T0]); a
|
||||
balltranslate joint ADDITIONALLY gets a per-frame translation from the separate **`[KeyJointPos]`**
|
||||
section (ANIMTOOL.cpp:478-483 packs EulerAngles+Vector3D; JMOVER/seqctl apply SetRotation +
|
||||
SetTranslation). For the root `jointlocal`, KeyJointPos.y = the gait vertical bob;
|
||||
`[RootTranslation]` per-keyframe `x y z` where **`.z` = forward root SPEED** (units/s). Walk anims
|
||||
have no baked root translation — forward motion is animation-driven from `.z` ([[locomotion]]).
|
||||
Walk/run CYCLE clips author NO body pitch — root keys are a ≤1.5° roll sway only; the authored
|
||||
LEAN lives in the transition (`rwl/rwr/wrl/wrr` jointhip ~7-12°) + `bmp` stagger (~25°) clips
|
||||
(2026-07-13 sweep of all 331 source ANIs). [T1]
|
||||
|
||||
## Materials / textures
|
||||
- **`.BMF`** — `DIV-BIZ2` (FILETYPE=1) material+texture LIBRARY (no pixels). Material → DIFFUSE color
|
||||
|
||||
@@ -22,6 +22,48 @@ engine `AnimationInstance::Animate(dt, move_joints)` integrates `movement += Δt
|
||||
moves the body that far → feet plant by construction (no stride constant; k=1). Idle anims have
|
||||
z=0. The `.MOD` physics governed thrust VEHICLES, NOT walk gait. [T1]
|
||||
|
||||
## The walk lean IS authored — and INTERIOR clips omit it (2026-07-13, task #59 CORRECTION) [T1/T2]
|
||||
**A walking mech DOES lean forward — it's authored, sustained, and clip-set-specific.** (This
|
||||
supersedes the earlier "NO walk lean" audit conclusion, which was right that no CODE writes body
|
||||
pitch and that the CYCLE clips carry no pitch keys, but wrong about the net behavior: the
|
||||
TRANSITION clips ramp `jointhip` (hingex, parent of `jointtorso` → the whole upper body + cockpit)
|
||||
to a steady pose that the cycle clips never rebind, so it HOLDS.) Decoded from BTL4.RES + live
|
||||
`[hip]` probe [T2]:
|
||||
- `<pfx>swr` ramps `jointhip` 0 → **−8°** (stand→walk), `<pfx>wrl/wrr` −8 → **−11.1°** (walk→run),
|
||||
`<pfx>wsl/wsr` −8 → 0 (walk→stand). The dedicated return clip only exists because the lean
|
||||
persists. The BLH has NO `jointhip` channel in any clip → it stays level (both views).
|
||||
- **Two clip sets per mech.** The ctor gate (@part_012.c:10308-10320) binds the **INTERIOR** set
|
||||
(4-char `'i'` suffix, `LoadLocomotionClipsExt` @004a86c8) to the **local cockpit master** — those
|
||||
clips animate `jointshakey` (cockpit shake, a BALL joint) and OMIT `jointhip`, so the PILOT's own
|
||||
view stays level. The **EXTERIOR** set (3-char, `LoadLocomotionClips` @004a80d4) goes to network
|
||||
REPLICANTS and the forced `L4VIEWEXT` external view — it carries the `jointhip` lean, so everyone
|
||||
ELSE sees the mech lean into its stride.
|
||||
- **Port fix (task #59):** the interior loader was a stub aliased to the exterior loader, and the
|
||||
authentic ctor gate was a no-op (`LoadLowDetailBody`/`LoadHighDetailBody` mislabeled the
|
||||
FUN_004a80d4/86c8 clip-loader addresses as a body-LOD pair) — so the local cockpit mech played
|
||||
exterior clips and pitched −8°. Now `LoadLocomotionClipsExt` is the real 4-char interior loader
|
||||
and the ctor gate selects it (`getenv("L4VIEWEXT") || (instanceFlags&0xC)==4` → exterior; else
|
||||
interior). Verified live: default (interior) → 4 stray `jointhip` writes over a continuous walk;
|
||||
`L4VIEWEXT=1` (exterior) → 74 sustained writes at −8/−11°.
|
||||
- **Copy-flag → VIEWPOINT adaptation (the other half, DONE):** the port never sets the replicant
|
||||
COPY bit (`instanceFlags & 0xC == 4`) — all MP mechs build as local masters (replication rides
|
||||
the registry + update-records; heat-sim/scoring/torso-watcher-connect all run on both by design,
|
||||
so flipping the bit would break them). The ctor gate therefore lands EVERY mech on interior. To
|
||||
restore the authentic RESULT, `Mech::MaintainViewClipSet()` (mech3.cpp, called each frame from
|
||||
`PerformAndWatch`) picks the set by VIEWPOINT: the mech you pilot (== `GetViewpointEntity()`)
|
||||
keeps INTERIOR (level cockpit — you never see your own mech externally in the real pod); every
|
||||
other mech flips ONCE to EXTERIOR (the −8/−11° lean). Reloads only on a viewpoint-status change;
|
||||
the two sets share stride data so the swap is seamless. Verified live: your mech `jointhip`=0
|
||||
while walking; the peer's replica leans at −8°/−11.1° in your view. (Model pointers stashed at
|
||||
ctor via `BTStashClipState`; the GameModel resource persists for the mission.)
|
||||
|
||||
Other pitch sources (unchanged, still correct): `bmp` stagger (≈25° — THE knockdown pitch); JAK's
|
||||
four directional fall clips (`JAKFB/FF/FL/FR`, root pitch to 1.79 rad — the only mech with them);
|
||||
`IntegrateMotion`@0x4ab1c8 rotates orientation by `angularVelocity`@0x2d4·dt (yaw only); ground
|
||||
snap is Y-only (terrain pitch/roll → `jointshadow`). The gyro's `bodyOrientation` spring
|
||||
(@004b30ec) writes only `jointeye` (cockpit hit-bounce, not a lean). `fallDirection`@0x4a8/
|
||||
`fallScalar`@0x4b4 are record-payload only. [T1]
|
||||
|
||||
## The MARCHING-GHOST desync — ROOT-CAUSED + FIXED (2026-07-13, live diagnosis) [T2]
|
||||
The long-parked "replicant walks in place while its master stands still" reproduced live with
|
||||
telemetry running: the master's `[gaitSM]` showed `state=0 cycleSpeed=-2.50725 kfCur=19` — BOTH
|
||||
|
||||
@@ -344,6 +344,18 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
destroyed-subsystem dark-panel look.
|
||||
|
||||
## Locomotion / combat polish (non-gating)
|
||||
- **✅ Interior vs exterior gait CLIP SET — DONE 2026-07-13 (task #59) [T2].** The local cockpit
|
||||
mech was leaning −8° into every walk because the authentic ctor clip-set gate
|
||||
(@part_012.c:10308-10320) was a no-op (`LoadLowDetailBody`/`LoadHighDetailBody` mislabeled the
|
||||
FUN_004a80d4/86c8 clip-loader addresses) and `LoadLocomotionClipsExt` was a stub aliased to the
|
||||
exterior loader. Reconstructed the real 4-char INTERIOR (`'i'`-suffix) loader (omits `jointhip`,
|
||||
shakes `jointshakey`). Because the port never sets the replicant copy bit the ctor gate lands
|
||||
everyone on interior, so `Mech::MaintainViewClipSet()` picks the set by VIEWPOINT each frame
|
||||
(your mech → interior/level; every other mech → exterior/lean). Both verified live: cockpit
|
||||
level, peer replica leans −8/−11°. See [[locomotion]] "The walk lean IS authored".
|
||||
- FOLLOW-UP (non-blocking): if the copy bit ever gets set at construction for other reasons,
|
||||
the ctor gate already handles it and MaintainViewClipSet becomes a no-op agreement — no
|
||||
conflict, but revisit whether the per-frame maintenance is still needed.
|
||||
- Authentic per-mech TURN-RATE constant (currently a bring-up constant rate).
|
||||
- Body-callback gimp handlers (states 16-19, targets 0x70b2/0x7161 undecoded → fall back to stand);
|
||||
airborne callbacks (`FUN_004a6344`/`FUN_004a7970`).
|
||||
|
||||
Reference in New Issue
Block a user