@auraindustry/aurajs 0.1.1 → 0.1.5
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 +7 -0
- package/benchmarks/perf-thresholds.json +27 -0
- package/package.json +6 -1
- package/src/ai-guidance.mjs +302 -0
- package/src/asset-pack.mjs +2 -1
- package/src/authored-project.mjs +498 -2
- package/src/authored-runtime.mjs +14 -0
- package/src/bin-integrity.mjs +33 -26
- package/src/build-contract/capabilities.mjs +87 -1
- package/src/build-contract/constants.mjs +1 -0
- package/src/build-contract.mjs +2 -0
- package/src/bundler.mjs +143 -13
- package/src/cli.mjs +681 -13
- package/src/commands/packs.mjs +741 -0
- package/src/commands/project-authoring.mjs +128 -1
- package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
- package/src/conformance/cases/core-runtime-cases.mjs +6 -2
- package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
- package/src/conformance/cases/systems-and-gameplay-cases.mjs +1126 -10
- package/src/conformance-mobile.mjs +166 -0
- package/src/conformance.mjs +89 -30
- package/src/evidence-bundle.mjs +242 -0
- package/src/external-package-surface.mjs +1 -1
- package/src/headless-test/runtime-coordinator.mjs +186 -33
- package/src/headless-test.mjs +2 -0
- package/src/helpers/2d/index.mjs +183 -0
- package/src/helpers/index.mjs +26 -0
- package/src/helpers/starter-utils/adventure-objectives.js +102 -0
- package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
- package/src/helpers/starter-utils/animation-2d.js +337 -0
- package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
- package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
- package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
- package/src/helpers/starter-utils/avatar-3d.js +404 -0
- package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
- package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
- package/src/helpers/starter-utils/core.js +150 -0
- package/src/helpers/starter-utils/dialogue-2d.js +351 -0
- package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
- package/src/helpers/starter-utils/index.js +26 -0
- package/src/helpers/starter-utils/inventory-2d.js +268 -0
- package/src/helpers/starter-utils/journal-2d.js +267 -0
- package/src/helpers/starter-utils/platformer-3d.js +132 -0
- package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
- package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
- package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
- package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
- package/src/helpers/starter-utils/triggers.js +662 -0
- package/src/helpers/starter-utils/tween-2d.js +615 -0
- package/src/helpers/starter-utils/wave-director.js +101 -0
- package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
- package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
- package/src/mobile/android/build.mjs +606 -0
- package/src/mobile/android/host-artifact.mjs +280 -0
- package/src/mobile/ios/build.mjs +1323 -0
- package/src/mobile/ios/host-artifact.mjs +819 -0
- package/src/mobile/shared/capabilities.mjs +174 -0
- package/src/package-integrity.mjs +18 -4
- package/src/packs/catalog.mjs +259 -0
- package/src/perf-benchmark-runner.mjs +17 -12
- package/src/perf-benchmark.mjs +408 -4
- package/src/publish-command.mjs +434 -17
- package/src/publish-validation.mjs +22 -11
- package/src/replay-runtime.mjs +257 -0
- package/src/scaffold/config.mjs +2 -0
- package/src/scaffold/fs.mjs +8 -1
- package/src/scaffold/project-docs.mjs +101 -41
- package/src/scaffold.mjs +4 -0
- package/src/session-runtime.mjs +4 -3
- package/src/web-conformance.mjs +0 -36
- package/templates/create/2d/src/runtime/app.js +4 -0
- package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
- package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
- package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
- package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
- package/templates/create/2d-adventure/docs/design/loop.md +4 -3
- package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
- package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
- package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
- package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
- package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
- package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
- package/templates/create/2d-survivor/src/runtime/app.js +4 -0
- package/templates/create/3d/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d/src/runtime/app.js +4 -0
- package/templates/create/3d/src/runtime/capabilities.js +5 -0
- package/templates/create/3d/src/runtime/materials.js +10 -0
- package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
- package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
- package/templates/create/3d-collectathon/src/runtime/app.js +4 -0
- package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
- package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
- package/templates/create/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/create/blank/assets/splash/bg.webp +0 -0
- package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
- package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
- package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/create/blank/assets/splash/logoholo.webp +0 -0
- package/templates/create/blank/src/main.js +5 -1
- package/templates/create/blank/src/runtime/splash.js +305 -0
- package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
- package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
- package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/create/shared/assets/splash/bg.webp +0 -0
- package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
- package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
- package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/create/shared/assets/splash/logoholo.webp +0 -0
- package/templates/create/shared/src/runtime/splash.js +305 -0
- package/templates/create/shared/src/runtime/ui-forms.js +552 -0
- package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
- package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
- package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
- package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
- package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
- package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
- package/templates/create/shared/src/starter-utils/index.js +15 -1
- package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
- package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
- package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
- package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
- package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
- package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
- package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
- package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
- package/templates/create/video-cutscene/src/runtime/app.js +4 -0
- package/templates/create-bin/play.js +148 -7
- package/templates/skills/auramaxx/SKILL.md +46 -0
- package/templates/skills/auramaxx/project-requirements.md +68 -0
- package/templates/skills/auramaxx/starter-recipes.md +104 -0
- package/templates/skills/auramaxx/validation-checklist.md +49 -0
- package/templates/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
- package/templates/starter/assets/splash/bg.webp +0 -0
- package/templates/starter/assets/splash/boot-loop.wav +0 -0
- package/templates/starter/assets/splash/boot-sting.wav +0 -0
- package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
- package/templates/starter/assets/splash/logoholo.webp +0 -0
- package/templates/starter/src/main.js +4 -0
- package/templates/starter/src/runtime/splash.js +305 -0
- package/templates/skills/aurajs/SKILL.md +0 -96
- package/templates/skills/aurajs/api-contract-3d.md +0 -7
- package/templates/skills/aurajs/api-contract.md +0 -7
|
@@ -0,0 +1,499 @@
|
|
|
1
|
+
function finite(value, fallback = 0) {
|
|
2
|
+
const numeric = Number(value);
|
|
3
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function positiveInteger(value, fallback = null) {
|
|
7
|
+
const numeric = Math.floor(finite(value, Number.NaN));
|
|
8
|
+
return numeric >= 1 ? numeric : fallback;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function normalizeCell(entry) {
|
|
12
|
+
if (!entry || typeof entry !== 'object' || Array.isArray(entry)) return null;
|
|
13
|
+
const x = Math.floor(finite(entry.x, Number.NaN));
|
|
14
|
+
const y = Math.floor(finite(entry.y, Number.NaN));
|
|
15
|
+
if (!Number.isFinite(x) || !Number.isFinite(y)) return null;
|
|
16
|
+
return { x, y };
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function cellKey(x, y) {
|
|
20
|
+
return `${x}:${y}`;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function keyToCell(key) {
|
|
24
|
+
const [x, y] = String(key).split(':');
|
|
25
|
+
return {
|
|
26
|
+
x: Number.parseInt(x, 10),
|
|
27
|
+
y: Number.parseInt(y, 10),
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function inBounds(nav, x, y) {
|
|
32
|
+
return x >= 0 && y >= 0 && x < nav.width && y < nav.height;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function trimCache(nav) {
|
|
36
|
+
while (nav.cache.size > nav.maxCacheEntries) {
|
|
37
|
+
const firstKey = nav.cache.keys().next().value;
|
|
38
|
+
if (firstKey == null) return;
|
|
39
|
+
nav.cache.delete(firstKey);
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function createBlockedGrid(width, height) {
|
|
44
|
+
return new Uint8Array(width * height);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function applyBlockedCells(nav, entries = [], blocked = true) {
|
|
48
|
+
if (!(nav.blockedGrid instanceof Uint8Array)) return 0;
|
|
49
|
+
let changed = 0;
|
|
50
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
51
|
+
const cell = normalizeCell(entry);
|
|
52
|
+
if (!cell || !inBounds(nav, cell.x, cell.y)) continue;
|
|
53
|
+
const index = (cell.y * nav.width) + cell.x;
|
|
54
|
+
const nextValue = blocked ? 1 : 0;
|
|
55
|
+
if (nav.blockedGrid[index] === nextValue) continue;
|
|
56
|
+
nav.blockedGrid[index] = nextValue;
|
|
57
|
+
changed += 1;
|
|
58
|
+
}
|
|
59
|
+
return changed;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function loadBlockedGrid(nav, options = {}) {
|
|
63
|
+
const width = nav.width;
|
|
64
|
+
const height = nav.height;
|
|
65
|
+
const cellCount = width * height;
|
|
66
|
+
const grid = createBlockedGrid(width, height);
|
|
67
|
+
const blockedGrid = Array.isArray(options.blockedGrid) || ArrayBuffer.isView(options.blockedGrid)
|
|
68
|
+
? options.blockedGrid
|
|
69
|
+
: null;
|
|
70
|
+
|
|
71
|
+
if (blockedGrid && blockedGrid.length >= cellCount) {
|
|
72
|
+
for (let index = 0; index < cellCount; index += 1) {
|
|
73
|
+
grid[index] = blockedGrid[index] ? 1 : 0;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
nav.blockedGrid = grid;
|
|
78
|
+
applyBlockedCells(nav, options.blockedCells, true);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function markNavDirty(nav, kind = 'topology') {
|
|
82
|
+
if (kind === 'occupancy') {
|
|
83
|
+
nav.occupancyVersion += 1;
|
|
84
|
+
} else {
|
|
85
|
+
nav.topologyVersion += 1;
|
|
86
|
+
}
|
|
87
|
+
nav.cache.clear();
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
function sameCell(a, b) {
|
|
91
|
+
return !!a && !!b && a.x === b.x && a.y === b.y;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
function normalizeIgnoreCells(entries = []) {
|
|
95
|
+
const set = new Set();
|
|
96
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
97
|
+
const cell = normalizeCell(entry);
|
|
98
|
+
if (!cell) continue;
|
|
99
|
+
set.add(cellKey(cell.x, cell.y));
|
|
100
|
+
}
|
|
101
|
+
return set;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
function neighborDirections(allowDiagonal) {
|
|
105
|
+
if (allowDiagonal) {
|
|
106
|
+
return [
|
|
107
|
+
{ x: 1, y: 0, cost: 1 },
|
|
108
|
+
{ x: -1, y: 0, cost: 1 },
|
|
109
|
+
{ x: 0, y: 1, cost: 1 },
|
|
110
|
+
{ x: 0, y: -1, cost: 1 },
|
|
111
|
+
{ x: 1, y: 1, cost: Math.SQRT2 },
|
|
112
|
+
{ x: 1, y: -1, cost: Math.SQRT2 },
|
|
113
|
+
{ x: -1, y: 1, cost: Math.SQRT2 },
|
|
114
|
+
{ x: -1, y: -1, cost: Math.SQRT2 },
|
|
115
|
+
];
|
|
116
|
+
}
|
|
117
|
+
return [
|
|
118
|
+
{ x: 1, y: 0, cost: 1 },
|
|
119
|
+
{ x: -1, y: 0, cost: 1 },
|
|
120
|
+
{ x: 0, y: 1, cost: 1 },
|
|
121
|
+
{ x: 0, y: -1, cost: 1 },
|
|
122
|
+
];
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function heuristicFor(allowDiagonal, fromX, fromY, toX, toY) {
|
|
126
|
+
const dx = Math.abs(toX - fromX);
|
|
127
|
+
const dy = Math.abs(toY - fromY);
|
|
128
|
+
return allowDiagonal ? Math.max(dx, dy) : (dx + dy);
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
function cellBlocked(nav, x, y) {
|
|
132
|
+
if (!inBounds(nav, x, y)) return true;
|
|
133
|
+
if (nav.blockedGrid instanceof Uint8Array && nav.blockedGrid[(y * nav.width) + x] === 1) {
|
|
134
|
+
return true;
|
|
135
|
+
}
|
|
136
|
+
if (typeof nav.isBlocked === 'function') {
|
|
137
|
+
return nav.isBlocked(x, y, nav) === true;
|
|
138
|
+
}
|
|
139
|
+
return false;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function buildCacheKey(nav, start, goal, options = {}) {
|
|
143
|
+
return [
|
|
144
|
+
`${start.x}:${start.y}`,
|
|
145
|
+
`${goal.x}:${goal.y}`,
|
|
146
|
+
options.allowDiagonal === true || (options.allowDiagonal == null && nav.allowDiagonal === true) ? 'diag' : 'cardinal',
|
|
147
|
+
`occupied:${Number.isFinite(options.occupiedCost) ? Number(options.occupiedCost) : nav.occupiedCost}`,
|
|
148
|
+
options.allowGoalOccupied === true ? 'goal-ok' : 'goal-solid',
|
|
149
|
+
].join('|');
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
function cacheResult(nav, key, result) {
|
|
153
|
+
nav.cache.set(key, {
|
|
154
|
+
topologyVersion: nav.topologyVersion,
|
|
155
|
+
occupancyVersion: nav.occupancyVersion,
|
|
156
|
+
result,
|
|
157
|
+
});
|
|
158
|
+
trimCache(nav);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function cachedResult(nav, key) {
|
|
162
|
+
const entry = nav.cache.get(key);
|
|
163
|
+
if (!entry) return null;
|
|
164
|
+
if (
|
|
165
|
+
entry.topologyVersion !== nav.topologyVersion
|
|
166
|
+
|| entry.occupancyVersion !== nav.occupancyVersion
|
|
167
|
+
) {
|
|
168
|
+
nav.cache.delete(key);
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
return {
|
|
172
|
+
...entry.result,
|
|
173
|
+
usedCache: true,
|
|
174
|
+
};
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
export function createTilemapNav2D(options = {}) {
|
|
178
|
+
const width = positiveInteger(options.width, 0) || 0;
|
|
179
|
+
const height = positiveInteger(options.height, 0) || 0;
|
|
180
|
+
const nav = {
|
|
181
|
+
width,
|
|
182
|
+
height,
|
|
183
|
+
allowDiagonal: options.allowDiagonal === true,
|
|
184
|
+
occupiedCost: Number.isFinite(options.occupiedCost) ? Number(options.occupiedCost) : 4,
|
|
185
|
+
maxCacheEntries: positiveInteger(options.maxCacheEntries, 96) || 96,
|
|
186
|
+
isBlocked: typeof options.isBlocked === 'function' ? options.isBlocked : null,
|
|
187
|
+
blockedGrid: null,
|
|
188
|
+
occupiedCells: new Set(),
|
|
189
|
+
topologyVersion: 1,
|
|
190
|
+
occupancyVersion: 1,
|
|
191
|
+
cache: new Map(),
|
|
192
|
+
};
|
|
193
|
+
loadBlockedGrid(nav, options);
|
|
194
|
+
return nav;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
export function rebuildTilemapNav2D(nav, options = {}) {
|
|
198
|
+
if (!nav || typeof nav !== 'object') {
|
|
199
|
+
throw new Error('tilemap nav state is required.');
|
|
200
|
+
}
|
|
201
|
+
nav.width = positiveInteger(options.width ?? nav.width, nav.width) || 0;
|
|
202
|
+
nav.height = positiveInteger(options.height ?? nav.height, nav.height) || 0;
|
|
203
|
+
nav.allowDiagonal = options.allowDiagonal === true || (options.allowDiagonal == null && nav.allowDiagonal === true);
|
|
204
|
+
nav.occupiedCost = Number.isFinite(options.occupiedCost) ? Number(options.occupiedCost) : nav.occupiedCost;
|
|
205
|
+
nav.maxCacheEntries = positiveInteger(options.maxCacheEntries, nav.maxCacheEntries) || nav.maxCacheEntries;
|
|
206
|
+
nav.isBlocked = typeof options.isBlocked === 'function'
|
|
207
|
+
? options.isBlocked
|
|
208
|
+
: (options.isBlocked === null ? null : nav.isBlocked);
|
|
209
|
+
loadBlockedGrid(nav, options);
|
|
210
|
+
nav.occupiedCells = new Set();
|
|
211
|
+
markNavDirty(nav, 'topology');
|
|
212
|
+
return nav;
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
export function invalidateTilemapNav2D(nav, options = {}) {
|
|
216
|
+
if (!nav || typeof nav !== 'object') {
|
|
217
|
+
return {
|
|
218
|
+
ok: false,
|
|
219
|
+
reasonCode: 'invalid_nav_state',
|
|
220
|
+
changedCells: 0,
|
|
221
|
+
topologyVersion: 0,
|
|
222
|
+
};
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
let changedCells = 0;
|
|
226
|
+
if (Array.isArray(options.cells)) {
|
|
227
|
+
changedCells += applyBlockedCells(
|
|
228
|
+
nav,
|
|
229
|
+
options.cells,
|
|
230
|
+
options.blocked !== false,
|
|
231
|
+
);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (options.rect && typeof options.rect === 'object' && !Array.isArray(options.rect)) {
|
|
235
|
+
const x = Math.floor(finite(options.rect.x, Number.NaN));
|
|
236
|
+
const y = Math.floor(finite(options.rect.y, Number.NaN));
|
|
237
|
+
const w = positiveInteger(options.rect.w ?? options.rect.width, 0) || 0;
|
|
238
|
+
const h = positiveInteger(options.rect.h ?? options.rect.height, 0) || 0;
|
|
239
|
+
if (Number.isFinite(x) && Number.isFinite(y) && w > 0 && h > 0) {
|
|
240
|
+
const cells = [];
|
|
241
|
+
for (let row = y; row < y + h; row += 1) {
|
|
242
|
+
for (let col = x; col < x + w; col += 1) {
|
|
243
|
+
cells.push({ x: col, y: row });
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
changedCells += applyBlockedCells(nav, cells, options.blocked !== false);
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
markNavDirty(nav, 'topology');
|
|
251
|
+
return {
|
|
252
|
+
ok: true,
|
|
253
|
+
reasonCode: null,
|
|
254
|
+
changedCells,
|
|
255
|
+
topologyVersion: nav.topologyVersion,
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
export function collectTilemapOccupiedCells2D(entries = [], options = {}) {
|
|
260
|
+
const cells = [];
|
|
261
|
+
const seen = new Set();
|
|
262
|
+
const resolveCell = typeof options.resolveCell === 'function'
|
|
263
|
+
? options.resolveCell
|
|
264
|
+
: (entry) => entry;
|
|
265
|
+
const width = positiveInteger(options.width, null);
|
|
266
|
+
const height = positiveInteger(options.height, null);
|
|
267
|
+
|
|
268
|
+
for (const entry of Array.isArray(entries) ? entries : []) {
|
|
269
|
+
const cell = normalizeCell(resolveCell(entry));
|
|
270
|
+
if (!cell) continue;
|
|
271
|
+
if (width != null && height != null && (cell.x < 0 || cell.y < 0 || cell.x >= width || cell.y >= height)) {
|
|
272
|
+
continue;
|
|
273
|
+
}
|
|
274
|
+
const key = cellKey(cell.x, cell.y);
|
|
275
|
+
if (seen.has(key)) continue;
|
|
276
|
+
seen.add(key);
|
|
277
|
+
cells.push(cell);
|
|
278
|
+
}
|
|
279
|
+
return cells;
|
|
280
|
+
}
|
|
281
|
+
|
|
282
|
+
export function setTilemapNavOccupiedCells2D(nav, entries = [], options = {}) {
|
|
283
|
+
if (!nav || typeof nav !== 'object') {
|
|
284
|
+
return {
|
|
285
|
+
ok: false,
|
|
286
|
+
reasonCode: 'invalid_nav_state',
|
|
287
|
+
occupiedCount: 0,
|
|
288
|
+
occupancyVersion: 0,
|
|
289
|
+
};
|
|
290
|
+
}
|
|
291
|
+
|
|
292
|
+
const cells = collectTilemapOccupiedCells2D(entries, {
|
|
293
|
+
width: nav.width,
|
|
294
|
+
height: nav.height,
|
|
295
|
+
resolveCell: options.resolveCell,
|
|
296
|
+
});
|
|
297
|
+
const next = new Set(cells.map((cell) => cellKey(cell.x, cell.y)));
|
|
298
|
+
const currentKeys = [...nav.occupiedCells];
|
|
299
|
+
const changed = currentKeys.length !== next.size
|
|
300
|
+
|| currentKeys.some((key) => !next.has(key));
|
|
301
|
+
|
|
302
|
+
if (changed) {
|
|
303
|
+
nav.occupiedCells = next;
|
|
304
|
+
markNavDirty(nav, 'occupancy');
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
return {
|
|
308
|
+
ok: true,
|
|
309
|
+
reasonCode: null,
|
|
310
|
+
changed,
|
|
311
|
+
occupiedCount: next.size,
|
|
312
|
+
occupancyVersion: nav.occupancyVersion,
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
export function findTilemapPath2D(nav, startCell, goalCell, options = {}) {
|
|
317
|
+
if (!nav || typeof nav !== 'object' || nav.width <= 0 || nav.height <= 0) {
|
|
318
|
+
return {
|
|
319
|
+
ok: false,
|
|
320
|
+
reasonCode: 'invalid_nav_state',
|
|
321
|
+
cells: [],
|
|
322
|
+
nextCell: null,
|
|
323
|
+
steps: 0,
|
|
324
|
+
usedCache: false,
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
|
|
328
|
+
const start = normalizeCell(startCell);
|
|
329
|
+
const goal = normalizeCell(goalCell);
|
|
330
|
+
if (!start || !inBounds(nav, start.x, start.y)) {
|
|
331
|
+
return {
|
|
332
|
+
ok: false,
|
|
333
|
+
reasonCode: 'invalid_start_cell',
|
|
334
|
+
cells: [],
|
|
335
|
+
nextCell: null,
|
|
336
|
+
steps: 0,
|
|
337
|
+
usedCache: false,
|
|
338
|
+
};
|
|
339
|
+
}
|
|
340
|
+
if (!goal || !inBounds(nav, goal.x, goal.y)) {
|
|
341
|
+
return {
|
|
342
|
+
ok: false,
|
|
343
|
+
reasonCode: 'invalid_goal_cell',
|
|
344
|
+
cells: [],
|
|
345
|
+
nextCell: null,
|
|
346
|
+
steps: 0,
|
|
347
|
+
usedCache: false,
|
|
348
|
+
};
|
|
349
|
+
}
|
|
350
|
+
|
|
351
|
+
const allowDiagonal = options.allowDiagonal === true || (options.allowDiagonal == null && nav.allowDiagonal === true);
|
|
352
|
+
const allowGoalOccupied = options.allowGoalOccupied === true;
|
|
353
|
+
const occupiedCost = Number.isFinite(options.occupiedCost) ? Number(options.occupiedCost) : nav.occupiedCost;
|
|
354
|
+
const ignoreCells = normalizeIgnoreCells(options.ignoreCells);
|
|
355
|
+
|
|
356
|
+
if (!sameCell(start, goal) && cellBlocked(nav, goal.x, goal.y) === true) {
|
|
357
|
+
return {
|
|
358
|
+
ok: false,
|
|
359
|
+
reasonCode: 'goal_blocked',
|
|
360
|
+
cells: [],
|
|
361
|
+
nextCell: null,
|
|
362
|
+
steps: 0,
|
|
363
|
+
usedCache: false,
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const cacheKey = buildCacheKey(nav, start, goal, {
|
|
368
|
+
allowDiagonal,
|
|
369
|
+
allowGoalOccupied,
|
|
370
|
+
occupiedCost,
|
|
371
|
+
});
|
|
372
|
+
const cached = cachedResult(nav, cacheKey);
|
|
373
|
+
if (cached) {
|
|
374
|
+
return cached;
|
|
375
|
+
}
|
|
376
|
+
|
|
377
|
+
const startKey = cellKey(start.x, start.y);
|
|
378
|
+
const goalKey = cellKey(goal.x, goal.y);
|
|
379
|
+
const directions = neighborDirections(allowDiagonal);
|
|
380
|
+
const maxIterations = positiveInteger(options.maxIterations, Math.max(256, nav.width * nav.height * 4)) || 256;
|
|
381
|
+
|
|
382
|
+
const open = [startKey];
|
|
383
|
+
const openSet = new Set(open);
|
|
384
|
+
const cameFrom = new Map();
|
|
385
|
+
const gScore = new Map([[startKey, 0]]);
|
|
386
|
+
const fScore = new Map([[startKey, heuristicFor(allowDiagonal, start.x, start.y, goal.x, goal.y)]]);
|
|
387
|
+
|
|
388
|
+
let iterations = 0;
|
|
389
|
+
while (open.length > 0 && iterations < maxIterations) {
|
|
390
|
+
iterations += 1;
|
|
391
|
+
open.sort((left, right) => (fScore.get(left) ?? Infinity) - (fScore.get(right) ?? Infinity));
|
|
392
|
+
const currentKey = open.shift();
|
|
393
|
+
openSet.delete(currentKey);
|
|
394
|
+
if (currentKey === goalKey) {
|
|
395
|
+
const cells = [];
|
|
396
|
+
let key = currentKey;
|
|
397
|
+
while (key) {
|
|
398
|
+
cells.push(keyToCell(key));
|
|
399
|
+
key = cameFrom.get(key) || '';
|
|
400
|
+
}
|
|
401
|
+
cells.reverse();
|
|
402
|
+
const result = {
|
|
403
|
+
ok: true,
|
|
404
|
+
reasonCode: null,
|
|
405
|
+
cells,
|
|
406
|
+
nextCell: cells[1] || cells[0] || null,
|
|
407
|
+
steps: Math.max(0, cells.length - 1),
|
|
408
|
+
usedCache: false,
|
|
409
|
+
topologyVersion: nav.topologyVersion,
|
|
410
|
+
occupancyVersion: nav.occupancyVersion,
|
|
411
|
+
};
|
|
412
|
+
cacheResult(nav, cacheKey, result);
|
|
413
|
+
return result;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
const current = keyToCell(currentKey);
|
|
417
|
+
for (const direction of directions) {
|
|
418
|
+
const nextX = current.x + direction.x;
|
|
419
|
+
const nextY = current.y + direction.y;
|
|
420
|
+
if (!inBounds(nav, nextX, nextY)) continue;
|
|
421
|
+
|
|
422
|
+
if (direction.x !== 0 && direction.y !== 0) {
|
|
423
|
+
if (cellBlocked(nav, current.x + direction.x, current.y) || cellBlocked(nav, current.x, current.y + direction.y)) {
|
|
424
|
+
continue;
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
|
|
428
|
+
const nextKey = cellKey(nextX, nextY);
|
|
429
|
+
if (nextKey !== goalKey && nextKey !== startKey && !ignoreCells.has(nextKey) && cellBlocked(nav, nextX, nextY)) {
|
|
430
|
+
continue;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
const occupiedPenalty = nav.occupiedCells.has(nextKey)
|
|
434
|
+
&& nextKey !== goalKey
|
|
435
|
+
&& nextKey !== startKey
|
|
436
|
+
&& !ignoreCells.has(nextKey)
|
|
437
|
+
? occupiedCost
|
|
438
|
+
: 0;
|
|
439
|
+
if (!allowGoalOccupied && nextKey === goalKey && nav.occupiedCells.has(nextKey) && !ignoreCells.has(nextKey)) {
|
|
440
|
+
continue;
|
|
441
|
+
}
|
|
442
|
+
|
|
443
|
+
const tentativeG = (gScore.get(currentKey) ?? Infinity) + direction.cost + occupiedPenalty;
|
|
444
|
+
if (tentativeG >= (gScore.get(nextKey) ?? Infinity)) {
|
|
445
|
+
continue;
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
cameFrom.set(nextKey, currentKey);
|
|
449
|
+
gScore.set(nextKey, tentativeG);
|
|
450
|
+
fScore.set(
|
|
451
|
+
nextKey,
|
|
452
|
+
tentativeG + heuristicFor(allowDiagonal, nextX, nextY, goal.x, goal.y),
|
|
453
|
+
);
|
|
454
|
+
|
|
455
|
+
if (!openSet.has(nextKey)) {
|
|
456
|
+
open.push(nextKey);
|
|
457
|
+
openSet.add(nextKey);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
return {
|
|
463
|
+
ok: false,
|
|
464
|
+
reasonCode: iterations >= maxIterations ? 'search_budget_exhausted' : 'path_not_found',
|
|
465
|
+
cells: [],
|
|
466
|
+
nextCell: null,
|
|
467
|
+
steps: 0,
|
|
468
|
+
usedCache: false,
|
|
469
|
+
};
|
|
470
|
+
}
|
|
471
|
+
|
|
472
|
+
export function resolveTilemapStep2D(nav, startCell, goalCell, options = {}) {
|
|
473
|
+
const path = findTilemapPath2D(nav, startCell, goalCell, options);
|
|
474
|
+
if (path.ok !== true || !path.nextCell) {
|
|
475
|
+
return {
|
|
476
|
+
...path,
|
|
477
|
+
directionX: 0,
|
|
478
|
+
directionY: 0,
|
|
479
|
+
};
|
|
480
|
+
}
|
|
481
|
+
|
|
482
|
+
const start = normalizeCell(startCell);
|
|
483
|
+
if (!start || sameCell(start, path.nextCell)) {
|
|
484
|
+
return {
|
|
485
|
+
...path,
|
|
486
|
+
directionX: 0,
|
|
487
|
+
directionY: 0,
|
|
488
|
+
};
|
|
489
|
+
}
|
|
490
|
+
|
|
491
|
+
const dx = path.nextCell.x - start.x;
|
|
492
|
+
const dy = path.nextCell.y - start.y;
|
|
493
|
+
const length = Math.hypot(dx, dy);
|
|
494
|
+
return {
|
|
495
|
+
...path,
|
|
496
|
+
directionX: length > 0 ? dx / length : 0,
|
|
497
|
+
directionY: length > 0 ? dy / length : 0,
|
|
498
|
+
};
|
|
499
|
+
}
|
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
import { centeredRect } from './core.js';
|
|
2
|
+
|
|
3
|
+
function finite(value, fallback = 0) {
|
|
4
|
+
const numeric = Number(value);
|
|
5
|
+
return Number.isFinite(numeric) ? numeric : fallback;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function normalizeText(value, fallback = null) {
|
|
9
|
+
if (typeof value === 'string' && value.trim().length > 0) {
|
|
10
|
+
return value.trim();
|
|
11
|
+
}
|
|
12
|
+
return fallback;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
function resolveAura(auraRef = globalThis.aura) {
|
|
16
|
+
return auraRef && typeof auraRef === 'object' ? auraRef : null;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
function toLocalPoint(world, point = {}) {
|
|
20
|
+
return {
|
|
21
|
+
x: finite(point.x) - finite(world?.offsetX),
|
|
22
|
+
y: finite(point.y) - finite(world?.offsetY),
|
|
23
|
+
};
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function toWorldPoint(world, point = {}) {
|
|
27
|
+
return {
|
|
28
|
+
x: finite(point.x) + finite(world?.offsetX),
|
|
29
|
+
y: finite(point.y) + finite(world?.offsetY),
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function toLocalAabb(world, bounds = {}) {
|
|
34
|
+
const point = toLocalPoint(world, bounds);
|
|
35
|
+
return {
|
|
36
|
+
x: point.x,
|
|
37
|
+
y: point.y,
|
|
38
|
+
w: Math.max(0, finite(bounds.w ?? bounds.width)),
|
|
39
|
+
h: Math.max(0, finite(bounds.h ?? bounds.height)),
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function objectToWorldAnchor(world, object, centered = false) {
|
|
44
|
+
if (!object || typeof object !== 'object') return null;
|
|
45
|
+
const x = finite(object.x);
|
|
46
|
+
const y = finite(object.y);
|
|
47
|
+
const width = Math.max(0, finite(object.width));
|
|
48
|
+
const height = Math.max(0, finite(object.height));
|
|
49
|
+
return {
|
|
50
|
+
x: finite(world?.offsetX) + (centered && width > 0 ? x + (width * 0.5) : x),
|
|
51
|
+
y: finite(world?.offsetY) + (centered && height > 0 ? y + (height * 0.5) : y),
|
|
52
|
+
object,
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function queryObjects(aura, world, options = {}) {
|
|
57
|
+
if (!ensureTilemapWorldLoaded2D(world, aura)) return [];
|
|
58
|
+
const result = aura.tilemap.queryObjects(world.mapId, options) || null;
|
|
59
|
+
return Array.isArray(result?.hits) ? result.hits : [];
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
export function createTilemapWorld2D(options = {}) {
|
|
63
|
+
return {
|
|
64
|
+
mapSource: options.mapSource ?? options.map ?? null,
|
|
65
|
+
mapId: Number.isInteger(Number(options.mapId)) && Number(options.mapId) > 0 ? Number(options.mapId) : 0,
|
|
66
|
+
info: null,
|
|
67
|
+
loaded: Number.isInteger(Number(options.mapId)) && Number(options.mapId) > 0,
|
|
68
|
+
offsetX: finite(options.offsetX ?? options.x),
|
|
69
|
+
offsetY: finite(options.offsetY ?? options.y),
|
|
70
|
+
cull: options.cull !== false,
|
|
71
|
+
includeHidden: options.includeHidden === true,
|
|
72
|
+
lastDrawSummary: null,
|
|
73
|
+
};
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function ensureTilemapWorldLoaded2D(world, auraRef = globalThis.aura) {
|
|
77
|
+
const aura = resolveAura(auraRef);
|
|
78
|
+
if (!world || !aura?.tilemap || typeof aura.tilemap.import !== 'function') return false;
|
|
79
|
+
if (world.loaded === true && Number.isInteger(Number(world.mapId)) && Number(world.mapId) > 0) {
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
const mapId = aura.tilemap.import(world.mapSource);
|
|
83
|
+
if (!Number.isInteger(Number(mapId)) || Number(mapId) <= 0) return false;
|
|
84
|
+
world.mapId = Number(mapId);
|
|
85
|
+
world.loaded = true;
|
|
86
|
+
world.info = typeof aura.tilemap.getInfo === 'function' ? aura.tilemap.getInfo(world.mapId) || null : null;
|
|
87
|
+
return true;
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export function unloadTilemapWorld2D(world, auraRef = globalThis.aura) {
|
|
91
|
+
const aura = resolveAura(auraRef);
|
|
92
|
+
if (!world || !aura?.tilemap || typeof aura.tilemap.unload !== 'function') return false;
|
|
93
|
+
if (!Number.isInteger(Number(world.mapId)) || Number(world.mapId) <= 0) return false;
|
|
94
|
+
const unloaded = aura.tilemap.unload(world.mapId) === true;
|
|
95
|
+
if (!unloaded) return false;
|
|
96
|
+
world.mapId = 0;
|
|
97
|
+
world.loaded = false;
|
|
98
|
+
world.info = null;
|
|
99
|
+
world.lastDrawSummary = null;
|
|
100
|
+
return true;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function drawTilemapWorld2D(world, auraRef = globalThis.aura, options = {}) {
|
|
104
|
+
const aura = resolveAura(auraRef);
|
|
105
|
+
if (!ensureTilemapWorldLoaded2D(world, aura) || typeof aura.tilemap.draw !== 'function') return null;
|
|
106
|
+
const summary = aura.tilemap.draw(world.mapId, {
|
|
107
|
+
x: finite(options.x, finite(world.offsetX)),
|
|
108
|
+
y: finite(options.y, finite(world.offsetY)),
|
|
109
|
+
cull: options.cull ?? world.cull,
|
|
110
|
+
camera: options.camera,
|
|
111
|
+
view: options.view,
|
|
112
|
+
includeHidden: options.includeHidden ?? world.includeHidden,
|
|
113
|
+
}) || null;
|
|
114
|
+
world.lastDrawSummary = summary;
|
|
115
|
+
return summary;
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function listTilemapWorldObjects2D(world, auraRef = globalThis.aura, options = {}) {
|
|
119
|
+
const aura = resolveAura(auraRef);
|
|
120
|
+
return queryObjects(aura, world, options).map((entry) => ({
|
|
121
|
+
...entry,
|
|
122
|
+
worldX: finite(entry?.x) + finite(world?.offsetX),
|
|
123
|
+
worldY: finite(entry?.y) + finite(world?.offsetY),
|
|
124
|
+
}));
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function findTilemapWorldObject2D(world, auraRef = globalThis.aura, options = {}) {
|
|
128
|
+
return listTilemapWorldObjects2D(world, auraRef, options)[0] || null;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
export function resolveTilemapSpawn2D(world, auraRef = globalThis.aura, options = {}) {
|
|
132
|
+
const normalized = typeof options === 'string' ? { name: options } : options;
|
|
133
|
+
const object = findTilemapWorldObject2D(world, auraRef, {
|
|
134
|
+
layer: normalized.layer,
|
|
135
|
+
name: normalized.name,
|
|
136
|
+
type: normalized.type ?? 'spawn',
|
|
137
|
+
className: normalized.className ?? normalized.class,
|
|
138
|
+
property: normalized.property,
|
|
139
|
+
properties: normalized.properties,
|
|
140
|
+
includeHidden: normalized.includeHidden,
|
|
141
|
+
});
|
|
142
|
+
if (!object) return null;
|
|
143
|
+
return objectToWorldAnchor(world, object, normalized.centered === true);
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
export function queryTilemapWorldPoint2D(world, auraRef = globalThis.aura, point = {}) {
|
|
147
|
+
const aura = resolveAura(auraRef);
|
|
148
|
+
if (!ensureTilemapWorldLoaded2D(world, aura) || typeof aura.tilemap.queryPoint !== 'function') return null;
|
|
149
|
+
return aura.tilemap.queryPoint(world.mapId, toLocalPoint(world, point)) || null;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
export function queryTilemapWorldAABB2D(world, auraRef = globalThis.aura, bounds = {}) {
|
|
153
|
+
const aura = resolveAura(auraRef);
|
|
154
|
+
if (!ensureTilemapWorldLoaded2D(world, aura) || typeof aura.tilemap.queryAABB !== 'function') return null;
|
|
155
|
+
return aura.tilemap.queryAABB(world.mapId, toLocalAabb(world, bounds)) || null;
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function isTilemapPointBlocked2D(world, auraRef = globalThis.aura, point = {}) {
|
|
159
|
+
const result = queryTilemapWorldPoint2D(world, auraRef, point);
|
|
160
|
+
return result?.hit === true;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
export function moveActorThroughTilemapWorld2D(
|
|
164
|
+
world,
|
|
165
|
+
auraRef = globalThis.aura,
|
|
166
|
+
actor = {},
|
|
167
|
+
dx = 0,
|
|
168
|
+
dy = 0,
|
|
169
|
+
width = 16,
|
|
170
|
+
height = width,
|
|
171
|
+
) {
|
|
172
|
+
const stepX = finite(dx);
|
|
173
|
+
const stepY = finite(dy);
|
|
174
|
+
const actorWidth = Math.max(0.1, finite(width, 16));
|
|
175
|
+
const actorHeight = Math.max(0.1, finite(height, actorWidth));
|
|
176
|
+
|
|
177
|
+
const targetX = finite(actor.x) + stepX;
|
|
178
|
+
const targetY = finite(actor.y) + stepY;
|
|
179
|
+
const blockedX = queryTilemapWorldAABB2D(
|
|
180
|
+
world,
|
|
181
|
+
auraRef,
|
|
182
|
+
centeredRect(targetX, finite(actor.y), actorWidth, actorHeight),
|
|
183
|
+
)?.hit === true;
|
|
184
|
+
if (!blockedX) {
|
|
185
|
+
actor.x = targetX;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
const blockedY = queryTilemapWorldAABB2D(
|
|
189
|
+
world,
|
|
190
|
+
auraRef,
|
|
191
|
+
centeredRect(finite(actor.x), targetY, actorWidth, actorHeight),
|
|
192
|
+
)?.hit === true;
|
|
193
|
+
if (!blockedY) {
|
|
194
|
+
actor.y = targetY;
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
return {
|
|
198
|
+
x: finite(actor.x),
|
|
199
|
+
y: finite(actor.y),
|
|
200
|
+
movedX: blockedX ? 0 : stepX,
|
|
201
|
+
movedY: blockedY ? 0 : stepY,
|
|
202
|
+
blockedX,
|
|
203
|
+
blockedY,
|
|
204
|
+
};
|
|
205
|
+
}
|