@series-inc/rundot-syncplay 5.26.0-beta.2 → 5.26.0-beta.4
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 +17 -0
- package/core/native/deterministic_math.hpp +4 -3
- package/core/native/fixed_math.cpp +741 -0
- package/core/native/fixed_math.hpp +111 -0
- package/core/native/kinetix-native-main.cpp +227 -88
- package/core/native/mobile/box3d_probe_scenario.cpp +101 -0
- package/core/native/mobile/box3d_probe_scenario.hpp +25 -0
- package/core/native/mobile/syncplay_native_probe.cpp +114 -0
- package/core/native/mobile/syncplay_native_probe_abi.h +46 -0
- package/core/physics.d.ts +48 -0
- package/core/scripts/build-mobile-native.mjs +101 -0
- package/core/scripts/build-native.mjs +15 -1
- package/core/scripts/build-wasm-fixed-math.mjs +58 -0
- package/core/scripts/run-box3d-benchmark.mjs +22 -124
- package/core/src/physics/box3d-probe.mjs +171 -0
- package/core/src/physics/index.mjs +156 -19
- package/core/src/physics/physics-wasm-module.generated.mjs +3 -3
- package/dist/browser.d.ts +2 -1
- package/dist/browser.js +1 -0
- package/dist/creator.d.ts +1 -0
- package/dist/creator.js +1 -0
- package/dist/index.d.ts +2 -1
- package/dist/index.js +1 -0
- package/dist/movement3d.d.ts +31 -0
- package/dist/movement3d.js +265 -30
- package/dist/physics3d-heightfield.d.ts +10 -0
- package/dist/physics3d-heightfield.js +33 -0
- package/dist/physics3d-hull.d.ts +14 -0
- package/dist/physics3d-hull.js +45 -0
- package/dist/physics3d.js +28 -1
- package/package.json +8 -4
- package/dist/prediction-culling.d.ts +0 -34
- package/dist/prediction-culling.js +0 -200
package/dist/physics3d.js
CHANGED
|
@@ -2080,6 +2080,33 @@ function warmMapToCache(warm, bodies) {
|
|
|
2080
2080
|
|| left.anchorY - right.anchorY
|
|
2081
2081
|
|| left.anchorZ - right.anchorZ);
|
|
2082
2082
|
}
|
|
2083
|
+
/**
|
|
2084
|
+
* Exact-overlap memo for static/static pairs. Neither body can move, so the
|
|
2085
|
+
* result is invariant for the lifetime of both objects, and static bodies keep
|
|
2086
|
+
* their identity across steps (see the `kind === 'static'` passthrough in the
|
|
2087
|
+
* solver's body rebuild). Without this, every prop resting on a heightfield
|
|
2088
|
+
* regenerates the terrain's triangles on every step to re-derive an answer that
|
|
2089
|
+
* cannot change.
|
|
2090
|
+
*/
|
|
2091
|
+
// rdm-ignore-next-line determinism/no-mutable-collection-state: pure derived overlap memo keyed by immutable body identity
|
|
2092
|
+
const staticOverlapCache = new WeakMap();
|
|
2093
|
+
function shapesOverlapForPair(left, right) {
|
|
2094
|
+
if (left.kind !== 'static' || right.kind !== 'static') {
|
|
2095
|
+
return shapesOverlap3D(bodyPose(left), bodyPose(right));
|
|
2096
|
+
}
|
|
2097
|
+
let memo = staticOverlapCache.get(left);
|
|
2098
|
+
if (memo === undefined) {
|
|
2099
|
+
memo = new WeakMap();
|
|
2100
|
+
staticOverlapCache.set(left, memo);
|
|
2101
|
+
}
|
|
2102
|
+
const cached = memo.get(right);
|
|
2103
|
+
if (cached !== undefined) {
|
|
2104
|
+
return cached;
|
|
2105
|
+
}
|
|
2106
|
+
const overlap = shapesOverlap3D(bodyPose(left), bodyPose(right));
|
|
2107
|
+
memo.set(right, overlap);
|
|
2108
|
+
return overlap;
|
|
2109
|
+
}
|
|
2083
2110
|
function detectContacts(bodies, keepPair) {
|
|
2084
2111
|
const contacts = [];
|
|
2085
2112
|
for (const pair of broadphaseCandidatePairs(bodies)) {
|
|
@@ -2091,7 +2118,7 @@ function detectContacts(bodies, keepPair) {
|
|
|
2091
2118
|
if ((left.mask & right.layer) === 0 ||
|
|
2092
2119
|
(right.mask & left.layer) === 0 ||
|
|
2093
2120
|
!boundsOverlap(pair.leftBounds, pair.rightBounds) ||
|
|
2094
|
-
!
|
|
2121
|
+
!shapesOverlapForPair(left, right)) {
|
|
2095
2122
|
continue;
|
|
2096
2123
|
}
|
|
2097
2124
|
const pairId = [left.id, right.id].sort(compareStrings).join('|');
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@series-inc/rundot-syncplay",
|
|
3
|
-
"version": "5.26.0-beta.
|
|
3
|
+
"version": "5.26.0-beta.4",
|
|
4
4
|
"description": "Kinetix orchestration, rollback, replay, late-join, and deterministic networking for RUN.game",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -70,7 +70,9 @@
|
|
|
70
70
|
"types": "./core/physics.d.ts",
|
|
71
71
|
"default": "./core/src/physics/index.mjs"
|
|
72
72
|
},
|
|
73
|
+
"./core/box3d-probe": "./core/src/physics/box3d-probe.mjs",
|
|
73
74
|
"./core/build-native": "./core/scripts/build-native.mjs",
|
|
75
|
+
"./core/build-mobile-native": "./core/scripts/build-mobile-native.mjs",
|
|
74
76
|
"./core/build-wasm-physics": "./core/scripts/build-wasm-physics.mjs",
|
|
75
77
|
"./core/preflight": "./core/scripts/preflight.mjs"
|
|
76
78
|
},
|
|
@@ -88,17 +90,19 @@
|
|
|
88
90
|
"scripts": {
|
|
89
91
|
"build": "node -e \"require('node:fs').rmSync('dist',{recursive:true,force:true})\" && tsc -p tsconfig.json",
|
|
90
92
|
"test": "npm run test:core",
|
|
91
|
-
"test:core": "tsx --test tests/canonical.test.ts tests/runtime-session.test.ts tests/kinetix-runtime-session.test.ts tests/runtime-support.test.ts tests/runtime-guards.test.ts tests/r3f-render-adapter.test.ts tests/snapshot-cost.test.ts tests/sdkSession.test.ts tests/input-authority.test.ts tests/session-wire.test.ts tests/session-snapshot.test.ts tests/authority-room.test.ts tests/networked-client-snapshot.test.ts tests/runner.test.ts tests/testing.test.ts tests/browser-session.test.ts tests/secret-config.test.ts tests/secret-protocol.test.ts tests/secret-authority.test.ts tests/secret-client.test.ts tests/late-join-checksum.test.ts tests/late-join-input-backfill.test.ts tests/replay-scoring.test.ts tests/bot-backfill.test.ts tests/latency-simulation.test.ts tests/time-sync.test.ts tests/match-log.test.ts tests/spectator.test.ts tests/synctest.test.ts tests/instant-replay.test.ts tests/session-promotion.test.ts tests/physics3d-broadphase-stats.test.ts tests/entrypoints.test.ts tests/export-surface.test.ts tests/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/platformer-content.test.ts tests/command-ingress.test.ts tests/session-reconfigure.test.ts tests/prediction-mode.test.ts tests/ephemeral-channel.test.ts tests/runner-surface.test.ts tests/runner-harness.test.ts tests/effects-adapter-bounds.test.ts tests/simulated-match-secrets.test.ts tests/run-glue-surface.test.mjs core/tests/core-boundary.test.mjs tests/math-core-parity.test.ts",
|
|
93
|
+
"test:core": "tsx --test tests/canonical.test.ts tests/runtime-session.test.ts tests/kinetix-runtime-session.test.ts tests/runtime-support.test.ts tests/runtime-guards.test.ts tests/r3f-render-adapter.test.ts tests/snapshot-cost.test.ts tests/sdkSession.test.ts tests/input-authority.test.ts tests/session-wire.test.ts tests/session-snapshot.test.ts tests/authority-room.test.ts tests/networked-client-snapshot.test.ts tests/runner.test.ts tests/testing.test.ts tests/browser-session.test.ts tests/secret-config.test.ts tests/secret-protocol.test.ts tests/secret-authority.test.ts tests/secret-client.test.ts tests/late-join-checksum.test.ts tests/late-join-input-backfill.test.ts tests/replay-scoring.test.ts tests/bot-backfill.test.ts tests/latency-simulation.test.ts tests/time-sync.test.ts tests/match-log.test.ts tests/spectator.test.ts tests/synctest.test.ts tests/instant-replay.test.ts tests/session-promotion.test.ts tests/physics3d-broadphase-stats.test.ts tests/entrypoints.test.ts tests/export-surface.test.ts tests/test-manifest.test.ts tests/coarse-region.test.ts tests/input-codec.test.ts tests/platformer-content.test.ts tests/command-ingress.test.ts tests/session-reconfigure.test.ts tests/prediction-mode.test.ts tests/ephemeral-channel.test.ts tests/runner-surface.test.ts tests/runner-harness.test.ts tests/effects-adapter-bounds.test.ts tests/simulated-match-secrets.test.ts tests/run-glue-surface.test.mjs core/tests/core-boundary.test.mjs tests/math-core-parity.test.ts tests/movement3d-kcc-unit-contract.test.ts tests/movement3d-static-ground.test.ts",
|
|
92
94
|
"test:soak": "tsx --test tests/runtime-soak.test.ts",
|
|
93
|
-
"test:extended": "tsx --test tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
|
|
95
|
+
"test:extended": "tsx --test tests/kcc-vehicle-characterization.test.ts tests/animation.test.ts tests/asset-cooking.test.ts tests/backend-proof-checksum-vectors.test.ts tests/bot-documents.test.ts tests/bot-primitives.test.ts tests/collider-cooking.test.ts tests/collision.test.ts tests/command-timeline.test.ts tests/config-bundle.test.ts tests/cosmetic-physics3d.test.ts tests/deterministic-primitives.test.ts tests/ecs-lite.test.ts tests/effects-adapter.test.ts tests/engine-runtime.test.ts tests/errors.test.ts tests/events.test.ts tests/input-button.test.ts tests/load-128-players.test.ts tests/math.test.ts tests/movement.test.ts tests/movement3d.test.ts tests/multiclient-authority.test.ts tests/navigation.test.ts tests/noise.test.ts tests/physics-cert.test.ts tests/physics2d.test.ts tests/physics3d-ccd.test.ts tests/physics3d-coverage.test.ts tests/physics3d-destruction-cert.test.ts tests/physics3d-geometry-limits.test.ts tests/physics3d-heightfield.test.ts tests/physics3d-hull.test.ts tests/physics3d-joints.test.ts tests/physics3d-kinematic-mesh.test.ts tests/physics3d-mass.test.ts tests/physics3d-materials.test.ts tests/physics3d-mesh-onesided.test.ts tests/physics3d-query-index.test.ts tests/physics3d-recipes.test.ts tests/physics3d-rotation-convention.test.ts tests/physics3d-shared.test.ts tests/physics3d-sleeping-manifold-reuse.test.ts tests/physics3d-solver.test.ts tests/physics3d-static-memoization.test.ts tests/physics3d-static-overlap-memo.test.ts tests/physics3d-cylinder-hull.test.ts tests/physics3d-stacking.test.ts tests/physics3d-toi.test.ts tests/physics3d-vehicle.test.ts tests/physics3d-voxel.test.ts tests/physics3d-world-edit.test.ts tests/physics3d.test.ts tests/physics-vector-generator.test.ts tests/random.test.ts tests/rollback-history.test.ts tests/schema-artifacts.test.ts tests/schema-codegen.test.ts tests/schema-dsl-binary.test.ts tests/schema.test.ts tests/signals.test.ts tests/sparse-set.test.ts tests/static-checker.test.ts tests/system-lifecycle.test.ts tests/wire-canonical-sentinels.test.ts tests/ws-frame.test.ts",
|
|
94
96
|
"test:ci": "npm run build && npm run test:core",
|
|
95
97
|
"build:native": "node core/scripts/build-native.mjs",
|
|
98
|
+
"build:mobile-native": "node core/scripts/build-mobile-native.mjs",
|
|
96
99
|
"build:wasm-physics": "node core/scripts/build-wasm-physics.mjs",
|
|
97
100
|
"test:determinism": "node --test core/tests/*.test.mjs",
|
|
98
101
|
"generate:platformer-manifest": "node core/scripts/generate-platformer2d-manifest.mjs",
|
|
99
102
|
"generate:physics-vectors": "node scripts/gen-physics-vectors.mjs",
|
|
100
103
|
"test:physics-vectors": "node --import tsx/esm --test tests/physics-vector-generator.test.ts",
|
|
101
|
-
"pack:dry-run": "npm pack --dry-run"
|
|
104
|
+
"pack:dry-run": "npm pack --dry-run",
|
|
105
|
+
"bench:cross-lane": "node core/bench/cross-lane.mjs"
|
|
102
106
|
},
|
|
103
107
|
"license": "SEE LICENSE IN LICENSE.md",
|
|
104
108
|
"keywords": [
|
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
export type PredictionCullingMode = 'off' | 'local-area';
|
|
2
|
-
export interface PredictionCullingOptions {
|
|
3
|
-
readonly mode: PredictionCullingMode;
|
|
4
|
-
readonly players: number;
|
|
5
|
-
readonly objectsPerPlayer: number;
|
|
6
|
-
readonly frames: number;
|
|
7
|
-
readonly seed: number;
|
|
8
|
-
readonly radius: number;
|
|
9
|
-
readonly includeHiddenThreat?: boolean;
|
|
10
|
-
}
|
|
11
|
-
export interface PredictionCullingResult {
|
|
12
|
-
readonly mode: PredictionCullingMode;
|
|
13
|
-
readonly players: number;
|
|
14
|
-
readonly objects: number;
|
|
15
|
-
readonly frames: number;
|
|
16
|
-
readonly seed: number;
|
|
17
|
-
readonly radius: number;
|
|
18
|
-
readonly averagePredictedObjects: string;
|
|
19
|
-
readonly minPredictedObjects: number;
|
|
20
|
-
readonly maxPredictedObjects: number;
|
|
21
|
-
readonly visibleCorrections: number;
|
|
22
|
-
readonly hiddenAuthoritativeInteractions: number;
|
|
23
|
-
readonly predictedObjectSteps: number;
|
|
24
|
-
readonly verifiedObjectSteps: number;
|
|
25
|
-
readonly culledObjectSteps: number;
|
|
26
|
-
readonly restoreFrame: number;
|
|
27
|
-
readonly predictedChecksum: string;
|
|
28
|
-
readonly verifiedChecksum: string;
|
|
29
|
-
readonly resimulatedChecksum: string;
|
|
30
|
-
readonly predictionFilterChecksum: string;
|
|
31
|
-
readonly completeChecksum: string;
|
|
32
|
-
readonly result: 'pass' | 'fail';
|
|
33
|
-
}
|
|
34
|
-
export declare function runPredictionCullingLab(options: PredictionCullingOptions): PredictionCullingResult;
|
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
import { defaultChecksum } from './canonical.js';
|
|
2
|
-
export function runPredictionCullingLab(options) {
|
|
3
|
-
validateOptions(options);
|
|
4
|
-
const objects = createObjects(options);
|
|
5
|
-
let previousPredicted = new Set();
|
|
6
|
-
let predictedTotal = 0;
|
|
7
|
-
let minPredictedObjects = Number.POSITIVE_INFINITY;
|
|
8
|
-
let maxPredictedObjects = 0;
|
|
9
|
-
let visibleCorrections = 0;
|
|
10
|
-
let hiddenAuthoritativeInteractions = 0;
|
|
11
|
-
let predictedObjectSteps = 0;
|
|
12
|
-
let verifiedObjectSteps = 0;
|
|
13
|
-
let predictedChecksum = '';
|
|
14
|
-
let verifiedChecksum = '';
|
|
15
|
-
let completeChecksum = '';
|
|
16
|
-
const filterHistory = [];
|
|
17
|
-
const frameHistory = [];
|
|
18
|
-
const restoreFrame = Math.floor(options.frames * 0.6);
|
|
19
|
-
for (let frame = 0; frame < options.frames; frame += 1) {
|
|
20
|
-
const predictedBySlot = [];
|
|
21
|
-
const frameState = [];
|
|
22
|
-
for (const object of objects) {
|
|
23
|
-
frameState.push(getObjectFrameState(object, frame, options.seed));
|
|
24
|
-
}
|
|
25
|
-
frameHistory.push(frameState);
|
|
26
|
-
const localPredicted = new Set();
|
|
27
|
-
for (let slot = 0; slot < options.players; slot += 1) {
|
|
28
|
-
const player = getPlayerPosition(slot, frame, options.seed);
|
|
29
|
-
const slotPredicted = [];
|
|
30
|
-
for (const objectState of frameState) {
|
|
31
|
-
const object = objects[objectState.id];
|
|
32
|
-
if (shouldPredictObject(options.mode, player, object, objectState, options.radius)) {
|
|
33
|
-
slotPredicted.push(objectState.id);
|
|
34
|
-
if (slot === 0) {
|
|
35
|
-
localPredicted.add(objectState.id);
|
|
36
|
-
}
|
|
37
|
-
}
|
|
38
|
-
}
|
|
39
|
-
predictedObjectSteps += slotPredicted.length;
|
|
40
|
-
predictedBySlot.push(slotPredicted);
|
|
41
|
-
}
|
|
42
|
-
for (const objectState of frameState) {
|
|
43
|
-
if (objectState.canAffectPlayer && !localPredicted.has(objectState.id)) {
|
|
44
|
-
hiddenAuthoritativeInteractions += 1;
|
|
45
|
-
}
|
|
46
|
-
}
|
|
47
|
-
for (const objectId of localPredicted) {
|
|
48
|
-
if (!previousPredicted.has(objectId) && frame > 0) {
|
|
49
|
-
visibleCorrections += 1;
|
|
50
|
-
}
|
|
51
|
-
}
|
|
52
|
-
previousPredicted = localPredicted;
|
|
53
|
-
filterHistory.push(predictedBySlot);
|
|
54
|
-
predictedTotal += localPredicted.size;
|
|
55
|
-
minPredictedObjects = Math.min(minPredictedObjects, localPredicted.size);
|
|
56
|
-
maxPredictedObjects = Math.max(maxPredictedObjects, localPredicted.size);
|
|
57
|
-
predictedChecksum = defaultChecksum({
|
|
58
|
-
frame,
|
|
59
|
-
predictedBySlot,
|
|
60
|
-
previousChecksum: predictedChecksum,
|
|
61
|
-
});
|
|
62
|
-
verifiedObjectSteps += frameState.length;
|
|
63
|
-
verifiedChecksum = defaultChecksum({
|
|
64
|
-
frame,
|
|
65
|
-
objects: frameState,
|
|
66
|
-
previousChecksum: verifiedChecksum,
|
|
67
|
-
});
|
|
68
|
-
completeChecksum = defaultChecksum({
|
|
69
|
-
frame,
|
|
70
|
-
player: getPlayerPosition(0, frame, options.seed),
|
|
71
|
-
objects: frameState,
|
|
72
|
-
previousChecksum: completeChecksum,
|
|
73
|
-
});
|
|
74
|
-
}
|
|
75
|
-
const resimulatedChecksum = resimulateVerifiedChecksum(frameHistory, restoreFrame);
|
|
76
|
-
const predictionFilterChecksum = defaultChecksum(filterHistory);
|
|
77
|
-
const culledObjectSteps = options.mode === 'off' ? 0 : verifiedObjectSteps * options.players - predictedObjectSteps;
|
|
78
|
-
return {
|
|
79
|
-
mode: options.mode,
|
|
80
|
-
players: options.players,
|
|
81
|
-
objects: objects.length,
|
|
82
|
-
frames: options.frames,
|
|
83
|
-
seed: options.seed,
|
|
84
|
-
radius: options.radius,
|
|
85
|
-
averagePredictedObjects: (predictedTotal / options.frames).toFixed(2),
|
|
86
|
-
minPredictedObjects,
|
|
87
|
-
maxPredictedObjects,
|
|
88
|
-
visibleCorrections,
|
|
89
|
-
hiddenAuthoritativeInteractions,
|
|
90
|
-
predictedObjectSteps,
|
|
91
|
-
verifiedObjectSteps,
|
|
92
|
-
culledObjectSteps,
|
|
93
|
-
restoreFrame,
|
|
94
|
-
predictedChecksum,
|
|
95
|
-
verifiedChecksum,
|
|
96
|
-
resimulatedChecksum,
|
|
97
|
-
predictionFilterChecksum,
|
|
98
|
-
completeChecksum,
|
|
99
|
-
result: hiddenAuthoritativeInteractions === 0 && verifiedChecksum === resimulatedChecksum ? 'pass' : 'fail',
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
function createObjects(options) {
|
|
103
|
-
const objects = [];
|
|
104
|
-
for (let slot = 0; slot < options.players; slot += 1) {
|
|
105
|
-
for (let index = 0; index < options.objectsPerPlayer; index += 1) {
|
|
106
|
-
const id = objects.length;
|
|
107
|
-
objects.push({
|
|
108
|
-
id,
|
|
109
|
-
ownerSlot: slot,
|
|
110
|
-
offsetX: ((index * 37 + options.seed) % 90) - 45,
|
|
111
|
-
offsetY: ((index * 53 + options.seed) % 90) - 45,
|
|
112
|
-
alwaysPredict: index % 17 === 0,
|
|
113
|
-
canAffectPlayer: index % 17 === 0,
|
|
114
|
-
});
|
|
115
|
-
}
|
|
116
|
-
}
|
|
117
|
-
if (options.includeHiddenThreat === true) {
|
|
118
|
-
objects.push({
|
|
119
|
-
id: objects.length,
|
|
120
|
-
ownerSlot: Math.max(1, options.players - 1),
|
|
121
|
-
offsetX: 10_000,
|
|
122
|
-
offsetY: 10_000,
|
|
123
|
-
alwaysPredict: false,
|
|
124
|
-
canAffectPlayer: true,
|
|
125
|
-
});
|
|
126
|
-
}
|
|
127
|
-
return objects;
|
|
128
|
-
}
|
|
129
|
-
function shouldPredictObject(mode, player, object, objectState, radius) {
|
|
130
|
-
if (mode === 'off') {
|
|
131
|
-
return true;
|
|
132
|
-
}
|
|
133
|
-
if (object.alwaysPredict) {
|
|
134
|
-
return true;
|
|
135
|
-
}
|
|
136
|
-
const dx = objectState.x - player.x;
|
|
137
|
-
const dy = objectState.y - player.y;
|
|
138
|
-
return dx * dx + dy * dy <= radius * radius;
|
|
139
|
-
}
|
|
140
|
-
function getPlayerPosition(slot, frame, seed) {
|
|
141
|
-
return {
|
|
142
|
-
x: slot * 100 + ((frame * 3 + seed) % 37),
|
|
143
|
-
y: ((frame * 5 + seed) % 41) - 20,
|
|
144
|
-
};
|
|
145
|
-
}
|
|
146
|
-
function getObjectFrameState(object, frame, seed) {
|
|
147
|
-
const owner = getPlayerPosition(object.ownerSlot, frame, seed);
|
|
148
|
-
return {
|
|
149
|
-
id: object.id,
|
|
150
|
-
x: owner.x + object.offsetX + ((frame + object.id) % 7) - 3,
|
|
151
|
-
y: owner.y + object.offsetY + ((frame * 2 + object.id) % 7) - 3,
|
|
152
|
-
active: (frame + object.id) % 29 === 0 ? 0 : 1,
|
|
153
|
-
canAffectPlayer: object.canAffectPlayer,
|
|
154
|
-
};
|
|
155
|
-
}
|
|
156
|
-
function validateOptions(options) {
|
|
157
|
-
if (options.mode !== 'off' && options.mode !== 'local-area') {
|
|
158
|
-
throw new Error('prediction culling mode must be off or local-area');
|
|
159
|
-
}
|
|
160
|
-
if (!Number.isInteger(options.players) || options.players < 1 || options.players > 128) {
|
|
161
|
-
throw new Error('prediction culling players must be between 1 and 128');
|
|
162
|
-
}
|
|
163
|
-
if (!Number.isInteger(options.objectsPerPlayer) || options.objectsPerPlayer < 1 || options.objectsPerPlayer > 128) {
|
|
164
|
-
throw new Error('objectsPerPlayer must be between 1 and 128');
|
|
165
|
-
}
|
|
166
|
-
if (!Number.isInteger(options.frames) || options.frames < 1) {
|
|
167
|
-
throw new Error('prediction culling frames must be at least 1');
|
|
168
|
-
}
|
|
169
|
-
if (!Number.isInteger(options.seed)) {
|
|
170
|
-
throw new Error('prediction culling seed must be an integer');
|
|
171
|
-
}
|
|
172
|
-
if (!Number.isInteger(options.radius) || options.radius < 1) {
|
|
173
|
-
throw new Error('prediction culling radius must be a positive integer');
|
|
174
|
-
}
|
|
175
|
-
}
|
|
176
|
-
function resimulateVerifiedChecksum(frameHistory, restoreFrame) {
|
|
177
|
-
let checksum = '';
|
|
178
|
-
for (let frame = 0; frame < frameHistory.length; frame += 1) {
|
|
179
|
-
if (frame === restoreFrame) {
|
|
180
|
-
checksum = resimulateVerifiedChecksumPrefix(frameHistory, frame);
|
|
181
|
-
}
|
|
182
|
-
checksum = defaultChecksum({
|
|
183
|
-
frame,
|
|
184
|
-
objects: frameHistory[frame],
|
|
185
|
-
previousChecksum: checksum,
|
|
186
|
-
});
|
|
187
|
-
}
|
|
188
|
-
return checksum;
|
|
189
|
-
}
|
|
190
|
-
function resimulateVerifiedChecksumPrefix(frameHistory, endFrameExclusive) {
|
|
191
|
-
let checksum = '';
|
|
192
|
-
for (let frame = 0; frame < endFrameExclusive; frame += 1) {
|
|
193
|
-
checksum = defaultChecksum({
|
|
194
|
-
frame,
|
|
195
|
-
objects: frameHistory[frame],
|
|
196
|
-
previousChecksum: checksum,
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
return checksum;
|
|
200
|
-
}
|