#150 -- one kill credited twice (Rajel on Oracle, Oracle on Conn Man). CAUGHT: one death -> two type-2 arms 1ms apart (awards 501.96/505.88 -- the tally GREW between them), victim inst=R. Mechanism: this port deliberately applies damage on replicants for local visual response; the report tail ran there too, and a replicant's movement mode cannot flip to wreck until the victim's death record round-trips -- so each round of a killing VOLLEY inside that window re-posted the kill. Why missiles, why intermittent. The binary forbids the whole situation: its handler OPENS with the assertion if ((flags & 0xc) == 4) Fail("Replicant Mech recieving takedamage", MECH.CPP, 0x3da) [part_012.c:14613] -- TakeDamage on a replicant was a BUG in 1995; the tail only ever ran on the victim's master. Fix: (1) gate the REPORT TAIL master-only (local damage application stays); (2) adopt the binary's once-latch -- the 0->1 edge of IsMechDestroyed() across the handler call (local_14), replacing the deathBlastArmed form for the report gate. VERIFIED (scratchpad/night14/scorepair.sh): 3 victim deaths, 5 shooter respawns -> exactly one type-2 per credited kill, kills 0->1->2, zero doubles (was: kills 0->2 on one death). #151 -- panic eject "-499 instead of 0" is AUTHENTIC 1995 SCORING, decoded from EjectPilot @0049f854: the punch-out dispatches a SELF TakeDamage of damageAmount = role->killBonus (+0x1c, 500 in dfltrole), type Explosive, senderMechID = SELF, player->suppressConsole = 1, graphicAlarm -> 10 which flows through the report tail as a SUICIDE kill: award = -CalcKillScore (your own death), plus the type-1 death cost. 1000 - ~999 - 500 = -499: the binary predicts Oracle's exact number. Our port already used role->killBonus for the blast (mech.cpp:652) -- faithful end to end; bench shows each eject charged exactly once (-900/-1050 across three ejects). Panic eject = scored as suicide. Whether that is fun is a design question; it is not a porting defect. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018SgmXGNMXavXiafKXf9MDC
This commit is contained in:
co-authored by
Claude Opus 5
parent
773595b347
commit
46ff8dd262
@@ -1032,6 +1032,12 @@ void
|
|||||||
{
|
{
|
||||||
Check(message);
|
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
|
// MP DIAGNOSTIC (task #47): confirm the handler runs on the OWNING MASTER
|
||||||
// for a cross-pod (network-delivered) TakeDamage + what state it carries.
|
// for a cross-pod (network-delivered) TakeDamage + what state it carries.
|
||||||
if (getenv("BT_MP_NET"))
|
if (getenv("BT_MP_NET"))
|
||||||
@@ -1242,11 +1248,31 @@ void
|
|||||||
// vital-wreck flag, and the loop-ENTRY zone. This retires
|
// vital-wreck flag, and the loop-ENTRY zone. This retires
|
||||||
// BTPostDamageScore/BTPostKillScore -- see the btplayer.cpp tombstone.
|
// 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);
|
extern void BTMechPostCombatReports(void *, void *, float, int, int, int);
|
||||||
BTMechPostCombatReports((void *)this, (void *)message,
|
BTMechPostCombatReports((void *)this, (void *)message,
|
||||||
(float)damageTally, zoneDestroyed, reportZone,
|
(float)damageTally, zoneDestroyed, reportZone,
|
||||||
deathBlastArmed && IsMechDestroyed());
|
!destroyedAtEntry && IsMechDestroyed());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -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
|
||||||
Reference in New Issue
Block a user