feat(progression): use cases (slice 6)

ProgressionService holds the loaded Profile, active Playthrough and
active Attempt, so "exactly one active Attempt" has a testable home.

EnterLevel always announces LevelStarted - the reason ghost recording
and splits never worked on level 1 or after a Continue. Refuses
unreachable levels. Does not touch campaign position, so a Replay
leaves it alone.

SkillCatalogue returns null for a skill with no authored upgrades,
killing the Upgrades[0].Cost overrun. PurchaseSkill reads the current
level from the ledger first, so one Attempt can unlock then upgrade.
HandlePlayerDeath now persists - death was free if you quit.

SkillLevel.First/Next driven out here by a real caller. 53 tests.
This commit is contained in:
2026-07-28 19:10:06 +02:00
parent 003ec4b010
commit 330e10880c
8 changed files with 433 additions and 6 deletions

View File

@@ -0,0 +1,22 @@
namespace Mr.BrickAdventures.Progression;
/// <summary>
/// Why a Skill purchase did or did not happen. Named outcomes rather than a bool, because the
/// marketplace wants to say something different for each.
/// </summary>
public enum PurchaseOutcome
{
Purchased,
/// <summary>The Bank plus this Attempt's coins, less what it has already spent, is not enough.</summary>
NotAffordable,
/// <summary>The Skill is already at the top level its authored upgrade table defines.</summary>
AlreadyAtTopLevel,
/// <summary>The Skill has no authored upgrades, so it cannot be bought at any price.</summary>
NotPurchasable,
/// <summary>There is no Playthrough loaded, or no Attempt in progress to charge it to.</summary>
NotPlaying,
}