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) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-06 22:22:33 -05:00
co-authored by Claude Opus 4.8
parent 0bceb9a638
commit fb3ed38fa7
+50 -17
View File
@@ -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);
}
}
}