diff --git a/context/multiplayer.md b/context/multiplayer.md index bd5d53e..bcb22c7 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -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 diff --git a/engine/MUNGA/ENTITY.cpp b/engine/MUNGA/ENTITY.cpp index c214eb2..1fdb495 100644 --- a/engine/MUNGA/ENTITY.cpp +++ b/engine/MUNGA/ENTITY.cpp @@ -241,6 +241,13 @@ void // If this is a replicant instance, reroute the message to the master //------------------------------------------------------------------- // + if (getenv("BT_MP_NET") && message->messageID != 6) + DEBUG_STREAM << "[mp-disp] Entity::Dispatch msgID=" << (int)message->messageID + << " inst=" << (int)GetInstance() + << (GetInstance() == ReplicantInstance ? " REPLICANT -> reroute to master" + : (IsValid() ? " master/valid -> local Receive" : " INVALID -> Post")) + << "\n" << std::flush; + if (GetInstance() == ReplicantInstance) { application->SendMessage( diff --git a/engine/MUNGA/EVENT.cpp b/engine/MUNGA/EVENT.cpp index 1ce56e0..56d338a 100644 --- a/engine/MUNGA/EVENT.cpp +++ b/engine/MUNGA/EVENT.cpp @@ -258,6 +258,11 @@ void Event::Process() { Check(this); + // MP DIAGNOSTIC (task #47): a network-delivered TakeDamage (msgID 21) is + // carried by an event -- confirm it actually gets Processed (vs deferred). + if (messageToSend && messageToSend->messageID == 21 && getenv("BT_MP_NET")) + DEBUG_STREAM << "[mp-evt] Event::Process TakeDamage target=" + << (void*)targetReceiver.GetCurrent() << "\n" << std::flush; targetReceiver.GetCurrent()->Receive(this); } diff --git a/engine/MUNGA/NTTMGR.cpp b/engine/MUNGA/NTTMGR.cpp index 8847f06..13725af 100644 --- a/engine/MUNGA/NTTMGR.cpp +++ b/engine/MUNGA/NTTMGR.cpp @@ -130,6 +130,23 @@ void Entity *entity = application->GetHostManager()->GetEntityPointer(message->entityID); + + // MP DIAGNOSTIC (task #47, BT_MP_NET): log every incoming entity-manager + // message (a dispatched cross-pod message arrives here) + whether the + // target entity was found. msgID 6 (update records) filtered as noise. + if (getenv("BT_MP_NET") && message->messageID != 6) + DEBUG_STREAM << "[mp-recv] msgID=" << (int)message->messageID + << " entityID=" << message->entityID + << " len=" << (int)message->messageLength + << (entity ? " ENTITY FOUND" : " NO ENTITY (remake?)") + << (entity ? " ptr=" : "") << (entity ? (void*)entity : (void*)0) + << (entity ? " classID=" : "") ; + if (getenv("BT_MP_NET") && message->messageID != 6 && entity) + DEBUG_STREAM << (int)entity->GetClassID() + << ((int)entity->GetClassID() == 0xBB9 ? " (MECH) -> Post" : " (NOT MECH!) -> Post"); + if (getenv("BT_MP_NET") && message->messageID != 6) + DEBUG_STREAM << "\n" << std::flush; + if (entity) { // diff --git a/engine/MUNGA/RECEIVER.cpp b/engine/MUNGA/RECEIVER.cpp index 6966179..90a57bf 100644 --- a/engine/MUNGA/RECEIVER.cpp +++ b/engine/MUNGA/RECEIVER.cpp @@ -98,6 +98,12 @@ void // Receiver::SharedData *sharedData = GetSharedData(); Handler handler = sharedData->activeMessageHandlers->Find(what->messageID); + // MP DIAGNOSTIC (task #47): for a TakeDamage (msgID 21), report whether a + // handler is found on this receiver (the cross-pod delivery endpoint). + if (what->messageID == 21 && getenv("BT_MP_NET")) + DEBUG_STREAM << "[mp-recv2] Receive TakeDamage this=" << (void*)this + << " handler=" << (handler != Receiver::NullHandler ? "FOUND" : "NULL") + << "\n" << std::flush; if (handler != Receiver::NullHandler) { (this->*handler)(what); diff --git a/engine/MUNGA_L4/L4NET.CPP b/engine/MUNGA_L4/L4NET.CPP index 71b816e..46250f7 100644 --- a/engine/MUNGA_L4/L4NET.CPP +++ b/engine/MUNGA_L4/L4NET.CPP @@ -1140,6 +1140,14 @@ void L4NetworkManager::Send( //Net_Common_Ptr->Buffer_Length = (short)SEND_BUFFER_SIZE(packet_size); //send_packet_request->Socket_Ptr = l4host->GetNetworkSocket(); //NetNub::SendCommand(); + // MP DIAGNOSTIC (task #47, BT_MP_NET): log every non-update send so a + // dispatched cross-pod message (e.g. TakeDamage) is visible on the wire. + // (Update records are msgID 6, ~144 bytes -- filtered out to cut noise.) + if (getenv("BT_MP_NET") && !(message->messageID == 6 && message->messageLength <= 160)) + DEBUG_STREAM << "[mp-send] host=" << (long)host_ID << " client=" << (int)client + << " msgID=" << (int)message->messageID + << " len=" << (int)message->messageLength << "\n" << std::flush; + send(l4host->GetNetworkSocket(), (char *)my_temp_packet, packet_size, 0); free(my_temp_packet); // Check for errors and abort if there were any diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index 0732c9b..1eb2dfd 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -548,6 +548,17 @@ void Mech::TakeDamageMessageHandler(TakeDamageMessage *message) { Check(message); + + // MP DIAGNOSTIC (task #47): confirm the handler runs on the OWNING MASTER + // for a cross-pod (network-delivered) TakeDamage + what state it carries. + if (getenv("BT_MP_NET")) + DEBUG_STREAM << "[mp-hdlr] TakeDamageHandler this=" << (void*)this + << " inst=" << (int)GetInstance() + << " invalidZone=" << (int)message->invalidDamageZone + << " amount=" << message->damageData.damageAmount + << " table=" << (void*)damageLookupTable + << " zones=" << damageZoneCount << "\n" << std::flush; + // Maintain the last-attacker bookkeeping (mech[0x43c]): read by the // DamageZone LOD router (same-attacker redirect reuse, mechdmg.cpp:374) // and by the damage-band effect orientation. Was declared but never diff --git a/game/reconstructed/mech4.cpp b/game/reconstructed/mech4.cpp index 1cdd704..7f163d6 100644 --- a/game/reconstructed/mech4.cpp +++ b/game/reconstructed/mech4.cpp @@ -2397,9 +2397,10 @@ void if (m == 0) continue; Point3D mp = m->localOrigin.linearPosition; DEBUG_STREAM << "[mp-cand] " << (void*)m + << " entityID=" << m->GetEntityID() + << " ownerID=" << (int)m->GetOwnerID() << " inst=" << (int)m->GetInstance() - << " interest=" << m->interestCount - << " dead=" << (int)m->IsMechDestroyed() + << " classID=" << (int)m->GetClassID() << " pos=(" << mp.x << "," << mp.y << "," << mp.z << ")" << " zones=" << m->damageZoneCount << "\n" << std::flush; } @@ -2429,10 +2430,11 @@ void Entity::TakeDamageMessageID, sizeof(Entity::TakeDamageMessage), GetEntityID(), -1, dmg); + DEBUG_STREAM << "[mp-force] PRE m->GetEntityID()=" << m->GetEntityID() + << " td.entityID=" << td.entityID << "\n" << std::flush; ((Entity *)m)->Dispatch(&td); - DEBUG_STREAM << "[mp-force] dispatched TakeDamage at replicant " - << (void*)m << " ownerID=" << (long)m->GetOwnerID() - << " (reroute -> master via SendMessage)\n" << std::flush; + DEBUG_STREAM << "[mp-force] POST td.entityID=" << td.entityID + << " (== what Dispatch put on the wire)\n" << std::flush; break; } }