Document canonical CultMesh access rules

This commit is contained in:
Meta Vens 2026-06-29 00:28:12 +01:00
commit 8a76e4f9fb
9 changed files with 317 additions and 89 deletions

81
AGENTS.md Normal file
View file

@ -0,0 +1,81 @@
# Aetheria Agent Instructions
This repo is mid-migration toward CultMesh-native gameplay state. Treat access
simplicity as an architectural requirement, not a nice-to-have.
## Canonical Typed State
- Default to one canonical shared typed document for each gameplay state concept.
The daemon may own authority over that document, but it must not imply a
separate private daemon truth plus a copied client-facing state document.
- The shared runtime assembly defines the document. The daemon mutates/publishes
it. Unity, Electron, tools, tests, and later runtimes read the same document
type through CultMesh.
- Client input should feel like grabbing a managed typed state handle and either
reading it for display or writing/submitting through it according to authority
policy. Prediction, debounce, routing, quorum, reconciliation, and smoothing
are CultMesh responsibilities.
Use projections only when the state shape is intentionally different:
- hidden-information filtering;
- expensive or shared derived aggregation;
- viewport/windowed selection;
- SoA/native render or physics layout;
- lossy UI summaries;
- Eve/CultUI surface documents;
- compatibility mirrors with a named removal stage.
"The client needs to see it" is not a projection reason.
## Access Simplicity
- A UI-only inline value should be derived from already accessible typed state.
- A shared UI read should usually be: define the typed document, then read it
with `client.State.Reactive<TDocument>()` or `client.State.Latest<TDocument>()`.
- A simulation feature should usually be: define the canonical typed document,
let daemon authority mutate/publish it, then read or modify that same managed
document from clients according to authority policy.
- Add named handles only when type alone cannot identify the state, such as
parameterized viewport/detail documents, multiple documents sharing one CLR
type, semantic query identity, operation policy, or native view ownership.
## Heretek Shape
The facade/projector/adapter/surface-builder chain is heretek now. Do not add a
stack of translation layers to recover one typed domain value.
One adapter at a true boundary is allowed:
- Unity GameObject presentation;
- Eve/CultUI lowering;
- legacy import;
- persistence;
- native view ownership.
Two or more translation layers in a row mean the code is missing a CultMesh
primitive, generated typed handle, canonical document, query surface, operation
handle, state pointer, or native view descriptor.
## Verifier Discipline
The verifier should encode the desired architecture, not the current accidental
shape. When removing old access paths, add checks that reject their return. Do
not write verifier rules that require wrapper/session/facade/projector chains as
proof of correctness.
Good verifier pressure:
- client code owns `CultMeshReactiveDocument<TDocument>`;
- client code reads `.Current`;
- client code uses generic typed document access where possible;
- hot render paths use named SoA/native views;
- operations are typed and authority-aware;
- legacy adapters are quarantined and named for deletion.
Bad verifier pressure:
- requiring `LatestFoo()`/`ReactiveFoo()` wrappers for single fixed documents;
- requiring one-document session wrappers;
- requiring facade/projector/adapter/surface-builder chains;
- treating renderer-local facade indexes as gameplay-state APIs.

View file

@ -32,6 +32,28 @@ compiles down to the fastest safe path the runtime can prove.
6. Native views expose zero-copy or near-zero-copy slices when safe, with the same state semantics as remote query/subscription fallbacks.
7. Schema evolution is part of the developer experience. Bindings and migration manifests come from one source instead of hand-maintained slot maps.
## Canonical State Before Projection
CultMesh should make one canonical typed document feel native everywhere. If a
feature's game state is `AetheriaRuntimeFooDocument`, the daemon, Unity,
Electron, browser, tools, and future Rust clients should all hold managed
handles to that document type. Authority, prediction, debouncing,
reconciliation, quorum, locality, and transport are handle/runtime behavior,
not reasons for each application to create a private "truth" object and then a
client projection copy.
A projection is a different state shape, not the normal visibility mechanism.
Use projected documents or query surfaces for hidden-information filtering,
derived aggregation, viewport/windowing, SoA/native memory layout, lossy UI
summaries, Eve/CultUI surfaces, or temporary compatibility bridges. Do not
project merely because a client needs to read daemon-owned state.
This rule is cross-runtime. C#, Rust, TypeScript, Unity, browser/WASM, and tool
hosts should all express the same intent: grab a typed document/collection/query
handle, read it for display, or mutate/submit through it when authority policy
allows. CultMesh supplies the managed access conventions and generated handles
that make that simple.
## Ownership Boundary
| Belongs in CultMesh/CultLib | Belongs in Aetheria |

View file

