@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.
Files changed (144) 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/asset-pack.mjs +2 -1
  6. package/src/authored-project.mjs +498 -2
  7. package/src/authored-runtime.mjs +14 -0
  8. package/src/bin-integrity.mjs +33 -26
  9. package/src/build-contract/capabilities.mjs +87 -1
  10. package/src/build-contract/constants.mjs +1 -0
  11. package/src/build-contract.mjs +2 -0
  12. package/src/bundler.mjs +143 -13
  13. package/src/cli.mjs +681 -13
  14. package/src/commands/packs.mjs +741 -0
  15. package/src/commands/project-authoring.mjs +128 -1
  16. package/src/conformance/cases/app-and-ui-runtime-cases.mjs +1 -2
  17. package/src/conformance/cases/core-runtime-cases.mjs +6 -2
  18. package/src/conformance/cases/scene3d-and-media-cases.mjs +238 -0
  19. package/src/conformance/cases/systems-and-gameplay-cases.mjs +1126 -10
  20. package/src/conformance-mobile.mjs +166 -0
  21. package/src/conformance.mjs +89 -30
  22. package/src/evidence-bundle.mjs +242 -0
  23. package/src/external-package-surface.mjs +1 -1
  24. package/src/headless-test/runtime-coordinator.mjs +186 -33
  25. package/src/headless-test.mjs +2 -0
  26. package/src/helpers/2d/index.mjs +183 -0
  27. package/src/helpers/index.mjs +26 -0
  28. package/src/helpers/starter-utils/adventure-objectives.js +102 -0
  29. package/src/helpers/starter-utils/adventure-world-2d.js +221 -0
  30. package/src/helpers/starter-utils/animation-2d.js +337 -0
  31. package/src/helpers/starter-utils/animation-packaging-2d.js +203 -0
  32. package/src/helpers/starter-utils/atlas-assets-2d.js +111 -0
  33. package/src/helpers/starter-utils/autoplay-debug-2d.js +215 -0
  34. package/src/helpers/starter-utils/avatar-3d.js +404 -0
  35. package/src/helpers/starter-utils/combat-feedback-2d.js +320 -0
  36. package/src/helpers/starter-utils/combat-runtime-2d.js +290 -0
  37. package/src/helpers/starter-utils/core.js +150 -0
  38. package/src/helpers/starter-utils/dialogue-2d.js +351 -0
  39. package/src/helpers/starter-utils/enemy-archetypes-2d.js +68 -0
  40. package/src/helpers/starter-utils/index.js +26 -0
  41. package/src/helpers/starter-utils/inventory-2d.js +268 -0
  42. package/src/helpers/starter-utils/journal-2d.js +267 -0
  43. package/src/helpers/starter-utils/platformer-3d.js +132 -0
  44. package/src/helpers/starter-utils/scene-audio-2d.js +236 -0
  45. package/src/helpers/starter-utils/streamed-world-2d.js +378 -0
  46. package/src/helpers/starter-utils/tilemap-nav-2d.js +499 -0
  47. package/src/helpers/starter-utils/tilemap-world-2d.js +205 -0
  48. package/src/helpers/starter-utils/triggers.js +662 -0
  49. package/src/helpers/starter-utils/tween-2d.js +615 -0
  50. package/src/helpers/starter-utils/wave-director.js +101 -0
  51. package/src/helpers/starter-utils/world-compositor-2d.js +253 -0
  52. package/src/helpers/starter-utils/world-persistence-2d.js +180 -0
  53. package/src/mobile/android/build.mjs +606 -0
  54. package/src/mobile/android/host-artifact.mjs +280 -0
  55. package/src/mobile/ios/build.mjs +1323 -0
  56. package/src/mobile/ios/host-artifact.mjs +819 -0
  57. package/src/mobile/shared/capabilities.mjs +174 -0
  58. package/src/package-integrity.mjs +18 -4
  59. package/src/packs/catalog.mjs +259 -0
  60. package/src/perf-benchmark-runner.mjs +17 -12
  61. package/src/perf-benchmark.mjs +408 -4
  62. package/src/publish-command.mjs +434 -17
  63. package/src/publish-validation.mjs +22 -11
  64. package/src/replay-runtime.mjs +257 -0
  65. package/src/scaffold/config.mjs +2 -0
  66. package/src/scaffold/fs.mjs +8 -1
  67. package/src/scaffold/project-docs.mjs +101 -41
  68. package/src/scaffold.mjs +4 -0
  69. package/src/session-runtime.mjs +4 -3
  70. package/src/web-conformance.mjs +0 -36
  71. package/templates/create/2d/src/runtime/app.js +4 -0
  72. package/templates/create/2d-adventure/config/gameplay/adventure.config.js +9 -6
  73. package/templates/create/2d-adventure/content/gameplay/dialogue.js +85 -0
  74. package/templates/create/2d-adventure/content/gameplay/world.js +32 -36
  75. package/templates/create/2d-adventure/content/gameplay/world.tilemap.json +273 -0
  76. package/templates/create/2d-adventure/docs/design/loop.md +4 -3
  77. package/templates/create/2d-adventure/prefabs/relic.prefab.js +10 -10
  78. package/templates/create/2d-adventure/prefabs/world.prefab.js +127 -74
  79. package/templates/create/2d-adventure/scenes/gameplay.scene.js +603 -112
  80. package/templates/create/2d-adventure/src/runtime/capabilities.js +16 -0
  81. package/templates/create/2d-adventure/ui/hud.screen.js +187 -4
  82. package/templates/create/2d-adventure/ui/journal.screen.js +183 -0
  83. package/templates/create/2d-survivor/src/runtime/app.js +4 -0
  84. package/templates/create/3d/scenes/gameplay.scene.js +30 -3
  85. package/templates/create/3d/src/runtime/app.js +4 -0
  86. package/templates/create/3d/src/runtime/capabilities.js +5 -0
  87. package/templates/create/3d/src/runtime/materials.js +10 -0
  88. package/templates/create/3d-adventure/scenes/gameplay.scene.js +30 -3
  89. package/templates/create/3d-adventure/src/runtime/capabilities.js +5 -0
  90. package/templates/create/3d-adventure/src/runtime/materials.js +11 -0
  91. package/templates/create/3d-collectathon/scenes/gameplay.scene.js +30 -3
  92. package/templates/create/3d-collectathon/src/runtime/app.js +4 -0
  93. package/templates/create/3d-collectathon/src/runtime/capabilities.js +5 -0
  94. package/templates/create/3d-collectathon/src/runtime/materials.js +10 -0
  95. package/templates/create/blank/assets/splash/aurajs-gg-wordmark.webp +0 -0
  96. package/templates/create/blank/assets/splash/bg.webp +0 -0
  97. package/templates/create/blank/assets/splash/boot-loop.wav +0 -0
  98. package/templates/create/blank/assets/splash/boot-sting.wav +0 -0
  99. package/templates/create/blank/assets/splash/logo-mascot-sheet.webp +0 -0
  100. package/templates/create/blank/assets/splash/logoholo.webp +0 -0
  101. package/templates/create/blank/src/main.js +5 -1
  102. package/templates/create/blank/src/runtime/splash.js +305 -0
  103. package/templates/create/local-multiplayer/scenes/gameplay.scene.js +186 -12
  104. package/templates/create/local-multiplayer/src/runtime/capabilities.js +8 -1
  105. package/templates/create/shared/assets/splash/aurajs-gg-wordmark.webp +0 -0
  106. package/templates/create/shared/assets/splash/bg.webp +0 -0
  107. package/templates/create/shared/assets/splash/boot-loop.wav +0 -0
  108. package/templates/create/shared/assets/splash/boot-sting.wav +0 -0
  109. package/templates/create/shared/assets/splash/logo-mascot-sheet.webp +0 -0
  110. package/templates/create/shared/assets/splash/logoholo.webp +0 -0
  111. package/templates/create/shared/src/runtime/splash.js +305 -0
  112. package/templates/create/shared/src/runtime/ui-forms.js +552 -0
  113. package/templates/create/shared/src/starter-utils/adventure-world-2d.js +221 -0
  114. package/templates/create/shared/src/starter-utils/animation-packaging-2d.js +203 -0
  115. package/templates/create/shared/src/starter-utils/atlas-assets-2d.js +111 -0
  116. package/templates/create/shared/src/starter-utils/autoplay-debug-2d.js +215 -0
  117. package/templates/create/shared/src/starter-utils/combat-runtime-2d.js +290 -0
  118. package/templates/create/shared/src/starter-utils/dialogue-2d.js +351 -0
  119. package/templates/create/shared/src/starter-utils/index.js +15 -1
  120. package/templates/create/shared/src/starter-utils/inventory-2d.js +268 -0
  121. package/templates/create/shared/src/starter-utils/journal-2d.js +267 -0
  122. package/templates/create/shared/src/starter-utils/scene-audio-2d.js +236 -0
  123. package/templates/create/shared/src/starter-utils/streamed-world-2d.js +378 -0
  124. package/templates/create/shared/src/starter-utils/tilemap-nav-2d.js +499 -0
  125. package/templates/create/shared/src/starter-utils/tilemap-world-2d.js +205 -0
  126. package/templates/create/shared/src/starter-utils/world-compositor-2d.js +253 -0
  127. package/templates/create/shared/src/starter-utils/world-persistence-2d.js +180 -0
  128. package/templates/create/video-cutscene/src/runtime/app.js +4 -0
  129. package/templates/create-bin/play.js +148 -7
  130. package/templates/skills/auramaxx/SKILL.md +46 -0
  131. package/templates/skills/auramaxx/project-requirements.md +68 -0
  132. package/templates/skills/auramaxx/starter-recipes.md +104 -0
  133. package/templates/skills/auramaxx/validation-checklist.md +49 -0
  134. package/templates/starter/assets/splash/aurajs-gg-wordmark.webp +0 -0
  135. package/templates/starter/assets/splash/bg.webp +0 -0
  136. package/templates/starter/assets/splash/boot-loop.wav +0 -0
  137. package/templates/starter/assets/splash/boot-sting.wav +0 -0
  138. package/templates/starter/assets/splash/logo-mascot-sheet.webp +0 -0
  139. package/templates/starter/assets/splash/logoholo.webp +0 -0
  140. package/templates/starter/src/main.js +4 -0
  141. package/templates/starter/src/runtime/splash.js +305 -0
  142. package/templates/skills/aurajs/SKILL.md +0 -96
  143. package/templates/skills/aurajs/api-contract-3d.md +0 -7
  144. package/templates/skills/aurajs/api-contract.md +0 -7
