8e90574437
Promotes the replay feature from disk-only to a real in-engine playback path. A new ReplayPlaybackState resource models a three- state machine (Inactive / Playing / Completed); start_replay_playback resets the live game to the recorded deal via GameState::new_with_mode(seed, draw_mode, mode) and a tick system fires the canonical MoveRequestEvent / DrawRequestEvent for each recorded move at REPLAY_MOVE_INTERVAL_SECS (0.45s). The reset path bypasses NewGameRequestEvent because the existing event always sources draw_mode from Settings — a Draw-1 replay would silently coerce to Draw-3 (or vice versa) on a player whose preference doesn't match the recording. Inserting GameStateResource directly applies the recording's exact draw_mode and sidesteps the abandon-current-game confirmation modal that would otherwise block playback. Recording suppression during playback is non-invasive: a sibling system snapshots RecordingReplay's length on entry to playback and truncates the buffer back to that mark every frame while is_playing or is_completed. game_plugin's recording append paths are untouched. Completion lingers for REPLAY_COMPLETION_LINGER_SECS (5s) so the overlay can show "Replay complete" before the auto-clear flips state to Inactive. Six new tests cover the state transitions, tick cadence, canonical event firing, completion, stop-clears-state, and the recording-suppression contract. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>