World-pick targeting: 0x388 = whatever is downrange -- mech, terrain, or nothing (task #41)

User video evidence (lasers firing at nothing) + the colleague's
torso-locked report + the binary all reconcile into one model:

The boresight pick hits the WORLD, and the target slot mech+0x388 holds
whatever entity is downrange:
- enemy mech under the boresight -> aimed target (hull point -> STEP-6
  zone under the boresight, hotbox + lock ring, damage);
- else the TERRAIN downrange (BTGroundRayHit to 1200) -> the beam and
  missiles fire at the scenery ("firing at nothing", as in the pod
  videos), range caret reads the ground distance (authentic :5639), no
  damage;
- else (sky) -> no target, and the weapon's own double 0x388 gate
  refuses the discharge.

Binary evidence for non-mech targets: HudSimulation :5620 explicitly
handles a target WITHOUT damage zones (target->0x120 == 0) -- dead code
if only mechs were ever targeted. The pick is automatic every frame
(0x388: 11 reads / 0 direct stores in CODE).

Implementation: gBTTerrainEntity captured in MakeEntityRenderables
(Terrain::GetClassDerivations); mech4 pick = enemy PickRayHit else
ground ray -> terrain entity + ground point; damage/hotbox/lock only
for the mech pick; the task-40 enemy auto-converge REMOVED (facing away
now fires at the scenery, not magically at the enemy -- the user's
complaint). Verified all three states headless (BT_AIM 0,0 / .6,.5 /
0,-.8): aimed zone hits / terrain beams + 0 damage / no discharge.

Fourth (and evidence-complete) targeting model iteration; KB updated.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 00:06:00 -05:00
co-authored by Claude Fable 5
parent 60ed54e008
commit e30a61a62f
4 changed files with 133 additions and 102 deletions
+79 -77
View File
@@ -2286,38 +2286,62 @@ void
}
}
// --- TARGETING (task #36 -- the AUTHENTIC Reticle acquisition,
// engine/MUNGA/RETICLE.h [T0]): the pilot slews the crosshair
// (gBTAimX/Y); a pick ray through it tests the enemy's collision
// volume; the mech UNDER the crosshair becomes the designated target
// (sticky -- it persists when the crosshair drifts off, marked by
// the HUD designator; re-hover to refresh, burial clears it). The
// designation feeds the engine-Entity target slots the whole weapon
// path reads (target position @0x37c, target entity @0x388, zone
// @0x38c). While the crosshair is ON the target ("hot"), the aim
// point is the PICKED HULL POINT -- the STEP-6 cylinder lookup then
// resolves damage to the zone under the crosshair (aimed fire).
Entity *hotTarget = 0; // mech under the crosshair NOW
// --- TARGETING: the WORLD-PICK model (task #41, the reconciliation of
// ALL the evidence). The boresight ray picks whatever is DOWNRANGE:
// 1. the enemy mech's collision volume -> aimed target (hull point
// -> STEP-6 zone under the boresight; hotbox + lock ring);
// 2. else the TERRAIN (BTGroundRayHit) -> the ground point becomes
// the target -- the beam fires at the scenery ("firing at
// nothing", seen in the pod demo videos), no damage;
// 3. else (sky, nothing within range) -> NO target, and the
// weapon's own double gate (FUN_004baa88:7689 + FUN_004bace8:
// 7727 -- both require mech+0x388 != 0) refuses the discharge.
// Binary evidence for non-mech targets: HudSimulation :5620
// explicitly handles a target WITHOUT damage zones (target->0x120
// == 0) -- dead code if only mechs were ever targeted. The pick is
// AUTOMATIC every frame (0x388 has 11 reads / 0 direct stores in
// CODE -- written indirectly, never a manual player lock).
Entity *hotTarget = 0; // the enemy mech under the boresight
Point3D hotPoint; // picked world point on its hull
static int gAimNoRay = 0, gAimNoPick = 0, gAimHits = 0; // 1Hz diagnostics
Entity *pickTarget = 0; // what the boresight ray hit (mech/terrain)
Point3D pickPoint; // where it hit
static int gAimNoRay = 0, gAimGround = 0, gAimHits = 0; // 1Hz diagnostics
{
extern int BTGetAimRay(float rx, float ry, float outStart[3], float outDir[3]);
extern Entity *gBTTerrainEntity; // captured by MakeEntityRenderables
float rs[3], rd[3];
if (!BTGetAimRay(gBTAimX, gBTAimY, rs, rd))
{
++gAimNoRay;
}
else if (gEnemyMech != 0)
else
{
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))
if (gEnemyMech != 0 && !((Mech *)gEnemyMech)->IsMechDestroyed()
&& ((Mech *)gEnemyMech)->PickRayHit(rayStart, rayDir, 4000.0f, &hotPoint))
{
hotTarget = gEnemyMech;
hotTarget = gEnemyMech;
pickTarget = gEnemyMech;
pickPoint = hotPoint;
++gAimHits;
}
else
++gAimNoPick;
{
// the ground downrange of the guns (max = the pod's HUD
// range scale; past it the shot is a sky shot -> no target)
extern bool BTGroundRayHit(float,float,float, float,float,float,
float, float*,float*,float*);
float hx, hy, hz;
if (gBTTerrainEntity != 0 &&
BTGroundRayHit(rs[0], rs[1], rs[2], rd[0], rd[1], rd[2],
1200.0f, &hx, &hy, &hz))
{
pickTarget = gBTTerrainEntity;
pickPoint.x = hx; pickPoint.y = hy; pickPoint.z = hz;
++gAimGround;
}
}
}
// The Reticle struct (the mech's TargetReticle attribute): position,
@@ -2325,53 +2349,44 @@ void
// damage delivery (the authentic percent-table roll, STEP 6).
targetReticle.reticlePosition.x = gBTAimX;
targetReticle.reticlePosition.y = gBTAimY;
targetReticle.targetEntity = hotTarget;
targetReticle.targetEntity = pickTarget;
targetReticle.targetDamageZone = -1;
if (hotTarget != 0)
targetReticle.rayIntersection = hotPoint;
if (pickTarget != 0)
targetReticle.rayIntersection = pickPoint;
// TARGET (mech+0x388) is AUTO-ACQUIRED, not manually locked (task #40,
// binary-confirmed): the weapon's fire path (Emitter FUN_004baa88 :7689
// + FireWeapon FUN_004bace8 :7727) is DOUBLY gated on mech+0x388 != 0 --
// no target, NO beam, ever -- but 0x388 is WRITTEN INDIRECTLY (a
// disasm scan of the whole CODE section finds 11 reads and ZERO direct
// stores), i.e. the game AUTO-SELECTS the target for you; you never
// manually "lock" to fire. The spinning-ring LOCK (HudSimulation
// 5619-5634) is a SEPARATE, stricter state layered on top. So: 0x388
// = the enemy whenever it is alive (the pod's auto-target); firing
// needs only that, NOT a pinpoint boresight hit. A pinpoint hull hit
// (hotTarget) UPGRADES the shot to aimed-zone damage + the lock ring.
Entity *autoTarget =
(gEnemyMech != 0 && !((Mech *)gEnemyMech)->IsMechDestroyed())
? gEnemyMech : 0;
if (autoTarget != 0)
// the engine-Entity target slots the whole weapon path reads
if (pickTarget != 0)
{
MECH_TARGET_ENTITY(this) = autoTarget;
MECH_TARGET_ENTITY(this) = pickTarget;
MECH_TARGET_SUBIDX(this) = -1;
if (hotTarget != 0)
MECH_TARGET_POS(this) = hotPoint; // aimed: the picked hull point
else
{
// no pinpoint pick -> converge on the target's centre mass
// (the beam still fires; the zone resolves near-centre).
Point3D cm = ((Mech *)autoTarget)->localOrigin.linearPosition;
cm.y += kMuzzleHeight;
MECH_TARGET_POS(this) = cm;
}
MECH_TARGET_POS(this) = pickPoint; // beam endpoint = the pick
}
else
{
MECH_TARGET_ENTITY(this) = 0; // no living enemy -> no target
MECH_TARGET_ENTITY(this) = 0; // sky: no target, no discharge
MECH_TARGET_SUBIDX(this) = -1;
}
}
// HUD feeds: the range caret + the hotbox (world point + state) + the
// recovered-Execute instruments (compass, twist tape, group mask).
// The range caret tracks the PICK (authentic: :5639 computes it from
// mech+0x37c whatever the target is -- a terrain pick reads the ground
// distance); the hotbox + lock exist ONLY for a mech target (:5620 --
// a target with no damage zones gets neither).
{
extern void BTSetHudTargetRange(Scalar range);
Entity *des = MECH_TARGET_ENTITY(this);
if (des != 0)
if (des != 0 && des != hotTarget)
{
// terrain pick: range to the ground point; no hotbox, no ring.
Point3D tp = MECH_TARGET_POS(this);
float hddx = tp.x - localOrigin.linearPosition.x;
float hddz = tp.z - localOrigin.linearPosition.z;
BTSetHudTargetRange((Scalar)sqrtf(hddx*hddx + hddz*hddz));
gBTHudLockState = 0;
}
else if (des != 0)
{
Mech *dm = (Mech *)des;
Point3D dp = dm->localOrigin.linearPosition;
@@ -2426,7 +2441,8 @@ void
{
Point3D enemyPos = ((Mech *)gEnemyMech)->localOrigin.linearPosition;
const int designated = (MECH_TARGET_ENTITY(this) != 0);
// damage routes ONLY when the boresight pick is the enemy mech
const int designated = (hotTarget != 0);
float ddx = enemyPos.x - localOrigin.linearPosition.x;
float ddy = enemyPos.y - localOrigin.linearPosition.y;
@@ -2438,14 +2454,15 @@ void
{
gTargetLogAccum = 0.0f;
DEBUG_STREAM << "[target] aim=(" << gBTAimX << "," << gBTAimY << ")"
<< (hotTarget != 0 ? " HOT-aimed (zone under crosshair)"
: (designated ? " auto-target (fire freely, body hits)" : " no living target"))
<< (hotTarget != 0 ? " ENEMY under boresight (aimed)"
: (pickTarget != 0 ? " terrain downrange (beam at scenery)"
: " sky (no target, no discharge)"))
<< " range=" << range
<< (range <= kWeaponRange ? " IN RANGE" : "")
<< " [aimedHits=" << gAimHits << " noRay=" << gAimNoRay
<< " offHull=" << gAimNoPick << "]"
<< " [enemyPicks=" << gAimHits << " groundPicks=" << gAimGround
<< " noRay=" << gAimNoRay << "]"
<< "\n" << std::flush;
gAimHits = 0; gAimNoRay = 0; gAimNoPick = 0;
gAimHits = 0; gAimNoRay = 0; gAimGround = 0;
}
// --- FIRING (bring-up): on the trigger, with a target in range and the
@@ -2502,29 +2519,14 @@ void
gFireCooldown = kFireCooldown;
++gShotCount;
// Impact point (task #36): while the crosshair is ON the target the
// shot lands at the PICKED HULL POINT -- the STEP-6 cylinder lookup
// resolves damage to the zone under the crosshair (aimed fire: leg
// shots hit legs, torso shots hit the torso). Off-crosshair (the
// sticky designation converging), the shot enters on the surface
// toward the shooter at chest height (the unaimed synth point --
// exactly the axis point would be angularly degenerate for the
// cylinder sector lookup).
Point3D impact;
if (hotTarget != 0)
{
impact = hotPoint;
}
else
{
impact = enemyPos;
if (range > 1e-3f)
{
impact.x -= (ddx / range) * 3.0f; // ~torso radius toward shooter
impact.z -= (ddz / range) * 3.0f;
}
impact.y += kMuzzleHeight; // chest height (beam aim height)
}
// Impact point (tasks #36/#41): damage happens ONLY when the enemy
// is under the boresight (designated == hotTarget != 0, checked in
// the gate above), so the shot always lands at the PICKED HULL
// POINT -- the STEP-6 cylinder lookup resolves damage to the zone
// under the boresight (aimed fire: leg shots hit legs, torso shots
// the torso). Terrain picks never reach here (beam + scenery
// impact only, no damage dispatch).
Point3D impact = hotPoint;
Origin exp_origin = ((Mech *)gEnemyMech)->localOrigin;
exp_origin.linearPosition = impact; // at the hit point