@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,61 @@
|
|
|
1
|
+
import type { DeterministicOfflineRuntime } from './sdk-offline.js';
|
|
2
|
+
export type DeterministicRenderReadonly<T> = T extends (...args: readonly never[]) => unknown ? never : T extends readonly (infer Item)[] ? readonly DeterministicRenderReadonly<Item>[] : T extends object ? {
|
|
3
|
+
readonly [Key in keyof T]: DeterministicRenderReadonly<T[Key]>;
|
|
4
|
+
} : T;
|
|
5
|
+
export interface DeterministicR3fRenderFrame<RenderState> {
|
|
6
|
+
readonly mode: 'offline';
|
|
7
|
+
readonly frame: number;
|
|
8
|
+
readonly predictedFrame: number;
|
|
9
|
+
readonly verifiedFrame: number;
|
|
10
|
+
readonly checksum: string;
|
|
11
|
+
readonly debug: DeterministicR3fRenderDebug;
|
|
12
|
+
readonly state: DeterministicRenderReadonly<RenderState>;
|
|
13
|
+
}
|
|
14
|
+
export interface DeterministicR3fRenderDebug {
|
|
15
|
+
readonly frame: number;
|
|
16
|
+
readonly predictedFrame: number;
|
|
17
|
+
readonly verifiedFrame: number;
|
|
18
|
+
readonly correctionFrames: number;
|
|
19
|
+
readonly isPredicted: boolean;
|
|
20
|
+
readonly isVerified: boolean;
|
|
21
|
+
readonly checksum: string;
|
|
22
|
+
}
|
|
23
|
+
export interface DeterministicRenderPose {
|
|
24
|
+
readonly key: string;
|
|
25
|
+
readonly x: number;
|
|
26
|
+
readonly y?: number;
|
|
27
|
+
readonly z?: number;
|
|
28
|
+
}
|
|
29
|
+
export interface DeterministicR3fErrorCorrectionConfig<RenderState> {
|
|
30
|
+
readonly poses: (state: DeterministicRenderReadonly<RenderState>) => readonly DeterministicRenderPose[];
|
|
31
|
+
readonly errorCorrectionMs?: number;
|
|
32
|
+
readonly snapThreshold?: number;
|
|
33
|
+
readonly maxErrorSnapDistance?: number;
|
|
34
|
+
}
|
|
35
|
+
export interface DeterministicR3fRenderAdapter<State, Input, RenderState = State> {
|
|
36
|
+
getFrame(): DeterministicR3fRenderFrame<RenderState>;
|
|
37
|
+
setInput(slot: number, input: Input): void;
|
|
38
|
+
bindInput<RawInput>(slot: number, normalize: (rawInput: RawInput) => Input): (rawInput: RawInput) => Input;
|
|
39
|
+
update(deltaMs: number): number;
|
|
40
|
+
stepFrames(count: number): void;
|
|
41
|
+
subscribe(listener: (frame: DeterministicR3fRenderFrame<RenderState>) => void): () => void;
|
|
42
|
+
getSmoothedPose(key: string): DeterministicRenderPose | undefined;
|
|
43
|
+
}
|
|
44
|
+
export interface DeterministicR3fRenderAdapterConfig<State, Input, RenderState = State> {
|
|
45
|
+
readonly runtime: DeterministicOfflineRuntime<State, Input>;
|
|
46
|
+
readonly projectState?: (state: DeterministicRenderReadonly<State>) => RenderState;
|
|
47
|
+
readonly errorCorrection?: DeterministicR3fErrorCorrectionConfig<RenderState>;
|
|
48
|
+
}
|
|
49
|
+
export declare function createDeterministicR3fRenderAdapter<State, Input, RenderState = State>(config: DeterministicR3fRenderAdapterConfig<State, Input, RenderState>): DeterministicR3fRenderAdapter<State, Input, RenderState>;
|
|
50
|
+
export declare function createDeterministicR3fDebugOverlay<RenderState>(frame: DeterministicR3fRenderFrame<RenderState>): DeterministicR3fRenderDebug;
|
|
51
|
+
export declare function interpolateDeterministicRenderNumber(previous: number, next: number, alpha: number): number;
|
|
52
|
+
export declare function interpolateDeterministicRenderVec2(previous: {
|
|
53
|
+
readonly x: number;
|
|
54
|
+
readonly y: number;
|
|
55
|
+
}, next: {
|
|
56
|
+
readonly x: number;
|
|
57
|
+
readonly y: number;
|
|
58
|
+
}, alpha: number): {
|
|
59
|
+
readonly x: number;
|
|
60
|
+
readonly y: number;
|
|
61
|
+
};
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
import { cloneCanonical } from './canonical.js';
|
|
2
|
+
export function createDeterministicR3fRenderAdapter(config) {
|
|
3
|
+
const smoother = config.errorCorrection === undefined ? undefined : createRenderErrorSmoother(config.errorCorrection);
|
|
4
|
+
if (smoother !== undefined && config.errorCorrection !== undefined) {
|
|
5
|
+
const extractPoses = config.errorCorrection.poses;
|
|
6
|
+
config.runtime.subscribe((frame) => {
|
|
7
|
+
smoother.observe(frame.frame, extractPoses(toRenderFrame(frame, config.projectState).state));
|
|
8
|
+
});
|
|
9
|
+
}
|
|
10
|
+
const adapter = {
|
|
11
|
+
getFrame() {
|
|
12
|
+
return toRenderFrame(config.runtime.getFrame(), config.projectState);
|
|
13
|
+
},
|
|
14
|
+
setInput(slot, input) {
|
|
15
|
+
config.runtime.setInput(slot, input);
|
|
16
|
+
},
|
|
17
|
+
bindInput(slot, normalize) {
|
|
18
|
+
return (rawInput) => {
|
|
19
|
+
const input = normalize(rawInput);
|
|
20
|
+
config.runtime.setInput(slot, input);
|
|
21
|
+
return input;
|
|
22
|
+
};
|
|
23
|
+
},
|
|
24
|
+
update(deltaMs) {
|
|
25
|
+
// Decay before stepping so a correction detected in this update renders
|
|
26
|
+
// at the full mispredicted pose first, then blends on subsequent updates.
|
|
27
|
+
smoother?.advance(deltaMs);
|
|
28
|
+
return config.runtime.update(deltaMs);
|
|
29
|
+
},
|
|
30
|
+
stepFrames(count) {
|
|
31
|
+
config.runtime.stepFrames(count);
|
|
32
|
+
},
|
|
33
|
+
subscribe(listener) {
|
|
34
|
+
return config.runtime.subscribe((frame) => {
|
|
35
|
+
listener(toRenderFrame(frame, config.projectState));
|
|
36
|
+
});
|
|
37
|
+
},
|
|
38
|
+
getSmoothedPose(key) {
|
|
39
|
+
if (smoother === undefined) {
|
|
40
|
+
throw new Error('getSmoothedPose requires errorCorrection to be configured on the render adapter');
|
|
41
|
+
}
|
|
42
|
+
return smoother.pose(key);
|
|
43
|
+
},
|
|
44
|
+
};
|
|
45
|
+
return adapter;
|
|
46
|
+
}
|
|
47
|
+
// Render-side only (plain floats, never fed back into sim state): when a
|
|
48
|
+
// rollback re-emits an already-rendered frame with a different pose, the
|
|
49
|
+
// rendered transform blends mispredicted -> corrected over errorCorrectionMs
|
|
50
|
+
// instead of snapping (Quantum "Error Prediction" analog).
|
|
51
|
+
function createRenderErrorSmoother(options) {
|
|
52
|
+
const windowMs = options.errorCorrectionMs ?? 120;
|
|
53
|
+
const snapThreshold = options.snapThreshold ?? 1e-6;
|
|
54
|
+
const maxErrorSnapDistance = options.maxErrorSnapDistance ?? Number.POSITIVE_INFINITY;
|
|
55
|
+
if (!Number.isFinite(windowMs) || windowMs < 0) {
|
|
56
|
+
throw new Error('errorCorrectionMs must be a finite number >= 0');
|
|
57
|
+
}
|
|
58
|
+
if (!Number.isFinite(snapThreshold) || snapThreshold < 0) {
|
|
59
|
+
throw new Error('snapThreshold must be a finite number >= 0');
|
|
60
|
+
}
|
|
61
|
+
if (Number.isNaN(maxErrorSnapDistance) || maxErrorSnapDistance < 0) {
|
|
62
|
+
throw new Error('maxErrorSnapDistance must be >= 0');
|
|
63
|
+
}
|
|
64
|
+
let lastFrame;
|
|
65
|
+
let tracked = new Map();
|
|
66
|
+
const errors = new Map();
|
|
67
|
+
function currentError(key) {
|
|
68
|
+
const entry = errors.get(key);
|
|
69
|
+
if (entry === undefined || windowMs <= 0) {
|
|
70
|
+
return { x: 0, y: 0, z: 0 };
|
|
71
|
+
}
|
|
72
|
+
const remaining = Math.min(1, Math.max(0, 1 - entry.elapsedMs / windowMs));
|
|
73
|
+
return { x: entry.x * remaining, y: entry.y * remaining, z: entry.z * remaining };
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
observe(frame, poses) {
|
|
77
|
+
const isCorrection = lastFrame !== undefined && frame <= lastFrame;
|
|
78
|
+
const nextTracked = new Map();
|
|
79
|
+
for (const pose of poses) {
|
|
80
|
+
if (!Number.isFinite(pose.x) || (pose.y !== undefined && !Number.isFinite(pose.y)) || (pose.z !== undefined && !Number.isFinite(pose.z))) {
|
|
81
|
+
throw new Error(`Render pose "${pose.key}" requires finite coordinates`);
|
|
82
|
+
}
|
|
83
|
+
if (nextTracked.has(pose.key)) {
|
|
84
|
+
throw new Error(`Duplicate render pose key: ${pose.key}`);
|
|
85
|
+
}
|
|
86
|
+
const previous = tracked.get(pose.key);
|
|
87
|
+
if (isCorrection && previous !== undefined) {
|
|
88
|
+
const carried = currentError(pose.key);
|
|
89
|
+
const dx = previous.x + carried.x - pose.x;
|
|
90
|
+
const dy = (previous.y ?? 0) + carried.y - (pose.y ?? 0);
|
|
91
|
+
const dz = (previous.z ?? 0) + carried.z - (pose.z ?? 0);
|
|
92
|
+
const distance = Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
93
|
+
if (windowMs > 0 && distance > snapThreshold && distance <= maxErrorSnapDistance) {
|
|
94
|
+
errors.set(pose.key, { x: dx, y: dy, z: dz, elapsedMs: 0 });
|
|
95
|
+
}
|
|
96
|
+
else {
|
|
97
|
+
errors.delete(pose.key);
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
nextTracked.set(pose.key, { x: pose.x, y: pose.y, z: pose.z });
|
|
101
|
+
}
|
|
102
|
+
tracked = nextTracked;
|
|
103
|
+
for (const key of [...errors.keys()]) {
|
|
104
|
+
if (!tracked.has(key)) {
|
|
105
|
+
errors.delete(key);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
lastFrame = frame;
|
|
109
|
+
},
|
|
110
|
+
advance(deltaMs) {
|
|
111
|
+
if (!Number.isFinite(deltaMs)) {
|
|
112
|
+
throw new Error('Render error-correction advance requires a finite deltaMs');
|
|
113
|
+
}
|
|
114
|
+
if (deltaMs <= 0) {
|
|
115
|
+
return;
|
|
116
|
+
}
|
|
117
|
+
for (const [key, entry] of errors) {
|
|
118
|
+
entry.elapsedMs += deltaMs;
|
|
119
|
+
if (entry.elapsedMs >= windowMs) {
|
|
120
|
+
errors.delete(key);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
},
|
|
124
|
+
pose(key) {
|
|
125
|
+
const base = tracked.get(key);
|
|
126
|
+
if (base === undefined) {
|
|
127
|
+
return undefined;
|
|
128
|
+
}
|
|
129
|
+
const error = currentError(key);
|
|
130
|
+
const result = { key, x: base.x + error.x };
|
|
131
|
+
if (base.y !== undefined) {
|
|
132
|
+
result.y = base.y + error.y;
|
|
133
|
+
}
|
|
134
|
+
if (base.z !== undefined) {
|
|
135
|
+
result.z = base.z + error.z;
|
|
136
|
+
}
|
|
137
|
+
return freezeRenderValue(result);
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
export function createDeterministicR3fDebugOverlay(frame) {
|
|
142
|
+
return freezeRenderValue({
|
|
143
|
+
frame: frame.frame,
|
|
144
|
+
predictedFrame: frame.predictedFrame,
|
|
145
|
+
verifiedFrame: frame.verifiedFrame,
|
|
146
|
+
correctionFrames: Math.max(0, frame.predictedFrame - frame.verifiedFrame),
|
|
147
|
+
isPredicted: frame.predictedFrame > frame.verifiedFrame,
|
|
148
|
+
isVerified: frame.predictedFrame === frame.verifiedFrame,
|
|
149
|
+
checksum: frame.checksum,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
export function interpolateDeterministicRenderNumber(previous, next, alpha) {
|
|
153
|
+
if (!Number.isFinite(previous) || !Number.isFinite(next) || !Number.isFinite(alpha)) {
|
|
154
|
+
throw new Error('Render interpolation requires finite numbers');
|
|
155
|
+
}
|
|
156
|
+
const clampedAlpha = Math.min(1, Math.max(0, alpha));
|
|
157
|
+
return previous + (next - previous) * clampedAlpha;
|
|
158
|
+
}
|
|
159
|
+
export function interpolateDeterministicRenderVec2(previous, next, alpha) {
|
|
160
|
+
return freezeRenderValue({
|
|
161
|
+
x: interpolateDeterministicRenderNumber(previous.x, next.x, alpha),
|
|
162
|
+
y: interpolateDeterministicRenderNumber(previous.y, next.y, alpha),
|
|
163
|
+
});
|
|
164
|
+
}
|
|
165
|
+
function toRenderFrame(frame, projectState) {
|
|
166
|
+
const state = freezeRenderValue(cloneCanonical(frame.state));
|
|
167
|
+
const renderState = projectState === undefined ? state : projectState(state);
|
|
168
|
+
const frozenRenderState = freezeRenderValue(cloneCanonical(renderState));
|
|
169
|
+
const renderFrame = {
|
|
170
|
+
mode: frame.mode,
|
|
171
|
+
frame: frame.frame,
|
|
172
|
+
predictedFrame: frame.predictedFrame,
|
|
173
|
+
verifiedFrame: frame.verifiedFrame,
|
|
174
|
+
checksum: frame.checksum,
|
|
175
|
+
debug: freezeRenderValue({
|
|
176
|
+
frame: frame.frame,
|
|
177
|
+
predictedFrame: frame.predictedFrame,
|
|
178
|
+
verifiedFrame: frame.verifiedFrame,
|
|
179
|
+
correctionFrames: Math.max(0, frame.predictedFrame - frame.verifiedFrame),
|
|
180
|
+
isPredicted: frame.predictedFrame > frame.verifiedFrame,
|
|
181
|
+
isVerified: frame.predictedFrame === frame.verifiedFrame,
|
|
182
|
+
checksum: frame.checksum,
|
|
183
|
+
}),
|
|
184
|
+
state: frozenRenderState,
|
|
185
|
+
};
|
|
186
|
+
return freezeRenderValue(renderFrame);
|
|
187
|
+
}
|
|
188
|
+
function freezeRenderValue(value) {
|
|
189
|
+
if (value === null || typeof value !== 'object') {
|
|
190
|
+
return value;
|
|
191
|
+
}
|
|
192
|
+
if (Array.isArray(value)) {
|
|
193
|
+
for (const item of value) {
|
|
194
|
+
freezeRenderValue(item);
|
|
195
|
+
}
|
|
196
|
+
return Object.freeze(value);
|
|
197
|
+
}
|
|
198
|
+
const record = value;
|
|
199
|
+
for (const key of Object.keys(record).sort()) {
|
|
200
|
+
freezeRenderValue(record[key]);
|
|
201
|
+
}
|
|
202
|
+
return Object.freeze(value);
|
|
203
|
+
}
|
package/dist/random.d.ts
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export interface DeterministicRandomSnapshot {
|
|
2
|
+
readonly state: number;
|
|
3
|
+
}
|
|
4
|
+
export declare class DeterministicRandom {
|
|
5
|
+
private state;
|
|
6
|
+
constructor(seed: number);
|
|
7
|
+
nextUint32(): number;
|
|
8
|
+
nextInt(minInclusive: number, maxInclusive: number): number;
|
|
9
|
+
nextFixed(scale?: number): number;
|
|
10
|
+
fork(label: string | number): DeterministicRandom;
|
|
11
|
+
snapshot(): DeterministicRandomSnapshot;
|
|
12
|
+
restore(snapshot: DeterministicRandomSnapshot): void;
|
|
13
|
+
}
|
package/dist/random.js
ADDED
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
export class DeterministicRandom {
|
|
2
|
+
state;
|
|
3
|
+
constructor(seed) {
|
|
4
|
+
this.state = seed >>> 0;
|
|
5
|
+
if (this.state === 0) {
|
|
6
|
+
this.state = 0x6d2b79f5;
|
|
7
|
+
}
|
|
8
|
+
}
|
|
9
|
+
nextUint32() {
|
|
10
|
+
let nextState = this.state;
|
|
11
|
+
nextState ^= nextState << 13;
|
|
12
|
+
nextState ^= nextState >>> 17;
|
|
13
|
+
nextState ^= nextState << 5;
|
|
14
|
+
this.state = nextState >>> 0;
|
|
15
|
+
return this.state;
|
|
16
|
+
}
|
|
17
|
+
nextInt(minInclusive, maxInclusive) {
|
|
18
|
+
if (!Number.isInteger(minInclusive) || !Number.isInteger(maxInclusive) || maxInclusive < minInclusive) {
|
|
19
|
+
throw new Error('nextInt requires integer bounds with max >= min');
|
|
20
|
+
}
|
|
21
|
+
const range = maxInclusive - minInclusive + 1;
|
|
22
|
+
return minInclusive + (this.nextUint32() % range);
|
|
23
|
+
}
|
|
24
|
+
nextFixed(scale = 1_000_000) {
|
|
25
|
+
if (!Number.isInteger(scale) || scale <= 0) {
|
|
26
|
+
throw new Error('nextFixed scale must be a positive integer');
|
|
27
|
+
}
|
|
28
|
+
return this.nextUint32() % scale;
|
|
29
|
+
}
|
|
30
|
+
fork(label) {
|
|
31
|
+
const labelHash = hashLabel(label);
|
|
32
|
+
const child = new DeterministicRandom(1);
|
|
33
|
+
child.restore({ state: (this.state ^ labelHash) >>> 0 });
|
|
34
|
+
return child;
|
|
35
|
+
}
|
|
36
|
+
snapshot() {
|
|
37
|
+
return { state: this.state };
|
|
38
|
+
}
|
|
39
|
+
restore(snapshot) {
|
|
40
|
+
// Mirror the constructor's zero-guard: xorshift32 is permanently dead at 0.
|
|
41
|
+
// Reachable via fork() when `state ^ labelHash` lands on 0 — a 1-in-2^32
|
|
42
|
+
// landmine per fork on a frozen deterministic RNG.
|
|
43
|
+
const state = snapshot.state >>> 0;
|
|
44
|
+
this.state = state === 0 ? 0x6d2b79f5 : state;
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
function hashLabel(label) {
|
|
48
|
+
const value = String(label);
|
|
49
|
+
let hash = 0x811c9dc5;
|
|
50
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
51
|
+
hash ^= value.charCodeAt(index);
|
|
52
|
+
hash = Math.imul(hash, 0x01000193) >>> 0;
|
|
53
|
+
}
|
|
54
|
+
return hash;
|
|
55
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { type CanonicalValue } from './canonical.js';
|
|
2
|
+
import { type DeterministicFrameRuntime, type DeterministicFrameRuntimeDescriptor, type DeterministicSystem } from './engine-runtime.js';
|
|
3
|
+
import type { RecordedChecksum, RecordedCommandFrame, RecordedDebugEvent, RecordedInputFrame } from './types.js';
|
|
4
|
+
export interface DeterministicReplayBundle {
|
|
5
|
+
readonly schemaVersion: 1;
|
|
6
|
+
readonly runtimeVersion: string;
|
|
7
|
+
readonly engineHash: string;
|
|
8
|
+
readonly schemaHash: string;
|
|
9
|
+
readonly assetDbHash: string;
|
|
10
|
+
readonly configHash: string;
|
|
11
|
+
readonly sampleId: string;
|
|
12
|
+
readonly inputHistory: readonly RecordedInputFrame[];
|
|
13
|
+
readonly commandHistory: readonly RecordedCommandFrame[];
|
|
14
|
+
readonly checksums: readonly RecordedChecksum[];
|
|
15
|
+
readonly debugEvents: readonly RecordedDebugEvent[];
|
|
16
|
+
readonly snapshots: readonly DeterministicReplaySnapshot[];
|
|
17
|
+
readonly sliceOrigin?: DeterministicReplaySliceOrigin;
|
|
18
|
+
readonly runtimeReplay?: DeterministicReplayRuntimeManifest;
|
|
19
|
+
readonly semanticReplay?: DeterministicReplaySemanticModel;
|
|
20
|
+
readonly semanticReplayHash?: string;
|
|
21
|
+
readonly metadata: Readonly<Record<string, string | number | boolean>>;
|
|
22
|
+
}
|
|
23
|
+
export type DeterministicReplayRuntimeSystemId = 'score-accumulator-v1' | 'score-runtime-replay-v1' | 'project-game-state-runtime-replay-v1';
|
|
24
|
+
export type DeterministicReplayRuntimeBeforeFrameId = 'score-input-command-delta-v1' | 'project-game-state-input-command-delta-v1';
|
|
25
|
+
export interface DeterministicReplayRuntimeManifest {
|
|
26
|
+
readonly descriptor: DeterministicFrameRuntimeDescriptor;
|
|
27
|
+
readonly descriptorHash: string;
|
|
28
|
+
readonly systemId: DeterministicReplayRuntimeSystemId;
|
|
29
|
+
readonly beforeFrameId: DeterministicReplayRuntimeBeforeFrameId;
|
|
30
|
+
readonly systemsSourceHash: string;
|
|
31
|
+
readonly acceptedSystemsSourceHashes?: readonly string[];
|
|
32
|
+
readonly seed: number;
|
|
33
|
+
readonly sparse?: boolean;
|
|
34
|
+
readonly manifestHash: string;
|
|
35
|
+
}
|
|
36
|
+
export interface DeterministicReplaySliceOrigin {
|
|
37
|
+
readonly sourceBundleHash: string;
|
|
38
|
+
readonly sourceStartFrame: number;
|
|
39
|
+
readonly sourceEndFrame: number;
|
|
40
|
+
readonly sliceStartFrame: number;
|
|
41
|
+
readonly sliceEndFrame: number;
|
|
42
|
+
readonly baselineSnapshot: DeterministicReplaySnapshot;
|
|
43
|
+
}
|
|
44
|
+
export interface DeterministicReplaySnapshot {
|
|
45
|
+
readonly frame: number;
|
|
46
|
+
readonly checksum: string;
|
|
47
|
+
readonly bytes: string;
|
|
48
|
+
}
|
|
49
|
+
export interface DeterministicReplaySemanticModel {
|
|
50
|
+
readonly kind: 'operation-model-v1';
|
|
51
|
+
readonly initialState: CanonicalValue;
|
|
52
|
+
readonly operations: readonly DeterministicReplaySemanticOperation[];
|
|
53
|
+
}
|
|
54
|
+
export interface DeterministicReplaySemanticOperation {
|
|
55
|
+
readonly source: 'input' | 'command' | 'command.payload';
|
|
56
|
+
readonly sourcePath: string;
|
|
57
|
+
readonly targetPath: string;
|
|
58
|
+
readonly op: 'add' | 'set';
|
|
59
|
+
readonly slotField?: string;
|
|
60
|
+
}
|
|
61
|
+
export interface DeterministicReplayBundleVerification {
|
|
62
|
+
readonly result: 'pass' | 'fail';
|
|
63
|
+
readonly bundleHash: string;
|
|
64
|
+
readonly replayBytes: number;
|
|
65
|
+
readonly snapshotBytes: number;
|
|
66
|
+
readonly inputCount: number;
|
|
67
|
+
readonly checksumCount: number;
|
|
68
|
+
readonly commandCount: number;
|
|
69
|
+
readonly debugEventCount: number;
|
|
70
|
+
readonly semanticReplayExecuted: boolean;
|
|
71
|
+
readonly semanticReplayFrames: number;
|
|
72
|
+
readonly firstMismatch?: DeterministicReplayBundleMismatch;
|
|
73
|
+
}
|
|
74
|
+
export interface DeterministicReplayBundleMismatch {
|
|
75
|
+
readonly frame: number;
|
|
76
|
+
readonly subsystem: string;
|
|
77
|
+
readonly path: string;
|
|
78
|
+
readonly expectedChecksum: string;
|
|
79
|
+
readonly actualChecksum: string;
|
|
80
|
+
readonly minimalReproPath: string;
|
|
81
|
+
}
|
|
82
|
+
export interface DeterministicReplayDesyncMinimization {
|
|
83
|
+
readonly mismatch: DeterministicReplayBundleMismatch;
|
|
84
|
+
readonly bundle: DeterministicReplayBundle;
|
|
85
|
+
readonly verifier: DeterministicReplayBundleVerification;
|
|
86
|
+
readonly searchSteps: number;
|
|
87
|
+
readonly originalReplayBytes: number;
|
|
88
|
+
readonly minimizedReplayBytes: number;
|
|
89
|
+
readonly removedInputFrames: number;
|
|
90
|
+
readonly removedInputs: number;
|
|
91
|
+
readonly removedCommandFrames: number;
|
|
92
|
+
readonly removedCommands: number;
|
|
93
|
+
readonly removedDebugEvents: number;
|
|
94
|
+
readonly removedSnapshots: number;
|
|
95
|
+
readonly removedChecksumFrames: number;
|
|
96
|
+
}
|
|
97
|
+
export interface DeterministicFrameRuntimeReplayBundleOptions {
|
|
98
|
+
readonly descriptor: DeterministicFrameRuntimeDescriptor;
|
|
99
|
+
readonly systems: Readonly<Record<string, DeterministicSystem>>;
|
|
100
|
+
readonly descriptorHash?: string;
|
|
101
|
+
readonly systemsSourceHash?: string;
|
|
102
|
+
readonly requireRuntimeIdentity?: boolean;
|
|
103
|
+
readonly seed?: number;
|
|
104
|
+
readonly sparse?: boolean;
|
|
105
|
+
beforeFrame?(runtime: DeterministicFrameRuntime, frame: number, inputs: RecordedInputFrame['inputs'], commands: RecordedCommandFrame['commands']): void;
|
|
106
|
+
}
|
|
107
|
+
export interface DeterministicFrameRuntimeReplayBundleVerification {
|
|
108
|
+
readonly result: 'pass' | 'fail';
|
|
109
|
+
readonly bundleHash: string;
|
|
110
|
+
readonly frames: number;
|
|
111
|
+
readonly baselineFrame: number;
|
|
112
|
+
readonly finalFrame: number;
|
|
113
|
+
readonly finalChecksum: string;
|
|
114
|
+
readonly firstMismatch?: DeterministicReplayBundleMismatch;
|
|
115
|
+
}
|
|
116
|
+
export interface DeterministicReplayRuntimeManifestSource {
|
|
117
|
+
readonly descriptor: DeterministicFrameRuntimeDescriptor;
|
|
118
|
+
readonly descriptorHash?: string;
|
|
119
|
+
readonly systemId: DeterministicReplayRuntimeSystemId;
|
|
120
|
+
readonly beforeFrameId: DeterministicReplayRuntimeBeforeFrameId;
|
|
121
|
+
readonly systemsSourceHash?: string;
|
|
122
|
+
readonly acceptedSystemsSourceHashes?: readonly string[];
|
|
123
|
+
readonly seed?: number;
|
|
124
|
+
readonly sparse?: boolean;
|
|
125
|
+
}
|
|
126
|
+
export interface DeterministicReplayDebugTables {
|
|
127
|
+
readonly ecs: readonly CanonicalValue[];
|
|
128
|
+
readonly physics: readonly CanonicalValue[];
|
|
129
|
+
readonly navigation: readonly CanonicalValue[];
|
|
130
|
+
readonly commands: readonly CanonicalValue[];
|
|
131
|
+
readonly events: readonly CanonicalValue[];
|
|
132
|
+
readonly rng: readonly CanonicalValue[];
|
|
133
|
+
readonly assets: readonly CanonicalValue[];
|
|
134
|
+
readonly timings: readonly CanonicalValue[];
|
|
135
|
+
readonly checksum: string;
|
|
136
|
+
}
|
|
137
|
+
export declare function createDeterministicReplayBundle(bundle: Omit<DeterministicReplayBundle, 'schemaVersion'>): DeterministicReplayBundle;
|
|
138
|
+
export declare function createDeterministicReplayRuntimeManifest(source: DeterministicReplayRuntimeManifestSource): DeterministicReplayRuntimeManifest;
|
|
139
|
+
export declare function verifyDeterministicReplayBundle(bundle: DeterministicReplayBundle, expected?: Partial<Pick<DeterministicReplayBundle, 'engineHash' | 'schemaHash' | 'assetDbHash' | 'configHash' | 'runtimeVersion' | 'semanticReplayHash'>>): DeterministicReplayBundleVerification;
|
|
140
|
+
export declare function verifyDeterministicFrameRuntimeReplayBundle(bundle: DeterministicReplayBundle, options: DeterministicFrameRuntimeReplayBundleOptions): DeterministicFrameRuntimeReplayBundleVerification;
|
|
141
|
+
export declare function verifyDeterministicSelfContainedFrameRuntimeReplayBundle(bundle: DeterministicReplayBundle, options?: {
|
|
142
|
+
readonly sparse?: boolean;
|
|
143
|
+
}): DeterministicFrameRuntimeReplayBundleVerification;
|
|
144
|
+
export declare function minimizeDeterministicReplayDesync(expected: DeterministicReplayBundle, actual: DeterministicReplayBundle): DeterministicReplayBundleMismatch | undefined;
|
|
145
|
+
export declare function minimizeDeterministicReplayDesyncBundle(expected: DeterministicReplayBundle, actual: DeterministicReplayBundle): DeterministicReplayDesyncMinimization | undefined;
|
|
146
|
+
export declare function createDeterministicReplayDebugTables(events: readonly RecordedDebugEvent[]): DeterministicReplayDebugTables;
|
|
147
|
+
export declare function sliceDeterministicReplayBundle(bundle: DeterministicReplayBundle, startFrame: number, endFrame: number): DeterministicReplayBundle;
|
|
148
|
+
export declare const __replayBundleCoverageProof: {
|
|
149
|
+
readonly exercisePrivateFallbacks: () => Readonly<Record<string, CanonicalValue>>;
|
|
150
|
+
};
|