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
+27 -22
View File
@@ -104,30 +104,35 @@ off in solo. `[mp-hdlr]` (BT_MP_NET, mech.cpp:554) logs each applied cross-pod h
`scratchpad/mp_test.sh`: A `-egg MP.EGG -net 1501`, B `-net 1601`, then `python tools/btconsole.py MP.EGG `scratchpad/mp_test.sh`: A `-egg MP.EGG -net 1501`, B `-net 1601`, then `python tools/btconsole.py MP.EGG
127.0.0.1:1501 127.0.0.1:1601` (MP.EGG `[pilots]` = game ports 1502/1602). 127.0.0.1:1501 127.0.0.1:1601` (MP.EGG `[pilots]` = game ports 1502/1602).
## Interactive -net aim + drive (task #48, 2026-07-09) — partial; the blocker is NOT the netcode [T2] ## Interactive -net aim + drive (task #48, 2026-07-09) [T2]
Re-measured on a 2-node one-box run (A `BT_AUTODRIVE`/`BT_GOTO`, `BT_AIM_LOG`, `BT_MP_LOG`). **The prior Re-measured on a 2-node one-box run. **The prior "aim ray dead / drive dead in -net" claim was STALE — both
"aim ray dead / drive dead in -net" claim was STALE — both work now:** A's aim ray is live (`noRay=0`, the work:** A's aim ray is live (`noRay=0`) and A drives (`BT_AUTODRIVE`/`BT_GOTO` move it).
active eye publishes the aim camera), and A DRIVES (`BT_AUTODRIVE`/`BT_GOTO` move it). What actually blocks
a HUMAN cross-pod kill via the real beam path: **FIXED — torso-pitch aim ray (gyro-stabilized boresight).** The aim/pick ray was the active RENDER EYE's
- **Boresight inherits the mech BODY pitch.** The aim ray = the active EYE's view direction (with reticle view direction, which (a) inherits the mech BODY pitch — on a slope the body tilts ~8° to conform to terrain
at 0,0). On sloped terrain the mech body pitches to conform (ground model), the cockpit eye/boresight and the boresight pointed that far DOWN, hitting terrain ~50-100 m short of the enemy (`groundPicks`, never
pitches with it, and the pick ray points ~8° DOWN (`[EYE] up=(-.03,.99,-.14)` == `[pick] ldir.y≈-0.145`, `mechPicks`); and (b) in CHASE view (the DEFAULT) is the elevated behind-the-shoulder camera, so the ray
every frame). At range that ray hits terrain (`groundPicks`) ~50-100 m short of the enemy → `mechPicks=0`. started over the mech's shoulder. Fix (L4VIDRND `DPLEyeRenderable::Execute`, the `BTSetAimCamera` publish):
A close enemy (< ~50 m) would still resolve. Real BT aims the TORSO pitch independently of leg/body tilt; LEVEL the boresight (drop the view-dir pitch, rebuild an upright basis with world +Y) AND anchor its ORIGIN
the reconstruction ties the boresight to the render eye. Fixing = derive the aim ray from a level torso at the mech's torso (`myEntity->localToWorld` translation + ~5) instead of the render eye. The guns now fire
boresight (yaw only) OR add torso pitch, not the pitched body eye. along the mech's horizontal heading from the torso, independent of view mode and terrain tilt. The reticle X
- **Drive/goto stalls.** A repeatedly stops (`gait adv=0`) ~700-750 m from the enemy before closing to still carries the torso twist (`BTTwistToReticleX`); reticle Y any elevation. VERIFIED end-to-end: solo,
weapon/pick range — a locomotion/goto reliability issue, not MP. enemy 120 m ahead, **both chase AND cockpit** view → `[target] MECH under boresight ... mechPicks=59
- **PickRayHit vs a replicant is UNCONFIRMED** (blocked by the two above — A never faces the enemy at close groundPicks=0` → beam → `*** DESTROYED`. Also proved `PickRayHit` resolves a REPLICANT when aimed at it
range). `localToWorld` on the replicant IS correct (`[pick] lstart` magnitude ≈ true A→B distance; the (`hit=1` at 757 m in a 2-node run); the replicant `localToWorld` is correct (DeadReckon + `localToWorld=
DeadReckon + `localToWorld=localOrigin` block at mech4.cpp:1191 tracks the replicated position). localOrigin`, mech4.cpp:1191). [T2]
Test aid added: `BT_GOTO="enemy"` beelines toward the nearest live replicant (spawn coords vary per run, so
a fixed "x z" can't reliably face the peer). Recipe: `scratchpad/mp_kill_test.sh`. **STILL OPEN — drive-to-range.** `BT_GOTO`/`BT_AUTODRIVE` steer + move A, but A stalls (`gait adv=0`) short
of the enemy and often fails to line the boresight onto a peer at range — so an AUTOMATED 2-node kill via the
real beam path isn't demonstrated (the `BT_MP_FORCE_DMG` hook still is). A HUMAN can now aim (the ray is
correct in the default view); the gap is the auto-drive facing the target. Locomotion/goto issue, not MP.
Test aid: `BT_GOTO="enemy"` beelines toward the nearest live peer (solo dummy OR replicant); `scratchpad/
mp_kill_test.sh`.
## Remaining (P6 phase 4 / Phase 7) ## Remaining (P6 phase 4 / Phase 7)
Human-playable cross-pod aim (torso-pitch / level-boresight aim ray + drive-to-range reliability, above) so a Drive-to-range / auto-face reliability (above) for a fully AUTOMATED cross-pod kill demo; replicant GAIT
HUMAN — not just the FORCE_DMG hook — can aim + fire cross-pod; replicant GAIT animation (derive from animation (derive from replicated velocity); the pod-LAN config (real IPs, bare-IP pilot entries). See
replicated velocity); the pod-LAN config (real IPs, bare-IP pilot entries). See [[open-questions]]. [T3] [[open-questions]]. [T3]
## MP-front scout (task #45, 2026-07-09) — the P6 chain SURVIVES the combat/HUD rework [T2] ## MP-front scout (task #45, 2026-07-09) — the P6 chain SURVIVES the combat/HUD rework [T2]
Re-ran the one-box smoke test on the current build (world-pick targeting, weapon groups, death Re-ran the one-box smoke test on the current build (world-pick targeting, weapon groups, death
+40 -4
View File
@@ -5588,14 +5588,50 @@ void
{ {
extern void BTSetAimCamera(const float pos[3], const float xax[3], extern void BTSetAimCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[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; D3DXVECTOR3 zax = pos - at;
D3DXVec3Normalize(&zax, &zax); D3DXVec3Normalize(&zax, &zax);
D3DXVECTOR3 xax; D3DXVECTOR3 xax;
D3DXVec3Cross(&xax, &up, &zax);
D3DXVec3Normalize(&xax, &xax);
D3DXVECTOR3 yax; D3DXVECTOR3 yax;
D3DXVec3Cross(&yax, &zax, &xax); D3DXVECTOR3 fwdLevel(at.x - pos.x, 0.0f, at.z - pos.z);
const float p[3] = { pos.x, pos.y, 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 x3[3] = { xax.x, xax.y, xax.z };
const float y3[3] = { yax.x, yax.y, yax.z }; const float y3[3] = { yax.x, yax.y, yax.z };
const float z3[3] = { zax.x, zax.y, zax.z }; const float z3[3] = { zax.x, zax.y, zax.z };
+1 -2
View File
@@ -1515,8 +1515,7 @@ void
for (int ei = 0; ei < enc; ++ei) for (int ei = 0; ei < enc; ++ei)
{ {
Mech *em = (Mech *)ec[ei]; Mech *em = (Mech *)ec[ei];
if (em == 0 || em->GetInstance() != ReplicantInstance if (em == 0 || em->IsMechDestroyed()) continue; // any peer (solo dummy OR replicant)
|| em->IsMechDestroyed()) continue;
Point3D ep = em->localOrigin.linearPosition; Point3D ep = em->localOrigin.linearPosition;
float edx = (float)ep.x - (float)localOrigin.linearPosition.x; float edx = (float)ep.x - (float)localOrigin.linearPosition.x;
float edz = (float)ep.z - (float)localOrigin.linearPosition.z; float edz = (float)ep.z - (float)localOrigin.linearPosition.z;