MP cross-pod damage ROOT-CAUSED: EntityID localID corrupted by hostID on the wire (task #47)

Traced the dispatched-message delivery end to end with BT_MP_NET /
BT_MP_FORCE_DMG. Everything works except one wire step:
- A's Entity::Dispatch reroutes the replicant's TakeDamage
  (application->SendMessage(ownerID=3, EntityManager, msg)); POST-Dispatch
  the message carries entityID=3:22 (host:local) -- the replicant's own
  ID, matching B's master (GetEntityID()==3:22). VERIFIED sent.
- On B the message ARRIVES, GetEntityPointer finds an entity, Posts it,
  the event drains (ProcessEventTask = ProcessOneEvent(0)), Event::Process
  runs, Receive finds+calls a handler. VERIFIED the full deliver chain.
- BUG: B receives entityID=3:19, NOT 3:22 -- the localID dropped by
  exactly the hostID (3) between A's send and B's receive. So
  GetEntityPointer(3:19) returns the WRONG entity (classID 48, not the
  mech 0xBB9), whose base handler ignores the unaimed hit -> 0 damage.
  Auto-replicated UPDATE records (msgID 18) arrive with the correct 3:22
  and find the mech, so the corruption is specific to the dispatched-
  message wire path/direction.

Next: the host-relative EntityID (de)serialization on the dispatched-msg
path (RoutePacket / packet EntityID encoding) -- diff vs the update path
which translates correctly. Diagnostics retained (all BT_MP_NET-gated,
off in solo -- verified: solo 22 hits, 0 probe noise).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-09 11:03:32 -05:00
co-authored by Claude Fable 5
parent a9c3e96f20
commit c78662a0f1
8 changed files with 86 additions and 18 deletions
+25 -13
View File
@@ -62,19 +62,31 @@ the solo `gEnemyMech` to the picked `hotTarget` (missile impact + projectile val
generalised via `BTIsRegisteredMech`). Verified one-box: instance A correctly enumerates B's
mech as a live `ReplicantInstance` (inst=4), 20 zones, `ownerID=3`, alive. Solo un-regressed
(pick→damage→kill chain clean).
**Step 2 — cross-pod damage: dispatch + reroute CORRECT, wire delivery is the GAP.** The engine
`Entity::Dispatch` (ENTITY.cpp:244) DOES reroute a replicant's message:
`if (GetInstance()==ReplicantInstance) application->SendMessage(ownerID, EntityManagerClientID,
message)`. Verified (`BT_MP_FORCE_DMG`): A dispatches TakeDamage at B's replicant with a VALID
`ownerID=3` → the reroute is invoked. BUT B's master takes **0** damage (no STEP-6 `unaimed
hit`), and the message resolves nowhere on A either. So the reroute FIRES but the dispatched
entity-message never reaches the owning master. ⚠ Corrects the earlier [T3] "Dispatch already
reroutes" to: reroute code exists + is invoked with a valid owner; the break is DOWNSTREAM in the
transport/delivery of a *dispatched* message (vs the auto-replicated update records, which flow —
`net-rx` works both ways). ⚠ `[net-tx]` trace tag is NOT wired on the send path (logs 0 even
though rx proves sends happen) — instrument the actual `Application::SendMessage``RoutePacket`
L4NET send + B-side `EntityManager` receive/deliver to localise. Diagnostics: `BT_MP_LOG`
(candidate dump), `BT_MP_FORCE_DMG` (direct replicant-dispatch proof hook).
**Step 2 — cross-pod damage: ROOT-CAUSED to an EntityID wire-corruption (task #47, full trace).**
The whole chain works EXCEPT one wire step; traced end-to-end with `BT_MP_NET`/`BT_MP_FORCE_DMG`:
1. `Entity::Dispatch` (ENTITY.cpp:244) reroutes a replicant's msg — `if(ReplicantInstance)
application->SendMessage(ownerID, EntityManagerClientID, msg)` → `NetworkManager::Send`
(L4NET.CPP:1062) → `send()`. VERIFIED: A dispatches TakeDamage (msgID 21) at B's replicant
(`ownerID=3`), and POST-Dispatch the message carries `entityID=3:22` (host:local) — the
replicant's own ID, matching B's master (`m->GetEntityID()==3:22`).
2. On B: `EntityManager::ReceiveNetworkPacket` (NTTMGR.cpp:107) → `GetEntityPointer(entityID)` →
`Post(EntityManagerEventPriority, entity, msg)` → event drains (ProcessEventTask =
`ProcessOneEvent(0)`, all priorities) → `Event::Process` → `entity->Receive(msg)` →
`activeMessageHandlers->Find(21)` → handler. VERIFIED: the message ARRIVES, is Posted,
Processed, and a handler is Found+called.
3. ⚠ **THE BUG:** B receives the message with `entityID=3:19`, NOT `3:22` — the localID dropped by
exactly the **hostID (3)** somewhere between A's `send()` (3:22) and B's receive (3:19). So
`GetEntityPointer(3:19)` returns the WRONG entity (a **classID 48**, not the mech 0xBB9), whose
base `Entity::TakeDamageMessageHandler` ignores the unaimed (zone 1) hit → B's mech takes 0
damage. The AUTO-REPLICATED UPDATE records (msgID 18) arrive with the CORRECT `3:22` and find
the mech — so the corruption is specific to the DISPATCHED-message path/direction, not the
receive lookup itself.
**Next:** find the host-relative EntityID (de)serialization that subtracts/adds the hostID on the
dispatched-message wire path (RoutePacket / the packet EntityID encoding) — the update path shows
the correct translation, so diff the two. Diagnostics retained (all `BT_MP_NET`-gated, off in
solo — verified): `[mp-force]` (pre/post-Dispatch entityID), `[mp-send]`/`[mp-recv]` (wire +
classID), `[mp-disp]` (reroute branch), `[mp-evt]`/`[mp-recv2]`/`[mp-hdlr]` (deliver chain),
`BT_MP_LOG` (candidate dump).
## Remaining (P6 phase 4 / Phase 7)
Cross-pod damage WIRE DELIVERY (the step-2 gap above — the next MP task); interactive 2-window