From b2498ca39a4c55874ae4737d72135f88d6fc0450 Mon Sep 17 00:00:00 2001 From: Joe DiPrima Date: Fri, 7 Aug 2026 10:00:24 -0500 Subject: [PATCH] scoring: the type-0 arm must RETURN, not break -- it was clobbering scoreAward Chasing the duplicate rows from 1324c81 (80 real awards + 80 reading award=0.00). Not a double delivery -- Entity::Dispatch sends exactly once on a replicant. It was the `break` I left in the delegating arm. After delegating to ScoreInflictedMessageHandler, control fell into ScoreMessageHandler's post-switch tail, where the LOCAL `award` is still 0: message->scoreAward = award; // clobbered to 0 BTMatchLog("SCORE", ... award=0.00 ...); // the phantom row Player::ScoreMessageHandler(message); // base: currentScore += 0 Harmless to the total only because the value added happened to be zero -- but it mutated a message on a shared path and ran a base handler for nothing. A later reader of scoreAward, or any side effect gained by that tail, would have turned it into a real bug with no obvious cause. ScoreInflictedMessageHandler is self-contained (accumulates, ForceUpdate()s, logs its own receipt), so the arm returns. Re-benched cross-node: rows on shooter's master 100, ZERO phantom rows (was 80 + 80) rows on victim's node 0 running total 417.85, climbing continuously, no resets type-0 Verify rejects 0 kill path intact (kills=1) Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01NCJQkvq6G2JNrpVbA75tVZ --- game/reconstructed/btplayer.cpp | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 3db4a4f..5cb9f36 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -1019,8 +1019,15 @@ void // exactly as the binary does; wire deliveries land here and get the same // handler. One accumulator, on the machine that owns the score. // + // RETURN, not break. The post-switch tail folds the local `award` into + // message->scoreAward and hands it to the base handler -- and for this + // arm `award` is still 0, so falling through clobbered scoreAward to + // zero, added nothing, and emitted a second SCORE row reading + // "type=0 award=0.00" (the 80 real + 80 zero rows in the bench). + // ScoreInflictedMessageHandler is self-contained: it accumulates, + // ForceUpdate()s and logs its own receipt. ScoreInflictedMessageHandler(message); - break; + return; case BTPlayer::ScoreMessage::DamageReceivedScore: // 1 {