Fix torso-pitch aim ray: gyro-stabilized boresight from the mech torso

The aim/pick ray was the active RENDER EYE's view direction, which made a
mech unable to aim at a distant enemy:
  (a) it inherited the mech BODY pitch -- on a slope the body tilts ~8 deg to
      conform to terrain, so the boresight pointed that far DOWN and the pick
      ray hit the ground ~50-100m short of the target (groundPicks, never
      mechPicks); and
  (b) in CHASE view (the default) it was the elevated behind-the-shoulder
      camera, so the ray started over the mech's shoulder and flew high.

Fix (L4VIDRND DPLEyeRenderable::Execute, the BTSetAimCamera publish):
- LEVEL the boresight: drop the view-direction pitch and rebuild an upright
  basis (world +Y up) -- the guns fire along the mech's gyro-stabilized
  HORIZONTAL heading, not the terrain-pitched body.  Reticle X still carries
  the torso twist (BTTwistToReticleX); reticle Y any elevation.
- ANCHOR the ray origin at the mech's TORSO (myEntity->localToWorld
  translation + ~5) instead of the render eye, so it is correct in BOTH the
  chase (default) and cockpit views.

Verified end-to-end: solo, enemy 120m ahead, BOTH chase and cockpit view ->
[target] "MECH under boresight ... mechPicks=59 groundPicks=0" -> beam ->
*** DESTROYED.  Also proved PickRayHit resolves a REPLICANT when aimed at it
(hit=1 at 757m, 2-node run).

mech4: BT_GOTO="enemy" test aid now chases the nearest peer of ANY instance
(solo dummy OR replicant), not just replicants.

Still open (locomotion, not aim/MP): BT_GOTO/BT_AUTODRIVE stall short of the
enemy, so an AUTOMATED 2-node kill via the real beam isn't demonstrated yet
(the FORCE_DMG hook still is).  A human can now aim correctly in the default
view.  Solo un-regressed; build clean; KB validator clean.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 14:11:54 -05:00
co-authored by Claude Fable 5
parent bac45851ad
commit 0dfbb97dcc
3 changed files with 68 additions and 28 deletions
+40 -4
View File
@@ -5588,14 +5588,50 @@ void
{
extern void BTSetAimCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[3]);
// AIM BORESIGHT (task #48): the guns fire along the mech's
// GYRO-STABILIZED horizontal heading, NOT the terrain-pitched
// body/eye. Publishing the raw eye basis made a mech standing
// on a slope (body pitched ~8 deg to conform to the ground) aim
// its pick ray INTO the ground, short of a distant target
// (mechPicks=0). Level the boresight: drop the view direction's
// pitch and rebuild an upright basis (world +Y up). The reticle
// X still carries the torso twist (BTTwistToReticleX); the reticle
// Y carries any aim elevation. Falls back to the raw basis only
// if the view is (degenerately) near-vertical.
D3DXVECTOR3 zax = pos - at;
D3DXVec3Normalize(&zax, &zax);
D3DXVECTOR3 xax;
D3DXVec3Cross(&xax, &up, &zax);
D3DXVec3Normalize(&xax, &xax);
D3DXVECTOR3 yax;
D3DXVec3Cross(&yax, &zax, &xax);
const float p[3] = { pos.x, pos.y, pos.z };
D3DXVECTOR3 fwdLevel(at.x - pos.x, 0.0f, at.z - pos.z);
if (D3DXVec3LengthSq(&fwdLevel) > 1e-6f)
{
D3DXVec3Normalize(&fwdLevel, &fwdLevel);
zax = D3DXVECTOR3(-fwdLevel.x, 0.0f, -fwdLevel.z); // back = -level fwd
D3DXVECTOR3 wup(0.0f, 1.0f, 0.0f);
D3DXVec3Cross(&xax, &wup, &zax);
D3DXVec3Normalize(&xax, &xax);
D3DXVec3Cross(&yax, &zax, &xax); // == world +Y
}
else
{
D3DXVec3Cross(&xax, &up, &zax);
D3DXVec3Normalize(&xax, &xax);
D3DXVec3Cross(&yax, &zax, &xax);
}
// ORIGIN: the guns fire from the MECH's torso, not the render
// eye. In CHASE view the eye sits behind+above the mech, so a
// ray from `pos` would start over the shoulder (and, being level,
// fly above a ground target). Anchor the boresight at the mech's
// XZ + a torso/gun height so the pick works in BOTH views (chase
// is the default; cockpit toggles with V). In cockpit view this
// is ~the eyepoint anyway.
float p[3] = { pos.x, pos.y, pos.z };
if (myEntity != 0)
{
p[0] = myEntity->localToWorld(3,0);
p[1] = myEntity->localToWorld(3,1) + 5.0f; // feet -> torso/gun height
p[2] = myEntity->localToWorld(3,2);
}
const float x3[3] = { xax.x, xax.y, xax.z };
const float y3[3] = { yax.x, yax.y, yax.z };
const float z3[3] = { zax.x, zax.y, zax.z };