@waica/cli 0.3.0

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 (231) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +64 -0
  3. package/dist/cli.js +184 -0
  4. package/dist/editor/assets/abap-08VXUWAP.js +1 -0
  5. package/dist/editor/assets/apex-BWPQTe0t.js +1 -0
  6. package/dist/editor/assets/azcli-Bc_sGQ0U.js +1 -0
  7. package/dist/editor/assets/bat-i0X4ZdIN.js +1 -0
  8. package/dist/editor/assets/bicep-B5-_aFwp.js +2 -0
  9. package/dist/editor/assets/cameligo-DMUM7wLl.js +1 -0
  10. package/dist/editor/assets/clojure-Cm7r79vr.js +1 -0
  11. package/dist/editor/assets/codicon-DCmgc-ay.ttf +0 -0
  12. package/dist/editor/assets/coffee-Ba7i2nA0.js +1 -0
  13. package/dist/editor/assets/cpp-C7h46wYY.js +1 -0
  14. package/dist/editor/assets/csharp-BKxtCVv1.js +1 -0
  15. package/dist/editor/assets/csp-bTuwJoIa.js +1 -0
  16. package/dist/editor/assets/css-DIMkf-bt.js +3 -0
  17. package/dist/editor/assets/css.worker-Fwwr_HdT.js +88 -0
  18. package/dist/editor/assets/cssMode-CQE-WdwL.js +4 -0
  19. package/dist/editor/assets/cypher-CVaqCwHa.js +1 -0
  20. package/dist/editor/assets/dart-onAF5SnQ.js +1 -0
  21. package/dist/editor/assets/dockerfile-DZFCIeNp.js +1 -0
  22. package/dist/editor/assets/ecl-D05T4iGw.js +1 -0
  23. package/dist/editor/assets/editor.worker-DsBDrWS5.js +12 -0
  24. package/dist/editor/assets/elixir-6RTg0lbw.js +1 -0
  25. package/dist/editor/assets/flow9-C5_-GSwl.js +1 -0
  26. package/dist/editor/assets/freemarker2-CYgdVabn.js +3 -0
  27. package/dist/editor/assets/fsharp-C8Ef5oNN.js +1 -0
  28. package/dist/editor/assets/go-C-y9NEjX.js +1 -0
  29. package/dist/editor/assets/graphql-fmXr3nnJ.js +1 -0
  30. package/dist/editor/assets/handlebars-DdeORCJD.js +1 -0
  31. package/dist/editor/assets/hcl-CpzslTdj.js +1 -0
  32. package/dist/editor/assets/html-DTIAGV84.js +1 -0
  33. package/dist/editor/assets/html.worker-B3Ztmmz2.js +495 -0
  34. package/dist/editor/assets/htmlMode-CRV3iBih.js +4 -0
  35. package/dist/editor/assets/index-Btzc6zxr.css +1 -0
  36. package/dist/editor/assets/index-Dhkqk31v.js +11016 -0
  37. package/dist/editor/assets/ini-sBoK_t0W.js +1 -0
  38. package/dist/editor/assets/java-BEtHBSE6.js +1 -0
  39. package/dist/editor/assets/javascript-dxBW86cl.js +1 -0
  40. package/dist/editor/assets/json.worker-PWQxM7ZO.js +51 -0
  41. package/dist/editor/assets/jsonMode-DFA5-FFK.js +10 -0
  42. package/dist/editor/assets/julia-Bri6UV-V.js +1 -0
  43. package/dist/editor/assets/kotlin-B0qVs-E9.js +1 -0
  44. package/dist/editor/assets/less-B9JPFI3C.js +2 -0
  45. package/dist/editor/assets/lexon-CfSJPG6W.js +1 -0
  46. package/dist/editor/assets/liquid-CScWaXYV.js +1 -0
  47. package/dist/editor/assets/lua-CsQS60Ue.js +1 -0
  48. package/dist/editor/assets/m3-D-oSqn_W.js +1 -0
  49. package/dist/editor/assets/markdown-Cimd5fb3.js +1 -0
  50. package/dist/editor/assets/mdx-Bvp84SqA.js +1 -0
  51. package/dist/editor/assets/mips-CIPQ_RoX.js +1 -0
  52. package/dist/editor/assets/msdax-DauUninz.js +1 -0
  53. package/dist/editor/assets/mysql-SOo6toE5.js +1 -0
  54. package/dist/editor/assets/objective-c-FvmIjYaQ.js +1 -0
  55. package/dist/editor/assets/pascal-DrH0SRf2.js +1 -0
  56. package/dist/editor/assets/pascaligo-D-ptJ9y-.js +1 -0
  57. package/dist/editor/assets/perl-oz_6vUea.js +1 -0
  58. package/dist/editor/assets/pgsql-DTj74zXo.js +1 -0
  59. package/dist/editor/assets/php-nr791fC2.js +1 -0
  60. package/dist/editor/assets/pla-CopQ2nXW.js +1 -0
  61. package/dist/editor/assets/postiats-43DmfD33.js +1 -0
  62. package/dist/editor/assets/powerquery-D3hlyOfw.js +1 -0
  63. package/dist/editor/assets/powershell-DmHpPYUd.js +1 -0
  64. package/dist/editor/assets/protobuf-C531GsRP.js +2 -0
  65. package/dist/editor/assets/pug-Z5eAx3Zn.js +1 -0
  66. package/dist/editor/assets/python-CG-OtvFa.js +1 -0
  67. package/dist/editor/assets/qsharp-DkqhCAOL.js +1 -0
  68. package/dist/editor/assets/r-BwWrilGY.js +1 -0
  69. package/dist/editor/assets/razor-r-iUEZPj.js +1 -0
  70. package/dist/editor/assets/redis-ClamHrr6.js +1 -0
  71. package/dist/editor/assets/redshift-DT7zqm-g.js +1 -0
  72. package/dist/editor/assets/restructuredtext-BYgofb2h.js +1 -0
  73. package/dist/editor/assets/ruby-DezsRK8O.js +1 -0
  74. package/dist/editor/assets/rust-DdL9SqIa.js +1 -0
  75. package/dist/editor/assets/sb-CcwsVR0C.js +1 -0
  76. package/dist/editor/assets/scala-DHpiXF5c.js +1 -0
  77. package/dist/editor/assets/scheme-BeGwcela.js +1 -0
  78. package/dist/editor/assets/scss-gp-XZpBa.js +3 -0
  79. package/dist/editor/assets/shell-CC2rA5mh.js +1 -0
  80. package/dist/editor/assets/solidity-BEEn4gHE.js +1 -0
  81. package/dist/editor/assets/sophia-CRfGWb83.js +1 -0
  82. package/dist/editor/assets/sparql-D_Lu-MrJ.js +1 -0
  83. package/dist/editor/assets/sql-NEE52Syq.js +1 -0
  84. package/dist/editor/assets/st-DbInun42.js +1 -0
  85. package/dist/editor/assets/swift-Bxkupp3x.js +1 -0
  86. package/dist/editor/assets/systemverilog-Bz4Y3fRF.js +1 -0
  87. package/dist/editor/assets/tcl-DISqw1ZD.js +1 -0
  88. package/dist/editor/assets/ts.worker-0mEyHVru.js +51339 -0
  89. package/dist/editor/assets/tsMode-Bth1rJwM.js +11 -0
  90. package/dist/editor/assets/twig-De2hgUGE.js +1 -0
  91. package/dist/editor/assets/typescript-tNRuHsoe.js +1 -0
  92. package/dist/editor/assets/typespec-B8J7ngcE.js +1 -0
  93. package/dist/editor/assets/vb-DV3o63ZY.js +1 -0
  94. package/dist/editor/assets/wgsl-DpFanUEy.js +298 -0
  95. package/dist/editor/assets/xml-BCx9e6CK.js +1 -0
  96. package/dist/editor/assets/yaml-Bb-peGP_.js +1 -0
  97. package/dist/editor/index.html +14 -0
  98. package/dist/mcp/archetypes.js +103 -0
  99. package/dist/mcp/cli.js +6 -0
  100. package/dist/mcp/create-project.js +151 -0
  101. package/dist/mcp/introspection.js +201 -0
  102. package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-coin.png +0 -0
  103. package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-dog.png +0 -0
  104. package/dist/mcp/node_modules/@waica/archetype-platformer/assets/waica-slime.png +0 -0
  105. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/art.d.ts +10 -0
  106. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/art.js +13 -0
  107. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/bundle.d.ts +3 -0
  108. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/bundle.js +10 -0
  109. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/controls.d.ts +5 -0
  110. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/controls.js +12 -0
  111. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/index.d.ts +38 -0
  112. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/index.js +34 -0
  113. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/manifest.d.ts +15 -0
  114. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/manifest.js +26 -0
  115. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/prefabs.d.ts +7 -0
  116. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/prefabs.js +97 -0
  117. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry-data.d.ts +5 -0
  118. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry-data.js +45 -0
  119. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry.d.ts +6 -0
  120. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/registry.js +15 -0
  121. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/scene-default.d.ts +40 -0
  122. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/scene-default.js +93 -0
  123. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/ui.d.ts +10 -0
  124. package/dist/mcp/node_modules/@waica/archetype-platformer/dist/ui.js +24 -0
  125. package/dist/mcp/node_modules/@waica/archetype-platformer/package.json +30 -0
  126. package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.d.ts +59 -0
  127. package/dist/mcp/node_modules/@waica/behaviors/dist/chaser.js +127 -0
  128. package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.d.ts +25 -0
  129. package/dist/mcp/node_modules/@waica/behaviors/dist/collectible.js +27 -0
  130. package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.d.ts +29 -0
  131. package/dist/mcp/node_modules/@waica/behaviors/dist/hazard.js +42 -0
  132. package/dist/mcp/node_modules/@waica/behaviors/dist/index.d.ts +9 -0
  133. package/dist/mcp/node_modules/@waica/behaviors/dist/index.js +9 -0
  134. package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.d.ts +16 -0
  135. package/dist/mcp/node_modules/@waica/behaviors/dist/lifetime.js +19 -0
  136. package/dist/mcp/node_modules/@waica/behaviors/dist/npc.d.ts +4 -0
  137. package/dist/mcp/node_modules/@waica/behaviors/dist/npc.js +14 -0
  138. package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.d.ts +43 -0
  139. package/dist/mcp/node_modules/@waica/behaviors/dist/patrol.js +68 -0
  140. package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.d.ts +124 -0
  141. package/dist/mcp/node_modules/@waica/behaviors/dist/platformer-motor.js +146 -0
  142. package/dist/mcp/node_modules/@waica/behaviors/dist/player-identity.d.ts +3 -0
  143. package/dist/mcp/node_modules/@waica/behaviors/dist/player-identity.js +5 -0
  144. package/dist/mcp/node_modules/@waica/behaviors/dist/player-states.d.ts +18 -0
  145. package/dist/mcp/node_modules/@waica/behaviors/dist/player-states.js +95 -0
  146. package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.d.ts +23 -0
  147. package/dist/mcp/node_modules/@waica/behaviors/dist/respawnable.js +27 -0
  148. package/dist/mcp/node_modules/@waica/behaviors/package.json +24 -0
  149. package/dist/mcp/node_modules/@waica/engine/dist/aabb.d.ts +2 -0
  150. package/dist/mcp/node_modules/@waica/engine/dist/aabb.js +4 -0
  151. package/dist/mcp/node_modules/@waica/engine/dist/animation/clip-player.d.ts +20 -0
  152. package/dist/mcp/node_modules/@waica/engine/dist/animation/clip-player.js +24 -0
  153. package/dist/mcp/node_modules/@waica/engine/dist/animation/contract.d.ts +21 -0
  154. package/dist/mcp/node_modules/@waica/engine/dist/animation/contract.js +23 -0
  155. package/dist/mcp/node_modules/@waica/engine/dist/animation/sheet.d.ts +65 -0
  156. package/dist/mcp/node_modules/@waica/engine/dist/animation/sheet.js +44 -0
  157. package/dist/mcp/node_modules/@waica/engine/dist/archetype.d.ts +37 -0
  158. package/dist/mcp/node_modules/@waica/engine/dist/archetype.js +1 -0
  159. package/dist/mcp/node_modules/@waica/engine/dist/camera.d.ts +71 -0
  160. package/dist/mcp/node_modules/@waica/engine/dist/camera.js +59 -0
  161. package/dist/mcp/node_modules/@waica/engine/dist/collision-shape.d.ts +26 -0
  162. package/dist/mcp/node_modules/@waica/engine/dist/collision-shape.js +136 -0
  163. package/dist/mcp/node_modules/@waica/engine/dist/component-registry.d.ts +16 -0
  164. package/dist/mcp/node_modules/@waica/engine/dist/component-registry.js +44 -0
  165. package/dist/mcp/node_modules/@waica/engine/dist/component.d.ts +58 -0
  166. package/dist/mcp/node_modules/@waica/engine/dist/component.js +11 -0
  167. package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.d.ts +80 -0
  168. package/dist/mcp/node_modules/@waica/engine/dist/components/animated-sprite.js +210 -0
  169. package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.d.ts +68 -0
  170. package/dist/mcp/node_modules/@waica/engine/dist/components/dynamic-body.js +189 -0
  171. package/dist/mcp/node_modules/@waica/engine/dist/components/hitbox.d.ts +25 -0
  172. package/dist/mcp/node_modules/@waica/engine/dist/components/hitbox.js +21 -0
  173. package/dist/mcp/node_modules/@waica/engine/dist/components/solid.d.ts +32 -0
  174. package/dist/mcp/node_modules/@waica/engine/dist/components/solid.js +45 -0
  175. package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.d.ts +51 -0
  176. package/dist/mcp/node_modules/@waica/engine/dist/components/sprite.js +112 -0
  177. package/dist/mcp/node_modules/@waica/engine/dist/entity.d.ts +22 -0
  178. package/dist/mcp/node_modules/@waica/engine/dist/entity.js +52 -0
  179. package/dist/mcp/node_modules/@waica/engine/dist/events.d.ts +8 -0
  180. package/dist/mcp/node_modules/@waica/engine/dist/events.js +20 -0
  181. package/dist/mcp/node_modules/@waica/engine/dist/game.d.ts +95 -0
  182. package/dist/mcp/node_modules/@waica/engine/dist/game.js +263 -0
  183. package/dist/mcp/node_modules/@waica/engine/dist/index.d.ts +39 -0
  184. package/dist/mcp/node_modules/@waica/engine/dist/index.js +26 -0
  185. package/dist/mcp/node_modules/@waica/engine/dist/input.d.ts +40 -0
  186. package/dist/mcp/node_modules/@waica/engine/dist/input.js +84 -0
  187. package/dist/mcp/node_modules/@waica/engine/dist/scene.d.ts +80 -0
  188. package/dist/mcp/node_modules/@waica/engine/dist/scene.js +93 -0
  189. package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.d.ts +21 -0
  190. package/dist/mcp/node_modules/@waica/engine/dist/solid-axis.js +77 -0
  191. package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.d.ts +90 -0
  192. package/dist/mcp/node_modules/@waica/engine/dist/state/hooks.js +81 -0
  193. package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.d.ts +83 -0
  194. package/dist/mcp/node_modules/@waica/engine/dist/state/state-machine.js +173 -0
  195. package/dist/mcp/node_modules/@waica/engine/dist/stats.d.ts +27 -0
  196. package/dist/mcp/node_modules/@waica/engine/dist/stats.js +54 -0
  197. package/dist/mcp/node_modules/@waica/engine/dist/ui.d.ts +47 -0
  198. package/dist/mcp/node_modules/@waica/engine/dist/ui.js +175 -0
  199. package/dist/mcp/node_modules/@waica/engine/package.json +25 -0
  200. package/dist/mcp/package-resolver.js +240 -0
  201. package/dist/mcp/project-path.js +86 -0
  202. package/dist/mcp/scaffolds.js +153 -0
  203. package/dist/mcp/server.js +217 -0
  204. package/dist/mcp/stdio.js +13 -0
  205. package/dist/mcp/template/README.md +22 -0
  206. package/dist/mcp/template/_gitignore +5 -0
  207. package/dist/mcp/template/index.html +25 -0
  208. package/dist/mcp/template/package.json.tpl +20 -0
  209. package/dist/mcp/template/public/waica.params.json +1 -0
  210. package/dist/mcp/template/src/art/waica-coin.png +0 -0
  211. package/dist/mcp/template/src/art/waica-dog.png +0 -0
  212. package/dist/mcp/template/src/art/waica-slime.png +0 -0
  213. package/dist/mcp/template/src/characters/player.character.json +133 -0
  214. package/dist/mcp/template/src/characters/slime.character.json +61 -0
  215. package/dist/mcp/template/src/controls.json +8 -0
  216. package/dist/mcp/template/src/game.json +10 -0
  217. package/dist/mcp/template/src/main.ts +105 -0
  218. package/dist/mcp/template/src/objects/coin.object.json +41 -0
  219. package/dist/mcp/template/src/scenes/main.scene.json +217 -0
  220. package/dist/mcp/template/src/stats.json +7 -0
  221. package/dist/mcp/template/src/tiles/block.tile.json +21 -0
  222. package/dist/mcp/template/src/tiles/decor.tile.json +14 -0
  223. package/dist/mcp/template/src/tiles/platform.tile.json +21 -0
  224. package/dist/mcp/template/src/ui/coin-counter.html +12 -0
  225. package/dist/mcp/template/tsconfig.json +15 -0
  226. package/dist/mcp/template/vite.config.ts +3 -0
  227. package/dist/mcp/validation.js +323 -0
  228. package/dist/mcp/workspace-loader.js +14 -0
  229. package/dist/mcp/workspace-runtime.js +109 -0
  230. package/dist/server.js +210 -0
  231. package/package.json +38 -0
