diff --git a/restoration/source410/BT/MECH.CPP b/restoration/source410/BT/MECH.CPP index 90edc089..e72ef3e7 100644 --- a/restoration/source410/BT/MECH.CPP +++ b/restoration/source410/BT/MECH.CPP @@ -50,6 +50,8 @@ #include // MissileLauncher #include // AmmoBin #include // MechControlsMapper -- the drive reads its demands +#include // Joint / JointSubsystem -- ResolveJoint +#include // EntitySegment -- the skeleton segment table // //############################################################################# @@ -327,6 +329,29 @@ Mech::Mech( { DEBUG_STREAM << "[mech] segment walk done: subsystemCount=" << subsystemCount << " weaponCount=" << weaponCount << endl << flush; + + // + // Skeleton summary: confirm the JointedMover base streamed the segment / + // joint tables (so joint-driven aim / animation / damage has something to + // bind to). BT_SKEL_DUMP additionally lists every segment name + joint + // index (used to identify the twist / gun / leg joints). + // + JointSubsystem *joints = GetJointSubsystem(); + DEBUG_STREAM << "[skel] jointSubsystem=" << (void *)joints + << " jointCount=" << (joints ? joints->GetJointCount() : -1) << endl << flush; + if (getenv("BT_SKEL_DUMP")) + { + EntitySegment::SegmentTableIterator it(segmentTable); + EntitySegment *seg; + int i = 0; + while ((seg = it.ReadAndNext()) != NULL && i < 60) + { + DEBUG_STREAM << "[skel] seg[" << i << "] name=" << seg->GetName() + << " jointIdx=" << seg->GetJointIndex() << endl; + ++i; + } + DEBUG_STREAM << "[skel] segments=" << i << endl << flush; + } } // @@ -416,6 +441,40 @@ void subsystemArray[0] = subsystem; } +// +//############################################################################# +// ResolveJoint -- the shared skeleton-joint resolver (mech.cpp @00424b60). +// A subsystem hands us the joint NAME from its resource; we look up the +// skeleton segment of that name, read its joint index, and fetch the animated +// Joint from the JointSubsystem. NULL for an empty/unknown name or a mech with +// no skeleton/joint subsystem. +//############################################################################# +// +Joint* + Mech::ResolveJoint(const char *joint_name) +{ + Check(this); + + if (joint_name == NULL || joint_name[0] == '\0') + { + return NULL; + } + + EntitySegment *segment = GetSegment(CString(joint_name)); + if (segment == NULL) + { + return NULL; + } + + JointSubsystem *joints = GetJointSubsystem(); + if (joints == NULL) + { + return NULL; + } + + return joints->GetJoint(segment->GetJointIndex()); +} + // //############################################################################# // Simulate -- the mech's per-frame body Performance (the Mover locomotion tick). diff --git a/restoration/source410/BT/MECH.HPP b/restoration/source410/BT/MECH.HPP index 431ea179..ffa609d2 100644 --- a/restoration/source410/BT/MECH.HPP +++ b/restoration/source410/BT/MECH.HPP @@ -59,6 +59,7 @@ class PlatformTool; class MechControlsMapper; class Mech__DamageZone; + class Joint; //########################################################################### //######################### Mech Model Resource ######################### @@ -187,6 +188,14 @@ void SetMappingSubsystem(Subsystem *mapper); + // + // Resolve a skeleton joint by name (the shared resolver the subsystems + // use to bind their animated joints -- Torso twist, etc.): + // GetSegment(name) -> segment jointIndex -> JointSubsystem::GetJoint. + // + Joint* + ResolveJoint(const char *joint_name); + //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ // Per-frame simulation (the mech's body Performance; installed by the ctor // and dispatched each frame from Simulation::PerformAndWatch). diff --git a/restoration/source410/BT/MECH.NOTES.md b/restoration/source410/BT/MECH.NOTES.md index 182fb01d..dfe4e339 100644 --- a/restoration/source410/BT/MECH.NOTES.md +++ b/restoration/source410/BT/MECH.NOTES.md @@ -237,3 +237,30 @@ animation clips), a later wave. Still deferred: gait-clip-exact advance + leg animation (needs the animation subsystem + renderer), model-resource/LoadLocomotionClips sourcing, terrain drop, torso/free-look aiming (Torso/HUD analog axes), telemetry filters. + +## PHASE 5.3 INCREMENT 3+: skeleton-joint resolver + torso aim wiring (2026-07-21) + +- Torso weapon-elevation aim reconstructed (`Torso::TorsoSimulation`, wired into + `InterpretControls`): stick pitch → elevation, slew + clamp to the resource + limit (verified: clamps at the authentic 20°). See TORSO.NOTES.md. +- **`Mech::ResolveJoint(name)`** reconstructed (mech.cpp @00424b60): `GetSegment + (name)` → segment joint index → `GetJointSubsystem()->GetJoint(idx)`. The + shared skeleton-joint resolver the subsystems use to bind their animated + joints. +- **The skeleton is live headlessly** (verified, `BT_MECH_LOG` `[skel]` summary + + `BT_SKEL_DUMP` per-segment list): the JointedMover base streams the full + segment/joint tables — `jointCount=19`, 40 named segments including the gun + joints (`jointlgun`/`jointrgun`), shoulders, hip, and all leg joints + (`jointlthigh`…`jointrankle`). So joint-driven aim / gait / damage has real + joints to bind to. +- The Torso now resolves + binds its twist joint (`ResolveJoint(torsoHorizontal + Joint)`) and pushes `currentTwist` onto it via `Joint::SetRotation` (hinge → + Radian, ball → EulerAngles yaw). The bring-up TEST.EGG mech has a FIXED torso + (`horizJoint=''`, `enabled=0`) so its twist path is inert — correct and + guarded; a torso-twist mech would drive the joint. + +Next joint piece: elevation → the gun joints (`jointlgun`/`jointrgun`) — the +aim mechanism for fixed-torso mechs — which needs the weapon-joint binding +(resolved from the weapon subsystems, not the torso resource). Then the gait +leg-clip animation. Both become VISIBLE only under the renderer; headlessly the +joint transforms are computable/loggable. diff --git a/restoration/source410/BT/TORSO.CPP b/restoration/source410/BT/TORSO.CPP index f28fe718..c4c7ebbd 100644 --- a/restoration/source410/BT/TORSO.CPP +++ b/restoration/source410/BT/TORSO.CPP @@ -19,6 +19,10 @@ # include #endif +#if !defined(JOINT_HPP) +# include +#endif + Derivation Torso::ClassDerivations( PowerWatcher::ClassDerivations, @@ -67,11 +71,24 @@ Torso::Torso( horizontalEnabled = r->torsoHorizontalEnabled; // - // The named skeleton joints are resolved once the Mech skeleton link is - // live (per-frame sim / phase-4 wiring); leave the handles null here. + // Resolve the named torso-twist skeleton joints (twist body + shadow) from + // the mech skeleton (now live). A mech with a fixed torso, or one whose + // joint name is absent from the skeleton, resolves NULL and simply never + // twists. // - horizontalJointNode = NULL; - horizontalShadowJointNode = NULL; + horizontalJointNode = owner->ResolveJoint(r->torsoHorizontalJoint); + horizontalShadowJointNode = owner->ResolveJoint(r->torsoHorizontalShadowJoint); + + if (getenv("BT_MECH_LOG")) + { + DEBUG_STREAM << "[torso] horizJoint '" << r->torsoHorizontalJoint + << "' -> " << (void *)horizontalJointNode; + if (horizontalJointNode != NULL) + { + DEBUG_STREAM << " type=" << (int)horizontalJointNode->GetJointType(); + } + DEBUG_STREAM << " enabled=" << (int)horizontalEnabled << endl << flush; + } currentTwist = 0.0f; currentElevation = 0.0f; @@ -153,6 +170,26 @@ void ? horizontalLimitRight : horizontalLimitLeft; if (currentTwist < lo) currentTwist = lo; if (currentTwist > hi) currentTwist = hi; + + // + // Push the twist onto the skeleton torso joint so the upper body rotates + // on the model. Hinge joints take the scalar angle about their axis; + // ball joints take it as the yaw component. + // + if (horizontalJointNode != NULL) + { + Joint::JointType jt = horizontalJointNode->GetJointType(); + if (jt == Joint::HingeXJointType + || jt == Joint::HingeYJointType + || jt == Joint::HingeZJointType) + { + horizontalJointNode->SetRotation(Radian(currentTwist)); + } + else if (jt == Joint::BallJointType) + { + horizontalJointNode->SetRotation(EulerAngles(0.0f, currentTwist, 0.0f)); + } + } } Check_Fpu(); diff --git a/restoration/source410/BT/TORSO.HPP b/restoration/source410/BT/TORSO.HPP index 6b373d73..57886b99 100644 --- a/restoration/source410/BT/TORSO.HPP +++ b/restoration/source410/BT/TORSO.HPP @@ -17,6 +17,7 @@ //##################### Forward Class Declarations ####################### class Mech; + class Joint; //########################################################################### //##################### Torso Model Resource ########################### @@ -117,8 +118,8 @@ Scalar buttonAccelerationStart; int buttonRampActive; Scalar buttonRamp; - void *horizontalJointNode; - void *horizontalShadowJointNode; + Joint *horizontalJointNode; + Joint *horizontalShadowJointNode; Scalar currentTwist; Scalar currentElevation; Scalar analogElevationAxis; diff --git a/restoration/source410/BT/TORSO.NOTES.md b/restoration/source410/BT/TORSO.NOTES.md index f86e2d9b..f52858b8 100644 --- a/restoration/source410/BT/TORSO.NOTES.md +++ b/restoration/source410/BT/TORSO.NOTES.md @@ -24,11 +24,25 @@ stick pitch 0.8 slews the elevation up and clamps at the authentic `verticalLimitTop` = 0.349 rad = **20°**. Zero Fail. -## Deferred (skeleton-link + render wave) +## Skeleton-joint binding (2026-07-21) -`TorsoSimulation` advances the SCALAR aim state (elevation/twist), which the HUD -reticle / weapon-aim read. APPLYING those angles onto the skeleton — rotating -the `horizontalJointNode` (torso twist) and the elevation joint so the model's -torso/gun barrels visibly aim — is deferred with the skeleton-joint-link + -renderer wave (the joint handles are resolved null for now). `TorsoCopySimulation` -(replicant console-driven aim) also stays staged. +The twist joints are now RESOLVED and BOUND: the ctor calls +`owner->ResolveJoint(torsoHorizontalJoint / torsoHorizontalShadowJoint)` (the +skeleton is live — see MECH.NOTES.md), stores them as `Joint*`, and +`TorsoSimulation` pushes `currentTwist` onto `horizontalJointNode` via +`Joint::SetRotation` (hinge → `Radian`; ball → `EulerAngles` yaw). + +The bring-up TEST.EGG mech has a FIXED torso (`horizJoint=''`, `enabled=0`), so +its twist path is inert — correctly guarded (null joint → no apply, no crash). A +torso-twist mech drives the joint. Verified headlessly; the VISUAL rotation +needs the renderer. + +## Deferred + +- **Elevation → gun joints.** This mech aims by pitching the guns + (`jointlgun`/`jointrgun`), not twisting the torso. `currentElevation` still + only advances the scalar state; applying it to the gun joints needs the + weapon-joint binding (resolved from the weapon subsystems, not the torso + resource) — the next joint piece. +- HUD free-aim slew, look/eyepoint commit, `TorsoCopySimulation` (replicant + console-driven aim) — still staged.