@zylem/behaviors 0.4.1 → 0.5.2

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 (148) hide show
  1. package/README.md +13 -1
  2. package/dist/{behavior-system-y8SGrGWy.d.ts → behavior-system-CldqNUgr.d.ts} +334 -19
  3. package/dist/{chunk-GFARAJV2.js → chunk-2PAHQZZD.js} +44 -73
  4. package/dist/chunk-2PAHQZZD.js.map +1 -0
  5. package/dist/chunk-4CEW64HO.js +210 -0
  6. package/dist/chunk-4CEW64HO.js.map +1 -0
  7. package/dist/chunk-4LLK2FJI.js +344 -0
  8. package/dist/chunk-4LLK2FJI.js.map +1 -0
  9. package/dist/chunk-4MZ57HPD.js +323 -0
  10. package/dist/chunk-4MZ57HPD.js.map +1 -0
  11. package/dist/chunk-52FJIDGL.js +30 -0
  12. package/dist/chunk-52FJIDGL.js.map +1 -0
  13. package/dist/{chunk-UYFOLI2X.js → chunk-5A5AYP2C.js} +1 -1
  14. package/dist/{chunk-UYFOLI2X.js.map → chunk-5A5AYP2C.js.map} +1 -1
  15. package/dist/chunk-5WRI5ZAA.js +31 -0
  16. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  17. package/dist/{chunk-ZRQWSURL.js → chunk-5XPPLTC7.js} +2 -2
  18. package/dist/chunk-5XPPLTC7.js.map +1 -0
  19. package/dist/{chunk-VMQHNJJI.js → chunk-7DGXUAPV.js} +17 -23
  20. package/dist/chunk-7DGXUAPV.js.map +1 -0
  21. package/dist/{chunk-5SM5SH5O.js → chunk-7JLWZXPA.js} +2 -2
  22. package/dist/{chunk-QDCJLJUV.js → chunk-7W3KY55G.js} +22 -13
  23. package/dist/chunk-7W3KY55G.js.map +1 -0
  24. package/dist/chunk-AS3HR7AS.js +188 -0
  25. package/dist/chunk-AS3HR7AS.js.map +1 -0
  26. package/dist/chunk-BAXJ7XHJ.js +155 -0
  27. package/dist/chunk-BAXJ7XHJ.js.map +1 -0
  28. package/dist/chunk-DZ4LLQON.js +867 -0
  29. package/dist/chunk-DZ4LLQON.js.map +1 -0
  30. package/dist/{chunk-4L36OPY2.js → chunk-G2KUXVU6.js} +81 -135
  31. package/dist/chunk-G2KUXVU6.js.map +1 -0
  32. package/dist/{chunk-BXP4K7RF.js → chunk-JVADRLWX.js} +57 -186
  33. package/dist/chunk-JVADRLWX.js.map +1 -0
  34. package/dist/{chunk-ZA43LPRM.js → chunk-KIYUUSPK.js} +2 -2
  35. package/dist/chunk-LZIHSJZR.js +191 -0
  36. package/dist/chunk-LZIHSJZR.js.map +1 -0
  37. package/dist/{chunk-WWVD5DR5.js → chunk-M3QDKKDF.js} +2 -8
  38. package/dist/chunk-M3QDKKDF.js.map +1 -0
  39. package/dist/{chunk-FTDZAKZW.js → chunk-NQPYMUDF.js} +7 -5
  40. package/dist/{chunk-FTDZAKZW.js.map → chunk-NQPYMUDF.js.map} +1 -1
  41. package/dist/{chunk-XP3ASVGJ.js → chunk-NW4SE5XW.js} +2 -2
  42. package/dist/{chunk-KDY5N6J4.js → chunk-OPZ2LVS2.js} +1 -22
  43. package/dist/{chunk-KDY5N6J4.js.map → chunk-OPZ2LVS2.js.map} +1 -1
  44. package/dist/{chunk-5XZWTFGF.js → chunk-PLIXVF7B.js} +3 -3
  45. package/dist/chunk-PLIXVF7B.js.map +1 -0
  46. package/dist/{chunk-27UPCP5B.js → chunk-QS3VXWVO.js} +22 -13
  47. package/dist/chunk-QS3VXWVO.js.map +1 -0
  48. package/dist/{chunk-Y7YIAWPW.js → chunk-SGHIGTVZ.js} +1 -11
  49. package/dist/{chunk-Y7YIAWPW.js.map → chunk-SGHIGTVZ.js.map} +1 -1
  50. package/dist/{chunk-UL6Q5HDP.js → chunk-TBUKJKY6.js} +3 -25
  51. package/dist/chunk-TBUKJKY6.js.map +1 -0
  52. package/dist/{chunk-Z72F2ENM.js → chunk-WU6XXL3D.js} +17 -26
  53. package/dist/chunk-WU6XXL3D.js.map +1 -0
  54. package/dist/{chunk-A4CA35DQ.js → chunk-Y4PHT2HG.js} +25 -45
  55. package/dist/chunk-Y4PHT2HG.js.map +1 -0
  56. package/dist/components-BmievOxF.d.ts +30 -0
  57. package/dist/cooldown.d.ts +2 -2
  58. package/dist/cooldown.js +2 -1
  59. package/dist/coordinators/boundary-ricochet-3d.d.ts +4 -4
  60. package/dist/coordinators/boundary-ricochet-3d.js +1 -0
  61. package/dist/coordinators/boundary-ricochet.d.ts +4 -4
  62. package/dist/coordinators/boundary-ricochet.js +3 -2
  63. package/dist/coordinators/first-person-shooter.js +1 -0
  64. package/dist/coordinators/multidirectional-space-shooter.d.ts +3 -3
  65. package/dist/coordinators/multidirectional-space-shooter.js +5 -3
  66. package/dist/coordinators/top-down-shooter.d.ts +3 -3
  67. package/dist/coordinators/top-down-shooter.js +5 -3
  68. package/dist/core.d.ts +298 -8
  69. package/dist/core.js +502 -14
  70. package/dist/core.js.map +1 -1
  71. package/dist/destructible-3d.d.ts +32 -4
  72. package/dist/destructible-3d.js +2 -1
  73. package/dist/destructible-prebake.js +1 -0
  74. package/dist/{entity-types-CmtQ-IBp.d.ts → entity-types-DhfsZbSK.d.ts} +5 -3
  75. package/dist/first-person.d.ts +27 -38
  76. package/dist/first-person.js +8 -8
  77. package/dist/index.d.ts +14 -15
  78. package/dist/index.js +38 -53
  79. package/dist/jumper-2d.d.ts +3 -17
  80. package/dist/jumper-2d.js +4 -9
  81. package/dist/jumper-3d.d.ts +5 -39
  82. package/dist/jumper-3d.js +5 -10
  83. package/dist/{moveable-Cl9h-_w7.d.ts → moveable-rdIvWiak.d.ts} +21 -4
  84. package/dist/particle-emitter/presets.js +1 -0
  85. package/dist/particle-emitter.d.ts +3 -3
  86. package/dist/particle-emitter.js +3 -2
  87. package/dist/platformer-2d.d.ts +21 -82
  88. package/dist/platformer-2d.js +4 -5
  89. package/dist/platformer-3d.d.ts +21 -78
  90. package/dist/platformer-3d.js +4 -5
  91. package/dist/ricochet-2d.d.ts +3 -3
  92. package/dist/ricochet-2d.js +4 -3
  93. package/dist/ricochet-3d.d.ts +3 -3
  94. package/dist/ricochet-3d.js +4 -3
  95. package/dist/runtime-2d.d.ts +2 -2
  96. package/dist/runtime-2d.js +1 -0
  97. package/dist/runtime-2d.js.map +1 -1
  98. package/dist/screen-visibility.d.ts +2 -2
  99. package/dist/screen-visibility.js +1 -0
  100. package/dist/screen-wrap.d.ts +2 -2
  101. package/dist/screen-wrap.js +4 -3
  102. package/dist/shooter-2d.d.ts +3 -3
  103. package/dist/shooter-2d.js +4 -3
  104. package/dist/thruster.d.ts +5 -34
  105. package/dist/thruster.js +4 -6
  106. package/dist/top-down-movement.d.ts +9 -27
  107. package/dist/top-down-movement.js +6 -6
  108. package/dist/{vector-Bm65-FOG.d.ts → vector-DjhLUrak.d.ts} +2 -4
  109. package/dist/world-boundary-2d.d.ts +2 -2
  110. package/dist/world-boundary-2d.js +5 -4
  111. package/dist/world-boundary-3d.d.ts +2 -2
  112. package/dist/world-boundary-3d.js +4 -3
  113. package/package.json +4 -5
  114. package/dist/chunk-27UPCP5B.js.map +0 -1
  115. package/dist/chunk-4L36OPY2.js.map +0 -1
  116. package/dist/chunk-4URF65BR.js +0 -44
  117. package/dist/chunk-4URF65BR.js.map +0 -1
  118. package/dist/chunk-5XZWTFGF.js.map +0 -1
  119. package/dist/chunk-67XLJ26X.js +0 -536
  120. package/dist/chunk-67XLJ26X.js.map +0 -1
  121. package/dist/chunk-75FJIUIG.js +0 -240
  122. package/dist/chunk-75FJIUIG.js.map +0 -1
  123. package/dist/chunk-A4CA35DQ.js.map +0 -1
  124. package/dist/chunk-BXP4K7RF.js.map +0 -1
  125. package/dist/chunk-F72WX4MH.js +0 -422
  126. package/dist/chunk-F72WX4MH.js.map +0 -1
  127. package/dist/chunk-GFARAJV2.js.map +0 -1
  128. package/dist/chunk-JXMF4NUI.js +0 -309
  129. package/dist/chunk-JXMF4NUI.js.map +0 -1
  130. package/dist/chunk-OTLSUYQQ.js +0 -29
  131. package/dist/chunk-OTLSUYQQ.js.map +0 -1
  132. package/dist/chunk-Q75LKHDF.js +0 -56
  133. package/dist/chunk-Q75LKHDF.js.map +0 -1
  134. package/dist/chunk-QDCJLJUV.js.map +0 -1
  135. package/dist/chunk-RDHSHEZM.js +0 -479
  136. package/dist/chunk-RDHSHEZM.js.map +0 -1
  137. package/dist/chunk-UIG7MWS2.js +0 -518
  138. package/dist/chunk-UIG7MWS2.js.map +0 -1
  139. package/dist/chunk-UL6Q5HDP.js.map +0 -1
  140. package/dist/chunk-VMQHNJJI.js.map +0 -1
  141. package/dist/chunk-WWVD5DR5.js.map +0 -1
  142. package/dist/chunk-Z72F2ENM.js.map +0 -1
  143. package/dist/chunk-ZRQWSURL.js.map +0 -1
  144. package/dist/components-BWG1GkZA.d.ts +0 -20
  145. package/dist/use-behavior-B85d5u7-.d.ts +0 -13
  146. /package/dist/{chunk-5SM5SH5O.js.map → chunk-7JLWZXPA.js.map} +0 -0
  147. /package/dist/{chunk-ZA43LPRM.js.map → chunk-KIYUUSPK.js.map} +0 -0
  148. /package/dist/{chunk-XP3ASVGJ.js.map → chunk-NW4SE5XW.js.map} +0 -0
