@nodetool-ai/agents 0.8.2 → 0.8.4-a24.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 (323) hide show
  1. package/dist/.nodetool-build-stamp +1 -1
  2. package/dist/apify/assets.d.ts.map +1 -1
  3. package/dist/apify/assets.js +4 -0
  4. package/dist/apify/assets.js.map +1 -1
  5. package/dist/capabilities/agent-activity.js +1 -1
  6. package/dist/capabilities/agentic-storyboard-finish.d.ts.map +1 -1
  7. package/dist/capabilities/agentic-storyboard-finish.js +2 -1
  8. package/dist/capabilities/agentic-storyboard-finish.js.map +1 -1
  9. package/dist/capabilities/apps.specs.d.ts.map +1 -1
  10. package/dist/capabilities/apps.specs.js +4 -3
  11. package/dist/capabilities/apps.specs.js.map +1 -1
  12. package/dist/capabilities/assets.d.ts.map +1 -1
  13. package/dist/capabilities/assets.js +40 -10
  14. package/dist/capabilities/assets.js.map +1 -1
  15. package/dist/capabilities/code-grading.d.ts +4 -0
  16. package/dist/capabilities/code-grading.d.ts.map +1 -1
  17. package/dist/capabilities/code-grading.js +8 -1
  18. package/dist/capabilities/code-grading.js.map +1 -1
  19. package/dist/capabilities/code.d.ts.map +1 -1
  20. package/dist/capabilities/code.js +13 -7
  21. package/dist/capabilities/code.js.map +1 -1
  22. package/dist/capabilities/collections.d.ts +4 -6
  23. package/dist/capabilities/collections.d.ts.map +1 -1
  24. package/dist/capabilities/collections.js +16 -11
  25. package/dist/capabilities/collections.js.map +1 -1
  26. package/dist/capabilities/derive-shot.d.ts +45 -0
  27. package/dist/capabilities/derive-shot.d.ts.map +1 -0
  28. package/dist/capabilities/derive-shot.js +83 -0
  29. package/dist/capabilities/derive-shot.js.map +1 -0
  30. package/dist/capabilities/dispatcher.js +2 -2
  31. package/dist/capabilities/dispatcher.js.map +1 -1
  32. package/dist/capabilities/documents.d.ts +2 -1
  33. package/dist/capabilities/documents.d.ts.map +1 -1
  34. package/dist/capabilities/documents.js +12 -6
  35. package/dist/capabilities/documents.js.map +1 -1
  36. package/dist/capabilities/email.d.ts +2 -1
  37. package/dist/capabilities/email.d.ts.map +1 -1
  38. package/dist/capabilities/email.js +5 -3
  39. package/dist/capabilities/email.js.map +1 -1
  40. package/dist/capabilities/files.d.ts.map +1 -1
  41. package/dist/capabilities/files.js +3 -7
  42. package/dist/capabilities/files.js.map +1 -1
  43. package/dist/capabilities/files.specs.d.ts.map +1 -1
  44. package/dist/capabilities/files.specs.js +2 -1
  45. package/dist/capabilities/files.specs.js.map +1 -1
  46. package/dist/capabilities/flow.d.ts +3 -0
  47. package/dist/capabilities/flow.d.ts.map +1 -1
  48. package/dist/capabilities/flow.js +7 -0
  49. package/dist/capabilities/flow.js.map +1 -1
  50. package/dist/capabilities/game-asset-browser.d.ts +3 -0
  51. package/dist/capabilities/game-asset-browser.d.ts.map +1 -0
  52. package/dist/capabilities/game-asset-browser.js +153 -0
  53. package/dist/capabilities/game-asset-browser.js.map +1 -0
  54. package/dist/capabilities/game.d.ts +30 -0
  55. package/dist/capabilities/game.d.ts.map +1 -1
  56. package/dist/capabilities/game.js +266 -149
  57. package/dist/capabilities/game.js.map +1 -1
  58. package/dist/capabilities/game.specs.d.ts +2 -0
  59. package/dist/capabilities/game.specs.d.ts.map +1 -1
  60. package/dist/capabilities/game.specs.js +21 -6
  61. package/dist/capabilities/game.specs.js.map +1 -1
  62. package/dist/capabilities/game3d.d.ts.map +1 -1
  63. package/dist/capabilities/game3d.js +24 -10
  64. package/dist/capabilities/game3d.js.map +1 -1
  65. package/dist/capabilities/generations.d.ts.map +1 -1
  66. package/dist/capabilities/generations.js +7 -21
  67. package/dist/capabilities/generations.js.map +1 -1
  68. package/dist/capabilities/invoke.d.ts.map +1 -1
  69. package/dist/capabilities/invoke.js +15 -2
  70. package/dist/capabilities/invoke.js.map +1 -1
  71. package/dist/capabilities/jobs.d.ts +7 -2
  72. package/dist/capabilities/jobs.d.ts.map +1 -1
  73. package/dist/capabilities/jobs.js +13 -3
  74. package/dist/capabilities/jobs.js.map +1 -1
  75. package/dist/capabilities/lazy-tool-core.d.ts +23 -0
  76. package/dist/capabilities/lazy-tool-core.d.ts.map +1 -0
  77. package/dist/capabilities/lazy-tool-core.js +56 -0
  78. package/dist/capabilities/lazy-tool-core.js.map +1 -0
  79. package/dist/capabilities/lazy-tool.d.ts +2 -21
  80. package/dist/capabilities/lazy-tool.d.ts.map +1 -1
  81. package/dist/capabilities/lazy-tool.js +3 -55
  82. package/dist/capabilities/lazy-tool.js.map +1 -1
  83. package/dist/capabilities/legacy-tools.js +1 -1
  84. package/dist/capabilities/legacy-tools.js.map +1 -1
  85. package/dist/capabilities/media.d.ts.map +1 -1
  86. package/dist/capabilities/media.js +26 -1
  87. package/dist/capabilities/media.js.map +1 -1
  88. package/dist/capabilities/media.specs.d.ts.map +1 -1
  89. package/dist/capabilities/media.specs.js +17 -0
  90. package/dist/capabilities/media.specs.js.map +1 -1
  91. package/dist/capabilities/metadata.d.ts +62 -0
  92. package/dist/capabilities/metadata.d.ts.map +1 -0
  93. package/dist/capabilities/metadata.js +114 -0
  94. package/dist/capabilities/metadata.js.map +1 -0
  95. package/dist/capabilities/model3d.d.ts.map +1 -1
  96. package/dist/capabilities/model3d.js +6 -1
  97. package/dist/capabilities/model3d.js.map +1 -1
  98. package/dist/capabilities/nodes.specs.js +1 -1
  99. package/dist/capabilities/nodes.specs.js.map +1 -1
  100. package/dist/capabilities/registry.d.ts +4 -19
  101. package/dist/capabilities/registry.d.ts.map +1 -1
  102. package/dist/capabilities/registry.js +48 -117
  103. package/dist/capabilities/registry.js.map +1 -1
  104. package/dist/capabilities/run-agent.js +1 -1
  105. package/dist/capabilities/scripts.js +2 -2
  106. package/dist/capabilities/scripts.js.map +1 -1
  107. package/dist/capabilities/settings.d.ts.map +1 -1
  108. package/dist/capabilities/settings.js +4 -2
  109. package/dist/capabilities/settings.js.map +1 -1
  110. package/dist/capabilities/storyboard-layout-check.d.ts.map +1 -1
  111. package/dist/capabilities/storyboard-layout-check.js +2 -1
  112. package/dist/capabilities/storyboard-layout-check.js.map +1 -1
  113. package/dist/capabilities/storyboard-measured-sources.d.ts +1 -1
  114. package/dist/capabilities/storyboard-measured-sources.d.ts.map +1 -1
  115. package/dist/capabilities/storyboard-measured-sources.js +2 -1
  116. package/dist/capabilities/storyboard-measured-sources.js.map +1 -1
  117. package/dist/capabilities/storyboards.d.ts +11 -1
  118. package/dist/capabilities/storyboards.d.ts.map +1 -1
  119. package/dist/capabilities/storyboards.js +467 -5
  120. package/dist/capabilities/storyboards.js.map +1 -1
  121. package/dist/capabilities/storyboards.specs.d.ts +6 -0
  122. package/dist/capabilities/storyboards.specs.d.ts.map +1 -1
  123. package/dist/capabilities/storyboards.specs.js +139 -3
  124. package/dist/capabilities/storyboards.specs.js.map +1 -1
  125. package/dist/capabilities/threads.js +1 -1
  126. package/dist/capabilities/threads.js.map +1 -1
  127. package/dist/capabilities/timeline-asset-ids.d.ts +27 -0
  128. package/dist/capabilities/timeline-asset-ids.d.ts.map +1 -0
  129. package/dist/capabilities/timeline-asset-ids.js +88 -0
  130. package/dist/capabilities/timeline-asset-ids.js.map +1 -0
  131. package/dist/capabilities/timeline-bake.d.ts.map +1 -1
  132. package/dist/capabilities/timeline-bake.js +2 -1
  133. package/dist/capabilities/timeline-bake.js.map +1 -1
  134. package/dist/capabilities/timeline-bridge.d.ts +9 -0
  135. package/dist/capabilities/timeline-bridge.d.ts.map +1 -1
  136. package/dist/capabilities/timeline-bridge.js +60 -68
  137. package/dist/capabilities/timeline-bridge.js.map +1 -1
  138. package/dist/capabilities/timeline-clip-frames.d.ts.map +1 -1
  139. package/dist/capabilities/timeline-clip-frames.js +2 -1
  140. package/dist/capabilities/timeline-clip-frames.js.map +1 -1
  141. package/dist/capabilities/timeline-operations.d.ts +52 -0
  142. package/dist/capabilities/timeline-operations.d.ts.map +1 -0
  143. package/dist/capabilities/timeline-operations.js +413 -0
  144. package/dist/capabilities/timeline-operations.js.map +1 -0
  145. package/dist/capabilities/timelines.d.ts +4 -39
  146. package/dist/capabilities/timelines.d.ts.map +1 -1
  147. package/dist/capabilities/timelines.js +45 -414
  148. package/dist/capabilities/timelines.js.map +1 -1
  149. package/dist/capabilities/timelines.specs.d.ts.map +1 -1
  150. package/dist/capabilities/timelines.specs.js +8 -3
  151. package/dist/capabilities/timelines.specs.js.map +1 -1
  152. package/dist/capabilities/web.d.ts.map +1 -1
  153. package/dist/capabilities/web.js +5 -1
  154. package/dist/capabilities/web.js.map +1 -1
  155. package/dist/capabilities/web.specs.js +1 -1
  156. package/dist/capabilities/web.specs.js.map +1 -1
  157. package/dist/capabilities/workflows.d.ts +3 -23
  158. package/dist/capabilities/workflows.d.ts.map +1 -1
  159. package/dist/capabilities/workflows.js +197 -15
  160. package/dist/capabilities/workflows.js.map +1 -1
  161. package/dist/capabilities/workflows.specs.d.ts +2 -0
  162. package/dist/capabilities/workflows.specs.d.ts.map +1 -1
  163. package/dist/capabilities/workflows.specs.js +65 -1
  164. package/dist/capabilities/workflows.specs.js.map +1 -1
  165. package/dist/codeact/nodetool-api.d.ts +17 -1
  166. package/dist/codeact/nodetool-api.d.ts.map +1 -1
  167. package/dist/codeact/nodetool-api.js +168 -48
  168. package/dist/codeact/nodetool-api.js.map +1 -1
  169. package/dist/codeact/sandbox-packages.d.ts +0 -7
  170. package/dist/codeact/sandbox-packages.d.ts.map +1 -1
  171. package/dist/codeact/sandbox-packages.js +1 -10
  172. package/dist/codeact/sandbox-packages.js.map +1 -1
  173. package/dist/codeact/tool-api.d.ts.map +1 -1
  174. package/dist/codeact/tool-api.js +6 -2
  175. package/dist/codeact/tool-api.js.map +1 -1
  176. package/dist/evals/app-build-plan.d.ts.map +1 -1
  177. package/dist/evals/app-build-plan.js +3 -0
  178. package/dist/evals/app-build-plan.js.map +1 -1
  179. package/dist/evals/codeact-api-core.d.ts.map +1 -1
  180. package/dist/evals/codeact-api-core.js +6 -0
  181. package/dist/evals/codeact-api-core.js.map +1 -1
  182. package/dist/evals/surfaces/creative-pipeline.d.ts +1 -1
  183. package/dist/evals/surfaces/creative-pipeline.d.ts.map +1 -1
  184. package/dist/evals/surfaces/creative-pipeline.js +2 -2
  185. package/dist/evals/surfaces/creative-pipeline.js.map +1 -1
  186. package/dist/evals/surfaces/game.d.ts +9 -2
  187. package/dist/evals/surfaces/game.d.ts.map +1 -1
  188. package/dist/evals/surfaces/game.js +714 -2
  189. package/dist/evals/surfaces/game.js.map +1 -1
  190. package/dist/evals/surfaces/sketch.d.ts +1 -1
  191. package/dist/evals/surfaces/sketch.d.ts.map +1 -1
  192. package/dist/evals/surfaces/sketch.js +6 -13
  193. package/dist/evals/surfaces/sketch.js.map +1 -1
  194. package/dist/evals/tool-loop-eval.d.ts +1 -1
  195. package/dist/evals/tool-loop-eval.d.ts.map +1 -1
  196. package/dist/evals/tool-loop-eval.js +1 -1
  197. package/dist/evals/tool-loop-eval.js.map +1 -1
  198. package/dist/game-authoring.d.ts +18868 -3824
  199. package/dist/game-authoring.d.ts.map +1 -1
  200. package/dist/game-build.d.ts +3 -0
  201. package/dist/game-build.d.ts.map +1 -0
  202. package/dist/game-build.js +3 -0
  203. package/dist/game-build.js.map +1 -0
  204. package/dist/game-examples.d.ts +5310 -1056
  205. package/dist/game-examples.d.ts.map +1 -1
  206. package/dist/generation-cancel.d.ts +31 -0
  207. package/dist/generation-cancel.d.ts.map +1 -0
  208. package/dist/generation-cancel.js +28 -0
  209. package/dist/generation-cancel.js.map +1 -0
  210. package/dist/host-access-gate.d.ts +29 -0
  211. package/dist/host-access-gate.d.ts.map +1 -0
  212. package/dist/host-access-gate.js +39 -0
  213. package/dist/host-access-gate.js.map +1 -0
  214. package/dist/host-modules/dispatcher.d.ts +7 -1
  215. package/dist/host-modules/dispatcher.d.ts.map +1 -1
  216. package/dist/host-modules/dispatcher.js +13 -2
  217. package/dist/host-modules/dispatcher.js.map +1 -1
  218. package/dist/host-modules/docx.d.ts.map +1 -1
  219. package/dist/host-modules/docx.js +2 -2
  220. package/dist/host-modules/docx.js.map +1 -1
  221. package/dist/host-modules/epub.d.ts.map +1 -1
  222. package/dist/host-modules/epub.js +2 -2
  223. package/dist/host-modules/epub.js.map +1 -1
  224. package/dist/host-modules/fabric.d.ts.map +1 -1
  225. package/dist/host-modules/fabric.js +1 -1
  226. package/dist/host-modules/fabric.js.map +1 -1
  227. package/dist/host-modules/limits.d.ts +10 -8
  228. package/dist/host-modules/limits.d.ts.map +1 -1
  229. package/dist/host-modules/limits.js +14 -9
  230. package/dist/host-modules/limits.js.map +1 -1
  231. package/dist/host-modules/mammoth.d.ts.map +1 -1
  232. package/dist/host-modules/mammoth.js +2 -2
  233. package/dist/host-modules/mammoth.js.map +1 -1
  234. package/dist/host-modules/ocr.d.ts.map +1 -1
  235. package/dist/host-modules/ocr.js +1 -1
  236. package/dist/host-modules/ocr.js.map +1 -1
  237. package/dist/host-modules/pdf.d.ts.map +1 -1
  238. package/dist/host-modules/pdf.js +2 -2
  239. package/dist/host-modules/pdf.js.map +1 -1
  240. package/dist/host-modules/pdflib.d.ts.map +1 -1
  241. package/dist/host-modules/pdflib.js +2 -2
  242. package/dist/host-modules/pdflib.js.map +1 -1
  243. package/dist/host-modules/pptx.d.ts.map +1 -1
  244. package/dist/host-modules/pptx.js +2 -2
  245. package/dist/host-modules/pptx.js.map +1 -1
  246. package/dist/host-modules/pptxgen.d.ts.map +1 -1
  247. package/dist/host-modules/pptxgen.js +2 -2
  248. package/dist/host-modules/pptxgen.js.map +1 -1
  249. package/dist/host-modules/tfjs.d.ts.map +1 -1
  250. package/dist/host-modules/tfjs.js +4 -4
  251. package/dist/host-modules/tfjs.js.map +1 -1
  252. package/dist/host-modules/tokens.js +2 -2
  253. package/dist/host-modules/tokens.js.map +1 -1
  254. package/dist/host-modules/xlsx.d.ts.map +1 -1
  255. package/dist/host-modules/xlsx.js +2 -2
  256. package/dist/host-modules/xlsx.js.map +1 -1
  257. package/dist/index.d.ts +3 -0
  258. package/dist/index.d.ts.map +1 -1
  259. package/dist/index.js +4 -0
  260. package/dist/index.js.map +1 -1
  261. package/dist/js-sandbox.d.ts +8 -1
  262. package/dist/js-sandbox.d.ts.map +1 -1
  263. package/dist/js-sandbox.js +27 -4
  264. package/dist/js-sandbox.js.map +1 -1
  265. package/dist/jtbd/registry.d.ts.map +1 -1
  266. package/dist/jtbd/registry.js +16 -3
  267. package/dist/jtbd/registry.js.map +1 -1
  268. package/dist/jtbd/run.d.ts +2 -2
  269. package/dist/jtbd/run.d.ts.map +1 -1
  270. package/dist/jtbd/run.js +3 -3
  271. package/dist/jtbd/run.js.map +1 -1
  272. package/dist/jtbd/types.d.ts +1 -1
  273. package/dist/jtbd/types.d.ts.map +1 -1
  274. package/dist/model-prompting-skills.d.ts +8 -7
  275. package/dist/model-prompting-skills.d.ts.map +1 -1
  276. package/dist/model-prompting-skills.js +35 -16
  277. package/dist/model-prompting-skills.js.map +1 -1
  278. package/dist/sandbox-media.d.ts +1 -1
  279. package/dist/sandbox-media.d.ts.map +1 -1
  280. package/dist/sandbox-media.js +10 -9
  281. package/dist/sandbox-media.js.map +1 -1
  282. package/dist/skill-prompt.d.ts.map +1 -1
  283. package/dist/skill-prompt.js +1 -1
  284. package/dist/skill-prompt.js.map +1 -1
  285. package/dist/system-skills.d.ts +4 -3
  286. package/dist/system-skills.d.ts.map +1 -1
  287. package/dist/system-skills.js +4 -3
  288. package/dist/system-skills.js.map +1 -1
  289. package/dist/task-planner.js +1 -1
  290. package/dist/timeline-code-bake.js +2 -2
  291. package/dist/timeline-code-bake.js.map +1 -1
  292. package/dist/timeline-preview/compare.d.ts.map +1 -1
  293. package/dist/timeline-preview/compare.js +4 -1
  294. package/dist/timeline-preview/compare.js.map +1 -1
  295. package/dist/timeline-preview/frames.d.ts.map +1 -1
  296. package/dist/timeline-preview/frames.js +7 -4
  297. package/dist/timeline-preview/frames.js.map +1 -1
  298. package/dist/timeline-preview/model3d.d.ts +1 -1
  299. package/dist/timeline-preview/model3d.d.ts.map +1 -1
  300. package/dist/timeline-preview/model3d.js +4 -23
  301. package/dist/timeline-preview/model3d.js.map +1 -1
  302. package/dist/timeline-preview/rasterize.d.ts +4 -2
  303. package/dist/timeline-preview/rasterize.d.ts.map +1 -1
  304. package/dist/timeline-preview/rasterize.js +10 -4
  305. package/dist/timeline-preview/rasterize.js.map +1 -1
  306. package/dist/timeline-preview/sheet.d.ts.map +1 -1
  307. package/dist/timeline-preview/sheet.js +3 -2
  308. package/dist/timeline-preview/sheet.js.map +1 -1
  309. package/dist/tools/builtin-tools.d.ts +4 -3
  310. package/dist/tools/builtin-tools.d.ts.map +1 -1
  311. package/dist/tools/builtin-tools.js +11 -3
  312. package/dist/tools/builtin-tools.js.map +1 -1
  313. package/dist/tools/graph-validation-registry.d.ts.map +1 -1
  314. package/dist/tools/graph-validation-registry.js +3 -0
  315. package/dist/tools/graph-validation-registry.js.map +1 -1
  316. package/dist/tools/mcp-tool-support.js +1 -1
  317. package/dist/tools/mcp-tool-support.js.map +1 -1
  318. package/dist/tools/mcp-tools.d.ts.map +1 -1
  319. package/dist/tools/mcp-tools.js +4 -0
  320. package/dist/tools/mcp-tools.js.map +1 -1
  321. package/dist/utils/json-parser.js +1 -1
  322. package/dist/utils/json-parser.js.map +1 -1
  323. package/package.json +131 -47
