aimed fire strikes the part under the crosshair: the per-segment pick (#73)
The port's target pick was a whole-mech bounding-box slab test, and every hit -- aimed or not -- dispatched zone -1 into the victim's cylinder lottery. The recovered 1995 model (the division-card scene intersection) struck a SEGMENT and credited that segment's own damage zone. This is the port's equivalent. At render-tree build, each segment's draw object and its PrimaryDamageZone -- authored per segment in the skeleton stream, read by JMOVER.cpp:290 -- are recorded in MechRenderTree::segPick. The pick (BTL4VideoRenderer:: MechSegmentPick) ray-tests the per-segment bounding spheres on the live posed skeleton, using the draw-cached mLocalToWorld (at most one frame stale, fine for aiming). Selection is SPECIFICITY-FIRST: among the spheres the ray pierces, the smallest radius wins, normalized-distance tie-break. Both obvious rules were measured failing the same way before this one: the torso mesh's sphere (r~4.1 on the MadCat, vs shoulders at r~1.0) envelops nearly the whole mech, so its front face is nearest for any aim AND any near-body ray normalizes to ~0 against it. Limb spheres nest inside the envelope; smallest-pierced picks the most specific part on the aim line, and the torso wins only when no limb is threaded -- the per-part semantic the pod's mesh test produced. mech4.cpp tries the segment pick per candidate; the box PickRayHit survives only as the fallback (no tree yet, wrecked, spectator), still carrying zone -1 into the lottery, and a structure occlusion clears the zone. The winner's zone rides MECH_TARGET_SUBIDX + targetReticle.targetDamageZone into SendDamageMessage, so aimed hits now dispatch a real zone; the victim's handler applies it directly (bursts 2+ still re-lottery, authentic per the recovered @0x4a0230 loop). Bench, the same L/C/R sweep that exposed the bug: aiming left now lands 36/41 hits on zone 2 = jointlshoulder -- the left arm -- with a 0.30 thread score, where the same aim was a 6-way lottery spray before. The MadCat's authored segment->zone map is rich (shoulders 2/9, guns 6/17, hip 1, six leg zones, torso 0). Known approximations, flagged for field verification: sphere bounds rather than triangles, and a torso-envelope graze credits the torso where the pod's exact mesh test would have missed into air. The field protocol is the one the testers already ran on night 6: stationary mechs, short range, fire only at one arm -- the paper doll should now damage THAT arm. Diag: BT_PICK_LOG ([segpick] map at build, [pickwin] per pick). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
e110b10ac8
commit
8a99972f22
@@ -4796,6 +4796,7 @@ void
|
||||
// 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
|
||||
int hotZone = -1; // #73: the struck SEGMENT's zone (-1 = box fallback -> lottery)
|
||||
Entity *pickTarget = 0; // what the boresight ray hit (mech/terrain)
|
||||
Point3D pickPoint; // where it hit
|
||||
// BT_RANGE_LOG (Gitea #4 VERDICT instrumentation -- uncommitted diag):
|
||||
@@ -4973,13 +4974,33 @@ void
|
||||
// pick the CLOSEST one the boresight ray strikes -- generalising
|
||||
// the solo gEnemyMech.
|
||||
float bestDist = 1e30f;
|
||||
hotZone = -1; // #73: reset per frame (declared with hotTarget)
|
||||
for (int ci = 0; ci < nc; ++ci)
|
||||
{
|
||||
Mech *m = (Mech *)cand[ci];
|
||||
if (m == 0 || m->IsMechDestroyed())
|
||||
continue;
|
||||
// #73 -- PER-PART pick first. The 1995 pick was a dpl scene
|
||||
// intersection against the DRAWN geometry (the division card
|
||||
// cast from the view; results at DCS/segment granularity), so
|
||||
// aimed fire struck a SEGMENT and credited that segment's own
|
||||
// damage zone. The renderer-side equivalent ray-tests the
|
||||
// per-segment bounding spheres on the live posed skeleton and
|
||||
// returns the nearest segment's hit point + PrimaryDamageZone.
|
||||
// The whole-mech box (PickRayHit) survives only as the
|
||||
// fallback when no tree/segment resolves -- its zone stays -1,
|
||||
// which routes through the unaimed cylinder lottery as before.
|
||||
extern int BTMechSegmentPick(void *mech, const float s[3],
|
||||
const float dir[3], float max_range,
|
||||
float hit_out[3], int *zone_out);
|
||||
Point3D hp;
|
||||
if (!m->PickRayHit(rayStart, rayDir, 4000.0f, &hp))
|
||||
int zone = -1;
|
||||
float segHit[3];
|
||||
if (BTMechSegmentPick(m, rs, rd, 4000.0f, segHit, &zone))
|
||||
{
|
||||
hp.x = segHit[0]; hp.y = segHit[1]; hp.z = segHit[2];
|
||||
}
|
||||
else if (!m->PickRayHit(rayStart, rayDir, 4000.0f, &hp))
|
||||
continue;
|
||||
float dx = hp.x - rs[0], dy = hp.y - rs[1], dz = hp.z - rs[2];
|
||||
float d = dx*dx + dy*dy + dz*dz;
|
||||
@@ -4988,6 +5009,7 @@ void
|
||||
bestDist = d;
|
||||
hotTarget = cand[ci];
|
||||
hotPoint = hp;
|
||||
hotZone = zone;
|
||||
}
|
||||
}
|
||||
// WORLD-STRUCTURE pick (task #50): ray-test the STATIC collision
|
||||
@@ -5054,6 +5076,7 @@ void
|
||||
// HudSim part_013.c:5620).
|
||||
pickTarget = (structOwner != 0) ? structOwner : gBTTerrainEntity;
|
||||
pickPoint = structPoint;
|
||||
hotZone = -1; // #73: a structure has no mech zone
|
||||
rlPickClass = 2; // BT_RANGE_LOG (issue #4)
|
||||
++gAimGround;
|
||||
}
|
||||
@@ -5078,12 +5101,16 @@ void
|
||||
}
|
||||
|
||||
// The Reticle struct (the mech's TargetReticle attribute): position,
|
||||
// pick result. targetDamageZone stays -1 -- the zone ROLL happens at
|
||||
// damage delivery (the authentic percent-table roll, STEP 6).
|
||||
// pick result. #73: targetDamageZone now carries the struck
|
||||
// SEGMENT's zone from the per-part pick -- the authentic aimed-fire
|
||||
// model ("For BattleTech, damage zones are only valid via reticle
|
||||
// based weapons", ENTITY3.h:131). It stays -1 only on the box
|
||||
// fallback, which routes through the unaimed cylinder lottery
|
||||
// (STEP 6) exactly as before.
|
||||
targetReticle.reticlePosition.x = gBTAimX;
|
||||
targetReticle.reticlePosition.y = gBTAimY;
|
||||
targetReticle.targetEntity = pickTarget;
|
||||
targetReticle.targetDamageZone = -1;
|
||||
targetReticle.targetDamageZone = (pickTarget != 0) ? hotZone : -1;
|
||||
if (pickTarget != 0)
|
||||
targetReticle.rayIntersection = pickPoint;
|
||||
|
||||
@@ -5091,7 +5118,7 @@ void
|
||||
if (pickTarget != 0)
|
||||
{
|
||||
MECH_TARGET_ENTITY(this) = pickTarget;
|
||||
MECH_TARGET_SUBIDX(this) = -1;
|
||||
MECH_TARGET_SUBIDX(this) = hotZone; // #73: aimed zone (or -1)
|
||||
MECH_TARGET_POS(this) = pickPoint; // beam endpoint = the pick
|
||||
}
|
||||
else if (gBTTerrainEntity != 0)
|
||||
|
||||
Reference in New Issue
Block a user