@@ -2,14 +2,14 @@ import {
2
2
  computeBounds2DHits,
3
3
  constrainMovementToBounds2D,
4
4
  hasAnyBounds2DHit
5
- } from "./chunk-KDY5N6J4.js";
5
+ } from "./chunk-OPZ2LVS2.js";
6
6
  import {
7
7
  SyncStateMachine,
8
- t
9
- } from "./chunk-4URF65BR.js";
8
+ import_typescript_fsm
9
+ } from "./chunk-AS3HR7AS.js";
10
10
  import {
11
11
  StageBoundaryDim
12
- } from "./chunk-Q75LKHDF.js";
12
+ } from "./chunk-DZ4LLQON.js";
13
13
  import {
14
14
  defineBehavior
15
15
  } from "./chunk-EZNCCO4L.js";
@@ -45,11 +45,11 @@ var WorldBoundary2DFSM = class {
45
45
  this.machine = new SyncStateMachine(
46
46
  "inside" /* Inside */,
47
47
  [
48
- t("inside" /* Inside */, "touch-boundary" /* TouchBoundary */, "touching" /* Touching */),
49
- t("touching" /* Touching */, "enter-inside" /* EnterInside */, "inside" /* Inside */),
48
+ (0, import_typescript_fsm.t)("inside" /* Inside */, "touch-boundary" /* TouchBoundary */, "touching" /* Touching */),
49
+ (0, import_typescript_fsm.t)("touching" /* Touching */, "enter-inside" /* EnterInside */, "inside" /* Inside */),
50
50
  // Self transitions (no-ops)
51
- t("inside" /* Inside */, "enter-inside" /* EnterInside */, "inside" /* Inside */),
52
- t("touching" /* Touching */, "touch-boundary" /* TouchBoundary */, "touching" /* Touching */)
51
+ (0, import_typescript_fsm.t)("inside" /* Inside */, "enter-inside" /* EnterInside */, "inside" /* Inside */),
52
+ (0, import_typescript_fsm.t)("touching" /* Touching */, "touch-boundary" /* TouchBoundary */, "touching" /* Touching */)
53
53
  ]
54
54
  );
55
55
  }
@@ -148,21 +148,15 @@ var WorldBoundary2DSystem = class {
148
148
  boundaryRef.fsm = new WorldBoundary2DFSM();
149
149
  }
150
150
  const handle = gameEntity.runtimeHandle ?? -1;
151
- if (this.wasmStage && handle >= 0) {
152
- this.ensureWasmAttached(handle, options.boundaries);
153
- const query = this.wasmStage.queryWorldBoundary(handle);
154
- if (query) {
155
- boundaryRef.fsm.update(
156
- { x: query.clamped[0], y: query.clamped[1] },
157
- options.boundaries
158
- );
159
- continue;
160
- }
151
+ if (!this.wasmStage || handle < 0) continue;
152
+ this.ensureWasmAttached(handle, options.boundaries);
153
+ const query = this.wasmStage.queryWorldBoundary(handle);
154
+ if (query) {
155
+ boundaryRef.fsm.update(
156
+ { x: query.clamped[0], y: query.clamped[1] },
157
+ options.boundaries
158
+ );
161
159
  }
162
- if (!gameEntity.body) continue;
163
- const body = gameEntity.body;
164
- const pos = body.translation();
165
- boundaryRef.fsm.update({ x: pos.x, y: pos.y }, options.boundaries);
166
160
  }
167
161
  }
