@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
package/dist/errors.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export declare class SyncplayError extends Error {
|
|
2
|
+
readonly code: string;
|
|
3
|
+
readonly frame?: number;
|
|
4
|
+
readonly slot?: number;
|
|
5
|
+
constructor(message: string, options: {
|
|
6
|
+
code: string;
|
|
7
|
+
frame?: number;
|
|
8
|
+
slot?: number;
|
|
9
|
+
});
|
|
10
|
+
}
|
|
11
|
+
export declare function invariant(condition: boolean, message: string, code: string): asserts condition;
|
package/dist/errors.js
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export class SyncplayError extends Error {
|
|
2
|
+
code;
|
|
3
|
+
frame;
|
|
4
|
+
slot;
|
|
5
|
+
constructor(message, options) {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = 'SyncplayError';
|
|
8
|
+
this.code = options.code;
|
|
9
|
+
this.frame = options.frame;
|
|
10
|
+
this.slot = options.slot;
|
|
11
|
+
}
|
|
12
|
+
}
|
|
13
|
+
export function invariant(condition, message, code) {
|
|
14
|
+
if (!condition) {
|
|
15
|
+
throw new SyncplayError(message, { code });
|
|
16
|
+
}
|
|
17
|
+
}
|
package/dist/events.d.ts
ADDED
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type DeterministicEventPredictionState = 'predicted' | 'confirmed' | 'canceled' | 'verifiedOnly' | 'synced';
|
|
2
|
+
export type DeterministicEventDeliveryScope = 'all' | 'local' | 'remote';
|
|
3
|
+
export interface DeterministicEventEmitOptions {
|
|
4
|
+
readonly dedupeKey?: string;
|
|
5
|
+
readonly notHashedKeys?: readonly string[];
|
|
6
|
+
readonly deliveryScope?: DeterministicEventDeliveryScope;
|
|
7
|
+
readonly playerSlot?: number;
|
|
8
|
+
}
|
|
9
|
+
export interface DeterministicEventRecord<Payload> {
|
|
10
|
+
readonly eventId: string;
|
|
11
|
+
readonly frame: number;
|
|
12
|
+
readonly sequence: number;
|
|
13
|
+
readonly payload: Payload;
|
|
14
|
+
readonly predictionState: DeterministicEventPredictionState;
|
|
15
|
+
readonly dedupeKey: string;
|
|
16
|
+
readonly notHashedKeys: readonly string[];
|
|
17
|
+
readonly deliveryScope?: DeterministicEventDeliveryScope;
|
|
18
|
+
readonly playerSlot?: number;
|
|
19
|
+
}
|
|
20
|
+
export interface DeterministicEventTimeline<Payload> {
|
|
21
|
+
emitPredicted(eventId: string, frame: number, payload: Payload, options?: string | DeterministicEventEmitOptions): DeterministicEventRecord<Payload>;
|
|
22
|
+
emitSynced(eventId: string, frame: number, payload: Payload, options?: string | DeterministicEventEmitOptions): DeterministicEventRecord<Payload>;
|
|
23
|
+
emitVerifiedOnly(eventId: string, frame: number, payload: Payload, options?: string | DeterministicEventEmitOptions): DeterministicEventRecord<Payload>;
|
|
24
|
+
confirmThroughFrame(frame: number): readonly DeterministicEventRecord<Payload>[];
|
|
25
|
+
cancelFromFrame(frame: number): readonly DeterministicEventRecord<Payload>[];
|
|
26
|
+
records(): readonly DeterministicEventRecord<Payload>[];
|
|
27
|
+
}
|
|
28
|
+
export interface DeterministicEventKeywordFixtureResult {
|
|
29
|
+
readonly checksum: string;
|
|
30
|
+
readonly restoredChecksum: string;
|
|
31
|
+
readonly rollbackEquivalent: boolean;
|
|
32
|
+
readonly syncedEvents: number;
|
|
33
|
+
readonly confirmedEvents: number;
|
|
34
|
+
readonly canceledEvents: number;
|
|
35
|
+
readonly verifiedOnlyEvents: number;
|
|
36
|
+
readonly notHashedDuplicateRejected: boolean;
|
|
37
|
+
readonly notHashedDedupeStable: boolean;
|
|
38
|
+
readonly syncedSurvivedConfirmCancel: boolean;
|
|
39
|
+
readonly localEvents: number;
|
|
40
|
+
readonly remoteEvents: number;
|
|
41
|
+
readonly localRemoteDispatchStable: boolean;
|
|
42
|
+
}
|
|
43
|
+
export declare function createDeterministicEventTimeline<Payload>(): DeterministicEventTimeline<Payload>;
|
|
44
|
+
export declare function runDeterministicEventKeywordFixture(): DeterministicEventKeywordFixtureResult;
|
|
45
|
+
export declare function filterDeterministicEventsForPlayer<Payload>(records: readonly DeterministicEventRecord<Payload>[], localSlot: number): readonly DeterministicEventRecord<Payload>[];
|
package/dist/events.js
ADDED
|
@@ -0,0 +1,242 @@
|
|
|
1
|
+
import { canonicalStringify, cloneCanonical, defaultChecksum } from './canonical.js';
|
|
2
|
+
import { SyncplayError } from './errors.js';
|
|
3
|
+
export function createDeterministicEventTimeline() {
|
|
4
|
+
const records = [];
|
|
5
|
+
const emittedVerifiedOnly = new Set();
|
|
6
|
+
let nextSequence = 0;
|
|
7
|
+
return {
|
|
8
|
+
emitPredicted(eventId, frame, payload, options) {
|
|
9
|
+
validateEvent(eventId, frame);
|
|
10
|
+
const record = makeRecord(eventId, frame, nextSequence, payload, 'predicted', normalizeEmitOptions(options));
|
|
11
|
+
nextSequence += 1;
|
|
12
|
+
records.push(record);
|
|
13
|
+
return cloneEventRecord(record);
|
|
14
|
+
},
|
|
15
|
+
emitSynced(eventId, frame, payload, options) {
|
|
16
|
+
validateEvent(eventId, frame);
|
|
17
|
+
const record = makeRecord(eventId, frame, nextSequence, payload, 'synced', normalizeEmitOptions(options));
|
|
18
|
+
nextSequence += 1;
|
|
19
|
+
records.push(record);
|
|
20
|
+
return cloneEventRecord(record);
|
|
21
|
+
},
|
|
22
|
+
emitVerifiedOnly(eventId, frame, payload, options) {
|
|
23
|
+
validateEvent(eventId, frame);
|
|
24
|
+
const emitOptions = normalizeEmitOptions(options);
|
|
25
|
+
const key = emitOptions.dedupeKey ?? makeDedupeKey(eventId, frame, payload, emitOptions.notHashedKeys);
|
|
26
|
+
if (emittedVerifiedOnly.has(key)) {
|
|
27
|
+
throw new SyncplayError(`Verified-only event ${eventId} already emitted for ${key}`, {
|
|
28
|
+
code: 'events.duplicate-verified-only',
|
|
29
|
+
frame,
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
emittedVerifiedOnly.add(key);
|
|
33
|
+
const record = makeRecord(eventId, frame, nextSequence, payload, 'verifiedOnly', { ...emitOptions, dedupeKey: key });
|
|
34
|
+
nextSequence += 1;
|
|
35
|
+
records.push(record);
|
|
36
|
+
return cloneEventRecord(record);
|
|
37
|
+
},
|
|
38
|
+
confirmThroughFrame(frame) {
|
|
39
|
+
validateFrame(frame);
|
|
40
|
+
const confirmed = [];
|
|
41
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
42
|
+
const record = records[index];
|
|
43
|
+
if (record.predictionState === 'predicted' && record.frame <= frame) {
|
|
44
|
+
const next = { ...record, predictionState: 'confirmed' };
|
|
45
|
+
records[index] = next;
|
|
46
|
+
confirmed.push(next);
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
return confirmed.map((record) => cloneEventRecord(record));
|
|
50
|
+
},
|
|
51
|
+
cancelFromFrame(frame) {
|
|
52
|
+
validateFrame(frame);
|
|
53
|
+
const canceled = [];
|
|
54
|
+
for (let index = 0; index < records.length; index += 1) {
|
|
55
|
+
const record = records[index];
|
|
56
|
+
if (record.predictionState === 'predicted' && record.frame >= frame) {
|
|
57
|
+
const next = { ...record, predictionState: 'canceled' };
|
|
58
|
+
records[index] = next;
|
|
59
|
+
canceled.push(next);
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
return canceled.map((record) => cloneEventRecord(record));
|
|
63
|
+
},
|
|
64
|
+
records() {
|
|
65
|
+
return records.map((record) => cloneEventRecord(record));
|
|
66
|
+
},
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function runDeterministicEventKeywordFixture() {
|
|
70
|
+
const first = runEventKeywordScenario();
|
|
71
|
+
const restored = runEventKeywordScenario();
|
|
72
|
+
const checksum = defaultChecksum(first);
|
|
73
|
+
const restoredChecksum = defaultChecksum(restored);
|
|
74
|
+
return {
|
|
75
|
+
checksum,
|
|
76
|
+
restoredChecksum,
|
|
77
|
+
rollbackEquivalent: checksum === restoredChecksum,
|
|
78
|
+
syncedEvents: first.records.filter((record) => record.predictionState === 'synced').length,
|
|
79
|
+
confirmedEvents: first.records.filter((record) => record.predictionState === 'confirmed').length,
|
|
80
|
+
canceledEvents: first.records.filter((record) => record.predictionState === 'canceled').length,
|
|
81
|
+
verifiedOnlyEvents: first.records.filter((record) => record.predictionState === 'verifiedOnly').length,
|
|
82
|
+
notHashedDuplicateRejected: first.notHashedDuplicateRejected,
|
|
83
|
+
notHashedDedupeStable: first.notHashedDedupeStable,
|
|
84
|
+
syncedSurvivedConfirmCancel: first.syncedSurvivedConfirmCancel,
|
|
85
|
+
localEvents: first.localPlayerRecords.length,
|
|
86
|
+
remoteEvents: first.remotePlayerRecords.length,
|
|
87
|
+
localRemoteDispatchStable: defaultChecksum(first.localPlayerRecords) === defaultChecksum(restored.localPlayerRecords)
|
|
88
|
+
&& defaultChecksum(first.remotePlayerRecords) === defaultChecksum(restored.remotePlayerRecords),
|
|
89
|
+
};
|
|
90
|
+
}
|
|
91
|
+
export function filterDeterministicEventsForPlayer(records, localSlot) {
|
|
92
|
+
if (!Number.isInteger(localSlot) || localSlot < 0) {
|
|
93
|
+
throw new SyncplayError('Local event player slot must be a non-negative integer', {
|
|
94
|
+
code: 'events.local-slot',
|
|
95
|
+
slot: localSlot,
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
return records.filter((record) => shouldDispatchRecordToPlayer(record, localSlot)).map((record) => cloneEventRecord(record));
|
|
99
|
+
}
|
|
100
|
+
function runEventKeywordScenario() {
|
|
101
|
+
const timeline = createDeterministicEventTimeline();
|
|
102
|
+
timeline.emitPredicted('dash', 2, { slot: 0 }, { deliveryScope: 'local', playerSlot: 0 });
|
|
103
|
+
timeline.emitPredicted('remote.dash', 2, { slot: 0 }, { deliveryScope: 'remote', playerSlot: 0 });
|
|
104
|
+
const synced = timeline.emitSynced('spawn.synced', 2, { slot: 1, cosmetic: 'spark' }, { notHashedKeys: ['cosmetic'] });
|
|
105
|
+
const verified = timeline.emitVerifiedOnly('score', 3, { slot: 1, cosmetic: 'spark' }, { notHashedKeys: ['cosmetic'] });
|
|
106
|
+
let duplicateRejected = false;
|
|
107
|
+
try {
|
|
108
|
+
timeline.emitVerifiedOnly('score', 3, { slot: 1, cosmetic: 'smoke' }, { notHashedKeys: ['cosmetic'] });
|
|
109
|
+
}
|
|
110
|
+
catch (error) {
|
|
111
|
+
duplicateRejected = error instanceof SyncplayError;
|
|
112
|
+
}
|
|
113
|
+
timeline.confirmThroughFrame(2);
|
|
114
|
+
timeline.emitPredicted('late.predicted', 5, { slot: 2 });
|
|
115
|
+
timeline.cancelFromFrame(4);
|
|
116
|
+
const records = timeline.records();
|
|
117
|
+
return {
|
|
118
|
+
records,
|
|
119
|
+
localPlayerRecords: filterDeterministicEventsForPlayer(records, 0),
|
|
120
|
+
remotePlayerRecords: filterDeterministicEventsForPlayer(records, 1),
|
|
121
|
+
notHashedDuplicateRejected: duplicateRejected,
|
|
122
|
+
notHashedDedupeStable: verified.dedupeKey === `score:3:${defaultChecksum({ slot: 1 })}`,
|
|
123
|
+
syncedSurvivedConfirmCancel: records.some((record) => record.sequence === synced.sequence && record.predictionState === 'synced'),
|
|
124
|
+
};
|
|
125
|
+
}
|
|
126
|
+
function makeRecord(eventId, frame, sequence, payload, predictionState, options) {
|
|
127
|
+
return {
|
|
128
|
+
eventId,
|
|
129
|
+
frame,
|
|
130
|
+
sequence,
|
|
131
|
+
payload: cloneCanonical(payload),
|
|
132
|
+
predictionState,
|
|
133
|
+
dedupeKey: options.dedupeKey ?? makeDedupeKey(eventId, frame, payload, options.notHashedKeys),
|
|
134
|
+
notHashedKeys: [...(options.notHashedKeys ?? [])].sort(compareStrings),
|
|
135
|
+
deliveryScope: options.deliveryScope ?? 'all',
|
|
136
|
+
playerSlot: options.playerSlot,
|
|
137
|
+
};
|
|
138
|
+
}
|
|
139
|
+
function cloneEventRecord(record) {
|
|
140
|
+
return {
|
|
141
|
+
eventId: record.eventId,
|
|
142
|
+
frame: record.frame,
|
|
143
|
+
sequence: record.sequence,
|
|
144
|
+
payload: cloneCanonical(record.payload),
|
|
145
|
+
predictionState: record.predictionState,
|
|
146
|
+
dedupeKey: record.dedupeKey,
|
|
147
|
+
notHashedKeys: [...record.notHashedKeys],
|
|
148
|
+
deliveryScope: record.deliveryScope,
|
|
149
|
+
playerSlot: record.playerSlot,
|
|
150
|
+
};
|
|
151
|
+
}
|
|
152
|
+
function normalizeEmitOptions(options) {
|
|
153
|
+
if (options === undefined) {
|
|
154
|
+
return {};
|
|
155
|
+
}
|
|
156
|
+
if (typeof options === 'string') {
|
|
157
|
+
return { dedupeKey: options };
|
|
158
|
+
}
|
|
159
|
+
if (options.deliveryScope !== undefined && options.deliveryScope !== 'all' && options.deliveryScope !== 'local' && options.deliveryScope !== 'remote') {
|
|
160
|
+
throw new SyncplayError(`Unsupported event delivery scope ${options.deliveryScope}`, {
|
|
161
|
+
code: 'events.delivery-scope',
|
|
162
|
+
});
|
|
163
|
+
}
|
|
164
|
+
if (options.playerSlot !== undefined &&
|
|
165
|
+
(!Number.isInteger(options.playerSlot) || options.playerSlot < 0)) {
|
|
166
|
+
throw new SyncplayError('Event playerSlot must be a non-negative integer', {
|
|
167
|
+
code: 'events.player-slot',
|
|
168
|
+
slot: options.playerSlot,
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
dedupeKey: options.dedupeKey,
|
|
173
|
+
notHashedKeys: options.notHashedKeys === undefined ? undefined : [...options.notHashedKeys].sort(compareStrings),
|
|
174
|
+
deliveryScope: options.deliveryScope ?? 'all',
|
|
175
|
+
playerSlot: options.playerSlot,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
function shouldDispatchRecordToPlayer(record, localSlot) {
|
|
179
|
+
const scope = record.deliveryScope ?? 'all';
|
|
180
|
+
if (scope === 'all') {
|
|
181
|
+
return true;
|
|
182
|
+
}
|
|
183
|
+
if (scope !== 'local' && scope !== 'remote') {
|
|
184
|
+
throw new SyncplayError(`Unsupported event delivery scope ${scope}`, {
|
|
185
|
+
code: 'events.delivery-scope',
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
if (record.playerSlot === undefined) {
|
|
189
|
+
throw new SyncplayError(`Event ${record.eventId} is missing playerSlot for ${scope} delivery`, {
|
|
190
|
+
code: 'events.player-slot',
|
|
191
|
+
frame: record.frame,
|
|
192
|
+
});
|
|
193
|
+
}
|
|
194
|
+
if (!Number.isInteger(record.playerSlot) || record.playerSlot < 0) {
|
|
195
|
+
throw new SyncplayError('Event playerSlot must be a non-negative integer', {
|
|
196
|
+
code: 'events.player-slot',
|
|
197
|
+
frame: record.frame,
|
|
198
|
+
slot: record.playerSlot,
|
|
199
|
+
});
|
|
200
|
+
}
|
|
201
|
+
return scope === 'local' ? record.playerSlot === localSlot : record.playerSlot !== localSlot;
|
|
202
|
+
}
|
|
203
|
+
function makeDedupeKey(eventId, frame, payload, notHashedKeys = []) {
|
|
204
|
+
return `${eventId}:${frame}:${defaultChecksum(stripNotHashedPayloadKeys(payload, notHashedKeys))}`;
|
|
205
|
+
}
|
|
206
|
+
function stripNotHashedPayloadKeys(payload, notHashedKeys) {
|
|
207
|
+
if (notHashedKeys.length === 0 || payload === null || typeof payload !== 'object' || Array.isArray(payload)) {
|
|
208
|
+
return payload;
|
|
209
|
+
}
|
|
210
|
+
const ignored = new Set(notHashedKeys);
|
|
211
|
+
const source = payload;
|
|
212
|
+
const stripped = {};
|
|
213
|
+
for (const key of stableRecordKeys(source)) {
|
|
214
|
+
if (!ignored.has(key)) {
|
|
215
|
+
stripped[key] = source[key];
|
|
216
|
+
}
|
|
217
|
+
}
|
|
218
|
+
return stripped;
|
|
219
|
+
}
|
|
220
|
+
function validateEvent(eventId, frame) {
|
|
221
|
+
if (eventId.length === 0) {
|
|
222
|
+
throw new SyncplayError('eventId is required', { code: 'events.event-id' });
|
|
223
|
+
}
|
|
224
|
+
canonicalStringify(eventId);
|
|
225
|
+
validateFrame(frame);
|
|
226
|
+
}
|
|
227
|
+
function validateFrame(frame) {
|
|
228
|
+
if (!Number.isInteger(frame) || frame < 0) {
|
|
229
|
+
throw new SyncplayError('Event frame must be a non-negative integer', {
|
|
230
|
+
code: 'events.frame',
|
|
231
|
+
frame,
|
|
232
|
+
});
|
|
233
|
+
}
|
|
234
|
+
}
|
|
235
|
+
function compareStrings(left, right) {
|
|
236
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
237
|
+
}
|
|
238
|
+
function stableRecordKeys(record) {
|
|
239
|
+
return Reflect.ownKeys(record)
|
|
240
|
+
.filter((key) => typeof key === 'string')
|
|
241
|
+
.sort(compareStrings);
|
|
242
|
+
}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { type DeterministicEventRecord } from '../events.js';
|
|
2
|
+
import { DeterministicRandom } from '../random.js';
|
|
3
|
+
import type { OfflineSession, ReplayFile, ReplayRecordingMode } from '../types.js';
|
|
4
|
+
export interface CounterDuelInput {
|
|
5
|
+
readonly attack: boolean;
|
|
6
|
+
readonly targetSlot: number;
|
|
7
|
+
}
|
|
8
|
+
export interface CounterDuelState {
|
|
9
|
+
readonly frame: number;
|
|
10
|
+
readonly health: readonly number[];
|
|
11
|
+
readonly score: readonly number[];
|
|
12
|
+
readonly cooldown: readonly number[];
|
|
13
|
+
}
|
|
14
|
+
export interface CounterDuelOptions {
|
|
15
|
+
readonly players: number;
|
|
16
|
+
readonly frames: number;
|
|
17
|
+
readonly seed: number;
|
|
18
|
+
readonly verify?: boolean;
|
|
19
|
+
}
|
|
20
|
+
export interface CounterDuelSessionOptions {
|
|
21
|
+
readonly snapshotBufferSize?: number;
|
|
22
|
+
readonly replayRecordingMode?: ReplayRecordingMode;
|
|
23
|
+
}
|
|
24
|
+
export interface CounterDuelResult {
|
|
25
|
+
readonly frames: number;
|
|
26
|
+
readonly players: number;
|
|
27
|
+
readonly events: number;
|
|
28
|
+
readonly replayChecksums: number;
|
|
29
|
+
readonly finalChecksum: string;
|
|
30
|
+
readonly verified: boolean;
|
|
31
|
+
readonly result: 'pass';
|
|
32
|
+
readonly replay: ReplayFile;
|
|
33
|
+
readonly eventTimeline: readonly DeterministicEventRecord<{
|
|
34
|
+
readonly attacker: number;
|
|
35
|
+
readonly target: number;
|
|
36
|
+
readonly score: number;
|
|
37
|
+
}>[];
|
|
38
|
+
}
|
|
39
|
+
export declare const counterDuelDefaultInput: CounterDuelInput;
|
|
40
|
+
export declare function createCounterDuelSession(players: number, seed: number, options?: CounterDuelSessionOptions): OfflineSession<CounterDuelState, CounterDuelInput>;
|
|
41
|
+
export declare function runCounterDuelExample(options: CounterDuelOptions): CounterDuelResult;
|
|
42
|
+
export declare function verifyCounterDuelReplay(replay: ReplayFile): void;
|
|
43
|
+
export declare function makeCounterDuelInput(random: DeterministicRandom, players: number, slot: number): CounterDuelInput;
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import { defaultChecksum } from '../canonical.js';
|
|
2
|
+
import { createDeterministicEventTimeline } from '../events.js';
|
|
3
|
+
import { createOfflineSession } from '../offline-session.js';
|
|
4
|
+
import { DeterministicRandom } from '../random.js';
|
|
5
|
+
import { verifyReplay } from '../replay.js';
|
|
6
|
+
export const counterDuelDefaultInput = {
|
|
7
|
+
attack: false,
|
|
8
|
+
targetSlot: 0,
|
|
9
|
+
};
|
|
10
|
+
export function createCounterDuelSession(players, seed, options = {}) {
|
|
11
|
+
return createOfflineSession({
|
|
12
|
+
tickRate: 30,
|
|
13
|
+
playerCount: players,
|
|
14
|
+
initialState: createInitialCounterDuelState(players),
|
|
15
|
+
step: counterDuelStep,
|
|
16
|
+
defaultInput: counterDuelDefaultInput,
|
|
17
|
+
inputSchemaId: 'counter-duel-input/v1',
|
|
18
|
+
stateSchemaId: 'counter-duel-state/v1',
|
|
19
|
+
deterministicVersion: 'counter-duel@1',
|
|
20
|
+
randomSeed: seed,
|
|
21
|
+
snapshotBufferSize: options.snapshotBufferSize,
|
|
22
|
+
replayRecordingMode: options.replayRecordingMode,
|
|
23
|
+
});
|
|
24
|
+
}
|
|
25
|
+
export function runCounterDuelExample(options) {
|
|
26
|
+
validateCounterDuelOptions(options);
|
|
27
|
+
const session = createCounterDuelSession(options.players, options.seed);
|
|
28
|
+
const inputRandom = new DeterministicRandom(options.seed);
|
|
29
|
+
const eventTimeline = createDeterministicEventTimeline();
|
|
30
|
+
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
31
|
+
const previousState = session.getState();
|
|
32
|
+
const frameInputs = Array.from({ length: options.players }, (_, slot) => makeCounterDuelInput(inputRandom, options.players, slot));
|
|
33
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
34
|
+
session.setInput(slot, frameInputs[slot]);
|
|
35
|
+
}
|
|
36
|
+
session.stepFrames(1);
|
|
37
|
+
const nextState = session.getState();
|
|
38
|
+
for (let slot = 0; slot < options.players; slot += 1) {
|
|
39
|
+
if (frameInputs[slot].attack && previousState.cooldown[slot] === 0) {
|
|
40
|
+
const target = normalizeTarget(frameInputs[slot].targetSlot, options.players, slot);
|
|
41
|
+
eventTimeline.emitVerifiedOnly('counter-duel.hit', nextState.frame, {
|
|
42
|
+
attacker: slot,
|
|
43
|
+
target,
|
|
44
|
+
score: nextState.score[slot],
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
const replay = session.exportReplay();
|
|
50
|
+
if (options.verify === true) {
|
|
51
|
+
verifyCounterDuelReplay(replay);
|
|
52
|
+
}
|
|
53
|
+
const finalChecksum = replay.checksums.at(-1)?.checksum ?? defaultChecksum(session.getState());
|
|
54
|
+
return {
|
|
55
|
+
frames: options.frames,
|
|
56
|
+
players: options.players,
|
|
57
|
+
events: eventTimeline.records().length,
|
|
58
|
+
replayChecksums: replay.checksums.length,
|
|
59
|
+
finalChecksum,
|
|
60
|
+
verified: options.verify === true,
|
|
61
|
+
result: 'pass',
|
|
62
|
+
replay,
|
|
63
|
+
eventTimeline: eventTimeline.records(),
|
|
64
|
+
};
|
|
65
|
+
}
|
|
66
|
+
export function verifyCounterDuelReplay(replay) {
|
|
67
|
+
const result = verifyReplay({
|
|
68
|
+
replay,
|
|
69
|
+
step: counterDuelStep,
|
|
70
|
+
defaultInput: counterDuelDefaultInput,
|
|
71
|
+
inputSchemaId: 'counter-duel-input/v1',
|
|
72
|
+
stateSchemaId: 'counter-duel-state/v1',
|
|
73
|
+
deterministicVersion: 'counter-duel@1',
|
|
74
|
+
hydrateInput: hydrateCounterDuelInput,
|
|
75
|
+
});
|
|
76
|
+
if (result.mismatches !== 0) {
|
|
77
|
+
throw new Error(`Counter Duel replay mismatch: ${result.mismatches}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
function createInitialCounterDuelState(players) {
|
|
81
|
+
return {
|
|
82
|
+
frame: 0,
|
|
83
|
+
health: Array.from({ length: players }, () => 3),
|
|
84
|
+
score: Array.from({ length: players }, () => 0),
|
|
85
|
+
cooldown: Array.from({ length: players }, () => 0),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
function counterDuelStep(state, inputs) {
|
|
89
|
+
const health = [...state.health];
|
|
90
|
+
const score = [...state.score];
|
|
91
|
+
const cooldown = state.cooldown.map((value) => Math.max(0, value - 1));
|
|
92
|
+
for (let slot = 0; slot < inputs.length; slot += 1) {
|
|
93
|
+
const input = inputs[slot];
|
|
94
|
+
if (!input.attack || cooldown[slot] > 0) {
|
|
95
|
+
continue;
|
|
96
|
+
}
|
|
97
|
+
const target = normalizeTarget(input.targetSlot, inputs.length, slot);
|
|
98
|
+
health[target] -= 1;
|
|
99
|
+
cooldown[slot] = 12;
|
|
100
|
+
if (health[target] <= 0) {
|
|
101
|
+
score[slot] += 1;
|
|
102
|
+
health[target] = 3;
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
return {
|
|
106
|
+
frame: state.frame + 1,
|
|
107
|
+
health,
|
|
108
|
+
score,
|
|
109
|
+
cooldown,
|
|
110
|
+
};
|
|
111
|
+
}
|
|
112
|
+
export function makeCounterDuelInput(random, players, slot) {
|
|
113
|
+
const attackRoll = random.nextInt(0, 99);
|
|
114
|
+
return {
|
|
115
|
+
attack: attackRoll < 15,
|
|
116
|
+
targetSlot: findLastTarget(slot, players),
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function findLastTarget(slot, players) {
|
|
120
|
+
return (slot + 1) % players;
|
|
121
|
+
}
|
|
122
|
+
function normalizeTarget(targetSlot, players, selfSlot) {
|
|
123
|
+
if (!Number.isInteger(targetSlot) || targetSlot < 0 || targetSlot >= players || targetSlot === selfSlot) {
|
|
124
|
+
return (selfSlot + 1) % players;
|
|
125
|
+
}
|
|
126
|
+
return targetSlot;
|
|
127
|
+
}
|
|
128
|
+
function hydrateCounterDuelInput(recordedInput) {
|
|
129
|
+
if (recordedInput === null ||
|
|
130
|
+
typeof recordedInput !== 'object' ||
|
|
131
|
+
typeof recordedInput.attack !== 'boolean' ||
|
|
132
|
+
typeof recordedInput.targetSlot !== 'number') {
|
|
133
|
+
throw new Error('Invalid Counter Duel replay input');
|
|
134
|
+
}
|
|
135
|
+
return {
|
|
136
|
+
attack: recordedInput.attack,
|
|
137
|
+
targetSlot: recordedInput.targetSlot,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function validateCounterDuelOptions(options) {
|
|
141
|
+
if (!Number.isInteger(options.players) || options.players < 2 || options.players > 8) {
|
|
142
|
+
throw new Error('Counter Duel requires 2 to 8 players');
|
|
143
|
+
}
|
|
144
|
+
if (!Number.isInteger(options.frames) || options.frames < 1) {
|
|
145
|
+
throw new Error('Counter Duel requires at least one frame');
|
|
146
|
+
}
|
|
147
|
+
if (!Number.isInteger(options.seed)) {
|
|
148
|
+
throw new Error('Counter Duel seed must be an integer');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
import { type DeterministicEventRecord } from '../events.js';
|
|
2
|
+
import { DeterministicRandom } from '../random.js';
|
|
3
|
+
import { type DeterministicGameDescriptor } from '../schema.js';
|
|
4
|
+
import type { OfflineSession, ReplayFile, ReplayRecordingMode } from '../types.js';
|
|
5
|
+
export interface FixedArenaInput {
|
|
6
|
+
readonly thrustX: number;
|
|
7
|
+
readonly thrustY: number;
|
|
8
|
+
readonly dash: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface FixedArenaState {
|
|
11
|
+
readonly frame: number;
|
|
12
|
+
readonly x: readonly number[];
|
|
13
|
+
readonly y: readonly number[];
|
|
14
|
+
readonly vx: readonly number[];
|
|
15
|
+
readonly vy: readonly number[];
|
|
16
|
+
readonly score: readonly number[];
|
|
17
|
+
readonly collisions: readonly number[];
|
|
18
|
+
}
|
|
19
|
+
export interface FixedArenaRenderState {
|
|
20
|
+
readonly players: readonly {
|
|
21
|
+
readonly key: string;
|
|
22
|
+
readonly x: number;
|
|
23
|
+
readonly y: number;
|
|
24
|
+
readonly radius: number;
|
|
25
|
+
readonly color: string;
|
|
26
|
+
readonly score: number;
|
|
27
|
+
readonly collisions: number;
|
|
28
|
+
}[];
|
|
29
|
+
readonly bounds: {
|
|
30
|
+
readonly minX: number;
|
|
31
|
+
readonly minY: number;
|
|
32
|
+
readonly maxX: number;
|
|
33
|
+
readonly maxY: number;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
export interface FixedArenaReferenceView {
|
|
37
|
+
readonly camera: {
|
|
38
|
+
readonly position: readonly [number, number, number];
|
|
39
|
+
readonly zoom: number;
|
|
40
|
+
};
|
|
41
|
+
readonly arena: {
|
|
42
|
+
readonly width: number;
|
|
43
|
+
readonly height: number;
|
|
44
|
+
readonly wallThickness: number;
|
|
45
|
+
readonly backgroundColor: string;
|
|
46
|
+
readonly wallColor: string;
|
|
47
|
+
};
|
|
48
|
+
readonly players: readonly {
|
|
49
|
+
readonly key: string;
|
|
50
|
+
readonly slot: number;
|
|
51
|
+
readonly position: readonly [number, number, number];
|
|
52
|
+
readonly scale: readonly [number, number, number];
|
|
53
|
+
readonly color: string;
|
|
54
|
+
readonly ringColor: string;
|
|
55
|
+
readonly velocity: readonly [number, number, number];
|
|
56
|
+
readonly score: number;
|
|
57
|
+
readonly collisions: number;
|
|
58
|
+
readonly rank: number;
|
|
59
|
+
}[];
|
|
60
|
+
readonly leaderboard: readonly {
|
|
61
|
+
readonly slot: number;
|
|
62
|
+
readonly score: number;
|
|
63
|
+
readonly collisions: number;
|
|
64
|
+
readonly color: string;
|
|
65
|
+
}[];
|
|
66
|
+
readonly debug: {
|
|
67
|
+
readonly frame: number;
|
|
68
|
+
readonly players: number;
|
|
69
|
+
readonly topScore: number;
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
export interface FixedArenaOptions {
|
|
73
|
+
readonly players: number;
|
|
74
|
+
readonly frames: number;
|
|
75
|
+
readonly seed: number;
|
|
76
|
+
readonly verify?: boolean;
|
|
77
|
+
}
|
|
78
|
+
export interface FixedArenaSessionOptions {
|
|
79
|
+
readonly snapshotBufferSize?: number;
|
|
80
|
+
readonly replayRecordingMode?: ReplayRecordingMode;
|
|
81
|
+
}
|
|
82
|
+
export interface FixedArenaResult {
|
|
83
|
+
readonly frames: number;
|
|
84
|
+
readonly players: number;
|
|
85
|
+
readonly events: number;
|
|
86
|
+
readonly replayChecksums: number;
|
|
87
|
+
readonly finalChecksum: string;
|
|
88
|
+
readonly verified: boolean;
|
|
89
|
+
readonly result: 'pass';
|
|
90
|
+
readonly replay: ReplayFile;
|
|
91
|
+
readonly renderState: FixedArenaRenderState;
|
|
92
|
+
readonly eventTimeline: readonly DeterministicEventRecord<{
|
|
93
|
+
readonly a: number;
|
|
94
|
+
readonly b: number;
|
|
95
|
+
readonly ax: number;
|
|
96
|
+
readonly ay: number;
|
|
97
|
+
readonly bx: number;
|
|
98
|
+
readonly by: number;
|
|
99
|
+
}>[];
|
|
100
|
+
}
|
|
101
|
+
export declare const fixedArenaDefaultInput: FixedArenaInput;
|
|
102
|
+
export declare function createFixedArenaDescriptor(maxPlayers?: number): DeterministicGameDescriptor;
|
|
103
|
+
export declare function createFixedArenaSession(players: number, seed: number, options?: FixedArenaSessionOptions): OfflineSession<FixedArenaState, FixedArenaInput>;
|
|
104
|
+
export declare function runFixedArenaExample(options: FixedArenaOptions): FixedArenaResult;
|
|
105
|
+
export declare function verifyFixedArenaReplay(replay: ReplayFile): void;
|
|
106
|
+
export declare function projectFixedArenaRenderState(state: Readonly<FixedArenaState>): FixedArenaRenderState;
|
|
107
|
+
export declare function projectFixedArenaReferenceView(state: Readonly<FixedArenaState>): FixedArenaReferenceView;
|
|
108
|
+
export declare function makeFixedArenaInput(random: DeterministicRandom): FixedArenaInput;
|
|
109
|
+
export declare function createInitialFixedArenaState(players: number, seed: number): FixedArenaState;
|
|
110
|
+
export declare function fixedArenaStep(state: Readonly<FixedArenaState>, inputs: readonly Readonly<FixedArenaInput>[]): FixedArenaState;
|