@zylem/behaviors 0.4.0 → 0.5.1

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 (146) hide show
  1. package/README.md +13 -1
  2. package/dist/behavior-system-CeJXzNvr.d.ts +868 -0
  3. package/dist/{chunk-GFARAJV2.js → chunk-2PAHQZZD.js} +44 -73
  4. package/dist/chunk-2PAHQZZD.js.map +1 -0
  5. package/dist/chunk-52FJIDGL.js +30 -0
  6. package/dist/chunk-52FJIDGL.js.map +1 -0
  7. package/dist/{chunk-UYFOLI2X.js → chunk-5A5AYP2C.js} +1 -1
  8. package/dist/{chunk-UYFOLI2X.js.map → chunk-5A5AYP2C.js.map} +1 -1
  9. package/dist/{chunk-64HKCSXD.js → chunk-5EBIIYZD.js} +25 -45
  10. package/dist/chunk-5EBIIYZD.js.map +1 -0
  11. package/dist/{chunk-WXXXJ22R.js → chunk-5NJGASUJ.js} +87 -129
  12. package/dist/chunk-5NJGASUJ.js.map +1 -0
  13. package/dist/{chunk-XLUJDKCT.js → chunk-5UMCWV5A.js} +24 -15
  14. package/dist/chunk-5UMCWV5A.js.map +1 -0
  15. package/dist/chunk-5WRI5ZAA.js +31 -0
  16. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  17. package/dist/{chunk-5SM5SH5O.js → chunk-7JLWZXPA.js} +2 -2
  18. package/dist/{chunk-HNESASBB.js → chunk-AEBDZWCP.js} +19 -28
  19. package/dist/chunk-AEBDZWCP.js.map +1 -0
  20. package/dist/chunk-AS3HR7AS.js +188 -0
  21. package/dist/chunk-AS3HR7AS.js.map +1 -0
  22. package/dist/chunk-E4X4YJZB.js +841 -0
  23. package/dist/chunk-E4X4YJZB.js.map +1 -0
  24. package/dist/{chunk-PWF757B6.js → chunk-F6ESSHQK.js} +19 -25
  25. package/dist/chunk-F6ESSHQK.js.map +1 -0
  26. package/dist/chunk-GQAPBNGT.js +210 -0
  27. package/dist/chunk-GQAPBNGT.js.map +1 -0
  28. package/dist/chunk-JGXRQUTC.js +191 -0
  29. package/dist/chunk-JGXRQUTC.js.map +1 -0
  30. package/dist/{chunk-BXP4K7RF.js → chunk-JVADRLWX.js} +57 -186
  31. package/dist/chunk-JVADRLWX.js.map +1 -0
  32. package/dist/{chunk-ZA43LPRM.js → chunk-KIYUUSPK.js} +2 -2
  33. package/dist/{chunk-V74N7D6Q.js → chunk-KOWGILCN.js} +24 -15
  34. package/dist/chunk-KOWGILCN.js.map +1 -0
  35. package/dist/{chunk-WWVD5DR5.js → chunk-M3QDKKDF.js} +2 -8
  36. package/dist/chunk-M3QDKKDF.js.map +1 -0
  37. package/dist/{chunk-FTDZAKZW.js → chunk-NQPYMUDF.js} +7 -5
  38. package/dist/{chunk-FTDZAKZW.js.map → chunk-NQPYMUDF.js.map} +1 -1
  39. package/dist/{chunk-XP3ASVGJ.js → chunk-NW4SE5XW.js} +2 -2
  40. package/dist/{chunk-KDY5N6J4.js → chunk-OPZ2LVS2.js} +1 -22
  41. package/dist/{chunk-KDY5N6J4.js.map → chunk-OPZ2LVS2.js.map} +1 -1
  42. package/dist/{chunk-5XZWTFGF.js → chunk-PLIXVF7B.js} +3 -3
  43. package/dist/chunk-PLIXVF7B.js.map +1 -0
  44. package/dist/{chunk-Y7YIAWPW.js → chunk-SGHIGTVZ.js} +1 -11
  45. package/dist/{chunk-Y7YIAWPW.js.map → chunk-SGHIGTVZ.js.map} +1 -1
  46. package/dist/{chunk-UL6Q5HDP.js → chunk-TBUKJKY6.js} +3 -25
  47. package/dist/chunk-TBUKJKY6.js.map +1 -0
  48. package/dist/chunk-VFDUKGRZ.js +155 -0
  49. package/dist/chunk-VFDUKGRZ.js.map +1 -0
  50. package/dist/chunk-VT3H43QA.js +288 -0
  51. package/dist/chunk-VT3H43QA.js.map +1 -0
  52. package/dist/chunk-W26PHBME.js +309 -0
  53. package/dist/chunk-W26PHBME.js.map +1 -0
  54. package/dist/components-BANNjAZP.d.ts +30 -0
  55. package/dist/cooldown.d.ts +2 -2
  56. package/dist/cooldown.js +1 -0
  57. package/dist/coordinators/boundary-ricochet-3d.d.ts +4 -4
  58. package/dist/coordinators/boundary-ricochet-3d.js +1 -0
  59. package/dist/coordinators/boundary-ricochet.d.ts +4 -4
  60. package/dist/coordinators/boundary-ricochet.js +3 -2
  61. package/dist/coordinators/first-person-shooter.js +1 -0
  62. package/dist/coordinators/multidirectional-space-shooter.d.ts +3 -3
  63. package/dist/coordinators/multidirectional-space-shooter.js +5 -3
  64. package/dist/coordinators/top-down-shooter.d.ts +3 -3
  65. package/dist/coordinators/top-down-shooter.js +5 -3
  66. package/dist/core.d.ts +288 -8
  67. package/dist/core.js +492 -19
  68. package/dist/core.js.map +1 -1
  69. package/dist/destructible-3d.d.ts +32 -4
  70. package/dist/destructible-3d.js +2 -1
  71. package/dist/destructible-prebake.js +1 -0
  72. package/dist/{entity-types-CmtQ-IBp.d.ts → entity-types-DhfsZbSK.d.ts} +5 -3
  73. package/dist/first-person.d.ts +27 -38
  74. package/dist/first-person.js +8 -7
  75. package/dist/index.d.ts +14 -15
  76. package/dist/index.js +37 -52
  77. package/dist/jumper-2d.d.ts +3 -17
  78. package/dist/jumper-2d.js +4 -8
  79. package/dist/jumper-3d.d.ts +5 -39
  80. package/dist/jumper-3d.js +5 -9
  81. package/dist/{moveable-BLTHO7Jq.d.ts → moveable-XhzWPAw5.d.ts} +21 -4
  82. package/dist/particle-emitter/presets.js +1 -0
  83. package/dist/particle-emitter.d.ts +3 -3
  84. package/dist/particle-emitter.js +3 -2
  85. package/dist/platformer-2d.d.ts +13 -82
  86. package/dist/platformer-2d.js +4 -4
  87. package/dist/platformer-3d.d.ts +13 -78
  88. package/dist/platformer-3d.js +4 -4
  89. package/dist/ricochet-2d.d.ts +3 -3
  90. package/dist/ricochet-2d.js +4 -3
  91. package/dist/ricochet-3d.d.ts +3 -3
  92. package/dist/ricochet-3d.js +4 -3
  93. package/dist/runtime-2d.d.ts +2 -2
  94. package/dist/runtime-2d.js +1 -0
  95. package/dist/runtime-2d.js.map +1 -1
  96. package/dist/screen-visibility.d.ts +2 -2
  97. package/dist/screen-visibility.js +1 -0
  98. package/dist/screen-wrap.d.ts +2 -2
  99. package/dist/screen-wrap.js +4 -3
  100. package/dist/shooter-2d.d.ts +3 -3
  101. package/dist/shooter-2d.js +4 -3
  102. package/dist/thruster.d.ts +5 -34
  103. package/dist/thruster.js +4 -6
  104. package/dist/top-down-movement.d.ts +9 -27
  105. package/dist/top-down-movement.js +6 -6
  106. package/dist/{vector-Bm65-FOG.d.ts → vector-DjhLUrak.d.ts} +2 -4
  107. package/dist/world-boundary-2d.d.ts +2 -2
  108. package/dist/world-boundary-2d.js +5 -4
  109. package/dist/world-boundary-3d.d.ts +2 -2
  110. package/dist/world-boundary-3d.js +4 -3
  111. package/package.json +4 -3
  112. package/dist/behavior-system-DSNek0W9.d.ts +0 -483
  113. package/dist/chunk-4URF65BR.js +0 -44
  114. package/dist/chunk-4URF65BR.js.map +0 -1
  115. package/dist/chunk-4V7TIRO2.js +0 -44
  116. package/dist/chunk-4V7TIRO2.js.map +0 -1
  117. package/dist/chunk-5XZWTFGF.js.map +0 -1
  118. package/dist/chunk-64HKCSXD.js.map +0 -1
  119. package/dist/chunk-BXP4K7RF.js.map +0 -1
  120. package/dist/chunk-FRJDQM23.js +0 -506
  121. package/dist/chunk-FRJDQM23.js.map +0 -1
  122. package/dist/chunk-GFARAJV2.js.map +0 -1
  123. package/dist/chunk-GTLA6LOJ.js +0 -400
  124. package/dist/chunk-GTLA6LOJ.js.map +0 -1
  125. package/dist/chunk-HNESASBB.js.map +0 -1
  126. package/dist/chunk-JXMF4NUI.js +0 -309
  127. package/dist/chunk-JXMF4NUI.js.map +0 -1
  128. package/dist/chunk-MJFMNRMZ.js +0 -240
  129. package/dist/chunk-MJFMNRMZ.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-PWF757B6.js.map +0 -1
  133. package/dist/chunk-RONH2KDI.js +0 -455
  134. package/dist/chunk-RONH2KDI.js.map +0 -1
  135. package/dist/chunk-RTRFJVCZ.js +0 -454
  136. package/dist/chunk-RTRFJVCZ.js.map +0 -1
  137. package/dist/chunk-UL6Q5HDP.js.map +0 -1
  138. package/dist/chunk-V74N7D6Q.js.map +0 -1
  139. package/dist/chunk-WWVD5DR5.js.map +0 -1
  140. package/dist/chunk-WXXXJ22R.js.map +0 -1
  141. package/dist/chunk-XLUJDKCT.js.map +0 -1
  142. package/dist/components-BWG1GkZA.d.ts +0 -20
  143. package/dist/use-behavior-h9-T5FSv.d.ts +0 -13
  144. /package/dist/{chunk-5SM5SH5O.js.map → chunk-7JLWZXPA.js.map} +0 -0
  145. /package/dist/{chunk-ZA43LPRM.js.map → chunk-KIYUUSPK.js.map} +0 -0
  146. /package/dist/{chunk-XP3ASVGJ.js.map → chunk-NW4SE5XW.js.map} +0 -0
