@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,26 @@
|
|
|
1
|
+
export type EcsEntityId = number;
|
|
2
|
+
export type EcsComponentName = string;
|
|
3
|
+
export type EcsLiteSnapshot = {
|
|
4
|
+
readonly nextEntityId: number;
|
|
5
|
+
readonly entities: readonly EcsEntityId[];
|
|
6
|
+
readonly components: Readonly<Record<EcsComponentName, Readonly<Record<string, unknown>>>>;
|
|
7
|
+
readonly singletons: Readonly<Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
export type EcsLiteQueryResult<ComponentNames extends readonly EcsComponentName[] = readonly EcsComponentName[]> = {
|
|
10
|
+
readonly entityId: EcsEntityId;
|
|
11
|
+
readonly components: Readonly<Record<ComponentNames[number], unknown>>;
|
|
12
|
+
};
|
|
13
|
+
export type EcsLiteWorld = {
|
|
14
|
+
readonly createEntity: () => EcsEntityId;
|
|
15
|
+
readonly destroyEntity: (entityId: EcsEntityId) => void;
|
|
16
|
+
readonly addComponent: (entityId: EcsEntityId, componentName: EcsComponentName, value: unknown) => void;
|
|
17
|
+
readonly removeComponent: (entityId: EcsEntityId, componentName: EcsComponentName) => void;
|
|
18
|
+
readonly getComponent: <T>(entityId: EcsEntityId, componentName: EcsComponentName) => T | undefined;
|
|
19
|
+
readonly setSingleton: (name: string, value: unknown) => void;
|
|
20
|
+
readonly getSingleton: <T>(name: string) => T | undefined;
|
|
21
|
+
readonly query: <ComponentNames extends readonly EcsComponentName[]>(componentNames: ComponentNames) => readonly EcsLiteQueryResult<ComponentNames>[];
|
|
22
|
+
readonly snapshot: () => EcsLiteSnapshot;
|
|
23
|
+
readonly restore: (snapshot: EcsLiteSnapshot) => void;
|
|
24
|
+
readonly checksum: () => string;
|
|
25
|
+
};
|
|
26
|
+
export declare function createEcsLiteWorld(snapshot?: EcsLiteSnapshot): EcsLiteWorld;
|
package/dist/ecs-lite.js
ADDED
|
@@ -0,0 +1,152 @@
|
|
|
1
|
+
import { cloneCanonical, defaultChecksum } from './canonical.js';
|
|
2
|
+
export function createEcsLiteWorld(snapshot) {
|
|
3
|
+
const world = snapshot === undefined
|
|
4
|
+
? { nextEntityId: 1, entities: [], components: new Map(), singletons: new Map() }
|
|
5
|
+
: mutableFromSnapshot(snapshot);
|
|
6
|
+
return {
|
|
7
|
+
createEntity() {
|
|
8
|
+
const entityId = world.nextEntityId;
|
|
9
|
+
world.nextEntityId += 1;
|
|
10
|
+
world.entities.push(entityId);
|
|
11
|
+
world.entities.sort((left, right) => left - right);
|
|
12
|
+
return entityId;
|
|
13
|
+
},
|
|
14
|
+
destroyEntity(entityId) {
|
|
15
|
+
requireEntity(world, entityId);
|
|
16
|
+
world.entities = world.entities.filter((candidate) => candidate !== entityId);
|
|
17
|
+
for (const componentName of Array.from(world.components.keys()).sort(compareStrings)) {
|
|
18
|
+
world.components.get(componentName)?.delete(String(entityId));
|
|
19
|
+
}
|
|
20
|
+
},
|
|
21
|
+
addComponent(entityId, componentName, value) {
|
|
22
|
+
requireEntity(world, entityId);
|
|
23
|
+
const entityKey = String(entityId);
|
|
24
|
+
const componentValues = world.components.get(componentName) ?? new Map();
|
|
25
|
+
componentValues.set(entityKey, cloneCanonical(value));
|
|
26
|
+
world.components.set(componentName, componentValues);
|
|
27
|
+
},
|
|
28
|
+
removeComponent(entityId, componentName) {
|
|
29
|
+
requireEntity(world, entityId);
|
|
30
|
+
world.components.get(componentName)?.delete(String(entityId));
|
|
31
|
+
},
|
|
32
|
+
getComponent(entityId, componentName) {
|
|
33
|
+
requireEntity(world, entityId);
|
|
34
|
+
const value = world.components.get(componentName)?.get(String(entityId));
|
|
35
|
+
return value === undefined ? undefined : cloneCanonical(value);
|
|
36
|
+
},
|
|
37
|
+
setSingleton(name, value) {
|
|
38
|
+
world.singletons.set(name, cloneCanonical(value));
|
|
39
|
+
},
|
|
40
|
+
getSingleton(name) {
|
|
41
|
+
const value = world.singletons.get(name);
|
|
42
|
+
return value === undefined ? undefined : cloneCanonical(value);
|
|
43
|
+
},
|
|
44
|
+
query(componentNames) {
|
|
45
|
+
const sortedNames = componentNames.slice().sort(compareStrings);
|
|
46
|
+
return world.entities.flatMap((entityId) => {
|
|
47
|
+
const components = {};
|
|
48
|
+
for (const componentName of sortedNames) {
|
|
49
|
+
const component = world.components.get(componentName)?.get(String(entityId));
|
|
50
|
+
if (component === undefined) {
|
|
51
|
+
return [];
|
|
52
|
+
}
|
|
53
|
+
components[componentName] = cloneCanonical(component);
|
|
54
|
+
}
|
|
55
|
+
return [{ entityId, components }];
|
|
56
|
+
});
|
|
57
|
+
},
|
|
58
|
+
snapshot() {
|
|
59
|
+
return snapshotFromMutable(world);
|
|
60
|
+
},
|
|
61
|
+
restore(snapshotToRestore) {
|
|
62
|
+
const restored = mutableFromSnapshot(snapshotToRestore);
|
|
63
|
+
world.nextEntityId = restored.nextEntityId;
|
|
64
|
+
world.entities = restored.entities;
|
|
65
|
+
world.components = restored.components;
|
|
66
|
+
world.singletons = restored.singletons;
|
|
67
|
+
},
|
|
68
|
+
checksum() {
|
|
69
|
+
return defaultChecksum(snapshotFromMutable(world));
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
function requireEntity(world, entityId) {
|
|
74
|
+
if (!world.entities.includes(entityId)) {
|
|
75
|
+
throw new Error(`Unknown ECS entity ${entityId}`);
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
function mutableFromSnapshot(snapshot) {
|
|
79
|
+
validateSnapshot(snapshot);
|
|
80
|
+
return {
|
|
81
|
+
nextEntityId: snapshot.nextEntityId,
|
|
82
|
+
entities: snapshot.entities.slice().sort((left, right) => left - right),
|
|
83
|
+
components: componentsMapFromSnapshot(snapshot.components),
|
|
84
|
+
singletons: new Map(Object.keys(snapshot.singletons)
|
|
85
|
+
.sort(compareStrings)
|
|
86
|
+
.map((key) => [key, cloneCanonical(snapshot.singletons[key])])),
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function validateSnapshot(snapshot) {
|
|
90
|
+
validateEntityId(snapshot.nextEntityId, 'nextEntityId');
|
|
91
|
+
const entityIds = new Set();
|
|
92
|
+
let maxEntityId = 0;
|
|
93
|
+
for (const entityId of snapshot.entities) {
|
|
94
|
+
validateEntityId(entityId, 'entity');
|
|
95
|
+
if (entityIds.has(entityId)) {
|
|
96
|
+
throw new Error(`Duplicate ECS entity ${entityId}`);
|
|
97
|
+
}
|
|
98
|
+
entityIds.add(entityId);
|
|
99
|
+
maxEntityId = Math.max(maxEntityId, entityId);
|
|
100
|
+
}
|
|
101
|
+
if (snapshot.nextEntityId <= maxEntityId) {
|
|
102
|
+
throw new Error(`Invalid ECS nextEntityId ${snapshot.nextEntityId}`);
|
|
103
|
+
}
|
|
104
|
+
for (const componentName of Object.keys(snapshot.components).sort(compareStrings)) {
|
|
105
|
+
for (const entityKey of Object.keys(snapshot.components[componentName]).sort(compareStrings)) {
|
|
106
|
+
const entityId = Number(entityKey);
|
|
107
|
+
validateEntityId(entityId, `component ${componentName}`);
|
|
108
|
+
if (entityKey !== String(entityId)) {
|
|
109
|
+
throw new Error(`Invalid ECS component ${componentName} key ${entityKey}`);
|
|
110
|
+
}
|
|
111
|
+
if (!entityIds.has(entityId)) {
|
|
112
|
+
throw new Error(`Unknown ECS entity ${entityId} in component ${componentName}`);
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function validateEntityId(entityId, label) {
|
|
118
|
+
if (!Number.isSafeInteger(entityId) || entityId < 1) {
|
|
119
|
+
throw new Error(`Invalid ECS ${label} ${entityId}`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
function snapshotFromMutable(world) {
|
|
123
|
+
const components = Object.fromEntries(Array.from(world.components.keys()).sort(compareStrings).map((componentName) => {
|
|
124
|
+
const values = world.components.get(componentName);
|
|
125
|
+
return [
|
|
126
|
+
componentName,
|
|
127
|
+
Object.fromEntries(Array.from(values.keys())
|
|
128
|
+
.sort((left, right) => Number(left) - Number(right))
|
|
129
|
+
.map((entityId) => [entityId, cloneCanonical(values.get(entityId))])),
|
|
130
|
+
];
|
|
131
|
+
}));
|
|
132
|
+
const singletons = Object.fromEntries(Array.from(world.singletons.keys())
|
|
133
|
+
.sort(compareStrings)
|
|
134
|
+
.map((key) => [key, cloneCanonical(world.singletons.get(key))]));
|
|
135
|
+
return {
|
|
136
|
+
nextEntityId: world.nextEntityId,
|
|
137
|
+
entities: world.entities.slice().sort((left, right) => left - right),
|
|
138
|
+
components,
|
|
139
|
+
singletons,
|
|
140
|
+
};
|
|
141
|
+
}
|
|
142
|
+
function componentsMapFromSnapshot(components) {
|
|
143
|
+
return new Map(Object.keys(components).sort(compareStrings).map((componentName) => [
|
|
144
|
+
componentName,
|
|
145
|
+
new Map(Object.keys(components[componentName])
|
|
146
|
+
.sort((left, right) => Number(left) - Number(right))
|
|
147
|
+
.map((entityId) => [entityId, cloneCanonical(components[componentName][entityId])])),
|
|
148
|
+
]));
|
|
149
|
+
}
|
|
150
|
+
function compareStrings(left, right) {
|
|
151
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
152
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import type { DeterministicEventRecord } from './events.js';
|
|
2
|
+
export type DeterministicEffectTarget = 'r3f' | 'audio' | 'ui' | 'custom';
|
|
3
|
+
export interface DeterministicEffectDispatch<Payload, Effect> {
|
|
4
|
+
readonly target: DeterministicEffectTarget;
|
|
5
|
+
readonly record: DeterministicEventRecord<Payload>;
|
|
6
|
+
readonly effect: DeterministicRenderSafeEffect<Effect>;
|
|
7
|
+
}
|
|
8
|
+
export type DeterministicRenderSafeEffect<T> = T extends (...args: readonly never[]) => unknown ? never : T extends readonly (infer Item)[] ? readonly DeterministicRenderSafeEffect<Item>[] : T extends object ? {
|
|
9
|
+
readonly [Key in keyof T]: DeterministicRenderSafeEffect<T[Key]>;
|
|
10
|
+
} : T;
|
|
11
|
+
export interface DeterministicEventEffectsAdapter<Payload, Effect> {
|
|
12
|
+
dispatch(records: readonly DeterministicEventRecord<Payload>[]): readonly DeterministicEffectDispatch<Payload, Effect>[];
|
|
13
|
+
getDispatched(): readonly DeterministicEffectDispatch<Payload, Effect>[];
|
|
14
|
+
subscribe(listener: (dispatch: DeterministicEffectDispatch<Payload, Effect>) => void): () => void;
|
|
15
|
+
reset(): void;
|
|
16
|
+
}
|
|
17
|
+
export interface DeterministicEventEffectsAdapterConfig<Payload, Effect> {
|
|
18
|
+
readonly target: DeterministicEffectTarget;
|
|
19
|
+
readonly mapEvent: (record: DeterministicEventRecord<Payload>) => Effect | undefined;
|
|
20
|
+
}
|
|
21
|
+
export declare function createDeterministicEventEffectsAdapter<Payload, Effect>(config: DeterministicEventEffectsAdapterConfig<Payload, Effect>): DeterministicEventEffectsAdapter<Payload, Effect>;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
import { cloneCanonical } from './canonical.js';
|
|
2
|
+
import { stableSortedKeys } from './collections.js';
|
|
3
|
+
import { SyncplayError } from './errors.js';
|
|
4
|
+
export function createDeterministicEventEffectsAdapter(config) {
|
|
5
|
+
validateTarget(config.target);
|
|
6
|
+
const dispatched = [];
|
|
7
|
+
const seen = new Set();
|
|
8
|
+
const listeners = new Set();
|
|
9
|
+
return {
|
|
10
|
+
dispatch(records) {
|
|
11
|
+
const nextDispatches = [];
|
|
12
|
+
for (const record of records) {
|
|
13
|
+
const dedupeKey = `${record.sequence}:${record.predictionState}`;
|
|
14
|
+
if (seen.has(dedupeKey)) {
|
|
15
|
+
continue;
|
|
16
|
+
}
|
|
17
|
+
const effect = config.mapEvent(cloneEventRecord(record));
|
|
18
|
+
seen.add(dedupeKey);
|
|
19
|
+
if (effect === undefined) {
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
const dispatch = freezeEffectDispatch({
|
|
23
|
+
target: config.target,
|
|
24
|
+
record: cloneEventRecord(record),
|
|
25
|
+
effect: freezeEffectValue(cloneCanonical(effect)),
|
|
26
|
+
});
|
|
27
|
+
dispatched.push(dispatch);
|
|
28
|
+
nextDispatches.push(dispatch);
|
|
29
|
+
for (const listener of listeners) {
|
|
30
|
+
listener(dispatch);
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return nextDispatches;
|
|
34
|
+
},
|
|
35
|
+
getDispatched() {
|
|
36
|
+
return dispatched.map((dispatch) => freezeEffectDispatch({
|
|
37
|
+
target: dispatch.target,
|
|
38
|
+
record: cloneEventRecord(dispatch.record),
|
|
39
|
+
effect: freezeEffectValue(cloneCanonical(dispatch.effect)),
|
|
40
|
+
}));
|
|
41
|
+
},
|
|
42
|
+
subscribe(listener) {
|
|
43
|
+
listeners.add(listener);
|
|
44
|
+
return () => {
|
|
45
|
+
listeners.delete(listener);
|
|
46
|
+
};
|
|
47
|
+
},
|
|
48
|
+
reset() {
|
|
49
|
+
dispatched.length = 0;
|
|
50
|
+
seen.clear();
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function cloneEventRecord(record) {
|
|
55
|
+
return freezeEffectValue({
|
|
56
|
+
eventId: record.eventId,
|
|
57
|
+
frame: record.frame,
|
|
58
|
+
sequence: record.sequence,
|
|
59
|
+
payload: cloneCanonical(record.payload),
|
|
60
|
+
predictionState: record.predictionState,
|
|
61
|
+
dedupeKey: record.dedupeKey,
|
|
62
|
+
notHashedKeys: [...record.notHashedKeys],
|
|
63
|
+
deliveryScope: record.deliveryScope,
|
|
64
|
+
playerSlot: record.playerSlot,
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
function freezeEffectDispatch(dispatch) {
|
|
68
|
+
freezeEffectValue(dispatch.record);
|
|
69
|
+
freezeEffectValue(dispatch.effect);
|
|
70
|
+
return Object.freeze(dispatch);
|
|
71
|
+
}
|
|
72
|
+
function freezeEffectValue(value) {
|
|
73
|
+
if (value === null || typeof value !== 'object') {
|
|
74
|
+
return value;
|
|
75
|
+
}
|
|
76
|
+
if (Array.isArray(value)) {
|
|
77
|
+
for (const item of value) {
|
|
78
|
+
freezeEffectValue(item);
|
|
79
|
+
}
|
|
80
|
+
return Object.freeze(value);
|
|
81
|
+
}
|
|
82
|
+
const record = value;
|
|
83
|
+
for (const key of stableSortedKeys(record)) {
|
|
84
|
+
freezeEffectValue(record[key]);
|
|
85
|
+
}
|
|
86
|
+
return Object.freeze(value);
|
|
87
|
+
}
|
|
88
|
+
function validateTarget(target) {
|
|
89
|
+
if (target !== 'r3f' && target !== 'audio' && target !== 'ui' && target !== 'custom') {
|
|
90
|
+
throw new SyncplayError(`Unsupported deterministic effect target ${target}`, {
|
|
91
|
+
code: 'effects.target',
|
|
92
|
+
});
|
|
93
|
+
}
|
|
94
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type DeterministicEngineCompleteCategory = 'typed-frame-runtime' | 'schema-dsl-binary' | 'sparse-set-storage' | 'input-button' | 'system-lifecycle' | 'event-keywords' | 'simulation-signals' | 'commands' | 'replay-bundle' | 'integrated-headless-scene' | 'project-runtime' | 'physics3d' | 'kcc3d' | 'navmesh' | 'bot-documents' | 'animation' | 'config-bundle' | 'local-authority' | 'deterministic-session';
|
|
2
|
+
export interface DeterministicEngineCompleteOptions {
|
|
3
|
+
readonly players?: number;
|
|
4
|
+
readonly frames?: number;
|
|
5
|
+
readonly seed?: number;
|
|
6
|
+
readonly benchmark?: boolean;
|
|
7
|
+
readonly noFastStress?: boolean;
|
|
8
|
+
readonly enforceThresholds?: boolean;
|
|
9
|
+
readonly filter?: string;
|
|
10
|
+
}
|
|
11
|
+
export interface DeterministicEngineCompleteCategoryReport {
|
|
12
|
+
readonly category: DeterministicEngineCompleteCategory;
|
|
13
|
+
readonly result: 'pass' | 'fail';
|
|
14
|
+
readonly checksum: string;
|
|
15
|
+
readonly invariants: Readonly<Record<string, boolean | number | string>>;
|
|
16
|
+
readonly metrics: Readonly<Record<string, boolean | number | string>>;
|
|
17
|
+
readonly failures: readonly string[];
|
|
18
|
+
}
|
|
19
|
+
export interface DeterministicEngineCompleteReport {
|
|
20
|
+
readonly schemaVersion: 1;
|
|
21
|
+
readonly reportId: string;
|
|
22
|
+
readonly players: number;
|
|
23
|
+
readonly frames: number;
|
|
24
|
+
readonly seed: number;
|
|
25
|
+
readonly benchmark: boolean;
|
|
26
|
+
readonly noFastStress: boolean;
|
|
27
|
+
readonly enforceThresholds: boolean;
|
|
28
|
+
readonly provenance?: {
|
|
29
|
+
readonly kind: 'engine-complete-suite';
|
|
30
|
+
readonly version: 1;
|
|
31
|
+
readonly sourceHash: string;
|
|
32
|
+
readonly generator: 'runDeterministicEngineCompleteSuite';
|
|
33
|
+
};
|
|
34
|
+
readonly categories: readonly DeterministicEngineCompleteCategoryReport[];
|
|
35
|
+
readonly failures: readonly string[];
|
|
36
|
+
readonly summary: {
|
|
37
|
+
readonly passed: number;
|
|
38
|
+
readonly failed: number;
|
|
39
|
+
readonly requiredCategories: readonly DeterministicEngineCompleteCategory[];
|
|
40
|
+
};
|
|
41
|
+
readonly result: 'pass' | 'fail';
|
|
42
|
+
}
|
|
43
|
+
export declare function runDeterministicEngineCompleteSuite(...args: readonly [DeterministicEngineCompleteOptions?]): DeterministicEngineCompleteReport;
|
|
44
|
+
export declare function stableDeterministicEngineCompleteJson(report: DeterministicEngineCompleteReport): string;
|
|
45
|
+
export declare function engineCompleteExecutionProofHash(category: DeterministicEngineCompleteCategory, subsystemChecksum: string, metrics: Readonly<Record<string, boolean | number | string>>): string;
|