Initial commit: Theriapolis baseline at port/godot branch point
Captures the pre-Godot-port state of the codebase. This is the rollback anchor for the Godot port (M0 of theriapolis-rpg-implementation-plan-godot-port.md). All Phase 0 through Phase 6.5 work is included; Phase 7 is in flight. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
namespace Theriapolis.Core.Persistence.SaveMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Single-step migration from one schema version to the next. Migrations
|
||||
/// chain — Migrations.MigrateUp finds a path from header.Version to
|
||||
/// C.SAVE_SCHEMA_VERSION and applies each step in order.
|
||||
/// </summary>
|
||||
public interface ISaveMigration
|
||||
{
|
||||
int FromVersion { get; }
|
||||
int ToVersion { get; }
|
||||
void Apply(SaveHeader header, SaveBody body);
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
namespace Theriapolis.Core.Persistence.SaveMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Registry + chain runner for SaveBody migrations. Phase 4 is the v4 baseline;
|
||||
/// older versions are not supported because nothing earlier shipped. As we
|
||||
/// bump the schema in Phase 5+, register migrations here.
|
||||
///
|
||||
/// The registry seeds itself with every built-in migration on first use, so
|
||||
/// callers don't need to remember to <see cref="Register"/> them at startup.
|
||||
/// </summary>
|
||||
public static class Migrations
|
||||
{
|
||||
private static readonly List<ISaveMigration> _registry = new();
|
||||
private static bool _seeded;
|
||||
|
||||
public static void Register(ISaveMigration m)
|
||||
{
|
||||
EnsureSeeded();
|
||||
_registry.Add(m);
|
||||
}
|
||||
|
||||
private static void EnsureSeeded()
|
||||
{
|
||||
if (_seeded) return;
|
||||
_seeded = true;
|
||||
// Built-in migrations registered in version order:
|
||||
_registry.Add(new V5ToV6Migration());
|
||||
_registry.Add(new V6ToV7Migration());
|
||||
_registry.Add(new V7ToV8Migration());
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Walk migrations from header.Version up to C.SAVE_SCHEMA_VERSION. Returns
|
||||
/// false if no chain exists; the caller decides whether to hard-block or
|
||||
/// best-effort the load (see SaveCodec docs).
|
||||
/// </summary>
|
||||
public static bool MigrateUp(SaveHeader header, SaveBody body)
|
||||
{
|
||||
EnsureSeeded();
|
||||
while (header.Version < C.SAVE_SCHEMA_VERSION)
|
||||
{
|
||||
var step = _registry.FirstOrDefault(m => m.FromVersion == header.Version);
|
||||
if (step is null) return false;
|
||||
step.Apply(header, body);
|
||||
header.Version = step.ToVersion;
|
||||
}
|
||||
return header.Version == C.SAVE_SCHEMA_VERSION;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Theriapolis.Core.Persistence.SaveMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Phase 6 M2 — additive migration from save schema v5 (Phase 5 ship) to
|
||||
/// v6 (Phase 6 reputation core). Non-destructive: every v5 field carries
|
||||
/// over unchanged. The new <see cref="SaveBody.ReputationState"/> is
|
||||
/// already initialised to a fresh empty <see cref="ReputationSnapshot"/>
|
||||
/// by the SaveBody constructor, so this migration just bumps the header
|
||||
/// version.
|
||||
///
|
||||
/// Phase 5's placeholder <see cref="SaveBody.Factions"/> and
|
||||
/// <see cref="SaveBody.Reputation"/> dictionaries were never populated
|
||||
/// (Phase 5 didn't ship the reputation system), so we don't need to
|
||||
/// translate any data — they stay empty and ignored.
|
||||
/// </summary>
|
||||
public sealed class V5ToV6Migration : ISaveMigration
|
||||
{
|
||||
public int FromVersion => 5;
|
||||
public int ToVersion => 6;
|
||||
|
||||
public void Apply(SaveHeader header, SaveBody body)
|
||||
{
|
||||
// Body fields all default-initialise to empty in SaveBody — the
|
||||
// ReputationState is already a fresh ReputationSnapshot. Phase-5
|
||||
// saves had nothing to translate, so this is a pure version bump.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,30 @@
|
||||
namespace Theriapolis.Core.Persistence.SaveMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Phase 6.5 M0 — additive migration from save schema v6 (Phase 6 ship) to
|
||||
/// v7 (Phase 6.5 levelling). Non-destructive: every v6 field carries over
|
||||
/// unchanged. The new <see cref="PlayerCharacterState.SubclassId"/>,
|
||||
/// <see cref="PlayerCharacterState.LearnedFeatureIds"/>, and
|
||||
/// <see cref="PlayerCharacterState.LevelUpHistory"/> default-initialise
|
||||
/// to empty values by the constructor, so this migration just bumps the
|
||||
/// header version.
|
||||
///
|
||||
/// Phase-6 saves had no level-up history (every character stayed at level
|
||||
/// 1, Xp = 0). On load they continue to be valid level-1 characters; the
|
||||
/// player can immediately start earning XP and levelling up under the new
|
||||
/// rules.
|
||||
/// </summary>
|
||||
public sealed class V6ToV7Migration : ISaveMigration
|
||||
{
|
||||
public int FromVersion => 6;
|
||||
public int ToVersion => 7;
|
||||
|
||||
public void Apply(SaveHeader header, SaveBody body)
|
||||
{
|
||||
// No data translation needed. PlayerCharacterState's new fields
|
||||
// (SubclassId, LearnedFeatureIds, LevelUpHistory) default-initialise
|
||||
// to empty in their record, and SaveCodec.ReadCharacter handles
|
||||
// missing-section bytes via the EOS-check pattern Phase 5 already
|
||||
// established. Pure version bump.
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
namespace Theriapolis.Core.Persistence.SaveMigrations;
|
||||
|
||||
/// <summary>
|
||||
/// Phase 7 M0 — additive migration from save schema v7 (Phase 6.5 ship) to
|
||||
/// v8 (Phase 7 dungeons). Non-destructive: every v7 field carries over
|
||||
/// unchanged. Phase 7 reserves three new save sections — anchors, building
|
||||
/// deltas, and per-PoI dungeon state — but ships M0 with the version bump
|
||||
/// only; the fields default-initialise to empty and a v7 save loads as
|
||||
/// "no anchors persisted, no buildings modified, no dungeons visited"
|
||||
/// which is the truth for any pre-Phase-7 save.
|
||||
///
|
||||
/// Subsequent Phase 7 milestones (M1+) will populate these sections;
|
||||
/// the migration stays additive throughout.
|
||||
/// </summary>
|
||||
public sealed class V7ToV8Migration : ISaveMigration
|
||||
{
|
||||
public int FromVersion => 7;
|
||||
public int ToVersion => 8;
|
||||
|
||||
public void Apply(SaveHeader header, SaveBody body)
|
||||
{
|
||||
// No data translation needed. Phase 7's new save sections
|
||||
// (TAG_ANCHORS / TAG_BUILDINGS / TAG_DUNGEONS) default to empty in
|
||||
// SaveBody and SaveCodec uses the established EOS-check pattern
|
||||
// for additive sections. Pure version bump.
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user