SeededChallengeService class

FEAT-97: ties together this kit's existing deterministic-replay primitives — SeededRandomService (RNG snapshot/resume), ReplayRecorder/ReplayCapsule (recorded event replay + divergence detection), and LocalScoreboardService (on-device ranking) — into one "same challenge, reproducible run, fully offline" building block, so a consuming game doesn't have to wire all 3 together itself and risk a seed/clock/replay mismatch doing it.

Deliberately a thin glue layer, not a reimplementation: every correctness property (RNG determinism, replay divergence detection, leaderboard ranking, clock-rewind resistance) is inherited unchanged from the primitive that already owns it — this class only derives the shared seed and scopes a LocalScoreboardService instance per (challengeId, periodKey) pair.

No backend/network: every piece of state here is either derived (the seed) or local-only (LocalScoreboardService's on-device table) — nothing in this class makes a network call or depends on one.

Constructors

SeededChallengeService({required String challengeId, required ChallengePeriod period, int scoreboardCapacity = 50})

Properties

challengeId → String
final
currentSeed → int
Deterministically derived from (periodKey, challengeId) — the SAME pair always yields the SAME seed, on any instance/process/restart (FNV-1a is a pure function of its input string, see utils/fnv1a.dart — no process-local state). Changes only when periodKey rolls over to a new period, or when a different challengeId is used.
no setter
hashCode → int
The hash code for this object.
no setterinherited
period → ChallengePeriod
final
periodKey → String
Clock-derived — see ChallengePeriod's doc. Always goes through todayEpochDayClamped, so winding the device clock BACK can never reopen a period that has already advanced past (same anti-cheat clamp every other time-gated system in this package relies on); it only ever "loses" real future time, never replays a past one.
no setter
runtimeType → Type
A representation of the runtime type of the object.
no setterinherited

Methods

endRun(ReplayRecorder recorder, {String appVersion = '1'}) → ReplayCapsule
Stops recorder and packages everything it captured since startRun into a ReplayCapsule — hand this to replay (locally, or after exporting it elsewhere) to verify the run reproduces deterministically.
noSuchMethod(Invocation invocation) → dynamic
Invoked when a nonexistent method or property is accessed.
inherited
replay(ReplayCapsule capsule, Object? handler(ReplayEvent event, SeededRandomService rng)) → SeededChallengeReplayResult
Replays capsule through handler (same contract as the free function replayCapsule) and reports whether the replay reproduced the original run's outcomes exactly — see ReplayDivergence for what counts as a mismatch and why handler must read randomness only through the SeededRandomService it's given.
startRun({ReplayRecorder? recorder}) → SeededRandomService
Starts a fresh run for the current period: a SeededRandomService seeded from currentSeed for the caller's own gameplay namespaces (e.g. rng.stream('gameplay')), optionally paired with recorder (if given, ReplayRecorder.started with the same seed) so the run can later be packaged into a ReplayCapsule via endRun.
submitScore(String playerLabel, int score) → void
Submits score for playerLabel into this challenge's current-period local scoreboard. See LocalScoreboardService.submitScore.
topScores(int n) → List<LeaderboardEntry>
This challenge's current-period top n scores. See LocalScoreboardService.topN.
toString() → String
A string representation of this object.
inherited

Operators

operator ==(Object other) → bool
The equality operator.
inherited