Score replication: peers' scores now reach every pod (rank fix)

Field report (playtest night, voice channel): everyone's HUD showed
them in 1st place.  Root cause: the engine's score-replication
machinery was complete -- Player::WriteUpdateRecord ships currentScore,
the replicant ReadUpdateRecord applies it, CalcRanking sums every
scoring player -- but nothing marked the player's update record dirty
on a score change (the engine only ForceUpdates at mission end for the
fade sync).  Every peer's replicant player therefore stayed at score 0
and each machine ranked its own (only-nonzero) player first.

Fix: ForceUpdate() after every score application in the BTPlayer
handlers (main, inflicted, and the severed-vehicle branch) -- the
existing update-record path does the rest.

Verified 2-node (force-damage kill scoring): the victim's machine ranks
the killer's REPLICANT at its replicated score (24) above its own
master (0); standings consistent on both nodes.  BT_RANK_LOG=1 kept as
the 1 Hz per-player ranking dump.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
arcattack
2026-07-23 09:39:19 -05:00
co-authored by Claude Opus 4.8
parent c8cba8c764
commit 12a1fea823
3 changed files with 44 additions and 0 deletions
+16
View File
@@ -468,6 +468,22 @@ phase). Plan: `~/.claude/plans/partitioned-snuggling-piglet.md`.
per-event cost) is an OPEN perf item -- deliberately not attempted pre-8-player-night.
Diags: `[loadphase]` markers always on; the full queue dump gates on `BT_LOAD_DIAG=1`;
`GeneralEventQueue::DumpBlockers` + the named fallback in `Event::DumpData`.
- **SCORE REPLICATION / "everyone sees themselves 1st" (2026-07-23, FIXED) [T2]**: voice-channel
field report from the first playtest night. The engine's machinery was COMPLETE -- `Player::
WriteUpdateRecord` ships `currentScore` (PLAYER.cpp:640), the replicant's ReadUpdateRecord
applies it (:620), `CalcRanking` sums every scoring player (:567) -- but NOTHING marked the
player's update record dirty on a score change (the engine only ForceUpdates at mission end
for the fade), so every peer's replicant player stayed at score 0 and each machine ranked its
own (only-nonzero) player first. FIX: `ForceUpdate()` after every score application in the
BTPlayer handlers (main + inflicted + the severed-vehicle branch). VERIFIED 2-node: the
victim's machine ranks the killer's REPLICANT at its replicated score (24) above its own
master (0) -- standings consistent on both nodes. Diagnostic kept: `BT_RANK_LOG=1` = 1 Hz
dump of every player CalcRanking sees (id/instance/score), engine PLAYER.cpp.
RELATED finding [T3]: in relay mode the pod-side console score FLUSH (the binary's
10s delta-to-console + zero, btplayer.cpp:822) appears NOT to fire (field totals grew
monotonically to 1000+), implying GetConsoleHost() is null mid-mission in relay mode --
accidentally GOOD (the RANK/SCORE gauges need the running total) but the console-score-push
channel is therefore dead; revisit if an operator live-standings feed is ever wanted.
- **CONNECT/DISCONNECT AUDIT (2026-07-23) [T2, every cell verified]**: systematic sweep of
actor-death x lifecycle-phase, requested by the operator after the WaitingForEgg zombie.
VERIFIED-HANDLED (previously): relay-not-up patient retry; seated-quit seat-free+reclaim;
+18
View File
@@ -553,6 +553,19 @@ int
{
int num_players = 0;
Player *active_player;
// BT_RANK_LOG (score-replication fix verification): 1 Hz dump of every
// scoring player this node ranks, with instance + score.
int rank_log_this_pass = 0;
if (getenv("BT_RANK_LOG"))
{
static unsigned long s_rankLogAt = 0;
unsigned long now_ms = GetTickCount();
if (now_ms - s_rankLogAt > 1000)
{
s_rankLogAt = now_ms;
rank_log_this_pass = 1;
}
}
//
//-----------------------------------------------------
// Iterate through the players add to the sorted chain
@@ -564,6 +577,11 @@ int
{
if(active_player->IsScoringPlayer())
{
if (rank_log_this_pass)
DEBUG_STREAM << "[rank] player " << active_player->GetEntityID()
<< " inst=" << (int)active_player->GetInstance()
<< " score=" << (float)active_player->currentScore
<< std::endl << std::flush;
player_rank.AddValue(active_player, active_player->currentScore);
++num_players;
//
+10
View File
@@ -528,6 +528,7 @@ void
//
currentScore +=
(MECH_TONNAGE(target_mech) / MECH_TONNAGE(our_mech)) * award; // (+0x4bc / +0x4bc), this+0x278
ForceUpdate(); // replicate the score (rank fix)
// MP MATCH FORENSICS (matchlog.hpp): inflicted-damage score (type=0),
// logged with the post-accumulate total.
@@ -741,9 +742,18 @@ void
if (playerVehicle == 0)
{
currentScore += message->scoreAward; // the base's award apply
ForceUpdate(); // replicate the score (rank fix)
return;
}
Player::ScoreMessageHandler(message); // FUN_0042da20
// SCORE REPLICATION (2026-07-23, the "everyone sees themselves 1st"
// field report): Player::WriteUpdateRecord SHIPS currentScore and the
// replicant's ReadUpdateRecord APPLIES it -- but nothing ever marked the
// player's record dirty on a score change (the engine only ForceUpdates
// at mission end), so every peer's replicant player stayed at 0 and
// CalcRanking put the local player first on every machine.
ForceUpdate();
}
//~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~