Skip to content

Resolve replicated game state through the replay name table - #288

Merged
colonelpanic8 merged 2 commits into
rlrml:masterfrom
wilbo:fix/game-state-name-index
Oct 2, 2026
Merged

colonelpanic8 merged 2 commits into
rlrml:masterfrom
wilbo:fix/game-state-name-index

Conversation

@wilbo

@wilbo wilbo commented Oct 2, 2026

Copy link
Copy Markdown
Contributor

Summary

ReplicatedStateName is an index into the replay's own name table, not a fixed enum. The same state gets a different number in each replay. GAME_STATE_KICKOFF_COUNTDOWN = 53 and GAME_STATE_GOAL_SCORED_REPLAY = 67 only match replays that happen to use that layout (53 = Countdown, 67 = PostGoalScored, as in most recent net-11 replays).

When index 67 is Active, LivePlayTracker classes all normal play as PostGoal. Live play then lasts about one second, and positioning, movement, boost and kickoff stats come out nearly empty. In one 3v3 ranked fixture, live play drops from 199s to 1.0s and movement tracked_time from 1236s to 6s.

I scanned about 45,000 ranked 1v1, 2v2 and 3v3 replays from Ballchasing, ranging from Bronze to high-level play. Index 67 is Active in roughly 2% of them. Almost all are 3v3 replays from before season 19, where up to nearly a quarter of the replays in some ranks are affected.

Change

  • canonical_game_state(name) maps the state name to a code: Countdown → GAME_STATE_KICKOFF_COUNTDOWN; PostGoalScored and ReplayPlayback → GAME_STATE_GOAL_SCORED_REPLAY; any other state → None.
  • ReplayProcessor::get_game_state() looks up the raw index in replay.names and maps it.
  • ProcessorView::get_game_state() is added with a default that returns the raw value, so the live view keeps its canonical codes.
  • FrameInput and the boost updater's kickoff check now use it. Every existing GAME_STATE_* comparison is unchanged.
  • get_replicated_state_name() still returns the raw value, so the ndarray and replay-data exports are unchanged.

Effect on unaffected replays

I compared StatsCollector output before and after on a few hundred of those replays. Two stretches no longer count as live play:

  • the pre-match Countdown frames before ReplicatedGameStateTimeRemaining starts replicating (about 1s);
  • the PostGoalScored frames before the score update arrives (about 1.5s per goal).

tracked_time drops by about 0.3%, and dependent aggregates move by 0.2–2%.

Tests

  • New tests/game_state_name_index_test.rs uses a 3v3 ranked Ballchasing replay where names[67] == "Active", added under assets/ and whitelisted in .gitignore. It fails on master (1.0s live play) and passes with this change.
  • cargo test -p subtr-actor -p subtr-actor-live, cargo fmt --check and cargo clippy --all-targets all pass.

Not in this PR: in js/player/src/player/adapter/SubtrActorPlayer.ts, the fallback in _detectFirstKickoffGoTime checks replicated_game_state_name === 54, which has the same problem. It only runs when no kickoff countdown is replicated. Fixing it means exposing the name table or a canonical state in the replay-data export, so it is left for a separate change.

🤖 Generated with Claude Code

wilbo and others added 2 commits October 2, 2026 07:59
ReplicatedStateName is an index into each replay's name table, so the
hardcoded 53/67 codes named different states per replay. Where index 67
was "Active", all play was classed as post-goal and almost nothing was
tracked.

Map the state name to the canonical GAME_STATE_* codes instead, and add
a regression fixture.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Replace the player's hardcoded `=== 54` Active fallback with the most
  common replicated state, since the value is a name-table index.
- Document that get_replicated_state_name is a raw name-table index and
  when ProcessorView implementors must override get_game_state.
- Make the GAME_STATE_* codes public so the trait docs reference
  reachable items.
- Test live-play classification against name strings across three
  name-table layouts, including one where Active sits at index 53
  (the old countdown code). All three fail on master.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@colonelpanic8
colonelpanic8 merged commit 7c78a58 into rlrml:master Oct 2, 2026
9 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants