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,56 @@
1
+ import { mkdirSync, writeFileSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+ import { expect, test } from "@playwright/test";
4
+
5
+ test.setTimeout(60_000);
6
+
7
+ test("Aura3D cinematic scene screenshot shows the rainy neon hero prompt", async ({ page }) => {
8
+ await page.goto("/");
9
+ await expect.poll(() => page.locator("body").getAttribute("data-aura3d-ready"), { timeout: 45_000 }).toBe("true");
10
+ const canvas = page.locator("canvas");
11
+ const profile = await canvas.evaluate((element) => {
12
+ const target = element as HTMLCanvasElement;
13
+ const gl = target.getContext("webgl2", { preserveDrawingBuffer: true });
14
+ if (!gl) return { error: "missing-webgl2", cyanPixels: 0, amberPixels: 0, rainPixels: 0, wetReflectionPixels: 0, centerHeroPixels: 0, darkAlleyPixels: 0, uniqueBuckets: 0 };
15
+ const pixels = new Uint8Array(target.width * target.height * 4);
16
+ gl.readPixels(0, 0, target.width, target.height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);
17
+ const buckets = new Set<string>();
18
+ let cyanPixels = 0;
19
+ let amberPixels = 0;
20
+ let rainPixels = 0;
21
+ let wetReflectionPixels = 0;
22
+ let centerHeroPixels = 0;
23
+ let darkAlleyPixels = 0;
24
+ for (let y = 0; y < target.height; y += 4) {
25
+ for (let x = 0; x < target.width; x += 4) {
26
+ if (x > target.width * 0.76 && y > target.height * 0.74) continue;
27
+ const offset = (y * target.width + x) * 4;
28
+ const r = pixels[offset] ?? 0;
29
+ const g = pixels[offset + 1] ?? 0;
30
+ const b = pixels[offset + 2] ?? 0;
31
+ const luminance = r * 0.2126 + g * 0.7152 + b * 0.0722;
32
+ if (luminance > 30) buckets.add(`${r >> 5}-${g >> 5}-${b >> 5}`);
33
+ if (b > 110 && g > 90 && b > r * 1.22) cyanPixels += 1;
34
+ if (r > 160 && g > 105 && b < 125) amberPixels += 1;
35
+ if (r > 165 && g > 185 && b > 205) rainPixels += 1;
36
+ if (y < target.height * 0.36 && ((b > 95 && g > 65 && b > r * 1.12) || (r > 105 && g > 62 && b < 95))) wetReflectionPixels += 1;
37
+ if (x > target.width * 0.32 && x < target.width * 0.68 && y > target.height * 0.26 && y < target.height * 0.78 && luminance > 75) centerHeroPixels += 1;
38
+ if (luminance > 8 && luminance < 36 && b >= r && x > target.width * 0.08 && x < target.width * 0.92) darkAlleyPixels += 1;
39
+ }
40
+ }
41
+ return { cyanPixels, amberPixels, rainPixels, wetReflectionPixels, centerHeroPixels, darkAlleyPixels, uniqueBuckets: buckets.size };
42
+ });
43
+ const screenshot = await page.screenshot({ fullPage: false });
44
+ mkdirSync(resolve("tests/reports"), { recursive: true });
45
+ writeFileSync(resolve("tests/reports/screenshot.png"), screenshot);
46
+ writeFileSync(resolve("tests/reports/screenshot.json"), `${JSON.stringify({ bytes: screenshot.byteLength, profile }, null, 2)}\n`);
47
+ expect(profile.error).toBeUndefined();
48
+ expect(profile.cyanPixels).toBeGreaterThan(320);
49
+ expect(profile.amberPixels).toBeGreaterThan(20);
50
+ expect(profile.rainPixels).toBeGreaterThan(90);
51
+ expect(profile.wetReflectionPixels).toBeGreaterThan(60);
52
+ expect(profile.centerHeroPixels).toBeGreaterThan(600);
53
+ expect(profile.darkAlleyPixels).toBeGreaterThan(180);
54
+ expect(profile.uniqueBuckets).toBeGreaterThan(22);
55
+ expect(screenshot.byteLength).toBeGreaterThan(1000);
56
+ });
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "useDefineForClassFields": true,
5
+ "module": "ESNext",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "skipLibCheck": true,
8
+ "moduleResolution": "Bundler",
9
+ "allowImportingTsExtensions": true,
10
+ "isolatedModules": true,
11
+ "noEmit": true,
12
+ "strict": true
13
+ },
14
+ "include": ["src", "tests", "playwright.config.ts"]
15
+ }
@@ -0,0 +1,64 @@
1
+ # Aura3D fighting-game template
2
+
3
+ This template demonstrates Aura3D's public game runtime APIs:
4
+
5
+ - `createAuraApp(...)` for one mounted Aura app.
6
+ - `.runtime(game.runtimeNode("id"))` for mutable fighter and camera nodes.
7
+ - `app.input(...)` for app-owned keyboard/gamepad input.
8
+ - `game.inputReplay(...)` and `game.inputReplayDriver(...)` for deterministic replay smoke paths.
9
+ - `game.kinematicBody(...)` and `game.jumpAssist(...)` for 2.5D movement, gravity, coyote time, jump buffering, dash, and knockback.
10
+ - `game.combatWorld(...)` for hitboxes, hurtboxes, guard, hit-stop, stun, recovery, and typed combat events.
11
+ - `AnimationController` bound to runtime nodes for public animation clip switching and pose-baked fallback declarations.
12
+ - `game.effects(...)`, `game.cameraDirector(...)`, and `game.debug.overlay(...)` for hit sparks, impact shake, zoom, colliders, and runtime evidence.
13
+ - `games.fighting.stagePreset(...)` and `games.fighting.validateStage(...)` for fighting-stage source, safe zones, combat bounds, and readiness warnings.
14
+ - `game.hud.*`, `game.accessibility.*`, and `ui.*` helpers for HUD, controls, and a11y evidence.
15
+ - `game.evidence(app, ...)` for proving the route is using runtime systems instead of a static scene.
16
+
17
+ ## Source layout
18
+
19
+ - `src/main.ts` mounts the route, owns frame updates, and publishes `window.__AURA3D_GAME_*` evidence objects.
20
+ - `src/game/fighters.ts` owns typed fighter asset resolution, runtime fighter nodes, and animation controller setup.
21
+ - `src/game/moves.ts` owns combat move declarations and hitbox source.
22
+ - `src/game/stage.ts` owns `games.fighting` stage helpers, touch controls, debug colliders, and route/build readiness declarations.
23
+ - `src/aura-assets.ts` is the typed asset manifest generated by the Aura3D CLI.
24
+
25
+ The scaffold does not ship invented fighter GLBs or raw model URLs. It starts
26
+ with Aura3D runtime-node source placeholders so the route is playable and the
27
+ combat systems are inspectable. Add real fighter assets through the Aura3D CLI
28
+ to switch the placeholders to typed `model(assets.playerFighter)` and
29
+ `model(assets.rivalFighter)` nodes automatically.
30
+
31
+ Controls:
32
+
33
+ - Move: `A/D` or arrow keys.
34
+ - Jump: `W`, `ArrowUp`, or `Space`.
35
+ - Guard: `Q`.
36
+ - Dash: `Shift`.
37
+ - Light: `J`.
38
+ - Heavy: `K`.
39
+ - Special: `L`.
40
+ - Pause: `Escape` or the HUD pause button.
41
+ - Replay: HUD replay button.
42
+
43
+ Add fighter assets:
44
+
45
+ ```bash
46
+ npx @aura3d/cli@latest assets add ./assets/player-fighter.glb --name playerFighter
47
+ npx @aura3d/cli@latest assets add ./assets/rival-fighter.glb --name rivalFighter
48
+ ```
49
+
50
+ The CLI writes typed definitions to `src/aura-assets.ts`. The starter imports
51
+ `assets` from `./aura-assets` and only calls `model(...)` with those typed asset
52
+ refs. Do not use string asset ids, three.js loaders, `GLTFLoader`, raw GLB URLs,
53
+ or `unsafeModelUrl(...)`.
54
+
55
+ Before claiming the route is asset-ready, run:
56
+
57
+ ```bash
58
+ npx @aura3d/cli@latest assets validate-game
59
+ ```
60
+
61
+ The source-level readiness declaration in `src/game/stage.ts` is not launch
62
+ evidence. Before claiming build, package, visual, accessibility, or launch
63
+ readiness, archive the matching command output, runtime evidence JSON,
64
+ browser screenshot, and review result.
@@ -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 Fighting Game Runtime</title>
7
+ </head>
8
+ <body>
9
+ <div id="app" aria-label="Aura3D fighting game viewport"></div>
10
+ <div id="hud" class="hud" role="region" aria-label="Fighting game status and controls" aria-live="polite"></div>
11
+ <script type="module" src="/src/main.ts"></script>
12
+ </body>
13
+ </html>
@@ -0,0 +1,21 @@
1
+ {
2
+ "name": "aura3d-fighting-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/gameplay-smoke.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,12 @@
1
+ import { defineConfig } from "@playwright/test";
2
+
3
+ export default defineConfig({
4
+ webServer: {
5
+ command: "npm run dev",
6
+ url: "http://127.0.0.1:5173",
7
+ reuseExistingServer: !process.env.CI
8
+ },
9
+ use: {
10
+ baseURL: "http://127.0.0.1:5173"
11
+ }
12
+ });
@@ -0,0 +1,11 @@
1
+ import { defineAuraAssets } from "@aura3d/engine";
2
+
3
+ export const assets = defineAuraAssets({
4
+ /*
5
+ * Add your own fighter GLBs with the Aura3D CLI, then keep these generated
6
+ * keys so src/main.ts can switch from source placeholders to typed models:
7
+ *
8
+ * npx @aura3d/cli@latest assets add ./assets/player-fighter.glb --name playerFighter
9
+ * npx @aura3d/cli@latest assets add ./assets/rival-fighter.glb --name rivalFighter
10
+ */
11
+ });
@@ -0,0 +1,152 @@
1
+ import {
2
+ AnimationController,
3
+ type AuraAssetRef,
4
+ type AuraNamedAnimationClipDefinition,
5
+ game,
6
+ material,
7
+ model,
8
+ primitives
9
+ } from "@aura3d/engine";
10
+
11
+ export type FighterId = "player" | "rival";
12
+ export type FighterAssetKey = "playerFighter" | "rivalFighter";
13
+ export type FighterClip = "idle" | "walk" | "jump" | "dash" | "guard" | "light" | "heavy" | "special" | "hitstun";
14
+
15
+ export type TypedFighterAssets = Partial<Record<FighterAssetKey, AuraAssetRef<"model">>>;
16
+
17
+ export interface ResolvedFighterAssets {
18
+ readonly typedFighterAssets: TypedFighterAssets;
19
+ readonly missingFighterAssets: readonly FighterAssetKey[];
20
+ readonly typedFighterAssetCount: number;
21
+ }
22
+
23
+ export const REQUIRED_FIGHTER_ASSETS: readonly FighterAssetKey[] = ["playerFighter", "rivalFighter"];
24
+ export const REQUIRED_FIGHTER_CLIPS: readonly FighterClip[] = [
25
+ "idle",
26
+ "walk",
27
+ "jump",
28
+ "dash",
29
+ "guard",
30
+ "light",
31
+ "heavy",
32
+ "special",
33
+ "hitstun"
34
+ ];
35
+
36
+ export const publicAssetInstructions = [
37
+ "npx @aura3d/cli@latest assets add ./assets/player-fighter.glb --name playerFighter",
38
+ "npx @aura3d/cli@latest assets add ./assets/rival-fighter.glb --name rivalFighter",
39
+ "npx @aura3d/cli@latest assets validate-game"
40
+ ] as const;
41
+
42
+ export const fighterAnimationClips: readonly AuraNamedAnimationClipDefinition<FighterClip>[] = REQUIRED_FIGHTER_CLIPS.map((id) => ({
43
+ id,
44
+ duration: animationDuration(id),
45
+ frameRate: 60,
46
+ loop: id === "idle" || id === "walk" || id === "guard" || id === "jump",
47
+ layer: animationLayer(id),
48
+ layerMetadata: {
49
+ id: animationLayer(id),
50
+ role: id === "light" || id === "heavy" || id === "special" ? "attack" : "base",
51
+ bodyMask: id === "light" || id === "heavy" || id === "special" ? "upper-body" : "full-body",
52
+ restartFromFrameZero: id === "light" || id === "heavy" || id === "special" || id === "dash"
53
+ },
54
+ restartFromFrameZero: id === "light" || id === "heavy" || id === "special" || id === "dash",
55
+ suppressRootMotion: true,
56
+ attack: id === "light" || id === "heavy" || id === "special",
57
+ tracks: [],
58
+ metadata: {
59
+ source: "fighting-game-template-pose-fallback",
60
+ poseBakedFallback: true,
61
+ suppressRootMotion: true,
62
+ templateReadiness: "source-only"
63
+ }
64
+ }));
65
+
66
+ export function resolveTypedFighterAssets(assetManifest: TypedFighterAssets): ResolvedFighterAssets {
67
+ const typedFighterAssets: TypedFighterAssets = {
68
+ playerFighter: assetManifest.playerFighter,
69
+ rivalFighter: assetManifest.rivalFighter
70
+ };
71
+ const missingFighterAssets = REQUIRED_FIGHTER_ASSETS.filter((key) => !typedFighterAssets[key]);
72
+ return {
73
+ typedFighterAssets,
74
+ missingFighterAssets,
75
+ typedFighterAssetCount: REQUIRED_FIGHTER_ASSETS.length - missingFighterAssets.length
76
+ };
77
+ }
78
+
79
+ export function createFighterAnimationController(
80
+ id: FighterId,
81
+ asset?: AuraAssetRef<"model">
82
+ ): AnimationController<FighterClip> {
83
+ return new AnimationController<FighterClip>({
84
+ id: `${id}-animation-controller`,
85
+ ...(asset ? { clipRegistry: asset } : {}),
86
+ clips: fighterAnimationClips,
87
+ requiredClips: REQUIRED_FIGHTER_CLIPS,
88
+ suppressRootMotion: true,
89
+ layers: [
90
+ { id: "base", role: "base", bodyMask: "full-body" },
91
+ { id: "upper-body", role: "attack", bodyMask: "upper-body", restartFromFrameZero: true }
92
+ ]
93
+ });
94
+ }
95
+
96
+ export function createFighterNode(
97
+ id: FighterId,
98
+ assetKey: FighterAssetKey,
99
+ label: string,
100
+ position: readonly [number, number, number],
101
+ facing: 1 | -1,
102
+ color: string,
103
+ typedFighterAssets: TypedFighterAssets
104
+ ) {
105
+ const asset = typedFighterAssets[assetKey];
106
+ const runtime = game.runtimeNode(id, {
107
+ tags: ["fighter", id, asset ? "typed-asset" : "source-placeholder"]
108
+ });
109
+
110
+ if (asset) {
111
+ return model(asset, { name: label })
112
+ .position(position[0], position[1], position[2])
113
+ .rotate(0, facing < 0 ? Math.PI : 0, 0)
114
+ .scale(0.72)
115
+ .runtime(runtime);
116
+ }
117
+
118
+ return primitives
119
+ .capsule({
120
+ name: `${label} source placeholder - add ${assetKey} with the Aura3D CLI`,
121
+ material: material.pbr({ color, roughness: 0.62 })
122
+ })
123
+ .position(position[0], position[1] + 0.82, position[2])
124
+ .rotate(0, facing < 0 ? Math.PI : 0, 0)
125
+ .scale([0.34, 0.74, 0.34])
126
+ .runtime(runtime);
127
+ }
128
+
129
+ export function isLoopingFighterClip(clip: FighterClip): boolean {
130
+ return clip === "idle" || clip === "walk" || clip === "guard" || clip === "jump";
131
+ }
132
+
133
+ export function animationLayer(clip: FighterClip): "base" | "upper-body" {
134
+ return clip === "light" || clip === "heavy" || clip === "special" ? "upper-body" : "base";
135
+ }
136
+
137
+ function animationDuration(clip: FighterClip): number {
138
+ switch (clip) {
139
+ case "light":
140
+ return 0.28;
141
+ case "heavy":
142
+ return 0.46;
143
+ case "special":
144
+ return 0.72;
145
+ case "dash":
146
+ return 0.22;
147
+ case "hitstun":
148
+ return 0.34;
149
+ default:
150
+ return 1;
151
+ }
152
+ }
@@ -0,0 +1,70 @@
1
+ export type FighterFacing = 1 | -1;
2
+
3
+ export function lightMove(id: string, facing: FighterFacing) {
4
+ return {
5
+ id,
6
+ name: "Template Light Strike",
7
+ damage: 7,
8
+ guardDamage: 3,
9
+ hitStop: 0.08,
10
+ hitStun: 0.22,
11
+ blockStun: 0.12,
12
+ recovery: 0.18,
13
+ activeFrames: [2, 8] as const,
14
+ durationFrames: 18,
15
+ knockback: [facing * 1.8, 0.5, 0] as const,
16
+ hitboxes: [
17
+ {
18
+ id: "jab",
19
+ offset: [facing * 0.68, 0.82, 0] as const,
20
+ size: [0.86, 0.45, 0.45] as const
21
+ }
22
+ ]
23
+ };
24
+ }
25
+
26
+ export function heavyMove(id: string, facing: FighterFacing) {
27
+ return {
28
+ id,
29
+ name: "Template Heavy Breaker",
30
+ damage: 13,
31
+ guardDamage: 8,
32
+ hitStop: 0.12,
33
+ hitStun: 0.34,
34
+ blockStun: 0.18,
35
+ recovery: 0.32,
36
+ activeFrames: [4, 12] as const,
37
+ durationFrames: 28,
38
+ knockback: [facing * 2.5, 0.8, 0] as const,
39
+ hitboxes: [
40
+ {
41
+ id: "heavy",
42
+ offset: [facing * 0.78, 0.92, 0] as const,
43
+ size: [1.08, 0.6, 0.52] as const
44
+ }
45
+ ]
46
+ };
47
+ }
48
+
49
+ export function specialMove(id: string, facing: FighterFacing) {
50
+ return {
51
+ id,
52
+ name: "Template Aura Burst",
53
+ damage: 18,
54
+ guardDamage: 12,
55
+ hitStop: 0.16,
56
+ hitStun: 0.42,
57
+ blockStun: 0.24,
58
+ recovery: 0.46,
59
+ activeFrames: [3, 18] as const,
60
+ durationFrames: 42,
61
+ knockback: [facing * 3.2, 1.1, 0] as const,
62
+ hitboxes: [
63
+ {
64
+ id: "aura-burst",
65
+ offset: [facing * 0.86, 0.95, 0] as const,
66
+ size: [1.8, 0.85, 0.65] as const
67
+ }
68
+ ]
69
+ };
70
+ }
@@ -0,0 +1,98 @@
1
+ import { game, games } from "@aura3d/engine";
2
+ import { publicAssetInstructions, REQUIRED_FIGHTER_ASSETS, REQUIRED_FIGHTER_CLIPS } from "./fighters";
3
+
4
+ type GameVec3 = readonly [number, number, number];
5
+
6
+ export const fightingControls = games.fighting.controls();
7
+ export const fightingStage = games.fighting.stagePreset("neon-dojo", {
8
+ id: "fighting-template-arena",
9
+ width: 7.6,
10
+ depth: 2.5,
11
+ safeInset: 0.48
12
+ });
13
+ export const fightingStageIssues = games.fighting.validateStage(fightingStage);
14
+
15
+ export const fightingStageBounds = {
16
+ minX: fightingStage.combatBounds.minX,
17
+ maxX: fightingStage.combatBounds.maxX,
18
+ minY: fightingStage.combatBounds.minY,
19
+ maxY: fightingStage.combatBounds.maxY,
20
+ minZ: fightingStage.combatBounds.minZ,
21
+ maxZ: fightingStage.combatBounds.maxZ
22
+ };
23
+
24
+ export const fightingRouteReadiness = {
25
+ kind: "aura3d-fighting-game-route-readiness",
26
+ sourceOnly: true,
27
+ template: "fighting-game",
28
+ route: "/",
29
+ packageName: "aura3d-fighting-game",
30
+ entry: "src/main.ts",
31
+ sourceFiles: ["src/main.ts", "src/aura-assets.ts", "src/game/fighters.ts", "src/game/moves.ts", "src/game/stage.ts"],
32
+ publicEngineApis: [
33
+ "createAuraApp",
34
+ "scene",
35
+ "model(typed AuraAssetRef)",
36
+ "game.runtimeNode",
37
+ "app.onFrame",
38
+ "app.input",
39
+ "game.evidence",
40
+ "game.kinematicBody",
41
+ "game.jumpAssist",
42
+ "game.combatWorld",
43
+ "AnimationController",
44
+ "game.effects",
45
+ "game.cameraDirector",
46
+ "games.fighting.stagePreset",
47
+ "games.fighting.validateStage",
48
+ "game.hud",
49
+ "game.accessibility",
50
+ "game.debug.overlay"
51
+ ],
52
+ requiredTypedAssets: REQUIRED_FIGHTER_ASSETS,
53
+ requiredAnimationClips: REQUIRED_FIGHTER_CLIPS,
54
+ assetCommands: publicAssetInstructions,
55
+ buildDeclarations: {
56
+ routeHealthSpec: "tests/route-health.spec.ts",
57
+ gameplaySmokeSpec: "tests/gameplay-smoke.spec.ts",
58
+ localCommandsNotRunByTemplate: ["npm run typecheck", "npm run build", "npm run test"],
59
+ evidenceRequiredBeforeLaunchReady: [
60
+ "npx @aura3d/cli@latest assets validate-game output",
61
+ "npm run test scaffold output proving route load and replay hit",
62
+ "browser screenshot showing a typed GLB fighter moved by runtime node mutation",
63
+ "archived collectGameRuntimeEvidence/app.evidence JSON under tests/reports/game-runtime/",
64
+ "human or automated visual/accessibility approval"
65
+ ]
66
+ },
67
+ stage: {
68
+ id: fightingStage.id,
69
+ safeZone: fightingStage.safeZone,
70
+ combatBounds: fightingStage.combatBounds,
71
+ validationIssues: fightingStageIssues.map((issue) => ({
72
+ severity: issue.severity,
73
+ code: issue.code,
74
+ message: issue.message
75
+ }))
76
+ }
77
+ } as const;
78
+
79
+ export function createFighterColliders(playerPosition: GameVec3, rivalPosition: GameVec3) {
80
+ return [
81
+ game.collider.capsule({ id: "player-body", center: playerPosition, radius: 0.34, height: 1.7, tags: ["fighter", "player"] }),
82
+ game.collider.capsule({ id: "rival-body", center: rivalPosition, radius: 0.34, height: 1.7, tags: ["fighter", "rival"] })
83
+ ];
84
+ }
85
+
86
+ export function createTouchLayout(width: number, height: number) {
87
+ return game.touchControls({
88
+ width,
89
+ height,
90
+ buttons: [
91
+ { action: "jump", label: "Jump", binding: "TouchJump" },
92
+ { action: "guard", label: "Guard", binding: "TouchGuard" },
93
+ { action: "light", label: "Light", binding: "TouchLight" },
94
+ { action: "heavy", label: "Heavy", binding: "TouchHeavy" },
95
+ { action: "special", label: "Special", binding: "TouchSpecial" }
96
+ ]
97
+ });
98
+ }