@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.
- package/README.md +4 -2
- package/benchmarks/perf-thresholds.json +27 -0
- package/package.json +2 -1
- package/src/build-contract.mjs +1644 -56
- package/src/cli.mjs +1048 -321
- package/src/conformance.mjs +356 -11
- package/src/cutscene.mjs +205 -0
- package/src/game-state-runtime.mjs +260 -0
- package/src/headless-test.mjs +92 -9
- package/src/perf-benchmark.mjs +103 -0
- package/src/scaffold.mjs +413 -13
- package/src/state-artifacts.mjs +321 -0
- package/src/state-dev-reload.mjs +120 -0
- package/templates/create/2d-survivor/aura.config.json +28 -0
- package/templates/create/2d-survivor/src/main.js +344 -0
- package/templates/create/3d-collectathon/aura.config.json +28 -0
- package/templates/create/3d-collectathon/src/main.js +367 -0
- package/templates/skills/aurajs/api-contract-3d.md +1 -1
- package/templates/skills/aurajs/api-contract.md +1 -1
- package/src/.gitkeep +0 -0
package/src/perf-benchmark.mjs
CHANGED
|
@@ -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
|
|
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:
|
|
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
|
-
|
|
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
|
|
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);
|