feat(progression): snapshots, ports, in-memory adapters (slice 5)

flat snapshot record per aggregate - separate files mean saving a run
doesn't rewrite the Profile, deleting a slot is deleting a file, and a
corrupt slot doesn't cost achievements or best times.

snapshots hold primitives so Restore re-runs the value-object factories;
a serialiser building LevelId directly would bypass every guard.

LoadResult has three outcomes: Absent != Corrupt. Collapsing them is the
bug where a version bump made ContinueGame overwrite the save.

Profile.Restore writes collections directly - replaying UnlockAchievement
would re-fire popups and Steam pushes on every load. 44 tests.
This commit is contained in:
2026-07-28 19:02:41 +02:00
parent 05149bb4a9
commit 003ec4b010
13 changed files with 378 additions and 0 deletions

View File

@@ -0,0 +1,14 @@
namespace Mr.BrickAdventures.Progression;
/// <summary>
/// A Profile's state as plain data, for crossing the persistence seam. Primitives only, for the same
/// reasons as <see cref="PlaythroughSnapshot"/>.
/// </summary>
/// <remarks>
/// Statistics are keyed by <see cref="StatName"/>'s name rather than its numeric value, so
/// reordering the enum cannot silently reassign a player's history.
/// </remarks>
public sealed record ProfileSnapshot(
IReadOnlyDictionary<string, double> BestTimes,
IReadOnlyList<string> Achievements,
IReadOnlyDictionary<string, int> Statistics);