2026-06-28 16:17:38 +01:00
|
|
|
# Implementing Aetheria Features With CultMesh
|
|
|
|
|
|
|
|
|
|
Date: 2026-06-28
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
This guide describes the target developer experience for adding Aetheria state
|
|
|
|
|
with CultMesh. If the workflow grows into a chain of facades, sessions,
|
|
|
|
|
projectors, adapters, and surface builders, stop and simplify the state API.
|
2026-06-29 00:28:12 +01:00
|
|
|
That chain is heretek now: it means the feature is compensating for a missing
|
|
|
|
|
typed document, query, operation, pointer, native view, or generated handle.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
The rule is blunt:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-28 22:09:10 +01:00
|
|
|
small UI-only value: derive it inline from already accessible typed state
|
2026-06-30 19:14:49 +01:00
|
|
|
named UI-only derived view: define the typed derived document, then read it
|
2026-06-29 00:28:12 +01:00
|
|
|
simulation feature: define one canonical typed document, let daemon authority mutate it, consume the same document everywhere
|
2026-06-28 22:09:10 +01:00
|
|
|
hot rendering/physics: use SoA, not object-graph UI documents
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
CultMesh owns document identity, sync, prediction, reconciliation, and reactive
|
|
|
|
|
access. Aetheria code should read like it is using game state, not walking
|
|
|
|
|
protocol layers.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
## Canonical Document Rule
|
|
|
|
|
|
|
|
|
|
The default shape is one shared typed document contract.
|
|
|
|
|
|
|
|
|
|
```text
|
|
|
|
|
AetheriaRuntimeFooDocument
|
|
|
|
|
-> daemon authority mutates/publishes it
|
|
|
|
|
-> Unity/Electron/tools read the same document type
|
|
|
|
|
-> authorized clients write intent or predictions through the managed handle
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
Do not split a feature into "daemon truth" and a separate "CultMesh typed
|
|
|
|
|
state" unless the second shape is intentionally different. The assembly is
|
|
|
|
|
shared, so the document the daemon owns is also the document clients receive.
|
|
|
|
|
Across runtimes, callers should feel like they are grabbing a handle on typed
|
|
|
|
|
state and either reading it for display or writing to it for client input.
|
|
|
|
|
|
|
|
|
|
A second document is justified only when it earns a distinct job:
|
|
|
|
|
|
|
|
|
|
- hidden-information filtering;
|
|
|
|
|
- expensive or shared derived state;
|
|
|
|
|
- viewport/windowed selection;
|
|
|
|
|
- SoA/native render or physics layout;
|
|
|
|
|
- lossy presentation summaries;
|
2026-06-30 19:14:49 +01:00
|
|
|
- UI surface documents, including nested CultUI `embeddedDocuments` slots when
|
|
|
|
|
a parent surface reserves layout for a separately synced child document;
|
2026-06-29 00:28:12 +01:00
|
|
|
- compatibility mirrors with a named removal stage.
|
|
|
|
|
|
|
|
|
|
Projection is not the normal way to make state client-visible. Projection is an
|
|
|
|
|
exception for a different shape.
|
|
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
Nested UI does not loosen this rule. If a daemon-owned panel needs an embedded
|
|
|
|
|
dropdown, item inspector, scene insert, or drag/drop target surface, the parent
|
|
|
|
|
surface carries a `surface.slot` with `embeddedDocuments`, and each child is
|
|
|
|
|
resolved as its own managed CultMesh document. The renderer lowers the retained
|
|
|
|
|
tree and wires commands; it does not invent a Unity-only state model for the
|
|
|
|
|
child surface.
|
|
|
|
|
|
|
|
|
|
Discovery and parity live with the shared Eve contract, not in an Aetheria-only
|
|
|
|
|
helper. Any CultUI feature that affects nested surfaces must update the
|
|
|
|
|
canonical fixture at `E:\Projects\Eve\web\fixtures\cultui-embedded-surface.json`
|
|
|
|
|
or an equivalent versioned fixture, and it must be discoverable from
|
|
|
|
|
`E:\Projects\Eve\docs\parity-testing-harness.md`. The active runtime checks are
|
|
|
|
|
the browser DSL test, Eve parity harness, Flutter parity smoke, Unity build, and
|
|
|
|
|
Aetheria verifier scripts. Android/Kotlin and Fensalir must remain listed in
|
|
|
|
|
the matrix with explicit pending/unsupported status until they become active
|
|
|
|
|
GUI CultUI renderers. Do not let an untested target disappear into politeness.
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## The Ergonomic Bar
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
UI-only state should take one or two steps.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use one step when the value is small and already derivable from state the caller
|
|
|
|
|
has:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-29 00:28:12 +01:00
|
|
|
var currentDocking = client.State.Reactive<AetheriaRuntimeCurrentDockingDocument>();
|
2026-06-28 22:09:10 +01:00
|
|
|
var bayName = currentDocking.Current?.DockingBayName ?? "";
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
Use two steps when the derived view is shared, non-trivial, or should have a
|
2026-06-28 22:09:10 +01:00
|
|
|
stable schema:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-30 19:14:49 +01:00
|
|
|
1. Define the typed derived document.
|
2026-06-28 22:09:10 +01:00
|
|
|
2. Read it from Unity as a managed reactive typed document.
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Simulation state should take two or three steps:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-29 00:28:12 +01:00
|
|
|
1. Define the canonical typed document in the shared runtime package.
|
|
|
|
|
2. Let the daemon mutate/publish that document as the authority for the Verse.
|
|
|
|
|
3. Read or modify the same managed document from clients according to authority policy.
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Anything beyond that needs a clear reason. Performance-sensitive entity
|
|
|
|
|
rendering uses SoA because it is a different data shape, not because ordinary UI
|
|
|
|
|
state should become ceremonial.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## What CultMesh Should Hide
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Feature authors should not manually handle:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- document routing keys;
|
|
|
|
|
- protocol envelopes;
|
|
|
|
|
- schema lookup;
|
|
|
|
|
- cache hydration;
|
|
|
|
|
- sync subscriptions;
|
|
|
|
|
- prediction dispatch;
|
|
|
|
|
- reconciliation smoothing;
|
|
|
|
|
- session wrapper lifetime around a single document;
|
|
|
|
|
- joins against raw daemon frame rows in UI code.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Those are CultMesh/runtime concerns. Unity callers should hold either a
|
|
|
|
|
`CultMeshReactiveDocument<TDocument>` or a named typed handle that returns one.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## What Not To Add
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Do not add new single-document wrappers such as:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-28 22:09:10 +01:00
|
|
|
AetheriaRuntimeSomeStateSession
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
when the wrapper only stores a document, exposes `Current`, and calls
|
|
|
|
|
`Dispose()`. That is a managed reactive document with a worse name.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Do not add feature-specific access paths like this:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-28 22:09:10 +01:00
|
|
|
daemon document -> session -> facade -> projector -> adapter -> surface builder -> caller
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
That shape preserves reconstruction-era scaffolding. It does not express the
|
2026-06-29 00:28:12 +01:00
|
|
|
game. At most, one boundary adapter may exist at an actual boundary: Unity
|
|
|
|
|
GameObject presentation, Eve/CultUI lowering, legacy import, persistence, or
|
|
|
|
|
native view ownership. If two or more layers appear in a row just to obtain one
|
|
|
|
|
domain value, stop and move the missing primitive into CultMesh/CultLib or the
|
|
|
|
|
generated Aetheria handle surface.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Choosing The State Shape
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use inline derivation when:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- the value is UI-only;
|
|
|
|
|
- the derivation is local and obvious;
|
|
|
|
|
- the source typed document is already available to the caller;
|
|
|
|
|
- no other system needs the result as a named state surface.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
Use a derived typed document when:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- multiple callers need the same derived state;
|
|
|
|
|
- the state is useful to Unity, RTS, tests, tooling, or the website simulator;
|
|
|
|
|
- the derivation joins multiple source documents;
|
|
|
|
|
- the result needs a stable schema or cross-runtime binding;
|
2026-06-30 19:14:49 +01:00
|
|
|
- the derived view should update reactively as daemon state changes.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
Use a canonical simulation document when:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- the value affects game rules;
|
|
|
|
|
- input can mutate it;
|
|
|
|
|
- it needs authority, replay, persistence, or reconciliation;
|
|
|
|
|
- the daemon must validate operations before publishing the next state.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
Use a derived typed document when the caller needs a different state shape,
|
2026-06-29 00:28:12 +01:00
|
|
|
not merely because a client needs to see daemon-owned state.
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use SoA/native views when:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- Unity jobs, Burst, Ymir, or rendering need large columnar data;
|
|
|
|
|
- row count and per-frame access make object graphs the wrong format;
|
2026-06-29 00:28:12 +01:00
|
|
|
- the data is a hot view over canonical simulation state rather than a UI document.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## One-Step UI Derivation
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
For a small UI-only value, read existing typed state directly.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
|
|
|
|
using GameCult.Aetheria.State.Verse;
|
|
|
|
|
using GameCult.Mesh;
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public sealed class DockingBadge : IDisposable
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
private readonly AetheriaClient _client;
|
|
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeCurrentDockingDocument> _docking;
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public DockingBadge(AetheriaClient client)
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
_client = client;
|
2026-06-28 16:17:38 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public string Text
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
get
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-29 00:28:12 +01:00
|
|
|
_docking ??= _client.State
|
|
|
|
|
.Reactive<AetheriaRuntimeCurrentDockingDocument>();
|
2026-06-28 22:09:10 +01:00
|
|
|
var current = _docking.Current;
|
|
|
|
|
return current?.IsDocked == true ? current.DockingBayName : "";
|
2026-06-28 16:17:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public void Dispose()
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
_docking?.Dispose();
|
2026-06-28 16:17:38 +01:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
The caller does not need a custom facade, session, projector, or adapter. If the
|
2026-06-30 19:14:49 +01:00
|
|
|
inline derivation becomes shared or hard to read, promote it to a derived
|
2026-06-28 22:09:10 +01:00
|
|
|
document.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
## Two-Step Derived UI Document
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use this for shared presentation state such as player HUD, docking summary,
|
|
|
|
|
commander wave status, station service summary, or Starbridge support alerts.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
Before adding this document, ask whether the UI can read the canonical
|
|
|
|
|
simulation document directly. If it can, do that. The derived document exists only
|
2026-06-29 00:28:12 +01:00
|
|
|
when the UI shape is genuinely derived, filtered, windowed, or shared enough to
|
|
|
|
|
deserve its own schema.
|
|
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
### Step 1: Define The Derived Document
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Put the document type in the shared runtime package so every runtime sees the
|
|
|
|
|
same schema:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-28 22:09:10 +01:00
|
|
|
Packages/org.gamecult.aetheria.state/Runtime
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Example:
|
|
|
|
|
|
2026-06-28 16:17:38 +01:00
|
|
|
```csharp
|
2026-06-28 22:09:10 +01:00
|
|
|
[MessagePackObject]
|
|
|
|
|
[CultDocument(AetheriaRuntimeDaemonSchemas.ZoneDefenseStatus)]
|
|
|
|
|
public sealed class AetheriaRuntimeZoneDefenseStatusDocument
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(0)]
|
|
|
|
|
public int ZoneIndex { get; set; } = -1;
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(1)]
|
|
|
|
|
public double BaseShieldRatio { get; set; }
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(2)]
|
|
|
|
|
public int IncomingHostileCount { get; set; }
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(3)]
|
|
|
|
|
public int ActiveTurretCount { get; set; }
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(4)]
|
|
|
|
|
public string AlertLevel { get; set; } = "";
|
|
|
|
|
}
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
Then make it available through generic managed state access:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-29 00:28:12 +01:00
|
|
|
using var status =
|
|
|
|
|
client.State.Reactive<AetheriaRuntimeZoneDefenseStatusDocument>();
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
The exact registration/publishing code belongs in the runtime plumbing. It
|
|
|
|
|
should be generated or centralized. The feature author should not have to write
|
2026-06-29 00:28:12 +01:00
|
|
|
five separate access classes, wrapper methods, sessions, or facades for one
|
2026-06-30 19:14:49 +01:00
|
|
|
derived state shape.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
### Step 2: Read It From Unity
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Unity holds the typed reactive document and reads `Current`.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-28 22:09:10 +01:00
|
|
|
using GameCult.Aetheria.State.Verse;
|
|
|
|
|
using GameCult.Mesh;
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public sealed class ZoneDefenseHud : IDisposable
|
|
|
|
|
{
|
|
|
|
|
private readonly AetheriaClient _client;
|
|
|
|
|
private CultMeshReactiveDocument<AetheriaRuntimeZoneDefenseStatusDocument> _status;
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public ZoneDefenseHud(AetheriaClient client)
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
_client = client;
|
2026-06-28 16:17:38 +01:00
|
|
|
}
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public void Refresh()
|
|
|
|
|
{
|
2026-06-29 00:28:12 +01:00
|
|
|
_status ??= _client.State
|
|
|
|
|
.Reactive<AetheriaRuntimeZoneDefenseStatusDocument>();
|
2026-06-28 22:09:10 +01:00
|
|
|
var status = _status.Current;
|
|
|
|
|
if (status == null)
|
|
|
|
|
return;
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
DrawShield(status.BaseShieldRatio);
|
|
|
|
|
DrawIncoming(status.IncomingHostileCount);
|
|
|
|
|
DrawAlert(status.AlertLevel);
|
|
|
|
|
}
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
public void Dispose()
|
|
|
|
|
{
|
|
|
|
|
_status?.Dispose();
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
That is the whole derived UI document workflow. If more steps are required, they
|
2026-06-28 22:09:10 +01:00
|
|
|
belong in CultMesh/runtime infrastructure, not in every feature.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Two Or Three-Step Simulation Feature
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use this when the feature changes gameplay: heat support, repair drones,
|
|
|
|
|
station construction, scenario progression, commander infrastructure, or wave
|
|
|
|
|
director state.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
### Step 1: Define The Canonical Document
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
Simulation truth is the shared typed document. The daemon owns authority over
|
|
|
|
|
that document, but the document contract is not daemon-private.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-28 22:09:10 +01:00
|
|
|
[MessagePackObject]
|
|
|
|
|
[CultDocument(AetheriaRuntimeDaemonSchemas.StationSupport)]
|
|
|
|
|
public sealed class AetheriaRuntimeStationSupportDocument
|
|
|
|
|
{
|
|
|
|
|
[Key(0)]
|
|
|
|
|
public double CoolingReserve { get; set; }
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(1)]
|
|
|
|
|
public double RepairNaniteReserve { get; set; }
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
[Key(2)]
|
|
|
|
|
public int ActiveSupportDrones { get; set; }
|
|
|
|
|
}
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
### Step 2: Let The Daemon Own Authority
|
|
|
|
|
|
|
|
|
|
The daemon validates operations, applies ticks, and publishes the canonical
|
|
|
|
|
document into the Verse. Unity does not own gameplay truth, but Unity does not
|
|
|
|
|
need a separate client-facing copy either. CultMesh handles sync. If the client
|
|
|
|
|
has prediction authority, local changes to the managed document are
|
|
|
|
|
predictions; reconciliation corrects them without requiring feature code to
|
2026-06-28 22:09:10 +01:00
|
|
|
manually shuttle deltas.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
### Step 3: Read Or Mutate Through The Managed Handle
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
Unity reads the same document through the managed typed handle:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-29 00:28:12 +01:00
|
|
|
var support = client.State.Reactive<AetheriaRuntimeStationSupportDocument>();
|
2026-06-28 22:09:10 +01:00
|
|
|
var cooling = support.Current?.CoolingReserve ?? 0;
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
When the client does not have direct simulation authority, it submits a typed
|
|
|
|
|
operation:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```csharp
|
2026-06-28 22:09:10 +01:00
|
|
|
client.Operations.Submit(new AetheriaRuntimeDeploySupportDroneOperation
|
2026-06-28 16:17:38 +01:00
|
|
|
{
|
2026-06-28 22:09:10 +01:00
|
|
|
ZoneIndex = currentZone,
|
|
|
|
|
TargetEntityIndex = targetEntity,
|
|
|
|
|
DroneLoadoutId = selectedLoadout
|
2026-06-28 16:17:38 +01:00
|
|
|
});
|
|
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
The daemon validates and applies the operation. The next published document is
|
|
|
|
|
the authoritative result.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
When the client has prediction or simulation authority, the ergonomic target is
|
|
|
|
|
even simpler: modifying the managed reactive document records a prediction,
|
|
|
|
|
debounced by the update frame, and CultMesh routes/reconciles it according to
|
|
|
|
|
the Verse authority policy.
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## SoA For Hot Paths
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Use SoA when Unity needs fast columnar access, not when a UI needs a label.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Good SoA candidates:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- visible entity transforms;
|
|
|
|
|
- render splats;
|
|
|
|
|
- physics bodies;
|
|
|
|
|
- sensor/contact rows for thousands of entities;
|
|
|
|
|
- Ymir/Burst job inputs.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Bad SoA candidates:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- current docking bay;
|
|
|
|
|
- selected object details;
|
|
|
|
|
- HUD status;
|
|
|
|
|
- player settings;
|
|
|
|
|
- station service summary.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
SoA should be a named high-performance view over canonical simulation state. It
|
|
|
|
|
should not force ordinary presentation code to understand frame slabs, column
|
|
|
|
|
handles, or native buffer ownership.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Reconnection
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
State needed to reconstruct a player after a client crash must be in daemon or
|
|
|
|
|
scenario/session CultMesh documents, not Unity locals.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Persist or republish:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- player identity and seat;
|
|
|
|
|
- current entity or cockpit module;
|
|
|
|
|
- docked station and bay;
|
|
|
|
|
- inventory/loadout references;
|
|
|
|
|
- scenario progress;
|
|
|
|
|
- commander infrastructure state;
|
|
|
|
|
- active operations that survive reconnect;
|
|
|
|
|
- escape pod/proxy ship state when ejected.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
On reconnect, Unity should reacquire typed documents and rebuild presentation
|
|
|
|
|
from `Current`. Reconnection should not require replaying UI actions.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Tests
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
For inline UI derivation, test the source document and the small consuming
|
|
|
|
|
component when behavior is non-trivial.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
For derived UI documents, test:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
- derivation from representative daemon state;
|
2026-06-28 22:09:10 +01:00
|
|
|
- empty/default state;
|
|
|
|
|
- reactive update after source state changes;
|
2026-06-29 00:28:12 +01:00
|
|
|
- Unity caller reads the typed document directly.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-30 19:14:49 +01:00
|
|
|
For nested CultUI surfaces, test every target runtime listed in the Eve parity
|
|
|
|
|
matrix:
|
|
|
|
|
|
|
|
|
|
- browser DSL lowering discovers and renders `embeddedDocuments`;
|
|
|
|
|
- Eve parity harness preserves slots, child document identity, and command
|
|
|
|
|
wiring;
|
|
|
|
|
- Flutter parity smoke renders the same nested surface contract;
|
|
|
|
|
- Unity build/lowering binds the parent surface and embedded child documents
|
|
|
|
|
without local projector state;
|
|
|
|
|
- Aetheria verifier scripts prove the game daemon publishes the discoverable
|
|
|
|
|
CultMesh surface document;
|
|
|
|
|
- pending runtimes such as Android/Kotlin and Fensalir remain explicitly marked
|
|
|
|
|
pending/unsupported until they have a real renderer test.
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
For simulation features, test:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-29 00:28:12 +01:00
|
|
|
- daemon tick/application mutates the canonical typed document;
|
2026-06-28 22:09:10 +01:00
|
|
|
- typed operation validation accepts and rejects correctly;
|
2026-06-29 00:28:12 +01:00
|
|
|
- clients read the same shared document type the daemon publishes;
|
2026-06-28 22:09:10 +01:00
|
|
|
- prediction/reconciliation behavior when the runtime supports local authority;
|
|
|
|
|
- reconnect can reconstruct the required player state.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
For SoA, test:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- row identity and generation;
|
|
|
|
|
- bounds/ownership;
|
|
|
|
|
- empty views;
|
|
|
|
|
- large views;
|
|
|
|
|
- native buffer lifetime.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Verifier Rules
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Verifier rules should guard the desired ergonomic shape. They should forbid
|
|
|
|
|
reconstruction-era detours.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Good verifier expectations:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-28 22:09:10 +01:00
|
|
|
Unity caller owns CultMeshReactiveDocument<TDocument>
|
|
|
|
|
Unity caller reads .Current
|
2026-06-29 00:28:12 +01:00
|
|
|
Unity caller uses client.State.Reactive<TDocument>()
|
2026-06-28 22:09:10 +01:00
|
|
|
Unity caller submits typed operations for mutations
|
2026-06-29 00:28:12 +01:00
|
|
|
authorized caller mutates managed typed state as prediction/input
|
2026-06-28 22:09:10 +01:00
|
|
|
hot renderer uses named SoA view
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Bad verifier expectations:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
|
|
|
|
```text
|
2026-06-28 22:09:10 +01:00
|
|
|
Unity caller owns AetheriaRuntime*Session for one document
|
|
|
|
|
Unity caller calls ObserveFeatureName() wrapper only to read Current
|
|
|
|
|
UI caller reads raw daemon frames and joins rows
|
|
|
|
|
feature adds facade/projector/adapter/surface-builder chain
|
2026-06-29 00:28:12 +01:00
|
|
|
feature introduces multiple translation layers to get one typed value
|
2026-06-28 16:17:38 +01:00
|
|
|
```
|
|
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
The verifier should make the codebase harder to regress into ceremony, not
|
|
|
|
|
freeze the ceremony in place.
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
## Final Check
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
Before calling a CultMesh feature ergonomic, answer these questions:
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
- Can a UI-only inline value be implemented in one local derivation?
|
2026-06-30 19:14:49 +01:00
|
|
|
- Can a shared UI view be implemented by defining the document and reading
|
2026-06-28 22:09:10 +01:00
|
|
|
it from Unity?
|
2026-06-29 00:28:12 +01:00
|
|
|
- Can a simulation feature be explained as one canonical typed document plus
|
|
|
|
|
authority policy and typed operations?
|
2026-06-28 22:09:10 +01:00
|
|
|
- Does Unity code read domain state instead of protocol plumbing?
|
|
|
|
|
- Is SoA reserved for hot paths?
|
|
|
|
|
- Did the verifier protect the clean path instead of the old wrappers?
|
2026-06-28 16:17:38 +01:00
|
|
|
|
2026-06-28 22:09:10 +01:00
|
|
|
If the answer is no, fix the access shape before adding more feature code.
|