@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
@@ -217,6 +217,399 @@ export const DEFAULT_PERF_SCENES = [
217
217
  };
218
218
  `,
219
219
  },
220
+ {
221
+ id: 'native_2d_flagship_stress',
222
+ lane: 'stage183-native-2d',
223
+ surfaceTags: [
224
+ 'tilemap-mutation',
225
+ 'tilemap-query',
226
+ 'render-targets',
227
+ 'draw2d-compositor',
228
+ 'particles',
229
+ 'runtime-inspector',
230
+ ],
231
+ frames: 360,
232
+ source: `
233
+ let image = null;
234
+ let mapId = 0;
235
+ let worldTarget = null;
236
+ let lightTarget = null;
237
+ let finalTarget = null;
238
+ let elapsed = 0;
239
+ let frameIndex = 0;
240
+ let canUseParticles = false;
241
+ let lastStats = null;
242
+ const agents = [];
243
+ const coverCells = [];
244
+ const fallbackParticles = [];
245
+ const brokenCover = new Set();
246
+
247
+ function keyForCell(x, y) {
248
+ return x + ':' + y;
249
+ }
250
+
251
+ function buildMap() {
252
+ const width = 48;
253
+ const height = 32;
254
+ const tileCount = width * height;
255
+ const ground = Array.from({ length: tileCount }, () => 1);
256
+ const cover = Array.from({ length: tileCount }, () => 0);
257
+ const hazards = Array.from({ length: tileCount }, () => 0);
258
+
259
+ for (let y = 4; y < height - 4; y += 4) {
260
+ for (let x = 6; x < width - 6; x += 6) {
261
+ const coverIndex = (y * width) + x;
262
+ cover[coverIndex] = 1;
263
+ coverCells.push({ x, y });
264
+
265
+ const neighborX = Math.min(width - 1, x + 1);
266
+ const neighborIndex = (y * width) + neighborX;
267
+ cover[neighborIndex] = 1;
268
+ coverCells.push({ x: neighborX, y });
269
+ }
270
+ }
271
+
272
+ for (let x = 3; x < width - 3; x += 5) {
273
+ const y = 14 + ((Math.floor(x / 5)) % 5);
274
+ const index = (y * width) + x;
275
+ hazards[index] = 1;
276
+ }
277
+
278
+ return aura.tilemap.import({
279
+ width,
280
+ height,
281
+ tilewidth: 16,
282
+ tileheight: 16,
283
+ solidLayerNames: ['cover', 'hazards'],
284
+ layers: [
285
+ { name: 'ground', type: 'tilelayer', width, height, data: ground },
286
+ { name: 'cover', type: 'tilelayer', width, height, data: cover, properties: { solid: true } },
287
+ { name: 'hazards', type: 'tilelayer', width, height, data: hazards, properties: { solid: true } },
288
+ ],
289
+ tilesets: [
290
+ { firstgid: 1, image: 'player.png', tilewidth: 16, tileheight: 16, tilecount: 1, columns: 1 },
291
+ ],
292
+ });
293
+ }
294
+
295
+ function seedAgents() {
296
+ for (let i = 0; i < 96; i += 1) {
297
+ agents.push({
298
+ x: 72 + ((i % 16) * 34),
299
+ y: 56 + (Math.floor(i / 16) * 28),
300
+ heading: (i * 0.37) % (Math.PI * 2),
301
+ speed: 26 + ((i % 7) * 4),
302
+ phase: i * 0.21,
303
+ radius: 6 + (i % 4),
304
+ hue: (i % 5) / 5,
305
+ });
306
+ }
307
+ }
308
+
309
+ function seedParticles() {
310
+ canUseParticles = !!(
311
+ aura.particles
312
+ && typeof aura.particles.emit === 'function'
313
+ && typeof aura.particles.update === 'function'
314
+ && typeof aura.particles.draw === 'function'
315
+ );
316
+
317
+ if (canUseParticles) {
318
+ for (let i = 0; i < 6; i += 1) {
319
+ const emitter = aura.particles.emit({
320
+ x: 92 + (i * 96),
321
+ y: 86 + ((i % 3) * 54),
322
+ count: 18,
323
+ life: 0.9,
324
+ size: 2,
325
+ speedMin: 16,
326
+ speedMax: 28,
327
+ direction: i * 0.31,
328
+ spread: 0.9,
329
+ loop: true,
330
+ rate: 24,
331
+ });
332
+ if (emitter?.emitterId && typeof aura.particles.setLayer === 'function') {
333
+ aura.particles.setLayer(emitter.emitterId, 2);
334
+ }
335
+ if (emitter?.emitterId && typeof aura.particles.setBlend === 'function') {
336
+ aura.particles.setBlend(emitter.emitterId, 'add');
337
+ }
338
+ }
339
+ return;
340
+ }
341
+
342
+ for (let i = 0; i < 96; i += 1) {
343
+ fallbackParticles.push({
344
+ originX: 84 + ((i % 12) * 44),
345
+ originY: 80 + (Math.floor(i / 12) * 22),
346
+ phase: i * 0.17,
347
+ radius: 4 + ((i % 5) * 2),
348
+ speed: 0.8 + ((i % 7) * 0.12),
349
+ });
350
+ }
351
+ }
352
+
353
+ function mutateTilemap() {
354
+ if (!mapId || !aura.tilemap || typeof aura.tilemap.setTile !== 'function') {
355
+ return;
356
+ }
357
+
358
+ const baseIndex = (frameIndex * 5) % coverCells.length;
359
+ for (let i = 0; i < 4; i += 1) {
360
+ const cell = coverCells[(baseIndex + (i * 11)) % coverCells.length];
361
+ const cellKey = keyForCell(cell.x, cell.y);
362
+ const shouldBreak = ((Math.floor(frameIndex / 18) + i) % 2) === 0;
363
+ if (shouldBreak && !brokenCover.has(cellKey)) {
364
+ aura.tilemap.setTile(mapId, 'cover', { x: cell.x, y: cell.y }, 0);
365
+ if (typeof aura.tilemap.setTileCollision === 'function') {
366
+ aura.tilemap.setTileCollision(mapId, 'cover', { x: cell.x, y: cell.y }, false);
367
+ }
368
+ brokenCover.add(cellKey);
369
+ continue;
370
+ }
371
+ if (!shouldBreak && brokenCover.has(cellKey)) {
372
+ aura.tilemap.setTile(mapId, 'cover', { x: cell.x, y: cell.y }, 1);
373
+ if (typeof aura.tilemap.setTileCollision === 'function') {
374
+ aura.tilemap.setTileCollision(mapId, 'cover', { x: cell.x, y: cell.y }, true);
375
+ }
376
+ brokenCover.delete(cellKey);
377
+ }
378
+ }
379
+
380
+ if ((frameIndex % 72) === 0 && typeof aura.tilemap.removeRegion === 'function') {
381
+ const column = 8 + (Math.floor(frameIndex / 72) % 8) * 4;
382
+ aura.tilemap.removeRegion(mapId, 'hazards', {
383
+ x: column,
384
+ y: 14,
385
+ w: 2,
386
+ h: 2,
387
+ });
388
+ }
389
+ }
390
+
391
+ function worldCamera() {
392
+ return {
393
+ x: 72 + ((Math.sin(elapsed * 0.6) + 1) * 76),
394
+ y: 42 + ((Math.cos(elapsed * 0.4) + 1) * 54),
395
+ width: 320,
396
+ height: 208,
397
+ };
398
+ }
399
+
400
+ function drawAgent(agent, options = {}) {
401
+ const width = 12 + (agent.radius * 2);
402
+ const height = 12 + (agent.radius * 2);
403
+ const tint = options.tint || aura.rgba(
404
+ 0.35 + (agent.hue * 0.45),
405
+ 0.75,
406
+ 1,
407
+ options.alpha ?? 0.92,
408
+ );
409
+
410
+ if (image) {
411
+ aura.draw2d.image(image, agent.x, agent.y, {
412
+ width,
413
+ height,
414
+ originX: 0.5,
415
+ originY: 0.5,
416
+ rotation: agent.heading,
417
+ tint,
418
+ alpha: options.alpha ?? 0.92,
419
+ });
420
+ return;
421
+ }
422
+
423
+ aura.draw2d.circle(agent.x, agent.y, agent.radius, tint);
424
+ }
425
+
426
+ function drawFallbackParticles() {
427
+ for (let i = 0; i < fallbackParticles.length; i += 1) {
428
+ const particle = fallbackParticles[i];
429
+ const theta = (elapsed * particle.speed * 2.2) + particle.phase;
430
+ aura.draw2d.circle(
431
+ particle.originX + Math.cos(theta) * particle.radius,
432
+ particle.originY + Math.sin(theta * 1.4) * particle.radius,
433
+ 2,
434
+ aura.rgba(0.78, 0.9, 1, 0.72),
435
+ );
436
+ }
437
+ }
438
+
439
+ function drawWorldStage() {
440
+ aura.draw2d.clear(2, 4, 8, 1);
441
+ aura.tilemap.draw(mapId, { camera: worldCamera() });
442
+
443
+ for (let i = 0; i < agents.length; i += 1) {
444
+ drawAgent(agents[i]);
445
+ }
446
+
447
+ if (canUseParticles) {
448
+ aura.particles.draw({ maxParticles: 768 });
449
+ } else {
450
+ drawFallbackParticles();
451
+ }
452
+ }
453
+
454
+ function drawLightStage(source) {
455
+ aura.draw2d.clear(0, 0, 0, 0);
456
+ aura.draw2d.sprite(source, 0, 0, { width: 384, height: 256 });
457
+ for (let i = 0; i < agents.length; i += 8) {
458
+ const agent = agents[i];
459
+ aura.draw2d.circleFill(
460
+ agent.x,
461
+ agent.y,
462
+ 18 + (Math.sin(elapsed * 3 + agent.phase) * 3),
463
+ aura.rgba(0.2, 0.85, 1, 0.12),
464
+ );
465
+ }
466
+ aura.draw2d.tileSprite(source, 0, 0, 384, 256, {
467
+ tileScaleX: 1.2,
468
+ tileScaleY: 1.2,
469
+ tileOffsetX: elapsed * 38,
470
+ tileOffsetY: elapsed * 22,
471
+ tint: aura.rgba(0.06, 0.12, 0.18, 0.24),
472
+ });
473
+ }
474
+
475
+ function drawFinalStage(worldSource, lightSource) {
476
+ aura.draw2d.clear(0, 0, 0, 0);
477
+ aura.draw2d.sprite(worldSource, 0, 0, { width: 384, height: 256 });
478
+ aura.draw2d.sprite(lightSource, 0, 0, { width: 384, height: 256, alpha: 0.94 });
479
+ aura.draw2d.rectFill(10, 10, 196, 28, aura.rgba(0.04, 0.07, 0.12, 0.84));
480
+ const particles = Number(lastStats?.phase2?.particles?.active || 0);
481
+ const tilemaps = Number(lastStats?.phase2?.tilemap?.active || 0);
482
+ const compositor = Number(lastStats?.draw2dRuntime?.compositor?.knownGraphCount || 0);
483
+ aura.draw2d.text(
484
+ '2D FLAGSHIP ' + particles + 'P ' + tilemaps + 'T ' + compositor + 'G',
485
+ 18,
486
+ 20,
487
+ { size: 12, color: aura.colors.white },
488
+ );
489
+ aura.draw2d.text(
490
+ 'frame ' + frameIndex + ' cover ' + brokenCover.size,
491
+ 18,
492
+ 34,
493
+ { size: 10, color: aura.colors.white },
494
+ );
495
+ }
496
+
497
+ aura.setup = function () {
498
+ image = aura.assets.image('player.png');
499
+ mapId = buildMap();
500
+ worldTarget = aura.draw2d.createRenderTarget(384, 256);
501
+ lightTarget = aura.draw2d.createRenderTarget(384, 256);
502
+ finalTarget = aura.draw2d.createRenderTarget(384, 256);
503
+ seedAgents();
504
+ seedParticles();
505
+ };
506
+
507
+ aura.update = function (dt) {
508
+ elapsed += dt;
509
+ frameIndex += 1;
510
+ mutateTilemap();
511
+ if (canUseParticles) {
512
+ aura.particles.update(dt);
513
+ }
514
+ lastStats = typeof aura.debug?.inspectorStats === 'function'
515
+ ? aura.debug.inspectorStats()
516
+ : null;
517
+
518
+ for (let i = 0; i < agents.length; i += 1) {
519
+ const agent = agents[i];
520
+ const theta = (elapsed * (0.9 + ((i % 5) * 0.14))) + agent.phase;
521
+ const dx = Math.cos(theta) * agent.speed * dt;
522
+ const dy = Math.sin(theta * 1.2) * agent.speed * dt;
523
+ const nextX = agent.x + dx;
524
+ const nextY = agent.y + dy;
525
+ const ahead = aura.tilemap.queryPoint(mapId, { x: nextX, y: nextY });
526
+ const sweep = aura.tilemap.queryAABB(mapId, {
527
+ x: nextX - agent.radius,
528
+ y: nextY - agent.radius,
529
+ w: agent.radius * 2,
530
+ h: agent.radius * 2,
531
+ });
532
+ const ray = aura.tilemap.queryRay(mapId, {
533
+ x: agent.x,
534
+ y: agent.y,
535
+ dx: Math.cos(agent.heading),
536
+ dy: Math.sin(agent.heading),
537
+ maxDistance: 22,
538
+ });
539
+
540
+ if (ahead?.hit || sweep?.hit || ray?.hit) {
541
+ agent.heading += 1.2 + ((i % 3) * 0.3);
542
+ } else {
543
+ agent.x = nextX;
544
+ agent.y = nextY;
545
+ agent.heading = theta;
546
+ }
547
+
548
+ if (agent.x < 24) agent.x = 24;
549
+ if (agent.y < 24) agent.y = 24;
550
+ if (agent.x > 740) agent.x = 740;
551
+ if (agent.y > 500) agent.y = 500;
552
+ }
553
+ };
554
+
555
+ aura.draw = function () {
556
+ aura.draw2d.clear(3, 5, 9);
557
+
558
+ let composed = finalTarget;
559
+
560
+ if (typeof aura.draw2d.runCompositorGraph === 'function') {
561
+ const graph = aura.draw2d.runCompositorGraph('perf-native-2d-flagship', [
562
+ { id: 'world', target: worldTarget, draw: drawWorldStage },
563
+ {
564
+ id: 'light',
565
+ target: lightTarget,
566
+ draw: () => drawLightStage({
567
+ type: 'compositorStage',
568
+ graph: 'perf-native-2d-flagship',
569
+ stage: 'world',
570
+ }),
571
+ },
572
+ {
573
+ id: 'final',
574
+ target: finalTarget,
575
+ draw: () => drawFinalStage(
576
+ {
577
+ type: 'compositorStage',
578
+ graph: 'perf-native-2d-flagship',
579
+ stage: 'world',
580
+ },
581
+ {
582
+ type: 'compositorStage',
583
+ graph: 'perf-native-2d-flagship',
584
+ stage: 'light',
585
+ },
586
+ ),
587
+ },
588
+ ]);
589
+ if (graph?.ok === true && Array.isArray(graph.stages) && graph.stages.length === 3) {
590
+ composed = graph.stages[2];
591
+ }
592
+ } else {
593
+ drawWorldStage();
594
+ return;
595
+ }
596
+
597
+ aura.draw2d.sprite(composed, 16, 16, {
598
+ width: 704,
599
+ height: 440,
600
+ });
601
+ aura.draw2d.sprite(composed, 744, 56, {
602
+ width: 256,
603
+ height: 160,
604
+ alpha: 0.88,
605
+ });
606
+ aura.draw2d.text('native_2d_flagship_stress', 748, 228, {
607
+ size: 11,
608
+ color: aura.colors.white,
609
+ });
610
+ };
611
+ `,
612
+ },
220
613
  {
221
614
  id: 'ui_composition_surface_stress',
222
615
  frames: 360,
@@ -910,16 +1303,24 @@ export async function runPerfBenchmarks(options = {}) {
910
1303
  const sceneReports = [];
911
1304
 
912
1305
  for (const scene of scenes) {
913
- const entryRelative = `src/${scene.id}.js`;
914
- const entryAbsolute = resolve(workspaceRoot, entryRelative);
915
- writeFileSync(entryAbsolute, scene.source.trimStart(), 'utf8');
1306
+ const hasInlineSource = typeof scene.source === 'string' && scene.source.trim().length > 0;
1307
+ const entryRelative = typeof scene.entryFile === 'string' && scene.entryFile.trim().length > 0
1308
+ ? scene.entryFile.trim()
1309
+ : `src/${scene.id}.js`;
1310
+ const sceneProjectRoot = hasInlineSource
1311
+ ? workspaceRoot
1312
+ : resolve(scene.projectRoot || projectRoot);
1313
+ if (hasInlineSource) {
1314
+ const entryAbsolute = resolve(workspaceRoot, entryRelative);
1315
+ writeFileSync(entryAbsolute, scene.source.trimStart(), 'utf8');
1316
+ }
916
1317
 
917
1318
  const samplesMs = [];
918
1319
 
919
1320
  for (let run = 0; run < repeats; run += 1) {
920
1321
  const started = process.hrtime.bigint();
921
1322
  await runHeadlessTest({
922
- projectRoot: workspaceRoot,
1323
+ projectRoot: sceneProjectRoot,
923
1324
  file: entryRelative,
924
1325
  frames: scene.frames,
925
1326
  width: scene.width ?? 1280,
@@ -945,6 +1346,9 @@ export async function runPerfBenchmarks(options = {}) {
945
1346
  id: scene.id,
946
1347
  scenario: scene.id,
947
1348
  assetMode,
1349
+ lane: typeof scene.lane === 'string' && scene.lane.trim().length > 0 ? scene.lane.trim() : null,
1350
+ surfaceTags: Array.isArray(scene.surfaceTags) ? [...scene.surfaceTags] : [],
1351
+ sourceMode: hasInlineSource ? 'synthetic-source' : 'authored-entry',
948
1352
  frames: scene.frames,
949
1353
  repeats,
950
1354
  frameTimeSamplesMs: frameTimes,