reads for the UI: TotalCoins, Lives, IsRunOver, CurrentLevel, LevelOf/IsOwned/CanAfford, HasCleared. The read half of candidate 01 - callers get a narrow surface instead of reaching into state. new domain events LivesChanged / TotalCoinsChanged / SkillSetChanged, announced by comparing against the last announced value, so an operation can neither forget to announce nor announce something unchanged. HandlePlayerDeath now clears the spent attempt - it was still counting towards TotalCoins after its ledger had been discarded. republisher and EventBus extended; SkillSetChanged is what SkillManager will reconcile against instead of the non-atomic call pairs. 65 tests.
43 lines
2.0 KiB
C#
43 lines
2.0 KiB
C#
namespace Mr.BrickAdventures.Progression;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public abstract record ProgressionEvent;
|
|
|
|
/// <summary>Raised when a Playthrough loses its last life.</summary>
|
|
public sealed record RunEnded : ProgressionEvent;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed record AchievementUnlocked(AchievementId Achievement) : ProgressionEvent;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed record LevelStarted(LevelId Level) : ProgressionEvent;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed record LivesChanged(Lives Remaining) : ProgressionEvent;
|
|
|
|
/// <summary>
|
|
/// 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.
|
|
/// </summary>
|
|
public sealed record TotalCoinsChanged(Coins Total) : ProgressionEvent;
|
|
|
|
/// <summary>
|
|
/// The set of owned Skills, or the level of one of them, changed. What <c>SkillManager</c> reconciles
|
|
/// its live components against, instead of being told separately by every caller.
|
|
/// </summary>
|
|
public sealed record SkillSetChanged : ProgressionEvent;
|