create-aura3d 1.0.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 (83) hide show
  1. package/dist/cli.d.ts +3 -0
  2. package/dist/cli.d.ts.map +1 -0
  3. package/dist/cli.js +27 -0
  4. package/dist/cli.js.map +1 -0
  5. package/dist/index.d.ts +16 -0
  6. package/dist/index.d.ts.map +1 -0
  7. package/dist/index.js +70 -0
  8. package/dist/index.js.map +1 -0
  9. package/package.json +55 -0
  10. package/templates/cartoon-channel/README.md +42 -0
  11. package/templates/cartoon-channel/index.html +13 -0
  12. package/templates/cartoon-channel/package.json +21 -0
  13. package/templates/cartoon-channel/playwright.config.ts +13 -0
  14. package/templates/cartoon-channel/src/aura-assets.ts +14 -0
  15. package/templates/cartoon-channel/src/characters.ts +28 -0
  16. package/templates/cartoon-channel/src/contract.ts +1 -0
  17. package/templates/cartoon-channel/src/episode.ts +300 -0
  18. package/templates/cartoon-channel/src/main.ts +322 -0
  19. package/templates/cartoon-channel/src/render-plan.ts +405 -0
  20. package/templates/cartoon-channel/src/sets.ts +14 -0
  21. package/templates/cartoon-channel/tests/route-health.spec.ts +7 -0
  22. package/templates/cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
  23. package/templates/cartoon-channel/tsconfig.json +12 -0
  24. package/templates/cinematic-scene/README.md +11 -0
  25. package/templates/cinematic-scene/index.html +13 -0
  26. package/templates/cinematic-scene/package.json +21 -0
  27. package/templates/cinematic-scene/playwright.config.ts +14 -0
  28. package/templates/cinematic-scene/public/aura-assets/hero-fixture.glb +0 -0
  29. package/templates/cinematic-scene/public/aura-assets/hero.thumb.svg +1 -0
  30. package/templates/cinematic-scene/src/aura-assets.ts +17 -0
  31. package/templates/cinematic-scene/src/main.ts +27 -0
  32. package/templates/cinematic-scene/tests/route-health.spec.ts +16 -0
  33. package/templates/cinematic-scene/tests/screenshot.spec.ts +56 -0
  34. package/templates/cinematic-scene/tsconfig.json +15 -0
  35. package/templates/fighting-game/README.md +64 -0
  36. package/templates/fighting-game/index.html +13 -0
  37. package/templates/fighting-game/package.json +21 -0
  38. package/templates/fighting-game/playwright.config.ts +12 -0
  39. package/templates/fighting-game/src/aura-assets.ts +11 -0
  40. package/templates/fighting-game/src/game/fighters.ts +152 -0
  41. package/templates/fighting-game/src/game/moves.ts +70 -0
  42. package/templates/fighting-game/src/game/stage.ts +98 -0
  43. package/templates/fighting-game/src/main.ts +417 -0
  44. package/templates/fighting-game/src/styles.css +124 -0
  45. package/templates/fighting-game/tests/gameplay-smoke.spec.ts +23 -0
  46. package/templates/fighting-game/tests/route-health.spec.ts +11 -0
  47. package/templates/fighting-game/tsconfig.json +19 -0
  48. package/templates/mini-game/README.md +10 -0
  49. package/templates/mini-game/index.html +13 -0
  50. package/templates/mini-game/package.json +21 -0
  51. package/templates/mini-game/playwright.config.ts +14 -0
  52. package/templates/mini-game/public/aura-assets/player-fixture.glb +0 -0
  53. package/templates/mini-game/public/aura-assets/player.thumb.svg +8 -0
  54. package/templates/mini-game/src/aura-assets.ts +17 -0
  55. package/templates/mini-game/src/main.ts +27 -0
  56. package/templates/mini-game/tests/route-health.spec.ts +16 -0
  57. package/templates/mini-game/tests/screenshot.spec.ts +63 -0
  58. package/templates/mini-game/tsconfig.json +15 -0
  59. package/templates/product-viewer/README.md +16 -0
  60. package/templates/product-viewer/index.html +16 -0
  61. package/templates/product-viewer/package.json +21 -0
  62. package/templates/product-viewer/playwright.config.ts +14 -0
  63. package/templates/product-viewer/public/aura-assets/product-fixture.glb +0 -0
  64. package/templates/product-viewer/public/aura-assets/product.thumb.svg +1 -0
  65. package/templates/product-viewer/src/aura-assets.ts +17 -0
  66. package/templates/product-viewer/src/main.ts +27 -0
  67. package/templates/product-viewer/tests/route-health.spec.ts +16 -0
  68. package/templates/product-viewer/tests/screenshot.spec.ts +60 -0
  69. package/templates/product-viewer/tsconfig.json +15 -0
  70. package/templates/prompt-cartoon-channel/README.md +42 -0
  71. package/templates/prompt-cartoon-channel/index.html +13 -0
  72. package/templates/prompt-cartoon-channel/package.json +21 -0
  73. package/templates/prompt-cartoon-channel/playwright.config.ts +13 -0
  74. package/templates/prompt-cartoon-channel/src/aura-assets.ts +14 -0
  75. package/templates/prompt-cartoon-channel/src/characters.ts +28 -0
  76. package/templates/prompt-cartoon-channel/src/contract.ts +1 -0
  77. package/templates/prompt-cartoon-channel/src/episode.ts +300 -0
  78. package/templates/prompt-cartoon-channel/src/main.ts +314 -0
  79. package/templates/prompt-cartoon-channel/src/render-plan.ts +405 -0
  80. package/templates/prompt-cartoon-channel/src/sets.ts +14 -0
  81. package/templates/prompt-cartoon-channel/tests/route-health.spec.ts +7 -0
  82. package/templates/prompt-cartoon-channel/tests/storyboard-playback.spec.ts +74 -0
  83. package/templates/prompt-cartoon-channel/tsconfig.json +12 -0
