feat(progression): remaining slice 1 value objects

Lives (floors at zero, exhaustion observable), LevelId, SkillId,
PlaythroughId (never self-generates, keeps tests deterministic),
StatName enum, LevelTime rejects NaN/infinity.

14 tests. Arithmetic/comparison members deferred to the slices whose
behaviour needs them rather than added speculatively.
This commit is contained in:
2026-07-28 18:27:26 +02:00
parent 313a5315b1
commit 9767941c65
12 changed files with 196 additions and 3 deletions

21
progression/LevelId.cs Normal file
View File

@@ -0,0 +1,21 @@
namespace Mr.BrickAdventures.Progression;
/// <summary>
/// Stable identity of a Level. Never a position in a list — Playthroughs and Best Times both
/// reference Levels, so ids must survive inserting and reordering.
/// </summary>
public readonly struct LevelId : IEquatable<LevelId>
{
public string Value { get; }
private LevelId(string value) => Value = value;
public static LevelId Of(string value) => string.IsNullOrWhiteSpace(value)
? throw new ArgumentException("A level id cannot be blank.", nameof(value))
: new LevelId(value);
public bool Equals(LevelId other) => string.Equals(Value, other.Value, StringComparison.Ordinal);
public override bool Equals(object? obj) => obj is LevelId other && Equals(other);
public override int GetHashCode() => StringComparer.Ordinal.GetHashCode(Value ?? string.Empty);
public override string ToString() => Value;
}