diff --git a/engine/MUNGA_L4/L4RIO.cpp b/engine/MUNGA_L4/L4RIO.cpp index 2e1ac19..fc6a2bc 100644 --- a/engine/MUNGA_L4/L4RIO.cpp +++ b/engine/MUNGA_L4/L4RIO.cpp @@ -1377,11 +1377,19 @@ Logical if (operational) { DWORD now = GetTickCount(); + // #160 telemetry fix: the detector used to key on REQUEST age -- but the + // poll loop sends a fresh request every ~50ms regardless of replies, so + // request-age never reached 500ms and the STALL line could never fire + // (the field logs' age= field carried the signal instead). Key on the + // REPLY drought: requests outstanding AND no AnalogReply for 500ms + // since the last good one. Requires one good reply first (boot-mute + // shows in the req/rep counters, not here). if (!gRioStallActive && gRioReqPending - && now - gRioLastReqTick > 500) + && gRioLastReplyTick != 0 + && now - gRioLastReplyTick > 500) { gRioStallActive = 1; - gRioStallStart = gRioLastReqTick; + gRioStallStart = gRioLastReplyTick; gRioStallBtnBase = gRioBtnCount; } gRioPresent = 1; diff --git a/engine/MUNGA_L4/L4VIDEO.cpp b/engine/MUNGA_L4/L4VIDEO.cpp index cfc7570..ef1d668 100644 --- a/engine/MUNGA_L4/L4VIDEO.cpp +++ b/engine/MUNGA_L4/L4VIDEO.cpp @@ -8441,6 +8441,107 @@ void DPLRenderer::ExecuteImplementation(RendererComplexity, RendererOrigin::Inte lastFrameTime = mTargetRenderTime; currentFrameTime = Now(); + // #156 -- the MISSION START/END FADE (POVStartEndRenderable, ctor @00454394, + // Execute @0045447c; the port class is armed by MakeEntityRenderables' + // BTPlayer master case via BTMissionFadeArm). The 1995 renderable drives + // the POV zone fog through a 5-phase machine keyed on the local player's + // SimulationState; constants byte-read from BTL4OPT.EXE @0x454740-0x454750: + // flash hold 0.1s, fades 1.0s, plane floors near 0.01 / far 0.05. Phases: + // 0 armed -> (MissionStarting) 1 WHITE FLASH (fog 1,1,1 @ 0.01/0.05) -> + // 2 FADE-IN (white -> live style; planes floor -> authored) -> 3 running -> + // (MissionEnding) 4 FADE-OUT (color x local -> BLACK, planes close) -> + // re-arm. The binary re-reads the LIVE fog each frame mid-fade (style + // swaps track); we read the STYLE members (fogRed/G/B, fogNear/Far), never + // currentFogNear/Far which we are overriding. Placed at the frame head: + // the Clear below samples FOGCOLOR, so the fade owns the WHOLE frame + // including sky/void. Receipts: [fade] phase lines under BT_FADE_LOG or + // BT_FOG_LOG. + { + extern void *gBTFadeIndicator; // BTMissionFadeArm (btl4vid.cpp) + extern int gBTFadeStart, gBTFadeEnd; + static int s_phase = 0; + static float s_T = 0.0f; + if (gBTFadeIndicator != 0) + { + const float kFlash = 0.1f, kFade = 1.0f; // @0x454740 / @0x454744 + const float kNearFloor = 0.01f, kFarFloor = 0.05f; // @0x454750 / @0x45474c + int st = ((StateIndicator *)gBTFadeIndicator)->GetState(); + float fdt = (float)dT; + if (fdt < 0.0f) fdt = 0.0f; else if (fdt > 0.1f) fdt = 0.1f; + float r = -1.0f, g = 0.0f, b = 0.0f, fn = 0.0f, ff = 0.0f; + switch (s_phase) + { + case 0: + if (st == gBTFadeStart) { s_phase = 1; s_T = 0.0f; } + break; + case 1: + r = 1.0f; g = 1.0f; b = 1.0f; fn = kNearFloor; ff = kFarFloor; + s_T += fdt; + if (s_T >= kFlash) { s_phase = 2; s_T = 0.0f; } + break; + case 2: + { + float local = kFade - s_T; if (local < 0.0f) local = 0.0f; + float el = kFade - local; + r = (1.0f - fogRed) * local + fogRed; + g = (1.0f - fogGreen) * local + fogGreen; + b = (1.0f - fogBlue) * local + fogBlue; + fn = fogNear * el + kNearFloor; + ff = fogFar * el + kFarFloor; + s_T += fdt; + if (local <= 0.0f) { s_phase = 3; r = -1.0f; } // fog back to the style + break; + } + case 3: + if (st == gBTFadeEnd) { s_phase = 4; s_T = 0.0f; } + break; + case 4: + { + float local = kFade - s_T; if (local < 0.0f) local = 0.0f; + r = fogRed * local; + g = fogGreen * local; + b = fogBlue * local; + fn = fogNear * local + kNearFloor; + ff = fogFar * local + kFarFloor; + s_T += fdt; + if (local <= 0.0f) { s_phase = 0; } // re-armed; final frame stays black + break; + } + } + if (r >= 0.0f) + { + currentFogNear = fn; + currentFogFar = ff; + mDevice->SetRenderState(D3DRS_FOGCOLOR, + D3DCOLOR_XRGB((int)(255 * r), (int)(255 * g), (int)(255 * b))); + mDevice->SetRenderState(D3DRS_FOGENABLE, TRUE); + } + else if (s_phase == 3 || s_phase == 0) + { + // Out of the fade: restore the style planes once so a fade that + // ended mid-style leaves the world exactly on its authored fog. + static int s_prev = -1; + if (s_prev != s_phase) + { + currentFogNear = fogNear; + currentFogFar = fogFar; + mDevice->SetRenderState(D3DRS_FOGCOLOR, + D3DCOLOR_XRGB((int)(255 * fogRed), (int)(255 * fogGreen), + (int)(255 * fogBlue))); + } + s_prev = s_phase; + } + static int s_lastLogged = -1; + if (s_phase != s_lastLogged) + { + s_lastLogged = s_phase; + if (getenv("BT_FADE_LOG") || getenv("BT_FOG_LOG")) + DEBUG_STREAM << "[fade] phase " << s_phase + << " (simState=" << st << ")\n" << std::flush; + } + } + } + // DIAG (turn-hitch hunt): time the render phases -- draw CPU vs Present // (GPU-queue block). Logged on slow frames + 1 Hz stats. LARGE_INTEGER _rt0; QueryPerformanceCounter(&_rt0); diff --git a/game/reconstructed/btl4vid.cpp b/game/reconstructed/btl4vid.cpp index caa1e56..a57283c 100644 --- a/game/reconstructed/btl4vid.cpp +++ b/game/reconstructed/btl4vid.cpp @@ -3687,6 +3687,35 @@ namespace { extern void BTSetWorldDead(int dead); // L4VIDRND.cpp bridge -> l4_application->SetIsDead +//===========================================================================// +// #156 -- the mission start/end fade arming seam. The phase machine itself +// runs at the renderer frame head (L4VIDEO.cpp, beside the FOGCOLOR clear); +// these globals are what MakeEntityRenderables' BTPlayer master case arms +// through the now-real BTPOVStartEndRenderable ctor below. +//===========================================================================// +void *gBTFadeIndicator = 0; +int gBTFadeStart = 3, gBTFadeEnd = 4; + +// The real ctor (was a btstubs.cpp inert stub -- the #156 root cause: rounds +// hard-cut instead of fading). Binary ctor @00454394 stores the indicator + +// the authored fog params and registers on the view's watcher list; the port +// keeps the object inert (house pattern, like the translocation renderable) +// and arms the frame-head machine instead. The fog params ride the renderer +// members live, exactly as the binary re-reads them per fade frame. +BTPOVStartEndRenderable::BTPOVStartEndRenderable( + Entity *entity, int, dpl_VIEW *, dpl_ZONE *, dpl_ZONE *, + StateIndicator *sim_state, + float, float, float, float, float, int start_state, int end_state) + : BTRenderableBase(entity) +{ + gBTFadeIndicator = (void *)sim_state; + gBTFadeStart = start_state; + gBTFadeEnd = end_state; + if (getenv("BT_FADE_LOG") || getenv("BT_FOG_LOG")) + DEBUG_STREAM << "[fade] armed (states " << start_state << "/" + << end_state << ")\n" << std::flush; +} + // The renderable objects the entity tree builds for the translocation wiring are // inert now (the warp is the self-contained one-shot below); the ctor/dtor just // satisfy MakeEntityRenderables. diff --git a/game/reconstructed/btstubs.cpp b/game/reconstructed/btstubs.cpp index 509a6c7..352fbcb 100644 --- a/game/reconstructed/btstubs.cpp +++ b/game/reconstructed/btstubs.cpp @@ -336,10 +336,9 @@ BTMarkerWatcherRenderable::BTMarkerWatcherRenderable( // BTTranslocationRenderable (the "blue warp" translocation sphere) is now a real // reconstruction in btl4vid.cpp (task #52) -- no longer a stub here. -BTPOVStartEndRenderable::BTPOVStartEndRenderable( - Entity *entity, int, dpl_VIEW *, dpl_ZONE *, dpl_ZONE *, StateIndicator *, - float, float, float, float, float, int, int) - : BTRenderableBase(entity) {} +// BTPOVStartEndRenderable (the mission start/end fade) is now a real +// reconstruction: the arming ctor lives in btl4vid.cpp and the 5-phase fog +// machine at the renderer frame head (L4VIDEO.cpp) -- #156. BTTracerEffectRenderable::BTTracerEffectRenderable( Entity *entity, int, void *, int, dpl_ZONE *, dpl_DCS *, LinearMatrix *) diff --git a/scratchpad/night15/fade_bench.sh b/scratchpad/night15/fade_bench.sh new file mode 100644 index 0000000..c62391b --- /dev/null +++ b/scratchpad/night15/fade_bench.sh @@ -0,0 +1,44 @@ +#!/usr/bin/env bash +# #156: the mission start/end fade. 2-node, 40s round -- the relay owns the +# clock and sends StopMission at length end (btconsole.py), so both fade edges +# fire: MissionStarting (white flash 0.1s + 1.0s fade-in from white) and +# MissionEnding (1.0s fade-out to black; the app stops ~2s later inside the +# player's 3.0s ladder). Receipts: [fade] armed + phase 1/2/3/4 transitions +# (BT_FADE_LOG). PASS: node A's log shows armed, then phases 1,2,3,4 in +# order, and the phase-4 line's simState is the ending state (4). +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 +taskkill //F //IM btl4.exe >/dev/null 2>&1; sleep 3 +rm -f fade_a.log fade_b.log fade_r.log +bt_expert_egg MP.EGG FADE.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/; s/^length=.*/length=40/" FADE.EGG +grep -aq "^length=40" FADE.EGG || { echo "FAIL: egg length not set"; exit 1; } + +( export BT_FADE_LOG=1 BT_MATCHLOG=1 + bt_launch fade_b.log FADE.EGG 0x0C -net 1601 ) +sleep 2 +( export BT_FADE_LOG=1 BT_MATCHLOG=1 + bt_launch fade_a.log FADE.EGG 0x03 -net 1501 ) +sleep 5 +python ../tools/btconsole.py FADE.EGG 127.0.0.1:1501 127.0.0.1:1601 > fade_r.log 2>&1 & +R=$!; sleep 110; kill $R 2>/dev/null; sleep 2 +bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1 + +python - <<'PY' +import io, re, sys +ok = True +for node in ("fade_a.log", "fade_b.log"): + txt = io.open(node, encoding="latin-1", errors="replace").read() + armed = "[fade] armed" in txt + phases = [int(m.group(1)) for m in re.finditer(r"\[fade\] phase (\d)", txt)] + end4 = re.search(r"\[fade\] phase 4 \(simState=4\)", txt) is not None + print("%s: armed=%d phases=%s end-edge-ok=%d" % (node, armed, phases, end4)) + want = [1, 2, 3, 4] + seq_ok = [p for p in phases if p in want] [:4] == want + if not (armed and seq_ok and end4): + ok = False +print("VERDICT:", "PASS -- flash + fade-in + fade-out ran on both nodes" + if ok else "FAIL -- phase sequence incomplete (see above)") +sys.exit(0 if ok else 1) +PY