@@ -0,0 +1,417 @@
1
+ import { camera, createAuraApp, effects, game, lights, scene, ui } from "@aura3d/engine";
2
+ import { assets } from "./aura-assets";
3
+ import {
4
+ animationLayer,
5
+ createFighterAnimationController,
6
+ createFighterNode,
7
+ isLoopingFighterClip,
8
+ publicAssetInstructions,
9
+ REQUIRED_FIGHTER_ASSETS,
10
+ resolveTypedFighterAssets,
11
+ type FighterClip
12
+ } from "./game/fighters";
13
+ import { heavyMove, lightMove, specialMove } from "./game/moves";
14
+ import {
15
+ createFighterColliders,
16
+ createTouchLayout,
17
+ fightingControls,
18
+ fightingRouteReadiness,
19
+ fightingStage,
20
+ fightingStageBounds,
21
+ fightingStageIssues
22
+ } from "./game/stage";
23
+ import "./styles.css";
24
+
25
+ type Aura3DGameWindow = Window & {
26
+ __AURA3D_GAME_DEBUG__?: unknown;
27
+ __AURA3D_GAME_EVIDENCE__?: unknown;
28
+ __AURA3D_GAME_REPLAY__?: unknown;
29
+ __AURA3D_GAME_SOURCE__?: unknown;
30
+ };
31
+
32
+ const gameWindow = window as Aura3DGameWindow;
33
+ const { typedFighterAssets, missingFighterAssets, typedFighterAssetCount } = resolveTypedFighterAssets(assets);
34
+ const mediaMatches = (query: string) => typeof window.matchMedia === "function" && window.matchMedia(query).matches;
35
+ const reducedMotion = mediaMatches("(prefers-reduced-motion: reduce)");
36
+ const highContrast = mediaMatches("(prefers-contrast: more)");
37
+ const reducedFlash = reducedMotion;
38
+
39
+ const playerStart = [-0.9, 0, 0] as const;
40
+ const rivalStart = [0.9, 0, 0] as const;
41
+ const stageWarnings = fightingStageIssues.map((issue) => issue.message);
42
+ const touchLayout = createTouchLayout(window.innerWidth, window.innerHeight);
43
+
44
+ ui.html(
45
+ "#hud",
46
+ `
47
+ <section class="hud__panel hud__panel--status" aria-label="Round status">
48
+ <p class="hud__eyebrow">Public Aura3D game runtime starter</p>
49
+ <h1>Aura3D Fighting Game Runtime</h1>
50
+ <div class="hud__bars" aria-label="Health and meter">
51
+ <span class="hud__bar"><b>Player</b><span id="hud-player-health">100 HP</span></span>
52
+ <span class="hud__bar"><b>Rival</b><span id="hud-rival-health">100 HP</span></span>
53
+ <span class="hud__bar hud__bar--meter"><b>Meter</b><span id="hud-player-meter">0%</span></span>
54
+ </div>
55
+ </section>
56
+ <section class="hud__panel hud__panel--meta" aria-label="Runtime evidence">
57
+ <span id="hud-round">Round 1 - 99</span>
58
+ <span id="hud-frame">Frame 0</span>
59
+ <span id="hud-camera">Camera zoom 1.00</span>
60
+ <span id="hud-assets">Typed assets ${typedFighterAssetCount}/2</span>
61
+ <span id="hud-stage">Stage ${fightingStage.id}</span>
62
+ <span id="hud-replay">Replay ready</span>
63
+ </section>
64
+ <section id="hud-controls" class="hud__panel hud__panel--controls" aria-label="Controls">
65
+ <button id="hud-replay-button" type="button">Run replay</button>
66
+ <button id="hud-pause-button" type="button" aria-pressed="false">Pause</button>
67
+ <span class="hud__help">Move A/D - Jump W/Space - Guard Q - Dash Shift - J/K/L attacks</span>
68
+ </section>
69
+ `
70
+ );
71
+
72
+ const hudPlayerHealth = ui.text("#hud-player-health");
73
+ const hudRivalHealth = ui.text("#hud-rival-health");
74
+ const hudPlayerMeter = ui.text("#hud-player-meter");
75
+ const hudRound = ui.text("#hud-round");
76
+ const hudFrame = ui.text("#hud-frame");
77
+ const hudCamera = ui.text("#hud-camera");
78
+ const hudAssets = ui.text("#hud-assets");
79
+ const hudStage = ui.text("#hud-stage");
80
+ const hudReplay = ui.text("#hud-replay");
81
+ const replayButton = ui.button("#hud-replay-button");
82
+ const pauseButton = ui.button("#hud-pause-button");
83
+
84
+ const hudBindings = game.hud.bindings([
85
+ game.hud.health({ actorId: "player", label: "Player health", a11yLabel: "player health" }),
86
+ game.hud.health({ actorId: "rival", label: "Rival health", a11yLabel: "rival health" }),
87
+ game.hud.meter({ actorId: "player", label: "Player meter", a11yLabel: "player special meter" }),
88
+ game.hud.timer({ label: "Round timer", valuePath: "round.timeRemaining", a11yLabel: "round timer" }),
89
+ game.hud.combo({ actorId: "player", label: "Player combo", valuePath: "combat.player.combo", a11yLabel: "player combo" }),
90
+ game.hud.round({ label: "Round", valuePath: "round.index", a11yLabel: "round index" }),
91
+ game.hud.debugToggle({ label: "Runtime evidence", action: "debug", statePath: "debug.visible" })
92
+ ]);
93
+
94
+ const accessibilitySources = [
95
+ game.accessibility.label({
96
+ targetId: "hud",
97
+ label: "Live fighting game HUD with health, timer, stage, assets, and replay status.",
98
+ live: true
99
+ }),
100
+ game.accessibility.focus({
101
+ scopeId: "hud-controls",
102
+ label: "HUD controls",
103
+ targets: ["#hud-replay-button", "#hud-pause-button"]
104
+ }),
105
+ game.accessibility.reducedMotion({ enabled: reducedMotion }),
106
+ game.accessibility.reducedFlash({ enabled: reducedFlash }),
107
+ game.accessibility.highContrast({ enabled: highContrast }),
108
+ game.accessibility.pauseControls({
109
+ actions: ["pause", "Escape"],
110
+ resumeActions: ["pause", "Enter"],
111
+ menuId: "hud-controls"
112
+ })
113
+ ];
114
+
115
+ const arena = scene()
116
+ .background("#10071c")
117
+ .addMany(fightingStage.nodes)
118
+ .add(createFighterNode("player", "playerFighter", "Player fighter", playerStart, 1, "#45f5bb", typedFighterAssets))
119
+ .add(createFighterNode("rival", "rivalFighter", "Rival fighter", rivalStart, -1, "#ffca5f", typedFighterAssets))
120
+ .addMany([
121
+ effects.bloom({ intensity: 0.32 }),
122
+ lights.studio({ intensity: 1.15 }),
123
+ lights.directional({ name: "rim light", color: "#80ffd4", intensity: 0.7 }).position(0, 4, 3),
124
+ camera
125
+ .perspective({ position: [0, 1.75, 5.8], target: [0, 0.85, 0], fov: 42 })
126
+ ]);
127
+
128
+ const app = createAuraApp("#app", {
129
+ diagnostics: { overlay: true, performancePanel: true },
130
+ scene: arena
131
+ });
132
+
133
+ const inputOptions = {
134
+ actions: {
135
+ ...fightingControls.actions,
136
+ guard: ["KeyQ", "GamepadLB"],
137
+ heavy: ["KeyK", "GamepadY"],
138
+ pause: ["Escape", "GamepadStart"]
139
+ },
140
+ axes: fightingControls.axes,
141
+ bufferMs: 150,
142
+ gamepad: true
143
+ } as const;
144
+
145
+ const input = app.input(inputOptions);
146
+ const replayInput = game.input({ ...inputOptions, autoListen: false, gamepad: false });
147
+ const openingReplay = game.inputReplay(
148
+ [
149
+ { frame: 2, time: 2 / 60, type: "press", binding: "KeyD" },
150
+ { frame: 16, time: 16 / 60, type: "press", binding: "KeyL" },
151
+ { frame: 20, time: 20 / 60, type: "release", binding: "KeyL" },
152
+ { frame: 32, time: 32 / 60, type: "release", binding: "KeyD" },
153
+ { frame: 44, time: 44 / 60, type: "press", binding: "KeyJ" },
154
+ { frame: 48, time: 48 / 60, type: "release", binding: "KeyJ" }
155
+ ],
156
+ { label: "starter approach-special replay", fps: 60, seed: 105 }
157
+ );
158
+ const replayDriver = game.inputReplayDriver(replayInput, openingReplay);
159
+
160
+ const playerBody = game.kinematicBody({
161
+ id: "player",
162
+ position: playerStart,
163
+ bounds: fightingStageBounds,
164
+ groundY: 0,
165
+ coyoteMs: 100,
166
+ jumpBufferMs: 120
167
+ });
168
+ const rivalBody = game.kinematicBody({
169
+ id: "rival",
170
+ position: rivalStart,
171
+ bounds: fightingStageBounds,
172
+ groundY: 0
173
+ });
174
+ const playerJumpAssist = game.jumpAssist({ coyoteMs: 100, bufferMs: 120 });
175
+ const combat = game.combatWorld();
176
+ const runtimeEffects = game.effects({ poolSize: 64, reducedMotion, reducedFlash });
177
+ const director = game.cameraDirector({
178
+ stageBounds: { minX: fightingStage.combatBounds.minX, maxX: fightingStage.combatBounds.maxX },
179
+ reducedMotion
180
+ });
181
+ const playerNode = app.nodes.require("player");
182
+ const rivalNode = app.nodes.require("rival");
183
+ const playerAnimation = createFighterAnimationController("player", typedFighterAssets.playerFighter);
184
+ const rivalAnimation = createFighterAnimationController("rival", typedFighterAssets.rivalFighter);
185
+
186
+ playerAnimation.bindRuntimeNode(playerNode, { defaultClipId: "idle", fallbackClipId: "idle" });
187
+ rivalAnimation.bindRuntimeNode(rivalNode, { defaultClipId: "idle", fallbackClipId: "idle" });
188
+ playerAnimation.play("idle", { restart: true, loop: "loop" });
189
+ rivalAnimation.play("idle", { restart: true, loop: "loop" });
190
+
191
+ combat.addActor({ id: "player", team: "player", position: playerBody.position, facing: 1 });
192
+ combat.addActor({ id: "rival", team: "rival", position: rivalBody.position, facing: -1 });
193
+
194
+ gameWindow.__AURA3D_GAME_SOURCE__ = {
195
+ route: window.location.pathname,
196
+ template: "fighting-game",
197
+ package: "create-aura3d",
198
+ publicEngineApi: true,
199
+ typedAssetPattern: "src/aura-assets.ts",
200
+ typedAssetKeys: REQUIRED_FIGHTER_ASSETS,
201
+ missingAssets: missingFighterAssets,
202
+ addAssets: publicAssetInstructions,
203
+ touchControls: touchLayout,
204
+ readiness: fightingRouteReadiness
205
+ };
206
+
207
+ let aiCooldown = 0;
208
+ let roundTime = 99;
209
+ let replayActive = false;
210
+ let paused = false;
211
+ let replayHitCount = 0;
212
+ let totalHitCount = 0;
213
+ let playerClip: FighterClip = "idle";
214
+ let rivalClip: FighterClip = "idle";
215
+
216
+ ui.onClick(replayButton, () => {
217
+ replayActive = true;
218
+ replayHitCount = 0;
219
+ replayDriver.reset();
220
+ replayInput.clearReplay();
221
+ ui.setText(hudReplay, `Replay ${openingReplay.checksum} running`);
222
+ });
223
+
224
+ ui.onClick(pauseButton, () => setPaused(!paused));
225
+
226
+ app.onFrame(({ dt }) => {
227
+ const activeInput = replayActive ? replayInput : input;
228
+ if (replayActive) {
229
+ replayDriver.step(dt);
230
+ if (replayDriver.snapshot().complete) replayActive = false;
231
+ } else {
232
+ input.update(dt);
233
+ }
234
+
235
+ if (activeInput.pressed("pause")) setPaused(!paused);
236
+
237
+ roundTime = Math.max(0, roundTime - dt);
238
+ const moveX = activeInput.axis("moveX");
239
+ playerBody.move(moveX);
240
+
241
+ const jumpAssist = playerJumpAssist.update(dt, {
242
+ grounded: playerBody.grounded,
243
+ jumpPressed: activeInput.pressed("jump")
244
+ });
245
+ if (jumpAssist.canJump && jumpAssist.jumpBuffered && playerJumpAssist.consume()) playerBody.jump();
246
+
247
+ if (activeInput.pressed("dash")) {
248
+ playerBody.dash([playerBody.facing, 0, 0], 8);
249
+ runtimeEffects.dashTrail(playerBody.position, { ownerId: "player", intensity: 0.55 });
250
+ }
251
+
252
+ aiCooldown -= dt;
253
+ const distance = playerBody.position[0] - rivalBody.position[0];
254
+ const rivalFacing = directionToPlayer();
255
+ rivalBody.move(Math.abs(distance) > 1.45 ? rivalFacing * 0.65 : -rivalFacing * 0.18);
256
+ if (aiCooldown <= 0 && Math.abs(distance) < 1.3) {
257
+ aiCooldown = 0.72;
258
+ combat.beginAttack("rival", heavyMove("rival-heavy", rivalFacing));
259
+ }
260
+
261
+ const liveSpecialCombo = input.combo(["light", "heavy", "special"], 650);
262
+ const replaySpecialCombo = replayInput.combo(["light", "heavy", "special"], 650);
263
+ const playerAttack = activeInput.pressed("heavy")
264
+ ? heavyMove("player-heavy", playerBody.facing)
265
+ : activeInput.pressed("special") || liveSpecialCombo || replaySpecialCombo
266
+ ? specialMove("player-special", playerBody.facing)
267
+ : activeInput.pressed("light")
268
+ ? lightMove("player-light", playerBody.facing)
269
+ : undefined;
270
+ if (playerAttack) {
271
+ combat.beginAttack("player", playerAttack);
272
+ if (playerAttack.id.includes("special")) runtimeEffects.auraBurst(playerBody.position, { ownerId: "player", intensity: 0.7 });
273
+ }
274
+
275
+ playerBody.update(dt);
276
+ rivalBody.update(dt);
277
+ combat.setActor("player", { position: playerBody.position, facing: playerBody.facing, guarding: activeInput.held("guard") });
278
+ combat.setActor("rival", { position: rivalBody.position, facing: rivalFacing, guarding: aiCooldown > 0.5 });
279
+ combat.update(dt);
280
+
281
+ const combatEvents = combat.consumeEvents();
282
+ for (const event of combatEvents) {
283
+ if (event.type === "hit" || event.type === "blocked") {
284
+ const spark = event.type === "hit" ? runtimeEffects.hitSpark : runtimeEffects.blockSpark;
285
+ spark(event.position, { ownerId: event.attackerId, intensity: event.type === "hit" ? 1.1 : 0.65 });
286
+ director.impact(event.type === "hit" ? 0.42 : 0.18, 0.16);
287
+ if (event.type === "hit") {
288
+ totalHitCount += 1;
289
+ if (replayActive || replayDriver.snapshot().frame > 0) replayHitCount += 1;
290
+ }
291
+ if (event.targetId === "player") playerBody.applyKnockback([event.attackerId === "rival" ? -2.2 : 2.2, 1.5, 0]);
292
+ if (event.targetId === "rival") rivalBody.applyKnockback([event.attackerId === "player" ? 2.2 : -2.2, 1.5, 0]);
293
+ }
294
+ }
295
+
296
+ runtimeEffects.update(dt);
297
+ const cameraFrame = director.update(dt, [
298
+ { id: "player", position: playerBody.position },
299
+ { id: "rival", position: rivalBody.position }
300
+ ]);
301
+
302
+ playerClip = activeInput.held("guard")
303
+ ? "guard"
304
+ : playerAttack
305
+ ? playerAttack.id.includes("special")
306
+ ? "special"
307
+ : playerAttack.id.includes("heavy")
308
+ ? "heavy"
309
+ : "light"
310
+ : !playerBody.grounded
311
+ ? "jump"
312
+ : Math.abs(moveX) > 0.05
313
+ ? "walk"
314
+ : "idle";
315
+ rivalClip = aiCooldown > 0.5 ? "heavy" : !rivalBody.grounded ? "jump" : "idle";
316
+
317
+ syncFighterAnimation(playerAnimation, playerClip, dt);
318
+ syncFighterAnimation(rivalAnimation, rivalClip, dt);
319
+
320
+ playerNode
321
+ .setPosition(playerBody.position[0], playerBody.position[1], playerBody.position[2])
322
+ .setRotation(0, playerBody.facing < 0 ? Math.PI : 0, 0);
323
+ rivalNode
324
+ .setPosition(rivalBody.position[0], rivalBody.position[1], rivalBody.position[2])
325
+ .setRotation(0, rivalFacing < 0 ? Math.PI : 0, 0);
326
+
327
+ const snapshot = combat.snapshot();
328
+ const player = snapshot.actors.find((actor) => actor.id === "player");
329
+ const rival = snapshot.actors.find((actor) => actor.id === "rival");
330
+ if (player && rival) {
331
+ ui.setText(hudPlayerHealth, `${Math.round(player.health)} HP`);
332
+ ui.setText(hudRivalHealth, `${Math.round(rival.health)} HP`);
333
+ ui.setText(hudPlayerMeter, `${Math.round(player.meter)}%`);
334
+ ui.setText(hudRound, `Round 1 - ${Math.ceil(roundTime)}`);
335
+ ui.setText(hudFrame, `Frame ${app.runtime.frame}`);
336
+ ui.setText(hudCamera, `Camera zoom ${cameraFrame.zoom.toFixed(2)}`);
337
+ ui.setText(
338
+ hudAssets,
339
+ missingFighterAssets.length === 0
340
+ ? "Typed assets ready"
341
+ : `Source placeholders: add ${missingFighterAssets.join(", ")}`
342
+ );
343
+ ui.setText(hudStage, stageWarnings.length === 0 ? `Stage ${fightingStage.id} ready` : `Stage warnings ${stageWarnings.length}`);
344
+ ui.setText(
345
+ hudReplay,
346
+ replayActive
347
+ ? `Replay ${replayDriver.snapshot().frame}/${openingReplay.frameCount}`
348
+ : `Replay ${openingReplay.checksum} hits ${replayHitCount}`
349
+ );
350
+ }
351
+
352
+ const colliders = createFighterColliders(playerBody.position, rivalBody.position);
353
+ const animationSnapshots = [playerAnimation.snapshot(), rivalAnimation.snapshot()];
354
+ const activeClips = animationSnapshots.map((animation) => animation.activeClipId ?? "idle");
355
+
356
+ gameWindow.__AURA3D_GAME_REPLAY__ = {
357
+ plan: openingReplay,
358
+ driver: replayDriver.snapshot(),
359
+ hitCount: replayHitCount,
360
+ totalHitCount,
361
+ liveInputEvents: input.recorded()
362
+ };
363
+ gameWindow.__AURA3D_GAME_DEBUG__ = game.debug.overlay({
364
+ runtime: app.runtime,
365
+ input: activeInput,
366
+ bodies: [playerBody, rivalBody],
367
+ combat,
368
+ effects: runtimeEffects,
369
+ camera: director,
370
+ colliders,
371
+ warnings: stageWarnings
372
+ });
373
+ gameWindow.__AURA3D_GAME_EVIDENCE__ = app.evidence({
374
+ input: activeInput,
375
+ bodies: [playerBody, rivalBody],
376
+ combat,
377
+ effects: runtimeEffects,
378
+ camera: director,
379
+ animation: { controllers: animationSnapshots.length, activeClips, eventCount: runtimeEffects.snapshot().spawned },
380
+ assets: { typedAssets: typedFighterAssetCount, missingAssets: missingFighterAssets },
381
+ stage: { id: fightingStage.id, safeZones: true, bounds: fightingStage.combatBounds, warnings: stageWarnings },
382
+ hud: hudBindings,
383
+ accessibility: accessibilitySources,
384
+ source: {
385
+ mode: "mounted-runtime",
386
+ expectsGame: true,
387
+ label: "create-aura3d fighting-game scaffold"
388
+ }
389
+ });
390
+ });
391
+
392
+ function directionToPlayer(): 1 | -1 {
393
+ return playerBody.position[0] >= rivalBody.position[0] ? 1 : -1;
394
+ }
395
+
396
+ function setPaused(next: boolean): void {
397
+ paused = next;
398
+ ui.setPressed(pauseButton, paused);
399
+ ui.setText(pauseButton, paused ? "Resume" : "Pause");
400
+ if (paused) app.pause();
401
+ else app.resume();
402
+ }
403
+
404
+ function syncFighterAnimation(controller: ReturnType<typeof createFighterAnimationController>, clip: FighterClip, dt: number): void {
405
+ const current = controller.snapshot().activeClipId;
406
+ const loop = isLoopingFighterClip(clip) ? "loop" : false;
407
+ const layer = animationLayer(clip);
408
+ if (current !== clip) {
409
+ controller.crossFade(clip, 0.08, {
410
+ restart: !isLoopingFighterClip(clip),
411
+ loop,
412
+ layer,
413
+ attack: layer === "upper-body"
414
+ });
415
+ }
416
+ controller.update(dt);
417
+ }
@@ -0,0 +1,124 @@
1
+ @font-face {
2
+ font-family: "AuraFight";
3
+ src: local("Impact");
4
+ }
5
+
6
+ html,
7
+ body,
8
+ #app {
9
+ width: 100%;
10
+ height: 100%;
11
+ margin: 0;
12
+ overflow: hidden;
13
+ background:
14
+ radial-gradient(circle at 50% 8%, rgba(0, 255, 191, 0.2), transparent 36%),
15
+ linear-gradient(135deg, #020705, #071d14 55%, #020504);
16
+ color: #eafff5;
17
+ font-family: AuraFight, "Arial Narrow", sans-serif;
18
+ }
19
+
20
+ canvas {
21
+ display: block;
22
+ }
23
+
24
+ .hud {
25
+ position: fixed;
26
+ inset: auto 18px 18px 18px;
27
+ display: grid;
28
+ grid-template-columns: minmax(260px, 1.1fr) minmax(210px, 0.8fr) minmax(260px, 1fr);
29
+ gap: 10px;
30
+ pointer-events: none;
31
+ letter-spacing: 0.02em;
32
+ }
33
+
34
+ .hud__panel {
35
+ border: 1px solid rgba(71, 255, 190, 0.36);
36
+ border-radius: 18px;
37
+ background: linear-gradient(145deg, rgba(2, 9, 6, 0.82), rgba(9, 31, 23, 0.68));
38
+ box-shadow: 0 0 36px rgba(0, 255, 191, 0.14), inset 0 0 24px rgba(85, 255, 197, 0.05);
39
+ backdrop-filter: blur(14px);
40
+ pointer-events: auto;
41
+ padding: 12px 14px;
42
+ }
43
+
44
+ .hud__eyebrow {
45
+ margin: 0 0 4px;
46
+ color: #98ffdb;
47
+ font-size: 0.72rem;
48
+ letter-spacing: 0.14em;
49
+ text-transform: uppercase;
50
+ }
51
+
52
+ .hud h1 {
53
+ margin: 0 0 10px;
54
+ color: #eafff5;
55
+ font-size: clamp(1.2rem, 2.6vw, 2rem);
56
+ line-height: 0.95;
57
+ text-transform: uppercase;
58
+ }
59
+
60
+ .hud__bars,
61
+ .hud__panel--meta,
62
+ .hud__panel--controls {
63
+ display: flex;
64
+ gap: 8px;
65
+ align-items: center;
66
+ flex-wrap: wrap;
67
+ }
68
+
69
+ .hud__bar,
70
+ .hud__panel--meta span,
71
+ .hud__help {
72
+ display: inline-flex;
73
+ gap: 8px;
74
+ align-items: center;
75
+ min-height: 28px;
76
+ padding: 5px 9px;
77
+ border-radius: 999px;
78
+ background: rgba(6, 24, 17, 0.72);
79
+ color: #d8fff0;
80
+ font-size: 0.85rem;
81
+ }
82
+
83
+ .hud__bar b {
84
+ color: #55ffc5;
85
+ }
86
+
87
+ .hud__bar--meter b {
88
+ color: #ffd166;
89
+ }
90
+
91
+ .hud button {
92
+ min-height: 34px;
93
+ padding: 0 12px;
94
+ border: 1px solid rgba(85, 255, 197, 0.42);
95
+ border-radius: 999px;
96
+ background: #103726;
97
+ color: #eafff5;
98
+ cursor: pointer;
99
+ font: inherit;
100
+ text-transform: uppercase;
101
+ }
102
+
103
+ .hud button:hover,
104
+ .hud button:focus-visible {
105
+ outline: 2px solid #55ffc5;
106
+ outline-offset: 2px;
107
+ background: #155039;
108
+ }
109
+
110
+ .hud button[aria-pressed="true"] {
111
+ border-color: #ffd166;
112
+ background: #4c3410;
113
+ }
114
+
115
+ @media (max-width: 720px) {
116
+ .hud {
117
+ grid-template-columns: 1fr;
118
+ inset: auto 12px 12px 12px;
119
+ }
120
+
121
+ .hud__panel {
122
+ border-radius: 14px;
123
+ }
124
+ }
@@ -0,0 +1,23 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("input replay produces runtime evidence and a hit declaration", async ({ page }) => {
4
+ await page.goto("/");
5
+ await page.click("#hud-replay-button");
6
+ await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_EVIDENCE__?.systems?.inputPlan));
7
+ await page.waitForFunction(() => ((window as any).__AURA3D_GAME_REPLAY__?.hitCount ?? 0) > 0);
8
+ const evidence = await page.evaluate(() => (window as any).__AURA3D_GAME_EVIDENCE__);
9
+ const replay = await page.evaluate(() => (window as any).__AURA3D_GAME_REPLAY__);
10
+ const source = await page.evaluate(() => (window as any).__AURA3D_GAME_SOURCE__);
11
+
12
+ expect(evidence.systems.mutableNodes).toBeTruthy();
13
+ expect(evidence.systems.inputPlan).toBeTruthy();
14
+ expect(evidence.systems.physicsPlan).toBeTruthy();
15
+ expect(evidence.systems.collisionPlan).toBeTruthy();
16
+ expect(evidence.systems.animationPlan).toBeTruthy();
17
+ expect(evidence.systems.effectsPlan).toBeTruthy();
18
+ expect(evidence.systems.cameraPlan).toBeTruthy();
19
+ expect(evidence.systems.stagePlan).toBeTruthy();
20
+ expect(replay.hitCount).toBeGreaterThan(0);
21
+ expect(source.readiness.sourceOnly).toBe(true);
22
+ expect(source.readiness.publicEngineApis).toContain("games.fighting.stagePreset");
23
+ });
@@ -0,0 +1,11 @@
1
+ import { expect, test } from "@playwright/test";
2
+
3
+ test("fighting-game route loads", async ({ page }) => {
4
+ await page.goto("/");
5
+ await expect(page.locator("#app")).toBeVisible();
6
+ await expect(page.locator("#hud")).toContainText("Aura3D Fighting Game Runtime");
7
+ await page.waitForFunction(() => Boolean((window as any).__AURA3D_GAME_SOURCE__?.readiness));
8
+ const source = await page.evaluate(() => (window as any).__AURA3D_GAME_SOURCE__);
9
+ expect(source.readiness.route).toBe("/");
10
+ expect(source.readiness.buildDeclarations.routeHealthSpec).toBe("tests/route-health.spec.ts");
11
+ });
@@ -0,0 +1,19 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "allowJs": false,
8
+ "skipLibCheck": true,
9
+ "esModuleInterop": true,
10
+ "allowSyntheticDefaultImports": true,
11
+ "strict": true,
12
+ "forceConsistentCasingInFileNames": true,
13
+ "moduleResolution": "Bundler",
14
+ "resolveJsonModule": true,
15
+ "isolatedModules": true,
16
+ "noEmit": true
17
+ },
18
+ "include": ["src", "tests"]
19
+ }
@@ -0,0 +1,10 @@
1
+ # Aura3D Mini Game
2
+
3
+ Small interactive scene starter for agent-authored controls, movement, and route
4
+ health checks.
5
+
6
+ ```bash
7
+ npm install
8
+ npm run dev
9
+ npm run test
10
+ ```
@@ -0,0 +1,13 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Aura3D Mini Game</title>
7
+ <style>html, body, #app { margin: 0; width: 100%; height: 100%; background: #071015; }</style>
8
+ </head>
9
+ <body>
10
+ <div id="app"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aura3d-mini-game",
3
+ "version": "1.0.0",
4
+ "private": true,
5
+ "type": "module",
6
+ "scripts": {
7
+ "dev": "vite --host 127.0.0.1",
8
+ "typecheck": "tsc --noEmit",
9
+ "build": "npm run typecheck && vite build",
10
+ "preview": "vite preview --host 127.0.0.1",
11
+ "test": "playwright test tests/route-health.spec.ts tests/screenshot.spec.ts --workers=1"
12
+ },
13
+ "dependencies": {
14
+ "@aura3d/engine": "1.0.5"
15
+ },
16
+ "devDependencies": {
17
+ "@playwright/test": "^1.52.0",
18
+ "typescript": "^5.8.3",
19
+ "vite": "^7.3.2"
20
+ }
21
+ }
@@ -0,0 +1,14 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ testDir: "./tests",
5
+ use: {
6
+ baseURL: "http://127.0.0.1:4173"
7
+ },
8
+ webServer: {
9
+ command: "npm exec vite -- --host 127.0.0.1 --port 4173 --strictPort",
10
+ url: "http://127.0.0.1:4173",
11
+ reuseExistingServer: !process.env.CI,
12
+ timeout: 120_000
13
+ }
14
+ });
@@ -0,0 +1,8 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 160 120">
2
+ <rect width="160" height="120" rx="16" fill="#07111d"/>
3
+ <circle cx="74" cy="62" r="29" fill="#ffd84a"/>
4
+ <circle cx="97" cy="50" r="19" fill="#ffe680"/>
5
+ <path d="M112 55h22l-20 12z" fill="#ff8a4c"/>
6
+ <circle cx="101" cy="45" r="3" fill="#111827"/>
7
+ <path d="M40 91c27 12 62 12 91 0" fill="none" stroke="#55e7ff" stroke-width="6" stroke-linecap="round"/>
8
+ </svg>