Aim coordinates: route all conversions through the true NDC (fixes the

sideways lock offset)

The windowed present stretches the fixed 800x600 backbuffer into the
client area while the projection's aspect follows the CLIENT (resize
rebuild) -- so the dpl2d/reticle frame, the client frame and NDC only
coincide for a backbuffer-shaped window. The mouse mapping and the
square-frame ray shortcut (cx = rx*tanHalfFov) were exact at screen
centre and drifted outward with a side-flipped sign: aiming left of the
enemy locked as if right of it (user report).

- BTSetAimProjection now publishes BOTH proj scales (P11 carries the
  aspect) + the backbuffer size (the dpl2d frame). NOTE: GetViewport at
  the loop top holds the PREVIOUS frame's last pass viewport (gauge
  passes shrink it) -> use the renderer's own GetWidth/GetHeight.
- BTGetAimRay / BTProjectToReticle convert reticle <-> NDC per axis
  (ndc_x = rx * vh/vw; camera x = ndc_x/P11) -- exact for any window
  shape and FOV.
- New BTClientToReticle undoes the present stretch for the mouse
  (client px -> viewport px -> reticle coords); mech4 uses it instead
  of the raw client-height mapping.
- 1Hz aim telemetry in the [target] log (hits/noRay/noPick): verified
  100% pick hit rate once the projection is live (the pre-visibility
  mission-load seconds report noRay -- that was the "flicker").

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 22:13:45 -05:00
co-authored by Claude Fable 5
parent 9666dc5b5e
commit cf9f56044e
2 changed files with 98 additions and 30 deletions
+25 -12
View File
@@ -1272,17 +1272,17 @@ void
pCur(&cp) && pStc(wnd, &cp) && pCr(wnd, &rc) &&
rc.right > rc.left && rc.bottom > rc.top)
{
const float w = (float)(rc.right - rc.left);
const float h = (float)(rc.bottom - rc.top);
const float hh = h * 0.5f;
float rx = ((float)cp.x - w * 0.5f) / hh;
float ry = ((float)cp.y - h * 0.5f) / hh;
// clamp to the visible frame (aspect-wide in x)
const float xmax = (w / h);
if (rx < -xmax) rx = -xmax;
if (rx > xmax) rx = xmax;
if (ry < -1.0f) ry = -1.0f;
if (ry > 1.0f) ry = 1.0f;
// client px -> reticle coords through the RENDERER
// (it undoes the backbuffer->client present stretch;
// a raw client-height mapping is only exact for a
// backbuffer-shaped window and skews the pick
// sideways otherwise).
extern void BTClientToReticle(float mx, float my,
float cw, float ch, float *rx, float *ry);
float rx = 0.0f, ry = 0.0f;
BTClientToReticle((float)cp.x, (float)cp.y,
(float)(rc.right - rc.left),
(float)(rc.bottom - rc.top), &rx, &ry);
gBTAimX = rx;
gBTAimY = ry;
}
@@ -2311,15 +2311,25 @@ void
// resolves damage to the zone under the crosshair (aimed fire).
Entity *hotTarget = 0; // mech under the crosshair NOW
Point3D hotPoint; // picked world point on its hull
static int gAimNoRay = 0, gAimNoPick = 0, gAimHits = 0; // 1Hz diagnostics
{
extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
float rs[3], rd[3];
if (BTGetAimRay(gBTAimX, gBTAimY, rs, rd) && gEnemyMech != 0)
if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
{
++gAimNoRay;
}
else if (gEnemyMech != 0)
{
Point3D rayStart(rs[0], rs[1], rs[2]);
Vector3D rayDir(rd[0], rd[1], rd[2]);
if (((Mech *)gEnemyMech)->PickRayHit(rayStart, rayDir, 4000.0f, &hotPoint))
{
hotTarget = gEnemyMech;
++gAimHits;
}
else
++gAimNoPick;
}
// The Reticle struct (the mech's TargetReticle attribute): position,
@@ -2396,7 +2406,10 @@ void
: (designated ? " designated (off-crosshair)" : " no target"))
<< " range=" << range
<< (range <= kWeaponRange ? " IN RANGE" : "")
<< " [hits=" << gAimHits << " noRay=" << gAimNoRay
<< " noPick=" << gAimNoPick << "]"
<< "\n" << std::flush;
gAimHits = 0; gAimNoRay = 0; gAimNoPick = 0;
}
// --- FIRING (bring-up): on the trigger, with a target in range and the