@auraindustry/aurajs 0.1.3 → 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.
Files changed (108) hide show
  1. package/README.md +7 -0
  2. package/benchmarks/perf-thresholds.json +27 -0
  3. package/package.json +6 -1
  4. package/src/ai-guidance.mjs +302 -0
  5. package/src/authored-project.mjs +498 -2
  6. package/src/build-contract/capabilities.mjs +87 -1
  7. package/src/build-contract/constants.mjs +1 -0
  8. package/src/build-contract.mjs +2 -0
  9. package/src/bundler.mjs +143 -13
  10. package/src/cli.mjs +681 -13
  11. package/src/commands/packs.mjs +741 -0
  12. package/src/commands/project-authoring.mjs +128 -1
  13. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
  14. package/src/conformance/cases/core-runtime-cases.mjs +6 -2
  15. package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
  16. package/src/conformance/cases/systems-and-gameplay-cases.mjs +265 -4
  17. package/src/conformance-mobile.mjs +166 -0
  18. package/src/conformance.mjs +89 -30
  19. package/src/evidence-bundle.mjs +242 -0
  20. package/src/headless-test/runtime-coordinator.mjs +186 -33
  21. package/src/headless-test.mjs +2 -0
  22. package/src/helpers/2d/index.mjs +183 -0
  23. package/src/helpers/index.mjs +26 -0
  24. package/src/helpers/starter-utils/adventure-objectives.js +102 -0
  25. package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
  26. package/src/helpers/starter-utils/animation-2d.js +337 -0
  27. package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
  28. package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
  29. package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
  30. package/src/helpers/starter-utils/avatar-3d.js +404 -0
  31. package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
  32. package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
  33. package/src/helpers/starter-utils/core.js +150 -0
  34. package/src/helpers/starter-utils/dialogue-2d.js +351 -0
  35. package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
  36. package/src/helpers/starter-utils/index.js +26 -0
  37. package/src/helpers/starter-utils/inventory-2d.js +268 -0
  38. package/src/helpers/starter-utils/journal-2d.js +267 -0
  39. package/src/helpers/starter-utils/platformer-3d.js +132 -0
  40. package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
  41. package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
  42. package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
  43. package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
  44. package/src/helpers/starter-utils/triggers.js +662 -0
  45. package/src/helpers/starter-utils/tween-2d.js +615 -0
  46. package/src/helpers/starter-utils/wave-director.js +101 -0
  47. package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
  48. package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
  49. package/src/mobile/android/build.mjs +606 -0
  50. package/src/mobile/android/host-artifact.mjs +280 -0
  51. package/src/mobile/ios/build.mjs +1323 -0
  52. package/src/mobile/ios/host-artifact.mjs +819 -0
  53. package/src/mobile/shared/capabilities.mjs +174 -0
  54. package/src/packs/catalog.mjs +259 -0
  55. package/src/perf-benchmark-runner.mjs +17 -12
  56. package/src/perf-benchmark.mjs +408 -4
  57. package/src/publish-command.mjs +303 -6
  58. package/src/replay-runtime.mjs +257 -0
  59. package/src/scaffold/config.mjs +2 -0
  60. package/src/scaffold/fs.mjs +8 -1
  61. package/src/scaffold/project-docs.mjs +43 -1
  62. package/src/scaffold.mjs +4 -0
  63. package/src/session-runtime.mjs +4 -3
  64. package/src/web-conformance.mjs +0 -36
  65. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
  66. package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
  67. package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
  68. package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
  69. package/templates/create/2d-adventure/docs/design/loop.md +4 -3
  70. package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
  71. package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
  72. package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
  73. package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
  74. package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
  75. package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
  76. package/templates/create/3d/scenes/gameplay.scene.js +30 -3
  77. package/templates/create/3d/src/runtime/capabilities.js +5 -0
  78. package/templates/create/3d/src/runtime/materials.js +10 -0
  79. package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
  80. package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
  81. package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
  82. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
  83. package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
  84. package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
  85. package/templates/create/shared/src/runtime/ui-forms.js +552 -0
  86. package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
  87. package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
  88. package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
  89. package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
  90. package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
  91. package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
  92. package/templates/create/shared/src/starter-utils/index.js +15 -1
  93. package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
  94. package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
  95. package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
  96. package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
  97. package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
  98. package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
  99. package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
  100. package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
  101. package/templates/create-bin/play.js +36 -7
  102. package/templates/skills/auramaxx/SKILL.md +46 -0
  103. package/templates/skills/auramaxx/project-requirements.md +68 -0
  104. package/templates/skills/auramaxx/starter-recipes.md +104 -0
  105. package/templates/skills/auramaxx/validation-checklist.md +49 -0
  106. package/templates/skills/aurajs/SKILL.md +0 -96
  107. package/templates/skills/aurajs/api-contract-3d.md +0 -7
  108. package/templates/skills/aurajs/api-contract.md +0 -7
