Persist run generation seed in typed state

This commit is contained in:
Meta Vens 2026-06-14 15:12:38 +01:00
commit 9ca3de2ed6
12 changed files with 53 additions and 13 deletions

Binary file not shown.

View file

@ -286,6 +286,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
ExitZoneIndex = 1,
CurrentZoneIndex = 0,
CurrentZoneEntityIndex = 0,
GenerationSeed = 424242,
DiscoveredZoneIndices = [0],
ZoneKeys = [zoneKey.ToString()],
ActionBarBindings =
@ -441,6 +442,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
}
if (runState?.RunId != "smoke" ||
runState.GenerationSeed != 424242 ||
runState.ZoneKeys.Length != 1 ||
runState.ActionBarBindings.Length != 1)
{

View file

@ -216,6 +216,7 @@ try
new AetheriaRuntimeRunCheckpointCommit
{
RunId = "smoke-run",
GenerationSeed = 424242,
CurrentZoneIndex = 0,
CurrentZoneEntityIndex = 0,
DiscoveredZoneIndices = new[] { 0 },
@ -504,6 +505,7 @@ try
loadout.RootEntity.CargoContents.Length == 1 &&
loadout.RootEntity.CargoContents[0].Items[0].Item.Quantity == 5 &&
run?.ZoneKeys.Length == 1 &&
run.GenerationSeed == 424242 &&
run.ActionBarBindings.Length == 2 &&
run.ActionBarBindings[0].ControlPath == "<Keyboard>/1" &&
run.ActionBarBindings[0].Kind == "weapon_group" &&
@ -593,6 +595,7 @@ try
packageLoadouts[0].RootEntity.CargoContents[0].Items[0].Item.Quantity != 5 ||
packageRuns.Count != 1 ||
packageRuns[0].RunId != "smoke-run" ||
packageRuns[0].GenerationSeed != 424242 ||
packageRuns[0].ZoneKeys.Count != 1 ||
packageRuns[0].ActionBarBindings.Count != 2 ||
packageRuns[0].ActionBarBindings[1].TargetKey != "aetheria.item_definition:legacy:smoke:weapon" ||

View file

@ -227,7 +227,8 @@ public static class AetheriaRuntimeCommitLogApplier
DiscoveredZoneIndices = ToIntArray(checkpoint.DiscoveredZoneIndices),
ActionBarBindings = ToActionBarBindings(checkpoint.ActionBarBindings),
FactionRelationships = ToFactionRelationships(checkpoint.FactionRelationships),
UpdatedAtUtc = updatedAtUtc
UpdatedAtUtc = updatedAtUtc,
GenerationSeed = checkpoint.GenerationSeed
};
var zoneKeys = new List<string>();

View file

@ -225,6 +225,9 @@ public sealed class AetheriaRunState
[Key(10)]
public string UpdatedAtUtc { get; set; } = "";
[Key(11)]
public uint GenerationSeed { get; set; }
}
[CultDocument("aetheria.zone_state", "aetheria.zone_state.v1")]

View file

@ -538,6 +538,7 @@ public class ActionGameManager : MonoBehaviour
ExitZoneIndex = ZoneIndex(CurrentGalaxy?.Exit),
CurrentZoneIndex = ZoneIndex(Zone?.GalaxyZone),
CurrentZoneEntityIndex = Zone?.Entities?.IndexOf(CurrentEntity) ?? -1,
GenerationSeed = CurrentGalaxy?.GenerationSeed ?? 0,
DiscoveredZoneIndices = CurrentGalaxy?.DiscoveredZones
.Select(ZoneIndex)
.Where(index => index >= 0)

View file

