Add Python CultMesh client facade parity

This commit is contained in:
Meta Vens 2026-06-14 01:50:15 +01:00
commit bd50ecd15b
7 changed files with 74 additions and 3 deletions

View file

@ -17,6 +17,10 @@ CultCache, CultNet, and CultMesh. It is evidence, not confetti.
- Python CultMesh discovery client:
Fetches Verse catalogs and peer exchange responses from the live Python
endpoint over CultNet frames.
- Python CultMesh facade:
Exposes C#-matching client entrypoints for Verse discovery, peer exchange,
raw CultNet client construction, local nodes, catalogs, sessions, streams,
authority leases, and simulation fact commits.
- Python CultMesh committed simulation facts:
The live interop harness sends Python a simulation observation, receives a
consensus candidate, then requests the committed `gamecult.mesh.simulation_fact`

View file

@ -219,7 +219,7 @@ local prediction reconciliation:
```python
from cultcache_py import define_database_entry_type
from cultnet_py import CultNetClientAuthorityScope, CultNetRawClient
from cultmesh_py import CultMesh, CultMeshDiscoveryClient, CultMeshGameSessionOptions, peer_exchange_request
from cultmesh_py import CultMesh, CultMeshGameSessionOptions, peer_exchange_request
note_doc = define_database_entry_type("mesh.note", [("body", 0)])
node = CultMesh.start_node("mesh.cc", runtime_id="python-runtime")
@ -232,7 +232,7 @@ delete_message = node.delete_raw_message(note_doc, "note:2", shard_id="interop",
peers = CultMesh.create_peer_catalog()
response = peers.create_response(peer_exchange_request("pex-1", verse_id="local"))
client = CultMeshDiscoveryClient("127.0.0.1", 3075)
client = CultMesh.create_verse_discovery_client("127.0.0.1", 3075)
verses = client.fetch_verses(transport_version="cultmesh.v0")
mesh_peers = client.fetch_peers(verse_id="python-interop", roles=["read-replica"])
client.sync_peer_catalog(peers, verse_id="python-interop", roles=["read-replica"])

View file

@ -30,8 +30,10 @@ EXPECTED_EXPORTS: dict[str, tuple[str, ...]] = {
"cultmesh_py": (
"CultMesh",
"CultMeshGameSession",
"CultMeshPeerExchangeClient",
"CultMeshSimulationFact",
"CultMeshStreamCatalog",
"CultMeshVerseDiscoveryClient",
"simulation_fact_document",
),
}

View file

@ -1,4 +1,4 @@
from .client import CultMeshDiscoveryClient
from .client import CultMeshDiscoveryClient, CultMeshPeerExchangeClient, CultMeshVerseDiscoveryClient
from .facade import CultMesh
from .node import CultMeshNode, create_node
from .session import (
@ -53,6 +53,8 @@ __all__ = [
"CultMeshAuthorityLease",
"CultMeshAuthorityLeaseCatalog",
"CultMeshDiscoveryClient",
"CultMeshPeerExchangeClient",
"CultMeshVerseDiscoveryClient",
"CultMeshStreamCatalog",
"CultMeshStreamConsumerProfile",
"CultMeshStreamDescriptor",

View file

@ -114,3 +114,11 @@ class CultMeshDiscoveryClient:
limit=limit,
)
return catalog.apply_response(response)
class CultMeshVerseDiscoveryClient(CultMeshDiscoveryClient):
pass
class CultMeshPeerExchangeClient(CultMeshDiscoveryClient):
pass

View file

@ -2,6 +2,9 @@ from __future__ import annotations
from pathlib import Path
from cultnet_py import CultNetRawClient
from .client import CultMeshPeerExchangeClient, CultMeshVerseDiscoveryClient
from .node import CultMeshNode, create_node
from .session import CultMeshGameSession, CultMeshGameSessionOptions
from .simulation import CultMeshSimulationFactCommitter
@ -38,6 +41,24 @@ class CultMesh:
def create_stream_catalog() -> CultMeshStreamCatalog:
return CultMeshStreamCatalog()
@staticmethod
def create_verse_discovery_client(
host: str = "localhost",
port: int = 3075,
*,
timeout_seconds: float = 4.0,
) -> CultMeshVerseDiscoveryClient:
return CultMeshVerseDiscoveryClient(host, port, timeout_seconds)
@staticmethod
def create_peer_exchange_client(
host: str = "localhost",
port: int = 3075,
*,
timeout_seconds: float = 4.0,
) -> CultMeshPeerExchangeClient:
return CultMeshPeerExchangeClient(host, port, timeout_seconds)
@staticmethod
def create_simulation_fact_committer(node: CultMeshNode) -> CultMeshSimulationFactCommitter:
return CultMeshSimulationFactCommitter(node)
@ -48,3 +69,21 @@ class CultMesh:
options: CultMeshGameSessionOptions | None = None,
) -> CultMeshGameSession:
return CultMeshGameSession(node, options)
@staticmethod
def create_client(
host: str = "localhost",
port: int = 3075,
*,
timeout_seconds: float = 4.0,
) -> CultNetRawClient:
return CultNetRawClient(host, port, timeout_seconds)
@staticmethod
def connect_client(
host: str = "localhost",
port: int = 3075,
*,
timeout_seconds: float = 4.0,
) -> CultNetRawClient:
return CultNetRawClient(host, port, timeout_seconds)

View file

@ -54,6 +54,7 @@ from cultmesh_py import (
CultMeshAuthorityLease,
CultMeshAuthorityLeaseCatalog,
CultMeshDiscoveryClient,
CultMeshPeerExchangeClient,
CultMeshStreamCatalog,
CultMeshStreamConsumerProfile,
CultMeshStreamDescriptor,
@ -61,6 +62,7 @@ from cultmesh_py import (
CultMeshVerseCatalog,
CultMeshVerseCompatibility,
CultMeshVerseDescriptor,
CultMeshVerseDiscoveryClient,
peer_exchange_request,
simulation_fact_document,
verse_catalog_request,
@ -884,6 +886,20 @@ class CultCacheTests(unittest.TestCase):
self.assertIsInstance(CultMesh.create_peer_catalog(), CultMeshPeerCatalog)
self.assertIsInstance(CultMesh.create_authority_lease_catalog(), CultMeshAuthorityLeaseCatalog)
self.assertIsInstance(CultMesh.create_stream_catalog(), CultMeshStreamCatalog)
verse_client = CultMesh.create_verse_discovery_client("127.0.0.1", 4010, timeout_seconds=1.5)
peer_client = CultMesh.create_peer_exchange_client("127.0.0.1", 4011, timeout_seconds=1.5)
raw_client = CultMesh.create_client("127.0.0.1", 4012, timeout_seconds=1.5)
connected_client = CultMesh.connect_client("127.0.0.1", 4013, timeout_seconds=1.5)
self.assertIsInstance(verse_client, CultMeshVerseDiscoveryClient)
self.assertIsInstance(peer_client, CultMeshPeerExchangeClient)
self.assertIsInstance(verse_client, CultMeshDiscoveryClient)
self.assertIsInstance(peer_client, CultMeshDiscoveryClient)
self.assertIsInstance(raw_client, CultNetRawClient)
self.assertIsInstance(connected_client, CultNetRawClient)
self.assertEqual((verse_client.host, verse_client.port, verse_client.timeout_seconds), ("127.0.0.1", 4010, 1.5))
self.assertEqual((peer_client.host, peer_client.port, peer_client.timeout_seconds), ("127.0.0.1", 4011, 1.5))
self.assertEqual((raw_client.host, raw_client.port, raw_client.timeout_seconds), ("127.0.0.1", 4012, 1.5))
self.assertEqual((connected_client.host, connected_client.port, connected_client.timeout_seconds), ("127.0.0.1", 4013, 1.5))
def test_cultmesh_simulation_fact_uses_csharp_slot_contract(self) -> None:
import msgpack # type: ignore