GameSessionController.withTimeline constructor
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();
}