@ -103,6 +103,16 @@ The project already has the first layer of primitive sugar:
The remaining problem is that the sugar is not yet the only obvious path. There are still public or semi-public seams where clients can fall back into raw envelopes, record keys, ad hoc frame projection, manual state resolution, locality-specific wiring, and renderer-side facade adaptation.
The architectural correction is simple and strict: a normal gameplay feature has
one canonical typed document. The daemon may be the authority for that document,
but daemon authority is not a second state model. The shared runtime assembly
defines the document, the daemon mutates/publishes it, and every runtime grabs a
managed handle to that same type for display, input, prediction, or testing.
Projection documents exist only when the state shape is intentionally different:
hidden-information filtering, expensive aggregation, viewport/windowing,
SoA/native layout, lossy UI summaries, Eve/CultUI surfaces, or compatibility
bridges with a removal stage.
The worst current pattern is not one bad helper. It is a repeated protocol walk:
1. read one projected document;
@ -114,6 +124,12 @@ The worst current pattern is not one bad helper. It is a repeated protocol walk:
That entire stack must collapse behind one typed CultMesh document, collection, query, or operation handle. If a client wants current docking, current inventory, current target, current stats, zone contacts, station refit state, or visible render objects, the caller should make one semantic CultMesh call and receive a typed reactive value. CultMesh owns cache hydration, routing, sync, invalidation, watch lifetimes, schema binding, derived-state execution, and locality choice.
The facade/projector/adapter/surface-builder chain is no longer an acceptable
style. A single adapter at a real boundary can be honest; a chain of translation
objects to recover one domain value is heretek architecture. Replace it with a
canonical document handle, a generated typed query/operation handle, a state
pointer, or a native view descriptor.
## Migration Rules
1. Repeated Aetheria-specific glue becomes a generated Aetheria facade.
@ -125,6 +141,9 @@ That entire stack must collapse behind one typed CultMesh document, collection,
7. The browser/RTS client and Unity client must use the same semantic Verse facade, even when their locality/runtime routes differ.
8. Client code must not manually compose state access from multiple protocol layers. A semantic state read is one CultMesh call from the caller's point of view.
9. Renderer-local object facades are presentation caches. They are not the API for game state, inventory, docking, targeting, or station services.
10. Do not split "daemon truth" from "CultMesh typed state" for ordinary gameplay. Use one canonical document, then let authority policy decide who can mutate, predict, or only observe it.
11. Add projections only for a distinct derived/filter/window/native/UI shape. "A client needs to read it" is not a projection justification.
12. Do not add facade/projector/adapter/surface-builder chains. One real boundary adapter is allowed; stacked translation layers are a missing CultMesh primitive.
## Stage 0: Freeze The Vocabulary
@ -153,6 +172,11 @@ Gates:
- No new public API may introduce `Apply(command, payload)` style call sites.
- No new client API may require a caller to know transport record keys or raw schema slots.
- No new client API may require a caller to join projected documents, frame rows, record keys, and renderer-local facades to obtain one domain value.
- No new feature guide, example, or verifier rule may describe separate daemon
truth and client typed state when one canonical document would work.
- No verifier may require a facade/projector/adapter/surface-builder chain as
proof of correctness. It should require the semantic typed handle and reject
the translation chain.
- All new examples in docs must use the North Star shape.
## Stage 1: Harden Shared CultMesh Primitives
@ -235,7 +259,11 @@ npm run check:rts-bindings
Status: partially started; local projection still leaks.
Goal: clients ask the daemon for typed projections. They do not reconstruct world truth from frames unless they are inside a generated/internal query executor.
Goal: clients ask CultMesh for typed query surfaces or projected documents when
the requested shape is derived, filtered, windowed, or native-layout-specific.
They do not reconstruct world truth from frames unless they are inside a
generated/internal query executor, and they do not add projections for canonical
state that a typed document handle can expose directly.
Targets:

View file

