diff --git a/context/multiplayer.md b/context/multiplayer.md index 39a0505..1255d08 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -37,19 +37,30 @@ all connect. The **CONSOLE** (operator station — ABSENT from every archive) de emulator** (⚠ `NotationFile::ReadText` expects NUL-SEPARATED lines). [T2] ## Verified milestones (one box, two instances) -- ⚠ **`-net` NODE RENDER GLITCH — OPEN (observed live 2026-07-09, cause NOT yet found):** in a 2-node - session ONE window comes up visually GLITCHED FROM LOAD (before any death) — shadow clipping, camera - stuck inside/blocked, targeting/firing feels dead — while the OTHER is fine. **Isolated by test, not - theory:** (a) a SOLO instance is perfect; (b) **TWO SOLO instances (no `-net`) are BOTH perfect** - (each logs `MECH under boresight … IN WEAPON RANGE`, no device fallback/error, both hardware HAL - devices). So it is **NOT** a two-D3D9-apps-on-one-GPU / device-contention issue (an earlier note - claimed that — WRONG, retracted) and NOT the warp/respawn (from-load, pre-death). It is **specific - to the MP `-net` path** — the console-driven mission-start ladder / replication doing something to - ONE node's render+input state, asymmetrically (the two nodes differ in launch order, affinity, - net port, BT_MP_NET, BT_MP_FORCE_DMG). CAUSE UNKNOWN — next step: determine WHICH node glitches - (A@1501 vs B@1601) and correlate, then audit the MP viewpoint/camera/HUD setup vs the (clean) solo - path. Does NOT affect the pod (one instance per machine). Dev workflow: trust the SOLO window for - visual/gameplay checks; the 2-node logs are still trustworthy for replication/netcode logic. +- ⚠ **`-net` node render glitch — LIKELY ALREADY FIXED / WATCHING (task #53):** a 2-node MP session + once showed ONE window come up visually wrong — shadow clipping, camera stuck inside/blocked, + targeting/firing dead — while the other was fine. Two earlier framings were WRONG and are corrected: + (i) the "two-D3D9-apps-on-one-GPU / device contention" theory was retracted — a solo instance, AND + two solo instances, are all clean, so it is not device contention; (ii) "glitched FROM LOAD, before + any death" was ALSO wrong — **the trigger is a RESPAWN** (user correction, 2026-07-10). So it is a + **post-respawn render-rebuild fault on the MP path**, not a load-time one. Does NOT affect the pod + (one instance per machine). + - **What the respawn rebuild does (reproduced 2026-07-10):** on respawn the player's mech is rebuilt + via `MakeEntityRenderables`, which runs a TWO-STEP view sequence — inside skeleton + `COCKPIT + eyepoint`, then outside skeleton + `external chase` (`[view] …` logs, btl4vid.cpp:2158) — and the + external chase camera is NOT re-installed (only ONE `[BTrender] external debug chase camera + installed`, btl4vid.cpp:648, for the whole session despite multiple respawns). Plausible failure: + if that two-step completes only to the COCKPIT step (raced/interrupted on the `-net` ladder), the + node is left stuck INSIDE — body hidden by the inside skeleton, shadow/reticle flagging with it = + the reported bundle. Camera + reticle + view + `tshd` shadow are all gated on `buildDebugChaseCamera + = (view_type == insideEntity)` (btl4vid.cpp:297). + - **Status: could NOT reproduce (2026-07-10) → likely already fixed.** 9 automated 2-node loads PLUS a + forced death/respawn all came up CLEAN (the respawn completed to external chase; clean backbuffer via + the new `BT_SHOT` dump). Since debug uninit is a deterministic `0xCDCDCDCD` (would glitch every run), + the intermittency implies a **race**, and the task #52 respawn fixes (the `LoadMission` re-entry + killer bug, viewpoint relink, death-latch) plausibly ALREADY closed it. **Left as WATCH** — re-open + and capture with `BT_SHOT` if it recurs. Tools kept: **`BT_SHOT=`** (per-instance backbuffer + PNG, off by default, L4VIDEO.cpp) + `scratchpad/hunt53.sh` (N-run divergence hunter). - **P6 smoke test:** two instances share a world — console egg → TCP mesh → synchronized start → bidirectional entity replication (each renders its own mech + the peer's REPLICANT), 0 crashes. - **Movement replication:** A's mech WALKS on B's screen (the replicant tracks the master to ~1.5u via diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index 025f4b0..259beee 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -7798,6 +7798,34 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte BTDrawTranslocationSpheres(mDevice, &viewTransform, (float)dT, mTargetRenderTime); } + // DIAG (off by default): BT_SHOT= dumps this instance's backbuffer (the rendered + // WORLD, before the 2D HUD overlay) to a PNG once ~180 frames in, for non-disruptive + // per-instance frame capture without foregrounding the window. Each instance sets its + // own path. Built for the -net render glitch (task #53) but general. + { + const char *shotPath = getenv("BT_SHOT"); + if (shotPath) + { + static int s_btShotN = 0; + ++s_btShotN; + // dump (overwriting) every 90 frames once past 90, so the file always holds a + // recent frame regardless of load timing / debug fps. + if (s_btShotN >= 90 && (s_btShotN % 90) == 0) + { + IDirect3DSurface9 *bb = 0; + if (SUCCEEDED(mDevice->GetBackBuffer(0, 0, D3DBACKBUFFER_TYPE_MONO, &bb)) && bb) + { + HRESULT hr = D3DXSaveSurfaceToFileA(shotPath, D3DXIFF_PNG, bb, 0, 0); + bb->Release(); + DEBUG_STREAM << "[btshot] frame " << s_btShotN << " -> " << shotPath + << " hr=0x" << std::hex << (unsigned)hr << std::dec << "\n" << std::flush; + } + else + DEBUG_STREAM << "[btshot] GetBackBuffer FAILED frame " << s_btShotN << "\n" << std::flush; + } + } + } + // The BT targeting reticle / weapon pips (2D screen space, cockpit view // only -- the dpl2d layer; see game/reconstructed/dpl2d.cpp). {