@@ -1,19 +1,23 @@
1
1
  import { createHash, randomUUID } from "node:crypto";
2
2
  import { mkdtemp, readFile, readdir, rm } from "node:fs/promises";
3
3
  import { join } from "node:path";
4
+ import { z } from "zod";
5
+ import { importPack } from "@nodetool-ai/node-catalog";
4
6
  import { createLogger, getManagedWorkspaceDir, managedWorkspaceKey, workspaceStorageKind } from "@nodetool-ai/config";
5
- import { AmbiguousGameIdError, Asset, Game, Prediction, Project, Workspace } from "@nodetool-ai/models";
7
+ import { AmbiguousGameIdError, MissingGameDraftSourceError, Asset, Game, Prediction, Project, Workspace } from "@nodetool-ai/models";
6
8
  import { anyGameAssetBinding as gameAssetBinding, gameAssetBinding as legacyAssetBinding, gameAssetBinding3D, anyGameDocument as gameDocument, gameInputFrame, shortResourceId } from "@nodetool-ai/protocol";
7
- import { autoplayNativeGame, MAX_GAME_ROUTE_TICKS, createScriptedGameSession, createTopDownRoomGame, createNative3DGame, decodePreparedGameCollider3D, validateAnyGame, trackGameAuthoringEdits, anyGameDocumentOp as gameDocumentOp, GameOpError } from "@nodetool-ai/game-runtime";
9
+ import { autoplayNativeGame, MAX_GAME_ROUTE_TICKS, createMemoryGameSaveStorage, createScriptedGameSession, GamePlayerSaves, createTopDownRoomGame, createNative3DGame, decodePreparedGameCollider3D, validateAnyGame, trackGameAuthoringEdits, anyGameDocumentOp as gameDocumentOp, GameOpError } from "@nodetool-ai/game-runtime";
8
10
  import { workspaceFromRow } from "@nodetool-ai/execution/service";