@ -143,9 +143,10 @@ Completed and usable:
daemon delegates the temporary RUDP compatibility hook to it.
- `AetheriaRuntimeStarbridgeProjection` owns the first S0 session-summary
projection, and `AetheriaClient.StarbridgeSessionSummaryAsync` exposes it as
a named facade read without routing through map viewport state. Scenario and
session facts now have latest-record keys in local Verse, so clients can ask
the facade for the active Starbridge summary without passing documents around.
a semantic managed read without routing through map viewport state. Scenario
and session facts now have latest-record keys in local Verse, so clients can
ask the client state surface for the active Starbridge summary without passing
documents around.
- The daemon boot path seeds the first Starbridge scenario/session facts from
daemon-owned local state. A fresh once-mode daemon publishes `Frontier
Fabricator Defense` as typed scenario/session documents, and the authority
@ -1147,7 +1148,7 @@ allowed to become Electron/RTS features.
| 1 | Sector topology | `SectorMap`, `SectorRenderer`, and main-menu observed-game boot consume `SectorMapAsync`, `CurrentZoneAsync`, daemon zone contents, catalog, and player settings through explicit clients. `SectorMapAsync` carries tutorial mode, generation seed, and faction relationships so Unity can lower the temporary observed galaxy without a whole daemon frame. | `SectorMap` no longer reads `ActionGameManager.TryGetObservedGalaxy`, passes `GalaxyZone`, or owns a legacy faction graph. `MainMenu` may not call `LatestAuthoritativeRunFrameAsync` for observed-game boot. | `verify-stage7d-unity-parity.ps1`; scoped search for `TryGetObservedGalaxy`, `GalaxyZone`, `Dictionary<Faction` in sector menu files, and `LatestAuthoritativeRunFrameAsync` in `MainMenu` returns no forbidden hits. |
| 2 | Zone render feed | Split `ActionGameManager` frame lowering into `gamecult.aetheria.zone_render.v1`, currently a typed transitional feed over explicit render facts, entity facade rows, orbit rows, and body rows. It no longer exposes the whole current-zone snapshot. Body poses, asteroid-belt poses, asteroid-instance poses, dropped pickups, entity facades, orbits, and bodies are render-feed facts, not per-frame Unity recomputations over the snapshot. Target, compass, and visibility rows come from `gamecult.aetheria.zone_contacts.v1`; viewport-scoped presentation loading comes from `ObjectsViewportAsync(...)`; visible body discovery and gravity terrain sampling facts come from `GravityViewportAsync(...)`. | `ZoneRenderer.LoadDaemonZoneView` remains only as a listed Stage 8 shim; it may not gain new state ownership. Raw run restoration in `ActionGameManager`, whole-zone exposure in `zone_render`, run/zone snapshot storage in `ZoneRenderer`, and `ZoneRenderer` calls to body/belt/asteroid-instance/target/compass/visibility/presentation/gravity-terrain daemon queries are forbidden. | Unity compile plus verifier checks for `ZoneRenderAsync`, `ProjectZoneRender`, no `TryRestoreEntityGraphFromDaemonRun`, no `render.Run`, no `render?.Zone`, no zone snapshot property on `AetheriaRuntimeZoneRenderDocument`, no `_daemonRunSnapshot`, no `_daemonZoneSnapshot`, no body/belt/asteroid-instance pose recomputation, no `QueryBodyViews`, no `EvaluateGravityTerrainHeight`, no `QueryGravityTerrainBand`, no `TryQueryEntityTarget`, no `QueryCompassMarkers`, no `QueryVisibleEntityIndices`, and no `QueryPresentationEntityIndices` in `ZoneRenderer`. |
| 3 | Current entity/status feed | Replace `ActionGameManager` entity facade reads needed by HUD, docking camera, local menu, click selection, inventory, and station/refit affordances with typed current-subject projections. `gamecult.aetheria.current_entity.v1` owns current entity identity/status/inventory/equipment/cargo, shutdown threshold, and player HUD status facts. `gamecult.aetheria.current_docking.v1` owns the current entity's docked state, dock parent identity, and docking bay index. | Unity facade objects may remain only as presentation adapters after their daemon keys are validated against `CurrentEntityAsync` or `CurrentDockingAsync`. Direct `TryGetObservedCurrentEntity` and `TryGetObservedDockingBay` calls are forbidden in newly migrated menu logic. Manager methods that only resolve daemon identity become private shims or move to state projection code. Target/enemy schematic rows remain a Stage 8 presentation shim until a target-entity projection exists. | HUD/menu surfaces read through `AetheriaClient`; no public manager read API returns Unity facade objects for portable state. Verifier requires `CurrentEntityAsync`, `CurrentDockingAsync`, `ProjectCurrentEntity`, `ProjectCurrentDocking`, typed-key validation in `InventoryMenu`, and typed HUD status consumption in `SchematicDisplay`. |
| 4 | Docking/refit projection split | Promote station stock, docked ships, loadout slots, cargo, and refit eligibility from menu-local facade graph reads into named typed projections that match Starbridge S2. Keep pricing through `aetheria.trade_value_policy.v1`. | `InventoryPanel`, `InventoryMenu`, and `TradeMenu` may adapt Unity UI controls, but stock/refit truth cannot come from manager-global observed cargo, docked ship, or trade collections. Broad available-entity reads are deleted; any remaining facade-object bridge must be key-scoped and listed as a Stage 8 shim with the typed projection that will replace it. | Unity and Electron can inspect the same selected station/pawn inventory and submit the same typed refit operation through `AetheriaClient`; verifier searches reject new manager-global inventory/stock reads. |
| 4 | Docking/refit state split | Promote station stock, docked ships, loadout slots, cargo, and refit eligibility from menu-local facade graph reads into canonical typed documents or derived projections as appropriate. Keep pricing through `aetheria.trade_value_policy.v1`. | `InventoryPanel`, `InventoryMenu`, and `TradeMenu` may adapt Unity UI controls, but stock/refit truth cannot come from manager-global observed cargo, docked ship, or trade collections. Broad available-entity reads are deleted; any remaining facade-object bridge must be key-scoped and listed as a Stage 8 shim with the typed document/projection that will replace it. | Unity and Electron can inspect the same selected station/pawn inventory and submit the same typed refit operation through `AetheriaClient`; verifier searches reject new manager-global inventory/stock reads. |
| 5 | Gravity/object viewport parity | Finish using `ObjectsViewportAsync` and `GravityViewportAsync` wherever Unity currently asks for whole-zone data to draw map or tactical views. | Whole-zone daemon frame reads are kept only for renderer compatibility until 7D.5. | Electron and Unity use equivalent projection calls for XY map bounds. |
| 6 | Policy/editor surfaces | Extend the same typed Eve/CultUI authoring shape to Starbridge station/wave data after station/refit read parity is complete. | No return to Unity `GameSettings` as gameplay truth. | New policy surfaces must define typed bodies, command lowering, daemon bridge persistence, and verifier coverage before UI work depends on them. |

