2026-04-18 06:04:39 +01:00
# CultLib
2026-04-18 13:01:24 +01:00
2026-06-21 16:31:50 +01:00
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.
2026-04-18 13:01:24 +01:00
2026-06-21 16:31:50 +01:00
The stack is less a single product than a set of capabilities:
2026-04-18 13:01:24 +01:00
- logging primitives and implementations
2026-06-21 16:31:50 +01:00
- CultCache typed documents, `.cc` persistence, and SoA memory access
2026-08-18 00:05:57 +02:00
- 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
2026-06-21 16:31:50 +01:00
- CultMesh distributed realtime database, Verse discovery, authority leases,
shard replication, and witness-authoritative simulation consensus
2026-05-29 23:25:36 +01:00
- typed geometry domain/chunk documents for distributed CSG and LOD streaming
2026-06-21 16:31:50 +01:00
- low-latency stream catalogs and frame-handle negotiation for media, tensors,
and runtime-native buffers
2026-06-21 17:12:57 +01:00
- Eve/CultUI daemon-published surfaces that lower into GUI, TUI, web, native,
overlay, or agent-facing clients
2026-04-18 13:12:56 +01:00
- declarative Unity UI composition and reflective runtime inspector tooling
2026-04-18 13:01:24 +01:00
2026-06-21 16:31:50 +01:00
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
2026-06-21 17:12:57 +01:00
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.
2026-06-21 16:31:50 +01:00
2026-05-20 16:08:22 +01:00
## 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 |
2026-08-26 17:35:51 +02:00
| 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 |
2026-08-18 00:05:57 +02:00
| 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 |
2026-05-20 16:08:22 +01:00
| 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 |
2026-06-02 01:13:08 +01:00
| 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 |
2026-06-21 17:12:57 +01:00
| 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 |
2026-05-20 16:08:22 +01:00
Quick rule:
- Choose CultCache when the problem is "how do I model and persist typed state?"
2026-08-26 17:35:51 +02:00
- 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?"
2026-05-20 16:08:22 +01:00
- 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?"
2026-06-02 00:57:07 +01:00
- 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?"
2026-06-21 17:12:57 +01:00
- Choose Eve/CultUI when the problem is "how does this daemon publish its own
operable surface instead of waiting for a hosted admin console?"
2026-05-20 16:08:22 +01:00
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.
2026-08-17 23:18:15 +02:00
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 ).
2026-08-18 00:05:57 +02:00
The real-process browser continuation is
[`samples/eve-browser-network` ](samples/eve-browser-network/README.md ).
2026-08-18 03:17:52 +02:00
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
2026-08-17 23:26:51 +02:00
[`browser-verse-transport.md` ](src/GameCult.Mesh/docs/browser-verse-transport.md ).
2026-08-17 23:18:15 +02:00
If you want only the smallest durable "open node -> put typed doc -> get typed doc"
path, use
2026-05-21 00:00:15 +01:00
[`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
2026-05-20 21:27:33 +01:00
[`src/GameCult.Mesh/docs/typed-document-path.md` ](src/GameCult.Mesh/docs/typed-document-path.md ).
2026-04-18 13:01:24 +01:00
## 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
2026-08-26 17:35:51 +02:00
- `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
2026-06-21 16:31:50 +01:00
- `GameCult.Networking` : encrypted login/register/verify flows, schema-v0 contracts, transport adapters, and native RUDP sessions
2026-08-18 00:05:57 +02:00
- `GameCult.Networking.WebSockets` : bounded ASP.NET Core binary WebSocket host adapter for browser CultMesh clients
2026-05-19 19:08:48 +01:00
- `GameCult.Mesh` : CultMesh package home for distributed realtime database, shard replication, client prediction, Verse discovery, and mesh witness consensus
2026-04-18 13:01:24 +01:00
- `GameCult.Caching.Tests` : NUnit tests for cache and backing-store behavior
- `GameCult.Networking.Tests` : NUnit tests for networking behavior
2026-04-18 13:12:56 +01:00
- `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
2026-05-30 00:41:19 +01:00
- `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
2026-08-18 00:05:57 +02:00
- `packages/cultmesh-browser` : browser-safe, read-only CultMesh document leases and typed operations over an Odin-resolved WebSocket route
Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py
One distribution named cultcache-py carried three libraries. Rust and
TypeScript layer them as cultcache < cultnet < cultmesh; Python now does
the same, one package per layer, module names unchanged so every
`from cultmesh_py import ...` keeps working.
Two imports ran against that layering and are resolved by moving code to
where the dependency direction says it lives, not by adding a shim:
- cultnet_py.interop_peer imported the Verse/peer catalog, authority
lease, stream, and simulation-fact contracts from cultmesh_py. They are
wire contracts a bare peer has to speak without owning a node, which is
why cultnet-rs and cultnet-ts keep them in the net layer. They now live
in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them.
- cultcache_py.benchmark imported cultnet_py. It measures framed CultNet
parse beside cache paths, so it and compare_csharp move to cultnet_py.
verify checks all three packages and moves to cultmesh_py.
The single 6,693-line test module is split by what each test touches: a
test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in
cultnet-py, else cultcache-py. Each suite runs with only its layer and the
layers below on the path, so a test that reaches up fails to import
rather than quietly binding the packages back together. 11 / 53 / 78 =
the same 142.
Workflows install all three; the publish job is one matrix over the
three packages keyed by tag prefix. The TS test harnesses put all three
src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no
longer contains cultnet_py or cultmesh_py; the new packages start at
0.1.0. Nothing was ever published, so no PyPI consumer moves.
cultcache-py 11 tests, path: cultcache-py/src
cultnet-py 53 tests, path: + cultnet-py/src
cultmesh-py 78 tests, path: + cultmesh-py/src
python -m cultmesh_py.verify -> ok, all five checks
cultnet_py.interop_peer imports with cultmesh_py absent
Not run here: the cultnet-ts interop ring and cultcache-ts store matrix
need dotnet/kotlin peers; their PYTHONPATH change is four lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 13:10:47 +02:00
- `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
2026-05-30 00:41:19 +01:00
- `packages/cultcache-rs` : Rust CultCache and derive macro
- `packages/cultnet-rs` : Rust CultNet contracts, framing, discovery, and interop peer
2026-06-21 16:31:50 +01:00
- `packages/cultmesh-kotlin` : Kotlin/JVM CultMesh and CultNet surface for Android-adjacent runtimes
2026-04-18 13:01:24 +01:00
## Repository Layout
```text
2026-05-30 00:41:19 +01:00
package.json
2026-04-18 13:01:24 +01:00
src/
GameCult.Logging/
GameCult.Caching/
GameCult.Caching.MessagePack/
GameCult.Caching.NewtonsoftJson/
2026-05-29 23:25:36 +01:00
GameCult.Geometry/
2026-04-18 13:01:24 +01:00
GameCult.Networking/
2026-08-18 00:05:57 +02:00
GameCult.Networking.WebSockets/
2026-05-19 19:08:48 +01:00
GameCult.Mesh/
2026-04-18 13:01:24 +01:00
GameCult.Unity/
tests/
GameCult.Caching.Tests/
2026-05-29 23:25:36 +01:00
GameCult.Geometry.Tests/
2026-04-18 13:01:24 +01:00
GameCult.Networking.Tests/
2026-05-30 00:41:19 +01:00
packages/
2026-08-26 17:35:51 +02:00
cultmath/
2026-05-30 00:41:19 +01:00
cultcache-ts/
cultnet-ts/
cultmesh-ts/
2026-08-18 00:05:57 +02:00
cultmesh-browser/
2026-06-21 16:31:50 +01:00
cultmesh-kotlin/
2026-06-13 20:35:58 +01:00
cultcache-py/
Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py
One distribution named cultcache-py carried three libraries. Rust and
TypeScript layer them as cultcache < cultnet < cultmesh; Python now does
the same, one package per layer, module names unchanged so every
`from cultmesh_py import ...` keeps working.
Two imports ran against that layering and are resolved by moving code to
where the dependency direction says it lives, not by adding a shim:
- cultnet_py.interop_peer imported the Verse/peer catalog, authority
lease, stream, and simulation-fact contracts from cultmesh_py. They are
wire contracts a bare peer has to speak without owning a node, which is
why cultnet-rs and cultnet-ts keep them in the net layer. They now live
in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them.
- cultcache_py.benchmark imported cultnet_py. It measures framed CultNet
parse beside cache paths, so it and compare_csharp move to cultnet_py.
verify checks all three packages and moves to cultmesh_py.
The single 6,693-line test module is split by what each test touches: a
test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in
cultnet-py, else cultcache-py. Each suite runs with only its layer and the
layers below on the path, so a test that reaches up fails to import
rather than quietly binding the packages back together. 11 / 53 / 78 =
the same 142.
Workflows install all three; the publish job is one matrix over the
three packages keyed by tag prefix. The TS test harnesses put all three
src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no
longer contains cultnet_py or cultmesh_py; the new packages start at
0.1.0. Nothing was ever published, so no PyPI consumer moves.
cultcache-py 11 tests, path: cultcache-py/src
cultnet-py 53 tests, path: + cultnet-py/src
cultmesh-py 78 tests, path: + cultmesh-py/src
python -m cultmesh_py.verify -> ok, all five checks
cultnet_py.interop_peer imports with cultmesh_py absent
Not run here: the cultnet-ts interop ring and cultcache-ts store matrix
need dotnet/kotlin peers; their PYTHONPATH change is four lines.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 13:10:47 +02:00
cultnet-py/
cultmesh-py/
2026-05-30 00:41:19 +01:00
cultcache-rs/
cultnet-rs/
2026-04-18 13:01:24 +01:00
```
## Build
```powershell
dotnet build CultLib.sln
```
## Test
```powershell
dotnet test CultLib.sln
```
2026-05-30 00:41:19 +01:00
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
2026-08-18 00:05:57 +02:00
npm test --workspace packages/cultmesh-browser
2026-05-30 00:41:19 +01:00
```
2026-08-18 04:03:06 +02:00
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
2026-08-18 04:18:46 +02:00
telemetry on Windows named maps and Unix file-backed maps. The second includes
10,000 real WebSocket operations on one retained
2026-08-18 04:03:06 +02:00
identity-owned C# session after provider route replacement.
2026-05-30 00:41:19 +01:00
Rust package tests:
```powershell
cargo test --manifest-path packages/cultcache-rs/Cargo.toml
cargo test --manifest-path packages/cultnet-rs/Cargo.toml
```
2026-06-21 16:31:50 +01:00
Kotlin package build:
```powershell
.\packages\cultmesh-kotlin\build.ps1
```
2026-04-18 13:01:24 +01:00
## 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
2026-09-13 16:01:32 +02:00
### Multiple Backing Stores
2026-04-18 13:01:24 +01:00
2026-09-13 16:01:32 +02:00
Every document type has exactly one home store. There are no mirrors.
2026-04-18 13:01:24 +01:00
```csharp
2026-09-13 16:01:32 +02:00
cache.AddBackingStore(catalogStore, typeof(ItemData), typeof(Faction));
cache.AddBackingStore(runStore, typeof(SavedGame));
2026-04-18 13:01:24 +01:00
```
2026-09-13 16:01:32 +02:00
- 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
2026-04-18 13:01:24 +01:00
2026-09-13 16:01:32 +02:00
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.
2026-04-18 13:01:24 +01:00
## 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 )
2026-05-29 23:49:14 +01:00
- [GameCult.Geometry ](src/GameCult.Geometry/README.md )
2026-04-18 13:01:24 +01:00
- [GameCult.Networking ](src/GameCult.Networking/README.md )
2026-05-20 16:08:22 +01:00
- [GameCult.Mesh ](src/GameCult.Mesh/README.md )
2026-05-21 00:00:15 +01:00
- [Durable Node Quickstart ](src/GameCult.Mesh/docs/durable-node-quickstart.md )
2026-05-20 21:27:33 +01:00
- [Typed Document Path ](src/GameCult.Mesh/docs/typed-document-path.md )
2026-04-18 13:01:24 +01:00
- [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 )