| Filename | Latest commit message | Latest commit date |
|---|---|---|
|
Some checks failed
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
CultMesh portability and performance / ubuntu-latest reactive semantics and leases (pull_request) Failing after 5m49s
CultMesh QUIC native / Verify committed binaries match SHA256SUMS (pull_request) Successful in 36s
Unscoped npm names are unclaimed and open to dependency confusion; cultcache-ts was already @gamecult/cultcache-ts. package.json names, intra-repo dependencies, package-lock, import specifiers (src, tests, samples, docs/start, first-call docs test), the workspace list in run-typescript-workspaces, the verify-eve module map and package READMEs now use the scoped names. Directory names, workspace paths in CI and release tag prefixes are unchanged. Historical cut docs and past CHANGELOG entries keep the old names. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com> |
||
| .. | ||
| src/cultnet_py | ||
| tests | ||
| tools/GameCult.Caching.Benchmark | ||
| CHANGELOG.md | ||
| pyproject.toml | ||
| README.md | ||
cultnet-py
cultnet-py is the Python CultNet runtime: schema-v0 MessagePack wire helpers,
4-byte big-endian framing, the raw client, database subscriptions, shard
catch-up and replication, simulation observations and consensus, witness
artifact bundles, the RUDP codec/session/socket transport, and the CultMesh
wire contracts that both a node and a bare peer have to agree on.
It depends on cultcache-py and msgpack; AES-GCM helpers on CultNetSecret
light up with the optional crypto extra. cultmesh-py builds on it. The
layering mirrors cultnet-rs and @gamecult/cultnet-ts.
Example
from datetime import UTC, datetime
from cultnet_py import (
CultNetSecret,
CultNetServerSecurityOptions,
CultNetRawClient,
CultNetSimulationObservation,
CultNetWitnessArtifactBundle,
apply_raw_snapshot,
apply_shard_log_response,
compute_simulation_claim_hash,
database_subscribe,
decode_frame,
encode_frame,
hello,
parse_message,
shard_catalog_request,
simulation_observation,
witness_artifact_bundle,
)
payload = hello(runtime_id="python-runtime").to_bytes()
message = parse_message(decode_frame(encode_frame(payload)))
client = CultNetRawClient("127.0.0.1", 3075)
catalog = client.fetch_schema_catalog(kinds=["wire_message"])
snapshot = client.fetch_snapshot_response(schema_ids=["cultnet.interop-note"])
shard_catalog = client.fetch_shard_catalog(schema_ids=["cultnet.interop-note"])
# With a local CultCache and the matching registered document definitions:
# applied = apply_raw_snapshot(cache, [note_doc], snapshot)
subscription = database_subscribe(subscription_id="ui", schema_ids=["cultnet.interop-note"])
shard_request = shard_catalog_request(message_id="shards", schema_ids=["cultnet.interop-note"])
shard_log = client.fetch_shard_log_response(shard_id="interop", shard_epoch=1, after_sequence=0)
# log_changes = apply_shard_log_response(cache, [note_doc], shard_log)
with client.subscribe_database(subscription_id="ui", schema_ids=["cultnet.interop-note"]) as live:
initial_snapshot = live.read_next_snapshot_response()
claim_hash = compute_simulation_claim_hash("frame:42", "subject:player-1", "hit")
observation = simulation_observation(
message_id="obs-1",
witness_runtime_id="python-runtime",
shard_id="interop",
shard_epoch=1,
frame=42,
subject_id="player-1",
claim_kind="hit",
claim_hash=claim_hash,
)
typed_observation = CultNetSimulationObservation.from_wire(observation.to_wire())
witness = witness_artifact_bundle(
bundle_id="bundle-1",
witness_kind="interop-proof",
captured_at="2026-06-13T00:00:00Z",
subject={"documentType": "cultnet.interop-note", "subjectId": "note:python"},
contracts=[{"role": "payload", "schemaId": "cultnet.interop-note"}],
artifacts=[{"role": "log", "uri": "cultcache://bundle-1/log", "mediaType": "text/plain"}],
provenance={"pipelineId": "interop", "runId": "run-1", "runtimeId": "python-runtime"},
)
typed_witness = CultNetWitnessArtifactBundle.from_wire(witness)
witness_payload = typed_witness.to_payload()
security = CultNetServerSecurityOptions.development()
token = CultNetSecret.create_session_token(
"318fb4b6-ff5e-4c4f-b911-d81807de53a8",
datetime(2035, 1, 1, tzinfo=UTC),
security,
session_version=1,
)
session = CultNetSecret.try_validate_session_token(token, security)
The raw-client request surface can ride RUDP when the peer advertises a
rudp:// endpoint and shares the connection id for that lane;
create_rudp_schema_transport(...) builds that transport, and
cultmesh_py.CultMesh.create_client(endpoint="rudp://...") selects it from an
endpoint string.
CultMesh wire contracts
cultnet_py.cultmesh_contracts carries the Verse catalog, peer exchange,
authority lease, stream negotiation, and committed simulation fact contracts.
They live here rather than in cultmesh-py for the same reason
CultMeshPeerCard lives in cultnet-rs: the interop peer has to speak them
without owning a node. cultmesh_py re-exports every one of them, so callers
that already import from there keep working.
RUDP
CultNetRudpSession is the shared reliable/unreliable/sequenced/ordered
session over the RUDP packet codec, with fragmentation above
max_fragment_bytes, a bounded reliable window, reliable expiry, and a
bounded fragment reassembly map that evicts the oldest stranded set and counts
it in fragment_sets_evicted rather than refusing new payloads. The
socket-backed transport, reconnect policy/controller, and reconnect loop follow
the Rust and TypeScript entrypoint pattern.
Interop peer
The peer can serve, dial, and probe the same raw-state interop lane used by the TypeScript, Rust, and C# test peers, and answers Verse catalog and peer exchange requests without a CultMesh node:
python -m cultnet_py.interop_peer serve --runtime-id python-peer --runtime-kind python --display-name "Python Peer" --agent-id python-agent --advertise-host 127.0.0.1 --tcp-port 3075 --discovery-port 4075 --discovery-group 239.77.44.11 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json
python -m cultnet_py.interop_peer dial --runtime-id python-client --runtime-kind python --display-name "Python Client" --agent-id python-client --target-host 127.0.0.1 --target-port 3075 --schema-path ..\cultnet-ts\integration\contracts\cultnet.interop-note.schema.json
python -m cultnet_py.interop_peer probe --runtime-id python-prober --discovery-port 4075 --discovery-group 239.77.44.11
packages/cultnet-ts/test/interop/cultnet-interop.test.ts drives it inside the
live TS/Rust/C#/Python peer ring.
Performance baseline
The package ships a lightweight benchmark for Python-owned hot paths. In a CultLib source checkout, it can also run the C# reference benchmark beside it:
python -m cultnet_py.benchmark --records 1000 --json
python -m cultnet_py.compare_csharp --records 1000 --json
It reports slot-indexed DatabaseEntry encode/decode throughput, framed
CultNet MessagePack parse throughput, raw snapshot application into a
registered cache, and public CultCache upsert/get throughput. The C# compare
command also runs packages/cultnet-py/tools/GameCult.Caching.Benchmark and
reports median Python-to-C# ratios for the shared public cache operations. It
uses three samples by default; pass --samples when you need a different
evidence shape. Treat the numbers as a local baseline, not a claim that Python
matches the C# reference in every workload.
Tests
$env:PYTHONPATH="$PWD\packages\cultcache-py\src;$PWD\packages\cultnet-py\src"
python -m unittest discover -s packages\cultnet-py\tests
The suite runs without cultmesh_py on the path. See
docs/python-runtime-parity.md for the
evidence map across the three Python packages.