BT410 Phase 5.3.4: Mech::ResolveJoint + skeleton-joint binding (torso twist)

Reconstructs the shared skeleton-joint resolver and binds the first subsystem
(Torso) to the live skeleton.

- Mech::ResolveJoint(name) (mech.cpp @00424b60): GetSegment(name) -> segment
  joint index -> GetJointSubsystem()->GetJoint(idx).
- Verified the JointedMover base streams the full skeleton headlessly:
  jointCount=19, 40 named segments (gun joints jointlgun/jointrgun, shoulders,
  hip, leg joints jointlthigh..jointrankle). BT_MECH_LOG [skel] summary;
  BT_SKEL_DUMP lists every segment/jointIdx.
- Torso resolves + binds its twist joint (ResolveJoint(torsoHorizontalJoint))
  and pushes currentTwist onto it via Joint::SetRotation (hinge->Radian,
  ball->EulerAngles yaw). The TEST.EGG mech has a fixed torso (horizJoint='',
  enabled=0) so its twist path is inert -- correct + guarded, zero Fail.

Next joint piece: elevation -> gun joints (jointlgun/jointrgun, the fixed-torso
aim mechanism) via weapon-joint binding; then gait leg animation. Both are
VISIBLE only under the renderer; headlessly the joint transforms are loggable.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Cyd
2026-07-21 16:17:37 -05:00
co-authored by Claude Fable 5
parent 3a0920acc0
commit 9fff0e969e
6 changed files with 160 additions and 13 deletions
+59
View File
@@ -50,6 +50,8 @@
#include <mislanch.hpp> // MissileLauncher
#include <ammobin.hpp> // AmmoBin
#include <mechmppr.hpp> // MechControlsMapper -- the drive reads its demands
#include <joint.hpp> // Joint / JointSubsystem -- ResolveJoint
#include <segment.hpp> // 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).
+9
View File
@@ -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).
+27
View File
@@ -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.
+41 -4
View File
@@ -19,6 +19,10 @@
# include <mech.hpp>
#endif
#if !defined(JOINT_HPP)
# include <joint.hpp>
#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();
+3 -2
View File
@@ -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;
+21 -7
View File
@@ -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.