diff --git a/context/multiplayer.md b/context/multiplayer.md index c59a501..94ad51e 100644 --- a/context/multiplayer.md +++ b/context/multiplayer.md @@ -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; diff --git a/engine/MUNGA/PLAYER.cpp b/engine/MUNGA/PLAYER.cpp index 73bf11a..4ca2c72 100644 --- a/engine/MUNGA/PLAYER.cpp +++ b/engine/MUNGA/PLAYER.cpp @@ -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; // diff --git a/game/reconstructed/btplayer.cpp b/game/reconstructed/btplayer.cpp index 5d6b67f..4d3a4c5 100644 --- a/game/reconstructed/btplayer.cpp +++ b/game/reconstructed/btplayer.cpp @@ -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(); } //~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~