View file

@ -1370,7 +1370,7 @@ Current staged work order:
| Order | Build now | Code surface | Verifier | Demolition rule |
| --- | --- | --- | --- | --- |
| 1 | Finish S2 station/refit read parity. `current_entity`, `current_docking`, inventory rows, source slot identity, and loadout templates are already moving; split remaining station stock, docked ship, cargo, equipment, pricing, and refit eligibility reads into named projections. | `AetheriaRuntimeRtsProjection`, `AetheriaRuntimeRtsViewportDocuments`, `AetheriaClient`, Unity inventory/trade menus, generated TS bindings. | `verify-stage7d-unity-parity.ps1`, `Aetheria.State.Verify`, `AuthoritySmoke`, `npm run check:rts-bindings`, Unity compile. | Menu logic may adapt Unity controls, but station/refit truth may not come from manager-global observed cargo, docked ship, catalog, pricing, or loadout caches. |
| 1 | Finish S2 station/refit read parity. `current_entity`, `current_docking`, inventory rows, source slot identity, and loadout templates are already moving; split remaining station stock, docked ship, cargo, equipment, pricing, and refit eligibility reads into canonical typed documents or derived projections as appropriate. | `AetheriaRuntimeRtsProjection`, `AetheriaRuntimeRtsViewportDocuments`, `AetheriaClient`, Unity inventory/trade menus, generated TS bindings. | `verify-stage7d-unity-parity.ps1`, `Aetheria.State.Verify`, `AuthoritySmoke`, `npm run check:rts-bindings`, Unity compile. | Menu logic may adapt Unity controls, but station/refit truth may not come from manager-global observed cargo, docked ship, catalog, pricing, or loadout caches. |
| 2 | Add S2 typed refit operation parity. Dock, undock, select docked ship, equip, store, transfer, restore loadout, and purchase/refit become named operations with typed receipts. | `AetheriaRuntimeDaemonOperationClient`, `AetheriaRuntimeVerseClient`, generated TS operation bindings, Unity/Electron facade methods, daemon operation validation. | Cross-client operation smoke: one runtime issues an allowed refit operation, the other observes the committed result through local Verse state. | Delete or quarantine any public client path that mutates inventory/loadout through operation strings, raw item bags, UI-local rules, or Unity checkpoint rewrites. |
| 3 | Contract Unity to a render/input shell for S2. Inventory/trade UI can remain Unity UI, but every gameplay read/write goes through typed state. | `InventoryMenu`, `InventoryPanel`, `TradeMenu`, `ActionGameManager`, `ZoneRenderer` shims. | Stage 7D verifier rejects direct manager-global refit reads, public manager gameplay reads, new command buses, and Unity-only command paths. | Remaining facade-object bridges must validate daemon keys against typed projections and name the Stage 8 projection/native slab that deletes them. |
| 4 | Prove 7E.1 two-client local Verse parity. Raven Unity and Starfire Electron are peers with separate local Verse nodes, not one runtime viewing another runtime's projection. | launch harness, scoped peer sync, committed facts, session/refit/authority projections, diagnostics. | Raven/Starfire smoke: both clients see the same session, station/refit state, controlled entities, policy id, and fact receipts through local Verse state. | No test may prove co-op by reading a remote viewport or one daemon's debug projection as gameplay state. |
@ -1406,11 +1406,19 @@ Implementation discipline:
- Treat every Starbridge feature as either `state`, `projection`, `operation`,
`policy`, or `presentation`. If it does not fit one of those boxes, stop and
define the missing typed primitive first.
- Start with one canonical shared typed document for gameplay state wherever
possible. Daemon authority mutates/publishes that document; clients receive
the same document type from the shared assembly and read or write it through a
managed CultMesh handle according to authority policy.
- Add a projection only when the shape is intentionally different: hidden-info
filtering, aggregation, viewport/windowing, SoA/native layout, UI summaries,
Eve/CultUI surfaces, or a named compatibility bridge.
- Build vertical proof slices, not horizontal subsystems. A slice is better
when it proves one designer-visible verb end to end than when it creates ten
unused abstractions.
- Prefer small daemon-owned facts plus local projections over broad documents
that become the next viewport-shaped dumping ground.
- Prefer small canonical facts over broad documents that become the next
viewport-shaped dumping ground. Use local projections for derived views, not
merely to make canonical state visible to clients.
- Every UI feature must name the typed state it reads and the typed operation it
submits. A button without that contract is not ready to implement.
- Every temporary Unity facade bridge must validate daemon keys against a typed
@ -1745,7 +1753,10 @@ Progress:
status, station stock, wave forecast, and runtime roles from daemon frame plus
optional scenario/session documents without going through an RTS viewport.
- `AetheriaClient.StarbridgeSessionSummaryAsync` exposes that projection as a
named facade read for Unity, Electron, and future clients.
semantic managed read for Unity, Electron, and future clients. New canonical
state should prefer generic typed document handles; named reads are justified
when they carry derived/query identity rather than single-document wrapper
convenience.
- `AetheriaRuntimeVerseClient` now owns latest Starbridge scenario/session
record keys plus typed put/read/watch helpers; the facade resolves those local
Verse records automatically when callers ask for a session summary.