@@ -0,0 +1,257 @@
1
+ import { resolve } from 'node:path';
2
+
3
+ import { executeFrame } from './headless-test.mjs';
4
+ import {
5
+ applySessionState,
6
+ bootSessionRuntime,
7
+ exportSessionState,
8
+ } from './session-runtime.mjs';
9
+
10
+ export const GAME_REPLAY_SCHEMA_VERSION = 'aurajs.game-replay.v1';
11
+ export const GAME_REPLAY_REPORT_SCHEMA_VERSION = 'aurajs.game-replay-report.v1';
12
+
13
+ export class ReplayError extends Error {
14
+ constructor(reasonCode, message, details = {}) {
15
+ super(message);
16
+ this.name = 'ReplayError';
17
+ this.reasonCode = reasonCode;
18
+ this.details = details;
19
+ }
20
+ }
21
+
22
+ function parseReplayFrames(value, fallback = 1) {
23
+ if (value == null) return fallback;
24
+ const numeric = Number(value);
25
+ if (!Number.isInteger(numeric) || numeric < 1) {
26
+ throw new ReplayError('invalid_replay_frames', 'Replay step frames must be a positive integer.');
27
+ }
28
+ return numeric;
29
+ }
30
+
31
+ function normalizeReplayPayload(payload) {
32
+ if (!payload || typeof payload !== 'object' || Array.isArray(payload)) {
33
+ throw new ReplayError('invalid_replay_payload', 'Replay payload must be an object.');
34
+ }
35
+ if (payload.schemaVersion !== GAME_REPLAY_SCHEMA_VERSION) {
36
+ throw new ReplayError(
37
+ 'schema_version_mismatch',
38
+ `Unsupported replay schema "${payload.schemaVersion}". Expected "${GAME_REPLAY_SCHEMA_VERSION}".`,
39
+ );
40
+ }
41
+ if (!Array.isArray(payload.steps) || payload.steps.length === 0) {
42
+ throw new ReplayError('invalid_replay_payload', 'Replay payload requires a non-empty steps array.');
43
+ }
44
+
45
+ return {
46
+ schemaVersion: GAME_REPLAY_SCHEMA_VERSION,
47
+ seed: payload.seed == null ? null : String(payload.seed),
48
+ stopOnDivergence: payload.stopOnDivergence !== false,
49
+ initialState: payload.initialState ?? null,
50
+ steps: payload.steps.map((step, index) => {
51
+ if (!step || typeof step !== 'object' || Array.isArray(step)) {
52
+ throw new ReplayError('invalid_replay_step', `Replay step at index ${index} must be an object.`);
53
+ }
54
+ return {
55
+ label: typeof step.label === 'string' && step.label.trim().length > 0 ? step.label.trim() : null,
56
+ checkpointId: typeof step.checkpointId === 'string' && step.checkpointId.trim().length > 0 ? step.checkpointId.trim() : null,
57
+ expectFingerprint: typeof step.expectFingerprint === 'string' && step.expectFingerprint.trim().length > 0
58
+ ? step.expectFingerprint.trim()
59
+ : null,
60
+ frames: parseReplayFrames(step.frames, 1),
61
+ input: step.input && typeof step.input === 'object' && !Array.isArray(step.input)
62
+ ? step.input
63
+ : null,
64
+ };
65
+ }),
66
+ };
67
+ }
68
+
69
+ function hashReplaySeed(seed) {
70
+ const text = String(seed || '');
71
+ let hash = 2166136261;
72
+ for (let index = 0; index < text.length; index += 1) {
73
+ hash ^= text.charCodeAt(index);
74
+ hash = Math.imul(hash, 16777619);
75
+ }
76
+ return hash >>> 0;
77
+ }
78
+
79
+ function createSeededMath(seed) {
80
+ if (seed == null) return Math;
81
+ let state = hashReplaySeed(seed);
82
+ if (state === 0) state = 0x6d2b79f5;
83
+ const nextRandom = () => {
84
+ state = (state + 0x6d2b79f5) >>> 0;
85
+ let t = state;
86
+ t = Math.imul(t ^ (t >>> 15), t | 1);
87
+ t ^= t + Math.imul(t ^ (t >>> 7), t | 61);
88
+ return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
89
+ };
90
+ const math = Object.create(Math);
91
+ math.random = nextRandom;
92
+ return math;
93
+ }
94
+
95
+ function captureStateOrThrow(runtime, seed, capturedAt = null) {
96
+ const result = exportSessionState(runtime, {
97
+ mode: 'headless',
98
+ seed,
99
+ capturedAt,
100
+ });
101
+ if (!result || typeof result !== 'object' || result.ok !== true || !result.payload) {
102
+ throw new ReplayError(
103
+ typeof result?.reasonCode === 'string' ? result.reasonCode : 'replay_state_export_failed',
104
+ result?.detail
105
+ ? `Replay state export failed: ${result.detail}`
106
+ : 'Replay state export failed.',
107
+ { stateResult: result || null },
108
+ );
109
+ }
110
+ return result.payload;
111
+ }
112
+
113
+ function buildCheckpoint({ step, stepIndex, runtime, payload, matchedExpectation }) {
114
+ return {
115
+ checkpointId: step.checkpointId || null,
116
+ label: step.label || null,
117
+ stepIndex,
118
+ frameIndex: runtime.frameIndex,
119
+ elapsedSeconds: runtime.elapsedSeconds,
120
+ fingerprint: payload.export?.fingerprint || null,
121
+ matchedExpectation,
122
+ };
123
+ }
124
+
125
+ export async function runHeadlessReplay(options = {}) {
126
+ const projectRoot = resolve(options.projectRoot || process.cwd());
127
+ const replay = normalizeReplayPayload(options.payload);
128
+ const runtime = await bootSessionRuntime({
129
+ projectRoot,
130
+ file: options.file,
131
+ width: options.width,
132
+ height: options.height,
133
+ math: createSeededMath(replay.seed),
134
+ });
135
+
136
+ const inputController = runtime.aura?.input?.__headless;
137
+ if (!inputController || typeof inputController.applyFrame !== 'function' || typeof inputController.clearFrameTransitions !== 'function') {
138
+ throw new ReplayError(
139
+ 'replay_input_unavailable',
140
+ 'Headless replay requires the headless input controller.',
141
+ );
142
+ }
143
+
144
+ let initialStateApplied = false;
145
+ if (replay.initialState) {
146
+ const applyResult = applySessionState(runtime, replay.initialState);
147
+ if (!applyResult || typeof applyResult !== 'object' || applyResult.ok !== true) {
148
+ throw new ReplayError(
149
+ typeof applyResult?.reasonCode === 'string' ? applyResult.reasonCode : 'replay_initial_state_apply_failed',
150
+ applyResult?.detail
151
+ ? `Replay initial state apply failed: ${applyResult.detail}`
152
+ : 'Replay initial state apply failed.',
153
+ { applyResult: applyResult || null },
154
+ );
155
+ }
156
+ initialStateApplied = true;
157
+ }
158
+
159
+ const checkpoints = [];
160
+ let divergence = null;
161
+
162
+ for (let index = 0; index < replay.steps.length; index += 1) {
163
+ const step = replay.steps[index];
164
+ if (step.input) {
165
+ inputController.applyFrame(step.input);
166
+ } else {
167
+ inputController.clearFrameTransitions();
168
+ }
169
+
170
+ for (let frame = 0; frame < step.frames; frame += 1) {
171
+ await executeFrame(runtime.aura, 1 / 60);
172
+ runtime.frameIndex += 1;
173
+ runtime.elapsedSeconds += 1 / 60;
174
+ if (frame < step.frames - 1) {
175
+ inputController.clearFrameTransitions();
176
+ }
177
+ }
178
+
179
+ const needsCheckpoint = step.checkpointId || step.expectFingerprint;
180
+ if (!needsCheckpoint) {
181
+ inputController.clearFrameTransitions();
182
+ continue;
183
+ }
184
+
185
+ const payload = captureStateOrThrow(runtime, replay.seed, null);
186
+ const actualFingerprint = payload.export?.fingerprint || null;
187
+ const matchedExpectation = step.expectFingerprint == null || step.expectFingerprint === actualFingerprint;
188
+ checkpoints.push(buildCheckpoint({
189
+ step,
190
+ stepIndex: index,
191
+ runtime,
192
+ payload,
193
+ matchedExpectation,
194
+ }));
195
+
196
+ if (!matchedExpectation) {
197
+ divergence = {
198
+ stepIndex: index,
199
+ label: step.label || null,
200
+ checkpointId: step.checkpointId || null,
201
+ expectedFingerprint: step.expectFingerprint,
202
+ actualFingerprint,
203
+ frameIndex: runtime.frameIndex,
204
+ elapsedSeconds: runtime.elapsedSeconds,
205
+ };
206
+ if (replay.stopOnDivergence) {
207
+ break;
208
+ }
209
+ }
210
+
211
+ inputController.clearFrameTransitions();
212
+ }
213
+
214
+ const finalState = captureStateOrThrow(runtime, replay.seed, null);
215
+ const completed = divergence == null;
216
+ const report = {
217
+ schemaVersion: GAME_REPLAY_REPORT_SCHEMA_VERSION,
218
+ ok: completed,
219
+ reasonCode: completed ? 'replay_ok' : 'replay_diverged',
220
+ replaySchemaVersion: GAME_REPLAY_SCHEMA_VERSION,
221
+ mode: 'headless',
222
+ projectRoot,
223
+ entryFile: runtime.entryFile,
224
+ seed: replay.seed,
225
+ initialStateApplied,
226
+ totalSteps: replay.steps.length,
227
+ executedFrames: runtime.frameIndex,
228
+ elapsedSeconds: runtime.elapsedSeconds,
229
+ finalFingerprint: finalState.export?.fingerprint || null,
230
+ checkpoints,
231
+ divergence,
232
+ };
233
+
234
+ return {
235
+ replay,
236
+ report,
237
+ finalState,
238
+ runtime,
239
+ runtimeSummary: {
240
+ width: runtime.width,
241
+ height: runtime.height,
242
+ framesExecuted: runtime.frameIndex,
243
+ elapsedSeconds: runtime.elapsedSeconds,
244
+ bundleOutFile: runtime.bundle.outFile,
245
+ moduleCount: runtime.bundle.moduleCount,
246
+ assertionsPassed: runtime.testState.passes,
247
+ drawCalls: runtime.testState.drawCalls,
248
+ audioCalls: runtime.testState.audioCalls,
249
+ },
250
+ };
251
+ }
252
+
253
+ export function serializeReplayReport(payload, compact = false) {
254
+ return compact
255
+ ? `${JSON.stringify(payload)}\n`
256
+ : `${JSON.stringify(payload, null, 2)}\n`;
257
+ }
@@ -4,9 +4,11 @@ import { fileURLToPath } from 'node:url';
4
4
  const SCAFFOLD_DIR = dirname(fileURLToPath(import.meta.url));
