CultLib/packages/cultnet-ts/test/cultnet.test.ts
Meta Vens 3b83cb6c08 Merge origin/main into hands/variants-c2a-main (main-into-c2a)
Sixteen conflicted files resolved by the spec's rule: main owns payload
ownership, bounds and the Clock seam; C2a owns persistence semantics and exact
binding. MintStoredAt is internal static (previous, now); the cache and the
store pass their cache's Clock. Package versions are main's. The cultmesh-ts
payload-parse acceptance and its test are deleted under retire-schema-alias;
peer snapshot reads take the schema's own id, else an id the definition lists
as compatible. The held firstRecordAtKey fallback is removed: main's handles
take definitions only, so nothing could reach it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-14 04:06:41 +02:00

3061 lines
125 KiB
TypeScript

import test from "node:test";
import assert from "node:assert/strict";
import { EventEmitter } from "node:events";
import { Duplex } from "node:stream";
import dgram, { type Socket } from "node:dgram";
import { rmSync, mkdtempSync } from "node:fs";
import { join } from "node:path";
import { networkInterfaces, tmpdir } from "node:os";
import { z } from "zod";
import { decode as decodeMsgpack, encode } from "@msgpack/msgpack";
import {
CultCache,
SchemaConflictError,
SingleFileMessagePackBackingStore,
defineDocumentType,
schemaIdentityOf,
} from "@gamecult/cultcache-ts";
import {
CultNetClientSecurityOptions,
CultNetDocumentRegistry,
CultNetPeer,
CultNetRudpReconnectLoop,
CultNetRudpSession,
CultNetRudpSocketTransportConnection,
CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS,
CultNetSchemaCatalog,
CultNetSchemaRegistry,
CultNetSecret,
CultNetServerSecurityOptions,
CultNetShardCatalog,
TcpFramedTransportConnection,
CultNetReconnectController,
cultNetSchemas,
cultNetBuiltinSchemaRegistry,
computeCultNetReconnectDelayMs,
createCultNetReconnectPolicy,
createTcpFramedCultNetPeer,
createTcpFramedTransportProfile,
createRudpTransportProfile,
defineCultNetDocumentBinding,
decodeRudpPacket,
type CultNetOperationRequestMessage,
type CultNetOperationResponseMessage,
type CultNetRudpPacket,
encodeCultNetMessageForWire,
encodeRudpPacket,
ghostlightAgentStateGeneratedContract,
parseCultNetMessage,
invokeCultNetOperation,
rudpClientBindHost,
startCultNetOperationServer,
validateGhostlightAgentStateGenerated,
validateGhostlightAgentState,
shardServes,
type CultNetLoginMessage,
type GhostlightAgentStateShape,
type GhostlightAgentStateDocument,
} from "../src";
import {
INTEROP_SCHEMA_VERSION,
createInteropFormatter,
createLegacyInteropNoteFormatter,
createMismatchedInteropNoteFormatter,
type InteropNote,
} from "./interop/cultnet-interop-shared";
test("CultNet validates the canonical CultMesh Verse catalog messages", () => {
const request = parseCultNetMessage({
schemaVersion: "cultmesh.verse_catalog_request.v0",
messageId: "catalog-1",
verseIds: ["sample.counter"],
transportVersion: "cultmesh.v1",
});
assert.equal(request.schemaVersion, "cultmesh.verse_catalog_request.v0");
const response = parseCultNetMessage({
schemaVersion: "cultmesh.verse_catalog_response.v0",
messageId: "catalog-1",
verses: [{
verseId: "sample.counter",
displayName: "Counter",
authorityModel: "OperatorCluster",
compatibility: {
transportVersion: "cultmesh.v1",
rulesHash: "counter-v1",
compatibleVerseIds: [],
requiredPluginIds: [],
optionalPluginIds: [],
},
discoveryEndpoints: ["wss://provider.example/cultmesh"],
authorityRuntimeIds: ["sample.counter-provider"],
authorityRoutes: [{
authorityRuntimeId: "sample.counter-provider",
endpoint: "wss://provider.example/cultmesh",
protocolIds: ["cultmesh.documents.v1"],
priority: 0,
generation: "provider-route-1",
}],
}],
});
assert.equal(response.schemaVersion, "cultmesh.verse_catalog_response.v0");
assert.equal(response.verses[0].authorityRuntimeIds[0], "sample.counter-provider");
assert.equal(response.verses[0].authorityRoutes![0].generation, "provider-route-1");
const session = parseCultNetMessage({
schemaVersion: "cultmesh.session_accepted.v2",
messageId: "session-1",
accepted: true,
verseId: "sample.counter",
authorityRuntimeId: "sample.counter-provider",
protocolId: "cultmesh.documents.v1",
routeGeneration: "provider-route-1",
clientNonce: "bm9uY2U=",
providerKeyId: "provider-key-1",
providerSignature: "c2lnbmF0dXJl",
});
assert.equal(session.schemaVersion, "cultmesh.session_accepted.v2");
assert.throws(() => parseCultNetMessage({
...response,
verses: [{ ...response.verses[0], compatibility: { ...response.verses[0].compatibility, rulesHash: "" } }],
}), /rulesHash/);
});
class FakeRudpReconnectTransport extends EventEmitter {
public connectCalls = 0;
public closeCalls = 0;
public lastPayload: Uint8Array | undefined;
public connect(payload = new Uint8Array()): void {
this.connectCalls += 1;
this.lastPayload = payload;
}
public close(): void {
this.closeCalls += 1;
this.emit("close");
}
}
class LinkedDuplex extends Duplex {
peer?: LinkedDuplex;
// eslint-disable-next-line @typescript-eslint/no-empty-function
_read(): void {}
_write(
chunk: Buffer,
_encoding: BufferEncoding,
callback: (error?: Error | null) => void,
): void {
this.peer?.push(Buffer.from(chunk));
callback();
}
_final(callback: (error?: Error | null) => void): void {
this.peer?.push(null);
callback();
}
}
function createDuplexPair(): { a: Duplex; b: Duplex } {
const a = new LinkedDuplex();
const b = new LinkedDuplex();
a.peer = b;
b.peer = a;
return { a, b };
}
/// Whether the packet's ack field and mask cover the sequence.
function acknowledges(packet: CultNetRudpPacket, sequence: number): boolean {
if (packet.ack === sequence) return true;
return packet.ack > sequence
&& packet.ack - sequence <= 32
&& (packet.ackMask & (1 << (packet.ack - sequence - 1))) !== 0;
}
async function bindUdpSocket(): Promise<Socket> {
const socket = dgram.createSocket("udp4");
await new Promise<void>((resolve) => socket.bind(0, "127.0.0.1", resolve));
return socket;
}
function udpPort(socket: Socket): number {
const address = socket.address();
assert.notEqual(typeof address, "string");
return address.port;
}
async function waitFor(predicate: () => boolean, description: string): Promise<void> {
const startedAt = Date.now();
while (!predicate()) {
if (Date.now() - startedAt > 1_000) {
throw new Error(`Timed out waiting for ${description}.`);
}
await new Promise((resolve) => setTimeout(resolve, 5));
}
}
test("CultNet secret helpers round-trip encrypted strings and validate sessions", () => {
const serverSecurity = CultNetServerSecurityOptions.development();
const clientSecurity = serverSecurity.toClientOptions();
const nonce = CultNetSecret.newNonce();
const encrypted = CultNetSecret.encryptString("hello", nonce, clientSecurity);
assert.ok(encrypted);
assert.equal(CultNetSecret.decryptString(encrypted, nonce, serverSecurity), "hello");
const token = CultNetSecret.createSessionToken(
"runtime-face",
new Date(Date.now() + 60_000),
serverSecurity,
);
const validated = CultNetSecret.tryValidateSessionToken(token, serverSecurity);
assert.ok(validated);
assert.equal(validated?.userId, "runtime-face");
assert.equal(validated?.sessionVersion, 0);
});
test("CultNet secret helpers validate C# and Python compatible versioned sessions", () => {
const serverSecurity = CultNetServerSecurityOptions.development();
const expires = new Date(Date.now() + 60_000);
const token = CultNetSecret.createSessionToken(
"318fb4b6-ff5e-4c4f-b911-d81807de53a8",
expires,
serverSecurity,
42,
);
const [payload] = token.split(".");
assert.equal(
new TextDecoder().decode(CultNetSecret.fromBase64Url(payload!)),
`318fb4b6ff5e4c4fb911d81807de53a8|${Math.floor(expires.getTime() / 1000)}|42`,
);
const validated = CultNetSecret.tryValidateSessionToken(token, serverSecurity);
assert.ok(validated);
assert.equal(validated?.userId, "318fb4b6ff5e4c4fb911d81807de53a8");
assert.equal(validated?.sessionVersion, 42);
assert.throws(
() => CultNetSecret.createSessionToken("runtime-face", expires, serverSecurity, 1),
/Guid-compatible/,
);
});
test("CultNet secret helpers validate Python-created versioned sessions", () => {
const token = [
"MzE4ZmI0YjZmZjVlNGM0ZmI5MTFkODE4MDdkZTUzYTh8MjA1MTIyMjQwMHw3Nw",
"jRrUiE5Om7NQVKMJP4PkBkLFVLXqNb8Uu9jg4VG13pU",
].join(".");
const validated = CultNetSecret.tryValidateSessionToken(token, CultNetServerSecurityOptions.development());
assert.ok(validated);
assert.equal(validated?.userId, "318fb4b6ff5e4c4fb911d81807de53a8");
assert.equal(validated?.sessionVersion, 77);
});
test("CultNet peer frames and decodes typed messages over a direct pipe", async () => {
const { a, b } = createDuplexPair();
const sender = new CultNetPeer(a, { wireContract: "cultnet.schema.v0" });
const receiver = new CultNetPeer(b, { wireContract: "cultnet.schema.v0" });
const message = await new Promise<ReturnType<typeof parseCultNetMessage>>((resolve, reject) => {
receiver.once("message", resolve);
receiver.once("invalidMessage", reject);
sender.sendHello({
schemaVersion: "cultnet.hello.v0",
runtimeId: "voidbot-main",
runtimeKind: "node-worker",
agentId: "void",
displayName: "Void",
supportedDocumentTypes: ["ghostlight.agent-state"],
transportProfiles: [
{
schemaVersion: "cultnet.transport_profile.v0",
runtimeId: "voidbot-main",
transports: [
{
transportId: "direct-pipe",
protocol: "tcp_framed",
wireContracts: ["cultnet.schema.v0"],
channels: [{ channelId: "schema", delivery: "reliable", ordering: "ordered" }],
},
],
},
],
});
});
assert.equal(message.schemaVersion, "cultnet.hello.v0");
if (message.schemaVersion === "cultnet.hello.v0") {
assert.equal(message.runtimeId, "voidbot-main");
assert.equal(message.agentId, "void");
assert.equal(message.transportProfiles?.[0]?.transports[0]?.protocol, "tcp_framed");
}
sender.close();
receiver.close();
});
test("tcp_framed transport carries raw schema channel payloads with stats", async () => {
const { a, b } = createDuplexPair();
const left = new TcpFramedTransportConnection(a, createTcpFramedTransportProfile("left"));
const right = new TcpFramedTransportConnection(b, createTcpFramedTransportProfile("right"));
const frame = await new Promise<{ channelId: string; payload: Uint8Array }>((resolve, reject) => {
right.once("frame", resolve);
right.once("error", reject);
left.send("schema", Buffer.from("payload", "utf8"));
});
assert.equal(frame.channelId, "schema");
assert.equal(Buffer.from(frame.payload).toString("utf8"), "payload");
assert.equal(left.stats.framesSent, 1);
assert.equal(right.stats.framesReceived, 1);
assert.throws(() => left.send("unreliable", Buffer.alloc(0)), /only supports the schema channel/);
left.close();
right.close();
});
test("rudp packet codec has a deterministic reliable ordered fixture", () => {
const encoded = encodeRudpPacket({
packetType: "data",
connectionId: 0x01020304,
sequence: 0x0000002a,
ack: 0x00000029,
ackMask: 0x80000001,
channelId: "schema",
reliable: true,
ordered: true,
fragmentId: 7,
fragmentIndex: 1,
fragmentCount: 3,
payload: Buffer.from("hello", "utf8"),
});
assert.equal(
Buffer.from(encoded).toString("hex"),
"434e523000030b2a010203040000002a0000002980000001000700010003000000050600736368656d6168656c6c6f",
);
const decoded = decodeRudpPacket(encoded);
assert.equal(decoded.packetType, "data");
assert.equal(decoded.connectionId, 0x01020304);
assert.equal(decoded.sequence, 0x0000002a);
assert.equal(decoded.ack, 0x00000029);
assert.equal(decoded.ackMask, 0x80000001);
assert.equal(decoded.channelId, "schema");
assert.equal(decoded.reliable, true);
assert.equal(decoded.ordered, true);
assert.equal(decoded.sequenced, false);
assert.equal(decoded.fragmentId, 7);
assert.equal(decoded.fragmentIndex, 1);
assert.equal(decoded.fragmentCount, 3);
assert.equal(Buffer.from(decoded.payload ?? []).toString("utf8"), "hello");
});
test("rudp transport profile advertises state and realtime channel semantics", () => {
const profile = createRudpTransportProfile("node-rudp", {
transportId: "public-rudp",
host: "127.0.0.1",
port: 7777,
maxPayloadBytes: 1200,
maxFragmentBytes: 1000,
});
assert.equal(profile.transports[0]?.protocol, "rudp");
assert.equal(profile.transports[0]?.reconnectPolicy?.schemaVersion, "cultnet.reconnect_policy.v0");
assert.equal(profile.transports[0]?.reconnectPolicy?.baseDelayMs, 1_000);
assert.deepEqual(
profile.transports[0]?.channels.map((channel) => [channel.channelId, channel.delivery, channel.ordering]),
[
["schema", "reliable", "ordered"],
["latest", "unreliable", "sequenced"],
["realtime", "unreliable", "unordered"],
],
);
});
test("reconnect policy exposes the shared portable delay contract", () => {
const policy = createCultNetReconnectPolicy({ policyId: "rudp-default", maxAttempts: 8 });
assert.equal(policy.schemaVersion, "cultnet.reconnect_policy.v0");
assert.equal(policy.policyId, "rudp-default");
assert.equal(policy.maxAttempts, 8);
assert.equal(computeCultNetReconnectDelayMs(policy, 1), 1_000);
assert.equal(computeCultNetReconnectDelayMs(policy, 3, 17), 4_017);
assert.equal(computeCultNetReconnectDelayMs(policy, 9, 999), 30_250);
assert.equal(computeCultNetReconnectDelayMs(policy, 0, -5), 1_000);
});
test("reconnect controller schedules attempts and reset with the shared policy", () => {
const policy = createCultNetReconnectPolicy({ maxAttempts: 2 });
const controller = new CultNetReconnectController(policy);
const first = controller.recordFailure(10_000);
assert.deepEqual(first, {
attempt: 1,
shouldRetry: true,
delayMs: 1_000,
nextAttemptAtMs: 11_000,
exhausted: false,
});
assert.equal(controller.canAttempt(10_999), false);
assert.equal(controller.canAttempt(11_000), true);
const second = controller.recordFailure(11_000, 17);
assert.equal(second.attempt, 2);
assert.equal(second.delayMs, 2_017);
assert.equal(second.nextAttemptAtMs, 13_017);
assert.equal(second.shouldRetry, true);
const exhausted = controller.recordFailure(13_017);
assert.deepEqual(exhausted, {
attempt: 2,
shouldRetry: false,
delayMs: 0,
exhausted: true,
});
assert.equal(controller.exhausted, true);
assert.equal(controller.canAttempt(99_000), false);
controller.reset();
assert.equal(controller.attempt, 0);
assert.equal(controller.nextAttemptAtMs, undefined);
assert.equal(controller.exhausted, false);
assert.equal(controller.canAttempt(99_000), true);
});
test("rudp reconnect loop consumes the shared reconnect controller", () => {
let nowMs = 10_000;
let capturedTimer: (() => void) | undefined;
const transports: FakeRudpReconnectTransport[] = [];
const loop = new CultNetRudpReconnectLoop({
reconnectPolicy: createCultNetReconnectPolicy({ maxAttempts: 2 }),
connectPayload: Buffer.from("join", "utf8"),
createTransport: () => {
const transport = new FakeRudpReconnectTransport();
transports.push(transport);
return transport;
},
nowMs: () => nowMs,
jitterMs: () => 17,
setTimer: (callback, delayMs) => {
assert.equal(delayMs, 1_017);
capturedTimer = callback;
return "timer";
},
clearTimer: () => {
capturedTimer = undefined;
},
});
const first = loop.start() as FakeRudpReconnectTransport;
assert.equal(first.connectCalls, 1);
assert.equal(Buffer.from(first.lastPayload ?? []).toString("utf8"), "join");
first.emit("close");
assert.equal(loop.reconnectController.attempt, 1);
assert.equal(loop.reconnectController.nextAttemptAtMs, 11_017);
assert.equal(typeof capturedTimer, "function");
nowMs = 11_017;
capturedTimer?.();
assert.equal(transports.length, 2);
assert.equal(transports[1]?.connectCalls, 1);
loop.markConnected();
assert.equal(loop.reconnectController.attempt, 0);
loop.stop();
assert.equal(transports[1]?.closeCalls, 1);
assert.equal(loop.transport, undefined);
});
test("rudp session handshake acks reliable connect and accept packets", () => {
const client = new CultNetRudpSession({ connectionId: 0x0a0b0c0d, initialSequence: 1, resendDelayMs: 50 });
const server = new CultNetRudpSession({ connectionId: 0x0a0b0c0d, initialSequence: 100, resendDelayMs: 50 });
const connect = client.createConnect(0, Buffer.from("join", "utf8"));
assert.equal(connect.packetType, "connect");
assert.equal(connect.sequence, 1);
assert.deepEqual(client.pendingReliableSequences, [1]);
const accept = server.acceptConnect(connect, 10, Buffer.from("ok", "utf8"));
assert.equal(accept.packetType, "accept");
assert.equal(accept.ack, 1);
assert.equal(server.connected, true);
assert.deepEqual(server.pendingReliableSequences, [100]);
const retransmittedAccept = server.acceptConnect(connect, 15, Buffer.from("ok", "utf8"));
assert.deepEqual(
encodeRudpPacket(retransmittedAccept),
encodeRudpPacket(accept),
"a retransmitted Connect reuses the pending Accept packet",
);
assert.deepEqual(server.pendingReliableSequences, [100]);
client.receive(accept, 20);
assert.equal(client.connected, true);
assert.deepEqual(client.pendingReliableSequences, []);
const ack = client.createAck();
assert.equal(ack.sequence, 0);
assert.equal(ack.ack, 100);
server.receive(ack, 30);
assert.deepEqual(server.pendingReliableSequences, []);
});
test("rudp session computes ack masks and clears pending reliable packets", () => {
const sender = new CultNetRudpSession({ connectionId: 7, initialSequence: 10, resendDelayMs: 100 });
const receiver = new CultNetRudpSession({ connectionId: 7, initialSequence: 200, resendDelayMs: 100 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
const first = sender.send("schema", Buffer.from("first"), { reliable: true, ordered: true, nowMs: 0 });
const second = sender.send("schema", Buffer.from("second"), { reliable: true, ordered: true, nowMs: 0 });
const third = sender.send("schema", Buffer.from("third"), { reliable: true, ordered: true, nowMs: 0 });
assert.deepEqual(sender.pendingReliableSequences, [11, 12, 13]);
receiver.receive(first);
receiver.receive(third);
const ackWithGap = receiver.createAck();
assert.equal(ackWithGap.ack, 13);
assert.equal(ackWithGap.ackMask, 0b110);
sender.receive(ackWithGap);
assert.deepEqual(sender.pendingReliableSequences, [12]);
receiver.receive(second);
const fullAck = receiver.createAck();
assert.equal(fullAck.ack, 13);
assert.equal(fullAck.ackMask, 0b111);
sender.receive(fullAck);
assert.deepEqual(sender.pendingReliableSequences, []);
});
test("rudp session schedules reliable resends until acked", () => {
const session = new CultNetRudpSession({ connectionId: 99, initialSequence: 1, resendDelayMs: 100 });
session.receive(new CultNetRudpSession({ connectionId: 99, initialSequence: 900 }).acceptConnect(session.createConnect(0), 0), 0);
const sent = session.send("schema", Buffer.from("payload"), { reliable: true, ordered: true, nowMs: 10 });
assert.deepEqual(session.dueResends(90), []);
assert.deepEqual(session.dueResends(110).map((packet) => packet.sequence), [sent.sequence]);
assert.deepEqual(session.dueResends(150), []);
session.receive({ packetType: "ack", connectionId: 99, sequence: 51, ack: sent.sequence, ackMask: 0, channelId: "control" });
assert.deepEqual(session.dueResends(250), []);
});
test("rudp session pings and detects receive timeout", () => {
const client = new CultNetRudpSession({ connectionId: 101, initialSequence: 1 });
const server = new CultNetRudpSession({ connectionId: 101, initialSequence: 100 });
const connect = client.createConnect(0, Buffer.from("join"));
const accept = server.acceptConnect(connect, 10);
client.receive(accept, 20);
const ping = client.createPing(Buffer.from("pulse"));
const pingResult = server.receive(ping, 30);
assert.equal(pingResult.reply?.packetType, "pong");
assert.deepEqual(Buffer.from(pingResult.reply?.payload ?? []), Buffer.from("pulse"));
const pongResult = client.receive(pingResult.reply!, 40);
assert.equal(pongResult.pong, true);
assert.deepEqual(Buffer.from(pongResult.pongPayload ?? []), Buffer.from("pulse"));
assert.equal(client.checkTimeout(90, 50), false);
assert.equal(client.checkTimeout(91, 50), true);
assert.equal(client.connected, false);
});
test("rudp lossy packets cannot create reliable ordered gaps", () => {
const client = new CultNetRudpSession({ connectionId: 198, initialSequence: 1 });
const server = new CultNetRudpSession({ connectionId: 198, initialSequence: 100 });
const connect = client.createConnect(0);
const accept = server.acceptConnect(connect, 1);
client.receive(accept, 2);
const ping = client.createPing(Buffer.from("pulse"));
const pong = server.receive(ping, 3).reply!;
const realtime = server.send("realtime", Buffer.from("discarded realtime"));
const latest = server.send("latest", Buffer.from("discarded latest state"), { sequenced: true });
const schema = server.send("schema", Buffer.from("committed response"), { reliable: true, ordered: true });
assert.equal(ping.sequence, 0);
assert.equal(pong.sequence, 0);
assert.equal(realtime.sequence, 0);
assert.equal(latest.sequence, 1);
assert.equal(schema.sequence, accept.sequence + 1);
assert.deepEqual(client.receive(schema, 4).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), [
"committed response",
]);
});
test("rudp unreliable sequenced delivery is scoped to its channel", () => {
const sender = new CultNetRudpSession({ connectionId: 197, initialSequence: 50 });
const receiver = new CultNetRudpSession({ connectionId: 197, initialSequence: 100 });
const connect = sender.createConnect(0);
const accept = receiver.acceptConnect(connect, 1);
sender.receive(accept, 2);
const older = sender.send("latest", Buffer.from("older"), { sequenced: true });
const newer = sender.send("latest", Buffer.from("newer"), { sequenced: true });
assert.equal(older.sequence, 1);
assert.equal(newer.sequence, 2);
assert.deepEqual(receiver.receive(newer, 3).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), ["newer"]);
assert.deepEqual(receiver.receive(older, 4).delivered, []);
});
test("rudp rejects unreliable ordered delivery", () => {
const session = new CultNetRudpSession({ connectionId: 196 });
session.receive(new CultNetRudpSession({ connectionId: 196, initialSequence: 900 }).acceptConnect(session.createConnect(0), 0), 0);
assert.throws(
() => session.send("schema", Buffer.from("cannot order what will not retransmit"), { ordered: true }),
/ordered delivery requires reliability/,
);
});
test("rudp session bounds pending reliable packets before enqueue", () => {
const session = new CultNetRudpSession({ connectionId: 102, initialSequence: 1, maxPendingReliablePackets: 2 });
session.receive(new CultNetRudpSession({ connectionId: 102, initialSequence: 900 }).acceptConnect(session.createConnect(0), 0), 0);
session.send("schema", Buffer.from("first"), { reliable: true, ordered: true });
session.send("schema", Buffer.from("second"), { reliable: true, ordered: true });
assert.throws(
() => session.send("schema", Buffer.from("third"), { reliable: true, ordered: true }),
/reliable send queue is full/,
);
assert.deepEqual(session.pendingReliableSequences, [2, 3]);
const fragmented = new CultNetRudpSession({ connectionId: 103, initialSequence: 1, maxPendingReliablePackets: 3 });
fragmented.receive(new CultNetRudpSession({ connectionId: 103, initialSequence: 900 }).acceptConnect(fragmented.createConnect(0), 0), 0);
assert.throws(
() => fragmented.sendMany("schema", Buffer.from("fragment-me"), { reliable: true, ordered: true, maxFragmentBytes: 3 }),
/reliable send queue is full/,
);
assert.deepEqual(fragmented.pendingReliableSequences, []);
});
test("rudp session suppresses duplicates and delivers reliable ordered payloads in sequence", () => {
const sender = new CultNetRudpSession({ connectionId: 123, initialSequence: 1 });
const receiver = new CultNetRudpSession({ connectionId: 123, initialSequence: 100 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0));
const first = sender.send("schema", Buffer.from("first"), { reliable: true, ordered: true });
const second = sender.send("schema", Buffer.from("second"), { reliable: true, ordered: true });
const third = sender.send("schema", Buffer.from("third"), { reliable: true, ordered: true });
assert.deepEqual(receiver.receive(first).delivered.map((frame) => Buffer.from(frame.payload).toString("utf8")), ["first"]);
assert.deepEqual(receiver.receive(third).delivered, []);
assert.deepEqual(receiver.receive(first).delivered, []);
assert.deepEqual(receiver.receive(second).delivered.map((frame) => Buffer.from(frame.payload).toString("utf8")), [
"second",
"third",
]);
});
test("rudp session skips received control packets while ordering schema payloads", () => {
const sender = new CultNetRudpSession({ connectionId: 124, initialSequence: 1 });
const receiver = new CultNetRudpSession({ connectionId: 124, initialSequence: 100 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
const first = sender.send("schema", Buffer.from("first"), { reliable: true, ordered: true });
const control = sender.createAck();
const second = sender.send("schema", Buffer.from("second"), { reliable: true, ordered: true });
assert.equal(control.sequence, 0);
assert.equal(second.sequence, first.sequence + 1);
assert.deepEqual(receiver.receive(first).delivered.map((frame) => Buffer.from(frame.payload).toString("utf8")), ["first"]);
assert.deepEqual(receiver.receive(control).delivered, []);
assert.deepEqual(receiver.receive(second).delivered.map((frame) => Buffer.from(frame.payload).toString("utf8")), ["second"]);
});
test("rudp session fragments and reassembles reliable ordered payloads", () => {
const sender = new CultNetRudpSession({ connectionId: 456, initialSequence: 1 });
const receiver = new CultNetRudpSession({ connectionId: 456, initialSequence: 100 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
const packets = sender.sendMany("schema", Buffer.from("fragment-me-please"), {
reliable: true,
ordered: true,
nowMs: 10,
maxFragmentBytes: 5,
});
assert.equal(packets.length, 4);
assert.deepEqual(packets.map((packet) => packet.fragmentCount), [4, 4, 4, 4]);
assert.deepEqual(packets.map((packet) => packet.fragmentIndex), [0, 1, 2, 3]);
assert.ok(packets.every((packet) => packet.fragmentId === packets[0]?.fragmentId));
assert.deepEqual(receiver.receive(packets[0]!).delivered, []);
assert.deepEqual(receiver.receive(packets[1]!).delivered, []);
assert.deepEqual(receiver.receive(packets[2]!).delivered, []);
const delivered = receiver.receive(packets[3]!).delivered;
assert.equal(delivered.length, 1);
assert.equal(Buffer.from(delivered[0]!.payload).toString("utf8"), "fragment-me-please");
assert.equal(delivered[0]!.sequence, packets[0]!.sequence);
});
test("rudp socket transport handshakes and carries reliable ordered schema frames over UDP", async () => {
const serverSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const connectionId = 0x10203040;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const client = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(serverSocket),
connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
try {
const serverFrame = new Promise<{ channelId: string; payload: Uint8Array }>((resolve, reject) => {
server.once("frame", resolve);
server.once("error", reject);
});
client.connect(Buffer.from("join", "utf8"));
await waitFor(() => client.connected && server.connected, "RUDP socket handshake");
client.send("schema", Buffer.from("client-state", "utf8"));
const receivedByServer = await serverFrame;
assert.equal(receivedByServer.channelId, "schema");
assert.equal(Buffer.from(receivedByServer.payload).toString("utf8"), "client-state");
const clientFrame = new Promise<{ channelId: string; payload: Uint8Array }>((resolve, reject) => {
client.once("frame", resolve);
client.once("error", reject);
});
server.send("schema", Buffer.from("server-state", "utf8"));
const receivedByClient = await clientFrame;
assert.equal(receivedByClient.channelId, "schema");
assert.equal(Buffer.from(receivedByClient.payload).toString("utf8"), "server-state");
assert.equal(client.stats.framesSent, 1);
assert.equal(server.stats.framesReceived, 1);
assert.equal(server.profile.transports[0]?.protocol, "rudp");
} finally {
client.close();
server.close();
}
});
test("rudp socket transport carries fragmented reliable ordered schema frames over UDP", async () => {
const serverSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const connectionId = 0x10203041;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-fragment-server",
socket: serverSocket,
mode: "server",
connectionId,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
maxFragmentBytes: 8,
});
const client = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-fragment-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(serverSocket),
connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
maxFragmentBytes: 8,
});
try {
const payload = Buffer.from("this-schema-frame-is-larger-than-one-rudp-fragment", "utf8");
const serverFrame = new Promise<{ channelId: string; payload: Uint8Array }>((resolve, reject) => {
server.once("frame", resolve);
server.once("error", reject);
});
client.connect(Buffer.from("join", "utf8"));
await waitFor(() => client.connected && server.connected, "fragmented RUDP socket handshake");
client.send("schema", payload);
const receivedByServer = await serverFrame;
assert.equal(receivedByServer.channelId, "schema");
assert.equal(Buffer.from(receivedByServer.payload).toString("utf8"), payload.toString("utf8"));
assert.equal(client.stats.framesSent, 1);
assert.equal(server.stats.framesReceived, 1);
} finally {
client.close();
server.close();
}
});
test("CultNet peer can speak schema messages through the RUDP socket transport", async () => {
const serverSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const connectionId = 0x50607080;
const serverTransport = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-peer-server",
socket: serverSocket,
mode: "server",
connectionId,
initialSequence: 500,
resendDelayMs: 25,
resendPollMs: 5,
});
const clientTransport = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-peer-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(serverSocket),
connectionId,
initialSequence: 10,
resendDelayMs: 25,
resendPollMs: 5,
});
try {
const sender = new CultNetPeer(clientTransport, { wireContract: "cultnet.schema.v0" });
const receiver = new CultNetPeer(serverTransport, { wireContract: "cultnet.schema.v0" });
clientTransport.connect();
await waitFor(() => clientTransport.connected && serverTransport.connected, "RUDP peer socket handshake");
const message = await new Promise<ReturnType<typeof parseCultNetMessage>>((resolve, reject) => {
receiver.once("message", resolve);
receiver.once("invalidMessage", reject);
clientTransport.once("error", reject);
serverTransport.once("error", reject);
sender.sendHello({
schemaVersion: "cultnet.hello.v0",
runtimeId: "rudp-peer-client",
runtimeKind: "node-worker",
transportProfiles: [clientTransport.profile],
});
});
assert.equal(message.schemaVersion, "cultnet.hello.v0");
if (message.schemaVersion === "cultnet.hello.v0") {
assert.equal(message.runtimeId, "rudp-peer-client");
assert.equal(message.transportProfiles?.[0]?.transports[0]?.protocol, "rudp");
}
sender.close();
receiver.close();
} finally {
clientTransport.close();
serverTransport.close();
}
});
test("CultNet peer can speak through a transport connection", async () => {
const { a, b } = createDuplexPair();
const leftTransport = new TcpFramedTransportConnection(a, createTcpFramedTransportProfile("left"));
const rightTransport = new TcpFramedTransportConnection(b, createTcpFramedTransportProfile("right"));
const sender = new CultNetPeer(leftTransport, { wireContract: "cultnet.schema.v0" });
const receiver = new CultNetPeer(rightTransport, { wireContract: "cultnet.schema.v0" });
const message = await new Promise<ReturnType<typeof parseCultNetMessage>>((resolve, reject) => {
receiver.once("message", resolve);
receiver.once("invalidMessage", reject);
sender.sendHello({
schemaVersion: "cultnet.hello.v0",
runtimeId: "transport-sender",
runtimeKind: "node-worker",
});
});
assert.equal(message.schemaVersion, "cultnet.hello.v0");
if (message.schemaVersion === "cultnet.hello.v0") {
assert.equal(message.runtimeId, "transport-sender");
}
assert.equal(leftTransport.stats.framesSent, 1);
assert.equal(rightTransport.stats.framesReceived, 1);
sender.close();
receiver.close();
});
test("CultNet TCP helper constructs a transport-backed peer with profile metadata", async () => {
const { a, b } = createDuplexPair();
const sender = createTcpFramedCultNetPeer(a, {
runtimeId: "tcp-helper-left",
wireContract: "cultnet.schema.v0",
transportId: "helper-left",
host: "127.0.0.1",
port: 17777,
maxPayloadBytes: 64 * 1024,
});
const receiver = createTcpFramedCultNetPeer(b, {
runtimeId: "tcp-helper-right",
wireContract: "cultnet.schema.v0",
});
assert.equal(sender.transportProfile?.runtimeId, "tcp-helper-left");
assert.equal(sender.transportProfile?.transports[0]?.transportId, "helper-left");
assert.equal(sender.transportProfile?.transports[0]?.protocol, "tcp_framed");
assert.equal(sender.transportProfile?.transports[0]?.host, "127.0.0.1");
assert.equal(sender.transportProfile?.transports[0]?.port, 17777);
assert.equal(sender.transportProfile?.transports[0]?.channels[0]?.channelId, "schema");
assert.equal(sender.transportProfile?.transports[0]?.channels[0]?.maxPayloadBytes, 64 * 1024);
const message = await new Promise<ReturnType<typeof parseCultNetMessage>>((resolve, reject) => {
receiver.once("message", resolve);
receiver.once("invalidMessage", reject);
sender.sendHello({
schemaVersion: "cultnet.hello.v0",
runtimeId: "tcp-helper-sender",
runtimeKind: "node-worker",
});
});
assert.equal(message.schemaVersion, "cultnet.hello.v0");
if (message.schemaVersion === "cultnet.hello.v0") {
assert.equal(message.runtimeId, "tcp-helper-sender");
}
sender.close();
receiver.close();
});
test("CultNet can round-trip gamecult.networking.v0 auth messages through the explicit legacy contract", () => {
const message: CultNetLoginMessage = {
schemaVersion: "cultnet.login.v0",
nonce: "bm9uY2U",
auth: "YXV0aA",
password: "cGFzc3dvcmQ",
};
const wireValue = encodeCultNetMessageForWire(message, "gamecult.networking.v0");
assert.deepEqual(wireValue, [
0,
[
new TextEncoder().encode("nonce"),
new TextEncoder().encode("auth"),
new TextEncoder().encode("password"),
],
]);
const decoded = parseCultNetMessage(wireValue, "gamecult.networking.v0");
assert.deepEqual(decoded, message);
});
test("CultNet schema discovery catalog can advertise canonical schemas without inline bodies by default", () => {
const response = cultNetBuiltinSchemaRegistry.createCatalogResponse({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "catalog-1",
});
const ghostlight = response.schemas.find((schema) => schema.documentType === "ghostlight.agent-state");
assert.ok(ghostlight);
assert.equal(ghostlight?.kind, "document_payload");
assert.equal(ghostlight?.documentType, "ghostlight.agent-state");
assert.equal(typeof ghostlight?.contentHash, "string");
assert.equal(ghostlight?.schemaJson, undefined);
const transportProfile = response.schemas.find((schema) => schema.schemaVersion === "cultnet.transport_profile.v0");
assert.ok(transportProfile);
assert.equal(transportProfile?.kind, "shared_contract");
assert.equal(transportProfile?.schemaId, cultNetSchemas.transportProfileSchema.$id);
assert.deepEqual(transportProfile?.wireContracts, ["cultnet.schema.v0"]);
const shardCatalogRequest = response.schemas.find((schema) => schema.schemaVersion === "cultnet.shard_catalog_request.v0");
assert.ok(shardCatalogRequest);
assert.equal(shardCatalogRequest?.schemaId, cultNetSchemas.shardCatalogRequestSchema.$id);
assert.deepEqual(shardCatalogRequest?.wireContracts, ["cultnet.schema.v0"]);
const shardCatalogResponse = response.schemas.find((schema) => schema.schemaVersion === "cultnet.shard_catalog_response.v0");
assert.ok(shardCatalogResponse);
assert.equal(shardCatalogResponse?.schemaId, cultNetSchemas.shardCatalogResponseSchema.$id);
assert.deepEqual(shardCatalogResponse?.wireContracts, ["cultnet.schema.v0"]);
assert.equal(
cultNetSchemas.transportProfileSchema.properties.transports.items.properties.reconnectPolicy.properties.schemaVersion.const,
"cultnet.reconnect_policy.v0",
);
});
test("CultNet schema catalog applies remote descriptors and notifies watchers", () => {
const catalog = new CultNetSchemaCatalog();
let watchedSchemaId: string | undefined;
const unsubscribe = catalog.watch((descriptor) => {
watchedSchemaId = descriptor.schemaId;
});
const response = cultNetBuiltinSchemaRegistry.createCatalogResponse({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "catalog-apply",
includeSchemaJson: true,
kinds: ["document_payload"],
});
const applied = catalog.applyResponse(response);
assert.ok(applied.some((descriptor) => descriptor.documentType === "ghostlight.agent-state"));
assert.equal(watchedSchemaId, applied.at(-1)?.schemaId);
assert.equal(catalog.get(applied[0]!.schemaId)?.schemaJson, undefined);
assert.equal(typeof catalog.get(applied[0]!.schemaId, { includeSchemaJson: true })?.schemaJson, "string");
assert.deepEqual(
catalog.list({ kinds: ["document_payload"] }).map((descriptor) => descriptor.kind),
catalog.list().map((descriptor) => descriptor.kind),
);
unsubscribe();
});
test("CultNet peer can request and sync schema catalogs by message id", async () => {
const { a, b } = createDuplexPair();
const requester = createTcpFramedCultNetPeer(a, {
runtimeId: "catalog-requester",
wireContract: "cultnet.schema.v0",
});
const responder = createTcpFramedCultNetPeer(b, {
runtimeId: "catalog-responder",
wireContract: "cultnet.schema.v0",
});
responder.on("message", (message) => {
if (message.schemaVersion === "cultnet.schema_catalog_request.v0") {
responder.sendSchemaCatalogResponse(cultNetBuiltinSchemaRegistry.createCatalogResponse(message));
}
});
const synced = new CultNetSchemaCatalog();
const applied = await requester.syncSchemaCatalog(synced, {
messageId: "peer-schema-catalog",
includeSchemaJson: true,
kinds: ["document_payload"],
timeoutMs: 1_000,
});
assert.ok(applied.some((descriptor) => descriptor.documentType === "ghostlight.agent-state"));
assert.equal(synced.get(applied[0]!.schemaId)?.schemaJson, undefined);
assert.equal(typeof synced.get(applied[0]!.schemaId, { includeSchemaJson: true })?.schemaJson, "string");
requester.close();
responder.close();
});
test("CultNet shard catalog filters descriptors and answers catalog requests", () => {
const catalog = new CultNetShardCatalog();
let watchedShardId: string | undefined;
const unsubscribe = catalog.watch((descriptor) => {
watchedShardId = descriptor.shardId;
});
catalog.upsert({
shardId: "notes-a",
ownerRuntimeId: "ts-owner",
epoch: 7,
isPrimary: true,
schemaIds: ["note.v1"],
keyPrefix: "note:",
primaryEndpoints: ["rudp://127.0.0.1:4100"],
authorityLeaseId: "lease-a",
});
catalog.upsert({
shardId: "profiles-a",
ownerRuntimeId: "ts-owner",
epoch: 3,
schemaIds: ["profile.v1"],
keyPrefix: "profile:",
});
assert.equal(watchedShardId, "profiles-a");
assert.equal(shardServes(catalog.get("notes-a")!, { schemaId: "note.v1", recordKey: "note:1" }), true);
assert.equal(shardServes(catalog.get("notes-a")!, { schemaId: "note", recordKey: "note:1" }), true);
assert.equal(shardServes(catalog.get("notes-a")!, { schemaId: "profile.v1", recordKey: "note:1" }), false);
assert.deepEqual(catalog.list({ schemaIds: ["note.v1"], recordKeys: ["note:1"] }).map((shard) => shard.shardId), ["notes-a"]);
assert.deepEqual(catalog.list({ schemaIds: ["note"], recordKeys: ["note:1"] }).map((shard) => shard.shardId), ["notes-a"]);
const response = catalog.createCatalogResponse({
schemaVersion: "cultnet.shard_catalog_request.v0",
messageId: "shards",
schemaIds: ["note"],
recordKeys: ["note:2"],
});
assert.equal(response.schemaVersion, "cultnet.shard_catalog_response.v0");
assert.equal(response.shards[0]?.shardId, "notes-a");
const parsed = parseCultNetMessage(response);
assert.equal(parsed.schemaVersion, "cultnet.shard_catalog_response.v0");
if (parsed.schemaVersion === "cultnet.shard_catalog_response.v0") {
assert.equal(parsed.shards[0]?.ownerRuntimeId, "ts-owner");
}
unsubscribe();
});
test("CultNet peer can request and sync shard catalogs by message id", async () => {
const { a, b } = createDuplexPair();
const requester = createTcpFramedCultNetPeer(a, {
runtimeId: "shard-requester",
wireContract: "cultnet.schema.v0",
});
const responder = createTcpFramedCultNetPeer(b, {
runtimeId: "shard-responder",
wireContract: "cultnet.schema.v0",
});
const remote = new CultNetShardCatalog();
remote.upsert({
shardId: "notes-peer",
ownerRuntimeId: "shard-responder",
epoch: 1,
isPrimary: true,
schemaIds: ["note.v1"],
keyPrefix: "note:",
primaryEndpoints: ["rudp://127.0.0.1:4200"],
});
responder.on("message", (message) => {
if (message.schemaVersion === "cultnet.shard_catalog_request.v0") {
responder.sendShardCatalogResponse(remote.createCatalogResponse(message));
}
});
const synced = new CultNetShardCatalog();
const applied = await requester.syncShardCatalog(synced, {
messageId: "peer-shards",
schemaIds: ["note"],
recordKeys: ["note:local"],
timeoutMs: 1_000,
});
assert.equal(applied[0]?.shardId, "notes-peer");
assert.equal(synced.get("notes-peer")?.primaryEndpoints?.[0], "rudp://127.0.0.1:4200");
requester.close();
responder.close();
});
test("CultNet schema discovery can round-trip over the legacy wire contract when schemas are requested inline", () => {
const registry = new CultNetSchemaRegistry([
{
schemaId: "https://example.test/contracts/example.schema.json",
kind: "shared_contract",
schema: {
$schema: "https://json-schema.org/draft/2020-12/schema",
$id: "https://example.test/contracts/example.schema.json",
type: "object",
properties: {
value: { type: "string" },
},
required: ["value"],
additionalProperties: false,
},
title: "Example Schema",
wireContracts: ["cultnet.schema.v0", "gamecult.networking.v0"],
},
]);
const response = registry.createCatalogResponse({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "catalog-legacy",
includeSchemaJson: true,
});
const wireValue = encodeCultNetMessageForWire(response, "gamecult.networking.v0");
const decoded = parseCultNetMessage(wireValue, "gamecult.networking.v0");
assert.equal(decoded.schemaVersion, "cultnet.schema_catalog_response.v0");
if (decoded.schemaVersion === "cultnet.schema_catalog_response.v0") {
assert.equal(decoded.messageId, "catalog-legacy");
assert.equal(decoded.schemas[0]?.schemaId, "https://example.test/contracts/example.schema.json");
assert.match(decoded.schemas[0]?.schemaJson ?? "", /"value"/u);
}
});
test("CultNet document registry builds snapshots and applies document puts through CultCache", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-"));
try {
const documentDefinition = defineDocumentType({
type: "ghostlight.agent-state",
schemaId: cultNetSchemas.ghostlightAgentStateSchema.$id,
schemaName: "ghostlight.agent-state",
schemaVersion: "ghostlight.agent_state.v0",
schema: z.custom<GhostlightAgentStateDocument>((value) => {
try {
validateGhostlightAgentState(value);
return true;
} catch {
return false;
}
}),
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({
definition: documentDefinition,
payloadSchemaVersion: "ghostlight.agent_state.v0",
}),
]);
const originStore = new SingleFileMessagePackBackingStore(join(tempDir, "origin.msgpack"));
const targetStore = new SingleFileMessagePackBackingStore(join(tempDir, "target.msgpack"));
const originCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(originStore)
.build();
const targetCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(targetStore)
.build();
const payload = validateGhostlightAgentState({
schema_version: "ghostlight.agent_state.v0",
world: {
world_id: "epiphany-face",
setting: "test harness",
time: { label: "now" },
canon_context: ["test"],
},
agents: [
{
agent_id: "epiphany.persona",
identity: {
name: "Persona",
roles: ["public-surface"],
origin: "test",
public_description: "test",
},
canonical_state: {
underlying_organization: {},
stable_dispositions: {},
behavioral_dimensions: {},
presentation_strategy: {},
voice_style: {},
situational_state: {},
values: [],
},
goals: [],
memories: {
episodic: [],
semantic: [],
relationship_summaries: [],
},
perceived_state_overlays: [],
},
],
relationships: [],
events: [],
scenes: [],
});
await originCache.put(documentDefinition, "epiphany.persona", payload);
const snapshot = registry.createSnapshotResponse(originCache, "snapshot-1");
await registry.applySnapshotResponse(targetCache, snapshot);
const roundTrip = targetCache.get(documentDefinition, "epiphany.persona");
assert.ok(roundTrip);
assert.equal(roundTrip?.schema_version, "ghostlight.agent_state.v0");
assert.equal(roundTrip?.agents[0]?.agent_id, "epiphany.persona");
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("CultNet raw replication preserves CultCache payload bytes for bit-compatible neighbors", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-raw-"));
try {
const documentDefinition = defineDocumentType({
type: "ghostlight.agent-state",
schemaId: cultNetSchemas.ghostlightAgentStateSchema.$id,
schemaName: "ghostlight.agent-state",
schemaVersion: "ghostlight.agent_state.v0",
schema: z.custom<GhostlightAgentStateDocument>((value) => {
try {
validateGhostlightAgentState(value);
return true;
} catch {
return false;
}
}),
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({
definition: documentDefinition,
payloadSchemaVersion: "ghostlight.agent_state.v0",
}),
]);
const originCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "origin.msgpack")))
.build();
const targetCache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "target.msgpack")))
.build();
const payload = validateGhostlightAgentState({
schema_version: "ghostlight.agent_state.v0",
world: {
world_id: "epiphany-face",
setting: "test harness",
time: { label: "now" },
canon_context: ["test"],
},
agents: [
{
agent_id: "epiphany.persona",
identity: {
name: "Persona",
roles: ["public-surface"],
origin: "test",
public_description: "test",
},
canonical_state: {
underlying_organization: {},
stable_dispositions: {},
behavioral_dimensions: {},
presentation_strategy: {},
voice_style: {},
situational_state: {},
values: [],
},
goals: [],
memories: {
episodic: [],
semantic: [],
relationship_summaries: [],
},
perceived_state_overlays: [],
},
],
relationships: [],
events: [],
scenes: [],
});
await originCache.put(documentDefinition, "epiphany.persona", payload);
const rawSnapshot = registry.createRawSnapshotResponse(originCache, "raw-snapshot-1");
assert.equal(rawSnapshot.documents[0]?.schemaId, cultNetSchemas.ghostlightAgentStateSchema.$id);
assert.equal(rawSnapshot.documents[0]?.recordKey, "epiphany.persona");
await registry.applyRawSnapshotResponse(targetCache, rawSnapshot);
const sourceEnvelope = originCache.getRequiredEnvelope(documentDefinition, "epiphany.persona");
const targetEnvelope = targetCache.getRequiredEnvelope(documentDefinition, "epiphany.persona");
assert.deepEqual(targetEnvelope.payload, sourceEnvelope.payload);
assert.equal(targetCache.getRequired(documentDefinition, "epiphany.persona").schema_version, "ghostlight.agent_state.v0");
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("CultNet raw replication under a compatible schema id leaves a store that reopens and takes the next write", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-raw-compatible-"));
try {
const definition = defineDocumentType({
type: "tests.raw-compatible",
schema: z.object({ name: z.string() }),
schemaId: "tests.raw-compatible.current",
schemaName: "tests.raw-compatible",
schemaVersion: "tests.raw_compatible.v1",
compatibleSchemaIds: ["tests.raw-compatible.current", "tests.raw-compatible.older"],
});
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition, payloadSchemaVersion: "tests.raw_compatible.v1" }),
]);
const open = async (): Promise<CultCache> => {
const cache = CultCache.builder()
.withDocumentType(definition)
.withGenericStore(new SingleFileMessagePackBackingStore(join(tempDir, "target.msgpack")))
.build();
await cache.pullAllBackingStores();
return cache;
};
const origin = CultCache.builder().withDocumentType(definition).build();
await origin.put(definition, "seed", { name: "seed" });
const payload = origin.getRequiredEnvelope(definition, "seed").payload;
const target = await open();
await registry.applyRawDocumentPutMessage(target, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: "raw-compatible-1",
document: {
schemaId: "tests.raw-compatible.older",
recordKey: "replicated",
storedAt: "2026-09-30T00:00:00.0000000Z",
payloadEncoding: "messagepack",
payload,
},
} as Parameters<CultNetDocumentRegistry["applyRawDocumentPutMessage"]>[1]);
const reopened = await open();
assert.deepEqual(reopened.getRequired(definition, "replicated"), { name: "seed" });
await reopened.put(definition, "next", { name: "next" });
assert.deepEqual((await open()).getRequired(definition, "next"), { name: "next" });
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("operation envelopes preserve typed service routing and payload correlation", () => {
const request = parseCultNetMessage({
schemaVersion: "cultnet.operation_request.v0",
messageId: "plugin-42",
serviceId: "sai.vn",
operation: "project",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWHEJ2dhbWVjdWx0LmV2ZS5wbHVnaW5fYWJpLnJlcXVlc3QudjE=",
sourceRuntimeId: "eve-unity",
});
assert.equal(request.schemaVersion, "cultnet.operation_request.v0");
assert.equal(request.messageId, "plugin-42");
assert.equal(request.serviceId, "sai.vn");
const response = parseCultNetMessage({
schemaVersion: "cultnet.operation_response.v0",
messageId: "plugin-42",
serviceId: "sai.vn",
operation: "project",
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzdGF0dXOkb2s=",
diagnostics: [],
});
assert.equal(response.schemaVersion, "cultnet.operation_response.v0");
assert.equal(response.messageId, request.messageId);
});
test("rudp session advances large fragment sets through a bounded reliable window", () => {
const connectionId = 457;
const sender = new CultNetRudpSession({ connectionId, initialSequence: 1, resendDelayMs: 25 });
const receiver = new CultNetRudpSession({ connectionId, initialSequence: 100, resendDelayMs: 25 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
const fragmentCount = CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS + 17;
const payload = Uint8Array.from(
{ length: fragmentCount * 8 },
(_, index) => index % 251,
);
const wire = sender.sendMany("schema", payload, {
reliable: true,
ordered: true,
nowMs: 1,
maxFragmentBytes: 8,
});
assert.equal(wire.length, CULTNET_RUDP_RELIABLE_SEND_WINDOW_PACKETS);
const oldestSequence = wire[0]!.sequence;
assert.equal(sender.pendingReliableSequences.length, wire.length);
assert.equal(sender.queuedReliablePacketCount, 17);
const delivered = [];
while (wire.length > 0) {
const admitted = wire.length;
for (let index = 0; index < admitted; index += 1) {
const packet = wire.shift()!;
delivered.push(...receiver.receive(packet, 2).delivered);
}
const acknowledged = sender.receive(receiver.createAck(), 3);
wire.push(...(acknowledged.readyToSend ?? []));
}
assert.equal(sender.outstandingReliablePacketCount, 0);
assert.equal(delivered.length, 1);
assert.deepEqual(delivered[0]?.payload, payload);
const oldAck = receiver.createAckForReceived(oldestSequence);
assert.equal(oldAck.ack, oldestSequence);
assert.equal(oldAck.ackMask, 0);
});
// The sender's flow window: a reliable packet goes on the wire only while its sequence is at most 1,023 above the
// lowest unacked one and the payload above that sequence stays within 4 MiB.
const FLOW_CONNECTION_ID = 0x464c4f57;
const MIB = 1024 * 1024;
function flowSession(initialSequence: number): CultNetRudpSession {
return new CultNetRudpSession({ connectionId: FLOW_CONNECTION_ID, initialSequence, resendDelayMs: 25 });
}
// A sender and a receiver that have shaken hands: the receiver's watermark is seeded by the sender's Connect, and the
// sender's Connect is acknowledged, so nothing is pending on the sender.
function connectedFlowPair(senderInitialSequence: number, receiverInitialSequence: number): [CultNetRudpSession, CultNetRudpSession] {
const sender = flowSession(senderInitialSequence);
const receiver = flowSession(receiverInitialSequence);
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
return [sender, receiver];
}
function connectedFlowSession(initialSequence: number): CultNetRudpSession {
return connectedFlowPair(initialSequence, 900)[0];
}
function flowSend(session: CultNetRudpSession, payloadBytes: number): CultNetRudpPacket[] {
return session.sendMany("state", new Uint8Array(payloadBytes), { reliable: true });
}
function flowAck(sequence: number): CultNetRudpPacket {
return { packetType: "ack", connectionId: FLOW_CONNECTION_ID, sequence: 0, ack: sequence, ackMask: 0, channelId: "control" };
}
const sequencesOf = (packets: CultNetRudpPacket[]): number[] => packets.map((packet) => packet.sequence);
function loseGAndFillTheSpan(session: CultNetRudpSession): CultNetRudpPacket {
const g = flowSend(session, 1)[0]!;
for (let offset = 1; offset <= 1023; offset++) {
assert.deepEqual(sequencesOf(flowSend(session, 1)), [g.sequence + offset]);
session.receive(flowAck(g.sequence + offset), 1);
}
return g;
}
test("a lost packet holds the sender 1023 sequences ahead and is still delivered", () => {
const [sender, receiver] = connectedFlowPair(1, 100);
const g = flowSend(sender, 1)[0]!;
let admittedAfterG = 0;
for (let index = 0; index < 4200; index++) {
for (const packet of flowSend(sender, 1)) {
admittedAfterG++;
receiver.receive(packet, 1);
sender.receive(receiver.createAck(), 1);
}
}
assert.equal(admittedAfterG, 1023);
assert.equal(sender.queuedReliablePacketCount, 4200 - 1023);
const retransmit = sender.dueResends(1000).find((packet) => packet.sequence === g.sequence)!;
const delivered = receiver.receive(retransmit, 1000).delivered;
assert.deepEqual(delivered.map((frame) => frame.sequence), [g.sequence]);
const promoted = sender.receive(receiver.createAckForReceived(g.sequence), 1001).readyToSend ?? [];
assert.equal(promoted[0]!.sequence, g.sequence + 1024);
});
test("a direct send past the span is refused without consuming a sequence", () => {
const sender = connectedFlowSession(1);
const g = loseGAndFillTheSpan(sender);
assert.throws(() => sender.send("state", new Uint8Array(1), { reliable: true }));
assert.equal(sender.queuedReliablePacketCount, 0);
sender.receive(flowAck(g.sequence), 2);
assert.equal(sender.send("state", new Uint8Array(1), { reliable: true }).sequence, g.sequence + 1024);
});
test("payload above the lowest unacked sequence is bounded by 4 MiB", () => {
const sender = connectedFlowSession(1);
const g = flowSend(sender, MIB)[0]!;
// The lowest unacked packet's own bytes do not count: four more MiB fit above it.
for (let index = 0; index < 4; index++) assert.equal(flowSend(sender, MIB).length, 1);
assert.equal(flowSend(sender, MIB).length, 0);
assert.equal(flowSend(sender, MIB).length, 0);
assert.equal(sender.queuedReliablePacketCount, 2);
// Acking g moves the floor up one packet: exactly one MiB more fits.
const promoted = sender.receive(flowAck(g.sequence), 1).readyToSend ?? [];
assert.deepEqual(sequencesOf(promoted), [g.sequence + 5]);
assert.equal(sender.queuedReliablePacketCount, 1);
});
test("a packet larger than the window goes out alone and waits behind anything unacked", () => {
assert.equal(flowSend(connectedFlowSession(1), 5 * MIB).length, 1);
const sender = connectedFlowSession(1);
const g = flowSend(sender, 1)[0]!;
assert.equal(flowSend(sender, 5 * MIB).length, 0);
assert.deepEqual(sequencesOf(sender.receive(flowAck(g.sequence), 1).readyToSend ?? []), [g.sequence + 1]);
});
test("a small packet does not overtake a queued large one", () => {
const sender = connectedFlowSession(1);
const g = flowSend(sender, 1)[0]!;
assert.equal(flowSend(sender, 3 * MIB).length, 1);
assert.equal(flowSend(sender, 2 * MIB).length, 0);
// A direct send refuses instead of queueing behind it, and takes no sequence.
assert.throws(() => sender.send("state", new Uint8Array(1), { reliable: true }));
assert.equal(sender.queuedReliablePacketCount, 1);
// One byte would fit above g, but the 2 MiB packet is ahead of it.
assert.equal(flowSend(sender, 1).length, 0);
assert.deepEqual(sequencesOf(sender.receive(flowAck(g.sequence), 1).readyToSend ?? []), [g.sequence + 2, g.sequence + 3]);
});
test("acknowledged bytes above a lost packet still count against 4 MiB", () => {
const sender = connectedFlowSession(1);
const g = flowSend(sender, 1)[0]!;
// g is lost. Each 1 MiB frame above it is delivered and acknowledged, and the receiver still holds it behind the
// gap, so it keeps counting.
for (let offset = 1; offset <= 4; offset++) {
assert.deepEqual(sequencesOf(flowSend(sender, MIB)), [g.sequence + offset]);
sender.receive(flowAck(g.sequence + offset), 1);
}
assert.equal(flowSend(sender, MIB).length, 0);
assert.equal(sender.queuedReliablePacketCount, 1);
assert.throws(() => sender.send("state", new Uint8Array(MIB), { reliable: true }));
// g arrives: nothing is held behind a gap any more, and the queue drains.
assert.deepEqual(sequencesOf(sender.receive(flowAck(g.sequence), 2).readyToSend ?? []), [g.sequence + 5]);
// The bytes above the old gap no longer count: another MiB fits.
assert.deepEqual(sequencesOf(flowSend(sender, MIB)), [g.sequence + 6]);
});
test("acknowledged bytes count against the lowest unacked sequence as it advances", () => {
const sender = connectedFlowSession(1);
const g = flowSend(sender, 1)[0]!;
const h = flowSend(sender, 1)[0]!;
for (let index = 0; index < 3; index++) flowSend(sender, MIB);
// g and the 3 MiB above h are acknowledged; h is lost.
for (const offset of [2, 3, 4, 0]) sender.receive(flowAck(g.sequence + offset), 1);
assert.deepEqual(sequencesOf(flowSend(sender, MIB)), [g.sequence + 5]);
// 4 MiB sit above h, acknowledged or not: one more byte does not fit.
assert.equal(flowSend(sender, 1).length, 0);
// h arrives, the floor moves to g + 5, and the byte fits.
assert.deepEqual(sequencesOf(sender.receive(flowAck(h.sequence), 2).readyToSend ?? []), [g.sequence + 6]);
});
// A server that accepts a fresh Connect may send before its Accept is acknowledged, so no acknowledgement of the new
// generation has moved the floor yet. Bytes the ended generation had acknowledged above its own lost packet belong to
// no receiver's hold any more and must not count.
test("a new generation on the server does not inherit acknowledged bytes", () => {
const server = flowSession(500);
const accept = server.acceptConnect(flowSession(1).createConnect(0), 0);
server.receive(flowAck(accept.sequence), 0);
const g = flowSend(server, 1)[0]!;
for (let offset = 1; offset <= 4; offset++) {
assert.deepEqual(sequencesOf(flowSend(server, MIB)), [g.sequence + offset]);
server.receive(flowAck(g.sequence + offset), 1);
}
assert.equal(flowSend(server, MIB).length, 0);
const nextAccept = server.acceptConnect(flowSession(200_000).createConnect(2), 2);
assert.equal(nextAccept.packetType, "accept");
// The Accept is unacknowledged and the only thing pending: 1 MiB fits above it.
assert.equal(server.send("state", new Uint8Array(MIB), { reliable: true }).sequence, nextAccept.sequence + 1);
});
test("CultNet contracts encode legacy bytes without Node Buffer authority", () => {
const originalBuffer = globalThis.Buffer;
try {
Object.defineProperty(globalThis, "Buffer", { configurable: true, value: undefined });
const message = parseCultNetMessage({
schemaVersion: "cultnet.login.v0",
nonce: "AQID",
auth: "BAUG",
password: "BwgJ",
});
const wire = encodeCultNetMessageForWire(message, "gamecult.networking.v0") as [number, Uint8Array[]];
assert.equal(wire[0], 0);
assert.deepEqual([...wire[1][0]], [1, 2, 3]);
} finally {
Object.defineProperty(globalThis, "Buffer", { configurable: true, value: originalBuffer });
}
});
test("database subscription contracts match the C# live document lane", () => {
const subscribe = parseCultNetMessage({
schemaVersion: "cultnet.database_subscribe.v0",
messageId: "subscribe-counter",
subscriptionId: "counter:browser-1",
schemaIds: ["sample.counter_state.v1"],
recordKeys: ["counter:main"],
includeSnapshot: true,
consumerRuntimeId: "browser-1",
});
assert.equal(subscribe.schemaVersion, "cultnet.database_subscribe.v0");
assert.equal(subscribe.subscriptionId, "counter:browser-1");
const added = parseCultNetMessage({
schemaVersion: "cultnet.database_change_raw.v0",
messageId: "change-counter-1",
subscriptionId: "counter:browser-1",
changeKind: "added",
document: {
schemaId: "sample.counter_state.v1",
recordKey: "counter:main",
storedAt: "2026-08-17T00:00:00Z",
payloadEncoding: "messagepack",
payload: new Uint8Array([0x81, 0xa5, 0x63, 0x6f, 0x75, 0x6e, 0x74, 0x01]),
},
});
assert.equal(added.schemaVersion, "cultnet.database_change_raw.v0");
assert.equal(added.document?.recordKey, "counter:main");
const removed = parseCultNetMessage({
schemaVersion: "cultnet.database_change_raw.v0",
messageId: "change-counter-2",
subscriptionId: "counter:browser-1",
changeKind: "removed",
recordKey: "counter:main",
schemaId: "sample.counter_state.v1",
});
assert.equal(removed.schemaVersion, "cultnet.database_change_raw.v0");
assert.equal(removed.recordKey, "counter:main");
const unsubscribe = parseCultNetMessage({
schemaVersion: "cultnet.database_unsubscribe.v0",
messageId: "unsubscribe-counter",
subscriptionId: "counter:browser-1",
});
assert.equal(unsubscribe.schemaVersion, "cultnet.database_unsubscribe.v0");
assert.throws(
() => parseCultNetMessage({
schemaVersion: "cultnet.database_change_raw.v0",
messageId: "invalid-change",
subscriptionId: "counter:browser-1",
changeKind: "updated",
}),
/document: is required/,
);
});
test("RUDP operation service correlates typed payload envelopes", async () => {
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
handler: request => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
}),
});
try {
const response = await invokeCultNetOperation(server.endpoint, {
schemaVersion: "cultnet.operation_request.v0",
messageId: "service-1",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWHEJ2dhbWVjdWx0LmV2ZS5wbHVnaW5fYWJpLnJlcXVlc3QudjE=",
}, { runtimeId: "eve-test" });
assert.equal(response.messageId, "service-1");
assert.equal(response.serviceId, "sai.vn");
assert.equal(response.status, "ok");
} finally {
await server.close();
}
});
test("rudp sequence-neutral acknowledgements interoperate with ordered receivers", () => {
const sender = new CultNetRudpSession({ connectionId: 125, initialSequence: 1 });
const receiver = new CultNetRudpSession({ connectionId: 125, initialSequence: 100 });
sender.receive(receiver.acceptConnect(sender.createConnect(0), 0), 0);
const ack = sender.createAck();
const request = sender.send("schema", Buffer.from("snapshot"), { reliable: true, ordered: true });
assert.equal(ack.sequence, 0);
assert.equal(request.sequence, 2);
assert.deepEqual(receiver.receive(ack).delivered, []);
assert.deepEqual(receiver.receive(request).delivered.map(frame => Buffer.from(frame.payload).toString("utf8")), ["snapshot"]);
});
// A replicated record whose schema id no binding declares is refused, whatever its payload parses as: the registry never
// guesses a type from a payload's shape and never relabels a record it does not understand.
test("CultNet raw replication refuses a record under a schema id no binding declares, whatever its shape", async () => {
const note = defineDocumentType({
type: "p5.note", schemaId: "p5.note.v1", schemaName: "p5.note", schema: z.object({ body: z.string() }),
});
// Registered first, and accepts every payload: shape routing would store the record under it.
const anything = defineDocumentType({ type: "p5.anything", schema: z.any() });
const registry = new CultNetDocumentRegistry([
defineCultNetDocumentBinding({ definition: anything }),
defineCultNetDocumentBinding({ definition: note }),
]);
const origin = CultCache.builder().withDocumentType(note).build();
await origin.put(note, "k", { body: "from a peer" });
const snapshot = registry.createRawSnapshotResponse(origin, "p5");
assert.equal(snapshot.documents[0]?.schemaId, "p5.note.v1");
assert.equal(snapshot.documents[0]?.payloadEncoding, "messagepack");
for (const schemaId of ["runtime.generated.p5.note.ui.99", "p5.note.v7", "p5.note"]) {
const target = CultCache.builder().withDocumentType(note).withDocumentType(anything).build();
await assert.rejects(
registry.applyRawSnapshotResponse(target, {
...snapshot,
documents: snapshot.documents.map(document => ({ ...document, schemaId })),
}),
(error: unknown) => error instanceof Error
&& error.message === `No CultNet document binding is registered for schema "${schemaId}".`,
);
assert.deepEqual(target.snapshot(), [], `nothing was stored for ${schemaId}`);
}
});
// The registry stamps and resolves a definition's schema id exactly as CultCache does, so a record a cache wrote is read
// back as the type that wrote it on every peer.
test("CultNet registry derives a definition's schema id as the cache does, and refuses what the cache refuses", async () => {
const shape = z.object({ body: z.string() });
const a = defineDocumentType({ type: "p1.a", schemaName: "p1.shared", schema: shape });
const b = defineDocumentType({ type: "p1.b", schemaId: "p1.shared", schemaName: "p1.b", schema: shape });
const fillers = [0, 1].map(index => defineCultNetDocumentBinding({
definition: defineDocumentType({ type: `p1.filler${index}`, schema: shape }),
}));
assert.deepEqual(schemaIdentityOf(a), {
schemaId: "p1.shared", schemaName: "p1.shared", schemaVersion: "p1.shared.v1", compatibleSchemaIds: ["p1.shared"],
});
// The cache refuses B beside A: A's records carry "p1.shared".
await assert.rejects(CultCache.builder().withDocumentType(a).build().registerDocumentType(b), SchemaConflictError);
for (const [first, second] of [[a, b], [b, a]]) {
// Two bindings stand ahead of the holder, so a check that looks only at the first holder misses it.
const registry = new CultNetDocumentRegistry([...fillers, defineCultNetDocumentBinding({ definition: first })]);
assert.throws(() => registry.register(defineCultNetDocumentBinding({ definition: second })), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
assert.equal(error.schemaId, "p1.shared");
return true;
});
assert.equal(registry.get(second.type), undefined);
}
const registry = new CultNetDocumentRegistry([...fillers, defineCultNetDocumentBinding({ definition: a })]);
const origin = CultCache.builder().withDocumentType(a).build();
await origin.put(a, "k", { body: "written by a" });
const stamped = origin.getRequiredEnvelope(a, "k").schemaId;
assert.equal(stamped, "p1.shared");
assert.equal(registry.createDocumentPutMessage(registry.get("p1.a")!, "m", "k", { body: "x" }).document.schemaId, stamped);
assert.equal(registry.createRawDocumentPutMessage(registry.get("p1.a")!, "m", "k", { body: "x" }).document.schemaId, stamped);
assert.equal(registry.getBySchemaId(stamped!)?.definition, a);
const peer = CultCache.builder().withDocumentType(a).build();
await registry.applyRawSnapshotResponse(peer, registry.createRawSnapshotResponse(origin, "p1"));
assert.deepEqual(peer.getRequired(a, "k"), { body: "written by a" });
assert.equal(peer.getRequiredEnvelope(a, "k").schemaId, stamped);
});
// An id nobody owns resolves to the one binding that lists it as compatible. Several listers and no owner resolve to
// none, and the refusal does not depend on the order they were registered in. An owner beats every lister.
test("CultNet registry refuses an unowned schema id that several bindings list, in any order", async () => {
const shape = z.object({ body: z.string() });
const first = defineDocumentType({ type: "p2.first", schemaId: "p2.first", compatibleSchemaIds: ["p2.old"], schema: shape });
const second = defineDocumentType({ type: "p2.second", schemaId: "p2.second", compatibleSchemaIds: ["p2.old"], schema: shape });
const owner = defineDocumentType({ type: "p2.owner", schemaId: "p2.old", schema: shape });
const bind = (definition: typeof first) => defineCultNetDocumentBinding({ definition });
for (const order of [[first, second], [second, first]]) {
const registry = new CultNetDocumentRegistry(order.map(bind));
assert.throws(() => registry.getBySchemaId("p2.old"), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
assert.equal(error.schemaId, "p2.old");
assert.deepEqual(error.schemaNames, ["p2.first", "p2.second"]);
assert.equal(error.recordKey, "");
assert.ok(error.message.includes("(p2.first, p2.second)"), error.message);
assert.ok(error.message.includes("so it resolves to none. Bind the type that owns the id"), error.message);
return true;
});
const cache = CultCache.builder().withDocumentType(first).withDocumentType(second).build();
const origin = CultCache.builder().withDocumentType(first).build();
await origin.put(first, "k", { body: "old" });
const document = { ...registry.createRawSnapshotResponse(origin, "p2").documents[0]!, schemaId: "p2.old" };
await assert.rejects(
registry.applyRawDocumentPutMessage(cache, { schemaVersion: "cultnet.document_put_raw.v0", messageId: "p2", document }),
SchemaConflictError,
);
assert.deepEqual(cache.snapshot(), []);
for (const withOwner of [[...order, owner], [owner, ...order]]) {
assert.equal(new CultNetDocumentRegistry(withOwner.map(bind)).getBySchemaId("p2.old")?.definition, owner);
}
}
// One lister, bound twice, is still one lister.
const single = new CultNetDocumentRegistry([bind(first), bind(first)]);
assert.equal(single.getBySchemaId("p2.old")?.definition, first);
// A snapshot filtered by an id a binding lists returns that binding's records under their own id.
const origin = CultCache.builder().withDocumentType(first).build();
await origin.put(first, "k", { body: "listed" });
const filter = { schemaVersion: "cultnet.snapshot_request.v0" as const, messageId: "f", schemaIds: ["p2.old"] };
assert.deepEqual(single.createRawSnapshotResponse(origin, "f", filter).documents.map(document => document.schemaId), ["p2.first"]);
assert.deepEqual(single.createSnapshotResponse(origin, "f", filter).documents.map(document => document.schemaId), ["p2.first"]);
});
// A schema id is only ever an id a definition declares. A version string, a schema name, or an id that differs from a
// declared one by its version suffix names nothing.
test("CultNet registry resolves only declared schema ids, never a version, a name or an inferred version", async () => {
const note = defineDocumentType({
type: "p3.note",
schemaId: "p3.note.v1",
schemaName: "p3.note",
schemaVersion: "p4.note.v3",
schema: z.object({ body: z.string() }),
});
const registry = new CultNetDocumentRegistry([defineCultNetDocumentBinding({ definition: note })]);
const cache = CultCache.builder().withDocumentType(note).build();
await cache.put(note, "k", { body: "held" });
assert.equal(registry.getBySchemaId("p3.note.v1")?.definition, note);
for (const undeclared of ["p3.note.v7", "p4.note.v3", "p3.note"]) {
assert.equal(registry.getBySchemaId(undeclared), undefined, undeclared);
const filter = { schemaVersion: "cultnet.snapshot_request.v0" as const, messageId: "f", schemaIds: [undeclared] };
assert.deepEqual(registry.createSnapshotResponse(cache, "f", filter).documents, [], undeclared);
assert.deepEqual(registry.createRawSnapshotResponse(cache, "f", filter).documents, [], undeclared);
await assert.rejects(registry.applyDocumentPutMessage(cache, {
schemaVersion: "cultnet.document_put.v0",
messageId: "put",
document: { schemaId: undeclared, recordKey: "k", storedAt: "2026-09-30T00:00:00.000Z", payload: { body: "relabelled" } },
}), /No CultNet document binding/);
await assert.rejects(registry.applyDocumentDeleteMessage(cache, {
schemaVersion: "cultnet.document_delete.v0", messageId: "delete", schemaId: undeclared, recordKey: "k",
}), /No CultNet document binding/);
assert.deepEqual(cache.getRequired(note, "k"), { body: "held" });
}
const filter = { schemaVersion: "cultnet.snapshot_request.v0" as const, messageId: "f", schemaIds: ["p3.note.v1"] };
assert.equal(registry.createSnapshotResponse(cache, "f", filter).documents.length, 1);
assert.equal(registry.createRawSnapshotResponse(cache, "f", filter).documents.length, 1);
});
test("CultNet document registry binds one type per schema and refuses a second, typed", () => {
const noteShape = z.object({ noteId: z.string() });
const owner = defineDocumentType({ type: "tests.note", schemaId: "id.note", schemaName: "tests.note", schema: noteShape });
const sameId = defineDocumentType({ type: "tests.note-ui", schemaId: "id.note", schemaName: "tests.note-ui", schema: noteShape });
const sameName = defineDocumentType({ type: "tests.note-named", schemaId: "id.other", schemaName: "tests.note", schema: noteShape });
const sameType = defineDocumentType({ type: "tests.note", schemaId: "id.third", schemaName: "tests.third", schema: noteShape });
const lister = defineDocumentType({
type: "tests.note-lister", schemaId: "id.lister", schemaName: "tests.note-lister", compatibleSchemaIds: ["id.note"], schema: noteShape,
});
for (const [claimant, schemaId, names] of [
[sameId, "id.note", ["tests.note", "tests.note-ui"]],
[sameName, "id.other", ["tests.note", "tests.note"]],
[sameType, "id.third", ["tests.note", "tests.third"]],
] as const) {
for (const order of [[owner, claimant], [claimant, owner]]) {
// Two bindings are registered ahead of the holder, so a conflict loop that checks only the first holder fails.
const registry = new CultNetDocumentRegistry([
...["tests.filler-a", "tests.filler-b"].map(type => defineCultNetDocumentBinding({
definition: defineDocumentType({ type, schema: noteShape }),
})),
defineCultNetDocumentBinding({ definition: order[0] }),
]);
assert.throws(() => registry.register(defineCultNetDocumentBinding({ definition: order[1] })), (error: unknown) => {
assert.ok(error instanceof SchemaConflictError, String(error));
const ownerFirst = order[0] === owner;
assert.equal(error.schemaId, ownerFirst ? schemaId : "id.note");
assert.deepEqual(error.schemaNames, ownerFirst ? names : [...names].reverse());
assert.equal(error.recordKey, "");
assert.ok(error.message.includes(`schema id "${error.schemaId}"`), error.message);
return true;
});
// The refused binding left nothing behind: the schema still resolves to the first type.
assert.equal(registry.getBySchemaId(order[0].schemaId!)?.definition, order[0]);
assert.equal(registry.get(order[1].type)?.definition, order[1].type === order[0].type ? order[0] : undefined);
}
}
// The same definition may be bound again, and a definition listing the id as compatible is not a second owner.
const registry = new CultNetDocumentRegistry([defineCultNetDocumentBinding({ definition: owner })]);
registry.register(defineCultNetDocumentBinding({ definition: owner, payloadSchemaVersion: "tests.note.v2" }));
registry.register(defineCultNetDocumentBinding({ definition: lister }));
assert.equal(registry.get("tests.note")?.payloadSchemaVersion, "tests.note.v2");
assert.equal(registry.getBySchemaId("id.note")?.definition, owner);
assert.equal(registry.getBySchemaId("id.lister")?.definition, lister);
});
test("CultNet interop slot compatibility defaults missing trailing fields and rejects mismatched slots", () => {
const note: InteropNote = {
schemaVersion: INTEROP_SCHEMA_VERSION,
documentId: "note:compat",
authorRuntimeId: "compat-peer",
title: "Compatibility",
body: "Missing trailing fields are allowed when declared defaults cover them.",
tags: ["compat"],
};
assert.deepEqual(createInteropFormatter().decode(createLegacyInteropNoteFormatter().encode(note)), {
...note,
tags: [],
});
assert.throws(
() => createInteropFormatter().decode(createMismatchedInteropNoteFormatter().encode(note)),
/Expected string/u,
);
});
test("Ghostlight contract mirror rejects nested payloads that violate the canonical schema", () => {
assert.throws(
() => validateGhostlightAgentState({
schema_version: "ghostlight.agent_state.v0",
world: {
world_id: "ghostlight-lab",
setting: "test",
time: { label: "now" },
canon_context: ["test"],
},
agents: [
{
identity: {
name: "Persona",
roles: ["public-surface"],
origin: "test",
public_description: "test",
},
canonical_state: {
underlying_organization: {},
stable_dispositions: {},
behavioral_dimensions: {},
presentation_strategy: {},
voice_style: {},
situational_state: {},
values: [],
},
goals: [],
memories: {
episodic: [],
semantic: [],
relationship_summaries: [],
},
perceived_state_overlays: [],
},
],
relationships: [],
events: [],
scenes: [],
}),
/agent_id/u,
);
});
test("Generated Ghostlight contracts can feed CultCacheTS directly without a Zod mirror", async () => {
const tempDir = mkdtempSync(join(tmpdir(), "cultnetts-generated-"));
try {
const documentDefinition = defineDocumentType({
type: "ghostlight.agent-state.generated",
schema: ghostlightAgentStateGeneratedContract,
global: true,
});
const store = new SingleFileMessagePackBackingStore(join(tempDir, "generated.msgpack"));
const cache = CultCache.builder()
.withDocumentType(documentDefinition)
.withGenericStore(store)
.build();
const payload: GhostlightAgentStateShape = {
schema_version: "ghostlight.agent_state.v0",
world: {
world_id: "ghostlight-lab",
setting: "test harness",
time: { label: "now" },
canon_context: ["test"],
},
agents: [
{
agent_id: "void",
identity: {
name: "Void",
roles: ["observer"],
origin: "test",
public_description: "test",
},
canonical_state: {
underlying_organization: {},
stable_dispositions: {},
behavioral_dimensions: {},
presentation_strategy: {},
voice_style: {},
situational_state: {},
values: [],
},
goals: [],
memories: {
episodic: [],
semantic: [],
relationship_summaries: [],
},
perceived_state_overlays: [],
},
],
relationships: [],
events: [],
scenes: [],
};
await cache.putGlobal(documentDefinition, payload);
const roundTrip = cache.getRequiredGlobal(documentDefinition);
assert.equal(validateGhostlightAgentStateGenerated(roundTrip), true);
assert.equal(roundTrip.schema_version, "ghostlight.agent_state.v0");
assert.equal(roundTrip.agents[0]?.agent_id, "void");
} finally {
rmSync(tempDir, { recursive: true, force: true });
}
});
test("rudp receiver evicts stranded fragment sets instead of refusing new ones", () => {
// A fragment set is removed only on successful reassembly, so every set that
// loses a fragment is stranded for the life of the session. Ghostlight sends
// Heimdall fragmented envelopes (max_fragment_bytes = 2048), so on the live
// auth path this map grew for every lost fragment and was never swept.
const receiver = new CultNetRudpSession({ connectionId: 9, initialSequence: 1, resendDelayMs: 50 });
receiver.acceptConnect(new CultNetRudpSession({ connectionId: 9, initialSequence: 1 }).createConnect(0), 0);
// 200 fragment sets that each lose their second fragment.
for (let id = 1; id <= 200; id += 1) {
const stranded = receiver.receive({
packetType: "data",
connectionId: 9,
sequence: 100 + id,
ack: 0,
ackMask: 0,
channelId: "schema",
fragmentId: id,
fragmentIndex: 0,
fragmentCount: 2,
payload: Buffer.from([id & 0xff]),
});
assert.equal(stranded.delivered.length, 0, "an incomplete set must not reassemble");
}
// Bounded rather than unbounded, and still serving.
assert.ok(receiver.pendingFragmentSetCount <= 64, `pending ${receiver.pendingFragmentSetCount}`);
assert.ok(receiver.fragmentSetsEvicted > 0, "nothing was evicted");
// The receiver still completes a fresh fragmented payload, which is what
// erroring at the cap took away: any nonzero loss killed the channel.
const first = receiver.receive({
packetType: "data", connectionId: 9, sequence: 900, ack: 0, ackMask: 0,
channelId: "schema", fragmentId: 1000, fragmentIndex: 0, fragmentCount: 2,
payload: Buffer.from("he"),
});
assert.equal(first.delivered.length, 0);
const second = receiver.receive({
packetType: "data", connectionId: 9, sequence: 901, ack: 0, ackMask: 0,
channelId: "schema", fragmentId: 1000, fragmentIndex: 1, fragmentCount: 2,
payload: Buffer.from("llo"),
});
assert.equal(second.delivered.length, 1);
assert.equal(Buffer.from(second.delivered[0]!.payload).toString(), "hello");
});
test("rudp socket transport drops strays without an error event and keeps serving its peer", async () => {
const serverSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const strayHost = await bindUdpSocket();
const connectionId = 0x10203050;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 5,
});
const client = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(serverSocket),
connectionId,
resendPollMs: 5,
});
try {
// No "error" listener on either side: an unhandled "error" event throws
// and would end the process, which is what a stray must never do.
const frames: string[] = [];
server.on("frame", (frame) => frames.push(Buffer.from(frame.payload).toString("utf8")));
client.connect();
await waitFor(() => client.connected && server.connected, "RUDP socket handshake");
const foreign = new CultNetRudpSession({ connectionId: 0x0badf10e });
const strays = [
Buffer.from("not a rudp packet"),
Buffer.alloc(0),
Buffer.from(encodeRudpPacket(foreign.createConnect(0, Buffer.from("foreign")))),
];
for (const stray of strays) {
// From an unrelated address, and from the peer's own address.
strayHost.send(stray, udpPort(serverSocket), "127.0.0.1");
clientSocket.send(stray, udpPort(serverSocket), "127.0.0.1");
}
await waitFor(() => server.stats.packetsDropped >= strays.length * 2, "strays counted");
client.send("schema", Buffer.from("still here", "utf8"));
await waitFor(() => frames.length === 1, "the real frame");
assert.deepEqual(frames, ["still here"]);
// The client side too: a moved flow answers from the server address.
for (const stray of strays) {
serverSocket.send(stray, udpPort(clientSocket), "127.0.0.1");
}
await waitFor(() => client.stats.packetsDropped >= strays.length, "client strays counted");
const clientFrames: string[] = [];
client.on("frame", (frame) => clientFrames.push(Buffer.from(frame.payload).toString("utf8")));
server.send("schema", Buffer.from("server still here", "utf8"));
await waitFor(() => clientFrames.length === 1, "the real server frame");
} finally {
strayHost.close();
client.close();
server.close();
}
});
/** A reliable frame no session accepts: a fragment set with no fragment id. */
function poisonedFrame(session: CultNetRudpSession): CultNetRudpPacket {
const [packet] = session.sendMany("schema", Buffer.from("poison"), { reliable: true, ordered: true, nowMs: 0 });
return { ...packet!, fragmentCount: 2, fragmentId: 0, fragmentIndex: 0 };
}
test("rudp socket transport ends the session that refuses a reliable frame and never acknowledges it", async () => {
const serverSocket = await bindUdpSocket();
const peerSocket = await bindUdpSocket();
const connectionId = 0x10203051;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 1000,
});
const received: CultNetRudpPacket[] = [];
peerSocket.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const peer = new CultNetRudpSession({ connectionId });
const toServer = (packet: CultNetRudpPacket) =>
peerSocket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
const disconnects: string[] = [];
server.on("disconnect", ({ reason }) => disconnects.push(Buffer.from(reason).toString("utf8")));
try {
toServer(peer.createConnect(0));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the Accept");
peer.receive(received.find((p) => p.packetType === "accept")!, 0);
const poison = poisonedFrame(peer);
toServer(poison);
await waitFor(() => disconnects.length === 1, "the server ending the session");
await waitFor(() => received.some((p) => p.packetType === "disconnect"), "the goodbye");
assert.equal(server.stats.packetsDropped, 1);
assert.equal(server.connected, false);
// The peer sees the session end and never sees the frame acknowledged,
// neither in the goodbye nor after it retransmits.
const goodbye = received.find((p) => p.packetType === "disconnect")!;
assert.equal(peer.receive(goodbye, 1).disconnected, true);
assert.equal(acknowledges(goodbye, poison.sequence), false, "the goodbye acknowledged the refused frame");
const before = received.length;
toServer(poison);
await waitFor(() => server.stats.packetsDropped === 2, "the retransmit dropped");
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(received.length, before, "the server must not answer a retransmit for an ended session");
// Only a Connect claims the endpoint again.
const again = new CultNetRudpSession({ connectionId });
toServer(again.createConnect(0));
await waitFor(() => received.length > before, "the new Accept");
assert.ok(received.slice(before).some((p) => p.packetType === "accept"));
} finally {
peerSocket.close();
server.close();
}
});
test("rudp sessions reject sequence spaces and queues that cannot admit a Connect", () => {
assert.throws(() => new CultNetRudpSession({ connectionId: 1, initialSequence: 0xffff_ffff }), /initialSequence/);
assert.throws(() => new CultNetRudpSession({ connectionId: 1, maxPendingReliablePackets: 0 }), /greater than zero/);
assert.doesNotThrow(() => new CultNetRudpSession({ connectionId: 1, initialSequence: 0xffff_fffe, maxPendingReliablePackets: 1 }));
});
test("operation service drops and counts a packet its session refuses and keeps serving", async () => {
const handler = (request: CultNetOperationRequestMessage): CultNetOperationResponseMessage => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
});
const server = await startCultNetOperationServer({ runtimeId: "sai-sidecar", handler });
const raw = await bindUdpSocket();
const port = Number(new URL(server.endpoint).port);
const connectionId = 0x43554c54;
const received: CultNetRudpPacket[] = [];
raw.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const peer = new CultNetRudpSession({ connectionId });
const toServer = (packet: CultNetRudpPacket) => raw.send(encodeRudpPacket(packet), port, "127.0.0.1");
try {
toServer(peer.createConnect(0));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the Accept");
peer.receive(received[0]!, 0);
toServer(poisonedFrame(peer));
await waitFor(() => server.packetsDropped === 1, "the poisoned frame dropped");
await waitFor(() => received.some((p) => p.packetType === "disconnect"), "the session ended");
// A frame the session accepts but the service cannot handle rejects inside
// the handler; that too is a dropped packet, not a dead process.
const garbage = new CultNetRudpSession({ connectionId });
received.length = 0;
toServer(garbage.createConnect(0));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the second Accept");
garbage.receive(received[0]!, 0);
const [notMessagePack] = garbage.sendMany("schema", Buffer.from([0xc1]), { reliable: true, ordered: true, nowMs: 0 });
toServer(notMessagePack!);
await waitFor(() => server.packetsDropped === 2, "the unhandled frame dropped");
// The session that could not serve it ends and the client is told, so its
// retransmit is not acknowledged for a request that was never handled.
await waitFor(() => received.some((p) => p.packetType === "disconnect"), "the goodbye");
const beforeRetransmit = received.length;
toServer(notMessagePack!);
await waitFor(() => server.packetsDropped === 3, "the retransmit dropped");
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(received.length, beforeRetransmit, "the retransmit of an unhandled request was answered");
const response = await invokeCultNetOperation(server.endpoint, {
schemaVersion: "cultnet.operation_request.v0",
messageId: "after-poison",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
}, { runtimeId: "eve-test" });
assert.equal(response.messageId, "after-poison");
} finally {
raw.close();
await server.close();
}
});
test("operation service drops a Connect once its session table is full", async () => {
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
handler: () => { throw new Error("no requests expected"); },
});
const port = Number(new URL(server.endpoint).port);
const sockets: Socket[] = [];
try {
for (let index = 0; index < 66; index += 1) {
const socket = await bindUdpSocket();
sockets.push(socket);
socket.send(encodeRudpPacket(new CultNetRudpSession({ connectionId: 0x43554c54 }).createConnect(0)), port, "127.0.0.1");
}
await waitFor(() => server.packetsDropped === 2, "the Connects past the cap dropped");
await new Promise((resolve) => setTimeout(resolve, 30));
assert.equal(server.packetsDropped, 2);
} finally {
for (const socket of sockets) socket.close();
await server.close();
}
});
test("rudp flush fails for a write the ended session forgot", async () => {
const serverSocket = await bindUdpSocket();
const peerSocket = await bindUdpSocket();
const connectionId = 0x10203056;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 1000,
});
const received: CultNetRudpPacket[] = [];
peerSocket.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const peer = new CultNetRudpSession({ connectionId });
const toServer = (packet: CultNetRudpPacket) =>
peerSocket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
try {
toServer(peer.createConnect(0));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the Accept");
peer.receive(received.find((p) => p.packetType === "accept")!, 0);
server.send("schema", Buffer.from("never acknowledged"));
toServer(poisonedFrame(peer));
await waitFor(() => server.stats.packetsDropped === 1, "the refusal");
await assert.rejects(server.flush(200), /ended before its reliable writes were acknowledged/);
} finally {
peerSocket.close();
server.close();
}
});
test("a Connect that repeats after its Accept was acknowledged is answered with an Ack and queues nothing", () => {
const connectionId = 0x10203057;
const client = new CultNetRudpSession({ connectionId });
const server = new CultNetRudpSession({ connectionId });
const connect = client.createConnect(0);
const accept = server.acceptConnect(connect, 10);
client.receive(accept, 11);
server.receive(client.createAckForReceived(accept.sequence), 12);
assert.equal(server.outstandingReliablePacketCount, 0);
for (let attempt = 0; attempt < 20; attempt += 1) {
assert.equal(server.acceptConnect(connect, 20 + attempt).packetType, "ack");
}
assert.equal(server.outstandingReliablePacketCount, 0);
});
test("server-mode transport and operation service owe one reliable Accept however many Connects repeat", async () => {
const connectionId = 0x43554c54;
const serverSocket = await bindUdpSocket();
const transport = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 1000,
});
const service = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
handler: () => { throw new Error("no requests expected"); },
});
const raw = await bindUdpSocket();
let received: CultNetRudpPacket[] = [];
raw.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const settle = () => new Promise((resolve) => setTimeout(resolve, 100));
try {
for (const port of [udpPort(serverSocket), Number(new URL(service.endpoint).port)]) {
const peer = new CultNetRudpSession({ connectionId });
const connect = peer.createConnect(0);
received = [];
for (let attempt = 0; attempt < 20; attempt += 1) raw.send(encodeRudpPacket(connect), port, "127.0.0.1");
await settle();
const accepts = received.filter((p) => p.packetType === "accept");
assert.equal(new Set(accepts.map((p) => p.sequence)).size, 1, `reliable Accepts sent by port ${port}`);
// Once the Accept is acknowledged, a repeated Connect is answered with an Ack, never a new Accept.
peer.receive(accepts[0]!, 0);
raw.send(encodeRudpPacket(peer.createAckForReceived(accepts[0]!.sequence)), port, "127.0.0.1");
await settle();
received = [];
for (let attempt = 0; attempt < 20; attempt += 1) raw.send(encodeRudpPacket(connect), port, "127.0.0.1");
await settle();
assert.equal(received.filter((p) => p.packetType === "accept").length, 0, `Accepts sent by port ${port} after the ack`);
assert.ok(received.some((p) => p.packetType === "ack"), `an Ack from port ${port}`);
}
assert.equal(transport.outstandingReliablePacketCount, 0);
} finally {
raw.close();
transport.close();
await service.close();
}
});
test("operation service frees the slots of abandoned sessions once they idle out", async () => {
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
sessionIdleTimeoutMs: 300,
handler: (request): CultNetOperationResponseMessage => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
}),
});
const port = Number(new URL(server.endpoint).port);
const sockets: Socket[] = [];
const request: CultNetOperationRequestMessage = {
schemaVersion: "cultnet.operation_request.v0",
messageId: "after-abandonment",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
};
try {
for (let index = 0; index < 64; index += 1) {
const socket = await bindUdpSocket();
sockets.push(socket);
socket.send(encodeRudpPacket(new CultNetRudpSession({ connectionId: 0x43554c54 }).createConnect(0)), port, "127.0.0.1");
}
await new Promise((resolve) => setTimeout(resolve, 60));
await assert.rejects(invokeCultNetOperation(server.endpoint, request, { runtimeId: "eve-test", timeoutMs: 100 }));
await new Promise((resolve) => setTimeout(resolve, 400));
const response = await invokeCultNetOperation(server.endpoint, request, { runtimeId: "eve-test" });
assert.equal(response.messageId, "after-abandonment");
} finally {
for (const socket of sockets) socket.close();
await server.close();
}
});
test("an RUDP client binds loopback only for a loopback endpoint", async () => {
assert.equal(await rudpClientBindHost("10.77.0.1"), "0.0.0.0");
assert.equal(await rudpClientBindHost("2001:db8::1"), "::");
assert.equal(await rudpClientBindHost("[2001:db8::1]"), "::");
assert.equal(await rudpClientBindHost("127.0.0.1"), "127.0.0.1");
assert.equal(await rudpClientBindHost("127.0.0.2"), "127.0.0.1");
assert.equal(await rudpClientBindHost("::1"), "::1");
assert.equal(await rudpClientBindHost("[::1]"), "::1");
});
// A socket bound to loopback cannot send off the host on Windows. On Linux it
// reaches a local non-loopback address, but from 127.0.0.1, so the source
// address shows which one the client bound.
test("an operation client for a non-loopback endpoint does not send from loopback", async (t) => {
const address = Object.values(networkInterfaces())
.flat()
.find((entry) => entry && entry.family === "IPv4" && !entry.internal)?.address;
if (!address) {
t.skip("no non-loopback IPv4 interface on this host");
return;
}
const listener = dgram.createSocket("udp4");
await new Promise<void>((resolve) => listener.bind(0, "0.0.0.0", resolve));
const source = new Promise<string>((resolve) => listener.once("message", (_wire, remote) => resolve(remote.address)));
const request: CultNetOperationRequestMessage = {
schemaVersion: "cultnet.operation_request.v0",
messageId: "bind-host",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
};
try {
await assert.rejects(invokeCultNetOperation(`rudp://${address}:${udpPort(listener)}`, request, { runtimeId: "bind-test", timeoutMs: 300 }));
assert.equal(await source, address);
} finally {
listener.close();
}
});
test("server-mode transport replaces its peer when a Connect arrives from another endpoint", async () => {
const serverSocket = await bindUdpSocket();
const socketA = await bindUdpSocket();
const socketB = await bindUdpSocket();
const connectionId = 0x10203058;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 1000,
});
const receivedByA: CultNetRudpPacket[] = [];
socketA.on("message", (wire) => receivedByA.push(decodeRudpPacket(wire)));
const receivedByB: CultNetRudpPacket[] = [];
socketB.on("message", (wire) => receivedByB.push(decodeRudpPacket(wire)));
const frames: string[] = [];
server.on("frame", (frame: { payload: Uint8Array }) => frames.push(Buffer.from(frame.payload).toString("utf8")));
const peerA = new CultNetRudpSession({ connectionId });
const peerB = new CultNetRudpSession({ connectionId });
const send = (socket: Socket, packet: CultNetRudpPacket) =>
socket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
try {
send(socketA, peerA.createConnect(0));
await waitFor(() => receivedByA.some((p) => p.packetType === "accept"), "A's Accept");
const acceptA = receivedByA.find((p) => p.packetType === "accept")!;
peerA.receive(acceptA, 0);
send(socketA, peerA.createAckForReceived(acceptA.sequence));
await waitFor(() => server.outstandingReliablePacketCount === 0, "A's Accept acknowledged");
send(socketB, peerB.createConnect(0));
await waitFor(() => receivedByB.some((p) => p.packetType === "accept"), "B's Accept");
peerB.receive(receivedByB.find((p) => p.packetType === "accept")!, 0);
// B owns the session; A's traffic is a stranger's and is dropped.
const [fromA] = peerA.sendMany("schema", Buffer.from("from-A"), { reliable: true, ordered: true, nowMs: 0 });
send(socketA, fromA!);
const [fromB] = peerB.sendMany("schema", Buffer.from("from-B"), { reliable: true, ordered: true, nowMs: 0 });
send(socketB, fromB!);
await waitFor(() => frames.length > 0, "B's frame");
await new Promise((resolve) => setTimeout(resolve, 30));
assert.deepEqual(frames, ["from-B"]);
assert.equal(server.stats.packetsDropped, 1);
} finally {
socketA.close();
socketB.close();
assert.doesNotThrow(() => server.close());
}
});
test("operation service does not idle out a session while its handler is running", async () => {
const respond = (request: CultNetOperationRequestMessage): CultNetOperationResponseMessage => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
});
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
sessionIdleTimeoutMs: 200,
handler: async (request) => {
await new Promise((resolve) => setTimeout(resolve, 700));
return respond(request);
},
});
try {
const response = await invokeCultNetOperation(server.endpoint, {
schemaVersion: "cultnet.operation_request.v0",
messageId: "slow",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
}, { runtimeId: "eve-test", timeoutMs: 3000 });
assert.equal(response.messageId, "slow");
} finally {
await server.close();
}
});
test("a flush is bound to the session it started in, even when a disconnect listener reconnects", async () => {
const peerSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const connectionId = 0x10203059;
const client = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(peerSocket),
connectionId,
resendDelayMs: 1000,
resendPollMs: 1000,
});
const received: CultNetRudpPacket[] = [];
let clientPort = 0;
peerSocket.on("message", (wire, remote) => {
clientPort = remote.port;
received.push(decodeRudpPacket(wire));
});
const peer = new CultNetRudpSession({ connectionId });
try {
client.connect();
await waitFor(() => received.some((p) => p.packetType === "connect"), "the Connect");
const accept = peer.acceptConnect(received.find((p) => p.packetType === "connect")!, 0);
peerSocket.send(encodeRudpPacket(accept), clientPort, "127.0.0.1");
await waitFor(() => client.connected, "the client connected");
client.send("schema", Buffer.from("never acknowledged"));
client.on("disconnect", () => client.connect());
const flushed = client.flush(500);
const outcome = assert.rejects(flushed, /ended before its reliable writes were acknowledged/);
peerSocket.send(encodeRudpPacket(peer.createDisconnect(Buffer.from("bye"))), clientPort, "127.0.0.1");
await outcome;
} finally {
peerSocket.close();
client.close();
}
});
test("a repeated Connect acknowledges what it carries", () => {
const connectionId = 0x1020305a;
const client = new CultNetRudpSession({ connectionId });
const server = new CultNetRudpSession({ connectionId });
const connect = client.createConnect(0);
const accept = server.acceptConnect(connect, 0);
const reply = server.acceptConnect({ ...connect, ack: accept.sequence }, 1);
assert.equal(reply.packetType, "ack");
assert.equal(server.outstandingReliablePacketCount, 0);
});
async function timedOutServerWithUnackedWrite(connectionId: number) {
const serverSocket = await bindUdpSocket();
const socketA = await bindUdpSocket();
const socketB = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendDelayMs: 1000,
resendPollMs: 1000,
});
const receivedByA: CultNetRudpPacket[] = [];
socketA.on("message", (wire) => receivedByA.push(decodeRudpPacket(wire)));
const peerA = new CultNetRudpSession({ connectionId });
const send = (socket: Socket, packet: CultNetRudpPacket) =>
socket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
send(socketA, peerA.createConnect(0));
await waitFor(() => receivedByA.some((p) => p.packetType === "accept"), "A's Accept");
const acceptA = receivedByA.find((p) => p.packetType === "accept")!;
peerA.receive(acceptA, 0);
send(socketA, peerA.createAckForReceived(acceptA.sequence));
await waitFor(() => server.outstandingReliablePacketCount === 0, "A's Accept acknowledged");
server.send("schema", Buffer.from("never acknowledged"));
assert.ok(server.outstandingReliablePacketCount > 0);
const flushed = server.flush(2000);
const outcome = assert.rejects(flushed, /ended before its reliable writes were acknowledged/);
// The flush must have started before the timeout fires.
await new Promise((resolve) => setTimeout(resolve, 20));
assert.equal(server.checkTimeout(50, Date.now() + 10_000), true);
return {
server, socketA, socketB, outcome, send,
close: () => {
socketA.close();
socketB.close();
server.close();
},
};
}
test("a flush waiting on an unacknowledged write fails as soon as the session times out", async () => {
const t = await timedOutServerWithUnackedWrite(0x1020305a);
try {
await t.outcome;
} finally {
t.close();
}
});
test("a flush fails when a Connect from another endpoint follows a timeout", async () => {
const t = await timedOutServerWithUnackedWrite(0x1020305b);
try {
t.send(t.socketB, new CultNetRudpSession({ connectionId: 0x1020305b }).createConnect(0));
await t.outcome;
} finally {
t.close();
}
});
test("a flush fails when a Connect from the same endpoint follows a timeout", async () => {
const t = await timedOutServerWithUnackedWrite(0x1020305c);
try {
t.send(t.socketA, new CultNetRudpSession({ connectionId: 0x1020305c }).createConnect(0));
await t.outcome;
} finally {
t.close();
}
});
test("operation service sweeps a session that ran a handler once it idles out", async () => {
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
sessionIdleTimeoutMs: 300,
handler: (request): CultNetOperationResponseMessage => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
}),
});
const port = Number(new URL(server.endpoint).port);
const sockets: Socket[] = [];
const request: CultNetOperationRequestMessage = {
schemaVersion: "cultnet.operation_request.v0",
messageId: "after-handling",
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
};
const connectionId = 0x43554c54;
try {
// Sixty-four clients each run a handler and then go silent without a
// Disconnect. Only the idle sweep can free their slots.
const clients: { socket: Socket; peer: CultNetRudpSession; handled: () => boolean }[] = [];
for (let index = 0; index < 64; index += 1) {
const socket = await bindUdpSocket();
sockets.push(socket);
const peer = new CultNetRudpSession({ connectionId });
let handled = false;
let accepted = false;
socket.on("message", (wire) => {
const packet = decodeRudpPacket(wire);
if (packet.packetType === "accept" && !accepted) {
accepted = true;
peer.receive(packet, 0);
for (const out of peer.sendMany("schema", Buffer.from(encode(encodeCultNetMessageForWire(request, "cultnet.schema.v0"))), { reliable: true, ordered: true, nowMs: 0 })) {
socket.send(encodeRudpPacket(out), port, "127.0.0.1");
}
} else if (packet.channelId === "schema") {
handled = true;
}
});
socket.send(encodeRudpPacket(peer.createConnect(0)), port, "127.0.0.1");
clients.push({ socket, peer, handled: () => handled });
}
await waitFor(() => clients.every((c) => c.handled()), "every handler ran");
await new Promise((resolve) => setTimeout(resolve, 700));
const response = await invokeCultNetOperation(server.endpoint, request, { runtimeId: "eve-test", timeoutMs: 1000 });
assert.equal(response.messageId, "after-handling");
} finally {
for (const socket of sockets) socket.close();
await server.close();
}
});
test("a flush started after a reconnect waits on the new session", async () => {
const peerSocket = await bindUdpSocket();
const clientSocket = await bindUdpSocket();
const connectionId = 0x1020305b;
const client = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-client",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: udpPort(peerSocket),
connectionId,
resendDelayMs: 1000,
resendPollMs: 1000,
});
const received: CultNetRudpPacket[] = [];
let clientPort = 0;
peerSocket.on("message", (wire, remote) => {
clientPort = remote.port;
received.push(decodeRudpPacket(wire));
});
try {
client.connect();
await waitFor(() => received.some((p) => p.packetType === "connect"), "the Connect");
const first = new CultNetRudpSession({ connectionId });
peerSocket.send(encodeRudpPacket(first.acceptConnect(received[0]!, 0)), clientPort, "127.0.0.1");
await waitFor(() => client.connected, "the client connected");
client.send("schema", Buffer.from("owed to the old session"));
client.checkTimeout(50, Date.now() + 10_000);
assert.equal(client.connected, false);
client.connect();
await waitFor(() => received.filter((p) => p.packetType === "connect").length === 2, "the reconnect");
const second = new CultNetRudpSession({ connectionId });
const reconnect = received.filter((p) => p.packetType === "connect")[1]!;
peerSocket.send(encodeRudpPacket(second.acceptConnect(reconnect, 0)), clientPort, "127.0.0.1");
await waitFor(() => client.connected, "the client reconnected");
await client.flush(500);
} finally {
peerSocket.close();
client.close();
}
});
test("a flush started after the server accepts a new Connect waits on the new session", async () => {
const serverSocket = await bindUdpSocket();
const peerSocket = await bindUdpSocket();
const connectionId = 0x1020305c;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendDelayMs: 1000,
resendPollMs: 1000,
});
const received: CultNetRudpPacket[] = [];
peerSocket.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const toServer = (packet: CultNetRudpPacket) =>
peerSocket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
try {
const first = new CultNetRudpSession({ connectionId });
toServer(first.createConnect(0));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the first Accept");
toServer(first.createDisconnect(Buffer.from("bye")));
await waitFor(() => !server.connected, "the first session ended");
const next = new CultNetRudpSession({ connectionId });
toServer(next.createConnect(0));
await waitFor(() => received.filter((p) => p.packetType === "accept").length === 2, "the second Accept");
const accept = received.filter((p) => p.packetType === "accept")[1]!;
next.receive(accept, 0);
toServer(next.createAckForReceived(accept.sequence));
await waitFor(() => server.outstandingReliablePacketCount === 0, "the second Accept acknowledged");
await server.flush(500);
} finally {
peerSocket.close();
server.close();
}
});
test("server-mode transport admits a restarted client and keeps its session when a Connect is retransmitted", async () => {
const serverSocket = await bindUdpSocket();
const peerSocket = await bindUdpSocket();
const connectionId = 0x10203059;
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "rudp-server",
socket: serverSocket,
mode: "server",
connectionId,
resendPollMs: 1000,
});
let received: CultNetRudpPacket[] = [];
peerSocket.on("message", (wire) => received.push(decodeRudpPacket(wire)));
const frames: string[] = [];
server.on("frame", (frame: { payload: Uint8Array }) => frames.push(Buffer.from(frame.payload).toString("utf8")));
const toServer = (packet: CultNetRudpPacket) =>
peerSocket.send(encodeRudpPacket(packet), udpPort(serverSocket), "127.0.0.1");
const reliable = { reliable: true, ordered: true };
try {
const first = new CultNetRudpSession({ connectionId, initialSequence: 50_000 });
const connect = first.createConnect(0, Buffer.from("same"));
toServer(connect);
await waitFor(() => received.some((p) => p.packetType === "accept"), "the first Accept");
first.receive(received.find((p) => p.packetType === "accept")!, 0);
const sent = [0, 1, 2].map((index) => first.send("schema", Buffer.from(`c${index}`), reliable));
sent.forEach(toServer);
await waitFor(() => frames.length === 3, "the first client's frames");
// A retransmitted Connect is a repeat: what was delivered stays delivered once.
toServer(connect);
toServer(sent[0]!);
await new Promise((resolve) => setTimeout(resolve, 100));
assert.deepEqual(frames, ["c0", "c1", "c2"]);
// The same client restarted on the same address, connection id and payload.
received = [];
const second = new CultNetRudpSession({ connectionId, initialSequence: 7 });
toServer(second.createConnect(0, Buffer.from("same")));
await waitFor(() => received.some((p) => p.packetType === "accept"), "the restarted client's Accept");
second.receive(received.find((p) => p.packetType === "accept")!, 0);
toServer(second.send("schema", Buffer.from("after restart"), reliable));
await waitFor(() => frames.includes("after restart"), "the restarted client's frame");
} finally {
peerSocket.close();
server.close();
}
});
// A request belongs to the session generation it arrived on. A client that connects again
// from the same endpoint while a handler runs owns that endpoint now: nothing the handler
// returns or throws for the old generation may reach it.
async function restartDuringHandler(settle: (release: { resolve: () => void; reject: (error: Error) => void }) => void) {
let release: { resolve: () => void; reject: (error: Error) => void } | undefined;
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
handler: async (request) => {
if (request.messageId === "held") {
await new Promise<void>((resolve, reject) => { release = { resolve, reject: (error) => reject(error) }; });
}
return {
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
};
},
});
const connectionId = 0x43554c54;
const port = Number(new URL(server.endpoint).port);
const socket = await bindUdpSocket();
let session = new CultNetRudpSession({ connectionId, initialSequence: 50_000 });
const answered: string[] = [];
const goodbyes: string[] = [];
socket.on("message", (wire) => {
const packet = decodeRudpPacket(wire);
if (packet.packetType === "disconnect") goodbyes.push(Buffer.from(packet.payload ?? []).toString("utf8"));
const result = session.receive(packet, Date.now());
if (packet.packetType === "accept" || packet.reliable) {
socket.send(encodeRudpPacket(session.createAckForReceived(packet.sequence)), port, "127.0.0.1");
}
for (const frame of result.delivered) {
answered.push((decodeMsgpack(frame.payload) as { messageId: string }).messageId);
}
});
const toServer = (packet: CultNetRudpPacket) => socket.send(encodeRudpPacket(packet), port, "127.0.0.1");
const request = (messageId: string) => Buffer.from(encode(encodeCultNetMessageForWire({
schemaVersion: "cultnet.operation_request.v0",
messageId,
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
} satisfies CultNetOperationRequestMessage)));
try {
toServer(session.createConnect(Date.now()));
await waitFor(() => session.connected, "the first Accept");
toServer(session.send("schema", request("held"), { reliable: true, ordered: true }));
await waitFor(() => release !== undefined, "the held handler");
session = new CultNetRudpSession({ connectionId, initialSequence: 7 });
toServer(session.createConnect(Date.now()));
await waitFor(() => session.connected, "the restarted client's Accept");
settle(release!);
await new Promise((resolve) => setTimeout(resolve, 100));
toServer(session.send("schema", request("after-restart"), { reliable: true, ordered: true }));
await waitFor(() => answered.includes("after-restart"), "the restarted client's answer");
assert.deepEqual(answered, ["after-restart"], "the old generation's response reached the new session");
assert.deepEqual(goodbyes, [], "a goodbye for the old generation reached the new session");
assert.equal(session.connected, true);
} finally {
socket.close();
await server.close();
}
}
test("an operation response for a generation a new Connect replaced is not sent into the new session", async () => {
await restartDuringHandler((release) => release.resolve());
});
test("an operation handler that fails after a new Connect replaced its generation says no goodbye to the new session", async () => {
await restartDuringHandler((release) => release.reject(new Error("handler failed")));
});
test("RUDP operation service admits a restarted client on the same address", async () => {
const server = await startCultNetOperationServer({
runtimeId: "sai-sidecar",
handler: request => ({
schemaVersion: "cultnet.operation_response.v0",
messageId: request.messageId,
serviceId: request.serviceId,
operation: request.operation,
status: "ok",
payloadSchema: "gamecult.eve.plugin_abi.response.v1",
payloadEncoding: "messagepack-base64",
payload: request.payload,
diagnostics: [],
sourceRuntimeId: "sai-sidecar",
}),
});
const connectionId = 0x43554c54;
const port = Number(new URL(server.endpoint).port);
const socket = await bindUdpSocket();
let session = new CultNetRudpSession({ connectionId, initialSequence: 50_000 });
const answered: string[] = [];
socket.on("message", (wire) => {
const packet = decodeRudpPacket(wire);
const result = session.receive(packet, Date.now());
if (packet.packetType === "accept" || packet.reliable) {
socket.send(encodeRudpPacket(session.createAckForReceived(packet.sequence)), port, "127.0.0.1");
}
for (const frame of result.delivered) {
answered.push((decodeMsgpack(frame.payload) as { messageId: string }).messageId);
}
});
const toServer = (packet: CultNetRudpPacket) => socket.send(encodeRudpPacket(packet), port, "127.0.0.1");
const request = (messageId: string) => Buffer.from(encode(encodeCultNetMessageForWire({
schemaVersion: "cultnet.operation_request.v0",
messageId,
serviceId: "sai.vn",
operation: "describe",
payloadSchema: "gamecult.eve.plugin_abi.request.v1",
payloadEncoding: "messagepack-base64",
payload: "gaZzY2hlbWE=",
} satisfies CultNetOperationRequestMessage)));
try {
toServer(session.createConnect(Date.now()));
await waitFor(() => session.connected, "the first Accept");
toServer(session.send("schema", request("before-restart"), { reliable: true, ordered: true }));
await waitFor(() => answered.includes("before-restart"), "the first answer");
session = new CultNetRudpSession({ connectionId, initialSequence: 7 });
toServer(session.createConnect(Date.now()));
await waitFor(() => session.connected, "the restarted client's Accept");
toServer(session.send("schema", request("after-restart"), { reliable: true, ordered: true }));
await waitFor(() => answered.includes("after-restart"), "the restarted client's answer");
} finally {
socket.close();
await server.close();
}
});