#108 forensics block (pre-717): ghost detector + IDs + field envs
The night-11 instrumentation that makes the next ghost/K-D report diagnosable instead of anecdotal: - UNGATED [ghost] stale-replicant detector: ReadUpdateRecord stamps every applied record; the death-handler tick logs ONE line per starvation episode (>600 frames, unburied) with entity id + mode + last position, plus a GHOST matchlog record. Verified both ways: zero false positives on a healthy 2-node session; fires on both nodes at frame 601 after a mid-session relay kill. - Entity IDs on the render forensics (MakeMechRenderables / RemakeEntity / wreck-swap fallbacks) and the replicant un-wreck line un-gated -- ghost triage no longer needs players to set envs. - players/*.bat (steam + both joins): BT_MATCHLOG/BT_SCORE_LOG/BT_DEATH_LOG on for every field session. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
c38377a6d2
commit
81dda84e9b
@@ -401,7 +401,8 @@ HierarchicalDrawComponent*
|
|||||||
// bring-up diagnostics (counts geometry actually loaded vs. requested)
|
// bring-up diagnostics (counts geometry actually loaded vs. requested)
|
||||||
int dbg_obj_requested = 0, dbg_obj_loaded = 0, dbg_eye = 0;
|
int dbg_obj_requested = 0, dbg_obj_loaded = 0, dbg_eye = 0;
|
||||||
DEBUG_STREAM << "[BTrender] MakeMechRenderables: " << segment_count
|
DEBUG_STREAM << "[BTrender] MakeMechRenderables: " << segment_count
|
||||||
<< " segments, view=" << (int)type << "\n" << std::flush;
|
<< " segments, view=" << (int)type
|
||||||
|
<< " entity=" << entity->GetEntityID() << "\n" << std::flush;
|
||||||
|
|
||||||
JointSubsystem *joint_subsystem = jointed_mover->GetJointSubsystem(); // [0x31c]
|
JointSubsystem *joint_subsystem = jointed_mover->GetJointSubsystem(); // [0x31c]
|
||||||
|
|
||||||
@@ -884,7 +885,8 @@ void
|
|||||||
if (swapped != 0 || getenv("BT_DEATH_LOG"))
|
if (swapped != 0 || getenv("BT_DEATH_LOG"))
|
||||||
DEBUG_STREAM << "[BTrender] RemakeEntity: " << swapped
|
DEBUG_STREAM << "[BTrender] RemakeEntity: " << swapped
|
||||||
<< " mesh(es) swapped (" << mapped << " body segs mapped of "
|
<< " mesh(es) swapped (" << mapped << " body segs mapped of "
|
||||||
<< checked << " checked)\n" << std::flush;
|
<< checked << " checked) entity="
|
||||||
|
<< entity->GetEntityID() << "\n" << std::flush;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@@ -1171,13 +1173,15 @@ void
|
|||||||
if (hulk != NULL && hulk->GetVertCount() == 0)
|
if (hulk != NULL && hulk->GetVertCount() == 0)
|
||||||
{
|
{
|
||||||
DEBUG_STREAM << "[BTrender] wreck: '" << hulk_name
|
DEBUG_STREAM << "[BTrender] wreck: '" << hulk_name
|
||||||
<< "' is an EMPTY placeholder -> gendbr.bgf fallback\n" << std::flush;
|
<< "' is an EMPTY placeholder -> gendbr.bgf fallback (entity="
|
||||||
|
<< victim->GetEntityID() << ")\n" << std::flush;
|
||||||
hulk = NULL;
|
hulk = NULL;
|
||||||
}
|
}
|
||||||
if (hulk == NULL)
|
if (hulk == NULL)
|
||||||
{
|
{
|
||||||
DEBUG_STREAM << "[BTrender] wreck: '" << hulk_name
|
DEBUG_STREAM << "[BTrender] wreck: '" << hulk_name
|
||||||
<< "' missing -> gendbr.bgf fallback\n" << std::flush;
|
<< "' missing -> gendbr.bgf fallback (entity="
|
||||||
|
<< victim->GetEntityID() << ")\n" << std::flush;
|
||||||
hulk = d3d_OBJECT::LoadObject(GetDevice(), "gendbr.bgf");
|
hulk = d3d_OBJECT::LoadObject(GetDevice(), "gendbr.bgf");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -2424,6 +2424,13 @@ Logical
|
|||||||
void
|
void
|
||||||
Mech::ReadUpdateRecord(Simulation::UpdateRecord *message)
|
Mech::ReadUpdateRecord(Simulation::UpdateRecord *message)
|
||||||
{
|
{
|
||||||
|
// #108 ghost forensics: stamp the staleness detector on every applied
|
||||||
|
// record (replicants only -- the detector lives on the death handler).
|
||||||
|
if (GetInstance() == ReplicantInstance)
|
||||||
|
{
|
||||||
|
extern void BTMechNoteUpdateApplied(void *mech_v);
|
||||||
|
BTMechNoteUpdateApplied((void *)this);
|
||||||
|
}
|
||||||
// RECORD-CADENCE probe (BT_RXJIT): quantify how EVENLY records arrive (wall-clock
|
// RECORD-CADENCE probe (BT_RXJIT): quantify how EVENLY records arrive (wall-clock
|
||||||
// ms between arrivals) -- the jitter that makes the peer's corrections random.
|
// ms between arrivals) -- the jitter that makes the peer's corrections random.
|
||||||
if (getenv("BT_RXJIT") && GetInstance() == ReplicantInstance)
|
if (getenv("BT_RXJIT") && GetInstance() == ReplicantInstance)
|
||||||
|
|||||||
@@ -1119,6 +1119,9 @@ protected:
|
|||||||
// from its exported consumers + the RP VTV::DeathShutdown analog) ---
|
// from its exported consumers + the RP VTV::DeathShutdown analog) ---
|
||||||
Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9
|
Logical IsMechDestroyed(); // damage-side death flag: graphicAlarm level >= 9
|
||||||
void UpdateDeathState(Scalar dt); // death freeze + subsystem DeathShutdown + wreck smoke
|
void UpdateDeathState(Scalar dt); // death freeze + subsystem DeathShutdown + wreck smoke
|
||||||
|
// --- #108 ghost-forensics accessors (PORT; the fields are protected) ---
|
||||||
|
void *DeathHandlerPtr() { return (void *)deathHandler; }
|
||||||
|
int WreckBuried() const { return collisionVolumeCount == 0; }
|
||||||
|
|
||||||
// --- clip loaders (mech3) -------------------------------------------
|
// --- clip loaders (mech3) -------------------------------------------
|
||||||
ResourceDescription::ResourceID *
|
ResourceDescription::ResourceID *
|
||||||
|
|||||||
@@ -1193,8 +1193,20 @@ extern void BTSpawnDamageEffect(Mech *mech, int effect_resource, int segment_ind
|
|||||||
// unported "RemakeEntity" render state). Entity* param -> Mech* binds fine.
|
// unported "RemakeEntity" render state). Entity* param -> Mech* binds fine.
|
||||||
extern void BTRemakeMechModel(Entity *entity);
|
extern void BTRemakeMechModel(Entity *entity);
|
||||||
|
|
||||||
|
//
|
||||||
|
// #108 bridge: Mech::ReadUpdateRecord (mech.cpp, incomplete-type TU for the
|
||||||
|
// handler) stamps the ghost detector through this.
|
||||||
|
//
|
||||||
|
void
|
||||||
|
BTMechNoteUpdateApplied(void *mech_v)
|
||||||
|
{
|
||||||
|
Mech *m = (Mech *)mech_v;
|
||||||
|
if (m != 0 && m->DeathHandlerPtr() != 0)
|
||||||
|
((MechDeathHandler *)m->DeathHandlerPtr())->NoteUpdateApplied();
|
||||||
|
}
|
||||||
|
|
||||||
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
MechDeathHandler::MechDeathHandler(Mech *mech) // @0042a984
|
||||||
: owner(mech), prevMode(0)
|
: owner(mech), prevMode(0), ghostFrames(0), ghostLogged(0)
|
||||||
{
|
{
|
||||||
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
// per-zone last-damage cache, zeroed (binary this[0x10], size mech+0x11c).
|
||||||
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
int count = (mech != 0 && mech->damageZoneCount > 0) ? mech->damageZoneCount : 0;
|
||||||
@@ -1217,6 +1229,35 @@ void
|
|||||||
if (owner == 0)
|
if (owner == 0)
|
||||||
return;
|
return;
|
||||||
|
|
||||||
|
// #108 GHOST DETECTOR (PORT, ungated -- the night-9 forensic): a visible
|
||||||
|
// REPLICANT that stops receiving update records is exactly what "everyone
|
||||||
|
// shoots thin air" looks like from the other side. ReadUpdateRecord
|
||||||
|
// stamps arrivals (NoteUpdateApplied); ~20s of silence on an unburied
|
||||||
|
// replicant logs ONE [ghost] line per episode with the identity + last
|
||||||
|
// position, then re-arms when records resume. Frame-counted (Tick has no
|
||||||
|
// dt); 600 frames ~ 10-20s at field rates.
|
||||||
|
if (owner->GetInstance() == Entity::ReplicantInstance)
|
||||||
|
{
|
||||||
|
++ghostFrames;
|
||||||
|
if (ghostFrames > 600 && !ghostLogged
|
||||||
|
&& !owner->WreckBuried()) // buried wrecks are expected-silent
|
||||||
|
{
|
||||||
|
ghostLogged = 1;
|
||||||
|
DEBUG_STREAM << "[ghost] replicant " << owner->GetEntityID()
|
||||||
|
<< " has received NO update records for " << ghostFrames
|
||||||
|
<< " frames (mode " << (int)owner->MovementMode()
|
||||||
|
<< ") last pos (" << owner->localOrigin.linearPosition.x
|
||||||
|
<< "," << owner->localOrigin.linearPosition.z
|
||||||
|
<< ") -- stale-replicant / ghost signature (#108)\n" << std::flush;
|
||||||
|
if (BTMatchLogActive())
|
||||||
|
BTMatchLog("GHOST", "mech=%d:%d frames=%d mode=%d x=%.1f z=%.1f",
|
||||||
|
BTMatchHostOf(owner->GetEntityID()), (int)owner->GetEntityID(),
|
||||||
|
ghostFrames, (int)owner->MovementMode(),
|
||||||
|
(float)owner->localOrigin.linearPosition.x,
|
||||||
|
(float)owner->localOrigin.linearPosition.z);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// REPLICANT UN-WRECK (#94): fire on the DEATH-STATE EXIT edge of the
|
// REPLICANT UN-WRECK (#94): fire on the DEATH-STATE EXIT edge of the
|
||||||
// replicated SimulationState dial (MovementMode 9/2 are the death modes;
|
// replicated SimulationState dial (MovementMode 9/2 are the death modes;
|
||||||
// they never revert except through Mech::Reset, whose SetMovementMode(0)
|
// they never revert except through Mech::Reset, whose SetMovementMode(0)
|
||||||
@@ -1251,11 +1292,13 @@ void
|
|||||||
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
BTStartWarpEffect((float)owner->localOrigin.linearPosition.x,
|
||||||
(float)owner->localOrigin.linearPosition.y,
|
(float)owner->localOrigin.linearPosition.y,
|
||||||
(float)owner->localOrigin.linearPosition.z);
|
(float)owner->localOrigin.linearPosition.z);
|
||||||
if (getenv("BT_DEATH_LOG"))
|
// #108 forensics: UNGATED -- one line per peer respawn; a field
|
||||||
DEBUG_STREAM << "[respawn] replicant un-wrecked + warp (mode "
|
// night's ghost triage needs these without asking players for envs.
|
||||||
<< oldMode << "->" << mode << ") at ("
|
DEBUG_STREAM << "[respawn] replicant " << owner->GetEntityID()
|
||||||
<< owner->localOrigin.linearPosition.x << ","
|
<< " un-wrecked + warp (mode "
|
||||||
<< owner->localOrigin.linearPosition.z << ")\n" << std::flush;
|
<< oldMode << "->" << mode << ") at ("
|
||||||
|
<< owner->localOrigin.linearPosition.x << ","
|
||||||
|
<< owner->localOrigin.linearPosition.z << ")\n" << std::flush;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -363,6 +363,11 @@ class MechDeathHandler
|
|||||||
MechDeathHandler(Mech *mech); // @0042a984
|
MechDeathHandler(Mech *mech); // @0042a984
|
||||||
~MechDeathHandler(); // @0042a9f4
|
~MechDeathHandler(); // @0042a9f4
|
||||||
void Tick(); // @0042aa2c (the Performance)
|
void Tick(); // @0042aa2c (the Performance)
|
||||||
|
// #108 forensics (PORT, ungated): the replicant staleness detector.
|
||||||
|
// Mech::ReadUpdateRecord stamps every applied record; Tick counts
|
||||||
|
// frames since -- a visible replicant starved of records IS the ghost
|
||||||
|
// signature, logged once per episode with id + age + last position.
|
||||||
|
void NoteUpdateApplied() { ghostFrames = 0; ghostLogged = 0; }
|
||||||
|
|
||||||
private:
|
private:
|
||||||
Mech *owner; // @0x14
|
Mech *owner; // @0x14
|
||||||
@@ -378,6 +383,9 @@ class MechDeathHandler
|
|||||||
// authentic un-wreck rides the type-0 graphic-state hook
|
// authentic un-wreck rides the type-0 graphic-state hook
|
||||||
// (Mech::ReadUpdateRecord case 0), which our port has not reconstructed.
|
// (Mech::ReadUpdateRecord case 0), which our port has not reconstructed.
|
||||||
int prevMode;
|
int prevMode;
|
||||||
|
// #108 ghost detector state (PORT)
|
||||||
|
int ghostFrames;
|
||||||
|
int ghostLogged;
|
||||||
};
|
};
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|||||||
@@ -27,6 +27,13 @@ rem Clear any INHERITED BT_LOG: with it set the exe uses that path verbatim
|
|||||||
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
||||||
rem change removes. Empty forces the per-day self-named file + append.
|
rem change removes. Empty forces the per-day self-named file + append.
|
||||||
set BT_LOG=
|
set BT_LOG=
|
||||||
|
rem #108 FORENSICS (night-11): every field session records the match ledger
|
||||||
|
rem (matchlog also auto-arms on -net; forcing covers menu-relaunch paths) and
|
||||||
|
rem the score/death chain lines -- the ghost/K-D triage needs them from every
|
||||||
|
rem player, not just the ones we can reach for env setup.
|
||||||
|
set BT_MATCHLOG=1
|
||||||
|
set BT_SCORE_LOG=1
|
||||||
|
set BT_DEATH_LOG=1
|
||||||
cd content
|
cd content
|
||||||
rem JOIN-GAME menu (2026-07-22): pick your CALLSIGN + MECH, click JOIN --
|
rem JOIN-GAME menu (2026-07-22): pick your CALLSIGN + MECH, click JOIN --
|
||||||
rem your choice is sent to the operator and lands in the mission roster.
|
rem your choice is sent to the operator and lands in the mission roster.
|
||||||
|
|||||||
@@ -30,6 +30,13 @@ rem Clear any INHERITED BT_LOG: with it set the exe uses that path verbatim
|
|||||||
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
||||||
rem change removes. Empty forces the per-day self-named file + append.
|
rem change removes. Empty forces the per-day self-named file + append.
|
||||||
set BT_LOG=
|
set BT_LOG=
|
||||||
|
rem #108 FORENSICS (night-11): every field session records the match ledger
|
||||||
|
rem (matchlog also auto-arms on -net; forcing covers menu-relaunch paths) and
|
||||||
|
rem the score/death chain lines -- the ghost/K-D triage needs them from every
|
||||||
|
rem player, not just the ones we can reach for env setup.
|
||||||
|
set BT_MATCHLOG=1
|
||||||
|
set BT_SCORE_LOG=1
|
||||||
|
set BT_DEATH_LOG=1
|
||||||
cd content
|
cd content
|
||||||
rem JOIN-GAME menu (2026-07-22): pick your CALLSIGN + MECH, click JOIN --
|
rem JOIN-GAME menu (2026-07-22): pick your CALLSIGN + MECH, click JOIN --
|
||||||
rem your choice is sent to the operator and lands in the mission roster.
|
rem your choice is sent to the operator and lands in the mission roster.
|
||||||
|
|||||||
@@ -29,6 +29,13 @@ rem Clear any INHERITED BT_LOG: with it set the exe uses that path verbatim
|
|||||||
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
rem and, with no BT_LOG_APPEND, TRUNCATES it -- the exact evidence loss this
|
||||||
rem change removes. Empty forces the per-day self-named file + append.
|
rem change removes. Empty forces the per-day self-named file + append.
|
||||||
set BT_LOG=
|
set BT_LOG=
|
||||||
|
rem #108 FORENSICS (night-11): every field session records the match ledger
|
||||||
|
rem (matchlog also auto-arms on -net; forcing covers menu-relaunch paths) and
|
||||||
|
rem the score/death chain lines -- the ghost/K-D triage needs them from every
|
||||||
|
rem player, not just the ones we can reach for env setup.
|
||||||
|
set BT_MATCHLOG=1
|
||||||
|
set BT_SCORE_LOG=1
|
||||||
|
set BT_DEATH_LOG=1
|
||||||
cd content
|
cd content
|
||||||
rem ------------------------------------------------------------------
|
rem ------------------------------------------------------------------
|
||||||
rem OUR GENERATION ONLY (2026-07-27). The wait loop below used to hold
|
rem OUR GENERATION ONLY (2026-07-27). The wait loop below used to hold
|
||||||
|
|||||||
Reference in New Issue
Block a user