@auraindustry/aurajs 0.0.5 → 0.0.6

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.
@@ -114,6 +114,109 @@ export const DEFAULT_PERF_SCENES = [
114
114
  };
115
115
  `,
116
116
  },
117
+ {
118
+ id: 'tilemap_particles_stress',
119
+ frames: 360,
120
+ source: `
121
+ let mapId = 0;
122
+ let elapsed = 0;
123
+ let canUseParticles = false;
124
+ const fallbackParticles = [];
125
+
126
+ aura.setup = function () {
127
+ const width = 32;
128
+ const height = 24;
129
+ const tileCount = width * height;
130
+ const ground = Array.from({ length: tileCount }, () => 1);
131
+ const decor = Array.from({ length: tileCount }, (_, index) => ((index + Math.floor(index / width)) % 3 === 0 ? 1 : 0));
132
+
133
+ mapId = aura.tilemap.import({
134
+ width,
135
+ height,
136
+ tilewidth: 16,
137
+ tileheight: 16,
138
+ layers: [
139
+ { name: 'ground', type: 'tilelayer', width, height, data: ground },
140
+ { name: 'decor', type: 'tilelayer', width, height, data: decor },
141
+ ],
142
+ tilesets: [
143
+ { firstgid: 1, image: 'player.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 },
144
+ ],
145
+ });
146
+
147
+ canUseParticles = !!(
148
+ aura.particles
149
+ && typeof aura.particles.emit === 'function'
150
+ && typeof aura.particles.update === 'function'
151
+ && typeof aura.particles.draw === 'function'
152
+ );
153
+
154
+ for (let i = 0; i < 6; i += 1) {
155
+ const originX = 48 + (i * 32);
156
+ const originY = 72 + ((i % 3) * 36);
157
+ if (canUseParticles) {
158
+ aura.particles.emit({
159
+ x: originX,
160
+ y: originY,
161
+ count: 24,
162
+ life: 0.8,
163
+ size: 2,
164
+ speedMin: 12,
165
+ speedMax: 18,
166
+ direction: i * 0.35,
167
+ spread: 1.2,
168
+ loop: true,
169
+ rate: 40,
170
+ });
171
+ continue;
172
+ }
173
+
174
+ for (let j = 0; j < 24; j += 1) {
175
+ fallbackParticles.push({
176
+ originX,
177
+ originY,
178
+ phase: (i * 0.6) + (j * 0.21),
179
+ radius: 4 + ((j % 6) * 3),
180
+ speed: 0.8 + ((j % 5) * 0.14),
181
+ });
182
+ }
183
+ }
184
+ };
185
+
186
+ aura.update = function (dt) {
187
+ elapsed += dt;
188
+ if (canUseParticles) {
189
+ aura.particles.update(dt);
190
+ }
191
+ };
192
+
193
+ aura.draw = function () {
194
+ aura.draw2d.clear(4, 6, 10);
195
+
196
+ const camera = {
197
+ x: 64 + ((Math.sin(elapsed * 0.8) + 1) * 64),
198
+ y: 32 + ((Math.cos(elapsed * 0.6) + 1) * 40),
199
+ width: 320,
200
+ height: 192,
201
+ };
202
+
203
+ aura.tilemap.draw(mapId, { camera });
204
+
205
+ if (canUseParticles) {
206
+ aura.particles.draw({ maxParticles: 512 });
207
+ return;
208
+ }
209
+
210
+ for (let i = 0; i < fallbackParticles.length; i += 1) {
211
+ const particle = fallbackParticles[i];
212
+ const theta = (elapsed * particle.speed * 3.0) + particle.phase;
213
+ const x = particle.originX + Math.cos(theta) * particle.radius;
214
+ const y = particle.originY + Math.sin(theta * 1.3) * (particle.radius * 0.8);
215
+ aura.draw2d.circle(x, y, 2, aura.colors.white);
216
+ }
217
+ };
218
+ `,
219
+ },
117
220
  ];
118
221
 
119
222
  function mean(values) {
package/src/scaffold.mjs CHANGED
@@ -8,7 +8,9 @@ const LEGACY_STARTER_TEMPLATE_DIR = resolve(CLI_SRC_DIR, '../templates/starter')
8
8
 
9
9
  const CREATE_TEMPLATE_DIRS = {
10
10
  '2d-shooter': resolve(CLI_SRC_DIR, '../templates/create/2d'),
11
+ '2d-survivor': resolve(CLI_SRC_DIR, '../templates/create/2d-survivor'),
11
12
  '3d-platformer': resolve(CLI_SRC_DIR, '../templates/create/3d'),
13
+ '3d-collectathon': resolve(CLI_SRC_DIR, '../templates/create/3d-collectathon'),
12
14
  blank: resolve(CLI_SRC_DIR, '../templates/create/blank'),
13
15
  };
14
16
  const CREATE_SHARED_TEMPLATE_DIR = resolve(CLI_SRC_DIR, '../templates/create/shared');
@@ -18,14 +20,27 @@ const CREATE_TEMPLATE_ALIASES = {
18
20
  shooter: '2d-shooter',
19
21
  '2d shooter': '2d-shooter',
20
22
  '2d-shooter': '2d-shooter',
23
+ '2d-survivor': '2d-survivor',
24
+ survivor: '2d-survivor',
25
+ '2d survival': '2d-survivor',
26
+ '2d-survival': '2d-survivor',
27
+ topdown: '2d-survivor',
28
+ 'top-down': '2d-survivor',
21
29
  '3d': '3d-platformer',
22
30
  platformer: '3d-platformer',
23
31
  platformers: '3d-platformer',
24
32
  '3d platformer': '3d-platformer',
25
33
  '3d platformers': '3d-platformer',
26
34
  '3d-platformer': '3d-platformer',
35
+ '3d-collectathon': '3d-collectathon',
36
+ collectathon: '3d-collectathon',
37
+ explorer: '3d-collectathon',
38
+ exploration: '3d-collectathon',
39
+ '3d collectathon': '3d-collectathon',
40
+ '3d explorer': '3d-collectathon',
27
41
  blank: 'blank',
28
42
  };
43
+ const CREATE_TEMPLATE_ALIAS_HINTS = ['2d', '3d', 'shooter', 'platformer', 'survivor', 'collectathon', 'blank'];
29
44
 
30
45
  const ROOT_SKILLS_DIR = resolve(CLI_SRC_DIR, '../../../skills');
31
46
  const PACKAGED_SKILLS_DIR = resolve(CLI_SRC_DIR, '../templates/skills');
@@ -95,10 +110,227 @@ child.on('close', (code) => {
95
110
  });
96
111
  `;
97
112
 
113
+ const CREATE_TEMPLATE_METADATA = {
114
+ '2d-shooter': {
115
+ summary: 'Arcade shooter baseline with waves, score, and lives loop.',
116
+ controls: [
117
+ 'Move: Arrow keys or WASD',
118
+ 'Fire: Space or Z',
119
+ 'Restart: Enter (after game over)',
120
+ ],
121
+ firstEdits: [
122
+ 'Tune encounter pacing in src/main.js -> WAVE_CONFIG.',
123
+ 'Add custom enemy variants in src/starter-utils/enemy-archetypes-2d.js.',
124
+ 'Swap starter palette and HUD text in src/main.js.',
125
+ ],
126
+ keywords: ['2d', 'shooter', 'arcade'],
127
+ requiredApis: [
128
+ 'aura.window.getSize',
129
+ 'aura.window.getFPS',
130
+ 'aura.input.isKeyDown',
131
+ 'aura.input.isKeyPressed',
132
+ 'aura.draw2d.clear',
133
+ 'aura.draw2d.rect',
134
+ 'aura.draw2d.text',
135
+ 'aura.draw2d.measureText',
136
+ 'aura.rgb',
137
+ 'aura.rgba',
138
+ 'aura.Color.WHITE',
139
+ ],
140
+ starterAssetPlan: {
141
+ file: 'wave-plan.json',
142
+ title: '2D Shooter Wave Plan',
143
+ steps: [
144
+ 'Replace this JSON with your own wave progression and enemy mix.',
145
+ 'Load this file in game logic after adding your preferred file-loading flow.',
146
+ ],
147
+ payload: {
148
+ waves: [
149
+ { id: 'intro', maxSpawns: 9, spawnEvery: 0.62, archetype: 'scout' },
150
+ { id: 'pressure', maxSpawns: 10, spawnEvery: 0.56, archetype: 'striker' },
151
+ { id: 'anchor', maxSpawns: 8, spawnEvery: 0.74, archetype: 'tank' },
152
+ ],
153
+ },
154
+ },
155
+ },
156
+ '2d-survivor': {
157
+ summary: 'Top-down auto-fire survival loop tuned for quick iteration.',
158
+ controls: [
159
+ 'Move: Arrow keys or WASD',
160
+ 'Dash: Shift',
161
+ 'Restart: Enter (after game over)',
162
+ ],
163
+ firstEdits: [
164
+ 'Adjust spawn pressure in src/main.js -> spawnDirector + difficulty ramps.',
165
+ 'Tune dash timings in src/main.js constants.',
166
+ 'Extend enemy archetypes in src/starter-utils/enemy-archetypes-2d.js.',
167
+ ],
168
+ keywords: ['2d', 'survivor', 'topdown'],
169
+ requiredApis: [
170
+ 'aura.window.getSize',
171
+ 'aura.window.getFPS',
172
+ 'aura.input.isKeyDown',
173
+ 'aura.input.isKeyPressed',
174
+ 'aura.draw2d.clear',
175
+ 'aura.draw2d.rect',
176
+ 'aura.draw2d.text',
177
+ 'aura.draw2d.measureText',
178
+ 'aura.rgb',
179
+ 'aura.rgba',
180
+ 'aura.Color.WHITE',
181
+ ],
182
+ starterAssetPlan: {
183
+ file: 'survivor-zones.json',
184
+ title: '2D Survivor Spawn Zones',
185
+ steps: [
186
+ 'Use these zones to bias enemy spawns by map phase.',
187
+ 'Add your own tags (boss, swarm, elite) and branch in update().',
188
+ ],
189
+ payload: {
190
+ zones: [
191
+ { id: 'north-lane', x: 0.5, y: 0.08, weight: 1.0 },
192
+ { id: 'east-lane', x: 0.92, y: 0.52, weight: 1.2 },
193
+ { id: 'south-lane', x: 0.5, y: 0.94, weight: 1.0 },
194
+ { id: 'west-lane', x: 0.08, y: 0.52, weight: 1.1 },
195
+ ],
196
+ },
197
+ },
198
+ },
199
+ '3d-platformer': {
200
+ summary: '3D checkpoint platformer loop with moving platform helper primitives.',
201
+ controls: [
202
+ 'Move: Arrow keys or WASD',
203
+ 'Jump: Space',
204
+ 'Restart: Enter (after goal)',
205
+ ],
206
+ firstEdits: [
207
+ 'Adjust traversal beats in src/main.js -> STATIC_PLATFORMS and CHECKPOINTS.',
208
+ 'Tune camera offsets in aura.update.',
209
+ 'Swap material colors in aura.setup for your theme.',
210
+ ],
211
+ keywords: ['3d', 'platformer', 'checkpoint'],
212
+ requiredApis: [
213
+ 'aura.mesh.createBox',
214
+ 'aura.material.create',
215
+ 'aura.light.ambient',
216
+ 'aura.light.directional',
217
+ 'aura.camera3d.perspective',
218
+ 'aura.camera3d.setPosition',
219
+ 'aura.camera3d.lookAt',
220
+ 'aura.input.isKeyDown',
221
+ 'aura.input.isKeyPressed',
222
+ 'aura.draw3d.clear3d',
223
+ 'aura.draw3d.drawMesh',
224
+ 'aura.draw2d.text',
225
+ 'aura.draw2d.measureText',
226
+ 'aura.rgb',
227
+ 'aura.Color.WHITE',
228
+ ],
229
+ starterAssetPlan: {
230
+ file: 'checkpoint-route.json',
231
+ title: '3D Platformer Route Notes',
232
+ steps: [
233
+ 'Use this path list to plan encounter/collectible placement between checkpoints.',
234
+ 'Attach analytics ids if you track failed jump segments.',
235
+ ],
236
+ payload: {
237
+ path: [
238
+ { id: 'spawn', x: 0.0, y: 1.1, z: 5.5 },
239
+ { id: 'ridge-a', x: -3.2, y: 2.0, z: -2.4 },
240
+ { id: 'ridge-b', x: 1.4, y: 3.2, z: -0.8 },
241
+ { id: 'goal', x: 4.4, y: 4.8, z: -3.0 },
242
+ ],
243
+ },
244
+ },
245
+ },
246
+ '3d-collectathon': {
247
+ summary: '3D collection race starter with timer pressure and checkpoint fallback.',
248
+ controls: [
249
+ 'Move: Arrow keys or WASD',
250
+ 'Jump: Space',
251
+ 'Restart: Enter (after clear/failure)',
252
+ ],
253
+ firstEdits: [
254
+ 'Add world pieces in src/main.js -> WORLD_PLATFORMS + COLLECTIBLES.',
255
+ 'Balance the timer and required collectible count in constants.',
256
+ 'Customize checkpoint/goal feedback text in aura.draw.',
257
+ ],
258
+ keywords: ['3d', 'collectathon', 'exploration'],
259
+ requiredApis: [
260
+ 'aura.mesh.createBox',
261
+ 'aura.material.create',
262
+ 'aura.light.ambient',
263
+ 'aura.light.directional',
264
+ 'aura.camera3d.perspective',
265
+ 'aura.camera3d.setPosition',
266
+ 'aura.camera3d.lookAt',
267
+ 'aura.input.isKeyDown',
268
+ 'aura.input.isKeyPressed',
269
+ 'aura.draw3d.clear3d',
270
+ 'aura.draw3d.drawMesh',
271
+ 'aura.draw2d.text',
272
+ 'aura.draw2d.measureText',
273
+ 'aura.rgb',
274
+ 'aura.Color.WHITE',
275
+ ],
276
+ starterAssetPlan: {
277
+ file: 'collectible-layout.json',
278
+ title: '3D Collectathon Layout Plan',
279
+ steps: [
280
+ 'Extend this list with your final collectible and checkpoint route.',
281
+ 'Optionally mirror ids in your quest/state system for save/load.',
282
+ ],
283
+ payload: {
284
+ collectibles: [
285
+ { id: 'orb-a', x: -2.8, y: 1.8, z: 2.2, value: 1 },
286
+ { id: 'orb-b', x: 2.4, y: 2.4, z: 0.4, value: 1 },
287
+ { id: 'orb-c', x: 4.6, y: 3.8, z: -2.8, value: 1 },
288
+ ],
289
+ },
290
+ },
291
+ },
292
+ blank: {
293
+ summary: 'Minimal blank loop with runtime API checks for quick custom starts.',
294
+ controls: [
295
+ 'No controls wired yet.',
296
+ 'Add input + systems directly in src/main.js.',
297
+ ],
298
+ firstEdits: [
299
+ 'Define your own game-state object and update loop in src/main.js.',
300
+ 'Set modules and identity in aura.config.json.',
301
+ 'Replace starter asset notes under assets/starter/.',
302
+ ],
303
+ keywords: ['blank', 'starter'],
304
+ requiredApis: [
305
+ 'aura.draw2d.clear',
306
+ 'aura.rgba',
307
+ ],
308
+ starterAssetPlan: {
309
+ file: 'starter-notes.json',
310
+ title: 'Blank Starter Notes',
311
+ steps: [
312
+ 'Write your core loop goals before coding to keep agent prompts precise.',
313
+ 'Track capability assumptions here as you expand runtime usage.',
314
+ ],
315
+ payload: {
316
+ goals: [
317
+ 'Define camera and movement model.',
318
+ 'Pick combat/progression loop.',
319
+ 'Specify first milestone acceptance criteria.',
320
+ ],
321
+ },
322
+ },
323
+ },
324
+ };
325
+
98
326
  export function listCreateTemplates() {
99
327
  return Object.keys(CREATE_TEMPLATE_DIRS);
100
328
  }
101
329
 
330
+ export function listCreateTemplateAliasHints() {
331
+ return [...CREATE_TEMPLATE_ALIAS_HINTS];
332
+ }
333
+
102
334
  export function normalizeCreateTemplate(template) {
103
335
  const normalized = String(template || '').trim().toLowerCase();
104
336
  return CREATE_TEMPLATE_ALIASES[normalized] || null;
@@ -126,7 +358,7 @@ export function scaffold(name, dest) {
126
358
  * @param {object} options - Scaffolding options.
127
359
  * @param {string} options.name - Project directory name (npm package defaults to @aurajs/<name>).
128
360
  * @param {string} options.dest - Absolute destination directory.
129
- * @param {string} [options.template='2d-shooter'] - Template key: 2d-shooter | 3d-platformer | blank.
361
+ * @param {string} [options.template='2d-shooter'] - Template key from listCreateTemplates().
130
362
  * @param {string} [options.version='0.1.0'] - Initial package version.
131
363
  * @param {string} [options.license='MIT'] - Initial package license.
132
364
  */
@@ -143,7 +375,7 @@ export function scaffoldGame(options) {
143
375
  const templateDir = CREATE_TEMPLATE_DIRS[normalizedTemplate];
144
376
  if (!templateDir || !existsSync(templateDir)) {
145
377
  throw new Error(
146
- `Unknown template "${template}". Expected one of: ${listCreateTemplates().join(', ')} (aliases: 2d, 3d, shooter, platformer, blank).`,
378
+ `Unknown template "${template}". Expected one of: ${listCreateTemplates().join(', ')} (aliases: ${listCreateTemplateAliasHints().join(', ')}).`,
147
379
  );
148
380
  }
149
381
 
@@ -158,6 +390,7 @@ export function scaffoldGame(options) {
158
390
  PROJECT_VERSION: version,
159
391
  PROJECT_LICENSE: license,
160
392
  };
393
+ const templateMetadata = resolveTemplateMetadata(normalizedTemplate);
161
394
 
162
395
  copyTree(templateDir, dest, replacements);
163
396
  if (normalizedTemplate !== 'blank' && existsSync(CREATE_SHARED_TEMPLATE_DIR)) {
@@ -174,12 +407,35 @@ export function scaffoldGame(options) {
174
407
  version,
175
408
  license,
176
409
  binName,
177
- template: normalizedTemplate,
410
+ templateMetadata,
178
411
  });
179
412
 
180
413
  writeFileSync(join(dest, 'package.json'), JSON.stringify(pkg, null, 2) + '\n', 'utf8');
181
414
  writeFileSync(join(dest, '.gitignore'), GITIGNORE_TEMPLATE, 'utf8');
182
415
  writeFileSync(join(dest, 'bin', 'play.js'), PLAY_BIN_TEMPLATE, { encoding: 'utf8', mode: 0o755 });
416
+ writeFileSync(
417
+ join(dest, 'README.md'),
418
+ renderProjectReadme({ name, projectTitle, template: normalizedTemplate, templateMetadata }),
419
+ 'utf8',
420
+ );
421
+ writeFileSync(
422
+ join(dest, 'RUNBOOK.md'),
423
+ renderProjectRunbook({ name, projectTitle, template: normalizedTemplate, templateMetadata }),
424
+ 'utf8',
425
+ );
426
+ writeFileSync(
427
+ join(dest, 'aura.capabilities.json'),
428
+ JSON.stringify(
429
+ buildCapabilitiesDeclaration({
430
+ template: normalizedTemplate,
431
+ templateMetadata,
432
+ }),
433
+ null,
434
+ 2,
435
+ ) + '\n',
436
+ 'utf8',
437
+ );
438
+ writeStarterAssetPack({ dest, projectTitle, template: normalizedTemplate, templateMetadata });
183
439
 
184
440
  const skillsSource = resolveSkillsSourceDir();
185
441
  if (skillsSource) {
@@ -242,17 +498,14 @@ function resolveSkillsSourceDir() {
242
498
  return null;
243
499
  }
244
500
 
245
- function buildPackageJson({ name, version, license, binName, template }) {
501
+ function resolveTemplateMetadata(template) {
502
+ return CREATE_TEMPLATE_METADATA[template] || CREATE_TEMPLATE_METADATA.blank;
503
+ }
504
+
505
+ function buildPackageJson({ name, version, license, binName, templateMetadata }) {
246
506
  const packageSlug = sanitizePackageSlug(name);
247
507
  const packageName = `@aurajs/${packageSlug}`;
248
- const keywords = ['aurajs', 'game'];
249
- if (template.includes('3d')) {
250
- keywords.push('3d');
251
- keywords.push('platformer');
252
- } else if (template.includes('2d')) {
253
- keywords.push('2d');
254
- keywords.push('shooter');
255
- }
508
+ const keywords = ['aurajs', 'game', ...(templateMetadata?.keywords || [])];
256
509
 
257
510
  return {
258
511
  name: packageName,
@@ -262,7 +515,7 @@ function buildPackageJson({ name, version, license, binName, template }) {
262
515
  bin: {
263
516
  [binName]: './bin/play.js',
264
517
  },
265
- files: ['bin/', 'src/', 'assets/', 'skills/', 'aura.config.json'],
518
+ files: ['bin/', 'src/', 'assets/', 'skills/', 'aura.config.json', 'aura.capabilities.json', 'README.md', 'RUNBOOK.md'],
266
519
  scripts: {
267
520
  dev: 'aura dev',
268
521
  build: 'aura build',
@@ -277,6 +530,153 @@ function buildPackageJson({ name, version, license, binName, template }) {
277
530
  };
278
531
  }
279
532
 
533
+ function buildCapabilitiesDeclaration({ template, templateMetadata }) {
534
+ return {
535
+ schema: 'aurajs.create-capabilities.v1',
536
+ template,
537
+ summary: templateMetadata.summary,
538
+ requiredApis: [...templateMetadata.requiredApis],
539
+ optionalModules: {
540
+ physics: false,
541
+ network: true,
542
+ multiplayer: false,
543
+ steam: false,
544
+ },
545
+ notes: [
546
+ 'Keep this file in sync with runtime API usage added in src/main.js.',
547
+ 'Treat missing APIs as scaffold contract mismatches, not silent fallbacks.',
548
+ ],
549
+ };
550
+ }
551
+
552
+ function renderProjectReadme({ name, projectTitle, template, templateMetadata }) {
553
+ const controls = templateMetadata.controls.map((line) => `- ${line}`).join('\n');
554
+ const firstEdits = templateMetadata.firstEdits.map((line) => `1. ${line}`).join('\n');
555
+
556
+ return `# ${projectTitle}
557
+
558
+ Scaffolded with \`aura create ${name} --template ${template}\`.
559
+
560
+ ## Quick Start
561
+
562
+ \`\`\`bash
563
+ npm install
564
+ npm run dev
565
+ \`\`\`
566
+
567
+ Optional commands:
568
+
569
+ \`\`\`bash
570
+ npm run build
571
+ npm run play
572
+ npm run publish
573
+ \`\`\`
574
+
575
+ ## Template Summary
576
+
577
+ ${templateMetadata.summary}
578
+
579
+ ## Controls
580
+
581
+ ${controls}
582
+
583
+ ## Project Map
584
+
585
+ - \`src/main.js\` - gameplay loop and rendering.
586
+ - \`src/starter-utils/\` - reusable helpers copied for non-blank templates.
587
+ - \`assets/starter/\` - starter asset pack and editable design payloads.
588
+ - \`aura.config.json\` - identity/window/build/modules.
589
+ - \`aura.capabilities.json\` - canonical runtime API declaration for this scaffold.
590
+ - \`RUNBOOK.md\` - first-hour implementation and triage checklist.
591
+
592
+ ## First Edits
593
+
594
+ ${firstEdits}
595
+ `;
596
+ }
597
+
598
+ function renderProjectRunbook({ projectTitle, template, templateMetadata }) {
599
+ const firstEdits = templateMetadata.firstEdits.map((line) => `1. ${line}`).join('\n');
600
+ const requiredApis = templateMetadata.requiredApis.map((entry) => `- \`${entry}\``).join('\n');
601
+
602
+ return `# ${projectTitle} Runbook
603
+
604
+ Template: \`${template}\`
605
+
606
+ ## First 30 Minutes
607
+
608
+ 1. Run \`npm install\` then \`npm run dev\` and confirm the starter loop is playable.
609
+ 1. Read \`src/main.js\` once end-to-end before editing prompts or logic.
610
+ 1. Review \`assets/starter/\` and replace placeholder payloads with game-specific values.
611
+
612
+ ## First-Hour Implementation Pass
613
+
614
+ ${firstEdits}
615
+
616
+ ## Capability Contract
617
+
618
+ The scaffold assumes the runtime APIs below:
619
+
620
+ ${requiredApis}
621
+
622
+ If any are unavailable at runtime, fail fast and capture the reason code before adding fallback behavior.
623
+
624
+ ## Build + Share
625
+
626
+ 1. \`npm run build\` to emit native/web artifacts.
627
+ 1. \`npm run play\` to package + execute with sibling assets.
628
+ 1. \`npm run publish\` once metadata and binaries are ready.
629
+ `;
630
+ }
631
+
632
+ function writeStarterAssetPack({ dest, projectTitle, template, templateMetadata }) {
633
+ const starterDir = join(dest, 'assets', 'starter');
634
+ mkdirSync(starterDir, { recursive: true });
635
+
636
+ writeFileSync(
637
+ join(starterDir, 'README.md'),
638
+ `# ${projectTitle} Starter Assets
639
+
640
+ Template: \`${template}\`
641
+
642
+ Use this folder as the first source of truth for seed content:
643
+
644
+ - Keep machine-readable balancing data in JSON files.
645
+ - Store design intent in markdown notes so agents can edit safely.
646
+ - Replace placeholders as soon as your core loop is stable.
647
+ `,
648
+ 'utf8',
649
+ );
650
+
651
+ writeFileSync(
652
+ join(starterDir, 'dev-notes.md'),
653
+ `# First Pass Notes
654
+
655
+ - Core loop hypothesis:
656
+ - Success metric for this week:
657
+ - Next three gameplay experiments:
658
+ `,
659
+ 'utf8',
660
+ );
661
+
662
+ const plan = templateMetadata.starterAssetPlan;
663
+ writeFileSync(
664
+ join(starterDir, plan.file),
665
+ JSON.stringify(
666
+ {
667
+ schema: 'aurajs.starter-asset-plan.v1',
668
+ title: plan.title,
669
+ template,
670
+ steps: plan.steps,
671
+ payload: plan.payload,
672
+ },
673
+ null,
674
+ 2,
675
+ ) + '\n',
676
+ 'utf8',
677
+ );
678
+ }
679
+
280
680
  function assertDestinationIsEmpty(name, dest) {
281
681
  if (existsSync(dest)) {
282
682
  const entries = readdirSync(dest);