168
162
  ensureWasmAttached(handle, bounds) {
@@ -196,4 +190,4 @@ export {
196
190
  WorldBoundary2DFSM,
197
191
  WorldBoundary2DBehavior
198
192
  };
199
- //# sourceMappingURL=chunk-VMQHNJJI.js.map
193
+ //# sourceMappingURL=chunk-7DGXUAPV.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/world-boundary-2d/world-boundary-2d-fsm.ts","../src/lib/behaviors/world-boundary-2d/world-boundary-2d.descriptor.ts"],"sourcesContent":["/**\n * WorldBoundary2DFSM\n *\n * Minimal FSM + extended state to track which world boundaries were hit.\n *\n * Notes:\n * - \"Hit boundaries\" is inherently a *set* (can hit left+bottom in one frame),\n * so we store it as extended state (`lastHits`) rather than a single FSM state.\n * - The FSM state is still useful for coarse status like \"inside\" vs \"touching\".\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport {\n\tcomputeBounds2DHits,\n\tconstrainMovementToBounds2D,\n\thasAnyBounds2DHit,\n} from '../shared/bounds-2d';\n\nexport type WorldBoundary2DHit = 'top' | 'bottom' | 'left' | 'right';\nexport type WorldBoundary2DHits = Record<WorldBoundary2DHit, boolean>;\n\nexport interface WorldBoundary2DPosition {\n\tx: number;\n\ty: number;\n}\n\nexport interface WorldBoundary2DBounds {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n}\n\nexport enum WorldBoundary2DState {\n\tInside = 'inside',\n\tTouching = 'touching',\n}\n\nexport enum WorldBoundary2DEvent {\n\tEnterInside = 'enter-inside',\n\tTouchBoundary = 'touch-boundary',\n}\n\n/**\n * Compute which boundaries are being hit for a position and bounds.\n * This matches the semantics of the legacy `boundary2d` behavior:\n * - left hit if x <= left\n * - right hit if x >= right\n * - bottom hit if y <= bottom\n * - top hit if y >= top\n */\nexport function computeWorldBoundary2DHits(\n\tposition: WorldBoundary2DPosition,\n\tbounds: WorldBoundary2DBounds\n): WorldBoundary2DHits {\n\treturn computeBounds2DHits(position, {\n\t\tminX: bounds.left,\n\t\tmaxX: bounds.right,\n\t\tminY: bounds.bottom,\n\t\tmaxY: bounds.top,\n\t});\n}\n\nexport function hasAnyWorldBoundary2DHit(hits: WorldBoundary2DHits): boolean {\n\treturn hasAnyBounds2DHit(hits);\n}\n\n/**\n * FSM wrapper with \"extended state\" (lastHits / lastPosition).\n * Systems should call `update(...)` once per frame.\n */\nexport class WorldBoundary2DFSM {\n\tpublic readonly machine: SyncStateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>;\n\n\tprivate lastHits: WorldBoundary2DHits = { top: false, bottom: false, left: false, right: false };\n\tprivate lastPosition: WorldBoundary2DPosition | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>(\n\t\t\tWorldBoundary2DState.Inside,\n\t\t\t[\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): WorldBoundary2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed hits (always available after first update call).\n\t */\n\tgetLastHits(): WorldBoundary2DHits {\n\t\treturn this.lastHits;\n\t}\n\n\t/**\n\t * Returns adjusted movement values based on boundary hits.\n\t * If the entity is touching a boundary and trying to move further into it,\n\t * that axis component is zeroed out.\n\t *\n\t * @param moveX - The desired X movement\n\t * @param moveY - The desired Y movement\n\t * @returns Adjusted { moveX, moveY } with boundary-blocked axes zeroed\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number } {\n\t\treturn constrainMovementToBounds2D(this.lastHits, moveX, moveY);\n\t}\n\n\t/**\n\t * Returns the last position passed to `update`, if any.\n\t */\n\tgetLastPosition(): WorldBoundary2DPosition | null {\n\t\treturn this.lastPosition;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last `update(...)` call.\n\t * This is optional metadata; systems can ignore it.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Update FSM + extended state based on current position and bounds.\n\t * Returns the computed hits for convenience.\n\t */\n\tupdate(position: WorldBoundary2DPosition, bounds: WorldBoundary2DBounds): WorldBoundary2DHits {\n\t\tconst hits = computeWorldBoundary2DHits(position, bounds);\n\n\t\tthis.lastHits = hits;\n\t\tthis.lastPosition = { x: position.x, y: position.y };\n\t\tthis.lastUpdatedAtMs = Date.now();\n\n\t\tif (hasAnyWorldBoundary2DHit(hits)) {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.TouchBoundary);\n\t\t} else {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.EnterInside);\n\t\t}\n\n\t\treturn hits;\n\t}\n\n\tprivate dispatch(event: WorldBoundary2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","/**\n * WorldBoundary2DBehavior\n *\n * Tracks which 2D world boundaries an entity is touching.\n * Use `getMovement()` on the behavior handle to adjust movement based on hits.\n *\n * Source of truth: the WASM stage boundary query, consistent with other behaviors.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tWorldBoundary2DFSM,\n\ttype WorldBoundary2DBounds,\n\ttype WorldBoundary2DHits,\n} from './world-boundary-2d-fsm';\nimport {\n\tStageBoundaryDim,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport interface WorldBoundary2DOptions {\n\t/**\n\t * World boundaries (in world units).\n\t * - left hit if x <= left\n\t * - right hit if x >= right\n\t * - bottom hit if y <= bottom\n\t * - top hit if y >= top\n\t */\n\tboundaries: WorldBoundary2DBounds;\n}\n\n/**\n * Handle methods provided by WorldBoundary2DBehavior\n */\nexport interface WorldBoundary2DHandle {\n\t/**\n\t * Get the last computed boundary hits.\n\t * Returns null until entity is spawned and FSM is initialized.\n\t */\n\tgetLastHits(): WorldBoundary2DHits | null;\n\n\t/**\n\t * Get adjusted movement values based on boundary hits.\n\t * Zeros out movement into boundaries the entity is touching.\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number };\n}\n\nconst defaultOptions: WorldBoundary2DOptions = {\n\tboundaries: { top: 0, bottom: 0, left: 0, right: 0 },\n};\n\nconst WORLD_BOUNDARY_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:world-boundary-2d',\n);\n\n/**\n * Creates behavior-specific handle methods for WorldBoundary2DBehavior.\n */\nfunction createWorldBoundary2DHandle(\n\tref: BehaviorRef<WorldBoundary2DOptions>\n): WorldBoundary2DHandle {\n\treturn {\n\t\tgetLastHits: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getLastHits() ?? null;\n\t\t},\n\t\tgetMovement: (moveX: number, moveY: number) => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getMovement(moveX, moveY) ?? { moveX, moveY };\n\t\t},\n\t};\n}\n\n/**\n * WorldBoundary2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - computes and tracks boundary hits using the FSM\n *\n * When a {@link BehaviorRuntime} is present in `BehaviorSystemContext` and the\n * entity has been registered with the unified Stage (`runtimeHandle >= 0`), the\n * system uses the Rust `world_boundary` behavior as the source of truth and\n * mirrors its query into the local FSM so handle methods (`getLastHits`,\n * `getMovement`) return the same shape as the legacy code path.\n */\nclass WorldBoundary2DSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(WORLD_BOUNDARY_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst boundaryRef = link.ref as any;\n\t\t\tconst options = boundaryRef.options as WorldBoundary2DOptions;\n\n\t\t\tif (!boundaryRef.fsm) {\n\t\t\t\tboundaryRef.fsm = new WorldBoundary2DFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options.boundaries);\n\t\t\tconst query = this.wasmStage.queryWorldBoundary(handle);\n\t\t\tif (query) {\n\t\t\t\tboundaryRef.fsm.update(\n\t\t\t\t\t{ x: query.clamped[0], y: query.clamped[1] },\n\t\t\t\t\toptions.boundaries,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, bounds: WorldBoundary2DBounds): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachWorldBoundary(handle, StageBoundaryDim.Two, {\n\t\t\ttop: bounds.top,\n\t\t\tbottom: bounds.bottom,\n\t\t\tleft: bounds.left,\n\t\t\tright: bounds.right,\n\t\t\tfront: 0,\n\t\t\tback: 0,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * WorldBoundary2DBehavior\n *\n * @example\n * ```ts\n * import { WorldBoundary2DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ship = createSprite({ ... });\n * const boundary = ship.use(WorldBoundary2DBehavior, {\n * boundaries: { left: -10, right: 10, bottom: -7.5, top: 7.5 },\n * });\n *\n * ship.onUpdate(({ me }) => {\n * let moveX = ..., moveY = ...;\n * const hits = boundary.getLastHits(); // Fully typed!\n * ({ moveX, moveY } = boundary.getMovement(moveX, moveY));\n * me.moveXY(moveX, moveY);\n * });\n * ```\n */\nexport const WorldBoundary2DBehavior = defineBehavior({\n\tname: 'world-boundary-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew WorldBoundary2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createWorldBoundary2DHandle,\n});\n"],"mappings":"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","names":["WorldBoundary2DState","WorldBoundary2DEvent"]}
@@ -10,7 +10,7 @@ import {
10
10
  import {
11
11
  VEC3_ZERO,
12
12
  normalizeVec3
13
- } from "./chunk-WWVD5DR5.js";
13
+ } from "./chunk-M3QDKKDF.js";
14
14
  import {
15
15
  defineBehavior
16
16
  } from "./chunk-EZNCCO4L.js";
@@ -410,4 +410,4 @@ export {
410
410
  ParticleEmitterBehavior,
411
411
  particlePresets
412
412
  };
413
- //# sourceMappingURL=chunk-5SM5SH5O.js.map
413
+ //# sourceMappingURL=chunk-7JLWZXPA.js.map
@@ -1,16 +1,23 @@
1
1
  import {
2
2
  SyncStateMachine,
3
- t
4
- } from "./chunk-4URF65BR.js";
3
+ import_typescript_fsm
4
+ } from "./chunk-AS3HR7AS.js";
5
5
  import {
6
6
  StageRicochetDim,
7
7
  StageRicochetReflection
8
- } from "./chunk-Q75LKHDF.js";
8
+ } from "./chunk-DZ4LLQON.js";
9
9
  import {
10
10
  defineBehavior
11
11
  } from "./chunk-EZNCCO4L.js";
