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:
@@ -81,12 +81,16 @@ public sealed class Playthrough
|
||||
/// if it is a Replay. Decides which from the Attempt's own Level, so a Replay cannot advance
|
||||
/// campaign position (ADR-0003). Does nothing if the Attempt has already ended.
|
||||
/// </summary>
|
||||
public void SettleCompletion(Attempt attempt, Campaign campaign)
|
||||
/// <returns>
|
||||
/// True when this was a First Clear and the ledger was committed. False for a Replay, or when the
|
||||
/// Attempt had already ended — in both cases nothing here changed.
|
||||
/// </returns>
|
||||
public bool SettleCompletion(Attempt attempt, Campaign campaign)
|
||||
{
|
||||
var isFirstClear = !HasCleared(attempt.Level);
|
||||
|
||||
if (!attempt.TryEndInCompletion()) return;
|
||||
if (!isFirstClear) return;
|
||||
if (!attempt.TryEndInCompletion()) return false;
|
||||
if (!isFirstClear) return false;
|
||||
|
||||
if (!Bank.Add(attempt.CoinsFound).TrySubtract(attempt.CoinsSpent, out var settled))
|
||||
throw new InvalidOperationException(
|
||||
@@ -101,20 +105,25 @@ public sealed class Playthrough
|
||||
|
||||
if (campaign.Next(attempt.Level) is { } next)
|
||||
CurrentLevel = next;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Ends the given Attempt in death and charges one life for it. Does nothing if the Attempt has
|
||||
/// already ended, so a repeated death event costs no further lives.
|
||||
/// </summary>
|
||||
public void SettleDeath(Attempt attempt)
|
||||
/// <returns>False if the Attempt had already ended, in which case nothing changed.</returns>
|
||||
public bool SettleDeath(Attempt attempt)
|
||||
{
|
||||
if (!attempt.TryEndInDeath()) return;
|
||||
if (!attempt.TryEndInDeath()) return false;
|
||||
|
||||
Lives = Lives.Decrement();
|
||||
|
||||
if (Lives.IsExhausted)
|
||||
_events.Raise(new RunEnded());
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
|
||||
Reference in New Issue
Block a user