9
11
  import { assetKeyCandidates, assetObjectKey } from "@nodetool-ai/storage";
12
+ import { loadCanvas } from "@nodetool-ai/runtime";
10
13
  import { gameOutline3D, inputFrames3D, playtestGame3D, captureFrames3D, colliderBinding3D, stageModelGameAsset3D } from "./game3d.js";
11
14
  import { gameSpecs } from "./game.specs.js";
12
15
  import { persistOutput } from "../tools/asset-persist.js";
13
16
  import { getExampleGameBundle, installExampleGameAssets, listExampleGames } from "../game-examples.js";
14
17
  import { previewGameAuthoring, applyGameAuthoring } from "../game-authoring.js";
15
18
  import { gameDocumentDigest } from "../game-code-bake.js";
16
- import { previewGameAuthoringSpec, applyGameAuthoringSpec } from "./game.specs.js";
19
+ import { previewGameAuthoringSpec, applyGameAuthoringSpec, browseGameAssetsSpec } from "./game.specs.js";
20
+ import { browseGameAssets } from "./game-asset-browser.js";
17
21
  const log = createLogger("capabilities.game");
18
22
  const MAX_PLAYTEST_TICKS = MAX_GAME_ROUTE_TICKS;
19
23
  const MAX_CAPTURE_FRAMES = 8;
