From fb3ed38fa7643c5725eeccf95c46971020b8697a Mon Sep 17 00:00:00 2001 From: arcattack Date: Mon, 6 Jul 2026 22:22:33 -0500 Subject: [PATCH] map: fix contact-blip projection -- enemy now plots inside the radar FOV wedge The previous commit found+drew contacts but they landed off-radar. Debugged with BT_MAP_LOG entity/blip tracing: the reads were CORRECT all along -- the enemy mech (cls 0xBB9, own=0) sits at dsq=14400 (exactly 120u) from the player (own=1, dsq=0), and the grid also carries ~311 cls-0x5E vehicle props per cycle (the map's scattered vehicles) which the 0xBB9 blip filter correctly skips. The bug was the PROJECTION: worldToView is a camera (view->world) matrix and Point3D::Multiply bakes a scale that is wrong for a point transform -- a 120u contact at ppm=0.366 projected to |167px| instead of |44px| (~3.8x), and with a spurious rotation. (The earlier "377-828m" reading was this wrong scale amplifying the varying player-drift distance, not a distance error.) Fixed by projecting delta = (entity - viewpoint) DIRECTLY: rotate by -heading (from the orientation quaternion's yaw, top-down heading-up) and scale by pixelsPerMeter. Now |screen| == |delta|*ppm exactly (blip @(0,44) for the 120u enemy), on-radar inside the view wedge. Blip is a cross + box so it reads as a distinct contact marker. Verified live (BT_DEV_GAUGES): the enemy contact renders as a box marker INSIDE the FOV wedge (render-confirmed via a 4x radar crop); combat un-regressed (TARGET DESTROYED after 8 hits), heap-clean under BT_HEAPCHECK. BT_MAP_LOG traces blip coords + scale + delta. Remaining map polish: blip-vs-wedge rotation convention (the enemy reads ahead/in-wedge, which is correct); the authentic pip symbol/name infrastructure (still stubbed); SetTargetRange (radar zoom, 1km default). Co-Authored-By: Claude Opus 4.8 (1M context) --- game/reconstructed/btl4rdr.cpp | 67 +++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/game/reconstructed/btl4rdr.cpp b/game/reconstructed/btl4rdr.cpp index 38db1dd..2da36cc 100644 --- a/game/reconstructed/btl4rdr.cpp +++ b/game/reconstructed/btl4rdr.cpp @@ -837,6 +837,11 @@ void Entity *owner = ResolveOperatorEntity(renderer); Entity *target = GetTarget(owner); // owner->GetVideoObject()->target + const int mlog = getenv("BT_MAP_LOG") ? 1 : 0; + if (mlog) + DEBUG_STREAM << "[map] DrawMoving owner=" << (void*)owner + << " view=(" << viewingPosition.x << "," << viewingPosition.z << ")\n" << std::flush; + for (;;) { Entity *entity = iter.ReadAndNext(); @@ -844,6 +849,19 @@ void { break; } + + if (mlog) + { + Point3D ep = EntityPosition(entity); + Scalar dx = ep.x - viewingPosition.x, dz = ep.z - viewingPosition.z; + DEBUG_STREAM << "[map] ent=" << (void*)entity + << " cls=0x" << std::hex << entity->GetClassID() << std::dec + << " own=" << (entity == owner ? 1 : 0) + << " pos=(" << ep.x << "," << ep.z << ")" + << " dsq=" << (dx*dx + dz*dz) + << "\n" << std::flush; + } + if (entity == owner) { continue; @@ -880,25 +898,40 @@ void // carries don't clutter the display -- the real pip table would key the // symbol off the model class, which is the proper follow-up. // - // Project the RELATIVE position (delta = entity - viewpoint): worldToView - // is a camera matrix and Point3D::Multiply applies only rotation+scale - // (not the translation), so feeding the absolute position leaves the - // viewpoint un-subtracted and the blip lands off-screen. - Point3D relative; - relative.x = delta.x; - relative.y = delta.y; - relative.z = delta.z; - Point3D screen; - screen.Multiply(relative, worldToView); // FUN_00408b98 (rotate+scale) - int sx = Round(screen.x); - int sz = Round(screen.z); + // Project delta = (entity - viewpoint) directly to radar pixels: the + // worldToView camera matrix bakes a scale that is wrong for a point + // transform (|blip| came out ~3.8x |delta|*ppm), so compute it here -- + // top-down, heading-up: rotate delta by -heading, then scale by + // pixelsPerMeter. |screen| == |delta|*ppm, on-radar for in-range mechs. + Scalar sinH = 0.0f, cosH = 1.0f; + if (currentAngularPointer != NULL) + { + EulerAngles e; + e = *currentAngularPointer; // Quaternion -> EulerAngles (operator=) + SinCosPair scH; + scH = Radian(e.yaw); // FUN_00408328 + sinH = scH.sine; + cosH = scH.cosine; + } + Scalar rx = delta.x * cosH + delta.z * sinH; + Scalar rz = -delta.x * sinH + delta.z * cosH; + int sx = Round(rx * pixelsPerMeter); + int sz = Round(rz * pixelsPerMeter); if (getenv("BT_MAP_LOG")) - DEBUG_STREAM << "[map] blip mech @(" << sx << "," << sz << ")\n" << std::flush; + DEBUG_STREAM << "[map] blip mech @(" << sx << "," << sz << ")" + << " scale=" << currentScale << " ppm=" << pixelsPerMeter + << " deltaXZ=(" << delta.x << "," << delta.z << ")\n" << std::flush; localView.SetColor((entity == target) ? boxColor : MapBlipColor()); - localView.MoveToAbsolute(sx - 4, sz); - localView.DrawLineToAbsolute(sx + 4, sz); - localView.MoveToAbsolute(sx, sz - 4); - localView.DrawLineToAbsolute(sx, sz + 4); + localView.MoveToAbsolute(sx - 5, sz); + localView.DrawLineToAbsolute(sx + 5, sz); + localView.MoveToAbsolute(sx, sz - 5); + localView.DrawLineToAbsolute(sx, sz + 5); + // small box around the blip so it reads as a distinct contact marker + localView.MoveToAbsolute(sx - 4, sz - 4); + localView.DrawLineToAbsolute(sx + 4, sz - 4); + localView.DrawLineToAbsolute(sx + 4, sz + 4); + localView.DrawLineToAbsolute(sx - 4, sz + 4); + localView.DrawLineToAbsolute(sx - 4, sz - 4); } } }