diff --git a/context/multiplayer.md b/context/multiplayer.md index 7e645d3..742f78e 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -104,11 +104,30 @@ 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`. + ## Remaining (P6 phase 4 / Phase 7) -Interactive 2-window driving + the `-net`-mode aim projection / drive (A's aim ray was dead — `noRay`, and -`BT_AUTODRIVE`/`BT_GOTO` didn't move A in `-net` mode) 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] +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] ## 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/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index c6f07d3..5b4ed02 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -1499,10 +1499,33 @@ void if (s_goto < 0) { const char *gv = getenv("BT_GOTO"); - if (gv && sscanf(gv, "%f %f", &s_gx, &s_gz) == 2) s_goto = 1; + if (gv && !stricmp(gv, "enemy")) s_goto = 2; // chase nearest replicant + else if (gv && sscanf(gv, "%f %f", &s_gx, &s_gz) == 2) s_goto = 1; else s_goto = 0; } - if (s_goto == 1) + if (s_goto == 2) + { + // DYNAMIC target (test harness): beeline toward the CLOSEST live + // peer replicant wherever it currently is -- MP spawn positions + // vary per run, so a fixed "x z" can't reliably face the enemy. + extern int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut); + Entity *ec[32]; + const int enc = BTGetTargetCandidates((Entity *)this, ec, 32); + float best = 1e30f; int found = 0; + for (int ei = 0; ei < enc; ++ei) + { + Mech *em = (Mech *)ec[ei]; + if (em == 0 || em->GetInstance() != ReplicantInstance + || em->IsMechDestroyed()) continue; + Point3D ep = em->localOrigin.linearPosition; + float edx = (float)ep.x - (float)localOrigin.linearPosition.x; + float edz = (float)ep.z - (float)localOrigin.linearPosition.z; + float ed2 = edx*edx + edz*edz; + if (ed2 < best) { best = ed2; s_gx = (float)ep.x; s_gz = (float)ep.z; found = 1; } + } + if (found) s_goto = 2; else { gBTGotoActive = 0; } + } + if (s_goto == 1 || (s_goto == 2 && (s_gx != 0.0f || s_gz != 0.0f))) { float ddx = s_gx - (float)localOrigin.linearPosition.x; float ddz = s_gz - (float)localOrigin.linearPosition.z;