CultLib/docs/start
Repository files (latest commit first)
Filename Latest commit message Latest commit date
Libby bb25bf04cb
Some checks are pending
CultMesh portability and performance / ubuntu-latest one-command multiplayer onboarding (pull_request) Waiting to run
CultMesh portability and performance / windows-latest one-command multiplayer onboarding (pull_request) Waiting to run
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Waiting to run
CultMesh portability and performance / windows-latest reactive semantics and leases (pull_request) Waiting to run
CultNet cross-runtime interop / C# / TypeScript / Rust / Python / Kotlin (pull_request) Waiting to run
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Waiting to run
CultMesh QUIC native / Build win32-x64 (pull_request) Has been skipped
CultMesh QUIC native / Build linux-x64 (pull_request) Has been skipped
CultMesh QUIC native / Release native binaries (pull_request) Has been skipped
docs/start: verbatim drift check for the C# and TypeScript first-call blocks
Both blocks now come from a snippet region in their test, and a second test
fails when docs/start/README.md stops showing it, as the Rust and Python
blocks already do. The TS block differs only in the package import specifier.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-11-05 09:43:55 +00:00
..
README.md docs/start: verbatim drift check for the C# and TypeScript first-call blocks 2026-11-05 09:43:55 +00:00

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 package; snippet not yet package; snippet not yet package; snippet not yet quickstart; run by scripts/verify-eve-getting-started.ps1
TypeScript first call, tested not yet not yet package; snippet not yet package; snippet not yet
Rust first call, tested native core; snippet not yet not yet package, examples; snippet not yet package; snippet not yet
Python first call, tested not yet not yet package; snippet not yet package; snippet not yet

For the whole picture of what each piece is for, see the package chooser in the top-level README.

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, which is what dotnet test tests/GameCult.Caching.Tests --filter FirstCallTests runs, so the snippet cannot drift without the test failing.

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<Item?> 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<Item>(handle.Key);       // by key
        var byName = reopened.GetByName<Item>("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, run by npm test in packages/cultcache-ts (or node --test dist-test/test/first-call.test.js after tsc).

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, 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.

#[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::<Item>()?;
    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::<Item>()?;
    reopened.add_generic_backing_store(SingleFileMessagePackBackingStore::new(&path));
    reopened.pull_all_backing_stores()?; // read it back
    let potion = reopened.get_required::<Item>("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, 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.

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")

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.