From 18fba98298c1ac49e92a856c224636724d701a04 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Tue, 11 Aug 2026 16:13:39 -0500 Subject: [PATCH] #165 does not reproduce -- the destroyed-weapon fire gate HOLDS on the exact field path: bench (xfire_bench.sh) kills ERPPC_1+ERSLaser_1 via the cascade's own ForceCriticalFailure writer under held autofire -- ERSLaser_1 9 FIRED pre-kill, 0 post, 4213 refusals (destroyed=1), surviving ERMLaser fires on. Rajel's field read explained: the refusal tail recomputes output voltage every tick (binary-authentic), so a destroyed weapon's recharge dial keeps CYCLING under its X while the surviving torso lasers put the beams peers saw. Rider: the [emitter] FIRED receipt now NAMES its weapon under BT_DMG_LOG (the #110 nit) so the next field session can settle it by name. Co-Authored-By: Claude Fable 5 --- game/reconstructed/emitter.cpp | 9 +++++- scratchpad/night15/xfire_bench.sh | 51 +++++++++++++++++++++++++++++++ 2 files changed, 59 insertions(+), 1 deletion(-) create mode 100644 scratchpad/night15/xfire_bench.sh diff --git a/game/reconstructed/emitter.cpp b/game/reconstructed/emitter.cpp index 9e16d58..b2b1996 100644 --- a/game/reconstructed/emitter.cpp +++ b/game/reconstructed/emitter.cpp @@ -304,7 +304,14 @@ void // BRING-UP verify (rate-limited): prove the real fire path executes + feeds the heat // sim (heatPortion -> pendingHeat -> flows to the central sink -> mech temp climbs). static int s_fireLog = 0; - if ((s_fireLog++ % 19) == 0) // 19 prime: rotates across the 5 emitters (20 aliased to one) + // #165 bench: EVERY discharge, NAMED, under BT_DMG_LOG (the old unnamed + // 1-in-19 sample could not answer "which weapon fired" -- the #110 nit); + // the rate-limited unnamed line stays for ambient runs without the env. + if (getenv("BT_DMG_LOG")) + DEBUG_STREAM << "[emitter] FIRED '" << (GetName() ? GetName() : "?") + << "' damage=" << damagePortion << " heat=" << heatPortion + << "\n" << std::flush; + else if ((s_fireLog++ % 19) == 0) // 19 prime: rotates across the 5 emitters (20 aliased to one) DEBUG_STREAM << "[emitter] FIRED #" << s_fireLog << " damage=" << damagePortion << " heat=" << heatPortion << " pendingHeat=" << pendingHeat << "\n" << std::flush; diff --git a/scratchpad/night15/xfire_bench.sh b/scratchpad/night15/xfire_bench.sh new file mode 100644 index 0000000..82bce75 --- /dev/null +++ b/scratchpad/night15/xfire_bench.sh @@ -0,0 +1,51 @@ +#!/usr/bin/env bash +# #165: do crit-DESTROYED energy weapons still fire? Rajel's field case: live +# arm cascade destroyed ERPPC_1+ERSLaser_1, he reports they still fired with +# X's showing. Rig: vulture, BT_AUTOFIRE holds the trigger, BT_KILL_SUBSYS +# destroys both left-arm weapons at frame 900 (~15s, ForceCriticalFailure -- +# the same call the cascade makes). Receipts: [emitter] '' fire REFUSED +# vs FIRED lines (BT_DMG_LOG). +# PASS: ERPPC_1 fires BEFORE the kill, only REFUSED after; a surviving weapon +# keeps firing after. FAIL: any post-kill FIRED from a destroyed weapon. +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 xfire.log +bt_expert_egg MP.EGG XF.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=vulture/" XF.EGG + +( export BT_AUTOFIRE=1 BT_FIRE_AT_ICON=1 BT_KILL_SUBSYS=ERPPC_1,ERSLaser_1 + export BT_DMG_LOG=1 BT_FIRE_LOG=1 + bt_launch xfire.log XF.EGG 0x03 -egg XF.EGG ) +sleep 100 +bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1 + +python - <<'PY' +import io, re +txt = io.open("xfire.log", encoding="latin-1", errors="replace").read() +lines = txt.splitlines() +kill = next((i for i,l in enumerate(lines) if "ForceCriticalFailure" in l or "KILL_SUBSYS" in l or "kill-subsys" in l.lower()), None) +# fall back: first REFUSED line marks the post-kill era +if kill is None: + kill = next((i for i,l in enumerate(lines) if "fire REFUSED" in l), None) +print("=== #165 X'D-WEAPON FIRE ===") +print("kill marker line:", kill) +def count(era, name, what): + if kill is None: return -1 + seg = lines[:kill] if era=="pre" else lines[kill:] + return sum(1 for l in seg if name in l and what in l) +for w in ("ERPPC_1", "ERSLaser_1", "ERMLaser_1"): + print("%-11s pre-kill FIRED=%3d | post-kill FIRED=%3d REFUSED=%3d" + % (w, count("pre", w, "FIRED"), count("post", w, "FIRED"), count("post", w, "REFUSED"))) +leak = (count("post","ERPPC_1","FIRED") or 0) + (count("post","ERSLaser_1","FIRED") or 0) +alive = count("post","ERMLaser_1","FIRED") +if kill is None: + print("VERDICT: INCONCLUSIVE -- no kill/refusal marker found (did the kill hook fire?)") +elif leak > 0: + print("VERDICT: FAIL(=field confirmed) -- destroyed weapons fired %d times post-kill" % leak) +elif alive == 0: + print("VERDICT: INCONCLUSIVE -- nothing fired post-kill (autofire/lock problem)") +else: + print("VERDICT: PASS -- destroyed weapons refuse, survivor keeps firing (port gate holds)") +PY