@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,338 @@
|
|
|
1
|
+
import { canonicalStringify, cloneCanonical, defaultChecksum } from './canonical.js';
|
|
2
|
+
export function cookDeterministicNavigationGraph(source) {
|
|
3
|
+
const nodes = [...source.nodes].sort(compareNavNodes);
|
|
4
|
+
const edges = [...expandBidirectionalEdges(source.edges)].sort(compareNavEdges);
|
|
5
|
+
const regions = [...(source.regions ?? [])].sort(compareNavRegions);
|
|
6
|
+
const nodeIds = new Set();
|
|
7
|
+
for (const node of nodes) {
|
|
8
|
+
if (nodeIds.has(node.id)) {
|
|
9
|
+
throw new Error(`Duplicate navigation node id ${node.id}`);
|
|
10
|
+
}
|
|
11
|
+
nodeIds.add(node.id);
|
|
12
|
+
}
|
|
13
|
+
for (const edge of edges) {
|
|
14
|
+
if (!nodeIds.has(edge.from) || !nodeIds.has(edge.to)) {
|
|
15
|
+
throw new Error(`Navigation edge ${edge.from}->${edge.to} references a missing node`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
const cooked = { id: source.id, nodes, edges, regions };
|
|
19
|
+
const bytes = canonicalStringify(cooked);
|
|
20
|
+
return {
|
|
21
|
+
...cooked,
|
|
22
|
+
bytes,
|
|
23
|
+
hash: defaultChecksum(bytes),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export function findDeterministicPath(graph, query) {
|
|
27
|
+
const nodes = nodeMap(graph);
|
|
28
|
+
if (!nodes.has(query.start) || !nodes.has(query.goal)) {
|
|
29
|
+
return emptyPathResult();
|
|
30
|
+
}
|
|
31
|
+
const disabledRegions = new Set(query.disabledRegionIds ?? []);
|
|
32
|
+
const blockedNodes = new Set(query.blockedNodeIds ?? []);
|
|
33
|
+
if (isNodeBlocked(nodes.get(query.start), disabledRegions, blockedNodes)
|
|
34
|
+
|| isNodeBlocked(nodes.get(query.goal), disabledRegions, blockedNodes)) {
|
|
35
|
+
return emptyPathResult();
|
|
36
|
+
}
|
|
37
|
+
const adjacency = adjacencyMap(graph);
|
|
38
|
+
const open = [{ id: query.start, f: heuristic(nodes.get(query.start), nodes.get(query.goal)), g: 0, order: 0 }];
|
|
39
|
+
const cameFrom = new Map();
|
|
40
|
+
const bestCost = new Map([[query.start, 0]]);
|
|
41
|
+
const visited = new Set();
|
|
42
|
+
let pushOrder = 1;
|
|
43
|
+
while (open.length > 0) {
|
|
44
|
+
open.sort(compareQueueEntries);
|
|
45
|
+
const current = open.shift();
|
|
46
|
+
if (visited.has(current.id)) {
|
|
47
|
+
continue;
|
|
48
|
+
}
|
|
49
|
+
visited.add(current.id);
|
|
50
|
+
if (current.id === query.goal) {
|
|
51
|
+
const path = reconstructPath(cameFrom, current.id);
|
|
52
|
+
return {
|
|
53
|
+
found: true,
|
|
54
|
+
nodes: path,
|
|
55
|
+
cost: current.g,
|
|
56
|
+
visitedNodeCount: visited.size,
|
|
57
|
+
offMeshTraversals: countOffMeshTraversals(graph, path),
|
|
58
|
+
pathHash: defaultChecksum(path),
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
const neighbors = [...(adjacency.get(current.id) ?? [])].sort(compareNavEdges);
|
|
62
|
+
for (const edge of neighbors) {
|
|
63
|
+
const next = nodes.get(edge.to);
|
|
64
|
+
if (next === undefined || isNodeBlocked(next, disabledRegions, blockedNodes)) {
|
|
65
|
+
continue;
|
|
66
|
+
}
|
|
67
|
+
const candidateCost = current.g + (edge.cost ?? 1) + (next.cost ?? 1);
|
|
68
|
+
const knownCost = bestCost.get(edge.to);
|
|
69
|
+
if (knownCost !== undefined && candidateCost >= knownCost) {
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
cameFrom.set(edge.to, current.id);
|
|
73
|
+
bestCost.set(edge.to, candidateCost);
|
|
74
|
+
open.push({
|
|
75
|
+
id: edge.to,
|
|
76
|
+
f: candidateCost + heuristic(next, nodes.get(query.goal)),
|
|
77
|
+
g: candidateCost,
|
|
78
|
+
order: pushOrder,
|
|
79
|
+
});
|
|
80
|
+
pushOrder += 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
found: false,
|
|
85
|
+
nodes: [],
|
|
86
|
+
cost: 0,
|
|
87
|
+
visitedNodeCount: visited.size,
|
|
88
|
+
offMeshTraversals: 0,
|
|
89
|
+
pathHash: defaultChecksum([]),
|
|
90
|
+
};
|
|
91
|
+
}
|
|
92
|
+
export function createDeterministicFlowField(graph, goal, options = {}) {
|
|
93
|
+
const nextByNodeId = {};
|
|
94
|
+
for (const node of graph.nodes) {
|
|
95
|
+
const path = findDeterministicPath(graph, {
|
|
96
|
+
...options,
|
|
97
|
+
start: node.id,
|
|
98
|
+
goal,
|
|
99
|
+
});
|
|
100
|
+
if (path.found && path.nodes.length > 1) {
|
|
101
|
+
nextByNodeId[node.id] = path.nodes[1];
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
const stable = Object.fromEntries(Object.entries(nextByNodeId).sort(compareStringEntries));
|
|
105
|
+
return {
|
|
106
|
+
goal,
|
|
107
|
+
nextByNodeId: stable,
|
|
108
|
+
hash: defaultChecksum(stable),
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
export function smoothDeterministicPath(graph, path) {
|
|
112
|
+
if (path.length <= 2) {
|
|
113
|
+
return path;
|
|
114
|
+
}
|
|
115
|
+
const nodes = nodeMap(graph);
|
|
116
|
+
const smoothed = [path[0]];
|
|
117
|
+
for (let index = 1; index < path.length - 1; index += 1) {
|
|
118
|
+
const prev = nodes.get(smoothed[smoothed.length - 1]);
|
|
119
|
+
const current = nodes.get(path[index]);
|
|
120
|
+
const next = nodes.get(path[index + 1]);
|
|
121
|
+
if (prev === undefined || current === undefined || next === undefined) {
|
|
122
|
+
smoothed.push(path[index]);
|
|
123
|
+
continue;
|
|
124
|
+
}
|
|
125
|
+
const collinear = (current.x - prev.x) * (next.y - current.y) === (current.y - prev.y) * (next.x - current.x);
|
|
126
|
+
if (!collinear || isOffMeshEdge(graph, path[index - 1], path[index]) || isOffMeshEdge(graph, path[index], path[index + 1])) {
|
|
127
|
+
smoothed.push(path[index]);
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
smoothed.push(path[path.length - 1]);
|
|
131
|
+
return smoothed;
|
|
132
|
+
}
|
|
133
|
+
export function applyDeterministicReciprocalAvoidance(agents) {
|
|
134
|
+
const occupancy = new Map();
|
|
135
|
+
for (const agent of [...agents].sort(compareAgents)) {
|
|
136
|
+
occupancy.set(agent.nodeId, [...(occupancy.get(agent.nodeId) ?? []), agent]);
|
|
137
|
+
}
|
|
138
|
+
let corrections = 0;
|
|
139
|
+
const adjusted = [...agents].sort(compareAgents).map((agent) => {
|
|
140
|
+
/* istanbul ignore next -- every adjusted agent was inserted into occupancy by nodeId above. */
|
|
141
|
+
const peers = occupancy.get(agent.nodeId) ?? [];
|
|
142
|
+
if (peers.length <= 1) {
|
|
143
|
+
return agent;
|
|
144
|
+
}
|
|
145
|
+
const peerIndex = peers.findIndex((peer) => peer.id === agent.id);
|
|
146
|
+
corrections += peerIndex === 0 ? 0 : 1;
|
|
147
|
+
return {
|
|
148
|
+
...agent,
|
|
149
|
+
pathIndex: (agent.pathIndex ?? 0) + peerIndex,
|
|
150
|
+
};
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
agents: cloneCanonical(adjusted),
|
|
154
|
+
corrections,
|
|
155
|
+
hash: defaultChecksum(adjusted),
|
|
156
|
+
};
|
|
157
|
+
}
|
|
158
|
+
export function cookTilemapNavigationGraph(source) {
|
|
159
|
+
const blocked = new Set(source.blocked ?? []);
|
|
160
|
+
const nodes = [];
|
|
161
|
+
const edges = [];
|
|
162
|
+
for (let y = 0; y < source.height; y += 1) {
|
|
163
|
+
for (let x = 0; x < source.width; x += 1) {
|
|
164
|
+
const id = `${x},${y}`;
|
|
165
|
+
if (blocked.has(id)) {
|
|
166
|
+
continue;
|
|
167
|
+
}
|
|
168
|
+
nodes.push({ id, x, y, cost: source.costs?.[id] ?? 1 });
|
|
169
|
+
for (const [dx, dy] of [[-1, 0], [0, -1]]) {
|
|
170
|
+
const neighbor = `${x + dx},${y + dy}`;
|
|
171
|
+
if (!blocked.has(neighbor) && x + dx >= 0 && y + dy >= 0) {
|
|
172
|
+
edges.push({ from: id, to: neighbor, bidirectional: true });
|
|
173
|
+
}
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
for (const [from, to] of Object.entries(source.portals ?? {}).sort(compareStringEntries)) {
|
|
178
|
+
edges.push({ from, to, cost: 1, offMesh: true });
|
|
179
|
+
}
|
|
180
|
+
return cookDeterministicNavigationGraph({
|
|
181
|
+
id: source.id,
|
|
182
|
+
nodes,
|
|
183
|
+
edges,
|
|
184
|
+
});
|
|
185
|
+
}
|
|
186
|
+
export function stepDeterministicNavigationAgents(graph, agents, options) {
|
|
187
|
+
const reserved = new Set();
|
|
188
|
+
const sortedAgents = [...agents].sort(compareAgents);
|
|
189
|
+
const nextAgents = [];
|
|
190
|
+
let replans = 0;
|
|
191
|
+
let avoidanceCorrections = 0;
|
|
192
|
+
let reciprocalAvoidanceCorrections = 0;
|
|
193
|
+
let offMeshTraversals = 0;
|
|
194
|
+
let visitedNodeCount = 0;
|
|
195
|
+
for (const agent of sortedAgents) {
|
|
196
|
+
let path = agent.path ?? [];
|
|
197
|
+
let pathIndex = agent.pathIndex ?? 0;
|
|
198
|
+
if (path.length === 0 || path[path.length - 1] !== agent.targetNodeId || options.frame % 31 === 0) {
|
|
199
|
+
const planned = findDeterministicPath(graph, {
|
|
200
|
+
start: agent.nodeId,
|
|
201
|
+
goal: agent.targetNodeId,
|
|
202
|
+
disabledRegionIds: options.disabledRegionIds,
|
|
203
|
+
blockedNodeIds: options.blockedNodeIds,
|
|
204
|
+
});
|
|
205
|
+
path = planned.nodes;
|
|
206
|
+
pathIndex = 0;
|
|
207
|
+
replans += 1;
|
|
208
|
+
visitedNodeCount += planned.visitedNodeCount;
|
|
209
|
+
}
|
|
210
|
+
const candidate = path[pathIndex + 1] ?? agent.nodeId;
|
|
211
|
+
if (reserved.has(candidate)) {
|
|
212
|
+
reserved.add(agent.nodeId);
|
|
213
|
+
nextAgents.push({ ...agent, path, pathIndex });
|
|
214
|
+
avoidanceCorrections += 1;
|
|
215
|
+
continue;
|
|
216
|
+
}
|
|
217
|
+
const moved = candidate !== agent.nodeId;
|
|
218
|
+
reserved.add(candidate);
|
|
219
|
+
if (moved && isOffMeshEdge(graph, agent.nodeId, candidate)) {
|
|
220
|
+
offMeshTraversals += 1;
|
|
221
|
+
}
|
|
222
|
+
nextAgents.push({
|
|
223
|
+
...agent,
|
|
224
|
+
nodeId: candidate,
|
|
225
|
+
path,
|
|
226
|
+
pathIndex: moved ? pathIndex + 1 : pathIndex,
|
|
227
|
+
});
|
|
228
|
+
}
|
|
229
|
+
const agentsByInputOrder = nextAgents.sort(compareAgents);
|
|
230
|
+
const reciprocal = applyDeterministicReciprocalAvoidance(agentsByInputOrder);
|
|
231
|
+
reciprocalAvoidanceCorrections += reciprocal.corrections;
|
|
232
|
+
return {
|
|
233
|
+
agents: reciprocal.agents,
|
|
234
|
+
replans,
|
|
235
|
+
avoidanceCorrections,
|
|
236
|
+
offMeshTraversals,
|
|
237
|
+
visitedNodeCount,
|
|
238
|
+
reciprocalAvoidanceCorrections,
|
|
239
|
+
agentChecksum: defaultChecksum({ agents: reciprocal.agents, reciprocalHash: reciprocal.hash }),
|
|
240
|
+
};
|
|
241
|
+
}
|
|
242
|
+
function emptyPathResult() {
|
|
243
|
+
return {
|
|
244
|
+
found: false,
|
|
245
|
+
nodes: [],
|
|
246
|
+
cost: 0,
|
|
247
|
+
visitedNodeCount: 0,
|
|
248
|
+
offMeshTraversals: 0,
|
|
249
|
+
pathHash: defaultChecksum([]),
|
|
250
|
+
};
|
|
251
|
+
}
|
|
252
|
+
function compareNavNodes(left, right) {
|
|
253
|
+
return compareStrings(left.id, right.id);
|
|
254
|
+
}
|
|
255
|
+
function compareNavEdges(left, right) {
|
|
256
|
+
return compareStrings(`${left.from}->${left.to}`, `${right.from}->${right.to}`);
|
|
257
|
+
}
|
|
258
|
+
function compareNavRegions(left, right) {
|
|
259
|
+
return compareStrings(left.id, right.id);
|
|
260
|
+
}
|
|
261
|
+
function compareAgents(left, right) {
|
|
262
|
+
return compareStrings(left.id, right.id);
|
|
263
|
+
}
|
|
264
|
+
function compareQueueEntries(left, right) {
|
|
265
|
+
const fDelta = left.f - right.f;
|
|
266
|
+
if (fDelta !== 0) {
|
|
267
|
+
return fDelta;
|
|
268
|
+
}
|
|
269
|
+
const gDelta = left.g - right.g;
|
|
270
|
+
if (gDelta !== 0) {
|
|
271
|
+
return gDelta;
|
|
272
|
+
}
|
|
273
|
+
const idDelta = compareStrings(left.id, right.id);
|
|
274
|
+
/* istanbul ignore else -- queue ids are unique after f/g tie-breakers in public graph searches. */
|
|
275
|
+
if (idDelta !== 0) {
|
|
276
|
+
return idDelta;
|
|
277
|
+
}
|
|
278
|
+
/* istanbul ignore next -- queue ids are unique after f/g/id tie-breakers in public graph searches. */
|
|
279
|
+
return left.order - right.order;
|
|
280
|
+
}
|
|
281
|
+
function compareStringEntries(left, right) {
|
|
282
|
+
return compareStrings(left[0], right[0]);
|
|
283
|
+
}
|
|
284
|
+
function compareStrings(left, right) {
|
|
285
|
+
return left < right ? -1 : left > right ? 1 : 0;
|
|
286
|
+
}
|
|
287
|
+
function expandBidirectionalEdges(edges) {
|
|
288
|
+
const expanded = [];
|
|
289
|
+
for (const edge of edges) {
|
|
290
|
+
expanded.push(edge);
|
|
291
|
+
if (edge.bidirectional === true) {
|
|
292
|
+
expanded.push({
|
|
293
|
+
from: edge.to,
|
|
294
|
+
to: edge.from,
|
|
295
|
+
cost: edge.cost,
|
|
296
|
+
offMesh: edge.offMesh,
|
|
297
|
+
});
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return expanded;
|
|
301
|
+
}
|
|
302
|
+
function nodeMap(graph) {
|
|
303
|
+
return new Map(graph.nodes.map((node) => [node.id, node]));
|
|
304
|
+
}
|
|
305
|
+
function adjacencyMap(graph) {
|
|
306
|
+
const adjacency = new Map();
|
|
307
|
+
for (const edge of graph.edges) {
|
|
308
|
+
adjacency.set(edge.from, [...(adjacency.get(edge.from) ?? []), edge]);
|
|
309
|
+
}
|
|
310
|
+
return adjacency;
|
|
311
|
+
}
|
|
312
|
+
function isNodeBlocked(node, disabledRegions, blockedNodes) {
|
|
313
|
+
return blockedNodes.has(node.id) || (node.region !== undefined && disabledRegions.has(node.region));
|
|
314
|
+
}
|
|
315
|
+
function heuristic(left, right) {
|
|
316
|
+
return Math.abs(left.x - right.x) + Math.abs(left.y - right.y);
|
|
317
|
+
}
|
|
318
|
+
function reconstructPath(cameFrom, goal) {
|
|
319
|
+
const path = [goal];
|
|
320
|
+
let current = goal;
|
|
321
|
+
while (cameFrom.has(current)) {
|
|
322
|
+
current = cameFrom.get(current);
|
|
323
|
+
path.push(current);
|
|
324
|
+
}
|
|
325
|
+
return path.reverse();
|
|
326
|
+
}
|
|
327
|
+
function countOffMeshTraversals(graph, path) {
|
|
328
|
+
let count = 0;
|
|
329
|
+
for (let index = 1; index < path.length; index += 1) {
|
|
330
|
+
if (isOffMeshEdge(graph, path[index - 1], path[index])) {
|
|
331
|
+
count += 1;
|
|
332
|
+
}
|
|
333
|
+
}
|
|
334
|
+
return count;
|
|
335
|
+
}
|
|
336
|
+
function isOffMeshEdge(graph, from, to) {
|
|
337
|
+
return graph.edges.some((edge) => edge.from === from && edge.to === to && edge.offMesh === true);
|
|
338
|
+
}
|
|
@@ -0,0 +1,236 @@
|
|
|
1
|
+
export interface DeterministicNavmeshPolygon {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly vertices: readonly DeterministicNavmeshPoint[];
|
|
4
|
+
readonly region?: string;
|
|
5
|
+
readonly cost?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface DeterministicNavmeshPoint {
|
|
8
|
+
readonly x: number;
|
|
9
|
+
readonly y: number;
|
|
10
|
+
readonly z?: number;
|
|
11
|
+
}
|
|
12
|
+
export interface DeterministicNavmeshLink {
|
|
13
|
+
readonly from: string;
|
|
14
|
+
readonly to: string;
|
|
15
|
+
readonly cost?: number;
|
|
16
|
+
readonly offMesh?: boolean;
|
|
17
|
+
readonly bidirectional?: boolean;
|
|
18
|
+
}
|
|
19
|
+
export interface DeterministicNavmeshSource {
|
|
20
|
+
readonly id: string;
|
|
21
|
+
readonly polygons: readonly DeterministicNavmeshPolygon[];
|
|
22
|
+
readonly links: readonly DeterministicNavmeshLink[];
|
|
23
|
+
readonly regions?: readonly {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly enabled: boolean;
|
|
26
|
+
}[];
|
|
27
|
+
}
|
|
28
|
+
export interface DeterministicNavmeshImportSource {
|
|
29
|
+
readonly id: string;
|
|
30
|
+
readonly polygons: readonly {
|
|
31
|
+
readonly id: string;
|
|
32
|
+
readonly vertices: readonly DeterministicNavmeshPoint[];
|
|
33
|
+
readonly region?: string;
|
|
34
|
+
readonly cost?: number;
|
|
35
|
+
}[];
|
|
36
|
+
readonly links: readonly {
|
|
37
|
+
readonly from: string;
|
|
38
|
+
readonly to: string;
|
|
39
|
+
readonly cost?: number;
|
|
40
|
+
readonly offMesh?: boolean;
|
|
41
|
+
readonly bidirectional?: boolean;
|
|
42
|
+
}[];
|
|
43
|
+
readonly regions?: readonly {
|
|
44
|
+
readonly id: string;
|
|
45
|
+
readonly enabled: boolean;
|
|
46
|
+
}[];
|
|
47
|
+
readonly spawnMarkers?: readonly {
|
|
48
|
+
readonly id: string;
|
|
49
|
+
readonly polygonId: string;
|
|
50
|
+
readonly point: DeterministicNavmeshPoint;
|
|
51
|
+
}[];
|
|
52
|
+
}
|
|
53
|
+
export interface CookedDeterministicNavmesh {
|
|
54
|
+
readonly id: string;
|
|
55
|
+
readonly polygons: readonly DeterministicNavmeshPolygon[];
|
|
56
|
+
readonly links: readonly DeterministicNavmeshLink[];
|
|
57
|
+
readonly regions: readonly {
|
|
58
|
+
readonly id: string;
|
|
59
|
+
readonly enabled: boolean;
|
|
60
|
+
}[];
|
|
61
|
+
readonly portalTable: readonly {
|
|
62
|
+
readonly from: string;
|
|
63
|
+
readonly to: string;
|
|
64
|
+
readonly midpoint: DeterministicNavmeshPoint;
|
|
65
|
+
readonly offMesh: boolean;
|
|
66
|
+
}[];
|
|
67
|
+
readonly spawnMarkers: readonly {
|
|
68
|
+
readonly id: string;
|
|
69
|
+
readonly polygonId: string;
|
|
70
|
+
readonly point: DeterministicNavmeshPoint;
|
|
71
|
+
}[];
|
|
72
|
+
readonly bytes: string;
|
|
73
|
+
readonly hash: string;
|
|
74
|
+
}
|
|
75
|
+
export interface DeterministicNavmeshPath {
|
|
76
|
+
readonly found: boolean;
|
|
77
|
+
readonly polygons: readonly string[];
|
|
78
|
+
readonly points: readonly DeterministicNavmeshPoint[];
|
|
79
|
+
readonly offMeshTraversals: number;
|
|
80
|
+
readonly visitedPolygons: number;
|
|
81
|
+
readonly pathHash: string;
|
|
82
|
+
readonly startPolygonId?: string;
|
|
83
|
+
readonly goalPolygonId?: string;
|
|
84
|
+
}
|
|
85
|
+
export interface DeterministicNavmeshAgent {
|
|
86
|
+
readonly id: string;
|
|
87
|
+
readonly polygonId: string;
|
|
88
|
+
readonly targetPolygonId: string;
|
|
89
|
+
readonly x?: number;
|
|
90
|
+
readonly y?: number;
|
|
91
|
+
readonly z?: number;
|
|
92
|
+
readonly speedPerFrame?: number;
|
|
93
|
+
readonly updateIntervalFrames?: number;
|
|
94
|
+
readonly stoppingDistance?: number;
|
|
95
|
+
readonly corridor?: readonly string[];
|
|
96
|
+
readonly corridorIndex?: number;
|
|
97
|
+
readonly avoidancePriority?: number;
|
|
98
|
+
readonly radius?: number;
|
|
99
|
+
}
|
|
100
|
+
export interface DeterministicNavmeshMovingAvoidanceObstacle {
|
|
101
|
+
readonly id: string;
|
|
102
|
+
readonly x: number;
|
|
103
|
+
readonly y: number;
|
|
104
|
+
readonly z?: number;
|
|
105
|
+
readonly vx: number;
|
|
106
|
+
readonly vy: number;
|
|
107
|
+
readonly vz?: number;
|
|
108
|
+
readonly radius: number;
|
|
109
|
+
readonly predictionFrames?: number;
|
|
110
|
+
readonly speedReductionPerFrame?: number;
|
|
111
|
+
}
|
|
112
|
+
export interface DeterministicNavmeshAgentStepResult {
|
|
113
|
+
readonly agents: readonly DeterministicNavmeshAgent[];
|
|
114
|
+
readonly replans: number;
|
|
115
|
+
readonly avoidanceCorrections: number;
|
|
116
|
+
readonly dynamicRegionToggles: number;
|
|
117
|
+
readonly obstacleCount: number;
|
|
118
|
+
readonly waypointAdvances: number;
|
|
119
|
+
readonly radiusAvoidanceCorrections: number;
|
|
120
|
+
readonly velocityObstacleAvoidanceCorrections: number;
|
|
121
|
+
readonly agentVelocityObstacleAvoidanceCorrections: number;
|
|
122
|
+
readonly agentAvoidancePriorityYields: number;
|
|
123
|
+
readonly avoidanceSpeedReductions: number;
|
|
124
|
+
readonly agentAvoidanceSpeedReductions: number;
|
|
125
|
+
readonly checksum: string;
|
|
126
|
+
}
|
|
127
|
+
export interface DeterministicNavmeshOffMeshTraversalState {
|
|
128
|
+
readonly agentId: string;
|
|
129
|
+
readonly linkFrom: string;
|
|
130
|
+
readonly linkTo: string;
|
|
131
|
+
readonly commandId: string;
|
|
132
|
+
readonly phase: 'idle' | 'entering' | 'traversing' | 'exiting' | 'complete';
|
|
133
|
+
readonly elapsedFrames: number;
|
|
134
|
+
readonly durationFrames: number;
|
|
135
|
+
}
|
|
136
|
+
export interface DeterministicNavmeshOffMeshTraversalStep {
|
|
137
|
+
readonly state: DeterministicNavmeshOffMeshTraversalState;
|
|
138
|
+
readonly agent: DeterministicNavmeshAgent;
|
|
139
|
+
readonly events: readonly string[];
|
|
140
|
+
readonly checksum: string;
|
|
141
|
+
}
|
|
142
|
+
export interface DeterministicNavmeshImportTraversalFixtureResult {
|
|
143
|
+
readonly checksum: string;
|
|
144
|
+
readonly restoredChecksum: string;
|
|
145
|
+
readonly rollbackEquivalent: boolean;
|
|
146
|
+
readonly importedHash: string;
|
|
147
|
+
readonly permutedHash: string;
|
|
148
|
+
readonly importOrderStable: boolean;
|
|
149
|
+
readonly portalCount: number;
|
|
150
|
+
readonly spawnMarkerCount: number;
|
|
151
|
+
readonly missingRegionRejected: boolean;
|
|
152
|
+
readonly missingLinkRejected: boolean;
|
|
153
|
+
readonly missingSpawnRejected: boolean;
|
|
154
|
+
readonly offMeshEnterEvents: number;
|
|
155
|
+
readonly offMeshTraverseEvents: number;
|
|
156
|
+
readonly offMeshExitEvents: number;
|
|
157
|
+
readonly offMeshCompleteEvents: number;
|
|
158
|
+
readonly offMeshCommandIdStable: boolean;
|
|
159
|
+
readonly offMeshTraversalFrames: number;
|
|
160
|
+
readonly pointPathFound: boolean;
|
|
161
|
+
readonly pointPathHash: string;
|
|
162
|
+
}
|
|
163
|
+
export interface DeterministicNavmeshLayered3DFixtureResult {
|
|
164
|
+
readonly checksum: string;
|
|
165
|
+
readonly restoredChecksum: string;
|
|
166
|
+
readonly rollbackEquivalent: boolean;
|
|
167
|
+
readonly cookedHash: string;
|
|
168
|
+
readonly permutedHash: string;
|
|
169
|
+
readonly cookOrderStable: boolean;
|
|
170
|
+
readonly lowerLookupId: string;
|
|
171
|
+
readonly upperLookupId: string;
|
|
172
|
+
readonly lowerPathHash: string;
|
|
173
|
+
readonly upperPathHash: string;
|
|
174
|
+
readonly verticalOffMeshTraversals: number;
|
|
175
|
+
readonly stackedOverlapAccepted: boolean;
|
|
176
|
+
readonly sameLayerOverlapRejected: boolean;
|
|
177
|
+
readonly layerDisambiguationStable: boolean;
|
|
178
|
+
readonly upperAgentZ: number;
|
|
179
|
+
}
|
|
180
|
+
export interface DeterministicNavmeshVelocityObstacleFixtureResult {
|
|
181
|
+
readonly checksum: string;
|
|
182
|
+
readonly restoredChecksum: string;
|
|
183
|
+
readonly rollbackEquivalent: boolean;
|
|
184
|
+
readonly movingObstacleSpeedReduction: boolean;
|
|
185
|
+
readonly predictedFuturePositionAvoided: boolean;
|
|
186
|
+
readonly authoredSpeedRestored: boolean;
|
|
187
|
+
readonly obstacleOrderStable: boolean;
|
|
188
|
+
readonly obstacleValidationRejected: boolean;
|
|
189
|
+
readonly velocityObstacleAvoidanceCorrections: number;
|
|
190
|
+
readonly avoidanceSpeedReductions: number;
|
|
191
|
+
}
|
|
192
|
+
export declare function cookDeterministicNavmesh(source: DeterministicNavmeshSource): CookedDeterministicNavmesh;
|
|
193
|
+
export declare function cookImportedDeterministicNavmesh(source: DeterministicNavmeshImportSource): CookedDeterministicNavmesh;
|
|
194
|
+
export declare function findDeterministicNavmeshPath(navmesh: CookedDeterministicNavmesh, query: {
|
|
195
|
+
readonly start: string;
|
|
196
|
+
readonly goal: string;
|
|
197
|
+
readonly disabledRegions?: readonly string[];
|
|
198
|
+
readonly blockedPolygons?: readonly string[];
|
|
199
|
+
}): DeterministicNavmeshPath;
|
|
200
|
+
export declare function findDeterministicNavmeshPointPath(navmesh: CookedDeterministicNavmesh, query: {
|
|
201
|
+
readonly start: DeterministicNavmeshPoint;
|
|
202
|
+
readonly goal: DeterministicNavmeshPoint;
|
|
203
|
+
readonly disabledRegions?: readonly string[];
|
|
204
|
+
readonly blockedPolygons?: readonly string[];
|
|
205
|
+
}): DeterministicNavmeshPath;
|
|
206
|
+
export declare function locateDeterministicNavmeshPolygon(navmesh: CookedDeterministicNavmesh, point: DeterministicNavmeshPoint, disabledRegions?: readonly string[], blockedPolygons?: readonly string[]): DeterministicNavmeshPolygon | undefined;
|
|
207
|
+
export declare function smoothDeterministicNavmeshPath(navmesh: CookedDeterministicNavmesh, polygonIds: readonly string[]): readonly DeterministicNavmeshPoint[];
|
|
208
|
+
export declare function stepDeterministicNavmeshAgents(navmesh: CookedDeterministicNavmesh, agents: readonly DeterministicNavmeshAgent[], options?: {
|
|
209
|
+
readonly frame?: number;
|
|
210
|
+
readonly disabledRegions?: readonly string[];
|
|
211
|
+
readonly movingObstaclePolygons?: readonly string[];
|
|
212
|
+
readonly movingAvoidanceObstacles?: readonly DeterministicNavmeshMovingAvoidanceObstacle[];
|
|
213
|
+
}): DeterministicNavmeshAgentStepResult;
|
|
214
|
+
export declare function stepDeterministicNavmeshOffMeshTraversal(navmesh: CookedDeterministicNavmesh, agent: DeterministicNavmeshAgent, state: DeterministicNavmeshOffMeshTraversalState): DeterministicNavmeshOffMeshTraversalStep;
|
|
215
|
+
export declare function runDeterministicNavmeshStress(agents: number, frames: number): {
|
|
216
|
+
readonly checksum: string;
|
|
217
|
+
readonly agents: number;
|
|
218
|
+
readonly frames: number;
|
|
219
|
+
readonly replans: number;
|
|
220
|
+
readonly avoidanceCorrections: number;
|
|
221
|
+
readonly waypointAdvances: number;
|
|
222
|
+
readonly radiusAvoidanceCorrections: number;
|
|
223
|
+
readonly velocityObstacleAvoidanceCorrections: number;
|
|
224
|
+
readonly agentVelocityObstacleAvoidanceCorrections: number;
|
|
225
|
+
readonly agentAvoidancePriorityYields: number;
|
|
226
|
+
readonly avoidanceSpeedReductions: number;
|
|
227
|
+
readonly agentAvoidanceSpeedReductions: number;
|
|
228
|
+
readonly pathHash: string;
|
|
229
|
+
readonly pointPathHash: string;
|
|
230
|
+
readonly pointPathFound: boolean;
|
|
231
|
+
readonly pointLookupStable: boolean;
|
|
232
|
+
readonly rollbackEquivalent: boolean;
|
|
233
|
+
};
|
|
234
|
+
export declare function runDeterministicNavmeshVelocityObstacleFixture(): DeterministicNavmeshVelocityObstacleFixtureResult;
|
|
235
|
+
export declare function runDeterministicNavmeshImportTraversalFixture(): DeterministicNavmeshImportTraversalFixtureResult;
|
|
236
|
+
export declare function runDeterministicNavmeshLayered3DFixture(): DeterministicNavmeshLayered3DFixtureResult;
|