ROOT CAUSE: target designation wrote a Mech* into deathPending, disabling respawn (#57/#48)

MEASURED our compiled BTPlayer layout (new one-shot [layout] ctor diagnostic):
  sizeof=652 (0x28c)  killCount@0x270  pad_0x280@0x274
  objectiveMech@0x278  deathPending@0x284

The three target-designation sites wrote RAW at pilotArray[0]+0x284 intending the
BINARY's objectiveMech.  On our object 0x284 is deathPending -- the death-cycle
latch.  So EVERY target designation (the Comm bank 0x30-0x37, or the typed
t/y/u/i/o keys) stored a Mech pointer into deathPending, and a non-zero
deathPending makes VehicleDeadMessageHandler take its dedup early-return, which
skips the warp, ++deathCount, the PLAYER_DEAD record AND the drop-zone hunt.

=> designating a target silently disabled your respawn for the rest of the
   process.  That is #57's missing first cause: it explains David's very first
   death being swallowed with no prior death, and the 3-of-3 swallowed deaths in
   the final round.  It also explains the operator's persistent observation that
   the trouble began when the comms panel went live -- before the pilot list
   populated, there was nothing to designate.
   And the designation never worked either: the value never reached
   objectiveMech (input-audit finding #1).

FIX: all three sites now use named-member bridges in the complete-BTPlayer TU --
BTPilotSetObjectiveMech / BTPilotObjectiveMech / BTPilotVehicle / BTPilotPosition
/ BTVehicleDestroyed.  Also retires the raw +0x1fc (playerVehicle) reads and the
raw +0x100 position reads in ChooseNearestPilot, and routes its destroyed-check
through the real Mech predicate instead of the Is_Destroyed stub.

LAYOUT LOCKS: static_asserts on objectiveMech@0x278, deathPending@0x284,
killCount@0x270 and sizeof==0x28c, in the friend fn BTPlayerLayoutSelfCheck, so
a member shift fails the BUILD instead of silently clobbering the latch again.

Rigs: scratchpad/commstest.py (drives designation + captures panels),
valvetest.py, lampgallery.py (428-bitmap gallery for #48 visual search).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0166KTsC7ADm7VXEi1HF1jNg
This commit is contained in:
arcattack
2026-07-25 08:33:22 -05:00
co-authored by Claude Opus 5
parent cb50a1d491
commit 2dd4ee3910
6 changed files with 478 additions and 34 deletions
+53 -34
View File
@@ -81,6 +81,21 @@
extern void ToggleVoiceAssist(int voice_assist_subsystem);
extern Logical Is_Destroyed(int entity_handle);
//
// TARGET-DESIGNATION bridges (btplayer.cpp, the complete-BTPlayer TU). The three
// designation sites used to write RAW at `pilotArray[0] + 0x284` -- the BINARY's
// `objectiveMech` offset. Measured on our compiled object, 0x284 is
// **`deathPending`**, the death-cycle latch, so every designation silently
// disabled the pilot's respawn (Gitea #57) while the target never reached
// `objectiveMech` at all (Gitea #48 / input-audit finding #1). Named members
// only, from here on -- see the static_asserts in btplayer.cpp.
//
extern void BTPilotSetObjectiveMech(void *pilot, void *target_vehicle);
extern void *BTPilotObjectiveMech(void *pilot);
extern void *BTPilotVehicle(void *pilot);
extern int BTPilotPosition(void *pilot, float *out_xyz);
extern int BTVehicleDestroyed(void *vehicle);
//
// Mech::GetHorizontalFiringReach -- the reachable horizontal firing half-arc
// (radians) the mech's torso can bring its guns to bear off dead-ahead. Weapons
@@ -1337,11 +1352,9 @@ void
}
Pilot *chosen = pilotArray[index];
if (chosen != 0)
{
target = *(int *)((int)chosen + 0x1fc);
}
*(int *)((int)pilotArray[0] + 0x284) = target;
void *target_vehicle = (chosen != 0) ? BTPilotVehicle(chosen) : 0;
BTPilotSetObjectiveMech(pilotArray[0], target_vehicle);
(void)target;
}
}
@@ -1355,13 +1368,9 @@ void
{
if (pilotArrayBuilt && pilotArray[0] != 0)
{
int target = 0;
Pilot *chosen = pilotArray[page];
if (chosen != 0)
{
target = *(int *)((int)chosen + 0x1fc);
}
*(int *)((int)pilotArray[0] + 0x284) = target;
void *target_vehicle = (chosen != 0) ? BTPilotVehicle(chosen) : 0;
BTPilotSetObjectiveMech(pilotArray[0], target_vehicle);
}
}
@@ -1383,28 +1392,40 @@ void
int nearest_index = 0;
Logical none_yet = True;
//
// Positions come off each pilot's VEHICLE through the bridge (the binary's
// raw `pilot + 0x100` is an Entity-layout offset that does not exist on our
// Player). A pilot with no vehicle -- dead, or not yet acquired -- has no
// position and cannot be the nearest target, so it is skipped.
//
float self_pos[3];
if (!BTPilotPosition(pilotArray[0], self_pos))
{
Check_Fpu();
return;
}
void *self_vehicle = BTPilotVehicle(pilotArray[0]);
(void)self_id; // the binary compared raw handles
for (int i = 1; i < pilotCount; ++i)
{
int entity = *(int *)((int)pilotArray[i] + 0x1fc);
Vector3D delta;
delta.Subtract( // FUN_00408644
*(Vector3D *)((int)pilotArray[i] + 0x100),
*(Vector3D *)((int)pilotArray[0] + 0x100)
);
Scalar distance = delta.x * delta.x + delta.y * delta.y + delta.z * delta.z;
void *entity = BTPilotVehicle(pilotArray[i]);
float pos[3];
if (entity == 0 || entity == self_vehicle)
continue;
if (BTVehicleDestroyed(entity)) // the binary's Is_Destroyed(+0x1fc)
continue;
if (!BTPilotPosition(pilotArray[i], pos))
continue;
if (none_yet)
{
if (!Is_Destroyed(entity) && entity != self_id)
{
none_yet = False;
nearest_distance = distance;
nearest_index = i;
}
}
else if (!Is_Destroyed(entity) && entity != self_id
&& distance < nearest_distance)
const float dx = pos[0] - self_pos[0];
const float dy = pos[1] - self_pos[1];
const float dz = pos[2] - self_pos[2];
Scalar distance = dx * dx + dy * dy + dz * dz;
if (none_yet || distance < nearest_distance)
{
none_yet = False;
nearest_distance = distance;
nearest_index = i;
}
@@ -1413,12 +1434,10 @@ void
if (!none_yet)
{
Pilot *chosen = pilotArray[nearest_index];
if (chosen != 0)
{
target = *(int *)((int)chosen + 0x1fc);
}
*(int *)((int)pilotArray[0] + 0x284) = target;
void *target_vehicle = (chosen != 0) ? BTPilotVehicle(chosen) : 0;
BTPilotSetObjectiveMech(pilotArray[0], target_vehicle);
}
(void)target;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~