@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,370 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createOfflineSession = createOfflineSession;
|
|
4
|
+
const canonical_js_1 = require("./canonical.js");
|
|
5
|
+
const errors_js_1 = require("./errors.js");
|
|
6
|
+
const math_js_1 = require("./math.js");
|
|
7
|
+
const random_js_1 = require("./random.js");
|
|
8
|
+
const runtime_guards_js_1 = require("./runtime-guards.js");
|
|
9
|
+
function createOfflineSession(config) {
|
|
10
|
+
validateConfig(config);
|
|
11
|
+
const checksumInterval = config.checksumIntervalFrames ?? 1;
|
|
12
|
+
const snapshotBufferSize = config.snapshotBufferSize ?? 256;
|
|
13
|
+
const replayRecordingMode = config.replayRecordingMode ?? 'memory';
|
|
14
|
+
const randomSeed = config.randomSeed ?? 1;
|
|
15
|
+
const fixedFrameMs = 1000 / config.tickRate;
|
|
16
|
+
const math = (0, math_js_1.createDeterministicMath)();
|
|
17
|
+
const initialState = (0, canonical_js_1.cloneCanonical)(config.initialState);
|
|
18
|
+
const mutable = {
|
|
19
|
+
frame: 0,
|
|
20
|
+
accumulatedMs: 0,
|
|
21
|
+
state: (0, canonical_js_1.cloneCanonical)(initialState),
|
|
22
|
+
inputs: Array.from({ length: config.playerCount }, () => cloneInput(config, config.defaultInput)),
|
|
23
|
+
inputFlags: Array.from({ length: config.playerCount }, (_, slot) => defaultInputFlags(slot)),
|
|
24
|
+
random: new random_js_1.DeterministicRandom(randomSeed),
|
|
25
|
+
inputFrames: [],
|
|
26
|
+
commandFrames: [],
|
|
27
|
+
pendingCommandFrames: new Map(),
|
|
28
|
+
checksums: [],
|
|
29
|
+
snapshots: new Map(),
|
|
30
|
+
snapshotOrder: [],
|
|
31
|
+
};
|
|
32
|
+
let hydratedFromExternalSnapshot = false;
|
|
33
|
+
const session = {
|
|
34
|
+
get currentFrame() {
|
|
35
|
+
return mutable.frame;
|
|
36
|
+
},
|
|
37
|
+
schemaIdentity: {
|
|
38
|
+
inputSchemaId: config.inputSchemaId,
|
|
39
|
+
stateSchemaId: config.stateSchemaId,
|
|
40
|
+
deterministicVersion: config.deterministicVersion,
|
|
41
|
+
},
|
|
42
|
+
get accumulatedMs() {
|
|
43
|
+
return mutable.accumulatedMs;
|
|
44
|
+
},
|
|
45
|
+
setInput(slot, input) {
|
|
46
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= config.playerCount) {
|
|
47
|
+
throw new errors_js_1.SyncplayError(`Input slot ${slot} is outside playerCount ${config.playerCount}`, {
|
|
48
|
+
code: 'session.invalid-slot',
|
|
49
|
+
frame: mutable.frame,
|
|
50
|
+
slot,
|
|
51
|
+
});
|
|
52
|
+
}
|
|
53
|
+
mutable.inputs[slot] = cloneInput(config, input);
|
|
54
|
+
mutable.inputFlags[slot] = { ...mutable.inputFlags[slot], playerPresent: true, repeatedPrevious: false };
|
|
55
|
+
},
|
|
56
|
+
setInputFlags(slot, flags) {
|
|
57
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= config.playerCount) {
|
|
58
|
+
throw new errors_js_1.SyncplayError(`Input flag slot ${slot} is outside playerCount ${config.playerCount}`, {
|
|
59
|
+
code: 'session.invalid-slot',
|
|
60
|
+
frame: mutable.frame,
|
|
61
|
+
slot,
|
|
62
|
+
});
|
|
63
|
+
}
|
|
64
|
+
mutable.inputFlags[slot] = normalizeInputFlags(slot, { ...mutable.inputFlags[slot], ...flags });
|
|
65
|
+
},
|
|
66
|
+
recordCommands(commands) {
|
|
67
|
+
if (commands.length === 0) {
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
const frameCommands = mutable.pendingCommandFrames.get(mutable.frame) ?? [];
|
|
71
|
+
for (const command of commands) {
|
|
72
|
+
frameCommands.push((0, canonical_js_1.canonicalize)(config.canonicalizeCommand?.(command) ?? command));
|
|
73
|
+
}
|
|
74
|
+
mutable.pendingCommandFrames.set(mutable.frame, frameCommands);
|
|
75
|
+
},
|
|
76
|
+
update(deltaMs) {
|
|
77
|
+
if (!Number.isFinite(deltaMs) || deltaMs < 0) {
|
|
78
|
+
throw new errors_js_1.SyncplayError('deltaMs must be a finite non-negative number', {
|
|
79
|
+
code: 'session.invalid-delta',
|
|
80
|
+
frame: mutable.frame,
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
const nextAccumulatedMs = mutable.accumulatedMs + deltaMs;
|
|
84
|
+
// The 1e-9 epsilon rescues products floating just under an integer, but
|
|
85
|
+
// can leave accumulatedMs a hair negative after the subtraction below —
|
|
86
|
+
// a subsequent near-zero delta would then floor to -1, skip a frame's
|
|
87
|
+
// worth of accumulator with no step, and break the >= 0 contract.
|
|
88
|
+
const framesToStep = Math.max(0, Math.floor((nextAccumulatedMs * config.tickRate + 1e-9) / 1000));
|
|
89
|
+
const maxCatchUpFrames = config.maxCatchUpFrames ?? 240;
|
|
90
|
+
if (framesToStep > maxCatchUpFrames) {
|
|
91
|
+
throw new errors_js_1.SyncplayError(`deltaMs would require ${framesToStep} catch-up frames, above max ${maxCatchUpFrames}`, { code: 'session.catch-up-limit', frame: mutable.frame });
|
|
92
|
+
}
|
|
93
|
+
if (framesToStep === 0) {
|
|
94
|
+
mutable.accumulatedMs = nextAccumulatedMs;
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
mutable.accumulatedMs = nextAccumulatedMs;
|
|
98
|
+
runFrames(config, mutable, checksumInterval, snapshotBufferSize, replayRecordingMode, math, framesToStep);
|
|
99
|
+
mutable.accumulatedMs = Math.max(0, mutable.accumulatedMs - framesToStep * fixedFrameMs);
|
|
100
|
+
return framesToStep;
|
|
101
|
+
},
|
|
102
|
+
stepFrames(count) {
|
|
103
|
+
if (!Number.isInteger(count) || count < 0) {
|
|
104
|
+
throw new errors_js_1.SyncplayError('stepFrames count must be a non-negative integer', {
|
|
105
|
+
code: 'session.invalid-frame-count',
|
|
106
|
+
frame: mutable.frame,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
runFrames(config, mutable, checksumInterval, snapshotBufferSize, replayRecordingMode, math, count);
|
|
110
|
+
},
|
|
111
|
+
getState() {
|
|
112
|
+
return (0, canonical_js_1.cloneCanonical)(mutable.state);
|
|
113
|
+
},
|
|
114
|
+
exportReplay() {
|
|
115
|
+
if (hydratedFromExternalSnapshot) {
|
|
116
|
+
// A hydrated session's history starts at the snapshot frame, not tick 0 —
|
|
117
|
+
// exporting would silently produce an unverifiable replay.
|
|
118
|
+
throw new errors_js_1.SyncplayError('Replay export requires a session that started from tick 0', {
|
|
119
|
+
code: 'session.replay-not-from-origin',
|
|
120
|
+
frame: mutable.frame,
|
|
121
|
+
});
|
|
122
|
+
}
|
|
123
|
+
if (replayRecordingMode !== 'memory') {
|
|
124
|
+
throw new errors_js_1.SyncplayError('Replay export requires replayRecordingMode="memory"', {
|
|
125
|
+
code: 'session.replay-not-retained',
|
|
126
|
+
frame: mutable.frame,
|
|
127
|
+
});
|
|
128
|
+
}
|
|
129
|
+
return {
|
|
130
|
+
formatVersion: 1,
|
|
131
|
+
deterministicVersion: config.deterministicVersion,
|
|
132
|
+
tickRate: config.tickRate,
|
|
133
|
+
playerCount: config.playerCount,
|
|
134
|
+
inputSchemaId: config.inputSchemaId,
|
|
135
|
+
stateSchemaId: config.stateSchemaId,
|
|
136
|
+
engineIdentityHash: config.engineIdentityHash ?? (0, canonical_js_1.defaultChecksum)({
|
|
137
|
+
deterministicVersion: config.deterministicVersion,
|
|
138
|
+
inputSchemaId: config.inputSchemaId,
|
|
139
|
+
stateSchemaId: config.stateSchemaId,
|
|
140
|
+
}),
|
|
141
|
+
assetDbHash: config.assetDbHash ?? (0, canonical_js_1.defaultChecksum)('asset-db:none'),
|
|
142
|
+
configHash: config.configHash ?? (0, canonical_js_1.defaultChecksum)({
|
|
143
|
+
tickRate: config.tickRate,
|
|
144
|
+
playerCount: config.playerCount,
|
|
145
|
+
checksumIntervalFrames: checksumInterval,
|
|
146
|
+
snapshotBufferSize,
|
|
147
|
+
}),
|
|
148
|
+
metadata: config.replayMetadata ?? { package: 'rundot-syncplay' },
|
|
149
|
+
randomSeed,
|
|
150
|
+
initialState: (0, canonical_js_1.canonicalize)(config.canonicalizeState?.(initialState) ?? initialState),
|
|
151
|
+
inputFrames: mutable.inputFrames.map((inputFrame) => cloneReplayRecord(inputFrame)),
|
|
152
|
+
commandHistory: mutable.commandFrames.map((commandFrame) => cloneReplayRecord(commandFrame)),
|
|
153
|
+
checksums: mutable.checksums.map((checksum) => cloneReplayRecord(checksum)),
|
|
154
|
+
debugEvents: [
|
|
155
|
+
...mutable.commandFrames.map((commandFrame) => ({
|
|
156
|
+
frame: commandFrame.frame,
|
|
157
|
+
type: 'commands',
|
|
158
|
+
payload: (0, canonical_js_1.canonicalize)({ count: commandFrame.commands.length }),
|
|
159
|
+
})),
|
|
160
|
+
...mutable.checksums.map((checksum) => ({
|
|
161
|
+
frame: checksum.frame,
|
|
162
|
+
type: 'checksum',
|
|
163
|
+
payload: (0, canonical_js_1.canonicalize)({ checksum: checksum.checksum }),
|
|
164
|
+
})),
|
|
165
|
+
],
|
|
166
|
+
};
|
|
167
|
+
},
|
|
168
|
+
getSnapshot(frame) {
|
|
169
|
+
const snapshot = mutable.snapshots.get(frame);
|
|
170
|
+
return snapshot === undefined ? undefined : cloneSnapshot(snapshot);
|
|
171
|
+
},
|
|
172
|
+
restoreToFrame(frame) {
|
|
173
|
+
const snapshot = mutable.snapshots.get(frame);
|
|
174
|
+
if (snapshot === undefined) {
|
|
175
|
+
throw new errors_js_1.SyncplayError(`No snapshot exists for frame ${frame}`, {
|
|
176
|
+
code: 'session.missing-snapshot',
|
|
177
|
+
frame,
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
mutable.frame = snapshot.frame;
|
|
181
|
+
mutable.state = (0, canonical_js_1.cloneCanonical)(snapshot.state);
|
|
182
|
+
mutable.inputs = snapshot.inputs.map((input) => cloneInput(config, input));
|
|
183
|
+
mutable.inputFlags = Array.from({ length: config.playerCount }, (_, slot) => defaultInputFlags(slot));
|
|
184
|
+
mutable.random.restore({ state: snapshot.randomState });
|
|
185
|
+
mutable.accumulatedMs = 0;
|
|
186
|
+
mutable.inputFrames = mutable.inputFrames.filter((inputFrame) => inputFrame.frame < frame);
|
|
187
|
+
mutable.commandFrames = mutable.commandFrames.filter((commandFrame) => commandFrame.frame < frame);
|
|
188
|
+
mutable.pendingCommandFrames = new Map([...mutable.pendingCommandFrames.entries()].filter(([pendingFrame]) => pendingFrame < frame));
|
|
189
|
+
mutable.checksums = mutable.checksums.filter((checksum) => checksum.frame <= frame);
|
|
190
|
+
pruneSnapshotsAfterFrame(mutable, frame);
|
|
191
|
+
},
|
|
192
|
+
restoreSnapshot(snapshot) {
|
|
193
|
+
(0, errors_js_1.invariant)(Number.isInteger(snapshot.frame) && snapshot.frame >= 0, 'External snapshot frame must be a non-negative integer', 'session.invalid-snapshot-frame');
|
|
194
|
+
(0, errors_js_1.invariant)(snapshot.inputs.length === config.playerCount, 'External snapshot inputs must cover every player slot', 'session.invalid-snapshot-inputs');
|
|
195
|
+
mutable.frame = snapshot.frame;
|
|
196
|
+
mutable.state = deserializeState(config, snapshot.stateBytes);
|
|
197
|
+
mutable.inputs = snapshot.inputs.map((input) => cloneInput(config, input));
|
|
198
|
+
mutable.inputFlags = Array.from({ length: config.playerCount }, (_, slot) => defaultInputFlags(slot));
|
|
199
|
+
mutable.random.restore({ state: snapshot.randomState });
|
|
200
|
+
mutable.accumulatedMs = 0;
|
|
201
|
+
mutable.inputFrames = [];
|
|
202
|
+
mutable.commandFrames = [];
|
|
203
|
+
mutable.pendingCommandFrames = new Map();
|
|
204
|
+
mutable.checksums = [];
|
|
205
|
+
mutable.snapshots = new Map();
|
|
206
|
+
mutable.snapshotOrder = [];
|
|
207
|
+
hydratedFromExternalSnapshot = true;
|
|
208
|
+
recordSnapshot(config, mutable, snapshotBufferSize);
|
|
209
|
+
},
|
|
210
|
+
getSnapshotFrames() {
|
|
211
|
+
return [...mutable.snapshotOrder];
|
|
212
|
+
},
|
|
213
|
+
};
|
|
214
|
+
recordSnapshot(config, mutable, snapshotBufferSize);
|
|
215
|
+
return session;
|
|
216
|
+
}
|
|
217
|
+
function runFrames(config, mutable, checksumInterval, snapshotBufferSize, replayRecordingMode, math, count) {
|
|
218
|
+
for (let index = 0; index < count; index += 1) {
|
|
219
|
+
const frame = mutable.frame;
|
|
220
|
+
const commandFrame = readPendingCommandFrame(mutable, frame);
|
|
221
|
+
const commands = commandFrame?.commands ?? [];
|
|
222
|
+
const inputFlags = mutable.inputFlags.map((flags, slot) => normalizeInputFlags(slot, {
|
|
223
|
+
...flags,
|
|
224
|
+
commandInput: commands.some((command) => commandTargetsSlot(command, slot)),
|
|
225
|
+
}));
|
|
226
|
+
const effectiveInputs = mutable.inputs.map((input, slot) => inputFlags[slot].playerPresent ? cloneInput(config, input) : cloneInput(config, config.defaultInput));
|
|
227
|
+
const inputFrame = {
|
|
228
|
+
frame,
|
|
229
|
+
inputs: effectiveInputs.map((input) => (0, canonical_js_1.canonicalize)(config.canonicalizeInput?.(input) ?? input)),
|
|
230
|
+
inputFlags,
|
|
231
|
+
};
|
|
232
|
+
config.onInputFrameRecorded?.(inputFrame);
|
|
233
|
+
if (replayRecordingMode === 'memory') {
|
|
234
|
+
mutable.inputFrames.push(inputFrame);
|
|
235
|
+
}
|
|
236
|
+
if (commandFrame !== undefined) {
|
|
237
|
+
config.onCommandFrameRecorded?.(commandFrame);
|
|
238
|
+
if (replayRecordingMode === 'memory') {
|
|
239
|
+
mutable.commandFrames.push(commandFrame);
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
const stepContext = {
|
|
243
|
+
frame,
|
|
244
|
+
dtTicks: 1,
|
|
245
|
+
random: mutable.random,
|
|
246
|
+
math,
|
|
247
|
+
commands,
|
|
248
|
+
};
|
|
249
|
+
mutable.state =
|
|
250
|
+
config.runtimeGuards === true
|
|
251
|
+
? (0, runtime_guards_js_1.withRuntimeDeterminismGuards)(frame, () => config.step(mutable.state, effectiveInputs, frame, stepContext))
|
|
252
|
+
: config.step(mutable.state, effectiveInputs, frame, stepContext);
|
|
253
|
+
mutable.frame += 1;
|
|
254
|
+
const checksum = getChecksum(config, mutable.state);
|
|
255
|
+
if (mutable.frame % checksumInterval === 0) {
|
|
256
|
+
const checksumRecord = { frame: mutable.frame, checksum };
|
|
257
|
+
config.onChecksumRecorded?.(checksumRecord);
|
|
258
|
+
if (replayRecordingMode === 'memory') {
|
|
259
|
+
mutable.checksums.push(checksumRecord);
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
recordSnapshot(config, mutable, snapshotBufferSize, checksum);
|
|
263
|
+
mutable.inputFlags = inputFlags.map((flags, slot) => ({ ...defaultInputFlags(slot), repeatedPrevious: flags.playerPresent }));
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
function readPendingCommandFrame(mutable, frame) {
|
|
267
|
+
const commands = mutable.pendingCommandFrames.get(frame);
|
|
268
|
+
mutable.pendingCommandFrames.delete(frame);
|
|
269
|
+
if (commands === undefined || commands.length === 0) {
|
|
270
|
+
return undefined;
|
|
271
|
+
}
|
|
272
|
+
return {
|
|
273
|
+
frame,
|
|
274
|
+
commands: commands.map((command) => cloneReplayRecord(command)),
|
|
275
|
+
};
|
|
276
|
+
}
|
|
277
|
+
function cloneReplayRecord(value) {
|
|
278
|
+
return JSON.parse(JSON.stringify(value));
|
|
279
|
+
}
|
|
280
|
+
function defaultInputFlags(slot) {
|
|
281
|
+
return {
|
|
282
|
+
slot,
|
|
283
|
+
playerPresent: true,
|
|
284
|
+
repeatedPrevious: false,
|
|
285
|
+
replacedByServer: false,
|
|
286
|
+
commandInput: false,
|
|
287
|
+
};
|
|
288
|
+
}
|
|
289
|
+
function normalizeInputFlags(slot, flags) {
|
|
290
|
+
return {
|
|
291
|
+
slot,
|
|
292
|
+
playerPresent: flags.playerPresent === true,
|
|
293
|
+
repeatedPrevious: flags.repeatedPrevious === true,
|
|
294
|
+
replacedByServer: flags.replacedByServer === true,
|
|
295
|
+
commandInput: flags.commandInput === true,
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
function commandTargetsSlot(command, slot) {
|
|
299
|
+
if (typeof command !== 'object' || command === null || Array.isArray(command)) {
|
|
300
|
+
return false;
|
|
301
|
+
}
|
|
302
|
+
const record = command;
|
|
303
|
+
return record.slot === slot;
|
|
304
|
+
}
|
|
305
|
+
function validateConfig(config) {
|
|
306
|
+
(0, errors_js_1.invariant)([10, 20, 30, 60].includes(config.tickRate), 'tickRate must be one of 10, 20, 30, or 60', 'config.tick-rate');
|
|
307
|
+
(0, errors_js_1.invariant)(Number.isInteger(config.playerCount) && config.playerCount >= 1 && config.playerCount <= 128, 'playerCount must be an integer between 1 and 128', 'config.player-count');
|
|
308
|
+
(0, errors_js_1.invariant)(config.inputSchemaId.length > 0, 'inputSchemaId is required', 'config.input-schema');
|
|
309
|
+
(0, errors_js_1.invariant)(config.stateSchemaId.length > 0, 'stateSchemaId is required', 'config.state-schema');
|
|
310
|
+
(0, errors_js_1.invariant)(config.deterministicVersion.length > 0, 'deterministicVersion is required', 'config.version');
|
|
311
|
+
(0, errors_js_1.invariant)(config.checksumIntervalFrames === undefined ||
|
|
312
|
+
(Number.isInteger(config.checksumIntervalFrames) && config.checksumIntervalFrames > 0), 'checksumIntervalFrames must be a positive integer', 'config.checksum-interval');
|
|
313
|
+
(0, errors_js_1.invariant)(config.snapshotBufferSize === undefined ||
|
|
314
|
+
(Number.isInteger(config.snapshotBufferSize) && config.snapshotBufferSize >= 1), 'snapshotBufferSize must be a positive integer', 'config.snapshot-buffer');
|
|
315
|
+
}
|
|
316
|
+
function pruneSnapshotsAfterFrame(mutable, frame) {
|
|
317
|
+
mutable.snapshotOrder = mutable.snapshotOrder.filter((snapshotFrame) => {
|
|
318
|
+
if (snapshotFrame > frame) {
|
|
319
|
+
mutable.snapshots.delete(snapshotFrame);
|
|
320
|
+
return false;
|
|
321
|
+
}
|
|
322
|
+
return true;
|
|
323
|
+
});
|
|
324
|
+
}
|
|
325
|
+
function getChecksum(config, state) {
|
|
326
|
+
return String(config.checksum?.(state) ?? (0, canonical_js_1.defaultChecksum)(config.canonicalizeState?.(state) ?? state));
|
|
327
|
+
}
|
|
328
|
+
function cloneInput(config, input) {
|
|
329
|
+
return (0, canonical_js_1.cloneCanonical)(input);
|
|
330
|
+
}
|
|
331
|
+
function serializeState(config, state) {
|
|
332
|
+
return config.serializeState?.(state) ?? (0, canonical_js_1.encodeCanonicalBytes)(config.canonicalizeState?.(state) ?? state);
|
|
333
|
+
}
|
|
334
|
+
function deserializeState(config, bytes) {
|
|
335
|
+
return config.deserializeState?.(bytes) ?? (0, canonical_js_1.decodeCanonicalBytes)(bytes);
|
|
336
|
+
}
|
|
337
|
+
function recordSnapshot(config, mutable, snapshotBufferSize, checksum) {
|
|
338
|
+
const stateBytes = serializeState(config, mutable.state);
|
|
339
|
+
const snapshot = {
|
|
340
|
+
frame: mutable.frame,
|
|
341
|
+
inputs: mutable.inputs.map((input) => cloneInput(config, input)),
|
|
342
|
+
checksum: checksum ?? getChecksum(config, mutable.state),
|
|
343
|
+
state: deserializeState(config, stateBytes),
|
|
344
|
+
stateBytes,
|
|
345
|
+
randomState: mutable.random.snapshot().state,
|
|
346
|
+
};
|
|
347
|
+
const alreadyStored = mutable.snapshots.has(snapshot.frame);
|
|
348
|
+
mutable.snapshots.set(snapshot.frame, snapshot);
|
|
349
|
+
/* istanbul ignore else -- public session flow records at most one snapshot per frame. */
|
|
350
|
+
if (!alreadyStored) {
|
|
351
|
+
mutable.snapshotOrder.push(snapshot.frame);
|
|
352
|
+
}
|
|
353
|
+
while (mutable.snapshotOrder.length > snapshotBufferSize) {
|
|
354
|
+
const evictedFrame = mutable.snapshotOrder.shift();
|
|
355
|
+
/* istanbul ignore else -- shift cannot be undefined immediately after the length check above. */
|
|
356
|
+
if (evictedFrame !== undefined) {
|
|
357
|
+
mutable.snapshots.delete(evictedFrame);
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
function cloneSnapshot(snapshot) {
|
|
362
|
+
return {
|
|
363
|
+
frame: snapshot.frame,
|
|
364
|
+
inputs: snapshot.inputs.map((input) => (0, canonical_js_1.cloneCanonical)(input)),
|
|
365
|
+
checksum: snapshot.checksum,
|
|
366
|
+
state: (0, canonical_js_1.cloneCanonical)(snapshot.state),
|
|
367
|
+
stateBytes: new Uint8Array(snapshot.stateBytes),
|
|
368
|
+
randomState: snapshot.randomState,
|
|
369
|
+
};
|
|
370
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"commonjs"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.createPauseController = createPauseController;
|
|
4
|
+
function createPauseController(options) {
|
|
5
|
+
const step = (state, inputs, frame, ctx) => {
|
|
6
|
+
const previousHeld = state.pause.toggleHeld;
|
|
7
|
+
let risingEdges = 0;
|
|
8
|
+
const toggleHeld = inputs.map((input, slot) => {
|
|
9
|
+
const held = options.isPauseToggle(input, slot) === true;
|
|
10
|
+
if (held && previousHeld[slot] !== true) {
|
|
11
|
+
risingEdges += 1;
|
|
12
|
+
}
|
|
13
|
+
return held;
|
|
14
|
+
});
|
|
15
|
+
const flips = risingEdges % 2 === 1;
|
|
16
|
+
const status = flips
|
|
17
|
+
? state.pause.status === 'running' ? 'paused' : 'running'
|
|
18
|
+
: state.pause.status;
|
|
19
|
+
const toggleCount = state.pause.toggleCount + risingEdges;
|
|
20
|
+
if (status === 'paused') {
|
|
21
|
+
return {
|
|
22
|
+
pause: { status, pausedFrames: state.pause.pausedFrames + 1, toggleCount, toggleHeld },
|
|
23
|
+
game: state.game,
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return {
|
|
27
|
+
pause: { status, pausedFrames: state.pause.pausedFrames, toggleCount, toggleHeld },
|
|
28
|
+
game: options.wrap(state.game, inputs, frame, ctx),
|
|
29
|
+
};
|
|
30
|
+
};
|
|
31
|
+
return {
|
|
32
|
+
wrapInitialState(gameState) {
|
|
33
|
+
return {
|
|
34
|
+
pause: { status: 'running', pausedFrames: 0, toggleCount: 0, toggleHeld: [] },
|
|
35
|
+
game: gameState,
|
|
36
|
+
};
|
|
37
|
+
},
|
|
38
|
+
step,
|
|
39
|
+
isPaused(state) {
|
|
40
|
+
return state.pause.status === 'paused';
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
}
|