@ -26,6 +26,7 @@ public class Galaxy
public GalaxyZone[] Zones { get; }
public GalaxyZone Entrance { get; }
public GalaxyZone Exit { get; }
public uint GenerationSeed { get; }
public Dictionary<Faction, FactionRelationship> FactionRelationships { get; } = new Dictionary<Faction, FactionRelationship>();
private Action<string> Log { get; }
public bool IsPrelude { get; }
@ -60,7 +61,8 @@ public class Galaxy
IsPrelude = false;
Background = background;
Log = log;
var random = new Random(seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed);
GenerationSeed = seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed;
var random = new Random(GenerationSeed);
Factions = _allFactions.OrderBy(x => random.NextFloat()).Take(settings.MegaCount).ToArray();
foreach (var f in Factions) FactionRelationships[f] = FactionRelationship.Neutral;
@ -117,7 +119,8 @@ public class Galaxy
Background = background;
Log = log;
var random = new Random(seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed);
GenerationSeed = seed == 0 ? (uint) (DateTime.Now.Ticks % uint.MaxValue) : seed;
var random = new Random(GenerationSeed);
var factions = new List<Faction>();

View file

@ -107,6 +107,7 @@ public class MainMenu : MonoBehaviour
{
Settings.SectorBackgroundSettings.NoisePosition = Random.value * 1000;
ActionGameManager.IsTutorial = false;
var generationSeed = NextGenerationSeed();
Task.Run(() =>
{
var sector = new Galaxy(
@ -115,7 +116,8 @@ public class MainMenu : MonoBehaviour
Settings.NameGeneratorSettings,
ActionGameManager.RuntimeCatalog,
Debug.Log,
setState);
setState,
generationSeed);
Observable.NextFrame().Subscribe(_ =>
{
ActionGameManager.CurrentGalaxy = sector;
@ -133,6 +135,7 @@ public class MainMenu : MonoBehaviour
} while (Settings.TutorialBackgroundSettings.CloudDensity(float2(0.5f)) < .5f);
ActionGameManager.IsTutorial = true;
var generationSeed = NextGenerationSeed();
Task.Run(() =>
{
var sector = new Galaxy(
@ -143,7 +146,8 @@ public class MainMenu : MonoBehaviour
ActionGameManager.RuntimePlayerSettings,
ActionGameManager.GameDataDirectory.CreateSubdirectory("Narrative"),
Debug.Log,
setState);
setState,
generationSeed);
Observable.NextFrame().Subscribe(_ =>
{
ActionGameManager.CurrentGalaxy = sector;
@ -161,6 +165,12 @@ public class MainMenu : MonoBehaviour
_nextMenu.panel.AddButton("Quit", Application.Quit);
}
private static uint NextGenerationSeed()
{
var seed = (uint)Random.Range(1, int.MaxValue);
return seed == 0 ? 1u : seed;
}
private void ShowSettings()
{
_nextMenu.panel.Clear();

View file

@ -687,7 +687,8 @@ namespace GameCult.Aetheria.State.Unity
IReadOnlyList<string> zoneKeys,
IReadOnlyList<AetheriaRuntimeActionBarBindingSnapshot> actionBarBindings,
IReadOnlyList<AetheriaRuntimeFactionRelationshipSnapshot> factionRelationships,
string updatedAtUtc)
string updatedAtUtc,
uint generationSeed)
{
RunId = runId;
IsTutorial = isTutorial;
@ -700,6 +701,7 @@ namespace GameCult.Aetheria.State.Unity
ActionBarBindings = actionBarBindings;
FactionRelationships = factionRelationships;
UpdatedAtUtc = updatedAtUtc;
GenerationSeed = generationSeed;
}
public string RunId { get; }
@ -713,6 +715,7 @@ namespace GameCult.Aetheria.State.Unity
public IReadOnlyList<AetheriaRuntimeActionBarBindingSnapshot> ActionBarBindings { get; }
public IReadOnlyList<AetheriaRuntimeFactionRelationshipSnapshot> FactionRelationships { get; }
public string UpdatedAtUtc { get; }
public uint GenerationSeed { get; }
}
public sealed class AetheriaRuntimeZoneStateSnapshot

View file

@ -462,7 +462,8 @@ namespace GameCult.Aetheria.State.Unity
var actionBarBindings = ReadFieldActionBarBindings(ref reader, fields, 8);
var factionRelationships = ReadFieldFactionRelationships(ref reader, fields, 9);
var updatedAtUtc = ReadFieldString(ref reader, fields, 10);
SkipRemaining(ref reader, fields, 11);
var generationSeed = ReadFieldUInt32(ref reader, fields, 11);
SkipRemaining(ref reader, fields, 12);
return new AetheriaRuntimeRunStateSnapshot(
runId,
isTutorial,
@ -474,7 +475,8 @@ namespace GameCult.Aetheria.State.Unity
zoneKeys,
actionBarBindings,
factionRelationships,
updatedAtUtc);
updatedAtUtc,
generationSeed);
}
private static AetheriaRuntimeZoneStateSnapshot ReadZoneStatePayload(byte[] payload)

View file

@ -238,6 +238,9 @@ namespace GameCult.Aetheria.State.Unity
[Key(9)]
public IReadOnlyList<AetheriaRuntimeFactionRelationshipCommit> FactionRelationships { get; set; } = Array.Empty<AetheriaRuntimeFactionRelationshipCommit>();
[Key(10)]
public uint GenerationSeed { get; set; }
}
[MessagePackObject]

