GameSessionController.withTimeline constructor

GameSessionController.withTimeline({
  1. RoyLifecycleCoordinator? lifecycle,
  2. String hookName = 'game-session',
  3. int timelineCapacity = _defaultTimelineCapacity,
  4. Set<String> allowedMetadataKeys = const {},
  5. Stopwatch createStopwatch()?,
})

ENH-96: adds a bounded, privacy-sanitized outcome timeline on top of events — a consumer that doesn't need per-run causal history (pause reasons, win/lose metadata, monotonic offsets) keeps using the default constructor/withHookName unchanged; events itself is untouched by this feature either way (every constructor keeps appending to it exactly as before).

timelineCapacity must be > 0 (ENH-85 runtime-validated constructor invariant — a value sourced from remote config could otherwise sail through as 0/negative and corrupt the ring buffer silently). allowedMetadataKeys is a default-DENY allowlist: a key passed to winWithMetadata/loseWithMetadata is kept in the recorded/exported timeline ONLY if it's in this set AND not in ReproductionCapsule.defaultRedactedKeys (the blacklist always wins, even over an explicit allowlist entry — see that doc). createStopwatch exists purely so tests can inject a fake, deterministic clock; a real app never needs to pass it.

Implementation

GameSessionController.withTimeline({
  this.lifecycle,
  this.hookName = 'game-session',
  this.timelineCapacity = _defaultTimelineCapacity,
  this.allowedMetadataKeys = const {},
  Stopwatch Function()? createStopwatch,
}) : _createStopwatch = createStopwatch ?? Stopwatch.new {
  if (hookName.isEmpty) {
    throw ArgumentError.value(hookName, 'hookName', 'must not be empty');
  }
  if (timelineCapacity <= 0) {
    throw ArgumentError.value(
      timelineCapacity,
      'timelineCapacity',
      'must be > 0',
    );
  }
  _warnIfLifecycleMissing();
  _initTimeline();
}