@@ -0,0 +1,150 @@
1
+ export function clamp(value, min, max) {
2
+ if (!Number.isFinite(value)) return min;
3
+ if (value < min) return min;
4
+ if (value > max) return max;
5
+ return value;
6
+ }
7
+
8
+ export function axisFromKeys(input, negativeKeys, positiveKeys) {
9
+ const positive = positiveKeys.some((key) => input.isKeyDown(key)) ? 1 : 0;
10
+ const negative = negativeKeys.some((key) => input.isKeyDown(key)) ? 1 : 0;
11
+ return positive - negative;
12
+ }
13
+
14
+ export function createCooldown(durationSeconds) {
15
+ return {
16
+ duration: Math.max(0, Number(durationSeconds) || 0),
17
+ remaining: 0,
18
+ };
19
+ }
20
+
21
+ export function tickCooldown(cooldown, dt) {
22
+ cooldown.remaining = Math.max(0, cooldown.remaining - Math.max(0, Number(dt) || 0));
23
+ }
24
+
25
+ export function consumeCooldown(cooldown) {
26
+ if (cooldown.remaining > 0) return false;
27
+ cooldown.remaining = cooldown.duration;
28
+ return true;
29
+ }
30
+
31
+ export function createIntervalSpawner(intervalSeconds, options = {}) {
32
+ const interval = Math.max(0.016, Number(intervalSeconds) || 0.5);
33
+ const startReady = options.startReady === true;
34
+ return {
35
+ interval,
36
+ remaining: startReady ? 0 : interval,
37
+ };
38
+ }
39
+
40
+ export function tickIntervalSpawner(spawner, dt, onSpawn = null) {
41
+ spawner.remaining -= Math.max(0, Number(dt) || 0);
42
+ let spawned = 0;
43
+
44
+ while (spawner.remaining <= 0) {
45
+ spawned += 1;
46
+ spawner.remaining += spawner.interval;
47
+ if (typeof onSpawn === 'function') {
48
+ onSpawn(spawned);
49
+ }
50
+ }
51
+
52
+ return spawned;
53
+ }
54
+
55
+ export function centeredRect(cx, cy, w, h) {
56
+ return {
57
+ x: cx - (w * 0.5),
58
+ y: cy - (h * 0.5),
59
+ w,
60
+ h,
61
+ };
62
+ }
63
+
64
+ export function rectsOverlap(a, b) {
65
+ return (
66
+ a.x < b.x + b.w &&
67
+ a.x + a.w > b.x &&
68
+ a.y < b.y + b.h &&
69
+ a.y + a.h > b.y
70
+ );
71
+ }
72
+
73
+ function resolvePosition2D(value) {
74
+ if (!value || typeof value !== 'object') {
75
+ return { x: 0, y: 0 };
76
+ }
77
+ if (typeof value.x === 'number' && typeof value.y === 'number') {
78
+ return value;
79
+ }
80
+ if (value.position && typeof value.position === 'object') {
81
+ return resolvePosition2D(value.position);
82
+ }
83
+ return { x: 0, y: 0 };
84
+ }
85
+
86
+ export function distanceSquared2D(a, b) {
87
+ const left = resolvePosition2D(a);
88
+ const right = resolvePosition2D(b);
89
+ const dx = left.x - right.x;
90
+ const dy = left.y - right.y;
91
+ return (dx * dx) + (dy * dy);
92
+ }
93
+
94
+ export function removeWhere(items, predicate) {
95
+ let removed = 0;
96
+ for (let i = items.length - 1; i >= 0; i -= 1) {
97
+ if (!predicate(items[i], i)) continue;
98
+ items.splice(i, 1);
99
+ removed += 1;
100
+ }
101
+ return removed;
102
+ }
103
+
104
+ function resolvePosition3D(value) {
105
+ if (!value || typeof value !== 'object') {
106
+ return { x: 0, y: 0, z: 0 };
107
+ }
108
+ if (typeof value.x === 'number' && typeof value.y === 'number' && typeof value.z === 'number') {
109
+ return value;
110
+ }
111
+ if (value.position && typeof value.position === 'object') {
112
+ return resolvePosition3D(value.position);
113
+ }
114
+ return { x: 0, y: 0, z: 0 };
115
+ }
116
+
117
+ export function distanceSquared3D(a, b) {
118
+ const left = resolvePosition3D(a);
119
+ const right = resolvePosition3D(b);
120
+ const dx = left.x - right.x;
121
+ const dy = left.y - right.y;
122
+ const dz = left.z - right.z;
123
+ return (dx * dx) + (dy * dy) + (dz * dz);
124
+ }
125
+
126
+ export function tryJump(body, jumpSpeed) {
127
+ if (!body.onGround) return false;
128
+ body.vy = Number(jumpSpeed) || 0;
129
+ body.onGround = false;
130
+ return true;
131
+ }
132
+
133
+ export function stepVerticalMotion(body, dt, gravity, supportY) {
134
+ const frameDt = Math.max(0, Number(dt) || 0);
135
+ const grav = Math.max(0, Number(gravity) || 0);
136
+ const floorY = Number(supportY) || 0;
137
+
138
+ body.vy -= grav * frameDt;
139
+ body.y += body.vy * frameDt;
140
+
141
+ if (body.y <= floorY) {
142
+ body.y = floorY;
143
+ body.vy = 0;
144
+ body.onGround = true;
145
+ } else {
146
+ body.onGround = false;
147
+ }
148
+
149
+ return body.onGround;
150
+ }
@@ -0,0 +1,351 @@
1
+ function normalizeId(value, fallback = null) {
2
+ if (typeof value === 'string' && value.trim().length > 0) {
3
+ return value.trim();
4
+ }
5
+ if (typeof value === 'number' && Number.isFinite(value)) {
6
+ return String(value);
7
+ }
8
+ return fallback;
9
+ }
10
+
11
+ function normalizeText(value, fallback = null) {
12
+ if (typeof value === 'string' && value.trim().length > 0) {
13
+ return value.trim();
14
+ }
15
+ return fallback;
16
+ }
17
+
18
+ function normalizeLineEntry(entry, index, fallbackSpeaker = null) {
19
+ const raw = typeof entry === 'string'
20
+ ? { text: entry }
21
+ : (entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null);
22
+ const text = normalizeText(raw?.text ?? raw?.line ?? raw?.label, null);
23
+ if (!text) return null;
24
+ return {
25
+ id: normalizeId(raw?.id, `line-${index + 1}`),
26
+ text,
27
+ speaker: normalizeText(raw?.speaker, fallbackSpeaker),
28
+ cue: normalizeText(raw?.cue),
29
+ };
30
+ }
31
+
32
+ function normalizeChoiceEntry(entry, index) {
33
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
34
+ const label = normalizeText(raw?.label ?? raw?.text ?? raw?.title, null);
35
+ if (!label) return null;
36
+ return {
37
+ id: normalizeId(raw?.id, `choice-${index + 1}`),
38
+ label,
39
+ detail: normalizeText(raw?.detail ?? raw?.description),
40
+ nextNodeId: normalizeId(raw?.nextNodeId ?? raw?.next ?? raw?.target),
41
+ outcomeId: normalizeId(raw?.outcomeId ?? raw?.outcome ?? raw?.result),
42
+ disabled: raw?.disabled === true,
43
+ };
44
+ }
45
+
46
+ function normalizeNodeEntry(entry, index, fallbackSpeaker = null) {
47
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
48
+ const id = normalizeId(raw?.id, `node-${index + 1}`);
49
+ const speaker = normalizeText(raw?.speaker, fallbackSpeaker);
50
+ const rawLines = Array.isArray(raw?.lines)
51
+ ? raw.lines
52
+ : [raw?.line ?? raw?.text].filter((value) => value != null);
53
+ const lines = rawLines
54
+ .map((lineEntry, lineIndex) => normalizeLineEntry(lineEntry, lineIndex, speaker))
55
+ .filter(Boolean);
56
+ const choices = (Array.isArray(raw?.choices) ? raw.choices : [])
57
+ .map((choiceEntry, choiceIndex) => normalizeChoiceEntry(choiceEntry, choiceIndex))
58
+ .filter(Boolean);
59
+ return {
60
+ id,
61
+ label: normalizeText(raw?.label ?? raw?.title, id),
62
+ speaker,
63
+ lines,
64
+ choices,
65
+ nextNodeId: normalizeId(raw?.nextNodeId ?? raw?.next ?? raw?.target),
66
+ outcomeId: normalizeId(raw?.outcomeId ?? raw?.outcome ?? raw?.result),
67
+ };
68
+ }
69
+
70
+ function normalizeConversationEntry(entry, index = 0) {
71
+ const raw = entry && typeof entry === 'object' && !Array.isArray(entry) ? entry : null;
72
+ const id = normalizeId(raw?.id, `conversation-${index + 1}`);
73
+ const speaker = normalizeText(raw?.speaker);
74
+ const nodes = (Array.isArray(raw?.nodes) ? raw.nodes : [])
75
+ .map((nodeEntry, nodeIndex) => normalizeNodeEntry(nodeEntry, nodeIndex, speaker))
76
+ .filter(Boolean);
77
+ const nodesById = Object.fromEntries(nodes.map((node) => [node.id, node]));
78
+ const startNodeId = normalizeId(raw?.startNodeId ?? raw?.start, nodes[0]?.id || null);
79
+ return {
80
+ id,
81
+ title: normalizeText(raw?.title ?? raw?.label, id),
82
+ speaker,
83
+ startNodeId: Object.prototype.hasOwnProperty.call(nodesById, startNodeId) ? startNodeId : (nodes[0]?.id || null),
84
+ nodes,
85
+ nodesById,
86
+ };
87
+ }
88
+
89
+ function touchDialogueState(state) {
90
+ if (!state || typeof state !== 'object') return;
91
+ state.revision = Number(state.revision || 0) + 1;
92
+ }
93
+
94
+ function getConversationStore(state) {
95
+ return state && typeof state === 'object' && state.conversations && typeof state.conversations === 'object'
96
+ ? state.conversations
97
+ : null;
98
+ }
99
+
100
+ function getActiveConversation(state) {
101
+ const store = getConversationStore(state);
102
+ const conversationId = normalizeId(state?.activeConversationId);
103
+ if (!store || !conversationId) return null;
104
+ return store[conversationId] || null;
105
+ }
106
+
107
+ function getActiveNode(state, conversation = getActiveConversation(state)) {
108
+ const nodeId = normalizeId(state?.activeNodeId);
109
+ if (!conversation || !nodeId) return null;
110
+ return conversation.nodesById[nodeId] || null;
111
+ }
112
+
113
+ function getNodeViewLineIndex(node, state) {
114
+ if (!node || !Array.isArray(node.lines) || node.lines.length <= 0) return -1;
115
+ const rawIndex = Number(state?.activeLineIndex || 0);
116
+ const safeIndex = Number.isFinite(rawIndex) ? Math.max(0, Math.floor(rawIndex)) : 0;
117
+ return Math.min(safeIndex, node.lines.length - 1);
118
+ }
119
+
120
+ function choicesVisible(node, state) {
121
+ if (!node || !Array.isArray(node.choices) || node.choices.length <= 0) return false;
122
+ if (!Array.isArray(node.lines) || node.lines.length <= 0) return true;
123
+ return getNodeViewLineIndex(node, state) >= node.lines.length - 1;
124
+ }
125
+
126
+ function resetActiveDialogueState(state) {
127
+ state.activeConversationId = null;
128
+ state.activeNodeId = null;
129
+ state.activeLineIndex = 0;
130
+ state.lastChoiceId = null;
131
+ state.visitedNodeIds = [];
132
+ }
133
+
134
+ function transitionToNode(state, conversation, nextNodeId) {
135
+ const targetId = normalizeId(nextNodeId);
136
+ if (!targetId || !conversation?.nodesById?.[targetId]) return false;
137
+ state.activeConversationId = conversation.id;
138
+ state.activeNodeId = targetId;
139
+ state.activeLineIndex = 0;
140
+ state.visitedNodeIds.push(targetId);
141
+ return true;
142
+ }
143
+
144
+ function cloneCompletion(completion) {
145
+ if (!completion || typeof completion !== 'object') return null;
146
+ return {
147
+ conversationId: normalizeId(completion.conversationId),
148
+ outcomeId: normalizeId(completion.outcomeId),
149
+ nodeId: normalizeId(completion.nodeId),
150
+ lineIndex: Number.isFinite(completion.lineIndex) ? completion.lineIndex : -1,
151
+ choiceId: normalizeId(completion.choiceId),
152
+ reason: normalizeId(completion.reason),
153
+ visitedNodeIds: Array.isArray(completion.visitedNodeIds) ? [...completion.visitedNodeIds] : [],
154
+ revision: Number(completion.revision || 0),
155
+ };
156
+ }
157
+
158
+ function registerConversationInternal(state, conversation, { touch = true } = {}) {
159
+ const store = getConversationStore(state);
160
+ if (!store) return null;
161
+ const normalized = normalizeConversationEntry(conversation, state.conversationOrder.length);
162
+ store[normalized.id] = normalized;
163
+ if (!Array.isArray(state.conversationOrder)) {
164
+ state.conversationOrder = [];
165
+ }
166
+ if (!state.conversationOrder.includes(normalized.id)) {
167
+ state.conversationOrder.push(normalized.id);
168
+ }
169
+ if (touch) touchDialogueState(state);
170
+ return normalized;
171
+ }
172
+
173
+ export function createDialogueState2D(options = {}) {
174
+ const state = {
175
+ conversations: {},
176
+ conversationOrder: [],
177
+ activeConversationId: null,
178
+ activeNodeId: null,
179
+ activeLineIndex: 0,
180
+ lastChoiceId: null,
181
+ lastCompletion: null,
182
+ visitedNodeIds: [],
183
+ revision: 0,
184
+ };
185
+
186
+ for (const conversation of (Array.isArray(options.conversations) ? options.conversations : [])) {
187
+ registerConversationInternal(state, conversation, { touch: false });
188
+ }
189
+
190
+ if (options.startConversationId) {
191
+ startDialogueConversation2D(state, options.startConversationId);
192
+ }
193
+
194
+ return state;
195
+ }
196
+
197
+ export function registerDialogueConversation2D(state, conversation) {
198
+ return registerConversationInternal(state, conversation, { touch: true });
199
+ }
200
+
201
+ export function startDialogueConversation2D(state, conversationOrId, options = {}) {
202
+ const conversation = typeof conversationOrId === 'string'
203
+ ? getConversationStore(state)?.[normalizeId(conversationOrId)] || null
204
+ : registerConversationInternal(state, conversationOrId, { touch: true });
205
+ if (!conversation) return getDialogueView2D(state);
206
+
207
+ const startNodeId = normalizeId(options.startNodeId, conversation.startNodeId);
208
+ if (!startNodeId || !conversation.nodesById[startNodeId]) return getDialogueView2D(state);
209
+
210
+ state.activeConversationId = conversation.id;
211
+ state.activeNodeId = startNodeId;
212
+ state.activeLineIndex = 0;
213
+ state.lastChoiceId = null;
214
+ state.visitedNodeIds = [startNodeId];
215
+ touchDialogueState(state);
216
+ return getDialogueView2D(state);
217
+ }
218
+
219
+ export function completeDialogueConversation2D(state, outcomeId = 'completed', options = {}) {
220
+ const conversation = getActiveConversation(state);
221
+ const node = getActiveNode(state, conversation);
222
+ if (!conversation) return cloneCompletion(state?.lastCompletion);
223
+
224
+ const completion = {
225
+ conversationId: conversation.id,
226
+ outcomeId: normalizeId(outcomeId, 'completed'),
227
+ nodeId: normalizeId(options.nodeId, node?.id || null),
228
+ lineIndex: getNodeViewLineIndex(node, state),
229
+ choiceId: normalizeId(options.choiceId, state?.lastChoiceId || null),
230
+ reason: normalizeId(options.reason, 'completed'),
231
+ visitedNodeIds: Array.isArray(state?.visitedNodeIds) ? [...state.visitedNodeIds] : [],
232
+ revision: 0,
233
+ };
234
+
235
+ resetActiveDialogueState(state);
236
+ touchDialogueState(state);
237
+ completion.revision = Number(state.revision || 0);
238
+ state.lastCompletion = completion;
239
+ return cloneCompletion(completion);
240
+ }
241
+
242
+ export function advanceDialogueConversation2D(state) {
243
+ const conversation = getActiveConversation(state);
244
+ const node = getActiveNode(state, conversation);
245
+ if (!conversation || !node) return getDialogueView2D(state);
246
+
247
+ if (choicesVisible(node, state)) {
248
+ return getDialogueView2D(state);
249
+ }
250
+
251
+ const lineIndex = getNodeViewLineIndex(node, state);
252
+ if (lineIndex >= 0 && lineIndex < node.lines.length - 1) {
253
+ state.activeLineIndex = lineIndex + 1;
254
+ touchDialogueState(state);
255
+ return getDialogueView2D(state);
256
+ }
257
+
258
+ if (node.nextNodeId) {
259
+ if (transitionToNode(state, conversation, node.nextNodeId)) {
260
+ touchDialogueState(state);
261
+ return getDialogueView2D(state);
262
+ }
263
+ completeDialogueConversation2D(state, node.outcomeId || 'completed', {
264
+ reason: 'missing_next_node',
265
+ });
266
+ return getDialogueView2D(state);
267
+ }
268
+
269
+ completeDialogueConversation2D(state, node.outcomeId || 'completed', {
270
+ reason: 'node_complete',
271
+ });
272
+ return getDialogueView2D(state);
273
+ }
274
+
275
+ export function chooseDialogueChoice2D(state, choiceId) {
276
+ const conversation = getActiveConversation(state);
277
+ const node = getActiveNode(state, conversation);
278
+ if (!conversation || !node || !choicesVisible(node, state)) {
279
+ return getDialogueView2D(state);
280
+ }
281
+
282
+ const targetId = normalizeId(choiceId);
283
+ const choice = node.choices.find((entry) => entry.id === targetId && entry.disabled !== true) || null;
284
+ if (!choice) return getDialogueView2D(state);
285
+
286
+ state.lastChoiceId = choice.id;
287
+ if (choice.nextNodeId) {
288
+ if (transitionToNode(state, conversation, choice.nextNodeId)) {
289
+ touchDialogueState(state);
290
+ return getDialogueView2D(state);
291
+ }
292
+ completeDialogueConversation2D(state, choice.outcomeId || node.outcomeId || 'completed', {
293
+ choiceId: choice.id,
294
+ reason: 'missing_next_node',
295
+ });
296
+ return getDialogueView2D(state);
297
+ }
298
+
299
+ completeDialogueConversation2D(state, choice.outcomeId || node.outcomeId || 'completed', {
300
+ choiceId: choice.id,
301
+ reason: 'choice_selected',
302
+ });
303
+ return getDialogueView2D(state);
304
+ }
305
+
306
+ export function getDialogueView2D(state) {
307
+ const conversation = getActiveConversation(state);
308
+ const node = getActiveNode(state, conversation);
309
+ const lineIndex = getNodeViewLineIndex(node, state);
310
+ const line = lineIndex >= 0 ? node.lines[lineIndex] || null : null;
311
+ const awaitingChoice = choicesVisible(node, state);
312
+ const choices = awaitingChoice
313
+ ? node.choices.map((entry) => ({
314
+ id: entry.id,
315
+ label: entry.label,
316
+ detail: entry.detail,
317
+ nextNodeId: entry.nextNodeId,
318
+ outcomeId: entry.outcomeId,
319
+ disabled: entry.disabled === true,
320
+ }))
321
+ : [];
322
+ const canAdvance = Boolean(node) && awaitingChoice !== true && (
323
+ (lineIndex >= 0 && lineIndex < node.lines.length - 1)
324
+ || Boolean(node?.nextNodeId)
325
+ || (Boolean(node) && !node?.nextNodeId && choices.length === 0)
326
+ );
327
+
328
+ return {
329
+ active: Boolean(conversation && node),
330
+ conversationId: conversation?.id || null,
331
+ title: conversation?.title || null,
332
+ nodeId: node?.id || null,
333
+ nodeLabel: node?.label || null,
334
+ lineIndex,
335
+ lineCount: Array.isArray(node?.lines) ? node.lines.length : 0,
336
+ line: line ? {
337
+ id: line.id,
338
+ text: line.text,
339
+ speaker: line.speaker,
340
+ cue: line.cue,
341
+ index: lineIndex,
342
+ } : null,
343
+ choices,
344
+ awaitingChoice,
345
+ canAdvance,
346
+ lastChoiceId: normalizeId(state?.lastChoiceId),
347
+ visitedNodeIds: Array.isArray(state?.visitedNodeIds) ? [...state.visitedNodeIds] : [],
348
+ lastCompletion: cloneCompletion(state?.lastCompletion),
349
+ revision: Number(state?.revision || 0),
350
+ };
351
+ }
@@ -0,0 +1,68 @@
1
+ const ENEMY_ARCHETYPES_2D = Object.freeze({
2
+ scout: Object.freeze({
3
+ id: 'scout',
4
+ size: 24,
5
+ speedMin: 180,
6
+ speedMax: 260,
7
+ scoreValue: 8,
8
+ color: { r: 1.0, g: 0.44, b: 0.44 },
9
+ }),
10
+ striker: Object.freeze({
11
+ id: 'striker',
12
+ size: 30,
13
+ speedMin: 120,
14
+ speedMax: 210,
15
+ scoreValue: 12,
16
+ color: { r: 1.0, g: 0.58, b: 0.32 },
17
+ }),
18
+ tank: Object.freeze({
19
+ id: 'tank',
20
+ size: 38,
21
+ speedMin: 90,
22
+ speedMax: 150,
23
+ scoreValue: 20,
24
+ color: { r: 0.96, g: 0.34, b: 0.76 },
25
+ }),
26
+ });
27
+
28
+ export function listEnemyArchetypeIds2D() {
29
+ return Object.keys(ENEMY_ARCHETYPES_2D);
30
+ }
31
+
32
+ export function getEnemyArchetype2D(id) {
33
+ return ENEMY_ARCHETYPES_2D[id] || ENEMY_ARCHETYPES_2D.striker;
34
+ }
35
+
36
+ export function pickEnemyArchetypeForWave2D(waveIndex) {
37
+ const ids = listEnemyArchetypeIds2D();
38
+ if (ids.length === 0) return ENEMY_ARCHETYPES_2D.striker;
39
+ const normalized = Math.max(0, Math.trunc(Number(waveIndex) || 0));
40
+ const id = ids[normalized % ids.length];
41
+ return getEnemyArchetype2D(id);
42
+ }
43
+
44
+ export function spawnEnemy2D(options = {}) {
45
+ const {
46
+ x = 0,
47
+ y = 0,
48
+ waveIndex = 0,
49
+ archetypeId = null,
50
+ random = Math.random,
51
+ } = options;
52
+ const archetype = archetypeId
53
+ ? getEnemyArchetype2D(archetypeId)
54
+ : pickEnemyArchetypeForWave2D(waveIndex);
55
+
56
+ const randomValue = Math.max(0, Math.min(1, Number(random()) || 0));
57
+ const speed = archetype.speedMin + (randomValue * (archetype.speedMax - archetype.speedMin));
58
+
59
+ return {
60
+ x,
61
+ y,
62
+ speed,
63
+ size: archetype.size,
64
+ scoreValue: archetype.scoreValue,
65
+ color: archetype.color,
66
+ archetype: archetype.id,
67
+ };
68
+ }
@@ -0,0 +1,26 @@
1
+ import { clamp, axisFromKeys, createCooldown, tickCooldown, consumeCooldown, createIntervalSpawner, tickIntervalSpawner, centeredRect, rectsOverlap, removeWhere, distanceSquared2D, distanceSquared3D, tryJump, stepVerticalMotion } from './core.js';
2
+ import { createSpriteAnimationLibrary2D, hasSpriteAnimationState2D, createSpriteAnimationRuntime2D, registerSpriteAnimationLibrary2D, createSpriteAnimator2D, playSpriteAnimator2D, syncSpriteAnimatorState2D, stepSpriteAnimationRuntime2D, getSpriteAnimatorState2D, resolveSpriteAnimatorFrame2D, drawSpriteAnimator2D, resolveSpriteFrameRect2D } from './animation-2d.js';
3
+ import { createAnimationPackage2D, hasAnimationPackageState2D, registerAnimationPackage2D, createPackagedSpriteAnimator2D, getAnimationPackageLibrary2D, getAnimationPackageState2D } from './animation-packaging-2d.js';
4
+ import { createAtlasAssetManifest2D, createAnimationPackageFromAtlasAsset2D } from './atlas-assets-2d.js';
5
+ import { createSceneAudio2D, configureSceneAudio2D, ensureSceneMusic2D, setSceneMusicVolume2D, duckSceneMusic2D, playSceneStinger2D, pauseSceneAudio2D, resumeSceneAudio2D, syncSceneAudio2D, stopSceneAudio2D } from './scene-audio-2d.js';
6
+ import { propertyTween2D, delayTween2D, sequenceTweens2D, parallelTweens2D, repeatTween2D, yoyoTween2D, moveTo2D, driftTo2D, fadeTo2D, scaleTo2D, rotateTo2D, pulse2D, punch2D, settleTo2D, createTweenComposer2D, playTweenPlan2D, cancelTweenPlan2D, stepTweenComposer2D, getTweenHandleState2D, isTweenHandleActive2D } from './tween-2d.js';
7
+ import { createFloatingTextLayer2D, spawnFloatingText2D, stepFloatingTextLayer2D, getFloatingTextRenderState2D, drawFloatingTextLayer2D, createHitFlash2D, triggerHitFlash2D, stepHitFlash2D, isHitFlashVisible2D, createHitSparkLayer2D, spawnHitSpark2D, stepHitSparkLayer2D, drawHitSparkLayer2D, createHoverLift2D, setHoverLiftActive2D, stepHoverLift2D, getHoverLiftOffset2D } from './combat-feedback-2d.js';
8
+ import { applyShieldedDamage2D, stepShieldRecharge2D, restoreShieldedHealth2D, createWeightedDropTable2D, rollWeightedDrop2D, createEncounterPressure2D, recordEncounterKill2D, stepEncounterPressure2D, advanceBossPhase2D } from './combat-runtime-2d.js';
9
+ import { createRectTrigger2D, createProximityTrigger3D, createVolumeTrigger3D, createTriggerTracker, getTriggerTrackerSnapshot, stepRectTriggers2D, stepVolumeTriggers3D, stepTriggerTracker, resolveTriggerInteraction, createInteractionPromptState, updateInteractionPromptState } from './triggers.js';
10
+ import { createWaveDirector, activeWave, stepWaveDirector } from './wave-director.js';
11
+ import { listEnemyArchetypeIds2D, getEnemyArchetype2D, pickEnemyArchetypeForWave2D, spawnEnemy2D } from './enemy-archetypes-2d.js';
12
+ import { createAutoplayController2D, setAutoplayEnabled2D, toggleAutoplayEnabled2D, resolveAutoplayControl2D, createRenderTargetExportState2D, queueRenderTargetExport2D, flushRenderTargetExport2D } from './autoplay-debug-2d.js';
13
+ import { createInventory2D, addInventoryItem2D, removeInventoryItem2D, hasInventoryItem2D, listInventoryItems2D, selectInventoryCategory2D, selectInventoryItem2D, getSelectedInventoryItem2D, setInventoryItemEquipped2D, getInventoryView2D } from './inventory-2d.js';
14
+ import { createDialogueState2D, registerDialogueConversation2D, startDialogueConversation2D, advanceDialogueConversation2D, chooseDialogueChoice2D, completeDialogueConversation2D, getDialogueView2D } from './dialogue-2d.js';
15
+ import { createJournal2D, upsertJournalEntry2D, setJournalEntryStatus2D, listJournalEntries2D, selectJournalCategory2D, selectJournalEntry2D, getSelectedJournalEntry2D, getJournalView2D } from './journal-2d.js';
16
+ import { createTilemapWorld2D, ensureTilemapWorldLoaded2D, unloadTilemapWorld2D, drawTilemapWorld2D, listTilemapWorldObjects2D, findTilemapWorldObject2D, resolveTilemapSpawn2D, queryTilemapWorldPoint2D, queryTilemapWorldAABB2D, isTilemapPointBlocked2D, moveActorThroughTilemapWorld2D } from './tilemap-world-2d.js';
17
+ import { createStreamedWorld2D, listLoadedStreamedWorldRegions2D, findStreamedWorldRegionAtPoint2D, syncStreamedWorldFocus2D, queryStreamedWorldPoint2D, queryStreamedWorldAABB2D, queryStreamedWorldRay2D, unloadStreamedWorld2D } from './streamed-world-2d.js';
18
+ import { captureStreamedWorldSnapshot2D, applyStreamedWorldSnapshot2D, saveStreamedWorldSnapshot2D, loadStreamedWorldSnapshot2D, deleteStreamedWorldSnapshot2D } from './world-persistence-2d.js';
19
+ import { createTilemapNav2D, rebuildTilemapNav2D, invalidateTilemapNav2D, collectTilemapOccupiedCells2D, setTilemapNavOccupiedCells2D, findTilemapPath2D, resolveTilemapStep2D } from './tilemap-nav-2d.js';
20
+ import { createWorldCompositor2D, ensureWorldCompositorTargets2D, destroyWorldCompositor2D, runWorldCompositor2D } from './world-compositor-2d.js';
21
+ import { createAdventureInteractable2D, createAdventureInteractablesFromTilemapObjects2D, createAdventureInteractionState2D, stepAdventureInteractables2D } from './adventure-world-2d.js';
22
+ import { createCheckpointSystem, updateCheckpointProgress, activeCheckpoint, respawnAtCheckpoint, createGoalZone, isGoalReached, createMovingPlatform3D, stepMovingPlatform3D } from './platformer-3d.js';
23
+ import { createCharacterAvatar3D, setAvatarInput3D, setAvatarVisual3D, spawnCharacterAvatar3D, tickCharacterAvatar3D, getAvatarState3D, getAvatarRenderTransform3D } from './avatar-3d.js';
24
+ import { createAdventureObjectives, resetAdventureObjectives, countCompletedObjectives, objectivesComplete, firstPendingObjective, collectAdventureObjectives2D, collectAdventureObjectives3D } from './adventure-objectives.js';
25
+
26
+ export { clamp, axisFromKeys, createCooldown, tickCooldown, consumeCooldown, createIntervalSpawner, tickIntervalSpawner, centeredRect, rectsOverlap, removeWhere, distanceSquared2D, distanceSquared3D, tryJump, stepVerticalMotion, createSpriteAnimationLibrary2D, hasSpriteAnimationState2D, createSpriteAnimationRuntime2D, registerSpriteAnimationLibrary2D, createSpriteAnimator2D, playSpriteAnimator2D, syncSpriteAnimatorState2D, stepSpriteAnimationRuntime2D, getSpriteAnimatorState2D, resolveSpriteAnimatorFrame2D, drawSpriteAnimator2D, resolveSpriteFrameRect2D, createAnimationPackage2D, hasAnimationPackageState2D, registerAnimationPackage2D, createPackagedSpriteAnimator2D, getAnimationPackageLibrary2D, getAnimationPackageState2D, createAtlasAssetManifest2D, createAnimationPackageFromAtlasAsset2D, createSceneAudio2D, configureSceneAudio2D, ensureSceneMusic2D, setSceneMusicVolume2D, duckSceneMusic2D, playSceneStinger2D, pauseSceneAudio2D, resumeSceneAudio2D, syncSceneAudio2D, stopSceneAudio2D, propertyTween2D, delayTween2D, sequenceTweens2D, parallelTweens2D, repeatTween2D, yoyoTween2D, moveTo2D, driftTo2D, fadeTo2D, scaleTo2D, rotateTo2D, pulse2D, punch2D, settleTo2D, createTweenComposer2D, playTweenPlan2D, cancelTweenPlan2D, stepTweenComposer2D, getTweenHandleState2D, isTweenHandleActive2D, createFloatingTextLayer2D, spawnFloatingText2D, stepFloatingTextLayer2D, getFloatingTextRenderState2D, drawFloatingTextLayer2D, createHitFlash2D, triggerHitFlash2D, stepHitFlash2D, isHitFlashVisible2D, createHitSparkLayer2D, spawnHitSpark2D, stepHitSparkLayer2D, drawHitSparkLayer2D, createHoverLift2D, setHoverLiftActive2D, stepHoverLift2D, getHoverLiftOffset2D, applyShieldedDamage2D, stepShieldRecharge2D, restoreShieldedHealth2D, createWeightedDropTable2D, rollWeightedDrop2D, createEncounterPressure2D, recordEncounterKill2D, stepEncounterPressure2D, advanceBossPhase2D, createRectTrigger2D, createProximityTrigger3D, createVolumeTrigger3D, createTriggerTracker, getTriggerTrackerSnapshot, stepRectTriggers2D, stepVolumeTriggers3D, stepTriggerTracker, resolveTriggerInteraction, createInteractionPromptState, updateInteractionPromptState, createWaveDirector, activeWave, stepWaveDirector, listEnemyArchetypeIds2D, getEnemyArchetype2D, pickEnemyArchetypeForWave2D, spawnEnemy2D, createAutoplayController2D, setAutoplayEnabled2D, toggleAutoplayEnabled2D, resolveAutoplayControl2D, createRenderTargetExportState2D, queueRenderTargetExport2D, flushRenderTargetExport2D, createInventory2D, addInventoryItem2D, removeInventoryItem2D, hasInventoryItem2D, listInventoryItems2D, selectInventoryCategory2D, selectInventoryItem2D, getSelectedInventoryItem2D, setInventoryItemEquipped2D, getInventoryView2D, createDialogueState2D, registerDialogueConversation2D, startDialogueConversation2D, advanceDialogueConversation2D, chooseDialogueChoice2D, completeDialogueConversation2D, getDialogueView2D, createJournal2D, upsertJournalEntry2D, setJournalEntryStatus2D, listJournalEntries2D, selectJournalCategory2D, selectJournalEntry2D, getSelectedJournalEntry2D, getJournalView2D, createTilemapWorld2D, ensureTilemapWorldLoaded2D, unloadTilemapWorld2D, drawTilemapWorld2D, listTilemapWorldObjects2D, findTilemapWorldObject2D, resolveTilemapSpawn2D, queryTilemapWorldPoint2D, queryTilemapWorldAABB2D, isTilemapPointBlocked2D, moveActorThroughTilemapWorld2D, createStreamedWorld2D, listLoadedStreamedWorldRegions2D, findStreamedWorldRegionAtPoint2D, syncStreamedWorldFocus2D, queryStreamedWorldPoint2D, queryStreamedWorldAABB2D, queryStreamedWorldRay2D, unloadStreamedWorld2D, captureStreamedWorldSnapshot2D, applyStreamedWorldSnapshot2D, saveStreamedWorldSnapshot2D, loadStreamedWorldSnapshot2D, deleteStreamedWorldSnapshot2D, createTilemapNav2D, rebuildTilemapNav2D, invalidateTilemapNav2D, collectTilemapOccupiedCells2D, setTilemapNavOccupiedCells2D, findTilemapPath2D, resolveTilemapStep2D, createWorldCompositor2D, ensureWorldCompositorTargets2D, destroyWorldCompositor2D, runWorldCompositor2D, createAdventureInteractable2D, createAdventureInteractablesFromTilemapObjects2D, createAdventureInteractionState2D, stepAdventureInteractables2D, createCheckpointSystem, updateCheckpointProgress, activeCheckpoint, respawnAtCheckpoint, createGoalZone, isGoalReached, createMovingPlatform3D, stepMovingPlatform3D, createCharacterAvatar3D, setAvatarInput3D, setAvatarVisual3D, spawnCharacterAvatar3D, tickCharacterAvatar3D, getAvatarState3D, getAvatarRenderTransform3D, createAdventureObjectives, resetAdventureObjectives, countCompletedObjectives, objectivesComplete, firstPendingObjective, collectAdventureObjectives2D, collectAdventureObjectives3D };