@@ -1,454 +0,0 @@
1
- import {
2
- SyncStateMachine,
3
- t
4
- } from "./chunk-4URF65BR.js";
5
- import {
6
- defineBehavior
7
- } from "./chunk-EZNCCO4L.js";
8
-
9
- // src/lib/behaviors/platformer-2d/components.ts
10
- function createPlatformer2DMovementComponent(options = {}) {
11
- return {
12
- walkSpeed: options.walkSpeed ?? 8,
13
- runSpeed: options.runSpeed ?? 14,
14
- jumpForce: options.jumpForce ?? 12,
15
- maxJumps: options.maxJumps ?? 1,
16
- gravity: options.gravity ?? 20,
17
- coyoteTime: options.coyoteTime ?? 0.1,
18
- jumpBufferTime: options.jumpBufferTime ?? 0.1,
19
- jumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,
20
- multiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,
21
- slopeClimbAngle: options.slopeClimbAngle ?? 50,
22
- slopeSlideAngle: options.slopeSlideAngle ?? 40,
23
- autostep: options.autostep ?? { maxHeight: 0.25, minWidth: 0.05 },
24
- snapToGroundDistance: options.snapToGroundDistance ?? 0.2,
25
- characterOffset: options.characterOffset ?? 0.05
26
- };
27
- }
28
- function createPlatformer2DInputComponent() {
29
- return {
30
- moveX: 0,
31
- jump: false,
32
- run: false
33
- };
34
- }
35
- function createPlatformer2DStateComponent() {
36
- return {
37
- grounded: false,
38
- jumping: false,
39
- falling: false,
40
- jumpCount: 0,
41
- jumpStartHeight: 0,
42
- currentSpeed: 0,
43
- lastGroundedY: 0,
44
- jumpPressedLastFrame: false,
45
- timeSinceGrounded: 0,
46
- jumpBuffered: false,
47
- jumpBufferTimer: 0,
48
- jumpHeld: false,
49
- jumpCutApplied: false,
50
- jumpReleasedSinceLastJump: true,
51
- timeSinceJump: 0,
52
- jumpedThisFrame: false,
53
- jumpCutRampTimer: 0,
54
- verticalVelocity: 0
55
- };
56
- }
57
-
58
- // src/lib/behaviors/platformer-2d/platformer-2d-fsm.ts
59
- var Platformer2DState = /* @__PURE__ */ ((Platformer2DState2) => {
60
- Platformer2DState2["Idle"] = "idle";
61
- Platformer2DState2["Walking"] = "walking";
62
- Platformer2DState2["Running"] = "running";
63
- Platformer2DState2["Jumping"] = "jumping";
64
- Platformer2DState2["Falling"] = "falling";
65
- Platformer2DState2["Landing"] = "landing";
66
- return Platformer2DState2;
67
- })(Platformer2DState || {});
68
- var Platformer2DEvent = /* @__PURE__ */ ((Platformer2DEvent2) => {
69
- Platformer2DEvent2["Walk"] = "walk";
70
- Platformer2DEvent2["Run"] = "run";
71
- Platformer2DEvent2["Jump"] = "jump";
72
- Platformer2DEvent2["Fall"] = "fall";
73
- Platformer2DEvent2["Land"] = "land";
74
- Platformer2DEvent2["Stop"] = "stop";
75
- return Platformer2DEvent2;
76
- })(Platformer2DEvent || {});
77
- var Platformer2DFSM = class {
78
- constructor(ctx) {
79
- this.ctx = ctx;
80
- this.machine = new SyncStateMachine(
81
- "idle" /* Idle */,
82
- [
83
- // Idle transitions
84
- t("idle" /* Idle */, "walk" /* Walk */, "walking" /* Walking */),
85
- t("idle" /* Idle */, "run" /* Run */, "running" /* Running */),
86
- t("idle" /* Idle */, "jump" /* Jump */, "jumping" /* Jumping */),
87
- t("idle" /* Idle */, "fall" /* Fall */, "falling" /* Falling */),
88
- // Walking transitions
89
- t("walking" /* Walking */, "run" /* Run */, "running" /* Running */),
90
- t("walking" /* Walking */, "jump" /* Jump */, "jumping" /* Jumping */),
91
- t("walking" /* Walking */, "stop" /* Stop */, "idle" /* Idle */),
92
- t("walking" /* Walking */, "fall" /* Fall */, "falling" /* Falling */),
93
- // Running transitions
94
- t("running" /* Running */, "walk" /* Walk */, "walking" /* Walking */),
95
- t("running" /* Running */, "jump" /* Jump */, "jumping" /* Jumping */),
96
- t("running" /* Running */, "stop" /* Stop */, "idle" /* Idle */),
97
- t("running" /* Running */, "fall" /* Fall */, "falling" /* Falling */),
98
- // Jumping transitions
99
- t("jumping" /* Jumping */, "fall" /* Fall */, "falling" /* Falling */),
100
- t("jumping" /* Jumping */, "land" /* Land */, "landing" /* Landing */),
101
- t("jumping" /* Jumping */, "jump" /* Jump */, "jumping" /* Jumping */),
102
- // Multi-jump
103
- // Falling transitions
104
- t("falling" /* Falling */, "land" /* Land */, "landing" /* Landing */),
105
- t("falling" /* Falling */, "jump" /* Jump */, "jumping" /* Jumping */),
106
- // Air jump
107
- // Landing transitions
108
- t("landing" /* Landing */, "walk" /* Walk */, "walking" /* Walking */),
109
- t("landing" /* Landing */, "run" /* Run */, "running" /* Running */),
110
- t("landing" /* Landing */, "stop" /* Stop */, "idle" /* Idle */),
111
- // Self-transitions (no-ops)
112
- t("idle" /* Idle */, "stop" /* Stop */, "idle" /* Idle */)
113
- ]
114
- );
115
- }
116
- ctx;
117
- machine;
118
- getState() {
119
- return this.machine.getState();
120
- }
121
- dispatch(event) {
122
- if (this.machine.can(event)) {
123
- this.machine.syncDispatch(event);
124
- }
125
- }
126
- isGrounded() {
127
- const state = this.getState();
128
- return state === "idle" /* Idle */ || state === "walking" /* Walking */ || state === "running" /* Running */ || state === "landing" /* Landing */;
129
- }
130
- getJumpCount() {
131
- return this.ctx.state.jumpCount;
132
- }
133
- /**
134
- * Update FSM based on current component state.
135
- */
136
- update(input, state) {
137
- this.ctx.input = input;
138
- this.ctx.state = state;
139
- const currentState = this.getState();
140
- const hasInput = Math.abs(input.moveX) > 0.1;
141
- const isRunning = input.run;
142
- if (currentState === "falling" /* Falling */ && state.grounded) {
143
- this.dispatch("land" /* Land */);
144
- }
145
- if (state.grounded) {
146
- if (hasInput) {
147
- if (isRunning) {
148
- this.dispatch("run" /* Run */);
149
- } else {
150
- this.dispatch("walk" /* Walk */);
151
- }
152
- } else {
153
- this.dispatch("stop" /* Stop */);
154
- }
155
- } else if (state.falling) {
156
- this.dispatch("fall" /* Fall */);
157
- }
158
- if (input.jump && !state.jumpPressedLastFrame) {
159
- this.dispatch("jump" /* Jump */);
160
- }
161
- }
162
- };
163
-
164
- // src/lib/behaviors/platformer-2d/platformer-2d.system.ts
165
- import { RigidBodyType, QueryFilterFlags } from "@dimforge/rapier3d-compat";
166
- var JUMP_CUT_RAMP_DURATION = 0.05;
167
- var GROUND_STICK_SPEED = 2;
168
- var FALL_VELOCITY_THRESHOLD = -0.1;
169
- var DEG_TO_RAD = Math.PI / 180;
170
- var _desired = { x: 0, y: 0, z: 0 };
171
- var Platformer2DSystem = class {
172
- world;
173
- /** Per-entity character controllers, keyed by entity uuid. */
174
- controllers = /* @__PURE__ */ new Map();
175
- /** Spawn-time z per entity; re-pinned every tick to hold the 2D plane. */
176
- planeZ = /* @__PURE__ */ new Map();
177
- constructor(world) {
178
- this.world = world;
179
- }
180
- /**
181
- * One-time per-entity physics setup: convert the body to a
182
- * position-based kinematic body, record the plane z, and create the
183
- * entity's character controller. Done lazily on the first update because
184
- * the body isn't always available at construction time.
185
- */
186
- ensurePhysicsConfigured(entity) {
187
- const existing = this.controllers.get(entity.uuid);
188
- if (existing) return existing;
189
- const physicsWorld = this.world?.world;
190
- if (!physicsWorld || !entity.body) return null;
191
- if (entity.body.bodyType() !== RigidBodyType.KinematicPositionBased) {
192
- entity.body.setBodyType(RigidBodyType.KinematicPositionBased, true);
193
- }
194
- this.planeZ.set(entity.uuid, entity.body.translation().z);
195
- const movement = entity.platformer2D;
196
- const controller = physicsWorld.createCharacterController(movement.characterOffset);
197
- controller.setUp({ x: 0, y: 1, z: 0 });
198
- controller.setMaxSlopeClimbAngle(movement.slopeClimbAngle * DEG_TO_RAD);
199
- controller.setMinSlopeSlideAngle(movement.slopeSlideAngle * DEG_TO_RAD);
200
- if (movement.autostep) {
201
- controller.enableAutostep(movement.autostep.maxHeight, movement.autostep.minWidth, true);
202
- } else {
203
- controller.disableAutostep();
204
- }
205
- controller.enableSnapToGround(movement.snapToGroundDistance);
206
- controller.setApplyImpulsesToDynamicBodies(true);
207
- this.controllers.set(entity.uuid, controller);
208
- return controller;
209
- }
210
- /**
211
- * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut
212
- * ramp. All vertical motion is written to `state.verticalVelocity`.
213
- */
214
- handleJump(entity, delta) {
215
- const input = entity.$platformer2D;
216
- const movement = entity.platformer2D;
217
- const state = entity.platformer2DState;
218
- if (state.jumping || state.falling) {
219
- state.timeSinceJump += delta;
220
- }
221
- if (!input.jump && state.jumpHeld) {
222
- state.jumpReleasedSinceLastJump = true;
223
- }
224
- if (input.jump && !state.jumpPressedLastFrame) {
225
- state.jumpBuffered = true;
226
- state.jumpBufferTimer = movement.jumpBufferTime;
227
- }
228
- state.jumpPressedLastFrame = input.jump;
229
- state.jumpHeld = input.jump;
230
- if (state.jumpBuffered) {
231
- state.jumpBufferTimer -= delta;
232
- if (state.jumpBufferTimer <= 0) {
233
- state.jumpBuffered = false;
234
- }
235
- }
236
- const minTimeBeforeCut = 0.1;
237
- const canApplyCut = state.timeSinceJump >= minTimeBeforeCut;
238
- if (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {
239
- state.jumpCutApplied = true;
240
- state.jumpCutRampTimer = JUMP_CUT_RAMP_DURATION;
241
- }
242
- if (state.jumpCutRampTimer > 0) {
243
- if (state.verticalVelocity > 0) {
244
- const stepT = Math.min(delta / state.jumpCutRampTimer, 1);
245
- const targetY = state.verticalVelocity * movement.jumpCutMultiplier;
246
- state.verticalVelocity += (targetY - state.verticalVelocity) * stepT;
247
- }
248
- state.jumpCutRampTimer = Math.max(0, state.jumpCutRampTimer - delta);
249
- }
250
- if (!state.jumpBuffered) return;
251
- const inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;
252
- const isFirstJump = state.grounded || inCoyoteWindow && state.jumpCount === 0;
253
- const hasJumpsRemaining = state.jumpCount < movement.maxJumps;
254
- const buttonReleased = state.jumpReleasedSinceLastJump;
255
- const inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;
256
- const canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;
257
- if (isFirstJump || canMultiJump) {
258
- state.jumpBuffered = false;
259
- state.jumpCount++;
260
- state.jumpReleasedSinceLastJump = false;
261
- state.timeSinceJump = 0;
262
- state.jumpStartHeight = entity.body.translation().y;
263
- state.jumping = true;
264
- state.falling = false;
265
- state.jumpCutApplied = false;
266
- state.jumpCutRampTimer = 0;
267
- state.verticalVelocity = movement.jumpForce;
268
- state.jumpedThisFrame = true;
269
- }
270
- }
271
- /**
272
- * Integrate gravity into `state.verticalVelocity`.
273
- *
274
- * While grounded (and not on the jump frame) vertical velocity is held at
275
- * a small constant downward speed so the controller stays pressed into
276
- * the ground. Skipped entirely on the frame a jump was applied — the
277
- * grounded flag is still stale-true then and would clobber the jump.
278
- */
279
- applyGravity(entity, delta) {
280
- const movement = entity.platformer2D;
281
- const state = entity.platformer2DState;
282
- if (state.jumpedThisFrame) {
283
- state.jumpedThisFrame = false;
284
- return;
285
- }
286
- if (state.grounded) {
287
- state.verticalVelocity = -GROUND_STICK_SPEED;
288
- return;
289
- }
290
- state.verticalVelocity -= movement.gravity * delta;
291
- }
292
- /**
293
- * Update entity state from the controller's grounded result.
294
- */
295
- updateState(entity, delta, computedGrounded) {
296
- const state = entity.platformer2DState;
297
- const wasGrounded = state.grounded;
298
- const isAirborne = state.jumping || state.falling;
299
- let isGrounded;
300
- if (isAirborne) {
301
- const canLand = state.falling && !state.jumping;
302
- isGrounded = computedGrounded && canLand;
303
- } else {
304
- isGrounded = computedGrounded;
305
- }
306
- state.grounded = isGrounded;
307
- if (state.grounded) {
308
- state.timeSinceGrounded = 0;
309
- state.lastGroundedY = entity.body.translation().y;
310
- } else {
311
- state.timeSinceGrounded += delta;
312
- }
313
- if (!wasGrounded && state.grounded) {
314
- state.jumpCount = 0;
315
- state.jumping = false;
316
- state.falling = false;
317
- state.jumpCutApplied = false;
318
- state.jumpCutRampTimer = 0;
319
- if (state.verticalVelocity < 0) {
320
- state.verticalVelocity = 0;
321
- }
322
- }
323
- if (state.verticalVelocity < FALL_VELOCITY_THRESHOLD && !state.grounded) {
324
- if (state.jumping) {
325
- state.jumping = false;
326
- state.falling = true;
327
- } else if (!state.jumping) {
328
- state.falling = true;
329
- }
330
- }
331
- }
332
- /**
333
- * Update one platformer entity. Invoked per linked entity by the
334
- * Platformer2DBehaviorSystem adapter.
335
- */
336
- updateEntity(entity, delta) {
337
- if (!entity.platformer2D || !entity.$platformer2D || !entity.platformer2DState) {
338
- return;
339
- }
340
- const controller = this.ensurePhysicsConfigured(entity);
341
- const collider = entity.collider;
342
- if (!controller || !collider) return;
343
- const input = entity.$platformer2D;
344
- const movement = entity.platformer2D;
345
- const state = entity.platformer2DState;
346
- this.handleJump(entity, delta);
347
- this.applyGravity(entity, delta);
348
- const speed = input.run ? movement.runSpeed : movement.walkSpeed;
349
- state.currentSpeed = speed;
350
- _desired.x = input.moveX * speed * delta;
351
- _desired.y = state.verticalVelocity * delta;
352
- _desired.z = 0;
353
- controller.computeColliderMovement(collider, _desired, QueryFilterFlags.EXCLUDE_SENSORS);
354
- const corrected = controller.computedMovement();
355
- if (_desired.y > 1e-9 && corrected.y < _desired.y * 0.5) {
356
- state.verticalVelocity = 0;
357
- }
358
- const translation = entity.body.translation();
359
- entity.body.setNextKinematicTranslation({
360
- x: translation.x + corrected.x,
361
- y: translation.y + corrected.y,
362
- z: this.planeZ.get(entity.uuid) ?? translation.z
363
- });
364
- this.updateState(entity, delta, controller.computedGrounded());
365
- }
366
- destroy() {
367
- const physicsWorld = this.world?.world;
368
- if (physicsWorld) {
369
- for (const controller of this.controllers.values()) {
370
- physicsWorld.removeCharacterController(controller);
371
- }
372
- }
373
- this.controllers.clear();
374
- this.planeZ.clear();
375
- }
376
- };
377
-
378
- // src/lib/behaviors/platformer-2d/platformer-2d.descriptor.ts
379
- var defaultOptions = {
380
- walkSpeed: 8,
381
- runSpeed: 14,
382
- jumpForce: 12,
383
- maxJumps: 1,
384
- gravity: 20,
385
- coyoteTime: 0.1,
386
- jumpBufferTime: 0.1,
387
- jumpCutMultiplier: 0.5,
388
- slopeClimbAngle: 50,
389
- slopeSlideAngle: 40,
390
- autostep: { maxHeight: 0.25, minWidth: 0.05 },
391
- snapToGroundDistance: 0.2,
392
- characterOffset: 0.05
393
- };
394
- var PLATFORMER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-2d");
395
- var Platformer2DBehaviorSystem = class {
396
- constructor(world, getBehaviorLinks) {
397
- this.getBehaviorLinks = getBehaviorLinks;
398
- this.movementSystem = new Platformer2DSystem(world);
399
- }
400
- getBehaviorLinks;
401
- movementSystem;
402
- update(_ecs, delta) {
403
- const links = this.getBehaviorLinks?.(PLATFORMER_2D_BEHAVIOR_KEY);
404
- if (!links) return;
405
- for (const link of links) {
406
- const gameEntity = link.entity;
407
- const platformerRef = link.ref;
408
- const options = platformerRef.options;
409
- if (!gameEntity.platformer2D) {
410
- gameEntity.platformer2D = createPlatformer2DMovementComponent(options);
411
- }
412
- if (!gameEntity.$platformer2D) {
413
- gameEntity.$platformer2D = createPlatformer2DInputComponent();
414
- }
415
- if (!gameEntity.platformer2DState) {
416
- gameEntity.platformer2DState = createPlatformer2DStateComponent();
417
- }
418
- if (!platformerRef.fsm) {
419
- platformerRef.fsm = new Platformer2DFSM({
420
- input: gameEntity.$platformer2D,
421
- state: gameEntity.platformer2DState
422
- });
423
- }
424
- platformerRef.fsm.update(gameEntity.$platformer2D, gameEntity.platformer2DState);
425
- if (!gameEntity.body) continue;
426
- this.movementSystem.updateEntity(gameEntity, delta);
427
- }
428
- }
429
- destroy(_ecs) {
430
- this.movementSystem.destroy();
431
- }
432
- };
433
- var Platformer2DBehavior = defineBehavior({
434
- name: "platformer-2d",
435
- defaultOptions,
436
- systemFactory: (ctx) => new Platformer2DBehaviorSystem(ctx.world ?? null, ctx.getBehaviorLinks),
437
- createHandle: (ref) => ({
438
- getState: () => ref.fsm?.getState() ?? "idle" /* Idle */,
439
- isGrounded: () => ref.fsm?.isGrounded() ?? false,
440
- getJumpCount: () => ref.fsm?.getJumpCount() ?? 0
441
- })
442
- });
443
-
444
- export {
445
- createPlatformer2DMovementComponent,
446
- createPlatformer2DInputComponent,
447
- createPlatformer2DStateComponent,
448
- Platformer2DState,
449
- Platformer2DEvent,
450
- Platformer2DFSM,
451
- Platformer2DSystem,
452
- Platformer2DBehavior
453
- };
454
- //# sourceMappingURL=chunk-RTRFJVCZ.js.map
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/platformer-2d/components.ts","../src/lib/behaviors/platformer-2d/platformer-2d-fsm.ts","../src/lib/behaviors/platformer-2d/platformer-2d.system.ts","../src/lib/behaviors/platformer-2d/platformer-2d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 2D Components\n *\n * Components for 2D platformer movement on the X/Y plane: walking/running on\n * X, jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DMovementComponent (capability / configuration)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force */\n\tgravity: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for SNES-style step-ups, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\n}\n\nexport function createPlatformer2DMovementComponent(\n\toptions: Partial<Platformer2DMovementComponent> = {},\n): Platformer2DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 8,\n\t\trunSpeed: options.runSpeed ?? 14,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 20,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,\n\t\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.25, minWidth: 0.05 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.2,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer2DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer2DInputComponent(): Platformer2DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DStateComponent (runtime state)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: number;\n}\n\nexport function createPlatformer2DStateComponent(): Platformer2DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tverticalVelocity: 0,\n\t};\n}\n","/**\n * Platformer 2D Finite State Machine\n *\n * Manages state transitions for 2D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer2DInputComponent, Platformer2DStateComponent } from './components';\n\n/**\n * Simplified collision context (normal in the X/Y plane).\n */\nexport interface Platformer2DCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number };\n\t};\n}\n\nexport enum Platformer2DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\nexport enum Platformer2DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\nexport interface Platformer2DContext {\n\tinput: Platformer2DInputComponent;\n\tstate: Platformer2DStateComponent;\n}\n\nexport class Platformer2DFSM {\n\tmachine: SyncStateMachine<Platformer2DState, Platformer2DEvent, never>;\n\n\tconstructor(private ctx: Platformer2DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer2DState, Platformer2DEvent, never>(\n\t\t\tPlatformer2DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Multi-jump\n\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Air jump\n\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Platformer2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Platformer2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn (\n\t\t\tstate === Platformer2DState.Idle ||\n\t\t\tstate === Platformer2DState.Walking ||\n\t\t\tstate === Platformer2DState.Running ||\n\t\t\tstate === Platformer2DState.Landing\n\t\t);\n\t}\n\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Update FSM based on current component state.\n\t */\n\tupdate(input: Platformer2DInputComponent, state: Platformer2DStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst currentState = this.getState();\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer2DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer2DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer2DEvent.Stop);\n\t\t\t}\n\t\t} else if (state.falling) {\n\t\t\tthis.dispatch(Platformer2DEvent.Fall);\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in the system)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer2DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 2D System\n *\n * Kinematic character controller (SNES-style) on the X/Y plane: the\n * controller owns all motion and physics is only a collision database.\n *\n * - The entity's body is converted to `KinematicPositionBased` on first\n * update; Rapier never applies forces to it.\n * - Each tick, desired displacement (X input + integrated vertical velocity,\n * z locked to 0) is run through Rapier's `KinematicCharacterController`\n * (collide-and-slide with autostep, slope limits, and snap-to-ground) and\n * the corrected movement is applied via `setNextKinematicTranslation`. The\n * z coordinate is re-pinned to its first-seen value every tick so slide\n * resolution can never drift the entity off the 2D plane.\n * - Grounded comes from `controller.computedGrounded()` — no contact-pair or\n * raycast probing.\n * - Vertical velocity lives in `state.verticalVelocity` (`linvel()` is\n * meaningless on kinematic bodies). Jump buffering, coyote time,\n * multi-jump, and the jump-cut ramp all operate on that field.\n */\n\nimport { RigidBodyType, QueryFilterFlags } from '@dimforge/rapier3d-compat';\nimport type { Collider, KinematicCharacterController, RigidBody } from '@dimforge/rapier3d-compat';\nimport type { ZylemWorld } from '../../collision/world';\nimport type {\n\tPlatformer2DMovementComponent,\n\tPlatformer2DInputComponent,\n\tPlatformer2DStateComponent,\n} from './components';\n\n/**\n * Duration of the jump-cut velocity ramp in seconds.\n * Instead of cutting vertical velocity in a single frame (which produces a\n * visible pop), we lerp toward the cut velocity across roughly 3 physics\n * steps at 60 Hz.\n */\nconst JUMP_CUT_RAMP_DURATION = 0.05;\n\n/**\n * Constant downward speed applied while grounded (units/second). Keeps the\n * controller pressed into the ground so `computedGrounded()` stays engaged on\n * flat ground and going downhill (snap-to-ground handles larger drops).\n */\nconst GROUND_STICK_SPEED = 2;\n\n/** Vertical speed below which the airborne state flips from jumping to falling. */\nconst FALL_VELOCITY_THRESHOLD = -0.1;\n\nconst DEG_TO_RAD = Math.PI / 180;\n\n/**\n * Entity with 2D platformer components\n */\nexport interface Platformer2DEntity {\n\tuuid: string;\n\tbody: RigidBody;\n\tcollider?: Collider | null;\n\tplatformer2D: Platformer2DMovementComponent;\n\t$platformer2D: Platformer2DInputComponent;\n\tplatformer2DState: Platformer2DStateComponent;\n}\n\n/** Scratch vector reused for the desired per-tick displacement. */\nconst _desired = { x: 0, y: 0, z: 0 };\n\n/**\n * Platformer 2D movement system.\n *\n * Core kinematic controller for 2D platformer movement. Requires a live\n * `ZylemWorld` with the raw Rapier `World` exposed on `.world` — without it\n * the system is a no-op.\n */\nexport class Platformer2DSystem {\n\tprivate world: ZylemWorld | null;\n\t/** Per-entity character controllers, keyed by entity uuid. */\n\tprivate controllers = new Map<string, KinematicCharacterController>();\n\t/** Spawn-time z per entity; re-pinned every tick to hold the 2D plane. */\n\tprivate planeZ = new Map<string, number>();\n\n\tconstructor(world: ZylemWorld | null) {\n\t\tthis.world = world;\n\t}\n\n\t/**\n\t * One-time per-entity physics setup: convert the body to a\n\t * position-based kinematic body, record the plane z, and create the\n\t * entity's character controller. Done lazily on the first update because\n\t * the body isn't always available at construction time.\n\t */\n\tprivate ensurePhysicsConfigured(entity: Platformer2DEntity): KinematicCharacterController | null {\n\t\tconst existing = this.controllers.get(entity.uuid);\n\t\tif (existing) return existing;\n\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (!physicsWorld || !entity.body) return null;\n\n\t\tif (entity.body.bodyType() !== RigidBodyType.KinematicPositionBased) {\n\t\t\tentity.body.setBodyType(RigidBodyType.KinematicPositionBased, true);\n\t\t}\n\t\tthis.planeZ.set(entity.uuid, entity.body.translation().z);\n\n\t\tconst movement = entity.platformer2D;\n\t\tconst controller = physicsWorld.createCharacterController(movement.characterOffset);\n\t\tcontroller.setUp({ x: 0, y: 1, z: 0 });\n\t\tcontroller.setMaxSlopeClimbAngle(movement.slopeClimbAngle * DEG_TO_RAD);\n\t\tcontroller.setMinSlopeSlideAngle(movement.slopeSlideAngle * DEG_TO_RAD);\n\t\tif (movement.autostep) {\n\t\t\tcontroller.enableAutostep(movement.autostep.maxHeight, movement.autostep.minWidth, true);\n\t\t} else {\n\t\t\tcontroller.disableAutostep();\n\t\t}\n\t\tcontroller.enableSnapToGround(movement.snapToGroundDistance);\n\t\tcontroller.setApplyImpulsesToDynamicBodies(true);\n\n\t\tthis.controllers.set(entity.uuid, controller);\n\t\treturn controller;\n\t}\n\n\t/**\n\t * Handle jump logic with multi-jump, coyote time, buffering, and jump-cut\n\t * ramp. All vertical motion is written to `state.verticalVelocity`.\n\t */\n\tprivate handleJump(entity: Platformer2DEntity, delta: number): void {\n\t\tconst input = entity.$platformer2D;\n\t\tconst movement = entity.platformer2D;\n\t\tconst state = entity.platformer2DState;\n\n\t\t// Track time since last jump (for multi-jump window)\n\t\tif (state.jumping || state.falling) {\n\t\t\tstate.timeSinceJump += delta;\n\t\t}\n\n\t\t// Track jump button release (required for multi-jump)\n\t\tif (!input.jump && state.jumpHeld) {\n\t\t\tstate.jumpReleasedSinceLastJump = true;\n\t\t}\n\n\t\t// 1. Jump Buffering - queue on new press\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tstate.jumpBuffered = true;\n\t\t\tstate.jumpBufferTimer = movement.jumpBufferTime;\n\t\t}\n\n\t\tstate.jumpPressedLastFrame = input.jump;\n\t\tstate.jumpHeld = input.jump;\n\n\t\t// Decrement buffer timer\n\t\tif (state.jumpBuffered) {\n\t\t\tstate.jumpBufferTimer -= delta;\n\t\t\tif (state.jumpBufferTimer <= 0) {\n\t\t\t\tstate.jumpBuffered = false;\n\t\t\t}\n\t\t}\n\n\t\t// 2. Variable Jump Height (Jump Cut)\n\t\t// Only trigger a cut if we've been jumping for a bit (prevents cutting multi-jump immediately).\n\t\tconst minTimeBeforeCut = 0.1;\n\t\tconst canApplyCut = state.timeSinceJump >= minTimeBeforeCut;\n\t\tif (!input.jump && state.jumping && !state.jumpCutApplied && canApplyCut) {\n\t\t\tstate.jumpCutApplied = true;\n\t\t\tstate.jumpCutRampTimer = JUMP_CUT_RAMP_DURATION;\n\t\t}\n\n\t\t// 2a. Ramp verticalVelocity toward verticalVelocity * jumpCutMultiplier\n\t\t// across the ramp window so the change isn't a single-frame discontinuity.\n\t\tif (state.jumpCutRampTimer > 0) {\n\t\t\tif (state.verticalVelocity > 0) {\n\t\t\t\tconst stepT = Math.min(delta / state.jumpCutRampTimer, 1);\n\t\t\t\tconst targetY = state.verticalVelocity * movement.jumpCutMultiplier;\n\t\t\t\tstate.verticalVelocity += (targetY - state.verticalVelocity) * stepT;\n\t\t\t}\n\t\t\tstate.jumpCutRampTimer = Math.max(0, state.jumpCutRampTimer - delta);\n\t\t}\n\n\t\t// Execute Jump (if buffered input exists)\n\t\tif (!state.jumpBuffered) return;\n\n\t\t// 3. Jump eligibility check\n\t\tconst inCoyoteWindow = !state.grounded && state.timeSinceGrounded <= movement.coyoteTime;\n\t\tconst isFirstJump = state.grounded || (inCoyoteWindow && state.jumpCount === 0);\n\n\t\t// Multi-jump requirements:\n\t\t// 1. Not grounded\n\t\t// 2. Have jumps remaining\n\t\t// 3. Button was released since last jump (no holding for double jump)\n\t\t// 4. Within the jump window (after multiJumpWindowTime has passed)\n\t\tconst hasJumpsRemaining = state.jumpCount < movement.maxJumps;\n\t\tconst buttonReleased = state.jumpReleasedSinceLastJump;\n\t\tconst inMultiJumpWindow = state.timeSinceJump >= movement.multiJumpWindowTime;\n\t\tconst canMultiJump = !state.grounded && hasJumpsRemaining && buttonReleased && inMultiJumpWindow;\n\n\t\tif (isFirstJump || canMultiJump) {\n\t\t\t// Consume buffered input\n\t\t\tstate.jumpBuffered = false;\n\n\t\t\t// Increment jump count and reset tracking\n\t\t\tstate.jumpCount++;\n\t\t\tstate.jumpReleasedSinceLastJump = false; // Must release again for next jump\n\t\t\tstate.timeSinceJump = 0; // Reset window timer\n\n\t\t\t// Record jump start height\n\t\t\tstate.jumpStartHeight = entity.body.translation().y;\n\n\t\t\t// Apply jump force\n\t\t\tstate.jumping = true;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\n\t\t\tstate.verticalVelocity = movement.jumpForce;\n\n\t\t\t// Flag this frame so applyGravity skips exactly one grounded\n\t\t\t// clamp/gravity step (the jump velocity is authoritative).\n\t\t\tstate.jumpedThisFrame = true;\n\t\t}\n\t}\n\n\t/**\n\t * Integrate gravity into `state.verticalVelocity`.\n\t *\n\t * While grounded (and not on the jump frame) vertical velocity is held at\n\t * a small constant downward speed so the controller stays pressed into\n\t * the ground. Skipped entirely on the frame a jump was applied — the\n\t * grounded flag is still stale-true then and would clobber the jump.\n\t */\n\tprivate applyGravity(entity: Platformer2DEntity, delta: number): void {\n\t\tconst movement = entity.platformer2D;\n\t\tconst state = entity.platformer2DState;\n\n\t\tif (state.jumpedThisFrame) {\n\t\t\tstate.jumpedThisFrame = false;\n\t\t\treturn;\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tstate.verticalVelocity = -GROUND_STICK_SPEED;\n\t\t\treturn;\n\t\t}\n\n\t\tstate.verticalVelocity -= movement.gravity * delta;\n\t}\n\n\t/**\n\t * Update entity state from the controller's grounded result.\n\t */\n\tprivate updateState(entity: Platformer2DEntity, delta: number, computedGrounded: boolean): void {\n\t\tconst state = entity.platformer2DState;\n\t\tconst wasGrounded = state.grounded;\n\n\t\tconst isAirborne = state.jumping || state.falling;\n\t\tlet isGrounded: boolean;\n\t\tif (isAirborne) {\n\t\t\t// Never land while still moving upward in a jump arc.\n\t\t\tconst canLand = state.falling && !state.jumping;\n\t\t\tisGrounded = computedGrounded && canLand;\n\t\t} else {\n\t\t\tisGrounded = computedGrounded;\n\t\t}\n\n\t\tstate.grounded = isGrounded;\n\n\t\tif (state.grounded) {\n\t\t\tstate.timeSinceGrounded = 0;\n\t\t\tstate.lastGroundedY = entity.body.translation().y;\n\t\t} else {\n\t\t\tstate.timeSinceGrounded += delta;\n\t\t}\n\n\t\tif (!wasGrounded && state.grounded) {\n\t\t\tstate.jumpCount = 0;\n\t\t\tstate.jumping = false;\n\t\t\tstate.falling = false;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tstate.jumpCutRampTimer = 0;\n\t\t\tif (state.verticalVelocity < 0) {\n\t\t\t\tstate.verticalVelocity = 0;\n\t\t\t}\n\t\t}\n\n\t\tif (state.verticalVelocity < FALL_VELOCITY_THRESHOLD && !state.grounded) {\n\t\t\tif (state.jumping) {\n\t\t\t\tstate.jumping = false;\n\t\t\t\tstate.falling = true;\n\t\t\t} else if (!state.jumping) {\n\t\t\t\tstate.falling = true;\n\t\t\t}\n\t\t}\n\t}\n\n\t/**\n\t * Update one platformer entity. Invoked per linked entity by the\n\t * Platformer2DBehaviorSystem adapter.\n\t */\n\tupdateEntity(entity: Platformer2DEntity, delta: number): void {\n\t\tif (!entity.platformer2D || !entity.$platformer2D || !entity.platformer2DState) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst controller = this.ensurePhysicsConfigured(entity);\n\t\tconst collider = entity.collider;\n\t\tif (!controller || !collider) return;\n\n\t\tconst input = entity.$platformer2D;\n\t\tconst movement = entity.platformer2D;\n\t\tconst state = entity.platformer2DState;\n\n\t\t// 1. Jump logic (may set verticalVelocity = jumpForce this frame)\n\t\tthis.handleJump(entity, delta);\n\n\t\t// 2. Gravity / grounded stick on verticalVelocity\n\t\tthis.applyGravity(entity, delta);\n\n\t\t// 3. Desired displacement for this tick (z locked to the plane)\n\t\tconst speed = input.run ? movement.runSpeed : movement.walkSpeed;\n\t\tstate.currentSpeed = speed;\n\t\t_desired.x = input.moveX * speed * delta;\n\t\t_desired.y = state.verticalVelocity * delta;\n\t\t_desired.z = 0;\n\n\t\t// 4. Collide-and-slide\n\t\tcontroller.computeColliderMovement(collider, _desired, QueryFilterFlags.EXCLUDE_SENSORS);\n\t\tconst corrected = controller.computedMovement();\n\n\t\t// Head bump: upward motion was blocked, so kill the remaining ascent\n\t\t// instead of hanging under the ceiling for the rest of the arc.\n\t\tif (_desired.y > 1e-9 && corrected.y < _desired.y * 0.5) {\n\t\t\tstate.verticalVelocity = 0;\n\t\t}\n\n\t\tconst translation = entity.body.translation();\n\t\tentity.body.setNextKinematicTranslation({\n\t\t\tx: translation.x + corrected.x,\n\t\t\ty: translation.y + corrected.y,\n\t\t\tz: this.planeZ.get(entity.uuid) ?? translation.z,\n\t\t});\n\n\t\t// 5. State transitions from the controller's grounded result\n\t\tthis.updateState(entity, delta, controller.computedGrounded());\n\t}\n\n\tdestroy(): void {\n\t\tconst physicsWorld = this.world?.world;\n\t\tif (physicsWorld) {\n\t\t\tfor (const controller of this.controllers.values()) {\n\t\t\t\tphysicsWorld.removeCharacterController(controller);\n\t\t\t}\n\t\t}\n\t\tthis.controllers.clear();\n\t\tthis.planeZ.clear();\n\t}\n}\n","/**\n * Platformer 2D Behavior Descriptor\n *\n * Type-safe descriptor for the platformer 2D behavior system.\n * Provides entity.use() API for 2D platformer movement on the X/Y plane.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer2DSystem, Platformer2DEntity } from './platformer-2d.system';\nimport type { ZylemWorld } from '../../collision/world';\nimport { Platformer2DFSM, Platformer2DState } from './platformer-2d-fsm';\nimport {\n\tcreatePlatformer2DMovementComponent,\n\tcreatePlatformer2DInputComponent,\n\tcreatePlatformer2DStateComponent,\n} from './components';\n\n/**\n * Platformer 2D behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer2DBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 14) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 20) */\n\tgravity?: number;\n\t/** Coyote time in seconds (default: 0.1) */\n\tcoyoteTime?: number;\n\t/** Jump buffer time in seconds (default: 0.1) */\n\tjumpBufferTime?: number;\n\t/** Velocity multiplier when releasing jump early (default: 0.5) */\n\tjumpCutMultiplier?: number;\n\t/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small step-ups, or false to disable (default: { maxHeight: 0.25, minWidth: 0.05 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.2) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n}\n\nconst defaultOptions: Platformer2DBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 14,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 20,\n\tcoyoteTime: 0.1,\n\tjumpBufferTime: 0.1,\n\tjumpCutMultiplier: 0.5,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.25, minWidth: 0.05 },\n\tsnapToGroundDistance: 0.2,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-2d');\n\n/**\n * Adapter that wraps Platformer2DSystem as a BehaviorSystem.\n *\n * Wasm fast-path hook point: when the unified Stage runtime gains an\n * `attachPlatformer2D` entry point, this adapter should mirror the\n * platformer-3d pattern — attach once per `runtimeHandle`, forward input via\n * the stage setters, and skip the TS `updateEntity` path for wasm-managed\n * entities. Until then all entities take the TS path below.\n */\nclass Platformer2DBehaviorSystem implements BehaviorSystem {\n\tprivate movementSystem: Platformer2DSystem;\n\n\tconstructor(\n\t\tworld: ZylemWorld | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementSystem = new Platformer2DSystem(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_2D_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 platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer2DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer2D) {\n\t\t\t\tgameEntity.platformer2D = createPlatformer2DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer2D) {\n\t\t\t\tgameEntity.$platformer2D = createPlatformer2DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformer2DState) {\n\t\t\t\tgameEntity.platformer2DState = createPlatformer2DStateComponent();\n\t\t\t}\n\n\t\t\tif (!platformerRef.fsm) {\n\t\t\t\tplatformerRef.fsm = new Platformer2DFSM({\n\t\t\t\t\tinput: gameEntity.$platformer2D,\n\t\t\t\t\tstate: gameEntity.platformer2DState,\n\t\t\t\t});\n\t\t\t}\n\t\t\tplatformerRef.fsm.update(gameEntity.$platformer2D, gameEntity.platformer2DState);\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tthis.movementSystem.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.movementSystem.destroy();\n\t}\n}\n\n/**\n * Platformer2DBehavior - typed descriptor for 2D platformer movement\n *\n * Provides complete 2D platformer physics on the X/Y plane including:\n * - Walking and running on X\n * - Jumping with buffer, coyote time, multi-jump, and jump-cut\n * - Gravity ownership and falling\n * - Kinematic character controller (collide-and-slide, autostep, snap-to-ground)\n *\n * @example\n * ```typescript\n * import { Platformer2DBehavior } from \"@zylem/behaviors/platformer-2d\";\n *\n * const platformer = player.use(Platformer2DBehavior, {\n * walkSpeed: 8,\n * runSpeed: 14,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n *\n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer2D.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer2D.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer2D.run = inputs.p1.shoulders.LTrigger.held > 0;\n *\n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer2DBehavior = defineBehavior<\n\tPlatformer2DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer2DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t},\n\tPlatformer2DEntity\n>({\n\tname: 'platformer-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer2DBehaviorSystem(ctx.world ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => ref.fsm?.getState() ?? Platformer2DState.Idle,\n\t\tisGrounded: () => ref.fsm?.isGrounded() ?? false,\n\t\tgetJumpCount: () => ref.fsm?.getJumpCount() ?? 0,\n\t}),\n});\n"],"mappings":"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","names":["Platformer2DState","Platformer2DEvent"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/coordinators/shared/top-down-aim.ts","../src/lib/coordinators/shared/shooter-fire.ts"],"sourcesContent":["import {\n\tangleFromDirection2D,\n\tdirectionFromAngle2D,\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n} from '../../behaviors/shared/direction-2d';\n\nexport interface ResolvedAim2D {\n\tangle: number;\n\tdirection: { x: number; y: number };\n\thasAimInput: boolean;\n}\n\n/**\n * Minimal entity slice needed to derive a 2D facing angle. Structurally\n * satisfied by `Shooter2DSourceEntity` and the engine entity.\n */\nexport interface FacingAngle2DEntity {\n\ttopDownMovementState?: { facingAngle: number };\n\tbody?: {\n\t\trotation?: () => { x: number; y: number; z: number; w: number };\n\t} | null;\n\t/** Cached Z rotation maintained by 2D rotation helpers. */\n\t_rotation2DAngle?: number;\n}\n\nexport function getEntityFacingAngle2D(entity: FacingAngle2DEntity): number {\n\tif (entity.topDownMovementState) {\n\t\treturn entity.topDownMovementState.facingAngle;\n\t}\n\n\tif (entity.body?.rotation) {\n\t\treturn getRotationAngle2D(entity.body.rotation());\n\t}\n\n\treturn entity._rotation2DAngle ?? 0;\n}\n\nexport function resolveAim2D(\n\taimX: number,\n\taimY: number,\n\tfallbackAngle: number,\n): ResolvedAim2D {\n\tconst direction = normalizeDirection2D(aimX, aimY);\n\tif (direction) {\n\t\treturn {\n\t\t\tangle: angleFromDirection2D(direction),\n\t\t\tdirection,\n\t\t\thasAimInput: true,\n\t\t};\n\t}\n\n\treturn {\n\t\tangle: fallbackAngle,\n\t\tdirection: directionFromAngle2D(fallbackAngle),\n\t\thasAimInput: false,\n\t};\n}\n","import type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../../behaviors/shooter-2d/shooter-2d.descriptor';\nimport {\n\tgetEntityFacingAngle2D,\n\tresolveAim2D,\n\ttype ResolvedAim2D,\n} from './top-down-aim';\n\n/**\n * Aim + fire input fields shared by the 2D shooter coordinators\n * (top-down-shooter, multidirectional-space-shooter).\n */\nexport interface Shooter2DAimFireInput {\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\n/**\n * Fire the entity's Shooter2D behavior when the input requests it.\n *\n * Resolves the 2D aim (explicit aim axes, falling back to the entity's\n * current facing) and fires toward a point one unit along the aim direction.\n * Pass a pre-resolved `aim` to avoid recomputing when the coordinator already\n * resolved it for rotation.\n *\n * @returns true when a fire attempt was issued\n */\nexport function fireShooter2DIfRequested(\n\tentity: Shooter2DSourceEntity,\n\tshooter: Shooter2DHandle,\n\tstage: Shooter2DStageLike,\n\tinput: Shooter2DAimFireInput,\n\taim?: ResolvedAim2D,\n): boolean {\n\tif (!input.shootPressed && !input.shootHeld) {\n\t\treturn false;\n\t}\n\n\tconst sourcePosition = entity.body?.translation();\n\tif (!sourcePosition) {\n\t\treturn false;\n\t}\n\n\tconst resolved =\n\t\taim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));\n\n\tvoid shooter.fire({\n\t\tsource: entity,\n\t\tstage,\n\t\ttarget: {\n\t\t\tx: sourcePosition.x + resolved.direction.x,\n\t\t\ty: sourcePosition.y + resolved.direction.y,\n\t\t},\n\t});\n\treturn true;\n}\n"],"mappings":";;;;;;;;AA0BO,SAAS,uBAAuB,QAAqC;AAC3E,MAAI,OAAO,sBAAsB;AAChC,WAAO,OAAO,qBAAqB;AAAA,EACpC;AAEA,MAAI,OAAO,MAAM,UAAU;AAC1B,WAAO,mBAAmB,OAAO,KAAK,SAAS,CAAC;AAAA,EACjD;AAEA,SAAO,OAAO,oBAAoB;AACnC;AAEO,SAAS,aACf,MACA,MACA,eACgB;AAChB,QAAM,YAAY,qBAAqB,MAAM,IAAI;AACjD,MAAI,WAAW;AACd,WAAO;AAAA,MACN,OAAO,qBAAqB,SAAS;AAAA,MACrC;AAAA,MACA,aAAa;AAAA,IACd;AAAA,EACD;AAEA,SAAO;AAAA,IACN,OAAO;AAAA,IACP,WAAW,qBAAqB,aAAa;AAAA,IAC7C,aAAa;AAAA,EACd;AACD;;;ACzBO,SAAS,yBACf,QACA,SACA,OACA,OACA,KACU;AACV,MAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,OAAO,MAAM,YAAY;AAChD,MAAI,CAAC,gBAAgB;AACpB,WAAO;AAAA,EACR;AAEA,QAAM,WACL,OAAO,aAAa,MAAM,MAAM,MAAM,MAAM,uBAAuB,MAAM,CAAC;AAE3E,OAAK,QAAQ,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACP,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,MACzC,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,IAC1C;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":[]}
@@ -1 +0,0 @@
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\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\tif (ctx.entity?.body) {\n\t\t\tconst vel = ctx.entity.body.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };\n\t\t\tconst pos = ctx.entity.body.translation();\n\t\t\tselfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity?.body) {\n\t\t\tconst pos = ctx.otherEntity.body.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 WasmStageRuntime,\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: WasmStageRuntime | 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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/core/vector.ts"],"sourcesContent":["import { Vector2 as ThreeVector2, Vector3 as ThreeVector3 } from 'three';\nimport { Vector3 as RapierVector3 } from '@dimforge/rapier3d-compat';\n\nexport type Vec2 = ThreeVector2;\nexport type Vec3 = ThreeVector3 | RapierVector3;\n\nexport type Vec2Components = { x: number; y: number };\nexport type Vec3Components = { x: number; y: number; z: number };\n\ntype Vec2Tuple = readonly [number?, number?] & { x?: number; y?: number };\ntype Vec3Tuple = readonly [number?, number?, number?] & {\n x?: number;\n y?: number;\n z?: number;\n};\n\nexport type Vec2Input = Vec2 | { x?: number; y?: number } | Vec2Tuple;\nexport type Vec3Input =\n | Vec3\n | { x?: number; y?: number; z?: number }\n | Vec3Tuple;\n\nexport const VEC2_ZERO: Readonly<Vec2Components> = Object.freeze({\n x: 0,\n y: 0,\n});\nexport const VEC2_ONE: Readonly<Vec2Components> = Object.freeze({ x: 1, y: 1 });\nexport const VEC3_ZERO: Readonly<Vec3Components> = Object.freeze({\n x: 0,\n y: 0,\n z: 0,\n});\nexport const VEC3_ONE: Readonly<Vec3Components> = Object.freeze({\n x: 1,\n y: 1,\n z: 1,\n});\n\nfunction isFiniteNumber(value: unknown): value is number {\n return typeof value === 'number' && Number.isFinite(value);\n}\n\nfunction hasVec2Shape(value: unknown): value is { x?: unknown; y?: unknown } {\n return (\n value != null && typeof value === 'object' && ('x' in value || 'y' in value)\n );\n}\n\nfunction hasVec3Shape(\n value: unknown,\n): value is { x?: unknown; y?: unknown; z?: unknown } {\n return (\n value != null &&\n typeof value === 'object' &&\n ('x' in value || 'y' in value || 'z' in value)\n );\n}\n\nfunction getArrayAxis(\n value: readonly unknown[] | undefined,\n index: number,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n const axis = value[index];\n return isFiniteNumber(axis) ? axis : fallback;\n}\n\nfunction getObjectAxis(\n value: { [key: string]: unknown } | undefined,\n key: 'x' | 'y' | 'z',\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n const axis = value[key];\n return isFiniteNumber(axis) ? axis : fallback;\n}\n\nfunction getVec2Axis(\n value: Vec2Input | Partial<Vec2Components> | null | undefined,\n key: 'x' | 'y',\n index: 0 | 1,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n if (Array.isArray(value)) {\n return getArrayAxis(value, index, fallback);\n }\n return getObjectAxis(value as { [key: string]: unknown }, key, fallback);\n}\n\nfunction getVec3Axis(\n value: Vec3Input | Partial<Vec3Components> | null | undefined,\n key: 'x' | 'y' | 'z',\n index: 0 | 1 | 2,\n fallback: number,\n): number {\n if (!value) {\n return fallback;\n }\n if (Array.isArray(value)) {\n return getArrayAxis(value, index, fallback);\n }\n return getObjectAxis(value as { [key: string]: unknown }, key, fallback);\n}\n\nfunction normalizeVec2Defaults(\n defaults: Vec2Input | Partial<Vec2Components> | null | undefined,\n): Vec2Components {\n return {\n x: getVec2Axis(defaults, 'x', 0, 0),\n y: getVec2Axis(defaults, 'y', 1, 0),\n };\n}\n\nfunction normalizeVec3Defaults(\n defaults: Vec3Input | Partial<Vec3Components> | null | undefined,\n): Vec3Components {\n return {\n x: getVec3Axis(defaults, 'x', 0, 0),\n y: getVec3Axis(defaults, 'y', 1, 0),\n z: getVec3Axis(defaults, 'z', 2, 0),\n };\n}\n\nexport function isVec2Input(value: unknown): value is Vec2Input {\n return Array.isArray(value) || hasVec2Shape(value);\n}\n\nexport function isVec3Input(value: unknown): value is Vec3Input {\n return Array.isArray(value) || hasVec3Shape(value);\n}\n\nexport function normalizeVec2(\n input?: Vec2Input | null,\n defaults: Vec2Input | Partial<Vec2Components> = VEC2_ZERO,\n): Vec2Components {\n const fallback = normalizeVec2Defaults(defaults);\n return {\n x: getVec2Axis(input, 'x', 0, fallback.x),\n y: getVec2Axis(input, 'y', 1, fallback.y),\n };\n}\n\nexport function normalizeVec3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): Vec3Components {\n const fallback = normalizeVec3Defaults(defaults);\n return {\n x: getVec3Axis(input, 'x', 0, fallback.x),\n y: getVec3Axis(input, 'y', 1, fallback.y),\n z: getVec3Axis(input, 'z', 2, fallback.z),\n };\n}\n\nexport function toThreeVector2(\n input?: Vec2Input | null,\n defaults: Vec2Input | Partial<Vec2Components> = VEC2_ZERO,\n): ThreeVector2 {\n const { x, y } = normalizeVec2(input, defaults);\n return new ThreeVector2(x, y);\n}\n\nexport function toThreeVector3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): ThreeVector3 {\n const { x, y, z } = normalizeVec3(input, defaults);\n return new ThreeVector3(x, y, z);\n}\n\nexport function toRapierVector3(\n input?: Vec3Input | null,\n defaults: Vec3Input | Partial<Vec3Components> = VEC3_ZERO,\n): RapierVector3 {\n const { x, y, z } = normalizeVec3(input, defaults);\n return new RapierVector3(x, y, z);\n}\n"],"mappings":"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","names":[]}