The reflective CultDocumentRegistry is now the only descriptor authority, and payloads always go through MessagePackSerializer with OptionsFor(document assembly). No consumer builds AOT, IL2CPP or trimmed, Unity always ran the reflective path, and no benchmark showed a gain, while the generator duplicated the registry's shape rules. Removed: GameCult.Caching.MessagePack.Generator and .Analyzers (solution, project and analyzer references, NuGet pack list), CultGeneratedDocumentMetadata (provider attribute, interface, definitions, loader), the generated descriptor and codec branches in CultCache and CultDocumentMessagePackSerialization, DocumentShapeSweepTests and the generator half of the unkeyed test, the Microsoft.CodeAnalysis.CSharp test reference and InternalsVisibleTo. The registry rejects, with a message naming the fix, what MessagePackSerializer cannot serialize and what the generator used to paper over: a document without [MessagePackObject], a non-public document without AllowPrivate = true, and a non-public member of an AllowPrivate document that is not [IgnoreMember]. DocumentShapeTests pins every rejection message and the accepted shapes' round trips against MessagePackSerializer bytes (keeps the 93A16E0305 pin). Rejected shapes are emitted into a dynamic assembly, which registry discovery skips. Nested member type names use reflection's Outer+Inner form, not the generator's dot form, so ReferenceHolderEntry's fixture id is re-pinned (bd8506... -> 4ec0c5...). A scan of the schema catalogs in all 2786 .cc stores under F:\Projects (AetheriaEve and CultLib-* worktrees, bin and obj excluded) found no member type name naming a nested type in either form. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
355 lines
16 KiB
Markdown
355 lines
16 KiB
Markdown
# CultLib
|
|
|
|
CultLib is GameCult's shared state, transport, and runtime substrate. It started
|
|
as reusable C# game infrastructure, but the useful thing now is larger: typed
|
|
`.cc` persistence, schema-stable wire formats, ergonomic reliable-UDP
|
|
networking, distributed database sync, simulation witness consensus, streaming
|
|
surfaces, and UI/runtime affordances that can be used from C#, TypeScript, Rust,
|
|
Python, and Kotlin without each runtime inventing its own local folklore.
|
|
|
|
The stack is less a single product than a set of capabilities:
|
|
|
|
- logging primitives and implementations
|
|
- CultCache typed documents, `.cc` persistence, and SoA memory access
|
|
- CultNet schema-v0 messages over TCP and LiteNetLib adapters, a bounded
|
|
ASP.NET Core WebSocket host adapter, and CultLib's native cross-runtime RUDP
|
|
pipe
|
|
- CultMesh distributed realtime database, Verse discovery, authority leases,
|
|
shard replication, and witness-authoritative simulation consensus
|
|
- typed geometry domain/chunk documents for distributed CSG and LOD streaming
|
|
- low-latency stream catalogs and frame-handle negotiation for media, tensors,
|
|
and runtime-native buffers
|
|
- Eve/CultUI daemon-published surfaces that lower into GUI, TUI, web, native,
|
|
overlay, or agent-facing clients
|
|
- declarative Unity UI composition and reflective runtime inspector tooling
|
|
|
|
The practical pitch: a runtime can persist typed state, exchange it with peers,
|
|
discover compatible schemas, join a Verse, publish observations, reconcile
|
|
canonical state, and expose an operator surface without translating through a
|
|
bespoke JSON bridge at every boundary. A frontend is just another CultMesh
|
|
client: the daemon publishes a typed Eve/CultUI surface, and any compatible
|
|
runtime can lower that surface where it needs to live.
|
|
|
|
## Which Package Do I Want?
|
|
|
|
If the job is shared game state, start here before inventing a fourth drawer
|
|
and pretending the label makes it furniture.
|
|
|
|
| Job | Start With | Owns | Use When | Do Not Use It For |
|
|
| --- | --- | --- | --- | --- |
|
|
| Local typed state | `GameCult.Caching` / CultCache | Document identity, schema compatibility, record keys, indexes, globals, and local persistence | You need a typed cache, file-compatible save data, local reactive reads, or a stable domain document model | Peer discovery, transport security, shard routing, or mesh consensus |
|
|
| Portable math | `GameCult.Math` / CultMath | Shader-shaped numeric primitives, deterministic random, noise, and CPU/GPU parity helpers | Shared simulation and geometry code must not depend on an engine's math or RNG types | Domain-specific geometry, rendering, or world authority |
|
|
| Procedural geometry | `GameCult.Geometry` / CultGeometry | Isosurface extraction plus CultCache-native domains, LOD requests, selected cuts, and mesh artifacts | C#, Rust, Godot, or remote workers need shared geometry algorithms and typed results | Transport policy, peer discovery, or gameplay authority |
|
|
| Network transport and database plumbing | `GameCult.Networking` / CultNet | Native RUDP sessions, LiteNetLib/TCP adapters, an authenticated WebSocket host adapter, schema-v0 wire contracts, shard authority, raw document replication, snapshots, and subscriptions | You need a client/server pipe, login/session flow, schema discovery, reliable UDP, or a low-level distributed CultCache lane | Gameplay-facing mesh ergonomics, Verse policy, mod branches, or simulation consensus composition |
|
|
| Distributed realtime gameplay state | `GameCult.Mesh` / CultMesh | Public mesh entrypoints, Verse discovery, peer exchange, shard replication defaults, authority leases, client prediction, and witness consensus | You want the game to treat clients and servers as one reactive database for persistent state, input state, and simulation facts | A tiny local-only tool, a bare transport client, or a storage format contract |
|
|
| Realtime media/frame streams | `GameCult.Mesh` / CultMesh streaming mode | Stream identity, authority, clock metadata, body transport negotiation, frame cursors, and backpressure state | Audio/video/tensor frames need to move between runtimes through shared memory, GPU handles, platform buffers, or CultCache page refs | Durable document mutation, mesh consensus facts, or pretending inline bytes are zero-copy |
|
|
| Daemon-published UI | Eve / CultUI | Typed operator and user-facing surfaces over CultMesh state | A daemon should expose controls, inspection, or workflow UI that can lower into GUI, TUI, web, native, overlay, or agent clients | One-off vendor dashboards, untyped debug panels, or runtime-local UI that cannot travel with the daemon |
|
|
|
|
Quick rule:
|
|
|
|
- Choose CultCache when the problem is "how do I model and persist typed state?"
|
|
- Choose GameCult.Math when the problem is "which portable numeric and random
|
|
semantics do these runtimes share?"
|
|
- Choose GameCult.Geometry when the problem is "how do geometry workers build
|
|
and share engine-neutral geometry?"
|
|
- Choose CultNet when the problem is "how do peers exchange authenticated,
|
|
schema-aware database messages?"
|
|
- Choose CultMesh when the problem is "how does a game join a Verse and share
|
|
realtime state across a mesh?"
|
|
- Choose CultMesh streaming mode when the problem is "how do these runtimes move
|
|
audio/video frames while the mesh owns identity, clocks, cursors, and pressure?"
|
|
- Choose Eve/CultUI when the problem is "how does this daemon publish its own
|
|
operable surface instead of waiting for a hosted admin console?"
|
|
|
|
CultMesh sits on CultNet, and CultNet distributes CultCache documents. That is
|
|
the stack. If a design needs another peer-to-peer category, first check whether
|
|
it is really Verse policy, peer exchange, or shard authority wearing a fake
|
|
mustache.
|
|
|
|
If you want to build an application rather than tour the component drawer,
|
|
start with the maintained
|
|
[`CultMesh And Eve Getting Started`](src/GameCult.Mesh/docs/getting-started/README.md)
|
|
series. Its first executable package-consumer checkpoint is
|
|
[`samples/eve-two-runtime`](samples/eve-two-runtime/README.md).
|
|
The real-process browser continuation is
|
|
[`samples/eve-browser-network`](samples/eve-browser-network/README.md).
|
|
One documented command runs both layers:
|
|
|
|
```powershell
|
|
pwsh -File ./scripts/verify-eve-getting-started.ps1
|
|
```
|
|
|
|
The implemented browser client boundary and remaining deployed-infrastructure
|
|
gates are mapped explicitly in
|
|
[`browser-verse-transport.md`](src/GameCult.Mesh/docs/browser-verse-transport.md).
|
|
|
|
If you want only the smallest durable "open node -> put typed doc -> get typed doc"
|
|
path, use
|
|
[`src/GameCult.Mesh/docs/durable-node-quickstart.md`](src/GameCult.Mesh/docs/durable-node-quickstart.md).
|
|
If you want the lower-level "typed local document -> raw wire document ->
|
|
mesh/runtime watch surface" handoff, follow it with
|
|
[`src/GameCult.Mesh/docs/typed-document-path.md`](src/GameCult.Mesh/docs/typed-document-path.md).
|
|
|
|
## Repository Scope
|
|
|
|
The solution includes:
|
|
|
|
- `GameCult.Logging`: common logging abstraction plus console and file implementations
|
|
- `GameCult.Caching`: `DatabaseEntry`-based cache, indexes, global entries, and backing-store abstractions
|
|
- `GameCult.Caching.MessagePack`: MessagePack-backed persistence for the cache
|
|
- `GameCult.Caching.NewtonsoftJson`: Newtonsoft.Json-backed persistence for the cache
|
|
- `GameCult.Math` / CultMath: portable numeric primitives, deterministic random, noise, and shader-parity helpers
|
|
- `GameCult.Geometry` / CultGeometry: engine-neutral geometry algorithms plus CultCache-native domain, selected-cut, and mesh artifact documents
|
|
- `GameCult.Networking`: encrypted login/register/verify flows, schema-v0 contracts, transport adapters, and native RUDP sessions
|
|
- `GameCult.Networking.WebSockets`: bounded ASP.NET Core binary WebSocket host adapter for browser CultMesh clients
|
|
- `GameCult.Mesh`: CultMesh package home for distributed realtime database, shard replication, client prediction, Verse discovery, and mesh witness consensus
|
|
- `GameCult.Caching.Tests`: NUnit tests for cache and backing-store behavior
|
|
- `GameCult.Networking.Tests`: NUnit tests for networking behavior
|
|
- `GameCult.Unity`: CultUI, a Unity runtime UI composition framework with reflective inspector generation, prefab-backed field resolvers, reusable controls, and a demo project packaged for UPM-style consumption
|
|
- `packages/cultcache-ts`: TypeScript CultCache with MessagePack persistence and inspector tooling
|
|
- `packages/cultnet-ts`: TypeScript CultNet schema-v0 contracts, framing, discovery, raw document replication, and interop tests
|
|
- `packages/cultmesh-ts`: TypeScript CultMesh local node and mesh catalog surface for local runtimes such as VoidBot
|
|
- `packages/cultmesh-browser`: browser-safe, read-only CultMesh document leases and typed operations over an Odin-resolved WebSocket route
|
|
- `packages/cultcache-py`: Python CultCache with CultCache v1 wire parity
|
|
- `packages/cultnet-py`: Python CultNet contracts, framing, RUDP transport, replication, and interop peer
|
|
- `packages/cultmesh-py`: Python CultMesh local node, discovery, sessions, local server, and daemon
|
|
- `packages/cultcache-rs`: Rust CultCache and derive macro
|
|
- `packages/cultnet-rs`: Rust CultNet contracts, framing, discovery, and interop peer
|
|
- `packages/cultmesh-kotlin`: Kotlin/JVM CultMesh and CultNet surface for Android-adjacent runtimes
|
|
|
|
## Repository Layout
|
|
|
|
```text
|
|
package.json
|
|
src/
|
|
GameCult.Logging/
|
|
GameCult.Caching/
|
|
GameCult.Caching.MessagePack/
|
|
GameCult.Caching.NewtonsoftJson/
|
|
GameCult.Geometry/
|
|
GameCult.Networking/
|
|
GameCult.Networking.WebSockets/
|
|
GameCult.Mesh/
|
|
GameCult.Unity/
|
|
tests/
|
|
GameCult.Caching.Tests/
|
|
GameCult.Geometry.Tests/
|
|
GameCult.Networking.Tests/
|
|
packages/
|
|
cultmath/
|
|
cultcache-ts/
|
|
cultnet-ts/
|
|
cultmesh-ts/
|
|
cultmesh-browser/
|
|
cultmesh-kotlin/
|
|
cultcache-py/
|
|
cultnet-py/
|
|
cultmesh-py/
|
|
cultcache-rs/
|
|
cultnet-rs/
|
|
```
|
|
|
|
## Build
|
|
|
|
```powershell
|
|
dotnet build CultLib.sln
|
|
```
|
|
|
|
## Test
|
|
|
|
```powershell
|
|
dotnet test CultLib.sln
|
|
```
|
|
|
|
TypeScript package tests:
|
|
|
|
```powershell
|
|
npm test --workspace packages/cultcache-ts
|
|
npm test --workspace packages/cultnet-ts
|
|
npm run test:interop --workspace packages/cultnet-ts
|
|
npm test --workspace packages/cultmesh-ts
|
|
npm test --workspace packages/cultmesh-browser
|
|
```
|
|
|
|
Measured reactive, retained-session, and shared-memory hot-body gates:
|
|
|
|
```powershell
|
|
pwsh -File ./scripts/measure-reactive-document-performance.ps1 -Quick
|
|
pwsh -File ./scripts/verify-eve-getting-started.ps1
|
|
```
|
|
|
|
The first command measures the same 16 KiB document workload in C#,
|
|
TypeScript, and Python and includes a mapped hot-body probe with explicit copy
|
|
telemetry on Windows named maps and Unix file-backed maps. The second includes
|
|
10,000 real WebSocket operations on one retained
|
|
identity-owned C# session after provider route replacement.
|
|
|
|
Rust package tests:
|
|
|
|
```powershell
|
|
cargo test --manifest-path packages/cultcache-rs/Cargo.toml
|
|
cargo test --manifest-path packages/cultnet-rs/Cargo.toml
|
|
```
|
|
|
|
Kotlin package build:
|
|
|
|
```powershell
|
|
.\packages\cultmesh-kotlin\build.ps1
|
|
```
|
|
|
|
## Common Concepts
|
|
|
|
### `DatabaseEntry`
|
|
|
|
The cache-centric libraries revolve around `DatabaseEntry`. Every entry has a stable `Guid` identifier and can optionally:
|
|
|
|
- expose a human-readable name through `INamedEntry`
|
|
- participate in generic indexes registered at runtime
|
|
- be treated as a global singleton entry through `GlobalSettingsAttribute`
|
|
|
|
Typical entry shape:
|
|
|
|
```csharp
|
|
using GameCult.Caching;
|
|
|
|
public class ItemData : DatabaseEntry, INamedEntry
|
|
{
|
|
public string Name = string.Empty;
|
|
public int Value;
|
|
|
|
public string EntryName
|
|
{
|
|
get => Name;
|
|
set => Name = value;
|
|
}
|
|
}
|
|
```
|
|
|
|
### `CultCache` and Backing Stores
|
|
|
|
`CultCache` is an in-memory index over `DatabaseEntry` objects. It can operate entirely in memory, or it can be attached to one or more backing stores for persistence and synchronization.
|
|
|
|
- the cache is the query surface
|
|
- backing stores are persistence adapters
|
|
- indexes and name maps are maintained inside the cache, not inside the store
|
|
|
|
### Multiple Backing Stores
|
|
|
|
Every document type has exactly one home store. There are no mirrors.
|
|
|
|
```csharp
|
|
cache.AddBackingStore(catalogStore, typeof(ItemData), typeof(Faction));
|
|
cache.AddBackingStore(runStore, typeof(SavedGame));
|
|
```
|
|
|
|
- the home is chosen by the document's runtime type: the most derived routed type wins, otherwise the untyped store
|
|
- at most one untyped store; a type claimed by two stores is an error
|
|
- a write with no home is an error once any store is attached
|
|
- attaching a store reads it; attach routed stores first and the untyped store (if any) last, because a record's home cannot move after it is loaded
|
|
- loading never writes: opening and flushing without a mutation leaves every file byte-identical
|
|
- a read-only store refuses writes and is skipped by flush
|
|
|
|
The contract is `src/GameCult.Caching/Contracts/cultcache-store-composition.md`. Until the C# cache implements it, `AddBackingStore` takes no home types and every attached store receives every record; attach one store per cache.
|
|
|
|
## Example: Cache + Networking
|
|
|
|
```csharp
|
|
using GameCult.Caching;
|
|
using GameCult.Logging;
|
|
using GameCult.Networking;
|
|
|
|
var cache = new CultCache
|
|
{
|
|
Logger = new ConsoleLogger()
|
|
};
|
|
|
|
var server = new Server(cache)
|
|
{
|
|
Logger = new ConsoleLogger()
|
|
};
|
|
|
|
server.Start();
|
|
```
|
|
|
|
## Secrets and Runtime Configuration
|
|
|
|
`GameCult.Networking` uses two pieces of deployment configuration:
|
|
|
|
- `GAMECULT_CONNECTION_KEY`
|
|
- `GAMECULT_SESSION_SIGNING_SECRET`
|
|
|
|
Production guidance:
|
|
|
|
- set both values before starting the server
|
|
- treat `GAMECULT_CONNECTION_KEY` as shared client/server protocol configuration
|
|
- treat `GAMECULT_SESSION_SIGNING_SECRET` as a server-only secret
|
|
- generate high-entropy random values and store the server-side value in your platform's secret store
|
|
- do not rely on local-development defaults in production
|
|
- partial configuration is rejected at startup
|
|
|
|
Recommended value format:
|
|
|
|
- at least 32 random bytes per value
|
|
- encode as Base64 or Base64Url if you want a portable text representation
|
|
|
|
Example secret generation in PowerShell:
|
|
|
|
```powershell
|
|
[Convert]::ToBase64String((1..32 | ForEach-Object { Get-Random -Maximum 256 }))
|
|
```
|
|
|
|
Typical production setup patterns:
|
|
|
|
- inject `GAMECULT_SESSION_SIGNING_SECRET` and the server copy of `GAMECULT_CONNECTION_KEY` as environment variables from Docker secrets, Kubernetes secrets, systemd environment files, or your cloud secret manager
|
|
- ship the matching `GAMECULT_CONNECTION_KEY` to the client through a build-generated config asset, code-generated constants, or another explicit client configuration mechanism
|
|
- scope them per environment so development, staging, and production do not share values
|
|
- rotate `GAMECULT_SESSION_SIGNING_SECRET` operationally
|
|
- treat `GAMECULT_CONNECTION_KEY` as versioned protocol config; changing it requires a coordinated client/server rollout
|
|
|
|
Example server startup with strict environment validation:
|
|
|
|
```csharp
|
|
using GameCult.Caching;
|
|
using GameCult.Networking;
|
|
|
|
var cache = new CultCache();
|
|
var security = ServerSecurityOptions.FromEnvironment();
|
|
var server = new Server(cache, security);
|
|
```
|
|
|
|
Example local-development server startup:
|
|
|
|
```csharp
|
|
using GameCult.Caching;
|
|
using GameCult.Networking;
|
|
|
|
var cache = new CultCache();
|
|
var security = ServerSecurityOptions.Development();
|
|
var server = new Server(cache, security);
|
|
```
|
|
|
|
Example shipped-client startup with explicit client configuration:
|
|
|
|
```csharp
|
|
using GameCult.Networking;
|
|
|
|
var security = new ClientSecurityOptions("<matching-connection-key>");
|
|
var client = new Client(security);
|
|
```
|
|
|
|
## Project Docs
|
|
|
|
Each subproject has a local README with package-specific detail:
|
|
|
|
- [GameCult.Logging](src/GameCult.Logging/README.md)
|
|
- [GameCult.Caching](src/GameCult.Caching/README.md)
|
|
- [GameCult.Caching.MessagePack](src/GameCult.Caching.MessagePack/README.md)
|
|
- [GameCult.Caching.NewtonsoftJson](src/GameCult.Caching.NewtonsoftJson/README.md)
|
|
- [GameCult.Geometry](src/GameCult.Geometry/README.md)
|
|
- [GameCult.Networking](src/GameCult.Networking/README.md)
|
|
- [GameCult.Mesh](src/GameCult.Mesh/README.md)
|
|
- [Durable Node Quickstart](src/GameCult.Mesh/docs/durable-node-quickstart.md)
|
|
- [Typed Document Path](src/GameCult.Mesh/docs/typed-document-path.md)
|
|
- [GameCult.Unity](src/GameCult.Unity/README.md)
|
|
- [GameCult.Caching.Tests](tests/GameCult.Caching.Tests/README.md)
|
|
- [GameCult.Networking.Tests](tests/GameCult.Networking.Tests/README.md)
|