Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -0,0 +1,142 @@
// 2-client multiplayer integration test.
//
// Uses the deterministic `MemoryTransport` shipped in this package as the real
// `@suaveplan/network` Transport implementation. Real WebRTC E2E (via
// `@suaveplan/network-transport-webrtc`) would require Playwright + a TURN
// server, both out of scope for the unit-test layer; the canonical wiring at
// the application boundary swaps `MemoryTransport` for the WebRTC transport
// without touching the rest of the stack since both implement the same
// structural `Transport` contract.
//
// This test exercises:
// 1. Two peers connect over the memory hub.
// 2. The server peer authors a snapshot frame, encodes it, broadcasts it.
// 3. The client peer decodes the frame, feeds it into a sync manager,
// runs interpolation, and reads the smoothed transform.
// 4. The client builds and sends a delta back; the server applies it.
import { createEnvelope } from "@suaveplan/network";
import { describe, expect, it } from "@suaveplan/testing/runner";
import {
applyDeltaFrame,
buildDeltaFrame,
createMemoryHub,
decodeDeltaFrame,
decodeSnapshotFrame,
type EntitySnapshot,
EntitySyncManager,
encodeDeltaFrame,
encodeSnapshotFrame,
MemoryTransport,
type SnapshotFrame,
} from "../src/index.js";
const SNAPSHOT_TYPE = "r3f_net.snapshot";
const DELTA_TYPE = "r3f_net.delta";
describe("2-client integration over MemoryTransport", () => {
it("round-trips a snapshot from server to client and a delta back", {
timeout: 5000,
}, async () => {
const hub = createMemoryHub();
const server = new MemoryTransport("server", hub);
const client = new MemoryTransport("c1", hub);
const clientSync = new EntitySyncManager({
sendRate: 60,
interpolationDelay: 0,
});
clientSync.registerEntity("e1", "server", false);
let receivedSnapshot: SnapshotFrame | null = null;
client.onMessage((_from, env) => {
if (env.type === SNAPSHOT_TYPE) {
receivedSnapshot = decodeSnapshotFrame(env.payload as string);
}
});
const receivedDeltas: number[] = [];
server.onMessage((_from, env) => {
if (env.type === DELTA_TYPE) {
const d = decodeDeltaFrame(env.payload as string);
receivedDeltas.push(d.seq);
}
});
await server.connect("c1", "memory");
await client.connect("server", "memory");
// Step 1: server → client snapshot.
const baseState: EntitySnapshot = {
e1: {
position: [1, 0, 0],
rotation: [0, 0, 0, 1],
scale: [1, 1, 1],
},
};
const baseFrame: SnapshotFrame = {
seq: 1,
timestamp: 1000,
state: baseState,
};
await server.send(
"c1",
createEnvelope({
type: SNAPSHOT_TYPE,
senderId: "server",
targetId: "c1",
payload: encodeSnapshotFrame(baseFrame),
})
);
expect(receivedSnapshot).not.toBeNull();
const restored = receivedSnapshot as unknown as SnapshotFrame;
expect(restored.seq).toBe(1);
expect(restored.state.e1?.position[0]).toBe(1);
// Feed the snapshot through the client sync layer.
clientSync.receiveUpdate({
entityId: "e1",
transform: restored.state.e1!,
timestamp: restored.timestamp,
seq: restored.seq,
});
clientSync.receiveUpdate({
entityId: "e1",
transform: { ...restored.state.e1!, position: [2, 0, 0] },
timestamp: restored.timestamp + 100,
seq: 2,
});
clientSync.updateRemoteEntities(restored.timestamp + 100);
const e = clientSync.getEntity("e1");
expect(e?.transform.position[0]).toBeCloseTo(2);
// Step 2: client → server delta.
const nextFrame: SnapshotFrame = {
seq: 2,
timestamp: 1100,
state: {
e1: { ...baseState.e1!, position: [3, 0, 0] },
},
};
const delta = buildDeltaFrame(baseFrame, nextFrame);
await client.send(
"server",
createEnvelope({
type: DELTA_TYPE,
senderId: "c1",
targetId: "server",
payload: encodeDeltaFrame(delta),
})
);
expect(receivedDeltas).toEqual([2]);
// Server applies the delta to confirm the round-trip is valid.
const applied = applyDeltaFrame(baseFrame, delta);
expect(applied.state.e1?.position[0]).toBe(3);
await server.close();
await client.close();
});
});