# Start Here CultLib is one stack in several languages: **CultCache** (typed `.cc` persistence), **CultNet** (schema-v0 wire messages and transports) on top of it, and **CultMesh** (Verse discovery, replication, authority) on top of that. **CultMath** and **Geometry** sit beside the stack as portable numerics and engine-neutral geometry. The C# runtime in `src/` is the reference; the other runtimes keep wire parity with it. If you want typed state on disk, start with CultCache. Everything else assumes it. ## Language x package A cell links a snippet that a test in this repo runs, or says **not yet**. "Package" links the package's own README or source, which is real but carries no tested first-call snippet from this page. | | CultCache | CultMath | Geometry | CultNet | CultMesh | | --- | --- | --- | --- | --- | --- | | **C#** | [first call, tested](#c-first-call) | [package](../../packages/cultmath/README.md); snippet not yet | [package](../../src/GameCult.Geometry/README.md); snippet not yet | [first call, tested](#c-cultnet-first-call); [package](../../src/GameCult.Networking) | [quickstart](../../src/GameCult.Mesh/docs/durable-node-quickstart.md); run by `scripts/verify-eve-getting-started.ps1` | | **TypeScript** | [first call, tested](#typescript-first-call) | not yet | not yet | [first call, tested](#typescript-cultnet-first-call); [package](../../packages/cultnet-ts/README.md) | [package](../../packages/cultmesh-ts/README.md); snippet not yet; [recipe: two-client .io game](io-game.md), tested with two live clients, browser page not yet run | | **Rust** | [first call, tested](#rust-first-call) | [native core](../../packages/cultmath/native/cultmath-core); snippet not yet | not yet | [first call, tested](#rust-cultnet-first-call); [package](../../packages/cultnet-rs/README.md), [examples](../../packages/cultnet-rs/examples) | [package](../../packages/cultmesh-rs); snippet not yet | | **Python** | [first call, tested](#python-first-call) | not yet | not yet | [first call, tested](#python-cultnet-first-call); [package](../../packages/cultnet-py/README.md) | [package](../../packages/cultmesh-py/README.md); snippet not yet | For the whole picture of what each piece is for, see the [package chooser in the top-level README](../../README.md#which-package-do-i-want). ## C# first call Define a typed document, put it in a cache backed by a `.cc` file, read it back through a second cache. The source is [`tests/GameCult.Caching.Tests/FirstCallTests.cs`](../../tests/GameCult.Caching.Tests/FirstCallTests.cs), which is what `dotnet test tests/GameCult.Caching.Tests --filter FirstCallTests` runs, so the snippet cannot drift without the test failing. ```csharp using System.Threading.Tasks; using GameCult.Caching; using GameCult.Caching.MessagePack; using MessagePack; [CultDocument("gamecult.first_call_item", "gamecult.first_call_item.v1")] [MessagePackObject] public sealed class Item { [Key(0)] [CultName] public string Name = string.Empty; [Key(1)] [CultIndex] public string Category = string.Empty; [Key(2)] public int Value; } public static class FirstCall { public static async Task Run(string path) { var store = new SingleFileMessagePackBackingStore(path); var cache = new CultCache(); cache.AddBackingStore(store); var handle = await cache.AddAsync(new Item { Name = "Potion", Category = "Consumable", Value = 50 }); store.PushAll(); // write the .cc file var reopened = new CultCache(); reopened.AddBackingStore(new SingleFileMessagePackBackingStore(path)); await reopened.PullAllBackingStoresAsync(); // read it back var potion = reopened.Get(handle.Key); // by key var byName = reopened.GetByName("Potion"); // by [CultName] return byName ?? potion; } } ``` `FirstCall.Run` is the call; a second test in that file fails if the block above stops matching it. More: `src/GameCult.Caching/README.md`. ## TypeScript first call Same shape in TypeScript: the document is a name, a schema id and a `parse`-able schema (zod here), and the `.cc` file is wire-compatible with the C# one. The source is [`packages/cultcache-ts/test/first-call.test.ts`](../../packages/cultcache-ts/test/first-call.test.ts), run by `npm test` in `packages/cultcache-ts` (or `node --test dist-test/test/first-call.test.js` after `tsc`). ```ts import { z } from "zod"; import { CultCache, SingleFileMessagePackBackingStore, defineDocumentRegistry, defineDocumentType, } from "@gamecult/cultcache-ts"; const itemDocument = defineDocumentType({ type: "item", schemaId: "gamecult.first_call_item.v1", schemaName: "gamecult.first_call_item", schemaVersion: "gamecult.first_call_item.v1", schema: z.object({ name: z.string(), category: z.string(), value: z.number().int() }), name: "name", indexes: { category: "category" }, }); const registry = defineDocumentRegistry(itemDocument); async function firstCall(path: string) { const cache = CultCache.builder() .withRegistry(registry) .withGenericStore(new SingleFileMessagePackBackingStore(path)) .build(); await cache.pullAllBackingStores(); await cache.put(itemDocument, "item:potion", { name: "Potion", category: "Consumable", value: 50 }); const reopened = CultCache.builder() .withRegistry(registry) .withGenericStore(new SingleFileMessagePackBackingStore(path)) .build(); await reopened.pullAllBackingStores(); const potion = reopened.get(itemDocument, "item:potion"); const byName = reopened.getByName(itemDocument, "Potion"); return { potion, byName }; } ``` The test imports from `../src/index` where the block reads `@gamecult/cultcache-ts`; a second test in that file fails if the block stops matching the rest. More: `packages/cultcache-ts/README.md`. ## Rust first call Same shape with `derive(DatabaseEntry)`: slots are `#[cultcache(key = N)]`, so the `.cc` payload is the slot-indexed array the C# runtime writes. The source is [`packages/cultcache-rs/tests/first_call.rs`](../../packages/cultcache-rs/tests/first_call.rs), run by `cargo test --test first_call` in `packages/cultcache-rs`; a second test in that file fails if the block below stops matching the file. The Rust runtime has no name lookup, so the read-back is by key. More: `packages/cultcache-rs/README.md`. ```rust #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "item")] struct Item { #[cultcache(key = 0)] name: String, #[cultcache(key = 1)] category: String, #[cultcache(key = 2, default)] value: u32, } fn first_call() -> anyhow::Result<()> { let dir = tempfile::tempdir()?; let path = dir.path().join("first-call.cc"); let mut cache = CultCache::new(); cache.register_entry_type::()?; cache.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path)); cache.pull_all_backing_stores()?; cache.put("item:potion", &Item { name: "Potion".to_string(), category: "Consumable".to_string(), value: 50, })?; // written to the .cc file before this returns let mut reopened = CultCache::new(); reopened.register_entry_type::()?; reopened.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path)); reopened.pull_all_backing_stores()?; // read it back let potion = reopened.get_required::("item:potion")?; assert_eq!(potion.name, "Potion"); assert_eq!(potion.value, 50); Ok(()) } ``` ## Python first call A document is a definition with an encoder and decoder; `name=` and `indexes=` register the name lookup and indexes. The source is [`packages/cultcache-py/tests/test_first_call.py`](../../packages/cultcache-py/tests/test_first_call.py), run by `python -m unittest tests.test_first_call` in `packages/cultcache-py` with `src` on `PYTHONPATH` and `msgpack` installed (the store needs it). A second test fails if the block below stops matching the file. More: `packages/cultcache-py/README.md`. ```python import tempfile from dataclasses import asdict, dataclass from pathlib import Path from cultcache_py import CultCache, SingleFileMessagePackBackingStore, define_document_type @dataclass class Item: name: str category: str value: int item_doc = define_document_type( "item", encode=lambda item: asdict(item), decode=lambda raw: Item(**raw), name="name", indexes={"category": "category"}, ) def first_call() -> Item: path = Path(tempfile.mkdtemp()) / "first-call.cc" cache = ( CultCache.builder() .register_document_type(item_doc) .add_generic_store(SingleFileMessagePackBackingStore(path)) .build() ) cache.pull_all_backing_stores() cache.put(item_doc, "item:potion", Item("Potion", "Consumable", 50)) # written to the .cc file reopened = ( CultCache.builder() .register_document_type(item_doc) .add_generic_store(SingleFileMessagePackBackingStore(path)) .build() ) reopened.pull_all_backing_stores() # read it back assert reopened.get_by_name(item_doc, "Potion") == reopened.get_required(item_doc, "item:potion") return reopened.get_required(item_doc, "item:potion") ``` ## CultNet first calls CultNet is schema-v0 messages in length-prefixed frames. The first call in each runtime is the same shape: define a typed document, build a `cultnet.document_put_raw.v0` message carrying its MessagePack payload, send one frame over a loopback pipe, and apply the received message to a second cache. Each block is verbatim from the test that runs it; a second test in the same file fails if the block stops matching. ### C# CultNet first call Source: [`tests/GameCult.Networking.Tests/CultNetFirstCallTests.cs`](../../tests/GameCult.Networking.Tests/CultNetFirstCallTests.cs), run by `dotnet test tests/GameCult.Networking.Tests --filter CultNetFirstCallTests`. More: `src/GameCult.Networking/README.md`. ```csharp using System.Net; using System.Net.Sockets; using System.Threading.Tasks; using GameCult.Caching; using GameCult.Networking; using MessagePack; [CultDocument("gamecult.first_call_note", "gamecult.first_call_note.v1")] [MessagePackObject] public sealed class Note { [Key(0)] [CultName] public string Title = string.Empty; [Key(1)] public string Body = string.Empty; } public static class CultNetFirstCall { public static async Task Run() { var inbox = new CultCache(); // the receiver's cache var documents = new CultNetDocumentRegistry(); var received = new TaskCompletionSource(); // Receive: a loopback listener that applies each raw document put to the inbox. using var server = new TcpFramedCultNetSchemaServer(new TcpListener(IPAddress.Loopback, 0)); server.OnCultNet(async (put, peer) => { await documents.ApplyRawDocumentPutMessageAsync(inbox, put); received.TrySetResult(true); }); // Send: a typed document as a cultnet.document_put_raw.v0 message. using var client = new TcpFramedCultNetSchemaClient(); client.Connect("127.0.0.1", server.LocalEndPoint.Port); var handle = new CultRecordHandle(new CultRecordKey("note:hello")); client.SendCultNet(documents.CreateRawDocumentPutMessage( "put-1", handle, new Note { Title = "Hello", Body = "sent over CultNet" })); await received.Task.WaitAsync(TimeSpan.FromSeconds(5)); return inbox.Get(handle.Key); } } ``` ### TypeScript CultNet first call Source: [`packages/cultnet-ts/test/first-call.test.ts`](../../packages/cultnet-ts/test/first-call.test.ts), run by `npm test` in `packages/cultnet-ts` (build `packages/cultcache-ts` first: `npm run build` there). The test imports from `../src` where the block reads `cultnet-ts`. More: `packages/cultnet-ts/README.md`. ```ts import { createServer, connect } from "node:net"; import { z } from "zod"; import { CultCache, defineDocumentType } from "@gamecult/cultcache-ts"; import { CultNetDocumentRegistry, createTcpFramedCultNetPeer, defineCultNetDocumentBinding, } from "cultnet-ts"; const noteDocument = defineDocumentType({ type: "note", schemaId: "gamecult.first_call_note.v1", schemaName: "gamecult.first_call_note", schemaVersion: "gamecult.first_call_note.v1", schema: z.object({ title: z.string(), body: z.string() }), name: "title", }); const documents = new CultNetDocumentRegistry([defineCultNetDocumentBinding({ definition: noteDocument })]); async function firstCall() { const options = { runtimeId: "first-call", wireContract: "cultnet.schema.v0" } as const; const inbox = CultCache.builder().withDocumentType(noteDocument).build(); // the receiver's cache // Receiver: a loopback listener whose peer applies each document put to the inbox. const received = new Promise((resolve, reject) => { const server = createServer((socket) => { const peer = createTcpFramedCultNetPeer(socket, options); peer.on("invalidMessage", reject); peer.on("message", async (message) => { if (message.schemaVersion !== "cultnet.document_put_raw.v0") return; await documents.applyRawDocumentPutMessage(inbox, message); peer.close(); server.close(); resolve(); }); }).listen(0, "127.0.0.1", () => { // Sender: connect, build a typed put message, send it. const { port } = server.address() as { port: number }; const sender = createTcpFramedCultNetPeer(connect(port, "127.0.0.1"), options); sender.send( documents.createRawDocumentPutMessage(documents.get("note")!, "put-1", "note:hello", { title: "Hello", body: "sent over CultNet", }), ); received.finally(() => sender.close()); }); }); await received; return inbox.get(noteDocument, "note:hello"); } ``` ### Rust CultNet first call Source: [`packages/cultnet-rs/tests/first_call.rs`](../../packages/cultnet-rs/tests/first_call.rs), run by `cargo test --test first_call` in `packages/cultnet-rs`. More: `packages/cultnet-rs/README.md`. ```rust use std::net::{TcpListener, TcpStream}; use std::thread; use cultnet_rs::{ CultNetDocumentBinding, CultNetDocumentPutOptions, CultNetDocumentRegistry, CultNetWireContract, TcpFramedTransportConnection, create_tcp_framed_transport_profile, decode_cultnet_message_from_slice, encode_cultnet_message_to_vec, }; #[derive(Clone, Debug, PartialEq, Eq, DatabaseEntry)] #[cultcache(type = "note")] struct Note { #[cultcache(key = 0)] title: String, #[cultcache(key = 1)] body: String, } fn first_call() -> anyhow::Result { let mut documents = CultNetDocumentRegistry::new(); documents.register(CultNetDocumentBinding::for_entry_with_schema_id::( "gamecult.first_call_note.v1".to_string(), "gamecult.first_call_note.v1".to_string(), ))?; let profile = |runtime_id| create_tcp_framed_transport_profile(runtime_id, Default::default()); // Receiver: a loopback listener that applies the document put it receives to its own cache. let listener = TcpListener::bind("127.0.0.1:0")?; let port = listener.local_addr()?.port(); let receiver = thread::spawn({ let (documents, profile) = (documents.clone(), profile("receiver")); move || -> anyhow::Result { let (stream, _) = listener.accept()?; let frame = TcpFramedTransportConnection::new(stream, profile).receive()?; let message = decode_cultnet_message_from_slice(&frame.payload, CultNetWireContract::CultNetSchemaV0)?; let mut inbox = CultCache::new(); inbox.register_entry_type::()?; documents.apply_raw_document_put_message::(&mut inbox, &message) } }); // Sender: build a typed put message, encode it for the wire, send one frame. let put = documents.create_raw_document_put_message( "put-1", "note:hello", &Note { title: "Hello".to_string(), body: "sent over CultNet".to_string() }, CultNetDocumentPutOptions::default(), )?; let bytes = encode_cultnet_message_to_vec(&put, CultNetWireContract::CultNetSchemaV0)?; TcpFramedTransportConnection::new(TcpStream::connect(("127.0.0.1", port))?, profile("sender")) .send("schema", &bytes)?; receiver.join().expect("receiver thread") } ``` ### Python CultNet first call Source: [`packages/cultnet-py/tests/test_first_call.py`](../../packages/cultnet-py/tests/test_first_call.py), run by `python -m unittest tests.test_first_call` in `packages/cultnet-py` with both `src` and `../cultcache-py/src` on `PYTHONPATH` and `msgpack` installed. The socket pair stands in for a TCP connection. More: `packages/cultnet-py/README.md`. ```python import socket from dataclasses import asdict, dataclass from cultcache_py import CultCache, define_document_type from cultnet_py import ( apply_raw_document_record, document_put_raw, encode_frame, parse_message, read_frame, schema_document_map, ) @dataclass class Note: title: str body: str note_doc = define_document_type( "note", encode=lambda note: asdict(note), decode=lambda raw: Note(**raw), name="title", ) def first_call() -> Note: sender, receiver = socket.socketpair() # an in-process pipe; use socket.create_connection for TCP # Send: a typed document as a cultnet.document_put_raw.v0 message in one length-prefixed frame. put = document_put_raw( message_id="put-1", key="note:hello", schema_id=note_doc.catalog_entry().schema_id, stored_at="2026-01-01T00:00:00Z", payload=note_doc.encode_payload(Note("Hello", "sent over CultNet")), ) sender.sendall(encode_frame(put.to_bytes())) # Receive: read the frame, parse the message, apply its record to a second cache. message = parse_message(read_frame(receiver.makefile("rb"))) assert message.schema_version == "cultnet.document_put_raw.v0" inbox = CultCache.builder().register_document_type(note_doc).build() apply_raw_document_record(inbox, schema_document_map([note_doc]), message.body["document"]) sender.close() receiver.close() return inbox.get_required(note_doc, "note:hello") ``` ## Where next - Smallest durable mesh path: `src/GameCult.Mesh/docs/durable-node-quickstart.md`. - Application path, four chapters: `src/GameCult.Mesh/docs/getting-started/README.md`.