@zylem/behaviors 0.5.1 → 0.5.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (75) hide show
  1. package/README.md +1 -1
  2. package/dist/{behavior-system-CeJXzNvr.d.ts → behavior-system-CldqNUgr.d.ts} +32 -5
  3. package/dist/{chunk-GQAPBNGT.js → chunk-4CEW64HO.js} +2 -2
  4. package/dist/{chunk-W26PHBME.js → chunk-4LLK2FJI.js} +41 -6
  5. package/dist/chunk-4LLK2FJI.js.map +1 -0
  6. package/dist/{chunk-VT3H43QA.js → chunk-4MZ57HPD.js} +41 -6
  7. package/dist/chunk-4MZ57HPD.js.map +1 -0
  8. package/dist/{chunk-ZRQWSURL.js → chunk-5XPPLTC7.js} +2 -2
  9. package/dist/chunk-5XPPLTC7.js.map +1 -0
  10. package/dist/{chunk-F6ESSHQK.js → chunk-7DGXUAPV.js} +2 -2
  11. package/dist/{chunk-KOWGILCN.js → chunk-7W3KY55G.js} +2 -2
  12. package/dist/{chunk-VFDUKGRZ.js → chunk-BAXJ7XHJ.js} +1 -1
  13. package/dist/chunk-BAXJ7XHJ.js.map +1 -0
  14. package/dist/{chunk-E4X4YJZB.js → chunk-DZ4LLQON.js} +31 -5
  15. package/dist/chunk-DZ4LLQON.js.map +1 -0
  16. package/dist/{chunk-5NJGASUJ.js → chunk-G2KUXVU6.js} +2 -2
  17. package/dist/{chunk-JGXRQUTC.js → chunk-LZIHSJZR.js} +2 -2
  18. package/dist/{chunk-5UMCWV5A.js → chunk-QS3VXWVO.js} +2 -2
  19. package/dist/{chunk-AEBDZWCP.js → chunk-WU6XXL3D.js} +2 -2
  20. package/dist/{chunk-5EBIIYZD.js → chunk-Y4PHT2HG.js} +2 -2
  21. package/dist/{components-BANNjAZP.d.ts → components-BmievOxF.d.ts} +1 -1
  22. package/dist/cooldown.d.ts +1 -1
  23. package/dist/cooldown.js +1 -1
  24. package/dist/coordinators/boundary-ricochet-3d.d.ts +2 -2
  25. package/dist/coordinators/boundary-ricochet.d.ts +2 -2
  26. package/dist/coordinators/multidirectional-space-shooter.d.ts +1 -1
  27. package/dist/coordinators/top-down-shooter.d.ts +1 -1
  28. package/dist/core.d.ts +16 -6
  29. package/dist/core.js +26 -5
  30. package/dist/core.js.map +1 -1
  31. package/dist/destructible-3d.d.ts +1 -1
  32. package/dist/first-person.d.ts +1 -1
  33. package/dist/first-person.js +2 -2
  34. package/dist/index.d.ts +3 -3
  35. package/dist/index.js +13 -13
  36. package/dist/jumper-2d.d.ts +1 -1
  37. package/dist/jumper-2d.js +2 -2
  38. package/dist/jumper-3d.d.ts +1 -1
  39. package/dist/jumper-3d.js +2 -2
  40. package/dist/{moveable-XhzWPAw5.d.ts → moveable-rdIvWiak.d.ts} +1 -1
  41. package/dist/particle-emitter.d.ts +1 -1
  42. package/dist/platformer-2d.d.ts +9 -1
  43. package/dist/platformer-2d.js +2 -2
  44. package/dist/platformer-3d.d.ts +9 -1
  45. package/dist/platformer-3d.js +2 -2
  46. package/dist/ricochet-2d.d.ts +1 -1
  47. package/dist/ricochet-2d.js +2 -2
  48. package/dist/ricochet-3d.d.ts +1 -1
  49. package/dist/ricochet-3d.js +2 -2
  50. package/dist/runtime-2d.d.ts +1 -1
  51. package/dist/screen-visibility.d.ts +1 -1
  52. package/dist/screen-wrap.d.ts +1 -1
  53. package/dist/shooter-2d.d.ts +1 -1
  54. package/dist/thruster.d.ts +1 -1
  55. package/dist/thruster.js +1 -1
  56. package/dist/top-down-movement.d.ts +1 -1
  57. package/dist/top-down-movement.js +2 -2
  58. package/dist/world-boundary-2d.d.ts +1 -1
  59. package/dist/world-boundary-2d.js +2 -2
  60. package/dist/world-boundary-3d.d.ts +1 -1
  61. package/dist/world-boundary-3d.js +2 -2
  62. package/package.json +4 -4
  63. package/dist/chunk-E4X4YJZB.js.map +0 -1
  64. package/dist/chunk-VFDUKGRZ.js.map +0 -1
  65. package/dist/chunk-VT3H43QA.js.map +0 -1
  66. package/dist/chunk-W26PHBME.js.map +0 -1
  67. package/dist/chunk-ZRQWSURL.js.map +0 -1
  68. /package/dist/{chunk-GQAPBNGT.js.map → chunk-4CEW64HO.js.map} +0 -0
  69. /package/dist/{chunk-F6ESSHQK.js.map → chunk-7DGXUAPV.js.map} +0 -0
  70. /package/dist/{chunk-KOWGILCN.js.map → chunk-7W3KY55G.js.map} +0 -0
  71. /package/dist/{chunk-5NJGASUJ.js.map → chunk-G2KUXVU6.js.map} +0 -0
  72. /package/dist/{chunk-JGXRQUTC.js.map → chunk-LZIHSJZR.js.map} +0 -0
  73. /package/dist/{chunk-5UMCWV5A.js.map → chunk-QS3VXWVO.js.map} +0 -0
  74. /package/dist/{chunk-AEBDZWCP.js.map → chunk-WU6XXL3D.js.map} +0 -0
  75. /package/dist/{chunk-5EBIIYZD.js.map → chunk-Y4PHT2HG.js.map} +0 -0