@@ -121,7 +125,8 @@ function outline(document) {
121
125
  inputActions: document.inputActions,
122
126
  collisionLayers: document.collisionLayers,
123
127
  renderEffects: document.renderEffects,
124
- hudEffectOrder: document.hudEffectOrder
128
+ hudEffectOrder: document.hudEffectOrder,
129
+ audio: document.audio
125
130
  },
126
131
  scenes: document.scenes.map((scene) => ({
127
132
  id: scene.id,
@@ -183,7 +188,9 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
183
188
  wallTimeLimited = Date.now() >= deadlineAt;
184
189
  return cancelled || wallTimeLimited;
185
190
  };
186
- const { captureGameFrame } = await import("@nodetool-ai/game-renderer/node");
191
+ const { captureGameFrame } = await importPack("game", "@nodetool-ai/game-renderer/node");
192
+ const { GameParticles2D } = await importPack("game", "@nodetool-ai/game-renderer");
193
+ const particles = new GameParticles2D(document.tickRate);
187
194
  const diagnostics = [];
188
195
  const storage = run.context.assetStorage;
189
196
  const assetCache = new Map();
@@ -221,7 +228,9 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
221
228
  if (budgetReached()) {
222
229
  break;
223
230
  }
224
- simulatedTicks = session.step(inputs[simulatedTicks] ?? { pressed: [], justPressed: [] }).tick;
231
+ const step = session.step(inputs[simulatedTicks] ?? { pressed: [], justPressed: [] });
232
+ particles.tick(step.frame, session.takePresentationEvents());
233
+ simulatedTicks = step.tick;
225
234
  }
226
235
  if (budgetReached()) {
227
236
  break;
@@ -238,11 +247,11 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
238
247
  const start = diagnostics.length;
239
248
  let png;
240
249
  try {
241
- png = await captureGameFrame(frame, { resolveAsset, scale, effects: document.renderEffects, hudEffectOrder: document.hudEffectOrder, backend: document.renderEffects?.length ? "webgpu" : "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
250
+ png = await captureGameFrame(frame, { resolveAsset, scale, particles, effects: document.renderEffects, hudEffectOrder: document.hudEffectOrder, backend: document.renderEffects?.length ? "webgpu" : "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
242
251
  }
243
252
  catch (error) {
244
253
  diagnostics.push(`WebGPU capture failed: ${error instanceof Error ? error.message : String(error)}`);
245
- png = await captureGameFrame(frame, { resolveAsset, scale, backend: "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
254
+ png = await captureGameFrame(frame, { resolveAsset, scale, particles, backend: "canvas2d", onDiagnostic: (message) => diagnostics.push(message) });
246
255
  }
247
256
  if (budgetReached()) {
248
257
  break;
@@ -250,7 +259,7 @@ async function captureFrames(run, user, document, ticks, inputs, seed, options)
250
259
  const pixelScale = frame.pixelsPerUnit * frame.camera.zoom * scale;
251
260
  const overlays = Array.isArray(options.overlays) ? options.overlays.filter((value) => typeof value === "string") : [];
252
261
  if (overlays.length > 0) {
253
- const { createCanvas, loadImage } = await import("@napi-rs/canvas");
262
+ const { createCanvas, loadImage } = await loadCanvas();
254
263
  const source = await loadImage(Buffer.from(png));
255
264
  const canvas = createCanvas(source.width, source.height);
256
265
  const context = canvas.getContext("2d");
@@ -415,7 +424,7 @@ export async function createNativeGame(user, projectId, name, source, dimension
415
424
  return { error: "Project workspace is unavailable" };
416
425
  const id = randomUUID().replace(/-/g, "");
417
426
  const revision = randomUUID().replace(/-/g, "");
418
- const document = validateSource(source ?? (dimension === "3d" ? createNative3DGame(id) : { ...createTopDownRoomGame(id), schemaVersion: 2 }), id, revision);
427
+ const document = validateSource(source ?? (dimension === "3d" ? createNative3DGame(id) : { ...createTopDownRoomGame(id), schemaVersion: 4, engineVersion: "3" }), id, revision);
419
428
  if ("error" in document)
420
429
  return document;
421
430
  if (source && ((document.schemaVersion === 3) !== (dimension === "3d"))) {
@@ -559,6 +568,9 @@ const install = {
559
568
  if (parsed.data.mediaKind === "font" && !parsed.data.fontFormat) {
560
569
  return { error: "Font binding needs a TrueType or OpenType format" };
561
570
  }
571
+ if (parsed.data.mediaKind === "hdri") {
572
+ return { error: "HDRI candidates cannot be installed until HDRI preparation verifies their bytes and dimensions" };
573
+ }
562
574
  const workspace = await workspaceOf(user, game);
563
575
  if (!workspace)
564
576
  return { error: "Game workspace is unavailable" };
@@ -596,7 +608,9 @@ const install = {
596
608
  if (!candidate || createHash("sha256").update(candidate.bytes).digest("hex") !== parsed.data.digest) {
597
609
  return { error: "Candidate asset is missing or changed" };
598
610
  }
599
- const { path, extension, contentType, bytes } = candidate;
611
+ const { path, bytes } = candidate;
612
+ let { extension, contentType } = candidate;
613
+ let targetPath = path;
600
614
  if (base !== undefined && base !== game.current_revision)
601
615
  return { error: "Game was modified concurrently" };
602
616
  const draft = await Game.readDraft(user, game.id, workspace);
@@ -605,8 +619,19 @@ const install = {
605
619
  const expectedUpdatedAt = typeof args["base_updated_at"] === "string" ? args["base_updated_at"] : draft.game.draft_updated_at;
606
620
  let binding = parsed.data;
607
621
  try {
608
- if (binding.mediaKind === "model") {
609
- const { prepareGameModelBinding3D } = await import("@nodetool-ai/game-renderer/preparation3d");
622
+ if (draft.document.schemaVersion === 3 && binding.mediaKind === "image") {
623
+ const { prepareGameImage3D } = await importPack("game", "@nodetool-ai/game-renderer/preparation3d");
624
+ const prepared = await prepareGameImage3D(bytes, { assetId: binding.assetId, expectedDigest: binding.digest, signal: run.context.signal });
625
+ if (!prepared.ok) {
626
+ return { error: "Image preparation failed", diagnostics: prepared.diagnostics };
627
+ }
628
+ binding = { ...prepared.image.binding, required: binding.required ?? true };
629
+ extension = prepared.image.binding.format === "jpeg" ? "jpg" : prepared.image.binding.format;
630
+ contentType = `image/${prepared.image.binding.format}`;
631
+ targetPath = `${game.source_root}/assets/${binding.digest}.${extension}`;
632
+ }
633
+ else if (binding.mediaKind === "model") {
634
+ const { prepareGameModelBinding3D } = await importPack("game", "@nodetool-ai/game-renderer/preparation3d");
610
635
  const prepared = await prepareGameModelBinding3D(bytes, { assetId: binding.assetId, expectedDigest: binding.digest, signal: run.context.signal });
611
636
  if (!prepared.ok) {
612
637
  return { error: "Model preparation failed", diagnostics: prepared.diagnostics };
@@ -645,8 +670,8 @@ const install = {
645
670
  const storage = run.context.assetStorage;
646
671
  if (!storage)
647
672
  return { error: "Asset storage is unavailable" };
648
- if (candidateWorkspace !== workspace)
649
- await workspace.write(path, bytes, contentType);
673
+ if (candidateWorkspace !== workspace || targetPath !== path)
674
+ await workspace.write(targetPath, bytes, contentType);
650
675
  const installed = await Asset.create({
651
676
  user_id: user,
652
677
  project_id: game.project_id,
@@ -744,6 +769,8 @@ const playtest = {
744
769
  let wallTimeLimited = false;
745
770
  let cancelled = false;
746
771
  let scriptError = null;
772
+ // Script saves stay in memory for the playtest, so a run never reads or writes a player's saves.
773
+ const saves = new GamePlayerSaves(createMemoryGameSaveStorage());
747
774
  try {
748
775
  for (const input of inputs) {
749
776
  if (state.tick % 240 === 0)
@@ -758,7 +785,11 @@ const playtest = {
758
785
  }
759
786
  let result;
760
787
  try {
761
- result = session.step(input);
788
+ result = session.step(saves.withLoaded(input));
789
+ if (result.saveRequests) {
790
+ saves.handle(result.saveRequests, () => session.snapshot());
791
+ await saves.idle();
792
+ }
762
793
  }
763
794
  catch (error) {
764
795
  scriptError = { tick: state.tick + 1, message: error instanceof Error ? error.message : String(error) };
@@ -914,8 +945,11 @@ const generateAsset = {
914
945
  return { error: error instanceof Error ? error.message : String(error) };
915
946
  }
916
947
  }
917
- if (draft.document.schemaVersion === 3 && kind === "image") {
918
- return { error: "3D games use model assets with embedded textures" };
948
+ if (draft.document.schemaVersion === 3 && kind === "image" && args["preparation"] !== undefined) {
949
+ const preparation = z.record(z.string(), z.unknown()).safeParse(args["preparation"]);
950
+ if (!preparation.success || Object.keys(preparation.data).length > 0) {
951
+ return { error: "3D images do not support sprite preparation. Set UV tiling and offset on the material's texture slots." };
952
+ }
919
953
  }
920
954
  const reference = typeof args["reference_slot"] === "string" ? draft.document.assets[args["reference_slot"]] : undefined;
921
955
  if (args["reference_slot"] !== undefined && !reference) {
@@ -924,7 +958,7 @@ const generateAsset = {
924
958
  if (reference && kind !== "image") {
925
959
  return { error: "reference_slot requires kind image" };
926
960
  }
927
- const { imagePreparationSettings, prepareGameImage, imagePreparationMetadata, gameFontFormat } = await import("@nodetool-ai/game-nodes");
961
+ const { imagePreparationSettings, prepareGameImage, imagePreparationMetadata, gameFontFormat } = await importPack("game", "@nodetool-ai/game-nodes");
928
962
  const settings = kind === "image" ? imagePreparationSettings.safeParse(args["preparation"] ?? {}) : null;
929
963
  if (settings && !settings.success) {
930
964
  return { error: "Invalid image preparation", issues: settings.error.issues };
@@ -1048,6 +1082,20 @@ const generateAsset = {
1048
1082
  if (sourceBytes.byteLength > MAX_BUILD_ASSET_BYTES) {
1049
1083
  return { error: "Game asset exceeds the supported asset size" };
1050
1084
  }
1085
+ if (draft.document.schemaVersion === 3 && kind === "image") {
1086
+ const { prepareGameImage3D } = await importPack("game", "@nodetool-ai/game-renderer/preparation3d");
1087
+ const digest = createHash("sha256").update(sourceBytes).digest("hex");
1088
+ const prepared = await prepareGameImage3D(sourceBytes, { assetId: `generated:${digest}`, expectedDigest: digest, signal: run.context.signal });
1089
+ if (!prepared.ok) {
1090
+ return { error: "Image preparation failed", diagnostics: prepared.diagnostics };
1091
+ }
1092
+ const binding = prepared.image.binding;
1093
+ const extension = binding.format === "jpeg" ? "jpg" : binding.format;
1094
+ await workspace.write(`${game.source_root}/assets/${binding.digest}.${extension}`, prepared.image.bytes, `image/${binding.format}`);
1095
+ return { game_id: shortResourceId(game.id), slot, binding, candidate_workspace_id: game.workspace_id,
1096
+ draft_updated_at: draft.game.draft_updated_at, installed: false,
1097
+ next: "Call install_native_game_asset with this binding to install the verified candidate." };
1098
+ }
1051
1099
  let bytes = sourceBytes;
1052
1100
  let width = 1;
1053
1101
  let height = 1;
@@ -1085,7 +1133,7 @@ const generateAsset = {
1085
1133
  if (prepared.baseline !== undefined && frames?.[0]) {
1086
1134
  binding.pivot = { x: 0.5, y: (prepared.baseline + 1) / frames[0].height };
1087
1135
  }
1088
- if (draft.document.schemaVersion === 2) {
1136
+ if (draft.document.schemaVersion !== 1) {
1089
1137
  binding.preparation = imagePreparationMetadata(settings.data);
1090
1138
  binding.originalDimensions = { width: prepared.originalWidth, height: prepared.originalHeight };
1091
1139
  if (prepared.trim) {
@@ -1123,7 +1171,7 @@ const generateAsset = {
1123
1171
  binding.digest = digest;
1124
1172
  binding.width = width;
1125
1173
  binding.height = height;
1126
- if (draft.document.schemaVersion === 2) {
1174
+ if (draft.document.schemaVersion !== 1 && draft.document.schemaVersion !== 3) {
1127
1175
  binding.provenance = typeof inputFile === "string" ? `import:${inputFile}`
1128
1176
  : makeLut ? "generated:color-cube" : kind === "sfx" ? `node:${args["node_type"]}` : `${provider}:${model}:${generated.generation_id ?? ""}`;
1129
1177
  }
@@ -1139,6 +1187,25 @@ const generateAsset = {
1139
1187
  return { game_id: shortResourceId(game.id), slot, binding: parsedBinding.data, candidate_workspace_id: game.workspace_id,
1140
1188
  draft_updated_at: draft.game.draft_updated_at, installed: false, next: "Call install_native_game_asset with this binding to install the candidate." };
1141
1189
  }
1190
+ if (args["install"] === false) {
1191
+ const parsedBinding = legacyAssetBinding.safeParse(binding);
1192
+ if (!parsedBinding.success) {
1193
+ return { error: "Invalid asset binding", diagnostics: parsedBinding.error.issues };
1194
+ }
1195
+ const staged = { game_id: shortResourceId(game.id), slot, binding: parsedBinding.data, candidate_workspace_id: game.workspace_id,
1196
+ draft_updated_at: draft.game.draft_updated_at, installed: false, generation_id: generated.generation_id,
1197
+ next: "Call install_native_game_asset with this binding to install the candidate." };
1198
+ if (frames) {
1199
+ staged.frames = frames;
1200
+ }
1201
+ if (tiles) {
1202
+ staged.tiles = tiles;
1203
+ }
1204
+ if (lutSize) {
1205
+ staged.lut_size = lutSize;
1206
+ }
1207
+ return staged;
1208
+ }
1142
1209
  const installed = await install.impl(run, { game_id: game.id, slot, binding, base_updated_at: draft.game.draft_updated_at });
1143
1210
  if (typeof installed !== "object" || installed === null) {
1144
1211
  return { generation_id: generated.generation_id, result: installed };
@@ -1165,141 +1232,177 @@ const generateAsset = {
1165
1232
  return { generation_id: generated.generation_id, ...installed, ...extras };
1166
1233
  }
1167
1234
  };
1168
- const buildGame = {
1169
- spec: gameSpecs[5],
1170
- impl: async (run, args) => {
1171
- const user = userId(run);
1172
- const id = args["game_id"];
1173
- if (!user || typeof id !== "string")
1174
- return { error: "game_id is required in a user session" };
1175
- const game = await ownedGame(user, id);
1176
- if (!game)
1177
- return { error: "Game not found" };
1178
- const workspace = await workspaceOf(user, game);
1179
- if (!workspace)
1180
- return { error: "Game workspace is unavailable" };
1181
- const revision = typeof args["revision"] === "string" ? args["revision"] : game.current_revision;
1182
- const document = await readDocument(workspace, game, revision);
1183
- if (!document)
1184
- return { error: "Game revision not found" };
1185
- if (Object.keys(document.assets).length > MAX_BUILD_ASSETS) {
1186
- return { error: `A standalone build supports at most ${MAX_BUILD_ASSETS} assets` };
1235
+ const BUILD_CONTENT_TYPES = {
1236
+ js: "text/javascript", css: "text/css", json: "application/json", html: "text/html", png: "image/png", jpg: "image/jpeg",
1237
+ webp: "image/webp", wav: "audio/wav", ogg: "audio/ogg", mp3: "audio/mpeg", ttf: "font/ttf", otf: "font/otf",
1238
+ glb: "model/gltf-binary", wasm: "application/wasm", md: "text/markdown", toml: "text/plain", rs: "text/plain",
1239
+ ico: "image/vnd.microsoft.icon", icns: "image/icns"
1240
+ };
1241
+ /**
1242
+ * Builds an owned, published game revision into its project workspace: a static web player under
1243
+ * `game-builds/<game-id>/<revision>`, or a Tauri desktop project under `game-builds/<game-id>/<revision>-tauri-<settings>`.
1244
+ * The capability and the editor's export download share this path.
1245
+ */
1246
+ export async function buildOwnedNativeGame(request) {
1247
+ const user = request.userId;
1248
+ const target = request.target ?? "web";
1249
+ const game = await ownedGame(user, request.gameId);
1250
+ if (!game)
1251
+ return { error: "Game not found" };
1252
+ const workspace = await workspaceOf(user, game);
1253
+ if (!workspace)
1254
+ return { error: "Game workspace is unavailable" };
1255
+ const revision = request.revision ?? game.current_revision;
1256
+ const document = await readDocument(workspace, game, revision);
1257
+ if (!document)
1258
+ return { error: "Game revision not found" };
1259
+ if (Object.keys(document.assets).length > MAX_BUILD_ASSETS) {
1260
+ return { error: `A standalone build supports at most ${MAX_BUILD_ASSETS} assets` };
1261
+ }
1262
+ const expectedDigests = new Map();
1263
+ for (const binding of Object.values(document.assets)) {
1264
+ if (binding.assetId.startsWith("builtin:"))
1265
+ continue;
1266
+ const digests = expectedDigests.get(binding.assetId) ?? new Set();
1267
+ digests.add(binding.digest);
1268
+ expectedDigests.set(binding.assetId, digests);
1269
+ }
1270
+ if ([...expectedDigests.values()].some((digests) => digests.size !== 1)) {
1271
+ return { error: "One source asset has conflicting digests in this revision" };
1272
+ }
1273
+ let buildPath = `game-builds/${game.id}/${revision}`;
1274
+ let entryPath = `${buildPath}/index.html`;
1275
+ let tauriApp;
1276
+ let metadata;
1277
+ if (target === "tauri") {
1278
+ tauriApp = { name: game.name };
1279
+ const { tauriAppMetadata } = await importPack("game", "@nodetool-ai/game-renderer/build-tauri");
1280
+ try {
1281
+ metadata = tauriAppMetadata(document, tauriApp);
1187
1282
  }
1188
- const expectedDigests = new Map();
1189
- for (const binding of Object.values(document.assets)) {
1190
- if (binding.assetId.startsWith("builtin:"))
1191
- continue;
1192
- const digests = expectedDigests.get(binding.assetId) ?? new Set();
1193
- digests.add(binding.digest);
1194
- expectedDigests.set(binding.assetId, digests);
1283
+ catch (error) {
1284
+ return { error: error instanceof Error ? error.message : String(error) };
1285
+ }
1286
+ // The app name comes from the game row, which can change without a new revision.
1287
+ const settings = createHash("sha256").update(JSON.stringify(metadata)).digest("hex").slice(0, 12);
1288
+ buildPath = `game-builds/${game.id}/${revision}-tauri-${settings}`;
1289
+ entryPath = `${buildPath}/src-tauri/tauri.conf.json`;
1290
+ }
1291
+ const done = { game_id: game.id, revision, target, build_path: buildPath, entry_path: entryPath, workspace, app: metadata ?? null };
1292
+ if (await workspace.exists(entryPath)) {
1293
+ return { ...done, cached: true };
1294
+ }
1295
+ const storage = request.storage;
1296
+ const cache = new Map();
1297
+ let totalAssetBytes = 0;
1298
+ const resolveAsset = async (assetId) => {
1299
+ const cached = cache.get(assetId);
1300
+ if (cached)
1301
+ return cached;
1302
+ if (!/^[a-f0-9]{32}$/.test(assetId))
1303
+ return null;
1304
+ const asset = await Asset.find(user, assetId);
1305
+ if (!asset || !storage)
1306
+ return null;
1307
+ const extension = MEDIA_EXTENSIONS[asset.content_type];
1308
+ if (!extension || (asset.size !== null && asset.size > MAX_BUILD_ASSET_BYTES))
1309
+ return null;
1310
+ const keys = assetKeyCandidates(user, `${asset.id}.${extension}`);
1311
+ let bytes = null;
1312
+ for (const key of keys) {
1313
+ bytes = await storage.retrieve(storage.uriForKey(key));
1314
+ if (bytes)
1315
+ break;
1195
1316
  }
1196
- if ([...expectedDigests.values()].some((digests) => digests.size !== 1)) {
1197
- return { error: "One source asset has conflicting digests in this revision" };
1317
+ if (!bytes || bytes.byteLength > MAX_BUILD_ASSET_BYTES)
1318
+ return null;
1319
+ const digest = createHash("sha256").update(bytes).digest("hex");
1320
+ if (!expectedDigests.get(assetId)?.has(digest)) {
1321
+ throw new Error(`Asset digest changed for ${assetId}`);
1198
1322
  }
1199
- const buildPath = `game-builds/${game.id}/${revision}`;
1200
- const indexPath = `${buildPath}/index.html`;
1201
- if (await workspace.exists(indexPath)) {
1202
- return { game_id: game.id, revision, build_path: buildPath, entry_path: indexPath, cached: true };
1323
+ totalAssetBytes += bytes.byteLength;
1324
+ if (totalAssetBytes > MAX_BUILD_BYTES) {
1325
+ throw new Error(`A standalone build supports at most ${MAX_BUILD_BYTES} asset bytes`);
1203
1326
  }
1204
- const storage = run.context.assetStorage;
1205
- const cache = new Map();
1206
- let totalAssetBytes = 0;
1207
- const resolveAsset = async (assetId) => {
1208
- const cached = cache.get(assetId);
1209
- if (cached)
1210
- return cached;
1211
- if (!/^[a-f0-9]{32}$/.test(assetId))
1212
- return null;
1213
- const asset = await Asset.find(user, assetId);
1214
- if (!asset || !storage)
1215
- return null;
1216
- const extension = MEDIA_EXTENSIONS[asset.content_type];
1217
- if (!extension || (asset.size !== null && asset.size > MAX_BUILD_ASSET_BYTES))
1218
- return null;
1219
- const keys = assetKeyCandidates(user, `${asset.id}.${extension}`);
1220
- let bytes = null;
1221
- for (const key of keys) {
1222
- bytes = await storage.retrieve(storage.uriForKey(key));
1223
- if (bytes)
1224
- break;
1225
- }
1226
- if (!bytes || bytes.byteLength > MAX_BUILD_ASSET_BYTES)
1227
- return null;
1228
- const digest = createHash("sha256").update(bytes).digest("hex");
1229
- if (!expectedDigests.get(assetId)?.has(digest)) {
1230
- throw new Error(`Asset digest changed for ${assetId}`);
1231
- }
1232
- totalAssetBytes += bytes.byteLength;
1233
- if (totalAssetBytes > MAX_BUILD_BYTES) {
1234
- throw new Error(`A standalone build supports at most ${MAX_BUILD_BYTES} asset bytes`);
1235
- }
1236
- const resolved = { bytes, mimeType: asset.content_type };
1237
- cache.set(assetId, resolved);
1238
- return resolved;
1239
- };
1240
- const scratch = await workspace.scratchDir();
1241
- const staging = await mkdtemp(join(scratch, ".native-game-build-"));
1242
- try {
1243
- if (document.schemaVersion === 3) {
1244
- const { buildStandaloneGame3D } = await import("@nodetool-ai/game-renderer/build3d");
1245
- await buildStandaloneGame3D({ document, outputDir: join(staging, "player"), resolveAsset, signal: run.context.signal });
1246
- }
1247
- else {
1248
- const { buildStandaloneGame } = await import("@nodetool-ai/game-renderer/build");
1249
- await buildStandaloneGame({ document, outputDir: join(staging, "player"), resolveAsset });
1250
- }
1251
- const files = [];
1252
- const pending = [""];
1253
- let totalBytes = 0;
1254
- while (pending.length > 0) {
1255
- const directory = pending.pop() ?? "";
1256
- for (const entry of await readdir(join(staging, "player", directory), { withFileTypes: true })) {
1257
- const path = directory ? `${directory}/${entry.name}` : entry.name;
1258
- if (entry.isDirectory()) {
1259
- pending.push(path);
1260
- }
1261
- else if (entry.isFile()) {
1262
- const bytes = await readFile(join(staging, "player", path));
1263
- totalBytes += bytes.byteLength;
1264
- if (files.length >= MAX_BUILD_FILES || totalBytes > MAX_BUILD_BYTES) {
1265
- throw new Error("Standalone build exceeds file or byte limits");
1266
- }
1267
- files.push({ path, bytes });
1327
+ const resolved = { bytes, mimeType: asset.content_type };
1328
+ cache.set(assetId, resolved);
1329
+ return resolved;
1330
+ };
1331
+ const scratch = await workspace.scratchDir();
1332
+ const staging = await mkdtemp(join(scratch, ".native-game-build-"));
1333
+ try {
1334
+ const output = join(staging, "player");
1335
+ if (tauriApp) {
1336
+ const { buildTauriGameProject } = await importPack("game", "@nodetool-ai/game-renderer/build-tauri");
1337
+ await buildTauriGameProject({ document, outputDir: output, resolveAsset, app: tauriApp, signal: request.signal });
1338
+ }
1339
+ else if (document.schemaVersion === 3) {
1340
+ const { buildStandaloneGame3D } = await importPack("game", "@nodetool-ai/game-renderer/build3d");
1341
+ await buildStandaloneGame3D({ document, outputDir: output, resolveAsset, signal: request.signal });
1342
+ }
1343
+ else {
1344
+ const { buildStandaloneGame } = await importPack("game", "@nodetool-ai/game-renderer/build");
1345
+ await buildStandaloneGame({ document, outputDir: output, resolveAsset });
1346
+ }
1347
+ const files = [];
1348
+ const pending = [""];
1349
+ let totalBytes = 0;
1350
+ while (pending.length > 0) {
1351
+ const directory = pending.pop() ?? "";
1352
+ for (const entry of await readdir(join(output, directory), { withFileTypes: true })) {
1353
+ const path = directory ? `${directory}/${entry.name}` : entry.name;
1354
+ if (entry.isDirectory()) {
1355
+ pending.push(path);
1356
+ }
1357
+ else if (entry.isFile()) {
1358
+ const bytes = await readFile(join(output, path));
1359
+ totalBytes += bytes.byteLength;
1360
+ if (files.length >= MAX_BUILD_FILES || totalBytes > MAX_BUILD_BYTES) {
1361
+ throw new Error("Standalone build exceeds file or byte limits");
1268
1362
  }
1363
+ files.push({ path, bytes });
1269
1364
  }
1270
1365
  }
1271
- if (!files.some((file) => file.path === "index.html")) {
1272
- throw new Error("Standalone player entry is missing");
1273
- }
1274
- for (const file of files.filter((item) => item.path !== "index.html")) {
1275
- const contentType = file.path.endsWith(".js") ? "text/javascript"
1276
- : file.path.endsWith(".css") ? "text/css"
1277
- : file.path.endsWith(".json") ? "application/json"
1278
- : file.path.endsWith(".png") ? "image/png"
1279
- : file.path.endsWith(".jpg") ? "image/jpeg"
1280
- : file.path.endsWith(".webp") ? "image/webp"
1281
- : file.path.endsWith(".wav") ? "audio/wav"
1282
- : file.path.endsWith(".ogg") ? "audio/ogg"
1283
- : file.path.endsWith(".mp3") ? "audio/mpeg"
1284
- : file.path.endsWith(".ttf") ? "font/ttf"
1285
- : file.path.endsWith(".otf") ? "font/otf"
1286
- : file.path.endsWith(".glb") ? "model/gltf-binary"
1287
- : file.path.endsWith(".wasm") ? "application/wasm"
1288
- : "application/octet-stream";
1289
- await workspace.write(`${buildPath}/${file.path}`, file.bytes, contentType);
1290
- }
1291
- const index = files.find((file) => file.path === "index.html");
1292
- if (!index)
1293
- throw new Error("Standalone player entry is missing");
1294
- await workspace.write(indexPath, index.bytes, "text/html");
1295
- return { game_id: game.id, revision, build_path: buildPath, entry_path: indexPath, cached: false };
1296
- }
1297
- catch (error) {
1298
- return { error: error instanceof Error ? error.message : String(error) };
1299
- }
1300
- finally {
1301
- await rm(staging, { recursive: true, force: true });
1302
1366
  }
1367
+ const entryFile = entryPath.slice(buildPath.length + 1);
1368
+ const entry = files.find((file) => file.path === entryFile);
1369
+ if (!entry)
1370
+ throw new Error("Standalone build entry is missing");
1371
+ const contentType = (path) => BUILD_CONTENT_TYPES[path.slice(path.lastIndexOf(".") + 1)] ?? "application/octet-stream";
1372
+ // The entry file is written last, so its presence marks a complete build for the cache check above.
1373
+ for (const file of files.filter((item) => item !== entry)) {
1374
+ await workspace.write(`${buildPath}/${file.path}`, file.bytes, contentType(file.path));
1375
+ }
1376
+ await workspace.write(entryPath, entry.bytes, contentType(entry.path));
1377
+ return { ...done, cached: false };
1378
+ }
1379
+ catch (error) {
1380
+ return { error: error instanceof Error ? error.message : String(error) };
1381
+ }
1382
+ finally {
1383
+ await rm(staging, { recursive: true, force: true });
1384
+ }
1385
+ }
1386
+ const buildGame = {
1387
+ spec: gameSpecs[5],
1388
+ impl: async (run, args) => {
1389
+ const user = userId(run);
1390
+ const id = args["game_id"];
1391
+ if (!user || typeof id !== "string")
1392
+ return { error: "game_id is required in a user session" };
1393
+ const target = args["target"] ?? "web";
1394
+ if (target !== "web" && target !== "tauri")
1395
+ return { error: "target must be web or tauri" };
1396
+ const revision = args["revision"];
1397
+ const result = await buildOwnedNativeGame({ userId: user, gameId: id, revision: typeof revision === "string" ? revision : undefined,
1398
+ target, storage: run.context.assetStorage, signal: run.context.signal });
1399
+ if ("error" in result)
1400
+ return { error: result.error };
1401
+ const report = { game_id: result.game_id, revision: result.revision, target: result.target, build_path: result.build_path,
1402
+ entry_path: result.entry_path, cached: result.cached };
1403
+ return target === "tauri"
1404
+ ? { ...report, next: "The project builds desktop installers with `npm install && npm run build` in a checkout that has Rust and the Tauri prerequisites." }
1405
+ : report;
1303
1406
  }
1304
1407
  };
1305
1408
  const listExamples = {
@@ -1423,10 +1526,24 @@ const autoplay = {
1423
1526
  return { game_id: shortResourceId(game.id), revision: document.revision, ...route, win_tick: winTick, reached_targets: reachedTargets, level_stats: levelStats, state: snapshot };
1424
1527
  }
1425
1528
  };
1529
+ function withDraftRecovery(capability) {
1530
+ return { ...capability, impl: async (run, args) => {
1531
+ try {
1532
+ return await capability.impl(run, args);
1533
+ }
1534
+ catch (error) {
1535
+ if (error instanceof MissingGameDraftSourceError) {
1536
+ return { error: error.message, code: "game_draft_source_unavailable" };
1537
+ }
1538
+ throw error;
1539
+ }
1540
+ } };
1541
+ }
1426
1542
  export const module = {
1427
1543
  module: "game",
1428
1544
  exports: [create, get, save, install, playtest, buildGame, edit, capture, generateAsset, listExamples, getExample, installExample, autoplay,
1429
1545
  { spec: previewGameAuthoringSpec, impl: previewGameAuthoring },
1430
- { spec: applyGameAuthoringSpec, impl: applyGameAuthoring }]
1546
+ { spec: applyGameAuthoringSpec, impl: applyGameAuthoring },
1547
+ { spec: browseGameAssetsSpec, impl: browseGameAssets }].map(withDraftRecovery)
1431
1548
  };
1432
1549
  //# sourceMappingURL=game.js.map