@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,412 @@
|
|
|
1
|
+
import { defaultChecksum } from './canonical.js';
|
|
2
|
+
import { createDeterministicLocalAuthorityRuntime } from './local-authority.js';
|
|
3
|
+
import { DeterministicRandom } from './random.js';
|
|
4
|
+
import { createDeterministicSessionCore, verifyDeterministicSessionRuntimeReplay, } from './deterministic-session.js';
|
|
5
|
+
export function runDeterministicInputTimelineFixture(options) {
|
|
6
|
+
validateInputTimelineOptions(options);
|
|
7
|
+
const descriptor = createInputTimelineDescriptor();
|
|
8
|
+
const systems = createInputTimelineSystems();
|
|
9
|
+
const session = createDeterministicSessionCore({
|
|
10
|
+
players: options.players,
|
|
11
|
+
descriptor,
|
|
12
|
+
systems,
|
|
13
|
+
seed: options.seed,
|
|
14
|
+
mapInputsToSingleton,
|
|
15
|
+
});
|
|
16
|
+
const random = new DeterministicRandom(options.seed);
|
|
17
|
+
const lateJoinSlot = options.players - 1;
|
|
18
|
+
const spectatorSlot = 0;
|
|
19
|
+
const botSlot = 1;
|
|
20
|
+
const absentSlot = 2;
|
|
21
|
+
const transientSlot = 3;
|
|
22
|
+
const lateJoinFrame = Math.max(1, Math.floor(options.frames / 4));
|
|
23
|
+
const removeFrame = Math.max(1, Math.floor(options.frames / 3));
|
|
24
|
+
const readdFrame = Math.min(options.frames - 1, Math.max(removeFrame + 1, Math.floor(options.frames / 2)));
|
|
25
|
+
const lastRepeatableValue = Array.from({ length: options.players }, () => 0);
|
|
26
|
+
const hasRepeatableValue = Array.from({ length: options.players }, () => false);
|
|
27
|
+
const rows = [];
|
|
28
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
29
|
+
session.joinAsSpectator(slot, `actor-${slot}`, 0);
|
|
30
|
+
if (slot !== spectatorSlot && slot !== absentSlot && slot !== lateJoinSlot) {
|
|
31
|
+
session.addPlayer(slot, `actor-${slot}`, 0);
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
35
|
+
if (frame === lateJoinFrame) {
|
|
36
|
+
session.addPlayer(lateJoinSlot, `actor-${lateJoinSlot}`, frame);
|
|
37
|
+
}
|
|
38
|
+
if (frame === removeFrame) {
|
|
39
|
+
session.removePlayer(transientSlot, `actor-${transientSlot}`, frame);
|
|
40
|
+
}
|
|
41
|
+
if (frame === readdFrame) {
|
|
42
|
+
session.addPlayer(transientSlot, `actor-${transientSlot}`, frame);
|
|
43
|
+
}
|
|
44
|
+
const pendingRows = [];
|
|
45
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
46
|
+
const slotState = classifySlot(frame, slot, {
|
|
47
|
+
spectatorSlot,
|
|
48
|
+
botSlot,
|
|
49
|
+
absentSlot,
|
|
50
|
+
transientSlot,
|
|
51
|
+
lateJoinSlot,
|
|
52
|
+
lateJoinFrame,
|
|
53
|
+
removeFrame,
|
|
54
|
+
readdFrame,
|
|
55
|
+
});
|
|
56
|
+
if (!slotState.active) {
|
|
57
|
+
session.replaceInput({ frame, slot, value: 0, flags: ['player-not-present'] });
|
|
58
|
+
pendingRows.push({
|
|
59
|
+
frame,
|
|
60
|
+
slot,
|
|
61
|
+
lane: slotState.lane,
|
|
62
|
+
prediction: 'none',
|
|
63
|
+
verification: 'absent',
|
|
64
|
+
correction: 'none',
|
|
65
|
+
predictedValue: 0,
|
|
66
|
+
verifiedValue: 0,
|
|
67
|
+
flags: ['player-not-present'],
|
|
68
|
+
});
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const predictedPolicy = shouldSubmitExplicitPrediction(frame, slot, options.seed) ? 'explicit' : fallbackPolicy(hasRepeatableValue[slot]);
|
|
72
|
+
const predictedValue = predictedPolicy === 'explicit'
|
|
73
|
+
? random.nextInt(-8, 8)
|
|
74
|
+
: predictedPolicy === 'repeat-last'
|
|
75
|
+
? lastRepeatableValue[slot]
|
|
76
|
+
: 0;
|
|
77
|
+
const predictedFlags = inputFlags(frame, slot, options.seed);
|
|
78
|
+
if (predictedPolicy === 'explicit') {
|
|
79
|
+
session.submitPredictedInput({ frame, slot, value: predictedValue, flags: predictedFlags });
|
|
80
|
+
}
|
|
81
|
+
const replacement = shouldReplaceInput(frame, slot, options.seed);
|
|
82
|
+
const verifiedValue = replacement
|
|
83
|
+
? (hasRepeatableValue[slot] ? lastRepeatableValue[slot] : 0)
|
|
84
|
+
: predictedValue + shouldLateFill(frame, slot, options.seed);
|
|
85
|
+
const verifiedFlags = replacement
|
|
86
|
+
? sortDeterministicInputTimelineFlags(['replaced-by-server', ...(hasRepeatableValue[slot] ? ['repeatable'] : [])])
|
|
87
|
+
: predictedFlags;
|
|
88
|
+
pendingRows.push({
|
|
89
|
+
frame,
|
|
90
|
+
slot,
|
|
91
|
+
lane: slotState.lane,
|
|
92
|
+
prediction: predictedPolicy,
|
|
93
|
+
verification: replacement ? 'replacement' : 'confirmed',
|
|
94
|
+
correction: correctionFor(predictedPolicy, predictedValue, verifiedValue),
|
|
95
|
+
predictedValue,
|
|
96
|
+
verifiedValue,
|
|
97
|
+
flags: verifiedFlags,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
session.predictTo(frame + 1);
|
|
101
|
+
for (const row of pendingRows) {
|
|
102
|
+
if (row.verification === 'absent') {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
const input = {
|
|
106
|
+
frame: row.frame,
|
|
107
|
+
slot: row.slot,
|
|
108
|
+
value: row.verifiedValue,
|
|
109
|
+
flags: row.flags,
|
|
110
|
+
};
|
|
111
|
+
if (row.verification === 'replacement') {
|
|
112
|
+
session.replaceInput(input);
|
|
113
|
+
}
|
|
114
|
+
else {
|
|
115
|
+
session.confirmInput(input);
|
|
116
|
+
}
|
|
117
|
+
if (row.flags.includes('repeatable')) {
|
|
118
|
+
hasRepeatableValue[row.slot] = true;
|
|
119
|
+
lastRepeatableValue[row.slot] = row.verifiedValue;
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
session.advanceVerifiedTo(frame + 1);
|
|
123
|
+
rows.push(...pendingRows);
|
|
124
|
+
}
|
|
125
|
+
const replay = session.exportReplay();
|
|
126
|
+
const replayVerification = verifyDeterministicSessionRuntimeReplay(replay, {
|
|
127
|
+
players: options.players,
|
|
128
|
+
descriptor,
|
|
129
|
+
systems,
|
|
130
|
+
seed: options.seed,
|
|
131
|
+
mapInputsToSingleton,
|
|
132
|
+
});
|
|
133
|
+
const counts = countRows(rows);
|
|
134
|
+
const negativeCases = runInputTimelineNegativeCases(options.players);
|
|
135
|
+
const rowHash = defaultChecksum(rows);
|
|
136
|
+
const invariants = {
|
|
137
|
+
allFramesCovered: counts.frames === options.frames,
|
|
138
|
+
allSlotsCovered: counts.slots === options.players,
|
|
139
|
+
rowCountStable: rows.length === options.frames * options.players,
|
|
140
|
+
orderStable: rows.every((row, index) => index === 0 || compareRows(rows[index - 1], row) <= 0),
|
|
141
|
+
explicitPredictionCovered: counts.explicitPredictions > 0,
|
|
142
|
+
neutralPredictionCovered: counts.neutralPredictions > 0,
|
|
143
|
+
repeatLastPredictionCovered: counts.repeatLastPredictions > 0,
|
|
144
|
+
confirmedCovered: counts.confirmed > 0,
|
|
145
|
+
replacementCovered: counts.replacements > 0,
|
|
146
|
+
absentCovered: counts.absent > 0,
|
|
147
|
+
botCovered: counts.bot > 0,
|
|
148
|
+
lateJoinCovered: counts.lateJoin > 0,
|
|
149
|
+
lateFillCovered: counts.lateFill > 0,
|
|
150
|
+
rollbackTriggeredCovered: counts.rollbackTriggered > 0,
|
|
151
|
+
replayVerified: replayVerification.result === 'pass',
|
|
152
|
+
inactivePredictionRejected: negativeCases.inactivePredictionRejected,
|
|
153
|
+
malformedValueRejected: negativeCases.malformedValueRejected,
|
|
154
|
+
duplicateIngressRejected: negativeCases.duplicateIngressRejected,
|
|
155
|
+
duplicateAfterRestoreRejected: negativeCases.duplicateAfterRestoreRejected,
|
|
156
|
+
futureIngressRejected: negativeCases.futureIngressRejected,
|
|
157
|
+
staleIngressRejected: negativeCases.staleIngressRejected,
|
|
158
|
+
spectatorIngressRejected: negativeCases.spectatorIngressRejected,
|
|
159
|
+
badSenderRejected: negativeCases.badSenderRejected,
|
|
160
|
+
};
|
|
161
|
+
const invariantChecks = [
|
|
162
|
+
invariants.allFramesCovered,
|
|
163
|
+
invariants.allSlotsCovered,
|
|
164
|
+
invariants.rowCountStable,
|
|
165
|
+
invariants.orderStable,
|
|
166
|
+
invariants.explicitPredictionCovered,
|
|
167
|
+
invariants.neutralPredictionCovered,
|
|
168
|
+
invariants.repeatLastPredictionCovered,
|
|
169
|
+
invariants.confirmedCovered,
|
|
170
|
+
invariants.replacementCovered,
|
|
171
|
+
invariants.absentCovered,
|
|
172
|
+
invariants.botCovered,
|
|
173
|
+
invariants.lateJoinCovered,
|
|
174
|
+
invariants.lateFillCovered,
|
|
175
|
+
invariants.rollbackTriggeredCovered,
|
|
176
|
+
invariants.replayVerified,
|
|
177
|
+
invariants.inactivePredictionRejected,
|
|
178
|
+
invariants.malformedValueRejected,
|
|
179
|
+
invariants.duplicateIngressRejected,
|
|
180
|
+
invariants.duplicateAfterRestoreRejected,
|
|
181
|
+
invariants.futureIngressRejected,
|
|
182
|
+
invariants.staleIngressRejected,
|
|
183
|
+
invariants.spectatorIngressRejected,
|
|
184
|
+
invariants.badSenderRejected,
|
|
185
|
+
];
|
|
186
|
+
const result = evaluateDeterministicInputTimelineResult(invariantChecks);
|
|
187
|
+
const sampleLimit = options.sampleLimit ?? 64;
|
|
188
|
+
const reportWithoutChecksum = {
|
|
189
|
+
schemaVersion: 1,
|
|
190
|
+
kind: 'deterministic-input-timeline',
|
|
191
|
+
result,
|
|
192
|
+
players: options.players,
|
|
193
|
+
frames: options.frames,
|
|
194
|
+
seed: options.seed,
|
|
195
|
+
rowCount: rows.length,
|
|
196
|
+
rowHash,
|
|
197
|
+
sampledRows: rows.slice(0, sampleLimit),
|
|
198
|
+
...(options.includeRows === true ? { rows } : {}),
|
|
199
|
+
counts,
|
|
200
|
+
negativeCases,
|
|
201
|
+
replayVerified: replayVerification.result === 'pass',
|
|
202
|
+
replayChecksum: replayVerification.replayChecksum,
|
|
203
|
+
finalVerifiedChecksum: replay.finalVerifiedChecksum,
|
|
204
|
+
invariants,
|
|
205
|
+
};
|
|
206
|
+
return {
|
|
207
|
+
...reportWithoutChecksum,
|
|
208
|
+
checksum: defaultChecksum(reportWithoutChecksum),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function createInputTimelineDescriptor() {
|
|
212
|
+
return {
|
|
213
|
+
id: 'input-timeline-fixture',
|
|
214
|
+
tickRate: 60,
|
|
215
|
+
components: [
|
|
216
|
+
{ id: 'sessionInput', singleton: true, fields: [
|
|
217
|
+
{ name: 'sum', kind: 'int', defaultValue: 0 },
|
|
218
|
+
{ name: 'active', kind: 'int', defaultValue: 0 },
|
|
219
|
+
] },
|
|
220
|
+
{ id: 'sessionState', singleton: true, fields: [
|
|
221
|
+
{ name: 'sum', kind: 'int', defaultValue: 0 },
|
|
222
|
+
{ name: 'frames', kind: 'int', defaultValue: 0 },
|
|
223
|
+
{ name: 'activeInputs', kind: 'int', defaultValue: 0 },
|
|
224
|
+
] },
|
|
225
|
+
],
|
|
226
|
+
systems: [{ id: 'input-timeline-step', group: 'simulation', order: 0 }],
|
|
227
|
+
};
|
|
228
|
+
}
|
|
229
|
+
function createInputTimelineSystems() {
|
|
230
|
+
return {
|
|
231
|
+
'input-timeline-step'(ctx) {
|
|
232
|
+
const input = ctx.getSingleton('sessionInput');
|
|
233
|
+
const state = (ctx.getSingleton('sessionState') ?? { sum: 0, frames: 0, activeInputs: 0 });
|
|
234
|
+
ctx.setSingleton('sessionState', accumulateDeterministicInputTimelineState(state, input));
|
|
235
|
+
},
|
|
236
|
+
};
|
|
237
|
+
}
|
|
238
|
+
export function evaluateDeterministicInputTimelineResult(invariantChecks) {
|
|
239
|
+
return invariantChecks.every((value) => value) ? 'pass' : 'fail';
|
|
240
|
+
}
|
|
241
|
+
export function accumulateDeterministicInputTimelineState(state, input) {
|
|
242
|
+
return {
|
|
243
|
+
sum: Number(state.sum ?? 0) + Number(input.sum ?? 0),
|
|
244
|
+
frames: Number(state.frames ?? 0) + 1,
|
|
245
|
+
activeInputs: Number(state.activeInputs ?? 0) + Number(input.active ?? 0),
|
|
246
|
+
};
|
|
247
|
+
}
|
|
248
|
+
function mapInputsToSingleton(_frame, inputs) {
|
|
249
|
+
return {
|
|
250
|
+
sum: inputs.reduce((sum, input) => sum + input.value, 0),
|
|
251
|
+
active: inputs.length,
|
|
252
|
+
};
|
|
253
|
+
}
|
|
254
|
+
function classifySlot(frame, slot, config) {
|
|
255
|
+
if (slot === config.spectatorSlot) {
|
|
256
|
+
return { lane: 'spectator', active: false };
|
|
257
|
+
}
|
|
258
|
+
if (slot === config.absentSlot) {
|
|
259
|
+
return { lane: 'absent', active: false };
|
|
260
|
+
}
|
|
261
|
+
if (slot === config.lateJoinSlot && frame < config.lateJoinFrame) {
|
|
262
|
+
return { lane: 'late-join', active: false };
|
|
263
|
+
}
|
|
264
|
+
if (slot === config.transientSlot && frame >= config.removeFrame && frame < config.readdFrame) {
|
|
265
|
+
return { lane: 'absent', active: false };
|
|
266
|
+
}
|
|
267
|
+
if (slot === config.botSlot) {
|
|
268
|
+
return { lane: 'bot', active: true };
|
|
269
|
+
}
|
|
270
|
+
return { lane: 'human', active: true };
|
|
271
|
+
}
|
|
272
|
+
function shouldSubmitExplicitPrediction(frame, slot, seed) {
|
|
273
|
+
return (frame + slot + seed) % 11 !== 0;
|
|
274
|
+
}
|
|
275
|
+
function shouldReplaceInput(frame, slot, seed) {
|
|
276
|
+
return (frame * 3 + slot + seed) % 17 === 0;
|
|
277
|
+
}
|
|
278
|
+
function shouldLateFill(frame, slot, seed) {
|
|
279
|
+
return (frame + slot + seed) % 5 === 0 ? 1 : 0;
|
|
280
|
+
}
|
|
281
|
+
function inputFlags(frame, slot, seed) {
|
|
282
|
+
const flags = [];
|
|
283
|
+
if ((frame + slot + seed) % 7 === 0) {
|
|
284
|
+
flags.push('repeatable');
|
|
285
|
+
}
|
|
286
|
+
if ((frame + slot + seed) % 13 === 0) {
|
|
287
|
+
flags.push('command');
|
|
288
|
+
}
|
|
289
|
+
return sortDeterministicInputTimelineFlags(flags);
|
|
290
|
+
}
|
|
291
|
+
function fallbackPolicy(hasRepeatable) {
|
|
292
|
+
return hasRepeatable ? 'repeat-last' : 'neutral';
|
|
293
|
+
}
|
|
294
|
+
function correctionFor(prediction, predictedValue, verifiedValue) {
|
|
295
|
+
if (prediction === 'none' || predictedValue === verifiedValue) {
|
|
296
|
+
return 'none';
|
|
297
|
+
}
|
|
298
|
+
return prediction === 'explicit' ? 'late-fill' : 'rollback-triggered';
|
|
299
|
+
}
|
|
300
|
+
export function sortDeterministicInputTimelineFlags(flags) {
|
|
301
|
+
return [...new Set(flags)].sort(compareStrings);
|
|
302
|
+
}
|
|
303
|
+
function countRows(rows) {
|
|
304
|
+
const frames = new Set();
|
|
305
|
+
const slots = new Set();
|
|
306
|
+
const counts = {
|
|
307
|
+
rows: rows.length,
|
|
308
|
+
frames: 0,
|
|
309
|
+
slots: 0,
|
|
310
|
+
explicitPredictions: 0,
|
|
311
|
+
neutralPredictions: 0,
|
|
312
|
+
repeatLastPredictions: 0,
|
|
313
|
+
confirmed: 0,
|
|
314
|
+
replacements: 0,
|
|
315
|
+
absent: 0,
|
|
316
|
+
bot: 0,
|
|
317
|
+
lateJoin: 0,
|
|
318
|
+
lateFill: 0,
|
|
319
|
+
rollbackTriggered: 0,
|
|
320
|
+
};
|
|
321
|
+
for (const row of rows) {
|
|
322
|
+
frames.add(row.frame);
|
|
323
|
+
slots.add(row.slot);
|
|
324
|
+
counts.explicitPredictions += row.prediction === 'explicit' ? 1 : 0;
|
|
325
|
+
counts.neutralPredictions += row.prediction === 'neutral' ? 1 : 0;
|
|
326
|
+
counts.repeatLastPredictions += row.prediction === 'repeat-last' ? 1 : 0;
|
|
327
|
+
counts.confirmed += row.verification === 'confirmed' ? 1 : 0;
|
|
328
|
+
counts.replacements += row.verification === 'replacement' ? 1 : 0;
|
|
329
|
+
counts.absent += row.verification === 'absent' ? 1 : 0;
|
|
330
|
+
counts.bot += row.lane === 'bot' ? 1 : 0;
|
|
331
|
+
counts.lateJoin += row.lane === 'late-join' ? 1 : 0;
|
|
332
|
+
counts.lateFill += row.correction === 'late-fill' ? 1 : 0;
|
|
333
|
+
counts.rollbackTriggered += row.correction === 'rollback-triggered' ? 1 : 0;
|
|
334
|
+
}
|
|
335
|
+
counts.frames = frames.size;
|
|
336
|
+
counts.slots = slots.size;
|
|
337
|
+
return counts;
|
|
338
|
+
}
|
|
339
|
+
function runInputTimelineNegativeCases(players) {
|
|
340
|
+
const descriptor = createInputTimelineDescriptor();
|
|
341
|
+
const systems = createInputTimelineSystems();
|
|
342
|
+
const session = createDeterministicSessionCore({ players, descriptor, systems, seed: 19, mapInputsToSingleton });
|
|
343
|
+
session.joinAsSpectator(0, 'actor-0', 0);
|
|
344
|
+
const inactivePredictionRejected = throws(() => session.submitPredictedInput({ frame: 0, slot: 0, value: 1 }));
|
|
345
|
+
session.addPlayer(1, 'actor-1', 0);
|
|
346
|
+
const validPredictionAccepted = !throws(() => session.submitPredictedInput({ frame: 0, slot: 1, value: 1 }));
|
|
347
|
+
const malformedValueRejected = throws(() => session.submitPredictedInput({ frame: 0, slot: 1, value: 1.5 }));
|
|
348
|
+
const runtime = createDeterministicLocalAuthorityRuntime(players, { maxFrameLead: 4 });
|
|
349
|
+
const firstAccepted = runtime.submitInput({ slot: 1, frame: 0, value: 7, inputId: 'input:slot-1:frame-0' });
|
|
350
|
+
const duplicateIngressRejected = firstAccepted && !runtime.submitInput({ slot: 1, frame: 0, value: 7, inputId: 'input:slot-1:frame-0' });
|
|
351
|
+
const futureIngressRejected = !runtime.submitInput({ slot: 1, frame: 99, value: 1 });
|
|
352
|
+
runtime.step();
|
|
353
|
+
runtime.step();
|
|
354
|
+
const staleIngressRejected = !runtime.submitInput({ slot: 1, frame: 0, value: 1, inputId: 'input:stale' });
|
|
355
|
+
runtime.addSpectator(2);
|
|
356
|
+
const spectatorIngressRejected = !runtime.submitInput({ slot: 2, frame: runtime.state.frame, value: 3 });
|
|
357
|
+
const badSenderRejected = !runtime.submitInput({ slot: 1, senderSlot: 2, frame: runtime.state.frame, value: 3 });
|
|
358
|
+
const restoreSource = createDeterministicLocalAuthorityRuntime(players, { maxFrameLead: 4 });
|
|
359
|
+
const restoreAccepted = restoreSource.submitInput({ slot: 1, frame: 0, value: 5, inputId: 'input:persisted' });
|
|
360
|
+
const snapshot = restoreSource.snapshot();
|
|
361
|
+
const restored = createDeterministicLocalAuthorityRuntime(players, { maxFrameLead: 4 });
|
|
362
|
+
restored.restore(snapshot);
|
|
363
|
+
const duplicateAfterRestoreRejected = restoreAccepted && !restored.submitInput({ slot: 1, frame: 0, value: 5, inputId: 'input:persisted' });
|
|
364
|
+
return {
|
|
365
|
+
validPredictionAccepted,
|
|
366
|
+
inactivePredictionRejected,
|
|
367
|
+
malformedValueRejected,
|
|
368
|
+
duplicateIngressRejected,
|
|
369
|
+
duplicateAfterRestoreRejected,
|
|
370
|
+
futureIngressRejected,
|
|
371
|
+
staleIngressRejected,
|
|
372
|
+
spectatorIngressRejected,
|
|
373
|
+
badSenderRejected,
|
|
374
|
+
};
|
|
375
|
+
}
|
|
376
|
+
function compareRows(left, right) {
|
|
377
|
+
return left.frame - right.frame || left.slot - right.slot;
|
|
378
|
+
}
|
|
379
|
+
function compareStrings(left, right) {
|
|
380
|
+
if (left < right) {
|
|
381
|
+
return -1;
|
|
382
|
+
}
|
|
383
|
+
/* istanbul ignore else -- input timeline flag sorting de-duplicates before comparing. */
|
|
384
|
+
if (left > right) {
|
|
385
|
+
return 1;
|
|
386
|
+
}
|
|
387
|
+
/* istanbul ignore next -- stable flag sorting receives de-duplicated flag strings. */
|
|
388
|
+
return 0;
|
|
389
|
+
}
|
|
390
|
+
function throws(action) {
|
|
391
|
+
try {
|
|
392
|
+
action();
|
|
393
|
+
return false;
|
|
394
|
+
}
|
|
395
|
+
catch {
|
|
396
|
+
return true;
|
|
397
|
+
}
|
|
398
|
+
}
|
|
399
|
+
function validateInputTimelineOptions(options) {
|
|
400
|
+
if (!Number.isInteger(options.players) || options.players < 16 || options.players > 128) {
|
|
401
|
+
throw new Error('input timeline players must be an integer between 16 and 128');
|
|
402
|
+
}
|
|
403
|
+
if (!Number.isInteger(options.frames) || options.frames < 96) {
|
|
404
|
+
throw new Error('input timeline frames must be an integer >= 96');
|
|
405
|
+
}
|
|
406
|
+
if (!Number.isInteger(options.seed)) {
|
|
407
|
+
throw new Error('input timeline seed must be an integer');
|
|
408
|
+
}
|
|
409
|
+
if (options.sampleLimit !== undefined && (!Number.isInteger(options.sampleLimit) || options.sampleLimit < 0)) {
|
|
410
|
+
throw new Error('input timeline sampleLimit must be a non-negative integer');
|
|
411
|
+
}
|
|
412
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import type { DeterministicSessionConfig, ExternalSessionSnapshot, OfflineSession } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Instant replay / killcam: a parallel offline runner that re-simulates the
|
|
4
|
+
* last N confirmed ticks of a live session without ever touching it.
|
|
5
|
+
*
|
|
6
|
+
* The live session already keeps a rolling snapshot buffer (`getSnapshot`) and
|
|
7
|
+
* records confirmed input frames (`exportReplay().inputFrames` or the
|
|
8
|
+
* `onInputFrameRecorded` callback). Feed the snapshot at tick T-N plus the
|
|
9
|
+
* recorded input frames for [T-N, T) into `runInstantReplay` and it replays
|
|
10
|
+
* those ticks on a FRESH session, invoking `onFrame(state, frame)` after each
|
|
11
|
+
* tick so the game can render the killcam from it. Recorded input frames hold
|
|
12
|
+
* the CONFIRMED effective inputs (absent-player substitution already applied),
|
|
13
|
+
* so the replay reproduces the live checksums exactly.
|
|
14
|
+
*
|
|
15
|
+
* The replay session is created from the same deterministic config, restored
|
|
16
|
+
* via the external-snapshot seam, and discarded afterwards — the live session
|
|
17
|
+
* is never read or mutated during the run.
|
|
18
|
+
*/
|
|
19
|
+
/** The shape of one recorded input frame — `RecordedInputFrame` satisfies it. */
|
|
20
|
+
export interface InstantReplayInputFrame {
|
|
21
|
+
readonly frame: number;
|
|
22
|
+
readonly inputs: readonly unknown[];
|
|
23
|
+
}
|
|
24
|
+
export interface InstantReplayResult {
|
|
25
|
+
readonly framesPlayed: number;
|
|
26
|
+
readonly finalChecksum: string;
|
|
27
|
+
}
|
|
28
|
+
export interface RunInstantReplayOptions<State, Input> {
|
|
29
|
+
readonly createSession: () => OfflineSession<State, Input>;
|
|
30
|
+
/** Snapshot exported from the live session (`getSnapshot(frame)` returns a clone). */
|
|
31
|
+
readonly snapshot: ExternalSessionSnapshot<Input>;
|
|
32
|
+
/** Confirmed input frames, contiguous from `snapshot.frame`. */
|
|
33
|
+
readonly inputs: readonly InstantReplayInputFrame[];
|
|
34
|
+
/** Hydrates a recorded canonical input back into the game's Input (identity by default). */
|
|
35
|
+
readonly decodeInput?: (recordedInput: unknown, slot: number) => Input;
|
|
36
|
+
readonly onFrame: (state: State, frame: number) => void;
|
|
37
|
+
}
|
|
38
|
+
export declare function runInstantReplay<State, Input>(options: RunInstantReplayOptions<State, Input>): InstantReplayResult;
|
|
39
|
+
export interface InstantReplayRunnerOptions {
|
|
40
|
+
/** Maximum ticks a single replay may cover (killcam length). */
|
|
41
|
+
readonly window: number;
|
|
42
|
+
}
|
|
43
|
+
export interface InstantReplayRunOptions<State, Input> {
|
|
44
|
+
readonly snapshot: ExternalSessionSnapshot<Input>;
|
|
45
|
+
readonly inputs: readonly InstantReplayInputFrame[];
|
|
46
|
+
readonly decodeInput?: (recordedInput: unknown, slot: number) => Input;
|
|
47
|
+
readonly onFrame: (state: State, frame: number) => void;
|
|
48
|
+
}
|
|
49
|
+
export interface InstantReplayRunner<State, Input> {
|
|
50
|
+
readonly window: number;
|
|
51
|
+
run(options: InstantReplayRunOptions<State, Input>): InstantReplayResult;
|
|
52
|
+
}
|
|
53
|
+
/**
|
|
54
|
+
* Binds a session config to a replay window. Each `run` simulates on a fresh
|
|
55
|
+
* throwaway session with replay recording off, a single-frame snapshot buffer,
|
|
56
|
+
* and the config's recording callbacks stripped — so a killcam never leaks
|
|
57
|
+
* events into the live game's observers and stays allocation-light.
|
|
58
|
+
*/
|
|
59
|
+
export declare function createInstantReplayRunner<State, Input>(config: DeterministicSessionConfig<State, Input>, options: InstantReplayRunnerOptions): InstantReplayRunner<State, Input>;
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { SyncplayError, invariant } from './errors.js';
|
|
2
|
+
import { createOfflineSession } from './offline-session.js';
|
|
3
|
+
export function runInstantReplay(options) {
|
|
4
|
+
const session = options.createSession();
|
|
5
|
+
session.restoreSnapshot(options.snapshot);
|
|
6
|
+
let expectedFrame = options.snapshot.frame;
|
|
7
|
+
for (const inputFrame of options.inputs) {
|
|
8
|
+
if (inputFrame.frame !== expectedFrame) {
|
|
9
|
+
throw new SyncplayError(`Instant replay inputs must be contiguous from snapshot frame ${options.snapshot.frame}: expected frame ${expectedFrame}, got ${inputFrame.frame}`, { code: 'instant-replay.non-contiguous-inputs', frame: inputFrame.frame });
|
|
10
|
+
}
|
|
11
|
+
for (let slot = 0; slot < inputFrame.inputs.length; slot += 1) {
|
|
12
|
+
const recorded = inputFrame.inputs[slot];
|
|
13
|
+
// NOT `??`: a decoder returning null (the canonical neutral) must win
|
|
14
|
+
// over the raw recorded value (adversarial-review LOW).
|
|
15
|
+
session.setInput(slot, options.decodeInput !== undefined ? options.decodeInput(recorded, slot) : recorded);
|
|
16
|
+
}
|
|
17
|
+
session.stepFrames(1);
|
|
18
|
+
options.onFrame(session.getState(), session.currentFrame);
|
|
19
|
+
expectedFrame += 1;
|
|
20
|
+
}
|
|
21
|
+
const finalSnapshot = session.getSnapshot(session.currentFrame);
|
|
22
|
+
/* istanbul ignore if -- the session records a snapshot on every stepped frame. */
|
|
23
|
+
if (finalSnapshot === undefined) {
|
|
24
|
+
throw new SyncplayError('Instant replay session has no snapshot at its final frame', {
|
|
25
|
+
code: 'instant-replay.missing-final-snapshot',
|
|
26
|
+
frame: session.currentFrame,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return { framesPlayed: options.inputs.length, finalChecksum: finalSnapshot.checksum };
|
|
30
|
+
}
|
|
31
|
+
/**
|
|
32
|
+
* Binds a session config to a replay window. Each `run` simulates on a fresh
|
|
33
|
+
* throwaway session with replay recording off, a single-frame snapshot buffer,
|
|
34
|
+
* and the config's recording callbacks stripped — so a killcam never leaks
|
|
35
|
+
* events into the live game's observers and stays allocation-light.
|
|
36
|
+
*/
|
|
37
|
+
export function createInstantReplayRunner(config, options) {
|
|
38
|
+
invariant(Number.isInteger(options.window) && options.window >= 1, 'Instant replay window must be a positive integer', 'instant-replay.invalid-window');
|
|
39
|
+
const replayConfig = {
|
|
40
|
+
...config,
|
|
41
|
+
replayRecordingMode: 'none',
|
|
42
|
+
snapshotBufferSize: 1,
|
|
43
|
+
onInputFrameRecorded: undefined,
|
|
44
|
+
onCommandFrameRecorded: undefined,
|
|
45
|
+
onChecksumRecorded: undefined,
|
|
46
|
+
};
|
|
47
|
+
return {
|
|
48
|
+
window: options.window,
|
|
49
|
+
run(runOptions) {
|
|
50
|
+
if (runOptions.inputs.length > options.window) {
|
|
51
|
+
throw new SyncplayError(`Instant replay covers ${runOptions.inputs.length} ticks, above window ${options.window}`, { code: 'instant-replay.window-exceeded', frame: runOptions.snapshot.frame });
|
|
52
|
+
}
|
|
53
|
+
return runInstantReplay({
|
|
54
|
+
createSession: () => createOfflineSession(replayConfig),
|
|
55
|
+
snapshot: runOptions.snapshot,
|
|
56
|
+
inputs: runOptions.inputs,
|
|
57
|
+
decodeInput: runOptions.decodeInput,
|
|
58
|
+
onFrame: runOptions.onFrame,
|
|
59
|
+
});
|
|
60
|
+
},
|
|
61
|
+
};
|
|
62
|
+
}
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { type DeterministicPhysicsBody3D } from './physics3d.js';
|
|
2
|
+
export interface DeterministicLagCompensatedTarget3D {
|
|
3
|
+
readonly frame: number;
|
|
4
|
+
readonly targetId: string;
|
|
5
|
+
readonly x: number;
|
|
6
|
+
readonly y: number;
|
|
7
|
+
readonly z: number;
|
|
8
|
+
readonly radius: number;
|
|
9
|
+
readonly pose: 'standing' | 'crouching';
|
|
10
|
+
}
|
|
11
|
+
export interface DeterministicLagCompensatedShot3D {
|
|
12
|
+
readonly id: string;
|
|
13
|
+
readonly shooterSlot: number;
|
|
14
|
+
readonly shotFrame: number;
|
|
15
|
+
readonly latencyFrames: number;
|
|
16
|
+
readonly x: number;
|
|
17
|
+
readonly y: number;
|
|
18
|
+
readonly z: number;
|
|
19
|
+
readonly dx: number;
|
|
20
|
+
readonly dy: number;
|
|
21
|
+
readonly dz: number;
|
|
22
|
+
readonly maxDistance: number;
|
|
23
|
+
readonly weapon: 'rifle' | 'shotgun' | 'burst';
|
|
24
|
+
readonly pellets: readonly {
|
|
25
|
+
readonly dx: number;
|
|
26
|
+
readonly dy: number;
|
|
27
|
+
readonly dz: number;
|
|
28
|
+
}[];
|
|
29
|
+
readonly maxHistoryFrames: number;
|
|
30
|
+
}
|
|
31
|
+
export interface DeterministicLagCompensatedShot3DResult {
|
|
32
|
+
readonly id: string;
|
|
33
|
+
readonly reason: 'hit' | 'miss' | 'occluded' | 'stale-frame' | 'future-frame' | 'invalid-shot';
|
|
34
|
+
readonly rewindFrame: number;
|
|
35
|
+
readonly hitTargetId: string;
|
|
36
|
+
readonly hitboxId: string;
|
|
37
|
+
readonly distance: number;
|
|
38
|
+
readonly pelletHits: number;
|
|
39
|
+
readonly proxiesCreated: number;
|
|
40
|
+
readonly proxiesDestroyed: number;
|
|
41
|
+
readonly interpolatedProxies: number;
|
|
42
|
+
readonly checksum: string;
|
|
43
|
+
}
|
|
44
|
+
export type DeterministicLagCompensatedPellet3DResult = {
|
|
45
|
+
readonly kind: 'hit' | 'occluded';
|
|
46
|
+
readonly targetId: string;
|
|
47
|
+
readonly hitboxId: string;
|
|
48
|
+
readonly distance: number;
|
|
49
|
+
} | {
|
|
50
|
+
readonly kind: 'miss';
|
|
51
|
+
};
|
|
52
|
+
export interface DeterministicLagCompensation3DHistoryOptions {
|
|
53
|
+
readonly maxStoredFrames?: number;
|
|
54
|
+
readonly occluders?: readonly DeterministicPhysicsBody3D[];
|
|
55
|
+
}
|
|
56
|
+
export interface DeterministicLagCompensation3DHistorySnapshot {
|
|
57
|
+
readonly maxStoredFrames: number;
|
|
58
|
+
readonly targets: readonly DeterministicLagCompensatedTarget3D[];
|
|
59
|
+
readonly occluders: readonly DeterministicPhysicsBody3D[];
|
|
60
|
+
readonly checksum: string;
|
|
61
|
+
}
|
|
62
|
+
export interface DeterministicLagCompensation3DRewindSnapshot {
|
|
63
|
+
readonly frame: number;
|
|
64
|
+
readonly targets: readonly DeterministicLagCompensatedTarget3D[];
|
|
65
|
+
readonly proxies: readonly DeterministicPhysicsBody3D[];
|
|
66
|
+
readonly interpolatedTargets: number;
|
|
67
|
+
readonly checksum: string;
|
|
68
|
+
}
|
|
69
|
+
export interface DeterministicLagCompensation3DHistory {
|
|
70
|
+
readonly maxStoredFrames: number;
|
|
71
|
+
readonly frameCount: number;
|
|
72
|
+
readonly targetCount: number;
|
|
73
|
+
recordTargets(targets: readonly DeterministicLagCompensatedTarget3D[]): void;
|
|
74
|
+
rewind(frame: number, shooterSlot?: number): DeterministicLagCompensation3DRewindSnapshot;
|
|
75
|
+
validateShot(shot: DeterministicLagCompensatedShot3D): DeterministicLagCompensatedShot3DResult;
|
|
76
|
+
snapshot(): DeterministicLagCompensation3DHistorySnapshot;
|
|
77
|
+
restore(snapshot: DeterministicLagCompensation3DHistorySnapshot): void;
|
|
78
|
+
checksum(): string;
|
|
79
|
+
}
|
|
80
|
+
export interface DeterministicLagCompensation3DFixtureResult {
|
|
81
|
+
readonly checksum: string;
|
|
82
|
+
readonly restoredChecksum: string;
|
|
83
|
+
readonly rollbackEquivalent: boolean;
|
|
84
|
+
readonly players: number;
|
|
85
|
+
readonly frames: number;
|
|
86
|
+
readonly shots: number;
|
|
87
|
+
readonly rifleShots: number;
|
|
88
|
+
readonly shotgunShots: number;
|
|
89
|
+
readonly burstShots: number;
|
|
90
|
+
readonly hits: number;
|
|
91
|
+
readonly misses: number;
|
|
92
|
+
readonly occluded: number;
|
|
93
|
+
readonly staleRejected: number;
|
|
94
|
+
readonly futureRejected: number;
|
|
95
|
+
readonly multiHitboxQueries: number;
|
|
96
|
+
readonly animatedHitboxSamples: number;
|
|
97
|
+
readonly interpolatedHitboxSamples: number;
|
|
98
|
+
readonly staticOcclusionQueries: number;
|
|
99
|
+
readonly proxyBodiesCreated: number;
|
|
100
|
+
readonly proxyBodiesDestroyed: number;
|
|
101
|
+
readonly proxyLifecycleBalanced: boolean;
|
|
102
|
+
readonly perClientShotTimesCovered: boolean;
|
|
103
|
+
}
|
|
104
|
+
export interface DeterministicLagCompensation3DApiFixtureResult {
|
|
105
|
+
readonly checksum: string;
|
|
106
|
+
readonly restoredChecksum: string;
|
|
107
|
+
readonly rollbackEquivalent: boolean;
|
|
108
|
+
readonly generatedZeroDirectionRejectionReason?: string;
|
|
109
|
+
readonly exactRewindTargets: number;
|
|
110
|
+
readonly interpolatedRewindTargets: number;
|
|
111
|
+
readonly hitReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
112
|
+
readonly occludedReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
113
|
+
readonly staleReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
114
|
+
readonly futureReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
115
|
+
readonly proxiesCreated: number;
|
|
116
|
+
readonly proxiesDestroyed: number;
|
|
117
|
+
readonly historyFrameCount: number;
|
|
118
|
+
readonly prunedOldFrames: boolean;
|
|
119
|
+
}
|
|
120
|
+
export interface DeterministicAuthoritativeLagCompensation3DShotContext {
|
|
121
|
+
readonly acceptedShotFrame: number;
|
|
122
|
+
readonly serverReceiveFrame: number;
|
|
123
|
+
readonly maxHistoryFrames: number;
|
|
124
|
+
readonly maxLatencyFrames: number;
|
|
125
|
+
}
|
|
126
|
+
export interface DeterministicAuthoritativeLagCompensation3DFixtureResult {
|
|
127
|
+
readonly checksum: string;
|
|
128
|
+
readonly authorityHitReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
129
|
+
readonly forgedLatencyReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
130
|
+
readonly forgedShotFrameReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
131
|
+
readonly forgedHistoryReason: DeterministicLagCompensatedShot3DResult['reason'];
|
|
132
|
+
readonly derivedRewindFrame: number;
|
|
133
|
+
readonly rawForgedRewindFrame: number;
|
|
134
|
+
readonly authorityTimingRejected: number;
|
|
135
|
+
readonly replayChecksum: string;
|
|
136
|
+
readonly restoredChecksum: string;
|
|
137
|
+
readonly rollbackEquivalent: boolean;
|
|
138
|
+
}
|
|
139
|
+
export declare function runDeterministicLagCompensation3DFixture(players?: number, frames?: number, seed?: number): DeterministicLagCompensation3DFixtureResult;
|
|
140
|
+
export declare function createDeterministicLagCompensation3DHistory(options?: DeterministicLagCompensation3DHistoryOptions): DeterministicLagCompensation3DHistory;
|
|
141
|
+
export declare function rewindDeterministicLagCompensatedTargets3D(history: readonly DeterministicLagCompensatedTarget3D[], rewindFrame: number): readonly DeterministicLagCompensatedTarget3D[];
|
|
142
|
+
export declare function buildDeterministicLagCompensatedHitboxProxies3D(history: readonly DeterministicLagCompensatedTarget3D[], rewindFrame: number, shooterSlot?: number): readonly DeterministicPhysicsBody3D[];
|
|
143
|
+
export declare function validateDeterministicLagCompensatedShot3D(history: readonly DeterministicLagCompensatedTarget3D[], occluders: readonly DeterministicPhysicsBody3D[], shot: DeterministicLagCompensatedShot3D): DeterministicLagCompensatedShot3DResult;
|
|
144
|
+
export declare function validateDeterministicAuthoritativeLagCompensatedShot3D(history: readonly DeterministicLagCompensatedTarget3D[], occluders: readonly DeterministicPhysicsBody3D[], shot: DeterministicLagCompensatedShot3D, context: DeterministicAuthoritativeLagCompensation3DShotContext): DeterministicLagCompensatedShot3DResult;
|
|
145
|
+
export declare function runDeterministicLagCompensation3DApiFixture(): DeterministicLagCompensation3DApiFixtureResult;
|
|
146
|
+
export declare function runDeterministicAuthoritativeLagCompensation3DFixture(): DeterministicAuthoritativeLagCompensation3DFixtureResult;
|