replayFrames function
The full ordered frame history of a finished game.
Read from the replica when it holds all of it, which it always does for a game played on this device and does for an online game opened before (up to the replica's replay budget), so a replay opens offline. Otherwise fetched once and stored, since a finished game's history never changes.
A participant receives their own seat's projection; a non-participant replaying a public game receives the observer projection - the shape is identical either way, so the replay UI does not branch on it. The same range endpoint backs live gap recovery; replay is just the whole range rather than a missing slice of it.
Implementation
@riverpod
Future<List<Frame>> replayFrames(Ref ref, {required String gameId}) async {
final replica = await ref.watch(accountReplicaProvider.future);
final held = await replica?.replayFrames(gameId);
if (held != null) return held;
final frames = await ref.watch(gameRepositoryProvider).getFrames(gameId);
await replica?.applyReplay(gameId, frames);
return frames;
}