@aura3d/engine 1.3.3 → 1.4.1

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 (233) hide show
  1. package/README.md +138 -15
  2. package/dist/assets/GLTFLoader.js +1 -0
  3. package/dist/assets/GLTFLoader.js.map +1 -1
  4. package/dist/assets/GLTFRenderResources.d.ts.map +1 -1
  5. package/dist/assets/GLTFRenderResources.js +1 -0
  6. package/dist/assets/GLTFRenderResources.js.map +1 -1
  7. package/dist/aura3d-cli/asset-constants.d.ts +5 -0
  8. package/dist/aura3d-cli/asset-constants.d.ts.map +1 -0
  9. package/dist/aura3d-cli/asset-constants.js +5 -0
  10. package/dist/aura3d-cli/asset-constants.js.map +1 -0
  11. package/dist/aura3d-cli/asset-core-types.d.ts +222 -0
  12. package/dist/aura3d-cli/asset-core-types.d.ts.map +1 -0
  13. package/dist/aura3d-cli/asset-core-types.js +2 -0
  14. package/dist/aura3d-cli/asset-core-types.js.map +1 -0
  15. package/dist/aura3d-cli/asset-inspection-types.d.ts +131 -0
  16. package/dist/aura3d-cli/asset-inspection-types.d.ts.map +1 -0
  17. package/dist/aura3d-cli/asset-inspection-types.js +2 -0
  18. package/dist/aura3d-cli/asset-inspection-types.js.map +1 -0
  19. package/dist/aura3d-cli/asset-manifest.d.ts +8 -0
  20. package/dist/aura3d-cli/asset-manifest.d.ts.map +1 -0
  21. package/dist/aura3d-cli/asset-manifest.js +116 -0
  22. package/dist/aura3d-cli/asset-manifest.js.map +1 -0
  23. package/dist/aura3d-cli/asset-readiness-types.d.ts +164 -0
  24. package/dist/aura3d-cli/asset-readiness-types.d.ts.map +1 -0
  25. package/dist/aura3d-cli/asset-readiness-types.js +2 -0
  26. package/dist/aura3d-cli/asset-readiness-types.js.map +1 -0
  27. package/dist/aura3d-cli/asset-source-ast.d.ts +8 -0
  28. package/dist/aura3d-cli/asset-source-ast.d.ts.map +1 -0
  29. package/dist/aura3d-cli/asset-source-ast.js +241 -0
  30. package/dist/aura3d-cli/asset-source-ast.js.map +1 -0
  31. package/dist/aura3d-cli/asset-source-roles.d.ts +4 -0
  32. package/dist/aura3d-cli/asset-source-roles.d.ts.map +1 -0
  33. package/dist/aura3d-cli/asset-source-roles.js +79 -0
  34. package/dist/aura3d-cli/asset-source-roles.js.map +1 -0
  35. package/dist/aura3d-cli/asset-source-validation.d.ts +4 -0
  36. package/dist/aura3d-cli/asset-source-validation.d.ts.map +1 -0
  37. package/dist/aura3d-cli/asset-source-validation.js +200 -0
  38. package/dist/aura3d-cli/asset-source-validation.js.map +1 -0
  39. package/dist/aura3d-cli/character-assembly-types.d.ts +39 -0
  40. package/dist/aura3d-cli/character-assembly-types.d.ts.map +1 -0
  41. package/dist/aura3d-cli/character-assembly-types.js +2 -0
  42. package/dist/aura3d-cli/character-assembly-types.js.map +1 -0
  43. package/dist/aura3d-cli/cli-help.d.ts +4 -0
  44. package/dist/aura3d-cli/cli-help.d.ts.map +1 -0
  45. package/dist/aura3d-cli/cli-help.js +48 -0
  46. package/dist/aura3d-cli/cli-help.js.map +1 -0
  47. package/dist/aura3d-cli/cli-options.d.ts +66 -0
  48. package/dist/aura3d-cli/cli-options.d.ts.map +1 -0
  49. package/dist/aura3d-cli/cli-options.js +234 -0
  50. package/dist/aura3d-cli/cli-options.js.map +1 -0
  51. package/dist/aura3d-cli/cli.js +20 -177
  52. package/dist/aura3d-cli/cli.js.map +1 -1
  53. package/dist/aura3d-cli/index.d.ts +12 -402
  54. package/dist/aura3d-cli/index.d.ts.map +1 -1
  55. package/dist/aura3d-cli/index.js +948 -90
  56. package/dist/aura3d-cli/index.js.map +1 -1
  57. package/dist/aura3d-cli/pull-bridge/adapters.d.ts +4 -0
  58. package/dist/aura3d-cli/pull-bridge/adapters.d.ts.map +1 -0
  59. package/dist/aura3d-cli/pull-bridge/adapters.js +38 -0
  60. package/dist/aura3d-cli/pull-bridge/adapters.js.map +1 -0
  61. package/dist/aura3d-cli/pull-bridge/download.d.ts +6 -0
  62. package/dist/aura3d-cli/pull-bridge/download.d.ts.map +1 -0
  63. package/dist/aura3d-cli/pull-bridge/download.js +208 -0
  64. package/dist/aura3d-cli/pull-bridge/download.js.map +1 -0
  65. package/dist/aura3d-cli/pull-bridge/profiles.d.ts +14 -0
  66. package/dist/aura3d-cli/pull-bridge/profiles.d.ts.map +1 -0
  67. package/dist/aura3d-cli/pull-bridge/profiles.js +63 -0
  68. package/dist/aura3d-cli/pull-bridge/profiles.js.map +1 -0
  69. package/dist/aura3d-cli/pull-bridge/provenance.d.ts +9 -0
  70. package/dist/aura3d-cli/pull-bridge/provenance.d.ts.map +1 -0
  71. package/dist/aura3d-cli/pull-bridge/provenance.js +99 -0
  72. package/dist/aura3d-cli/pull-bridge/provenance.js.map +1 -0
  73. package/dist/aura3d-cli/pull-bridge/pullable.d.ts +14 -0
  74. package/dist/aura3d-cli/pull-bridge/pullable.d.ts.map +1 -0
  75. package/dist/aura3d-cli/pull-bridge/pullable.js +58 -0
  76. package/dist/aura3d-cli/pull-bridge/pullable.js.map +1 -0
  77. package/dist/aura3d-cli/pull-bridge/scoring.d.ts +21 -0
  78. package/dist/aura3d-cli/pull-bridge/scoring.d.ts.map +1 -0
  79. package/dist/aura3d-cli/pull-bridge/scoring.js +177 -0
  80. package/dist/aura3d-cli/pull-bridge/scoring.js.map +1 -0
  81. package/dist/aura3d-cli/pull-bridge/search.d.ts +39 -0
  82. package/dist/aura3d-cli/pull-bridge/search.d.ts.map +1 -0
  83. package/dist/aura3d-cli/pull-bridge/search.js +114 -0
  84. package/dist/aura3d-cli/pull-bridge/search.js.map +1 -0
  85. package/dist/aura3d-cli/pull-bridge/types.d.ts +9 -0
  86. package/dist/aura3d-cli/pull-bridge/types.d.ts.map +1 -0
  87. package/dist/aura3d-cli/pull-bridge/types.js +2 -0
  88. package/dist/aura3d-cli/pull-bridge/types.js.map +1 -0
  89. package/dist/aura3d-cli/pull-bridge/vector3.d.ts +2 -0
  90. package/dist/aura3d-cli/pull-bridge/vector3.d.ts.map +1 -0
  91. package/dist/aura3d-cli/pull-bridge/vector3.js +4 -0
  92. package/dist/aura3d-cli/pull-bridge/vector3.js.map +1 -0
  93. package/dist/aura3d-cli/pull-bridge.d.ts +18 -100
  94. package/dist/aura3d-cli/pull-bridge.d.ts.map +1 -1
  95. package/dist/aura3d-cli/pull-bridge.js +59 -526
  96. package/dist/aura3d-cli/pull-bridge.js.map +1 -1
  97. package/dist/create-aura3d/cli.js +10 -0
  98. package/dist/create-aura3d/cli.js.map +1 -1
  99. package/dist/create-aura3d/index.d.ts +1 -1
  100. package/dist/create-aura3d/index.d.ts.map +1 -1
  101. package/dist/create-aura3d/index.js +3 -1
  102. package/dist/create-aura3d/index.js.map +1 -1
  103. package/dist/create-aura3d/showcase-spec-artifacts.d.ts +105 -0
  104. package/dist/create-aura3d/showcase-spec-artifacts.d.ts.map +1 -0
  105. package/dist/create-aura3d/showcase-spec-artifacts.js +143 -0
  106. package/dist/create-aura3d/showcase-spec-artifacts.js.map +1 -0
  107. package/dist/create-aura3d/showcase-spec-category-plan.d.ts +15 -0
  108. package/dist/create-aura3d/showcase-spec-category-plan.d.ts.map +1 -0
  109. package/dist/create-aura3d/showcase-spec-category-plan.js +45 -0
  110. package/dist/create-aura3d/showcase-spec-category-plan.js.map +1 -0
  111. package/dist/create-aura3d/showcase-spec-compiler.d.ts +5 -0
  112. package/dist/create-aura3d/showcase-spec-compiler.d.ts.map +1 -0
  113. package/dist/create-aura3d/showcase-spec-compiler.js +240 -0
  114. package/dist/create-aura3d/showcase-spec-compiler.js.map +1 -0
  115. package/dist/create-aura3d/showcase-spec-evidence.d.ts +7 -0
  116. package/dist/create-aura3d/showcase-spec-evidence.d.ts.map +1 -0
  117. package/dist/create-aura3d/showcase-spec-evidence.js +881 -0
  118. package/dist/create-aura3d/showcase-spec-evidence.js.map +1 -0
  119. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts +21 -0
  120. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.d.ts.map +1 -0
  121. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js +725 -0
  122. package/dist/create-aura3d/showcase-spec-game-geometry-extractor.js.map +1 -0
  123. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts +14 -0
  124. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.d.ts.map +1 -0
  125. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js +219 -0
  126. package/dist/create-aura3d/showcase-spec-game-replacement-evidence.js.map +1 -0
  127. package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts +72 -0
  128. package/dist/create-aura3d/showcase-spec-game-template-evidence.d.ts.map +1 -0
  129. package/dist/create-aura3d/showcase-spec-game-template-evidence.js +435 -0
  130. package/dist/create-aura3d/showcase-spec-game-template-evidence.js.map +1 -0
  131. package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts +3 -0
  132. package/dist/create-aura3d/showcase-spec-gameplay-evidence.d.ts.map +1 -0
  133. package/dist/create-aura3d/showcase-spec-gameplay-evidence.js +108 -0
  134. package/dist/create-aura3d/showcase-spec-gameplay-evidence.js.map +1 -0
  135. package/dist/create-aura3d/showcase-spec-parser.d.ts +3 -0
  136. package/dist/create-aura3d/showcase-spec-parser.d.ts.map +1 -0
  137. package/dist/create-aura3d/showcase-spec-parser.js +690 -0
  138. package/dist/create-aura3d/showcase-spec-parser.js.map +1 -0
  139. package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts +3 -0
  140. package/dist/create-aura3d/showcase-spec-platformer-artifacts.d.ts.map +1 -0
  141. package/dist/create-aura3d/showcase-spec-platformer-artifacts.js +274 -0
  142. package/dist/create-aura3d/showcase-spec-platformer-artifacts.js.map +1 -0
  143. package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts +3 -0
  144. package/dist/create-aura3d/showcase-spec-racing-artifacts.d.ts.map +1 -0
  145. package/dist/create-aura3d/showcase-spec-racing-artifacts.js +273 -0
  146. package/dist/create-aura3d/showcase-spec-racing-artifacts.js.map +1 -0
  147. package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts +10 -0
  148. package/dist/create-aura3d/showcase-spec-replacement-apply.d.ts.map +1 -0
  149. package/dist/create-aura3d/showcase-spec-replacement-apply.js +166 -0
  150. package/dist/create-aura3d/showcase-spec-replacement-apply.js.map +1 -0
  151. package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts +16 -0
  152. package/dist/create-aura3d/showcase-spec-replacement-candidates.d.ts.map +1 -0
  153. package/dist/create-aura3d/showcase-spec-replacement-candidates.js +244 -0
  154. package/dist/create-aura3d/showcase-spec-replacement-candidates.js.map +1 -0
  155. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts +4 -0
  156. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.d.ts.map +1 -0
  157. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js +175 -0
  158. package/dist/create-aura3d/showcase-spec-replacement-game-geometry.js.map +1 -0
  159. package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts +41 -0
  160. package/dist/create-aura3d/showcase-spec-replacement-manifest.d.ts.map +1 -0
  161. package/dist/create-aura3d/showcase-spec-replacement-manifest.js +62 -0
  162. package/dist/create-aura3d/showcase-spec-replacement-manifest.js.map +1 -0
  163. package/dist/create-aura3d/showcase-spec-replacement-values.d.ts +9 -0
  164. package/dist/create-aura3d/showcase-spec-replacement-values.d.ts.map +1 -0
  165. package/dist/create-aura3d/showcase-spec-replacement-values.js +41 -0
  166. package/dist/create-aura3d/showcase-spec-replacement-values.js.map +1 -0
  167. package/dist/create-aura3d/showcase-spec-replacement.d.ts +13 -0
  168. package/dist/create-aura3d/showcase-spec-replacement.d.ts.map +1 -0
  169. package/dist/create-aura3d/showcase-spec-replacement.js +144 -0
  170. package/dist/create-aura3d/showcase-spec-replacement.js.map +1 -0
  171. package/dist/create-aura3d/showcase-spec-types.d.ts +319 -0
  172. package/dist/create-aura3d/showcase-spec-types.d.ts.map +1 -0
  173. package/dist/create-aura3d/showcase-spec-types.js +2 -0
  174. package/dist/create-aura3d/showcase-spec-types.js.map +1 -0
  175. package/dist/engine/agent-api/GameAssetValidation.d.ts +5 -0
  176. package/dist/engine/agent-api/GameAssetValidation.d.ts.map +1 -1
  177. package/dist/engine/agent-api/GameAssetValidation.js +103 -0
  178. package/dist/engine/agent-api/GameAssetValidation.js.map +1 -1
  179. package/dist/engine/agent-api/GameEvidence.d.ts +14 -1
  180. package/dist/engine/agent-api/GameEvidence.d.ts.map +1 -1
  181. package/dist/engine/agent-api/GameEvidence.js +77 -6
  182. package/dist/engine/agent-api/GameEvidence.js.map +1 -1
  183. package/dist/engine/agent-api/GameGenreKits.d.ts +473 -0
  184. package/dist/engine/agent-api/GameGenreKits.d.ts.map +1 -0
  185. package/dist/engine/agent-api/GameGenreKits.js +1269 -0
  186. package/dist/engine/agent-api/GameGenreKits.js.map +1 -0
  187. package/dist/engine/agent-api/GameRuntime.d.ts +190 -3
  188. package/dist/engine/agent-api/GameRuntime.d.ts.map +1 -1
  189. package/dist/engine/agent-api/GameRuntime.js +431 -2
  190. package/dist/engine/agent-api/GameRuntime.js.map +1 -1
  191. package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts +182 -0
  192. package/dist/engine/agent-api/GameSceneGeometryBindings.d.ts.map +1 -0
  193. package/dist/engine/agent-api/GameSceneGeometryBindings.js +488 -0
  194. package/dist/engine/agent-api/GameSceneGeometryBindings.js.map +1 -0
  195. package/dist/engine/agent-api/GameSceneGeometryMath.d.ts +39 -0
  196. package/dist/engine/agent-api/GameSceneGeometryMath.d.ts.map +1 -0
  197. package/dist/engine/agent-api/GameSceneGeometryMath.js +89 -0
  198. package/dist/engine/agent-api/GameSceneGeometryMath.js.map +1 -0
  199. package/dist/engine/agent-api/PublicGameGeometry.d.ts +117 -0
  200. package/dist/engine/agent-api/PublicGameGeometry.d.ts.map +1 -0
  201. package/dist/engine/agent-api/PublicGameGeometry.js +118 -0
  202. package/dist/engine/agent-api/PublicGameGeometry.js.map +1 -0
  203. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts +4 -0
  204. package/dist/engine/agent-api/RuntimeNodeHandle.d.ts.map +1 -1
  205. package/dist/engine/agent-api/RuntimeNodeHandle.js +2 -0
  206. package/dist/engine/agent-api/RuntimeNodeHandle.js.map +1 -1
  207. package/dist/engine/agent-api/SceneGroundingUtils.d.ts +86 -0
  208. package/dist/engine/agent-api/SceneGroundingUtils.d.ts.map +1 -1
  209. package/dist/engine/agent-api/SceneGroundingUtils.js +116 -0
  210. package/dist/engine/agent-api/SceneGroundingUtils.js.map +1 -1
  211. package/dist/engine/agent-api/index.d.ts +266 -5
  212. package/dist/engine/agent-api/index.d.ts.map +1 -1
  213. package/dist/engine/agent-api/index.js +2445 -113
  214. package/dist/engine/agent-api/index.js.map +1 -1
  215. package/dist/engine/agent-api/product-viewer-runtime.d.ts.map +1 -1
  216. package/dist/engine/production-runtime/TypedGLBActor.d.ts +14 -2
  217. package/dist/engine/production-runtime/TypedGLBActor.d.ts.map +1 -1
  218. package/dist/engine/production-runtime/TypedGLBActor.js +95 -4
  219. package/dist/engine/production-runtime/TypedGLBActor.js.map +1 -1
  220. package/dist/rendering/ForwardPass.js +7 -1
  221. package/dist/rendering/ForwardPass.js.map +1 -1
  222. package/dist/rendering/MorphTarget.d.ts +1 -0
  223. package/dist/rendering/MorphTarget.d.ts.map +1 -1
  224. package/dist/rendering/MorphTarget.js.map +1 -1
  225. package/dist/rendering/Renderer.js +7 -1
  226. package/dist/rendering/Renderer.js.map +1 -1
  227. package/package.json +30 -32
  228. package/templates/mini-game/README.md +13 -0
  229. package/templates/mini-game/index.html +1 -1
  230. package/templates/mini-game/package.json +1 -1
  231. package/templates/mini-game/src/main.ts +251 -21
  232. package/templates/mini-game/tests/playable.spec.ts +49 -0
  233. package/templates/mini-game/tests/screenshot.spec.ts +37 -32
