Rename runtime state documents

This commit is contained in:
Meta Vens 2026-06-13 06:27:59 +01:00
commit d3f07df8cc
6 changed files with 34 additions and 37 deletions

View file

@ -85,7 +85,7 @@ await using (var node = await AetheriaStateNode.OpenAsync(statePath, "aetheria-s
SampleNames = ["Ada", "Grace"]
});
await node.PutSavedRunAsync(runKey, new AetheriaSavedRun
await node.PutRunStateAsync(runKey, new AetheriaRunState
{
RunId = "smoke",
IsTutorial = false,
@ -122,7 +122,7 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
var nameFile = await reopened.GetNameFileAsync(nameFileKey);
var quarantine = await reopened.GetLegacyCatalogQuarantineAsync();
var playerSettings = await reopened.GetPlayerSettingsAsync();
var savedRun = await reopened.GetSavedRunAsync(runKey);
var runState = await reopened.GetRunStateAsync(runKey);
if (world?.WorldId != "aetheria")
{
@ -154,9 +154,9 @@ await using (var reopened = await AetheriaStateNode.OpenAsync(statePath, "aether
throw new InvalidOperationException("Player settings did not survive flush/reopen.");
}
if (savedRun?.RunId != "smoke" || savedRun.ActionBarBindings.Length != 1)
if (runState?.RunId != "smoke" || runState.ActionBarBindings.Length != 1)
{
throw new InvalidOperationException("Saved run did not survive flush/reopen.");
throw new InvalidOperationException("Run state did not survive flush/reopen.");
}
}

View file

@ -33,8 +33,8 @@ public static class AetheriaDocumentRegistry
CultNetDocumentBinding.ForDocument<AetheriaMigrationLedger>(registry),
CultNetDocumentBinding.ForDocument<AetheriaLegacyCatalogQuarantine>(registry),
CultNetDocumentBinding.ForDocument<AetheriaPlayerSettings>(registry),
CultNetDocumentBinding.ForDocument<AetheriaSavedRun>(registry),
CultNetDocumentBinding.ForDocument<AetheriaSavedZone>(registry),
CultNetDocumentBinding.ForDocument<AetheriaRunState>(registry),
CultNetDocumentBinding.ForDocument<AetheriaZoneState>(registry),
CultNetDocumentBinding.ForDocument<AetheriaEntitySnapshot>(registry)
});
}
@ -50,8 +50,8 @@ public static class AetheriaDocumentRegistry
typeof(AetheriaMigrationLedger),
typeof(AetheriaLegacyCatalogQuarantine),
typeof(AetheriaPlayerSettings),
typeof(AetheriaSavedRun),
typeof(AetheriaSavedZone),
typeof(AetheriaRunState),
typeof(AetheriaZoneState),
typeof(AetheriaEntitySnapshot)
];
}

View file

@ -130,14 +130,14 @@ public sealed class AetheriaStateNode : IAsyncDisposable, IDisposable
return Database.GetAsync<AetheriaPlayerSettings>(new CultRecordKey("global:aetheria.player_settings.v1"));
}
public Task<CultRecordHandle<AetheriaSavedRun>> PutSavedRunAsync(CultRecordKey key, AetheriaSavedRun run)
public Task<CultRecordHandle<AetheriaRunState>> PutRunStateAsync(CultRecordKey key, AetheriaRunState run)
{
return Database.PutAsync(key, run);
}
public Task<AetheriaSavedRun?> GetSavedRunAsync(CultRecordKey key)
public Task<AetheriaRunState?> GetRunStateAsync(CultRecordKey key)
{
return Database.GetAsync<AetheriaSavedRun>(key);
return Database.GetAsync<AetheriaRunState>(key);
}
public Task FlushAsync(bool soft = false)

View file

