CultLib/packages/cultnet-ts/test/interop/cultnet-interop.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

4450 lines
166 KiB
TypeScript

import assert from "node:assert/strict";
import { execFile, spawn, spawnSync, type ChildProcessByStdio } from "node:child_process";
import { createHash } from "node:crypto";
import dgram, { type Socket } from "node:dgram";
import { once } from "node:events";
import { existsSync, rmSync } from "node:fs";
import { connect as connectTcp, createServer } from "node:net";
import { homedir, networkInterfaces, tmpdir } from "node:os";
import { delimiter, dirname, join, resolve } from "node:path";
import type { Readable, Writable } from "node:stream";
import { test } from "node:test";
import { setTimeout as delay } from "node:timers/promises";
import { promisify } from "node:util";
import { decode, encode } from "@msgpack/msgpack";
import { encodeFrame, LengthPrefixedMessageFramer } from "../../src/framing";
import { CultNetRudpSocketTransportConnection, decodeRudpPacket } from "../../src";
import { INTEROP_SCHEMA_VERSION } from "./cultnet-interop-shared";
const execFileAsync = promisify(execFile);
const cargoCommand = process.env.CARGO ?? (process.platform === "win32" ? join(homedir(), ".cargo", "bin", "cargo.exe") : "cargo");
const dotnetCommand = process.env.DOTNET ?? (process.platform === "win32" ? join("C:", "Program Files", "dotnet", "dotnet.exe") : "dotnet");
const pythonCommand = process.env.PYTHON ?? resolvePythonCommand();
const cultNetTsRoot = resolve(__dirname, "../../..");
const cultLibRoot = findAncestor(cultNetTsRoot, "CultLib.sln") ?? resolve(cultNetTsRoot, "..", "CultLib");
const cultcachePyRoot = resolve(cultLibRoot, "packages", "cultcache-py");
const cultcachePySrc = ["cultcache-py", "cultnet-py", "cultmesh-py"]
.map((name) => resolve(cultLibRoot, "packages", name, "src"))
.join(delimiter);
const cultmeshKotlinRoot = resolve(cultLibRoot, "packages", "cultmesh-kotlin");
const kotlinHome = process.env.KOTLIN_HOME ?? "C:\\Program Files\\Android\\Android Studio\\plugins\\Kotlin\\kotlinc";
const kotlinJavaHome = process.env.KOTLIN_JAVA_HOME ?? "C:\\Program Files\\Android\\Android Studio\\jbr";
const kotlinJavaCommand = process.env.JAVA ?? join(kotlinJavaHome, "bin", process.platform === "win32" ? "java.exe" : "java");
const kotlinStdlibPath = join(kotlinHome, "lib", "kotlin-stdlib.jar");
const kotlinJarPath = resolve(cultLibRoot, "artifacts", "cultmesh-kotlin", "cultmesh-kotlin.jar");
const kotlinClasspath = [kotlinJarPath, kotlinStdlibPath].join(delimiter);
const cultnetRsRoot = existsSync(resolve(cultLibRoot, "packages", "cultnet-rs"))
? resolve(cultLibRoot, "packages", "cultnet-rs")
: resolve(cultNetTsRoot, "..", "cultnet-rs");
const tsPeerScript = resolve(cultNetTsRoot, "dist-test", "test", "interop", "cultnet-interop-peer.js");
const interopSchemaPath = resolve(cultNetTsRoot, "..", "..", "contracts", "cultnet", "interop", "cultnet.interop-note.schema.json");
const interopNoteSchemaId = "https://github.com/GameCult/CultLib/contracts/cultnet/interop/cultnet.interop-note.schema.json";
const witnessArtifactBundleSchemaId = "https://github.com/GameCult/CultLib/contracts/cultnet/cultnet.witness-artifact-bundle.schema.json";
const simulationFactSchemaId = "gamecult.mesh.simulation_fact";
const csharpProjectPath = resolve(
cultLibRoot,
"tests",
"GameCult.Networking.InteropPeer",
"GameCult.Networking.InteropPeer.csproj",
);
const csharpDllPath = resolve(
cultLibRoot,
"bin",
"GameCult.Networking.InteropPeer",
"Debug",
"net10.0",
"GameCult.Networking.InteropPeer.dll",
);
const cultMeshTsRoot = resolve(cultLibRoot, "packages", "cultmesh-ts");
const cultMeshQuicPeerScript = resolve(cultMeshTsRoot, "dist-test", "test", "interop", "cultmesh-quic-peer.js");
const quicNativeTestsProject = resolve(cultLibRoot, "tests", "GameCult.Mesh.Quic.Native.Tests", "GameCult.Mesh.Quic.Native.Tests.csproj");
const quicManagedTestsProject = resolve(cultLibRoot, "tests", "GameCult.Mesh.Quic.Tests", "GameCult.Mesh.Quic.Tests.csproj");
const discoveryGroup = "239.77.44.11";
const serveReadyTimeoutMs = 90_000;
const jsonCommandTimeoutMs = 180_000;
const processCleanupTimeoutMs = 5_000;
let rustInteropPeerBuild: Promise<void> | undefined;
let csharpInteropPeerBuild: Promise<void> | undefined;
let kotlinInteropPeerBuild: Promise<void> | undefined;
let cultMeshTsInteropPeerBuild: Promise<void> | undefined;
let rustInteropPeerPathCache: string | undefined;
let tsInteropPeerChecked = false;
let pythonInteropChecked = false;
// Cargo decides where it builds: CARGO_TARGET_DIR, a config's build.target-dir, or the crate's own
// target directory. The peer's path is asked of cargo, not assumed.
function rustInteropPeerPath(): string {
if (rustInteropPeerPathCache !== undefined) {
return rustInteropPeerPathCache;
}
const metadata = spawnSync(cargoCommand, ["metadata", "--format-version", "1", "--no-deps"], {
cwd: cultnetRsRoot,
encoding: "utf8",
maxBuffer: 64 * 1024 * 1024,
});
if (metadata.status !== 0) {
throw new Error(
`The Rust interop peer's target directory is unknown: \`cargo metadata\` failed in ${cultnetRsRoot}.\n${metadata.stderr ?? metadata.error?.message ?? ""}`,
);
}
const targetDirectory = (JSON.parse(metadata.stdout) as { target_directory: string }).target_directory;
rustInteropPeerPathCache = resolve(
targetDirectory,
"debug",
"examples",
process.platform === "win32" ? "cultnet_interop_peer.exe" : "cultnet_interop_peer",
);
return rustInteropPeerPathCache;
}
// The TypeScript peer imports @gamecult/cultcache-ts from its built output. Unbuilt, the peer dies
// on import and a lane fails with a spawn or timeout error that names neither.
function tsInteropPeerScript(): string {
if (!tsInteropPeerChecked) {
try {
require.resolve("@gamecult/cultcache-ts");
} catch {
throw new Error(
"@gamecult/cultcache-ts is not built, and the TypeScript interop peer imports it. Run `npm run build --workspace packages/cultcache-ts` first.",
);
}
tsInteropPeerChecked = true;
}
return tsPeerScript;
}
// The Python peers import msgpack. Missing, each Python lane fails with a traceback in a child's
// stderr instead of a reason.
function pythonInteropCommand(): string {
if (!pythonInteropChecked) {
const probe = spawnSync(pythonCommand, ["-c", "import msgpack"], { encoding: "utf8" });
if (probe.status !== 0) {
throw new Error(
`${pythonCommand} cannot import msgpack, which the Python interop peers need. Install the Python packages first: \`python -m pip install -e packages/cultcache-py -e packages/cultnet-py -e packages/cultmesh-py\`.\n${probe.stderr ?? probe.error?.message ?? ""}`,
);
}
pythonInteropChecked = true;
}
return pythonCommand;
}
function resolvePythonCommand(): string {
const codexPythonCommand = join(homedir(), ".cache", "codex-runtimes", "codex-primary-runtime", "dependencies", "python", "python.exe");
const candidates = process.platform === "win32"
? [
...(existsSync(codexPythonCommand) ? [codexPythonCommand] : []),
"py",
"python",
"python3",
]
: ["python3", "python"];
for (const candidate of candidates) {
const result = spawnSync(candidate, ["--version"], { stdio: "ignore" });
if (result.status === 0) {
return candidate;
}
}
return process.platform === "win32" ? "python" : "python3";
}
interface RudpServerInteropPeerSpec {
name: string;
connectionId: number;
build: () => Promise<void>;
spawn: (options?: RudpServerSpawnOptions) => RunningPythonRudpPeer;
joinPayload: string;
clientPayload: string;
expectedPayload: string;
}
interface RudpServerSpawnOptions {
clientPayload?: string;
serverPayload?: string;
serverExtraPayload?: string;
disconnectReason?: string;
maxFragmentBytes?: number;
}
function rudpServerInteropPeers(): RudpServerInteropPeerSpec[] {
return [
{
name: "Python",
connectionId: 0x55667788,
build: async () => undefined,
spawn: spawnPythonRudpPeer,
joinPayload: "ts-join",
clientPayload: "client-state",
expectedPayload: "python-state",
},
{
name: "Rust",
connectionId: 0x22446688,
build: buildRustInteropPeer,
spawn: spawnRustRudpServer,
joinPayload: "ts-rust-join",
clientPayload: "ts-rust-client-state",
expectedPayload: "rust-server-state",
},
{
name: "C#",
connectionId: 0x33557799,
build: buildCSharpInteropPeer,
spawn: spawnCSharpRudpServer,
joinPayload: "ts-csharp-join",
clientPayload: "ts-csharp-client-state",
expectedPayload: "csharp-server-state",
},
{
name: "Kotlin",
connectionId: 0x446688aa,
build: buildKotlinInteropPeer,
spawn: spawnKotlinRudpServer,
joinPayload: "ts-kotlin-join",
clientPayload: "ts-kotlin-client-state",
expectedPayload: "kotlin-server-state",
},
];
}
const pythonRudpPeerScript = String.raw`
import os
import socket
import time
from cultnet_py import (
CultNetRudpSocketMode,
CultNetRudpSocketTransportConnection,
CultNetRudpSocketTransportOptions,
)
expected_client_payload = os.environ.get("RUDP_CLIENT_PAYLOAD", "client-state").encode()
server_payload = os.environ.get("RUDP_SERVER_PAYLOAD", "python-state").encode()
server_extra_payload_raw = os.environ.get("RUDP_SERVER_EXTRA_PAYLOAD")
server_extra_payload = server_extra_payload_raw.encode() if server_extra_payload_raw else None
disconnect_reason_raw = os.environ.get("RUDP_DISCONNECT_REASON")
disconnect_reason = disconnect_reason_raw.encode() if disconnect_reason_raw else None
max_fragment_bytes_raw = os.environ.get("RUDP_MAX_FRAGMENT_BYTES")
max_fragment_bytes = int(max_fragment_bytes_raw) if max_fragment_bytes_raw else None
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
sock.settimeout(0.02)
transport = CultNetRudpSocketTransportConnection(CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-interop",
socket=sock,
mode=CultNetRudpSocketMode.SERVER,
connection_id=0x55667788,
initial_sequence=100,
resend_delay_ms=25,
max_fragment_bytes=max_fragment_bytes,
))
print(f"PORT {sock.getsockname()[1]}", flush=True)
deadline = time.time() + 5
while time.time() < deadline:
frame = transport.receive_once()
transport.poll_resends()
if frame is None:
time.sleep(0.005)
continue
if frame.channel_id != "schema" or frame.payload != expected_client_payload:
transport.close()
raise SystemExit(f"unexpected RUDP frame: {frame.channel_id!r} {frame.payload!r}")
transport.send("schema", server_payload)
if server_extra_payload is not None:
transport.send("schema", server_extra_payload)
if disconnect_reason is not None:
transport.disconnect(disconnect_reason)
print("OK", flush=True)
ack_deadline = time.time() + 0.25
while time.time() < ack_deadline:
transport.receive_once()
transport.poll_resends()
time.sleep(0.005)
transport.close()
raise SystemExit(0)
transport.close()
raise SystemExit("timed out waiting for RUDP frame")
`;
const pythonRudpClientScript = String.raw`
import socket
import sys
import time
from cultnet_py import (
CultNetRudpSocketMode,
CultNetRudpSocketTransportConnection,
CultNetRudpSocketTransportOptions,
)
remote_port = int(sys.argv[1])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
sock.settimeout(0.02)
transport = CultNetRudpSocketTransportConnection(CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-client-interop",
socket=sock,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=("127.0.0.1", remote_port),
connection_id=0x66554433,
initial_sequence=1,
resend_delay_ms=25,
))
transport.connect(b"python-join")
sent = False
deadline = time.time() + 5
while time.time() < deadline:
frame = transport.receive_once()
transport.poll_resends()
if transport.connected and not sent:
transport.send("schema", b"python-client-state")
sent = True
if frame is None:
time.sleep(0.005)
continue
if frame.channel_id != "schema" or frame.payload != b"ts-server-state":
transport.close()
raise SystemExit(f"unexpected RUDP frame: {frame.channel_id!r} {frame.payload!r}")
print("OK", flush=True)
transport.close()
raise SystemExit(0)
transport.close()
raise SystemExit("timed out waiting for TypeScript RUDP frame")
`;
const pythonRudpMessagePeerScript = String.raw`
import msgpack
import socket
import time
from cultnet_py import (
CultNetRudpSocketMode,
CultNetRudpSocketTransportConnection,
CultNetRudpSocketTransportOptions,
create_rudp_transport_profile,
)
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
sock.settimeout(0.02)
transport = CultNetRudpSocketTransportConnection(CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-message-interop",
socket=sock,
mode=CultNetRudpSocketMode.SERVER,
connection_id=0x55667789,
initial_sequence=100,
resend_delay_ms=25,
))
print(f"PORT {sock.getsockname()[1]}", flush=True)
deadline = time.time() + 5
while time.time() < deadline:
frame = transport.receive_once()
transport.poll_resends()
if frame is None:
time.sleep(0.005)
continue
if frame.channel_id != "schema":
transport.close()
raise SystemExit(f"unexpected RUDP channel: {frame.channel_id!r}")
message = msgpack.unpackb(frame.payload, raw=False)
if message.get("schemaVersion") != "cultnet.schema_catalog_request.v0":
transport.close()
raise SystemExit(f"unexpected schema message: {message!r}")
response = {
"schemaVersion": "cultnet.hello.v0",
"runtimeId": "python-rudp-message-interop",
"runtimeKind": "python",
"supportedDocumentTypes": [],
"supportedMutationContracts": [],
"supportedMessageVersions": ["cultnet.hello.v0", "cultnet.schema_catalog_request.v0"],
"transportProfiles": [create_rudp_transport_profile(
"python-rudp-message-interop",
host="127.0.0.1",
port=sock.getsockname()[1],
)],
"supportsSchemaCatalog": True,
}
transport.send("schema", msgpack.packb(response, use_bin_type=True))
print("OK", flush=True)
ack_deadline = time.time() + 0.25
while time.time() < ack_deadline:
transport.receive_once()
transport.poll_resends()
time.sleep(0.005)
transport.close()
raise SystemExit(0)
transport.close()
raise SystemExit("timed out waiting for RUDP schema-v0 message")
`;
const pythonRudpMessageClientScript = String.raw`
import msgpack
import socket
import sys
import time
from cultnet_py import (
CultNetRudpSocketMode,
CultNetRudpSocketTransportConnection,
CultNetRudpSocketTransportOptions,
)
remote_port = int(sys.argv[1])
sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
sock.bind(("127.0.0.1", 0))
sock.settimeout(0.02)
transport = CultNetRudpSocketTransportConnection(CultNetRudpSocketTransportOptions(
runtime_id="python-rudp-message-client-interop",
socket=sock,
mode=CultNetRudpSocketMode.CLIENT,
remote_addr=("127.0.0.1", remote_port),
connection_id=0x66554434,
initial_sequence=1,
resend_delay_ms=25,
))
transport.connect(b"python-message-join")
sent = False
deadline = time.time() + 5
while time.time() < deadline:
frame = transport.receive_once()
transport.poll_resends()
if transport.connected and not sent:
transport.send("schema", msgpack.packb({
"schemaVersion": "cultnet.schema_catalog_request.v0",
"messageId": "python-ts-schema-message",
"includeSchemaJson": False,
"schemaIds": [],
"kinds": ["wire_message"],
}, use_bin_type=True))
sent = True
if frame is None:
time.sleep(0.005)
continue
if frame.channel_id != "schema":
transport.close()
raise SystemExit(f"unexpected RUDP channel: {frame.channel_id!r}")
message = msgpack.unpackb(frame.payload, raw=False)
if message.get("schemaVersion") != "cultnet.hello.v0":
transport.close()
raise SystemExit(f"unexpected schema message: {message!r}")
if message.get("runtimeId") != "ts-python-rudp-message-server":
transport.close()
raise SystemExit(f"unexpected runtime id: {message!r}")
print("OK", flush=True)
transport.close()
raise SystemExit(0)
transport.close()
raise SystemExit("timed out waiting for TypeScript RUDP schema-v0 message")
`;
test("CultMesh QUIC realtime: C# managed provider and TypeScript consumer", async (t) => {
if (!process.env.CULTMESH_QUIC_NATIVE_DIR) {
t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge.");
return;
}
logInteropPhase("quic-realtime", "build peers");
await buildCSharpInteropPeer();
await buildCultMeshTsQuicPeer();
const servers: RunningServeProcess[] = [];
t.after(async () => {
for (const server of servers) {
// The C# quic-realtime-serve peer watches stdin for EOF as its own
// shutdown signal; end it explicitly before killing so a lingering pipe
// never masks whether the process exited on its own.
server.child.stdin?.end();
server.child.kill();
}
await delay(processCleanupTimeoutMs);
});
logInteropPhase("quic-realtime", "start csharp QUIC provider");
servers.push(await spawnServeProcess("csharp-quic", {
command: dotnetCommand,
args: [
csharpDllPath,
"quic-realtime-serve",
"--frames", "5",
"--delivery", "latest-only",
"--interval-ms", "50",
],
cwd: cultLibRoot,
stdin: "pipe",
}));
const ready = (await servers[servers.length - 1].ready) as { status: string; endpoint: string };
assert.equal(ready.status, "ready");
assert.match(ready.endpoint, /^cultmesh-state\+quic:\/\/127\.0\.0\.1:\d+\?cert-sha256=[0-9A-Fa-f]{64}$/);
logInteropPhase("quic-realtime", "typescript dials the csharp QUIC provider");
const tsDial = await runJsonCommand("ts-quic-dial", process.execPath, [
cultMeshQuicPeerScript,
"dial",
"--endpoint", ready.endpoint,
"--expect", "5",
], cultMeshTsRoot, { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" });
assert.equal(tsDial.transportId, "msquic-realtime");
assert.equal(tsDial.frames.length, 5);
for (const [index, frame] of tsDial.frames.entries()) {
assert.equal(frame.delivery, "latest-only");
assert.equal(frame.bodyId, "interop:quic-realtime:frame");
assert.equal(Buffer.from(frame.payloadHex, "hex").toString("utf8"), `frame-${index + 1}`);
}
});
// ---------------------------------------------------------------------------
// Cut 5: the TypeScript QUIC realtime provider (the StreamPixels role). Four
// lanes, spec section 10. Lane 1 is the headline: it proves the Unity
// overlay's transport, and is gated to Windows because the native connector
// under test (`CultMeshNativeQuicRealtimeTransportConnector`) is.
// ---------------------------------------------------------------------------
test("CultMesh QUIC realtime: TypeScript provider and C# native connector (the Unity path)", async (t) => {
if (process.platform !== "win32") {
t.skip("The native connector is Windows-only; this lane runs on Windows.");
return;
}
if (!process.env.CULTMESH_QUIC_NATIVE_DIR) {
t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge.");
return;
}
logInteropPhase("quic-provider-native", "build the typescript quic peer");
await buildCultMeshTsQuicPeer();
const servers: RunningServeProcess[] = [];
t.after(async () => {
for (const server of servers) {
server.child.stdin?.end();
server.child.kill();
}
await delay(processCleanupTimeoutMs);
});
logInteropPhase("quic-provider-native", "start the typescript QUIC provider");
servers.push(await spawnServeProcess("ts-quic-provider", {
command: process.execPath,
args: [cultMeshQuicPeerScript, "serve", "--frames", "5", "--delivery", "latest-only"],
cwd: cultMeshTsRoot,
env: { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" },
stdin: "pipe",
}));
const ready = (await servers[servers.length - 1].ready) as { status: string; endpoint: string };
assert.equal(ready.status, "ready");
assert.match(ready.endpoint, /^cultmesh-state\+quic:\/\/127\.0\.0\.1:\d+\?cert-sha256=[0-9A-F]{64}$/);
logInteropPhase("quic-provider-native", "dotnet test: the native connector dials the typescript provider");
const { stdout } = await runDotnetTestFilter(
quicNativeTestsProject,
"NativeConnectorReceivesFromExternalProvider",
{ CULTMESH_NATIVE_EXTERNAL_ENDPOINT: ready.endpoint },
);
// Real output observed from a live run (NUnit3TestExecutor via VSTest, not
// Microsoft.Testing.Platform's newer console format): "Test Run
// Successful." plus "Passed: N", never the literal word "Passed!".
assert.match(stdout, /Test Run Successful\./);
assert.match(stdout, /Passed:\s*1/, `expected exactly one passed test.\n${stdout}`);
assert.doesNotMatch(stdout, /Skipped:\s*1/, `the env var should have made the test run, not skip.\n${stdout}`);
});
// Cut C headline: a late-joining C# native connector receives the TS
// provider's retained frame. The TS provider's own trigger peer (a `dial`
// subprocess, needed only because the provider waits for a first connection
// before it broadcasts anything) receives every frame and disconnects before
// the native connector dials, so nothing here is a live retransmission: only
// Cut C's retained seed can deliver a frame to the late C# connector.
test("CultMesh QUIC realtime: a late-joining C# native connector receives the retained frame from a TypeScript provider", async (t) => {
if (process.platform !== "win32") {
t.skip("The native connector is Windows-only; this lane runs on Windows.");
return;
}
if (!process.env.CULTMESH_QUIC_NATIVE_DIR) {
t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge.");
return;
}
logInteropPhase("quic-provider-retention", "build the typescript quic peer");
await buildCultMeshTsQuicPeer();
const servers: RunningServeProcess[] = [];
t.after(async () => {
for (const server of servers) {
server.child.stdin?.end();
server.child.kill();
}
await delay(processCleanupTimeoutMs);
});
logInteropPhase("quic-provider-retention", "start the typescript QUIC provider");
servers.push(await spawnServeProcess("ts-quic-provider-retention", {
command: process.execPath,
args: [
cultMeshQuicPeerScript,
"serve",
"--frames", "3",
"--delivery", "latest-only",
"--interval-ms", "20",
"--linger-ms", "60000",
],
cwd: cultMeshTsRoot,
env: { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" },
stdin: "pipe",
}));
const ready = (await servers[servers.length - 1].ready) as { status: string; endpoint: string };
assert.equal(ready.status, "ready");
logInteropPhase("quic-provider-retention", "trigger peer receives every frame, then disconnects");
const trigger = await runJsonCommand("ts-quic-dial-trigger", process.execPath, [
cultMeshQuicPeerScript,
"dial",
"--endpoint", ready.endpoint,
"--expect", "3",
], cultMeshTsRoot, { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" });
assert.equal(trigger.frames.length, 3);
logInteropPhase("quic-provider-retention", "dotnet test: the native connector dials late, after the trigger peer is gone");
const { stdout } = await runDotnetTestFilter(
quicNativeTestsProject,
"NativeConnectorReceivesTheRetainedFrameWhenJoiningLate",
{
CULTMESH_NATIVE_EXTERNAL_ENDPOINT: ready.endpoint,
CULTMESH_NATIVE_EXTERNAL_EXPECTED_PAYLOAD: "frame-3",
},
);
assert.match(stdout, /Test Run Successful\./);
assert.match(stdout, /Passed:\s*1/, `expected exactly one passed test.\n${stdout}`);
assert.doesNotMatch(stdout, /Skipped:\s*1/, `the env vars should have made the test run, not skip.\n${stdout}`);
});
test("CultMesh QUIC realtime: TypeScript provider and C# managed connector, with golden bytes", async (t) => {
if (!process.env.CULTMESH_QUIC_NATIVE_DIR) {
t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge.");
return;
}
logInteropPhase("quic-provider-managed", "build the typescript quic peer");
await buildCultMeshTsQuicPeer();
const servers: RunningServeProcess[] = [];
t.after(async () => {
for (const server of servers) {
server.child.stdin?.end();
server.child.kill();
}
await delay(processCleanupTimeoutMs);
});
logInteropPhase("quic-provider-managed", "start the typescript QUIC provider");
servers.push(await spawnServeProcess("ts-quic-provider-managed", {
command: process.execPath,
args: [cultMeshQuicPeerScript, "serve", "--frames", "5", "--delivery", "latest-only"],
cwd: cultMeshTsRoot,
env: { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" },
stdin: "pipe",
}));
const server = servers[servers.length - 1];
const ready = (await server.ready) as { status: string; endpoint: string };
assert.equal(ready.status, "ready");
logInteropPhase("quic-provider-managed", "dotnet test: the managed connector dials the typescript provider");
const { stdout } = await runDotnetTestFilter(
quicManagedTestsProject,
"ManagedConnectorReceivesFromExternalProvider",
{ CULTMESH_NATIVE_EXTERNAL_ENDPOINT: ready.endpoint },
);
// Real output observed from a live run (NUnit3TestExecutor via VSTest, not
// Microsoft.Testing.Platform's newer console format): "Test Run
// Successful." plus "Passed: N", never the literal word "Passed!".
assert.match(stdout, /Test Run Successful\./);
assert.match(stdout, /Passed:\s*1/, `expected exactly one passed test.\n${stdout}`);
assert.doesNotMatch(stdout, /Skipped:\s*1/, `the env var should have made the test run, not skip.\n${stdout}`);
// `dotnet test --logger "console;verbosity=detailed"` prints a passed
// test's TestContext.Out lines twice (once live-streamed, once again under
// "Standard Output Messages:"), so matching SEQUENCE and HEX independently
// risks pairing a sequence from one copy with a hex from the other. The C#
// test writes them as one WriteLine call each, back to back; requiring them
// adjacent guarantees whichever pair matches first is self-consistent.
const goldenMatch = /CULTMESH_GOLDEN_SEQUENCE=(\d+)\s*\r?\n\s*CULTMESH_GOLDEN_HEX=([0-9A-Fa-f]+)/.exec(stdout);
assert.ok(goldenMatch, `expected the C# test to print its golden sequence and hex lines together.\n${stdout}`);
const csharpSequence = Number(goldenMatch![1]);
const csharpHex = goldenMatch![2].toLowerCase();
// Which frame the managed connector's single ReceiveAsync() call actually
// got is not pinned to frame 1: latest-only coalesces on both the
// provider's outbox and the connector's own receive-side inbox, so a slow
// start can land on a later sequence. Match the typescript peer's logged
// encoding of that same sequence rather than assuming the first one sent.
const tsSentLine = server.stderr
.join("")
.split("\n")
.map((line) => line.trim())
.filter(Boolean)
.map((line) => {
try {
return JSON.parse(line) as { event?: string; sent?: number; hex?: string };
} catch {
return undefined;
}
})
.find((entry) => entry?.event === "quic-realtime-serve" && entry.sent === csharpSequence);
assert.ok(tsSentLine?.hex, `expected the typescript peer to log frame ${csharpSequence}'s hex encoding`);
assert.equal(csharpHex, tsSentLine!.hex, "TypeScript and C# must encode the same frame identically");
});
test("CultMesh QUIC realtime: latest-only under a stalled consumer converges without blocking the provider", async (t) => {
if (!process.env.CULTMESH_QUIC_NATIVE_DIR) {
t.skip("CULTMESH_QUIC_NATIVE_DIR is not set to a built native bridge.");
return;
}
logInteropPhase("quic-provider-stalled", "build the typescript quic peer");
await buildCultMeshTsQuicPeer();
const servers: RunningServeProcess[] = [];
t.after(async () => {
for (const server of servers) {
server.child.stdin?.end();
server.child.kill();
}
await delay(processCleanupTimeoutMs);
});
logInteropPhase("quic-provider-stalled", "start the typescript QUIC provider");
const started = Date.now();
servers.push(await spawnServeProcess("ts-quic-provider-stalled", {
command: process.execPath,
args: [cultMeshQuicPeerScript, "serve", "--frames", "200", "--delivery", "latest-only", "--interval-ms", "1"],
cwd: cultMeshTsRoot,
env: { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" },
stdin: "pipe",
}));
const ready = (await servers[servers.length - 1].ready) as { status: string; endpoint: string };
logInteropPhase("quic-provider-stalled", "dial twice: one healthy, one paused mid-stream");
const env = { CULTMESH_QUIC_NATIVE_DIR: process.env.CULTMESH_QUIC_NATIVE_DIR ?? "" };
const [healthy, paused] = await Promise.all([
runJsonCommand("ts-quic-dial-healthy", process.execPath, [
cultMeshQuicPeerScript,
"dial",
"--endpoint", ready.endpoint,
"--expect-sequence", "200",
"--timeout-ms", "20000",
], cultMeshTsRoot, env),
runJsonCommand("ts-quic-dial-paused", process.execPath, [
cultMeshQuicPeerScript,
"dial",
"--endpoint", ready.endpoint,
"--expect-sequence", "200",
"--pause-after", "5",
"--pause-ms", "500",
"--timeout-ms", "20000",
], cultMeshTsRoot, env),
]);
// The provider's 200 broadcasts (bounded per-frame by --interval-ms 1, so
// ~200ms of pacing) plus the 500ms pause plus process/network overhead
// must still land well inside the bound below regardless of the pause: the
// provider itself was never blocked by the paused consumer.
assert.ok(Date.now() - started < 15_000, "the provider's broadcasts must not be blocked by a stalled peer");
assert.equal(healthy.frames.at(-1).sequence, "200");
assert.equal(paused.frames.at(-1).sequence, "200");
// Coalescing means the paused consumer saw far fewer than 200 frames.
assert.ok(paused.frames.length < 200, `expected coalescing under the pause, got ${paused.frames.length} frames`);
});
test("CultNet TS/Rust/C#/Python peers discover each other and exchange raw state over the shared schema-v0 lane", async (t) => {
logInteropPhase("schema-v0", "build peers");
await buildInteropPeers();
cleanInteropStores([
"rust-peer",
"rust-client-dial",
"ts-peer",
"ts-client-dial",
"ts-rudp-client-dial",
"ts-rust-dial",
"ts-csharp-dial",
"python-peer",
"python-client-dial",
"python-rudp-client-dial",
]);
const discoveryPort = await getFreePort();
const tsPort = await getFreePort();
const rustPort = await getFreePort();
const csharpPort = await getFreePort();
const pythonPort = await getFreePort();
const advertiseHost = findAdvertiseHost();
const servers: RunningServeProcess[] = [];
// Registered before anything is spawned. A peer whose readiness rejects
// would otherwise skip this and strand every peer already running.
t.after(async () => {
await Promise.all(servers.map(stopProcess));
});
logInteropPhase("schema-v0", "start rust peer");
servers.push(await spawnServeProcess("rust", {
command: rustInteropPeerPath(),
args: [
"serve",
"--runtime-id", "rust-peer",
"--runtime-kind", "rust",
"--display-name", "Rust Peer",
"--agent-id", "rust-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(rustPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultnetRsRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("schema-v0", "start typescript peer");
servers.push(await spawnServeProcess("ts", {
command: process.execPath,
args: [
tsInteropPeerScript(),
"serve",
"--runtime-id", "ts-peer",
"--runtime-kind", "node",
"--display-name", "TypeScript Peer",
"--agent-id", "ts-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(tsPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultNetTsRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("schema-v0", "start csharp peer");
servers.push(await spawnServeProcess("csharp", {
command: dotnetCommand,
args: [
csharpDllPath,
"serve",
"--runtime-id", "csharp-peer",
"--runtime-kind", "dotnet",
"--display-name", "CSharp Peer",
"--agent-id", "csharp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(csharpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultLibRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("schema-v0", "start python peer");
servers.push(await spawnServeProcess("python", {
command: pythonInteropCommand(),
args: [
"-m", "cultnet_py.interop_peer",
"serve",
"--runtime-id", "python-peer",
"--runtime-kind", "python",
"--display-name", "Python Peer",
"--agent-id", "python-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(pythonPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultcachePyRoot,
env: { PYTHONPATH: cultcachePySrc },
}));
await servers[servers.length - 1].ready;
logInteropPhase("schema-v0", "typescript dials rust");
const tsDial = await runJsonCommand("ts-dial", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-client",
"--runtime-kind", "node",
"--display-name", "TS Dialer",
"--agent-id", "ts-client-agent",
"--target-host", "127.0.0.1",
"--target-port", String(rustPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsDial.remoteHello.runtimeId, "rust-peer");
assert.equal(tsDial.hasInteropSchema, true);
assert.equal(tsDial.retrievedNote.authorRuntimeId, "rust-peer");
assert.ok(tsDial.mutatedNote.tags.includes("decorated:ts-client"));
assert.equal(tsDial.mutationReceipt.accepted, true);
assert.equal(tsDial.fireReceipt.shotsFired, 1);
logInteropPhase("schema-v0", "typescript dials typescript over rudp");
const tsRudpDial = await runJsonCommand("ts-rudp-dial", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-rudp-client",
"--runtime-kind", "node",
"--display-name", "TS RUDP Dialer",
"--agent-id", "ts-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(tsPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsRudpDial.transport, "rudp");
assert.equal(tsRudpDial.remoteHello.runtimeId, "ts-peer");
assert.equal(tsRudpDial.hasInteropSchema, true);
assert.equal(tsRudpDial.retrievedNote.authorRuntimeId, "ts-peer");
assert.ok(tsRudpDial.mutatedNote.tags.includes("decorated:ts-rudp-client"));
assert.equal(tsRudpDial.fireReceipt.accepted, true);
logInteropPhase("schema-v0", "rust dials csharp");
const rustDial = await runJsonCommand("rust-dial", rustInteropPeerPath(), [
"dial",
"--runtime-id", "rust-client",
"--runtime-kind", "rust",
"--display-name", "Rust Dialer",
"--agent-id", "rust-client-agent",
"--target-host", "127.0.0.1",
"--target-port", String(csharpPort),
"--schema-path", interopSchemaPath,
], cultnetRsRoot);
assert.equal(rustDial.remoteHello.runtimeId, "csharp-peer");
assert.equal(rustDial.hasInteropSchema, true);
assert.equal(rustDial.retrievedNote.authorRuntimeId, "csharp-peer");
assert.ok(rustDial.mutatedNote.tags.includes("decorated:rust-client"));
assert.equal(rustDial.fireReceipt.ammoRemaining, 29);
logInteropPhase("schema-v0", "csharp dials typescript");
const csharpDial = await runJsonCommand("csharp-dial", dotnetCommand, [
csharpDllPath,
"dial",
"--runtime-id", "csharp-client",
"--runtime-kind", "dotnet",
"--display-name", "CSharp Dialer",
"--agent-id", "csharp-client-agent",
"--target-host", "127.0.0.1",
"--target-port", String(tsPort),
"--schema-path", interopSchemaPath,
], cultLibRoot);
assert.equal(csharpDial.remoteHello.runtimeId, "ts-peer");
assert.equal(csharpDial.hasInteropSchema, true);
assert.equal(csharpDial.retrievedNote.authorRuntimeId, "ts-peer");
assert.ok(csharpDial.mutatedNote.tags.includes("decorated:csharp-client"));
assert.equal(csharpDial.fireReceipt.accepted, true);
logInteropPhase("schema-v0", "python dials rust");
const pythonDial = await runJsonCommand("python-dial", pythonInteropCommand(), [
"-m", "cultnet_py.interop_peer",
"dial",
"--runtime-id", "python-client",
"--runtime-kind", "python",
"--display-name", "Python Dialer",
"--agent-id", "python-client-agent",
"--target-host", "127.0.0.1",
"--target-port", String(rustPort),
"--schema-path", interopSchemaPath,
], cultcachePyRoot, { PYTHONPATH: cultcachePySrc });
assert.equal(pythonDial.remoteHello.runtimeId, "rust-peer");
assert.equal(pythonDial.hasInteropSchema, true);
assert.equal(pythonDial.retrievedNote.authorRuntimeId, "rust-peer");
assert.ok(pythonDial.mutatedNote.tags.includes("decorated:python-client"));
assert.equal(pythonDial.fireReceipt.accepted, true);
logInteropPhase("schema-v0", "python dials python over rudp");
const pythonRudpDial = await runJsonCommand("python-rudp-dial", pythonInteropCommand(), [
"-m", "cultnet_py.interop_peer",
"dial",
"--runtime-id", "python-rudp-client",
"--runtime-kind", "python",
"--display-name", "Python RUDP Dialer",
"--agent-id", "python-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(pythonPort),
"--schema-path", interopSchemaPath,
], cultcachePyRoot, { PYTHONPATH: cultcachePySrc });
assert.equal(pythonRudpDial.transport, "rudp");
assert.equal(pythonRudpDial.remoteHello.runtimeId, "python-peer");
assert.equal(pythonRudpDial.hasInteropSchema, true);
assert.equal(pythonRudpDial.retrievedNote.authorRuntimeId, "python-peer");
assert.ok(pythonRudpDial.mutatedNote.tags.includes("decorated:python-rudp-client"));
assert.equal(pythonRudpDial.fireReceipt.accepted, true);
logInteropPhase("schema-v0", "typescript dials python");
const tsDialPython = await runJsonCommand("ts-dial-python", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-python-client",
"--runtime-kind", "node",
"--display-name", "TS Python Dialer",
"--agent-id", "ts-python-client-agent",
"--target-host", "127.0.0.1",
"--target-port", String(pythonPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsDialPython.remoteHello.runtimeId, "python-peer");
assert.equal(tsDialPython.hasInteropSchema, true);
assert.equal(tsDialPython.retrievedNote.authorRuntimeId, "python-peer");
assert.ok(tsDialPython.mutatedNote.tags.includes("decorated:ts-python-client"));
assert.equal(tsDialPython.fireReceipt.ammoRemaining, 29);
logInteropPhase("schema-v0", "python wire catalog");
const pythonWireCatalog = await requestCultMeshFromPython(pythonPort, {
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "python-wire-catalog",
includeSchemaJson: true,
kinds: ["wire_message"],
});
assert.equal(pythonWireCatalog.schemaVersion, "cultnet.schema_catalog_response.v0");
assert.equal(pythonWireCatalog.messageId, "python-wire-catalog");
const pythonWireVersions = new Set(pythonWireCatalog.schemas.map((schema: any) => schema.schemaVersion));
for (const schemaVersion of [
"cultnet.document_delete.v0",
"cultnet.database_change_raw.v0",
"cultnet.shard_log_response.v0",
"cultnet.simulation_consensus_candidate.v0",
"cultmesh.peer_exchange_response.v0",
]) {
assert.ok(pythonWireVersions.has(schemaVersion), `Python wire catalog should include ${schemaVersion}`);
}
const pythonDeleteDescriptor = pythonWireCatalog.schemas.find((schema: any) => schema.schemaVersion === "cultnet.document_delete.v0");
assert.equal(pythonDeleteDescriptor.kind, "wire_message");
assert.ok(pythonDeleteDescriptor.wireContracts.includes("cultnet.schema.v0"));
assert.ok(String(pythonDeleteDescriptor.schemaJson).includes("cultnet.document_delete.v0"));
const pythonDeleteSchema = JSON.parse(String(pythonDeleteDescriptor.schemaJson));
assert.deepEqual(pythonDeleteSchema.required, ["schemaVersion", "messageId", "schemaId", "recordKey"]);
assert.equal(pythonDeleteSchema.properties.recordKey.type, "string");
logInteropPhase("schema-v0", "python verse catalog");
const pythonVerseCatalog = await requestCultMeshFromPython(pythonPort, {
schemaVersion: "cultmesh.verse_catalog_request.v0",
messageId: "python-verses",
transportVersion: "cultmesh.v0",
});
assert.equal(pythonVerseCatalog.schemaVersion, "cultmesh.verse_catalog_response.v0");
assert.equal(pythonVerseCatalog.messageId, "python-verses");
assert.equal(pythonVerseCatalog.verses[0].verseId, "python-interop");
assert.equal(pythonVerseCatalog.verses[0].compatibility.transportVersion, "cultmesh.v0");
const pythonPeerExchange = await requestCultMeshFromPython(pythonPort, {
schemaVersion: "cultmesh.peer_exchange_request.v0",
messageId: "python-peers",
verseId: "python-interop",
roles: ["read-replica"],
});
assert.equal(pythonPeerExchange.schemaVersion, "cultmesh.peer_exchange_response.v0");
assert.equal(pythonPeerExchange.messageId, "python-peers");
assert.equal(pythonPeerExchange.peers[0].peerId, "python-peer");
assert.ok(pythonPeerExchange.peers[0].roles.includes("read-replica"));
logInteropPhase("schema-v0", "python client helpers");
const pythonCultMeshClient = await runPythonCultMeshClient(pythonPort);
assert.equal(pythonCultMeshClient.verses[0].verseId, "python-interop");
assert.equal(pythonCultMeshClient.verses[0].transportVersion, "cultmesh.v0");
assert.equal(pythonCultMeshClient.peers[0].peerId, "python-peer");
assert.ok(pythonCultMeshClient.peers[0].roles.includes("read-replica"));
const pythonCultNetClient = await runPythonCultNetRawClient(pythonPort);
assert.ok(pythonCultNetClient.wireSchemaCount >= 1);
assert.ok(pythonCultNetClient.snapshotRecordKeys.includes("note:python-peer"));
assert.equal(pythonCultNetClient.shards[0].shardId, "interop");
const pythonCultMeshNodeSync = await runPythonCultMeshNodeSync(pythonPort);
assert.equal(pythonCultMeshNodeSync.synced[0].recordKey, "note:python-peer");
assert.equal(pythonCultMeshNodeSync.note.authorRuntimeId, "python-peer");
const pythonCultMeshNodeEmit = await runPythonCultMeshNodeEmit(pythonPort);
assert.equal(pythonCultMeshNodeEmit.synced[0].recordKey, "note:python-node-emit");
assert.equal(pythonCultMeshNodeEmit.note.authorRuntimeId, "python-node-emit");
assert.equal(pythonCultMeshNodeEmit.note.title, "Python node emitted raw put");
logInteropPhase("schema-v0", "python prediction/subscription");
const pythonPrediction = await runPythonCultMeshPredictionReconcile(pythonPort);
assert.ok(pythonPrediction.reconciledKeys.includes("note:python-node-emit"));
assert.equal(pythonPrediction.note.title, "Python node emitted raw put");
assert.equal(pythonPrediction.note.authorRuntimeId, "python-node-emit");
const pythonSubscription = await runPythonCultNetSubscription(pythonPort);
assert.equal(pythonSubscription.changeKind, "added");
assert.equal(pythonSubscription.recordKey, "note:python-sub-client");
assert.equal(pythonSubscription.subscriptionId, "python-public-sub");
const pythonChange = await requestPythonDatabaseChange(pythonPort, {
schemaVersion: "cultnet.database_subscribe.v0",
messageId: "python-subscribe",
subscriptionId: "python-sub",
schemaIds: [interopNoteSchemaId],
recordKeys: ["note:ts-python-sub"],
includeSnapshot: false,
}, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: "python-sub-put",
document: {
schemaId: interopNoteSchemaId,
recordKey: "note:ts-python-sub",
storedAt: "2026-06-13T00:00:00Z",
payloadEncoding: "messagepack",
payload: encode([
INTEROP_SCHEMA_VERSION,
"note:ts-python-sub",
"ts-python-sub",
"Subscription note",
"Python streamed this raw change through the CultNet database lane.",
["interop", "subscription"],
]),
sourceRuntimeId: "ts-python-sub",
},
});
assert.equal(pythonChange.schemaVersion, "cultnet.database_change_raw.v0");
assert.equal(pythonChange.subscriptionId, "python-sub");
assert.equal(pythonChange.changeKind, "added");
assert.equal(pythonChange.document.schemaId, interopNoteSchemaId);
assert.equal(pythonChange.document.recordKey, "note:ts-python-sub");
const pythonShard = await requestPythonShardCatalogAndLog(pythonPort, {
schemaVersion: "cultnet.shard_catalog_request.v0",
messageId: "python-shards",
schemaIds: [interopNoteSchemaId],
recordKeys: ["note:ts-python-log"],
}, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: "python-shard-put",
document: {
schemaId: interopNoteSchemaId,
recordKey: "note:ts-python-log",
storedAt: "2026-06-13T00:00:01Z",
payloadEncoding: "messagepack",
payload: encode([
INTEROP_SCHEMA_VERSION,
"note:ts-python-log",
"ts-python-log",
"Shard log note",
"Python exposes accepted raw puts through the CultNet shard log lane.",
["interop", "shard-log"],
]),
sourceRuntimeId: "ts-python-log",
},
}, {
schemaVersion: "cultnet.shard_log_request.v0",
messageId: "python-log",
shardId: "interop",
shardEpoch: 1,
afterSequence: 0,
});
assert.equal(pythonShard.catalog.schemaVersion, "cultnet.shard_catalog_response.v0");
assert.equal(pythonShard.catalog.messageId, "python-shards");
assert.equal(pythonShard.catalog.shards[0].shardId, "interop");
assert.equal(pythonShard.catalog.shards[0].ownerRuntimeId, "python-peer");
assert.equal(pythonShard.catalog.shards[0].epoch, 1);
assert.equal(pythonShard.log.schemaVersion, "cultnet.shard_log_response.v0");
assert.equal(pythonShard.log.messageId, "python-log");
assert.equal(pythonShard.log.shardId, "interop");
assert.equal(pythonShard.log.shardEpoch, 1);
assert.equal(pythonShard.log.resyncRequired, false);
assert.ok(pythonShard.log.entries.some((entry: any) =>
entry.changeKind === "added"
&& entry.put?.messageId === "python-shard-put"
&& entry.put?.document?.recordKey === "note:ts-python-log"
));
logInteropPhase("schema-v0", "python delete/simulation/witness");
const pythonDelete = await requestPythonDatabaseDelete(pythonPort, {
schemaVersion: "cultnet.database_subscribe.v0",
messageId: "python-delete-subscribe",
subscriptionId: "python-delete-sub",
schemaIds: [interopNoteSchemaId],
recordKeys: ["note:ts-python-delete"],
includeSnapshot: false,
}, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: "python-delete-put",
document: {
schemaId: interopNoteSchemaId,
recordKey: "note:ts-python-delete",
storedAt: "2026-06-13T00:00:01Z",
payloadEncoding: "messagepack",
payload: encode([
INTEROP_SCHEMA_VERSION,
"note:ts-python-delete",
"ts-python-delete",
"Delete note",
"Python should remove this through the CultNet delete lane.",
["interop", "delete"],
]),
sourceRuntimeId: "ts-python-delete",
},
}, {
schemaVersion: "cultnet.document_delete.v0",
messageId: "python-delete",
schemaId: interopNoteSchemaId,
recordKey: "note:ts-python-delete",
shardId: "interop",
shardEpoch: 1,
}, {
schemaVersion: "cultnet.shard_log_request.v0",
messageId: "python-delete-log",
shardId: "interop",
shardEpoch: 1,
afterSequence: 0,
});
assert.equal(pythonDelete.change.schemaVersion, "cultnet.database_change_raw.v0");
assert.equal(pythonDelete.change.subscriptionId, "python-delete-sub");
assert.equal(pythonDelete.change.changeKind, "removed");
assert.equal(pythonDelete.change.schemaId, interopNoteSchemaId);
assert.equal(pythonDelete.change.recordKey, "note:ts-python-delete");
assert.ok(pythonDelete.log.entries.some((entry: any) =>
entry.changeKind === "removed"
&& entry.delete?.messageId === "python-delete"
&& entry.delete?.schemaId === interopNoteSchemaId
&& entry.delete?.recordKey === "note:ts-python-delete"
&& entry.delete?.shardEpoch === 1
));
const claimHash = computeSimulationClaimHash("frame:42", "subject:python-target", "hit");
const pythonCandidate = await requestPythonSimulationCandidate(pythonPort, {
schemaVersion: "cultnet.simulation_observation.v0",
messageId: "python-observation",
observation: {
witnessRuntimeId: "ts-witness",
shardId: "interop",
shardEpoch: 1,
frame: 42,
subjectId: "python-target",
claimKind: "hit",
claimHash,
claimSummary: "python-target was hit by the TypeScript witness",
weight: 1,
observedAt: "2026-06-13T00:00:02Z",
},
});
assert.equal(pythonCandidate.schemaVersion, "cultnet.simulation_consensus_candidate.v0");
assert.equal(pythonCandidate.messageId, "python-observation");
assert.equal(pythonCandidate.shardId, "interop");
assert.equal(pythonCandidate.shardEpoch, 1);
assert.equal(pythonCandidate.frame, 42);
assert.equal(pythonCandidate.subjectId, "python-target");
assert.equal(pythonCandidate.claimKind, "hit");
assert.equal(pythonCandidate.claimHash, claimHash);
assert.equal(pythonCandidate.witnessCount, 1);
assert.equal(pythonCandidate.supportWeight, 1);
assert.equal(pythonCandidate.totalWeight, 1);
assert.equal(pythonCandidate.hasQuorum, true);
assert.equal(pythonCandidate.confidence, 1);
const simulationFactKey = createSimulationFactKey(pythonCandidate);
const pythonFactSnapshot = await requestPythonSnapshot(pythonPort, {
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "python-simulation-fact-snapshot",
schemaIds: [simulationFactSchemaId],
recordKeys: [simulationFactKey],
});
assert.equal(pythonFactSnapshot.schemaVersion, "cultnet.snapshot_response_raw.v0");
assert.equal(pythonFactSnapshot.documents[0].schemaId, simulationFactSchemaId);
assert.equal(pythonFactSnapshot.documents[0].recordKey, simulationFactKey);
const simulationFactSlots = decode(pythonFactSnapshot.documents[0].payload) as any[];
assert.equal(simulationFactSlots[0], simulationFactKey.slice("simulation:".length));
assert.equal(simulationFactSlots[1], "interop");
assert.equal(simulationFactSlots[3], 42);
assert.equal(simulationFactSlots[4], "python-target");
assert.equal(simulationFactSlots[5], "hit");
assert.equal(simulationFactSlots[6], claimHash);
assert.equal(simulationFactSlots[8], 1);
const pythonWitness = await putAndSnapshotPythonWitnessBundle(pythonPort, {
schemaVersion: "cultnet.document_put_raw.v0",
messageId: "python-witness-put",
document: {
schemaId: witnessArtifactBundleSchemaId,
recordKey: "bundle:ts-python-witness",
storedAt: "2026-06-13T00:00:03Z",
payloadEncoding: "messagepack",
payload: encode([
"bundle:ts-python-witness",
"interop-proof",
"2026-06-13T00:00:03Z",
{
documentType: "cultnet.interop-note",
subjectId: "note:python-peer",
schemaVersion: INTEROP_SCHEMA_VERSION,
schemaId: interopNoteSchemaId,
},
[{ id: "c0e1a2b3c4d5", role: "payload", schemaId: interopNoteSchemaId, schemaVersion: INTEROP_SCHEMA_VERSION }],
[{ id: "a0e1a2b3c4d5", role: "log", uri: "cultcache://bundle:ts-python-witness/log", mediaType: "text/plain" }],
[{ id: "e0e1a2b3c4d5", stage: "roundtrip", startedAt: "2026-06-13T00:00:03Z", completedAt: "2026-06-13T00:00:04Z", latencyMs: 1 }],
{ pipelineId: "interop", runId: "ts-python-witness", runtimeId: "ts-witness", agentId: "ts-agent" },
]),
sourceRuntimeId: "ts-witness",
},
});
assert.equal(pythonWitness.catalog.schemaVersion, "cultnet.schema_catalog_response.v0");
assert.equal(pythonWitness.catalog.schemas[0].schemaId, witnessArtifactBundleSchemaId);
assert.equal(pythonWitness.catalog.schemas[0].documentType, "cultnet.witness_artifact_bundle");
assert.ok(String(pythonWitness.catalog.schemas[0].schemaJson).includes("CultNet Witness Artifact Bundle"));
assert.equal(pythonWitness.snapshot.schemaVersion, "cultnet.snapshot_response_raw.v0");
assert.equal(pythonWitness.snapshot.documents[0].schemaId, witnessArtifactBundleSchemaId);
assert.equal(pythonWitness.snapshot.documents[0].recordKey, "bundle:ts-python-witness");
const witnessSlots = decode(pythonWitness.snapshot.documents[0].payload) as any[];
assert.equal(witnessSlots[0], "bundle:ts-python-witness");
assert.equal(witnessSlots[1], "interop-proof");
assert.equal(witnessSlots[3].subjectId, "note:python-peer");
assert.equal(witnessSlots[7].runtimeId, "ts-witness");
assert.deepEqual([witnessSlots[4][0].id, witnessSlots[5][0].id, witnessSlots[6][0].id], ["c0e1a2b3c4d5", "a0e1a2b3c4d5", "e0e1a2b3c4d5"], "element ids cross TS to Python and back");
const expectedPeers = ["csharp-peer", "python-peer", "rust-peer", "ts-peer"];
logInteropPhase("schema-v0", "typescript discovery probe");
await expectProbePeers("ts-probe", process.execPath, [
tsInteropPeerScript(),
"probe",
"--runtime-id", "ts-prober",
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--timeout-ms", "3000",
], cultNetTsRoot, {}, expectedPeers);
logInteropPhase("schema-v0", "rust discovery probe");
await expectProbePeers("rust-probe", rustInteropPeerPath(), [
"probe",
"--runtime-id", "rust-prober",
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--timeout-ms", "3000",
], cultnetRsRoot, {}, expectedPeers);
logInteropPhase("schema-v0", "csharp discovery probe");
await expectProbePeers("csharp-probe", dotnetCommand, [
csharpDllPath,
"probe",
"--runtime-id", "csharp-prober",
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--timeout-ms", "3000",
], cultLibRoot, {}, expectedPeers);
logInteropPhase("schema-v0", "python discovery probe");
await expectProbePeers("python-probe", pythonInteropCommand(), [
"-m", "cultnet_py.interop_peer",
"probe",
"--runtime-id", "python-prober",
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--timeout-ms", "3000",
], cultcachePyRoot, { PYTHONPATH: cultcachePySrc }, expectedPeers);
});
test("CultNet TypeScript and Rust full interop peers exchange schema-v0 over RUDP", async (t) => {
logInteropPhase("ts-rust-full-rudp", "build peers");
await buildRustInteropPeer();
cleanInteropStores([
"rust-rudp-peer",
"rust-rudp-client-dial",
"ts-rust-rudp-peer",
"ts-rust-rudp-client-dial",
]);
const rustTcpPort = await getFreePort();
const rustRudpPort = await getFreePort();
const tsTcpPort = await getFreePort();
const tsRudpPort = await getFreePort();
const discoveryPort = await getFreePort();
const advertiseHost = findAdvertiseHost();
const servers: RunningServeProcess[] = [];
// Registered before anything is spawned. A peer whose readiness rejects
// would otherwise skip this and strand every peer already running.
t.after(async () => {
await Promise.all(servers.map(stopProcess));
});
servers.push(await spawnServeProcess("rust-rudp", {
command: rustInteropPeerPath(),
args: [
"serve",
"--runtime-id", "rust-rudp-peer",
"--runtime-kind", "rust",
"--display-name", "Rust RUDP Peer",
"--agent-id", "rust-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(rustTcpPort),
"--rudp-port", String(rustRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultnetRsRoot,
}));
await servers[servers.length - 1].ready;
servers.push(await spawnServeProcess("ts-rust-rudp", {
command: process.execPath,
args: [
tsInteropPeerScript(),
"serve",
"--runtime-id", "ts-rust-rudp-peer",
"--runtime-kind", "node",
"--display-name", "TypeScript Rust RUDP Peer",
"--agent-id", "ts-rust-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(tsTcpPort),
"--rudp-port", String(tsRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultNetTsRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("ts-rust-full-rudp", "typescript dials rust over rudp");
const tsRudpDial = await runJsonCommand("ts-rust-rudp-dial", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-rust-rudp-client",
"--runtime-kind", "node",
"--display-name", "TS Rust RUDP Dialer",
"--agent-id", "ts-rust-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(rustRudpPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsRudpDial.transport, "rudp");
assert.equal(tsRudpDial.remoteHello.runtimeId, "rust-rudp-peer");
assert.equal(tsRudpDial.hasInteropSchema, true);
assert.equal(tsRudpDial.retrievedNote.authorRuntimeId, "rust-rudp-peer");
assert.ok(tsRudpDial.mutatedNote.tags.includes("decorated:ts-rust-rudp-client"));
assert.equal(tsRudpDial.fireReceipt.accepted, true);
logInteropPhase("ts-rust-full-rudp", "rust dials typescript over rudp");
const rustRudpDial = await runJsonCommand("rust-rudp-dial", rustInteropPeerPath(), [
"dial",
"--runtime-id", "rust-rudp-client",
"--runtime-kind", "rust",
"--display-name", "Rust RUDP Dialer",
"--agent-id", "rust-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(tsRudpPort),
"--schema-path", interopSchemaPath,
], cultnetRsRoot);
assert.equal(rustRudpDial.transport, "rudp");
assert.equal(rustRudpDial.remoteHello.runtimeId, "ts-rust-rudp-peer");
assert.equal(rustRudpDial.hasInteropSchema, true);
assert.equal(rustRudpDial.retrievedNote.authorRuntimeId, "ts-rust-rudp-peer");
assert.ok(rustRudpDial.mutatedNote.tags.includes("decorated:rust-rudp-client"));
assert.equal(rustRudpDial.fireReceipt.accepted, true);
});
test("CultNet TypeScript and Python exchange schema frames over the shared RUDP socket transport", async () => {
const pythonPeer = spawnPythonRudpPeer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const pythonPort = await pythonPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rudp-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: pythonPort,
connectionId: 0x55667788,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Python handshake");
client.send("schema", Buffer.from("client-state"));
const [frame] = await withTimeout(receivedFrame, 2_000, "Python RUDP response frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("python-state"));
const [exitCode] = await withTimeout(once(pythonPeer.child, "exit"), 2_000, "Python RUDP peer exit");
assert.equal(exitCode, 0, pythonPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (pythonPeer.child.exitCode === null && !pythonPeer.child.killed) {
pythonPeer.child.kill("SIGTERM");
await once(pythonPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Python and TypeScript exchange schema frames when Python dials a TypeScript RUDP server", async () => {
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rudp-server-interop",
socket: serverSocket,
mode: "server",
connectionId: 0x66554433,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const pythonClient = spawnPythonRudpClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 5_000, "Python RUDP client frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("python-client-state"));
server.send("schema", Buffer.from("ts-server-state"));
const [exitCode] = await withTimeout(once(pythonClient.child, "exit"), 5_000, "Python RUDP client exit");
assert.equal(exitCode, 0, pythonClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (pythonClient.child.exitCode === null && !pythonClient.child.killed) {
pythonClient.child.kill("SIGTERM");
await once(pythonClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and Python exchange schema-v0 MessagePack messages over RUDP", async () => {
const pythonPeer = spawnPythonRudpMessagePeer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const pythonPort = await pythonPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-python-rudp-message-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: pythonPort,
connectionId: 0x55667789,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-python-message-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Python message handshake");
client.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "ts-python-schema-message",
includeSchemaJson: false,
schemaIds: [],
kinds: ["wire_message"],
})));
const [frame] = await withTimeout(receivedFrame, 2_000, "Python RUDP schema-v0 response frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
runtimeId?: string;
supportsSchemaCatalog?: boolean;
transportProfiles?: Array<{ transports?: Array<{ protocol?: string; reconnectPolicy?: unknown; channels?: Array<{ channelId?: string }> }> }>;
};
assert.equal(message.schemaVersion, "cultnet.hello.v0");
assert.equal(message.runtimeId, "python-rudp-message-interop");
assert.equal(message.supportsSchemaCatalog, true);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.protocol, "rudp");
assertRudpReconnectPolicy("Python", message.transportProfiles?.[0]?.transports?.[0]?.reconnectPolicy);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.channels?.[0]?.channelId, "schema");
const [exitCode] = await withTimeout(once(pythonPeer.child, "exit"), 2_000, "Python RUDP message peer exit");
assert.equal(exitCode, 0, pythonPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (pythonPeer.child.exitCode === null && !pythonPeer.child.killed) {
pythonPeer.child.kill("SIGTERM");
await once(pythonPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Python and TypeScript exchange schema-v0 MessagePack messages over RUDP when Python dials", async () => {
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-python-rudp-message-server",
socket: serverSocket,
mode: "server",
connectionId: 0x66554434,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const pythonClient = spawnPythonRudpMessageClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 5_000, "Python RUDP schema-v0 client frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
messageId?: string;
kinds?: string[];
};
assert.equal(message.schemaVersion, "cultnet.schema_catalog_request.v0");
assert.equal(message.messageId, "python-ts-schema-message");
assert.deepEqual(message.kinds, ["wire_message"]);
server.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.hello.v0",
runtimeId: "ts-python-rudp-message-server",
runtimeKind: "typescript",
supportedDocumentTypes: [],
supportedMutationContracts: [],
supportedMessageVersions: ["cultnet.hello.v0", "cultnet.schema_catalog_request.v0"],
transportProfiles: [],
supportsSchemaCatalog: true,
})));
const [exitCode] = await withTimeout(once(pythonClient.child, "exit"), 5_000, "Python RUDP message client exit");
assert.equal(exitCode, 0, pythonClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (pythonClient.child.exitCode === null && !pythonClient.child.killed) {
pythonClient.child.kill("SIGTERM");
await once(pythonClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and Rust exchange schema frames over the shared RUDP socket transport", async () => {
await buildRustInteropPeer();
const rustPeer = spawnRustRudpServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const rustPort = await rustPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rust-rudp-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: rustPort,
connectionId: 0x22446688,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-rust-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Rust handshake");
client.send("schema", Buffer.from("ts-rust-client-state"));
const [frame] = await withTimeout(receivedFrame, 2_000, "Rust RUDP response frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("rust-server-state"));
const [exitCode] = await withTimeout(once(rustPeer.child, "exit"), 2_000, "Rust RUDP peer exit");
assert.equal(exitCode, 0, rustPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (rustPeer.child.exitCode === null && !rustPeer.child.killed) {
rustPeer.child.kill("SIGTERM");
await once(rustPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Rust and TypeScript exchange schema frames when Rust dials a TypeScript RUDP server", async () => {
await buildRustInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rust-rudp-server-interop",
socket: serverSocket,
mode: "server",
connectionId: 0x88664422,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const rustClient = spawnRustRudpClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "Rust RUDP client frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("rust-client-state"));
server.send("schema", Buffer.from("ts-rust-server-state"));
const [exitCode] = await withTimeout(once(rustClient.child, "exit"), 2_000, "Rust RUDP client exit");
assert.equal(exitCode, 0, rustClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (rustClient.child.exitCode === null && !rustClient.child.killed) {
rustClient.child.kill("SIGTERM");
await once(rustClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and Rust exchange schema-v0 MessagePack messages over RUDP", async () => {
await buildRustInteropPeer();
const rustPeer = spawnRustRudpMessageServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const rustPort = await rustPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rust-rudp-message-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: rustPort,
connectionId: 0x22446689,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-rust-message-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Rust message handshake");
client.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "ts-rust-schema-message",
includeSchemaJson: false,
schemaIds: [],
kinds: ["wire_message"],
})));
const [frame] = await withTimeout(receivedFrame, 2_000, "Rust RUDP schema-v0 response frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
runtimeId?: string;
supportsSchemaCatalog?: boolean;
transportProfiles?: Array<{ transports?: Array<{ protocol?: string; reconnectPolicy?: unknown; channels?: Array<{ channelId?: string }> }> }>;
};
assert.equal(message.schemaVersion, "cultnet.hello.v0");
assert.equal(message.runtimeId, "rust-rudp-message-interop");
assert.equal(message.supportsSchemaCatalog, true);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.protocol, "rudp");
assertRudpReconnectPolicy("Rust", message.transportProfiles?.[0]?.transports?.[0]?.reconnectPolicy);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.channels?.[0]?.channelId, "schema");
const [exitCode] = await withTimeout(once(rustPeer.child, "exit"), 2_000, "Rust RUDP message peer exit");
assert.equal(exitCode, 0, rustPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (rustPeer.child.exitCode === null && !rustPeer.child.killed) {
rustPeer.child.kill("SIGTERM");
await once(rustPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Rust and TypeScript exchange schema-v0 MessagePack messages over RUDP when Rust dials", async () => {
await buildRustInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-rust-rudp-message-server",
socket: serverSocket,
mode: "server",
connectionId: 0x88664423,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const rustClient = spawnRustRudpMessageClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "Rust RUDP schema-v0 client frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
messageId?: string;
kinds?: string[];
};
assert.equal(message.schemaVersion, "cultnet.schema_catalog_request.v0");
assert.equal(message.messageId, "rust-ts-schema-message");
assert.deepEqual(message.kinds, ["wire_message"]);
server.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.hello.v0",
runtimeId: "ts-rust-rudp-message-server",
runtimeKind: "typescript",
supportedDocumentTypes: [],
supportedMutationContracts: [],
supportedMessageVersions: ["cultnet.hello.v0", "cultnet.schema_catalog_request.v0"],
transportProfiles: [],
supportsSchemaCatalog: true,
})));
const [exitCode] = await withTimeout(once(rustClient.child, "exit"), 2_000, "Rust RUDP message client exit");
assert.equal(exitCode, 0, rustClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (rustClient.child.exitCode === null && !rustClient.child.killed) {
rustClient.child.kill("SIGTERM");
await once(rustClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and C# exchange schema frames over the shared RUDP socket transport", async () => {
await buildCSharpInteropPeer();
const csharpPeer = spawnCSharpRudpServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const csharpPort = await csharpPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-csharp-rudp-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: csharpPort,
connectionId: 0x33557799,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-csharp-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete C# handshake");
client.send("schema", Buffer.from("ts-csharp-client-state"));
const [frame] = await withTimeout(receivedFrame, 2_000, "C# RUDP response frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("csharp-server-state"));
const [exitCode] = await withTimeout(once(csharpPeer.child, "exit"), 2_000, "C# RUDP peer exit");
assert.equal(exitCode, 0, csharpPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (csharpPeer.child.exitCode === null && !csharpPeer.child.killed) {
csharpPeer.child.kill("SIGTERM");
await once(csharpPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet C# and TypeScript exchange schema frames when C# dials a TypeScript RUDP server", async () => {
await buildCSharpInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-csharp-rudp-server-interop",
socket: serverSocket,
mode: "server",
connectionId: 0x99775533,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const csharpClient = spawnCSharpRudpClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "C# RUDP client frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("csharp-client-state"));
server.send("schema", Buffer.from("ts-csharp-server-state"));
const [exitCode] = await withTimeout(once(csharpClient.child, "exit"), 2_000, "C# RUDP client exit");
assert.equal(exitCode, 0, csharpClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (csharpClient.child.exitCode === null && !csharpClient.child.killed) {
csharpClient.child.kill("SIGTERM");
await once(csharpClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and C# exchange schema-v0 MessagePack messages over RUDP", async () => {
await buildCSharpInteropPeer();
const csharpPeer = spawnCSharpRudpMessageServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const csharpPort = await csharpPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-csharp-rudp-message-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: csharpPort,
connectionId: 0x3355779a,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-csharp-message-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete C# message handshake");
client.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "ts-csharp-schema-message",
includeSchemaJson: false,
schemaIds: [],
kinds: ["wire_message"],
})));
const [frame] = await withTimeout(receivedFrame, 2_000, "C# RUDP schema-v0 response frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
runtimeId?: string;
supportsSchemaCatalog?: boolean;
transportProfiles?: Array<{ transports?: Array<{ protocol?: string; reconnectPolicy?: unknown; channels?: Array<{ channelId?: string }> }> }>;
};
assert.equal(message.schemaVersion, "cultnet.hello.v0");
assert.equal(message.runtimeId, "csharp-rudp-message-interop");
assert.equal(message.supportsSchemaCatalog, true);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.protocol, "rudp");
assertRudpReconnectPolicy("C#", message.transportProfiles?.[0]?.transports?.[0]?.reconnectPolicy);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.channels?.[0]?.channelId, "schema");
const [exitCode] = await withTimeout(once(csharpPeer.child, "exit"), 2_000, "C# RUDP message peer exit");
assert.equal(exitCode, 0, csharpPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (csharpPeer.child.exitCode === null && !csharpPeer.child.killed) {
csharpPeer.child.kill("SIGTERM");
await once(csharpPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet C# and TypeScript exchange schema-v0 MessagePack messages over RUDP when C# dials", async () => {
await buildCSharpInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-csharp-rudp-message-server",
socket: serverSocket,
mode: "server",
connectionId: 0x99775534,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const csharpClient = spawnCSharpRudpMessageClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "C# RUDP schema-v0 client frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
messageId?: string;
kinds?: string[];
};
assert.equal(message.schemaVersion, "cultnet.schema_catalog_request.v0");
assert.equal(message.messageId, "csharp-ts-schema-message");
assert.deepEqual(message.kinds, ["wire_message"]);
server.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.hello.v0",
runtimeId: "ts-csharp-rudp-message-server",
runtimeKind: "typescript",
supportedDocumentTypes: [],
supportedMutationContracts: [],
supportedMessageVersions: ["cultnet.hello.v0", "cultnet.schema_catalog_request.v0"],
transportProfiles: [],
supportsSchemaCatalog: true,
})));
const [exitCode] = await withTimeout(once(csharpClient.child, "exit"), 2_000, "C# RUDP message client exit");
assert.equal(exitCode, 0, csharpClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (csharpClient.child.exitCode === null && !csharpClient.child.killed) {
csharpClient.child.kill("SIGTERM");
await once(csharpClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and Kotlin exchange schema frames over the shared RUDP socket transport", async () => {
await buildKotlinInteropPeer();
const kotlinPeer = spawnKotlinRudpServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const kotlinPort = await kotlinPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-kotlin-rudp-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: kotlinPort,
connectionId: 0x446688aa,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-kotlin-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Kotlin handshake");
client.send("schema", Buffer.from("ts-kotlin-client-state"));
const [frame] = await withTimeout(receivedFrame, 2_000, "Kotlin RUDP response frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("kotlin-server-state"));
const [exitCode] = await withTimeout(once(kotlinPeer.child, "exit"), 2_000, "Kotlin RUDP peer exit");
assert.equal(exitCode, 0, kotlinPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (kotlinPeer.child.exitCode === null && !kotlinPeer.child.killed) {
kotlinPeer.child.kill("SIGTERM");
await once(kotlinPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Kotlin and TypeScript exchange schema frames when Kotlin dials a TypeScript RUDP server", async () => {
await buildKotlinInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-kotlin-rudp-server-interop",
socket: serverSocket,
mode: "server",
connectionId: 0xaa886644,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const kotlinClient = spawnKotlinRudpClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "Kotlin RUDP client frame");
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from("kotlin-client-state"));
server.send("schema", Buffer.from("ts-kotlin-server-state"));
const [exitCode] = await withTimeout(once(kotlinClient.child, "exit"), 2_000, "Kotlin RUDP client exit");
assert.equal(exitCode, 0, kotlinClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (kotlinClient.child.exitCode === null && !kotlinClient.child.killed) {
kotlinClient.child.kill("SIGTERM");
await once(kotlinClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and Kotlin exchange schema-v0 MessagePack messages over RUDP", async () => {
await buildKotlinInteropPeer();
const kotlinPeer = spawnKotlinRudpMessageServer();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
const kotlinPort = await kotlinPeer.ready;
client = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-kotlin-rudp-message-interop",
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: kotlinPort,
connectionId: 0x446688ab,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from("ts-kotlin-message-join"));
await waitFor(() => client?.connected === true, "TypeScript RUDP client to complete Kotlin message handshake");
client.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "ts-kotlin-schema-message",
includeSchemaJson: false,
schemaIds: [],
kinds: ["wire_message"],
})));
const [frame] = await withTimeout(receivedFrame, 2_000, "Kotlin RUDP schema-v0 response frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
runtimeId?: string;
supportsSchemaCatalog?: boolean;
transportProfiles?: Array<{ transports?: Array<{ protocol?: string; reconnectPolicy?: unknown; channels?: Array<{ channelId?: string }> }> }>;
};
assert.equal(message.schemaVersion, "cultnet.hello.v0");
assert.equal(message.runtimeId, "kotlin-rudp-message-interop");
assert.equal(message.supportsSchemaCatalog, true);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.protocol, "rudp");
assertRudpReconnectPolicy("Kotlin", message.transportProfiles?.[0]?.transports?.[0]?.reconnectPolicy);
assert.equal(message.transportProfiles?.[0]?.transports?.[0]?.channels?.[0]?.channelId, "schema");
const [exitCode] = await withTimeout(once(kotlinPeer.child, "exit"), 2_000, "Kotlin RUDP message peer exit");
assert.equal(exitCode, 0, kotlinPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (kotlinPeer.child.exitCode === null && !kotlinPeer.child.killed) {
kotlinPeer.child.kill("SIGTERM");
await once(kotlinPeer.child, "exit").catch(() => undefined);
}
}
});
test("CultNet Kotlin and TypeScript exchange schema-v0 MessagePack messages over RUDP when Kotlin dials", async () => {
await buildKotlinInteropPeer();
const serverSocket = await bindUdpSocket();
const server = new CultNetRudpSocketTransportConnection({
runtimeId: "ts-kotlin-rudp-message-server",
socket: serverSocket,
mode: "server",
connectionId: 0xaa886645,
initialSequence: 100,
resendDelayMs: 25,
resendPollMs: 5,
});
const kotlinClient = spawnKotlinRudpMessageClient(udpSocketPort(serverSocket));
try {
const receivedFrame = once(server, "frame");
const [frame] = await withTimeout(receivedFrame, 2_000, "Kotlin RUDP schema-v0 client frame");
assert.equal(frame.channelId, "schema");
const message = decode(frame.payload) as {
schemaVersion?: string;
messageId?: string;
kinds?: string[];
};
assert.equal(message.schemaVersion, "cultnet.schema_catalog_request.v0");
assert.equal(message.messageId, "kotlin-ts-schema-message");
assert.deepEqual(message.kinds, ["wire_message"]);
server.send("schema", Buffer.from(encode({
schemaVersion: "cultnet.hello.v0",
runtimeId: "ts-kotlin-rudp-message-server",
runtimeKind: "typescript",
supportedDocumentTypes: [],
supportedMutationContracts: [],
supportedMessageVersions: ["cultnet.hello.v0", "cultnet.schema_catalog_request.v0"],
transportProfiles: [],
supportsSchemaCatalog: true,
})));
const [exitCode] = await withTimeout(once(kotlinClient.child, "exit"), 2_000, "Kotlin RUDP message client exit");
assert.equal(exitCode, 0, kotlinClient.stderr.join(""));
} finally {
server.close();
closeUdpSocket(serverSocket);
if (kotlinClient.child.exitCode === null && !kotlinClient.child.killed) {
kotlinClient.child.kill("SIGTERM");
await once(kotlinClient.child, "exit").catch(() => undefined);
}
}
});
test("CultNet TypeScript and C# full interop peers exchange schema-v0 over RUDP", async (t) => {
logInteropPhase("ts-csharp-full-rudp", "build csharp peer");
await buildCSharpInteropPeer();
cleanInteropStores([
"ts-csharp-rudp-peer",
"ts-csharp-rudp-client-dial",
]);
const csharpTcpPort = await getFreePort();
const csharpRudpPort = await getFreePort();
const tsTcpPort = await getFreePort();
const tsRudpPort = await getFreePort();
const discoveryPort = await getFreePort();
const advertiseHost = findAdvertiseHost();
const servers: RunningServeProcess[] = [];
// Registered before anything is spawned. A peer whose readiness rejects
// would otherwise skip this and strand every peer already running.
t.after(async () => {
await Promise.all(servers.map(stopProcess));
});
servers.push(await spawnServeProcess("csharp-rudp", {
command: dotnetCommand,
args: [
csharpDllPath,
"serve",
"--runtime-id", "csharp-rudp-peer",
"--runtime-kind", "dotnet",
"--display-name", "CSharp RUDP Peer",
"--agent-id", "csharp-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(csharpTcpPort),
"--rudp-port", String(csharpRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultLibRoot,
}));
await servers[servers.length - 1].ready;
servers.push(await spawnServeProcess("ts-csharp-rudp", {
command: process.execPath,
args: [
tsInteropPeerScript(),
"serve",
"--runtime-id", "ts-csharp-rudp-peer",
"--runtime-kind", "node",
"--display-name", "TypeScript CSharp RUDP Peer",
"--agent-id", "ts-csharp-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(tsTcpPort),
"--rudp-port", String(tsRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultNetTsRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("ts-csharp-full-rudp", "typescript dials csharp over rudp");
const tsRudpDial = await runJsonCommand("ts-csharp-rudp-dial", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-csharp-rudp-client",
"--runtime-kind", "node",
"--display-name", "TS CSharp RUDP Dialer",
"--agent-id", "ts-csharp-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(csharpRudpPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsRudpDial.transport, "rudp");
assert.equal(tsRudpDial.remoteHello.runtimeId, "csharp-rudp-peer");
assert.equal(tsRudpDial.hasInteropSchema, true);
assert.equal(tsRudpDial.retrievedNote.authorRuntimeId, "csharp-rudp-peer");
assert.ok(tsRudpDial.mutatedNote.tags.includes("decorated:ts-csharp-rudp-client"));
assert.equal(tsRudpDial.fireReceipt.accepted, true);
logInteropPhase("ts-csharp-full-rudp", "csharp dials typescript over rudp");
const csharpRudpDial = await runJsonCommand("csharp-rudp-dial", dotnetCommand, [
csharpDllPath,
"dial",
"--runtime-id", "csharp-rudp-client",
"--runtime-kind", "dotnet",
"--display-name", "CSharp RUDP Dialer",
"--agent-id", "csharp-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(tsRudpPort),
"--schema-path", interopSchemaPath,
], cultLibRoot);
assert.equal(csharpRudpDial.transport, "rudp");
assert.equal(csharpRudpDial.remoteHello.runtimeId, "ts-csharp-rudp-peer");
assert.equal(csharpRudpDial.hasInteropSchema, true);
assert.equal(csharpRudpDial.retrievedNote.authorRuntimeId, "ts-csharp-rudp-peer");
assert.ok(csharpRudpDial.mutatedNote.tags.includes("decorated:csharp-rudp-client"));
assert.equal(csharpRudpDial.fireReceipt.accepted, true);
});
test("CultNet TypeScript and Kotlin full interop peers exchange schema-v0 over RUDP", async (t) => {
logInteropPhase("ts-kotlin-full-rudp", "build kotlin peer");
await buildKotlinInteropPeer();
cleanInteropStores([
"ts-kotlin-rudp-peer",
"ts-kotlin-rudp-client-dial",
]);
const kotlinTcpPort = await getFreePort();
const kotlinRudpPort = await getFreePort();
const tsTcpPort = await getFreePort();
const tsRudpPort = await getFreePort();
const discoveryPort = await getFreePort();
const advertiseHost = findAdvertiseHost();
const servers: RunningServeProcess[] = [];
// Registered before anything is spawned. A peer whose readiness rejects
// would otherwise skip this and strand every peer already running.
t.after(async () => {
await Promise.all(servers.map(stopProcess));
});
servers.push(await spawnServeProcess("kotlin-rudp", {
command: kotlinJavaCommand,
args: [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"serve",
"--runtime-id", "kotlin-rudp-peer",
"--runtime-kind", "kotlin",
"--display-name", "Kotlin RUDP Peer",
"--agent-id", "kotlin-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(kotlinTcpPort),
"--rudp-port", String(kotlinRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultLibRoot,
}));
await servers[servers.length - 1].ready;
servers.push(await spawnServeProcess("ts-kotlin-rudp", {
command: process.execPath,
args: [
tsInteropPeerScript(),
"serve",
"--runtime-id", "ts-kotlin-rudp-peer",
"--runtime-kind", "node",
"--display-name", "TypeScript Kotlin RUDP Peer",
"--agent-id", "ts-kotlin-rudp-agent",
"--bind-host", "127.0.0.1",
"--advertise-host", advertiseHost,
"--tcp-port", String(tsTcpPort),
"--rudp-port", String(tsRudpPort),
"--discovery-port", String(discoveryPort),
"--discovery-group", discoveryGroup,
"--schema-path", interopSchemaPath,
],
cwd: cultNetTsRoot,
}));
await servers[servers.length - 1].ready;
logInteropPhase("ts-kotlin-full-rudp", "typescript dials kotlin over rudp");
const tsRudpDial = await runJsonCommand("ts-kotlin-rudp-dial", process.execPath, [
tsInteropPeerScript(),
"dial",
"--runtime-id", "ts-kotlin-rudp-client",
"--runtime-kind", "node",
"--display-name", "TS Kotlin RUDP Dialer",
"--agent-id", "ts-kotlin-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(kotlinRudpPort),
"--schema-path", interopSchemaPath,
], cultNetTsRoot);
assert.equal(tsRudpDial.transport, "rudp");
assert.equal(tsRudpDial.remoteHello.runtimeId, "kotlin-rudp-peer");
assert.equal(tsRudpDial.hasInteropSchema, true);
assert.equal(tsRudpDial.retrievedNote.authorRuntimeId, "kotlin-rudp-peer");
assert.ok(tsRudpDial.mutatedNote.tags.includes("decorated:ts-kotlin-rudp-client"));
assert.equal(tsRudpDial.fireReceipt.accepted, true);
logInteropPhase("ts-kotlin-full-rudp", "kotlin dials typescript over rudp");
const kotlinRudpDial = await runJsonCommand("kotlin-rudp-dial", kotlinJavaCommand, [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"dial",
"--runtime-id", "kotlin-rudp-client",
"--runtime-kind", "kotlin",
"--display-name", "Kotlin RUDP Dialer",
"--agent-id", "kotlin-rudp-client-agent",
"--target-host", "127.0.0.1",
"--target-rudp-port", String(tsRudpPort),
"--schema-path", interopSchemaPath,
], cultLibRoot);
assert.equal(kotlinRudpDial.transport, "rudp");
assert.equal(kotlinRudpDial.remoteHello.runtimeId, "ts-kotlin-rudp-peer");
assert.equal(kotlinRudpDial.hasInteropSchema, true);
assert.equal(kotlinRudpDial.retrievedNote.authorRuntimeId, "ts-kotlin-rudp-peer");
assert.ok(kotlinRudpDial.mutatedNote.tags.includes("decorated:kotlin-rudp-client"));
assert.equal(kotlinRudpDial.fireReceipt.accepted, true);
});
test("CultNet RUDP reliable schema delivery survives one dropped client data packet across runtimes", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const serverPeer = peer.spawn();
const clientSocket = await bindUdpSocket();
const lossyBridge = await createRudpLossyBridge({
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
shouldDropClientPacket: (wire) => {
const packet = decodeRudpPacket(wire);
return packet.packetType === "data"
&& packet.channelId === "schema"
&& Buffer.from(packet.payload ?? []).equals(Buffer.from(peer.clientPayload));
},
});
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-lossy-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: lossyBridge.port,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} handshake through lossy bridge`);
client.send("schema", Buffer.from(peer.clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame after dropped data packet`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
assert.equal(lossyBridge.droppedClientPackets, 1, `${peer.name} bridge should drop exactly one client data packet`);
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after lossy delivery`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
lossyBridge.close();
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP reliable schema response survives one dropped server data packet across runtimes", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const serverPeer = peer.spawn();
const clientSocket = await bindUdpSocket();
const lossyBridge = await createRudpLossyBridge({
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
shouldDropServerPacket: (wire) => {
const packet = decodeRudpPacket(wire);
return packet.packetType === "data"
&& packet.channelId === "schema"
&& Buffer.from(packet.payload ?? []).equals(Buffer.from(peer.expectedPayload));
},
});
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-server-loss-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: lossyBridge.port,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} handshake through server-loss bridge`);
client.send("schema", Buffer.from(peer.clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame after dropped server data packet`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
assert.equal(lossyBridge.droppedServerPackets, 1, `${peer.name} bridge should drop exactly one server data packet`);
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after server resend`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
lossyBridge.close();
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP reliable schema delivery survives reordered client data packets across runtimes", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const serverPeer = peer.spawn();
const clientSocket = await bindUdpSocket();
const lossyBridge = await createRudpLossyBridge({
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
shouldDelayClientPacket: (wire) => {
const packet = decodeRudpPacket(wire);
return packet.packetType === "data"
&& packet.channelId === "schema"
&& Buffer.from(packet.payload ?? []).equals(Buffer.from(peer.clientPayload));
},
});
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-reordered-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: lossyBridge.port,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} handshake through reorder bridge`);
client.send("schema", Buffer.from(peer.clientPayload));
client.send("schema", Buffer.from(`${peer.clientPayload}:second`));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame after reordered client data packets`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
assert.equal(lossyBridge.delayedClientPackets, 1, `${peer.name} bridge should delay exactly one client data packet`);
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after reordered delivery`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
lossyBridge.close();
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP reliable schema response survives reordered server data packets across runtimes", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const serverPeer = peer.spawn({
serverExtraPayload: `${peer.expectedPayload}:second`,
});
const clientSocket = await bindUdpSocket();
const lossyBridge = await createRudpLossyBridge({
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
shouldDelayServerPacket: (wire) => {
const packet = decodeRudpPacket(wire);
return packet.packetType === "data"
&& packet.channelId === "schema"
&& Buffer.from(packet.payload ?? []).equals(Buffer.from(peer.expectedPayload));
},
});
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-server-reordered-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: lossyBridge.port,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} handshake through server reorder bridge`);
client.send("schema", Buffer.from(peer.clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame after reordered server data packets`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
assert.equal(lossyBridge.delayedServerPackets, 1, `${peer.name} bridge should delay exactly one server data packet`);
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after server reorder`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
lossyBridge.close();
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP fragmented schema frames cross runtime boundaries", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const clientPayload = `${peer.clientPayload}:fragmented:${"x".repeat(96)}`;
const serverPayload = `${peer.expectedPayload}:fragmented:${"y".repeat(96)}`;
const serverPeer = peer.spawn({
clientPayload,
serverPayload,
maxFragmentBytes: 16,
});
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-fragmented-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
maxFragmentBytes: 16,
});
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} fragmented handshake`);
client.send("schema", Buffer.from(clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} fragmented RUDP response frame`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(serverPayload));
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after fragmented exchange`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP disconnect reasons cross runtime boundaries", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const disconnectReason = `${peer.name.toLowerCase()}-done`;
const serverPeer = peer.spawn({ disconnectReason });
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-disconnect-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedFrame = once(client, "frame");
const disconnected = once(client, "disconnect");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} disconnect handshake`);
client.send("schema", Buffer.from(peer.clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame before disconnect`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
const [disconnect] = await withTimeout(disconnected, 2_000, `${peer.name} RUDP disconnect reason`);
assert.deepEqual(Buffer.from(disconnect.reason), Buffer.from(disconnectReason));
assert.equal(client.connected, false);
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after disconnect`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
test("CultNet RUDP ping/pong keepalive crosses runtime boundaries", async () => {
for (const peer of rudpServerInteropPeers()) {
await peer.build();
const serverPeer = peer.spawn();
const clientSocket = await bindUdpSocket();
let client: CultNetRudpSocketTransportConnection | undefined;
try {
client = new CultNetRudpSocketTransportConnection({
runtimeId: `ts-ping-${peer.name.replace(/\W/g, "").toLowerCase()}-rudp-interop`,
socket: clientSocket,
mode: "client",
remoteHost: "127.0.0.1",
remotePort: await serverPeer.ready,
connectionId: peer.connectionId,
initialSequence: 1,
resendDelayMs: 25,
resendPollMs: 5,
});
const receivedPong = once(client, "pong");
const receivedFrame = once(client, "frame");
client.connect(Buffer.from(peer.joinPayload));
await waitFor(() => client?.connected === true, `TypeScript RUDP client to complete ${peer.name} ping handshake`);
const pingPayload = Buffer.from(`${peer.name.toLowerCase()}-ping`);
client.ping(pingPayload);
const [pong] = await withTimeout(receivedPong, 2_000, `${peer.name} RUDP pong`);
assert.deepEqual(Buffer.from(pong.payload), pingPayload);
assert.equal(client.checkTimeout(1_000), false);
client.send("schema", Buffer.from(peer.clientPayload));
const [frame] = await withTimeout(receivedFrame, 2_000, `${peer.name} RUDP response frame after ping`);
assert.equal(frame.channelId, "schema");
assert.deepEqual(Buffer.from(frame.payload), Buffer.from(peer.expectedPayload));
const [exitCode] = await withTimeout(once(serverPeer.child, "exit"), 2_000, `${peer.name} RUDP peer exit after ping`);
assert.equal(exitCode, 0, serverPeer.stderr.join(""));
} finally {
if (client) {
client.close();
} else {
clientSocket.close();
}
if (serverPeer.child.exitCode === null && !serverPeer.child.killed) {
serverPeer.child.kill("SIGTERM");
await once(serverPeer.child, "exit").catch(() => undefined);
}
}
}
});
async function buildInteropPeers(): Promise<void> {
await buildRustInteropPeer();
await buildCSharpInteropPeer();
}
async function buildRustInteropPeer(): Promise<void> {
rustInteropPeerBuild ??= execFileAsync(cargoCommand, ["build", "--quiet", "--example", "cultnet_interop_peer"], {
cwd: cultnetRsRoot,
}).then(() => undefined);
await rustInteropPeerBuild;
}
async function buildCSharpInteropPeer(): Promise<void> {
csharpInteropPeerBuild ??= execFileAsync(dotnetCommand, ["build", csharpProjectPath, "-nologo"], {
cwd: cultLibRoot,
}).then(() => undefined);
await csharpInteropPeerBuild;
}
async function buildCultMeshTsQuicPeer(): Promise<void> {
cultMeshTsInteropPeerBuild ??= (async () => {
const tsc = resolve(cultLibRoot, "node_modules", "typescript", "bin", "tsc");
await execFileAsync(process.execPath, [tsc, "-p", "tsconfig.json"], { cwd: cultMeshTsRoot });
await execFileAsync(process.execPath, [tsc, "-p", "tsconfig.test.json"], { cwd: cultMeshTsRoot });
})();
await cultMeshTsInteropPeerBuild;
}
/**
* Runs `dotnet test` against one project, filtered to a single test by name,
* with extra environment (normally `CULTMESH_NATIVE_EXTERNAL_ENDPOINT`).
* `verbosity=detailed` is what makes VSTest's console logger print a passed
* test's own `TestContext.Out` lines (the golden-bytes hex line, section
* 10's lane 4) instead of only failures.
*/
async function runDotnetTestFilter(
projectPath: string,
testName: string,
env: NodeJS.ProcessEnv,
): Promise<{ stdout: string; stderr: string }> {
try {
return await execFileAsync(
dotnetCommand,
[
"test",
projectPath,
"--filter",
`FullyQualifiedName~${testName}`,
"--logger",
"console;verbosity=detailed",
"-nologo",
],
{ cwd: cultLibRoot, env: { ...process.env, ...env }, timeout: jsonCommandTimeoutMs },
);
} catch (error) {
const execError = error as { stdout?: string; stderr?: string; message: string };
throw new Error(
`dotnet test ${testName} failed.\nstdout:\n${execError.stdout ?? ""}\nstderr:\n${execError.stderr ?? execError.message}`,
);
}
}
async function buildKotlinInteropPeer(): Promise<void> {
kotlinInteropPeerBuild ??= execFileAsync(
"powershell",
["-NoProfile", "-ExecutionPolicy", "Bypass", "-File", resolve(cultmeshKotlinRoot, "build.ps1")],
{
cwd: cultLibRoot,
timeout: 180_000,
},
).then(() => undefined);
await kotlinInteropPeerBuild;
}
function cleanInteropStores(runtimeIds: readonly string[]): void {
for (const runtimeId of runtimeIds) {
rmSync(join(tmpdir(), `cultnet-rs-interop-${runtimeId}.msgpack`), { force: true });
rmSync(join(tmpdir(), `cultnet-ts-interop-${runtimeId}.msgpack`), { force: true });
rmSync(join(tmpdir(), `cultnet-py-interop-${runtimeId}.msgpack`), { force: true });
}
}
function logInteropPhase(lane: string, phase: string): void {
console.error(`[interop:${lane}] ${new Date().toISOString()} ${phase}`);
}
function findAncestor(start: string, marker: string): string | undefined {
let current = start;
while (true) {
if (existsSync(resolve(current, marker))) {
return current;
}
const parent = dirname(current);
if (parent === current) {
return undefined;
}
current = parent;
}
}
interface ServeCommand {
command: string;
args: string[];
cwd: string;
env?: NodeJS.ProcessEnv;
/**
* Defaults to "ignore" (every existing serve mode's stdin is unused, so it
* is closed at spawn). "pipe" keeps stdin open until the caller ends it: the
* C# `quic-realtime-serve` mode watches stdin for EOF as its own shutdown
* signal (`WatchStdinCloseAsync`, `Program.cs:1261-1269`), so spawning it
* with "ignore" hands it an already-closed stdin and it tears down before
* sending anything. Only that mode should ask for "pipe"; changing the
* default would affect every other serve process spawned here.
*/
stdin?: "ignore" | "pipe";
}
interface RunningServeProcess {
name: string;
child: ChildProcessByStdio<Writable | null, Readable, Readable>;
ready: Promise<unknown>;
stderr: string[];
}
async function spawnServeProcess(name: string, command: ServeCommand): Promise<RunningServeProcess> {
// A non-literal first stdio element (it varies per caller) loses `spawn`'s
// typed-tuple overload entirely, including for the two fixed "pipe"
// elements, so this is asserted rather than inferred: stdout/stderr are
// always piped here, and only stdin's presence varies.
const child = spawn(command.command, command.args, {
cwd: command.cwd,
env: { ...process.env, ...command.env },
stdio: [command.stdin ?? "ignore", "pipe", "pipe"],
}) as ChildProcessByStdio<Writable | null, Readable, Readable>;
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<unknown>((resolve, reject) => {
const timeout = setTimeout(() => {
reject(new Error(`${name} serve process did not become ready within ${serveReadyTimeoutMs}ms.\n${stderr.join("")}`));
}, serveReadyTimeoutMs);
const resolveReady = (value: unknown) => {
clearTimeout(timeout);
resolve(value);
};
const rejectReady = (error: Error) => {
clearTimeout(timeout);
reject(error);
};
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string };
if (parsed.status === "ready") {
resolveReady(parsed);
return;
}
} catch (error) {
rejectReady(new Error(`${name} emitted non-JSON stdout while starting: ${line}`));
return;
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`${name} serve process exited before becoming ready (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { name, child, ready, stderr };
}
async function runJsonCommand(
name: string,
command: string,
args: string[],
cwd: string,
env: NodeJS.ProcessEnv = {},
): Promise<any> {
const { stdout, stderr } = await execFileAsync(command, args, {
cwd,
env: { ...process.env, ...env },
timeout: jsonCommandTimeoutMs,
});
const trimmed = stdout.trim();
if (!trimmed) {
throw new Error(`${name} produced no stdout.\n${stderr}`);
}
const lines = trimmed.split(/\r?\n/).filter(Boolean);
try {
return JSON.parse(lines.at(-1) as string);
} catch (error) {
throw new Error(`${name} did not end with JSON stdout.\nstdout:\n${stdout}\nstderr:\n${stderr}`);
}
}
interface RunningPythonRudpPeer {
child: ChildProcessByStdio<null, Readable, Readable>;
ready: Promise<number>;
stderr: string[];
}
function spawnPythonRudpPeer(options: RudpServerSpawnOptions = {}): RunningPythonRudpPeer {
const child = spawn(pythonInteropCommand(), ["-c", pythonRudpPeerScript], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
...(options.clientPayload ? { RUDP_CLIENT_PAYLOAD: options.clientPayload } : {}),
...(options.serverPayload ? { RUDP_SERVER_PAYLOAD: options.serverPayload } : {}),
...(options.serverExtraPayload ? { RUDP_SERVER_EXTRA_PAYLOAD: options.serverExtraPayload } : {}),
...(options.disconnectReason ? { RUDP_DISCONNECT_REASON: options.disconnectReason } : {}),
...(options.maxFragmentBytes ? { RUDP_MAX_FRAGMENT_BYTES: String(options.maxFragmentBytes) } : {}),
},
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
const portMatch = /^PORT\s+(\d+)$/.exec(line);
if (portMatch) {
resolveReady(Number(portMatch[1]));
continue;
}
if (line !== "OK") {
rejectReady(new Error(`Python RUDP peer emitted unexpected stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Python RUDP peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnPythonRudpMessagePeer(): RunningPythonRudpPeer {
const child = spawn(pythonInteropCommand(), ["-c", pythonRudpMessagePeerScript], {
cwd: cultcachePyRoot,
env: { ...process.env, PYTHONPATH: cultcachePySrc },
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
const portMatch = /^PORT\s+(\d+)$/.exec(line);
if (portMatch) {
resolveReady(Number(portMatch[1]));
continue;
}
if (line !== "OK") {
rejectReady(new Error(`Python RUDP message peer emitted unexpected stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Python RUDP message peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnRustRudpServer(options: RudpServerSpawnOptions = {}): RunningPythonRudpPeer {
const child = spawn(rustInteropPeerPath(), [
"rudp-serve-once",
"--bind-host", "127.0.0.1",
...(options.clientPayload ? ["--client-payload", options.clientPayload] : []),
...(options.serverPayload ? ["--server-payload", options.serverPayload] : []),
...(options.serverExtraPayload ? ["--server-extra-payload", options.serverExtraPayload] : []),
...(options.disconnectReason ? ["--disconnect-reason", options.disconnectReason] : []),
...(options.maxFragmentBytes ? ["--max-fragment-bytes", String(options.maxFragmentBytes)] : []),
], {
cwd: cultnetRsRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`Rust RUDP peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`Rust RUDP peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Rust RUDP peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnRustRudpMessageServer(): RunningPythonRudpPeer {
const child = spawn(rustInteropPeerPath(), [
"rudp-serve-message-once",
"--bind-host", "127.0.0.1",
], {
cwd: cultnetRsRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`Rust RUDP message peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`Rust RUDP message peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Rust RUDP message peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnCSharpRudpServer(options: RudpServerSpawnOptions = {}): RunningPythonRudpPeer {
const child = spawn(dotnetCommand, [
csharpDllPath,
"rudp-serve-once",
"--bind-host", "127.0.0.1",
...(options.clientPayload ? ["--client-payload", options.clientPayload] : []),
...(options.serverPayload ? ["--server-payload", options.serverPayload] : []),
...(options.serverExtraPayload ? ["--server-extra-payload", options.serverExtraPayload] : []),
...(options.disconnectReason ? ["--disconnect-reason", options.disconnectReason] : []),
...(options.maxFragmentBytes ? ["--max-fragment-bytes", String(options.maxFragmentBytes)] : []),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`C# RUDP peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`C# RUDP peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`C# RUDP peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnCSharpRudpMessageServer(): RunningPythonRudpPeer {
const child = spawn(dotnetCommand, [
csharpDllPath,
"rudp-serve-message-once",
"--bind-host", "127.0.0.1",
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`C# RUDP message peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`C# RUDP message peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`C# RUDP message peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnKotlinRudpServer(options: RudpServerSpawnOptions = {}): RunningPythonRudpPeer {
const child = spawn(kotlinJavaCommand, [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"rudp-serve-once",
"--bind-host", "127.0.0.1",
...(options.clientPayload ? ["--client-payload", options.clientPayload] : []),
...(options.serverPayload ? ["--server-payload", options.serverPayload] : []),
...(options.serverExtraPayload ? ["--server-extra-payload", options.serverExtraPayload] : []),
...(options.disconnectReason ? ["--disconnect-reason", options.disconnectReason] : []),
...(options.maxFragmentBytes ? ["--max-fragment-bytes", String(options.maxFragmentBytes)] : []),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`Kotlin RUDP peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`Kotlin RUDP peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Kotlin RUDP peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnKotlinRudpMessageServer(): RunningPythonRudpPeer {
const child = spawn(kotlinJavaCommand, [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"rudp-serve-message-once",
"--bind-host", "127.0.0.1",
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
let stdoutBuffer = "";
const ready = new Promise<number>((resolveReady, rejectReady) => {
child.stdout.setEncoding("utf8");
child.stdout.on("data", (chunk: string) => {
stdoutBuffer += chunk;
while (true) {
const newline = stdoutBuffer.indexOf("\n");
if (newline === -1) {
break;
}
const line = stdoutBuffer.slice(0, newline).trim();
stdoutBuffer = stdoutBuffer.slice(newline + 1);
if (!line) {
continue;
}
try {
const parsed = JSON.parse(line) as { status?: string; port?: number };
if (parsed.status === "ready" && typeof parsed.port === "number") {
resolveReady(parsed.port);
} else if (parsed.status !== "ok") {
rejectReady(new Error(`Kotlin RUDP message peer emitted unexpected stdout: ${line}`));
}
} catch (error) {
rejectReady(new Error(`Kotlin RUDP message peer emitted non-JSON stdout: ${line}`));
}
}
});
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
child.once("exit", (code, signal) => {
rejectReady(new Error(`Kotlin RUDP message peer exited before publishing a port (code=${code}, signal=${signal}).\n${stderr.join("")}`));
});
child.once("error", rejectReady);
});
return { child, ready, stderr };
}
function spawnPythonRudpClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(pythonInteropCommand(), ["-c", pythonRudpClientScript, String(remotePort)], {
cwd: cultcachePyRoot,
env: { ...process.env, PYTHONPATH: cultcachePySrc },
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnPythonRudpMessageClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(pythonInteropCommand(), ["-c", pythonRudpMessageClientScript, String(remotePort)], {
cwd: cultcachePyRoot,
env: { ...process.env, PYTHONPATH: cultcachePySrc },
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnKotlinRudpClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(kotlinJavaCommand, [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"rudp-dial-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnKotlinRudpMessageClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(kotlinJavaCommand, [
"-cp", kotlinClasspath,
"org.gamecult.cultmesh.CultMeshKt",
"rudp-dial-message-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnCSharpRudpClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(dotnetCommand, [
csharpDllPath,
"rudp-dial-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnCSharpRudpMessageClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(dotnetCommand, [
csharpDllPath,
"rudp-dial-message-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultLibRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnRustRudpClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(rustInteropPeerPath(), [
"rudp-dial-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultnetRsRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
function spawnRustRudpMessageClient(remotePort: number): Omit<RunningPythonRudpPeer, "ready"> {
const child = spawn(rustInteropPeerPath(), [
"rudp-dial-message-once",
"--target-host", "127.0.0.1",
"--target-port", String(remotePort),
], {
cwd: cultnetRsRoot,
env: process.env,
stdio: ["ignore", "pipe", "pipe"],
});
const stderr: string[] = [];
child.stderr.setEncoding("utf8");
child.stderr.on("data", (chunk: string) => {
stderr.push(chunk);
});
return { child, stderr };
}
/** Closes a socket this file bound, tolerating a transport that adopted and
* already closed it. A socket left open holds the event loop forever, which is
* invisible while tests pass and fatal to the runner's exit. */
function closeUdpSocket(socket: Socket | undefined): void {
if (!socket) {
return;
}
try {
socket.close();
} catch {
// already closed
}
}
async function bindUdpSocket(): Promise<Socket> {
const socket = dgram.createSocket("udp4");
await new Promise<void>((resolveBind, rejectBind) => {
socket.once("error", rejectBind);
socket.bind(0, "127.0.0.1", () => {
socket.off("error", rejectBind);
resolveBind();
});
});
return socket;
}
interface RudpLossyBridge {
port: number;
readonly droppedClientPackets: number;
readonly droppedServerPackets: number;
readonly delayedClientPackets: number;
readonly delayedServerPackets: number;
close(): void;
}
async function createRudpLossyBridge(options: {
remoteHost: string;
remotePort: number;
shouldDropClientPacket?: (wire: Buffer) => boolean;
shouldDropServerPacket?: (wire: Buffer) => boolean;
shouldDelayClientPacket?: (wire: Buffer) => boolean;
shouldDelayServerPacket?: (wire: Buffer) => boolean;
}): Promise<RudpLossyBridge> {
const socket = dgram.createSocket("udp4");
const remote = { address: options.remoteHost, port: options.remotePort };
let client: { address: string; port: number } | undefined;
let droppedClientPackets = 0;
let droppedServerPackets = 0;
let delayedClientPackets = 0;
let delayedServerPackets = 0;
let heldClientWire: Buffer | undefined;
let heldServerWire: Buffer | undefined;
let heldClientTimer: NodeJS.Timeout | undefined;
let heldServerTimer: NodeJS.Timeout | undefined;
const flushHeldClient = () => {
if (!heldClientWire) {
return;
}
const wire = heldClientWire;
heldClientWire = undefined;
if (heldClientTimer) {
clearTimeout(heldClientTimer);
heldClientTimer = undefined;
}
socket.send(wire, remote.port, remote.address);
};
const flushHeldServer = () => {
if (!heldServerWire || !client) {
return;
}
const wire = heldServerWire;
heldServerWire = undefined;
if (heldServerTimer) {
clearTimeout(heldServerTimer);
heldServerTimer = undefined;
}
socket.send(wire, client.port, client.address);
};
socket.on("message", (wire, sender) => {
if (sender.address === remote.address && sender.port === remote.port) {
if (droppedServerPackets === 0 && options.shouldDropServerPacket?.(wire)) {
droppedServerPackets += 1;
return;
}
if (delayedServerPackets === 0 && options.shouldDelayServerPacket?.(wire)) {
delayedServerPackets += 1;
heldServerWire = Buffer.from(wire);
heldServerTimer = setTimeout(flushHeldServer, 25);
return;
}
if (client) {
socket.send(wire, client.port, client.address);
flushHeldServer();
}
return;
}
client ??= { address: sender.address, port: sender.port };
if (droppedClientPackets === 0 && options.shouldDropClientPacket?.(wire)) {
droppedClientPackets += 1;
return;
}
if (delayedClientPackets === 0 && options.shouldDelayClientPacket?.(wire)) {
delayedClientPackets += 1;
heldClientWire = Buffer.from(wire);
heldClientTimer = setTimeout(flushHeldClient, 25);
return;
}
socket.send(wire, remote.port, remote.address);
flushHeldClient();
});
await new Promise<void>((resolveBind, rejectBind) => {
socket.once("error", rejectBind);
socket.bind(0, "127.0.0.1", () => {
socket.off("error", rejectBind);
resolveBind();
});
});
return {
port: udpSocketPort(socket),
get droppedClientPackets() {
return droppedClientPackets;
},
get droppedServerPackets() {
return droppedServerPackets;
},
get delayedClientPackets() {
return delayedClientPackets;
},
get delayedServerPackets() {
return delayedServerPackets;
},
close() {
if (heldClientTimer) {
clearTimeout(heldClientTimer);
}
if (heldServerTimer) {
clearTimeout(heldServerTimer);
}
socket.close();
},
};
}
function udpSocketPort(socket: Socket): number {
const address = socket.address();
if (typeof address === "string") {
throw new Error(`Expected UDP socket address info, got ${address}.`);
}
return address.port;
}
async function waitFor(predicate: () => boolean, description: string, timeoutMs = 2_000): Promise<void> {
const deadline = Date.now() + timeoutMs;
while (Date.now() < deadline) {
if (predicate()) {
return;
}
await delay(5);
}
throw new Error(`Timed out waiting for ${description}.`);
}
async function withTimeout<T>(promise: Promise<T>, timeoutMs: number, description: string): Promise<T> {
let timeout: NodeJS.Timeout | undefined;
try {
return await Promise.race([
promise,
new Promise<never>((_, reject) => {
timeout = setTimeout(() => reject(new Error(`Timed out waiting for ${description}.`)), timeoutMs);
}),
]);
} finally {
if (timeout) {
clearTimeout(timeout);
}
}
}
async function expectProbePeers(
name: string,
command: string,
args: string[],
cwd: string,
env: NodeJS.ProcessEnv,
expectedPeers: string[],
): Promise<void> {
const found = new Set<string>();
const profiles = new Map<string, unknown>();
for (let attempt = 0; attempt < 3; attempt += 1) {
const probe = await runJsonCommand(`${name}-${attempt + 1}`, command, args, cwd, env);
for (const peer of probe.peers as Array<{ runtimeId: string; transportProfiles?: unknown }>) {
found.add(peer.runtimeId);
profiles.set(peer.runtimeId, peer.transportProfiles);
}
if (expectedPeers.every((peer) => found.has(peer))) {
assert.deepEqual([...found].sort(), expectedPeers);
for (const peerId of expectedPeers) {
assertTcpFramedTransportProfile(peerId, profiles.get(peerId));
}
return;
}
}
assert.deepEqual([...found].sort(), expectedPeers);
}
function assertTcpFramedTransportProfile(peerId: string, value: unknown): void {
assert.ok(Array.isArray(value), `${peerId} did not advertise transport profiles`);
const firstProfile = value[0] as any;
assert.equal(firstProfile?.schemaVersion, "cultnet.transport_profile.v0");
assert.equal(firstProfile?.runtimeId, peerId);
const transport = firstProfile?.transports?.[0];
assert.equal(transport?.protocol, "tcp_framed");
assert.equal(transport?.channels?.[0]?.delivery, "reliable");
assert.equal(transport?.channels?.[0]?.ordering, "ordered");
}
function assertRudpReconnectPolicy(peerName: string, value: unknown): void {
assert.ok(value && typeof value === "object", `${peerName} did not advertise an RUDP reconnect policy`);
const policy = value as {
schemaVersion?: string;
policyId?: string;
baseDelayMs?: number;
maxDelayMs?: number;
maxJitterMs?: number;
};
assert.equal(policy.schemaVersion, "cultnet.reconnect_policy.v0");
assert.equal(policy.policyId, "default");
assert.equal(policy.baseDelayMs, 1_000);
assert.equal(policy.maxDelayMs, 30_000);
assert.equal(policy.maxJitterMs, 250);
}
async function requestCultMeshFromPython(port: number, request: Record<string, unknown>): Promise<any> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error(`Timed out waiting for Python CultMesh response to ${request.schemaVersion}.`));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
clearTimeout(timeout);
socket.end();
resolve(decode(frame));
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(request)));
});
}
async function runPythonCultMeshClient(port: number): Promise<any> {
const script = [
"import json",
"from cultmesh_py import CultMeshDiscoveryClient",
`client = CultMeshDiscoveryClient("127.0.0.1", ${port}, timeout_seconds=4.0)`,
"verses = client.fetch_verses(transport_version='cultmesh.v0')",
"peers = client.fetch_peers(verse_id='python-interop', roles=['read-replica'])",
"print(json.dumps({",
" 'verses': [{'verseId': verse.verse_id, 'transportVersion': verse.compatibility.transport_version} for verse in verses],",
" 'peers': [{'peerId': peer.peer_id, 'roles': list(peer.roles)} for peer in peers],",
"}))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function runPythonCultNetRawClient(port: number): Promise<any> {
const script = [
"import json",
"from cultnet_py import CultNetRawClient",
`client = CultNetRawClient("127.0.0.1", ${port}, timeout_seconds=4.0)`,
"catalog = client.fetch_schema_catalog(kinds=['wire_message'])",
`snapshot = client.fetch_snapshot(schema_ids=['${interopNoteSchemaId}'])`,
`shards = client.fetch_shard_catalog(schema_ids=['${interopNoteSchemaId}'])`,
"print(json.dumps({",
" 'wireSchemaCount': len(catalog.get('schemas', [])),",
" 'snapshotRecordKeys': [document.get('recordKey') for document in snapshot.get('documents', [])],",
" 'shards': [{'shardId': shard.get('shardId'), 'epoch': shard.get('epoch')} for shard in shards.get('shards', [])],",
"}))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function runPythonCultMeshNodeSync(port: number): Promise<any> {
const schemaPathJson = JSON.stringify(interopSchemaPath);
const script = [
"import json",
"from pathlib import Path",
"from cultcache_py import define_database_entry_type",
"from cultmesh_py import create_node",
"from cultnet_py import CultNetRawClient",
`schema_json = Path(${schemaPathJson}).read_text(encoding='utf-8')`,
"note_doc = define_database_entry_type(",
" 'cultnet.interop-note',",
" [('schemaVersion', 0), ('documentId', 1), ('authorRuntimeId', 2), ('title', 3), ('body', 4), ('tags', 5, [])],",
` schema_id='${interopNoteSchemaId}',`,
" schema_version='cultnet.interop_note.v0',",
" canonical_schema_json=schema_json,",
")",
"node = create_node(runtime_id='python-node-sync')",
"node.register_document(note_doc)",
`client = CultNetRawClient('127.0.0.1', ${port}, timeout_seconds=4.0)`,
`changes = node.sync_snapshot(client, schema_ids=['${interopNoteSchemaId}'], record_keys=['note:python-peer'])`,
"note = node.get_required(note_doc, 'note:python-peer')",
"print(json.dumps({",
" 'synced': [{'schemaId': change.schema_id, 'recordKey': change.record_key} for change in changes],",
" 'note': note,",
"}))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function runPythonCultMeshNodeEmit(port: number): Promise<any> {
const schemaPathJson = JSON.stringify(interopSchemaPath);
const script = [
"import json",
"import socket",
"import time",
"from pathlib import Path",
"from cultcache_py import define_database_entry_type",
"from cultmesh_py import create_node",
"from cultnet_py import CultNetRawClient, write_frame",
`schema_json = Path(${schemaPathJson}).read_text(encoding='utf-8')`,
"note_doc = define_database_entry_type(",
" 'cultnet.interop-note',",
" [('schemaVersion', 0), ('documentId', 1), ('authorRuntimeId', 2), ('title', 3), ('body', 4), ('tags', 5, [])],",
` schema_id='${interopNoteSchemaId}',`,
" schema_version='cultnet.interop_note.v0',",
" canonical_schema_json=schema_json,",
")",
"writer = create_node(runtime_id='python-node-emit')",
"writer.register_document(note_doc)",
"message = writer.put_raw_message(",
" note_doc,",
" 'note:python-node-emit',",
" {",
" 'schemaVersion': 'cultnet.interop_note.v0',",
" 'documentId': 'note:python-node-emit',",
" 'authorRuntimeId': 'python-node-emit',",
" 'title': 'Python node emitted raw put',",
" 'body': 'node-level Python emit reached the peer',",
" 'tags': ['python', 'node', 'emit'],",
" },",
" message_id='python-node-emit-put',",
" shard_id='interop',",
" shard_epoch=1,",
")",
`with socket.create_connection(('127.0.0.1', ${port}), timeout=4.0) as connection:`,
" stream = connection.makefile('rwb')",
" write_frame(stream, message.to_bytes())",
" stream.flush()",
"reader = create_node(runtime_id='python-node-emit-reader')",
"reader.register_document(note_doc)",
`client = CultNetRawClient('127.0.0.1', ${port}, timeout_seconds=4.0)`,
"changes = []",
"for _ in range(20):",
` changes = reader.sync_snapshot(client, schema_ids=['${interopNoteSchemaId}'], record_keys=['note:python-node-emit'])`,
" if changes:",
" break",
" time.sleep(0.1)",
"note = reader.get_required(note_doc, 'note:python-node-emit')",
"print(json.dumps({",
" 'synced': [{'schemaId': change.schema_id, 'recordKey': change.record_key} for change in changes],",
" 'note': note,",
"}))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function runPythonCultNetSubscription(port: number): Promise<any> {
const schemaPathJson = JSON.stringify(interopSchemaPath);
const script = [
"import json",
"from pathlib import Path",
"from cultcache_py import define_database_entry_type",
"from cultmesh_py import create_node",
"from cultnet_py import CultNetRawClient",
`schema_json = Path(${schemaPathJson}).read_text(encoding='utf-8')`,
"note_doc = define_database_entry_type(",
" 'cultnet.interop-note',",
" [('schemaVersion', 0), ('documentId', 1), ('authorRuntimeId', 2), ('title', 3), ('body', 4), ('tags', 5, [])],",
` schema_id='${interopNoteSchemaId}',`,
" schema_version='cultnet.interop_note.v0',",
" canonical_schema_json=schema_json,",
")",
"node = create_node(runtime_id='python-sub-client')",
"node.register_document(note_doc)",
"message = node.put_raw_message(",
" note_doc,",
" 'note:python-sub-client',",
" {",
" 'schemaVersion': 'cultnet.interop_note.v0',",
" 'documentId': 'note:python-sub-client',",
" 'authorRuntimeId': 'python-sub-client',",
" 'title': 'Python subscription client emitted raw put',",
" 'body': 'subscription client saw its own live change',",
" 'tags': ['python', 'subscription'],",
" },",
" message_id='python-sub-client-put',",
" shard_id='interop',",
" shard_epoch=1,",
")",
`client = CultNetRawClient('127.0.0.1', ${port}, timeout_seconds=4.0)`,
`with client.subscribe_database(subscription_id='python-public-sub', schema_ids=['${interopNoteSchemaId}'], record_keys=['note:python-sub-client'], include_snapshot=False) as subscription:`,
" subscription.send(message)",
" change = subscription.read_next()",
"print(json.dumps({",
" 'subscriptionId': change.get('subscriptionId'),",
" 'changeKind': change.get('changeKind'),",
" 'recordKey': change.get('document', {}).get('recordKey'),",
"}))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function runPythonCultMeshPredictionReconcile(port: number): Promise<any> {
const schemaPathJson = JSON.stringify(interopSchemaPath);
const script = [
"import json",
"import tempfile",
"from pathlib import Path",
"from cultcache_py import define_database_entry_type",
"from cultmesh_py import CultMesh, CultMeshGameSessionOptions",
"from cultnet_py import CultNetClientAuthorityScope, CultNetRawClient",
`schema_json = Path(${schemaPathJson}).read_text(encoding='utf-8')`,
"note_doc = define_database_entry_type(",
" 'cultnet.interop-note',",
" [('schemaVersion', 0), ('documentId', 1), ('authorRuntimeId', 2), ('title', 3), ('body', 4), ('tags', 5, [])],",
` schema_id='${interopNoteSchemaId}',`,
" schema_version='cultnet.interop_note.v0',",
" canonical_schema_json=schema_json,",
")",
"with tempfile.TemporaryDirectory() as tmp:",
" node = CultMesh.create_node(Path(tmp) / 'prediction.cc', runtime_id='python-predict-client')",
" node.register_document(note_doc)",
" session = CultMesh.create_game_session(",
" node,",
" CultMeshGameSessionOptions(client_authority_scopes=(",
` CultNetClientAuthorityScope('python-predict-client', schema_ids=('${interopNoteSchemaId}',), key_prefix='note:python-node-emit'),`,
" )),",
" )",
" session.predict(note_doc, 'note:python-node-emit', {",
" 'schemaVersion': 'cultnet.interop_note.v0',",
" 'documentId': 'note:python-node-emit',",
" 'authorRuntimeId': 'python-predict-client',",
" 'title': 'Predicted Python node emit',",
" 'body': 'local predicted body',",
" 'tags': ['python', 'prediction'],",
" })",
` client = CultNetRawClient('127.0.0.1', ${port}, timeout_seconds=4.0)`,
" changes = session.sync_shard_log(client, shard_id='interop', shard_epoch=1, after_sequence=0)",
" note = node.get_required(note_doc, 'note:python-node-emit')",
" print(json.dumps({",
" 'reconciledKeys': [change.record_key for change in changes if change.change_kind == 'reconciled'],",
" 'changes': [{'kind': change.change_kind, 'key': change.record_key} for change in changes],",
" 'note': note,",
" }))",
].join("\n");
const { stdout } = await execFileAsync(pythonInteropCommand(), ["-c", script], {
cwd: cultcachePyRoot,
env: {
...process.env,
PYTHONPATH: cultcachePySrc,
},
timeout: 8000,
});
return JSON.parse(stdout);
}
async function requestPythonDatabaseChange(port: number, subscribe: Record<string, unknown>, put: Record<string, unknown>): Promise<any> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python database change response."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.database_change_raw.v0") {
clearTimeout(timeout);
socket.end();
resolve(decoded);
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(subscribe)));
socket.write(encodeFrame(encode(put)));
});
}
async function requestPythonDatabaseDelete(
port: number,
subscribe: Record<string, unknown>,
put: Record<string, unknown>,
remove: Record<string, unknown>,
logRequest: Record<string, unknown>,
): Promise<{ change: any; log: any }> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const result: { change?: any; log?: any } = {};
let sawAdded = false;
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python delete change/log responses."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.database_change_raw.v0" && decoded.changeKind === "added") {
sawAdded = true;
socket.write(encodeFrame(encode(remove)));
socket.write(encodeFrame(encode(logRequest)));
} else if (decoded?.schemaVersion === "cultnet.database_change_raw.v0" && decoded.changeKind === "removed") {
result.change = decoded;
} else if (decoded?.schemaVersion === "cultnet.shard_log_response.v0") {
result.log = decoded;
}
if (sawAdded && result.change && result.log) {
clearTimeout(timeout);
socket.end();
resolve({ change: result.change, log: result.log });
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(subscribe)));
socket.write(encodeFrame(encode(put)));
});
}
async function requestPythonShardCatalogAndLog(
port: number,
catalogRequest: Record<string, unknown>,
put: Record<string, unknown>,
logRequest: Record<string, unknown>,
): Promise<{ catalog: any; log: any }> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const result: { catalog?: any; log?: any } = {};
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python shard catalog/log responses."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.shard_catalog_response.v0") {
result.catalog = decoded;
socket.write(encodeFrame(encode(put)));
socket.write(encodeFrame(encode(logRequest)));
} else if (decoded?.schemaVersion === "cultnet.shard_log_response.v0") {
result.log = decoded;
}
if (result.catalog && result.log) {
clearTimeout(timeout);
socket.end();
resolve({ catalog: result.catalog, log: result.log });
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(catalogRequest)));
});
}
async function requestPythonSimulationCandidate(port: number, observation: Record<string, unknown>): Promise<any> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python simulation consensus candidate."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.simulation_consensus_candidate.v0") {
clearTimeout(timeout);
socket.end();
resolve(decoded);
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(observation)));
});
}
function computeSimulationClaimHash(...parts: string[]): string {
return createHash("sha256").update(parts.join("\x1f"), "utf8").digest("hex");
}
function createSimulationFactKey(candidate: any): string {
const factId = createHash("sha256")
.update([
candidate.shardId,
String(candidate.shardEpoch),
String(candidate.frame),
candidate.subjectId,
candidate.claimKind,
].join("\x1f"), "utf8")
.digest("hex");
return `simulation:${factId}`;
}
async function requestPythonSnapshot(port: number, request: Record<string, unknown>): Promise<any> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python snapshot response."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.snapshot_response_raw.v0") {
clearTimeout(timeout);
socket.end();
resolve(decoded);
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode(request)));
});
}
async function putAndSnapshotPythonWitnessBundle(port: number, put: Record<string, unknown>): Promise<{ catalog: any; snapshot: any }> {
const socket = connectTcp(port, "127.0.0.1");
const framer = new LengthPrefixedMessageFramer();
await once(socket, "connect");
return await new Promise((resolve, reject) => {
const result: { catalog?: any; snapshot?: any } = {};
const timeout = setTimeout(() => {
socket.destroy();
reject(new Error("Timed out waiting for Python witness artifact bundle roundtrip."));
}, 5000);
socket.on("data", (chunk) => {
for (const frame of framer.push(chunk)) {
const decoded = decode(frame) as any;
if (decoded?.schemaVersion === "cultnet.schema_catalog_response.v0") {
result.catalog = decoded;
socket.write(encodeFrame(encode(put)));
socket.write(encodeFrame(encode({
schemaVersion: "cultnet.snapshot_request.v0",
messageId: "python-witness-snapshot",
schemaIds: [witnessArtifactBundleSchemaId],
recordKeys: ["bundle:ts-python-witness"],
})));
} else if (decoded?.schemaVersion === "cultnet.snapshot_response_raw.v0") {
result.snapshot = decoded;
}
if (result.catalog && result.snapshot) {
clearTimeout(timeout);
socket.end();
resolve({ catalog: result.catalog, snapshot: result.snapshot });
}
}
});
socket.once("error", (error) => {
clearTimeout(timeout);
reject(error);
});
socket.write(encodeFrame(encode({
schemaVersion: "cultnet.schema_catalog_request.v0",
messageId: "python-witness-catalog",
includeSchemaJson: true,
schemaIds: [witnessArtifactBundleSchemaId],
})));
});
}
async function stopProcess(processState: RunningServeProcess): Promise<void> {
if (processState.child.exitCode !== null) {
return;
}
if (process.platform === "win32") {
await execFileAsync("taskkill.exe", ["/PID", String(processState.child.pid), "/T", "/F"], {
timeout: processCleanupTimeoutMs,
})
.catch(() => undefined);
await Promise.race([
once(processState.child, "exit").then(() => true),
delay(processCleanupTimeoutMs).then(() => false),
]);
return;
}
const gracefulExit = once(processState.child, "exit").then(() => true);
processState.child.kill("SIGTERM");
const exited = await Promise.race([
gracefulExit,
delay(2_000).then(() => false),
]);
if (exited || processState.child.exitCode !== null) {
return;
}
const forcedExit = once(processState.child, "exit").then(() => true);
processState.child.kill("SIGKILL");
const killed = await Promise.race([
forcedExit,
delay(2_000).then(() => false),
]);
if (!killed && processState.child.exitCode === null) {
throw new Error(`${processState.name} serve process did not exit after SIGKILL.`);
}
}
async function getFreePort(): Promise<number> {
const server = createServer();
server.listen(0, "127.0.0.1");
await once(server, "listening");
const address = server.address();
if (!address || typeof address === "string") {
throw new Error("Failed to allocate an ephemeral port.");
}
const { port } = address;
await new Promise<void>((resolve, reject) => {
server.close((error) => {
if (error) {
reject(error);
return;
}
resolve();
});
});
return port;
}
function findAdvertiseHost(): string {
const interfaces = networkInterfaces();
for (const entries of Object.values(interfaces)) {
for (const entry of entries ?? []) {
if (entry.family === "IPv4" && !entry.internal) {
return entry.address;
}
}
}
return "127.0.0.1";
}