feat(progression): Coins, LevelTime, SkillLevel core invariants (slice 1)
three bug-encoding invariants, one red/green cycle each: - Coins.TrySubtract fails instead of clamping - LevelTime rejects zero (continued-run zero best-times) - SkillLevel has no zero (unowned is absence, not Level=1) plan: drop signed NetCoins; add Attempt outcome as idempotency guard
This commit is contained in:
17
progression.tests/CoinsTests.cs
Normal file
17
progression.tests/CoinsTests.cs
Normal file
@@ -0,0 +1,17 @@
|
||||
namespace Mr.BrickAdventures.Progression.Tests;
|
||||
|
||||
public class CoinsTests
|
||||
{
|
||||
[Fact]
|
||||
public void Subtracting_more_than_held_fails_and_leaves_the_amount_untouched()
|
||||
{
|
||||
var held = Coins.Of(10);
|
||||
|
||||
var succeeded = held.TrySubtract(Coins.Of(30), out var remaining);
|
||||
|
||||
Assert.False(succeeded);
|
||||
// Not clamped to zero. A caller that ignores the bool sees coins un-spent,
|
||||
// which is the safe outcome; clamping would silently consume what wasn't there.
|
||||
Assert.Equal(Coins.Of(10), remaining);
|
||||
}
|
||||
}
|
||||
13
progression.tests/LevelTimeTests.cs
Normal file
13
progression.tests/LevelTimeTests.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Mr.BrickAdventures.Progression.Tests;
|
||||
|
||||
public class LevelTimeTests
|
||||
{
|
||||
[Fact]
|
||||
public void Zero_is_not_a_completion_time()
|
||||
{
|
||||
// GetCurrentLevelTime() returns 0 whenever the speedrun timer was never started, which
|
||||
// today is every continued run — so zero-second "best times" get written to the ghost
|
||||
// files. Rejecting zero makes that unrepresentable rather than merely unlikely.
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => LevelTime.Of(0));
|
||||
}
|
||||
}
|
||||
13
progression.tests/SkillLevelTests.cs
Normal file
13
progression.tests/SkillLevelTests.cs
Normal file
@@ -0,0 +1,13 @@
|
||||
namespace Mr.BrickAdventures.Progression.Tests;
|
||||
|
||||
public class SkillLevelTests
|
||||
{
|
||||
[Fact]
|
||||
public void There_is_no_level_zero_because_unowned_is_absence()
|
||||
{
|
||||
// SkillData.Level defaults to 1 today, so "not owned" and "owned at level 1" are the same
|
||||
// value and nothing can tell them apart. A Playthrough records only the skills it owns, so
|
||||
// absence from that record is the only way to say "unowned" — level zero must not exist.
|
||||
Assert.Throws<ArgumentOutOfRangeException>(() => SkillLevel.Of(0));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user