@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,443 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.defaultNetworkedInputAuthoritySpikeOptions = void 0;
|
|
4
|
+
exports.runNetworkedInputAuthoritySpike = runNetworkedInputAuthoritySpike;
|
|
5
|
+
const canonical_js_1 = require("./canonical.js");
|
|
6
|
+
const errors_js_1 = require("./errors.js");
|
|
7
|
+
const input_authority_js_1 = require("./input-authority.js");
|
|
8
|
+
const replay_js_1 = require("./replay.js");
|
|
9
|
+
const session_wire_js_1 = require("./session-wire.js");
|
|
10
|
+
const sample_lab_js_1 = require("./sample-lab.js");
|
|
11
|
+
const ws_frame_js_1 = require("./ws-frame.js");
|
|
12
|
+
exports.defaultNetworkedInputAuthoritySpikeOptions = {
|
|
13
|
+
players: 2,
|
|
14
|
+
ticks: 300,
|
|
15
|
+
seed: 71,
|
|
16
|
+
hardToleranceTicks: 6,
|
|
17
|
+
maxPredictionTicks: 8,
|
|
18
|
+
lossPermille: 80,
|
|
19
|
+
minDelayTicks: 1,
|
|
20
|
+
maxDelayTicks: 4,
|
|
21
|
+
resendWindowTicks: 6,
|
|
22
|
+
redundancyWindowTicks: 8,
|
|
23
|
+
};
|
|
24
|
+
const TAG_INPUT = 1;
|
|
25
|
+
const TAG_CONFIRM = 2;
|
|
26
|
+
// createLabSession's default tick rate; carried in session-start so a real client could pace itself.
|
|
27
|
+
const SESSION_TICK_RATE_HZ = 30;
|
|
28
|
+
async function runNetworkedInputAuthoritySpike(options) {
|
|
29
|
+
(0, errors_js_1.invariant)(options.players >= 2, 'spike requires at least two players', 'spike.players');
|
|
30
|
+
(0, errors_js_1.invariant)(options.ticks >= 1, 'spike requires at least one tick', 'spike.ticks');
|
|
31
|
+
(0, errors_js_1.invariant)(options.maxDelayTicks >= options.minDelayTicks && options.minDelayTicks >= 0, 'delay bounds must satisfy 0 <= min <= max', 'spike.delay-bounds');
|
|
32
|
+
const neutralInput = (0, sample_lab_js_1.canonicalizeLabInput)(sample_lab_js_1.labDefaultInput);
|
|
33
|
+
const authority = (0, input_authority_js_1.createInputAuthority)({
|
|
34
|
+
playerCount: options.players,
|
|
35
|
+
hardToleranceTicks: options.hardToleranceTicks,
|
|
36
|
+
neutralInput,
|
|
37
|
+
maxFutureTicks: options.maxPredictionTicks + options.hardToleranceTicks + 4,
|
|
38
|
+
retainedConfirmedFrames: options.ticks + options.redundancyWindowTicks + 8,
|
|
39
|
+
});
|
|
40
|
+
const server = await startServer(authority, options);
|
|
41
|
+
try {
|
|
42
|
+
const clients = [];
|
|
43
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
44
|
+
const connection = await (0, ws_frame_js_1.connectWebSocketClient)(server.url);
|
|
45
|
+
clients.push(await createSpikeClient(connection, slot, options));
|
|
46
|
+
}
|
|
47
|
+
const settleCap = options.ticks + options.hardToleranceTicks + options.maxDelayTicks + 64;
|
|
48
|
+
let now = 0;
|
|
49
|
+
let settleRounds = 0;
|
|
50
|
+
for (; now < settleCap; now += 1) {
|
|
51
|
+
for (const client of clients) {
|
|
52
|
+
client.pump(now);
|
|
53
|
+
}
|
|
54
|
+
await flush();
|
|
55
|
+
server.pump(now);
|
|
56
|
+
await flush();
|
|
57
|
+
for (const client of clients) {
|
|
58
|
+
client.drain(now);
|
|
59
|
+
}
|
|
60
|
+
const done = clients.every((client) => client.appliedThrough >= options.ticks - 1 && client.predictedThrough >= options.ticks);
|
|
61
|
+
if (done) {
|
|
62
|
+
break;
|
|
63
|
+
}
|
|
64
|
+
if (now >= options.ticks) {
|
|
65
|
+
settleRounds += 1;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
const confirmedTick = options.ticks - 1;
|
|
69
|
+
const converged = clients.every((client) => client.appliedThrough >= confirmedTick && client.predictedThrough >= options.ticks);
|
|
70
|
+
const clientChecksums = converged ? clients.map((client) => client.confirmedStateChecksum(options.ticks)) : [];
|
|
71
|
+
const replays = converged ? clients.map((client) => client.exportConfirmedReplay()) : [];
|
|
72
|
+
const clientReplayChecksums = replays.map((replay) => replayIdentity(replay));
|
|
73
|
+
const replaysVerified = converged && replays.every((replay) => verifyLabReplay(replay));
|
|
74
|
+
const checksumsMatch = converged && clientChecksums.every((checksum) => checksum === clientChecksums[0]);
|
|
75
|
+
const replaysMatch = converged && clientReplayChecksums.every((checksum) => checksum === clientReplayChecksums[0]);
|
|
76
|
+
// Independent oracle: replay ONLY the authority's confirmed inputs through a clean sim with no
|
|
77
|
+
// prediction/rollback. Comparing clients against it catches a symmetric rollback bug that would
|
|
78
|
+
// otherwise leave both clients identically wrong (both consume the same confirmed inputs).
|
|
79
|
+
const authorityOracleChecksum = converged ? computeAuthorityOracleChecksum(authority, options) : '';
|
|
80
|
+
const oracleMatch = converged && authorityOracleChecksum !== '' && clientChecksums.every((checksum) => checksum === authorityOracleChecksum);
|
|
81
|
+
const totalRollbacks = clients.reduce((sum, client) => sum + client.rollbacks, 0);
|
|
82
|
+
const maxRollbackDepth = clients.reduce((max, client) => Math.max(max, client.maxRollbackDepth), 0);
|
|
83
|
+
const rollbackDepthSum = clients.reduce((sum, client) => sum + client.rollbackDepthSum, 0);
|
|
84
|
+
const maxConfirmationLagTicks = clients.reduce((max, client) => Math.max(max, client.maxConfirmationLag), 0);
|
|
85
|
+
const pass = converged && checksumsMatch && replaysMatch && replaysVerified && oracleMatch;
|
|
86
|
+
return {
|
|
87
|
+
players: options.players,
|
|
88
|
+
ticks: options.ticks,
|
|
89
|
+
seed: options.seed,
|
|
90
|
+
converged,
|
|
91
|
+
confirmedThrough: authority.confirmedThrough,
|
|
92
|
+
confirmedStateChecksum: clientChecksums[0] ?? '',
|
|
93
|
+
replayChecksum: clientReplayChecksums[0] ?? '',
|
|
94
|
+
clientChecksums,
|
|
95
|
+
clientReplayChecksums,
|
|
96
|
+
checksumsMatch,
|
|
97
|
+
replaysMatch,
|
|
98
|
+
replaysVerified,
|
|
99
|
+
authorityOracleChecksum,
|
|
100
|
+
oracleMatch,
|
|
101
|
+
totalRollbacks,
|
|
102
|
+
maxRollbackDepth,
|
|
103
|
+
meanRollbackDepth: totalRollbacks === 0 ? 0 : Number((rollbackDepthSum / totalRollbacks).toFixed(3)),
|
|
104
|
+
maxConfirmationLagTicks,
|
|
105
|
+
substitutedInputs: authority.stats().substitutedInputs,
|
|
106
|
+
droppedInputMessages: server.droppedInputMessages(),
|
|
107
|
+
droppedConfirmPackets: clients.reduce((sum, client) => sum + client.droppedConfirmPackets, 0),
|
|
108
|
+
settleRounds,
|
|
109
|
+
result: pass ? 'pass' : 'fail',
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
finally {
|
|
113
|
+
await server.close();
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
async function startServer(authority, options) {
|
|
117
|
+
const connections = [];
|
|
118
|
+
const inbox = [];
|
|
119
|
+
let nextSlot = 0;
|
|
120
|
+
let droppedInputs = 0;
|
|
121
|
+
const handle = await (0, ws_frame_js_1.startWebSocketServer)({
|
|
122
|
+
path: '/authority',
|
|
123
|
+
onConnection(connection) {
|
|
124
|
+
const slot = nextSlot;
|
|
125
|
+
nextSlot += 1;
|
|
126
|
+
connections.push(connection);
|
|
127
|
+
connection.send((0, session_wire_js_1.encodeDeterministicSessionMessage)({
|
|
128
|
+
kind: 'session-start',
|
|
129
|
+
slot,
|
|
130
|
+
playerCount: options.players,
|
|
131
|
+
seed: options.seed,
|
|
132
|
+
tickRateHz: SESSION_TICK_RATE_HZ,
|
|
133
|
+
hardToleranceTicks: options.hardToleranceTicks,
|
|
134
|
+
role: 'player',
|
|
135
|
+
}));
|
|
136
|
+
connection.onMessage((text) => {
|
|
137
|
+
// Fail closed: malformed or unknown messages are dropped, never coerced.
|
|
138
|
+
const decoded = (0, session_wire_js_1.decodeDeterministicSessionMessage)(text);
|
|
139
|
+
if (!decoded.ok || decoded.message.kind !== 'input') {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
const { slot: msgSlot, tick, input, attempt } = decoded.message;
|
|
143
|
+
// Server owns the slot mapping — reject input claiming a slot other than this connection's.
|
|
144
|
+
if (msgSlot !== slot) {
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
const roll = networkRoll(options, TAG_INPUT, tick, slot, attempt);
|
|
148
|
+
if (roll.drop) {
|
|
149
|
+
droppedInputs += 1;
|
|
150
|
+
return;
|
|
151
|
+
}
|
|
152
|
+
inbox.push({ slot, tick, input, releaseAt: tick + roll.delay });
|
|
153
|
+
});
|
|
154
|
+
},
|
|
155
|
+
});
|
|
156
|
+
return {
|
|
157
|
+
url: handle.url,
|
|
158
|
+
pump(now) {
|
|
159
|
+
for (let index = inbox.length - 1; index >= 0; index -= 1) {
|
|
160
|
+
const entry = inbox[index];
|
|
161
|
+
if (entry.releaseAt <= now) {
|
|
162
|
+
authority.receiveInput(entry.slot, entry.tick, entry.input);
|
|
163
|
+
inbox.splice(index, 1);
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
authority.advance(now);
|
|
167
|
+
const from = Math.max(0, authority.confirmedThrough - options.redundancyWindowTicks + 1);
|
|
168
|
+
const frames = [];
|
|
169
|
+
for (let tick = from; tick <= authority.confirmedThrough; tick += 1) {
|
|
170
|
+
const frame = authority.confirmedFrame(tick);
|
|
171
|
+
if (frame !== undefined) {
|
|
172
|
+
frames.push(frame);
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
if (frames.length === 0) {
|
|
176
|
+
return;
|
|
177
|
+
}
|
|
178
|
+
const packet = (0, session_wire_js_1.encodeDeterministicSessionMessage)({ kind: 'confirmed-input', authorityTick: now, frames });
|
|
179
|
+
for (const connection of connections) {
|
|
180
|
+
connection.send(packet);
|
|
181
|
+
}
|
|
182
|
+
},
|
|
183
|
+
droppedInputMessages() {
|
|
184
|
+
return droppedInputs;
|
|
185
|
+
},
|
|
186
|
+
async close() {
|
|
187
|
+
for (const connection of connections) {
|
|
188
|
+
connection.close();
|
|
189
|
+
}
|
|
190
|
+
await handle.close();
|
|
191
|
+
},
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
async function createSpikeClient(connection, slot, options) {
|
|
195
|
+
const session = (0, sample_lab_js_1.createLabSession)({ players: options.players, seed: options.seed, snapshotBufferSize: Math.max(256, options.ticks + 16) });
|
|
196
|
+
const timeline = [];
|
|
197
|
+
const ownInputs = new Map();
|
|
198
|
+
const confirmed = new Map();
|
|
199
|
+
const lastConfirmedBySlot = Array.from({ length: options.players }, () => (0, sample_lab_js_1.canonicalizeLabInput)(sample_lab_js_1.labDefaultInput));
|
|
200
|
+
const sendAttempts = new Map();
|
|
201
|
+
const inbox = [];
|
|
202
|
+
let predictedThrough = 0;
|
|
203
|
+
let appliedThrough = -1;
|
|
204
|
+
let rollbacks = 0;
|
|
205
|
+
let maxRollbackDepth = 0;
|
|
206
|
+
let rollbackDepthSum = 0;
|
|
207
|
+
let maxConfirmationLag = 0;
|
|
208
|
+
let droppedConfirmPackets = 0;
|
|
209
|
+
await new Promise((resolveWelcome) => {
|
|
210
|
+
connection.onMessage((text) => {
|
|
211
|
+
const decoded = (0, session_wire_js_1.decodeDeterministicSessionMessage)(text);
|
|
212
|
+
if (!decoded.ok) {
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
const message = decoded.message;
|
|
216
|
+
if (message.kind === 'session-start') {
|
|
217
|
+
resolveWelcome();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
if (message.kind === 'confirmed-input') {
|
|
221
|
+
const roll = networkRoll(options, TAG_CONFIRM, message.authorityTick, slot, 0);
|
|
222
|
+
if (roll.drop) {
|
|
223
|
+
droppedConfirmPackets += 1;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
inbox.push({ frames: message.frames, releaseAt: message.authorityTick + roll.delay, authorityNow: message.authorityTick });
|
|
227
|
+
}
|
|
228
|
+
});
|
|
229
|
+
});
|
|
230
|
+
function ensureOwnInput(tick) {
|
|
231
|
+
let own = ownInputs.get(tick);
|
|
232
|
+
if (own === undefined) {
|
|
233
|
+
own = ownInputForTick(options.seed, slot, tick);
|
|
234
|
+
ownInputs.set(tick, own);
|
|
235
|
+
}
|
|
236
|
+
return own;
|
|
237
|
+
}
|
|
238
|
+
function predictedInputForSlot(targetSlot, tick) {
|
|
239
|
+
// A confirmed tick is authoritative for every slot — including our own, whose real input
|
|
240
|
+
// the authority may have substituted (repeated) when it never arrived in time.
|
|
241
|
+
const conf = confirmed.get(tick);
|
|
242
|
+
if (conf !== undefined) {
|
|
243
|
+
return conf[targetSlot];
|
|
244
|
+
}
|
|
245
|
+
if (targetSlot === slot) {
|
|
246
|
+
return ensureOwnInput(tick);
|
|
247
|
+
}
|
|
248
|
+
return lastConfirmedBySlot[targetSlot];
|
|
249
|
+
}
|
|
250
|
+
function simulateTick(tick) {
|
|
251
|
+
const inputs = [];
|
|
252
|
+
for (let s = 0; s < options.players; s += 1) {
|
|
253
|
+
const input = predictedInputForSlot(s, tick);
|
|
254
|
+
inputs.push(input);
|
|
255
|
+
session.setInput(s, decodeLabInput(input));
|
|
256
|
+
}
|
|
257
|
+
timeline[tick] = inputs;
|
|
258
|
+
session.stepFrames(1);
|
|
259
|
+
}
|
|
260
|
+
function resimulateFrom(tick) {
|
|
261
|
+
// Frame/tick invariant: session snapshot at frame N is the state BEFORE tick N's inputs (after
|
|
262
|
+
// ticks 0..N-1). restoreToFrame(tick) leaves the session ready to re-apply tick `tick` onward.
|
|
263
|
+
session.restoreToFrame(tick);
|
|
264
|
+
for (let t = tick; t < predictedThrough; t += 1) {
|
|
265
|
+
const inputs = [];
|
|
266
|
+
for (let s = 0; s < options.players; s += 1) {
|
|
267
|
+
const input = predictedInputForSlot(s, t);
|
|
268
|
+
inputs.push(input);
|
|
269
|
+
session.setInput(s, decodeLabInput(input));
|
|
270
|
+
}
|
|
271
|
+
timeline[t] = inputs;
|
|
272
|
+
session.stepFrames(1);
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
function pump(now) {
|
|
276
|
+
while (predictedThrough < options.ticks && predictedThrough <= appliedThrough + options.maxPredictionTicks) {
|
|
277
|
+
const tick = predictedThrough;
|
|
278
|
+
const own = ensureOwnInput(tick);
|
|
279
|
+
simulateTick(tick);
|
|
280
|
+
predictedThrough = tick + 1;
|
|
281
|
+
sendInput(tick, own);
|
|
282
|
+
}
|
|
283
|
+
const resendFrom = Math.max(appliedThrough + 1, predictedThrough - options.resendWindowTicks);
|
|
284
|
+
for (let tick = resendFrom; tick < predictedThrough; tick += 1) {
|
|
285
|
+
const own = ownInputs.get(tick);
|
|
286
|
+
if (own !== undefined) {
|
|
287
|
+
sendInput(tick, own);
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
function sendInput(tick, input) {
|
|
292
|
+
const attempt = sendAttempts.get(tick) ?? 0;
|
|
293
|
+
sendAttempts.set(tick, attempt + 1);
|
|
294
|
+
connection.send((0, session_wire_js_1.encodeDeterministicSessionMessage)({ kind: 'input', slot, tick, input, attempt }));
|
|
295
|
+
}
|
|
296
|
+
function drain(now) {
|
|
297
|
+
for (let index = inbox.length - 1; index >= 0; index -= 1) {
|
|
298
|
+
const packet = inbox[index];
|
|
299
|
+
if (packet.releaseAt > now) {
|
|
300
|
+
continue;
|
|
301
|
+
}
|
|
302
|
+
inbox.splice(index, 1);
|
|
303
|
+
for (const frame of packet.frames) {
|
|
304
|
+
if (frame.tick <= appliedThrough) {
|
|
305
|
+
continue;
|
|
306
|
+
}
|
|
307
|
+
confirmed.set(frame.tick, frame.inputs);
|
|
308
|
+
}
|
|
309
|
+
maxConfirmationLag = Math.max(maxConfirmationLag, packet.authorityNow - appliedThrough);
|
|
310
|
+
}
|
|
311
|
+
applyConfirmed();
|
|
312
|
+
}
|
|
313
|
+
function applyConfirmed() {
|
|
314
|
+
while (confirmed.has(appliedThrough + 1)) {
|
|
315
|
+
const tick = appliedThrough + 1;
|
|
316
|
+
const confInputs = confirmed.get(tick);
|
|
317
|
+
const predInputs = timeline[tick];
|
|
318
|
+
if (predInputs === undefined || !equalInputs(confInputs, predInputs)) {
|
|
319
|
+
const depth = predictedThrough - tick;
|
|
320
|
+
if (depth > 0) {
|
|
321
|
+
rollbacks += 1;
|
|
322
|
+
rollbackDepthSum += depth;
|
|
323
|
+
maxRollbackDepth = Math.max(maxRollbackDepth, depth);
|
|
324
|
+
}
|
|
325
|
+
timeline[tick] = confInputs.slice();
|
|
326
|
+
if (depth > 0) {
|
|
327
|
+
resimulateFrom(tick);
|
|
328
|
+
}
|
|
329
|
+
else {
|
|
330
|
+
// Confirm leads prediction (a lagging client): the authority confirmed a tick we had not
|
|
331
|
+
// predicted yet. Extend the predicted head and step through it so the stepped session keeps
|
|
332
|
+
// pace with appliedThrough — mirrors the fix in createNetworkedSyncplayClient.
|
|
333
|
+
predictedThrough = tick + 1;
|
|
334
|
+
resimulateFrom(tick);
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
for (let s = 0; s < options.players; s += 1) {
|
|
338
|
+
lastConfirmedBySlot[s] = confInputs[s];
|
|
339
|
+
}
|
|
340
|
+
appliedThrough = tick;
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
return {
|
|
344
|
+
slot,
|
|
345
|
+
get predictedThrough() {
|
|
346
|
+
return predictedThrough;
|
|
347
|
+
},
|
|
348
|
+
get appliedThrough() {
|
|
349
|
+
return appliedThrough;
|
|
350
|
+
},
|
|
351
|
+
get rollbacks() {
|
|
352
|
+
return rollbacks;
|
|
353
|
+
},
|
|
354
|
+
get maxRollbackDepth() {
|
|
355
|
+
return maxRollbackDepth;
|
|
356
|
+
},
|
|
357
|
+
get rollbackDepthSum() {
|
|
358
|
+
return rollbackDepthSum;
|
|
359
|
+
},
|
|
360
|
+
get maxConfirmationLag() {
|
|
361
|
+
return maxConfirmationLag;
|
|
362
|
+
},
|
|
363
|
+
get droppedConfirmPackets() {
|
|
364
|
+
return droppedConfirmPackets;
|
|
365
|
+
},
|
|
366
|
+
pump,
|
|
367
|
+
drain,
|
|
368
|
+
confirmedStateChecksum(ticks) {
|
|
369
|
+
session.restoreToFrame(ticks);
|
|
370
|
+
return String((0, canonical_js_1.defaultChecksum)(session.getState()));
|
|
371
|
+
},
|
|
372
|
+
exportConfirmedReplay() {
|
|
373
|
+
return session.exportReplay();
|
|
374
|
+
},
|
|
375
|
+
};
|
|
376
|
+
}
|
|
377
|
+
function computeAuthorityOracleChecksum(authority, options) {
|
|
378
|
+
const session = (0, sample_lab_js_1.createLabSession)({ players: options.players, seed: options.seed, snapshotBufferSize: options.ticks + 16 });
|
|
379
|
+
for (let tick = 0; tick < options.ticks; tick += 1) {
|
|
380
|
+
const frame = authority.confirmedFrame(tick);
|
|
381
|
+
if (frame === undefined) {
|
|
382
|
+
return '';
|
|
383
|
+
}
|
|
384
|
+
for (let s = 0; s < options.players; s += 1) {
|
|
385
|
+
session.setInput(s, (0, sample_lab_js_1.hydrateLabInput)(frame.inputs[s]));
|
|
386
|
+
}
|
|
387
|
+
session.stepFrames(1);
|
|
388
|
+
}
|
|
389
|
+
return String((0, canonical_js_1.defaultChecksum)(session.getState()));
|
|
390
|
+
}
|
|
391
|
+
function ownInputForTick(seed, slot, tick) {
|
|
392
|
+
const h = mixHash([seed, slot, tick, 0x51ed]);
|
|
393
|
+
const dx = (h % 3) - 1;
|
|
394
|
+
const dy = (Math.floor(h / 3) % 3) - 1;
|
|
395
|
+
const action = (h & 1);
|
|
396
|
+
return (0, sample_lab_js_1.canonicalizeLabInput)({ dx, dy, action });
|
|
397
|
+
}
|
|
398
|
+
function decodeLabInput(input) {
|
|
399
|
+
return (0, sample_lab_js_1.hydrateLabInput)(input);
|
|
400
|
+
}
|
|
401
|
+
function equalInputs(left, right) {
|
|
402
|
+
return (0, canonical_js_1.canonicalStringify)(left) === (0, canonical_js_1.canonicalStringify)(right);
|
|
403
|
+
}
|
|
404
|
+
function replayIdentity(replay) {
|
|
405
|
+
return String((0, canonical_js_1.defaultChecksum)(replay));
|
|
406
|
+
}
|
|
407
|
+
function verifyLabReplay(replay) {
|
|
408
|
+
const result = (0, replay_js_1.verifyReplay)({
|
|
409
|
+
replay,
|
|
410
|
+
step: sample_lab_js_1.labStep,
|
|
411
|
+
defaultInput: sample_lab_js_1.labDefaultInput,
|
|
412
|
+
inputSchemaId: 'lab-input/v1',
|
|
413
|
+
stateSchemaId: 'lab-state/v1',
|
|
414
|
+
deterministicVersion: '0.0.0-source',
|
|
415
|
+
canonicalizeInput: sample_lab_js_1.canonicalizeLabInput,
|
|
416
|
+
hydrateInput: sample_lab_js_1.hydrateLabInput,
|
|
417
|
+
});
|
|
418
|
+
return result.mismatches === 0;
|
|
419
|
+
}
|
|
420
|
+
function networkRoll(options, tag, tick, slot, attempt) {
|
|
421
|
+
const h = mixHash([options.seed, tag, tick, slot, attempt]);
|
|
422
|
+
const drop = h % 1000 < options.lossPermille;
|
|
423
|
+
const span = options.maxDelayTicks - options.minDelayTicks + 1;
|
|
424
|
+
const delay = options.minDelayTicks + (mixHash([h, 0x9e3779b9]) % span);
|
|
425
|
+
return { drop, delay };
|
|
426
|
+
}
|
|
427
|
+
// FNV-1a variant with a per-step avalanche, over integer tuples. Used only for the seeded network
|
|
428
|
+
// model (loss/delay) — order-independent so results are reproducible regardless of arrival order.
|
|
429
|
+
function mixHash(values) {
|
|
430
|
+
let h = 0x811c9dc5;
|
|
431
|
+
for (const value of values) {
|
|
432
|
+
h ^= value | 0;
|
|
433
|
+
h = Math.imul(h, 0x01000193);
|
|
434
|
+
h ^= h >>> 15;
|
|
435
|
+
}
|
|
436
|
+
return h >>> 0;
|
|
437
|
+
}
|
|
438
|
+
// Yields to the event loop (not a timing delay — logical time is the discrete `now` counter) so
|
|
439
|
+
// real loopback WebSocket messages sent this round are delivered before the next round processes them.
|
|
440
|
+
async function flush() {
|
|
441
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
442
|
+
await new Promise((resolve) => setImmediate(resolve));
|
|
443
|
+
}
|
package/dist/cjs/node.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __exportStar = (this && this.__exportStar) || function(m, exports) {
|
|
14
|
+
for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p);
|
|
15
|
+
};
|
|
16
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
17
|
+
exports.stableEngineParityJson = exports.runEngineParitySuite = exports.explainEngineParityFixture = exports.runChaosCertificationSuite = exports.explainChaosFixture = exports.startLocalWebSocketProtocolServer = exports.runLocalWebSocketAdapterTest = exports.runNetworkedInputAuthoritySpike = exports.defaultNetworkedInputAuthoritySpikeOptions = exports.verifyReplay = exports.createOfflineSession = void 0;
|
|
18
|
+
__exportStar(require("./multiplayer-service.js"), exports);
|
|
19
|
+
__exportStar(require("./input-authority.js"), exports);
|
|
20
|
+
__exportStar(require("./local-authority-wire.js"), exports);
|
|
21
|
+
__exportStar(require("./session-wire.js"), exports);
|
|
22
|
+
__exportStar(require("./session-snapshot.js"), exports);
|
|
23
|
+
__exportStar(require("./networked-client.js"), exports);
|
|
24
|
+
var offline_session_js_1 = require("./offline-session.js");
|
|
25
|
+
Object.defineProperty(exports, "createOfflineSession", { enumerable: true, get: function () { return offline_session_js_1.createOfflineSession; } });
|
|
26
|
+
__exportStar(require("./instant-replay.js"), exports);
|
|
27
|
+
__exportStar(require("./pause-resume.js"), exports);
|
|
28
|
+
__exportStar(require("./session-promotion.js"), exports);
|
|
29
|
+
var replay_js_1 = require("./replay.js");
|
|
30
|
+
Object.defineProperty(exports, "verifyReplay", { enumerable: true, get: function () { return replay_js_1.verifyReplay; } });
|
|
31
|
+
__exportStar(require("./replay-scoring.js"), exports);
|
|
32
|
+
__exportStar(require("./match-log.js"), exports);
|
|
33
|
+
__exportStar(require("./authority-room.js"), exports);
|
|
34
|
+
__exportStar(require("./ws-frame.js"), exports);
|
|
35
|
+
var networked_input_authority_js_1 = require("./networked-input-authority.js");
|
|
36
|
+
Object.defineProperty(exports, "defaultNetworkedInputAuthoritySpikeOptions", { enumerable: true, get: function () { return networked_input_authority_js_1.defaultNetworkedInputAuthoritySpikeOptions; } });
|
|
37
|
+
Object.defineProperty(exports, "runNetworkedInputAuthoritySpike", { enumerable: true, get: function () { return networked_input_authority_js_1.runNetworkedInputAuthoritySpike; } });
|
|
38
|
+
var local_ws_adapter_js_1 = require("./local-ws-adapter.js");
|
|
39
|
+
Object.defineProperty(exports, "runLocalWebSocketAdapterTest", { enumerable: true, get: function () { return local_ws_adapter_js_1.runLocalWebSocketAdapterTest; } });
|
|
40
|
+
Object.defineProperty(exports, "startLocalWebSocketProtocolServer", { enumerable: true, get: function () { return local_ws_adapter_js_1.startLocalWebSocketProtocolServer; } });
|
|
41
|
+
var chaos_certification_js_1 = require("./chaos-certification.js");
|
|
42
|
+
Object.defineProperty(exports, "explainChaosFixture", { enumerable: true, get: function () { return chaos_certification_js_1.explainChaosFixture; } });
|
|
43
|
+
Object.defineProperty(exports, "runChaosCertificationSuite", { enumerable: true, get: function () { return chaos_certification_js_1.runChaosCertificationSuite; } });
|
|
44
|
+
var engine_parity_js_1 = require("./engine-parity.js");
|
|
45
|
+
Object.defineProperty(exports, "explainEngineParityFixture", { enumerable: true, get: function () { return engine_parity_js_1.explainEngineParityFixture; } });
|
|
46
|
+
Object.defineProperty(exports, "runEngineParitySuite", { enumerable: true, get: function () { return engine_parity_js_1.runEngineParitySuite; } });
|
|
47
|
+
Object.defineProperty(exports, "stableEngineParityJson", { enumerable: true, get: function () { return engine_parity_js_1.stableEngineParityJson; } });
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.runNumberLab = runNumberLab;
|
|
4
|
+
const canonical_js_1 = require("./canonical.js");
|
|
5
|
+
const random_js_1 = require("./random.js");
|
|
6
|
+
const q16 = 65_536;
|
|
7
|
+
function runNumberLab(options) {
|
|
8
|
+
validateOptions(options);
|
|
9
|
+
const random = new random_js_1.DeterministicRandom(options.seed);
|
|
10
|
+
let state = { value: 0, accumulator: 0, frame: 0 };
|
|
11
|
+
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
12
|
+
state = stepNumberLab(options.mode, state, random, frame);
|
|
13
|
+
}
|
|
14
|
+
return {
|
|
15
|
+
mode: options.mode,
|
|
16
|
+
frames: options.frames,
|
|
17
|
+
seed: options.seed,
|
|
18
|
+
finalValue: state.value,
|
|
19
|
+
finalChecksum: (0, canonical_js_1.defaultChecksum)(state),
|
|
20
|
+
result: 'pass',
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
function stepNumberLab(mode, state, random, frame) {
|
|
24
|
+
const signed = random.nextInt(-1000, 1000);
|
|
25
|
+
if (mode === 'integer') {
|
|
26
|
+
return {
|
|
27
|
+
value: state.value + signed,
|
|
28
|
+
accumulator: state.accumulator + (signed % 17),
|
|
29
|
+
frame: frame + 1,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
if (mode === 'fixed-q16') {
|
|
33
|
+
const delta = Math.trunc((signed * q16) / 1000);
|
|
34
|
+
return {
|
|
35
|
+
value: state.value + delta,
|
|
36
|
+
accumulator: state.accumulator + Math.trunc((delta * delta) / q16),
|
|
37
|
+
frame: frame + 1,
|
|
38
|
+
};
|
|
39
|
+
}
|
|
40
|
+
if (mode === 'quantized-float') {
|
|
41
|
+
const raw = state.value / 1000 + signed / 1000;
|
|
42
|
+
const quantized = Math.round(raw * 1000);
|
|
43
|
+
return {
|
|
44
|
+
value: quantized,
|
|
45
|
+
// rdm-ignore-next-line determinism/no-trig-math: number lab intentionally compares quantized float behavior outside gameplay runtime
|
|
46
|
+
accumulator: state.accumulator + Math.round(Math.sqrt(Math.abs(quantized)) * 1000),
|
|
47
|
+
frame: frame + 1,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
if (mode === 'raw-float-basic') {
|
|
51
|
+
const value = state.value + signed / 1000 + 0.1;
|
|
52
|
+
return {
|
|
53
|
+
value,
|
|
54
|
+
accumulator: state.accumulator + value / 3,
|
|
55
|
+
frame: frame + 1,
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
const angle = (frame % 360) * (Math.PI / 180);
|
|
59
|
+
// rdm-ignore-next-line determinism/no-trig-math: raw-float-math mode intentionally demonstrates host math drift in diagnostics
|
|
60
|
+
const value = state.value + Math.sin(angle) * (signed / 1000) + Math.sqrt(Math.abs(signed));
|
|
61
|
+
return {
|
|
62
|
+
value,
|
|
63
|
+
// rdm-ignore-next-line determinism/no-trig-math: raw-float-math mode intentionally demonstrates host math drift in diagnostics
|
|
64
|
+
accumulator: state.accumulator + Math.cos(angle) + Math.atan2(signed, frame + 1),
|
|
65
|
+
frame: frame + 1,
|
|
66
|
+
};
|
|
67
|
+
}
|
|
68
|
+
function validateOptions(options) {
|
|
69
|
+
if (!['integer', 'fixed-q16', 'quantized-float', 'raw-float-basic', 'raw-float-math'].includes(options.mode)) {
|
|
70
|
+
throw new Error(`Unsupported number lab mode ${options.mode}`);
|
|
71
|
+
}
|
|
72
|
+
if (!Number.isInteger(options.frames) || options.frames < 1) {
|
|
73
|
+
throw new Error('number lab requires at least one frame');
|
|
74
|
+
}
|
|
75
|
+
if (!Number.isInteger(options.seed)) {
|
|
76
|
+
throw new Error('number lab seed must be an integer');
|
|
77
|
+
}
|
|
78
|
+
}
|