One distribution named cultcache-py carried three libraries. Rust and TypeScript layer them as cultcache < cultnet < cultmesh; Python now does the same, one package per layer, module names unchanged so every `from cultmesh_py import ...` keeps working. Two imports ran against that layering and are resolved by moving code to where the dependency direction says it lives, not by adding a shim: - cultnet_py.interop_peer imported the Verse/peer catalog, authority lease, stream, and simulation-fact contracts from cultmesh_py. They are wire contracts a bare peer has to speak without owning a node, which is why cultnet-rs and cultnet-ts keep them in the net layer. They now live in cultnet_py.cultmesh_contracts; cultmesh_py re-exports them. - cultcache_py.benchmark imported cultnet_py. It measures framed CultNet parse beside cache paths, so it and compare_csharp move to cultnet_py. verify checks all three packages and moves to cultmesh_py. The single 6,693-line test module is split by what each test touches: a test using any cultmesh_py name lands in cultmesh-py, else cultnet_py in cultnet-py, else cultcache-py. Each suite runs with only its layer and the layers below on the path, so a test that reaches up fails to import rather than quietly binding the packages back together. 11 / 53 / 78 = the same 142. Workflows install all three; the publish job is one matrix over the three packages keyed by tag prefix. The TS test harnesses put all three src directories on PYTHONPATH. cultcache-py bumps to 0.2.0 because it no longer contains cultnet_py or cultmesh_py; the new packages start at 0.1.0. Nothing was ever published, so no PyPI consumer moves. cultcache-py 11 tests, path: cultcache-py/src cultnet-py 53 tests, path: + cultnet-py/src cultmesh-py 78 tests, path: + cultmesh-py/src python -m cultmesh_py.verify -> ok, all five checks cultnet_py.interop_peer imports with cultmesh_py absent Not run here: the cultnet-ts interop ring and cultcache-ts store matrix need dotnet/kotlin peers; their PYTHONPATH change is four lines. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
539 lines
33 KiB
Markdown
539 lines
33 KiB
Markdown
# CultNet Transport Parity Map
|
|
|
|
CultNet payload parity is not enough. The runtimes can already trade schema-v0
|
|
MessagePack documents through the interop lane, and the shared
|
|
`cultnet.transport.rudp.v0` packet/session language now exists across C#,
|
|
TypeScript, Rust, Python, and Kotlin. The remaining split is service adoption:
|
|
C# still carries the production LiteNetLib path, and older TCP-framed or
|
|
WebSocket-like bodies still exist for local services and harnesses.
|
|
|
|
This document is the working map for promoting transport into CultNet itself so
|
|
C#, TypeScript, Rust, Python, Kotlin, and future runtimes can speak the same
|
|
network language. Runtime role boundaries live in
|
|
[runtime-parity-scope.md](runtime-parity-scope.md).
|
|
|
|
## Objective
|
|
|
|
CultNet owns cross-runtime transport semantics:
|
|
|
|
- peers discover each other without runtime-specific side channels;
|
|
- connection/session lifecycle is described the same way in every runtime;
|
|
- reliable ordered delivery is portable and testable across runtimes;
|
|
- unreliable and sequenced realtime channels can exist beside reliable state
|
|
sync without changing payload contracts;
|
|
- LiteNetLib becomes one adapter, not the hidden specification.
|
|
|
|
## Current Mechanism
|
|
|
|
| Runtime | Current data transport | Discovery | Transport authority today |
|
|
| --- | --- | --- | --- |
|
|
| C# `GameCult.Networking` | LiteNetLib UDP `NetManager` / `NetPeer`; sends legacy union messages and schema-v0 messages with `DeliveryMethod.ReliableOrdered`; single-peer RUDP socket transport exists in the library | LiteNetLib connection requests and app-level peer/catalog surfaces | Production LiteNetLib adapter profile plus UDP socket binding for the shared RUDP reliability owner |
|
|
| C# interop peer | Schema-v0 MessagePack over TCP-framed or shared RUDP transport | UDP multicast probe/announce with TCP and RUDP transport profiles | Test harness transport parity |
|
|
| TypeScript `cultnet-ts` | `CultNetPeer` over any Node `Duplex`, TCP-framed transport, or single-peer RUDP socket transport; interop uses TCP | UDP multicast probe/announce in the interop peer | First UDP socket binding for the shared RUDP reliability owner |
|
|
| Rust `cultnet-rs` | Interop example serves and dials schema-v0 MessagePack over TCP-framed or shared RUDP transport; single-peer RUDP socket transport exists in the library | UDP multicast probe/announce with TCP and RUDP transport profiles | UDP socket binding for the shared RUDP reliability owner |
|
|
| Python `cultnet-py` | TCP sockets with 4-byte length-prefixed MessagePack frames for local CultMesh/CultNet server and client; single-peer RUDP socket transport exists in the library | Endpoint lists and CultMesh peer/Verse catalogs | UDP socket binding for the shared RUDP reliability owner |
|
|
| Kotlin `cultmesh-kotlin` | Channel-aware WebSocket transport connection for reliable ordered `schema` frames; single-peer RUDP socket transport exists in the library; interop CLI serves and dials schema-v0 MessagePack over TCP-framed or shared RUDP transport | CultMesh Verse and peer catalogs, plus endpoint lists carried in schema-v0 catalog messages | WebSocket/TCP adapters plus UDP socket binding for the shared RUDP reliability owner, with build-script and TypeScript harness proof |
|
|
|
|
The live split is therefore: payload language and the native RUDP packet/session
|
|
language now converge across the targeted runtimes, while production service
|
|
adoption still varies by runtime and older TCP/LiteNetLib/WebSocket bodies have
|
|
not all been lowered behind the shared transport port.
|
|
|
|
## Invariants
|
|
|
|
- `cultnet.schema.v0` payloads are transport-neutral. Message schemas must not
|
|
know whether they rode TCP, LiteNetLib, WebSocket, or CultNet reliable UDP.
|
|
- Discovery is its own concern. A discovery packet may advertise transport
|
|
endpoints and capabilities, but it does not own reliable delivery semantics.
|
|
- The first portable realtime channel must provide reliable ordered delivery,
|
|
because schema catalogs, snapshots, shard logs, auth/session messages, and
|
|
mutation forwarding already depend on ordered request/response behavior.
|
|
- Transport capability negotiation must happen before higher-level CultMesh
|
|
state claims a peer is usable for realtime work.
|
|
- Auth/session primitives bind to a connection, but credential encryption and
|
|
session token formats remain owned by CultNet security, not by UDP packet
|
|
framing.
|
|
- Backpressure, resend pressure, MTU limits, fragmentation, ping/timeout, and
|
|
reconnect posture are transport-owned signals. They must not leak upward as
|
|
ad hoc message-level retry loops.
|
|
|
|
## Intended Ownership
|
|
|
|
`cultnet.transport.v0` owns the transport contract:
|
|
|
|
- endpoint description: protocol, host, port, path/group when relevant;
|
|
- channel descriptions: reliable ordered, reliable unordered, sequenced, and
|
|
unreliable;
|
|
- packet envelope: connection id, sequence/ack state, channel id, fragment id,
|
|
payload encoding, and payload bytes;
|
|
- session lifecycle: connect, accept/reject, disconnect reason, timeout, ping,
|
|
reconnect state, and feature negotiation;
|
|
- flow control: send queue limits, resend window, fragmentation limit, and
|
|
backpressure reporting.
|
|
|
|
Adapters own socket mechanics:
|
|
|
|
- `cultnet.transport.tcp_framed.v0`: existing 4-byte length-prefixed MessagePack
|
|
stream, reliable ordered only;
|
|
- `cultnet.transport.litenetlib.v0`: C# adapter preserving current
|
|
`GameCult.Networking` behavior while exposing CultNet channel semantics;
|
|
- `cultnet.transport.websocket.v0`: browser/mobile-friendly stream adapter,
|
|
reliable ordered only;
|
|
- `cultnet.transport.rudp.v0`: native cross-runtime reliable UDP transport,
|
|
replacing LiteNetLib as the portable realtime specification.
|
|
|
|
Payload handlers own documents:
|
|
|
|
- `cultnet.login.v0`, `cultnet.register.v0`, `cultnet.verify.v0`, and
|
|
`cultnet.login_success.v0` are security/session messages;
|
|
- `cultnet.schema_catalog_*`, snapshot, shard catalog, shard log, subscription,
|
|
and simulation messages remain schema-v0 payloads;
|
|
- no payload handler decides packet resend, channel ordering, or fragmentation.
|
|
|
|
## Cut Line
|
|
|
|
- Do not build a Python-only LiteNetLib clone.
|
|
- Do not let the TCP interop harness become the implied production transport.
|
|
- Do not hide transport capability in loose endpoint strings.
|
|
- Do not add message-level retry compensators for missing reliable UDP.
|
|
- Do not put UDP packet mechanics into CultMesh database/session code.
|
|
- Do not treat C# LiteNetLib behavior as optional folklore; mine it for the
|
|
required semantics, then express those semantics in CultNet-owned contracts.
|
|
|
|
## First Portable Contract
|
|
|
|
The first shippable slice should be a transport profile document that every
|
|
runtime can advertise through schema catalogs and hello/peer descriptors:
|
|
|
|
```text
|
|
cultnet.transport_profile.v0
|
|
runtimeId
|
|
transports[]
|
|
transportId
|
|
protocol
|
|
endpoint
|
|
wireContracts[]
|
|
channels[]
|
|
channelId
|
|
delivery
|
|
ordering
|
|
maxPayloadBytes
|
|
maxFragmentBytes
|
|
```
|
|
|
|
This is not the reliable UDP implementation. It is the shared language that
|
|
lets the runtimes stop guessing what kind of pipe a peer is offering.
|
|
|
|
## Implementation Sequence
|
|
|
|
1. Add `cultnet.transport_profile.v0` as a shared contract in C#, TS, Rust,
|
|
Python, and Kotlin catalogs.
|
|
2. Make every runtime advertise its current transport honestly:
|
|
`tcp_framed`, `litenetlib`, or `websocket`.
|
|
3. Add a cross-runtime transport-profile interop check so parody surfaces fail
|
|
loudly.
|
|
4. Introduce a narrow transport port in each runtime:
|
|
`connect`, `accept`, `send(channel, payload)`, `receive`, `close`, `stats`.
|
|
5. Put current TCP/LiteNetLib/WebSocket bodies behind that port.
|
|
6. Build `cultnet.transport.rudp.v0` once against the shared port and prove it
|
|
with C#/TS/Rust/Python/Kotlin ping, loss, reordering, fragmentation,
|
|
reconnect, and schema-message tests.
|
|
|
|
Current progress:
|
|
|
|
- Steps 1-3 are live in C#, TypeScript, Rust, Python, and Kotlin.
|
|
- C# now has the shared `tcp_framed` transport profile helper plus a
|
|
`TcpFramedTransportConnection` with schema-channel `SendAsync`,
|
|
`ReceiveAsync`, and transfer stats. The C# interop peer advertises and uses
|
|
that shared port instead of owning raw TCP frame I/O directly. The C# interop
|
|
peer now also advertises and serves the full schema-v0 flow over
|
|
`interop-rudp`, and its dial path can use the shared RUDP transport while
|
|
keeping TCP-framed as compatibility.
|
|
- C# now has a `litenetlib` transport profile helper, and the production
|
|
`Client`/`Server` expose profiles for the LiteNetLib lane. The profile names
|
|
both the modern reliable ordered `schema` channel and the legacy reliable
|
|
ordered `legacy` union-message channel, so LiteNetLib is described as an
|
|
adapter instead of remaining the hidden default. `NetPeer` send helpers now
|
|
route through `LiteNetLibTransportConnection`; `Client` and `Server` now keep
|
|
a per-peer adapter and route their own schema/legacy sends through it as
|
|
well. Inbound payload classification and transfer stats also belong to that
|
|
channel-aware adapter before dispatching schema-v0 or legacy messages. The
|
|
C# database, simulation-observation, Verse-discovery, and peer-exchange
|
|
service wrappers now enter their built-in handlers through
|
|
`CultNetServerPeer`, a transport-aware server peer context. Responses send
|
|
through that context instead of reaching around the per-peer adapter with
|
|
direct `NetPeer` extension sends. The C# schema-client port now also has an
|
|
RUDP adapter; shard log fetch, shard snapshot fetch, and shard write
|
|
forwarding select it for `rudp://` primary endpoints and keep LiteNetLib for
|
|
`cultnet://` endpoints, so those service clients no longer need custom
|
|
injection to leave the LiteNetLib lane.
|
|
- TypeScript has the first narrow transport connection port:
|
|
`TcpFramedTransportConnection` owns length-prefixed frame delivery, exposes
|
|
frame/close/error events, `send(channel, payload)`, `close`, and transfer
|
|
stats, and destroys its owned stream on close. `CultNetPeer` can still run
|
|
over either the compatibility `Duplex` stream path or the transport
|
|
connection path, while the TypeScript interop peer now accepts and dials TCP
|
|
peers through `TcpFramedTransportConnection` so the parity harness exercises
|
|
the shared port instead of raw socket framing. `createTcpFramedCultNetPeer`
|
|
gives application callers the same transport-first path without hand-building
|
|
the profile and adapter; raw `Duplex` peer construction is now explicitly the
|
|
compatibility lane.
|
|
- Python now has the same `tcp_framed` transport profile helper plus a
|
|
synchronous `TcpFramedTransportConnection` with `send`, `receive`, `close`,
|
|
and stats. `CultNetRawClient`, database subscriptions, and the local
|
|
CultMesh server connection loop send/read through that port instead of owning
|
|
frame I/O directly. The Python TCP interop peer now also accepts and dials
|
|
through that port, so the parity harness no longer keeps a raw framing
|
|
side-channel for Python's schema lane.
|
|
- Rust now has the same `tcp_framed` transport profile helper plus a
|
|
`TcpFramedTransportConnection` with schema-channel `send`, `receive`, and
|
|
transfer stats. The Rust interop peer advertises TCP and RUDP profiles and
|
|
serves/dials schema-v0 through the shared RUDP path without giving RUDP its
|
|
own document semantics.
|
|
- Kotlin now has a `websocket` transport profile helper plus
|
|
`CultNetWebSocketTransportConnection`, which wraps the older binary
|
|
WebSocket client behind the same `CultNetTransportFrame` and stats shape for
|
|
reliable ordered `schema` frames. The adapter now also has `sendSchemaMessage`
|
|
and `receiveSchemaMessage` sugar so JVM callers can exchange schema-v0
|
|
MessagePack messages without hand-parsing transport frames.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share the first
|
|
`cultnet.transport.rudp.v0` packet codec fixture: the same reliable ordered
|
|
fragmented data packet encodes to the same bytes in every runtime. This is not
|
|
the RUDP runtime yet; it is the binary packet language the runtimes must
|
|
converge on before resend loops, windows, and timeout behavior are allowed to
|
|
claim parity.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share the first deterministic RUDP
|
|
reliability state machine: connect/accept handshake, packet-level
|
|
ack/ack-mask accounting, reliable resend scheduling, duplicate suppression,
|
|
and reliable ordered channel delivery. It is in-memory and socket-free so the
|
|
behavior can be tested before any runtime binds it to UDP I/O.
|
|
- C#, TypeScript, Rust, Python, and Kotlin now have socket-backed RUDP transport
|
|
connections that bind a single UDP peer to the shared RUDP session. TypeScript
|
|
emits the same `frame` events as the TCP-framed transport and can carry
|
|
`CultNetPeer` schema messages over reliable ordered `schema` frames; C#,
|
|
Rust, Python, and Kotlin expose the same transport frame/stats shape through
|
|
synchronous socket polling.
|
|
- The TypeScript interop harness now proves bidirectional cross-runtime RUDP
|
|
socket exchange with C#, Kotlin, Python, and Rust: TypeScript can dial those
|
|
UDP peers, C#/Kotlin/Python/Rust can dial a TypeScript UDP peer, and all sides
|
|
exchange reliable ordered `schema` frames through the shared handshake and
|
|
packet language.
|
|
- The same harness now proves schema-v0 MessagePack messages over RUDP in both
|
|
directions for C#, Kotlin, Python, and Rust peers against TypeScript. Those tests
|
|
send `cultnet.schema_catalog_request.v0` payloads through reliable ordered
|
|
`schema` frames and validate `cultnet.hello.v0` responses with advertised
|
|
RUDP transport profiles where the responding runtime owns them, including the
|
|
shared `reconnectPolicy` field.
|
|
- The same harness now proves reliable ordered `schema` delivery survives one
|
|
dropped data packet in both directions between TypeScript and C#/Kotlin/Python/Rust.
|
|
The drops happen below the transport connection through a UDP bridge, so the
|
|
recovery is RUDP resend/ack behavior, not application-level retry.
|
|
- The same harness now proves reliable ordered `schema` delivery survives
|
|
reordered data packets in both directions between TypeScript and
|
|
C#/Kotlin/Python/Rust. The bridge delays one packet below the transport, sends
|
|
the following packet first, and then releases the held packet; delivered frames
|
|
still surface in sequence.
|
|
- C#, TypeScript, Rust, Python, and Kotlin now also guard against control-packet
|
|
sequence bleed in ordered channels: a received `ack`/control packet may
|
|
advance the global acknowledgement window, but it must not block the next
|
|
ordered `schema` frame for that channel. Runtime-local RUDP tests prove the
|
|
data-control-data sequence in each implementation.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share the first RUDP
|
|
fragmentation implementation: the session can split oversized reliable
|
|
ordered payloads into fragment packets, reassemble them before delivery, and
|
|
the socket transport can carry fragmented `schema` frames when
|
|
`maxFragmentBytes` is configured.
|
|
- The TypeScript interop harness now proves fragmented reliable ordered
|
|
`schema` frames in both request and response direction between TypeScript and
|
|
C#/Kotlin/Python/Rust one-shot UDP peers.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share a transport-level RUDP
|
|
disconnect primitive: sessions create and consume `disconnect` packets with
|
|
raw reason bytes, socket transports surface the remote reason, and the
|
|
TypeScript interop harness proves reason propagation from C#/Kotlin/Python/Rust
|
|
one-shot UDP peers.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share transport-level ping/pong
|
|
and receive-timeout state: sessions create pings, answer pings with pongs,
|
|
expose pong payloads, and mark themselves disconnected after a configured
|
|
silent receive window. The TypeScript interop harness proves ping/pong payload
|
|
echo against C#/Kotlin/Python/Rust UDP peers; local runtime tests prove timeout
|
|
state transitions.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share bounded reliable-send
|
|
backpressure for RUDP sessions. At most 32 reliable packets are admitted to
|
|
the wire at once; later fragments remain in a session-owned FIFO and
|
|
acknowledgements promote the next packets. Receivers emit a cumulative ACK
|
|
for packets inside the 32-sequence ACK horizon and an exact ACK for an older
|
|
retransmit, so unrelated unreliable traffic cannot strand reliable work.
|
|
This keeps the send frontier well
|
|
inside the 4,096-sequence receive history instead of blasting an entire large
|
|
document and hoping the UDP buffers impersonate flow control.
|
|
`maxPendingReliablePackets` bounds both queued and in-flight packets before a
|
|
reliable frame is accepted. Socket transports expose an explicit reliable
|
|
flush where the runtime supports one, so callers that require confirmed
|
|
transport delivery do not substitute a fixed sleep. TCP-framed profiles do
|
|
not publish the field because they do not own the RUDP pending-reliable queue.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share a portable
|
|
`cultnet.reconnect_policy.v0` document and deterministic exponential delay
|
|
helper, and RUDP transport profiles advertise that policy under
|
|
`reconnectPolicy`. This gives discovery the same policy language for
|
|
reconnect posture.
|
|
- TypeScript, C#, Rust, Python, and Kotlin now share a portable reconnect
|
|
controller shape over that policy: failed connection attempts produce the
|
|
same attempt number, delay, next-attempt time, exhaustion signal, and
|
|
successful-connection reset semantics. This is the orchestration primitive
|
|
service loops must use; it does not yet claim every production daemon or
|
|
socket path automatically redials through it.
|
|
- The C# LiteNetLib `Client` reconnect loop now consumes
|
|
`CultNetReconnectController` directly instead of owning a private attempt
|
|
counter and duplicate backoff constants. It still owns LiteNetLib socket
|
|
mechanics and UI-facing reconnect state; the controller owns retry attempt
|
|
scheduling.
|
|
- C# now has an `ICultNetSchemaClient` service-client port for schema-v0
|
|
request/response bodies. Verse discovery, peer exchange, shard-log fetch,
|
|
shard-snapshot fetch, and shard write forwarding clients consume the port and
|
|
choose the native RUDP schema client for `rudp://` endpoints while preserving
|
|
LiteNetLib for `cultnet://` endpoints. This moves transport choice below
|
|
service payload ownership without claiming every daemon uses native RUDP yet.
|
|
- C# now also has a poll-driven `CultNetRudpReconnectLoop` for the native RUDP
|
|
socket transport. Caller-owned game/service loops report closure, ask
|
|
`ReconnectIfDue(nowMs)` inside their own scheduler, and the shared controller
|
|
owns retry delay/exhaustion state while the caller-owned factory opens the
|
|
next `CultNetRudpSocketTransportConnection`.
|
|
- C# now has `CultNetRudpSocketTransportServer`, a multi-peer UDP listener that
|
|
demultiplexes remote endpoints into separate RUDP sessions and delivers
|
|
`(peer, frame)` records to higher service owners. This is the production
|
|
server substrate cut: UDP/session ownership is no longer limited to the
|
|
single-peer interop/client helper, but authentication and game-session
|
|
authority still remain above the transport.
|
|
- C# now also has `RudpCultNetSchemaServer`, a multi-peer schema-v0 service
|
|
host over that listener. Service code can register `OnCultNet<T>` handlers
|
|
and reply through `peer.SendCultNet(...)` without owning UDP demux or RUDP
|
|
session state. Database, Verse discovery, peer exchange, and simulation
|
|
bridges still need to adopt the shared schema-server peer port before the
|
|
old LiteNetLib `Server` bridge can be demoted.
|
|
- C# `GameCult.Mesh` now mirrors the branded RUDP facade shape used by
|
|
TypeScript, Python, Kotlin, and Rust: it parses `rudp://host:port` endpoints,
|
|
creates RUDP client/server transports from endpoint or peer-card contact
|
|
hints, and can create a RUDP client from the first peer authorized by the
|
|
authority lease catalog for a Verse role. The facade can also return a
|
|
connected RUDP client from a direct endpoint, peer card, or authorized peer
|
|
lookup, so schema-message helpers can ride the RUDP pipe without caller-side
|
|
handshake ceremony. RUDP packet/session behavior remains owned by
|
|
`GameCult.Networking`.
|
|
- TypeScript now has a `CultNetRudpReconnectLoop` that consumes the shared
|
|
controller while keeping socket construction caller-owned. A closed RUDP
|
|
transport schedules the next attempt with the portable policy, opens a fresh
|
|
caller-provided transport when the controller says it may retry, and resets
|
|
attempt state when the caller marks the connection established.
|
|
- Python now has the same `CultNetRudpReconnectLoop` shape for synchronous
|
|
callers: the caller-owned receive loop reports closure, the shared controller
|
|
owns retry delay/exhaustion state, and the caller-provided factory opens the
|
|
next `CultNetRudpSocketTransportConnection`.
|
|
- Python raw CultNet clients now consume a caller-provided
|
|
`CultNetSchemaTransport` factory for request/response and database
|
|
subscription flows. The default factory opens the existing TCP-framed schema
|
|
transport, but the raw client no longer owns inline socket setup as its
|
|
request truth. Python now also exposes `create_rudp_schema_transport(...)`,
|
|
which handshakes a socket-backed RUDP client and gives `CultNetRawClient` the
|
|
same reliable ordered `schema` request path over `cultnet.transport.rudp.v0`;
|
|
`CultMesh.create_client(endpoint="rudp://...")` is the branded ergonomic
|
|
helper for that path.
|
|
- Python's branded `CultMesh` facade now creates schema catalogs, built-in
|
|
schema catalogs, and shard catalogs by delegating to the existing
|
|
`cultnet_py` owners, matching the TypeScript/Kotlin/Rust entrypoint pattern
|
|
without creating a second catalog authority. The facade now also parses
|
|
`rudp://host:port` endpoints and creates RUDP client/server transports from
|
|
endpoint or peer-card contact hints while leaving packet/session semantics in
|
|
`cultnet_py`. Python peer catalogs also expose authorized-peer lookup, and
|
|
the branded facade can create a RUDP client from the first peer authorized by
|
|
the authority lease catalog for a Verse role. `CultMeshLocalServer` now also
|
|
starts a RUDP listener beside the TCP-framed listener by default and advertises
|
|
both transport profiles in `cultnet.hello.v0`; the RUDP listener keeps schema
|
|
request handling in the same server authority while demultiplexing per-remote
|
|
`CultNetRudpSession` state. The Python interop peer now also serves and dials
|
|
schema-v0 over `interop-rudp`, advertises TCP-framed and RUDP in discovery and
|
|
hello, and caps interop RUDP schema frames into 1024-byte fragments so catalog
|
|
responses stay inside UDP datagram limits while remaining session-owned.
|
|
- Rust now has a poll-driven `CultNetRudpReconnectLoop` for substrate callers:
|
|
service loops report closure, ask `reconnect_if_due(now_ms)` inside their own
|
|
reactor, and the shared controller owns retry delay/exhaustion state while the
|
|
caller-owned factory opens the next socket transport.
|
|
- Kotlin now has the same RUDP reconnect-loop shape for JVM/Android callers:
|
|
the app-owned receive loop reports closure, `CultNetRudpReconnectLoop`
|
|
schedules retry through `CultNetReconnectController`, and the caller-provided
|
|
factory opens the next `CultNetRudpSocketTransportConnection`.
|
|
- Kotlin now mirrors the C#/TypeScript/Python local authority lease gate:
|
|
peer cards may advertise `authorityLeaseId`, but `CultMeshAuthorityLeaseCatalog`
|
|
owns role/shard/time authorization so discovery contact cannot impersonate
|
|
trust.
|
|
- TypeScript CultMesh local catalogs now expose the same practical discovery
|
|
ergonomics as the other runtimes: sorted local views, direct `get(...)`
|
|
lookup, unsubscribe-able `watch(...)` callbacks for Verse and peer catalogs,
|
|
watch callbacks for authority leases and stream declarations/frame cursors,
|
|
plus lease listing and lookup beside the authority check. The branded
|
|
`CultMesh` facade also creates schema catalogs, built-in schema catalogs, and
|
|
shard catalogs by delegating to the existing `cultnet-ts` owners, so callers
|
|
do not have to leave the CultMesh entrypoint for schema discovery or shard
|
|
topology. The facade now also parses `rudp://host:port` endpoints and creates
|
|
RUDP client/server transports from endpoint or peer-card contact hints while
|
|
leaving packet/session semantics in `cultnet-ts`. TypeScript now also mirrors
|
|
Kotlin's authorized-peer lookup and RUDP client helper, so peer cards remain
|
|
contact hints until the authority lease catalog authorizes the requested
|
|
Verse role. The facade can now also create a connected `CultNetPeer` from a
|
|
direct RUDP endpoint, peer card, or authorized peer lookup, so schema and shard
|
|
catalog request helpers ride the RUDP transport without callers hand-wrapping
|
|
the socket transport. The TypeScript interop peer now also serves and dials
|
|
the schema-v0 interop flow over `interop-rudp` while keeping TCP-framed as the
|
|
compatibility lane; discovery and hello responses advertise both profiles.
|
|
- Kotlin now has the same full interop RUDP proof shape as the other targeted
|
|
runtimes: the JVM CLI can serve schema-v0 hello/catalog/snapshot/mutation/fire
|
|
over `interop-rudp`, and it can dial a TypeScript full peer over the same
|
|
shared RUDP connection id and reliable ordered `schema` channel. TCP-framed
|
|
remains a compatibility lane for the interop peer; WebSocket remains the
|
|
JVM/Android-friendly client transport surface.
|
|
- TypeScript now also has a `CultNetSchemaCatalog` for remote schema
|
|
descriptors plus `CultNetPeer` request/fetch/sync helpers for
|
|
`cultnet.schema_catalog_request.v0` / `cultnet.schema_catalog_response.v0`.
|
|
`CultNetSchemaRegistry` remains the local definition owner; the catalog owns
|
|
imported descriptors so callers do not treat raw response objects as state.
|
|
- TypeScript now has the matching `CultNetShardCatalog` and peer
|
|
request/fetch/sync helpers for
|
|
`cultnet.shard_catalog_request.v0` / `cultnet.shard_catalog_response.v0`.
|
|
Shard descriptors can be filtered by schema id and record key without
|
|
requiring callers to inspect raw response maps. The built-in schema registry
|
|
also advertises the shard catalog request/response wire-message schemas, so
|
|
discovery matches the helper surface.
|
|
- Kotlin now mirrors the CultMesh stream catalog shape for Android-adjacent
|
|
media, sensor, tensor, and byte streams: stream declaration, clock metadata,
|
|
preferred body transports, consumer negotiation, copy-budget classification,
|
|
max in-flight frame pressure, latest-frame cursors, and watch callbacks for
|
|
stream declarations plus latest-frame publication.
|
|
- Kotlin now has a branded `CultMesh` facade matching the C#/TypeScript/Python
|
|
entrypoint pattern for local nodes, schema catalogs, shard catalogs, Verse
|
|
catalogs, peer catalogs, authority lease catalogs, stream catalogs, and RUDP
|
|
client/server construction. Kotlin also parses advertised
|
|
`rudp://host:port` endpoints and can build a RUDP client directly from a peer
|
|
card contact hint or the first peer authorized by the authority lease catalog.
|
|
The facade can now also return a connected RUDP client from a direct endpoint,
|
|
peer card, or authorized peer lookup, so schema-message transport helpers can
|
|
use RUDP without caller-side handshake ceremony. The facade delegates to the
|
|
same owners as the top-level helpers instead of creating a second authority
|
|
path.
|
|
- Kotlin WebSocket and RUDP transports now expose catalog fetch/sync helpers for
|
|
schema and shard catalogs. The helpers send standard schema-v0 requests,
|
|
require the matching response schema, and apply through the caller's catalog,
|
|
giving JVM/Android callers the same client ergonomics Python already had
|
|
without making the transport own catalog truth. Both adapters implement the
|
|
shared `CultNetSchemaMessageTransport` shape so the helper logic has one
|
|
owner across Kotlin stream and RUDP paths.
|
|
- Kotlin now also has a built-in schema catalog factory on the branded
|
|
`CultMesh` surface. It advertises the Kotlin-supported CultNet/CultMesh
|
|
request and response message descriptors, including schema catalog, shard
|
|
catalog, shard log, Verse catalog, peer exchange, and the shared transport
|
|
profile contract. The branded factory accepts the same schema-id, kind, and
|
|
inline-schema filters as the catalog request path, while still returning
|
|
ordinary `CultNetSchemaCatalog` state. Descriptors for contracts already
|
|
present in the canonical schema set use those schema ids and content hashes;
|
|
Kotlin-local descriptor bodies use schema-version ids until shared schema
|
|
files exist. Transports do not own catalog truth.
|
|
- Kotlin authority lease catalogs now also expose watch callbacks, so JVM and
|
|
Android clients can react to lease changes without treating peer-card contact
|
|
hints as trust. Kotlin peer catalogs also expose authorized-peer lookup
|
|
helpers, and the branded `CultMesh` facade can create a RUDP client from the
|
|
first peer authorized for a Verse role while still delegating the trust check
|
|
to the authority lease catalog.
|
|
- Rust now has a small `CultMesh` facade for its substrate role: peer cards,
|
|
sorted peer catalogs, authority lease catalogs, schema registry factories,
|
|
shard catalog factories, `rudp://host:port` endpoint parsing, and RUDP
|
|
client/server construction from endpoint, peer-card contact hints, or the
|
|
first peer authorized by the authority lease catalog for a Verse role. The
|
|
facade delegates to the existing Rust schema registry, shard catalog, and RUDP
|
|
socket transport owners; discovery contact remains separate from trust, which
|
|
is still gated by the authority lease catalog. The facade can now also return
|
|
a connected RUDP client from a direct endpoint, peer card, or authorized peer
|
|
lookup. Rust RUDP transports now also send and receive typed
|
|
`cultnet.schema.v0` messages on the reliable ordered `schema` channel, so
|
|
callers do not have to hand-encode MessagePack payloads or hand-roll the
|
|
client handshake before using the shared RUDP pipe.
|
|
- Rust now also has substrate-shaped shard catalog helpers:
|
|
`CultNetShardDescriptor`, `CultNetShardCatalog`, request filtering by schema
|
|
id and record key, response creation, response application, and built-in
|
|
schema registry entries for `cultnet.shard_catalog_request.v0` /
|
|
`cultnet.shard_catalog_response.v0`. This gives Rust the same topology
|
|
vocabulary without claiming stream catalog or production game-session server
|
|
ownership.
|
|
- The remaining adoption work beyond the current role-scoped parity claim is
|
|
to finish production service-body migration behind the shared transport
|
|
ports, then wire any remaining daemon/socket reconnect loops through the
|
|
shared controller. C# built-in LiteNetLib service wrappers now register
|
|
transport-aware `CultNetServerPeer` listeners instead of requiring each bridge
|
|
to bounce through `NetPeer`, TypeScript exposes a transport-first TCP peer
|
|
helper, C# schema-v0 service clients consume `ICultNetSchemaClient`, and the
|
|
core schema-message RUDP matrix is live in the TypeScript interop gate;
|
|
broader service adoption remains the unclaimed layer.
|
|
|
|
## RUDP Packet Contract V0
|
|
|
|
`cultnet.transport.rudp.v0` packets start with a fixed binary header, followed
|
|
by a UTF-8 channel id and payload bytes. All integer fields are unsigned
|
|
big-endian. The first deterministic fixture is live in TypeScript, C#, Rust,
|
|
Python, and Kotlin; every future runtime must port it byte-for-byte before
|
|
runtime-specific resend loops are allowed to claim parity.
|
|
|
|
| Offset | Size | Field | Notes |
|
|
| --- | ---: | --- | --- |
|
|
| 0 | 4 | magic | ASCII `CNR0` |
|
|
| 4 | 1 | version | `0` |
|
|
| 5 | 1 | packet type | `1=connect`, `2=accept`, `3=data`, `4=ack`, `5=ping`, `6=pong`, `7=disconnect` |
|
|
| 6 | 1 | flags | bit 0 reliable, bit 1 ordered, bit 2 sequenced, bit 3 fragmented |
|
|
| 7 | 1 | header bytes | fixed header plus channel id bytes |
|
|
| 8 | 4 | connection id | peer/session binding token |
|
|
| 12 | 4 | sequence | packet sequence number |
|
|
| 16 | 4 | ack | latest received sequence being acknowledged |
|
|
| 20 | 4 | ack mask | selective acknowledgement mask for prior packets |
|
|
| 24 | 2 | fragment id | `0` when not fragmented |
|
|
| 26 | 2 | fragment index | zero-based fragment index |
|
|
| 28 | 2 | fragment count | total fragments, `0` when not fragmented |
|
|
| 30 | 4 | payload bytes | payload length |
|
|
| 34 | 1 | channel id bytes | UTF-8 byte count, max 255 |
|
|
| 35 | 1 | reserved | `0` |
|
|
| 36 | n | channel id | UTF-8 channel id |
|
|
| 36+n | m | payload | transport-neutral payload bytes |
|
|
|
|
Canonical reliable ordered data fixture:
|
|
|
|
```text
|
|
packetType=data
|
|
connectionId=0x01020304
|
|
sequence=0x0000002a
|
|
ack=0x00000029
|
|
ackMask=0x80000001
|
|
channelId=schema
|
|
flags=reliable|ordered|fragmented
|
|
fragmentId=7
|
|
fragmentIndex=1
|
|
fragmentCount=3
|
|
payload=hello
|
|
hex=434e523000030b2a010203040000002a0000002980000001000700010003000000050600736368656d6168656c6c6f
|
|
```
|
|
|
|
## Reliable UDP Semantics To Lift From LiteNetLib
|
|
|
|
The portable RUDP contract must cover at least:
|
|
|
|
- connection handshake with application connection key support;
|
|
- peer id/session id binding;
|
|
- ping and timeout detection;
|
|
- reliable ordered channel delivery;
|
|
- unreliable channel delivery for realtime observations/input;
|
|
- sequenced delivery for latest-state streams;
|
|
- duplicate suppression;
|
|
- ack masks or equivalent selective acknowledgement;
|
|
- resend windows and retransmission timers;
|
|
- MTU-safe fragmentation and reassembly;
|
|
- disconnect reason propagation;
|
|
- bounded send queues and observable backpressure;
|
|
- deterministic wire fixtures for packet encode/decode in every runtime.
|
|
|
|
Until those are runtime-owned CultNet contracts, the runtimes do not truly
|
|
"speak the same language"; they are only translating documents after somebody
|
|
else already solved the pipe.
|