@@ -0,0 +1,1269 @@
1
+ const DEFAULT_LOCOMOTION_CLIP_MAP = {
2
+ idle: "idle",
3
+ run: "run",
4
+ jump: "jump",
5
+ fall: "fall",
6
+ land: "land",
7
+ hit: "hit"
8
+ };
9
+ export function createGameLocomotionKit(options = {}) {
10
+ const clipMap = { ...DEFAULT_LOCOMOTION_CLIP_MAP, ...options.clipMap };
11
+ const availableClips = new Set((options.availableClips ?? []).map((clip) => clip.toLowerCase()));
12
+ const runSpeedThreshold = options.runSpeedThreshold ?? 0.08;
13
+ const jumpVelocityThreshold = options.jumpVelocityThreshold ?? 0.05;
14
+ const fallVelocityThreshold = options.fallVelocityThreshold ?? -0.05;
15
+ const landDuration = Math.max(0.02, options.landDuration ?? 0.16);
16
+ const hitDuration = Math.max(0.02, options.hitDuration ?? 0.28);
17
+ let frame = 0;
18
+ let time = 0;
19
+ let state = options.initialState ?? "idle";
20
+ let previousState = state;
21
+ let stateTime = 0;
22
+ let oneShotRemaining = 0;
23
+ let previousGrounded = true;
24
+ let currentInput = {
25
+ speed: 0,
26
+ vx: 0,
27
+ vy: 0,
28
+ grounded: true
29
+ };
30
+ let eventTypes = [];
31
+ let restart = true;
32
+ const resolveClip = (nextState) => clipMap[nextState];
33
+ const missingClips = () => {
34
+ if (availableClips.size === 0)
35
+ return [];
36
+ return Object.keys(clipMap)
37
+ .map((nextState) => clipMap[nextState])
38
+ .filter((clip, index, all) => all.indexOf(clip) === index && !availableClips.has(clip.toLowerCase()));
39
+ };
40
+ const snapshot = () => ({
41
+ kind: "aura-game-locomotion-state",
42
+ frame,
43
+ time,
44
+ state,
45
+ previousState,
46
+ clip: resolveClip(state),
47
+ loop: state === "idle" || state === "run" || state === "fall",
48
+ restart,
49
+ oneShot: state === "land" || state === "hit",
50
+ oneShotRemaining,
51
+ stateTime,
52
+ speed: currentInput.speed,
53
+ vx: currentInput.vx,
54
+ vy: currentInput.vy,
55
+ grounded: currentInput.grounded,
56
+ missingClips: missingClips(),
57
+ eventTypes: [...eventTypes]
58
+ });
59
+ const setState = (next, oneShotSeconds = 0) => {
60
+ restart = next !== state;
61
+ previousState = state;
62
+ if (restart)
63
+ stateTime = 0;
64
+ state = next;
65
+ oneShotRemaining = oneShotSeconds;
66
+ };
67
+ const reset = (nextState = options.initialState ?? "idle") => {
68
+ frame = 0;
69
+ time = 0;
70
+ state = nextState;
71
+ previousState = nextState;
72
+ stateTime = 0;
73
+ oneShotRemaining = 0;
74
+ previousGrounded = true;
75
+ currentInput = { speed: 0, vx: 0, vy: 0, grounded: true };
76
+ eventTypes = [];
77
+ restart = true;
78
+ return snapshot();
79
+ };
80
+ reset(state);
81
+ return {
82
+ kind: "aura-game-locomotion-kit",
83
+ step(dt, input = {}) {
84
+ const step = Math.min(0.1, Math.max(0, Number.isFinite(dt) ? dt : 0));
85
+ frame += 1;
86
+ time += step;
87
+ eventTypes = (input.events ?? []).map((event) => event.type.toLowerCase());
88
+ const vx = input.vx ?? 0;
89
+ const vy = input.vy ?? 0;
90
+ const speed = Math.max(0, input.speed ?? Math.abs(vx));
91
+ const grounded = input.grounded ?? previousGrounded;
92
+ currentInput = { speed, vx, vy, grounded };
93
+ const hitRequested = input.hit === true || eventTypes.some((type) => type === "hit" || type === "hazard" || type === "damage");
94
+ const landRequested = input.land === true || eventTypes.includes("land") || (!previousGrounded && grounded && state !== "hit");
95
+ const continuingOneShot = oneShotRemaining > 0 && (state === "hit" || state === "land");
96
+ if (hitRequested) {
97
+ setState("hit", hitDuration);
98
+ }
99
+ else if (landRequested) {
100
+ setState("land", landDuration);
101
+ }
102
+ else if (continuingOneShot && oneShotRemaining > step) {
103
+ restart = false;
104
+ oneShotRemaining = Math.max(0, oneShotRemaining - step);
105
+ }
106
+ else if (!grounded) {
107
+ if (vy > jumpVelocityThreshold)
108
+ setState("jump");
109
+ else if (vy < fallVelocityThreshold || state === "jump")
110
+ setState("fall");
111
+ else
112
+ setState("fall");
113
+ }
114
+ else if (speed > runSpeedThreshold) {
115
+ setState("run");
116
+ }
117
+ else {
118
+ setState("idle");
119
+ }
120
+ stateTime += step;
121
+ if (oneShotRemaining > 0 && !hitRequested && !landRequested)
122
+ oneShotRemaining = Math.max(0, oneShotRemaining - step);
123
+ previousGrounded = grounded;
124
+ return snapshot();
125
+ },
126
+ reset,
127
+ snapshot,
128
+ resolveClip
129
+ };
130
+ }
131
+ export const GAME_FALLING_BLOCK_PIECES = ["I", "J", "L", "O", "S", "T", "Z"];
132
+ const DEFAULT_PLATFORMER_LEVEL = {
133
+ id: "starter-platformer",
134
+ gravity: -22,
135
+ lowerBound: -3,
136
+ moveSpeed: 5.25,
137
+ jumpVelocity: 8.25,
138
+ dashSpeed: 9,
139
+ coyoteMs: 110,
140
+ jumpBufferMs: 130,
141
+ ledgeGrabTolerance: 0.18,
142
+ playerSize: [0.45, 1],
143
+ start: { x: 0, y: 0.35 },
144
+ finish: { x: 8, y: 0.35 },
145
+ platforms: [{ id: "ground", x: -1, y: 0, width: 10.5, height: 0.35 }],
146
+ movingPlatforms: [],
147
+ collectibles: [],
148
+ hazards: [],
149
+ checkpoints: [],
150
+ lives: 3
151
+ };
152
+ export function createGamePlatformerKit(level = {}) {
153
+ const config = { ...DEFAULT_PLATFORMER_LEVEL, ...level };
154
+ const playerWidth = config.playerSize[0];
155
+ const playerHeight = config.playerSize[1];
156
+ let state = createPlatformerState(config);
157
+ let events = [];
158
+ const snapshot = () => ({
159
+ kind: "aura-game-platformer-kit",
160
+ levelId: config.id,
161
+ status: state.status,
162
+ frame: state.frame,
163
+ time: state.time,
164
+ player: {
165
+ x: state.player.x,
166
+ y: state.player.y,
167
+ vx: state.player.vx,
168
+ vy: state.player.vy,
169
+ facing: state.player.facing,
170
+ grounded: state.player.grounded,
171
+ ridingPlatformId: state.player.ridingPlatformId
172
+ },
173
+ score: state.score,
174
+ lives: state.lives,
175
+ deaths: state.deaths,
176
+ checkpointId: state.checkpointId,
177
+ collected: [...state.collected].sort(),
178
+ activatedCheckpoints: [...state.activatedCheckpoints].sort(),
179
+ events: [...events]
180
+ });
181
+ const emit = (type, id) => {
182
+ events.push({ type, id, frame: state.frame, time: state.time, x: state.player.x, y: state.player.y });
183
+ };
184
+ const respawn = (reason, id) => {
185
+ emit(reason, id);
186
+ state.deaths += 1;
187
+ state.lives = Math.max(0, state.lives - 1);
188
+ const spawn = platformerSpawn(config, state.checkpointId);
189
+ state.player = {
190
+ x: spawn.x,
191
+ y: spawn.y,
192
+ vx: 0,
193
+ vy: 0,
194
+ facing: state.player.facing,
195
+ grounded: false,
196
+ coyote: 0,
197
+ jumpBuffer: 0,
198
+ dashCooldown: 0,
199
+ ridingPlatformId: undefined
200
+ };
201
+ emit("respawn", state.checkpointId);
202
+ };
203
+ return {
204
+ kind: "aura-game-platformer-kit",
205
+ step(dt, input = {}) {
206
+ const step = Math.min(0.05, Math.max(0, dt || 0));
207
+ events = [];
208
+ if (state.status === "completed")
209
+ return snapshot();
210
+ state.frame += 1;
211
+ const previousTime = state.time;
212
+ state.time += step;
213
+ const previousPlayer = { ...state.player };
214
+ const previousMoving = platformerMovingRectsAt(previousTime, config);
215
+ const moving = platformerMovingRectsAt(state.time, config);
216
+ const carried = state.player.ridingPlatformId
217
+ ? moving.find((platform) => platform.id === state.player.ridingPlatformId)
218
+ : undefined;
219
+ const carriedPrevious = state.player.ridingPlatformId
220
+ ? previousMoving.find((platform) => platform.id === state.player.ridingPlatformId)
221
+ : undefined;
222
+ if (carried && carriedPrevious && state.player.grounded) {
223
+ state.player.x += carried.x - carriedPrevious.x;
224
+ state.player.y += platformTop(carried) - platformTop(carriedPrevious);
225
+ }
226
+ const moveX = clampNumber(input.moveX ?? 0, -1, 1);
227
+ if (Math.abs(moveX) > 0.01)
228
+ state.player.facing = moveX >= 0 ? 1 : -1;
229
+ state.player.vx = moveX * config.moveSpeed;
230
+ state.player.coyote = state.player.grounded ? config.coyoteMs / 1000 : Math.max(0, state.player.coyote - step);
231
+ state.player.jumpBuffer = input.jumpPressed ? config.jumpBufferMs / 1000 : Math.max(0, state.player.jumpBuffer - step);
232
+ state.player.dashCooldown = Math.max(0, state.player.dashCooldown - step);
233
+ if (input.dashPressed && state.player.dashCooldown <= 0) {
234
+ state.player.vx = state.player.facing * config.dashSpeed;
235
+ state.player.dashCooldown = 0.38;
236
+ emit("dash");
237
+ }
238
+ if (state.player.jumpBuffer > 0 && (state.player.grounded || state.player.coyote > 0)) {
239
+ state.player.vy = config.jumpVelocity;
240
+ state.player.grounded = false;
241
+ state.player.ridingPlatformId = undefined;
242
+ state.player.coyote = 0;
243
+ state.player.jumpBuffer = 0;
244
+ emit("jump");
245
+ }
246
+ const gravity = input.fastFall && state.player.vy < 0 ? config.gravity * 1.6 : config.gravity;
247
+ state.player.vy += gravity * step;
248
+ state.player.x += state.player.vx * step;
249
+ state.player.y += state.player.vy * step;
250
+ state.player.grounded = false;
251
+ state.player.ridingPlatformId = undefined;
252
+ const platforms = [...config.platforms, ...moving];
253
+ for (const platform of platforms) {
254
+ const top = platformTop(platform);
255
+ const wasAbove = previousPlayer.y >= top - 0.04;
256
+ const fallingOnto = state.player.vy <= 0 && state.player.y <= top + config.ledgeGrabTolerance;
257
+ if (wasAbove && fallingOnto && platformerHorizontalOverlap(state.player.x, playerWidth, platform, config.ledgeGrabTolerance)) {
258
+ state.player.y = top;
259
+ state.player.vy = 0;
260
+ state.player.grounded = true;
261
+ state.player.coyote = config.coyoteMs / 1000;
262
+ state.player.ridingPlatformId = moving.some((candidate) => candidate.id === platform.id) ? platform.id : undefined;
263
+ if (!previousPlayer.grounded)
264
+ emit("land", platform.id);
265
+ break;
266
+ }
267
+ }
268
+ for (const coin of config.collectibles) {
269
+ if (state.collected.has(coin.id))
270
+ continue;
271
+ const radius = coin.radius ?? 0.42;
272
+ if (Math.hypot(state.player.x - coin.x, state.player.y + playerHeight * 0.5 - coin.y) <= radius) {
273
+ state.collected.add(coin.id);
274
+ state.score += coin.value ?? 1;
275
+ emit("collect", coin.id);
276
+ }
277
+ }
278
+ for (const checkpoint of config.checkpoints) {
279
+ if (state.activatedCheckpoints.has(checkpoint.id))
280
+ continue;
281
+ if (Math.hypot(state.player.x - checkpoint.x, state.player.y - checkpoint.y) <= (checkpoint.radius ?? 0.7)) {
282
+ state.checkpointId = checkpoint.id;
283
+ state.activatedCheckpoints.add(checkpoint.id);
284
+ emit("checkpoint", checkpoint.id);
285
+ }
286
+ }
287
+ for (const hazard of config.hazards) {
288
+ if (rectsOverlap(platformerPlayerRect(state.player, playerWidth, playerHeight), hazard)) {
289
+ respawn("hazard", hazard.id);
290
+ break;
291
+ }
292
+ }
293
+ if (state.player.y < config.lowerBound)
294
+ respawn("fall");
295
+ if (config.finish && state.player.x >= config.finish.x && Math.abs(state.player.y - config.finish.y) <= 1.25) {
296
+ state.status = "completed";
297
+ emit("complete");
298
+ }
299
+ return snapshot();
300
+ },
301
+ reset(checkpointId) {
302
+ events = [];
303
+ state = createPlatformerState(config, checkpointId);
304
+ return snapshot();
305
+ },
306
+ snapshot,
307
+ events() {
308
+ return [...events];
309
+ },
310
+ consumeEvents() {
311
+ const consumed = [...events];
312
+ events = [];
313
+ return consumed;
314
+ }
315
+ };
316
+ }
317
+ export function createGameAssetBoundPlatformerLevel(options) {
318
+ assertGameAssetId(options.characterAsset, "characterAsset");
319
+ const level = options.level;
320
+ const platforms = [...(level.platforms ?? [])];
321
+ const checkpoints = [...(level.checkpoints ?? [])];
322
+ const minPlayableSeconds = options.minPlayableSeconds ?? 30;
323
+ const minCheckpoints = options.minCheckpoints ?? 3;
324
+ const minSurfaceCount = options.minSurfaceCount ?? 4;
325
+ const start = level.start ?? DEFAULT_PLATFORMER_LEVEL.start;
326
+ const finish = level.finish;
327
+ const moveSpeed = Math.max(0.001, level.moveSpeed ?? DEFAULT_PLATFORMER_LEVEL.moveSpeed);
328
+ if (!finish)
329
+ throw new Error("game.assetBoundPlatformerLevel requires a finish point for public platformer examples.");
330
+ if (finish.x <= start.x)
331
+ throw new Error("game.assetBoundPlatformerLevel requires finish.x to be ahead of start.x.");
332
+ if (platforms.length < minSurfaceCount) {
333
+ throw new Error(`game.assetBoundPlatformerLevel requires at least ${minSurfaceCount} playable surfaces.`);
334
+ }
335
+ if (checkpoints.length < minCheckpoints) {
336
+ throw new Error(`game.assetBoundPlatformerLevel requires at least ${minCheckpoints} checkpoints.`);
337
+ }
338
+ const authoredPlayableSeconds = roundGameMetric((finish.x - start.x) / moveSpeed);
339
+ if (authoredPlayableSeconds < minPlayableSeconds) {
340
+ throw new Error(`game.assetBoundPlatformerLevel requires at least ${minPlayableSeconds}s of authored playable path.`);
341
+ }
342
+ const surfaceIds = new Set(platforms.map((platform) => platform.id));
343
+ const playableSurfaceMap = options.playableSurfaceMap;
344
+ if (playableSurfaceMap) {
345
+ validatePlatformerPlayableSurfaceMap(playableSurfaceMap, options.worldAssetBindings, surfaceIds, minPlayableSeconds);
346
+ }
347
+ const worldAssets = validatePlatformerWorldBindings(options.worldAssetBindings, surfaceIds);
348
+ const worldAssetHashes = Object.fromEntries(options.worldAssetBindings
349
+ .filter((binding) => typeof binding.worldAssetHash === "string")
350
+ .map((binding) => [binding.worldAsset, binding.worldAssetHash]));
351
+ return {
352
+ ...level,
353
+ assetBinding: {
354
+ kind: "aura-game-asset-bound-platformer-level",
355
+ layoutContractVersion: "1.0",
356
+ characterAsset: options.characterAsset,
357
+ worldAssets,
358
+ ...(Object.keys(worldAssetHashes).length > 0 ? { worldAssetHashes } : {}),
359
+ ...(playableSurfaceMap ? { surfaceSource: playableSurfaceMap.source, characterScaleRatio: playableSurfaceMap.characterScaleRatio, confidence: playableSurfaceMap.confidence } : {}),
360
+ authoredPlayableSeconds,
361
+ surfaceCount: platforms.length,
362
+ checkpointCount: checkpoints.length
363
+ }
364
+ };
365
+ }
366
+ export function createGameAssetBoundRacingRoute(options) {
367
+ assertGameAssetId(options.vehicleAsset, "vehicleAsset");
368
+ assertGameAssetId(options.trackAsset, "trackAsset");
369
+ const points = [...options.route.points];
370
+ const checkpoints = [...(options.route.checkpoints ?? [])];
371
+ const minLapSeconds = options.minLapSeconds ?? 30;
372
+ const minCheckpoints = options.minCheckpoints ?? 4;
373
+ const minRouteLength = options.minRouteLength ?? 6;
374
+ if (points.length < 8)
375
+ throw new Error("game.assetBoundRacingRoute requires at least 8 route points.");
376
+ if (checkpoints.length < minCheckpoints) {
377
+ throw new Error(`game.assetBoundRacingRoute requires at least ${minCheckpoints} checkpoints.`);
378
+ }
379
+ const routeLength = roundGameMetric(measureRacingRouteLength(options.route));
380
+ if (routeLength < minRouteLength) {
381
+ throw new Error(`game.assetBoundRacingRoute requires route length >= ${minRouteLength}.`);
382
+ }
383
+ if (options.authoredLapSeconds < minLapSeconds) {
384
+ throw new Error(`game.assetBoundRacingRoute requires at least ${minLapSeconds}s authored lap duration.`);
385
+ }
386
+ if (options.topology) {
387
+ validateRacingTrackTopology(options.topology, options.trackAsset, points, checkpoints, minLapSeconds);
388
+ }
389
+ return {
390
+ ...options.route,
391
+ assetBinding: {
392
+ kind: "aura-game-asset-bound-racing-route",
393
+ layoutContractVersion: "1.0",
394
+ vehicleAsset: options.vehicleAsset,
395
+ trackAsset: options.trackAsset,
396
+ ...(options.topology ? { trackAssetHash: options.topology.assetHash, topologySource: options.topology.source, confidence: options.topology.confidence } : {}),
397
+ routeLength,
398
+ authoredLapSeconds: roundGameMetric(options.authoredLapSeconds),
399
+ pointCount: points.length,
400
+ checkpointCount: checkpoints.length
401
+ }
402
+ };
403
+ }
404
+ export function createGameRacingKit(options) {
405
+ const routeId = options.route.id ?? "racing-route";
406
+ const segments = createRaceSegments(options.route);
407
+ const length = segments.at(-1) ? segments[segments.length - 1].start + segments[segments.length - 1].length : 1;
408
+ const checkpoints = options.route.checkpoints?.length ? [...options.route.checkpoints] : [0.25, 0.5, 0.75, 0.98];
409
+ const width = Math.max(0.25, options.route.width ?? 1.2);
410
+ const maxSpeed = options.maxSpeed ?? 18;
411
+ const acceleration = options.acceleration ?? 16;
412
+ const brakeStrength = options.brakeStrength ?? 24;
413
+ const reverseSpeed = options.reverseSpeed ?? 4;
414
+ const drag = options.drag ?? 2.4;
415
+ const steerRate = options.steerRate ?? 2.7;
416
+ const boostAcceleration = options.boostAcceleration ?? 9;
417
+ const offTrackDrag = options.offTrackDrag ?? 5.5;
418
+ const checkpointRadius = options.checkpointRadius ?? 0.07;
419
+ const lapsToWin = Math.max(1, options.lapsToWin ?? 1);
420
+ let events = [];
421
+ let state = createRaceState(options.startProgress ?? 0);
422
+ function createRaceState(progress) {
423
+ const sample = sampleRaceRoute(segments, length, progress);
424
+ return {
425
+ frame: 0,
426
+ time: 0,
427
+ lap: 1,
428
+ checkpoint: 0,
429
+ lapTime: 0,
430
+ bestTime: undefined,
431
+ speed: 0,
432
+ drift: 0,
433
+ offTrack: false,
434
+ progress: normalizeProgress(progress),
435
+ distance: normalizeProgress(progress) * length,
436
+ trackOffset: 0,
437
+ position: { x: sample.x, y: sample.y },
438
+ heading: sample.heading,
439
+ status: "running"
440
+ };
441
+ }
442
+ const emit = (type, id) => {
443
+ events.push({ type, id, frame: state.frame, time: state.time, lap: state.lap, checkpoint: state.checkpoint });
444
+ };
445
+ const snapshot = () => ({
446
+ kind: "aura-game-racing-kit",
447
+ routeId,
448
+ checkpointCount: checkpoints.length,
449
+ lapsToWin,
450
+ events: [...events],
451
+ ...state
452
+ });
453
+ const placeAtProgress = (progress, offset = 0) => {
454
+ const sample = sampleRaceRoute(segments, length, progress);
455
+ const normal = { x: -Math.sin(sample.heading), y: Math.cos(sample.heading) };
456
+ state = {
457
+ ...state,
458
+ progress: normalizeProgress(progress),
459
+ distance: normalizeProgress(progress) * length,
460
+ trackOffset: Math.abs(offset),
461
+ position: { x: sample.x + normal.x * offset, y: sample.y + normal.y * offset },
462
+ heading: sample.heading
463
+ };
464
+ return snapshot();
465
+ };
466
+ return {
467
+ kind: "aura-game-racing-kit",
468
+ step(dt, input = {}) {
469
+ events = [];
470
+ const step = Math.min(0.05, Math.max(0, dt || 0));
471
+ if (input.reset) {
472
+ state = createRaceState(options.startProgress ?? 0);
473
+ emit("reset");
474
+ return snapshot();
475
+ }
476
+ if (state.status === "finished")
477
+ return snapshot();
478
+ const previousProgress = state.progress;
479
+ state = { ...state, frame: state.frame + 1, time: state.time + step, lapTime: state.lapTime + step };
480
+ const throttle = typeof input.throttle === "boolean" ? (input.throttle ? 1 : 0) : clampNumber(input.throttle ?? 0, 0, 1);
481
+ const brake = typeof input.brake === "boolean" ? (input.brake ? 1 : 0) : clampNumber(input.brake ?? 0, 0, 1);
482
+ let speed = state.speed + throttle * acceleration * step - brake * brakeStrength * step;
483
+ if (input.boost && state.drift > 0.18)
484
+ speed += boostAcceleration * step;
485
+ speed -= Math.sign(speed) * Math.min(Math.abs(speed), drag * step);
486
+ speed = clampNumber(speed, -reverseSpeed, maxSpeed);
487
+ const steer = clampNumber(input.steer ?? 0, -1, 1);
488
+ const drift = clampNumber(state.drift + ((input.drift && Math.abs(steer) > 0.1 && speed > 2) ? step * 1.8 : -step * 1.2), 0, 1);
489
+ const heading = state.heading + steer * steerRate * (0.28 + Math.min(1, Math.abs(speed) / maxSpeed)) * (1 + drift * 0.55) * step * (speed < 0 ? -1 : 1);
490
+ const position = {
491
+ x: state.position.x + Math.cos(heading) * speed * step,
492
+ y: state.position.y + Math.sin(heading) * speed * step
493
+ };
494
+ const nearest = nearestRacePoint(segments, length, position);
495
+ const offTrack = nearest.offset > width * 0.5;
496
+ if (offTrack) {
497
+ speed *= Math.max(0, 1 - offTrackDrag * step);
498
+ if (!state.offTrack)
499
+ emit("off-track");
500
+ }
501
+ state = {
502
+ ...state,
503
+ speed,
504
+ drift,
505
+ heading,
506
+ position,
507
+ progress: nearest.progress,
508
+ distance: nearest.distance,
509
+ trackOffset: nearest.offset,
510
+ offTrack
511
+ };
512
+ const target = checkpoints[state.checkpoint] ?? 1;
513
+ if (progressDistance(state.progress, target) <= checkpointRadius && !offTrack) {
514
+ emit("checkpoint", `checkpoint-${state.checkpoint + 1}`);
515
+ state = { ...state, checkpoint: state.checkpoint + 1 };
516
+ }
517
+ if (state.checkpoint >= checkpoints.length && previousProgress > 0.72 && state.progress < 0.18 && !offTrack) {
518
+ const bestTime = state.bestTime === undefined ? state.lapTime : Math.min(state.bestTime, state.lapTime);
519
+ emit("lap", `lap-${state.lap}`);
520
+ state = {
521
+ ...state,
522
+ lap: state.lap + 1,
523
+ checkpoint: 0,
524
+ bestTime,
525
+ lapTime: 0,
526
+ status: state.lap >= lapsToWin ? "finished" : "running"
527
+ };
528
+ if (state.status === "finished")
529
+ emit("finish");
530
+ }
531
+ return snapshot();
532
+ },
533
+ reset(progress = options.startProgress ?? 0) {
534
+ events = [];
535
+ state = createRaceState(progress);
536
+ return snapshot();
537
+ },
538
+ placeAtProgress,
539
+ snapshot,
540
+ camera() {
541
+ return {
542
+ kind: "aura-game-racing-camera",
543
+ position: [state.position.x - Math.cos(state.heading) * 4, 3.2, state.position.y - Math.sin(state.heading) * 4],
544
+ target: [state.position.x, 0.35, state.position.y],
545
+ fov: 48
546
+ };
547
+ },
548
+ events() {
549
+ return [...events];
550
+ },
551
+ consumeEvents() {
552
+ const consumed = [...events];
553
+ events = [];
554
+ return consumed;
555
+ }
556
+ };
557
+ }
558
+ const FALLING_BLOCK_SHAPES = {
559
+ I: [
560
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 3, y: 1 }],
561
+ [{ x: 2, y: 0 }, { x: 2, y: 1 }, { x: 2, y: 2 }, { x: 2, y: 3 }],
562
+ [{ x: 0, y: 2 }, { x: 1, y: 2 }, { x: 2, y: 2 }, { x: 3, y: 2 }],
563
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 1, y: 3 }]
564
+ ],
565
+ J: [
566
+ [{ x: 0, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
567
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }],
568
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
569
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 0, y: 2 }, { x: 1, y: 2 }]
570
+ ],
571
+ L: [
572
+ [{ x: 2, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
573
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
574
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 0, y: 2 }],
575
+ [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
576
+ ],
577
+ O: [
578
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
579
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
580
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
581
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }]
582
+ ],
583
+ S: [
584
+ [{ x: 1, y: 0 }, { x: 2, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }],
585
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 2, y: 2 }],
586
+ [{ x: 1, y: 1 }, { x: 2, y: 1 }, { x: 0, y: 2 }, { x: 1, y: 2 }],
587
+ [{ x: 0, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
588
+ ],
589
+ T: [
590
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
591
+ [{ x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
592
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
593
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }]
594
+ ],
595
+ Z: [
596
+ [{ x: 0, y: 0 }, { x: 1, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }],
597
+ [{ x: 2, y: 0 }, { x: 1, y: 1 }, { x: 2, y: 1 }, { x: 1, y: 2 }],
598
+ [{ x: 0, y: 1 }, { x: 1, y: 1 }, { x: 1, y: 2 }, { x: 2, y: 2 }],
599
+ [{ x: 1, y: 0 }, { x: 0, y: 1 }, { x: 1, y: 1 }, { x: 0, y: 2 }]
600
+ ]
601
+ };
602
+ export function createGameFallingBlocksKit(options = {}) {
603
+ const width = options.width ?? 10;
604
+ const height = options.height ?? 22;
605
+ const hiddenRows = options.hiddenRows ?? 2;
606
+ const lockDelayFrames = options.lockDelayFrames ?? 30;
607
+ let seed = options.seed ?? 1;
608
+ let rng = seed;
609
+ let frame = 0;
610
+ let board = cloneBoard(options.board ?? emptyBoard(width, height));
611
+ let queue = [];
612
+ let active = null;
613
+ let hold = null;
614
+ let holdUsed = false;
615
+ let score = 0;
616
+ let lines = 0;
617
+ let level = 1;
618
+ let combo = -1;
619
+ let backToBack = false;
620
+ let lockTimer = 0;
621
+ let gravityCounter = 0;
622
+ let gameOver = false;
623
+ let events = [];
624
+ let replay = [];
625
+ const gravityFrames = () => Math.max(1, options.gravityFrames ?? Math.floor(Math.max(2, 48 - (level - 1) * 5)));
626
+ const emit = (type, patch = {}) => {
627
+ events.push({ type, frame, ...patch });
628
+ };
629
+ const refillQueue = () => {
630
+ while (queue.length < 7) {
631
+ const shuffled = shuffleBag(rng);
632
+ rng = shuffled.rng;
633
+ queue.push(...shuffled.bag);
634
+ }
635
+ };
636
+ const spawn = () => {
637
+ refillQueue();
638
+ const kind = queue.shift() ?? "I";
639
+ const piece = { kind, x: Math.floor(width / 2) - 2, y: 0, rotation: 0 };
640
+ active = piece;
641
+ holdUsed = false;
642
+ lockTimer = 0;
643
+ emit("spawn", { piece: kind });
644
+ if (!canPlace(board, piece, width, height)) {
645
+ gameOver = true;
646
+ emit("game-over", { piece: kind });
647
+ }
648
+ };
649
+ const snapshot = () => ({
650
+ kind: "aura-game-falling-blocks-kit",
651
+ width,
652
+ height,
653
+ hiddenRows,
654
+ frame,
655
+ seed,
656
+ board: cloneBoard(board),
657
+ active,
658
+ hold,
659
+ holdUsed,
660
+ queue: [...queue],
661
+ score,
662
+ lines,
663
+ level,
664
+ combo,
665
+ backToBack,
666
+ lockDelayFrames,
667
+ gravityFrames: gravityFrames(),
668
+ gameOver,
669
+ checksum: fallingBlocksChecksum({ board, active, hold, queue, score, lines, level, frame, gameOver }),
670
+ events: [...events]
671
+ });
672
+ const lock = () => {
673
+ if (!active)
674
+ return;
675
+ board = placePiece(board, active);
676
+ emit("lock", { piece: active.kind });
677
+ const cleared = clearLines(board);
678
+ board = cleared.board;
679
+ if (cleared.lines > 0) {
680
+ lines += cleared.lines;
681
+ combo += 1;
682
+ const difficult = cleared.lines === 4;
683
+ score += scoreForLines(cleared.lines, level) + Math.max(0, combo) * 50;
684
+ backToBack = difficult ? backToBack || cleared.lines === 4 : false;
685
+ level = Math.floor(lines / 10) + 1;
686
+ emit("line-clear", { lines: cleared.lines, score });
687
+ }
688
+ else {
689
+ combo = -1;
690
+ }
691
+ active = null;
692
+ spawn();
693
+ };
694
+ const tryMove = (piece) => {
695
+ if (!canPlace(board, piece, width, height))
696
+ return false;
697
+ active = piece;
698
+ lockTimer = 0;
699
+ return true;
700
+ };
701
+ const hardDropDistance = () => {
702
+ if (!active)
703
+ return 0;
704
+ let distance = 0;
705
+ let candidate = { ...active, y: active.y + 1 };
706
+ while (canPlace(board, candidate, width, height)) {
707
+ distance += 1;
708
+ candidate = { ...candidate, y: candidate.y + 1 };
709
+ }
710
+ return distance;
711
+ };
712
+ const reset = (nextSeed = seed) => {
713
+ seed = nextSeed;
714
+ rng = seed;
715
+ frame = 0;
716
+ board = cloneBoard(options.board ?? emptyBoard(width, height));
717
+ queue = [];
718
+ active = null;
719
+ hold = null;
720
+ holdUsed = false;
721
+ score = 0;
722
+ lines = 0;
723
+ level = 1;
724
+ combo = -1;
725
+ backToBack = false;
726
+ lockTimer = 0;
727
+ gravityCounter = 0;
728
+ gameOver = false;
729
+ events = [];
730
+ replay = [];
731
+ emit("reset");
732
+ spawn();
733
+ return snapshot();
734
+ };
735
+ const kit = {
736
+ kind: "aura-game-falling-blocks-kit",
737
+ step(action) {
738
+ if (action.type !== "reset")
739
+ replay.push({ frame, action });
740
+ if (action.type === "reset")
741
+ return reset(action.seed);
742
+ if (action.type === "move")
743
+ return this.move(action.dx);
744
+ if (action.type === "rotate")
745
+ return this.rotate(action.direction);
746
+ if (action.type === "softDrop")
747
+ return this.softDrop();
748
+ if (action.type === "hardDrop")
749
+ return this.hardDrop();
750
+ if (action.type === "hold")
751
+ return this.hold();
752
+ return this.tick(action.frames);
753
+ },
754
+ tick(framesToAdvance = 1) {
755
+ events = [];
756
+ for (let index = 0; index < Math.max(1, framesToAdvance); index += 1) {
757
+ frame += 1;
758
+ if (gameOver || !active)
759
+ continue;
760
+ gravityCounter += 1;
761
+ if (gravityCounter >= gravityFrames()) {
762
+ gravityCounter = 0;
763
+ if (!tryMove({ ...active, y: active.y + 1 })) {
764
+ lockTimer += 1;
765
+ if (lockTimer >= lockDelayFrames)
766
+ lock();
767
+ }
768
+ }
769
+ }
770
+ return snapshot();
771
+ },
772
+ move(dx) {
773
+ events = [];
774
+ if (active && !gameOver && tryMove({ ...active, x: active.x + dx }))
775
+ emit("move", { piece: active.kind });
776
+ return snapshot();
777
+ },
778
+ rotate(direction = 1) {
779
+ events = [];
780
+ if (!active || gameOver)
781
+ return snapshot();
782
+ const nextRotation = wrapRotation(active.rotation + direction);
783
+ for (const [kickX, kickY] of [[0, 0], [1, 0], [-1, 0], [2, 0], [-2, 0], [0, -1]]) {
784
+ const candidate = { ...active, x: active.x + kickX, y: active.y + kickY, rotation: nextRotation };
785
+ if (tryMove(candidate)) {
786
+ emit("rotate", { piece: active.kind });
787
+ break;
788
+ }
789
+ }
790
+ return snapshot();
791
+ },
792
+ softDrop() {
793
+ events = [];
794
+ frame += 1;
795
+ if (active && !gameOver) {
796
+ if (tryMove({ ...active, y: active.y + 1 })) {
797
+ score += 1;
798
+ emit("soft-drop", { piece: active.kind, score });
799
+ }
800
+ else {
801
+ lockTimer = lockDelayFrames;
802
+ lock();
803
+ }
804
+ }
805
+ return snapshot();
806
+ },
807
+ hardDrop() {
808
+ events = [];
809
+ frame += 1;
810
+ if (active && !gameOver) {
811
+ const distance = hardDropDistance();
812
+ active = { ...active, y: active.y + distance };
813
+ score += distance * 2;
814
+ emit("hard-drop", { piece: active.kind, score });
815
+ lock();
816
+ }
817
+ return snapshot();
818
+ },
819
+ hold() {
820
+ events = [];
821
+ if (!active || holdUsed || gameOver)
822
+ return snapshot();
823
+ const previous = hold;
824
+ hold = active.kind;
825
+ holdUsed = true;
826
+ emit("hold", { piece: hold });
827
+ if (previous) {
828
+ active = { kind: previous, x: Math.floor(width / 2) - 2, y: 0, rotation: 0 };
829
+ }
830
+ else {
831
+ active = null;
832
+ spawn();
833
+ holdUsed = true;
834
+ }
835
+ return snapshot();
836
+ },
837
+ reset,
838
+ setBoard(nextBoard) {
839
+ events = [];
840
+ board = cloneBoard(nextBoard);
841
+ return snapshot();
842
+ },
843
+ setActive(piece) {
844
+ events = [];
845
+ active = piece;
846
+ return snapshot();
847
+ },
848
+ snapshot,
849
+ checksum() {
850
+ return snapshot().checksum;
851
+ },
852
+ replay() {
853
+ return [...replay];
854
+ },
855
+ events() {
856
+ return [...events];
857
+ },
858
+ consumeEvents() {
859
+ const consumed = [...events];
860
+ events = [];
861
+ return consumed;
862
+ }
863
+ };
864
+ reset(seed);
865
+ return kit;
866
+ }
867
+ function createPlatformerState(config, checkpointId = "start") {
868
+ const spawn = platformerSpawn(config, checkpointId);
869
+ return {
870
+ status: "playing",
871
+ frame: 0,
872
+ time: 0,
873
+ player: {
874
+ x: spawn.x,
875
+ y: spawn.y,
876
+ vx: 0,
877
+ vy: 0,
878
+ facing: 1,
879
+ grounded: false,
880
+ coyote: 0,
881
+ jumpBuffer: 0,
882
+ dashCooldown: 0
883
+ },
884
+ score: 0,
885
+ lives: config.lives,
886
+ deaths: 0,
887
+ checkpointId,
888
+ collected: new Set(),
889
+ activatedCheckpoints: checkpointId === "start" ? new Set() : new Set([checkpointId])
890
+ };
891
+ }
892
+ function platformerSpawn(config, checkpointId) {
893
+ if (checkpointId !== "start") {
894
+ const checkpoint = config.checkpoints.find((candidate) => candidate.id === checkpointId);
895
+ if (checkpoint)
896
+ return checkpoint;
897
+ }
898
+ return config.start;
899
+ }
900
+ function platformerMovingRectsAt(time, config) {
901
+ return config.movingPlatforms.map((platform) => {
902
+ const phase = ((time / Math.max(0.001, platform.period)) + (platform.phase ?? 0)) * Math.PI * 2;
903
+ const offset = Math.sin(phase) * platform.amplitude;
904
+ return {
905
+ id: platform.id,
906
+ x: platform.axis === "x" ? platform.x + offset : platform.x,
907
+ y: platform.axis === "y" ? platform.y + offset : platform.y,
908
+ width: platform.width,
909
+ height: platform.height
910
+ };
911
+ });
912
+ }
913
+ function platformTop(rect) {
914
+ return rect.y + rect.height;
915
+ }
916
+ function platformerHorizontalOverlap(playerX, playerWidth, rect, tolerance = 0) {
917
+ return playerX + playerWidth / 2 >= rect.x - tolerance && playerX - playerWidth / 2 <= rect.x + rect.width + tolerance;
918
+ }
919
+ function platformerPlayerRect(player, width, height) {
920
+ return { id: "player", x: player.x - width / 2, y: player.y, width, height };
921
+ }
922
+ function rectsOverlap(a, b) {
923
+ return a.x < b.x + b.width && a.x + a.width > b.x && a.y < b.y + b.height && a.y + a.height > b.y;
924
+ }
925
+ function createRaceSegments(route) {
926
+ const points = [...route.points];
927
+ if (points.length < 2)
928
+ throw new Error("game.racing route needs at least two points.");
929
+ if (route.closed !== false) {
930
+ const first = points[0];
931
+ const last = points[points.length - 1];
932
+ if (first.x !== last.x || first.y !== last.y)
933
+ points.push(first);
934
+ }
935
+ let cursor = 0;
936
+ return points.slice(0, -1).map((from, index) => {
937
+ const to = points[index + 1];
938
+ const dx = to.x - from.x;
939
+ const dy = to.y - from.y;
940
+ const length = Math.max(0.0001, Math.hypot(dx, dy));
941
+ const segment = { from, to, length, start: cursor, heading: Math.atan2(dy, dx) };
942
+ cursor += length;
943
+ return segment;
944
+ });
945
+ }
946
+ function measureRacingRouteLength(route) {
947
+ const segments = createRaceSegments(route);
948
+ return segments.at(-1) ? segments[segments.length - 1].start + segments[segments.length - 1].length : 0;
949
+ }
950
+ function validatePlatformerWorldBindings(bindings, surfaceIds) {
951
+ if (bindings.length === 0)
952
+ throw new Error("game.assetBoundPlatformerLevel requires at least one world asset binding.");
953
+ const covered = new Set();
954
+ const worldAssets = [];
955
+ for (const binding of bindings) {
956
+ assertGameAssetId(binding.worldAsset, "worldAsset");
957
+ if (binding.surfaceIds.length === 0) {
958
+ throw new Error(`game.assetBoundPlatformerLevel world asset ${binding.worldAsset} has no bound playable surfaces.`);
959
+ }
960
+ worldAssets.push(binding.worldAsset);
961
+ for (const surfaceId of binding.surfaceIds) {
962
+ if (!surfaceIds.has(surfaceId)) {
963
+ throw new Error(`game.assetBoundPlatformerLevel binding references unknown surface ${surfaceId}.`);
964
+ }
965
+ covered.add(surfaceId);
966
+ }
967
+ }
968
+ for (const surfaceId of surfaceIds) {
969
+ if (!covered.has(surfaceId))
970
+ throw new Error(`game.assetBoundPlatformerLevel surface ${surfaceId} is not bound to a world asset.`);
971
+ }
972
+ return [...new Set(worldAssets)];
973
+ }
974
+ function validateRacingTrackTopology(topology, trackAsset, points, checkpoints, minLapSeconds) {
975
+ assertGameAssetId(topology.assetId, "topology.assetId");
976
+ if (topology.assetId !== trackAsset) {
977
+ throw new Error(`game.assetBoundRacingRoute topology asset ${topology.assetId} does not match track asset ${trackAsset}.`);
978
+ }
979
+ assertSha256Hash(topology.assetHash, "topology.assetHash");
980
+ if (!isPublicGameGeometrySource(topology.source)) {
981
+ throw new Error("game.assetBoundRacingRoute requires asset-mesh-extracted or overlay-validated topology for public racing examples.");
982
+ }
983
+ if (topology.source !== "asset-mesh-extracted" && !topology.evidence.routeOverlay) {
984
+ throw new Error("game.assetBoundRacingRoute requires retained route overlay evidence for authored topology.");
985
+ }
986
+ if (topology.roadCenterline.length < points.length) {
987
+ throw new Error("game.assetBoundRacingRoute topology must include at least the generated route point count.");
988
+ }
989
+ if (topology.checkpoints.length < checkpoints.length) {
990
+ throw new Error("game.assetBoundRacingRoute topology must include every generated checkpoint.");
991
+ }
992
+ if (topology.estimatedLapSeconds < minLapSeconds) {
993
+ throw new Error(`game.assetBoundRacingRoute topology requires at least ${minLapSeconds}s estimated lap duration.`);
994
+ }
995
+ if (topology.confidence < 0.65) {
996
+ throw new Error("game.assetBoundRacingRoute topology confidence is too low for a public racing example.");
997
+ }
998
+ validateRacingModelAlignment(topology.modelAlignment, "game.assetBoundRacingRoute");
999
+ }
1000
+ function validatePlatformerPlayableSurfaceMap(playableSurfaceMap, bindings, surfaceIds, minPlayableSeconds) {
1001
+ assertGameAssetId(playableSurfaceMap.assetId, "playableSurfaceMap.assetId");
1002
+ assertSha256Hash(playableSurfaceMap.assetHash, "playableSurfaceMap.assetHash");
1003
+ if (!isPublicGameGeometrySource(playableSurfaceMap.source)) {
1004
+ throw new Error("game.assetBoundPlatformerLevel requires asset-mesh-extracted or overlay-validated playable surfaces for public platformer examples.");
1005
+ }
1006
+ if (playableSurfaceMap.source !== "asset-mesh-extracted" && !playableSurfaceMap.evidence.routeOverlay) {
1007
+ throw new Error("game.assetBoundPlatformerLevel requires retained surface overlay evidence for authored playable surfaces.");
1008
+ }
1009
+ if (playableSurfaceMap.estimatedCompletionSeconds < minPlayableSeconds) {
1010
+ throw new Error(`game.assetBoundPlatformerLevel playable surface map requires at least ${minPlayableSeconds}s estimated completion time.`);
1011
+ }
1012
+ if (playableSurfaceMap.characterScaleRatio <= 0 || playableSurfaceMap.characterScaleRatio > 1.25) {
1013
+ throw new Error("game.assetBoundPlatformerLevel playable surface map character scale ratio is invalid.");
1014
+ }
1015
+ if (playableSurfaceMap.confidence < 0.65) {
1016
+ throw new Error("game.assetBoundPlatformerLevel playable surface confidence is too low for a public platformer example.");
1017
+ }
1018
+ validatePlatformerModelAlignment(playableSurfaceMap.modelAlignment, "game.assetBoundPlatformerLevel");
1019
+ const publicPlayableSurfaceIds = new Set(playableSurfaceMap.surfaces
1020
+ .filter(isPublicPlatformerPlayableSurface)
1021
+ .map((surface) => surface.id));
1022
+ if (publicPlayableSurfaceIds.size < 5) {
1023
+ throw new Error("game.assetBoundPlatformerLevel playableSurfaceMap requires at least five ground/platform/moving surfaces.");
1024
+ }
1025
+ for (const surfaceId of surfaceIds) {
1026
+ if (!publicPlayableSurfaceIds.has(surfaceId)) {
1027
+ throw new Error(`game.assetBoundPlatformerLevel surface ${surfaceId} is missing from playableSurfaceMap.`);
1028
+ }
1029
+ }
1030
+ const binding = bindings.find((candidate) => candidate.worldAsset === playableSurfaceMap.assetId);
1031
+ if (!binding) {
1032
+ throw new Error(`game.assetBoundPlatformerLevel playableSurfaceMap asset ${playableSurfaceMap.assetId} is not bound to this level.`);
1033
+ }
1034
+ if (binding.worldAssetHash !== playableSurfaceMap.assetHash) {
1035
+ throw new Error(`game.assetBoundPlatformerLevel playableSurfaceMap hash does not match ${playableSurfaceMap.assetId}.`);
1036
+ }
1037
+ }
1038
+ function isPublicPlatformerPlayableSurface(surface) {
1039
+ return surface.kind === "ground" || surface.kind === "platform" || surface.kind === "moving";
1040
+ }
1041
+ function assertFiniteNumber(value, field) {
1042
+ if (!Number.isFinite(value))
1043
+ throw new Error(`game asset-bound helper requires ${field} to be finite.`);
1044
+ }
1045
+ function assertNonEmptyString(value, field) {
1046
+ if (!value?.trim())
1047
+ throw new Error(`game asset-bound helper requires ${field}.`);
1048
+ }
1049
+ function assertVec3(value, field) {
1050
+ if (!Array.isArray(value) || value.length !== 3) {
1051
+ throw new Error(`game asset-bound helper requires ${field} to be a 3D tuple.`);
1052
+ }
1053
+ assertFiniteNumber(value[0], `${field}.x`);
1054
+ assertFiniteNumber(value[1], `${field}.y`);
1055
+ assertFiniteNumber(value[2], `${field}.z`);
1056
+ }
1057
+ function validateModelBounds(bounds, field) {
1058
+ assertVec3(bounds.min, `${field}.min`);
1059
+ assertVec3(bounds.max, `${field}.max`);
1060
+ const width = bounds.max[0] - bounds.min[0];
1061
+ const height = bounds.max[1] - bounds.min[1];
1062
+ const depth = bounds.max[2] - bounds.min[2];
1063
+ if (width <= 0 || height <= 0 || depth <= 0) {
1064
+ throw new Error(`game asset-bound helper requires ${field} to describe nonzero model bounds.`);
1065
+ }
1066
+ }
1067
+ function assertPointInsideModelBounds(point, bounds, field) {
1068
+ const epsilon = 0.001;
1069
+ const axis = ["x", "y", "z"];
1070
+ for (let index = 0; index < 3; index += 1) {
1071
+ const value = point[index];
1072
+ if (value < bounds.min[index] - epsilon || value > bounds.max[index] + epsilon) {
1073
+ throw new Error(`game asset-bound helper requires ${field}.${axis[index]} to be inside model bounds.`);
1074
+ }
1075
+ }
1076
+ }
1077
+ function validateModelAlignmentSource(source, evidence, field) {
1078
+ if (!isPublicGameGeometrySource(source)) {
1079
+ throw new Error(`game asset-bound helper requires ${field}.source to be mesh-extracted or overlay-validated.`);
1080
+ }
1081
+ assertNonEmptyString(evidence.notes, `${field}.evidence.notes`);
1082
+ if (source !== "asset-mesh-extracted") {
1083
+ assertNonEmptyString(evidence.routeOverlay, `${field}.evidence.routeOverlay`);
1084
+ }
1085
+ }
1086
+ function validateRacingModelAlignment(alignment, context) {
1087
+ if (!alignment) {
1088
+ throw new Error(`${context} requires modelAlignment for scene-bound racing topology.`);
1089
+ }
1090
+ validateModelBounds(alignment.modelBounds, `${context}.modelAlignment.modelBounds`);
1091
+ assertVec3(alignment.modelPoint, `${context}.modelAlignment.modelPoint`);
1092
+ assertPointInsideModelBounds(alignment.modelPoint, alignment.modelBounds, `${context}.modelAlignment.modelPoint`);
1093
+ assertFiniteNumber(alignment.gamePoint.x, `${context}.modelAlignment.gamePoint.x`);
1094
+ assertFiniteNumber(alignment.gamePoint.z, `${context}.modelAlignment.gamePoint.z`);
1095
+ validateRacingModelAnchors(alignment, context);
1096
+ validateModelAlignmentSource(alignment.source, alignment.evidence, `${context}.modelAlignment`);
1097
+ }
1098
+ function validatePlatformerModelAlignment(alignment, context) {
1099
+ if (!alignment) {
1100
+ throw new Error(`${context} requires modelAlignment for scene-bound platformer surfaces.`);
1101
+ }
1102
+ validateModelBounds(alignment.modelBounds, `${context}.modelAlignment.modelBounds`);
1103
+ assertVec3(alignment.modelPoint, `${context}.modelAlignment.modelPoint`);
1104
+ assertPointInsideModelBounds(alignment.modelPoint, alignment.modelBounds, `${context}.modelAlignment.modelPoint`);
1105
+ assertFiniteNumber(alignment.gamePoint.x, `${context}.modelAlignment.gamePoint.x`);
1106
+ assertFiniteNumber(alignment.gamePoint.y, `${context}.modelAlignment.gamePoint.y`);
1107
+ validatePlatformerModelAnchors(alignment, context);
1108
+ validateModelAlignmentSource(alignment.source, alignment.evidence, `${context}.modelAlignment`);
1109
+ }
1110
+ function validateRacingModelAnchors(alignment, context) {
1111
+ if (!alignment.anchorPairs)
1112
+ return;
1113
+ if (alignment.anchorPairs.length < 2) {
1114
+ throw new Error(`${context}.modelAlignment.anchorPairs requires at least two anchors when provided.`);
1115
+ }
1116
+ for (const [index, anchor] of alignment.anchorPairs.entries()) {
1117
+ assertNonEmptyString(anchor.id, `${context}.modelAlignment.anchorPairs[${index}].id`);
1118
+ assertVec3(anchor.modelPoint, `${context}.modelAlignment.anchorPairs[${index}].modelPoint`);
1119
+ assertPointInsideModelBounds(anchor.modelPoint, alignment.modelBounds, `${context}.modelAlignment.anchorPairs[${index}].modelPoint`);
1120
+ assertFiniteNumber(anchor.gamePoint.x, `${context}.modelAlignment.anchorPairs[${index}].gamePoint.x`);
1121
+ assertFiniteNumber(anchor.gamePoint.z, `${context}.modelAlignment.anchorPairs[${index}].gamePoint.z`);
1122
+ }
1123
+ }
1124
+ function validatePlatformerModelAnchors(alignment, context) {
1125
+ if (!alignment.anchorPairs)
1126
+ return;
1127
+ if (alignment.anchorPairs.length < 2) {
1128
+ throw new Error(`${context}.modelAlignment.anchorPairs requires at least two anchors when provided.`);
1129
+ }
1130
+ for (const [index, anchor] of alignment.anchorPairs.entries()) {
1131
+ assertNonEmptyString(anchor.id, `${context}.modelAlignment.anchorPairs[${index}].id`);
1132
+ assertVec3(anchor.modelPoint, `${context}.modelAlignment.anchorPairs[${index}].modelPoint`);
1133
+ assertPointInsideModelBounds(anchor.modelPoint, alignment.modelBounds, `${context}.modelAlignment.anchorPairs[${index}].modelPoint`);
1134
+ assertFiniteNumber(anchor.gamePoint.x, `${context}.modelAlignment.anchorPairs[${index}].gamePoint.x`);
1135
+ assertFiniteNumber(anchor.gamePoint.y, `${context}.modelAlignment.anchorPairs[${index}].gamePoint.y`);
1136
+ }
1137
+ }
1138
+ function assertGameAssetId(value, field) {
1139
+ if (!value.trim())
1140
+ throw new Error(`game asset-bound helper requires ${field}.`);
1141
+ }
1142
+ function assertSha256Hash(value, field) {
1143
+ if (!/^sha256-[a-f0-9]{64}$/.test(value))
1144
+ throw new Error(`game asset-bound helper requires ${field} to be a sha256- hash.`);
1145
+ }
1146
+ function isPublicGameGeometrySource(source) {
1147
+ return source === "asset-mesh-extracted" || source === "manifest-authored-overlay-validated" || source === "compiler-authored-overlay-validated";
1148
+ }
1149
+ function roundGameMetric(value) {
1150
+ return Math.round(value * 1000) / 1000;
1151
+ }
1152
+ function sampleRaceRoute(segments, length, progress) {
1153
+ const distance = normalizeProgress(progress) * length;
1154
+ const segment = segments.find((candidate) => distance >= candidate.start && distance <= candidate.start + candidate.length) ?? segments[segments.length - 1];
1155
+ const t = clampNumber((distance - segment.start) / segment.length, 0, 1);
1156
+ return {
1157
+ x: segment.from.x + (segment.to.x - segment.from.x) * t,
1158
+ y: segment.from.y + (segment.to.y - segment.from.y) * t,
1159
+ heading: segment.heading
1160
+ };
1161
+ }
1162
+ function nearestRacePoint(segments, length, point) {
1163
+ let best = { distance: 0, offset: Number.POSITIVE_INFINITY };
1164
+ for (const segment of segments) {
1165
+ const dx = segment.to.x - segment.from.x;
1166
+ const dy = segment.to.y - segment.from.y;
1167
+ const t = clampNumber(((point.x - segment.from.x) * dx + (point.y - segment.from.y) * dy) / (segment.length * segment.length), 0, 1);
1168
+ const x = segment.from.x + dx * t;
1169
+ const y = segment.from.y + dy * t;
1170
+ const offset = Math.hypot(point.x - x, point.y - y);
1171
+ if (offset < best.offset)
1172
+ best = { distance: segment.start + segment.length * t, offset };
1173
+ }
1174
+ return {
1175
+ distance: best.distance,
1176
+ progress: normalizeProgress(best.distance / length),
1177
+ offset: best.offset
1178
+ };
1179
+ }
1180
+ function progressDistance(a, b) {
1181
+ const direct = Math.abs(normalizeProgress(a) - normalizeProgress(b));
1182
+ return Math.min(direct, 1 - direct);
1183
+ }
1184
+ function normalizeProgress(value) {
1185
+ return ((value % 1) + 1) % 1;
1186
+ }
1187
+ function emptyBoard(width, height) {
1188
+ return Array.from({ length: height }, () => Array.from({ length: width }, () => null));
1189
+ }
1190
+ function cloneBoard(board) {
1191
+ return board.map((row) => [...row]);
1192
+ }
1193
+ function pieceCells(piece) {
1194
+ return FALLING_BLOCK_SHAPES[piece.kind][piece.rotation].map((cell) => ({ x: piece.x + cell.x, y: piece.y + cell.y }));
1195
+ }
1196
+ function canPlace(board, piece, width, height) {
1197
+ return pieceCells(piece).every((cell) => {
1198
+ if (cell.x < 0 || cell.x >= width || cell.y >= height)
1199
+ return false;
1200
+ if (cell.y < 0)
1201
+ return true;
1202
+ return board[cell.y]?.[cell.x] === null;
1203
+ });
1204
+ }
1205
+ function placePiece(board, piece) {
1206
+ const next = cloneBoard(board);
1207
+ for (const cell of pieceCells(piece)) {
1208
+ if (cell.y >= 0 && cell.y < next.length && cell.x >= 0 && cell.x < next[cell.y].length)
1209
+ next[cell.y][cell.x] = piece.kind;
1210
+ }
1211
+ return next;
1212
+ }
1213
+ function clearLines(board) {
1214
+ const width = board[0]?.length ?? 10;
1215
+ const remaining = board.filter((row) => row.some((cell) => cell === null)).map((row) => [...row]);
1216
+ const lines = board.length - remaining.length;
1217
+ const empty = Array.from({ length: lines }, () => Array.from({ length: width }, () => null));
1218
+ return { board: [...empty, ...remaining], lines };
1219
+ }
1220
+ function scoreForLines(lines, level) {
1221
+ if (lines === 1)
1222
+ return 100 * level;
1223
+ if (lines === 2)
1224
+ return 300 * level;
1225
+ if (lines === 3)
1226
+ return 500 * level;
1227
+ if (lines === 4)
1228
+ return 800 * level;
1229
+ return 0;
1230
+ }
1231
+ function shuffleBag(seed) {
1232
+ const bag = [...GAME_FALLING_BLOCK_PIECES];
1233
+ let rng = seed || 1;
1234
+ for (let index = bag.length - 1; index > 0; index -= 1) {
1235
+ rng = lcg(rng);
1236
+ const swap = rng % (index + 1);
1237
+ [bag[index], bag[swap]] = [bag[swap], bag[index]];
1238
+ }
1239
+ return { bag, rng };
1240
+ }
1241
+ function lcg(value) {
1242
+ return (Math.imul(value || 1, 1664525) + 1013904223) >>> 0;
1243
+ }
1244
+ function wrapRotation(value) {
1245
+ return (((value % 4) + 4) % 4);
1246
+ }
1247
+ function fallingBlocksChecksum(input) {
1248
+ const payload = JSON.stringify({
1249
+ board: input.board,
1250
+ active: input.active,
1251
+ hold: input.hold,
1252
+ queue: input.queue.slice(0, 7),
1253
+ score: input.score,
1254
+ lines: input.lines,
1255
+ level: input.level,
1256
+ frame: input.frame,
1257
+ gameOver: input.gameOver
1258
+ });
1259
+ let hash = 2166136261;
1260
+ for (let index = 0; index < payload.length; index += 1) {
1261
+ hash ^= payload.charCodeAt(index);
1262
+ hash = Math.imul(hash, 16777619);
1263
+ }
1264
+ return (hash >>> 0).toString(16).padStart(8, "0");
1265
+ }
1266
+ function clampNumber(value, min, max) {
1267
+ return Math.max(min, Math.min(max, Number.isFinite(value) ? value : 0));
1268
+ }
1269
+ //# sourceMappingURL=GameGenreKits.js.map