@series-inc/rundot-syncplay 5.23.0-beta.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +610 -0
- package/dist/animation.d.ts +119 -0
- package/dist/animation.js +488 -0
- package/dist/asset-cooking.d.ts +82 -0
- package/dist/asset-cooking.js +364 -0
- package/dist/authority-room.d.ts +217 -0
- package/dist/authority-room.js +701 -0
- package/dist/bot-documents.d.ts +115 -0
- package/dist/bot-documents.js +549 -0
- package/dist/bot-primitives.d.ts +152 -0
- package/dist/bot-primitives.js +472 -0
- package/dist/browser.d.ts +57 -0
- package/dist/browser.js +53 -0
- package/dist/canonical.d.ts +12 -0
- package/dist/canonical.js +288 -0
- package/dist/certification.d.ts +455 -0
- package/dist/certification.js +1855 -0
- package/dist/chaos-certification.d.ts +56 -0
- package/dist/chaos-certification.js +1397 -0
- package/dist/cjs/animation.js +495 -0
- package/dist/cjs/asset-cooking.js +372 -0
- package/dist/cjs/authority-room.js +704 -0
- package/dist/cjs/bot-documents.js +556 -0
- package/dist/cjs/bot-primitives.js +482 -0
- package/dist/cjs/browser.js +96 -0
- package/dist/cjs/canonical.js +299 -0
- package/dist/cjs/certification.js +1860 -0
- package/dist/cjs/chaos-certification.js +1402 -0
- package/dist/cjs/collections.js +36 -0
- package/dist/cjs/collision.js +96 -0
- package/dist/cjs/commands.js +987 -0
- package/dist/cjs/config-bundle.js +491 -0
- package/dist/cjs/creator.js +30 -0
- package/dist/cjs/deterministic-primitives.js +134 -0
- package/dist/cjs/deterministic-session.js +1410 -0
- package/dist/cjs/ecs-lite.js +155 -0
- package/dist/cjs/effects-adapter.js +97 -0
- package/dist/cjs/engine-complete.js +2297 -0
- package/dist/cjs/engine-core-proof.js +70 -0
- package/dist/cjs/engine-parity-matrix.js +874 -0
- package/dist/cjs/engine-parity.js +3183 -0
- package/dist/cjs/engine-runtime.js +3141 -0
- package/dist/cjs/errors.js +22 -0
- package/dist/cjs/events.js +247 -0
- package/dist/cjs/examples/counter-duel.js +157 -0
- package/dist/cjs/examples/fixed-arena.js +368 -0
- package/dist/cjs/index.js +229 -0
- package/dist/cjs/input-authority.js +150 -0
- package/dist/cjs/input-button.js +256 -0
- package/dist/cjs/input-timeline.js +418 -0
- package/dist/cjs/instant-replay.js +66 -0
- package/dist/cjs/lag-compensation3d.js +837 -0
- package/dist/cjs/local-authority-server-runner.js +1121 -0
- package/dist/cjs/local-authority-wire.js +1497 -0
- package/dist/cjs/local-authority.js +817 -0
- package/dist/cjs/local-ws-adapter.js +150 -0
- package/dist/cjs/match-log.js +61 -0
- package/dist/cjs/math.js +157 -0
- package/dist/cjs/mock-network-adapter.js +26 -0
- package/dist/cjs/movement.js +703 -0
- package/dist/cjs/movement3d.js +1246 -0
- package/dist/cjs/multiclient-authority.js +308 -0
- package/dist/cjs/multiplayer-service.js +434 -0
- package/dist/cjs/navigation.js +347 -0
- package/dist/cjs/navmesh.js +1437 -0
- package/dist/cjs/networked-client.js +691 -0
- package/dist/cjs/networked-input-authority.js +443 -0
- package/dist/cjs/node.js +47 -0
- package/dist/cjs/number-lab.js +78 -0
- package/dist/cjs/offline-session.js +370 -0
- package/dist/cjs/package.json +1 -0
- package/dist/cjs/pause-resume.js +43 -0
- package/dist/cjs/physics-cert.js +426 -0
- package/dist/cjs/physics2d.js +751 -0
- package/dist/cjs/physics3d.js +1996 -0
- package/dist/cjs/prediction-culling.js +203 -0
- package/dist/cjs/protocol-lab.js +1299 -0
- package/dist/cjs/r3f-render-adapter.js +209 -0
- package/dist/cjs/random.js +59 -0
- package/dist/cjs/replay-bundle.js +1598 -0
- package/dist/cjs/replay-inspect.js +236 -0
- package/dist/cjs/replay-scoring.js +68 -0
- package/dist/cjs/replay.js +356 -0
- package/dist/cjs/reports.js +181 -0
- package/dist/cjs/rollback-history.js +251 -0
- package/dist/cjs/runtime-guards.js +111 -0
- package/dist/cjs/sample-lab.js +145 -0
- package/dist/cjs/sample-scenes.js +752 -0
- package/dist/cjs/schema-artifacts.js +359 -0
- package/dist/cjs/schema-codegen.js +544 -0
- package/dist/cjs/schema-dsl-binary.js +655 -0
- package/dist/cjs/schema.js +319 -0
- package/dist/cjs/sdk-offline.js +99 -0
- package/dist/cjs/session-promotion.js +74 -0
- package/dist/cjs/session-snapshot.js +265 -0
- package/dist/cjs/session-wire.js +216 -0
- package/dist/cjs/signals.js +103 -0
- package/dist/cjs/sparse-set.js +169 -0
- package/dist/cjs/synctest.js +136 -0
- package/dist/cjs/system-lifecycle.js +286 -0
- package/dist/cjs/timers.js +61 -0
- package/dist/cjs/tools/static-checker.js +1468 -0
- package/dist/cjs/types.js +2 -0
- package/dist/cjs/venus-mp-budget.js +125 -0
- package/dist/cjs/ws-frame.js +255 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +11379 -0
- package/dist/collections.d.ts +9 -0
- package/dist/collections.js +30 -0
- package/dist/collision.d.ts +25 -0
- package/dist/collision.js +85 -0
- package/dist/commands.d.ts +137 -0
- package/dist/commands.js +977 -0
- package/dist/config-bundle.d.ts +89 -0
- package/dist/config-bundle.js +483 -0
- package/dist/creator.d.ts +20 -0
- package/dist/creator.js +11 -0
- package/dist/deterministic-primitives.d.ts +59 -0
- package/dist/deterministic-primitives.js +126 -0
- package/dist/deterministic-session.d.ts +197 -0
- package/dist/deterministic-session.js +1401 -0
- package/dist/ecs-lite.d.ts +26 -0
- package/dist/ecs-lite.js +152 -0
- package/dist/effects-adapter.d.ts +21 -0
- package/dist/effects-adapter.js +94 -0
- package/dist/engine-complete.d.ts +45 -0
- package/dist/engine-complete.js +2292 -0
- package/dist/engine-core-proof.d.ts +26 -0
- package/dist/engine-core-proof.js +66 -0
- package/dist/engine-parity-matrix.d.ts +300 -0
- package/dist/engine-parity-matrix.js +869 -0
- package/dist/engine-parity.d.ts +74 -0
- package/dist/engine-parity.js +3178 -0
- package/dist/engine-runtime.d.ts +351 -0
- package/dist/engine-runtime.js +3123 -0
- package/dist/errors.d.ts +11 -0
- package/dist/errors.js +17 -0
- package/dist/events.d.ts +45 -0
- package/dist/events.js +242 -0
- package/dist/examples/counter-duel.d.ts +43 -0
- package/dist/examples/counter-duel.js +150 -0
- package/dist/examples/fixed-arena.d.ts +110 -0
- package/dist/examples/fixed-arena.js +356 -0
- package/dist/index.d.ts +101 -0
- package/dist/index.js +66 -0
- package/dist/input-authority.d.ts +82 -0
- package/dist/input-authority.js +147 -0
- package/dist/input-button.d.ts +36 -0
- package/dist/input-button.js +249 -0
- package/dist/input-timeline.d.ts +65 -0
- package/dist/input-timeline.js +412 -0
- package/dist/instant-replay.d.ts +59 -0
- package/dist/instant-replay.js +62 -0
- package/dist/lag-compensation3d.d.ts +146 -0
- package/dist/lag-compensation3d.js +827 -0
- package/dist/local-authority-server-runner.d.ts +79 -0
- package/dist/local-authority-server-runner.js +1118 -0
- package/dist/local-authority-wire.d.ts +142 -0
- package/dist/local-authority-wire.js +1477 -0
- package/dist/local-authority.d.ts +160 -0
- package/dist/local-authority.js +810 -0
- package/dist/local-ws-adapter.d.ts +14 -0
- package/dist/local-ws-adapter.js +146 -0
- package/dist/match-log.d.ts +74 -0
- package/dist/match-log.js +57 -0
- package/dist/math.d.ts +41 -0
- package/dist/math.js +154 -0
- package/dist/mock-network-adapter.d.ts +13 -0
- package/dist/mock-network-adapter.js +23 -0
- package/dist/movement.d.ts +155 -0
- package/dist/movement.js +694 -0
- package/dist/movement3d.d.ts +195 -0
- package/dist/movement3d.js +1238 -0
- package/dist/multiclient-authority.d.ts +30 -0
- package/dist/multiclient-authority.js +305 -0
- package/dist/multiplayer-service.d.ts +153 -0
- package/dist/multiplayer-service.js +429 -0
- package/dist/navigation.d.ts +91 -0
- package/dist/navigation.js +338 -0
- package/dist/navmesh.d.ts +236 -0
- package/dist/navmesh.js +1423 -0
- package/dist/networked-client.d.ts +124 -0
- package/dist/networked-client.js +688 -0
- package/dist/networked-input-authority.d.ts +51 -0
- package/dist/networked-input-authority.js +439 -0
- package/dist/node.d.ts +22 -0
- package/dist/node.js +19 -0
- package/dist/number-lab.d.ts +15 -0
- package/dist/number-lab.js +75 -0
- package/dist/offline-session.d.ts +2 -0
- package/dist/offline-session.js +367 -0
- package/dist/pause-resume.d.ts +48 -0
- package/dist/pause-resume.js +40 -0
- package/dist/physics-cert.d.ts +40 -0
- package/dist/physics-cert.js +389 -0
- package/dist/physics2d.d.ts +142 -0
- package/dist/physics2d.js +741 -0
- package/dist/physics3d.d.ts +285 -0
- package/dist/physics3d.js +1979 -0
- package/dist/prediction-culling.d.ts +34 -0
- package/dist/prediction-culling.js +200 -0
- package/dist/protocol-lab.d.ts +115 -0
- package/dist/protocol-lab.js +1295 -0
- package/dist/r3f-render-adapter.d.ts +61 -0
- package/dist/r3f-render-adapter.js +203 -0
- package/dist/random.d.ts +13 -0
- package/dist/random.js +55 -0
- package/dist/replay-bundle.d.ts +150 -0
- package/dist/replay-bundle.js +1586 -0
- package/dist/replay-inspect.d.ts +60 -0
- package/dist/replay-inspect.js +228 -0
- package/dist/replay-scoring.d.ts +28 -0
- package/dist/replay-scoring.js +65 -0
- package/dist/replay.d.ts +17 -0
- package/dist/replay.js +351 -0
- package/dist/reports.d.ts +176 -0
- package/dist/reports.js +174 -0
- package/dist/rollback-history.d.ts +46 -0
- package/dist/rollback-history.js +247 -0
- package/dist/runtime-guards.d.ts +1 -0
- package/dist/runtime-guards.js +108 -0
- package/dist/sample-lab.d.ts +32 -0
- package/dist/sample-lab.js +134 -0
- package/dist/sample-scenes.d.ts +86 -0
- package/dist/sample-scenes.js +745 -0
- package/dist/schema-artifacts.d.ts +49 -0
- package/dist/schema-artifacts.js +356 -0
- package/dist/schema-codegen.d.ts +21 -0
- package/dist/schema-codegen.js +538 -0
- package/dist/schema-dsl-binary.d.ts +75 -0
- package/dist/schema-dsl-binary.js +648 -0
- package/dist/schema.d.ts +87 -0
- package/dist/schema.js +315 -0
- package/dist/sdk-offline.d.ts +34 -0
- package/dist/sdk-offline.js +96 -0
- package/dist/session-promotion.d.ts +26 -0
- package/dist/session-promotion.js +70 -0
- package/dist/session-snapshot.d.ts +95 -0
- package/dist/session-snapshot.js +255 -0
- package/dist/session-wire.d.ts +89 -0
- package/dist/session-wire.js +211 -0
- package/dist/signals.d.ts +31 -0
- package/dist/signals.js +99 -0
- package/dist/sparse-set.d.ts +34 -0
- package/dist/sparse-set.js +165 -0
- package/dist/synctest.d.ts +31 -0
- package/dist/synctest.js +133 -0
- package/dist/system-lifecycle.d.ts +72 -0
- package/dist/system-lifecycle.js +281 -0
- package/dist/timers.d.ts +17 -0
- package/dist/timers.js +53 -0
- package/dist/tools/index.d.ts +4 -0
- package/dist/tools/index.js +4 -0
- package/dist/tools/static-checker.d.ts +31 -0
- package/dist/tools/static-checker.js +1462 -0
- package/dist/tools/vite-plugin.d.ts +69 -0
- package/dist/tools/vite-plugin.js +203 -0
- package/dist/types.d.ts +148 -0
- package/dist/types.js +1 -0
- package/dist/venus-mp-budget.d.ts +29 -0
- package/dist/venus-mp-budget.js +122 -0
- package/dist/ws-frame.d.ts +49 -0
- package/dist/ws-frame.js +247 -0
- package/package.json +169 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
export interface DeterministicMulticlientAuthorityFixtureResult {
|
|
2
|
+
readonly checksum: string;
|
|
3
|
+
readonly players: number;
|
|
4
|
+
readonly frames: number;
|
|
5
|
+
readonly predictedClientRunners: number;
|
|
6
|
+
readonly authoritySnapshotsDelivered: number;
|
|
7
|
+
readonly predictionMismatches: number;
|
|
8
|
+
readonly correctionsApplied: number;
|
|
9
|
+
readonly resimulatedFrames: number;
|
|
10
|
+
readonly convergedSnapshots: number;
|
|
11
|
+
readonly rollbackDepthP95: number;
|
|
12
|
+
readonly rollbackDepthMax: number;
|
|
13
|
+
readonly finalAuthorityProjection: number;
|
|
14
|
+
readonly finalClientProjectionChecksum: string;
|
|
15
|
+
readonly convergence: boolean;
|
|
16
|
+
readonly allClientsReachedFinalAuthorityFrame: boolean;
|
|
17
|
+
readonly runtimeBackedClients: number;
|
|
18
|
+
readonly runtimeBackedFrames: number;
|
|
19
|
+
readonly runtimeBackedAuthoritySnapshots: number;
|
|
20
|
+
readonly runtimeBackedPredictionMismatches: number;
|
|
21
|
+
readonly runtimeBackedCorrectionsApplied: number;
|
|
22
|
+
readonly runtimeBackedResimulatedFrames: number;
|
|
23
|
+
readonly runtimeBackedReplayVerifiedClients: number;
|
|
24
|
+
readonly runtimeBackedAuthorityChecksum: string;
|
|
25
|
+
readonly runtimeBackedClientChecksum: string;
|
|
26
|
+
readonly runtimeBackedConvergence: boolean;
|
|
27
|
+
readonly p95FrameCost: number;
|
|
28
|
+
readonly p99FrameCost: number;
|
|
29
|
+
}
|
|
30
|
+
export declare function runDeterministicMulticlientAuthorityFixture(players: number, frames: number, seed: number): DeterministicMulticlientAuthorityFixtureResult;
|
|
@@ -0,0 +1,305 @@
|
|
|
1
|
+
import { defaultChecksum } from './canonical.js';
|
|
2
|
+
import { createDeterministicFrameRuntime, } from './engine-runtime.js';
|
|
3
|
+
const projectionModulo = 2_147_483_647;
|
|
4
|
+
export function runDeterministicMulticlientAuthorityFixture(players, frames, seed) {
|
|
5
|
+
const authorityHistory = new Array(frames + 1).fill(0);
|
|
6
|
+
const authorityDeltas = new Array(frames).fill(0);
|
|
7
|
+
const clientStates = new Array(players).fill(0);
|
|
8
|
+
const clientHistories = Array.from({ length: players }, () => new Array(frames + 1).fill(0));
|
|
9
|
+
const clientPredictedDeltas = Array.from({ length: players }, () => new Array(frames).fill(0));
|
|
10
|
+
const lastAuthorityFrameByClient = new Array(players).fill(0);
|
|
11
|
+
const rollbackDepths = [];
|
|
12
|
+
const frameCosts = [];
|
|
13
|
+
let authorityState = 0;
|
|
14
|
+
let authoritySnapshotsDelivered = 0;
|
|
15
|
+
let predictionMismatches = 0;
|
|
16
|
+
let correctionsApplied = 0;
|
|
17
|
+
let resimulatedFrames = 0;
|
|
18
|
+
let convergedSnapshots = 0;
|
|
19
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
20
|
+
const authorityDelta = authorityFrameDelta(players, frame, seed);
|
|
21
|
+
authorityDeltas[frame] = authorityDelta;
|
|
22
|
+
authorityState = addProjection(authorityState, authorityDelta);
|
|
23
|
+
authorityHistory[frame + 1] = authorityState;
|
|
24
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
25
|
+
const predictedDelta = addProjection(authorityDelta, predictionError(slot, frame, seed));
|
|
26
|
+
clientPredictedDeltas[slot][frame] = predictedDelta;
|
|
27
|
+
clientStates[slot] = addProjection(clientStates[slot], predictedDelta);
|
|
28
|
+
clientHistories[slot][frame + 1] = clientStates[slot];
|
|
29
|
+
}
|
|
30
|
+
const currentFrameExclusive = frame + 1;
|
|
31
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
32
|
+
const confirmedFrame = currentFrameExclusive - snapshotDelay(slot, frame);
|
|
33
|
+
if (confirmedFrame > lastAuthorityFrameByClient[slot]) {
|
|
34
|
+
for (let frameToApply = lastAuthorityFrameByClient[slot] + 1; frameToApply <= confirmedFrame; frameToApply += 1) {
|
|
35
|
+
const applied = applyClientAuthoritySnapshot({
|
|
36
|
+
slot,
|
|
37
|
+
confirmedFrame: frameToApply,
|
|
38
|
+
currentFrameExclusive,
|
|
39
|
+
authorityHistory,
|
|
40
|
+
clientStates,
|
|
41
|
+
clientHistories,
|
|
42
|
+
clientPredictedDeltas,
|
|
43
|
+
});
|
|
44
|
+
authoritySnapshotsDelivered += 1;
|
|
45
|
+
lastAuthorityFrameByClient[slot] = frameToApply;
|
|
46
|
+
if (applied.rollbackDepth === 0) {
|
|
47
|
+
convergedSnapshots += 1;
|
|
48
|
+
}
|
|
49
|
+
else {
|
|
50
|
+
predictionMismatches += 1;
|
|
51
|
+
correctionsApplied += 1;
|
|
52
|
+
resimulatedFrames += applied.resimulatedFrames;
|
|
53
|
+
rollbackDepths.push(applied.rollbackDepth);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
frameCosts.push((players + authoritySnapshotsDelivered + correctionsApplied) / 100_000);
|
|
59
|
+
}
|
|
60
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
61
|
+
for (let confirmedFrame = lastAuthorityFrameByClient[slot] + 1; confirmedFrame <= frames; confirmedFrame += 1) {
|
|
62
|
+
const applied = applyClientAuthoritySnapshot({
|
|
63
|
+
slot,
|
|
64
|
+
confirmedFrame,
|
|
65
|
+
currentFrameExclusive: frames,
|
|
66
|
+
authorityHistory,
|
|
67
|
+
clientStates,
|
|
68
|
+
clientHistories,
|
|
69
|
+
clientPredictedDeltas,
|
|
70
|
+
});
|
|
71
|
+
authoritySnapshotsDelivered += 1;
|
|
72
|
+
lastAuthorityFrameByClient[slot] = confirmedFrame;
|
|
73
|
+
if (applied.rollbackDepth === 0) {
|
|
74
|
+
convergedSnapshots += 1;
|
|
75
|
+
}
|
|
76
|
+
else {
|
|
77
|
+
predictionMismatches += 1;
|
|
78
|
+
correctionsApplied += 1;
|
|
79
|
+
resimulatedFrames += applied.resimulatedFrames;
|
|
80
|
+
rollbackDepths.push(applied.rollbackDepth);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
const finalAuthorityProjection = authorityHistory[frames];
|
|
85
|
+
const finalClientProjectionChecksum = defaultChecksum(clientStates);
|
|
86
|
+
const convergence = clientStates.every((state) => state === finalAuthorityProjection);
|
|
87
|
+
const allClientsReachedFinalAuthorityFrame = lastAuthorityFrameByClient.every((frame) => frame === frames);
|
|
88
|
+
const runtimeBacked = runRuntimeBackedMulticlientAuthorityFixture(players, frames, seed);
|
|
89
|
+
const metrics = {
|
|
90
|
+
players,
|
|
91
|
+
frames,
|
|
92
|
+
predictedClientRunners: players,
|
|
93
|
+
authoritySnapshotsDelivered,
|
|
94
|
+
predictionMismatches,
|
|
95
|
+
correctionsApplied,
|
|
96
|
+
resimulatedFrames,
|
|
97
|
+
convergedSnapshots,
|
|
98
|
+
rollbackDepthP95: percentile(rollbackDepths, 95),
|
|
99
|
+
rollbackDepthMax: rollbackDepths.length === 0 ? 0 : Math.max(...rollbackDepths),
|
|
100
|
+
finalAuthorityProjection,
|
|
101
|
+
finalClientProjectionChecksum,
|
|
102
|
+
convergence,
|
|
103
|
+
allClientsReachedFinalAuthorityFrame,
|
|
104
|
+
runtimeBackedClients: runtimeBacked.clients,
|
|
105
|
+
runtimeBackedFrames: runtimeBacked.frames,
|
|
106
|
+
runtimeBackedAuthoritySnapshots: runtimeBacked.authoritySnapshots,
|
|
107
|
+
runtimeBackedPredictionMismatches: runtimeBacked.predictionMismatches,
|
|
108
|
+
runtimeBackedCorrectionsApplied: runtimeBacked.correctionsApplied,
|
|
109
|
+
runtimeBackedResimulatedFrames: runtimeBacked.resimulatedFrames,
|
|
110
|
+
runtimeBackedReplayVerifiedClients: runtimeBacked.replayVerifiedClients,
|
|
111
|
+
runtimeBackedAuthorityChecksum: runtimeBacked.authorityChecksum,
|
|
112
|
+
runtimeBackedClientChecksum: runtimeBacked.clientChecksum,
|
|
113
|
+
runtimeBackedConvergence: runtimeBacked.convergence,
|
|
114
|
+
p95FrameCost: percentile(frameCosts, 95),
|
|
115
|
+
p99FrameCost: percentile(frameCosts, 99),
|
|
116
|
+
};
|
|
117
|
+
return {
|
|
118
|
+
...metrics,
|
|
119
|
+
checksum: defaultChecksum(metrics),
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
function runRuntimeBackedMulticlientAuthorityFixture(players, frames, seed) {
|
|
123
|
+
const descriptor = createRuntimeBackedAuthorityDescriptor();
|
|
124
|
+
const systems = createRuntimeBackedAuthoritySystems();
|
|
125
|
+
const authority = createDeterministicFrameRuntime(descriptor, seed);
|
|
126
|
+
const clients = Array.from({ length: players }, () => createDeterministicFrameRuntime(descriptor, seed));
|
|
127
|
+
initializeRuntimeBackedProjection(authority);
|
|
128
|
+
for (const client of clients) {
|
|
129
|
+
initializeRuntimeBackedProjection(client);
|
|
130
|
+
}
|
|
131
|
+
const lastAuthorityFrameByClient = new Array(players).fill(0);
|
|
132
|
+
const clientSnapshotHistory = clients.map((client) => [client.snapshot()]);
|
|
133
|
+
const clientPredictedDeltas = clients.map(() => new Array(frames).fill(0));
|
|
134
|
+
const authoritySnapshots = [authority.snapshot()];
|
|
135
|
+
let authoritySnapshotsDelivered = 0;
|
|
136
|
+
let predictionMismatches = 0;
|
|
137
|
+
let correctionsApplied = 0;
|
|
138
|
+
let resimulatedFrames = 0;
|
|
139
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
140
|
+
const authorityDelta = authorityFrameDelta(players, frame, seed);
|
|
141
|
+
stepRuntimeBackedAuthority(authority, systems, authorityDelta);
|
|
142
|
+
authoritySnapshots[frame + 1] = authority.snapshot();
|
|
143
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
144
|
+
const predictedDelta = addProjection(authorityDelta, predictionError(slot, frame, seed));
|
|
145
|
+
clientPredictedDeltas[slot][frame] = predictedDelta;
|
|
146
|
+
stepRuntimeBackedAuthority(clients[slot], systems, predictedDelta);
|
|
147
|
+
clientSnapshotHistory[slot][frame + 1] = clients[slot].snapshot();
|
|
148
|
+
}
|
|
149
|
+
const currentFrameExclusive = frame + 1;
|
|
150
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
151
|
+
const confirmedFrame = currentFrameExclusive - snapshotDelay(slot, frame);
|
|
152
|
+
if (confirmedFrame > 0 && confirmedFrame % 16 === 0) {
|
|
153
|
+
const authoritySnapshot = authoritySnapshots[confirmedFrame];
|
|
154
|
+
authoritySnapshotsDelivered += 1;
|
|
155
|
+
lastAuthorityFrameByClient[slot] = confirmedFrame;
|
|
156
|
+
const applied = applyRuntimeBackedAuthoritySnapshot({
|
|
157
|
+
client: clients[slot],
|
|
158
|
+
systems,
|
|
159
|
+
authoritySnapshot,
|
|
160
|
+
confirmedFrame,
|
|
161
|
+
currentFrameExclusive,
|
|
162
|
+
clientSnapshotHistory: clientSnapshotHistory[slot],
|
|
163
|
+
clientPredictedDeltas: clientPredictedDeltas[slot],
|
|
164
|
+
});
|
|
165
|
+
if (applied.mismatch) {
|
|
166
|
+
predictionMismatches += 1;
|
|
167
|
+
correctionsApplied += 1;
|
|
168
|
+
resimulatedFrames += applied.resimulatedFrames;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
}
|
|
173
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
174
|
+
for (let confirmedFrame = lastAuthorityFrameByClient[slot] + 1; confirmedFrame <= frames; confirmedFrame += 1) {
|
|
175
|
+
const authoritySnapshot = authoritySnapshots[confirmedFrame];
|
|
176
|
+
authoritySnapshotsDelivered += 1;
|
|
177
|
+
lastAuthorityFrameByClient[slot] = confirmedFrame;
|
|
178
|
+
const applied = applyRuntimeBackedAuthoritySnapshot({
|
|
179
|
+
client: clients[slot],
|
|
180
|
+
systems,
|
|
181
|
+
authoritySnapshot,
|
|
182
|
+
confirmedFrame,
|
|
183
|
+
currentFrameExclusive: frames,
|
|
184
|
+
clientSnapshotHistory: clientSnapshotHistory[slot],
|
|
185
|
+
clientPredictedDeltas: clientPredictedDeltas[slot],
|
|
186
|
+
});
|
|
187
|
+
if (applied.mismatch) {
|
|
188
|
+
predictionMismatches += 1;
|
|
189
|
+
correctionsApplied += 1;
|
|
190
|
+
resimulatedFrames += applied.resimulatedFrames;
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
const finalAuthoritySnapshot = authority.snapshot();
|
|
195
|
+
const clientChecksums = clients.map((client) => client.snapshot().checksum);
|
|
196
|
+
const replayVerifiedClients = clientChecksums.filter((checksum) => checksum === finalAuthoritySnapshot.checksum).length;
|
|
197
|
+
return {
|
|
198
|
+
clients: players,
|
|
199
|
+
frames,
|
|
200
|
+
authoritySnapshots: authoritySnapshotsDelivered,
|
|
201
|
+
predictionMismatches,
|
|
202
|
+
correctionsApplied,
|
|
203
|
+
resimulatedFrames,
|
|
204
|
+
replayVerifiedClients,
|
|
205
|
+
authorityChecksum: finalAuthoritySnapshot.checksum,
|
|
206
|
+
clientChecksum: defaultChecksum(clientChecksums),
|
|
207
|
+
convergence: replayVerifiedClients === players,
|
|
208
|
+
};
|
|
209
|
+
}
|
|
210
|
+
function applyRuntimeBackedAuthoritySnapshot(options) {
|
|
211
|
+
const clientSnapshot = options.clientSnapshotHistory[options.confirmedFrame];
|
|
212
|
+
if (clientSnapshot?.checksum === options.authoritySnapshot.checksum) {
|
|
213
|
+
return { mismatch: false, resimulatedFrames: 0 };
|
|
214
|
+
}
|
|
215
|
+
options.client.restore(options.authoritySnapshot);
|
|
216
|
+
options.clientSnapshotHistory[options.confirmedFrame] = options.client.snapshot();
|
|
217
|
+
let resimulatedFrames = 0;
|
|
218
|
+
for (let resimFrame = options.confirmedFrame; resimFrame < options.currentFrameExclusive; resimFrame += 1) {
|
|
219
|
+
stepRuntimeBackedAuthority(options.client, options.systems, options.clientPredictedDeltas[resimFrame]);
|
|
220
|
+
options.clientSnapshotHistory[resimFrame + 1] = options.client.snapshot();
|
|
221
|
+
resimulatedFrames += 1;
|
|
222
|
+
}
|
|
223
|
+
return { mismatch: true, resimulatedFrames };
|
|
224
|
+
}
|
|
225
|
+
function createRuntimeBackedAuthorityDescriptor() {
|
|
226
|
+
return {
|
|
227
|
+
id: 'runtime-backed-multiclient-authority',
|
|
228
|
+
tickRate: 60,
|
|
229
|
+
components: [
|
|
230
|
+
{ id: 'projection', singleton: true, fields: [{ name: 'value', kind: 'int', defaultValue: 0 }] },
|
|
231
|
+
{ id: 'frameInput', singleton: true, fields: [{ name: 'delta', kind: 'int', defaultValue: 0 }] },
|
|
232
|
+
],
|
|
233
|
+
systems: [{ id: 'simulate', group: 'simulation', order: 0 }],
|
|
234
|
+
};
|
|
235
|
+
}
|
|
236
|
+
function createRuntimeBackedAuthoritySystems() {
|
|
237
|
+
return {
|
|
238
|
+
simulate(ctx) {
|
|
239
|
+
const projection = ctx.getSingleton('projection');
|
|
240
|
+
const input = ctx.getSingleton('frameInput');
|
|
241
|
+
ctx.setSingleton('projection', { value: addProjection(Number(projection.value), Number(input.delta)) });
|
|
242
|
+
ctx.setSingleton('frameInput', { delta: 0 });
|
|
243
|
+
},
|
|
244
|
+
};
|
|
245
|
+
}
|
|
246
|
+
function stepRuntimeBackedAuthority(runtime, systems, delta) {
|
|
247
|
+
runtime.setSingleton('frameInput', { delta });
|
|
248
|
+
runtime.step(systems);
|
|
249
|
+
}
|
|
250
|
+
function initializeRuntimeBackedProjection(runtime) {
|
|
251
|
+
runtime.setSingleton('projection', { value: 0 });
|
|
252
|
+
runtime.setSingleton('frameInput', { delta: 0 });
|
|
253
|
+
}
|
|
254
|
+
function applyClientAuthoritySnapshot(options) {
|
|
255
|
+
const expected = options.authorityHistory[options.confirmedFrame];
|
|
256
|
+
const history = options.clientHistories[options.slot];
|
|
257
|
+
if (history[options.confirmedFrame] === expected) {
|
|
258
|
+
return { rollbackDepth: 0, resimulatedFrames: 0 };
|
|
259
|
+
}
|
|
260
|
+
let state = expected;
|
|
261
|
+
history[options.confirmedFrame] = state;
|
|
262
|
+
let resimulatedFrames = 0;
|
|
263
|
+
for (let frame = options.confirmedFrame; frame < options.currentFrameExclusive; frame += 1) {
|
|
264
|
+
state = addProjection(state, options.clientPredictedDeltas[options.slot][frame]);
|
|
265
|
+
history[frame + 1] = state;
|
|
266
|
+
resimulatedFrames += 1;
|
|
267
|
+
}
|
|
268
|
+
options.clientStates[options.slot] = state;
|
|
269
|
+
return {
|
|
270
|
+
rollbackDepth: options.currentFrameExclusive - options.confirmedFrame,
|
|
271
|
+
resimulatedFrames,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function authorityFrameDelta(players, frame, seed) {
|
|
275
|
+
let delta = 0;
|
|
276
|
+
for (let slot = 0; slot < players; slot += 1) {
|
|
277
|
+
const value = ((seed + frame * 17 + slot * 13) % 19) - 9;
|
|
278
|
+
delta = addProjection(delta, value * (slot + 1));
|
|
279
|
+
}
|
|
280
|
+
return delta;
|
|
281
|
+
}
|
|
282
|
+
function predictionError(slot, frame, seed) {
|
|
283
|
+
if ((seed + slot * 3 + frame * 5) % 11 === 0) {
|
|
284
|
+
return slot + 1;
|
|
285
|
+
}
|
|
286
|
+
if ((seed + slot + frame * 7) % 17 === 0) {
|
|
287
|
+
return -slot - 1;
|
|
288
|
+
}
|
|
289
|
+
return 0;
|
|
290
|
+
}
|
|
291
|
+
function snapshotDelay(slot, frame) {
|
|
292
|
+
return 2 + ((slot + frame) % 5);
|
|
293
|
+
}
|
|
294
|
+
function addProjection(left, right) {
|
|
295
|
+
const sum = (left + right) % projectionModulo;
|
|
296
|
+
return sum < 0 ? sum + projectionModulo : sum;
|
|
297
|
+
}
|
|
298
|
+
function percentile(values, percentileValue) {
|
|
299
|
+
if (values.length === 0) {
|
|
300
|
+
return 0;
|
|
301
|
+
}
|
|
302
|
+
const sorted = [...values].sort((left, right) => left - right);
|
|
303
|
+
const index = Math.min(sorted.length - 1, Math.floor((percentileValue / 100) * sorted.length));
|
|
304
|
+
return sorted[index];
|
|
305
|
+
}
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
import { type CanonicalValue } from './canonical.js';
|
|
2
|
+
import { type DeterministicCommandChannelDescriptor, type DeterministicCommandTimelineSnapshot, type DeterministicPlayerCommand, type DeterministicRejectedCommand } from './commands.js';
|
|
3
|
+
import { type DeterministicLocalAuthorityRuntimeOptions, type DeterministicLocalAuthorityRuntimeSnapshot, type DeterministicLocalAuthorityState } from './local-authority.js';
|
|
4
|
+
import { deterministicLocalAuthorityWireProtocol } from './local-authority-wire.js';
|
|
5
|
+
export interface DeterministicAuthorityServerOptions extends DeterministicLocalAuthorityRuntimeOptions {
|
|
6
|
+
readonly players: number;
|
|
7
|
+
readonly commandChannels?: readonly DeterministicCommandChannelDescriptor[];
|
|
8
|
+
readonly validCommandAssetRefs?: readonly string[];
|
|
9
|
+
readonly validateCommandPayload?: (command: DeterministicPlayerCommand) => boolean;
|
|
10
|
+
}
|
|
11
|
+
export interface DeterministicAuthorityClientOptions {
|
|
12
|
+
readonly slot: number;
|
|
13
|
+
readonly spectator?: boolean;
|
|
14
|
+
readonly bot?: boolean;
|
|
15
|
+
readonly lateJoin?: boolean;
|
|
16
|
+
}
|
|
17
|
+
export interface SyncplayInput {
|
|
18
|
+
readonly frame: number;
|
|
19
|
+
readonly value: number;
|
|
20
|
+
readonly data?: CanonicalValue;
|
|
21
|
+
readonly delay?: number;
|
|
22
|
+
readonly inputId?: string;
|
|
23
|
+
}
|
|
24
|
+
export interface SyncplaySubmitResult {
|
|
25
|
+
readonly accepted: boolean;
|
|
26
|
+
readonly slot: number;
|
|
27
|
+
readonly frame: number;
|
|
28
|
+
}
|
|
29
|
+
export type SyncplayClientRole = 'player' | 'spectator' | 'bot';
|
|
30
|
+
export interface SyncplayLifecycleResult {
|
|
31
|
+
readonly slot: number;
|
|
32
|
+
readonly frame: number;
|
|
33
|
+
}
|
|
34
|
+
export interface SyncplayCommand {
|
|
35
|
+
readonly id: string;
|
|
36
|
+
readonly frame: number;
|
|
37
|
+
readonly channelId: string;
|
|
38
|
+
readonly payload: CanonicalValue;
|
|
39
|
+
readonly predicted?: boolean;
|
|
40
|
+
readonly slot?: number;
|
|
41
|
+
}
|
|
42
|
+
export interface SyncplayCommandSubmitResult {
|
|
43
|
+
readonly accepted: boolean;
|
|
44
|
+
readonly slot: number;
|
|
45
|
+
readonly frame: number;
|
|
46
|
+
readonly commandId: string;
|
|
47
|
+
readonly rejectionReason?: DeterministicRejectedCommand['reason'];
|
|
48
|
+
}
|
|
49
|
+
export interface SyncplayCommandPollOptions {
|
|
50
|
+
readonly slot?: number;
|
|
51
|
+
readonly channelId?: string;
|
|
52
|
+
}
|
|
53
|
+
export interface DeterministicAuthorityChecksums {
|
|
54
|
+
readonly authorityChecksum: string;
|
|
55
|
+
readonly clientChecksum: string;
|
|
56
|
+
readonly authorityProjectionChecksum: string;
|
|
57
|
+
readonly clientProjectionChecksum: string;
|
|
58
|
+
readonly convergence: boolean;
|
|
59
|
+
readonly commandChecksum: string;
|
|
60
|
+
readonly commandTimelineChecksum?: string;
|
|
61
|
+
}
|
|
62
|
+
export interface DeterministicAuthorityRecordedInput {
|
|
63
|
+
readonly accepted: boolean;
|
|
64
|
+
readonly slot: number;
|
|
65
|
+
readonly senderSlot?: number;
|
|
66
|
+
readonly frame: number;
|
|
67
|
+
readonly value: number;
|
|
68
|
+
readonly data?: CanonicalValue;
|
|
69
|
+
readonly delay?: number;
|
|
70
|
+
readonly inputId?: string;
|
|
71
|
+
}
|
|
72
|
+
export interface DeterministicAuthorityLifecycleEvent {
|
|
73
|
+
readonly kind: 'lateJoin' | 'spectator' | 'disconnect' | 'reconnect';
|
|
74
|
+
readonly slot: number;
|
|
75
|
+
readonly frame: number;
|
|
76
|
+
}
|
|
77
|
+
export interface DeterministicAuthorityReplayEvidence {
|
|
78
|
+
readonly mismatches: number;
|
|
79
|
+
readonly replayedAuthorityChecksum: string;
|
|
80
|
+
readonly replayedProjectionChecksum: string;
|
|
81
|
+
}
|
|
82
|
+
export interface DeterministicAuthorityCertificationArtifact {
|
|
83
|
+
readonly schemaVersion: 1;
|
|
84
|
+
readonly result: 'pass' | 'fail';
|
|
85
|
+
readonly protocol: typeof deterministicLocalAuthorityWireProtocol;
|
|
86
|
+
readonly players: number;
|
|
87
|
+
readonly frame: number;
|
|
88
|
+
readonly replayMismatches: number;
|
|
89
|
+
readonly botSlots: readonly number[];
|
|
90
|
+
readonly lateJoinSlots: readonly number[];
|
|
91
|
+
readonly spectatorSlots: readonly number[];
|
|
92
|
+
readonly inputHistory: readonly DeterministicAuthorityRecordedInput[];
|
|
93
|
+
readonly lifecycleEvents: readonly DeterministicAuthorityLifecycleEvent[];
|
|
94
|
+
readonly replay: DeterministicAuthorityReplayEvidence;
|
|
95
|
+
readonly snapshotChecksum: string;
|
|
96
|
+
readonly commandSnapshotChecksum?: string;
|
|
97
|
+
readonly checksums: DeterministicAuthorityChecksums;
|
|
98
|
+
readonly snapshot: DeterministicLocalAuthorityRuntimeSnapshot;
|
|
99
|
+
readonly commandSnapshot?: DeterministicCommandTimelineSnapshot;
|
|
100
|
+
}
|
|
101
|
+
export interface DeterministicAuthorityCertificationVerification {
|
|
102
|
+
readonly result: 'pass' | 'fail';
|
|
103
|
+
readonly diagnostics: readonly string[];
|
|
104
|
+
readonly replayMismatches: number;
|
|
105
|
+
readonly convergence: boolean;
|
|
106
|
+
}
|
|
107
|
+
export interface SyncplayClientConnection {
|
|
108
|
+
readonly slot: number;
|
|
109
|
+
readonly role: SyncplayClientRole;
|
|
110
|
+
submitInput(input: SyncplayInput): SyncplaySubmitResult;
|
|
111
|
+
submitCommand(command: SyncplayCommand): SyncplayCommandSubmitResult;
|
|
112
|
+
pollCommands(frame: number, options?: SyncplayCommandPollOptions): readonly DeterministicPlayerCommand[];
|
|
113
|
+
commandSnapshot(): DeterministicCommandTimelineSnapshot;
|
|
114
|
+
lateJoin(): SyncplayLifecycleResult;
|
|
115
|
+
disconnect(): void;
|
|
116
|
+
reconnect(): void;
|
|
117
|
+
snapshot(): DeterministicLocalAuthorityRuntimeSnapshot;
|
|
118
|
+
checksums(): DeterministicAuthorityChecksums;
|
|
119
|
+
exportCertificationArtifact(): DeterministicAuthorityCertificationArtifact;
|
|
120
|
+
}
|
|
121
|
+
export interface SyncplayClient {
|
|
122
|
+
readonly slot: number;
|
|
123
|
+
readonly role: SyncplayClientRole;
|
|
124
|
+
submitInput(input: SyncplayInput): SyncplaySubmitResult;
|
|
125
|
+
submitCommand(command: SyncplayCommand): SyncplayCommandSubmitResult;
|
|
126
|
+
pollCommands(frame: number, options?: SyncplayCommandPollOptions): readonly DeterministicPlayerCommand[];
|
|
127
|
+
commandSnapshot(): DeterministicCommandTimelineSnapshot;
|
|
128
|
+
lateJoin(): SyncplayLifecycleResult;
|
|
129
|
+
disconnect(): void;
|
|
130
|
+
reconnect(): void;
|
|
131
|
+
snapshot(): DeterministicLocalAuthorityRuntimeSnapshot;
|
|
132
|
+
checksums(): DeterministicAuthorityChecksums;
|
|
133
|
+
exportCertificationArtifact(): DeterministicAuthorityCertificationArtifact;
|
|
134
|
+
}
|
|
135
|
+
export interface DeterministicAuthorityServer {
|
|
136
|
+
readonly protocol: typeof deterministicLocalAuthorityWireProtocol;
|
|
137
|
+
readonly players: number;
|
|
138
|
+
readonly botSlots: readonly number[];
|
|
139
|
+
readonly state: DeterministicLocalAuthorityState;
|
|
140
|
+
connectClient(options: DeterministicAuthorityClientOptions): SyncplayClientConnection;
|
|
141
|
+
stepFrames(count: number): void;
|
|
142
|
+
pollCommands(frame: number, options?: SyncplayCommandPollOptions): readonly DeterministicPlayerCommand[];
|
|
143
|
+
commandSnapshot(): DeterministicCommandTimelineSnapshot;
|
|
144
|
+
snapshot(): DeterministicLocalAuthorityRuntimeSnapshot;
|
|
145
|
+
checksums(): DeterministicAuthorityChecksums;
|
|
146
|
+
exportCertificationArtifact(): DeterministicAuthorityCertificationArtifact;
|
|
147
|
+
close(): void;
|
|
148
|
+
}
|
|
149
|
+
export declare function createDeterministicAuthorityServer(options: DeterministicAuthorityServerOptions): DeterministicAuthorityServer;
|
|
150
|
+
export declare function createSyncplayClient(options: {
|
|
151
|
+
readonly connection: SyncplayClientConnection;
|
|
152
|
+
}): SyncplayClient;
|
|
153
|
+
export declare function verifyDeterministicAuthorityCertificationArtifact(artifact: DeterministicAuthorityCertificationArtifact): DeterministicAuthorityCertificationVerification;
|