12
12
 
13
13
  // src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts
14
+ function bodyOf(entity) {
15
+ const body = entity?.body;
16
+ if (body && typeof body.linvel === "function" && typeof body.translation === "function") {
17
+ return body;
18
+ }
19
+ return null;
20
+ }
14
21
  var Ricochet2DState = /* @__PURE__ */ ((Ricochet2DState2) => {
15
22
  Ricochet2DState2["Idle"] = "idle";
16
23
  Ricochet2DState2["Ricocheting"] = "ricocheting";
@@ -34,11 +41,11 @@ var Ricochet2DFSM = class {
34
41
  this.machine = new SyncStateMachine(
35
42
  "idle" /* Idle */,
36
43
  [
37
- t("idle" /* Idle */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */),
38
- t("ricocheting" /* Ricocheting */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
44
+ (0, import_typescript_fsm.t)("idle" /* Idle */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */),
45
+ (0, import_typescript_fsm.t)("ricocheting" /* Ricocheting */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
39
46
  // Self transitions (no-ops)
40
- t("idle" /* Idle */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
41
- t("ricocheting" /* Ricocheting */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */)
47
+ (0, import_typescript_fsm.t)("idle" /* Idle */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
48
+ (0, import_typescript_fsm.t)("ricocheting" /* Ricocheting */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */)
42
49
  ]
43
50
  );
44
51
  }
@@ -159,14 +166,16 @@ var Ricochet2DFSM = class {
159
166
  let selfPosition = ctx.selfPosition;
160
167
  let otherPosition = ctx.otherPosition;
161
168
  let otherSize = ctx.otherSize;
162
- if (ctx.entity?.body) {
163
- const vel = ctx.entity.body.linvel();
169
+ const selfBody = bodyOf(ctx.entity);
170
+ if (selfBody) {
171
+ const vel = selfBody.linvel();
164
172
  selfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };
165
- const pos = ctx.entity.body.translation();
173
+ const pos = selfBody.translation();
166
174
  selfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };
167
175
  }
168
- if (ctx.otherEntity?.body) {
169
- const pos = ctx.otherEntity.body.translation();
176
+ const otherBody = bodyOf(ctx.otherEntity);
177
+ if (otherBody) {
178
+ const pos = otherBody.translation();
170
179
  otherPosition = otherPosition ?? { x: pos.x, y: pos.y, z: pos.z };
171
180
  }
172
181
  if (ctx.otherEntity && "size" in ctx.otherEntity) {
@@ -410,4 +419,4 @@ export {
410
419
  Ricochet2DFSM,
411
420
  Ricochet2DBehavior
412
421
  };
413
- //# sourceMappingURL=chunk-QDCJLJUV.js.map
422
+ //# sourceMappingURL=chunk-7W3KY55G.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts","../src/lib/behaviors/ricochet-2d/ricochet-2d.descriptor.ts"],"sourcesContent":["/**\n * Ricochet2DFSM\n *\n * FSM + extended state to track ricochet events and results.\n * The FSM state tracks whether a ricochet is currently occurring.\n */\nimport { BaseEntityInterface } from \"../../types/entity-types\";\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\nexport interface Ricochet2DResult {\n\t/** The reflected velocity vector */\n\tvelocity: { x: number; y: number; z?: number };\n\t/** The resulting speed after reflection */\n\tspeed: number;\n\t/** The collision normal used for reflection */\n\tnormal: { x: number; y: number; z?: number };\n}\n\nexport interface Ricochet2DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\t/** Current velocity of the entity (optional if entity is provided) */\n\tselfVelocity?: { x: number; y: number; z?: number };\n\t/** Contact information from the collision */\n\tcontact: {\n\t\t/** The collision normal */\n\t\tnormal?: { x: number; y: number; z?: number };\n\t\t/**\n\t\t * Optional position where the collision occurred.\n\t\t * If provided, used for precise offset calculation.\n\t\t */\n\t\tposition?: { x: number; y: number; z?: number };\n\t};\n\t/**\n\t * Optional position of the entity that owns this behavior.\n\t * Used with contact.position for offset calculations.\n\t */\n\tselfPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional position of the other entity in the collision.\n\t * Used for paddle-style deflection: offset = (contactY - otherY) / halfHeight.\n\t */\n\totherPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional size of the other entity (e.g., paddle size).\n\t * If provided, used to normalize the offset based on the collision face.\n\t */\n\totherSize?: { x: number; y: number; z?: number };\n}\n\n/** Structural view of a host-attached body (harness/simulation facade). */\ninterface RicochetBodyLike {\n\tlinvel(): { x: number; y: number; z: number };\n\ttranslation(): { x: number; y: number; z: number };\n}\n\nfunction bodyOf(entity: BaseEntityInterface | undefined): RicochetBodyLike | null {\n\tconst body = entity?.body as Partial<RicochetBodyLike> | null | undefined;\n\tif (body && typeof body.linvel === 'function' && typeof body.translation === 'function') {\n\t\treturn body as RicochetBodyLike;\n\t}\n\treturn null;\n}\n\nexport enum Ricochet2DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet2DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\n/**\n * Callback type for ricochet event listeners.\n */\nexport type RicochetCallback = (result: Ricochet2DResult) => void;\n\n/**\n * FSM wrapper with extended state (lastResult).\n * Systems or consumers call `computeRicochet(...)` when a collision occurs.\n */\nexport class Ricochet2DFSM {\n\tpublic readonly machine: SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>;\n\n\tprivate lastResult: Ricochet2DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\tprivate currentTimeMs: number = 0;\n\tprivate listeners: Set<RicochetCallback> = new Set();\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>(\n\t\t\tRicochet2DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Add a listener for ricochet events.\n\t * @returns Unsubscribe function\n\t */\n\taddListener(callback: RicochetCallback): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\t/**\n\t * Emit result to all listeners.\n\t */\n\tprivate emitToListeners(result: Ricochet2DResult): void {\n\t\tfor (const callback of this.listeners) {\n\t\t\ttry {\n\t\t\t\tcallback(result);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error('[Ricochet2DFSM] Listener error:', e);\n\t\t\t}\n\t\t}\n\t}\n\n\tgetState(): Ricochet2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last computation.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Set current game time (called by system each frame).\n\t * Used for cooldown calculations.\n\t */\n\tsetCurrentTimeMs(timeMs: number): void {\n\t\tthis.currentTimeMs = timeMs;\n\t}\n\n\t/**\n\t * Check if ricochet is on cooldown (to prevent rapid duplicate applications).\n\t * @param cooldownMs Cooldown duration in milliseconds (default: 50ms)\n\t */\n\tisOnCooldown(cooldownMs: number = 50): boolean {\n\t\tif (this.lastUpdatedAtMs === null) return false;\n\t\treturn (this.currentTimeMs - this.lastUpdatedAtMs) < cooldownMs;\n\t}\n\n\t/**\n\t * Reset cooldown state (e.g., on entity respawn).\n\t */\n\tresetCooldown(): void {\n\t\tthis.lastUpdatedAtMs = null;\n\t}\n\n\t/**\n\t * Compute a ricochet result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t */\n\tcomputeRicochet(\n\t\tctx: Ricochet2DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {}\n\t): Ricochet2DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 60,\n\t\t} = options;\n\n\t\t// Extract data from entities if provided\n\t\tconst { selfVelocity, selfPosition, otherPosition, otherSize } = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = Math.hypot(selfVelocity.x, selfVelocity.y);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute or extract collision normal\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute basic reflection\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, normal);\n\n\t\t// Apply angled deflection if requested\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\tselfVelocity,\n\t\t\t\tnormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position\n\t\t\t);\n\t\t}\n\n\t\t// Apply final speed constraints\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet2DResult = {\n\t\t\tvelocity: { x: reflected.x, y: reflected.y, z: 0 },\n\t\t\tspeed: Math.hypot(reflected.x, reflected.y),\n\t\t\tnormal: { x: normal.x, y: normal.y, z: 0 },\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = this.currentTimeMs;\n\t\tthis.dispatch(Ricochet2DEvent.StartRicochet);\n\t\tthis.emitToListeners(result);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Extract velocity, position, and size data from entities or context.\n\t */\n\tprivate extractDataFromEntities(ctx: Ricochet2DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tconst selfBody = bodyOf(ctx.entity);\n\t\tif (selfBody) {\n\t\t\tconst vel = selfBody.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };\n\t\t\tconst pos = selfBody.translation();\n\t\t\tselfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tconst otherBody = bodyOf(ctx.otherEntity);\n\t\tif (otherBody) {\n\t\t\tconst pos = otherBody.translation();\n\t\t\totherPosition = otherPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\t/**\n\t * Compute collision normal from entity positions using AABB heuristic.\n\t */\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number; z?: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst dx = selfPosition.x - otherPosition.x;\n\t\tconst dy = selfPosition.y - otherPosition.y;\n\n\t\t// Simple \"which face was hit\" logic for box collisions\n\t\tif (Math.abs(dx) > Math.abs(dy)) {\n\t\t\treturn { x: dx > 0 ? 1 : -1, y: 0, z: 0 };\n\t\t} else {\n\t\t\treturn { x: 0, y: dy > 0 ? 1 : -1, z: 0 };\n\t\t}\n\t}\n\n\t/**\n\t * Compute basic reflection using the formula: v' = v - 2(v·n)n\n\t */\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst vx = velocity.x;\n\t\tconst vy = velocity.y;\n\t\tconst dotProduct = vx * normal.x + vy * normal.y;\n\n\t\treturn {\n\t\t\tx: vx - 2 * dotProduct * normal.x,\n\t\t\ty: vy - 2 * dotProduct * normal.y,\n\t\t};\n\t}\n\n\t/**\n\t * Compute angled deflection for paddle-style reflections.\n\t */\n\tprivate computeAngledDeflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\n\t\t// Tangent vector (perpendicular to normal)\n\t\tlet tx = -normal.y;\n\t\tlet ty = normal.x;\n\n\t\t// Ensure tangent points in positive direction of the deflection axis\n\t\t// so that positive offset (right/top) results in positive deflection\n\t\tif (Math.abs(normal.x) > Math.abs(normal.y)) {\n\t\t\t// Vertical face (Normal is X-aligned). Deflection axis is Y.\n\t\t\t// We want ty > 0.\n\t\t\tif (ty < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t} else {\n\t\t\t// Horizontal face (Normal is Y-aligned). Deflection axis is X.\n\t\t\t// We want tx > 0.\n\t\t\tif (tx < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t}\n\n\t\t// Compute offset based on hit position\n\t\tconst offset = this.computeHitOffset(\n\t\t\tvelocity,\n\t\t\tnormal,\n\t\t\tspeed,\n\t\t\ttx,\n\t\t\tty,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition\n\t\t);\n\n\t\tconst angle = clamp(offset, -1, 1) * maxAngleRad;\n\n\t\tconst cosA = Math.cos(angle);\n\t\tconst sinA = Math.sin(angle);\n\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: newSpeed * (normal.x * cosA + tx * sinA),\n\t\t\ty: newSpeed * (normal.y * cosA + ty * sinA),\n\t\t};\n\t}\n\n\t/**\n\t * Compute hit offset for angled deflection (-1 to 1).\n\t */\n\tprivate computeHitOffset(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\ttx: number,\n\t\tty: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): number {\n\t\t// Use position-based offset if available\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst useY = Math.abs(normal.x) > Math.abs(normal.y);\n\t\t\tconst halfExtent = useY ? otherSize.y / 2 : otherSize.x / 2;\n\n\t\t\tif (useY) {\n\t\t\t\tconst selfY = selfPosition?.y ?? contactPosition?.y ?? 0;\n\t\t\t\tconst paddleY = otherPosition.y;\n\t\t\t\treturn (selfY - paddleY) / halfExtent;\n\t\t\t} else {\n\t\t\t\tconst selfX = selfPosition?.x ?? contactPosition?.x ?? 0;\n\t\t\t\tconst paddleX = otherPosition.x;\n\t\t\t\treturn (selfX - paddleX) / halfExtent;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: use velocity-based offset\n\t\treturn (velocity.x * tx + velocity.y * ty) / speed;\n\t}\n\n\t/**\n\t * Apply speed constraints to the reflected velocity.\n\t */\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number\n\t): { x: number; y: number } {\n\t\tconst currentSpeed = Math.hypot(velocity.x, velocity.y);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t};\n\t}\n\n\t/**\n\t * Clear the ricochet state (call after consumer has applied the result).\n\t */\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t}\n\n\tprivate dispatch(event: Ricochet2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","/**\n * Ricochet2DBehavior\n *\n * Computes 2D ricochet/reflection results for entities during collisions.\n * The behavior computes the result; the consumer decides how to apply it.\n *\n * Use `getRicochet(ctx)` on the behavior handle to compute reflection results.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Ricochet2DFSM, type Ricochet2DResult, type Ricochet2DCollisionContext, type RicochetCallback } from './ricochet-2d-fsm';\nimport {\n\tStageRicochetDim,\n\tStageRicochetReflection,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\nexport type { Ricochet2DResult };\n\nexport interface Ricochet2DOptions {\n\t/**\n\t * Minimum speed after reflection.\n\t * Default: 2\n\t */\n\tminSpeed: number;\n\n\t/**\n\t * Maximum speed after reflection.\n\t * Default: 20\n\t */\n\tmaxSpeed: number;\n\n\t/**\n\t * Speed multiplier applied during angled reflection.\n\t * Default: 1.05\n\t */\n\tspeedMultiplier: number;\n\n\t/**\n\t * Reflection mode:\n\t * - 'simple': Basic axis inversion\n\t * - 'angled': Paddle-style deflection based on contact point\n\t * Default: 'angled'\n\t */\n\treflectionMode: 'simple' | 'angled';\n\n\t/**\n\t * Maximum deflection angle in degrees for angled mode.\n\t * Default: 60\n\t */\n\tmaxAngleDeg: number;\n}\n\n/**\n * Handle methods provided by Ricochet2DBehavior\n */\nexport interface Ricochet2DHandle {\n\t/**\n\t * Compute a ricochet/reflection result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t *\n\t * @param ctx - Collision context with selfVelocity and contact normal\n\t * @returns Ricochet result with velocity, speed, and normal, or null\n\t */\n\tgetRicochet(ctx: Ricochet2DCollisionContext): Ricochet2DResult | null;\n\n\t/**\n\t * Compute ricochet and apply velocity directly via transformStore.\n\t * Emits to onRicochet listeners if successful.\n\t *\n\t * @param ctx - Collision context with entity and contact normal\n\t * @returns true if ricochet was computed and applied, false otherwise\n\t */\n\tapplyRicochet(ctx: Ricochet2DCollisionContext): boolean;\n\n\t/**\n\t * Get the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null;\n\n\t/**\n\t * Register a listener for ricochet events.\n\t * Called whenever a ricochet is computed (from getRicochet or applyRicochet).\n\t *\n\t * @param callback - Function to call with ricochet result\n\t * @returns Unsubscribe function\n\t */\n\tonRicochet(callback: RicochetCallback): () => void;\n}\n\nconst defaultOptions: Ricochet2DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 60,\n};\n\nconst RICOCHET_BEHAVIOR_KEY = Symbol.for('zylem:behavior:ricochet-2d');\n\n/**\n * Creates behavior-specific handle methods for Ricochet2DBehavior.\n */\nfunction createRicochet2DHandle(\n\tref: BehaviorRef<Ricochet2DOptions>\n): Ricochet2DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet2DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tapplyRicochet: (ctx: Ricochet2DCollisionContext): boolean => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return false;\n\n\t\t\t// Skip if on cooldown (prevents rapid duplicate applications)\n\t\t\tif (fsm.isOnCooldown()) return false;\n\n\t\t\tconst result = fsm.computeRicochet(ctx, ref.options);\n\t\t\tif (!result) return false;\n\n\t\t\t// Apply velocity via transformStore\n\t\t\tconst entity = ctx.entity as any;\n\t\t\tif (entity?.transformStore) {\n\t\t\t\tentity.transformStore.velocity.x = result.velocity.x;\n\t\t\t\tentity.transformStore.velocity.y = result.velocity.y;\n\t\t\t\tentity.transformStore.velocity.z = result.velocity.z ?? 0;\n\t\t\t\tentity.transformStore.dirty.velocityX = true;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t\tentity.transformStore.dirty.velocityZ = true;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t\tonRicochet: (callback: RicochetCallback): (() => void) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) {\n\t\t\t\t// FSM not ready yet - queue callback for later\n\t\t\t\t// System will apply pending callbacks when FSM is created\n\t\t\t\tif (!(ref as any).pendingListeners) {\n\t\t\t\t\t(ref as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t\t(ref as any).pendingListeners.push(callback);\n\t\t\t\t\n\t\t\t\t// Return unsubscribe that removes from pending queue\n\t\t\t\treturn () => {\n\t\t\t\t\tconst pending = (ref as any).pendingListeners as RicochetCallback[];\n\t\t\t\t\tconst idx = pending.indexOf(callback);\n\t\t\t\t\tif (idx >= 0) pending.splice(idx, 1);\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn fsm.addListener(callback);\n\t\t},\n\t};\n}\n\n/**\n * Ricochet2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - lazily creates FSM instances for each entity\n *\n * Note: This behavior is consumer-driven. The system only manages FSM lifecycle.\n * Consumers call `getRicochet(ctx)` during collision callbacks to compute results.\n */\nclass Ricochet2DSystem implements BehaviorSystem {\n\tprivate elapsedMs: number = 0;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tthis.elapsedMs += delta * 1000;\n\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst ricochetRef = link.ref as any;\n\t\t\tconst gameEntity = link.entity as any;\n\n\t\t\tif (!ricochetRef.fsm) {\n\t\t\t\tricochetRef.fsm = new Ricochet2DFSM();\n\n\t\t\t\tconst pending = (ricochetRef as any).pendingListeners as RicochetCallback[] | undefined;\n\t\t\t\tif (pending) {\n\t\t\t\t\tfor (const cb of pending) {\n\t\t\t\t\t\tricochetRef.fsm.addListener(cb);\n\t\t\t\t\t}\n\t\t\t\t\t(ricochetRef as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tricochetRef.fsm.setCurrentTimeMs(this.elapsedMs);\n\n\t\t\tconst handle = (gameEntity?.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, ricochetRef.options as Ricochet2DOptions);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Ricochet2DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst reflectionMode = options.reflectionMode === 'simple'\n\t\t\t? StageRicochetReflection.Mirror\n\t\t\t: StageRicochetReflection.Angled;\n\t\tthis.wasmStage.attachRicochet(handle, StageRicochetDim.Two, {\n\t\t\tminSpeed: options.minSpeed,\n\t\t\tmaxSpeed: options.maxSpeed,\n\t\t\tspeedMultiplier: options.speedMultiplier,\n\t\t\tmaxAngleDeg: options.maxAngleDeg,\n\t\t\treflectionMode,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tconst ricochetRef = link.ref as any;\n\t\t\t\tif (ricochetRef?.fsm) {\n\t\t\t\t\tricochetRef.fsm.resetCooldown();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Ricochet2DBehavior\n *\n * @example\n * ```ts\n * import { Ricochet2DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ball = createSphere({ ... });\n * const ricochet = ball.use(Ricochet2DBehavior, {\n * minSpeed: 3,\n * maxSpeed: 15,\n * reflectionMode: 'angled',\n * });\n *\n * ball.onCollision(({ entity, other }) => {\n * const velocity = entity.body.linvel();\n * const result = ricochet.getRicochet({\n * selfVelocity: velocity,\n * contact: { normal: { x: 1, y: 0 } }, // from collision data\n * });\n *\n * if (result) {\n * entity.body.setLinvel(result.velocity, true);\n * }\n * });\n * ```\n */\nexport const Ricochet2DBehavior = defineBehavior({\n\tname: 'ricochet-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Ricochet2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createRicochet2DHandle,\n});\n"],"mappings":"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","names":["Ricochet2DState","Ricochet2DEvent"]}
@@ -0,0 +1,188 @@
1
+ import {
2
+ __commonJS,
3
+ __toESM
4
+ } from "./chunk-5WRI5ZAA.js";
5
+
6
+ // node_modules/.pnpm/typescript-fsm@1.6.0/node_modules/typescript-fsm/dist/stateMachine.js
7
+ var require_stateMachine = __commonJS({
8
+ "node_modules/.pnpm/typescript-fsm@1.6.0/node_modules/typescript-fsm/dist/stateMachine.js"(exports) {
9
+ "use strict";
10
+ Object.defineProperty(exports, "__esModule", { value: true });
11
+ exports.SyncStateMachine = exports.StateMachine = void 0;
12
+ exports.t = t2;
13
+ function t2(fromState, event, toState, cb) {
14
+ return { fromState, event, toState, cb };
15
+ }
16
+ var StateMachine = class {
17
+ // initialize the state-machine
18
+ constructor(init, transitions = [], logger = console) {
19
+ this.init = init;
20
+ this.transitions = transitions;
21
+ this.logger = logger;
22
+ this._current = init;
23
+ }
24
+ addTransitions(transitions) {
25
+ transitions.forEach((_tran) => {
26
+ const tran = Object.create(_tran);
27
+ if (tran.cb && !tran.cb.name?.startsWith("bound ")) {
28
+ tran.cb = tran.cb.bind(this);
29
+ }
30
+ this.transitions.push(tran);
31
+ });
32
+ }
33
+ getState() {
34
+ return this._current;
35
+ }
36
+ can(event) {
37
+ return this.transitions.some((trans) => trans.fromState === this._current && trans.event === event);
38
+ }
39
+ getNextState(event) {
40
+ const transition = this.transitions.find((tran) => tran.fromState === this._current && tran.event === event);
41
+ return transition?.toState;
42
+ }
43
+ isFinal() {
44
+ return this.transitions.every((trans) => trans.fromState !== this._current);
45
+ }
46
+ formatErr(fromState, event) {
47
+ return `No transition: from ${String(fromState)} event ${String(event)}`;
48
+ }
49
+ // post event async
50
+ async dispatch(event, ...args) {
51
+ return new Promise((resolve, reject) => {
52
+ setTimeout((me) => {
53
+ const found = this.transitions.some((tran) => {
54
+ if (tran.fromState === me._current && tran.event === event) {
55
+ me._current = tran.toState;
56
+ if (tran.cb) {
57
+ try {
58
+ const p = tran.cb(...args);
59
+ if (p instanceof Promise) {
60
+ p.then(resolve).catch(reject);
61
+ } else {
62
+ resolve();
63
+ }
64
+ } catch (e) {
65
+ this.logger.error("Exception in callback", e);
66
+ reject(e);
67
+ }
68
+ } else {
69
+ resolve();
70
+ }
71
+ return true;
72
+ }
73
+ return false;
74
+ });
75
+ if (!found) {
76
+ const errorMessage = this.formatErr(me._current, event);
77
+ this.logger.error(errorMessage);
78
+ reject(new Error(errorMessage));
79
+ }
80
+ }, 0, this);
81
+ });
82
+ }
83
+ /**
84
+ * Generate a Mermaid StateDiagram of the current machine.
85
+ */
86
+ toMermaid(title) {
87
+ const diagram = [];
88
+ if (title) {
89
+ diagram.push("---");
90
+ diagram.push(`title: ${title}`);
91
+ diagram.push("---");
92
+ }
93
+ diagram.push("stateDiagram-v2");
94
+ diagram.push(` [*] --> ${String(this.init)}`);
95
+ this.transitions.forEach(({ event, fromState, toState }) => {
96
+ const from = String(fromState);
97
+ const to = String(toState);
98
+ const evt = String(event);
99
+ diagram.push(` ${from} --> ${to}: ${evt}`);
100
+ });
101
+ const ts = /* @__PURE__ */ new Set();
102
+ this.transitions.forEach(({ toState }) => ts.add(toState));
103
+ this.transitions.forEach(({ fromState }) => ts.delete(fromState));
104
+ ts.forEach((state) => diagram.push(` ${String(state)} --> [*]`));
105
+ return diagram.join("\n");
106
+ }
107
+ };
108
+ exports.StateMachine = StateMachine;
109
+ var SyncStateMachine2 = class extends StateMachine {
110
+ constructor(init, transitions = [], logger = console) {
111
+ super(init, transitions, logger);
112
+ }
113
+ dispatch(_event, ..._args) {
114
+ throw new Error("SyncStateMachine does not support async dispatch.");
115
+ }
116
+ // post sync event
117
+ // returns true if the event was handled, false otherwise
118
+ syncDispatch(event, ...args) {
119
+ const found = this.transitions.some((tran) => {
120
+ if (tran.fromState === this._current && tran.event === event) {
121
+ const current = this._current;
122
+ this._current = tran.toState;
123
+ if (tran.cb) {
124
+ try {
125
+ tran.cb(...args);
126
+ } catch (e) {
127
+ this._current = current;
128
+ this.logger.error("Exception in callback", e);
129
+ throw e;
130
+ }
131
+ return true;
132
+ }
133
+ return false;
134
+ }
135
+ });
136
+ if (!found) {
137
+ const errorMessage = this.formatErr(this._current, event);
138
+ this.logger.error(errorMessage);
139
+ }
140
+ return !!found;
141
+ }
142
+ };
143
+ exports.SyncStateMachine = SyncStateMachine2;
144
+ }
145
+ });
146
+
147
+ // src/lib/core/utility/sync-state-machine.ts
148
+ var import_typescript_fsm = __toESM(require_stateMachine(), 1);
149
+ var import_typescript_fsm2 = __toESM(require_stateMachine(), 1);
150
+ var SyncStateMachine = class extends import_typescript_fsm.StateMachine {
151
+ constructor(init, transitions = [], logger = console) {
152
+ super(init, transitions, logger);
153
+ }
154
+ dispatch(_event, ..._args) {
155
+ throw new Error("SyncStateMachine does not support async dispatch.");
156
+ }
157
+ syncDispatch(event, ...args) {
158
+ const found = this.transitions.some((transition) => {
159
+ if (transition.fromState !== this._current || transition.event !== event) {
160
+ return false;
161
+ }
162
+ const current = this._current;
163
+ this._current = transition.toState;
164
+ if (!transition.cb) {
165
+ return true;
166
+ }
167
+ try {
168
+ transition.cb(...args);
169
+ return true;
170
+ } catch (error) {
171
+ this._current = current;
172
+ this.logger.error("Exception in callback", error);
173
+ throw error;
174
+ }
175
+ });
176
+ if (!found) {
177
+ const errorMessage = this.formatErr(this._current, event);
178
+ this.logger.error(errorMessage);
179
+ }
180
+ return found;
181
+ }
182
+ };
183
+
184
+ export {
185
+ SyncStateMachine,
186
+ import_typescript_fsm2 as import_typescript_fsm
187
+ };
188
+ //# sourceMappingURL=chunk-AS3HR7AS.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../node_modules/.pnpm/typescript-fsm@1.6.0/node_modules/typescript-fsm/src/stateMachine.ts","../src/lib/core/utility/sync-state-machine.ts"],"sourcesContent":[null,"import {\n\tStateMachine as BaseStateMachine,\n\ttype ILogger,\n\ttype ITransition,\n\ttype SyncCallback,\n} from 'typescript-fsm';\n\nexport { t } from 'typescript-fsm';\nexport type { ILogger, ITransition, SyncCallback };\n\n/**\n * Local wrapper around typescript-fsm's SyncStateMachine.\n *\n * typescript-fsm@1.6.0 incorrectly reports callback-less transitions as\n * unhandled even after moving to the next state, which causes noisy\n * `No transition...` console errors for valid FSM dispatches.\n */\nexport class SyncStateMachine<\n\tSTATE extends string | number | symbol,\n\tEVENT extends string | number | symbol,\n\tCALLBACK extends Record<EVENT, SyncCallback> = Record<EVENT, SyncCallback>,\n> extends BaseStateMachine<STATE, EVENT, CALLBACK> {\n\tconstructor(\n\t\tinit: STATE,\n\t\ttransitions: ITransition<STATE, EVENT, CALLBACK[EVENT]>[] = [],\n\t\tlogger: ILogger = console,\n\t) {\n\t\tsuper(init, transitions, logger);\n\t}\n\n\tdispatch<E extends EVENT>(_event: E, ..._args: unknown[]): Promise<void> {\n\t\tthrow new Error('SyncStateMachine does not support async dispatch.');\n\t}\n\n\tsyncDispatch<E extends EVENT>(event: E, ...args: unknown[]): boolean {\n\t\tconst found = this.transitions.some((transition) => {\n\t\t\tif (transition.fromState !== this._current || transition.event !== event) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst current = this._current;\n\t\t\tthis._current = transition.toState;\n\n\t\t\tif (!transition.cb) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\ttransition.cb(...args);\n\t\t\t\treturn true;\n\t\t\t} catch (error) {\n\t\t\t\tthis._current = current;\n\t\t\t\tthis.logger.error('Exception in callback', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\n\t\tif (!found) {\n\t\t\tconst errorMessage = this.formatErr(this._current, event);\n\t\t\tthis.logger.error(errorMessage);\n\t\t}\n\n\t\treturn found;\n\t}\n}\n"],"mappings":"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","names":["t","SyncStateMachine","import_typescript_fsm","BaseStateMachine"]}
@@ -0,0 +1,155 @@
1
+ import {
2
+ createThrusterInputComponent,
3
+ createThrusterMovementComponent
4
+ } from "./chunk-IZJFUAXB.js";
5
+ import {
6
+ SyncStateMachine,
7
+ import_typescript_fsm
8
+ } from "./chunk-AS3HR7AS.js";
9
+ import {
10
+ defineBehavior
11
+ } from "./chunk-EZNCCO4L.js";
12
+
13
+ // src/lib/behaviors/thruster/thruster-fsm.ts
14
+ var ThrusterState = /* @__PURE__ */ ((ThrusterState2) => {
15
+ ThrusterState2["Idle"] = "idle";
16
+ ThrusterState2["Active"] = "active";
17
+ ThrusterState2["Boosting"] = "boosting";
18
+ ThrusterState2["Disabled"] = "disabled";
19
+ ThrusterState2["Docked"] = "docked";
20
+ return ThrusterState2;
21
+ })(ThrusterState || {});
22
+ var ThrusterEvent = /* @__PURE__ */ ((ThrusterEvent2) => {
23
+ ThrusterEvent2["Activate"] = "activate";
24
+ ThrusterEvent2["Deactivate"] = "deactivate";
25
+ ThrusterEvent2["Boost"] = "boost";
26
+ ThrusterEvent2["EndBoost"] = "endBoost";
27
+ ThrusterEvent2["Disable"] = "disable";
28
+ ThrusterEvent2["Enable"] = "enable";
29
+ ThrusterEvent2["Dock"] = "dock";
30
+ ThrusterEvent2["Undock"] = "undock";
31
+ return ThrusterEvent2;
32
+ })(ThrusterEvent || {});
33
+ var ThrusterFSM = class {
34
+ constructor(ctx) {
35
+ this.ctx = ctx;
36
+ this.machine = new SyncStateMachine(
37
+ "idle" /* Idle */,
38
+ [
39
+ // Core transitions
40
+ (0, import_typescript_fsm.t)("idle" /* Idle */, "activate" /* Activate */, "active" /* Active */),
41
+ (0, import_typescript_fsm.t)("active" /* Active */, "deactivate" /* Deactivate */, "idle" /* Idle */),
42
+ (0, import_typescript_fsm.t)("active" /* Active */, "boost" /* Boost */, "boosting" /* Boosting */),
43
+ (0, import_typescript_fsm.t)("active" /* Active */, "disable" /* Disable */, "disabled" /* Disabled */),
44
+ (0, import_typescript_fsm.t)("active" /* Active */, "dock" /* Dock */, "docked" /* Docked */),
45
+ (0, import_typescript_fsm.t)("boosting" /* Boosting */, "endBoost" /* EndBoost */, "active" /* Active */),
46
+ (0, import_typescript_fsm.t)("boosting" /* Boosting */, "disable" /* Disable */, "disabled" /* Disabled */),
47
+ (0, import_typescript_fsm.t)("disabled" /* Disabled */, "enable" /* Enable */, "idle" /* Idle */),
48
+ (0, import_typescript_fsm.t)("docked" /* Docked */, "undock" /* Undock */, "idle" /* Idle */),
49
+ // Self-transitions (no-ops for redundant events)
50
+ (0, import_typescript_fsm.t)("idle" /* Idle */, "deactivate" /* Deactivate */, "idle" /* Idle */),
51
+ (0, import_typescript_fsm.t)("active" /* Active */, "activate" /* Activate */, "active" /* Active */)
52
+ ]
53
+ );
54
+ }
55
+ ctx;
56
+ machine;
57
+ /**
58
+ * Get current state
59
+ */
60
+ getState() {
61
+ return this.machine.getState();
62
+ }
63
+ /**
64
+ * Dispatch an event to transition state
65
+ */
66
+ dispatch(event) {
67
+ if (this.machine.can(event)) {
68
+ this.machine.syncDispatch(event);
69
+ }
70
+ }
71
+ /**
72
+ * Update FSM state based on player input.
73
+ * Auto-transitions between Idle/Active to report current state.
74
+ * Does NOT modify input - just observes and reports.
75
+ */
76
+ update(playerInput) {
77
+ const state = this.machine.getState();
78
+ const hasInput = Math.abs(playerInput.thrust) > 0.01 || Math.abs(playerInput.rotate) > 0.01 || Math.abs(playerInput.thrustX ?? 0) > 0.01 || Math.abs(playerInput.thrustY ?? 0) > 0.01;
79
+ if (hasInput && state === "idle" /* Idle */) {
80
+ this.dispatch("activate" /* Activate */);
81
+ } else if (!hasInput && state === "active" /* Active */) {
82
+ this.dispatch("deactivate" /* Deactivate */);
83
+ }
84
+ }
85
+ };
86
+
87
+ // src/lib/behaviors/thruster/thruster.descriptor.ts
88
+ var defaultOptions = {
89
+ linearThrust: 10,
90
+ angularThrust: 5,
91
+ linearDamping: void 0
92
+ };
93
+ var THRUSTER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:thruster");
94
+ var ThrusterBehaviorSystem = class {
95
+ constructor(wasmStage, getBehaviorLinks) {
96
+ this.wasmStage = wasmStage;
97
+ this.getBehaviorLinks = getBehaviorLinks;
98
+ }
99
+ wasmStage;
100
+ getBehaviorLinks;
101
+ attachedRuntimeSlots = /* @__PURE__ */ new Set();
102
+ update(_ecs, _delta) {
103
+ const links = this.getBehaviorLinks?.(THRUSTER_BEHAVIOR_KEY);
104
+ if (!links) return;
105
+ for (const link of links) {
106
+ const gameEntity = link.entity;
107
+ const thrusterRef = link.ref;
108
+ const options = thrusterRef.options;
109
+ if (!gameEntity.thruster) {
110
+ gameEntity.thruster = createThrusterMovementComponent(
111
+ options.linearThrust,
112
+ options.angularThrust,
113
+ { linearDamping: options.linearDamping }
114
+ );
115
+ }
116
+ if (!gameEntity.$thruster) {
117
+ gameEntity.$thruster = createThrusterInputComponent();
118
+ }
119
+ const handle = gameEntity.runtimeHandle ?? -1;
120
+ if (!this.wasmStage || handle < 0) continue;
121
+ this.ensureWasmAttached(handle, options);
122
+ const input = gameEntity.$thruster;
123
+ if (input) {
124
+ this.wasmStage.writeInputAxes(handle, input.thrust, input.rotate);
125
+ this.wasmStage.writeInputReserved(handle, input.thrustX, input.thrustY);
126
+ }
127
+ }
128
+ }
129
+ ensureWasmAttached(handle, options) {
130
+ if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
131
+ this.wasmStage.attachThruster(handle, {
132
+ maxSpeed: 50,
133
+ acceleration: options.linearThrust,
134
+ turnRateRadPerSec: options.angularThrust,
135
+ linearDamping: options.linearDamping ?? 0
136
+ });
137
+ this.attachedRuntimeSlots.add(handle);
138
+ }
139
+ destroy(_ecs) {
140
+ this.attachedRuntimeSlots.clear();
141
+ }
142
+ };
143
+ var ThrusterBehavior = defineBehavior({
144
+ name: "thruster",
145
+ defaultOptions,
146
+ systemFactory: (ctx) => new ThrusterBehaviorSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks)
147
+ });
148
+
149
+ export {
150
+ ThrusterState,
151
+ ThrusterEvent,
152
+ ThrusterFSM,
153
+ ThrusterBehavior
154
+ };
155
+ //# sourceMappingURL=chunk-BAXJ7XHJ.js.map