@@ -0,0 +1,21 @@
1
+ {
2
+ "waicaPrefab": 1,
3
+ "type": "tile",
4
+ "components": [
5
+ {
6
+ "type": "Sprite",
7
+ "props": {
8
+ "width": 2,
9
+ "height": 2,
10
+ "color": 2508371
11
+ }
12
+ },
13
+ {
14
+ "type": "Solid",
15
+ "props": {
16
+ "width": 2,
17
+ "height": 2
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "waicaPrefab": 1,
3
+ "type": "tile",
4
+ "components": [
5
+ {
6
+ "type": "Sprite",
7
+ "props": {
8
+ "width": 1,
9
+ "height": 1,
10
+ "color": 9358054
11
+ }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,21 @@
1
+ {
2
+ "waicaPrefab": 1,
3
+ "type": "tile",
4
+ "components": [
5
+ {
6
+ "type": "Sprite",
7
+ "props": {
8
+ "width": 3,
9
+ "height": 0.5,
10
+ "color": 2792847
11
+ }
12
+ },
13
+ {
14
+ "type": "Solid",
15
+ "props": {
16
+ "width": 3,
17
+ "height": 0.5
18
+ }
19
+ }
20
+ ]
21
+ }
@@ -0,0 +1,12 @@
1
+ <style>
2
+ .coin-counter {
3
+ position: absolute;
4
+ top: 12px;
5
+ left: 12px;
6
+ font: 600 20px system-ui, sans-serif;
7
+ color: #ffd166;
8
+ text-shadow: 0 1px 3px #000a;
9
+ user-select: none;
10
+ }
11
+ </style>
12
+ <div class="coin-counter">🪙 {{points}}</div>
@@ -0,0 +1,15 @@
1
+ {
2
+ "compilerOptions": {
3
+ "target": "ES2022",
4
+ "module": "ESNext",
5
+ "moduleResolution": "bundler",
6
+ "lib": ["ES2022", "DOM", "DOM.Iterable"],
7
+ "strict": true,
8
+ "verbatimModuleSyntax": true,
9
+ "isolatedModules": true,
10
+ "skipLibCheck": true,
11
+ "noEmit": true,
12
+ "types": ["vite/client"]
13
+ },
14
+ "include": ["src"]
15
+ }
@@ -0,0 +1,3 @@
1
+ import { defineConfig } from 'vite'
2
+
3
+ export default defineConfig({})
@@ -0,0 +1,323 @@
1
+ import { readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { discoverArchetypes, pickArchetype } from './archetypes.js';
4
+ import { PackageResolver, mixedSourceWarnings, provenanceRows, } from './package-resolver.js';
5
+ import { directFiles, requireWaicaProject } from './project-path.js';
6
+ function add(context, severity, code, message, file, ref) {
7
+ context.findings.push({ severity, code, message, file, ...(ref ? { ref } : {}) });
8
+ }
9
+ async function parseJson(projectPath, relative, findings) {
10
+ try {
11
+ return JSON.parse(await readFile(path.join(projectPath, relative), 'utf8'));
12
+ }
13
+ catch (error) {
14
+ if (error.code === 'ENOENT')
15
+ return undefined;
16
+ add({ findings }, 'error', 'unparseable-json', `Cannot parse JSON: ${error.message}`, relative);
17
+ return undefined;
18
+ }
19
+ }
20
+ async function projectComponentCandidates(projectPath) {
21
+ const names = new Set();
22
+ for (const directory of ['components', 'roles', 'states']) {
23
+ for (const file of await directFiles(path.join(projectPath, 'src', directory), '.ts')) {
24
+ const source = await readFile(path.join(projectPath, 'src', directory, file), 'utf8');
25
+ for (const match of source.matchAll(/\bcomponentName\s*=\s*['"]([^'"]+)['"]/g)) {
26
+ const name = match[1];
27
+ if (name)
28
+ names.add(name);
29
+ }
30
+ }
31
+ }
32
+ return names;
33
+ }
34
+ function checkComponent(component, file, ref, context) {
35
+ if (context.knownComponents.has(component.type))
36
+ return;
37
+ if (context.projectComponents.has(component.type)) {
38
+ add(context, 'info', 'unknown-component', `Component "${component.type}" is project-owned, not validated.`, file, ref);
39
+ return;
40
+ }
41
+ add(context, 'error', 'unknown-component', `Unknown component "${component.type}".`, file, ref);
42
+ }
43
+ function componentList(value) {
44
+ if (!Array.isArray(value))
45
+ return [];
46
+ return value.filter((entry) => !!entry && typeof entry === 'object' && typeof entry.type === 'string');
47
+ }
48
+ function objectRecord(value) {
49
+ return value && typeof value === 'object' && !Array.isArray(value)
50
+ ? value
51
+ : {};
52
+ }
53
+ function machineStates(component) {
54
+ return objectRecord(component.props?.states);
55
+ }
56
+ function escapedRegex(value) {
57
+ return value.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
58
+ }
59
+ async function projectRoleStateSources(projectPath) {
60
+ const sources = new Map();
61
+ for (const file of await directFiles(path.join(projectPath, 'src/roles'), '.ts')) {
62
+ const source = await readFile(path.join(projectPath, 'src/roles', file), 'utf8');
63
+ for (const match of source.matchAll(/\bdefine(?:Role|States)\s*\(\s*(['"])([^'"]+)\1/g)) {
64
+ const role = match[2];
65
+ if (!role)
66
+ continue;
67
+ sources.set(role, [...(sources.get(role) ?? []), source.slice(match.index)]);
68
+ }
69
+ }
70
+ return sources;
71
+ }
72
+ function roleSourceHasState(source, state) {
73
+ const statesMarkers = [...source.matchAll(/\bstates\s*:/g)];
74
+ const candidate = statesMarkers.at(-1);
75
+ const registration = candidate ? source.slice(candidate.index) : source;
76
+ return new RegExp(`\\b${escapedRegex(state)}\\s*:`).test(registration);
77
+ }
78
+ /** Mirrors flat state files and textually recognizes project role registrations. */
79
+ function stateCodeExists(role, state, context) {
80
+ const roleStates = context.manifest.bundle.roles[role]?.states;
81
+ const logicStates = context.manifest.bundle.logicSets?.[role];
82
+ return ((!!roleStates && Object.hasOwn(roleStates, state)) ||
83
+ (!!logicStates && Object.hasOwn(logicStates, state)) ||
84
+ context.stateFiles.has(state) ||
85
+ (context.roleStateSources.get(role) ?? []).some((source) => roleSourceHasState(source, state)));
86
+ }
87
+ function validateStateMachines(components, file, ref, context) {
88
+ const animated = components.find((component) => component.type === 'AnimatedSprite');
89
+ const clips = animated
90
+ ? new Set(Object.keys(objectRecord(animated.props?.clips)))
91
+ : undefined;
92
+ for (const machine of components.filter((component) => component.type === 'StateMachine')) {
93
+ const states = machineStates(machine);
94
+ const realNames = Object.keys(states).filter((name) => name !== '*');
95
+ const role = typeof machine.props?.role === 'string' ? machine.props.role : '';
96
+ const initial = typeof machine.props?.initial === 'string' && machine.props.initial
97
+ ? machine.props.initial
98
+ : (realNames[0] ?? '');
99
+ for (const name of realNames) {
100
+ const definition = objectRecord(states[name]);
101
+ const clip = typeof definition.clip === 'string' ? definition.clip : name;
102
+ if (clips && !clips.has(clip)) {
103
+ add(context, 'warning', 'missing-clip', `State "${name}" uses missing animation clip "${clip}".`, file, ref ?? name);
104
+ }
105
+ const transitions = Array.isArray(definition.transitions) ? definition.transitions : [];
106
+ for (const transition of transitions) {
107
+ if (!transition || typeof transition !== 'object')
108
+ continue;
109
+ const to = transition.to;
110
+ const on = transition.on;
111
+ if (typeof to === 'string' && to !== '*' && !Object.hasOwn(states, to)) {
112
+ add(context, 'warning', 'dangling-transition-target', `State "${name}" transitions to missing state "${to}".`, file, ref ?? name);
113
+ }
114
+ if (typeof on === 'string' && on.startsWith('input:')) {
115
+ const action = on.slice('input:'.length);
116
+ if (!context.bindings[action]?.length) {
117
+ add(context, 'warning', 'input-action-unbound', `Input action "${action}" has no bindings.`, file, action);
118
+ }
119
+ }
120
+ }
121
+ if (name !== initial) {
122
+ const reachable = Object.values(states).some((candidate) => (Array.isArray(candidate?.transitions) ? candidate.transitions : []).some((transition) => transition?.to === name));
123
+ if (!reachable) {
124
+ add(context, 'warning', 'unreachable-state', `Nothing transitions to state "${name}" and it is not initial.`, file, ref ?? name);
125
+ }
126
+ }
127
+ if (!stateCodeExists(role, name, context)) {
128
+ add(context, 'info', 'no-state-code', `State "${name}" has no built-in code, project role registration or src/states/${name}.ts.`, file, ref ?? name);
129
+ }
130
+ }
131
+ }
132
+ }
133
+ function resolvedEntityComponents(entity, prefab) {
134
+ const overrides = objectRecord(entity.overrides);
135
+ const inherited = componentList(prefab?.components).map((component) => ({
136
+ type: component.type,
137
+ props: {
138
+ ...objectRecord(component.props),
139
+ ...objectRecord(overrides[component.type]),
140
+ },
141
+ }));
142
+ return [...inherited, ...componentList(entity.components)];
143
+ }
144
+ function validatePrefab(prefab, file, ref, context) {
145
+ const components = componentList(prefab.components);
146
+ for (const component of components)
147
+ checkComponent(component, file, ref, context);
148
+ validateStateMachines(components, file, ref, context);
149
+ }
150
+ function validateScene(scene, file, prefabs, uiNames, context) {
151
+ const rawEntities = Array.isArray(scene.entities) ? scene.entities : [];
152
+ const entities = rawEntities
153
+ .map((entity, index) => ({ entity, index }))
154
+ .filter((entry) => !!entry.entity && typeof entry.entity === 'object' && !Array.isArray(entry.entity));
155
+ const entityNames = new Set(entities
156
+ .map(({ entity }) => (typeof entity.name === 'string' ? entity.name : ''))
157
+ .filter(Boolean));
158
+ const follow = scene.camera?.follow;
159
+ if (typeof follow === 'string' && follow && !entityNames.has(follow)) {
160
+ add(context, 'warning', 'camera-follow-unknown-entity', `Camera follows unknown entity "${follow}".`, file, follow);
161
+ }
162
+ for (const ui of Array.isArray(scene.ui) ? scene.ui : []) {
163
+ if (typeof ui === 'string' && !uiNames.has(ui)) {
164
+ add(context, 'warning', 'unknown-ui-piece', `Unknown UI piece "${ui}".`, file, ui);
165
+ }
166
+ }
167
+ for (const { entity, index } of entities) {
168
+ const entityRef = typeof entity.name === 'string' && entity.name ? entity.name : `entity[${index}]`;
169
+ const inline = componentList(entity.components);
170
+ for (const component of inline)
171
+ checkComponent(component, file, entityRef, context);
172
+ const prefabRef = typeof entity.prefab === 'string' ? entity.prefab : undefined;
173
+ const overrides = objectRecord(entity.overrides);
174
+ let prefab;
175
+ if (prefabRef) {
176
+ prefab = prefabs.get(prefabRef);
177
+ if (!prefab) {
178
+ add(context, 'error', 'broken-prefab-ref', `Entity "${entityRef}" references missing prefab "${prefabRef}".`, file, prefabRef);
179
+ }
180
+ else {
181
+ const componentTypes = new Set(componentList(prefab.components).map((component) => component.type));
182
+ for (const override of Object.keys(overrides)) {
183
+ if (!componentTypes.has(override)) {
184
+ add(context, 'warning', 'override-key-not-in-prefab', `Override "${override}" is not a component in prefab "${prefabRef}".`, file, prefabRef);
185
+ }
186
+ }
187
+ }
188
+ }
189
+ // Re-evaluate inherited state behavior only when this entity actually
190
+ // changes a StateMachine or AnimatedSprite. Unrelated overrides keep the
191
+ // prefab-level finding as the single source of truth.
192
+ const stateTypes = new Set(['StateMachine', 'AnimatedSprite']);
193
+ const changesStateBehavior = inline.some((component) => stateTypes.has(component.type)) ||
194
+ Object.keys(overrides).some((type) => stateTypes.has(type));
195
+ if (changesStateBehavior) {
196
+ validateStateMachines(resolvedEntityComponents(entity, prefab), file, entityRef, context);
197
+ }
198
+ }
199
+ }
200
+ function statBindings(html) {
201
+ // GameUi binds text nodes only: attributes, comments, style and script
202
+ // contents are not runtime bindings and must not create validator findings.
203
+ const textOnly = html
204
+ .replace(/<!--[\s\S]*?-->/g, '')
205
+ .replace(/<(style|script)\b[^>]*>[\s\S]*?<\/\1>/gi, '')
206
+ .replace(/<[^>]*>/g, ' ');
207
+ const names = new Set();
208
+ for (const match of textOnly.matchAll(/\{\{\s*([\w-]+)\s*\}\}/g)) {
209
+ if (match[1])
210
+ names.add(match[1]);
211
+ }
212
+ return names;
213
+ }
214
+ export async function validateProject(projectPath) {
215
+ const check = await requireWaicaProject(projectPath);
216
+ const findings = [];
217
+ const fixedPaths = [
218
+ 'package.json',
219
+ 'src/game.json',
220
+ 'src/controls.json',
221
+ 'src/stats.json',
222
+ 'public/waica.params.json',
223
+ ];
224
+ const fixed = new Map();
225
+ for (const relative of fixedPaths) {
226
+ fixed.set(relative, await parseJson(projectPath, relative, findings));
227
+ }
228
+ const game = objectRecord(fixed.get('src/game.json'));
229
+ const gameWasUnparseable = findings.some((finding) => finding.code === 'unparseable-json' && finding.file === 'src/game.json');
230
+ const activeId = typeof game.archetype === 'string' && game.archetype
231
+ ? game.archetype
232
+ : gameWasUnparseable
233
+ ? 'platformer'
234
+ : null;
235
+ const resolver = new PackageResolver(projectPath);
236
+ const discoveryWarnings = [];
237
+ const [engine, behaviors, archetypes, projectComponents, roleStateSources] = await Promise.all([
238
+ resolver.load('@waica/engine'),
239
+ resolver.load('@waica/behaviors'),
240
+ discoverArchetypes(projectPath, resolver, discoveryWarnings, activeId ? [activeId] : []),
241
+ projectComponentCandidates(projectPath),
242
+ projectRoleStateSources(projectPath),
243
+ ]);
244
+ const manifest = pickArchetype(archetypes, activeId, projectPath).manifest;
245
+ const controls = objectRecord(objectRecord(fixed.get('src/controls.json')).bindings);
246
+ const bindings = {};
247
+ for (const [name, value] of Object.entries(controls)) {
248
+ if (Array.isArray(value) && value.every((entry) => typeof entry === 'string')) {
249
+ bindings[name] = value;
250
+ }
251
+ }
252
+ const stateFiles = new Set((await directFiles(path.join(projectPath, 'src/states'), '.ts')).map((file) => file.slice(0, -'.ts'.length)));
253
+ const context = {
254
+ findings,
255
+ manifest,
256
+ knownComponents: new Set(Object.keys(manifest.registry.components)),
257
+ projectComponents,
258
+ stateFiles,
259
+ roleStateSources,
260
+ bindings,
261
+ };
262
+ const prefabs = new Map();
263
+ for (const [directory, type] of [
264
+ ['characters', 'character'],
265
+ ['objects', 'object'],
266
+ ['tiles', 'tile'],
267
+ ]) {
268
+ const suffix = `.${type}.json`;
269
+ for (const file of await directFiles(path.join(projectPath, 'src', directory), suffix)) {
270
+ const relative = `src/${directory}/${file}`;
271
+ const parsed = await parseJson(projectPath, relative, findings);
272
+ if (!parsed || typeof parsed !== 'object')
273
+ continue;
274
+ const ref = `${directory}/${file.slice(0, -suffix.length)}`;
275
+ const prefab = parsed;
276
+ prefabs.set(ref, prefab);
277
+ validatePrefab(prefab, relative, ref, context);
278
+ }
279
+ }
280
+ const uiFiles = await directFiles(path.join(projectPath, 'src/ui'), '.html');
281
+ const uiNames = new Set(uiFiles.map((file) => file.slice(0, -'.html'.length)));
282
+ const declaredStats = new Set(Object.keys(objectRecord(objectRecord(fixed.get('src/stats.json')).stats)));
283
+ for (const file of uiFiles) {
284
+ const relative = `src/ui/${file}`;
285
+ const html = await readFile(path.join(projectPath, relative), 'utf8');
286
+ for (const stat of statBindings(html)) {
287
+ if (!declaredStats.has(stat)) {
288
+ add(context, 'warning', 'undeclared-stat', `UI references undeclared stat "${stat}"; runtime writes may still create it.`, relative, stat);
289
+ }
290
+ }
291
+ }
292
+ const sceneFiles = await directFiles(path.join(projectPath, 'src/scenes'), '.scene.json');
293
+ for (const file of sceneFiles) {
294
+ const relative = `src/scenes/${file}`;
295
+ const parsed = await parseJson(projectPath, relative, findings);
296
+ if (!parsed || typeof parsed !== 'object')
297
+ continue;
298
+ validateScene(parsed, relative, prefabs, uiNames, context);
299
+ }
300
+ const params = objectRecord(fixed.get('public/waica.params.json'));
301
+ for (const [entity, rawComponents] of Object.entries(params)) {
302
+ for (const component of Object.keys(objectRecord(rawComponents))) {
303
+ checkComponent({ type: component }, 'public/waica.params.json', entity, context);
304
+ }
305
+ }
306
+ const summary = {
307
+ errors: findings.filter((finding) => finding.severity === 'error').length,
308
+ warnings: findings.filter((finding) => finding.severity === 'warning').length,
309
+ infos: findings.filter((finding) => finding.severity === 'info').length,
310
+ };
311
+ const provenance = provenanceRows([engine, behaviors, ...archetypes.map((entry) => entry.loaded)]);
312
+ return {
313
+ findings,
314
+ summary,
315
+ ok: summary.errors === 0,
316
+ notes: [
317
+ ...check.notes,
318
+ 'The shipped runtime loads src/scenes/main.scene.json; other scenes are validated but are not loaded automatically.',
319
+ ],
320
+ provenance,
321
+ warnings: [...discoveryWarnings, ...mixedSourceWarnings(provenance)],
322
+ };
323
+ }
@@ -0,0 +1,14 @@
1
+ let mappings = {};
2
+ let parentPrefixes = [];
3
+ export function initialize(data) {
4
+ mappings = data.mappings;
5
+ parentPrefixes = data.parentPrefixes;
6
+ }
7
+ export function resolve(specifier, context, nextResolve) {
8
+ const mapped = mappings[specifier];
9
+ const parentURL = context.parentURL;
10
+ const bundledParent = parentURL !== undefined && parentPrefixes.some((prefix) => parentURL.startsWith(prefix));
11
+ return mapped && bundledParent
12
+ ? Promise.resolve({ url: mapped, shortCircuit: true })
13
+ : nextResolve(specifier, context);
14
+ }
@@ -0,0 +1,109 @@
1
+ import { access, readFile } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { fileURLToPath, pathToFileURL } from 'node:url';
4
+ const here = path.dirname(fileURLToPath(import.meta.url));
5
+ const WORKSPACE_DIRECTORIES = {
6
+ '@waica/engine': 'engine',
7
+ '@waica/behaviors': 'behaviors',
8
+ '@waica/archetype-platformer': 'archetype-platformer',
9
+ };
10
+ let workspaceHooks;
11
+ let workspacePackages;
12
+ async function exists(target) {
13
+ try {
14
+ await access(target);
15
+ return true;
16
+ }
17
+ catch (error) {
18
+ if (error.code === 'ENOENT')
19
+ return false;
20
+ throw error;
21
+ }
22
+ }
23
+ /**
24
+ * The server runs both from `packages/mcp/dist` and from the copy the waica CLI
25
+ * bundles under its own `dist`, so the checkout root is searched for rather than
26
+ * assumed at a fixed depth. An unrelated pnpm workspace is harmless: the built
27
+ * engine files below are what actually gate the hook.
28
+ */
29
+ async function findWorkspaceRoot() {
30
+ for (let current = here;; current = path.dirname(current)) {
31
+ if (await exists(path.join(current, 'pnpm-workspace.yaml')))
32
+ return current;
33
+ const parent = path.dirname(current);
34
+ if (parent === current)
35
+ return undefined;
36
+ }
37
+ }
38
+ async function isPackageNamed(packageRoot, packageName) {
39
+ try {
40
+ const manifest = JSON.parse(await readFile(path.join(packageRoot, 'package.json'), 'utf8'));
41
+ return manifest.name === packageName;
42
+ }
43
+ catch {
44
+ return false;
45
+ }
46
+ }
47
+ async function loadWorkspacePackages() {
48
+ const found = new Map();
49
+ const repositoryRoot = await findWorkspaceRoot();
50
+ if (repositoryRoot === undefined)
51
+ return found;
52
+ for (const [packageName, directory] of Object.entries(WORKSPACE_DIRECTORIES)) {
53
+ const packageRoot = path.join(repositoryRoot, 'packages', directory);
54
+ if ((await exists(path.join(packageRoot, 'dist/index.js'))) &&
55
+ (await isPackageNamed(packageRoot, packageName))) {
56
+ found.set(packageName, packageRoot);
57
+ }
58
+ }
59
+ return found;
60
+ }
61
+ /**
62
+ * The built checkout copy of a bundled package, when the server runs from this
63
+ * repository. The waica CLI vendors its own copies under `dist`, so without
64
+ * this a workspace-linked project would look like a mismatch against them
65
+ * instead of like the very same package.
66
+ */
67
+ export async function workspacePackageRoot(packageName) {
68
+ workspacePackages ??= loadWorkspacePackages();
69
+ return (await workspacePackages).get(packageName);
70
+ }
71
+ /**
72
+ * Built workspace packages still import one another by bare package name while
73
+ * checkout exports point at source TypeScript. Map only imports whose parent is
74
+ * already inside one of those built dist trees. Project-anchored resolution is
75
+ * deliberately outside this scope and therefore remains authoritative.
76
+ */
77
+ export async function prepareWorkspaceRuntime() {
78
+ const repositoryRoot = await findWorkspaceRoot();
79
+ if (repositoryRoot === undefined)
80
+ return;
81
+ const files = {
82
+ '@waica/engine': path.join(repositoryRoot, 'packages/engine/dist/index.js'),
83
+ '@waica/behaviors': path.join(repositoryRoot, 'packages/behaviors/dist/index.js'),
84
+ '@waica/archetype-platformer': path.join(repositoryRoot, 'packages/archetype-platformer/dist/index.js'),
85
+ '@waica/archetype-platformer/manifest': path.join(repositoryRoot, 'packages/archetype-platformer/dist/manifest.js'),
86
+ };
87
+ if (!(await Promise.all(Object.values(files).map(exists))).every(Boolean))
88
+ return;
89
+ const mappings = Object.fromEntries(Object.entries(files).map(([specifier, file]) => [specifier, pathToFileURL(file).href]));
90
+ const parentPrefixes = ['engine', 'behaviors', 'archetype-platformer'].map((directory) => `${pathToFileURL(path.join(repositoryRoot, 'packages', directory, 'dist')).href}/`);
91
+ const moduleApi = await import('node:module');
92
+ if (typeof moduleApi.registerHooks === 'function') {
93
+ workspaceHooks = moduleApi.registerHooks({
94
+ resolve(specifier, context, nextResolve) {
95
+ const mapped = mappings[specifier];
96
+ const parentURL = context.parentURL;
97
+ const bundledParent = parentURL !== undefined && parentPrefixes.some((prefix) => parentURL.startsWith(prefix));
98
+ return mapped && bundledParent
99
+ ? { url: mapped, shortCircuit: true }
100
+ : nextResolve(specifier, context);
101
+ },
102
+ });
103
+ return;
104
+ }
105
+ moduleApi.register(new URL('./workspace-loader.js', import.meta.url), {
106
+ parentURL: import.meta.url,
107
+ data: { mappings, parentPrefixes },
108
+ });
109
+ }