5
5
  const CLI_SRC_DIR = resolve(SCAFFOLD_DIR, '..');
6
6
  const CLI_PACKAGE = JSON.parse(readFileSync(resolve(CLI_SRC_DIR, '../package.json'), 'utf8'));
7
+ export const CLI_PACKAGE_NAME = CLI_PACKAGE.name;
7
8
  const CLI_PACKAGE_VERSION = CLI_PACKAGE.version;
8
9
 
9
10
  export const LEGACY_STARTER_TEMPLATE_DIR = resolve(CLI_SRC_DIR, '../templates/starter');
11
+ export const PACKAGED_STARTER_UTILS_DIR = resolve(CLI_SRC_DIR, './helpers/starter-utils');
10
12
 
11
13
  export const CREATE_TEMPLATE_DIRS = {
12
14
  '2d-adventure': resolve(CLI_SRC_DIR, '../templates/create/2d-adventure'),
@@ -1,3 +1,4 @@
1
+ import { isUtf8 } from 'node:buffer';
1
2
  import { existsSync, mkdirSync, readdirSync, readFileSync, statSync, writeFileSync } from 'node:fs';
2
3
  import { dirname, join } from 'node:path';
3
4
 
@@ -33,7 +34,13 @@ export function copyTree(src, dest, replacements) {
33
34
  continue;
34
35
  }
35
36
 
36
- let content = readFileSync(srcPath, 'utf8');
37
+ const sourceBytes = readFileSync(srcPath);
38
+ if (!isUtf8(sourceBytes)) {
39
+ writeFileSync(destPath, sourceBytes);
40
+ continue;
41
+ }
42
+
43
+ let content = sourceBytes.toString('utf8');
37
44
  for (const [key, value] of Object.entries(replacements)) {
38
45
  content = content.replaceAll(`{{${key}}}`, value);
39
46
  }
@@ -1,3 +1,5 @@
1
+ import { describeProjectAiGuidance } from '../ai-guidance.mjs';
2
+
1
3
  export function renderProjectReadme({ name, projectTitle, template, templateMetadata }) {
2
4
  if (templateMetadata?.runtimeFamily === 'retro') {
3
5
  return renderRetroProjectReadme({ name, projectTitle, template, templateMetadata });
@@ -7,6 +9,7 @@ export function renderProjectReadme({ name, projectTitle, template, templateMeta
7
9
  const workflowSection = renderTemplateWorkflowSection(templateMetadata);
8
10
  const projectMap = renderProjectMapSection(template);
9
11
  const generateFilesSection = renderGenerateFilesSection(template);
12
+ const aiWorkflowSection = renderAiWorkflowSection(template);
10
13
  const stateOwnershipSection = renderStateOwnershipSection();
11
14
  const continuityOwnershipSection = renderContinuityOwnershipSection();
12
15
  const largeAssetSection = renderLargeAssetSection();
@@ -22,7 +25,7 @@ export function renderProjectReadme({ name, projectTitle, template, templateMeta
22
25
 
23
26
  return `# ${projectTitle}
24
27
 
25
- Scaffolded with \`aura create ${name} --template ${template}\`.
28
+ Scaffolded with \`auramaxx create ${name} --template ${template}\`.
26
29
 
27
30
  ## Quick Start
28
31
 
@@ -35,17 +38,18 @@ Optional commands:
35
38
 
36
39
  \`\`\`bash
37
40
  ${optionalCommands}
38
- npx aura explain
39
- npx aura check
41
+ auramaxx explain
42
+ auramaxx check
40
43
  \`\`\`
41
44
 
42
45
  ${template === 'blank' ? '' : 'Press `F1` while the starter is running to toggle the development-only playtest HUD. Use the top scene strip to jump between scenes and the toolbar to record, capture screenshots, restart, pause, or minimize the HUD.\n\n'}
43
46
 
44
47
  If you are publishing outside the \`@aurajs\` scope, update \`package.json -> name\`
45
- before the first \`npm run publish\`.
48
+ before the first \`auramaxx publish\`.
46
49
 
47
50
  ${largeAssetSection}
48
51
  ${workflowSection}
52
+ ${aiWorkflowSection}
49
53
  ${generateFilesSection}
50
54
  ${stateOwnershipSection}
51
55
  ${continuityOwnershipSection}
@@ -77,6 +81,7 @@ export function renderProjectRunbook({ projectTitle, template, templateMetadata
77
81
  const workflowSection = renderTemplateWorkflowSection(templateMetadata);
78
82
  const firstThirtyMinutes = renderFirstThirtyMinutesSteps(template);
79
83
  const generateFilesSection = renderGenerateFilesSection(template);
84
+ const aiWorkflowSection = renderAiWorkflowSection(template);
80
85
  const stateOwnershipSection = renderStateOwnershipSection();
81
86
  const continuityOwnershipSection = renderContinuityOwnershipSection();
82
87
  const largeAssetSection = renderLargeAssetSection();
@@ -93,6 +98,7 @@ ${stateOwnershipSection}
93
98
  ${continuityOwnershipSection}
94
99
 
95
100
  ${workflowSection}
101
+ ${aiWorkflowSection}
96
102
  ${generateFilesSection}
97
103
 
98
104
  ## First-Hour Implementation Pass
@@ -112,7 +118,7 @@ If any are unavailable at runtime, fail fast and capture the reason code before
112
118
  1. \`npm run build\` to emit native/web artifacts.
113
119
  1. \`npm run play\` to sanity-check the packaged local wrapper path.
114
120
  ${templateMetadata?.optionalModules?.multiplayer === true ? '1. `npm run join -- AURA2P` to join a room-code multiplayer session through the generated wrapper.\n' : ''}1. \`npm run session -- start\` to open a persistent local developer session.
115
- 1. \`npm run publish\` once metadata and binaries are ready.
121
+ 1. \`auramaxx publish\` once metadata and binaries are ready.
116
122
  1. If you are not publishing under \`@aurajs\`, update \`package.json -> name\` first.
117
123
 
118
124
  ${largeAssetSection}
@@ -131,13 +137,43 @@ ${steps.map((line) => `1. ${line}`).join('\n')}
131
137
  `;
132
138
  }
133
139
 
140
+ function renderAiWorkflowSection(template) {
141
+ const aiGuidance = describeProjectAiGuidance({
142
+ template,
143
+ starterUtils: template === 'blank'
144
+ ? null
145
+ : {
146
+ available: true,
147
+ recommendedImportPath: './src/starter-utils/index.js',
148
+ },
149
+ });
150
+
151
+ const importHint = aiGuidance.recommendedImportPath || aiGuidance.preferredPackageSpecifier || '(none)';
152
+ const modules = Array.isArray(aiGuidance.recommendedModules) && aiGuidance.recommendedModules.length > 0
153
+ ? aiGuidance.recommendedModules.map((entry) => `- \`${entry.id}\` via \`${importHint}\``).join('\n')
154
+ : '- no starter helper lane is preselected; build outward from `src/runtime/`, `scenes/`, and `content/`';
155
+
156
+ return `## AI Workflow
157
+
158
+ - run \`auramaxx explain --json\` before moving files so the current source boundary, starter helper lane, and paved-road guidance stay explicit
159
+ - run \`auramaxx check --json\` after edits when you want machine-readable import and registry validation
160
+ - keep authored gameplay code inside \`src/\`, \`scenes/\`, \`ui/\`, \`prefabs/\`, \`config/\`, and \`content/\`
161
+ - preferred helper import: \`${importHint}\`
162
+ - AI paved road lane: \`${aiGuidance.laneId}\`
163
+
164
+ Recommended starter modules:
165
+
166
+ ${modules}
167
+ `;
168
+ }
169
+
134
170
  function renderRetroProjectReadme({ name, projectTitle, template, templateMetadata }) {
135
171
  const controls = templateMetadata.controls.map((line) => `- ${line}`).join('\n');
136
172
  const firstEdits = templateMetadata.firstEdits.map((line) => `1. ${line}`).join('\n');
137
173
  const workflowSection = renderTemplateWorkflowSection(templateMetadata);
138
174
  return `# ${projectTitle}
139
175
 
140
- Scaffolded with \`aura create ${name} --template ${template}\`.
176
+ Scaffolded with \`auramaxx create ${name} --template ${template}\`.
141
177
 
142
178
  ## Quick Start
143
179
 
@@ -156,8 +192,8 @@ npm run retro:explain
156
192
  \`\`\`
157
193
 
158
194
  Aura Retro projects build through the main AuraScript CLI, but they do not use
159
- the default desktop play/dev wrapper flow. Treat \`aura retro check\` and
160
- \`aura build --target <retro-target>\` as the primary development loop.
195
+ the default desktop play/dev wrapper flow. Treat \`npm run retro:check\` and
196
+ \`auramaxx build --target <retro-target>\` as the primary development loop.
161
197
 
162
198
  ${workflowSection}
163
199
  ## Template Summary
@@ -225,6 +261,15 @@ export function renderStateOwnershipSection() {
225
261
  - \`screenShell\` payloads are only the data passed into HUD, overlay, and modal screens.
226
262
  - \`config/\` is for defaults and tuning.
227
263
  - \`content/\` is for authored definitions and registries.
264
+
265
+ Example shared session state:
266
+
267
+ \`\`\`js
268
+ const runFlags = context.ensureSessionState('runFlags', { DID_START: false });
269
+ if (!runFlags.DID_START) {
270
+ runFlags.DID_START = true;
271
+ }
272
+ \`\`\`
228
273
  `;
229
274
  }
230
275
 
@@ -240,16 +285,16 @@ export function renderContinuityOwnershipSection() {
240
285
  - \`scenes/*.scene.js\` should keep \`sceneState\` JSON-safe when it must survive restore.
241
286
  - \`context.getCurrentScenePayload()\` is the read seam for payloads restored through \`sceneFlow\`.
242
287
  - saved slots live under \`.aura/state/slots/\` and checkpoints live under \`.aura/state/checkpoints/\`.
243
- - native dev restore hooks use \`aura dev --restore-slot <name>\` or \`aura dev --restore-checkpoint <name>\`.
288
+ - native dev restore hooks use \`auramaxx dev --restore-slot <name>\` or \`auramaxx dev --restore-checkpoint <name>\`.
244
289
  `;
245
290
  }
246
291
 
247
292
  export function renderLargeAssetSection() {
248
293
  return `## Publish and Large Assets
249
294
 
250
- - \`npm run publish\` delegates to the engine-owned \`aura publish\` lane.
295
+ - \`auramaxx publish\` is the public publish command inside an AuraJS project.
251
296
  - AuraJS measures built asset payload size before publish. The default npm-first threshold is 50 MiB; operators can override it with \`AURA_PUBLISH_ASSET_THRESHOLD_BYTES\`.
252
- - If the payload is too large, use your own HTTPS host for the heavy assets and run \`aura external-assets generate --public-base-url <url>\`.
297
+ - If the payload is too large, use your own HTTPS host for the heavy assets and run \`auramaxx external-assets generate --public-base-url <url>\`.
253
298
  - That command writes \`aura.external-assets.json\` in the project root and stages manifests plus upload records under \`.aura/external-assets/\`.
254
299
  - Packaged \`npx <game> play\` and \`join\` hydrate self-hosted assets into a local cache before launch.
255
300
  - Cloudflare R2 and S3 are examples only. AuraPM and \`publishv2\` remain preview-only.
@@ -261,16 +306,16 @@ export function renderGenerateFilesSection(template) {
261
306
  return `## Generate Files
262
307
 
263
308
  \`\`\`bash
264
- npx aura make scene MainMenu
265
- npx aura make ui-screen PauseMenu
266
- npx aura make config EnemyTable
267
- npx aura make content SpawnTable
268
- npx aura explain
269
- npx aura check
309
+ auramaxx make scene MainMenu
310
+ auramaxx make ui-screen PauseMenu
311
+ auramaxx make config EnemyTable
312
+ auramaxx make content SpawnTable
313
+ auramaxx explain
314
+ auramaxx check
270
315
  \`\`\`
271
316
 
272
317
  Blank now ships the same authored roots as the other starters. Use the seeded
273
- files as the reference structure, then grow the project with \`npx aura make\`.
318
+ files as the reference structure, then grow the project with \`auramaxx make\`.
274
319
  `;
275
320
  }
276
321
 
@@ -278,12 +323,12 @@ files as the reference structure, then grow the project with \`npx aura make\`.
278
323
  return `## Generate Files
279
324
 
280
325
  \`\`\`bash
281
- npx aura make card StrikePlus
282
- npx aura make enemy JawWorm
283
- npx aura make relic BurningBlood
284
- npx aura make encounter Act1Hallway
285
- npx aura explain
286
- npx aura check
326
+ auramaxx make card StrikePlus
327
+ auramaxx make enemy JawWorm
328
+ auramaxx make relic BurningBlood
329
+ auramaxx make encounter Act1Hallway
330
+ auramaxx explain
331
+ auramaxx check
287
332
  \`\`\`
288
333
  `;
289
334
  }
@@ -291,12 +336,12 @@ npx aura check
291
336
  return `## Generate Files
292
337
 
293
338
  \`\`\`bash
294
- npx aura make scene MainMenu
295
- npx aura make ui-screen PauseMenu
296
- npx aura make config EnemyTable
297
- npx aura make content SpawnTable
298
- npx aura explain
299
- npx aura check
339
+ auramaxx make scene MainMenu
340
+ auramaxx make ui-screen PauseMenu
341
+ auramaxx make config EnemyTable
342
+ auramaxx make content SpawnTable
343
+ auramaxx explain
344
+ auramaxx check
300
345
  \`\`\`
301
346
  `;
302
347
  }
@@ -309,8 +354,8 @@ export function renderProjectMapSection(template) {
309
354
  - \`aura.config.json\` - identity/window/build/modules.
310
355
  - \`aura.capabilities.json\` - canonical runtime API declaration for this scaffold.
311
356
  - \`RUNBOOK.md\` - first-hour implementation and triage checklist.
312
- - \`npx aura explain\` - inspect how the current project is wired.
313
- - \`npx aura check\` - validate authored wiring before runtime.
357
+ - \`auramaxx explain\` - inspect how the current project is wired.
358
+ - \`auramaxx check\` - validate authored wiring before runtime.
314
359
  - \`src/runtime/\` - runtime/bootstrap helpers plus the project and scene registries.
315
360
  - \`src/runtime/project-registry.js\` - authored source of truth for scenes, screens, prefabs, \`configFiles\`, and \`contentFiles\`.
316
361
  - \`src/runtime/scene-flow.js\` - active-scene continuity, stack state, and route payload handoff.
@@ -324,13 +369,13 @@ export function renderProjectMapSection(template) {
324
369
  - \`config/\` - defaults and tunables.
325
370
  - \`content/\` - authored game definitions, progression payloads, and starter-owned registries.
326
371
  - \`content/registries/\` - starter-owned registries and future generator-owned indexes.
327
- - \`src/runtime/app-state.js\` - shared mutable \`appState\` split into \`session\`, \`ui\`, and \`runtime\` buckets plus helper-backed access from scenes.`;
372
+ - \`src/runtime/app-state.js\` - shared mutable \`appState\` split into \`session\`, \`ui\`, and \`runtime\` buckets plus helper-backed access from scenes. Starter example: \`context.ensureSessionState('runFlags', { DID_START: false })\`.`;
328
373
  }
329
374
 
330
375
  return `- \`src/main.js\` - stable bootstrap seam into the authored project layout.
331
376
  - \`src/runtime/\` - runtime/bootstrap helpers plus the project and scene registries.
332
377
  - \`src/runtime/project-registry.js\` - authored source of truth for scenes, screens, prefabs, \`configFiles\`, and \`contentFiles\`.
333
- - \`src/runtime/app-state.js\` - shared mutable \`appState\` split into \`session\`, \`ui\`, and \`runtime\` buckets plus helper-backed access from scenes.
378
+ - \`src/runtime/app-state.js\` - shared mutable \`appState\` split into \`session\`, \`ui\`, and \`runtime\` buckets plus helper-backed access from scenes. Starter example: \`context.ensureSessionState('runFlags', { DID_START: false })\`.
334
379
  - \`src/runtime/scene-flow.js\` - active-scene continuity, scene-stack restore ownership, and route payload handoff.
335
380
  - \`src/runtime/screen-shell.js\` - HUD, overlay, and modal payload continuity ownership.
336
381
  - \`src/runtime/ui-theme.js\` - shared theme presets plus \`appState.ui.preferences\` apply/reset helpers.
@@ -343,7 +388,13 @@ export function renderProjectMapSection(template) {
343
388
  - \`config/\` - editable defaults and tuning payloads.
344
389
  - \`content/\` - authored definitions, levels, registries, and progression content.
345
390
  - \`content/registries/\` - starter-owned registry files for cards, enemies, relics, and encounters.
346
- - \`src/starter-utils/\` - reusable helpers copied for non-blank templates.
391
+ - \`src/starter-utils/\` - local copies of reusable authored helper modules
392
+ shipped with AuraJS for non-blank templates. Keep imports local here for
393
+ starter-facing gameplay code; use the published
394
+ \`@auraindustry/aurajs/helpers\` lane only when you want the shared
395
+ package-backed helper surface directly. Run \`auramaxx vendor helpers\` when
396
+ you want to seed or resync the local helper lane in a project that does not
397
+ already have it.
347
398
  - \`assets/starter/\` - starter asset pack and editable design payloads.
348
399
  - \`aura.config.json\` - identity/window/build/modules.
349
400
  - \`aura.capabilities.json\` - canonical runtime API declaration for this scaffold.
@@ -355,16 +406,16 @@ export function renderFirstThirtyMinutesSteps(template) {
355
406
  return `1. Run \`npm install\` then \`npm run dev\` and confirm the starter loop is playable.
356
407
  1. Read \`src/main.js\`, \`src/runtime/app.js\`, \`src/runtime/app-state.js\`, \`scenes/\`, \`config/\`, \`content/\`, and \`docs/design/\` once before adding new structure.
357
408
  1. Decide whether your next change belongs in \`appState.session\`, \`appState.ui\`, \`appState.runtime\`, \`sceneState\`, \`config/\`, or \`content/\`, and whether it must survive save or restart continuity.
358
- 1. Run \`npx aura explain\` once so the bootstrap stays obvious before the project grows.
359
- 1. Use \`npx aura make\` when you want more authored files without inventing paths.`;
409
+ 1. Run \`auramaxx explain\` once so the bootstrap stays obvious before the project grows.
410
+ 1. Use \`auramaxx make\` when you want more authored files without inventing paths.`;
360
411
  }
361
412
 
362
413
  if (template === 'deckbuilder-2d') {
363
414
  return `1. Run \`npm install\` then \`npm run dev\` and finish one starter battle end to end.
364
415
  1. Read \`content/registries/\`, \`content/cards/\`, \`content/enemies/\`, and \`content/encounters/\` before changing scene flow.
365
416
  1. Open \`src/runtime/ui-theme.js\`, \`src/runtime/ui-settings.js\`, and \`src/runtime/ui-forms.js\` before adding pause/settings or form glue of your own.
366
- 1. Generate one extra card or encounter with \`npx aura make\` instead of hand-creating files.
367
- 1. Run \`npx aura explain\` or \`npx aura check\` once so the starter-owned registries stay obvious as the project grows.
417
+ 1. Generate one extra card or encounter with \`auramaxx make\` instead of hand-creating files.
418
+ 1. Run \`auramaxx explain\` or \`auramaxx check\` once so the starter-owned registries stay obvious as the project grows.
368
419
  1. Only then move into battle polish, rewards, or meta-progression.`;
369
420
  }
370
421
 
@@ -373,9 +424,9 @@ export function renderFirstThirtyMinutesSteps(template) {
373
424
  1. Open \`src/runtime/ui-theme.js\`, \`src/runtime/ui-settings.js\`, and \`src/runtime/ui-forms.js\` before inventing starter-local theme, settings, or form plumbing.
374
425
  1. Review \`assets/starter/\`, \`config/gameplay/\`, \`content/gameplay/\`, and \`docs/design/\` and rewrite the seed content so it matches your game's nouns, tuning, and milestone goals.
375
426
  1. Keep shared state in \`appState.session\` / \`appState.ui\`, keep route payloads in \`sceneFlow\`, and keep scene-only state inside \`sceneState\`.
376
- 1. Run \`npx aura explain\` or \`npx aura check\` when you want a fast map of the authored project wiring.
427
+ 1. Run \`auramaxx explain\` or \`auramaxx check\` when you want a fast map of the authored project wiring.
377
428
  1. Press \`F1\` in the running starter to open the playtest HUD, jump between authored scenes, and use the compact capture/restart toolbar.
378
- 1. Use \`npx aura make scene MainMenu\` or \`npx aura make ui-screen PauseMenu\` instead of hand-making new authored file paths.`;
429
+ 1. Use \`auramaxx make scene MainMenu\` or \`auramaxx make ui-screen PauseMenu\` instead of hand-making new authored file paths.`;
379
430
  }
380
431
 
381
432
  export function renderContentMapDoc({ template }) {
@@ -403,6 +454,15 @@ Template: \`${template}\`
403
454
  - \`src/runtime/project-inspector.js\` exposes the live F1 playtest HUD.
404
455
  - authored scenes read shared state with \`ensureSessionState\`, \`ensureUiState\`, and \`getCurrentScenePayload()\`.
405
456
 
457
+ ## Shared State Example
458
+
459
+ \`\`\`js
460
+ const runFlags = context.ensureSessionState('runFlags', { DID_START: false });
461
+ if (!runFlags.DID_START) {
462
+ runFlags.DID_START = true;
463
+ }
464
+ \`\`\`
465
+
406
466
  ## Current Starter Inventory
407
467
 
408
468
  - \`scenes/boot.scene.js\`
package/src/scaffold.mjs CHANGED
@@ -7,6 +7,7 @@ import {
7
7
  CREATE_TEMPLATE_DIRS,
8
8
  GITIGNORE_TEMPLATE,
9
9
  LEGACY_STARTER_TEMPLATE_DIR,
10
+ PACKAGED_STARTER_UTILS_DIR,
10
11
  PLAY_BIN_TEMPLATE,
11
12
  applyTemplateConfigOverrides,
12
13
  buildCapabilitiesDeclaration,
@@ -106,6 +107,9 @@ export function scaffoldGame(options) {
106
107
  copyTree(templateDir, dest, replacements);
107
108
  if (shouldCopyCreateSharedTemplate(normalizedTemplate, templateMetadata) && existsSync(CREATE_SHARED_TEMPLATE_DIR)) {
108
109
  copyTree(CREATE_SHARED_TEMPLATE_DIR, dest, replacements);
110
+ if (existsSync(PACKAGED_STARTER_UTILS_DIR)) {
111
+ copyTree(PACKAGED_STARTER_UTILS_DIR, join(dest, 'src', 'starter-utils'), replacements);
112
+ }
109
113
  }
110
114
 
111
115
  mkdirSync(join(dest, 'assets'), { recursive: true });
@@ -74,8 +74,10 @@ export async function bootSessionRuntime(options = {}) {
74
74
  });
75
75
 
76
76
  const testState = createSessionTestState();
77
- const aura = createHeadlessAura({ width, height, testState });
78
- const context = vm.createContext(createRuntimeContext(aura, testState));
77
+ const aura = createHeadlessAura({ width, height, testState, projectRoot });
78
+ const context = vm.createContext(createRuntimeContext(aura, testState, {
79
+ math: options.math,
80
+ }));
79
81
 
80
82
  const source = readFileSync(bundle.outFile, 'utf8');
81
83
  const script = new vm.Script(source, {
@@ -318,4 +320,3 @@ export async function exportSessionInspect(runtime, options = {}) {
318
320
  };
319
321
  }
320
322
  }
321
-