@ -13,16 +13,16 @@ public sealed class AetheriaPlayerSettings
public string Name { get; set; } = "Aetheria player settings";
[Key(1)]
[CultReference(typeof(AetheriaSavedRun))]
[CultReference(typeof(AetheriaRunState))]
public string ActiveRunKey { get; set; } = "";
[Key(2)]
public string LastUpdatedAtUtc { get; set; } = "";
}
[CultDocument("aetheria.saved_run", "aetheria.saved_run.v1")]
[CultDocument("aetheria.run_state", "aetheria.run_state.v1")]
[MessagePackObject]
public sealed class AetheriaSavedRun
public sealed class AetheriaRunState
{
[Key(0)]
[CultName]
@ -47,7 +47,7 @@ public sealed class AetheriaSavedRun
public int[] DiscoveredZoneIndices { get; set; } = [];
[Key(7)]
[CultReference(typeof(AetheriaSavedZone), many: true)]
[CultReference(typeof(AetheriaZoneState), many: true)]
public string[] ZoneKeys { get; set; } = [];
[Key(8)]
@ -60,9 +60,9 @@ public sealed class AetheriaSavedRun
public string UpdatedAtUtc { get; set; } = "";
}
[CultDocument("aetheria.saved_zone", "aetheria.saved_zone.v1")]
[CultDocument("aetheria.zone_state", "aetheria.zone_state.v1")]
[MessagePackObject]
public sealed class AetheriaSavedZone
public sealed class AetheriaZoneState
{
[Key(0)]
[CultName]

View file

@ -9,10 +9,11 @@ tree, RethinkDB, JsonKnownTypes, or Newtonsoft.Json.
The first live state file is `GameData/aetheria-world.cc`. Legacy files such as
`GameData/AetherDB.msgpack` are migration inputs only.
The typed save model is split into player settings, saved runs, saved zones,
entity snapshots, item slots, weapon groups, action-bar bindings, and stat
grids. Do not preserve `SavedGame` or `EntityPack` as opaque payloads in the
new store; they are source shapes for migration, not portable state authority.
The typed runtime state model is split into player settings, run state, zone
state, entity snapshots, item slots, weapon groups, action-bar bindings, and
stat grids. Do not preserve `SavedGame` or `EntityPack` as opaque payloads in
the new store; they are source shapes for migration, not portable state
authority.
The Unity client no longer writes `PlayerSettings.msgpack`, `.loadout`, or
`.zone` files. Until `Aetheria.State` is available to Unity as a runtime Verse
@ -21,8 +22,8 @@ disabled. That is intentional: the missing runtime package is the owner gap, and
the old bespoke file formats must not keep acting as durable truth while the
state spine is being rebuilt.
The old `SavedGame`/`SavedZone` DTOs and `Galaxy` loader constructor have also
been deleted; the only save model left is the typed `AetheriaSavedRun` document
family in this project.
been deleted; the new document family is live Verse run/zone state, not a
bespoke save-file format.
Keyboard layout rendering now reads the checked-in Unity text asset directly.
The old generated `GameData/KeyboardLayouts/*.msgpack` cache was deleted; layout

View file

@ -32,8 +32,8 @@ If you want to chat, please join [our Discord server](https://discord.gg/trbteNj
5. [Contributing](#Contributing)
- [Getting the Files](#Getting-the-Files)
- [Choosing a Task](#Choosing-a-Task)
- [Database Editor Tools](#Database-Editor-Tools)
- [Connecting to RethinkDB](#Connecting-to-RethinkDB)
- [Typed State Tools](#Typed-State-Tools)
- [Importing Legacy Catalog Data](#Importing-Legacy-Catalog-Data)
- [Editing Items](#Editing-Items)
- [Testing Locally](#Testing-Locally)
- [Debug Console](#Debug-Console)
@ -68,9 +68,7 @@ There are two solutions in this repository. One is a Unity project containing th
### Third Party Libraries
Client-Server communication is implemented using [LiteNetLib](https://github.com/RevenantX/LiteNetLib), a semi-reliable UDP transport library which we use to transmit [MessagePack](https://github.com/neuecc/MessagePack-CSharp) over the wire.
Aetheria uses [RethinkDB](https://rethinkdb.com/) for data persistence. To make this possible, all persistent data is marked with attributes for both MessagePack and [JSON.Net](https://www.newtonsoft.com/json) serialization. During operation, the client does not communicate with the database server directly, only the game server does that; the game server caches data relevant to the game and sends it to the clients.
Aetheria's new persistence spine is `Aetheria.State`: typed CultCache `.cc` documents exposed through CultNet/CultMesh. Legacy MessagePack catalog files are migration inputs only; they are fingerprinted and mapped into typed item, faction, and name-file documents before runtime systems are allowed to treat them as state.
### Programming Paradigms
@ -78,7 +76,7 @@ The codebase makes heavy use of C#'s [Language Integrated Queries (LINQ)](https:
### Data Structures
All of the persistent state classes inherit from the DatabaseEntry class, which uses a GUID as each entry's primary key. Whenever a reference to a database entry must be held, it should be stored as a GUID and when needed, retrieved directly from the DatabaseCache held by the appropriate manager, usually ItemManager for references to ItemData. The ItemManager contains GetData helper methods for retrieving the ItemData for the various subclasses of ItemInstance.
Persistent state belongs in typed CultCache documents with explicit record keys and CultNet schema bindings. Legacy `DatabaseEntry` identities may appear as migration provenance while the Unity runtime is being lowered onto the new state spine, but they are not the durable state owner.
#### Equipment
@ -114,23 +112,21 @@ We are organizing according to an [Agile development](https://en.wikipedia.org/w
You don't have to be a programmer to contribute, either! We have issue labels for and very much welcome contributions from [writers](https://github.com/rwvens/Aetheria-Economy/labels/worldbuilding) and [game designers](https://github.com/rwvens/Aetheria-Economy/labels/game%20design).
### Database Editor Tools
### Typed State Tools
In order to facilitate the creation and maintenance of game data, there is a Unity editor utility which communicates directly with RethinkDB. You can access the tools by selecting Window/Aetheria Database Tools in Unity's menu. This will cause two windows to appear, the Database List View and the Database Inspector.
The state spine lives in `Aetheria.State`. It defines CultCache documents, CultNet schema bindings, an embedded state node, and smoke coverage for writing, flushing, reopening, and reading `GameData/aetheria-world.cc`.
#### Connecting to RethinkDB
#### Importing Legacy Catalog Data
At the top of the list view there is a text field where you can enter the URL of the database server. When you click connect, the editor will download and cache all of the items in the game, as well as subscribe to the changefeed. The list should now populate with items. For access to our database servers and therefore live game data, please contact us; it would be dangerous to make our actual database URL public!
Use `Aetheria.State.Import` to quarantine the checked-in legacy catalog files and map stable MessagePack payload fields into typed CultCache state. The importer records path, size, and SHA-256 provenance for `GameData/AetherDB.msgpack` and `GameData/NameFile/*.msgpack`, then emits typed item, faction, and name-file records for the fields that have earned migration authority.
#### Editing Items
You can unfold the categories of items in the list view to see what items exist. If you select an item, the Database Inspector will populate with all of the available fields of that item. Any changes you make in the Inspector will automatically be pushed to RethinkDB. If you've connected to the production database, this will update the stats of in-game items in real-time!
Item editing should target typed CultCache documents and CultMesh/Eve surfaces. The old Unity database inspector has been removed from authority; if it returns, it should be a lowering over the state spine, not a separate store.
### Testing Locally
Testing the game entirely offline doesn't require running the economy server, but you still need to download the database contents. In the database list view, click the "Connect" button. Once the tools are finished syncing, which can take a while (there's a progress bar), you can click "Save" to create a local backup of the entire database. If you enter Play mode in the "ARPG" scene, the game will use that local copy instead of requiring a connection to the master server.
Note that this process needs to be repeated every time the data model changes or if you wish to test the game with updated database contents.
Testing the game entirely offline should use typed local state under `GameData/aetheria-world.cc`. Until the Unity runtime is fully lowered onto `Aetheria.State`, the checked-in legacy catalog files remain migration inputs and should be regenerated only by explicit import tooling.
### Debug Console