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,33 @@
import { describe, expect, it } from "@suaveplan/testing/runner";
describe("r3f-networking dep-graph", () => {
it("loads index and exposes every public surface", {
timeout: 30000,
}, async () => {
const mod = await import("../src/index.js");
expect(mod.R3F_NET_CODES).toBeDefined();
expect(mod.R3FNetError).toBeDefined();
expect(mod.StateBuffer).toBeDefined();
expect(mod.EntityInterpolator).toBeDefined();
expect(mod.ClientPredictor).toBeDefined();
expect(mod.buildReconciliationMachine).toBeDefined();
expect(mod.diffSnapshots).toBeDefined();
expect(mod.buildDeltaFrame).toBeDefined();
expect(mod.applyDeltaFrame).toBeDefined();
expect(mod.encodeSnapshotFrame).toBeDefined();
expect(mod.decodeSnapshotFrame).toBeDefined();
expect(mod.EntitySyncManager).toBeDefined();
expect(mod.InterestManager).toBeDefined();
expect(mod.ServerAuthority).toBeDefined();
expect(mod.createMemoryHub).toBeDefined();
expect(mod.MemoryTransport).toBeDefined();
expect(mod.useNetworkSync).toBeDefined();
expect(mod.useNetworkedEntity).toBeDefined();
expect(mod.useNetworkPredict).toBeDefined();
expect(mod.NetworkProvider).toBeDefined();
expect(mod.NetworkContext).toBeDefined();
expect(mod.NetworkedEntity).toBeDefined();
expect(mod.applyNetworkedFrame).toBeDefined();
expect(mod.NetworkStats).toBeDefined();
});
});

View File

@@ -0,0 +1,11 @@
// React-specific polyfills. The base webcrypto polyfill loads via
// `@suaveplan/testing/polyfills`. This sets the React 18+
// `IS_REACT_ACT_ENVIRONMENT` flag so DOM assertions outside `act(...)`
// don't generate runtime warnings.
interface ReactActEnvWindow {
IS_REACT_ACT_ENVIRONMENT?: boolean;
}
const win = globalThis as unknown as ReactActEnvWindow;
if (win.IS_REACT_ACT_ENVIRONMENT === undefined) {
win.IS_REACT_ACT_ENVIRONMENT = true;
}

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();
});
});