From 5d6ded73f0aee12a05ebf33db1c0b50e17d32f9b Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Sun, 2 Aug 2026 09:09:49 -0500 Subject: [PATCH] #110: cross-pod verified BOTH ways -- the cascade replicates, and peer fire alone drives it Two two-node benches close the last stated gap ("no two-node run has watched a peer's view of an arm death"), the second at the user's insistence on the REAL field composition after the first used the self-damage scalpel: 1. mp_armchain.sh (scalpel): B self-destroys dz_rarm mid-fight. A's screen receives the whole cascade -- [zone-repl] dz_rarm lvl 0->1 AND the cascaded dz_rgun lvl 0->1 -- plus the respawn reset coming back clean (1->0). B's post-cascade AFC pushes turned out to be the RESPAWN RE-ARM, correct behavior, not a gate failure. (Also confirmed: the self-damage harness dispatches a real Entity::TakeDamageMessage -- the identical path a peer's round takes, so the scalpel and the field share every line from the handler down.) 2. mp_armgrind.sh (the field composition): stand-off at 180, NO self-damage anywhere, A grinding B with real cross-pod fire for six minutes. On B: * 25 incremental arm-zone hits from A's weapons via the cylinder tables (dz_rgun reached 1.0 from pure accumulation -- 15 cascades on rehits) * dz_rarm finished by B's OWN AFC bay cooking off (AmmoBinAFC100 BAY FIRE 350, half into dz_rarm) -- itself lit by A's fire crits: peer-originated end to end * [cascade] zone 9 -> AFC100 + AmmoBinAFC100 + Condenser6 force-failed, fire gate refusals logged On A: [zone-repl] dz_rarm 0->1 and dz_rgun 0->1 -- the shooter SEES the arm and pod die. B symmetrically received A's torso deaths. Also: DamageZone::ReadUpdateRecord gets a change-only [zone-repl] diagnostic (BT_MP_LOG) -- the probe that made the peer side measurable; mp_armgrind.sh opens with the stale-node taskkill the other benches already had (a leftover node from an aborted launch joined a later lobby as a third instance). Co-Authored-By: Claude Opus 5 (1M context) --- engine/MUNGA/DAMAGE.cpp | 12 ++++++++++++ scratchpad/night9/mp_armchain.sh | 30 +++++++++++++++++++++++++++++ scratchpad/night9/mp_armgrind.sh | 33 ++++++++++++++++++++++++++++++++ 3 files changed, 75 insertions(+) create mode 100644 scratchpad/night9/mp_armchain.sh create mode 100644 scratchpad/night9/mp_armgrind.sh diff --git a/engine/MUNGA/DAMAGE.cpp b/engine/MUNGA/DAMAGE.cpp index 6e69f89..9bd65f8 100644 --- a/engine/MUNGA/DAMAGE.cpp +++ b/engine/MUNGA/DAMAGE.cpp @@ -160,6 +160,18 @@ void Check_Pointer(update_record); Verify(damageZoneIndex == update_record->damageZoneIndex); + // #110 MP verification (BT): log a PEER-side zone state transition, so the + // two-node bench can prove the destruction cascade replicates. Change-only. + if (getenv("BT_MP_LOG") + && (damageZoneGraphicState.GetState() != update_record->damageZoneGraphicState + || (damageLevel < 1.0f && update_record->damageLevel >= 1.0f))) + { + DEBUG_STREAM << "[zone-repl] zone " << damageZoneIndex + << " '" << (const char *)damageZoneName << "'" + << " lvl " << damageLevel << "->" << update_record->damageLevel + << " gstate " << damageZoneGraphicState.GetState() + << "->" << update_record->damageZoneGraphicState << std::endl; + } damageLevel = update_record->damageLevel; damageZoneState.SetState(update_record->damageZoneState); damageZoneGraphicState.SetState(update_record->damageZoneGraphicState); diff --git a/scratchpad/night9/mp_armchain.sh b/scratchpad/night9/mp_armchain.sh new file mode 100644 index 0000000..db6befb --- /dev/null +++ b/scratchpad/night9/mp_armchain.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# #110 MP: node B's ARM ZONE dies (self-damage); what does PEER node A see? +# PASS on A's log: +# [zone-repl] B's arm zone -> gstate 1, its child gun zone -> gstate 2 (Gone) +# B's arm-weapon salvo mirrors STOP after the cascade +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 +bt_assert_player_env +rm -f mpc_a.log mpc_b.log +bt_expert_egg MP.EGG MPC.EGG + +# node B: the victim -- self-destroys its right arm mid-fight +BT_MP_LOG=1 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_PROJ_LOG=1 \ +BT_SELF_DAMAGE=4 BT_SELF_DAMAGE_ZONE=dz_rarm \ +BT_GOTO=enemy BT_GOTO_STOP=100 BT_AUTOFIRE=1 BT_AF_PERIOD=5 \ + bt_launch mpc_b.log MPC.EGG 0x0C -net 1601 +sleep 2 +# node A: the observer +BT_MP_LOG=1 BT_DMG_LOG=1 BT_PROJ_LOG=1 \ +BT_GOTO=enemy BT_GOTO_STOP=100 BT_AUTOFIRE=1 BT_AF_PERIOD=5 \ + bt_launch mpc_a.log MPC.EGG 0x03 -net 1501 +sleep 5 +python ../tools/btconsole.py MPC.EGG 127.0.0.1:1501 127.0.0.1:1601 & +PC=$! +sleep 200 +kill $PC 2>/dev/null +bt_kill_ours +rm -f MPC.EGG +echo DONE diff --git a/scratchpad/night9/mp_armgrind.sh b/scratchpad/night9/mp_armgrind.sh new file mode 100644 index 0000000..fcb58af --- /dev/null +++ b/scratchpad/night9/mp_armgrind.sh @@ -0,0 +1,33 @@ +#!/usr/bin/env bash +# #110 FIELD-COMPOSITION test: no self-damage anywhere. A grinds B with real +# cross-pod fire from a stand-off distance; the cylinder hit tables route a +# share of every burst into B's arm wedges; when an arm zone crosses 1.0 the +# cascade must fire ON B (sourced purely from A's damage), kill the arm's +# weapons, and replicate to A ([zone-repl]). +set -x +. /c/git/bt411/scratchpad/night6/bench_common.sh +cd /c/git/bt411/content || exit 1 +# stale nodes poison the lobby (a third instance joins the pair) -- clear first +taskkill //F //IM btl4.exe > /dev/null 2>&1 +sleep 3 +bt_assert_player_env +rm -f mpg_a.log mpg_b.log +bt_expert_egg MP.EGG MPG.EGG + +# B: sparse return fire (observable weapons, loses the grind), stands its ground +BT_MP_LOG=1 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_PROJ_LOG=1 \ +BT_GOTO=enemy BT_GOTO_STOP=180 BT_AUTOFIRE=1 BT_AF_PERIOD=9 \ + bt_launch mpg_b.log MPG.EGG 0x0C -net 1601 +sleep 2 +# A: dense fire with missiles +BT_MP_LOG=1 BT_DMG_LOG=1 BT_DEATH_LOG=1 BT_PROJ_LOG=1 \ +BT_GOTO=enemy BT_GOTO_STOP=180 BT_AUTOFIRE=1 BT_AF_MISSILE=1 BT_AF_PERIOD=3 \ + bt_launch mpg_a.log MPG.EGG 0x03 -net 1501 +sleep 5 +python ../tools/btconsole.py MPG.EGG 127.0.0.1:1501 127.0.0.1:1601 & +PC=$! +sleep 360 +kill $PC 2>/dev/null +bt_kill_ours +rm -f MPG.EGG +echo DONE