@guinetik/gcanvas 1.0.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 (349) hide show
  1. package/.github/workflows/release.yaml +70 -0
  2. package/.jshintrc +4 -0
  3. package/.vscode/settings.json +22 -0
  4. package/CLAUDE.md +310 -0
  5. package/blackhole.jpg +0 -0
  6. package/demo.png +0 -0
  7. package/demos/CNAME +1 -0
  8. package/demos/animations.html +31 -0
  9. package/demos/basic.html +38 -0
  10. package/demos/baskara.html +31 -0
  11. package/demos/bezier.html +35 -0
  12. package/demos/beziersignature.html +29 -0
  13. package/demos/blackhole.html +28 -0
  14. package/demos/blob.html +35 -0
  15. package/demos/demos.css +289 -0
  16. package/demos/easing.html +28 -0
  17. package/demos/events.html +195 -0
  18. package/demos/fluent.html +647 -0
  19. package/demos/fractals.html +36 -0
  20. package/demos/genart.html +26 -0
  21. package/demos/gendream.html +26 -0
  22. package/demos/group.html +36 -0
  23. package/demos/home.html +587 -0
  24. package/demos/index.html +364 -0
  25. package/demos/isometric.html +34 -0
  26. package/demos/js/animations.js +452 -0
  27. package/demos/js/basic.js +204 -0
  28. package/demos/js/baskara.js +751 -0
  29. package/demos/js/bezier.js +692 -0
  30. package/demos/js/beziersignature.js +241 -0
  31. package/demos/js/blackhole/accretiondisk.obj.js +379 -0
  32. package/demos/js/blackhole/blackhole.obj.js +318 -0
  33. package/demos/js/blackhole/index.js +409 -0
  34. package/demos/js/blackhole/particle.js +56 -0
  35. package/demos/js/blackhole/starfield.obj.js +218 -0
  36. package/demos/js/blob.js +2263 -0
  37. package/demos/js/easing.js +477 -0
  38. package/demos/js/fluent.js +183 -0
  39. package/demos/js/fractals.js +931 -0
  40. package/demos/js/fractalworker.js +93 -0
  41. package/demos/js/genart.js +268 -0
  42. package/demos/js/gendream.js +209 -0
  43. package/demos/js/group.js +140 -0
  44. package/demos/js/info-toggle.js +25 -0
  45. package/demos/js/isometric.js +863 -0
  46. package/demos/js/kerr.js +1556 -0
  47. package/demos/js/lavalamp.js +590 -0
  48. package/demos/js/layout.js +354 -0
  49. package/demos/js/mondrian.js +285 -0
  50. package/demos/js/opacity.js +275 -0
  51. package/demos/js/painter.js +484 -0
  52. package/demos/js/particles-showcase.js +514 -0
  53. package/demos/js/particles.js +299 -0
  54. package/demos/js/patterns.js +397 -0
  55. package/demos/js/penrose/artifact.js +69 -0
  56. package/demos/js/penrose/blackhole.js +121 -0
  57. package/demos/js/penrose/constants.js +73 -0
  58. package/demos/js/penrose/game.js +943 -0
  59. package/demos/js/penrose/lore.js +278 -0
  60. package/demos/js/penrose/penrosescene.js +892 -0
  61. package/demos/js/penrose/ship.js +216 -0
  62. package/demos/js/penrose/sounds.js +211 -0
  63. package/demos/js/penrose/voidparticle.js +55 -0
  64. package/demos/js/penrose/voidscene.js +258 -0
  65. package/demos/js/penrose/voidship.js +144 -0
  66. package/demos/js/penrose/wormhole.js +46 -0
  67. package/demos/js/pipeline.js +555 -0
  68. package/demos/js/scene.js +304 -0
  69. package/demos/js/scenes.js +320 -0
  70. package/demos/js/schrodinger.js +410 -0
  71. package/demos/js/schwarzschild.js +1023 -0
  72. package/demos/js/shapes.js +628 -0
  73. package/demos/js/space/alien.js +171 -0
  74. package/demos/js/space/boom.js +98 -0
  75. package/demos/js/space/boss.js +353 -0
  76. package/demos/js/space/buff.js +73 -0
  77. package/demos/js/space/bullet.js +102 -0
  78. package/demos/js/space/constants.js +85 -0
  79. package/demos/js/space/game.js +1884 -0
  80. package/demos/js/space/hud.js +112 -0
  81. package/demos/js/space/laserbeam.js +179 -0
  82. package/demos/js/space/lightning.js +277 -0
  83. package/demos/js/space/minion.js +192 -0
  84. package/demos/js/space/missile.js +212 -0
  85. package/demos/js/space/player.js +430 -0
  86. package/demos/js/space/powerup.js +90 -0
  87. package/demos/js/space/starfield.js +58 -0
  88. package/demos/js/space/starpower.js +90 -0
  89. package/demos/js/spacetime.js +559 -0
  90. package/demos/js/svgtween.js +204 -0
  91. package/demos/js/tde/accretiondisk.js +418 -0
  92. package/demos/js/tde/blackhole.js +219 -0
  93. package/demos/js/tde/blackholescene.js +209 -0
  94. package/demos/js/tde/config.js +59 -0
  95. package/demos/js/tde/index.js +695 -0
  96. package/demos/js/tde/jets.js +290 -0
  97. package/demos/js/tde/lensedstarfield.js +147 -0
  98. package/demos/js/tde/tdestar.js +317 -0
  99. package/demos/js/tde/tidalstream.js +356 -0
  100. package/demos/js/tde_old/blackhole.obj.js +354 -0
  101. package/demos/js/tde_old/debris.obj.js +791 -0
  102. package/demos/js/tde_old/flare.obj.js +239 -0
  103. package/demos/js/tde_old/index.js +448 -0
  104. package/demos/js/tde_old/star.obj.js +812 -0
  105. package/demos/js/tiles.js +312 -0
  106. package/demos/js/tweendemo.js +79 -0
  107. package/demos/js/visibility.js +102 -0
  108. package/demos/kerr.html +28 -0
  109. package/demos/lavalamp.html +27 -0
  110. package/demos/layouts.html +37 -0
  111. package/demos/logo.svg +4 -0
  112. package/demos/loop.html +84 -0
  113. package/demos/mondrian.html +32 -0
  114. package/demos/og_image.png +0 -0
  115. package/demos/opacity.html +36 -0
  116. package/demos/painter.html +39 -0
  117. package/demos/particles-showcase.html +28 -0
  118. package/demos/particles.html +24 -0
  119. package/demos/patterns.html +33 -0
  120. package/demos/penrose-game.html +31 -0
  121. package/demos/pipeline.html +737 -0
  122. package/demos/scene.html +33 -0
  123. package/demos/scenes.html +96 -0
  124. package/demos/schrodinger.html +27 -0
  125. package/demos/schwarzschild.html +27 -0
  126. package/demos/shapes.html +16 -0
  127. package/demos/space.html +85 -0
  128. package/demos/spacetime.html +27 -0
  129. package/demos/svgtween.html +29 -0
  130. package/demos/tde.html +28 -0
  131. package/demos/tiles.html +28 -0
  132. package/demos/transforms.html +400 -0
  133. package/demos/tween.html +45 -0
  134. package/demos/visibility.html +33 -0
  135. package/disk_example.png +0 -0
  136. package/docs/README.md +222 -0
  137. package/docs/concepts/architecture-overview.md +204 -0
  138. package/docs/concepts/lifecycle.md +255 -0
  139. package/docs/concepts/rendering-pipeline.md +279 -0
  140. package/docs/concepts/tde-zorder.md +106 -0
  141. package/docs/concepts/two-layer-architecture.md +229 -0
  142. package/docs/getting-started/first-game.md +354 -0
  143. package/docs/getting-started/hello-world.md +269 -0
  144. package/docs/getting-started/installation.md +157 -0
  145. package/docs/modules/collision/README.md +453 -0
  146. package/docs/modules/fluent/README.md +1075 -0
  147. package/docs/modules/game/README.md +303 -0
  148. package/docs/modules/isometric-camera.md +210 -0
  149. package/docs/modules/isometric.md +275 -0
  150. package/docs/modules/painter/README.md +328 -0
  151. package/docs/modules/particle/README.md +559 -0
  152. package/docs/modules/shapes/README.md +221 -0
  153. package/docs/modules/shapes/base/euclidian.md +123 -0
  154. package/docs/modules/shapes/base/geometry2d.md +204 -0
  155. package/docs/modules/shapes/base/renderable.md +215 -0
  156. package/docs/modules/shapes/base/shape.md +262 -0
  157. package/docs/modules/shapes/base/transformable.md +243 -0
  158. package/docs/modules/shapes/hierarchy.md +218 -0
  159. package/docs/modules/state/README.md +577 -0
  160. package/docs/modules/util/README.md +99 -0
  161. package/docs/modules/util/camera3d.md +412 -0
  162. package/docs/modules/util/scene3d.md +395 -0
  163. package/index.html +17 -0
  164. package/jsdoc.json +50 -0
  165. package/package.json +55 -0
  166. package/readme.md +599 -0
  167. package/scripts/build-demo.js +69 -0
  168. package/scripts/bundle4llm.js +276 -0
  169. package/scripts/clearconsole.js +48 -0
  170. package/src/collision/collision-system.js +332 -0
  171. package/src/collision/collision.js +303 -0
  172. package/src/collision/index.js +10 -0
  173. package/src/fluent/fluent-game.js +430 -0
  174. package/src/fluent/fluent-go.js +1060 -0
  175. package/src/fluent/fluent-layer.js +152 -0
  176. package/src/fluent/fluent-scene.js +291 -0
  177. package/src/fluent/index.js +98 -0
  178. package/src/fluent/sketch.js +380 -0
  179. package/src/game/game.js +467 -0
  180. package/src/game/index.js +49 -0
  181. package/src/game/objects/go.js +220 -0
  182. package/src/game/objects/imagego.js +30 -0
  183. package/src/game/objects/index.js +54 -0
  184. package/src/game/objects/isometric-scene.js +260 -0
  185. package/src/game/objects/layoutscene.js +549 -0
  186. package/src/game/objects/scene.js +175 -0
  187. package/src/game/objects/scene3d.js +118 -0
  188. package/src/game/objects/text.js +221 -0
  189. package/src/game/objects/wrapper.js +232 -0
  190. package/src/game/pipeline.js +243 -0
  191. package/src/game/ui/button.js +396 -0
  192. package/src/game/ui/cursor.js +93 -0
  193. package/src/game/ui/fps.js +91 -0
  194. package/src/game/ui/index.js +5 -0
  195. package/src/game/ui/togglebutton.js +93 -0
  196. package/src/game/ui/tooltip.js +249 -0
  197. package/src/index.js +25 -0
  198. package/src/io/events.js +20 -0
  199. package/src/io/index.js +86 -0
  200. package/src/io/input.js +70 -0
  201. package/src/io/keys.js +152 -0
  202. package/src/io/mouse.js +61 -0
  203. package/src/io/touch.js +39 -0
  204. package/src/logger/debugtab.js +138 -0
  205. package/src/logger/index.js +3 -0
  206. package/src/logger/loggable.js +47 -0
  207. package/src/logger/logger.js +113 -0
  208. package/src/math/complex.js +37 -0
  209. package/src/math/constants.js +1 -0
  210. package/src/math/fractal.js +1271 -0
  211. package/src/math/gr.js +201 -0
  212. package/src/math/heat.js +202 -0
  213. package/src/math/index.js +12 -0
  214. package/src/math/noise.js +433 -0
  215. package/src/math/orbital.js +191 -0
  216. package/src/math/patterns.js +1339 -0
  217. package/src/math/penrose.js +259 -0
  218. package/src/math/quantum.js +115 -0
  219. package/src/math/random.js +195 -0
  220. package/src/math/tensor.js +1009 -0
  221. package/src/mixins/anchor.js +131 -0
  222. package/src/mixins/draggable.js +72 -0
  223. package/src/mixins/index.js +2 -0
  224. package/src/motion/bezier.js +132 -0
  225. package/src/motion/bounce.js +58 -0
  226. package/src/motion/easing.js +349 -0
  227. package/src/motion/float.js +130 -0
  228. package/src/motion/follow.js +125 -0
  229. package/src/motion/hop.js +52 -0
  230. package/src/motion/index.js +82 -0
  231. package/src/motion/motion.js +1124 -0
  232. package/src/motion/orbit.js +49 -0
  233. package/src/motion/oscillate.js +39 -0
  234. package/src/motion/parabolic.js +141 -0
  235. package/src/motion/patrol.js +147 -0
  236. package/src/motion/pendulum.js +48 -0
  237. package/src/motion/pulse.js +88 -0
  238. package/src/motion/shake.js +83 -0
  239. package/src/motion/spiral.js +144 -0
  240. package/src/motion/spring.js +150 -0
  241. package/src/motion/swing.js +47 -0
  242. package/src/motion/tween.js +92 -0
  243. package/src/motion/tweenetik.js +139 -0
  244. package/src/motion/waypoint.js +210 -0
  245. package/src/painter/index.js +8 -0
  246. package/src/painter/painter.colors.js +331 -0
  247. package/src/painter/painter.effects.js +230 -0
  248. package/src/painter/painter.img.js +229 -0
  249. package/src/painter/painter.js +295 -0
  250. package/src/painter/painter.lines.js +189 -0
  251. package/src/painter/painter.opacity.js +41 -0
  252. package/src/painter/painter.shapes.js +277 -0
  253. package/src/painter/painter.text.js +273 -0
  254. package/src/particle/emitter.js +124 -0
  255. package/src/particle/index.js +11 -0
  256. package/src/particle/particle-system.js +322 -0
  257. package/src/particle/particle.js +71 -0
  258. package/src/particle/updaters.js +170 -0
  259. package/src/shapes/arc.js +43 -0
  260. package/src/shapes/arrow.js +33 -0
  261. package/src/shapes/bezier.js +42 -0
  262. package/src/shapes/circle.js +62 -0
  263. package/src/shapes/clouds.js +56 -0
  264. package/src/shapes/cone.js +219 -0
  265. package/src/shapes/cross.js +70 -0
  266. package/src/shapes/cube.js +244 -0
  267. package/src/shapes/cylinder.js +254 -0
  268. package/src/shapes/diamond.js +48 -0
  269. package/src/shapes/euclidian.js +111 -0
  270. package/src/shapes/figure.js +115 -0
  271. package/src/shapes/geometry.js +220 -0
  272. package/src/shapes/group.js +375 -0
  273. package/src/shapes/heart.js +42 -0
  274. package/src/shapes/hexagon.js +26 -0
  275. package/src/shapes/image.js +192 -0
  276. package/src/shapes/index.js +111 -0
  277. package/src/shapes/line.js +29 -0
  278. package/src/shapes/pattern.js +90 -0
  279. package/src/shapes/pin.js +44 -0
  280. package/src/shapes/poly.js +31 -0
  281. package/src/shapes/prism.js +226 -0
  282. package/src/shapes/rect.js +35 -0
  283. package/src/shapes/renderable.js +333 -0
  284. package/src/shapes/ring.js +26 -0
  285. package/src/shapes/roundrect.js +95 -0
  286. package/src/shapes/shape.js +117 -0
  287. package/src/shapes/slice.js +26 -0
  288. package/src/shapes/sphere.js +314 -0
  289. package/src/shapes/sphere3d.js +537 -0
  290. package/src/shapes/square.js +15 -0
  291. package/src/shapes/star.js +99 -0
  292. package/src/shapes/svg.js +408 -0
  293. package/src/shapes/text.js +553 -0
  294. package/src/shapes/traceable.js +83 -0
  295. package/src/shapes/transform.js +357 -0
  296. package/src/shapes/transformable.js +172 -0
  297. package/src/shapes/triangle.js +26 -0
  298. package/src/sound/index.js +17 -0
  299. package/src/sound/sound.js +473 -0
  300. package/src/sound/synth.analyzer.js +149 -0
  301. package/src/sound/synth.effects.js +207 -0
  302. package/src/sound/synth.envelope.js +59 -0
  303. package/src/sound/synth.js +229 -0
  304. package/src/sound/synth.musical.js +160 -0
  305. package/src/sound/synth.noise.js +85 -0
  306. package/src/sound/synth.oscillators.js +293 -0
  307. package/src/state/index.js +10 -0
  308. package/src/state/state-machine.js +371 -0
  309. package/src/util/camera3d.js +438 -0
  310. package/src/util/index.js +6 -0
  311. package/src/util/isometric-camera.js +235 -0
  312. package/src/util/layout.js +317 -0
  313. package/src/util/position.js +147 -0
  314. package/src/util/tasks.js +47 -0
  315. package/src/util/zindex.js +287 -0
  316. package/src/webgl/index.js +9 -0
  317. package/src/webgl/shaders/sphere-shaders.js +994 -0
  318. package/src/webgl/webgl-renderer.js +388 -0
  319. package/tde.png +0 -0
  320. package/test/math/orbital.test.js +61 -0
  321. package/test/math/tensor.test.js +114 -0
  322. package/test/particle/emitter.test.js +204 -0
  323. package/test/particle/particle-system.test.js +310 -0
  324. package/test/particle/particle.test.js +116 -0
  325. package/test/particle/updaters.test.js +386 -0
  326. package/test/setup.js +120 -0
  327. package/test/shapes/euclidian.test.js +44 -0
  328. package/test/shapes/geometry.test.js +86 -0
  329. package/test/shapes/group.test.js +86 -0
  330. package/test/shapes/rectangle.test.js +64 -0
  331. package/test/shapes/transform.test.js +379 -0
  332. package/test/util/camera3d.test.js +428 -0
  333. package/test/util/scene3d.test.js +352 -0
  334. package/types/collision.d.ts +249 -0
  335. package/types/common.d.ts +155 -0
  336. package/types/game.d.ts +497 -0
  337. package/types/index.d.ts +309 -0
  338. package/types/io.d.ts +188 -0
  339. package/types/logger.d.ts +127 -0
  340. package/types/math.d.ts +268 -0
  341. package/types/mixins.d.ts +92 -0
  342. package/types/motion.d.ts +678 -0
  343. package/types/painter.d.ts +378 -0
  344. package/types/shapes.d.ts +864 -0
  345. package/types/sound.d.ts +672 -0
  346. package/types/state.d.ts +251 -0
  347. package/types/util.d.ts +253 -0
  348. package/vite.config.js +50 -0
  349. package/vitest.config.js +13 -0