View file

@ -6,7 +6,18 @@ Aetheria should follow the Odin/VoidBot daemon shape instead of growing a privat
Odin is the all-seer, not the owner. It discovers Aetheria's provider advertisement and interface bindings, indexes the schema/catalog surface, and gives agents a registry overview. Idunn keeps the daemon alive from daemon-published health and command-boundary records. Bifrost hosts the MCP crossing for Codex and other xeno agents. Eve/CultUI defines presentation shape. The Aetheria daemon remains the side-effect owner.
The daemon owns Aetheria simulation state, typed command application, health publication, command boundaries, provider advertisement, and provider-owned Eve GUI/TUI surfaces. Unity is a renderer and input lowerer. A browser, a terminal, Blender, or another runtime should see the same daemon-owned Verse through the same typed publications.
The daemon owns authority over Aetheria simulation state, typed command
application, health publication, command boundaries, provider advertisement,
and provider-owned Eve GUI/TUI surfaces. Authority does not imply a private
daemon truth plus a separate client state copy. Wherever possible, the daemon
mutates and publishes the canonical shared typed document, and Unity, a browser,
a terminal, Blender, or another runtime grabs a managed handle to that same
document type through CultMesh.
Projection documents are for different shapes: hidden-information filtering,
derived aggregation, viewport/windowing, SoA/native render layout, lossy UI
summaries, Eve/CultUI surfaces, and named compatibility bridges. A projection is
not required merely because a non-daemon runtime needs to read state.
Witness-authoritative networking sharpens this rule. Unity should not be a dumb terminal; it may hold local projection caches, prediction state, render-native SoA views, and eventually immutable witness observations for facts it could actually observe under the active Verse rules. Those local records are admissible testimony, not committed truth. Observation, prediction, consensus candidates, and committed facts are separate stages. Unity can lower input documents, predict local presentation, and publish typed witness documents through CultMesh, but it must not mutate canonical Aetheria state or promote a local projection cache into authority.

View file

