Document canonical CultMesh access rules
This commit is contained in:
parent
19c354ed9b
commit
8a76e4f9fb
9 changed files with 317 additions and 89 deletions
81
AGENTS.md
Normal file
81
AGENTS.md
Normal 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.
|
||||
|
|
@ -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 |
|
||||
|
|
|
|||
|
|
@ -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:
|
||||
|
||||
|
|
|
|||
|
|
@ -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. |
|
||||
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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?
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
Loading…
Reference in a new issue