diff --git a/context/multiplayer.md b/context/multiplayer.md index 742f78e..f538dd1 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -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 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] -Re-measured on a 2-node one-box run (A `BT_AUTODRIVE`/`BT_GOTO`, `BT_AIM_LOG`, `BT_MP_LOG`). **The prior -"aim ray dead / drive dead in -net" claim was STALE — both work now:** A's aim ray is live (`noRay=0`, the -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: -- **Boresight inherits the mech BODY pitch.** The aim ray = the active EYE's view direction (with reticle - at 0,0). On sloped terrain the mech body pitches to conform (ground model), the cockpit eye/boresight - pitches with it, and the pick ray points ~8° DOWN (`[EYE] up=(-.03,.99,-.14)` == `[pick] ldir.y≈-0.145`, - every frame). At range that ray hits terrain (`groundPicks`) ~50-100 m short of the enemy → `mechPicks=0`. - A close enemy (< ~50 m) would still resolve. Real BT aims the TORSO pitch independently of leg/body tilt; - the reconstruction ties the boresight to the render eye. Fixing = derive the aim ray from a level torso - boresight (yaw only) OR add torso pitch, not the pitched body eye. -- **Drive/goto stalls.** A repeatedly stops (`gait adv=0`) ~700-750 m from the enemy before closing to - weapon/pick range — a locomotion/goto reliability issue, not MP. -- **PickRayHit vs a replicant is UNCONFIRMED** (blocked by the two above — A never faces the enemy at close - range). `localToWorld` on the replicant IS correct (`[pick] lstart` magnitude ≈ true A→B distance; the - DeadReckon + `localToWorld=localOrigin` block at mech4.cpp:1191 tracks the replicated position). -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`. +## Interactive -net aim + drive (task #48, 2026-07-09) [T2] +Re-measured on a 2-node one-box run. **The prior "aim ray dead / drive dead in -net" claim was STALE — both +work:** A's aim ray is live (`noRay=0`) and A drives (`BT_AUTODRIVE`/`BT_GOTO` move it). + +**FIXED — torso-pitch aim ray (gyro-stabilized boresight).** The aim/pick ray was the active RENDER EYE's +view direction, which (a) inherits the mech BODY pitch — on a slope the body tilts ~8° to conform to terrain +and the boresight pointed that far DOWN, hitting terrain ~50-100 m short of the enemy (`groundPicks`, never +`mechPicks`); and (b) in CHASE view (the DEFAULT) is the elevated behind-the-shoulder camera, so the ray +started over the mech's shoulder. Fix (L4VIDRND `DPLEyeRenderable::Execute`, the `BTSetAimCamera` publish): +LEVEL the boresight (drop the view-dir pitch, rebuild an upright basis with world +Y) AND anchor its ORIGIN +at the mech's torso (`myEntity->localToWorld` translation + ~5) instead of the render eye. The guns now fire +along the mech's horizontal heading from the torso, independent of view mode and terrain tilt. The reticle X +still carries the torso twist (`BTTwistToReticleX`); reticle Y any elevation. VERIFIED end-to-end: solo, +enemy 120 m 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 757 m in a 2-node run); the replicant `localToWorld` is correct (DeadReckon + `localToWorld= +localOrigin`, mech4.cpp:1191). [T2] + +**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) -Human-playable cross-pod aim (torso-pitch / level-boresight aim ray + drive-to-range reliability, above) so a -HUMAN — not just the FORCE_DMG hook — can aim + fire cross-pod; replicant GAIT animation (derive from -replicated velocity); the pod-LAN config (real IPs, bare-IP pilot entries). See [[open-questions]]. [T3] +Drive-to-range / auto-face reliability (above) for a fully AUTOMATED cross-pod kill demo; replicant GAIT +animation (derive from replicated velocity); the pod-LAN config (real IPs, bare-IP pilot entries). See +[[open-questions]]. [T3] ## 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 diff --git a/engine/MUNGA_L4/L4VIDRND.cpp b/engine/MUNGA_L4/L4VIDRND.cpp index b6a6322..8952e6c 100644 --- a/engine/MUNGA_L4/L4VIDRND.cpp +++ b/engine/MUNGA_L4/L4VIDRND.cpp @@ -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 }; diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 5b4ed02..430499c 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1515,8 +1515,7 @@ void for (int ei = 0; ei < enc; ++ei) { Mech *em = (Mech *)ec[ei]; - if (em == 0 || em->GetInstance() != ReplicantInstance - || em->IsMechDestroyed()) continue; + if (em == 0 || em->IsMechDestroyed()) continue; // any peer (solo dummy OR replicant) Point3D ep = em->localOrigin.linearPosition; float edx = (float)ep.x - (float)localOrigin.linearPosition.x; float edz = (float)ep.z - (float)localOrigin.linearPosition.z;