@ -1,10 +1,11 @@
# Aetheria Verse Client Contract
Aetheria clients should enter the runtime through CultMesh typed state, not through
daemon internals. The daemon owns simulation authority and publishes daemon state
as Verse records. Unity, tools, test harnesses, and later non-Unity clients open
the same state store with the same runtime document registry and read/write typed
documents through CultNet.
daemon internals. The daemon owns simulation authority over canonical shared
documents; it does not own a separate private truth that is then copied into
client state. Unity, tools, test harnesses, and later non-Unity clients open the
same state store with the same runtime document registry and read/write the same
typed documents through CultMesh/CultNet according to authority policy.
The ergonomic contract is stricter than "typed records are available
somewhere." A client must be able to ask CultMesh for the domain state it wants
@ -14,6 +15,14 @@ through daemon frames, projected rows, record keys, schema slots, state refs,
renderer-local facade indexes, or transport route decisions to reconstruct one
gameplay value.
The default implementation contract is one canonical document type per gameplay
state concept. If `AetheriaRuntimeFooDocument` is the gameplay state, that is
the document the daemon mutates/publishes and the document clients receive from
the shared assembly. A separate projected document exists only when the shape is
intentionally different: hidden-info filtering, expensive aggregation, viewport
windowing, SoA/native layout, lossy UI summaries, surface documents, or a named
compatibility bridge. "The client needs to see it" is not a projection reason.
The shared entrypoint is `AetheriaRuntimeVerseClient` in the runtime state
package. It opens a local `CultMeshNode` with the runtime-only Aetheria contract
registry and exposes:
@ -55,11 +64,11 @@ handles:
using var verse = await AetheriaRuntimeVerseClient.OpenAsync(statePath, "tool-client");
var aetheria = verse.Aetheria();
using var inventory = aetheria.Current.Inventory.Watch(RenderInventory);
using var docking = aetheria.Current.Docking.Watch(RenderDocking);
using var dockingBay = aetheria.Current.DockingBay.Watch(RenderDockingBay);
using var inventory = aetheria.State.Reactive<AetheriaRuntimeInventoryDocument>(entityIndex);
using var docking = aetheria.State.Reactive<AetheriaRuntimeCurrentDockingDocument>();
using var support = aetheria.State.Reactive<AetheriaRuntimeStationSupportDocument>();
var stationRefit = await aetheria.Station.Refit.LatestAsync();
var stationRefit = await aetheria.State.LatestAsync<AetheriaRuntimeStationRefitDocument>();
var visibleObjects = await aetheria.Zone(currentZoneId)
.Objects
.VisibleToCurrentPlayer()
@ -73,6 +82,13 @@ Verse, bind an Eve state pointer, or expose a native slab view. That decision is
CultMesh infrastructure. The caller gets a typed domain value and diagnostics
when it asks for them.
Client input follows the same rule. When a runtime has authority or prediction
rights, mutating the managed typed document is the ergonomic target; CultMesh
debounces, routes, records the prediction, and reconciles against canonical
state. When a runtime does not have direct write authority, it submits a typed
operation handle that the daemon validates and applies to the same canonical
document.
Unity should treat the client as its Aetheria-facing runtime surface. It can
still use Burst, DOTS rendering, and native views for presentation, but the state
authority boundary is the typed CultMesh contract. No Unity-facing code should

View file

