diff --git a/game/reconstructed/mech.cpp b/game/reconstructed/mech.cpp index d96c376..88b42a4 100644 --- a/game/reconstructed/mech.cpp +++ b/game/reconstructed/mech.cpp @@ -1032,6 +1032,12 @@ void { Check(message); + // #150 -- the binary's ONCE-PER-DEATH latch (local_14 @0x4a0230 body, + // part_012.c:14625-14629): IsMechDestroyed() sampled AT ENTRY. The kill + // report posts only on the 0->1 edge (!destroyedAtEntry && destroyed-now); + // a later salvo of the killing volley takes the inflicted-only path. + const int destroyedAtEntry = IsMechDestroyed(); + // MP DIAGNOSTIC (task #47): confirm the handler runs on the OWNING MASTER // for a cross-pod (network-delivered) TakeDamage + what state it carries. if (getenv("BT_MP_NET")) @@ -1242,11 +1248,31 @@ void // vital-wreck flag, and the loop-ENTRY zone. This retires // BTPostDamageScore/BTPostKillScore -- see the btplayer.cpp tombstone. // + // #150 DOUBLE-KILL FIX, two binary-grounded gates (2026-08-10): + // + // (1) MASTER ONLY. The binary OPENS this handler with the assertion + // `if ((flags & 0xc) == 4) Fail("Replicant Mech recieving takedam...", + // MECH.CPP, 0x3da)` (part_012.c:14613-14616) -- in 1995 a replicant + // executing TakeDamage was a BUG, so the report tail could only ever + // run on the victim's master, once. This port deliberately applies + // damage on replicants too (local visual responsiveness), so the tail + // ALSO ran there -- and the replicant's movement mode can't flip to + // wreck until the death record round-trips, so a multi-round killing + // volley posted a kill report PER ROUND inside that window. Measured: + // one death -> two type-2 arms 1ms apart, kills 0->2 (the field's + // "one kill counted twice", #150). Gate the REPORTS to the master; + // the local damage application stays. + // + // (2) ENTRY LATCH. The binary's newly-destroyed test is the 0->1 edge of + // IsMechDestroyed() across THIS handler call (local_14), not the + // movement-mode-derived deathBlastArmed -- on the master both agree, + // but the binary's form is the spec. + if (GetInstance() != ReplicantInstance) { extern void BTMechPostCombatReports(void *, void *, float, int, int, int); BTMechPostCombatReports((void *)this, (void *)message, (float)damageTally, zoneDestroyed, reportZone, - deathBlastArmed && IsMechDestroyed()); + !destroyedAtEntry && IsMechDestroyed()); } } diff --git a/scratchpad/night14/scorepair.sh b/scratchpad/night14/scorepair.sh new file mode 100644 index 0000000..8209f57 --- /dev/null +++ b/scratchpad/night14/scorepair.sh @@ -0,0 +1,30 @@ +#!/usr/bin/env bash +# #150/#151: count type-2 arm executions per kill + measure the eject score arithmetic +. /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 sc_a.log sc_b.log sc_r.log +mkdir -p /c/git/bt411/scratchpad/night14/mlbak && mv matchlog_*.txt /c/git/bt411/scratchpad/night14/mlbak/ 2>/dev/null +bt_expert_egg MP.EGG SC.EGG +sed -i "s/^map=.*/map=grass/; s/^time=.*/time=day/; s/^vehicle=.*/vehicle=madcat/" SC.EGG +# B: unarmed slow target, respawns on death +( export BT_GOTO=enemy BT_GOTO_STOP=80 BT_SCORE_LOG=1 BT_DEATH_LOG=1 BT_MATCHLOG=1 BT_MP_LOG=1 + bt_launch sc_b.log SC.EGG 0x0C -net 1601 ) +sleep 2 +# A: kills B with autofire, then panic-ejects late +( export BT_GOTO=enemy BT_GOTO_STOP=100 BT_AUTOFIRE=1 BT_AF_MISSILE=1 BT_AF_PERIOD=4 + export BT_EJECT_AT=9000 + export BT_SCORE_LOG=1 BT_DEATH_LOG=1 BT_MATCHLOG=1 BT_MP_LOG=1 + bt_launch sc_a.log SC.EGG 0x03 -net 1501 ) +sleep 5 +python ../tools/btconsole.py SC.EGG 127.0.0.1:1501 127.0.0.1:1601 > sc_r.log 2>&1 & +R=$!; sleep 300; kill $R 2>/dev/null; sleep 2 +bt_kill_ours; sleep 2; taskkill //F //IM btl4.exe >/dev/null 2>&1 +echo RESULT +echo "--- B deaths (kills A should have earned) ---" +grep -ac "Mech::Reset" sc_b.log +echo "--- A: kill-arm executions + score sequence (matchlog) ---" +grep -ah "SCORE\|KILL\|EJECT\|DEATH" matchlog_*.txt 2>/dev/null | head -25 +echo "--- A: scorepost receipts (producer side) ---" +grep -a "scorepost" sc_a.log | head -10 +grep -a "scorepost" sc_b.log | head -10