Conversation
In versus a "match" is a whole campaign and almost never finishes (players leave
near the end), so player_versus_stats — which only rolled up at match close —
stayed empty in practice, and match_maps mostly sat 'incomplete'. This reworks
versus.sp's round/map lifecycle so completed chapters credit players immediately.
- Detect the two halves from the engine `round_start` event with a per-chapter
counter + idempotency guards, instead of the `versus_round_start`
is_secondary_round flag (which stays false on this build, so every half tried
to insert as round 1 and hit the match_rounds unique key). Round 2 is now
recorded and chapters close.
- Roll player_versus_stats up PER CHAPTER at chapter close, inside the round-2
transaction so the rollup SELECT sees both halves' player_round_stats
atomically. maps_won/maps_lost are the win/loss record; streaks count
consecutive chapters. Match close only touches matches_* (no double count).
- Fix the async race that silently dropped the FIRST chapter of every match:
open the first chapter from OnMatchInserted after g_MatchId is set, not inline
after the async OpenMatch.
- Chapter winner from plugin Survivor points (engine distance netprop reads 0 on
this build; engine-accurate winner is a documented future refinement). Flush
the open chapter + a dangling half on abandon/mode-change/finale so nothing is
lost; add a same-map re-entry guard.
- Make event hooks robust: HookEvent -> HookEventEx so a build missing an
optional event (e.g. entered_checkpoint) doesn't abort OnPluginStart.
- Guard GetAdminTopMenu() with LibraryExists("adminmenu") so startup doesn't
crash when the plugin loads before adminmenu (matchmode reload chain).
Verified: compiles clean on SM 1.12; loads Status=running on prod. Per-chapter
recording needs a live versus match to fully validate (empty-server can't
reproduce team flips) — deployed to the Dugout to bake.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01S6a1P8bjmnJ45TanqwQekA
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Versus stats now accrue per chapter, not per whole match
In versus a match is a whole campaign and almost never finishes — players
leave near the end. But
player_versus_stats(the per-player versus record aprofile/leaderboard reads) only rolled up at match close, so in practice it
stayed empty,
match_mapsmostly satincomplete, and everymatch_roundsrow was round 1 (round 2 never recorded). Diagnosed live: 85
match_rounds, 412player_round_statsrows, 0player_versus_statsrows.What changed
player_versus_statsnow rolls up at the close of eachchapter (a map's two halves — "play Survivor and Infected once"), which
reliably completes. It runs inside the round-2 DB transaction so the rollup sees
both halves'
player_round_statsatomically. A completed chapter credits everyplayer immediately, even if the match is later abandoned.
maps_won/maps_lostare the win/loss record (one per chapter); streaks count consecutive chapters.
Match close only touches
matches_*(no double count).versus_round_start/is_secondary_round(stays false here →Duplicate entry 'N-1') to counting the engineround_startwith idempotency guards.async race (
OpenMatchMap()ran whileg_MatchIdwas still 0); it's now openedfrom the
OnMatchInsertedcallback.reads 0 on this build — an engine-accurate winner is a documented follow-up).
HookEvent → HookEventEx(a build missing an optional eventlike
entered_checkpointno longer abortsOnPluginStart— this was blockingthe plugin from loading on the prod build), and
GetAdminTopMenu()guarded withLibraryExists("adminmenu").Verification
Status: runningon prod.semantics) — its findings (first-chapter race, engine-score index, round W/L
over-count, event-ordering) are addressed here.
recording needs a live versus match to fully validate — an empty server can't
reproduce team flips. Once a real match confirms
player_versus_stats/match_mapspopulate correctly, the compiled plugin will be vendored into thebizzymod-campaign monorepo PR and both merged.