@ -5,13 +5,15 @@ Date: 2026-06-28
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.
That chain is heretek now: it means the feature is compensating for a missing
typed document, query, operation, pointer, native view, or generated handle.
The rule is blunt:
```text
small UI-only value: derive it inline from already accessible typed state
named UI-only projection: define the projected document, then read it
simulation feature: define simulation state, publish typed state, consume it
simulation feature: define one canonical typed document, let daemon authority mutate it, consume the same document everywhere
hot rendering/physics: use SoA, not object-graph UI documents
```
@ -19,6 +21,36 @@ CultMesh owns document identity, sync, prediction, reconciliation, and reactive
access. Aetheria code should read like it is using game state, not walking
protocol layers.
## 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;
- UI surface documents;
- 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.
## The Ergonomic Bar
UI-only state should take one or two steps.
@ -27,7 +59,7 @@ Use one step when the value is small and already derivable from state the caller
has:
```csharp
var currentDocking = client.State.Current.ReactiveDocking();
var currentDocking = client.State.Reactive<AetheriaRuntimeCurrentDockingDocument>();
var bayName = currentDocking.Current?.DockingBayName ?? "";
```
@ -35,16 +67,16 @@ Use two steps when the projection is shared, non-trivial, or should have a
stable schema:
```text
1. Define the daemon-projected typed document.
1. Define the typed projected document.
2. Read it from Unity as a managed reactive typed document.
```
Simulation state should take two or three steps:
```text
1. Put the simulation data and rules in the daemon-owned domain model.
2. Publish the useful client-facing shape as typed CultMesh state.
3. Read that state from Unity and send typed operations back when input mutates truth.
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.
```
Anything beyond that needs a clear reason. Performance-sensitive entity
@ -86,7 +118,11 @@ daemon document -> session -> facade -> projector -> adapter -> surface builder
```
That shape preserves reconstruction-era scaffolding. It does not express the
game.
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.
## Choosing The State Shape
@ -105,18 +141,21 @@ Use a projected typed document when:
- the result needs a stable schema or cross-runtime binding;
- the projection should update reactively as daemon state changes.
Use daemon-owned simulation state when:
Use a canonical simulation document when:
- 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.
Use a projected typed document when the caller needs a different state shape,
not merely because a client needs to see daemon-owned state.
Use SoA/native views when:
- Unity jobs, Burst, Ymir, or rendering need large columnar data;
- row count and per-frame access make object graphs the wrong format;
- the data is a hot view over daemon truth rather than a UI document.
- the data is a hot view over canonical simulation state rather than a UI document.
## One-Step UI Derivation
@ -140,7 +179,8 @@ public sealed class DockingBadge : IDisposable
{
get
{
_docking ??= _client.State.Current.ReactiveDocking();
_docking ??= _client.State
.Reactive<AetheriaRuntimeCurrentDockingDocument>();
var current = _docking.Current;
return current?.IsDocked == true ? current.DockingBayName : "";
}
@ -162,6 +202,11 @@ document.
Use this for shared presentation state such as player HUD, docking summary,
commander wave status, station service summary, or Starbridge support alerts.
Before adding this projection, ask whether the UI can read the canonical
simulation document directly. If it can, do that. The projection exists only
when the UI shape is genuinely derived, filtered, windowed, or shared enough to
deserve its own schema.
### Step 1: Define The Projected Document
Put the document type in the shared runtime package so every runtime sees the
@ -195,20 +240,17 @@ public sealed class AetheriaRuntimeZoneDefenseStatusDocument
}
```
Then expose it as a named projection from `AetheriaClientState`:
Then make it available through generic managed state access:
```csharp
public CultMeshReactiveDocument<AetheriaRuntimeZoneDefenseStatusDocument>
ReactiveZoneDefenseStatus()
{
return Mesh.Reactive<AetheriaRuntimeZoneDefenseStatusDocument>(
AetheriaRuntimeDaemonDocuments.ZoneDefenseStatus);
}
using var status =
client.State.Reactive<AetheriaRuntimeZoneDefenseStatusDocument>();
```
The exact registration/publishing code belongs in the runtime plumbing. It
should be generated or centralized. The feature author should not have to write
five separate access classes for one projected state shape.
five separate access classes, wrapper methods, sessions, or facades for one
projected state shape.
### Step 2: Read It From Unity
@ -230,7 +272,8 @@ public sealed class ZoneDefenseHud : IDisposable
public void Refresh()
{
_status ??= _client.State.ReactiveZoneDefenseStatus();
_status ??= _client.State
.Reactive<AetheriaRuntimeZoneDefenseStatusDocument>();
var status = _status.Current;
if (status == null)
return;
@ -256,25 +299,10 @@ Use this when the feature changes gameplay: heat support, repair drones,
station construction, scenario progression, commander infrastructure, or wave
director state.
### Step 1: Put Truth In The Daemon
### Step 1: Define The Canonical Document
Simulation truth lives with the daemon domain model and tick/apply logic.
```csharp
public sealed class StationSupportState
{
public double CoolingReserve { get; set; }
public double RepairNaniteReserve { get; set; }
public int ActiveSupportDrones { get; set; }
}
```
The daemon mutates this state during ticks and command application. Unity does
not own gameplay truth.
### Step 2: Publish Typed CultMesh State
Expose the client-facing shape as a typed document.
Simulation truth is the shared typed document. The daemon owns authority over
that document, but the document contract is not daemon-private.
```csharp
[MessagePackObject]
@ -292,21 +320,26 @@ public sealed class AetheriaRuntimeStationSupportDocument
}
```
The daemon publishes this document from its current truth. 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
### 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
manually shuttle deltas.
### Step 3: Read State And Submit Typed Operations
### Step 3: Read Or Mutate Through The Managed Handle
Unity reads state through the managed typed document:
Unity reads the same document through the managed typed handle:
```csharp
var support = client.State.ReactiveStationSupport();
var support = client.State.Reactive<AetheriaRuntimeStationSupportDocument>();
var cooling = support.Current?.CoolingReserve ?? 0;
```
Unity asks the daemon to mutate truth through a typed operation:
When the client does not have direct simulation authority, it submits a typed
operation:
```csharp
client.Operations.Submit(new AetheriaRuntimeDeploySupportDroneOperation
@ -320,6 +353,11 @@ client.Operations.Submit(new AetheriaRuntimeDeploySupportDroneOperation
The daemon validates and applies the operation. The next published document is
the authoritative result.
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.
## SoA For Hot Paths
Use SoA when Unity needs fast columnar access, not when a UI needs a label.
@ -340,9 +378,9 @@ Bad SoA candidates:
- player settings;
- station service summary.
SoA should be a named high-performance view over daemon truth. It should not
force ordinary presentation code to understand frame slabs, column handles, or
native buffer ownership.
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.
## Reconnection
@ -373,13 +411,13 @@ For projected UI documents, test:
- projection from representative daemon state;
- empty/default state;
- reactive update after source state changes;
- Unity caller reads the projected document directly.
- Unity caller reads the typed document directly.
For simulation features, test:
- daemon tick/application mutates truth;
- daemon tick/application mutates the canonical typed document;
- typed operation validation accepts and rejects correctly;
- published document reflects daemon truth;
- clients read the same shared document type the daemon publishes;
- prediction/reconciliation behavior when the runtime supports local authority;
- reconnect can reconstruct the required player state.
@ -401,8 +439,9 @@ Good verifier expectations:
```text
Unity caller owns CultMeshReactiveDocument<TDocument>
Unity caller reads .Current
Unity caller uses client.State.ReactiveFeatureName()
Unity caller uses client.State.Reactive<TDocument>()
Unity caller submits typed operations for mutations
authorized caller mutates managed typed state as prediction/input
hot renderer uses named SoA view
```
@ -413,6 +452,7 @@ 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
feature introduces multiple translation layers to get one typed value
```
The verifier should make the codebase harder to regress into ceremony, not
@ -425,8 +465,8 @@ Before calling a CultMesh feature ergonomic, answer these questions:
- Can a UI-only inline value be implemented in one local derivation?
- Can a shared UI projection be implemented by defining the document and reading
it from Unity?
- Can a simulation feature be explained as daemon truth, typed state, typed
operation?
- Can a simulation feature be explained as one canonical typed document plus
authority policy and typed operations?
- 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?

View file

@ -10,8 +10,9 @@ ambushed by half-finished architecture.
The short version: Unity is being reduced to a renderer, input surface, and
editor environment. Durable game state and portable gameplay facts now live in
typed CultCache/CultNet/CultMesh records under `Aetheria.State`. The daemon is
the simulation owner. Unity and the Electron RTS client are clients of the same
typed Verse state.
the authority over canonical shared documents, not a private truth source that
exports a second client copy. Unity and the Electron RTS client are clients of
the same typed Verse state.
## Start Here
@ -29,8 +30,9 @@ Read these in this order:
5. `docs/aetheria-verse-client-contract.md`
Intended client contract: use typed Verse state, not daemon internals.
6. `docs/cultmesh-feature-implementation-guide.md`
Step-by-step guide for adding daemon-owned typed state, publishing it through
CultMesh, and consuming/interacting with it from Unity.
Step-by-step guide for adding canonical typed state once, letting daemon
authority own mutation, and consuming/interacting with the same document from
Unity and other clients.
7. `Aetheria.State/docs/verse-authority-implementation-plan.md`
Authority and Starbridge staged implementation map.
@ -186,8 +188,12 @@ Expected recurring warnings:
### State Owner
Typed CultCache/CultNet/CultMesh documents are the state owner. The primary
world file is:
Typed CultCache/CultNet/CultMesh documents are the state owner. For normal
gameplay features, define one canonical shared document and let authority policy
decide who may write or predict it. Do not create "daemon truth" plus a separate
client-facing copy unless the second document is intentionally filtered,
aggregated, windowed, lossy, SoA/native, or a named compatibility bridge. The
primary world file is:
```text
GameData/aetheria-world.cc
@ -220,14 +226,15 @@ Use `AetheriaClient` first:
Packages/org.gamecult.aetheria.state/Runtime/AetheriaClient.cs
```
It exposes named reads such as current entity, current docking, station/refit,
sector map, viewport, Starbridge summary, health, authority policy, and SoA
views. It also exposes typed controls through `AetheriaControl` and typed UI
surface helpers through `AetheriaUi`.
It exposes generic managed document reads such as `State.Latest<TDocument>()`
and `State.Reactive<TDocument>()`, parameterized handles for viewport/detail
documents, Starbridge seat handles, typed controls through `AetheriaControl`,
and typed UI surface helpers through `AetheriaUi`.
If a Unity panel or Electron surface needs a fact, prefer adding or extending a
named facade read. Avoid handing callers raw daemon frames unless the caller is
a transitional projection layer.
If a Unity panel or Electron surface needs a fact, prefer the generic managed
document call first. Add a named facade only when it carries semantic identity,
parameters, operation policy, or a distinct derived/native shape. Avoid handing
callers raw daemon frames unless the caller is a transitional projection layer.
### Unity Shell
@ -373,6 +380,12 @@ Work on Ymir/physics:
Git preserves history. The migration should delete dead shims when the typed
replacement exists and tests prove it.
8. Treat facade/projector/adapter/surface-builder chains as a design failure.
One adapter at a true boundary is fine. Stacked translation layers used to
recover one typed value mean the code is missing a CultMesh primitive,
generated handle, canonical document, query, operation, pointer, or native
view.
## Known Transitional Debt
These are not surprises; they are known pressure points.
@ -407,9 +420,13 @@ Use this checklist before writing code:
Add or extend a MessagePack/CultNet document in the runtime package. Use
explicit `[Key]` slots and stable names.
3. Add a facade read or operation.
Prefer `AetheriaClient`, `AetheriaControl`, or `AetheriaUi` over direct
low-level Verse calls in client code.
3. Add the simplest access path.
Prefer `AetheriaClient.State.Latest<TDocument>()` or
`AetheriaClient.State.Reactive<TDocument>()` for canonical documents. Add a
named handle only when type alone cannot identify the document, such as
parameterized viewport/detail state or multiple documents sharing one CLR
type. Use `AetheriaControl` or `AetheriaUi` for typed operations and UI
commands.
4. Lower Unity and Electron through the same semantics.
Unity and RTS do not need identical UI, but they should observe the same
@ -498,8 +515,9 @@ The useful way to look at it is:
When in doubt, follow the typed state:
```text
document -> facade -> client presentation
command document -> daemon apply -> projected result
canonical document -> managed CultMesh handle -> client presentation/input
typed operation -> daemon authority -> same canonical document
projection/native view -> only when the shape is intentionally different
```
If a path does not fit that shape, it is probably transitional debt or a bug.