@zylem/behaviors 0.2.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 (175) hide show
  1. package/README.md +26 -0
  2. package/dist/behavior-system-DYbx-x_-.d.ts +307 -0
  3. package/dist/chunk-2C3Z5GE7.js +176 -0
  4. package/dist/chunk-2C3Z5GE7.js.map +1 -0
  5. package/dist/chunk-2CLT36VB.js +241 -0
  6. package/dist/chunk-2CLT36VB.js.map +1 -0
  7. package/dist/chunk-3PYGRHAL.js +35 -0
  8. package/dist/chunk-3PYGRHAL.js.map +1 -0
  9. package/dist/chunk-4I6E6DGV.js +212 -0
  10. package/dist/chunk-4I6E6DGV.js.map +1 -0
  11. package/dist/chunk-4URF65BR.js +44 -0
  12. package/dist/chunk-4URF65BR.js.map +1 -0
  13. package/dist/chunk-6C6NXNEW.js +38 -0
  14. package/dist/chunk-6C6NXNEW.js.map +1 -0
  15. package/dist/chunk-ARJWUNRE.js +431 -0
  16. package/dist/chunk-ARJWUNRE.js.map +1 -0
  17. package/dist/chunk-BXP4K7RF.js +1205 -0
  18. package/dist/chunk-BXP4K7RF.js.map +1 -0
  19. package/dist/chunk-DYLH6K7B.js +44 -0
  20. package/dist/chunk-DYLH6K7B.js.map +1 -0
  21. package/dist/chunk-EIWQM6U3.js +27 -0
  22. package/dist/chunk-EIWQM6U3.js.map +1 -0
  23. package/dist/chunk-EZNCCO4L.js +14 -0
  24. package/dist/chunk-EZNCCO4L.js.map +1 -0
  25. package/dist/chunk-F3KRX2V4.js +457 -0
  26. package/dist/chunk-F3KRX2V4.js.map +1 -0
  27. package/dist/chunk-FPTWVCPN.js +53 -0
  28. package/dist/chunk-FPTWVCPN.js.map +1 -0
  29. package/dist/chunk-GCNRXAKH.js +37 -0
  30. package/dist/chunk-GCNRXAKH.js.map +1 -0
  31. package/dist/chunk-GYURWCIQ.js +1985 -0
  32. package/dist/chunk-GYURWCIQ.js.map +1 -0
  33. package/dist/chunk-HZMY3TT4.js +244 -0
  34. package/dist/chunk-HZMY3TT4.js.map +1 -0
  35. package/dist/chunk-ID5QJYF5.js +50 -0
  36. package/dist/chunk-ID5QJYF5.js.map +1 -0
  37. package/dist/chunk-JAN34G6S.js +552 -0
  38. package/dist/chunk-JAN34G6S.js.map +1 -0
  39. package/dist/chunk-K3UNLQ7W.js +118 -0
  40. package/dist/chunk-K3UNLQ7W.js.map +1 -0
  41. package/dist/chunk-KDY5N6J4.js +82 -0
  42. package/dist/chunk-KDY5N6J4.js.map +1 -0
  43. package/dist/chunk-KOZUNIIE.js +413 -0
  44. package/dist/chunk-KOZUNIIE.js.map +1 -0
  45. package/dist/chunk-KS72RZHX.js +30 -0
  46. package/dist/chunk-KS72RZHX.js.map +1 -0
  47. package/dist/chunk-LD5SMIB3.js +32 -0
  48. package/dist/chunk-LD5SMIB3.js.map +1 -0
  49. package/dist/chunk-OHCPV2NT.js +86 -0
  50. package/dist/chunk-OHCPV2NT.js.map +1 -0
  51. package/dist/chunk-OYWFPIHF.js +115 -0
  52. package/dist/chunk-OYWFPIHF.js.map +1 -0
  53. package/dist/chunk-POZER3MQ.js +407 -0
  54. package/dist/chunk-POZER3MQ.js.map +1 -0
  55. package/dist/chunk-RTRVBEIB.js +56 -0
  56. package/dist/chunk-RTRVBEIB.js.map +1 -0
  57. package/dist/chunk-RTVRUNYR.js +29 -0
  58. package/dist/chunk-RTVRUNYR.js.map +1 -0
  59. package/dist/chunk-SS7ELMJ7.js +406 -0
  60. package/dist/chunk-SS7ELMJ7.js.map +1 -0
  61. package/dist/chunk-STCCNLPC.js +160 -0
  62. package/dist/chunk-STCCNLPC.js.map +1 -0
  63. package/dist/chunk-SVLNF434.js +261 -0
  64. package/dist/chunk-SVLNF434.js.map +1 -0
  65. package/dist/chunk-UYFOLI2X.js +24 -0
  66. package/dist/chunk-UYFOLI2X.js.map +1 -0
  67. package/dist/chunk-VAN4WW3F.js +67 -0
  68. package/dist/chunk-VAN4WW3F.js.map +1 -0
  69. package/dist/chunk-WOMHL23C.js +369 -0
  70. package/dist/chunk-WOMHL23C.js.map +1 -0
  71. package/dist/chunk-WWVD5DR5.js +120 -0
  72. package/dist/chunk-WWVD5DR5.js.map +1 -0
  73. package/dist/chunk-XG2YMCJW.js +201 -0
  74. package/dist/chunk-XG2YMCJW.js.map +1 -0
  75. package/dist/chunk-XIJNE67Y.js +210 -0
  76. package/dist/chunk-XIJNE67Y.js.map +1 -0
  77. package/dist/chunk-Y6UJOUE5.js +44 -0
  78. package/dist/chunk-Y6UJOUE5.js.map +1 -0
  79. package/dist/chunk-YUO6SKJD.js +348 -0
  80. package/dist/chunk-YUO6SKJD.js.map +1 -0
  81. package/dist/components--hjBMTnz.d.ts +120 -0
  82. package/dist/components-B29WHGQN.d.ts +39 -0
  83. package/dist/components-BWG1GkZA.d.ts +20 -0
  84. package/dist/components-XCpL6IYt.d.ts +18 -0
  85. package/dist/components-qWwOsvwh.d.ts +43 -0
  86. package/dist/cooldown.d.ts +116 -0
  87. package/dist/cooldown.js +20 -0
  88. package/dist/cooldown.js.map +1 -0
  89. package/dist/coordinators/boundary-ricochet-3d.d.ts +19 -0
  90. package/dist/coordinators/boundary-ricochet-3d.js +8 -0
  91. package/dist/coordinators/boundary-ricochet-3d.js.map +1 -0
  92. package/dist/coordinators/boundary-ricochet.d.ts +34 -0
  93. package/dist/coordinators/boundary-ricochet.js +8 -0
  94. package/dist/coordinators/boundary-ricochet.js.map +1 -0
  95. package/dist/coordinators/first-person-shooter.d.ts +35 -0
  96. package/dist/coordinators/first-person-shooter.js +9 -0
  97. package/dist/coordinators/first-person-shooter.js.map +1 -0
  98. package/dist/coordinators/multidirectional-space-shooter.d.ts +27 -0
  99. package/dist/coordinators/multidirectional-space-shooter.js +10 -0
  100. package/dist/coordinators/multidirectional-space-shooter.js.map +1 -0
  101. package/dist/coordinators/top-down-shooter.d.ts +27 -0
  102. package/dist/coordinators/top-down-shooter.js +10 -0
  103. package/dist/coordinators/top-down-shooter.js.map +1 -0
  104. package/dist/core.d.ts +25 -0
  105. package/dist/core.js +327 -0
  106. package/dist/core.js.map +1 -0
  107. package/dist/destructible-3d.d.ts +42 -0
  108. package/dist/destructible-3d.js +13 -0
  109. package/dist/destructible-3d.js.map +1 -0
  110. package/dist/destructible-prebake.d.ts +28 -0
  111. package/dist/destructible-prebake.js +23 -0
  112. package/dist/destructible-prebake.js.map +1 -0
  113. package/dist/entity-types-CmtQ-IBp.d.ts +15 -0
  114. package/dist/first-person-perspective-C9Mptl0-.d.ts +23 -0
  115. package/dist/first-person.d.ts +204 -0
  116. package/dist/first-person.js +27 -0
  117. package/dist/first-person.js.map +1 -0
  118. package/dist/index.d.ts +40 -0
  119. package/dist/index.js +263 -0
  120. package/dist/index.js.map +1 -0
  121. package/dist/jumper-2d.d.ts +113 -0
  122. package/dist/jumper-2d.js +27 -0
  123. package/dist/jumper-2d.js.map +1 -0
  124. package/dist/jumper-3d.d.ts +164 -0
  125. package/dist/jumper-3d.js +29 -0
  126. package/dist/jumper-3d.js.map +1 -0
  127. package/dist/moveable-BCRpjAQb.d.ts +176 -0
  128. package/dist/particle-emitter/presets.d.ts +46 -0
  129. package/dist/particle-emitter/presets.js +15 -0
  130. package/dist/particle-emitter/presets.js.map +1 -0
  131. package/dist/particle-emitter.d.ts +78 -0
  132. package/dist/particle-emitter.js +15 -0
  133. package/dist/particle-emitter.js.map +1 -0
  134. package/dist/payload-BQzu-oY_.d.ts +89 -0
  135. package/dist/platformer-3d.d.ts +347 -0
  136. package/dist/platformer-3d.js +23 -0
  137. package/dist/platformer-3d.js.map +1 -0
  138. package/dist/ricochet-2d.d.ts +274 -0
  139. package/dist/ricochet-2d.js +16 -0
  140. package/dist/ricochet-2d.js.map +1 -0
  141. package/dist/ricochet-3d.d.ts +116 -0
  142. package/dist/ricochet-3d.js +16 -0
  143. package/dist/ricochet-3d.js.map +1 -0
  144. package/dist/runtime-2d.d.ts +2 -0
  145. package/dist/runtime-2d.js +32 -0
  146. package/dist/runtime-2d.js.map +1 -0
  147. package/dist/runtime-pong.d.ts +92 -0
  148. package/dist/runtime-pong.js +38 -0
  149. package/dist/runtime-pong.js.map +1 -0
  150. package/dist/screen-visibility.d.ts +74 -0
  151. package/dist/screen-visibility.js +10 -0
  152. package/dist/screen-visibility.js.map +1 -0
  153. package/dist/screen-wrap.d.ts +86 -0
  154. package/dist/screen-wrap.js +16 -0
  155. package/dist/screen-wrap.js.map +1 -0
  156. package/dist/shooter-2d.d.ts +77 -0
  157. package/dist/shooter-2d.js +12 -0
  158. package/dist/shooter-2d.js.map +1 -0
  159. package/dist/spark-DDlIg1nM.d.ts +148 -0
  160. package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
  161. package/dist/thruster.d.ts +132 -0
  162. package/dist/thruster.js +26 -0
  163. package/dist/thruster.js.map +1 -0
  164. package/dist/top-down-movement.d.ts +40 -0
  165. package/dist/top-down-movement.js +20 -0
  166. package/dist/top-down-movement.js.map +1 -0
  167. package/dist/use-behavior-D_82jAL3.d.ts +13 -0
  168. package/dist/vector-Bm65-FOG.d.ts +45 -0
  169. package/dist/world-boundary-2d.d.ts +141 -0
  170. package/dist/world-boundary-2d.js +21 -0
  171. package/dist/world-boundary-2d.js.map +1 -0
  172. package/dist/world-boundary-3d.d.ts +75 -0
  173. package/dist/world-boundary-3d.js +21 -0
  174. package/dist/world-boundary-3d.js.map +1 -0
  175. package/package.json +161 -0
