MP combat step 1 (target any peer) DONE; step 2 (cross-pod damage) localised (task #46)

STEP 1 -- target any peer mech: DONE + verified. A live-mech registry
(BTRegisterMech/BTDeregisterMech from the Mech ctor/dtor) collects
every mech -- player, dummy, and peer replicants. The boresight
world-pick walks BTGetTargetCandidates (all mechs != shooter, closest
ray hit) and the whole fire/damage block retargets from the solo
gEnemyMech to the picked hotTarget; missile/projectile validation
generalised via BTIsRegisteredMech. Verified one-box: instance A
enumerates B's mech as a live ReplicantInstance (20 zones, ownerID=3).
Solo un-regressed (pick->damage->kill clean, 28 hits + DESTROYED).

STEP 2 -- cross-pod damage: dispatch + engine reroute are CORRECT and
invoked with a valid owner, but the dispatched message doesn't reach
the master. Entity::Dispatch (ENTITY.cpp:244) reroutes a replicant's
msg via application->SendMessage(ownerID,...); BT_MP_FORCE_DMG proves A
dispatches at B's replicant with ownerID=3, yet B takes 0 STEP-6
damage. Corrects the KB's [T3] "Dispatch already reroutes": the reroute
FIRES; the break is downstream in the transport/delivery of a
dispatched entity-message (update records flow fine -- net-rx works).
Next MP task = that wire delivery. Diagnostics BT_MP_LOG /
BT_MP_FORCE_DMG retained.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 10:13:39 -05:00
co-authored by Claude Fable 5
parent 48191d6fdf
commit a9c3e96f20
5 changed files with 233 additions and 119 deletions
+57
View File
@@ -117,6 +117,63 @@
// current target. NULL when no enemy was spawned (BT_SPAWN_ENEMY unset).
Entity *gEnemyMech = 0;
//
// LIVE-MECH REGISTRY (task #46): every Mech (player, dummy, peer replicants)
// (de)registers here from its ctor/dtor. The boresight world-pick walks it to
// find target candidates -- generalising the solo gEnemyMech to MP peers. A
// plain fixed array (mech counts are tiny -- <= a few per arena) avoids any
// allocator interaction in the hot ctor path.
//
static Entity *gBTMechRegistry[32];
static int gBTMechCount = 0;
void BTRegisterMech(Entity *m)
{
if (m == 0) return;
for (int i = 0; i < gBTMechCount; ++i) // idempotent
if (gBTMechRegistry[i] == m) return;
if (gBTMechCount < 32)
gBTMechRegistry[gBTMechCount++] = m;
}
void BTDeregisterMech(Entity *m)
{
for (int i = 0; i < gBTMechCount; ++i)
{
if (gBTMechRegistry[i] == m)
{
gBTMechRegistry[i] = gBTMechRegistry[--gBTMechCount];
gBTMechRegistry[gBTMechCount] = 0;
return;
}
}
}
// The target-candidate list for a given shooter: every registered mech that is
// NOT the shooter itself. (Alive/pick filtering is the caller's -- mech4's
// world-pick already tests IsMechDestroyed + the collision-box ray.)
int BTGetTargetCandidates(Entity *shooter, Entity **out, int maxOut)
{
int n = 0;
for (int i = 0; i < gBTMechCount && n < maxOut; ++i)
{
Entity *m = gBTMechRegistry[i];
if (m != 0 && m != shooter)
out[n++] = m;
}
return n;
}
// Is this entity a live registered mech? (Projectile-impact target validation
// -- replaces the solo `== gEnemyMech` guard.)
int BTIsRegisteredMech(Entity *e)
{
for (int i = 0; i < gBTMechCount; ++i)
if (gBTMechRegistry[i] == e)
return 1;
return 0;
}
//
// Reconstruction helpers / data referenced by the decomp that have no direct
// analog in the surviving WinTesla headers (CROSS-FAMILY / engine gap -- see