Authentic target acquisition LIVE: reticle slew + pick-ray lock + aimed zone damage (task #36)

The engine Reticle model (MUNGA/RETICLE.h [T0]) reconstructed end to end:
- Mech::targetReticle is a real Reticle member bound to the TargetReticle
  attribute (0x1d), per the RP VTV analog (VTV.h targetReticle).
- Crosshair slew: mouse -> client rect -> reticle coords (the pod stick
  free-aim channel's dev-box stand-in); BT_AIM="x y" pins it headless.
  LMB fires lasers / RMB missiles (alongside SPACE/CTRL).
- Pick ray: the ACTIVE eye publishes pos + LookAtRH basis (BTSetAimCamera,
  L4VIDRND view-write site) + the render loop publishes proj._22;
  BTGetAimRay builds the world ray, Mech::PickRayHit slab-tests it against
  the collision template's ExtentBox via the engine's BoundingBox::HitBy
  (local frame; clips the Line at entry) -> world hull point.
- Designation: the mech under the crosshair designates (sticky; re-hover
  refreshes; cleared when the target leaves the roster at burial); the
  entity target slots 0x37c/0x388/0x38c feed the whole weapon path.
- Aimed fire: while HOT the impact point is the PICKED hull point -> the
  STEP-6 cylinder lookup resolves the zone under the crosshair (verified:
  center-aim -> head-band zone 13 dominant). Off-crosshair the sticky
  designation converges on center mass.
- HUD: the aim group draws at the slewed position ([0x9a] translate,
  contained by push/pop); the designator ring tracks the target's
  projected point (subB9 hot / subB8 designated, BTProjectToReticle);
  edge arrows when off-screen/behind.
- AUTHENTIC gating: no fire arc exists in the binary (FireWeapon fires
  whenever HasActiveTarget, part_013.c:7758) -> BT_FIRE_ARC is now an
  explicit OPT-IN presentation clamp; the hardwired gEnemyMech lock and
  the projectile path's gEnemyMech fallback are removed.
- Fixed en route: every renderable rebuild stomped mCamera back to the
  chase eye (start-inside silently lost the cockpit camera; the aim feed
  exposed it). BTL4VideoRenderer::mViewInside persists the chosen view.

Verified headless: BT_AIM="0 0" -> HOT lock, pick hits the hull face at
exact range, aimed zones resolve; BT_AIM="0.8 0.3" -> no lock, zero
damage, zero missile launches; kill chain completes to wreck + smoke.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-08 20:57:11 -05:00
co-authored by Claude Fable 5
parent 6988821525
commit d78bde066d
10 changed files with 541 additions and 185 deletions
+103
View File
@@ -86,6 +86,102 @@ void BTSetLodEye(float x, float y, float z)
}
}
// ---------------------------------------------------------------------------
// AIM CAMERA FEED (task #36 authentic targeting): the ACTIVE eye publishes its
// world pose here (DPLEyeRenderable, the mCamera==this write site) and the
// render loop publishes the live projection's FOV term; the game side then
// derives the reticle PICK RAY (crosshair -> world, the engine Reticle's
// "pick point intersection" input) and the inverse projection (world ->
// reticle coords, the target-designator box position). Reticle coordinate
// convention = the dpl2d frame: centered origin, +y down, unit = half the
// viewport height (see dpl2d.cpp MapX/MapY).
// ---------------------------------------------------------------------------
static float gBTAimCamPos[3] = {0, 0, 0};
static float gBTAimCamX[3] = {1, 0, 0}; // camera right (LookAtRH xaxis)
static float gBTAimCamY[3] = {0, 1, 0}; // camera up (LookAtRH yaxis)
static float gBTAimCamZ[3] = {0, 0, 1}; // camera BACK (LookAtRH zaxis; view dir = -Z)
static float gBTAimTanHalf = 0.0f; // tan(fovY/2) = 1 / proj._22
static int gBTAimCamValid = 0;
void BTSetAimCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[3])
{
for (int i = 0; i < 3; ++i)
{
gBTAimCamPos[i] = pos[i];
gBTAimCamX[i] = xax[i];
gBTAimCamY[i] = yax[i];
gBTAimCamZ[i] = zax[i];
}
gBTAimCamValid = 1;
}
void BTSetAimProjection(float proj22)
{
if (proj22 > 1e-6f)
gBTAimTanHalf = 1.0f / proj22;
}
//
// Crosshair (reticle coords) -> world pick ray. RH camera looks down -Z;
// a point at reticle (rx, ry) sits at camera-space (rx*t, -ry*t, -1)
// (reticle +y is DOWN, camera +y is UP; t = tan(fovY/2), and the reticle's
// half-viewport-height unit cancels the aspect term exactly).
//
int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3])
{
if (!gBTAimCamValid || gBTAimTanHalf <= 0.0f)
return 0;
const float cx = rx * gBTAimTanHalf;
const float cy = -ry * gBTAimTanHalf;
float d[3];
float len = 0.0f;
for (int i = 0; i < 3; ++i)
{
d[i] = cx * gBTAimCamX[i] + cy * gBTAimCamY[i] - gBTAimCamZ[i];
len += d[i] * d[i];
}
len = sqrtf(len);
if (len < 1e-6f) return 0;
for (int i = 0; i < 3; ++i)
{
outStart[i] = gBTAimCamPos[i];
outDir[i] = d[i] / len;
}
return 1;
}
//
// World point -> reticle coords (the inverse; the designator box position).
// Returns 1 when the point is IN FRONT of the camera (rx/ry valid for
// on-screen placement); 0 when behind (rx still carries the correct SIDE
// sign so the caller can pick the left/right off-screen arrow).
//
int BTProjectToReticle(const float world[3], float *rx, float *ry)
{
if (!gBTAimCamValid || gBTAimTanHalf <= 0.0f)
{
*rx = 0.0f; *ry = 0.0f;
return 0;
}
float rel[3];
for (int i = 0; i < 3; ++i)
rel[i] = world[i] - gBTAimCamPos[i];
const float xc = rel[0]*gBTAimCamX[0] + rel[1]*gBTAimCamX[1] + rel[2]*gBTAimCamX[2];
const float yc = rel[0]*gBTAimCamY[0] + rel[1]*gBTAimCamY[1] + rel[2]*gBTAimCamY[2];
const float zc = rel[0]*gBTAimCamZ[0] + rel[1]*gBTAimCamZ[1] + rel[2]*gBTAimCamZ[2];
const float depth = -zc; // camera looks down -Z
if (depth < 0.1f)
{
*rx = (xc >= 0.0f) ? 2.0f : -2.0f; // side sign only
*ry = 0.0f;
return 0;
}
*rx = xc / (gBTAimTanHalf * depth);
*ry = -yc / (gBTAimTanHalf * depth);
return 1;
}
void BTPushBeamKind(float fx, float fy, float fz, float tx, float ty, float tz,
unsigned color, float ttl, float width, int kind)
{
@@ -7158,6 +7254,13 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte
// the render lists
memset(mRenderLists, 0, sizeof(mRenderLists));
// aim-ray feed (task #36): publish the live projection's FOV term so the
// game side can build the reticle pick ray / designator projection.
{
extern void BTSetAimProjection(float proj22);
BTSetAimProjection(mProjectionMatrix._22);
}
HierarchicalDrawComponent *drawComp;
SChainIteratorOf<HierarchicalDrawComponent*> iterator(&mRenderables);
while ((drawComp = iterator.ReadAndNext()) != NULL)
+26
View File
@@ -5575,7 +5575,33 @@ void
// BOTH the cockpit and chase eyes; an unconditional write here let
// whichever eye executed LAST stomp the toggled camera every frame.
if (myRenderer->mCamera == this)
{
myDevice->SetTransform(D3DTS_VIEW, &view);
if (dbgEyeExec < 8)
DEBUG_STREAM << "[EYE] ACTIVE eye " << (void *)this
<< " (mCamera match) pos.y=" << pos.y << "\n" << std::flush;
// aim-ray feed (task #36): publish this eye's world pose --
// the LookAtRH basis (zaxis = back, view direction = -zaxis)
// -- for the reticle pick ray / designator projection.
{
extern void BTSetAimCamera(const float pos[3], const float xax[3],
const float yax[3], const float zax[3]);
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 };
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 };
BTSetAimCamera(p, x3, y3, z3);
}
}
myRenderer->GetMatrixStack()->Pop();
}
else if (dbgEyeExec < 8)