@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,389 @@
|
|
|
1
|
+
import { defaultChecksum } from './canonical.js';
|
|
2
|
+
import { fixedCircle, fixedVec2, circleIntersects } from './collision.js';
|
|
3
|
+
const rapier2dCompatPackage = '@dimforge/rapier2d-compat';
|
|
4
|
+
const physicsCertResults = {
|
|
5
|
+
true: 'pass',
|
|
6
|
+
false: 'fail',
|
|
7
|
+
};
|
|
8
|
+
export function runPhysicsCert(options) {
|
|
9
|
+
validateOptions(options);
|
|
10
|
+
const uninterrupted = runPhysicsSimulation(options, false);
|
|
11
|
+
const restored = runPhysicsSimulation(options, true);
|
|
12
|
+
const snapshotBytes = [...restored.snapshotBytes].sort((left, right) => left - right);
|
|
13
|
+
const stepTimings = [...restored.stepTimings].sort((left, right) => left - right);
|
|
14
|
+
const snapshotTimings = [...restored.snapshotTimings].sort((left, right) => left - right);
|
|
15
|
+
const restoreTimings = [...restored.restoreTimings].sort((left, right) => left - right);
|
|
16
|
+
const collisionEventsStable = defaultChecksum(uninterrupted.collisionEvents) === defaultChecksum(restored.collisionEvents);
|
|
17
|
+
const finalRestoreStable = uninterrupted.finalChecksum === restored.finalChecksum;
|
|
18
|
+
const snapshotHashStable = restored.snapshotChecksums.every((snapshot) => snapshot.first === snapshot.second);
|
|
19
|
+
const pass = finalRestoreStable &&
|
|
20
|
+
collisionEventsStable &&
|
|
21
|
+
restored.queryOrderStable &&
|
|
22
|
+
snapshotHashStable;
|
|
23
|
+
return {
|
|
24
|
+
engine: options.engine,
|
|
25
|
+
engineVersion: 'builtin-fixed@1',
|
|
26
|
+
adapterVersion: 'physics-cert@1',
|
|
27
|
+
shape: options.shape,
|
|
28
|
+
players: options.players,
|
|
29
|
+
bodies: createInitialBodies(options).length,
|
|
30
|
+
frames: options.frames,
|
|
31
|
+
snapshotEvery: options.snapshotEvery,
|
|
32
|
+
seed: options.seed,
|
|
33
|
+
uninterruptedChecksum: uninterrupted.finalChecksum,
|
|
34
|
+
restoredChecksum: restored.finalChecksum,
|
|
35
|
+
collisionEvents: restored.collisionEvents.length,
|
|
36
|
+
collisionEventsStable,
|
|
37
|
+
queryOrderStable: restored.queryOrderStable,
|
|
38
|
+
snapshotHashStable,
|
|
39
|
+
finalRestoreStable,
|
|
40
|
+
stepMsP95: percentile(stepTimings, 0.95),
|
|
41
|
+
stepMsMax: maxTiming(stepTimings),
|
|
42
|
+
snapshotMsP95: percentile(snapshotTimings, 0.95),
|
|
43
|
+
restoreMsP95: percentile(restoreTimings, 0.95),
|
|
44
|
+
snapshotBytesP95: percentileNumber(snapshotBytes, 0.95),
|
|
45
|
+
heapUsedBytes: getHeapUsedBytes(),
|
|
46
|
+
knownIssueRapier910: 'not-applicable',
|
|
47
|
+
result: physicsCertResult(pass),
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
export async function runRapierPhysicsCert(options) {
|
|
51
|
+
validateRapierOptions(options);
|
|
52
|
+
const rapier = await loadRapier2dCompat();
|
|
53
|
+
const uninterrupted = runRapierSimulation(rapier, options, false);
|
|
54
|
+
const restored = runRapierSimulation(rapier, options, true);
|
|
55
|
+
const snapshotBytes = [...restored.snapshotBytes].sort((left, right) => left - right);
|
|
56
|
+
const stepTimings = [...restored.stepTimings].sort((left, right) => left - right);
|
|
57
|
+
const snapshotTimings = [...restored.snapshotTimings].sort((left, right) => left - right);
|
|
58
|
+
const restoreTimings = [...restored.restoreTimings].sort((left, right) => left - right);
|
|
59
|
+
const collisionEventsStable = defaultChecksum(uninterrupted.collisionEvents) === defaultChecksum(restored.collisionEvents);
|
|
60
|
+
const finalRestoreStable = uninterrupted.finalChecksum === restored.finalChecksum;
|
|
61
|
+
const snapshotHashStable = restored.snapshotChecksums.every((snapshot) => snapshot.first === snapshot.second);
|
|
62
|
+
const pass = finalRestoreStable &&
|
|
63
|
+
collisionEventsStable &&
|
|
64
|
+
restored.queryOrderStable &&
|
|
65
|
+
snapshotHashStable;
|
|
66
|
+
return {
|
|
67
|
+
engine: options.engine,
|
|
68
|
+
engineVersion: `rapier2d-compat@${rapier.version()}`,
|
|
69
|
+
adapterVersion: 'physics-cert-rapier@1',
|
|
70
|
+
shape: options.shape,
|
|
71
|
+
players: options.players,
|
|
72
|
+
bodies: countRapierDynamicBodies(options),
|
|
73
|
+
frames: options.frames,
|
|
74
|
+
snapshotEvery: options.snapshotEvery,
|
|
75
|
+
seed: options.seed,
|
|
76
|
+
uninterruptedChecksum: uninterrupted.finalChecksum,
|
|
77
|
+
restoredChecksum: restored.finalChecksum,
|
|
78
|
+
collisionEvents: restored.collisionEvents.length,
|
|
79
|
+
collisionEventsStable,
|
|
80
|
+
queryOrderStable: restored.queryOrderStable,
|
|
81
|
+
snapshotHashStable,
|
|
82
|
+
finalRestoreStable,
|
|
83
|
+
stepMsP95: percentile(stepTimings, 0.95),
|
|
84
|
+
stepMsMax: maxTiming(stepTimings),
|
|
85
|
+
snapshotMsP95: percentile(snapshotTimings, 0.95),
|
|
86
|
+
restoreMsP95: percentile(restoreTimings, 0.95),
|
|
87
|
+
snapshotBytesP95: percentileNumber(snapshotBytes, 0.95),
|
|
88
|
+
heapUsedBytes: getHeapUsedBytes(),
|
|
89
|
+
knownIssueRapier910: finalRestoreStable && snapshotHashStable ? 'not-reproduced' : 'reproduced',
|
|
90
|
+
packageName: rapier2dCompatPackage,
|
|
91
|
+
packageImportPath: rapier2dCompatPackage,
|
|
92
|
+
result: physicsCertResult(pass),
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
async function loadRapier2dCompat() {
|
|
96
|
+
const mod = await import(rapier2dCompatPackage);
|
|
97
|
+
const warn = console.warn;
|
|
98
|
+
console.warn = () => { };
|
|
99
|
+
try {
|
|
100
|
+
await mod.init({});
|
|
101
|
+
}
|
|
102
|
+
finally {
|
|
103
|
+
console.warn = warn;
|
|
104
|
+
}
|
|
105
|
+
return mod;
|
|
106
|
+
}
|
|
107
|
+
function runRapierSimulation(rapier, options, withRestore) {
|
|
108
|
+
let world = createRapierWorld(rapier, options);
|
|
109
|
+
const stepTimings = [];
|
|
110
|
+
const snapshotTimings = [];
|
|
111
|
+
const restoreTimings = [];
|
|
112
|
+
const snapshotBytes = [];
|
|
113
|
+
const snapshotChecksums = [];
|
|
114
|
+
const collisionEvents = [];
|
|
115
|
+
let queryOrderStable = true;
|
|
116
|
+
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
117
|
+
const stepStartedAt = performance.now();
|
|
118
|
+
world.step();
|
|
119
|
+
stepTimings.push(performance.now() - stepStartedAt);
|
|
120
|
+
const firstContacts = collectRapierContactPairs(world);
|
|
121
|
+
const secondContacts = collectRapierContactPairs(world);
|
|
122
|
+
queryOrderStable = queryOrderStable && defaultChecksum(firstContacts.raw) === defaultChecksum(secondContacts.raw);
|
|
123
|
+
for (const contact of firstContacts.canonical) {
|
|
124
|
+
collisionEvents.push(`${frame + 1}:${contact}`);
|
|
125
|
+
}
|
|
126
|
+
if (withRestore && frame % options.snapshotEvery === 0) {
|
|
127
|
+
const snapshotStartedAt = performance.now();
|
|
128
|
+
const snapshot = world.takeSnapshot();
|
|
129
|
+
snapshotTimings.push(performance.now() - snapshotStartedAt);
|
|
130
|
+
snapshotBytes.push(snapshot.byteLength);
|
|
131
|
+
const restoreStartedAt = performance.now();
|
|
132
|
+
const restored = rapier.World.restoreSnapshot(snapshot);
|
|
133
|
+
restoreTimings.push(performance.now() - restoreStartedAt);
|
|
134
|
+
snapshotChecksums.push({
|
|
135
|
+
first: defaultChecksum(Array.from(snapshot)),
|
|
136
|
+
second: defaultChecksum(Array.from(restored.takeSnapshot())),
|
|
137
|
+
});
|
|
138
|
+
world.free?.();
|
|
139
|
+
world = restored;
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
const finalSnapshot = world.takeSnapshot();
|
|
143
|
+
const finalChecksum = defaultChecksum({
|
|
144
|
+
snapshot: Array.from(finalSnapshot),
|
|
145
|
+
sample: sampleRapierWorld(world),
|
|
146
|
+
});
|
|
147
|
+
world.free?.();
|
|
148
|
+
return {
|
|
149
|
+
finalChecksum,
|
|
150
|
+
collisionEvents,
|
|
151
|
+
queryOrderStable,
|
|
152
|
+
stepTimings,
|
|
153
|
+
snapshotTimings,
|
|
154
|
+
restoreTimings,
|
|
155
|
+
snapshotBytes,
|
|
156
|
+
snapshotChecksums,
|
|
157
|
+
};
|
|
158
|
+
}
|
|
159
|
+
function createRapierWorld(rapier, options) {
|
|
160
|
+
const world = new rapier.World({ x: 0, y: 0 });
|
|
161
|
+
world.integrationParameters.dt = 1 / 30;
|
|
162
|
+
const bounds = options.shape === 'fixed-arena' ? 32 : 96;
|
|
163
|
+
const wall = 1;
|
|
164
|
+
const walls = [
|
|
165
|
+
{ x: 0, y: -bounds - wall, halfWidth: bounds + wall, halfHeight: wall },
|
|
166
|
+
{ x: 0, y: bounds + wall, halfWidth: bounds + wall, halfHeight: wall },
|
|
167
|
+
{ x: -bounds - wall, y: 0, halfWidth: wall, halfHeight: bounds + wall },
|
|
168
|
+
{ x: bounds + wall, y: 0, halfWidth: wall, halfHeight: bounds + wall },
|
|
169
|
+
];
|
|
170
|
+
for (const wallConfig of walls) {
|
|
171
|
+
const body = world.createRigidBody(rapier.RigidBodyDesc.fixed().setTranslation(wallConfig.x, wallConfig.y));
|
|
172
|
+
world.createCollider(rapier.ColliderDesc.cuboid(wallConfig.halfWidth, wallConfig.halfHeight).setRestitution(1).setFriction(0), body);
|
|
173
|
+
}
|
|
174
|
+
const bodiesPerSlot = options.shape === 'fixed-arena' ? 2 : 4;
|
|
175
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
176
|
+
for (let index = 0; index < bodiesPerSlot; index += 1) {
|
|
177
|
+
const bodyIndex = slot * bodiesPerSlot + index;
|
|
178
|
+
const x = -bounds + 4 + ((bodyIndex * 7 + options.seed) % Math.max(8, bounds * 2 - 8));
|
|
179
|
+
const y = -bounds + 4 + ((bodyIndex * 11 + options.seed * 3) % Math.max(8, bounds * 2 - 8));
|
|
180
|
+
const vx = (((slot + index + options.seed) % 5) - 2 || 1) * 2;
|
|
181
|
+
const vy = (((slot * 3 + index + options.seed) % 5) - 2 || -1) * 2;
|
|
182
|
+
const body = world.createRigidBody(rapier.RigidBodyDesc.dynamic()
|
|
183
|
+
.setTranslation(x, y)
|
|
184
|
+
.setLinvel(vx, vy)
|
|
185
|
+
.setLinearDamping(0));
|
|
186
|
+
world.createCollider(rapier.ColliderDesc.ball(1 + ((slot + index + options.seed) % 3) * 0.25).setRestitution(1).setFriction(0), body);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
return world;
|
|
190
|
+
}
|
|
191
|
+
function collectRapierContactPairs(world) {
|
|
192
|
+
const colliderHandles = [];
|
|
193
|
+
const raw = [];
|
|
194
|
+
const canonical = new Set();
|
|
195
|
+
world.colliders.forEach((collider) => {
|
|
196
|
+
colliderHandles.push(String(collider.handle));
|
|
197
|
+
});
|
|
198
|
+
for (const colliderHandle of colliderHandles) {
|
|
199
|
+
world.narrowPhase.contactPairsWith(Number(colliderHandle), (otherCollider) => {
|
|
200
|
+
const pair = [colliderHandle, String(otherCollider)].sort((left, right) => left.localeCompare(right));
|
|
201
|
+
const canonicalPair = `${pair[0]}:${pair[1]}`;
|
|
202
|
+
raw.push(canonicalPair);
|
|
203
|
+
canonical.add(canonicalPair);
|
|
204
|
+
});
|
|
205
|
+
}
|
|
206
|
+
return {
|
|
207
|
+
raw,
|
|
208
|
+
canonical: [...canonical].sort((left, right) => left.localeCompare(right)),
|
|
209
|
+
};
|
|
210
|
+
}
|
|
211
|
+
function sampleRapierWorld(world) {
|
|
212
|
+
const bodies = [];
|
|
213
|
+
world.bodies.forEach((body) => {
|
|
214
|
+
const translation = body.translation();
|
|
215
|
+
const linvel = body.linvel();
|
|
216
|
+
bodies.push({
|
|
217
|
+
handle: String(body.handle),
|
|
218
|
+
x: quantizeRapierNumber(translation.x),
|
|
219
|
+
y: quantizeRapierNumber(translation.y),
|
|
220
|
+
vx: quantizeRapierNumber(linvel.x),
|
|
221
|
+
vy: quantizeRapierNumber(linvel.y),
|
|
222
|
+
});
|
|
223
|
+
});
|
|
224
|
+
return bodies.sort((left, right) => left.handle.localeCompare(right.handle));
|
|
225
|
+
}
|
|
226
|
+
function quantizeRapierNumber(value) {
|
|
227
|
+
const rounded = Math.round(value * 1_000_000);
|
|
228
|
+
return rounded === 0 ? 0 : rounded;
|
|
229
|
+
}
|
|
230
|
+
function countRapierDynamicBodies(options) {
|
|
231
|
+
return options.players * (options.shape === 'fixed-arena' ? 2 : 4);
|
|
232
|
+
}
|
|
233
|
+
function runPhysicsSimulation(options, withRestore) {
|
|
234
|
+
let state = {
|
|
235
|
+
frame: 0,
|
|
236
|
+
bodies: createInitialBodies(options),
|
|
237
|
+
collisionEvents: [],
|
|
238
|
+
};
|
|
239
|
+
const stepTimings = [];
|
|
240
|
+
const snapshotTimings = [];
|
|
241
|
+
const restoreTimings = [];
|
|
242
|
+
const snapshotBytes = [];
|
|
243
|
+
const snapshotChecksums = [];
|
|
244
|
+
let queryOrderStable = true;
|
|
245
|
+
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
246
|
+
const stepStartedAt = performance.now();
|
|
247
|
+
state = stepPhysics(state, options);
|
|
248
|
+
stepTimings.push(performance.now() - stepStartedAt);
|
|
249
|
+
if (withRestore && frame % options.snapshotEvery === 0) {
|
|
250
|
+
const snapshotStartedAt = performance.now();
|
|
251
|
+
const snapshot = JSON.stringify(state);
|
|
252
|
+
snapshotTimings.push(performance.now() - snapshotStartedAt);
|
|
253
|
+
snapshotBytes.push(Buffer.byteLength(snapshot));
|
|
254
|
+
snapshotChecksums.push({ first: defaultChecksum(snapshot), second: defaultChecksum(JSON.stringify(state)) });
|
|
255
|
+
const restoreStartedAt = performance.now();
|
|
256
|
+
state = JSON.parse(snapshot);
|
|
257
|
+
restoreTimings.push(performance.now() - restoreStartedAt);
|
|
258
|
+
}
|
|
259
|
+
const queried = queryBodiesNearOrigin(state);
|
|
260
|
+
const sorted = [...queried].sort((left, right) => left.localeCompare(right));
|
|
261
|
+
queryOrderStable = queryOrderStable && queried.every((id, index) => id === sorted[index]);
|
|
262
|
+
}
|
|
263
|
+
return {
|
|
264
|
+
finalChecksum: defaultChecksum(state),
|
|
265
|
+
collisionEvents: state.collisionEvents,
|
|
266
|
+
queryOrderStable,
|
|
267
|
+
stepTimings,
|
|
268
|
+
snapshotTimings,
|
|
269
|
+
restoreTimings,
|
|
270
|
+
snapshotBytes,
|
|
271
|
+
snapshotChecksums,
|
|
272
|
+
};
|
|
273
|
+
}
|
|
274
|
+
function createInitialBodies(options) {
|
|
275
|
+
const bodiesPerSlot = options.shape === 'fixed-arena' ? 2 : 4;
|
|
276
|
+
const bodies = [];
|
|
277
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
278
|
+
for (let index = 0; index < bodiesPerSlot; index += 1) {
|
|
279
|
+
bodies.push({
|
|
280
|
+
id: `slot-${slot}-body-${index}`,
|
|
281
|
+
slot,
|
|
282
|
+
radius: 6 + ((slot + index + options.seed) % 5),
|
|
283
|
+
x: -400 + slot * 9 + index * 11,
|
|
284
|
+
y: -120 + ((slot * 17 + index * 23 + options.seed) % 240),
|
|
285
|
+
vx: ((slot + index + options.seed) % 7) - 3 || 1,
|
|
286
|
+
vy: ((slot * 3 + index + options.seed) % 5) - 2 || -1,
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
}
|
|
290
|
+
return bodies;
|
|
291
|
+
}
|
|
292
|
+
function stepPhysics(state, options) {
|
|
293
|
+
const moved = state.bodies.map((body) => moveBody(body, options));
|
|
294
|
+
const collisionEvents = [...state.collisionEvents];
|
|
295
|
+
const circles = moved.map((body) => ({ body, circle: fixedCircle(fixedVec2(body.x, body.y), body.radius) }));
|
|
296
|
+
for (let leftIndex = 0; leftIndex < circles.length; leftIndex += 1) {
|
|
297
|
+
for (let rightIndex = leftIndex + 1; rightIndex < circles.length; rightIndex += 1) {
|
|
298
|
+
const left = circles[leftIndex];
|
|
299
|
+
const right = circles[rightIndex];
|
|
300
|
+
if (circleIntersects(left.circle, right.circle)) {
|
|
301
|
+
const ids = [left.body.id, right.body.id].sort();
|
|
302
|
+
collisionEvents.push(`${state.frame + 1}:${ids[0]}:${ids[1]}`);
|
|
303
|
+
}
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
return {
|
|
307
|
+
frame: state.frame + 1,
|
|
308
|
+
bodies: moved.sort((left, right) => left.id.localeCompare(right.id)),
|
|
309
|
+
collisionEvents,
|
|
310
|
+
};
|
|
311
|
+
}
|
|
312
|
+
function moveBody(body, options) {
|
|
313
|
+
const min = options.shape === 'fixed-arena' ? -512 : -1024;
|
|
314
|
+
const max = options.shape === 'fixed-arena' ? 512 : 1024;
|
|
315
|
+
let nextX = body.x + body.vx;
|
|
316
|
+
let nextY = body.y + body.vy;
|
|
317
|
+
let nextVx = body.vx;
|
|
318
|
+
let nextVy = body.vy;
|
|
319
|
+
if (nextX - body.radius < min || nextX + body.radius > max) {
|
|
320
|
+
nextVx = -nextVx;
|
|
321
|
+
nextX = body.x + nextVx;
|
|
322
|
+
}
|
|
323
|
+
if (nextY - body.radius < min || nextY + body.radius > max) {
|
|
324
|
+
nextVy = -nextVy;
|
|
325
|
+
nextY = body.y + nextVy;
|
|
326
|
+
}
|
|
327
|
+
return {
|
|
328
|
+
...body,
|
|
329
|
+
x: nextX,
|
|
330
|
+
y: nextY,
|
|
331
|
+
vx: nextVx,
|
|
332
|
+
vy: nextVy,
|
|
333
|
+
};
|
|
334
|
+
}
|
|
335
|
+
function queryBodiesNearOrigin(state) {
|
|
336
|
+
const origin = fixedCircle(fixedVec2(0, 0), 256);
|
|
337
|
+
return state.bodies
|
|
338
|
+
.filter((body) => circleIntersects(origin, bodyToCircle(body)))
|
|
339
|
+
.map((body) => body.id)
|
|
340
|
+
.sort((left, right) => left.localeCompare(right));
|
|
341
|
+
}
|
|
342
|
+
function bodyToCircle(body) {
|
|
343
|
+
return fixedCircle(fixedVec2(body.x, body.y), body.radius);
|
|
344
|
+
}
|
|
345
|
+
function percentile(timings, percent) {
|
|
346
|
+
return percentileNumber(timings, percent).toFixed(4);
|
|
347
|
+
}
|
|
348
|
+
function percentileNumber(values, percent) {
|
|
349
|
+
const index = Math.min(values.length - 1, Math.ceil(values.length * percent) - 1);
|
|
350
|
+
return values[index];
|
|
351
|
+
}
|
|
352
|
+
function maxTiming(timings) {
|
|
353
|
+
return timings.at(-1).toFixed(4);
|
|
354
|
+
}
|
|
355
|
+
function getHeapUsedBytes() {
|
|
356
|
+
return typeof process === 'undefined' ? 0 : process.memoryUsage().heapUsed;
|
|
357
|
+
}
|
|
358
|
+
function physicsCertResult(pass) {
|
|
359
|
+
return physicsCertResults[String(pass)];
|
|
360
|
+
}
|
|
361
|
+
function validateOptions(options) {
|
|
362
|
+
if (options.engine !== 'builtin-fixed') {
|
|
363
|
+
throw new Error('physics certification currently supports engine=builtin-fixed');
|
|
364
|
+
}
|
|
365
|
+
validateCommonOptions(options);
|
|
366
|
+
}
|
|
367
|
+
function validateRapierOptions(options) {
|
|
368
|
+
if (options.engine !== 'rapier2d-compat') {
|
|
369
|
+
throw new Error('Rapier physics certification requires engine=rapier2d-compat');
|
|
370
|
+
}
|
|
371
|
+
validateCommonOptions(options);
|
|
372
|
+
}
|
|
373
|
+
function validateCommonOptions(options) {
|
|
374
|
+
if (options.shape !== 'fixed-arena' && options.shape !== 'slot-load') {
|
|
375
|
+
throw new Error('physics certification shape must be fixed-arena or slot-load');
|
|
376
|
+
}
|
|
377
|
+
if (!Number.isInteger(options.players) || options.players < 1 || options.players > 128) {
|
|
378
|
+
throw new Error('physics certification players must be between 1 and 128');
|
|
379
|
+
}
|
|
380
|
+
if (!Number.isInteger(options.frames) || options.frames < 1) {
|
|
381
|
+
throw new Error('physics certification frames must be at least 1');
|
|
382
|
+
}
|
|
383
|
+
if (!Number.isInteger(options.seed)) {
|
|
384
|
+
throw new Error('physics certification seed must be an integer');
|
|
385
|
+
}
|
|
386
|
+
if (!Number.isInteger(options.snapshotEvery) || options.snapshotEvery < 1) {
|
|
387
|
+
throw new Error('snapshotEvery must be a positive integer');
|
|
388
|
+
}
|
|
389
|
+
}
|
|
@@ -0,0 +1,142 @@
|
|
|
1
|
+
import { type DeterministicPhysicsMaterial } from './deterministic-primitives.js';
|
|
2
|
+
export type DeterministicBodyKind = 'static' | 'dynamic' | 'kinematic';
|
|
3
|
+
export type DeterministicContactEventType = 'enter' | 'stay' | 'exit';
|
|
4
|
+
export type DeterministicPhysicsShape2D = {
|
|
5
|
+
readonly type: 'aabb';
|
|
6
|
+
} | {
|
|
7
|
+
readonly type: 'circle';
|
|
8
|
+
readonly radius: number;
|
|
9
|
+
} | {
|
|
10
|
+
readonly type: 'capsule';
|
|
11
|
+
readonly radius: number;
|
|
12
|
+
readonly halfHeight: number;
|
|
13
|
+
} | {
|
|
14
|
+
readonly type: 'convex';
|
|
15
|
+
readonly vertices: readonly {
|
|
16
|
+
readonly x: number;
|
|
17
|
+
readonly y: number;
|
|
18
|
+
}[];
|
|
19
|
+
};
|
|
20
|
+
export interface DeterministicPhysicsBody2D {
|
|
21
|
+
readonly id: string;
|
|
22
|
+
readonly kind: DeterministicBodyKind;
|
|
23
|
+
readonly x: number;
|
|
24
|
+
readonly y: number;
|
|
25
|
+
readonly z?: number;
|
|
26
|
+
readonly vx: number;
|
|
27
|
+
readonly vy: number;
|
|
28
|
+
readonly halfWidth: number;
|
|
29
|
+
readonly halfHeight: number;
|
|
30
|
+
readonly shape?: DeterministicPhysicsShape2D;
|
|
31
|
+
readonly halfDepth?: number;
|
|
32
|
+
readonly layer: number;
|
|
33
|
+
readonly mask: number;
|
|
34
|
+
readonly sensor?: boolean;
|
|
35
|
+
readonly trigger?: boolean;
|
|
36
|
+
readonly mass?: number;
|
|
37
|
+
readonly damping?: number;
|
|
38
|
+
readonly material?: DeterministicPhysicsMaterial;
|
|
39
|
+
}
|
|
40
|
+
export interface DeterministicPhysicsConstraint2D {
|
|
41
|
+
readonly id: string;
|
|
42
|
+
readonly type: 'distance' | 'fixed' | 'spring' | 'motor';
|
|
43
|
+
readonly a: string;
|
|
44
|
+
readonly b?: string;
|
|
45
|
+
readonly restLength?: number;
|
|
46
|
+
readonly offsetX?: number;
|
|
47
|
+
readonly offsetY?: number;
|
|
48
|
+
readonly stiffness?: number;
|
|
49
|
+
readonly motorVx?: number;
|
|
50
|
+
readonly motorVy?: number;
|
|
51
|
+
}
|
|
52
|
+
export interface DeterministicPhysicsWorld2D {
|
|
53
|
+
readonly frame: number;
|
|
54
|
+
readonly bodies: readonly DeterministicPhysicsBody2D[];
|
|
55
|
+
readonly activePairs: readonly string[];
|
|
56
|
+
readonly activePairMetadata: readonly DeterministicPhysicsActivePair2D[];
|
|
57
|
+
}
|
|
58
|
+
export interface DeterministicPhysicsActivePair2D {
|
|
59
|
+
readonly pair: string;
|
|
60
|
+
readonly a: string;
|
|
61
|
+
readonly b: string;
|
|
62
|
+
readonly sensor: boolean;
|
|
63
|
+
readonly trigger: boolean;
|
|
64
|
+
readonly material: DeterministicPhysicsMaterial;
|
|
65
|
+
}
|
|
66
|
+
export interface DeterministicPhysicsStepOptions2D {
|
|
67
|
+
readonly gravityX?: number;
|
|
68
|
+
readonly gravityY?: number;
|
|
69
|
+
readonly constraints?: readonly DeterministicPhysicsConstraint2D[];
|
|
70
|
+
readonly iterations?: number;
|
|
71
|
+
}
|
|
72
|
+
export interface DeterministicContactEvent2D {
|
|
73
|
+
readonly type: DeterministicContactEventType;
|
|
74
|
+
readonly pair: string;
|
|
75
|
+
readonly a: string;
|
|
76
|
+
readonly b: string;
|
|
77
|
+
readonly sensor: boolean;
|
|
78
|
+
readonly trigger: boolean;
|
|
79
|
+
readonly material: DeterministicPhysicsMaterial;
|
|
80
|
+
}
|
|
81
|
+
export interface DeterministicPhysicsStepResult2D {
|
|
82
|
+
readonly world: DeterministicPhysicsWorld2D;
|
|
83
|
+
readonly events: readonly DeterministicContactEvent2D[];
|
|
84
|
+
readonly broadphasePairs: number;
|
|
85
|
+
readonly narrowphaseContacts: number;
|
|
86
|
+
readonly solverIterations: number;
|
|
87
|
+
readonly checksum: string;
|
|
88
|
+
}
|
|
89
|
+
export interface DeterministicPhysicsQueryOptions2D {
|
|
90
|
+
readonly layerMask?: number;
|
|
91
|
+
readonly includeSensors?: boolean;
|
|
92
|
+
readonly includeTriggers?: boolean;
|
|
93
|
+
readonly hitStatics?: boolean;
|
|
94
|
+
readonly hitDynamics?: boolean;
|
|
95
|
+
readonly hitKinematics?: boolean;
|
|
96
|
+
readonly hitSolids?: boolean;
|
|
97
|
+
readonly hitSensors?: boolean;
|
|
98
|
+
readonly hitTriggers?: boolean;
|
|
99
|
+
}
|
|
100
|
+
export interface DeterministicPhysicsAabbQuery2D extends DeterministicPhysicsQueryOptions2D {
|
|
101
|
+
readonly x: number;
|
|
102
|
+
readonly y: number;
|
|
103
|
+
readonly halfWidth: number;
|
|
104
|
+
readonly halfHeight: number;
|
|
105
|
+
}
|
|
106
|
+
export interface DeterministicPhysicsRaycastQuery2D extends DeterministicPhysicsQueryOptions2D {
|
|
107
|
+
readonly x: number;
|
|
108
|
+
readonly y: number;
|
|
109
|
+
readonly dx: number;
|
|
110
|
+
readonly dy: number;
|
|
111
|
+
readonly maxDistance: number;
|
|
112
|
+
}
|
|
113
|
+
export interface DeterministicPhysicsShapeOverlapQuery2D extends DeterministicPhysicsQueryOptions2D {
|
|
114
|
+
readonly shape: 'aabb' | 'circle';
|
|
115
|
+
readonly x: number;
|
|
116
|
+
readonly y: number;
|
|
117
|
+
readonly z?: number;
|
|
118
|
+
readonly halfWidth?: number;
|
|
119
|
+
readonly halfHeight?: number;
|
|
120
|
+
readonly halfDepth?: number;
|
|
121
|
+
readonly radius?: number;
|
|
122
|
+
}
|
|
123
|
+
export interface DeterministicPhysicsCircleCastQuery2D extends DeterministicPhysicsQueryOptions2D {
|
|
124
|
+
readonly x: number;
|
|
125
|
+
readonly y: number;
|
|
126
|
+
readonly radius: number;
|
|
127
|
+
readonly dx: number;
|
|
128
|
+
readonly dy: number;
|
|
129
|
+
readonly maxDistance: number;
|
|
130
|
+
}
|
|
131
|
+
export interface DeterministicPhysicsHit2D {
|
|
132
|
+
readonly bodyId: string;
|
|
133
|
+
readonly distance: number;
|
|
134
|
+
}
|
|
135
|
+
export declare function createDeterministicPhysicsWorld2D(bodies: readonly DeterministicPhysicsBody2D[]): DeterministicPhysicsWorld2D;
|
|
136
|
+
export declare function stepDeterministicPhysicsWorld2D(world: DeterministicPhysicsWorld2D, options?: DeterministicPhysicsStepOptions2D): DeterministicPhysicsStepResult2D;
|
|
137
|
+
export declare function queryDeterministicPhysicsAabb2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsAabbQuery2D): readonly string[];
|
|
138
|
+
export declare function raycastDeterministicPhysics2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsRaycastQuery2D): readonly DeterministicPhysicsHit2D[];
|
|
139
|
+
export declare function raycastAllDeterministicPhysics2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsRaycastQuery2D): readonly DeterministicPhysicsHit2D[];
|
|
140
|
+
export declare function nearestDeterministicPhysicsHit2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsRaycastQuery2D): DeterministicPhysicsHit2D | undefined;
|
|
141
|
+
export declare function queryDeterministicPhysicsShapeOverlap2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsShapeOverlapQuery2D): readonly string[];
|
|
142
|
+
export declare function circleCastDeterministicPhysics2D(world: DeterministicPhysicsWorld2D, query: DeterministicPhysicsCircleCastQuery2D): readonly DeterministicPhysicsHit2D[];
|