CultLib/docs/cultnet-transport-parity.md
Meta Vens 95493862bd Split the Python runtime into cultcache-py, cultnet-py, cultmesh-py
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>
2026-09-10 13:10:47 +02:00

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.