namespace Mr.BrickAdventures.Progression; /// /// Something that happened to player progression, raised by an aggregate as part of the operation /// that caused it. Plain records with no engine dependency — an adapter republishes them as Godot /// signals (ADR-0004), which is what makes "state changed ⇒ signal fired" un-bypassable. /// public abstract record ProgressionEvent; /// Raised when a Playthrough loses its last life. public sealed record RunEnded : ProgressionEvent; /// /// Raised the first time an Achievement is unlocked, and only then — so a subscriber that shows a /// popup or pushes to Steam cannot fire twice however often it is told. /// public sealed record AchievementUnlocked(AchievementId Achievement) : ProgressionEvent; /// /// Raised whenever a Level is entered — every time, including the first Level of a new Playthrough /// and the first Level after resuming one. Ghost recording and the speedrun timer both start from /// this, so an entry path that does not announce is an entry path where neither works. /// public sealed record LevelStarted(LevelId Level) : ProgressionEvent; /// /// Lives remaining changed. Raised by the same operation that changed them, so no reader can observe /// a stale value between the write and the announcement. /// public sealed record LivesChanged(Lives Remaining) : ProgressionEvent; /// /// Spendable coins changed — the Bank plus the current Attempt's takings, less what it has spent. /// A single fact for readers, so nothing has to know the Attempt and Bank are separate. /// public sealed record TotalCoinsChanged(Coins Total) : ProgressionEvent; /// /// The set of owned Skills, or the level of one of them, changed. What SkillManager reconciles /// its live components against, instead of being told separately by every caller. /// public sealed record SkillSetChanged : ProgressionEvent;