package/README.md CHANGED
@@ -9,7 +9,7 @@ The simulation layer of the Zylem framework: tree-shakable behavior descriptors,
9
9
  ```
10
10
 
11
11
  - **`createSimulation()` / `Simulation`** — loads and owns the WASM stage, exposes `spawn`/`despawn` with opaque `EntityHandle`s, a fixed-timestep `update(delta)` loop, snapshot/pose accessors, raycasts, debug rendering, and translated `SimulationEvent`s (collision start/stop with handles instead of raw slots).
12
- - **Behavior descriptors** (`Platformer3DBehavior`, `TopDownMovementBehavior`, …) — attach WASM-side controllers to entities and forward per-frame input through batched buffers; FSM state is read back from snapshots.
12
+ - **Behavior descriptors** (`Platformer3DBehavior`, `TopDownMovementBehavior`, …) — attach WASM-side controllers to entities and forward per-frame input through slot-indexed buffers; FSM state is read back from snapshots.
13
13
  - **`MockBehaviorRuntime`** — in-memory `BehaviorRuntime` implementation for unit-testing behaviors without loading WASM.
14
14
 
15
15
  Physics simulation is WASM-only; there is no TypeScript physics fallback. Disposal chains downward: `Simulation.dispose()` destroys the WASM stage.
@@ -113,7 +113,7 @@ interface VelocityIntent {
113
113
  priority?: number;
114
114
  }
115
115
  /**
116
- * Transform state managed by Valtio for batched physics updates.
116
+ * Transform state managed by Valtio for per-frame physics intent accumulation.
117
117
  *
118
118
  * This store accumulates transformation intents during the frame,
119
119
  * then applies them all at once after onUpdate() callbacks complete.
@@ -372,7 +372,19 @@ interface StagePlatformer3DOptions {
372
372
  snapToGroundDistance?: number;
373
373
  /** KCC collision offset between collider and environment (default 0.05). */
374
374
  characterOffset?: number;
375
- }
375
+ /** Collider local offset X (fallback / override). */
376
+ offsetX?: number;
377
+ /** Collider local offset Y (fallback / override). */
378
+ offsetY?: number;
379
+ /** Collider local offset Z (fallback / override). */
380
+ offsetZ?: number;
381
+ /** 0 = auto-read attached collider; 1 = use stored shape fields. */
382
+ shapeSource?: number;
383
+ }
384
+ declare const StagePlatformerShapeSource: {
385
+ readonly Auto: 0;
386
+ readonly Override: 1;
387
+ };
376
388
  interface StagePlatformer2DOptions {
377
389
  halfHeight: number;
378
390
  radius: number;
@@ -401,6 +413,14 @@ interface StagePlatformer2DOptions {
401
413
  snapToGroundDistance?: number;
402
414
  /** KCC collision offset between collider and environment (default 0.05). */
403
415
  characterOffset?: number;
416
+ /** Collider local offset X (fallback / override). */
417
+ offsetX?: number;
418
+ /** Collider local offset Y (fallback / override). */
419
+ offsetY?: number;
420
+ /** Collider local offset Z (fallback / override). */
421
+ offsetZ?: number;
422
+ /** 0 = auto-read attached collider; 1 = use stored shape fields. */
423
+ shapeSource?: number;
404
424
  }
405
425
  interface StageFirstPersonOptions {
406
426
  walkSpeed: number;
@@ -415,7 +435,7 @@ interface StageFirstPersonInput {
415
435
  lookPitchDelta: number;
416
436
  run: boolean;
417
437
  }
418
- /** Button bits for the batched input buffer's bitmask field. */
438
+ /** Button bits for the slot-indexed input buffer's bitmask field. */
419
439
  declare const STAGE_INPUT_BUTTON_JUMP = 1;
420
440
  declare const STAGE_INPUT_BUTTON_RUN = 2;
421
441
  /**
@@ -639,6 +659,13 @@ declare class WasmStageRuntime implements BehaviorRuntime {
639
659
  get tickCount(): number;
640
660
  get activeCount(): number;
641
661
  setGravity(x: number, y: number, z: number): void;
662
+ /**
663
+ * Toggle per-step collision-event collection in the wasm stage. Hosts
664
+ * with no collision listeners should disable it — with thousands of
665
+ * touching bodies the per-step contact-pair hashing is a significant
666
+ * fraction of `step()`.
667
+ */
668
+ setEventsEnabled(enabled: boolean): void;
642
669
  createEntity(): number;
643
670
  destroyEntity(slot: number): boolean;
644
671
  attachBody(slot: number, config: StageBodyConfig): boolean;
@@ -676,7 +703,7 @@ declare class WasmStageRuntime implements BehaviorRuntime {
676
703
  get snapshot(): Float32Array;
677
704
  get snapshotStride(): number;
678
705
  /**
679
- * Write the primary movement axes for a slot into the batched input
706
+ * Write the primary movement axes for a slot into the slot-indexed input
680
707
  * buffer and mark the slot dirty. Field-scoped (doesn't touch look
681
708
  * deltas or buttons) so multiple behaviors composed on one entity can
682
709
  * each write their part.
@@ -865,4 +892,4 @@ interface BehaviorSystemContext {
865
892
  */
866
893
  type BehaviorSystemFactory<T extends BehaviorSystem = BehaviorSystem> = (stage: BehaviorSystemContext) => T;
867
894
 
868
- export { type WasmSnapshotBackref as $, type StageJumper2DOptions as A, type BaseBehaviorHandle as B, type CreateEntityFn as C, type DefineBehaviorConfig as D, type StageJumper3DOptions as E, type StagePlatformer2DOptions as F, GameEntity as G, type StagePlatformer3DOptions as H, type StagePose as I, type StageRaycastHit as J, type StageRenderSlot as K, StageRicochetDim as L, type StageRicochetDimValue as M, type StageRicochetOptions as N, StageRicochetReflection as O, type StageRicochetReflectionValue as P, type StageScreenWrapOptions as Q, type StageShooter2DOptions as R, STAGE_INPUT_BUTTON_JUMP as S, type StageThrusterOptions as T, type StageTopDownOptions as U, StageTopDownPlane as V, type StageTopDownPlaneValue as W, type StageWorldBoundaryOptions as X, type TransformState as Y, type VelocityIntent as Z, type VelocityIntentMode as _, type BehaviorDescriptor as a, WasmStageRuntime as a0, type WasmStageRuntimeOptions as a1, createTransformStore as a2, createWasmStageRuntime as a3, defineBehavior as a4, readBehaviorSnapshot as a5, readStageRenderSlot as a6, type BehaviorEntityLink as b, type BehaviorHandle as c, type BehaviorRef as d, type BehaviorRuntime as e, type BehaviorSystem as f, type BehaviorSystemContext as g, type BehaviorSystemFactory as h, type GameEntityOptions as i, STAGE_INPUT_BUTTON_RUN as j, STAGE_INVALID_SLOT as k, type StageBehaviorSnapshot as l, type StageBodyConfig as m, type StageBodyInfo as n, StageBodyKind as o, type StageBodyKindValue as p, StageBoundaryDim as q, type StageBoundaryDimValue as r, type StageColliderConfig as s, type StageColliderShape as t, type StageDebugRender as u, type StageEvent as v, StageEventType as w, type StageEventTypeValue as x, type StageFirstPersonInput as y, type StageFirstPersonOptions as z };
895
+ export { type VelocityIntentMode as $, type StageJumper2DOptions as A, type BaseBehaviorHandle as B, type CreateEntityFn as C, type DefineBehaviorConfig as D, type StageJumper3DOptions as E, type StagePlatformer2DOptions as F, GameEntity as G, type StagePlatformer3DOptions as H, StagePlatformerShapeSource as I, type StagePose as J, type StageRaycastHit as K, type StageRenderSlot as L, StageRicochetDim as M, type StageRicochetDimValue as N, type StageRicochetOptions as O, StageRicochetReflection as P, type StageRicochetReflectionValue as Q, type StageScreenWrapOptions as R, STAGE_INPUT_BUTTON_JUMP as S, type StageShooter2DOptions as T, type StageThrusterOptions as U, type StageTopDownOptions as V, StageTopDownPlane as W, type StageTopDownPlaneValue as X, type StageWorldBoundaryOptions as Y, type TransformState as Z, type VelocityIntent as _, type BehaviorDescriptor as a, type WasmSnapshotBackref as a0, WasmStageRuntime as a1, type WasmStageRuntimeOptions as a2, createTransformStore as a3, createWasmStageRuntime as a4, defineBehavior as a5, readBehaviorSnapshot as a6, readStageRenderSlot as a7, type BehaviorEntityLink as b, type BehaviorHandle as c, type BehaviorRef as d, type BehaviorRuntime as e, type BehaviorSystem as f, type BehaviorSystemContext as g, type BehaviorSystemFactory as h, type GameEntityOptions as i, STAGE_INPUT_BUTTON_RUN as j, STAGE_INVALID_SLOT as k, type StageBehaviorSnapshot as l, type StageBodyConfig as m, type StageBodyInfo as n, StageBodyKind as o, type StageBodyKindValue as p, StageBoundaryDim as q, type StageBoundaryDimValue as r, type StageColliderConfig as s, type StageColliderShape as t, type StageDebugRender as u, type StageEvent as v, StageEventType as w, type StageEventTypeValue as x, type StageFirstPersonInput as y, type StageFirstPersonOptions as z };
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  STAGE_INPUT_BUTTON_JUMP,
7
7
  readBehaviorSnapshot
8
- } from "./chunk-E4X4YJZB.js";
8
+ } from "./chunk-DZ4LLQON.js";
9
9
  import {
10
10
  defineBehavior
11
11
  } from "./chunk-EZNCCO4L.js";
@@ -207,4 +207,4 @@ export {
207
207
  Jumper2DFSM,
208
208
  Jumper2DBehavior
209
209
  };
210
- //# sourceMappingURL=chunk-GQAPBNGT.js.map
210
+ //# sourceMappingURL=chunk-4CEW64HO.js.map
@@ -5,8 +5,9 @@ import {
5
5
  import {
6
6
  STAGE_INPUT_BUTTON_JUMP,
7
7
  STAGE_INPUT_BUTTON_RUN,
8
+ StagePlatformerShapeSource,
8
9
  readBehaviorSnapshot
9
- } from "./chunk-E4X4YJZB.js";
10
+ } from "./chunk-DZ4LLQON.js";
10
11
  import {
11
12
  defineBehavior
12
13
  } from "./chunk-EZNCCO4L.js";
@@ -213,6 +214,40 @@ var defaultOptions = {
213
214
  characterOffset: 0.05
214
215
  };
215
216
  var PLATFORMER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-3d");
217
+ function resolvePlatformerAttachShape(options, gameEntity) {
218
+ const fallback = {
219
+ halfHeight: 0.9,
220
+ radius: 0.4,
221
+ offsetX: 0,
222
+ offsetY: 0,
223
+ offsetZ: 0,
224
+ shapeSource: StagePlatformerShapeSource.Auto
225
+ };
226
+ if (options.useAttachedCollider === false && options.shape) {
227
+ const offset = options.shape.offset ?? [0, 0, 0];
228
+ return {
229
+ halfHeight: options.shape.halfHeight,
230
+ radius: options.shape.radius,
231
+ offsetX: offset[0],
232
+ offsetY: offset[1],
233
+ offsetZ: offset[2],
234
+ shapeSource: StagePlatformerShapeSource.Override
235
+ };
236
+ }
237
+ const collider = gameEntity?.colliderDesc;
238
+ if (collider?.shape?.type === "capsule") {
239
+ const offset = collider.offset ?? [0, 0, 0];
240
+ return {
241
+ halfHeight: collider.shape.halfHeight ?? fallback.halfHeight,
242
+ radius: collider.shape.radius ?? fallback.radius,
243
+ offsetX: offset[0],
244
+ offsetY: offset[1],
245
+ offsetZ: offset[2],
246
+ shapeSource: StagePlatformerShapeSource.Auto
247
+ };
248
+ }
249
+ return fallback;
250
+ }
216
251
  var Platformer3DBehaviorSystem = class {
217
252
  constructor(wasmStage, getBehaviorLinks) {
218
253
  this.wasmStage = wasmStage;
@@ -239,7 +274,7 @@ var Platformer3DBehaviorSystem = class {
239
274
  }
240
275
  const handle = gameEntity.runtimeHandle ?? -1;
241
276
  if (!this.wasmStage || handle < 0) continue;
242
- this.ensureWasmAttached(handle, options);
277
+ this.ensureWasmAttached(handle, options, gameEntity);
243
278
  platformerRef.__wasmStage = this.wasmStage;
244
279
  platformerRef.__entity = gameEntity;
245
280
  const input = gameEntity.$platformer;
@@ -250,12 +285,12 @@ var Platformer3DBehaviorSystem = class {
250
285
  }
251
286
  }
252
287
  }
253
- ensureWasmAttached(handle, options) {
288
+ ensureWasmAttached(handle, options, gameEntity) {
254
289
  if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
255
290
  const autostep = options.autostep ?? defaultOptions.autostep;
291
+ const shape = resolvePlatformerAttachShape(options, gameEntity);
256
292
  this.wasmStage.attachPlatformer3D(handle, {
257
- halfHeight: 0.9,
258
- radius: 0.4,
293
+ ...shape,
259
294
  walkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed,
260
295
  runSpeed: options.runSpeed ?? defaultOptions.runSpeed,
261
296
  jumpForce: options.jumpForce ?? defaultOptions.jumpForce,
@@ -306,4 +341,4 @@ export {
306
341
  Platformer3DFSM,
307
342
  Platformer3DBehavior
308
343
  };
309
- //# sourceMappingURL=chunk-W26PHBME.js.map
344
+ //# sourceMappingURL=chunk-4LLK2FJI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/platformer-3d/components.ts","../src/lib/behaviors/platformer-3d/platformer-3d-fsm.ts","../src/lib/behaviors/platformer-3d/platformer-3d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 3D ECS Components\n * \n * Components for 3D platformer movement system including walking, running,\n * jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DMovementComponent (capability / configuration)\n// Defines the movement capabilities and physics parameters\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DMovementComponent {\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 (optional override) */\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 small ledges, 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 createPlatformer3DMovementComponent(\n\toptions: Partial<Platformer3DMovementComponent> = {}\n): Platformer3DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 12,\n\t\trunSpeed: options.runSpeed ?? 24,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 9.82,\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, // 150ms default\n\t\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.3, minWidth: 0.1 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.3,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer3DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Forward/backward movement input (-1 to 1) */\n\tmoveZ: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer3DInputComponent(): Platformer3DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tmoveZ: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer3DStateComponent (runtime state)\n// Tracks the current state of the platformer entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer3DStateComponent {\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\n\t// -- New fields --\n\t/** Is currently colliding with ground (from physics collision callback) */\n\tcollisionGrounded: boolean;\n\t/** Timestamp of last ground collision */\n\tgroundedCollisionTime: number;\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 createPlatformer3DStateComponent(): Platformer3DStateComponent {\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\tcollisionGrounded: false,\n\t\tgroundedCollisionTime: 0,\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 3D Finite State Machine\n * \n * Manages state transitions for 3D 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 { Platformer3DInputComponent, Platformer3DStateComponent } from './components';\n\n/**\n * Simplified Collision Context interface\n */\nexport interface PlatformerCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number; z?: number };\n\t};\n}\n\n/**\n * Platformer states\n */\nexport enum Platformer3DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum Platformer3DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface Platformer3DContext {\n\tinput: Platformer3DInputComponent;\n\tstate: Platformer3DStateComponent;\n}\n\n/**\n * Platformer 3D FSM\n */\nexport class Platformer3DFSM {\n\tmachine: SyncStateMachine<Platformer3DState, Platformer3DEvent, never>;\n\n\tconstructor(private ctx: Platformer3DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer3DState, Platformer3DEvent, never>(\n\t\t\tPlatformer3DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Walking, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Jump, Platformer3DState.Jumping),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\tt(Platformer3DState.Running, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\t\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Fall, Platformer3DState.Falling),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\tt(Platformer3DState.Jumping, Platformer3DEvent.Jump, Platformer3DState.Jumping), // Multi-jump\n\t\t\t\t\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer3DState.Falling, Platformer3DEvent.Land, Platformer3DState.Landing),\n\t\t\t\t\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Walk, Platformer3DState.Walking),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Run, Platformer3DState.Running),\n\t\t\t\tt(Platformer3DState.Landing, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t\t\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer3DState.Idle, Platformer3DEvent.Stop, Platformer3DState.Idle),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get the current state\n\t */\n\tgetState(): Platformer3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to the FSM\n\t */\n\tdispatch(event: Platformer3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Check if grounded\n\t */\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn state === Platformer3DState.Idle || \n\t\t state === Platformer3DState.Walking || \n\t\t state === Platformer3DState.Running || \n\t\t state === Platformer3DState.Landing;\n\t}\n\n\t/**\n\t * Get current jump count from context\n\t */\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Handle collision event to update ground state\n\t */\n\thandleCollision(ctx: PlatformerCollisionContext): void {\n\t\t// Check if collision is ground (normal pointing up)\n\t\t// Y normal > 0.5 means roughly within 60 degrees of up\n\t\tif (ctx.contact.normal.y > 0.5) {\n\t\t\tthis.ctx.state.collisionGrounded = true;\n\t\t\tthis.ctx.state.groundedCollisionTime = performance.now();\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on current state\n\t */\n\tupdate(input: Platformer3DInputComponent, state: Platformer3DStateComponent): void {\n\t\t// Update context\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\t// Get current FSM state\n\t\tconst currentState = this.getState();\n\n\t\t// Determine movement state\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 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 === Platformer3DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer3DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\t// Send appropriate events based on state\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer3DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Stop);\n\t\t\t}\n\t\t} else {\n\t\t\t// Not grounded - check if we should be falling\n\t\t\t// Transition to falling if: We're in jumping state and now falling, \n\t\t\t// OR we walked off a ledge (idle/walking/running and now falling)\n\t\t\tif (state.falling) {\n\t\t\t\tthis.dispatch(Platformer3DEvent.Fall);\n\t\t\t}\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in behavior)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer3DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 3D Behavior Descriptor\n * \n * Type-safe descriptor for the platformer 3D behavior system.\n * Provides entity.use() API for 3D platformer movement.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer3DState, PlatformerCollisionContext } from './platformer-3d-fsm';\nimport {\n\tcreatePlatformer3DMovementComponent,\n\tcreatePlatformer3DInputComponent,\n\tcreatePlatformer3DStateComponent,\n\ttype Platformer3DMovementComponent,\n\ttype Platformer3DInputComponent,\n\ttype Platformer3DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\tStagePlatformerShapeSource,\n\ttype BehaviorRuntime,\n\ttype StagePlatformer3DOptions,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust `Platformer3DFsmState` index → TS state (see `platformer_3d/fsm.rs`). */\nconst FSM_STATE_BY_INDEX: readonly Platformer3DState[] = [\n\tPlatformer3DState.Idle,\n\tPlatformer3DState.Walking,\n\tPlatformer3DState.Running,\n\tPlatformer3DState.Jumping,\n\tPlatformer3DState.Falling,\n\tPlatformer3DState.Landing,\n];\n\n/**\n * Platformer behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer3DBehaviorOptions {\n\t/** Base walking speed (default: 12) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 24) */\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: 9.82) */\n\tgravity?: 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 ledges, or false to disable (default: { maxHeight: 0.3, minWidth: 0.1 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.3) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n\t/** Explicit KCC capsule override (requires `useAttachedCollider: false`). */\n\tshape?: {\n\t\thalfHeight: number;\n\t\tradius: number;\n\t\toffset?: [number, number, number];\n\t};\n\t/** When true (default), KCC reads shape + offset from the attached Rapier collider. */\n\tuseAttachedCollider?: boolean;\n}\n\nconst defaultOptions: Platformer3DBehaviorOptions = {\n\twalkSpeed: 12,\n\trunSpeed: 24,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 9.82,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.3, minWidth: 0.1 },\n\tsnapToGroundDistance: 0.3,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-3d');\n\nfunction resolvePlatformerAttachShape(\n\toptions: Platformer3DBehaviorOptions,\n\tgameEntity?: {\n\t\tcolliderDesc?: {\n\t\t\tshape?: { type?: string; halfHeight?: number; radius?: number };\n\t\t\toffset?: [number, number, number];\n\t\t};\n\t},\n): Pick<StagePlatformer3DOptions, 'halfHeight' | 'radius' | 'offsetX' | 'offsetY' | 'offsetZ' | 'shapeSource'> {\n\tconst fallback = {\n\t\thalfHeight: 0.9,\n\t\tradius: 0.4,\n\t\toffsetX: 0,\n\t\toffsetY: 0,\n\t\toffsetZ: 0,\n\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t};\n\n\tif (options.useAttachedCollider === false && options.shape) {\n\t\tconst offset = options.shape.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: options.shape.halfHeight,\n\t\t\tradius: options.shape.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Override,\n\t\t};\n\t}\n\n\tconst collider = gameEntity?.colliderDesc;\n\tif (collider?.shape?.type === 'capsule') {\n\t\tconst offset = collider.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: collider.shape.halfHeight ?? fallback.halfHeight,\n\t\t\tradius: collider.shape.radius ?? fallback.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t\t};\n\t}\n\n\treturn fallback;\n}\n\n/**\n * Entity with platformer components (input written by the game; the wasm\n * runtime owns the kinematic character controller and FSM).\n */\nexport interface Platformer3DEntity {\n\tuuid: string;\n\tplatformer: Platformer3DMovementComponent;\n\t$platformer: Platformer3DInputComponent;\n\tplatformerState: Platformer3DStateComponent;\n}\n\n/**\n * Forwards platformer input into the wasm runtime. The Rust `platformer_3d`\n * Stage behavior (KCC + jump FSM) is the single simulation path; handles\n * read its state back via the snapshot buffer.\n */\nclass Platformer3DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_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 Platformer3DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer) {\n\t\t\t\tgameEntity.platformer = createPlatformer3DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer) {\n\t\t\t\tgameEntity.$platformer = createPlatformer3DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformerState) {\n\t\t\t\tgameEntity.platformerState = createPlatformer3DStateComponent();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options, gameEntity);\n\t\t\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$platformer;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(\n\t\thandle: number,\n\t\toptions: Platformer3DBehaviorOptions,\n\t\tgameEntity?: { colliderDesc?: { shape?: { type?: string; halfHeight?: number; radius?: number }; offset?: [number, number, number] } },\n\t): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst autostep = options.autostep ?? defaultOptions.autostep!;\n\t\tconst shape = resolvePlatformerAttachShape(options, gameEntity);\n\t\tthis.wasmStage.attachPlatformer3D(handle, {\n\t\t\t...shape,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.1,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer3DBehavior - typed descriptor for 3D platformer movement\n * \n * Provides complete 3D platformer physics including:\n * - Walking and running\n * - Jumping with multi-jump support\n * - Gravity and falling\n * - Ground detection\n * \n * @example\n * ```typescript\n * import { Platformer3DBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const player = await playgroundActor('player');\n * const platformer = player.use(Platformer3DBehavior, {\n * walkSpeed: 12,\n * runSpeed: 24,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n * \n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer.moveZ = inputs.p1.axes.Vertical.value;\n * player.$platformer.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer.run = inputs.p1.shoulders.LTrigger.held > 0;\n * \n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer3DBehavior = defineBehavior<\n\tPlatformer3DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer3DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t\tonPlatformCollision: (ctx: PlatformerCollisionContext) => void;\n\t},\n\tPlatformer3DEntity\n>({\n\tname: 'platformer-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer3DBehaviorSystem(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (values are from the end of\n\t// the previous step — one-frame lag). The Rust FSM is the single source\n\t// of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer3DState.Idle;\n\t\t\treturn Platformer3DState.Idle;\n\t\t},\n\t\tisGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,\n\t\tgetJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0,\n\t\tonPlatformCollision: (_ctx) => {},\n\t}),\n});\n"],"mappings":";;;;;;;;;;;;;;;AA2CO,SAAS,oCACf,UAAkD,CAAC,GACnB;AAChC,SAAO;AAAA,IACN,WAAW,QAAQ,aAAa;AAAA,IAChC,UAAU,QAAQ,YAAY;AAAA,IAC9B,WAAW,QAAQ,aAAa;AAAA,IAChC,UAAU,QAAQ,YAAY;AAAA,IAC9B,SAAS,QAAQ,WAAW;AAAA,IAC5B,YAAY,QAAQ,cAAc;AAAA,IAClC,gBAAgB,QAAQ,kBAAkB;AAAA,IAC1C,mBAAmB,QAAQ,qBAAqB;AAAA,IAChD,qBAAqB,QAAQ,uBAAuB;AAAA;AAAA,IACpD,iBAAiB,QAAQ,mBAAmB;AAAA,IAC5C,iBAAiB,QAAQ,mBAAmB;AAAA,IAC5C,UAAU,QAAQ,YAAY,EAAE,WAAW,KAAK,UAAU,IAAI;AAAA,IAC9D,sBAAsB,QAAQ,wBAAwB;AAAA,IACtD,iBAAiB,QAAQ,mBAAmB;AAAA,EAC7C;AACD;AAmBO,SAAS,mCAA+D;AAC9E,SAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,MAAM;AAAA,IACN,KAAK;AAAA,EACN;AACD;AAwDO,SAAS,mCAA+D;AAC9E,SAAO;AAAA,IACN,UAAU;AAAA,IACV,SAAS;AAAA,IACT,SAAS;AAAA,IACT,WAAW;AAAA,IACX,iBAAiB;AAAA,IACjB,cAAc;AAAA,IACd,eAAe;AAAA,IACf,sBAAsB;AAAA,IACtB,mBAAmB;AAAA,IACnB,uBAAuB;AAAA,IACvB,mBAAmB;AAAA,IACnB,cAAc;AAAA,IACd,iBAAiB;AAAA,IACjB,UAAU;AAAA,IACV,gBAAgB;AAAA,IAChB,2BAA2B;AAAA,IAC3B,eAAe;AAAA,IACf,iBAAiB;AAAA,IACjB,kBAAkB;AAAA,IAClB,kBAAkB;AAAA,EACnB;AACD;;;AC5IO,IAAK,oBAAL,kBAAKA,uBAAL;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AACV,EAAAA,mBAAA,aAAU;AANC,SAAAA;AAAA,GAAA;AAYL,IAAK,oBAAL,kBAAKC,uBAAL;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,SAAM;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AANI,SAAAA;AAAA,GAAA;AAoBL,IAAM,kBAAN,MAAsB;AAAA,EAG5B,YAAoB,KAA0B;AAA1B;AACnB,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA;AAAA,YAEC,yBAAE,mBAAwB,mBAAwB,uBAAyB;AAAA,YAC3E,yBAAE,mBAAwB,iBAAuB,uBAAyB;AAAA,YAC1E,yBAAE,mBAAwB,mBAAwB,uBAAyB;AAAA,YAC3E,yBAAE,mBAAwB,mBAAwB,uBAAyB;AAAA;AAAA,YAG3E,yBAAE,yBAA2B,iBAAuB,uBAAyB;AAAA,YAC7E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,mBAAwB,iBAAsB;AAAA,YAC3E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,YAG9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,mBAAwB,iBAAsB;AAAA,YAC3E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,YAG9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA;AAAA,YAG9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA;AAAA,YAG9E,yBAAE,yBAA2B,mBAAwB,uBAAyB;AAAA,YAC9E,yBAAE,yBAA2B,iBAAuB,uBAAyB;AAAA,YAC7E,yBAAE,yBAA2B,mBAAwB,iBAAsB;AAAA;AAAA,YAG3E,yBAAE,mBAAwB,mBAAwB,iBAAsB;AAAA,MACzE;AAAA,IACD;AAAA,EACD;AAAA,EAvCoB;AAAA,EAFpB;AAAA;AAAA;AAAA;AAAA,EA8CA,WAA8B;AAC7B,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,SAAS,OAAgC;AACxC,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,aAAsB;AACrB,UAAM,QAAQ,KAAK,SAAS;AAC5B,WAAO,UAAU,qBACV,UAAU,2BACV,UAAU,2BACV,UAAU;AAAA,EAClB;AAAA;AAAA;AAAA;AAAA,EAKA,eAAuB;AACtB,WAAO,KAAK,IAAI,MAAM;AAAA,EACvB;AAAA;AAAA;AAAA;AAAA,EAKA,gBAAgB,KAAuC;AAGtD,QAAI,IAAI,QAAQ,OAAO,IAAI,KAAK;AAC/B,WAAK,IAAI,MAAM,oBAAoB;AACnC,WAAK,IAAI,MAAM,wBAAwB,YAAY,IAAI;AAAA,IACxD;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,OAAmC,OAAyC;AAElF,SAAK,IAAI,QAAQ;AACjB,SAAK,IAAI,QAAQ;AAGjB,UAAM,eAAe,KAAK,SAAS;AAGnC,UAAM,WAAW,KAAK,IAAI,MAAM,KAAK,IAAI,OAAO,KAAK,IAAI,MAAM,KAAK,IAAI;AACxE,UAAM,YAAY,MAAM;AAGxB,QAAI,iBAAiB,2BAA6B,MAAM,UAAU;AACjE,WAAK,SAAS,iBAAsB;AAAA,IAErC;AAGA,QAAI,MAAM,UAAU;AACnB,UAAI,UAAU;AACb,YAAI,WAAW;AACd,eAAK,SAAS,eAAqB;AAAA,QACpC,OAAO;AACN,eAAK,SAAS,iBAAsB;AAAA,QACrC;AAAA,MACD,OAAO;AACN,aAAK,SAAS,iBAAsB;AAAA,MACrC;AAAA,IACD,OAAO;AAIN,UAAI,MAAM,SAAS;AAClB,aAAK,SAAS,iBAAsB;AAAA,MACrC;AAAA,IACD;AAGA,QAAI,MAAM,QAAQ,CAAC,MAAM,sBAAsB;AAC9C,WAAK,SAAS,iBAAsB;AAAA,IACrC;AAAA,EACD;AACD;;;ACvKA,IAAM,qBAAmD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAOzD;AAoCA,IAAM,iBAA8C;AAAA,EACnD,WAAW;AAAA,EACX,UAAU;AAAA,EACV,WAAW;AAAA,EACX,UAAU;AAAA,EACV,SAAS;AAAA,EACT,iBAAiB;AAAA,EACjB,iBAAiB;AAAA,EACjB,UAAU,EAAE,WAAW,KAAK,UAAU,IAAI;AAAA,EAC1C,sBAAsB;AAAA,EACtB,iBAAiB;AAClB;AAEA,IAAM,0BAA0B,uBAAO,IAAI,8BAA8B;AAEzE,SAAS,6BACR,SACA,YAM8G;AAC9G,QAAM,WAAW;AAAA,IAChB,YAAY;AAAA,IACZ,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,IACT,SAAS;AAAA,IACT,aAAa,2BAA2B;AAAA,EACzC;AAEA,MAAI,QAAQ,wBAAwB,SAAS,QAAQ,OAAO;AAC3D,UAAM,SAAS,QAAQ,MAAM,UAAU,CAAC,GAAG,GAAG,CAAC;AAC/C,WAAO;AAAA,MACN,YAAY,QAAQ,MAAM;AAAA,MAC1B,QAAQ,QAAQ,MAAM;AAAA,MACtB,SAAS,OAAO,CAAC;AAAA,MACjB,SAAS,OAAO,CAAC;AAAA,MACjB,SAAS,OAAO,CAAC;AAAA,MACjB,aAAa,2BAA2B;AAAA,IACzC;AAAA,EACD;AAEA,QAAM,WAAW,YAAY;AAC7B,MAAI,UAAU,OAAO,SAAS,WAAW;AACxC,UAAM,SAAS,SAAS,UAAU,CAAC,GAAG,GAAG,CAAC;AAC1C,WAAO;AAAA,MACN,YAAY,SAAS,MAAM,cAAc,SAAS;AAAA,MAClD,QAAQ,SAAS,MAAM,UAAU,SAAS;AAAA,MAC1C,SAAS,OAAO,CAAC;AAAA,MACjB,SAAS,OAAO,CAAC;AAAA,MACjB,SAAS,OAAO,CAAC;AAAA,MACjB,aAAa,2BAA2B;AAAA,IACzC;AAAA,EACD;AAEA,SAAO;AACR;AAkBA,IAAM,6BAAN,MAA2D;AAAA,EAG1D,YACS,WACA,kBACP;AAFO;AACA;AAAA,EACN;AAAA,EAFM;AAAA,EACA;AAAA,EAJD,uBAAuB,oBAAI,IAAY;AAAA,EAO/C,OAAO,MAAe,QAAsB;AAC3C,UAAM,QAAQ,KAAK,mBAAmB,uBAAuB;AAC7D,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,aAAa,KAAK;AACxB,YAAM,gBAAgB,KAAK;AAE3B,YAAM,UAAU,cAAc;AAE9B,UAAI,CAAC,WAAW,YAAY;AAC3B,mBAAW,aAAa,oCAAoC,OAAO;AAAA,MACpE;AACA,UAAI,CAAC,WAAW,aAAa;AAC5B,mBAAW,cAAc,iCAAiC;AAAA,MAC3D;AACA,UAAI,CAAC,WAAW,iBAAiB;AAChC,mBAAW,kBAAkB,iCAAiC;AAAA,MAC/D;AAEA,YAAM,SAAU,WAAW,iBAAiB;AAC5C,UAAI,CAAC,KAAK,aAAa,SAAS,EAAG;AAEnC,WAAK,mBAAmB,QAAQ,SAAS,UAAU;AACnD,oBAAc,cAAc,KAAK;AACjC,oBAAc,WAAW;AACzB,YAAM,QAAQ,WAAW;AACzB,UAAI,OAAO;AACV,aAAK,UAAU,eAAe,QAAQ,MAAM,SAAS,GAAG,MAAM,SAAS,CAAC;AACxE,aAAK,UAAU,iBAAiB,QAAQ,yBAAyB,CAAC,CAAC,MAAM,IAAI;AAC7E,aAAK,UAAU,iBAAiB,QAAQ,wBAAwB,CAAC,CAAC,MAAM,GAAG;AAAA,MAC5E;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,mBACP,QACA,SACA,YACO;AACP,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,UAAM,WAAW,QAAQ,YAAY,eAAe;AACpD,UAAM,QAAQ,6BAA6B,SAAS,UAAU;AAC9D,SAAK,UAAU,mBAAmB,QAAQ;AAAA,MACzC,GAAG;AAAA,MACH,WAAW,QAAQ,aAAa,eAAe;AAAA,MAC/C,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,WAAW,QAAQ,aAAa,eAAe;AAAA,MAC/C,UAAU,QAAQ,YAAY,eAAe;AAAA,MAC7C,SAAS,QAAQ,WAAW,eAAe;AAAA,MAC3C,iBAAiB,QAAQ,mBAAmB,eAAe;AAAA,MAC3D,iBAAiB,QAAQ,mBAAmB,eAAe;AAAA,MAC3D,gBAAgB,WAAW,SAAS,YAAY;AAAA,MAChD,kBAAkB,WAAW,SAAS,WAAW;AAAA,MACjD,sBAAsB,QAAQ,wBAAwB,eAAe;AAAA,MACrE,iBAAiB,QAAQ,mBAAmB,eAAe;AAAA,IAC5D,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AAmCO,IAAM,uBAAuB,eASlC;AAAA,EACD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI;AAAA,IACH,IAAI,aAAa;AAAA,IACjB,IAAI;AAAA,EACL;AAAA;AAAA;AAAA;AAAA,EAID,cAAc,CAAC,SAAS;AAAA,IACvB,UAAU,MAAM;AACf,YAAM,WAAW,qBAAqB,GAAG;AACzC,UAAI,SAAU,QAAO,mBAAmB,SAAS,QAAQ;AACzD;AAAA,IACD;AAAA,IACA,YAAY,MAAM,qBAAqB,GAAG,GAAG,YAAY;AAAA,IACzD,cAAc,MAAM,qBAAqB,GAAG,GAAG,aAAa;AAAA,IAC5D,qBAAqB,CAAC,SAAS;AAAA,IAAC;AAAA,EACjC;AACD,CAAC;","names":["Platformer3DState","Platformer3DEvent"]}
@@ -5,8 +5,9 @@ import {
5
5
  import {
6
6
  STAGE_INPUT_BUTTON_JUMP,
7
7
  STAGE_INPUT_BUTTON_RUN,
8
+ StagePlatformerShapeSource,
8
9
  readBehaviorSnapshot
9
- } from "./chunk-E4X4YJZB.js";
10
+ } from "./chunk-DZ4LLQON.js";
10
11
  import {
11
12
  defineBehavior
12
13
  } from "./chunk-EZNCCO4L.js";
@@ -191,6 +192,40 @@ var defaultOptions = {
191
192
  characterOffset: 0.05
192
193
  };
193
194
  var PLATFORMER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:platformer-2d");
195
+ function resolvePlatformer2DAttachShape(options, gameEntity) {
196
+ const fallback = {
197
+ halfHeight: 0.9,
198
+ radius: 0.4,
199
+ offsetX: 0,
200
+ offsetY: 0,
201
+ offsetZ: 0,
202
+ shapeSource: StagePlatformerShapeSource.Auto
203
+ };
204
+ if (options.useAttachedCollider === false && options.shape) {
205
+ const offset = options.shape.offset ?? [0, 0, 0];
206
+ return {
207
+ halfHeight: options.shape.halfHeight,
208
+ radius: options.shape.radius,
209
+ offsetX: offset[0],
210
+ offsetY: offset[1],
211
+ offsetZ: offset[2],
212
+ shapeSource: StagePlatformerShapeSource.Override
213
+ };
214
+ }
215
+ const collider = gameEntity?.colliderDesc;
216
+ if (collider?.shape?.type === "capsule") {
217
+ const offset = collider.offset ?? [0, 0, 0];
218
+ return {
219
+ halfHeight: collider.shape.halfHeight ?? fallback.halfHeight,
220
+ radius: collider.shape.radius ?? fallback.radius,
221
+ offsetX: offset[0],
222
+ offsetY: offset[1],
223
+ offsetZ: offset[2],
224
+ shapeSource: StagePlatformerShapeSource.Auto
225
+ };
226
+ }
227
+ return fallback;
228
+ }
194
229
  var Platformer2DBehaviorSystem = class {
195
230
  constructor(wasmStage, getBehaviorLinks) {
196
231
  this.wasmStage = wasmStage;
@@ -217,7 +252,7 @@ var Platformer2DBehaviorSystem = class {
217
252
  }
218
253
  const handle = gameEntity.runtimeHandle ?? -1;
219
254
  if (!this.wasmStage || handle < 0) continue;
220
- this.ensureWasmAttached(handle, options);
255
+ this.ensureWasmAttached(handle, options, gameEntity);
221
256
  platformerRef.__wasmStage = this.wasmStage;
222
257
  platformerRef.__entity = gameEntity;
223
258
  const input = gameEntity.$platformer2D;
@@ -228,12 +263,12 @@ var Platformer2DBehaviorSystem = class {
228
263
  }
229
264
  }
230
265
  }
231
- ensureWasmAttached(handle, options) {
266
+ ensureWasmAttached(handle, options, gameEntity) {
232
267
  if (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;
233
268
  const autostep = options.autostep ?? defaultOptions.autostep;
269
+ const shape = resolvePlatformer2DAttachShape(options, gameEntity);
234
270
  this.wasmStage.attachPlatformer2D(handle, {
235
- halfHeight: 0.9,
236
- radius: 0.4,
271
+ ...shape,
237
272
  walkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed,
238
273
  runSpeed: options.runSpeed ?? defaultOptions.runSpeed,
239
274
  jumpForce: options.jumpForce ?? defaultOptions.jumpForce,
@@ -285,4 +320,4 @@ export {
285
320
  Platformer2DFSM,
286
321
  Platformer2DBehavior
287
322
  };
288
- //# sourceMappingURL=chunk-VT3H43QA.js.map
323
+ //# sourceMappingURL=chunk-4MZ57HPD.js.map
@@ -0,0 +1 @@
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.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 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 { Platformer2DState } from './platformer-2d-fsm';\nimport {\n\tcreatePlatformer2DMovementComponent,\n\tcreatePlatformer2DInputComponent,\n\tcreatePlatformer2DStateComponent,\n\ttype Platformer2DMovementComponent,\n\ttype Platformer2DInputComponent,\n\ttype Platformer2DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\tStagePlatformerShapeSource,\n\ttype BehaviorRuntime,\n\ttype StagePlatformer2DOptions,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust FSM index → TS state (shared `platformer_3d/fsm.rs` ordering). */\nconst FSM_STATE_BY_INDEX: readonly Platformer2DState[] = [\n\tPlatformer2DState.Idle,\n\tPlatformer2DState.Walking,\n\tPlatformer2DState.Running,\n\tPlatformer2DState.Jumping,\n\tPlatformer2DState.Falling,\n\tPlatformer2DState.Landing,\n];\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\t/** Explicit KCC capsule override (requires `useAttachedCollider: false`). */\n\tshape?: {\n\t\thalfHeight: number;\n\t\tradius: number;\n\t\toffset?: [number, number, number];\n\t};\n\t/** When true (default), KCC reads shape + offset from the attached Rapier collider. */\n\tuseAttachedCollider?: boolean;\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\nfunction resolvePlatformer2DAttachShape(\n\toptions: Platformer2DBehaviorOptions,\n\tgameEntity?: {\n\t\tcolliderDesc?: {\n\t\t\tshape?: { type?: string; halfHeight?: number; radius?: number };\n\t\t\toffset?: [number, number, number];\n\t\t};\n\t},\n): Pick<StagePlatformer2DOptions, 'halfHeight' | 'radius' | 'offsetX' | 'offsetY' | 'offsetZ' | 'shapeSource'> {\n\tconst fallback = {\n\t\thalfHeight: 0.9,\n\t\tradius: 0.4,\n\t\toffsetX: 0,\n\t\toffsetY: 0,\n\t\toffsetZ: 0,\n\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t};\n\n\tif (options.useAttachedCollider === false && options.shape) {\n\t\tconst offset = options.shape.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: options.shape.halfHeight,\n\t\t\tradius: options.shape.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Override,\n\t\t};\n\t}\n\n\tconst collider = gameEntity?.colliderDesc;\n\tif (collider?.shape?.type === 'capsule') {\n\t\tconst offset = collider.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: collider.shape.halfHeight ?? fallback.halfHeight,\n\t\t\tradius: collider.shape.radius ?? fallback.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t\t};\n\t}\n\n\treturn fallback;\n}\n\n/**\n * Entity with 2D platformer components (input written by the game; the wasm\n * runtime owns the kinematic character controller and FSM).\n */\nexport interface Platformer2DEntity {\n\tuuid: string;\n\tplatformer2D: Platformer2DMovementComponent;\n\t$platformer2D: Platformer2DInputComponent;\n\tplatformer2DState: Platformer2DStateComponent;\n}\n\n/**\n * Forwards 2D platformer input into the wasm runtime. Entities with a\n * `runtimeHandle` are attached once to the Stage runtime and receive input\n * via the slot-indexed input buffer each frame; the Rust `platformer_2d` system\n * is the single simulation path.\n */\nclass Platformer2DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(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\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options, gameEntity);\n\t\t\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$platformer2D;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(\n\t\thandle: number,\n\t\toptions: Platformer2DBehaviorOptions,\n\t\tgameEntity?: { colliderDesc?: { shape?: { type?: string; halfHeight?: number; radius?: number }; offset?: [number, number, number] } },\n\t): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst autostep = options.autostep ?? defaultOptions.autostep!;\n\t\tconst shape = resolvePlatformer2DAttachShape(options, gameEntity);\n\t\tthis.wasmStage.attachPlatformer2D(handle, {\n\t\t\t...shape,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t\tcoyoteTime: options.coyoteTime ?? defaultOptions.coyoteTime!,\n\t\t\tjumpBufferTime: options.jumpBufferTime ?? defaultOptions.jumpBufferTime!,\n\t\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? defaultOptions.jumpCutMultiplier!,\n\t\t\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.05,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * 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(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (values are from the end of\n\t// the previous step — one-frame lag). The Rust FSM is the single source\n\t// of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer2DState.Idle;\n\t\t\treturn Platformer2DState.Idle;\n\t\t},\n\t\tisGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,\n\t\tgetJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0,\n\t}),\n});\n"],"mappings":"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","names":["Platformer2DState","Platformer2DEvent"]}
@@ -3,7 +3,7 @@ import {
3
3
  } from "./chunk-EZNCCO4L.js";
4
4
 
5
5
  // src/lib/behaviors/cooldown/cooldown-store.ts
6
- import { proxy } from "valtio";
6
+ import { proxy } from "valtio/vanilla";
7
7
  var cooldownStore = proxy({});
8
8
  function registerCooldown(name, duration, immediate = true) {
9
9
  cooldownStore[name] = {
@@ -115,4 +115,4 @@ export {
115
115
  getCooldownStore,
116
116
  CooldownBehavior
117
117
  };
118
- //# sourceMappingURL=chunk-ZRQWSURL.js.map
118
+ //# sourceMappingURL=chunk-5XPPLTC7.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/cooldown/cooldown-store.ts","../src/lib/behaviors/cooldown/cooldown.descriptor.ts"],"sourcesContent":["import { proxy } from 'valtio/vanilla';\n\n/**\n * A single cooldown entry in the global store.\n */\nexport interface CooldownEntry {\n\t/** Identifier for this cooldown */\n\tname: string;\n\t/** Total cooldown time in seconds */\n\tduration: number;\n\t/** Time accumulated since last fire */\n\telapsed: number;\n\t/** Whether the cooldown is ready (elapsed >= duration) */\n\tready: boolean;\n\t/** Normalized progress 0..1 (0 = just fired, 1 = ready) */\n\tprogress: number;\n\t/** Whether this cooldown is currently counting down */\n\tactive: boolean;\n}\n\n/**\n * Global reactive cooldown registry (valtio proxy).\n * Accessible from entities, behaviors, and UI components.\n */\nconst cooldownStore = proxy<Record<string, CooldownEntry>>({});\n\n/**\n * Register a named cooldown in the global store.\n * If a cooldown with the same name already exists, it is overwritten.\n *\n * @param name Unique cooldown identifier\n * @param duration Cooldown duration in seconds\n * @param immediate If true, the cooldown starts ready (default: true)\n */\nexport function registerCooldown(\n\tname: string,\n\tduration: number,\n\timmediate: boolean = true,\n): void {\n\tcooldownStore[name] = {\n\t\tname,\n\t\tduration,\n\t\telapsed: immediate ? duration : 0,\n\t\tready: immediate,\n\t\tprogress: immediate ? 1 : 0,\n\t\tactive: !immediate,\n\t};\n}\n\n/**\n * Get a cooldown entry by name.\n * Returns undefined if the cooldown has not been registered.\n */\nexport function getCooldown(name: string): CooldownEntry | undefined {\n\treturn cooldownStore[name];\n}\n\n/**\n * Fire (trigger) a cooldown, resetting its timer.\n * Sets the cooldown to not-ready and starts the countdown.\n */\nexport function fireCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = 0;\n\tentry.ready = false;\n\tentry.progress = 0;\n\tentry.active = true;\n}\n\n/**\n * Force a cooldown to ready immediately.\n */\nexport function resetCooldown(name: string): void {\n\tconst entry = cooldownStore[name];\n\tif (!entry) return;\n\tentry.elapsed = entry.duration;\n\tentry.ready = true;\n\tentry.progress = 1;\n\tentry.active = false;\n}\n\n/**\n * Advance all active cooldowns by delta seconds.\n * Called once per frame by the CooldownBehavior system.\n */\nexport function tickCooldowns(delta: number): void {\n\tfor (const name in cooldownStore) {\n\t\tconst entry = cooldownStore[name];\n\t\tif (!entry.active) continue;\n\n\t\tentry.elapsed += delta;\n\t\tif (entry.elapsed >= entry.duration) {\n\t\t\tentry.elapsed = entry.duration;\n\t\t\tentry.ready = true;\n\t\t\tentry.progress = 1;\n\t\t\tentry.active = false;\n\t\t} else {\n\t\t\tentry.progress = entry.duration > 0 ? entry.elapsed / entry.duration : 1;\n\t\t}\n\t}\n}\n\n/**\n * Get the raw cooldown store (for advanced/debugging use).\n */\nexport function getCooldownStore(): Record<string, CooldownEntry> {\n\treturn cooldownStore;\n}\n","/**\n * CooldownBehavior\n *\n * A stage-scoped behavior that manages named cooldowns via the global CooldownStore.\n * Entities use this to register, check, and fire cooldowns.\n * UI components (CooldownIcon) can read from the same store.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tregisterCooldown,\n\tgetCooldown,\n\tfireCooldown,\n\tresetCooldown,\n\ttickCooldowns,\n} from './cooldown-store';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface CooldownConfig {\n\t/** Duration in seconds */\n\tduration: number;\n\t/** Whether the cooldown starts ready (default: true) */\n\timmediate?: boolean;\n}\n\nexport interface CooldownOptions {\n\t/**\n\t * Named cooldowns to register.\n\t * Keys are the cooldown names, values are their config.\n\t */\n\tcooldowns: Record<string, CooldownConfig>;\n}\n\ninterface CompiledCooldownEntry {\n\tname: string;\n\tduration: number;\n\timmediate: boolean;\n}\n\nconst COOLDOWN_BEHAVIOR_KEY = Symbol.for('zylem:behavior:cooldown');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Handle methods returned by entity.use(CooldownBehavior, ...).\n */\nexport interface CooldownHandle {\n\t/** Check if a named cooldown is ready */\n\tisReady(name: string): boolean;\n\t/** Fire (trigger) a named cooldown, resetting its timer */\n\tfire(name: string): void;\n\t/** Force a cooldown to ready immediately */\n\treset(name: string): void;\n\t/** Get cooldown progress 0..1 (0 = just fired, 1 = ready) */\n\tgetProgress(name: string): number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// System\n// ─────────────────────────────────────────────────────────────────────────────\n\nclass CooldownSystem implements BehaviorSystem {\n\tconstructor(\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) { }\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(COOLDOWN_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tensureCooldownEntriesRegistered(link.ref as BehaviorRef<CooldownOptions>);\n\t\t\t}\n\t\t}\n\n\t\ttickCooldowns(delta);\n\t}\n\n\tdestroy(_ecs: unknown): void { }\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Handle factory\n// ─────────────────────────────────────────────────────────────────────────────\n\nfunction createCooldownHandle(ref: BehaviorRef<CooldownOptions>): CooldownHandle {\n\tensureCooldownEntriesRegistered(ref);\n\n\treturn {\n\t\tisReady: (name: string) => getCooldown(name)?.ready ?? false,\n\t\tfire: (name: string) => fireCooldown(name),\n\t\treset: (name: string) => resetCooldown(name),\n\t\tgetProgress: (name: string) => getCooldown(name)?.progress ?? 1,\n\t};\n}\n\nfunction getCompiledCooldownEntries(\n\tref: BehaviorRef<CooldownOptions>,\n): CompiledCooldownEntry[] {\n\tconst compiled = (ref as any).__compiledCooldownEntries as CompiledCooldownEntry[] | undefined;\n\tif (compiled) return compiled;\n\n\tconst entries = Object.entries(ref.options.cooldowns).map(([name, config]) => ({\n\t\tname,\n\t\tduration: config.duration,\n\t\timmediate: config.immediate ?? true,\n\t}));\n\t(ref as any).__compiledCooldownEntries = entries;\n\treturn entries;\n}\n\nfunction ensureCooldownEntriesRegistered(ref: BehaviorRef<CooldownOptions>): void {\n\tif ((ref as any).__cooldownsRegistered) return;\n\n\tfor (const entry of getCompiledCooldownEntries(ref)) {\n\t\tregisterCooldown(entry.name, entry.duration, entry.immediate);\n\t}\n\n\t(ref as any).__cooldownsRegistered = true;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior Definition\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * CooldownBehavior\n *\n * @example\n * ```ts\n * import { CooldownBehavior } from '@zylem/game-lib/behavior';\n * import { moveBy } from '@zylem/game-lib/actions';\n *\n * const hero = createBox({ ... });\n * const cd = hero.use(CooldownBehavior, {\n * cooldowns: {\n * attack: { duration: 5 },\n * dash: { duration: 2, immediate: false },\n * },\n * });\n *\n * hero.onUpdate(({ me, inputs }) => {\n * if (cd.isReady('attack') && inputs.p1.buttons.A.pressed) {\n * cd.fire('attack');\n * // perform attack\n * }\n * if (cd.isReady('dash') && inputs.p1.buttons.B.pressed) {\n * cd.fire('dash');\n * me.runAction(moveBy({ x: 10, duration: 0.3 }));\n * }\n * });\n * ```\n */\nexport const CooldownBehavior = defineBehavior<CooldownOptions, CooldownHandle>({\n\tname: 'cooldown',\n\tdefaultOptions: { cooldowns: {} },\n\tsystemFactory: (ctx) =>\n\t\tnew CooldownSystem(ctx.getBehaviorLinks),\n\tcreateHandle: createCooldownHandle,\n});\n"],"mappings":"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","names":[]}
@@ -9,7 +9,7 @@ import {
9
9
  } from "./chunk-AS3HR7AS.js";
10
10
  import {
11
11
  StageBoundaryDim
12
- } from "./chunk-E4X4YJZB.js";
12
+ } from "./chunk-DZ4LLQON.js";
13
13
  import {
14
14
  defineBehavior
15
15
  } from "./chunk-EZNCCO4L.js";
@@ -190,4 +190,4 @@ export {
190
190
  WorldBoundary2DFSM,
191
191
  WorldBoundary2DBehavior
192
192
  };
193
- //# sourceMappingURL=chunk-F6ESSHQK.js.map
193
+ //# sourceMappingURL=chunk-7DGXUAPV.js.map
@@ -5,7 +5,7 @@ import {
5
5
  import {
6
6
  StageRicochetDim,
7
7
  StageRicochetReflection
8
- } from "./chunk-E4X4YJZB.js";
8
+ } from "./chunk-DZ4LLQON.js";
9
9
  import {
10
10
  defineBehavior
11
11
  } from "./chunk-EZNCCO4L.js";
@@ -419,4 +419,4 @@ export {
419
419
  Ricochet2DFSM,
420
420
  Ricochet2DBehavior
421
421
  };
422
- //# sourceMappingURL=chunk-KOWGILCN.js.map
422
+ //# sourceMappingURL=chunk-7W3KY55G.js.map
@@ -152,4 +152,4 @@ export {
152
152
  ThrusterFSM,
153
153
  ThrusterBehavior
154
154
  };
155
- //# sourceMappingURL=chunk-VFDUKGRZ.js.map
155
+ //# sourceMappingURL=chunk-BAXJ7XHJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/thruster/thruster-fsm.ts","../src/lib/behaviors/thruster/thruster.descriptor.ts"],"sourcesContent":["/**\n * ThrusterFSM\n * \n * State machine controller for thruster behavior.\n * FSM does NOT touch physics or ThrusterSystem - it only writes ThrusterInputComponent.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { ThrusterInputComponent } from './components';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterState {\n\tIdle = 'idle',\n\tActive = 'active',\n\tBoosting = 'boosting',\n\tDisabled = 'disabled',\n\tDocked = 'docked',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterEvent {\n\tActivate = 'activate',\n\tDeactivate = 'deactivate',\n\tBoost = 'boost',\n\tEndBoost = 'endBoost',\n\tDisable = 'disable',\n\tEnable = 'enable',\n\tDock = 'dock',\n\tUndock = 'undock',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Context Object\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterFSMContext {\n\tinput: ThrusterInputComponent;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Player Input (raw input from controller/keyboard)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PlayerInput {\n\tthrust: number;\n\trotate: number;\n\tthrustX?: number;\n\tthrustY?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterFSM Controller\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ThrusterFSM {\n\tmachine: SyncStateMachine<ThrusterState, ThrusterEvent, never>;\n\n\tconstructor(private ctx: ThrusterFSMContext) {\n\t\tthis.machine = new SyncStateMachine<ThrusterState, ThrusterEvent, never>(\n\t\t\tThrusterState.Idle,\n\t\t\t[\n\t\t\t\t// Core transitions\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Boost, ThrusterState.Boosting),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Dock, ThrusterState.Docked),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.EndBoost, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Disabled, ThrusterEvent.Enable, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Docked, ThrusterEvent.Undock, ThrusterState.Idle),\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): ThrusterState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to transition state\n\t */\n\tdispatch(event: ThrusterEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM state based on player input.\n\t * Auto-transitions between Idle/Active to report current state.\n\t * Does NOT modify input - just observes and reports.\n\t */\n\tupdate(playerInput: PlayerInput): void {\n\t\tconst state = this.machine.getState();\n\t\tconst hasInput =\n\t\t\tMath.abs(playerInput.thrust) > 0.01\n\t\t\t|| Math.abs(playerInput.rotate) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustX ?? 0) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustY ?? 0) > 0.01;\n\n\t\t// Auto-transition to report state based on input\n\t\tif (hasInput && state === ThrusterState.Idle) {\n\t\t\tthis.dispatch(ThrusterEvent.Activate);\n\t\t} else if (!hasInput && state === ThrusterState.Active) {\n\t\t\tthis.dispatch(ThrusterEvent.Deactivate);\n\t\t}\n\t}\n}\n","/**\n * Thruster Behavior Descriptor\n *\n * Type-safe descriptor for the thruster behavior using the entity.use() API.\n * The wasm runtime owns the thruster simulation; this system attaches the\n * Rust behavior and forwards input into the slot-indexed input buffer.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tcreateThrusterInputComponent,\n\tcreateThrusterMovementComponent,\n\ttype ThrusterMovementComponent,\n\ttype ThrusterInputComponent,\n} from './components';\nimport type { BehaviorRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Entity with thruster components (input written by the game, state owned by\n * the wasm runtime).\n */\nexport interface ThrusterEntity {\n\tthruster: ThrusterMovementComponent;\n\t$thruster: ThrusterInputComponent;\n}\n\n/**\n * Thruster behavior options (typed for entity.use() autocomplete)\n */\nexport interface ThrusterBehaviorOptions {\n\t/** Forward thrust force (default: 10) */\n\tlinearThrust: number;\n\t/** Rotation torque (default: 5) */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n}\n\nconst defaultOptions: ThrusterBehaviorOptions = {\n\tlinearThrust: 10,\n\tangularThrust: 5,\n\tlinearDamping: undefined,\n};\n\nconst THRUSTER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:thruster');\n\n/**\n * Adapter that forwards thruster input into the wasm runtime.\n *\n * The Rust `thruster` Stage behavior is the single simulation path: this\n * system attaches it once per entity and writes `$thruster` input into the\n * slot-indexed input buffer each frame.\n */\nclass ThrusterBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(THRUSTER_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 thrusterRef = link.ref as any;\n\n\t\t\tconst options = thrusterRef.options as ThrusterBehaviorOptions;\n\n\t\t\tif (!gameEntity.thruster) {\n\t\t\t\tgameEntity.thruster = createThrusterMovementComponent(\n\t\t\t\t\toptions.linearThrust,\n\t\t\t\t\toptions.angularThrust,\n\t\t\t\t\t{ linearDamping: options.linearDamping },\n\t\t\t\t) as ThrusterMovementComponent;\n\t\t\t}\n\n\t\t\tif (!gameEntity.$thruster) {\n\t\t\t\tgameEntity.$thruster = createThrusterInputComponent() as ThrusterInputComponent;\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tconst input = gameEntity.$thruster as ThrusterInputComponent | undefined;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.thrust, input.rotate);\n\t\t\t\tthis.wasmStage.writeInputReserved(handle, input.thrustX, input.thrustY);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ThrusterBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachThruster(handle, {\n\t\t\tmaxSpeed: 50,\n\t\t\tacceleration: options.linearThrust,\n\t\t\tturnRateRadPerSec: options.angularThrust,\n\t\t\tlinearDamping: options.linearDamping ?? 0,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ThrusterBehavior - typed descriptor for thruster movement.\n *\n * @example\n * ```typescript\n * import { ThrusterBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ship = createSprite({ ... });\n * ship.use(ThrusterBehavior, { linearThrust: 15, angularThrust: 8 });\n * ```\n */\nexport const ThrusterBehavior = defineBehavior<ThrusterBehaviorOptions, Record<string, never>, ThrusterEntity>({\n\tname: 'thruster',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ThrusterBehaviorSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ThrusterState","ThrusterEvent"]}
@@ -40,6 +40,10 @@ var StageEventType = {
40
40
  Wrapped: 8,
41
41
  CooldownFired: 9
42
42
  };
43
+ var StagePlatformerShapeSource = {
44
+ Auto: 0,
45
+ Override: 1
46
+ };
43
47
  var STAGE_INPUT_BUTTON_JUMP = 1;
44
48
  var STAGE_INPUT_BUTTON_RUN = 2;
45
49
  function readBehaviorSnapshot(ref) {
@@ -145,6 +149,15 @@ var WasmStageRuntime = class {
145
149
  setGravity(x, y, z) {
146
150
  this.exports.zylem_stage_set_gravity(x, y, z);
147
151
  }
152
+ /**
153
+ * Toggle per-step collision-event collection in the wasm stage. Hosts
154
+ * with no collision listeners should disable it — with thousands of
155
+ * touching bodies the per-step contact-pair hashing is a significant
156
+ * fraction of `step()`.
157
+ */
158
+ setEventsEnabled(enabled) {
159
+ this.exports.zylem_stage_set_events_enabled(enabled ? 1 : 0);
160
+ }
148
161
  // ─── Entity lifecycle ───────────────────────────────────────────────
149
162
  createEntity() {
150
163
  const slot = this.exports.zylem_stage_create_entity() >>> 0;
@@ -420,6 +433,7 @@ var WasmStageRuntime = class {
420
433
  /** Wireframe debug render of every collider (all shapes). */
421
434
  getDebugRender() {
422
435
  const vertexCount = this.exports.zylem_stage_debug_render() >>> 0;
436
+ this.refreshViews();
423
437
  if (vertexCount === 0) return null;
424
438
  const buffer = this.memory.buffer;
425
439
  const vertices = new Float32Array(
@@ -432,7 +446,10 @@ var WasmStageRuntime = class {
432
446
  this.exports.zylem_stage_debug_colors_ptr(),
433
447
  this.exports.zylem_stage_debug_colors_len()
434
448
  );
435
- return { vertices, colors };
449
+ return {
450
+ vertices: new Float32Array(vertices),
451
+ colors: new Float32Array(colors)
452
+ };
436
453
  }
437
454
  // ─── Render / event reads ──────────────────────────────────────────
438
455
  /** Read-only view over the render buffer; one slot occupies {@link STAGE_RENDER_STRIDE} floats. */
@@ -484,7 +501,7 @@ var WasmStageRuntime = class {
484
501
  return STAGE_SNAPSHOT_STRIDE;
485
502
  }
486
503
  /**
487
- * Write the primary movement axes for a slot into the batched input
504
+ * Write the primary movement axes for a slot into the slot-indexed input
488
505
  * buffer and mark the slot dirty. Field-scoped (doesn't touch look
489
506
  * deltas or buttons) so multiple behaviors composed on one entity can
490
507
  * each write their part.
@@ -729,7 +746,11 @@ var WasmStageRuntime = class {
729
746
  opts.autostepHeight ?? 0.3,
730
747
  opts.autostepMinWidth ?? 0.1,
731
748
  opts.snapToGroundDistance ?? 0.3,
732
- opts.characterOffset ?? 0.05
749
+ opts.characterOffset ?? 0.05,
750
+ opts.offsetX ?? 0,
751
+ opts.offsetY ?? 0,
752
+ opts.offsetZ ?? 0,
753
+ opts.shapeSource ?? StagePlatformerShapeSource.Auto
733
754
  ) !== 0;
734
755
  }
735
756
  setPlatformer3DInputAxes(slot, moveX, moveZ) {
@@ -767,7 +788,11 @@ var WasmStageRuntime = class {
767
788
  opts.autostepHeight ?? 0.25,
768
789
  opts.autostepMinWidth ?? 0.05,
769
790
  opts.snapToGroundDistance ?? 0.2,
770
- opts.characterOffset ?? 0.05
791
+ opts.characterOffset ?? 0.05,
792
+ opts.offsetX ?? 0,
793
+ opts.offsetY ?? 0,
794
+ opts.offsetZ ?? 0,
795
+ opts.shapeSource ?? StagePlatformerShapeSource.Auto
771
796
  ) !== 0;
772
797
  }
773
798
  setPlatformer2DInputAxes(slot, moveX) {
@@ -831,6 +856,7 @@ export {
831
856
  StageRicochetReflection,
832
857
  StageTopDownPlane,
833
858
  StageEventType,
859
+ StagePlatformerShapeSource,
834
860
  STAGE_INPUT_BUTTON_JUMP,
835
861
  STAGE_INPUT_BUTTON_RUN,
836
862
  readBehaviorSnapshot,
@@ -838,4 +864,4 @@ export {
838
864
  WasmStageRuntime,
839
865
  createWasmStageRuntime
840
866
  };
841
- //# sourceMappingURL=chunk-E4X4YJZB.js.map
867
+ //# sourceMappingURL=chunk-DZ4LLQON.js.map