@@ -0,0 +1,457 @@
1
+ import {
2
+ createJumpConfig3D,
3
+ createJumpInput3D,
4
+ createJumpState3D
5
+ } from "./chunk-DYLH6K7B.js";
6
+ import {
7
+ GroundProbe3D,
8
+ getGroundAnchorOffsetY,
9
+ getGroundSnapTargetY
10
+ } from "./chunk-SVLNF434.js";
11
+ import {
12
+ setVelocityIntent
13
+ } from "./chunk-RTVRUNYR.js";
14
+ import {
15
+ SyncStateMachine,
16
+ t
17
+ } from "./chunk-4URF65BR.js";
18
+ import {
19
+ defineBehavior
20
+ } from "./chunk-EZNCCO4L.js";
21
+
22
+ // src/lib/behaviors/jumper-3d/jumper-3d.behavior.ts
23
+ function jumpVelocityFromHeight(gravity, height) {
24
+ return Math.sqrt(2 * gravity * height);
25
+ }
26
+ function planarSpeed(x, z) {
27
+ return Math.sqrt(x * x + z * z);
28
+ }
29
+ function normalizeMut(v) {
30
+ const len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);
31
+ if (len > 1e-6) {
32
+ v.x /= len;
33
+ v.y /= len;
34
+ v.z /= len;
35
+ }
36
+ return len;
37
+ }
38
+ var Jumper3DBehavior = class {
39
+ /**
40
+ * Advance the jump system by one frame.
41
+ *
42
+ * Call order within a frame:
43
+ * 1. Movement controller writes X/Z via transformStore.dirty.velocityX/Z.
44
+ * 2. `tick()` writes Y via ctx.setVerticalVelocity().
45
+ * (Optionally writes X/Z when airborne planar control is active.)
46
+ */
47
+ tick(config, input, ctx, state) {
48
+ const dtMs = ctx.dt * 1e3;
49
+ const now = performance.now();
50
+ let event = "none" /* None */;
51
+ let jumpedThisFrame = false;
52
+ if (ctx.isGrounded) {
53
+ state.coyoteMs = config.coyoteTimeMs;
54
+ if (config.resetJumpsOnGround) {
55
+ state.jumpsUsed = 0;
56
+ }
57
+ if (state.isJumping) {
58
+ state.isJumping = false;
59
+ state.jumpCutApplied = false;
60
+ event = "land" /* Land */;
61
+ }
62
+ } else {
63
+ state.coyoteMs = Math.max(0, state.coyoteMs - dtMs);
64
+ }
65
+ if (input.jumpPressed) {
66
+ state.bufferedJumpMs = config.jumpBufferMs;
67
+ } else {
68
+ state.bufferedJumpMs = Math.max(0, state.bufferedJumpMs - dtMs);
69
+ }
70
+ if (state.isJumping && input.jumpHeld) {
71
+ state.jumpHoldMs += dtMs;
72
+ }
73
+ const wantsJump = state.bufferedJumpMs > 0;
74
+ const isFirstJump = ctx.isGrounded || state.coyoteMs > 0 && state.jumpsUsed === 0;
75
+ const hasAirJumps = state.jumpsUsed > 0 && state.jumpsUsed < config.maxJumps;
76
+ const canJump = isFirstJump || hasAirJumps;
77
+ const cooldownOk = config.minTimeBetweenJumpsMs == null || now - state.lastJumpTimeMs >= config.minTimeBetweenJumpsMs;
78
+ if (wantsJump && canJump && cooldownOk) {
79
+ const vy = jumpVelocityFromHeight(config.gravity, config.jumpHeight);
80
+ ctx.setVerticalVelocity(vy);
81
+ jumpedThisFrame = true;
82
+ if (config.planar?.launchPlanarSpeed != null && !config.planar.preservePlanarSpeed) {
83
+ const dir = input.moveDirWorld;
84
+ if (dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6)) {
85
+ const d = { x: dir.x, y: 0, z: dir.z };
86
+ normalizeMut(d);
87
+ ctx.setHorizontalVelocity(
88
+ d.x * config.planar.launchPlanarSpeed,
89
+ d.z * config.planar.launchPlanarSpeed
90
+ );
91
+ }
92
+ }
93
+ state.jumpsUsed++;
94
+ state.lastJumpTimeMs = now;
95
+ state.bufferedJumpMs = 0;
96
+ state.coyoteMs = 0;
97
+ state.isJumping = true;
98
+ state.jumpHoldMs = 0;
99
+ state.jumpCutApplied = false;
100
+ event = "jump" /* Jump */;
101
+ }
102
+ if (!ctx.isGrounded || jumpedThisFrame) {
103
+ let gravityMul = 1;
104
+ const vy = ctx.velocityY;
105
+ const isFalling = vy < 0;
106
+ if (isFalling) {
107
+ gravityMul = config.fall?.fallGravityMultiplier ?? 1;
108
+ if (input.fastFall && config.fall?.fastFallMultiplier != null) {
109
+ gravityMul *= config.fall.fastFallMultiplier;
110
+ }
111
+ if (event !== "land" /* Land */) {
112
+ event = "fall" /* Fall */;
113
+ }
114
+ } else if (state.isJumping) {
115
+ const vj = config.variableJump;
116
+ if (vj?.enabled && !state.jumpCutApplied) {
117
+ const holdExpired = vj.maxHoldMs != null && state.jumpHoldMs >= vj.maxHoldMs;
118
+ if (!input.jumpHeld || holdExpired) {
119
+ gravityMul = vj.cutGravityMultiplier;
120
+ state.jumpCutApplied = true;
121
+ }
122
+ }
123
+ }
124
+ let newVy = ctx.velocityY - config.gravity * gravityMul * ctx.dt;
125
+ if (config.maxFallSpeed != null) {
126
+ newVy = Math.max(-config.maxFallSpeed, newVy);
127
+ }
128
+ ctx.setVerticalVelocity(newVy);
129
+ } else {
130
+ ctx.setVerticalVelocity(0);
131
+ }
132
+ const airCtrl = config.planar?.airControl;
133
+ if (airCtrl && !ctx.isGrounded) {
134
+ const dir = input.moveDirWorld;
135
+ const hasDir = dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6);
136
+ let vx = ctx.horizontalVelocity.x;
137
+ let vz = ctx.horizontalVelocity.z;
138
+ if (hasDir) {
139
+ const d = { x: dir.x, y: 0, z: dir.z };
140
+ normalizeMut(d);
141
+ vx += d.x * airCtrl.acceleration * ctx.dt;
142
+ vz += d.z * airCtrl.acceleration * ctx.dt;
143
+ const speed = Math.sqrt(vx * vx + vz * vz);
144
+ if (speed > airCtrl.maxPlanarSpeed) {
145
+ const scale = airCtrl.maxPlanarSpeed / speed;
146
+ vx *= scale;
147
+ vz *= scale;
148
+ }
149
+ } else {
150
+ const speed = planarSpeed(vx, vz);
151
+ if (speed > 1e-6) {
152
+ const decel = Math.min(airCtrl.deceleration * ctx.dt, speed);
153
+ const scale = (speed - decel) / speed;
154
+ vx *= scale;
155
+ vz *= scale;
156
+ }
157
+ }
158
+ ctx.setHorizontalVelocity(vx, vz);
159
+ }
160
+ return { event };
161
+ }
162
+ };
163
+ var JumperTickEvent = /* @__PURE__ */ ((JumperTickEvent2) => {
164
+ JumperTickEvent2["None"] = "none";
165
+ JumperTickEvent2["Jump"] = "jump";
166
+ JumperTickEvent2["Fall"] = "fall";
167
+ JumperTickEvent2["Land"] = "land";
168
+ return JumperTickEvent2;
169
+ })(JumperTickEvent || {});
170
+
171
+ // src/lib/behaviors/jumper-3d/jumper-3d-fsm.ts
172
+ var Jumper3DState = /* @__PURE__ */ ((Jumper3DState2) => {
173
+ Jumper3DState2["Grounded"] = "grounded";
174
+ Jumper3DState2["Jumping"] = "jumping";
175
+ Jumper3DState2["Falling"] = "falling";
176
+ return Jumper3DState2;
177
+ })(Jumper3DState || {});
178
+ var Jumper3DEvent = /* @__PURE__ */ ((Jumper3DEvent2) => {
179
+ Jumper3DEvent2["Jump"] = "jump";
180
+ Jumper3DEvent2["Fall"] = "fall";
181
+ Jumper3DEvent2["Land"] = "land";
182
+ return Jumper3DEvent2;
183
+ })(Jumper3DEvent || {});
184
+ var Jumper3DFSM = class {
185
+ constructor(ctx) {
186
+ this.ctx = ctx;
187
+ this.machine = new SyncStateMachine(
188
+ "grounded" /* Grounded */,
189
+ [
190
+ // Grounded
191
+ t("grounded" /* Grounded */, "jump" /* Jump */, "jumping" /* Jumping */),
192
+ t("grounded" /* Grounded */, "fall" /* Fall */, "falling" /* Falling */),
193
+ t("grounded" /* Grounded */, "land" /* Land */, "grounded" /* Grounded */),
194
+ // Jumping
195
+ t("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
196
+ t("jumping" /* Jumping */, "land" /* Land */, "grounded" /* Grounded */),
197
+ t("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
198
+ // Falling
199
+ t("falling" /* Falling */, "land" /* Land */, "grounded" /* Grounded */),
200
+ t("falling" /* Falling */, "jump" /* Jump */, "jumping" /* Jumping */),
201
+ t("falling" /* Falling */, "fall" /* Fall */, "falling" /* Falling */)
202
+ ]
203
+ );
204
+ }
205
+ ctx;
206
+ machine;
207
+ getState() {
208
+ return this.machine.getState();
209
+ }
210
+ dispatch(event) {
211
+ if (this.machine.can(event)) {
212
+ this.machine.syncDispatch(event);
213
+ }
214
+ }
215
+ isJumping() {
216
+ return this.getState() === "jumping" /* Jumping */;
217
+ }
218
+ isFalling() {
219
+ return this.getState() === "falling" /* Falling */;
220
+ }
221
+ isGrounded() {
222
+ return this.getState() === "grounded" /* Grounded */;
223
+ }
224
+ getJumpsUsed() {
225
+ return this.ctx.state.jumpsUsed;
226
+ }
227
+ /**
228
+ * Translate a tick event into an FSM dispatch.
229
+ * Also handles the implicit Grounded→Falling transition when the entity
230
+ * is no longer grounded but no explicit event was emitted (e.g. walked
231
+ * off a ledge without jumping).
232
+ */
233
+ applyTickEvent(tickEvent, isGrounded) {
234
+ switch (tickEvent) {
235
+ case "jump":
236
+ this.dispatch("jump" /* Jump */);
237
+ break;
238
+ case "fall":
239
+ this.dispatch("fall" /* Fall */);
240
+ break;
241
+ case "land":
242
+ this.dispatch("land" /* Land */);
243
+ break;
244
+ default:
245
+ if (!isGrounded && this.getState() === "grounded" /* Grounded */) {
246
+ this.dispatch("fall" /* Fall */);
247
+ }
248
+ if (isGrounded && this.getState() !== "grounded" /* Grounded */) {
249
+ this.dispatch("land" /* Land */);
250
+ }
251
+ break;
252
+ }
253
+ }
254
+ };
255
+
256
+ // src/lib/behaviors/jumper-3d/jumper-3d.descriptor.ts
257
+ var defaultOptions = {
258
+ jumpHeight: 2.5,
259
+ gravity: 20,
260
+ maxJumps: 1,
261
+ resetJumpsOnGround: true,
262
+ coyoteTimeMs: 100,
263
+ jumpBufferMs: 80,
264
+ groundRayLength: 1,
265
+ debugGroundProbe: false
266
+ };
267
+ var JUMPER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:jumper-3d");
268
+ function isJumper3DGrounded(params) {
269
+ if (params.snapToGroundDistance != null) {
270
+ return params.supportToi != null && params.supportToi <= params.snapToGroundDistance && params.velocityY <= 0.25;
271
+ }
272
+ return params.supportToi != null && params.velocityY > -2 && params.velocityY < 2;
273
+ }
274
+ function shouldSnapJumper3DToGround(support, options, velocityY) {
275
+ return support != null && options.snapToGroundDistance != null && velocityY <= 0.25 && support.toi <= options.snapToGroundDistance;
276
+ }
277
+ var Jumper3DBehaviorSystem = class {
278
+ constructor(world, scene, wasmStage, getBehaviorLinks) {
279
+ this.world = world;
280
+ this.scene = scene;
281
+ this.wasmStage = wasmStage;
282
+ this.getBehaviorLinks = getBehaviorLinks;
283
+ this.groundProbe = new GroundProbe3D(world);
284
+ }
285
+ world;
286
+ scene;
287
+ wasmStage;
288
+ getBehaviorLinks;
289
+ behavior = new Jumper3DBehavior();
290
+ groundProbe;
291
+ initializedEntities = /* @__PURE__ */ new Set();
292
+ timeSinceGroundedMs = /* @__PURE__ */ new Map();
293
+ wasJumpHeld = /* @__PURE__ */ new Map();
294
+ attachedRuntimeSlots = /* @__PURE__ */ new Set();
295
+ update(_ecs, delta) {
296
+ const links = this.getBehaviorLinks?.(JUMPER_BEHAVIOR_KEY);
297
+ if (!links) return;
298
+ for (const link of links) {
299
+ const gameEntity = link.entity;
300
+ const jumperRef = link.ref;
301
+ const options = jumperRef.options;
302
+ const handle = gameEntity.runtimeHandle ?? -1;
303
+ if (this.wasmStage && handle >= 0) {
304
+ this.ensureWasmAttached(handle, options);
305
+ const input2 = gameEntity.$jumper;
306
+ const jumpPressed = !!(input2?.jumpPressed || input2?.jumpHeld);
307
+ this.wasmStage.setJumper3DInput(handle, jumpPressed);
308
+ continue;
309
+ }
310
+ if (!gameEntity.body) continue;
311
+ if (!gameEntity.jumper) {
312
+ gameEntity.jumper = createJumpConfig3D(options);
313
+ }
314
+ if (!gameEntity.$jumper) {
315
+ gameEntity.$jumper = createJumpInput3D();
316
+ }
317
+ if (!gameEntity.jumperState) {
318
+ gameEntity.jumperState = createJumpState3D();
319
+ }
320
+ if (!this.initializedEntities.has(gameEntity.uuid)) {
321
+ this.initializedEntities.add(gameEntity.uuid);
322
+ gameEntity.body.setGravityScale(0, true);
323
+ }
324
+ if (!jumperRef.fsm && gameEntity.jumperState) {
325
+ jumperRef.fsm = new Jumper3DFSM({
326
+ state: gameEntity.jumperState
327
+ });
328
+ }
329
+ const rayLength = options.groundRayLength ?? 1;
330
+ const bodyVel = gameEntity.body.linvel();
331
+ const probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);
332
+ const support = this.groundProbe.probeSupport(gameEntity, {
333
+ rayLength,
334
+ mode: "center",
335
+ debug: options.debugGroundProbe ?? false,
336
+ scene: this.scene,
337
+ originYOffset: probeOriginYOffset
338
+ });
339
+ const isGrounded = isJumper3DGrounded({
340
+ supportToi: support?.toi ?? null,
341
+ velocityY: bodyVel.y,
342
+ snapToGroundDistance: options.snapToGroundDistance
343
+ });
344
+ if (shouldSnapJumper3DToGround(support, options, bodyVel.y)) {
345
+ const currentPosition = gameEntity.body.translation();
346
+ gameEntity.body.setTranslation(
347
+ {
348
+ x: currentPosition.x,
349
+ y: getGroundSnapTargetY(gameEntity, support),
350
+ z: currentPosition.z
351
+ },
352
+ true
353
+ );
354
+ }
355
+ let tsg = this.timeSinceGroundedMs.get(gameEntity.uuid) ?? 0;
356
+ if (isGrounded) {
357
+ tsg = 0;
358
+ } else {
359
+ tsg += delta * 1e3;
360
+ }
361
+ this.timeSinceGroundedMs.set(gameEntity.uuid, tsg);
362
+ const config = gameEntity.jumper;
363
+ const input = gameEntity.$jumper;
364
+ const state = gameEntity.jumperState;
365
+ const prevHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;
366
+ const held = input.jumpHeld === true;
367
+ const effectiveInput = {
368
+ ...input,
369
+ jumpPressed: input.jumpPressed || held && !prevHeld,
370
+ jumpReleased: input.jumpReleased || !held && prevHeld
371
+ };
372
+ const store = gameEntity.transformStore;
373
+ const ctx = {
374
+ dt: delta,
375
+ up: { x: 0, y: 1, z: 0 },
376
+ velocityY: bodyVel.y,
377
+ horizontalVelocity: { x: bodyVel.x, z: bodyVel.z },
378
+ isGrounded,
379
+ timeSinceGroundedMs: tsg,
380
+ setVerticalVelocity: (y) => {
381
+ setVelocityIntent(
382
+ store,
383
+ "jumper-3d",
384
+ { y },
385
+ { mode: "replace", priority: 20 }
386
+ );
387
+ ctx.velocityY = y;
388
+ },
389
+ setHorizontalVelocity: (x, z) => {
390
+ setVelocityIntent(
391
+ store,
392
+ "jumper-3d",
393
+ { x, z },
394
+ { mode: "replace", priority: 20 }
395
+ );
396
+ ctx.horizontalVelocity.x = x;
397
+ ctx.horizontalVelocity.z = z;
398
+ }
399
+ };
400
+ const result = this.behavior.tick(config, effectiveInput, ctx, state);
401
+ this.wasJumpHeld.set(gameEntity.uuid, held);
402
+ if (jumperRef.fsm) {
403
+ jumperRef.fsm.applyTickEvent(
404
+ result.event,
405
+ isGrounded
406
+ );
407
+ }
408
+ }
409
+ }
410
+ ensureWasmAttached(handle, options) {
411
+ if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
412
+ const jumpForce = Math.sqrt(2 * (options.gravity ?? 20) * (options.jumpHeight ?? 2.5));
413
+ this.wasmStage.attachJumper3D(handle, {
414
+ jumpForce,
415
+ maxJumps: options.maxJumps ?? 1,
416
+ gravity: options.gravity ?? 20
417
+ });
418
+ this.attachedRuntimeSlots.add(handle);
419
+ }
420
+ destroy(_ecs) {
421
+ this.groundProbe.destroy();
422
+ this.initializedEntities.clear();
423
+ this.timeSinceGroundedMs.clear();
424
+ this.wasJumpHeld.clear();
425
+ this.attachedRuntimeSlots.clear();
426
+ }
427
+ };
428
+ var Jumper3D = defineBehavior({
429
+ name: "jumper-3d",
430
+ defaultOptions,
431
+ systemFactory: (ctx) => new Jumper3DBehaviorSystem(
432
+ ctx.world,
433
+ ctx.scene,
434
+ ctx.wasmStage ?? null,
435
+ ctx.getBehaviorLinks
436
+ ),
437
+ createHandle: (ref) => ({
438
+ getState: () => ref.fsm?.getState() ?? "grounded" /* Grounded */,
439
+ isJumping: () => ref.fsm?.isJumping() ?? false,
440
+ getJumpsUsed: () => ref.fsm?.getJumpsUsed() ?? 0,
441
+ getJumpsRemaining: () => {
442
+ const maxJumps = ref.options.maxJumps ?? 1;
443
+ const used = ref.fsm?.getJumpsUsed() ?? 0;
444
+ return maxJumps - used;
445
+ }
446
+ })
447
+ });
448
+
449
+ export {
450
+ Jumper3DBehavior,
451
+ JumperTickEvent,
452
+ Jumper3DState,
453
+ Jumper3DEvent,
454
+ Jumper3DFSM,
455
+ Jumper3D
456
+ };
457
+ //# sourceMappingURL=chunk-F3KRX2V4.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/jumper-3d/jumper-3d.behavior.ts","../src/lib/behaviors/jumper-3d/jumper-3d-fsm.ts","../src/lib/behaviors/jumper-3d/jumper-3d.descriptor.ts"],"sourcesContent":["/**\n * Jumper 3D Behavior — Pure Jump Physics\n *\n * A stateless tick function that receives configuration, input, context, and\n * state, then applies jump physics through the context's velocity helpers.\n *\n * Vertical velocity (Y) is owned exclusively by this behavior.\n * Horizontal velocity (X/Z) is only touched when airborne planar control\n * is configured; otherwise the movement controller owns X/Z.\n */\n\nimport type {\n\tJumpConfig3D,\n\tJumpInput3D,\n\tJumpContext3D,\n\tJumpState3D,\n} from './components';\n\n/** v = sqrt(2 * g * h) */\nfunction jumpVelocityFromHeight(gravity: number, height: number): number {\n\treturn Math.sqrt(2 * gravity * height);\n}\n\nfunction planarSpeed(x: number, z: number): number {\n\treturn Math.sqrt(x * x + z * z);\n}\n\nfunction normalizeMut(v: { x: number; y: number; z: number }): number {\n\tconst len = Math.sqrt(v.x * v.x + v.y * v.y + v.z * v.z);\n\tif (len > 1e-6) {\n\t\tv.x /= len;\n\t\tv.y /= len;\n\t\tv.z /= len;\n\t}\n\treturn len;\n}\n\nexport class Jumper3DBehavior {\n\t/**\n\t * Advance the jump system by one frame.\n\t *\n\t * Call order within a frame:\n\t * 1. Movement controller writes X/Z via transformStore.dirty.velocityX/Z.\n\t * 2. `tick()` writes Y via ctx.setVerticalVelocity().\n\t * (Optionally writes X/Z when airborne planar control is active.)\n\t */\n\ttick(\n\t\tconfig: JumpConfig3D,\n\t\tinput: JumpInput3D,\n\t\tctx: JumpContext3D,\n\t\tstate: JumpState3D,\n\t): JumperTickResult {\n\t\tconst dtMs = ctx.dt * 1000;\n\t\tconst now = performance.now();\n\t\tlet event: JumperTickEvent = JumperTickEvent.None;\n\t\tlet jumpedThisFrame = false;\n\n\t\t// ── 1. Ground-contact bookkeeping ──────────────────────────────────\n\t\tif (ctx.isGrounded) {\n\t\t\tstate.coyoteMs = config.coyoteTimeMs;\n\t\t\tif (config.resetJumpsOnGround) {\n\t\t\t\tstate.jumpsUsed = 0;\n\t\t\t}\n\t\t\tif (state.isJumping) {\n\t\t\t\tstate.isJumping = false;\n\t\t\t\tstate.jumpCutApplied = false;\n\t\t\t\tevent = JumperTickEvent.Land;\n\t\t\t}\n\t\t} else {\n\t\t\tstate.coyoteMs = Math.max(0, state.coyoteMs - dtMs);\n\t\t}\n\n\t\t// ── 2. Jump buffer ─────────────────────────────────────────────────\n\t\tif (input.jumpPressed) {\n\t\t\tstate.bufferedJumpMs = config.jumpBufferMs;\n\t\t} else {\n\t\t\tstate.bufferedJumpMs = Math.max(0, state.bufferedJumpMs - dtMs);\n\t\t}\n\n\t\t// ── 3. Variable-jump hold tracking ─────────────────────────────────\n\t\tif (state.isJumping && input.jumpHeld) {\n\t\t\tstate.jumpHoldMs += dtMs;\n\t\t}\n\n\t\t// ── 4. Jump eligibility & execution ────────────────────────────────\n\t\tconst wantsJump = state.bufferedJumpMs > 0;\n\n\t\tconst isFirstJump =\n\t\t\tctx.isGrounded || (state.coyoteMs > 0 && state.jumpsUsed === 0);\n\t\tconst hasAirJumps = state.jumpsUsed > 0 && state.jumpsUsed < config.maxJumps;\n\t\tconst canJump = isFirstJump || hasAirJumps;\n\n\t\t\tconst cooldownOk =\n\t\t\t\tconfig.minTimeBetweenJumpsMs == null ||\n\t\t\t\tnow - state.lastJumpTimeMs >= config.minTimeBetweenJumpsMs;\n\n\t\t\tif (wantsJump && canJump && cooldownOk) {\n\t\t\t\tconst vy = jumpVelocityFromHeight(config.gravity, config.jumpHeight);\n\n\t\t\t\tctx.setVerticalVelocity(vy);\n\t\t\t\tjumpedThisFrame = true;\n\n\t\t\tif (config.planar?.launchPlanarSpeed != null && !config.planar.preservePlanarSpeed) {\n\t\t\t\tconst dir = input.moveDirWorld;\n\t\t\t\tif (dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6)) {\n\t\t\t\t\tconst d = { x: dir.x, y: 0, z: dir.z };\n\t\t\t\t\tnormalizeMut(d);\n\t\t\t\t\tctx.setHorizontalVelocity(\n\t\t\t\t\t\td.x * config.planar.launchPlanarSpeed,\n\t\t\t\t\t\td.z * config.planar.launchPlanarSpeed,\n\t\t\t\t\t);\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tstate.jumpsUsed++;\n\t\t\tstate.lastJumpTimeMs = now;\n\t\t\tstate.bufferedJumpMs = 0;\n\t\t\tstate.coyoteMs = 0;\n\t\t\tstate.isJumping = true;\n\t\t\tstate.jumpHoldMs = 0;\n\t\t\tstate.jumpCutApplied = false;\n\n\t\t\tevent = JumperTickEvent.Jump;\n\t\t}\n\n\t\t// ── 5. Gravity (with variable-jump and fall multipliers) ───────────\n\t\t// Treat a jump-launch frame as airborne so we don't immediately clamp\n\t\t// the freshly applied jump velocity back to zero.\n\t\tif (!ctx.isGrounded || jumpedThisFrame) {\n\t\t\tlet gravityMul = 1;\n\t\t\tconst vy = ctx.velocityY;\n\t\t\tconst isFalling = vy < 0;\n\n\t\t\tif (isFalling) {\n\t\t\t\tgravityMul = config.fall?.fallGravityMultiplier ?? 1;\n\t\t\t\tif (input.fastFall && config.fall?.fastFallMultiplier != null) {\n\t\t\t\t\tgravityMul *= config.fall.fastFallMultiplier;\n\t\t\t\t}\n\t\t\t\tif (event !== JumperTickEvent.Land) {\n\t\t\t\t\tevent = JumperTickEvent.Fall;\n\t\t\t\t}\n\t\t\t} else if (state.isJumping) {\n\t\t\t\tconst vj = config.variableJump;\n\t\t\t\tif (vj?.enabled && !state.jumpCutApplied) {\n\t\t\t\t\tconst holdExpired =\n\t\t\t\t\t\tvj.maxHoldMs != null && state.jumpHoldMs >= vj.maxHoldMs;\n\t\t\t\t\tif (!input.jumpHeld || holdExpired) {\n\t\t\t\t\t\tgravityMul = vj.cutGravityMultiplier;\n\t\t\t\t\t\tstate.jumpCutApplied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet newVy = ctx.velocityY - config.gravity * gravityMul * ctx.dt;\n\n\t\t\tif (config.maxFallSpeed != null) {\n\t\t\t\tnewVy = Math.max(-config.maxFallSpeed, newVy);\n\t\t\t}\n\n\t\t\tctx.setVerticalVelocity(newVy);\n\t\t} else {\n\t\t\t// Grounded: clamp Y to 0 so the entity doesn't drift\n\t\t\tctx.setVerticalVelocity(0);\n\t\t}\n\n\t\t// ── 6. Airborne planar control ─────────────────────────────────────\n\t\tconst airCtrl = config.planar?.airControl;\n\t\tif (airCtrl && !ctx.isGrounded) {\n\t\t\tconst dir = input.moveDirWorld;\n\t\t\tconst hasDir = dir && (Math.abs(dir.x) > 1e-6 || Math.abs(dir.z) > 1e-6);\n\n\t\t\tlet vx = ctx.horizontalVelocity.x;\n\t\t\tlet vz = ctx.horizontalVelocity.z;\n\n\t\t\tif (hasDir) {\n\t\t\t\tconst d = { x: dir!.x, y: 0, z: dir!.z };\n\t\t\t\tnormalizeMut(d);\n\n\t\t\t\tvx += d.x * airCtrl.acceleration * ctx.dt;\n\t\t\t\tvz += d.z * airCtrl.acceleration * ctx.dt;\n\n\t\t\t\tconst speed = Math.sqrt(vx * vx + vz * vz);\n\t\t\t\tif (speed > airCtrl.maxPlanarSpeed) {\n\t\t\t\t\tconst scale = airCtrl.maxPlanarSpeed / speed;\n\t\t\t\t\tvx *= scale;\n\t\t\t\t\tvz *= scale;\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tconst speed = planarSpeed(vx, vz);\n\t\t\t\tif (speed > 1e-6) {\n\t\t\t\t\tconst decel = Math.min(airCtrl.deceleration * ctx.dt, speed);\n\t\t\t\t\tconst scale = (speed - decel) / speed;\n\t\t\t\t\tvx *= scale;\n\t\t\t\t\tvz *= scale;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tctx.setHorizontalVelocity(vx, vz);\n\t\t}\n\n\t\treturn { event };\n\t}\n}\n\n/** Events emitted by a single tick (used by FSM). */\nexport enum JumperTickEvent {\n\tNone = 'none',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface JumperTickResult {\n\tevent: JumperTickEvent;\n}\n","/**\n * Jumper 3D Finite State Machine\n *\n * Three high-level states driven by events emitted from the behavior tick:\n * Grounded ──Jump──▶ Jumping\n * Grounded ──Fall──▶ Falling (walked off a ledge)\n * Jumping ──Fall──▶ Falling (apex / descending)\n * Jumping ──Jump──▶ Jumping (multi-jump)\n * Jumping ──Land──▶ Grounded (rare — very short hop)\n * Falling ──Land──▶ Grounded\n * Falling ──Jump──▶ Jumping (air-jump while falling)\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { JumpState3D } from './components';\n\nexport enum Jumper3DState {\n\tGrounded = 'grounded',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n}\n\nexport enum Jumper3DEvent {\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface Jumper3DFSMContext {\n\tstate: JumpState3D;\n}\n\nexport class Jumper3DFSM {\n\tmachine: SyncStateMachine<Jumper3DState, Jumper3DEvent, never>;\n\n\tconstructor(private ctx: Jumper3DFSMContext) {\n\t\tthis.machine = new SyncStateMachine<Jumper3DState, Jumper3DEvent, never>(\n\t\t\tJumper3DState.Grounded,\n\t\t\t[\n\t\t\t\t// Grounded\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\n\t\t\t\t// Jumping\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\n\t\t\t\t// Falling\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Jumper3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Jumper3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisJumping(): boolean {\n\t\treturn this.getState() === Jumper3DState.Jumping;\n\t}\n\n\tisFalling(): boolean {\n\t\treturn this.getState() === Jumper3DState.Falling;\n\t}\n\n\tisGrounded(): boolean {\n\t\treturn this.getState() === Jumper3DState.Grounded;\n\t}\n\n\tgetJumpsUsed(): number {\n\t\treturn this.ctx.state.jumpsUsed;\n\t}\n\n\t/**\n\t * Translate a tick event into an FSM dispatch.\n\t * Also handles the implicit Grounded→Falling transition when the entity\n\t * is no longer grounded but no explicit event was emitted (e.g. walked\n\t * off a ledge without jumping).\n\t */\n\tapplyTickEvent(tickEvent: string, isGrounded: boolean): void {\n\t\tswitch (tickEvent) {\n\t\t\tcase 'jump':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Jump);\n\t\t\t\tbreak;\n\t\t\tcase 'fall':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\tbreak;\n\t\t\tcase 'land':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// No explicit event — detect implicit fall\n\t\t\t\tif (!isGrounded && this.getState() === Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\t}\n\t\t\t\tif (isGrounded && this.getState() !== Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n","/**\n * Jumper 3D Behavior Descriptor\n *\n * Type-safe descriptor for the jumper-3d behavior system.\n * Provides `entity.use()` API for composable 3D jumping.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { setVelocityIntent } from '../../actions/capabilities/velocity-intents';\nimport { Jumper3DBehavior } from './jumper-3d.behavior';\nimport { Jumper3DFSM, Jumper3DState } from './jumper-3d-fsm';\nimport {\n\tGroundProbe3D,\n\tgetGroundAnchorOffsetY,\n\tgetGroundSnapTargetY,\n\ttype GroundProbeSupportHit,\n} from '../shared/ground-probe-3d';\nimport {\n\tcreateJumpConfig3D,\n\tcreateJumpInput3D,\n\tcreateJumpState3D,\n\ttype JumpConfig3D,\n\ttype JumpInput3D,\n\ttype JumpState3D,\n\ttype JumpContext3D,\n} from './components';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options exposed through entity.use()\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DBehaviorOptions {\n\t/** Desired peak height in world units (default: 2.5) */\n\tjumpHeight?: number;\n\t/** Gravity magnitude applied by the behavior (default: 20) */\n\tgravity?: number;\n\t/** Terminal fall speed (optional) */\n\tmaxFallSpeed?: number;\n\t/** Max concurrent jumps, 1 = single, 2 = double, etc. (default: 1) */\n\tmaxJumps?: number;\n\t/** Reset jump counter on ground (default: true) */\n\tresetJumpsOnGround?: boolean;\n\t/** Reset jump counter on wall contact (default: undefined) */\n\tresetJumpsOnWall?: boolean;\n\t/** Coyote time in ms (default: 100) */\n\tcoyoteTimeMs?: number;\n\t/** Jump buffer in ms (default: 80) */\n\tjumpBufferMs?: number;\n\t/** Min cooldown between jumps in ms (optional) */\n\tminTimeBetweenJumpsMs?: number;\n\t/** Variable jump height config (optional) */\n\tvariableJump?: JumpConfig3D['variableJump'];\n\t/** Planar control config (optional) */\n\tplanar?: JumpConfig3D['planar'];\n\t/** Fall tuning (optional) */\n\tfall?: JumpConfig3D['fall'];\n\t/** Ground-detection ray length (default: 1.0) */\n\tgroundRayLength?: number;\n\t/** Max support distance that counts as grounded and can snap to ground */\n\tsnapToGroundDistance?: number;\n\t/** Enable debug visualization for ground probe rays (default: false) */\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Jumper3DBehaviorOptions = {\n\tjumpHeight: 2.5,\n\tgravity: 20,\n\tmaxJumps: 1,\n\tresetJumpsOnGround: true,\n\tcoyoteTimeMs: 100,\n\tjumpBufferMs: 80,\n\tgroundRayLength: 1.0,\n\tdebugGroundProbe: false,\n};\n\nconst JUMPER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:jumper-3d');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Entity shape expected by the system\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DEntity {\n\tuuid: string;\n\tbody: any;\n\ttransformStore: any;\n\tjumper: JumpConfig3D;\n\t$jumper: JumpInput3D;\n\tjumperState: JumpState3D;\n}\n\nexport function isJumper3DGrounded(params: {\n\tsupportToi?: number | null;\n\tvelocityY: number;\n\tsnapToGroundDistance?: number;\n}): boolean {\n\tif (params.snapToGroundDistance != null) {\n\t\treturn (\n\t\t\tparams.supportToi != null &&\n\t\t\tparams.supportToi <= params.snapToGroundDistance &&\n\t\t\tparams.velocityY <= 0.25\n\t\t);\n\t}\n\n\treturn (\n\t\tparams.supportToi != null &&\n\t\tparams.velocityY > -2.0 &&\n\t\tparams.velocityY < 2.0\n\t);\n}\n\nfunction shouldSnapJumper3DToGround(\n\tsupport: GroundProbeSupportHit | null,\n\toptions: Jumper3DBehaviorOptions,\n\tvelocityY: number,\n): support is GroundProbeSupportHit {\n\treturn (\n\t\tsupport != null &&\n\t\toptions.snapToGroundDistance != null &&\n\t\tvelocityY <= 0.25 &&\n\t\tsupport.toi <= options.snapToGroundDistance\n\t);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior System adapter\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass Jumper3DBehaviorSystem implements BehaviorSystem {\n\tprivate behavior = new Jumper3DBehavior();\n\tprivate groundProbe: GroundProbe3D;\n\tprivate initializedEntities = new Set<string>();\n\tprivate timeSinceGroundedMs = new Map<string, number>();\n\tprivate wasJumpHeld = new Map<string, boolean>();\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate scene: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.groundProbe = new GroundProbe3D(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(JUMPER_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 jumperRef = link.ref as any;\n\n\t\t\tconst options = jumperRef.options as Jumper3DBehaviorOptions;\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, options);\n\t\t\t\tconst input = gameEntity.$jumper as JumpInput3D | undefined;\n\t\t\t\tconst jumpPressed = !!(input?.jumpPressed || input?.jumpHeld);\n\t\t\t\tthis.wasmStage.setJumper3DInput(handle, jumpPressed);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\n\t\t\t// ── Lazy component init ────────────────────────────────────────\n\t\t\tif (!gameEntity.jumper) {\n\t\t\t\tgameEntity.jumper = createJumpConfig3D(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$jumper) {\n\t\t\t\tgameEntity.$jumper = createJumpInput3D();\n\t\t\t}\n\t\t\tif (!gameEntity.jumperState) {\n\t\t\t\tgameEntity.jumperState = createJumpState3D();\n\t\t\t}\n\n\t\t\tif (!this.initializedEntities.has(gameEntity.uuid)) {\n\t\t\t\tthis.initializedEntities.add(gameEntity.uuid);\n\t\t\t\tgameEntity.body.setGravityScale(0, true);\n\t\t\t}\n\n\t\t\t// ── Lazy FSM init ──────────────────────────────────────────────\n\t\t\tif (!jumperRef.fsm && gameEntity.jumperState) {\n\t\t\t\tjumperRef.fsm = new Jumper3DFSM({\n\t\t\t\t\tstate: gameEntity.jumperState,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\t// ── Ground detection ───────────────────────────────────────────\n\t\t\tconst rayLength = options.groundRayLength ?? 1.0;\n\t\t\tconst bodyVel = gameEntity.body.linvel();\n\t\t\tconst probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);\n\t\t\tconst support = this.groundProbe.probeSupport(gameEntity, {\n\t\t\t\trayLength,\n\t\t\t\tmode: 'center',\n\t\t\t\tdebug: options.debugGroundProbe ?? false,\n\t\t\t\tscene: this.scene,\n\t\t\t\toriginYOffset: probeOriginYOffset,\n\t\t\t});\n\t\t\tconst isGrounded = isJumper3DGrounded({\n\t\t\t\tsupportToi: support?.toi ?? null,\n\t\t\t\tvelocityY: bodyVel.y,\n\t\t\t\tsnapToGroundDistance: options.snapToGroundDistance,\n\t\t\t});\n\t\t\tif (shouldSnapJumper3DToGround(support, options, bodyVel.y)) {\n\t\t\t\tconst currentPosition = gameEntity.body.translation();\n\t\t\t\tgameEntity.body.setTranslation(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: currentPosition.x,\n\t\t\t\t\t\ty: getGroundSnapTargetY(gameEntity, support),\n\t\t\t\t\t\tz: currentPosition.z,\n\t\t\t\t\t},\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet tsg = this.timeSinceGroundedMs.get(gameEntity.uuid) ?? 0;\n\t\t\tif (isGrounded) {\n\t\t\t\ttsg = 0;\n\t\t\t} else {\n\t\t\t\ttsg += delta * 1000;\n\t\t\t}\n\t\t\tthis.timeSinceGroundedMs.set(gameEntity.uuid, tsg);\n\n\t\t\t// ── Build context ──────────────────────────────────────────────\n\t\t\t// Each behavior owns its own velocity axes. The jumper only\n\t\t\t// reads/writes Y; horizontal axes are left to the movement\n\t\t\t// controller. We read the current Y from the physics body\n\t\t\t// (post-step) so the jumper always sees the real vertical state.\n\t\t\tconst config: JumpConfig3D = gameEntity.jumper;\n\t\t\tconst input: JumpInput3D = gameEntity.$jumper;\n\t\t\tconst state: JumpState3D = gameEntity.jumperState;\n\t\t\tconst prevHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;\n\t\t\tconst held = input.jumpHeld === true;\n\n\t\t\t// Input callbacks run after behavior systems in the frame loop.\n\t\t\t// Reconstruct button edges from held-state history so one-frame\n\t\t\t// `pressed`/`released` pulses are not missed by the jumper tick.\n\t\t\tconst effectiveInput: JumpInput3D = {\n\t\t\t\t...input,\n\t\t\t\tjumpPressed: input.jumpPressed || (held && !prevHeld),\n\t\t\t\tjumpReleased: input.jumpReleased || (!held && prevHeld),\n\t\t\t};\n\n\t\t\tconst store = gameEntity.transformStore;\n\n\t\t\tconst ctx: JumpContext3D = {\n\t\t\t\tdt: delta,\n\t\t\t\tup: { x: 0, y: 1, z: 0 },\n\t\t\t\tvelocityY: bodyVel.y,\n\t\t\t\thorizontalVelocity: { x: bodyVel.x, z: bodyVel.z },\n\t\t\t\tisGrounded,\n\t\t\t\ttimeSinceGroundedMs: tsg,\n\t\t\t\tsetVerticalVelocity: (y: number) => {\n\t\t\t\t\tsetVelocityIntent(\n\t\t\t\t\t\tstore,\n\t\t\t\t\t\t'jumper-3d',\n\t\t\t\t\t\t{ y },\n\t\t\t\t\t\t{ mode: 'replace', priority: 20 },\n\t\t\t\t\t);\n\t\t\t\t\tctx.velocityY = y;\n\t\t\t\t},\n\t\t\t\tsetHorizontalVelocity: (x: number, z: number) => {\n\t\t\t\t\tsetVelocityIntent(\n\t\t\t\t\t\tstore,\n\t\t\t\t\t\t'jumper-3d',\n\t\t\t\t\t\t{ x, z },\n\t\t\t\t\t\t{ mode: 'replace', priority: 20 },\n\t\t\t\t\t);\n\t\t\t\t\tctx.horizontalVelocity.x = x;\n\t\t\t\t\tctx.horizontalVelocity.z = z;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\t// ── Tick ───────────────────────────────────────────────────────\n\t\t\tconst result = this.behavior.tick(config, effectiveInput, ctx, state);\n\t\t\tthis.wasJumpHeld.set(gameEntity.uuid, held);\n\n\t\t\t// ── FSM sync ───────────────────────────────────────────────────\n\t\t\tif (jumperRef.fsm) {\n\t\t\t\t(jumperRef.fsm as Jumper3DFSM).applyTickEvent(\n\t\t\t\t\tresult.event,\n\t\t\t\t\tisGrounded,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Jumper3DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst jumpForce = Math.sqrt(2 * (options.gravity ?? 20) * (options.jumpHeight ?? 2.5));\n\t\tthis.wasmStage.attachJumper3D(handle, {\n\t\t\tjumpForce,\n\t\t\tmaxJumps: options.maxJumps ?? 1,\n\t\t\tgravity: options.gravity ?? 20,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.groundProbe.destroy();\n\t\tthis.initializedEntities.clear();\n\t\tthis.timeSinceGroundedMs.clear();\n\t\tthis.wasJumpHeld.clear();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public descriptor\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Jumper3D — composable 3D jump behavior.\n *\n * Provides height-based jumping, multi-jump, coyote time, jump buffering,\n * variable jump height, configurable fall gravity, and optional air control.\n *\n * Designed to compose with any movement controller (e.g. FirstPersonController)\n * — the controller writes X/Z velocity and Jumper3D writes Y velocity.\n *\n * @example\n * ```typescript\n * import { Jumper3D } from \"@zylem/game-lib/behavior\";\n *\n * const jumper = player.use(Jumper3D, {\n * jumpHeight: 2.5,\n * gravity: 20,\n * maxJumps: 2,\n * coyoteTimeMs: 100,\n * jumpBufferMs: 80,\n * variableJump: { enabled: true, cutGravityMultiplier: 3 },\n * fall: { fallGravityMultiplier: 1.5 },\n * });\n *\n * player.onUpdate(({ inputs }) => {\n * player.$jumper.jumpPressed = inputs.p1.buttons.A.pressed;\n * player.$jumper.jumpHeld = inputs.p1.buttons.A.held > 0;\n * player.$jumper.jumpReleased = inputs.p1.buttons.A.released;\n *\n * const state = jumper.getState(); // Grounded | Jumping | Falling\n * });\n * ```\n */\nexport const Jumper3D = defineBehavior<\n\tJumper3DBehaviorOptions,\n\t{\n\t\tgetState: () => Jumper3DState;\n\t\tisJumping: () => boolean;\n\t\tgetJumpsUsed: () => number;\n\t\tgetJumpsRemaining: () => number;\n\t},\n\tJumper3DEntity\n>({\n\tname: 'jumper-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Jumper3DBehaviorSystem(\n\t\t\tctx.world,\n\t\t\tctx.scene,\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getState() ??\n\t\t\tJumper3DState.Grounded,\n\t\tisJumping: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.isJumping() ?? false,\n\t\tgetJumpsUsed: () =>\n\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getJumpsUsed() ?? 0,\n\t\tgetJumpsRemaining: () => {\n\t\t\tconst maxJumps = ref.options.maxJumps ?? 1;\n\t\t\tconst used =\n\t\t\t\t(ref.fsm as Jumper3DFSM | undefined)?.getJumpsUsed() ?? 0;\n\t\t\treturn maxJumps - used;\n\t\t},\n\t}),\n});\n"],"mappings":"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","names":["JumperTickEvent","Jumper3DState","Jumper3DEvent","input"]}
@@ -0,0 +1,53 @@
1
+ import {
2
+ getEntityFacingAngle2D,
3
+ resolveAim2D
4
+ } from "./chunk-6C6NXNEW.js";
5
+ import {
6
+ createTopDownMovementInputComponent
7
+ } from "./chunk-EIWQM6U3.js";
8
+
9
+ // src/lib/coordinators/top-down-shooter.coordinator.ts
10
+ var TopDownShooterCoordinator = class {
11
+ constructor(entity, shooter, stage) {
12
+ this.entity = entity;
13
+ this.shooter = shooter;
14
+ this.stage = stage;
15
+ }
16
+ entity;
17
+ shooter;
18
+ stage;
19
+ update(input) {
20
+ if (!this.entity.$topDownMovement) {
21
+ this.entity.$topDownMovement = createTopDownMovementInputComponent();
22
+ }
23
+ this.entity.$topDownMovement.moveX = input.moveX;
24
+ this.entity.$topDownMovement.moveY = input.moveY;
25
+ this.entity.$topDownMovement.faceX = input.aimX;
26
+ this.entity.$topDownMovement.faceY = input.aimY;
27
+ if (!input.shootPressed && !input.shootHeld) {
28
+ return;
29
+ }
30
+ const sourcePosition = this.entity.body?.translation();
31
+ if (!sourcePosition) {
32
+ return;
33
+ }
34
+ const aim = resolveAim2D(
35
+ input.aimX,
36
+ input.aimY,
37
+ getEntityFacingAngle2D(this.entity)
38
+ );
39
+ void this.shooter.fire({
40
+ source: this.entity,
41
+ stage: this.stage,
42
+ target: {
43
+ x: sourcePosition.x + aim.direction.x,
44
+ y: sourcePosition.y + aim.direction.y
45
+ }
46
+ });
47
+ }
48
+ };
49
+
50
+ export {
51
+ TopDownShooterCoordinator
52
+ };
53
+ //# sourceMappingURL=chunk-FPTWVCPN.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/top-down-shooter.coordinator.ts"],"sourcesContent":["import {\n\tcreateTopDownMovementInputComponent,\n\ttype TopDownMovementInputComponent,\n} from '../behaviors/top-down-movement/components';\nimport type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../behaviors/shooter-2d/shooter-2d.descriptor';\nimport { getEntityFacingAngle2D, resolveAim2D } from './shared/top-down-aim';\n\nexport interface TopDownShooterInput {\n\tmoveX: number;\n\tmoveY: number;\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\ntype TopDownShooterEntity = Shooter2DSourceEntity & {\n\t$topDownMovement?: TopDownMovementInputComponent;\n};\n\nexport class TopDownShooterCoordinator {\n\tconstructor(\n\t\tprivate entity: TopDownShooterEntity,\n\t\tprivate shooter: Shooter2DHandle,\n\t\tprivate stage: Shooter2DStageLike,\n\t) {}\n\n\tpublic update(input: TopDownShooterInput): void {\n\t\tif (!this.entity.$topDownMovement) {\n\t\t\tthis.entity.$topDownMovement = createTopDownMovementInputComponent();\n\t\t}\n\n\t\tthis.entity.$topDownMovement.moveX = input.moveX;\n\t\tthis.entity.$topDownMovement.moveY = input.moveY;\n\t\tthis.entity.$topDownMovement.faceX = input.aimX;\n\t\tthis.entity.$topDownMovement.faceY = input.aimY;\n\n\t\tif (!input.shootPressed && !input.shootHeld) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst sourcePosition = this.entity.body?.translation();\n\t\tif (!sourcePosition) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst aim = resolveAim2D(\n\t\t\tinput.aimX,\n\t\t\tinput.aimY,\n\t\t\tgetEntityFacingAngle2D(this.entity),\n\t\t);\n\t\tvoid this.shooter.fire({\n\t\t\tsource: this.entity,\n\t\t\tstage: this.stage,\n\t\t\ttarget: {\n\t\t\t\tx: sourcePosition.x + aim.direction.x,\n\t\t\t\ty: sourcePosition.y + aim.direction.y,\n\t\t\t},\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;;AAwBO,IAAM,4BAAN,MAAgC;AAAA,EACtC,YACS,QACA,SACA,OACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EAGF,OAAO,OAAkC;AAC/C,QAAI,CAAC,KAAK,OAAO,kBAAkB;AAClC,WAAK,OAAO,mBAAmB,oCAAoC;AAAA,IACpE;AAEA,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAC3C,SAAK,OAAO,iBAAiB,QAAQ,MAAM;AAE3C,QAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C;AAAA,IACD;AAEA,UAAM,iBAAiB,KAAK,OAAO,MAAM,YAAY;AACrD,QAAI,CAAC,gBAAgB;AACpB;AAAA,IACD;AAEA,UAAM,MAAM;AAAA,MACX,MAAM;AAAA,MACN,MAAM;AAAA,MACN,uBAAuB,KAAK,MAAM;AAAA,IACnC;AACA,SAAK,KAAK,QAAQ,KAAK;AAAA,MACtB,QAAQ,KAAK;AAAA,MACb,OAAO,KAAK;AAAA,MACZ,QAAQ;AAAA,QACP,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,QACpC,GAAG,eAAe,IAAI,IAAI,UAAU;AAAA,MACrC;AAAA,IACD,CAAC;AAAA,EACF;AACD;","names":[]}
@@ -0,0 +1,37 @@
1
+ import {
2
+ getBounds2DNormalFromHits
3
+ } from "./chunk-KDY5N6J4.js";
4
+
5
+ // src/lib/coordinators/boundary-ricochet.coordinator.ts
6
+ var BoundaryRicochetCoordinator = class {
7
+ constructor(entity, boundary, ricochet) {
8
+ this.entity = entity;
9
+ this.boundary = boundary;
10
+ this.ricochet = ricochet;
11
+ }
12
+ entity;
13
+ boundary;
14
+ ricochet;
15
+ /**
16
+ * Update loop - call this every frame.
17
+ * Applies ricochet via transformStore and notifies listeners.
18
+ *
19
+ * @returns true if ricochet was applied, false otherwise
20
+ */
21
+ update() {
22
+ const hits = this.boundary.getLastHits();
23
+ if (!hits) return false;
24
+ const anyHit = hits.left || hits.right || hits.top || hits.bottom;
25
+ if (!anyHit) return false;
26
+ const normal = getBounds2DNormalFromHits(hits);
27
+ return this.ricochet.applyRicochet({
28
+ entity: this.entity,
29
+ contact: { normal: { x: normal.x, y: normal.y } }
30
+ });
31
+ }
32
+ };
33
+
34
+ export {
35
+ BoundaryRicochetCoordinator
36
+ };
37
+ //# sourceMappingURL=chunk-GCNRXAKH.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/boundary-ricochet.coordinator.ts"],"sourcesContent":["import { GameEntity } from '../entities/entity';\nimport { Ricochet2DHandle } from '../behaviors/ricochet-2d/ricochet-2d.descriptor';\nimport { WorldBoundary2DHandle } from '../behaviors/world-boundary-2d/world-boundary-2d.descriptor';\nimport { MoveableEntity } from '../actions/capabilities/moveable';\nimport { getBounds2DNormalFromHits } from '../behaviors/shared/bounds-2d';\n\n/**\n * Coordinator that bridges WorldBoundary2DBehavior and Ricochet2DBehavior.\n * \n * Automatically handles:\n * 1. Checking boundary hits\n * 2. Computing collision normals\n * 3. Requesting ricochet result\n * 4. Applying movement\n */\nexport class BoundaryRicochetCoordinator {\n constructor(\n private entity: GameEntity<any> & MoveableEntity,\n private boundary: WorldBoundary2DHandle,\n private ricochet: Ricochet2DHandle\n ) {}\n\n /**\n * Update loop - call this every frame.\n * Applies ricochet via transformStore and notifies listeners.\n * \n * @returns true if ricochet was applied, false otherwise\n */\n public update(): boolean {\n const hits = this.boundary.getLastHits();\n if (!hits) return false;\n\n const anyHit = hits.left || hits.right || hits.top || hits.bottom;\n if (!anyHit) return false;\n\n const normal = getBounds2DNormalFromHits(hits);\n\n // Apply ricochet (also emits to listeners)\n return this.ricochet.applyRicochet({\n entity: this.entity,\n contact: { normal: { x: normal.x, y: normal.y } },\n });\n }\n}\n"],"mappings":";;;;;AAeO,IAAM,8BAAN,MAAkC;AAAA,EACrC,YACY,QACA,UACA,UACV;AAHU;AACA;AACA;AAAA,EACT;AAAA,EAHS;AAAA,EACA;AAAA,EACA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASL,SAAkB;AACrB,UAAM,OAAO,KAAK,SAAS,YAAY;AACvC,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,SAAS,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,KAAK;AAC3D,QAAI,CAAC,OAAQ,QAAO;AAEpB,UAAM,SAAS,0BAA0B,IAAI;AAG7C,WAAO,KAAK,SAAS,cAAc;AAAA,MAC/B,QAAQ,KAAK;AAAA,MACb,SAAS,EAAE,QAAQ,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,EAAE,EAAE;AAAA,IACpD,CAAC;AAAA,EACL;AACJ;","names":[]}