@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,147 @@
|
|
|
1
|
+
import { defaultChecksum } from './canonical.js';
|
|
2
|
+
import { invariant } from './errors.js';
|
|
3
|
+
export function createInputAuthority(config) {
|
|
4
|
+
invariant(Number.isInteger(config.playerCount) && config.playerCount >= 1, 'playerCount must be a positive integer', 'input-authority.player-count');
|
|
5
|
+
invariant(Number.isInteger(config.hardToleranceTicks) && config.hardToleranceTicks >= 0, 'hardToleranceTicks must be a non-negative integer', 'input-authority.hard-tolerance');
|
|
6
|
+
const playerCount = config.playerCount;
|
|
7
|
+
const maxFutureTicks = config.maxFutureTicks ?? Number.POSITIVE_INFINITY;
|
|
8
|
+
const retainedConfirmedFrames = config.retainedConfirmedFrames ?? 512;
|
|
9
|
+
const pending = new Map();
|
|
10
|
+
const lastConfirmedInput = Array.from({ length: playerCount }, () => config.neutralInput);
|
|
11
|
+
const retained = new Map();
|
|
12
|
+
let confirmedThrough = -1;
|
|
13
|
+
let earliestRetained = -1;
|
|
14
|
+
let confirmedTicks = 0;
|
|
15
|
+
if (config.restoreFrom !== undefined) {
|
|
16
|
+
const { confirmedThrough: restoredThrough, frames } = config.restoreFrom;
|
|
17
|
+
invariant(frames.length > 0, 'restoreFrom requires at least one frame', 'input-authority.restore-empty');
|
|
18
|
+
for (let index = 0; index < frames.length; index += 1) {
|
|
19
|
+
const frame = frames[index];
|
|
20
|
+
invariant(Number.isInteger(frame.tick) && (index === 0 || frame.tick === frames[index - 1].tick + 1), 'restoreFrom frames must be tick-contiguous', 'input-authority.restore-gap');
|
|
21
|
+
invariant(frame.inputs.length === playerCount, 'restoreFrom frame inputs must cover every player slot', 'input-authority.restore-inputs');
|
|
22
|
+
retained.set(frame.tick, frame);
|
|
23
|
+
}
|
|
24
|
+
invariant(frames[frames.length - 1].tick === restoredThrough, 'restoreFrom frames must end at the restored frontier', 'input-authority.restore-frontier');
|
|
25
|
+
earliestRetained = frames[0].tick;
|
|
26
|
+
confirmedThrough = restoredThrough;
|
|
27
|
+
const lastFrame = frames[frames.length - 1];
|
|
28
|
+
for (let slot = 0; slot < playerCount; slot += 1) {
|
|
29
|
+
lastConfirmedInput[slot] = lastFrame.inputs[slot];
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
let substitutedInputs = 0;
|
|
33
|
+
let droppedLateInputs = 0;
|
|
34
|
+
let rejectedFutureInputs = 0;
|
|
35
|
+
function receiveInput(slot, tick, input) {
|
|
36
|
+
if (!Number.isInteger(slot) || slot < 0 || slot >= playerCount) {
|
|
37
|
+
return { accepted: false, reason: 'slot-out-of-range' };
|
|
38
|
+
}
|
|
39
|
+
if (!Number.isInteger(tick) || tick < 0) {
|
|
40
|
+
return { accepted: false, reason: 'invalid-tick' };
|
|
41
|
+
}
|
|
42
|
+
if (tick <= confirmedThrough) {
|
|
43
|
+
droppedLateInputs += 1;
|
|
44
|
+
return { accepted: false, reason: 'tick-already-confirmed' };
|
|
45
|
+
}
|
|
46
|
+
if (tick > confirmedThrough + maxFutureTicks) {
|
|
47
|
+
rejectedFutureInputs += 1;
|
|
48
|
+
return { accepted: false, reason: 'tick-too-far-ahead' };
|
|
49
|
+
}
|
|
50
|
+
let slots = pending.get(tick);
|
|
51
|
+
if (slots === undefined) {
|
|
52
|
+
slots = Array.from({ length: playerCount }, () => undefined);
|
|
53
|
+
pending.set(tick, slots);
|
|
54
|
+
}
|
|
55
|
+
slots[slot] = input;
|
|
56
|
+
return { accepted: true };
|
|
57
|
+
}
|
|
58
|
+
function confirmTick(tick) {
|
|
59
|
+
const slots = pending.get(tick);
|
|
60
|
+
const inputs = [];
|
|
61
|
+
const substitutedSlots = [];
|
|
62
|
+
for (let slot = 0; slot < playerCount; slot += 1) {
|
|
63
|
+
const submitted = slots?.[slot];
|
|
64
|
+
if (submitted === undefined) {
|
|
65
|
+
inputs.push(lastConfirmedInput[slot]);
|
|
66
|
+
substitutedSlots.push(slot);
|
|
67
|
+
substitutedInputs += 1;
|
|
68
|
+
}
|
|
69
|
+
else {
|
|
70
|
+
inputs.push(submitted);
|
|
71
|
+
lastConfirmedInput[slot] = submitted;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
pending.delete(tick);
|
|
75
|
+
const frame = {
|
|
76
|
+
tick,
|
|
77
|
+
inputs,
|
|
78
|
+
substitutedSlots,
|
|
79
|
+
checksum: String(defaultChecksum({ tick, inputs })),
|
|
80
|
+
};
|
|
81
|
+
retained.set(tick, frame);
|
|
82
|
+
if (earliestRetained === -1) {
|
|
83
|
+
earliestRetained = tick;
|
|
84
|
+
}
|
|
85
|
+
if (retained.size > retainedConfirmedFrames) {
|
|
86
|
+
const oldest = tick - retainedConfirmedFrames;
|
|
87
|
+
if (retained.delete(oldest)) {
|
|
88
|
+
earliestRetained = oldest + 1;
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
confirmedThrough = tick;
|
|
92
|
+
confirmedTicks += 1;
|
|
93
|
+
return frame;
|
|
94
|
+
}
|
|
95
|
+
function advance(now) {
|
|
96
|
+
const frames = [];
|
|
97
|
+
let next = confirmedThrough + 1;
|
|
98
|
+
while (next <= now) {
|
|
99
|
+
const slots = pending.get(next);
|
|
100
|
+
const allPresent = slots !== undefined && slots.every((value) => value !== undefined);
|
|
101
|
+
const deadlinePassed = now - next >= config.hardToleranceTicks;
|
|
102
|
+
if (!allPresent && !deadlinePassed) {
|
|
103
|
+
break;
|
|
104
|
+
}
|
|
105
|
+
frames.push(confirmTick(next));
|
|
106
|
+
next += 1;
|
|
107
|
+
}
|
|
108
|
+
return frames;
|
|
109
|
+
}
|
|
110
|
+
return {
|
|
111
|
+
playerCount,
|
|
112
|
+
get confirmedThrough() {
|
|
113
|
+
return confirmedThrough;
|
|
114
|
+
},
|
|
115
|
+
receiveInput,
|
|
116
|
+
advance,
|
|
117
|
+
get earliestRetainedTick() {
|
|
118
|
+
return earliestRetained;
|
|
119
|
+
},
|
|
120
|
+
confirmedFrame(tick) {
|
|
121
|
+
return retained.get(tick);
|
|
122
|
+
},
|
|
123
|
+
confirmedFrameRange(from, to) {
|
|
124
|
+
invariant(Number.isInteger(from) && Number.isInteger(to) && from <= to, 'confirmedFrameRange requires an ordered integer range', 'input-authority.range-invalid');
|
|
125
|
+
const frames = [];
|
|
126
|
+
for (let tick = from; tick <= to; tick += 1) {
|
|
127
|
+
const frame = retained.get(tick);
|
|
128
|
+
invariant(frame !== undefined, `confirmed frame ${tick} is no longer retained`, 'input-authority.range-unavailable');
|
|
129
|
+
frames.push(frame);
|
|
130
|
+
}
|
|
131
|
+
return frames;
|
|
132
|
+
},
|
|
133
|
+
pruneConfirmedBefore(tick) {
|
|
134
|
+
invariant(Number.isInteger(tick) && tick <= confirmedThrough + 1, 'pruneConfirmedBefore tick must be an integer at or below the confirmed frontier + 1', 'input-authority.prune-tick');
|
|
135
|
+
if (earliestRetained === -1 || tick <= earliestRetained) {
|
|
136
|
+
return;
|
|
137
|
+
}
|
|
138
|
+
for (let t = earliestRetained; t < tick; t += 1) {
|
|
139
|
+
retained.delete(t);
|
|
140
|
+
}
|
|
141
|
+
earliestRetained = tick;
|
|
142
|
+
},
|
|
143
|
+
stats() {
|
|
144
|
+
return { confirmedTicks, substitutedInputs, droppedLateInputs, rejectedFutureInputs };
|
|
145
|
+
},
|
|
146
|
+
};
|
|
147
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { CanonicalValue } from './canonical.js';
|
|
2
|
+
export interface DeterministicButtonState {
|
|
3
|
+
readonly down: boolean;
|
|
4
|
+
readonly previousDown: boolean;
|
|
5
|
+
readonly pressedFrame: number;
|
|
6
|
+
readonly releasedFrame: number;
|
|
7
|
+
}
|
|
8
|
+
export interface DeterministicButtonStep {
|
|
9
|
+
readonly state: DeterministicButtonState;
|
|
10
|
+
readonly isDown: boolean;
|
|
11
|
+
readonly wasPressed: boolean;
|
|
12
|
+
readonly wasReleased: boolean;
|
|
13
|
+
readonly checksum: string;
|
|
14
|
+
}
|
|
15
|
+
export interface DeterministicButtonFixtureResult {
|
|
16
|
+
readonly checksum: string;
|
|
17
|
+
readonly restoredChecksum: string;
|
|
18
|
+
readonly rollbackEquivalent: boolean;
|
|
19
|
+
readonly pressedCount: number;
|
|
20
|
+
readonly releasedCount: number;
|
|
21
|
+
readonly heldFrames: number;
|
|
22
|
+
readonly repeatedFrames: number;
|
|
23
|
+
readonly transitionsStable: boolean;
|
|
24
|
+
}
|
|
25
|
+
export interface DeterministicDescriptorButtonFixtureResult extends DeterministicButtonFixtureResult {
|
|
26
|
+
readonly descriptorArtifactId: string;
|
|
27
|
+
readonly descriptorButtonFields: number;
|
|
28
|
+
readonly serializedBytes: number;
|
|
29
|
+
readonly defaultApplied: boolean;
|
|
30
|
+
readonly schemaRoundTripStable: boolean;
|
|
31
|
+
}
|
|
32
|
+
export declare const deterministicButtonInitialState: DeterministicButtonState;
|
|
33
|
+
export declare function stepDeterministicButton(state: DeterministicButtonState, down: boolean, frame: number): DeterministicButtonStep;
|
|
34
|
+
export declare function runDeterministicButtonFixture(frames?: number): DeterministicButtonFixtureResult;
|
|
35
|
+
export declare function runDeterministicDescriptorButtonFixture(frames?: number): DeterministicDescriptorButtonFixtureResult;
|
|
36
|
+
export declare function readDeterministicDescriptorButtonValue(value: CanonicalValue): boolean;
|
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
import { defaultChecksum } from './canonical.js';
|
|
2
|
+
import { createSchemaArtifacts } from './schema-artifacts.js';
|
|
3
|
+
export const deterministicButtonInitialState = {
|
|
4
|
+
down: false,
|
|
5
|
+
previousDown: false,
|
|
6
|
+
pressedFrame: -1,
|
|
7
|
+
releasedFrame: -1,
|
|
8
|
+
};
|
|
9
|
+
export function stepDeterministicButton(state, down, frame) {
|
|
10
|
+
if (!Number.isInteger(frame) || frame < 0) {
|
|
11
|
+
throw new Error('Deterministic button frame must be a non-negative integer');
|
|
12
|
+
}
|
|
13
|
+
const wasPressed = down && !state.down;
|
|
14
|
+
const wasReleased = !down && state.down;
|
|
15
|
+
const next = {
|
|
16
|
+
down,
|
|
17
|
+
previousDown: state.down,
|
|
18
|
+
pressedFrame: wasPressed ? frame : state.pressedFrame,
|
|
19
|
+
releasedFrame: wasReleased ? frame : state.releasedFrame,
|
|
20
|
+
};
|
|
21
|
+
return {
|
|
22
|
+
state: next,
|
|
23
|
+
isDown: next.down,
|
|
24
|
+
wasPressed,
|
|
25
|
+
wasReleased,
|
|
26
|
+
checksum: defaultChecksum({ frame, next, wasPressed, wasReleased }),
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function runDeterministicButtonFixture(frames = 512) {
|
|
30
|
+
let state = deterministicButtonInitialState;
|
|
31
|
+
let snapshot = state;
|
|
32
|
+
let pressedCount = 0;
|
|
33
|
+
let releasedCount = 0;
|
|
34
|
+
let heldFrames = 0;
|
|
35
|
+
let repeatedFrames = 0;
|
|
36
|
+
const transitions = [];
|
|
37
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
38
|
+
if (frame === Math.floor(frames / 2)) {
|
|
39
|
+
snapshot = state;
|
|
40
|
+
}
|
|
41
|
+
const down = buttonDownAt(frame);
|
|
42
|
+
const step = stepDeterministicButton(state, down, frame);
|
|
43
|
+
state = step.state;
|
|
44
|
+
pressedCount += step.wasPressed ? 1 : 0;
|
|
45
|
+
releasedCount += step.wasReleased ? 1 : 0;
|
|
46
|
+
heldFrames += step.isDown ? 1 : 0;
|
|
47
|
+
repeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
48
|
+
if (step.wasPressed || step.wasReleased) {
|
|
49
|
+
transitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
const checksum = defaultChecksum({ state, pressedCount, releasedCount, heldFrames, repeatedFrames, transitions });
|
|
53
|
+
let restored = snapshot;
|
|
54
|
+
let restoredPressedCount = 0;
|
|
55
|
+
let restoredReleasedCount = 0;
|
|
56
|
+
let restoredHeldFrames = 0;
|
|
57
|
+
let restoredRepeatedFrames = 0;
|
|
58
|
+
const restoredTransitions = [];
|
|
59
|
+
for (let frame = Math.floor(frames / 2); frame < frames; frame += 1) {
|
|
60
|
+
const step = stepDeterministicButton(restored, buttonDownAt(frame), frame);
|
|
61
|
+
restored = step.state;
|
|
62
|
+
restoredPressedCount += step.wasPressed ? 1 : 0;
|
|
63
|
+
restoredReleasedCount += step.wasReleased ? 1 : 0;
|
|
64
|
+
restoredHeldFrames += step.isDown ? 1 : 0;
|
|
65
|
+
restoredRepeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
66
|
+
if (step.wasPressed || step.wasReleased) {
|
|
67
|
+
restoredTransitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
const prefix = replayButtonPrefix(Math.floor(frames / 2));
|
|
71
|
+
const restoredChecksum = defaultChecksum({
|
|
72
|
+
state: restored,
|
|
73
|
+
pressedCount: prefix.pressedCount + restoredPressedCount,
|
|
74
|
+
releasedCount: prefix.releasedCount + restoredReleasedCount,
|
|
75
|
+
heldFrames: prefix.heldFrames + restoredHeldFrames,
|
|
76
|
+
repeatedFrames: prefix.repeatedFrames + restoredRepeatedFrames,
|
|
77
|
+
transitions: [...prefix.transitions, ...restoredTransitions],
|
|
78
|
+
});
|
|
79
|
+
return {
|
|
80
|
+
checksum,
|
|
81
|
+
restoredChecksum,
|
|
82
|
+
rollbackEquivalent: checksum === restoredChecksum,
|
|
83
|
+
pressedCount,
|
|
84
|
+
releasedCount,
|
|
85
|
+
heldFrames,
|
|
86
|
+
repeatedFrames,
|
|
87
|
+
transitionsStable: pressedCount === releasedCount && transitions.length === pressedCount + releasedCount,
|
|
88
|
+
};
|
|
89
|
+
}
|
|
90
|
+
export function runDeterministicDescriptorButtonFixture(frames = 512) {
|
|
91
|
+
const artifacts = createSchemaArtifacts(buttonDescriptor());
|
|
92
|
+
const missingDefault = artifacts.deserializeInput(artifacts.serializeInput({ axis: 0 }));
|
|
93
|
+
let state = deterministicButtonInitialState;
|
|
94
|
+
let snapshot = state;
|
|
95
|
+
let pressedCount = 0;
|
|
96
|
+
let releasedCount = 0;
|
|
97
|
+
let heldFrames = 0;
|
|
98
|
+
let repeatedFrames = 0;
|
|
99
|
+
let serializedBytes = 0;
|
|
100
|
+
const transitions = [];
|
|
101
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
102
|
+
if (frame === Math.floor(frames / 2)) {
|
|
103
|
+
snapshot = state;
|
|
104
|
+
}
|
|
105
|
+
const input = descriptorButtonInputAt(frame);
|
|
106
|
+
const serialized = artifacts.serializeInput(input);
|
|
107
|
+
const roundTrip = artifacts.deserializeInput(serialized);
|
|
108
|
+
serializedBytes += serialized.byteLength;
|
|
109
|
+
const step = stepDeterministicButton(state, buttonValue(roundTrip), frame);
|
|
110
|
+
state = step.state;
|
|
111
|
+
pressedCount += step.wasPressed ? 1 : 0;
|
|
112
|
+
releasedCount += step.wasReleased ? 1 : 0;
|
|
113
|
+
heldFrames += step.isDown ? 1 : 0;
|
|
114
|
+
repeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
115
|
+
if (step.wasPressed || step.wasReleased) {
|
|
116
|
+
transitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
const checksum = defaultChecksum({ state, pressedCount, releasedCount, heldFrames, repeatedFrames, transitions });
|
|
120
|
+
const prefix = replayDescriptorButtonPrefix(Math.floor(frames / 2), artifacts);
|
|
121
|
+
let restored = snapshot;
|
|
122
|
+
let restoredPressedCount = 0;
|
|
123
|
+
let restoredReleasedCount = 0;
|
|
124
|
+
let restoredHeldFrames = 0;
|
|
125
|
+
let restoredRepeatedFrames = 0;
|
|
126
|
+
const restoredTransitions = [];
|
|
127
|
+
for (let frame = Math.floor(frames / 2); frame < frames; frame += 1) {
|
|
128
|
+
const roundTrip = artifacts.deserializeInput(artifacts.serializeInput(descriptorButtonInputAt(frame)));
|
|
129
|
+
const step = stepDeterministicButton(restored, buttonValue(roundTrip), frame);
|
|
130
|
+
restored = step.state;
|
|
131
|
+
restoredPressedCount += step.wasPressed ? 1 : 0;
|
|
132
|
+
restoredReleasedCount += step.wasReleased ? 1 : 0;
|
|
133
|
+
restoredHeldFrames += step.isDown ? 1 : 0;
|
|
134
|
+
restoredRepeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
135
|
+
if (step.wasPressed || step.wasReleased) {
|
|
136
|
+
restoredTransitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
const restoredChecksum = defaultChecksum({
|
|
140
|
+
state: restored,
|
|
141
|
+
pressedCount: prefix.pressedCount + restoredPressedCount,
|
|
142
|
+
releasedCount: prefix.releasedCount + restoredReleasedCount,
|
|
143
|
+
heldFrames: prefix.heldFrames + restoredHeldFrames,
|
|
144
|
+
repeatedFrames: prefix.repeatedFrames + restoredRepeatedFrames,
|
|
145
|
+
transitions: [...prefix.transitions, ...restoredTransitions],
|
|
146
|
+
});
|
|
147
|
+
const roundTripA = artifacts.serializeInput({ axis: 1, jump: true });
|
|
148
|
+
const roundTripB = artifacts.serializeInput(artifacts.deserializeInput(roundTripA));
|
|
149
|
+
const descriptorArtifactId = artifacts.descriptor.artifactId;
|
|
150
|
+
const descriptorButtonFields = artifacts.descriptor.input.fields.filter((field) => field.kind === 'button').length;
|
|
151
|
+
const defaultApplied = buttonValue(missingDefault) === false;
|
|
152
|
+
const schemaRoundTripStable = defaultChecksum(roundTripA) === defaultChecksum(roundTripB);
|
|
153
|
+
return {
|
|
154
|
+
checksum,
|
|
155
|
+
restoredChecksum,
|
|
156
|
+
rollbackEquivalent: checksum === restoredChecksum,
|
|
157
|
+
pressedCount,
|
|
158
|
+
releasedCount,
|
|
159
|
+
heldFrames,
|
|
160
|
+
repeatedFrames,
|
|
161
|
+
transitionsStable: pressedCount === releasedCount && transitions.length === pressedCount + releasedCount,
|
|
162
|
+
descriptorArtifactId,
|
|
163
|
+
descriptorButtonFields,
|
|
164
|
+
serializedBytes,
|
|
165
|
+
defaultApplied,
|
|
166
|
+
schemaRoundTripStable,
|
|
167
|
+
};
|
|
168
|
+
}
|
|
169
|
+
function replayButtonPrefix(frames) {
|
|
170
|
+
let state = deterministicButtonInitialState;
|
|
171
|
+
let pressedCount = 0;
|
|
172
|
+
let releasedCount = 0;
|
|
173
|
+
let heldFrames = 0;
|
|
174
|
+
let repeatedFrames = 0;
|
|
175
|
+
const transitions = [];
|
|
176
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
177
|
+
const step = stepDeterministicButton(state, buttonDownAt(frame), frame);
|
|
178
|
+
state = step.state;
|
|
179
|
+
pressedCount += step.wasPressed ? 1 : 0;
|
|
180
|
+
releasedCount += step.wasReleased ? 1 : 0;
|
|
181
|
+
heldFrames += step.isDown ? 1 : 0;
|
|
182
|
+
repeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
183
|
+
if (step.wasPressed || step.wasReleased) {
|
|
184
|
+
transitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
return { pressedCount, releasedCount, heldFrames, repeatedFrames, transitions };
|
|
188
|
+
}
|
|
189
|
+
function replayDescriptorButtonPrefix(frames, artifacts) {
|
|
190
|
+
let state = deterministicButtonInitialState;
|
|
191
|
+
let pressedCount = 0;
|
|
192
|
+
let releasedCount = 0;
|
|
193
|
+
let heldFrames = 0;
|
|
194
|
+
let repeatedFrames = 0;
|
|
195
|
+
const transitions = [];
|
|
196
|
+
for (let frame = 0; frame < frames; frame += 1) {
|
|
197
|
+
const roundTrip = artifacts.deserializeInput(artifacts.serializeInput(descriptorButtonInputAt(frame)));
|
|
198
|
+
const step = stepDeterministicButton(state, buttonValue(roundTrip), frame);
|
|
199
|
+
state = step.state;
|
|
200
|
+
pressedCount += step.wasPressed ? 1 : 0;
|
|
201
|
+
releasedCount += step.wasReleased ? 1 : 0;
|
|
202
|
+
heldFrames += step.isDown ? 1 : 0;
|
|
203
|
+
repeatedFrames += step.isDown && !step.wasPressed ? 1 : 0;
|
|
204
|
+
if (step.wasPressed || step.wasReleased) {
|
|
205
|
+
transitions.push(`${frame}:${step.wasPressed ? 'pressed' : 'released'}`);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
return { pressedCount, releasedCount, heldFrames, repeatedFrames, transitions };
|
|
209
|
+
}
|
|
210
|
+
function buttonDownAt(frame) {
|
|
211
|
+
const phase = frame % 16;
|
|
212
|
+
return (phase >= 2 && phase <= 6) || phase === 10 || phase === 11;
|
|
213
|
+
}
|
|
214
|
+
function descriptorButtonInputAt(frame) {
|
|
215
|
+
return {
|
|
216
|
+
axis: (frame % 3) - 1,
|
|
217
|
+
jump: buttonDownAt(frame),
|
|
218
|
+
};
|
|
219
|
+
}
|
|
220
|
+
export function readDeterministicDescriptorButtonValue(value) {
|
|
221
|
+
if (value === null || typeof value !== 'object' || Array.isArray(value)) {
|
|
222
|
+
throw new Error('Descriptor button input did not deserialize to a boolean jump field');
|
|
223
|
+
}
|
|
224
|
+
const record = value;
|
|
225
|
+
if (typeof record.jump !== 'boolean') {
|
|
226
|
+
throw new Error('Descriptor button input did not deserialize to a boolean jump field');
|
|
227
|
+
}
|
|
228
|
+
return record.jump;
|
|
229
|
+
}
|
|
230
|
+
const buttonValue = readDeterministicDescriptorButtonValue;
|
|
231
|
+
function buttonDescriptor() {
|
|
232
|
+
return {
|
|
233
|
+
deterministicVersion: 'button-input@1',
|
|
234
|
+
tickRate: 60,
|
|
235
|
+
maxPlayers: 128,
|
|
236
|
+
input: {
|
|
237
|
+
id: 'button-input/v1',
|
|
238
|
+
fields: [
|
|
239
|
+
{ name: 'axis', kind: 'integer', policy: 'repeat', defaultValue: 0, min: -1, max: 1, bitPacking: { bits: 2, signed: true } },
|
|
240
|
+
{ name: 'jump', kind: 'button', policy: 'command', defaultValue: false, bitPacking: { bits: 1 } },
|
|
241
|
+
],
|
|
242
|
+
},
|
|
243
|
+
state: {
|
|
244
|
+
id: 'button-state/v1',
|
|
245
|
+
fields: [{ name: 'pressed', kind: 'integer', numericMode: 'integer', checksummed: true }],
|
|
246
|
+
},
|
|
247
|
+
systems: [{ id: 'button-input' }],
|
|
248
|
+
};
|
|
249
|
+
}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
import { type DeterministicSessionInputFlag } from './deterministic-session.js';
|
|
2
|
+
export type DeterministicInputTimelineLane = 'human' | 'bot' | 'absent' | 'spectator' | 'late-join';
|
|
3
|
+
export type DeterministicInputTimelinePrediction = 'explicit' | 'repeat-last' | 'neutral' | 'none';
|
|
4
|
+
export type DeterministicInputTimelineVerification = 'confirmed' | 'replacement' | 'absent';
|
|
5
|
+
export type DeterministicInputTimelineCorrection = 'none' | 'late-fill' | 'rollback-triggered';
|
|
6
|
+
export interface DeterministicInputTimelineRow {
|
|
7
|
+
readonly frame: number;
|
|
8
|
+
readonly slot: number;
|
|
9
|
+
readonly lane: DeterministicInputTimelineLane;
|
|
10
|
+
readonly prediction: DeterministicInputTimelinePrediction;
|
|
11
|
+
readonly verification: DeterministicInputTimelineVerification;
|
|
12
|
+
readonly correction: DeterministicInputTimelineCorrection;
|
|
13
|
+
readonly predictedValue: number;
|
|
14
|
+
readonly verifiedValue: number;
|
|
15
|
+
readonly flags: readonly DeterministicSessionInputFlag[];
|
|
16
|
+
}
|
|
17
|
+
export interface DeterministicInputTimelineNegativeCases {
|
|
18
|
+
readonly validPredictionAccepted: boolean;
|
|
19
|
+
readonly inactivePredictionRejected: boolean;
|
|
20
|
+
readonly malformedValueRejected: boolean;
|
|
21
|
+
readonly duplicateIngressRejected: boolean;
|
|
22
|
+
readonly duplicateAfterRestoreRejected: boolean;
|
|
23
|
+
readonly futureIngressRejected: boolean;
|
|
24
|
+
readonly staleIngressRejected: boolean;
|
|
25
|
+
readonly spectatorIngressRejected: boolean;
|
|
26
|
+
readonly badSenderRejected: boolean;
|
|
27
|
+
}
|
|
28
|
+
export interface DeterministicInputTimelineReport {
|
|
29
|
+
readonly schemaVersion: 1;
|
|
30
|
+
readonly kind: 'deterministic-input-timeline';
|
|
31
|
+
readonly result: 'pass' | 'fail';
|
|
32
|
+
readonly players: number;
|
|
33
|
+
readonly frames: number;
|
|
34
|
+
readonly seed: number;
|
|
35
|
+
readonly rowCount: number;
|
|
36
|
+
readonly rowHash: string;
|
|
37
|
+
readonly sampledRows: readonly DeterministicInputTimelineRow[];
|
|
38
|
+
readonly rows?: readonly DeterministicInputTimelineRow[];
|
|
39
|
+
readonly counts: Readonly<Record<string, number>>;
|
|
40
|
+
readonly negativeCases: DeterministicInputTimelineNegativeCases;
|
|
41
|
+
readonly replayVerified: boolean;
|
|
42
|
+
readonly replayChecksum: string;
|
|
43
|
+
readonly finalVerifiedChecksum: string;
|
|
44
|
+
readonly invariants: Readonly<Record<string, boolean>>;
|
|
45
|
+
readonly checksum: string;
|
|
46
|
+
}
|
|
47
|
+
export interface DeterministicInputTimelineOptions {
|
|
48
|
+
readonly players: number;
|
|
49
|
+
readonly frames: number;
|
|
50
|
+
readonly seed: number;
|
|
51
|
+
readonly includeRows?: boolean;
|
|
52
|
+
readonly sampleLimit?: number;
|
|
53
|
+
}
|
|
54
|
+
export interface DeterministicInputTimelineAccumulatorState {
|
|
55
|
+
readonly sum: number;
|
|
56
|
+
readonly frames: number;
|
|
57
|
+
readonly activeInputs: number;
|
|
58
|
+
}
|
|
59
|
+
export declare function runDeterministicInputTimelineFixture(options: DeterministicInputTimelineOptions): DeterministicInputTimelineReport;
|
|
60
|
+
export declare function evaluateDeterministicInputTimelineResult(invariantChecks: readonly boolean[]): 'pass' | 'fail';
|
|
61
|
+
export declare function accumulateDeterministicInputTimelineState(state: Partial<DeterministicInputTimelineAccumulatorState>, input: Partial<{
|
|
62
|
+
readonly sum: number;
|
|
63
|
+
readonly active: number;
|
|
64
|
+
}>): DeterministicInputTimelineAccumulatorState;
|
|
65
|
+
export declare function sortDeterministicInputTimelineFlags(flags: readonly DeterministicSessionInputFlag[]): readonly DeterministicSessionInputFlag[];
|