@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,173 @@
1
+ import { Component } from '../component.js';
2
+ import { AnimatedSprite } from '../components/animated-sprite.js';
3
+ import { closestLogicSet, logicSet, registeredLogicSets, } from './hooks.js';
4
+ /** Whether one trigger fires. Unknown or malformed triggers never fire. */
5
+ export function evaluateTrigger(on, env) {
6
+ const sep = on.indexOf(':');
7
+ if (sep <= 0)
8
+ return false;
9
+ const kind = on.slice(0, sep);
10
+ const arg = on.slice(sep + 1);
11
+ if (kind === 'input')
12
+ return env.justPressed(arg);
13
+ if (kind === 'timer')
14
+ return env.elapsed >= Number(arg);
15
+ if (kind === 'signal')
16
+ return env.signals.has(arg);
17
+ return false;
18
+ }
19
+ /** The first firing transition: the state's own edges, then '*''s. */
20
+ export function nextTransition(states, current, env) {
21
+ const edges = [...(states[current]?.transitions ?? []), ...(states['*']?.transitions ?? [])];
22
+ return edges.find((t) => evaluateTrigger(t.on, env));
23
+ }
24
+ /**
25
+ * The hooks that actually run for one phase of a state: the state's own
26
+ * (logic set + instance), or — when none of them define the phase — the
27
+ * set's 'default' entry. A custom state keeps the role's stock body
28
+ * update unless it brings its own.
29
+ */
30
+ export function phaseHooks(hooks, fallback, phase) {
31
+ if (hooks.some((h) => h[phase]))
32
+ return hooks;
33
+ return fallback?.[phase] ? [fallback] : hooks;
34
+ }
35
+ /**
36
+ * Finite state machine: the single owner of a character's per-frame
37
+ * logic — only the active state's code moves the body, so behaviors
38
+ * never fight over velocity. States and transitions are prefab data;
39
+ * state code lives in the role's logic set (defineRole / defineStates)
40
+ * picked by `role`. Entering a state plays the sibling AnimatedSprite
41
+ * clip of the same name (or the state's `clip` override); a missing
42
+ * clip warns and keeps the previous one.
43
+ */
44
+ export class StateMachine extends Component {
45
+ static componentName = 'StateMachine';
46
+ static displayName = 'State Machine';
47
+ static params = {
48
+ role: { label: 'Role' },
49
+ };
50
+ /** The character's role — names the logic set providing its state code. */
51
+ role = '';
52
+ /** Starting state; defaults to the first declared state. */
53
+ initial = '';
54
+ states = {};
55
+ /** Active state name. */
56
+ current = '';
57
+ /** Seconds spent in the active state. */
58
+ elapsed = 0;
59
+ instanceHooks = new Map();
60
+ signals = new Set();
61
+ warnedClips = new Set();
62
+ onReady() {
63
+ if (this.role && !logicSet(this.role)) {
64
+ const sets = registeredLogicSets();
65
+ const hint = closestLogicSet(this.role);
66
+ console.error(`[waica] "${this.entity.name}": role "${this.role}" has no registered state code. ` +
67
+ `Known: ${sets.length ? sets.join(', ') : '(none)'}.` +
68
+ (hint ? ` Did you mean "${hint}"?` : ''));
69
+ }
70
+ const start = this.initial || Object.keys(this.states).find((name) => name !== '*');
71
+ if (start)
72
+ this.enter(start);
73
+ }
74
+ /** Adds instance-level hooks on top of the logic set — the escape hatch. */
75
+ on(state, hooks) {
76
+ const list = this.instanceHooks.get(state) ?? [];
77
+ list.push(hooks);
78
+ this.instanceHooks.set(state, list);
79
+ }
80
+ /** Queues a signal for 'signal:<name>' transitions, consumed this frame or the next. */
81
+ signal(name) {
82
+ this.signals.add(name);
83
+ }
84
+ /** Jumps straight to a state, ignoring transitions. */
85
+ goto(state) {
86
+ if (state !== this.current)
87
+ this.enter(state);
88
+ }
89
+ onUpdate(dt) {
90
+ if (!this.current)
91
+ return;
92
+ this.run('*', 'onUpdate', dt);
93
+ this.run(this.current, 'onUpdate', dt);
94
+ this.elapsed += dt;
95
+ // Chained transitions settle within the frame (e.g. land → idle → run),
96
+ // capped so a degenerate cyclic graph can't hang the loop.
97
+ for (let hops = 0; hops < 8; hops++) {
98
+ const edge = nextTransition(this.states, this.current, this.env());
99
+ if (!edge)
100
+ break;
101
+ // A transition fired by a key press spends it: one press, one
102
+ // transition — it can't fire again from the state just entered.
103
+ if (edge.on.startsWith('input:'))
104
+ this.game.input.consume(edge.on.slice('input:'.length));
105
+ this.enter(edge.to);
106
+ }
107
+ this.signals.clear();
108
+ }
109
+ onCollide(other) {
110
+ // The state that was active when the contact happened owns it: a
111
+ // transition fired inside the always-hook must not hand the collision
112
+ // to the state it just entered.
113
+ const state = this.current;
114
+ if (!state)
115
+ return;
116
+ this.runCollision('*', other);
117
+ // Either side can be destroyed by a hook. Game.dispatchCollisions guards
118
+ // the same way between the two sides of one contact.
119
+ if (!this.entity.alive || !other.alive)
120
+ return;
121
+ this.runCollision(state, other);
122
+ }
123
+ env() {
124
+ return {
125
+ justPressed: (action) => this.game.input.justPressed(action) && !this.game.input.consumed(action),
126
+ elapsed: this.elapsed,
127
+ signals: this.signals,
128
+ };
129
+ }
130
+ hooksFor(state) {
131
+ const fromSet = this.role ? logicSet(this.role)?.[state] : undefined;
132
+ return [...(fromSet ? [fromSet] : []), ...(this.instanceHooks.get(state) ?? [])];
133
+ }
134
+ run(state, phase, dt = 0) {
135
+ const ctx = { entity: this.entity, game: this.game, fsm: this };
136
+ const fallback = state !== '*' && this.role ? logicSet(this.role)?.['default'] : undefined;
137
+ for (const hooks of phaseHooks(this.hooksFor(state), fallback, phase)) {
138
+ if (phase === 'onUpdate')
139
+ hooks.onUpdate?.(ctx, dt);
140
+ else
141
+ hooks[phase]?.(ctx);
142
+ }
143
+ }
144
+ runCollision(state, other) {
145
+ const ctx = { entity: this.entity, game: this.game, fsm: this };
146
+ const fallback = state !== '*' && this.role ? logicSet(this.role)?.['default'] : undefined;
147
+ for (const hooks of phaseHooks(this.hooksFor(state), fallback, 'onCollide')) {
148
+ hooks.onCollide?.(ctx, other);
149
+ }
150
+ }
151
+ enter(state) {
152
+ if (this.current)
153
+ this.run(this.current, 'onExit');
154
+ this.current = state;
155
+ this.elapsed = 0;
156
+ this.playClip(state);
157
+ this.run(state, 'onEnter');
158
+ }
159
+ playClip(state) {
160
+ const sprite = this.entity.get(AnimatedSprite);
161
+ if (!sprite)
162
+ return;
163
+ const clip = this.states[state]?.clip ?? state;
164
+ if (sprite.clips[clip]) {
165
+ sprite.play(clip);
166
+ }
167
+ else if (!this.warnedClips.has(state)) {
168
+ this.warnedClips.add(state);
169
+ console.warn(`[waica] "${this.entity.name}": no clip "${clip}" for state "${state}" — ` +
170
+ `keeping "${sprite.current ?? 'none'}"`);
171
+ }
172
+ }
173
+ }
@@ -0,0 +1,27 @@
1
+ /** A stat's value: points/lives are numbers, flags are booleans, labels strings. */
2
+ export type StatValue = number | boolean | string;
3
+ /**
4
+ * Named game state (points, lives, door-open flags…) declared in the
5
+ * project's src/stats.json. Every new Game starts from the declared initial
6
+ * values; behaviours read and change them and can subscribe to changes.
7
+ */
8
+ export declare class Stats {
9
+ private readonly initial;
10
+ private values;
11
+ private readonly emitter;
12
+ constructor(initial?: Record<string, StatValue>);
13
+ /** Current value; undeclared stats read as undefined. */
14
+ get(name: string): StatValue | undefined;
15
+ /** Numeric read: non-number and undeclared stats count as 0. */
16
+ count(name: string): number;
17
+ /** Sets a stat (declared or not) and notifies subscribers on real changes. */
18
+ set(name: string, value: StatValue): void;
19
+ /** Adds delta (default 1) to a numeric stat; undeclared stats start at 0. */
20
+ add(name: string, delta?: number): number;
21
+ /** Back to the declared initial values, notifying every stat that changed. */
22
+ reset(): void;
23
+ /** Fires when the named stat changes. Returns the unsubscribe. */
24
+ onChange(name: string, handler: (value: StatValue | undefined) => void): () => void;
25
+ /** Every stat with its current value (declared and runtime-created). */
26
+ entries(): Array<[string, StatValue]>;
27
+ }
@@ -0,0 +1,54 @@
1
+ import { Emitter } from './events.js';
2
+ /**
3
+ * Named game state (points, lives, door-open flags…) declared in the
4
+ * project's src/stats.json. Every new Game starts from the declared initial
5
+ * values; behaviours read and change them and can subscribe to changes.
6
+ */
7
+ export class Stats {
8
+ initial;
9
+ values;
10
+ emitter = new Emitter();
11
+ constructor(initial = {}) {
12
+ this.initial = { ...initial };
13
+ this.values = { ...initial };
14
+ }
15
+ /** Current value; undeclared stats read as undefined. */
16
+ get(name) {
17
+ return this.values[name];
18
+ }
19
+ /** Numeric read: non-number and undeclared stats count as 0. */
20
+ count(name) {
21
+ const value = this.values[name];
22
+ return typeof value === 'number' ? value : 0;
23
+ }
24
+ /** Sets a stat (declared or not) and notifies subscribers on real changes. */
25
+ set(name, value) {
26
+ if (this.values[name] === value)
27
+ return;
28
+ this.values[name] = value;
29
+ this.emitter.emit(name, value);
30
+ }
31
+ /** Adds delta (default 1) to a numeric stat; undeclared stats start at 0. */
32
+ add(name, delta = 1) {
33
+ const next = this.count(name) + delta;
34
+ this.set(name, next);
35
+ return next;
36
+ }
37
+ /** Back to the declared initial values, notifying every stat that changed. */
38
+ reset() {
39
+ const previous = this.values;
40
+ this.values = { ...this.initial };
41
+ for (const name of new Set([...Object.keys(previous), ...Object.keys(this.values)])) {
42
+ if (previous[name] !== this.values[name])
43
+ this.emitter.emit(name, this.values[name]);
44
+ }
45
+ }
46
+ /** Fires when the named stat changes. Returns the unsubscribe. */
47
+ onChange(name, handler) {
48
+ return this.emitter.on(name, handler);
49
+ }
50
+ /** Every stat with its current value (declared and runtime-created). */
51
+ entries() {
52
+ return Object.entries(this.values);
53
+ }
54
+ }
@@ -0,0 +1,47 @@
1
+ import type { Stats } from './stats.js';
2
+ /**
3
+ * The HTML UI layer. Each piece is a self-contained HTML fragment
4
+ * (markup + <style>) that only DRAWS: it declares which stats it shows
5
+ * with {{stat}} placeholders and positions itself with its own CSS.
6
+ * Behaviour always comes from outside — code toggles pieces with
7
+ * show/hide and wires interactivity through element().
8
+ *
9
+ * Pieces mount inside a transparent overlay that covers the game canvas
10
+ * (each in its own shadow root, so styles never leak between pieces or
11
+ * into the hosting page). The whole overlay hides while the game is not
12
+ * simulating (pause / editor edit mode).
13
+ */
14
+ export declare class GameUi {
15
+ private readonly stats;
16
+ /** Resolved lazily: the canvas may not be in the DOM at construction. */
17
+ private readonly host;
18
+ private readonly sources;
19
+ private readonly pieces;
20
+ private overlay?;
21
+ private active;
22
+ constructor(stats: Stats,
23
+ /** Resolved lazily: the canvas may not be in the DOM at construction. */
24
+ host: () => HTMLElement);
25
+ /** Registers a piece's HTML source. Re-defining an unmounted name wins. */
26
+ define(name: string, html: string): void;
27
+ defineAll(pieces: Record<string, string>): void;
28
+ /** Piece names available to show (defined via the registry or define()). */
29
+ names(): string[];
30
+ show(name: string): void;
31
+ hide(name: string): void;
32
+ toggle(name: string): void;
33
+ isVisible(name: string): boolean;
34
+ /**
35
+ * The piece's DOM root — the escape hatch that keeps pieces logic-free:
36
+ * behaviour is wired from code (element(...).querySelector + listeners).
37
+ * Mounts the piece hidden if it wasn't mounted yet.
38
+ */
39
+ element(name: string): HTMLElement | null;
40
+ /** Called by the game loop: the overlay only draws while simulating. */
41
+ setActive(active: boolean): void;
42
+ /** Unmounts every piece and removes the overlay (Game.dispose). */
43
+ dispose(): void;
44
+ private mount;
45
+ private mountOverlay;
46
+ private sync;
47
+ }
@@ -0,0 +1,175 @@
1
+ /**
2
+ * The HTML UI layer. Each piece is a self-contained HTML fragment
3
+ * (markup + <style>) that only DRAWS: it declares which stats it shows
4
+ * with {{stat}} placeholders and positions itself with its own CSS.
5
+ * Behaviour always comes from outside — code toggles pieces with
6
+ * show/hide and wires interactivity through element().
7
+ *
8
+ * Pieces mount inside a transparent overlay that covers the game canvas
9
+ * (each in its own shadow root, so styles never leak between pieces or
10
+ * into the hosting page). The whole overlay hides while the game is not
11
+ * simulating (pause / editor edit mode).
12
+ */
13
+ export class GameUi {
14
+ stats;
15
+ host;
16
+ sources = new Map();
17
+ pieces = new Map();
18
+ overlay;
19
+ active = true;
20
+ constructor(stats,
21
+ /** Resolved lazily: the canvas may not be in the DOM at construction. */
22
+ host) {
23
+ this.stats = stats;
24
+ this.host = host;
25
+ }
26
+ /** Registers a piece's HTML source. Re-defining an unmounted name wins. */
27
+ define(name, html) {
28
+ this.sources.set(name, html);
29
+ }
30
+ defineAll(pieces) {
31
+ for (const [name, html] of Object.entries(pieces))
32
+ this.define(name, html);
33
+ }
34
+ /** Piece names available to show (defined via the registry or define()). */
35
+ names() {
36
+ return [...this.sources.keys()];
37
+ }
38
+ show(name) {
39
+ const piece = this.mount(name);
40
+ if (piece)
41
+ piece.visible = true;
42
+ this.sync();
43
+ }
44
+ hide(name) {
45
+ const piece = this.pieces.get(name);
46
+ if (piece)
47
+ piece.visible = false;
48
+ this.sync();
49
+ }
50
+ toggle(name) {
51
+ if (this.isVisible(name))
52
+ this.hide(name);
53
+ else
54
+ this.show(name);
55
+ }
56
+ isVisible(name) {
57
+ return this.pieces.get(name)?.visible ?? false;
58
+ }
59
+ /**
60
+ * The piece's DOM root — the escape hatch that keeps pieces logic-free:
61
+ * behaviour is wired from code (element(...).querySelector + listeners).
62
+ * Mounts the piece hidden if it wasn't mounted yet.
63
+ */
64
+ element(name) {
65
+ return this.mount(name)?.root ?? null;
66
+ }
67
+ /** Called by the game loop: the overlay only draws while simulating. */
68
+ setActive(active) {
69
+ if (this.active === active)
70
+ return;
71
+ this.active = active;
72
+ this.sync();
73
+ }
74
+ /** Unmounts every piece and removes the overlay (Game.dispose). */
75
+ dispose() {
76
+ for (const piece of this.pieces.values()) {
77
+ for (const off of piece.unsubs)
78
+ off();
79
+ }
80
+ this.pieces.clear();
81
+ this.overlay?.remove();
82
+ this.overlay = undefined;
83
+ }
84
+ mount(name) {
85
+ const existing = this.pieces.get(name);
86
+ if (existing)
87
+ return existing;
88
+ const html = this.sources.get(name);
89
+ if (html == null) {
90
+ console.warn(`[waica] unknown ui piece: "${name}"`);
91
+ return null;
92
+ }
93
+ // Same geometry as the overlay, so the piece's own CSS positions
94
+ // against the full canvas. pointer-events stays off unless the piece's
95
+ // CSS opts in (e.g. a button with pointer-events:auto).
96
+ const shell = document.createElement('div');
97
+ shell.style.cssText = 'position:absolute;inset:0;pointer-events:none';
98
+ const shadow = shell.attachShadow({ mode: 'open' });
99
+ const root = document.createElement('div');
100
+ root.style.display = 'contents';
101
+ root.innerHTML = html;
102
+ shadow.append(root);
103
+ const piece = { shell, root, visible: false, unsubs: bindStats(root, this.stats) };
104
+ this.mountOverlay().append(shell);
105
+ this.pieces.set(name, piece);
106
+ return piece;
107
+ }
108
+ mountOverlay() {
109
+ if (this.overlay)
110
+ return this.overlay;
111
+ const overlay = document.createElement('div');
112
+ overlay.style.cssText = 'position:absolute;inset:0;z-index:9000;pointer-events:none';
113
+ const host = this.host();
114
+ if (getComputedStyle(host).position === 'static')
115
+ host.style.position = 'relative';
116
+ host.append(overlay);
117
+ this.overlay = overlay;
118
+ this.sync();
119
+ return overlay;
120
+ }
121
+ sync() {
122
+ if (this.overlay)
123
+ this.overlay.style.display = this.active ? '' : 'none';
124
+ for (const piece of this.pieces.values()) {
125
+ piece.shell.style.display = piece.visible ? '' : 'none';
126
+ }
127
+ }
128
+ }
129
+ const BINDING = /\{\{\s*([\w-]+)\s*\}\}/g;
130
+ function renderStat(value) {
131
+ if (value === undefined)
132
+ return '';
133
+ if (typeof value === 'boolean')
134
+ return value ? '✓' : '✕';
135
+ return String(value);
136
+ }
137
+ /**
138
+ * Replaces {{stat}} placeholders in the fragment's text with reactive text
139
+ * nodes kept in sync with the stats. Text-only by design: the binding
140
+ * language has no expressions — presentation, never logic.
141
+ */
142
+ function bindStats(root, stats) {
143
+ const unsubs = [];
144
+ const walker = document.createTreeWalker(root, NodeFilter.SHOW_TEXT);
145
+ const targets = [];
146
+ for (let node = walker.nextNode(); node; node = walker.nextNode()) {
147
+ // Braces inside <style>/<script> are CSS/code, not bindings.
148
+ if (node.parentElement?.closest('style, script'))
149
+ continue;
150
+ if ((node.nodeValue ?? '').includes('{{'))
151
+ targets.push(node);
152
+ }
153
+ for (const text of targets) {
154
+ const source = text.nodeValue ?? '';
155
+ const parts = [];
156
+ let last = 0;
157
+ for (const match of source.matchAll(BINDING)) {
158
+ const stat = match[1];
159
+ if (stat === undefined)
160
+ continue;
161
+ if (match.index > last)
162
+ parts.push(document.createTextNode(source.slice(last, match.index)));
163
+ const bound = document.createTextNode(renderStat(stats.get(stat)));
164
+ unsubs.push(stats.onChange(stat, (value) => (bound.nodeValue = renderStat(value))));
165
+ parts.push(bound);
166
+ last = match.index + match[0].length;
167
+ }
168
+ if (parts.length === 0)
169
+ continue;
170
+ if (last < source.length)
171
+ parts.push(document.createTextNode(source.slice(last)));
172
+ text.replaceWith(...parts);
173
+ }
174
+ return unsubs;
175
+ }
@@ -0,0 +1,25 @@
1
+ {
2
+ "name": "@waica/engine",
3
+ "version": "0.3.0",
4
+ "description": "Waica game engine core — archetype-driven, web-first, 2D & 3D",
5
+ "license": "MIT",
6
+ "type": "module",
7
+ "repository": {
8
+ "type": "git",
9
+ "url": "https://github.com/chichex/waica",
10
+ "directory": "packages/engine"
11
+ },
12
+ "files": [
13
+ "dist"
14
+ ],
15
+ "exports": {
16
+ ".": {
17
+ "types": "./dist/index.d.ts",
18
+ "default": "./dist/index.js"
19
+ }
20
+ },
21
+ "dependencies": {
22
+ "@types/three": "^0.185.1",
23
+ "three": "^0.185.1"
24
+ }
25
+ }