Files
BT412/engine/MUNGA/NTTMGR.cpp
T
arcattackandClaude Fable 5 04a2049ce2 MP task #47: DISPROVE the Entity-layout root cause; correct the record
The prior commit (c78662a) filed cross-pod damage failure under a
"cross-TU Entity/EntityID layout divergence" (and the P5 base-region
audit). A compile-time offsetof probe injected into BOTH translation
units disproves that: game/reconstructed AND engine/MUNGA compute an
IDENTICAL Entity layout --

  sizeof(Entity)=444  offsetof(entityID)=380 (0x17C)
  offsetof(ownerID)=388  offsetof(simulationFlags)=32
  offsetof(Mech,entityID)=380  (Entity subobject at 0)

So Entity::Dispatch reads this->entityID at the SAME offset mech4's
GetEntityID() does -- there is no per-TU read difference, and the
logged 3:22 (mech4 candidate) vs 3:19 (engine Dispatch) cannot be one
object read two ways. Those lines were different objects/messages,
mis-correlated. The distinct, REAL P5 "base-region layout divergence"
(HARD_PROBLEMS.md) is about raw-offset stomps at this+0x2d4..0x2f0 --
far above entityID -- and is unrelated to #47.

Changes:
- Revert the committed BT_MP_NET engine diagnostics (ENTITY/EVENT/
  NTTMGR/RECEIVER/L4NET) to the clean a9c3e96 baseline -- those are
  the very instruments that produced the mis-correlated data.
- mech4 BT_MP_FORCE_DMG hook: dispatch via the real m->Dispatch(&td)
  path (no false id stamping); comment records the offsetof finding.
- context/multiplayer.md: layout-divergence RULED OUT; 3:22/3:19 marked
  unconfirmed; leading hypotheses reframed as H2 (wire host-relative
  (de)serialization) vs H3 (replicant id != master's registered key),
  to be distinguished by a 2-node run with per-message correlation.

Solo un-regressed (mech walks + targets, 0 faults). Cross-pod delivery
remains open, but the investigation is redirected off the wrong (large,
structural) layout-audit path onto the EntityID wire/id-assignment path.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-09 12:09:50 -05:00

217 lines
5.4 KiB
C++

#include "munga.h"
#pragma hdrstop
#include "nttmgr.h"
#include "namelist.h"
#include "app.h"
#include "entity.h"
#include "hostmgr.h"
#include "dropzone.h"
#include "registry.h"
//#############################################################################
//############################ EntityGroup ##############################
//#############################################################################
//#############################################################################
// Construction and Destruction
//
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityGroup::EntityGroup():
groupMembers(NULL)
{
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityGroup::~EntityGroup()
{
}
//#############################################################################
//########################### EntityManager #############################
//#############################################################################
Derivation* EntityManager::GetClassDerivations()
{
static Derivation classDerivations(NetworkClient::GetClassDerivations(), "EntityManager");
return &classDerivations;
}
EntityManager::SharedData
EntityManager::DefaultData(
EntityManager::GetClassDerivations(),
EntityManager::GetMessageHandlers()
);
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityManager::EntityManager():
NetworkClient(
EntityManager::EntityManagerClassID,
DefaultData,
NetworkClient::EntityManagerClientID
)
{
deathRow = UseGroup("DeathRow");
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityManager::~EntityManager()
{
//
//----------------------------------------------
// Erase all the groups listed in the group list
//----------------------------------------------
//
ObjectNameList::Entry *entry;
while ((entry = groupList.GetFirstEntry()) != NULL)
{
Check(entry);
EntityGroup *group = (EntityGroup*)entry->GetData();
groupList.DeleteEntry(entry->GetName());
Unregister_Object(group);
delete group;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
EntityManager::FryDeathRow()
{
Check(this);
Check(deathRow);
//
// ECH 8/23/95 - Just delete one ... this allows the application to
// delete as many entities as it can within the current frame
//
ChainIteratorOf<Node*> iterator(deathRow->groupMembers);
Node *node;
if ((node = iterator.GetCurrent()) != NULL)
{
Unregister_Object(node);
delete node;
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
EntityManager::ReceiveNetworkPacket(
NetworkPacket*,
Receiver::Message *packet_message
)
{
Check(this);
Check_Pointer(packet_message);
//
//-------------------------------------------------------------------------
// Check to see if the interest zone contained in the message is one we are
// interested in, and if not, ignore the message
//-------------------------------------------------------------------------
//
Entity::MakeMessage
*message = Cast_Object(Entity::MakeMessage*, packet_message);
//
//-----------------------------------------------------------------------
// Find the address of the entity. If it exists, post the message on the
// application event queue.
//-----------------------------------------------------------------------
//
Entity
*entity =
application->GetHostManager()->GetEntityPointer(message->entityID);
if (entity)
{
//
// HACK -- This hack puts dropzone messages on max priority. It should
// really be done with a message priority encoded in the message
//
if (
entity->GetClassID() == DropZoneClassID
&& message->messageID == DropZone::AssignDropZoneMessageID
)
{
application->Post(MaxEventPriority, entity, message);
}
else
{
application->Post(EntityManagerEventPriority, entity, message);
}
}
//
//--------------------------------------------------------------------------
// If the entity does not exist, check to see if this is a manager message,
// or see if we have to ask for a remake
//--------------------------------------------------------------------------
//
else
{
switch (message->messageID)
{
case Entity::MakeMessageID:
{
#if DEBUG_LEVEL>0
Entity *entity = application->GetRegistry()->MakeEntity(message);
Register_Object(entity);
#else
application->GetRegistry()->MakeEntity(message);
#endif
}
break;
//
//---------------------------------------------------------------------
// Send a remake message to the sending host to have our entity rebuilt
//---------------------------------------------------------------------
//
default:
break;
}
}
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
EntityGroup*
EntityManager::UseGroup(const char *name)
{
EntityGroup *group = FindGroup(name);
if (!group)
{
group = new EntityGroup;
group->groupName = groupList.AddEntry(name, group);
Register_Object(group);
}
return group;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
void
EntityManager::RemoveGroup(EntityGroup *group)
{
Check(group);
groupList.DeleteEntry(group->groupName);
Unregister_Object(group);
delete group;
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
//
Logical
EntityManager::TestInstance() const
{
return IsDerivedFrom(*GetClassDerivations());
}