View file

@ -40,9 +40,9 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
`AetheriaStateNode`. The embedded Unity state package can also read typed
run, zone, and entity snapshot documents back from the `.cc` store as
read-only runtime DTOs, including action-bar bindings, faction
relationships, zone entity/orbit/body references, entity session scalars,
active consumables, behavior progress, weapon state, behavior state, and
stat-grid rows. Local run saves, loadouts, player settings files, zone
relationships, generation seed, zone entity/orbit/body references, entity
session scalars, active consumables, behavior progress, weapon state,
behavior state, and stat-grid rows. Local run saves, loadouts, player settings files, zone
files, and generated keyboard layout caches no longer write bespoke durable files. The
legacy
`RuntimePlayerSettings` runtime object is no longer named or decorated as a
@ -122,8 +122,8 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
- `Aetheria.State` now exposes typed node put/get ports for run state, zone
state, and entity snapshots. The state smoke writes a run referencing a zone,
a zone referencing an entity snapshot, and an entity snapshot carrying
position, direction, faction, hull, equipment slots, weapon groups, and a stat
grid. This proves the `.zone` replacement graph is durable typed state. Unity
generation seed, position, direction, faction, hull, equipment slots, weapon
groups, and a stat grid. This proves the `.zone` replacement graph is durable typed state. Unity
now queues current-zone/current-entity-collection snapshots, current
action-bar bindings, and current faction relationship rows through the
runtime commit log during run checkpoints; `RuntimeZoneBlueprint` and
@ -136,6 +136,12 @@ legacy catalog cache, and legacy UI paths should be migration-only or deleted.
Runtime blueprints no longer capture or restore behavior-private
`PersistentBehaviorData` blobs; that dead save-shape hook and its
`IPersistentBehavior` interface are deleted.
- Galaxy generation now exposes the actual nonzero seed used to create topology.
The main menu passes explicit seeds into normal and tutorial galaxy
construction, and run checkpoints persist that seed into `AetheriaRunState`.
Current-zone snapshot restore must attach to a regenerated galaxy with the
same seed; restoring zone indices against a fresh random galaxy is forbidden
split-brain state, not a Continue feature.
- Run checkpoint zone snapshots include typed orbit and body rows. Orbit IDs,
parent IDs, distance, phase, fixed positions, body kind/name/orbit,
resources, gravity/body multipliers, asteroid belt entries, and gas/sun
@ -791,6 +797,9 @@ First Aetheria surfaces to publish:
entity snapshot documents, so the `.zone` replacement graph is not only
writable through the state node but also inspectable by runtime package
consumers.
- Done: persist the actual galaxy generation seed in typed run state, so
future Continue/restore work has a reproducible topology body before
applying current-zone/entity snapshots.
- Remaining: add typed documents/mappers for runtime object graphs,
typed behavior factory construction, remaining behavior-private state not
covered by progress, weapon, sensor, radiator, reactor, or capacitor rows,