RoutedUnicastRouter

Carry one per-recipient message to exactly one player who sits behind a different server — the cross-core routed unicast.

In a federation, three servers form a fully-meshed core and a game's players each connect to whichever server is nearest them: Alice through S1, Bob through S2, Carol through S3. When a message is meant for only one of them — a card only Bob may see — it must reach Bob and nobody else. If Bob is behind a different server than the one that holds the message, the message takes a two-hop route:

spoke → server → core → server → spoke

The origin server asks the attachment directory "which server is Bob behind right now?", hands the message across the core to that one server (and no other), and that server hands it down to that one player (and no other). At no point is the message ever copied to a second recipient.

The leak boundary — why this is unicast, never broadcast

kuilt keeps per-recipient secrets safe on one guarantee: a point-to-point send (Seam.sendTo) reaches only its addressee; only a broadcast fans out. Federation needs a frame to cross the core, but it must do so without ever becoming a broadcast. This router is that discipline made cross-server: the core hop is a single-addressee coreSeam.sendTo(theOneServer, …) — it never broadcasts to the core — and the last hop is delivered down the player's own two-peer server↔player link (a Seam.broadcast on a two-peer seam reaches exactly the one player, as RaftRelayHub.sendToLearner already does for a learner seam). It never iterates a set of recipients for one message. A frame for one player therefore reaches exactly that player; no other player, and no non-destination server, ever observes it. That is the leak-boundary invariant this slice exists to protect (ADR-005 / the spec's "honest seam").

The generalization of the per-learner relay

The single-server precedent is RaftRelayHub, which routes a learner's frames to the named voter (by dest) and a voter's sendToLearner to one learner seam — lock-guarded, best-effort, and explicitly warned against ever "falling back to fan-all." This router is the cross-core generalization of exactly that single-addressee shape: instead of "route to the one leader / one local learner," it is "route to the one server the directory names, which routes to the one addressee." It reuses the same shape — a lock-guarded recipient→seam map, a relay coroutine, and runCatchingCancellable best-effort sends.

What a stale or missing directory entry does (safe by construction)

The attachment directory is eventually consistent, so a lookup can be stale (name a server the player just left) or absent (null, the player is unknown/unattached here yet). Neither breaks the leak boundary:

  • null — the frame is dropped at the origin, never fanned. The sender resends once the directory converges. (That resend-on-convergence is slice 5D's concern — this slice only guarantees the drop is safe: it goes nowhere, not everywhere.)

  • stale — the frame is delivered to one wrong server, which finds no such local spoke and drops it. Still exactly-one-addressee; still never fanned.

Ownership & wiring

The router takes sole ownership of coreSeam's incoming stream (it runs the relay that pulls routing envelopes off the core), per the single-collection contract — do not run another collector over the same seam. In production the core routing traffic rides its own channel over the inter-server mesh, distinct from the channel the Raft transport uses and the channel the directory's replicator uses; here it is any Seam into the core.

A server registers each player attached to it via registerLocalSpoke (the two-peer server↔player link) and, when the player disconnects, removeLocalSpoke. route is the entry a server calls when it holds a unicast for a recipient: it decides, from the directory, whether the recipient is local (deliver straight down the player's link) or remote (cross the core to the one server behind which they sit).

Not thread-confined: the recipient→seam map is guarded by an reentrantLock and every suspend send is issued outside the locked section, so the router is correct under a multi-threaded dispatcher. Construct one per server via routedUnicastRouter.

See also

for construction and wiring over the inter-server seam.

for the (recipient) -> server lookup this consumes.

Functions

Link copied to clipboard
fun close()

Stop relaying and release the relay coroutine. Idempotent.

Link copied to clipboard
fun registerLocalSpoke(spoke: PeerId, seam: Seam)

Register a player attached to this server: spoke's messages will be delivered down seam (the two-peer server↔player link) when they arrive across the core or are routed locally.

Link copied to clipboard

Deregister a player that has disconnected from this server.

Link copied to clipboard
suspend fun route(recipient: PeerId, payload: ByteArray)

Route payload to exactly recipient, crossing the core if recipient is behind another server.