| Filename | Latest commit message | Latest commit date |
|---|---|---|
One conflict, at the end of Admit in CultCache.cs. The branch had added the IdsInMemoryOnly marking of a load's minted ids before Apply; main had turned the thread's hold into a Hold carrying Changes and Failures and run the journals right after Apply. Both are kept, in that order: the marking touches only the records and runs no user code, then Apply mints the Sequences, the hold records the changes, and the journals run under the gate. Id planning and Project do not collide. Ids are planned and applied first, Resolve (variants, then Project, then the name and index rules) reads the filled documents, land runs, and only then Apply mints. A Project getter that throws is caught by the same handler that undoes the id plans, so a refused admission changes no document and mints nothing. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> |
||
| .. | ||
| src/cultnet_py | ||
| tests | ||
| tools/GameCult.Caching.Benchmark | ||
| 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 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.