#81 follow-ups: peer warp must not stomp the POV vortex; panel draw probe; mech3 ghost symbols
Two field observations from watching the 4-node stress, both run down: 1. "A respawn happened without the blue vortex" -- REAL. The translocation effect is ONE global slot shared by the POV lifecycle and the world-anchored peer sphere (a port extension). A peer's un-wreck arriving while the local pilot's own collapse/wait/expand was in flight overwrote gWarpPhase/gWarpPOV and killed the POV vortex. Invisible before the respawn fix only because overlapping respawns barely existed; now they are routine. Fix: the POV lifecycle owns the slot -- BTStartWarpEffect self-skips while it is active ([tloc] peer warp SKIPPED). 2-node bench: 20/20 POV collapse+expand pairs, 9 peer spheres played, 11 correctly skipped; solo: 8/8 pairs unchanged. 2. "Comms panel counted no deaths" -- panel machinery CORRECT; the 4-node zeros were the bench's own CPU crush (4 core-pinned instances starved the PilotList to <0.6 Hz, so rows redrew minutes-stale). Added the arbiter: [score] panel DRAW slot/pilot/kills/deaths edge log (BT_SCORE_LOG) -- the 2-node rerun drew 0->9 / 0->11 live on both nodes, local AND replicated (SBMIRROR rows confirm owner->replicant tally flow on all 4 stress nodes). Also: the mech3 offline-authoring stubs declared every <Subsystem>::DefaultData as Entity__SharedData while the real statics are Simulation__SharedData (this engine derives Entity FROM Simulation) -- ~20 ghost symbols /FORCE silently resolved to garbage. DefaultData half fixed (SubsystemDefaultData now returns the true common base Simulation::SharedData); the CreateStreamedSubsystem stub signatures remain wrong (nested SubsystemResource* + ResourceFile*), are cold (no callers), and are tracked in open-questions + gotchas §6 stub-typedef corollary. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Fable 5
parent
830a976192
commit
bfa1b04990
@@ -879,6 +879,14 @@ register. ⚠ The audit also flags the damage-economy item as SELF-CONTRADICTOR
|
||||
## Content build sub-project (low priority)
|
||||
- Lab/other-build maps (`des`/`burnt`/`frstrm`) are source-only (`.map` in CONTENT/BT/MAPS/); would
|
||||
need compiling into a RES via the DOS `btl4tool.exe`. The 8 RES maps cover testing.
|
||||
- **Offline subsystem-authoring dispatch is un-linked (known, cold, 2026-07-30).** mech3.cpp's
|
||||
`Mech::CreateSubsystemStream` dispatches to 20 per-class `CreateStreamedSubsystem` stubs whose
|
||||
guessed signatures (`void*`/`int`) don't match the real modules (nested `<X>::SubsystemResource*`
|
||||
+ trailing `ResourceFile*`, e.g. mechtech.cpp:407) — all 20 are /FORCE-silenced unresolved
|
||||
externals. No callers exist, so it cannot crash today; before wiring any authoring tool through
|
||||
it, convert each to a per-module bridge fn in a complete-type TU (gotchas §6 stub-typedef
|
||||
corollary). The parallel `DefaultData` half of the table WAS the same trap and is fixed
|
||||
(`Simulation__SharedData`).
|
||||
|
||||
## Key Relationships
|
||||
- Feeds from: every subsystem/render topic (their deferral notes collect here).
|
||||
|
||||
@@ -121,6 +121,17 @@ mislanch.cpp's extern but not projweap.cpp's → first AUTOCANNON shot AV'd). **
|
||||
bridge-signature change, `grep -rn "extern .*<name>"` and update every declaration; then grep
|
||||
the fresh link output for the symbol name** — the pre-existing LNK2019 wall camouflages new
|
||||
entries if you only eyeball it.
|
||||
**Stub-typedef corollary (mech3 tool path, 2026-07-30):** a LOCAL stub TU that re-declares a
|
||||
sibling class's STATIC MEMBER with the wrong typedef mangles to a **ghost symbol** — mech3.cpp
|
||||
declared every `<Subsystem>::DefaultData` as `Mech::SharedData` (= `Entity__SharedData`) while
|
||||
the real definitions are the inherited `Simulation__SharedData` (in THIS engine `Entity` derives
|
||||
from `Simulation`; `Entity__SharedData : public Simulation::SharedData`, ENTITY3.h:9) → ~20
|
||||
unresolved externals, all /FORCE-silenced, all cold (offline authoring dispatch has no callers).
|
||||
Fixed for the DefaultData statics; the `CreateStreamedSubsystem` stub SIGNATURES are still wrong
|
||||
(real ones take the class's NESTED `SubsystemResource*` + trailing `ResourceFile*`) and stay
|
||||
unresolved-by-design until bridged per-module ([[open-questions]]). **Rule: when a "benign" LNK
|
||||
wall exists, `tail` on the build output hides the fleet — always grep the FULL log; and verify a
|
||||
stub's member TYPE against `dumpbin /symbols` of the defining obj, not against what looks right.**
|
||||
**Duplicate-GLOBAL corollary (glass per-display windows, 2026-07-20):** a global DEFINED in two
|
||||
libs (the 1995 headers declare free globals without `inline`/`extern`, so `application`,
|
||||
`ghWnd`, … exist in BOTH `munga_engine` and `bt410_l4`) links under `/FORCE:MULTIPLE` with
|
||||
|
||||
@@ -121,7 +121,12 @@ Local player's own death → `BTStartWarpCollapsePOV()` (btplayer.cpp `VehicleDe
|
||||
(local-guarded). `BTWarpForceUnmask()` on mission-end / no-DropZones. **Peer warp IS wired + visible
|
||||
(`160b78e`):** an observer sees a peer's death/respawn warp — the replicant un-wreck fires the
|
||||
world-anchored `BTStartWarpEffect(x,y,z)` at the peer's position (mechdmg.cpp:1074, gated to
|
||||
`ReplicantInstance`; `simulationState` rides every update-record header so the observer tracks the
|
||||
`ReplicantInstance`; the effect machine is a SINGLE global slot, and since 2026-07-30 the local
|
||||
pilot's own POV lifecycle OWNS it — `BTStartWarpEffect` self-skips while `gWarpPhase!=0 && gWarpPOV`
|
||||
(`[tloc] peer warp SKIPPED`), because a peer's un-wreck landing mid-collapse/wait/expand used to
|
||||
overwrite the machine and kill the POV vortex ("my respawn had no blue whirlwind"). Invisible
|
||||
before the #81 respawn fix only because overlapping respawns barely existed; 2-node bench: 20/20
|
||||
POV collapse+expand pairs, 9 peer spheres played, 11 correctly skipped; `simulationState` rides every update-record header so the observer tracks the
|
||||
peer's state). The ONLY remaining nuance [T3, non-gating]: the observer's peer sphere is anchored to
|
||||
the peer's WORLD position rather than the peer's authentic `DropZoneLocation` (that attribute isn't
|
||||
replicated) — a fidelity refinement, not a missing effect.
|
||||
|
||||
@@ -27,6 +27,15 @@ one increment per death (13/13/15/15) — the DEATHS column the comms panel draw
|
||||
kill-credit path evaluated all 56 deaths and correctly declined each self-kill (`NOCREDIT self=1`),
|
||||
KILLS untouched by the fix (separate `ScoreMessage` path). Bench: `scratchpad/night6/mp4_stress.sh`
|
||||
+ `content/MP4.EGG`.
|
||||
**Two observations from watching the 4-node run, both run down (2026-07-30):** (1) *"comms panel
|
||||
counted no deaths"* — the panel machinery is CORRECT (2-node rerun with a draw-value probe: both
|
||||
rows on both nodes drew 0→9/0→11 live, local + replicated); the 4-node zeros were the artificial
|
||||
load (4 core-pinned instances → PilotList Execute starved to <0.6 Hz, panel minutes-stale). Probe
|
||||
stays in (`[score] panel DRAW`, BT_SCORE_LOG). (2) *"a respawn had no blue vortex"* — REAL and now
|
||||
fixed: the warp effect is one global slot and a PEER's un-wreck sphere (`BTStartWarpEffect`) could
|
||||
stomp the local pilot's own POV vortex mid-lifecycle; fixed by POV-priority self-skip
|
||||
(btl4vid.cpp; `context/translocation-warp.md`). Pre-fix this never showed because overlapping
|
||||
respawns barely existed — the ghost fix CREATED the traffic that exposed it.
|
||||
**Verified:** solo **17 consecutive death/respawn cycles**, every one `START`→`RESET`, 0 swallowed /
|
||||
0 mismatch / 0 crash (pre-fix this strands permanently after cycle 1). Two-node MP over a real
|
||||
network path with cross-machine drop-zone replies: A 11 cycles, B 12, 0 swallowed / 0 mismatch /
|
||||
|
||||
@@ -547,8 +547,27 @@ void
|
||||
// briefly redirected to Player::deathCount@0x200, but that is the respawn-handshake
|
||||
// identity (seeded -2), which is why it needed a display clamp; +0x280 was never
|
||||
// dead, only unwritten. Both counters now replicate owner->replicant.
|
||||
e.nameDisplay->Draw(&localView, (Scalar)BTPilotKills(pilot)); // KILLS (killCount)
|
||||
e.mechDisplay->Draw(&localView, (Scalar)BTPilotDeaths(pilot)); // DEATHS (deathTally @0x280)
|
||||
int drawnKills = BTPilotKills(pilot);
|
||||
int drawnDeaths = BTPilotDeaths(pilot);
|
||||
e.nameDisplay->Draw(&localView, (Scalar)drawnKills); // KILLS (killCount)
|
||||
e.mechDisplay->Draw(&localView, (Scalar)drawnDeaths); // DEATHS (deathTally @0x280)
|
||||
// DIAG (BT_SCORE_LOG): the exact value handed to the panel numerics, edge-
|
||||
// logged per slot. This is the arbiter between "the tally moved but the
|
||||
// panel drew stale zeros" (this line shows 0) and "the panel drew it but
|
||||
// the on-screen surface didn't" (this line shows N).
|
||||
if (getenv("BT_SCORE_LOG"))
|
||||
{
|
||||
static int s_lastK[8] = { -1,-1,-1,-1,-1,-1,-1,-1 };
|
||||
static int s_lastD[8] = { -1,-1,-1,-1,-1,-1,-1,-1 };
|
||||
if (s_lastK[currentSlot] != drawnKills || s_lastD[currentSlot] != drawnDeaths)
|
||||
{
|
||||
s_lastK[currentSlot] = drawnKills;
|
||||
s_lastD[currentSlot] = drawnDeaths;
|
||||
DEBUG_STREAM << "[score] panel DRAW slot " << currentSlot
|
||||
<< " pilot " << pilot << " kills=" << drawnKills
|
||||
<< " deaths=" << drawnDeaths << "\n" << std::flush;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
++currentSlot;
|
||||
|
||||
@@ -2878,6 +2878,20 @@ void BTStartWarpExpandPOV()
|
||||
//
|
||||
void BTStartWarpEffect(float x, float y, float z)
|
||||
{
|
||||
// ONE warp slot (the authentic effect is POV-only; this world-anchored peer
|
||||
// sphere is the port extension). The local pilot's own death/respawn
|
||||
// lifecycle OWNS the slot: a peer's un-wreck landing mid-collapse/wait/expand
|
||||
// would overwrite gWarpPhase/gWarpPOV and kill the POV vortex ("my respawn
|
||||
// had no blue vortex"). Harmless before the #81 respawn fix only because
|
||||
// overlapping respawns barely existed; now they are routine. Skip the peer
|
||||
// sphere instead -- the observer still sees the un-wreck swap itself.
|
||||
if (gWarpPhase != 0 && gWarpPOV)
|
||||
{
|
||||
if (getenv("BT_TLOC_LOG"))
|
||||
DEBUG_STREAM << "[tloc] peer warp SKIPPED (own POV warp active, phase "
|
||||
<< gWarpPhase << ")\n" << std::flush;
|
||||
return;
|
||||
}
|
||||
BTWarpApplyScaleEnv();
|
||||
gWarpPhase = 2; // ExpandReveal
|
||||
gWarpT = 0.0f;
|
||||
|
||||
@@ -1134,7 +1134,15 @@ protected:
|
||||
NotationFile *model_file, const char *model_name,
|
||||
NotationFile *model_notation,
|
||||
const ResourceDirectories *directories);
|
||||
static SharedData *
|
||||
// Returns the BASE shared-data type: the subsystem blocks are
|
||||
// Simulation__SharedData and only Mech's own is the derived
|
||||
// Entity__SharedData (ENTITY3.h:9 -- Entity__SharedData : public
|
||||
// Simulation::SharedData), so the common type of the mixed table is
|
||||
// the Simulation one. Declaring this as Mech::SharedData made every
|
||||
// `&<Subsystem>::DefaultData` stub in mech3.cpp mangle to a symbol
|
||||
// that does not exist -- ~20 unresolved externals /FORCE silently
|
||||
// resolved to garbage (reconstruction-gotchas #3).
|
||||
static Simulation::SharedData *
|
||||
SubsystemDefaultData(const char *type_name);
|
||||
|
||||
// --- simulation / damage (mech4) ------------------------------------
|
||||
|
||||
@@ -93,19 +93,36 @@
|
||||
// Reconstruction stand-ins LOCAL to this translation unit.
|
||||
// The offline "Subsystems" authoring path dispatches each streamed component
|
||||
// to its subsystem class's static CreateStreamedSubsystem / DefaultData.
|
||||
// Those ~21 classes live in sibling modules not visible here (and PPC /
|
||||
// GaussRifle / SubsystemMessageManager have no reconstructed class at all),
|
||||
// so the uniform factory surface is declared here as minimal stubs. The
|
||||
// bodies/data are resolved at link against the real subsystem modules; these
|
||||
// declarations only let the dispatch compile. Kept LOCAL so they never
|
||||
// collide with the real sibling classes.
|
||||
// Those ~21 classes live in sibling modules not visible here, so the uniform
|
||||
// factory surface is declared here as minimal stubs. The bodies/data are
|
||||
// resolved at link against the real subsystem modules; these declarations
|
||||
// only let the dispatch compile. Kept LOCAL so they never collide with the
|
||||
// real sibling classes.
|
||||
// ⚠ The DefaultData member TYPE must be Simulation__SharedData -- the type
|
||||
// the real Subsystem-family definitions have (every sibling declares it via
|
||||
// the inherited SharedData typedef, dumpbin: ?DefaultData@X@@2VSimulation__
|
||||
// SharedData@@A). Declaring it as Mech::SharedData (== Entity__SharedData)
|
||||
// mangles to a DIFFERENT symbol, and /FORCE turned all ~20 of the resulting
|
||||
// unresolved externals into silent garbage pointers instead of link errors
|
||||
// (reconstruction-gotchas #3). Cold in-game (this is the tool path), but a
|
||||
// landmine for the authoring tools. FIXED for the DefaultData statics.
|
||||
// ⚠ STILL UNRESOLVED (known, cold): the CreateStreamedSubsystem stubs below
|
||||
// do NOT match the real modules' signatures -- the real ones take the
|
||||
// class's NESTED <X>::SubsystemResource * (arg 4) and a trailing
|
||||
// ResourceFile * (e.g. mechtech.cpp:407), where these stubs guessed
|
||||
// void* / int. All 20 factory references therefore stay unresolved under
|
||||
// /FORCE. Harmless while the offline authoring dispatch has no callers,
|
||||
// but before wiring a tool through CreateSubsystemStream these must become
|
||||
// per-module BRIDGE functions in complete-type TUs (the project's bridge
|
||||
// convention) -- the nested resource types make direct stubs impossible.
|
||||
// Tracked in context/open-questions.md.
|
||||
//===========================================================================//
|
||||
typedef Mech::SharedData SharedData; // namespace-scope alias (== Entity__SharedData)
|
||||
|
||||
// 7-arg (non-weapon) streamed-subsystem factory shape.
|
||||
#define STREAMED_SUBSYS(NAME) \
|
||||
struct NAME { \
|
||||
static SharedData DefaultData; \
|
||||
static Simulation__SharedData DefaultData; \
|
||||
static int CreateStreamedSubsystem(NotationFile *, const char *, \
|
||||
const char *, void *, NotationFile *, \
|
||||
const ResourceDirectories *, int); \
|
||||
@@ -113,7 +130,7 @@ typedef Mech::SharedData SharedData; // namespace-scope alias (== Entity__Shared
|
||||
// 8-arg (weapon) streamed-subsystem factory shape (leading pass counter).
|
||||
#define STREAMED_WEAPON(NAME) \
|
||||
struct NAME { \
|
||||
static SharedData DefaultData; \
|
||||
static Simulation__SharedData DefaultData; \
|
||||
static int CreateStreamedSubsystem(int, NotationFile *, \
|
||||
const char *, const char *, void *, NotationFile *, \
|
||||
const ResourceDirectories *, int); \
|
||||
@@ -141,13 +158,13 @@ STREAMED_WEAPON(AmmoBin);
|
||||
// MechTech is forward-declared in mech.hpp; complete it (non-weapon shape).
|
||||
struct MechTech
|
||||
{
|
||||
static SharedData DefaultData;
|
||||
static Simulation__SharedData DefaultData;
|
||||
static int CreateStreamedSubsystem(NotationFile *, const char *,
|
||||
const char *, void *, NotationFile *,
|
||||
const ResourceDirectories *, int);
|
||||
};
|
||||
// MechControlsMapper is forward-declared in mech.hpp; only its DefaultData is used.
|
||||
struct MechControlsMapper { static SharedData DefaultData; };
|
||||
struct MechControlsMapper { static Simulation__SharedData DefaultData; };
|
||||
// SubsystemMessageManager: leading pass, no trailing index, no DefaultData.
|
||||
struct SubsystemMessageManager
|
||||
{
|
||||
@@ -905,7 +922,7 @@ fail:
|
||||
// CLASSMAP / the per-module reconstructions). Returns 0 for an unknown name.
|
||||
//###########################################################################
|
||||
//###########################################################################
|
||||
/*static*/ SharedData *
|
||||
/*static*/ Simulation::SharedData *
|
||||
Mech::SubsystemDefaultData(const char *type_name)
|
||||
{
|
||||
if (Streq(type_name, "")) return &Mech::DefaultData; // 0050bde4 (DAT_0050dc1f == "")
|
||||
|
||||
Reference in New Issue
Block a user