@@ -0,0 +1,371 @@
1
+ /**
2
+ * StateMachine - Flexible state management for game objects
3
+ *
4
+ * Manages states with enter/update/exit lifecycle callbacks.
5
+ * Supports timed transitions, sequential phases, and conditional triggers.
6
+ *
7
+ * @example
8
+ * // Basic usage
9
+ * const fsm = new StateMachine({
10
+ * initial: 'idle',
11
+ * states: {
12
+ * idle: {
13
+ * enter: () => console.log('Now idle'),
14
+ * update: (dt) => { /* per-frame logic *\/ },
15
+ * exit: () => console.log('Leaving idle')
16
+ * },
17
+ * walking: {
18
+ * enter: () => player.startWalkAnimation(),
19
+ * update: (dt) => player.move(dt)
20
+ * }
21
+ * }
22
+ * });
23
+ *
24
+ * // Change state
25
+ * fsm.setState('walking');
26
+ *
27
+ * // Update each frame
28
+ * fsm.update(dt);
29
+ *
30
+ * @example
31
+ * // Timed phase sequence (like laser charging)
32
+ * const laser = new StateMachine({
33
+ * initial: 'warning',
34
+ * states: {
35
+ * warning: { duration: 0.3, next: 'charging' },
36
+ * charging: { duration: 0.2, next: 'active' },
37
+ * active: {
38
+ * duration: 0.4,
39
+ * next: 'fade',
40
+ * enter: () => { laser.canDamage = true; }
41
+ * },
42
+ * fade: {
43
+ * duration: 0.2,
44
+ * exit: () => { laser.destroy(); }
45
+ * }
46
+ * }
47
+ * });
48
+ */
49
+ export class StateMachine {
50
+ /**
51
+ * Create a new state machine
52
+ *
53
+ * @param {Object} config - Configuration object
54
+ * @param {string} config.initial - Initial state name
55
+ * @param {Object} config.states - State definitions
56
+ * @param {Object} [config.context] - Context object passed to callbacks (usually `this`)
57
+ */
58
+ constructor(config = {}) {
59
+ /** @type {Object} State definitions */
60
+ this.states = config.states || {};
61
+
62
+ /** @type {string|null} Current state name */
63
+ this.currentState = null;
64
+
65
+ /** @type {string|null} Previous state name */
66
+ this.previousState = null;
67
+
68
+ /** @type {number} Time spent in current state */
69
+ this.stateTime = 0;
70
+
71
+ /** @type {Object} Context passed to state callbacks */
72
+ this.context = config.context || null;
73
+
74
+ /** @type {boolean} Whether the state machine is paused */
75
+ this.paused = false;
76
+
77
+ /** @type {Function|null} Global state change callback */
78
+ this.onStateChange = null;
79
+
80
+ // Enter initial state
81
+ if (config.initial) {
82
+ this.setState(config.initial);
83
+ }
84
+ }
85
+
86
+ /**
87
+ * Get the current state name
88
+ * @returns {string|null}
89
+ */
90
+ get state() {
91
+ return this.currentState;
92
+ }
93
+
94
+ /**
95
+ * Get the current state definition
96
+ * @returns {Object|null}
97
+ */
98
+ get currentStateConfig() {
99
+ return this.currentState ? this.states[this.currentState] : null;
100
+ }
101
+
102
+ /**
103
+ * Check if currently in a specific state
104
+ *
105
+ * @param {string} stateName - State to check
106
+ * @returns {boolean}
107
+ */
108
+ is(stateName) {
109
+ return this.currentState === stateName;
110
+ }
111
+
112
+ /**
113
+ * Check if currently in any of the given states
114
+ *
115
+ * @param {...string} stateNames - States to check
116
+ * @returns {boolean}
117
+ */
118
+ isAny(...stateNames) {
119
+ return stateNames.includes(this.currentState);
120
+ }
121
+
122
+ /**
123
+ * Add or update a state definition
124
+ *
125
+ * @param {string} name - State name
126
+ * @param {Object} config - State configuration
127
+ * @returns {StateMachine} this for chaining
128
+ */
129
+ addState(name, config) {
130
+ this.states[name] = config;
131
+ return this;
132
+ }
133
+
134
+ /**
135
+ * Remove a state definition
136
+ *
137
+ * @param {string} name - State name to remove
138
+ * @returns {StateMachine} this for chaining
139
+ */
140
+ removeState(name) {
141
+ delete this.states[name];
142
+ return this;
143
+ }
144
+
145
+ /**
146
+ * Transition to a new state
147
+ *
148
+ * @param {string} newState - State to transition to
149
+ * @param {Object} [data] - Optional data passed to enter callback
150
+ * @returns {boolean} True if transition occurred
151
+ */
152
+ setState(newState, data) {
153
+ if (!this.states[newState]) {
154
+ console.warn(`StateMachine: Unknown state '${newState}'`);
155
+ return false;
156
+ }
157
+
158
+ // Exit current state
159
+ if (this.currentState) {
160
+ const currentConfig = this.states[this.currentState];
161
+ if (currentConfig?.exit) {
162
+ this._call(currentConfig.exit, data);
163
+ }
164
+ }
165
+
166
+ // Update state tracking
167
+ this.previousState = this.currentState;
168
+ this.currentState = newState;
169
+ this.stateTime = 0;
170
+
171
+ // Enter new state
172
+ const newConfig = this.states[newState];
173
+ if (newConfig?.enter) {
174
+ this._call(newConfig.enter, data);
175
+ }
176
+
177
+ // Global callback
178
+ if (this.onStateChange) {
179
+ this.onStateChange(newState, this.previousState, data);
180
+ }
181
+
182
+ return true;
183
+ }
184
+
185
+ /**
186
+ * Attempt to transition based on a trigger/event
187
+ *
188
+ * @param {string} trigger - Trigger name to check against state transitions
189
+ * @param {Object} [data] - Optional data passed to callbacks
190
+ * @returns {boolean} True if a transition occurred
191
+ */
192
+ trigger(trigger, data) {
193
+ const config = this.currentStateConfig;
194
+ if (!config?.on) return false;
195
+
196
+ const transition = config.on[trigger];
197
+ if (!transition) return false;
198
+
199
+ // Transition can be a string (state name) or object { target, guard, action }
200
+ if (typeof transition === "string") {
201
+ return this.setState(transition, data);
202
+ }
203
+
204
+ // Check guard condition
205
+ if (transition.guard && !this._call(transition.guard, data)) {
206
+ return false;
207
+ }
208
+
209
+ // Run action before transition
210
+ if (transition.action) {
211
+ this._call(transition.action, data);
212
+ }
213
+
214
+ // Transition to target state
215
+ if (transition.target) {
216
+ return this.setState(transition.target, data);
217
+ }
218
+
219
+ return false;
220
+ }
221
+
222
+ /**
223
+ * Update the state machine (call each frame)
224
+ *
225
+ * @param {number} dt - Delta time in seconds
226
+ */
227
+ update(dt) {
228
+ if (this.paused || !this.currentState) return;
229
+
230
+ this.stateTime += dt;
231
+
232
+ const config = this.states[this.currentState];
233
+ if (!config) return;
234
+
235
+ // Run update callback
236
+ if (config.update) {
237
+ this._call(config.update, dt);
238
+ }
239
+
240
+ // Check for timed auto-transition
241
+ if (config.duration !== undefined && this.stateTime >= config.duration) {
242
+ if (config.next) {
243
+ this.setState(config.next);
244
+ } else if (config.onComplete) {
245
+ this._call(config.onComplete);
246
+ }
247
+ }
248
+ }
249
+
250
+ /**
251
+ * Get normalized progress through current state (0-1)
252
+ * Only meaningful for states with a duration
253
+ *
254
+ * @returns {number} Progress from 0 to 1
255
+ */
256
+ get progress() {
257
+ const config = this.currentStateConfig;
258
+ if (!config?.duration) return 0;
259
+ return Math.min(1, this.stateTime / config.duration);
260
+ }
261
+
262
+ /**
263
+ * Get remaining time in current state
264
+ * Only meaningful for states with a duration
265
+ *
266
+ * @returns {number} Remaining time in seconds
267
+ */
268
+ get remaining() {
269
+ const config = this.currentStateConfig;
270
+ if (!config?.duration) return Infinity;
271
+ return Math.max(0, config.duration - this.stateTime);
272
+ }
273
+
274
+ /**
275
+ * Check if current state is a timed state
276
+ *
277
+ * @returns {boolean}
278
+ */
279
+ get isTimed() {
280
+ return this.currentStateConfig?.duration !== undefined;
281
+ }
282
+
283
+ /**
284
+ * Pause the state machine (stops update processing)
285
+ */
286
+ pause() {
287
+ this.paused = true;
288
+ }
289
+
290
+ /**
291
+ * Resume the state machine
292
+ */
293
+ resume() {
294
+ this.paused = false;
295
+ }
296
+
297
+ /**
298
+ * Reset to initial state or specific state
299
+ *
300
+ * @param {string} [state] - State to reset to (defaults to first defined state)
301
+ */
302
+ reset(state) {
303
+ this.stateTime = 0;
304
+ this.previousState = null;
305
+
306
+ if (state) {
307
+ this.setState(state);
308
+ } else {
309
+ // Find first state key
310
+ const firstState = Object.keys(this.states)[0];
311
+ if (firstState) {
312
+ this.setState(firstState);
313
+ }
314
+ }
315
+ }
316
+
317
+ /**
318
+ * Call a callback with context binding
319
+ * @private
320
+ */
321
+ _call(fn, ...args) {
322
+ if (typeof fn === "function") {
323
+ return this.context ? fn.call(this.context, ...args) : fn(...args);
324
+ }
325
+ return undefined;
326
+ }
327
+
328
+ /**
329
+ * Create a state machine from a simple phase sequence
330
+ * Convenience method for common "phase 1 → phase 2 → phase 3" patterns
331
+ *
332
+ * @param {Array<Object>} phases - Array of { name, duration, enter?, update?, exit? }
333
+ * @param {Object} [options] - Additional options
334
+ * @param {boolean} [options.loop=false] - Whether to loop back to first phase
335
+ * @param {Function} [options.onComplete] - Called when sequence completes (if not looping)
336
+ * @returns {StateMachine}
337
+ *
338
+ * @example
339
+ * const lightning = StateMachine.fromSequence([
340
+ * { name: 'tracing', duration: 0.4, enter: () => startTrace() },
341
+ * { name: 'active', duration: 0.3, enter: () => enableDamage() },
342
+ * { name: 'fade', duration: 0.2, exit: () => destroy() }
343
+ * ]);
344
+ */
345
+ static fromSequence(phases, options = {}) {
346
+ const states = {};
347
+
348
+ for (let i = 0; i < phases.length; i++) {
349
+ const phase = phases[i];
350
+ const isLast = i === phases.length - 1;
351
+ const nextPhase = isLast
352
+ ? (options.loop ? phases[0].name : null)
353
+ : phases[i + 1].name;
354
+
355
+ states[phase.name] = {
356
+ duration: phase.duration,
357
+ next: nextPhase,
358
+ enter: phase.enter,
359
+ update: phase.update,
360
+ exit: phase.exit,
361
+ onComplete: isLast && !options.loop ? options.onComplete : undefined,
362
+ };
363
+ }
364
+
365
+ return new StateMachine({
366
+ initial: phases[0]?.name,
367
+ states,
368
+ context: options.context,
369
+ });
370
+ }
371
+ }