@zylem/behaviors 0.3.1 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (138) hide show
  1. package/dist/{behavior-system-DYbx-x_-.d.ts → behavior-system-DSNek0W9.d.ts} +205 -29
  2. package/dist/{chunk-Y6UJOUE5.js → chunk-4V7TIRO2.js} +1 -1
  3. package/dist/chunk-4V7TIRO2.js.map +1 -0
  4. package/dist/{chunk-DYLH6K7B.js → chunk-4WH2ZN7P.js} +1 -1
  5. package/dist/{chunk-DYLH6K7B.js.map → chunk-4WH2ZN7P.js.map} +1 -1
  6. package/dist/{chunk-ZPSAXX73.js → chunk-5SM5SH5O.js} +26 -26
  7. package/dist/chunk-5SM5SH5O.js.map +1 -0
  8. package/dist/{chunk-STCCNLPC.js → chunk-5XZWTFGF.js} +5 -3
  9. package/dist/chunk-5XZWTFGF.js.map +1 -0
  10. package/dist/{chunk-OYWFPIHF.js → chunk-64HKCSXD.js} +19 -16
  11. package/dist/chunk-64HKCSXD.js.map +1 -0
  12. package/dist/{chunk-JAN34G6S.js → chunk-FRJDQM23.js} +108 -154
  13. package/dist/chunk-FRJDQM23.js.map +1 -0
  14. package/dist/{chunk-RTRVBEIB.js → chunk-FTDZAKZW.js} +5 -18
  15. package/dist/chunk-FTDZAKZW.js.map +1 -0
  16. package/dist/{chunk-XIJNE67Y.js → chunk-GFARAJV2.js} +3 -5
  17. package/dist/{chunk-XIJNE67Y.js.map → chunk-GFARAJV2.js.map} +1 -1
  18. package/dist/{chunk-SS7ELMJ7.js → chunk-GTLA6LOJ.js} +16 -22
  19. package/dist/chunk-GTLA6LOJ.js.map +1 -0
  20. package/dist/{chunk-VAN4WW3F.js → chunk-H4TJ7X4O.js} +4 -7
  21. package/dist/chunk-H4TJ7X4O.js.map +1 -0
  22. package/dist/{chunk-4I6E6DGV.js → chunk-HNESASBB.js} +4 -6
  23. package/dist/{chunk-4I6E6DGV.js.map → chunk-HNESASBB.js.map} +1 -1
  24. package/dist/{chunk-KS72RZHX.js → chunk-IZJFUAXB.js} +1 -1
  25. package/dist/{chunk-KS72RZHX.js.map → chunk-IZJFUAXB.js.map} +1 -1
  26. package/dist/{chunk-SVLNF434.js → chunk-JXMF4NUI.js} +51 -3
  27. package/dist/chunk-JXMF4NUI.js.map +1 -0
  28. package/dist/chunk-KFFETUAL.js +40 -0
  29. package/dist/chunk-KFFETUAL.js.map +1 -0
  30. package/dist/chunk-LL4FLHIY.js +12 -0
  31. package/dist/chunk-LL4FLHIY.js.map +1 -0
  32. package/dist/{chunk-2CLT36VB.js → chunk-MJFMNRMZ.js} +13 -14
  33. package/dist/chunk-MJFMNRMZ.js.map +1 -0
  34. package/dist/{chunk-RTVRUNYR.js → chunk-OTLSUYQQ.js} +2 -2
  35. package/dist/chunk-OTLSUYQQ.js.map +1 -0
  36. package/dist/{chunk-XG2YMCJW.js → chunk-PWF757B6.js} +4 -6
  37. package/dist/{chunk-XG2YMCJW.js.map → chunk-PWF757B6.js.map} +1 -1
  38. package/dist/{chunk-LD5SMIB3.js → chunk-QVCMJ4Q2.js} +1 -1
  39. package/dist/{chunk-LD5SMIB3.js.map → chunk-QVCMJ4Q2.js.map} +1 -1
  40. package/dist/{chunk-F3KRX2V4.js → chunk-RONH2KDI.js} +10 -12
  41. package/dist/chunk-RONH2KDI.js.map +1 -0
  42. package/dist/chunk-RTRFJVCZ.js +454 -0
  43. package/dist/chunk-RTRFJVCZ.js.map +1 -0
  44. package/dist/{chunk-6C6NXNEW.js → chunk-UL6Q5HDP.js} +25 -3
  45. package/dist/chunk-UL6Q5HDP.js.map +1 -0
  46. package/dist/{chunk-KOZUNIIE.js → chunk-V74N7D6Q.js} +7 -7
  47. package/dist/{chunk-KOZUNIIE.js.map → chunk-V74N7D6Q.js.map} +1 -1
  48. package/dist/{chunk-YUO6SKJD.js → chunk-WXXXJ22R.js} +11 -13
  49. package/dist/chunk-WXXXJ22R.js.map +1 -0
  50. package/dist/{chunk-ARJWUNRE.js → chunk-XLUJDKCT.js} +7 -7
  51. package/dist/{chunk-ARJWUNRE.js.map → chunk-XLUJDKCT.js.map} +1 -1
  52. package/dist/{chunk-GCNRXAKH.js → chunk-XP3ASVGJ.js} +13 -10
  53. package/dist/chunk-XP3ASVGJ.js.map +1 -0
  54. package/dist/{chunk-ID5QJYF5.js → chunk-Y7YIAWPW.js} +11 -1
  55. package/dist/chunk-Y7YIAWPW.js.map +1 -0
  56. package/dist/{chunk-FPTWVCPN.js → chunk-ZA43LPRM.js} +4 -24
  57. package/dist/chunk-ZA43LPRM.js.map +1 -0
  58. package/dist/{chunk-K3UNLQ7W.js → chunk-ZRQWSURL.js} +2 -2
  59. package/dist/chunk-ZRQWSURL.js.map +1 -0
  60. package/dist/cooldown.d.ts +2 -1
  61. package/dist/cooldown.js +1 -1
  62. package/dist/coordinators/boundary-ricochet-3d.d.ts +12 -5
  63. package/dist/coordinators/boundary-ricochet-3d.js +2 -1
  64. package/dist/coordinators/boundary-ricochet.d.ts +9 -7
  65. package/dist/coordinators/boundary-ricochet.js +2 -1
  66. package/dist/coordinators/first-person-shooter.d.ts +9 -8
  67. package/dist/coordinators/first-person-shooter.js +3 -3
  68. package/dist/coordinators/multidirectional-space-shooter.d.ts +12 -9
  69. package/dist/coordinators/multidirectional-space-shooter.js +4 -4
  70. package/dist/coordinators/top-down-shooter.d.ts +9 -8
  71. package/dist/coordinators/top-down-shooter.js +3 -3
  72. package/dist/core.d.ts +4 -4
  73. package/dist/core.js +4 -32
  74. package/dist/core.js.map +1 -1
  75. package/dist/destructible-3d.d.ts +2 -1
  76. package/dist/first-person.d.ts +12 -12
  77. package/dist/first-person.js +8 -8
  78. package/dist/index.d.ts +37 -14
  79. package/dist/index.js +73 -44
  80. package/dist/jumper-2d.d.ts +7 -6
  81. package/dist/jumper-2d.js +5 -5
  82. package/dist/jumper-3d.d.ts +12 -11
  83. package/dist/jumper-3d.js +6 -6
  84. package/dist/{moveable-BCRpjAQb.d.ts → moveable-BLTHO7Jq.d.ts} +2 -78
  85. package/dist/particle-emitter.d.ts +1 -1
  86. package/dist/particle-emitter.js +1 -1
  87. package/dist/platformer-2d.d.ts +306 -0
  88. package/dist/platformer-2d.js +23 -0
  89. package/dist/platformer-3d.d.ts +74 -87
  90. package/dist/platformer-3d.js +4 -4
  91. package/dist/ricochet-2d.d.ts +1 -1
  92. package/dist/ricochet-2d.js +2 -2
  93. package/dist/ricochet-3d.d.ts +1 -1
  94. package/dist/ricochet-3d.js +2 -2
  95. package/dist/runtime-2d.d.ts +93 -2
  96. package/dist/runtime-2d.js +145 -22
  97. package/dist/runtime-2d.js.map +1 -1
  98. package/dist/screen-visibility.d.ts +2 -1
  99. package/dist/screen-wrap.d.ts +2 -1
  100. package/dist/screen-wrap.js +1 -1
  101. package/dist/shooter-2d.d.ts +5 -3
  102. package/dist/shooter-2d.js +2 -2
  103. package/dist/shooter-fire-DGripl0z.d.ts +55 -0
  104. package/dist/thruster.d.ts +8 -18
  105. package/dist/thruster.js +6 -6
  106. package/dist/top-down-movement.d.ts +7 -4
  107. package/dist/top-down-movement.js +5 -5
  108. package/dist/{use-behavior-D_82jAL3.d.ts → use-behavior-h9-T5FSv.d.ts} +1 -1
  109. package/dist/world-boundary-2d.d.ts +2 -1
  110. package/dist/world-boundary-2d.js +2 -2
  111. package/dist/world-boundary-3d.d.ts +2 -1
  112. package/dist/world-boundary-3d.js +2 -2
  113. package/package.json +5 -5
  114. package/dist/chunk-2C3Z5GE7.js +0 -176
  115. package/dist/chunk-2C3Z5GE7.js.map +0 -1
  116. package/dist/chunk-2CLT36VB.js.map +0 -1
  117. package/dist/chunk-3PYGRHAL.js +0 -35
  118. package/dist/chunk-3PYGRHAL.js.map +0 -1
  119. package/dist/chunk-6C6NXNEW.js.map +0 -1
  120. package/dist/chunk-F3KRX2V4.js.map +0 -1
  121. package/dist/chunk-FPTWVCPN.js.map +0 -1
  122. package/dist/chunk-GCNRXAKH.js.map +0 -1
  123. package/dist/chunk-ID5QJYF5.js.map +0 -1
  124. package/dist/chunk-JAN34G6S.js.map +0 -1
  125. package/dist/chunk-K3UNLQ7W.js.map +0 -1
  126. package/dist/chunk-OYWFPIHF.js.map +0 -1
  127. package/dist/chunk-RTRVBEIB.js.map +0 -1
  128. package/dist/chunk-RTVRUNYR.js.map +0 -1
  129. package/dist/chunk-SS7ELMJ7.js.map +0 -1
  130. package/dist/chunk-STCCNLPC.js.map +0 -1
  131. package/dist/chunk-SVLNF434.js.map +0 -1
  132. package/dist/chunk-VAN4WW3F.js.map +0 -1
  133. package/dist/chunk-Y6UJOUE5.js.map +0 -1
  134. package/dist/chunk-YUO6SKJD.js.map +0 -1
  135. package/dist/chunk-ZPSAXX73.js.map +0 -1
  136. package/dist/runtime-pong.d.ts +0 -92
  137. package/dist/runtime-pong.js +0 -38
  138. /package/dist/{runtime-pong.js.map → platformer-2d.js.map} +0 -0
@@ -112,12 +112,10 @@ var defaultOptions = {
112
112
  };
113
113
  var SCREEN_WRAP_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:screen-wrap");
114
114
  var ScreenWrapSystem = class {
115
- constructor(world, wasmStage, getBehaviorLinks) {
116
- this.world = world;
115
+ constructor(wasmStage, getBehaviorLinks) {
117
116
  this.wasmStage = wasmStage;
118
117
  this.getBehaviorLinks = getBehaviorLinks;
119
118
  }
120
- world;
121
119
  wasmStage;
122
120
  getBehaviorLinks;
123
121
  attachedRuntimeSlots = /* @__PURE__ */ new Set();
@@ -198,7 +196,7 @@ var ScreenWrapSystem = class {
198
196
  var ScreenWrapBehavior = defineBehavior({
199
197
  name: "screen-wrap",
200
198
  defaultOptions,
201
- systemFactory: (ctx) => new ScreenWrapSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks)
199
+ systemFactory: (ctx) => new ScreenWrapSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks)
202
200
  });
203
201
 
204
202
  export {
@@ -207,4 +205,4 @@ export {
207
205
  ScreenWrapFSM,
208
206
  ScreenWrapBehavior
209
207
  };
210
- //# sourceMappingURL=chunk-XIJNE67Y.js.map
208
+ //# sourceMappingURL=chunk-GFARAJV2.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts","../src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts"],"sourcesContent":["/**\n * ScreenWrapFSM\n * \n * State machine for screen wrap behavior.\n * Reports position relative to bounds edges.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapState {\n\tCenter = 'center',\n\tNearEdgeLeft = 'near-edge-left',\n\tNearEdgeRight = 'near-edge-right',\n\tNearEdgeTop = 'near-edge-top',\n\tNearEdgeBottom = 'near-edge-bottom',\n\tWrapped = 'wrapped',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapEvent {\n\tEnterCenter = 'enter-center',\n\tApproachLeft = 'approach-left',\n\tApproachRight = 'approach-right',\n\tApproachTop = 'approach-top',\n\tApproachBottom = 'approach-bottom',\n\tWrap = 'wrap',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ScreenWrapFSM\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScreenWrapFSM {\n\tmachine: SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>(\n\t\t\tScreenWrapState.Center,\n\t\t\t[\n\t\t\t\t// From Center\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t\t\n\t\t\t\t// From NearEdge to Wrapped\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\n\t\t\t\t// From NearEdge back to Center\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped back to Center\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped to NearEdge (landed near opposite edge)\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): ScreenWrapState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: ScreenWrapEvent): 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 based on entity position relative to bounds\n\t */\n\tupdate(position: { x: number; y: number }, bounds: {\n\t\tminX: number; maxX: number; minY: number; maxY: number;\n\t\tedgeThreshold: number;\n\t}, wrapped: boolean): void {\n\t\tconst { x, y } = position;\n\t\tconst { minX, maxX, minY, maxY, edgeThreshold } = bounds;\n\n\t\tif (wrapped) {\n\t\t\tthis.dispatch(ScreenWrapEvent.Wrap);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if near edges\n\t\tconst nearLeft = x < minX + edgeThreshold;\n\t\tconst nearRight = x > maxX - edgeThreshold;\n\t\tconst nearBottom = y < minY + edgeThreshold;\n\t\tconst nearTop = y > maxY - edgeThreshold;\n\n\t\tif (nearLeft) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachLeft);\n\t\t} else if (nearRight) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachRight);\n\t\t} else if (nearTop) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachTop);\n\t\t} else if (nearBottom) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachBottom);\n\t\t} else {\n\t\t\tthis.dispatch(ScreenWrapEvent.EnterCenter);\n\t\t}\n\t}\n}\n","/**\n * ScreenWrapBehavior\n * \n * When an entity exits the defined 2D bounds, it wraps around to the opposite edge.\n * Asteroids-style screen wrapping with FSM for edge detection.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ScreenWrapFSM } from './screen-wrap-fsm';\nimport { createBounds2DRect, wrapPoint2D } from '../shared/bounds-2d';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Screen wrap options (typed for entity.use() autocomplete)\n */\nexport interface ScreenWrapOptions {\n\t/** Width of the wrapping area (default: 20) */\n\twidth: number;\n\t/** Height of the wrapping area (default: 15) */\n\theight: number;\n\t/** Center X position (default: 0) */\n\tcenterX: number;\n\t/** Center Y position (default: 0) */\n\tcenterY: number;\n\t/** Distance from edge to trigger NearEdge state (default: 2) */\n\tedgeThreshold: number;\n}\n\nconst defaultOptions: ScreenWrapOptions = {\n\twidth: 20,\n\theight: 15,\n\tcenterX: 0,\n\tcenterY: 0,\n\tedgeThreshold: 2,\n};\n\nconst SCREEN_WRAP_BEHAVIOR_KEY = Symbol.for('zylem:behavior:screen-wrap');\n\n/**\n * ScreenWrapSystem - Wraps entities around 2D bounds\n *\n * Mirrors the Rust `screen_wrap` Stage behavior: when `wasmStage` is present\n * and the entity has a `runtimeHandle`, the wasm runtime owns the wrap and\n * this system only mirrors the queried state into the local FSM. Otherwise\n * the legacy TS path runs against the Rapier `RigidBody` translation.\n */\nclass ScreenWrapSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(SCREEN_WRAP_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 wrapRef = link.ref as any;\n\t\t\tconst options = wrapRef.options as ScreenWrapOptions;\n\n\t\t\tif (!wrapRef.fsm) {\n\t\t\t\twrapRef.fsm = new ScreenWrapFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst pose = this.wasmStage.getPose(handle);\n\t\t\t\tconst query = this.wasmStage.queryScreenWrap(handle);\n\t\t\t\tif (pose && query) {\n\t\t\t\t\tconst bounds = createBounds2DRect(options);\n\t\t\t\t\twrapRef.fsm.update(\n\t\t\t\t\t\t{ x: pose.position[0], y: pose.position[1] },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tquery.lastWrappedAxes !== 0,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tconst wrapped = this.wrapEntity(gameEntity, options);\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tconst bounds = createBounds2DRect(options);\n\t\t\twrapRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y },\n\t\t\t\t{\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t\t\t},\n\t\t\t\twrapped,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ScreenWrapOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachScreenWrap(handle, {\n\t\t\twidth: options.width,\n\t\t\theight: options.height,\n\t\t\tcenterX: options.centerX,\n\t\t\tcenterY: options.centerY,\n\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tprivate wrapEntity(entity: any, options: ScreenWrapOptions): boolean {\n\t\tconst body = entity.body;\n\t\tif (!body) return false;\n\n\t\tconst pos = body.translation();\n\t\tconst bounds = createBounds2DRect(options);\n\t\tconst result = wrapPoint2D({ x: pos.x, y: pos.y }, bounds);\n\n\t\tif (result.wrapped) {\n\t\t\tbody.setTranslation({ x: result.x, y: result.y, z: pos.z }, true);\n\t\t}\n\n\t\treturn result.wrapped;\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ScreenWrapBehavior - Wraps entities around 2D bounds\n * \n * @example\n * ```typescript\n * import { ScreenWrapBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const ship = createSprite({ ... });\n * const wrapRef = ship.use(ScreenWrapBehavior, { width: 20, height: 15 });\n * \n * // Access FSM to observe edge state\n * const fsm = wrapRef.getFSM();\n * console.log(fsm?.getState()); // 'center', 'near-edge-left', 'wrapped', etc.\n * ```\n */\nexport const ScreenWrapBehavior = defineBehavior({\n\tname: 'screen-wrap',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ScreenWrapSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ScreenWrapState","ScreenWrapEvent","bounds"]}
1
+ {"version":3,"sources":["../src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts","../src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts"],"sourcesContent":["/**\n * ScreenWrapFSM\n * \n * State machine for screen wrap behavior.\n * Reports position relative to bounds edges.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapState {\n\tCenter = 'center',\n\tNearEdgeLeft = 'near-edge-left',\n\tNearEdgeRight = 'near-edge-right',\n\tNearEdgeTop = 'near-edge-top',\n\tNearEdgeBottom = 'near-edge-bottom',\n\tWrapped = 'wrapped',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapEvent {\n\tEnterCenter = 'enter-center',\n\tApproachLeft = 'approach-left',\n\tApproachRight = 'approach-right',\n\tApproachTop = 'approach-top',\n\tApproachBottom = 'approach-bottom',\n\tWrap = 'wrap',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ScreenWrapFSM\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScreenWrapFSM {\n\tmachine: SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>(\n\t\t\tScreenWrapState.Center,\n\t\t\t[\n\t\t\t\t// From Center\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t\t\n\t\t\t\t// From NearEdge to Wrapped\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\n\t\t\t\t// From NearEdge back to Center\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped back to Center\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped to NearEdge (landed near opposite edge)\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): ScreenWrapState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: ScreenWrapEvent): 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 based on entity position relative to bounds\n\t */\n\tupdate(position: { x: number; y: number }, bounds: {\n\t\tminX: number; maxX: number; minY: number; maxY: number;\n\t\tedgeThreshold: number;\n\t}, wrapped: boolean): void {\n\t\tconst { x, y } = position;\n\t\tconst { minX, maxX, minY, maxY, edgeThreshold } = bounds;\n\n\t\tif (wrapped) {\n\t\t\tthis.dispatch(ScreenWrapEvent.Wrap);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if near edges\n\t\tconst nearLeft = x < minX + edgeThreshold;\n\t\tconst nearRight = x > maxX - edgeThreshold;\n\t\tconst nearBottom = y < minY + edgeThreshold;\n\t\tconst nearTop = y > maxY - edgeThreshold;\n\n\t\tif (nearLeft) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachLeft);\n\t\t} else if (nearRight) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachRight);\n\t\t} else if (nearTop) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachTop);\n\t\t} else if (nearBottom) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachBottom);\n\t\t} else {\n\t\t\tthis.dispatch(ScreenWrapEvent.EnterCenter);\n\t\t}\n\t}\n}\n","/**\n * ScreenWrapBehavior\n * \n * When an entity exits the defined 2D bounds, it wraps around to the opposite edge.\n * Asteroids-style screen wrapping with FSM for edge detection.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ScreenWrapFSM } from './screen-wrap-fsm';\nimport { createBounds2DRect, wrapPoint2D } from '../shared/bounds-2d';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Screen wrap options (typed for entity.use() autocomplete)\n */\nexport interface ScreenWrapOptions {\n\t/** Width of the wrapping area (default: 20) */\n\twidth: number;\n\t/** Height of the wrapping area (default: 15) */\n\theight: number;\n\t/** Center X position (default: 0) */\n\tcenterX: number;\n\t/** Center Y position (default: 0) */\n\tcenterY: number;\n\t/** Distance from edge to trigger NearEdge state (default: 2) */\n\tedgeThreshold: number;\n}\n\nconst defaultOptions: ScreenWrapOptions = {\n\twidth: 20,\n\theight: 15,\n\tcenterX: 0,\n\tcenterY: 0,\n\tedgeThreshold: 2,\n};\n\nconst SCREEN_WRAP_BEHAVIOR_KEY = Symbol.for('zylem:behavior:screen-wrap');\n\n/**\n * ScreenWrapSystem - Wraps entities around 2D bounds\n *\n * Mirrors the Rust `screen_wrap` Stage behavior: when `wasmStage` is present\n * and the entity has a `runtimeHandle`, the wasm runtime owns the wrap and\n * this system only mirrors the queried state into the local FSM. Otherwise\n * the legacy TS path runs against the Rapier `RigidBody` translation.\n */\nclass ScreenWrapSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(SCREEN_WRAP_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 wrapRef = link.ref as any;\n\t\t\tconst options = wrapRef.options as ScreenWrapOptions;\n\n\t\t\tif (!wrapRef.fsm) {\n\t\t\t\twrapRef.fsm = new ScreenWrapFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst pose = this.wasmStage.getPose(handle);\n\t\t\t\tconst query = this.wasmStage.queryScreenWrap(handle);\n\t\t\t\tif (pose && query) {\n\t\t\t\t\tconst bounds = createBounds2DRect(options);\n\t\t\t\t\twrapRef.fsm.update(\n\t\t\t\t\t\t{ x: pose.position[0], y: pose.position[1] },\n\t\t\t\t\t\t{\n\t\t\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t\t\t\t\t},\n\t\t\t\t\t\tquery.lastWrappedAxes !== 0,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tconst wrapped = this.wrapEntity(gameEntity, options);\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tconst bounds = createBounds2DRect(options);\n\t\t\twrapRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y },\n\t\t\t\t{\n\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t\t\t},\n\t\t\t\twrapped,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ScreenWrapOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachScreenWrap(handle, {\n\t\t\twidth: options.width,\n\t\t\theight: options.height,\n\t\t\tcenterX: options.centerX,\n\t\t\tcenterY: options.centerY,\n\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tprivate wrapEntity(entity: any, options: ScreenWrapOptions): boolean {\n\t\tconst body = entity.body;\n\t\tif (!body) return false;\n\n\t\tconst pos = body.translation();\n\t\tconst bounds = createBounds2DRect(options);\n\t\tconst result = wrapPoint2D({ x: pos.x, y: pos.y }, bounds);\n\n\t\tif (result.wrapped) {\n\t\t\tbody.setTranslation({ x: result.x, y: result.y, z: pos.z }, true);\n\t\t}\n\n\t\treturn result.wrapped;\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ScreenWrapBehavior - Wraps entities around 2D bounds\n * \n * @example\n * ```typescript\n * import { ScreenWrapBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const ship = createSprite({ ... });\n * const wrapRef = ship.use(ScreenWrapBehavior, { width: 20, height: 15 });\n * \n * // Access FSM to observe edge state\n * const fsm = wrapRef.getFSM();\n * console.log(fsm?.getState()); // 'center', 'near-edge-left', 'wrapped', etc.\n * ```\n */\nexport const ScreenWrapBehavior = defineBehavior({\n\tname: 'screen-wrap',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ScreenWrapSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":";;;;;;;;;;;;;AAaO,IAAK,kBAAL,kBAAKA,qBAAL;AACN,EAAAA,iBAAA,YAAS;AACT,EAAAA,iBAAA,kBAAe;AACf,EAAAA,iBAAA,mBAAgB;AAChB,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,oBAAiB;AACjB,EAAAA,iBAAA,aAAU;AANC,SAAAA;AAAA,GAAA;AAaL,IAAK,kBAAL,kBAAKC,qBAAL;AACN,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,kBAAe;AACf,EAAAA,iBAAA,mBAAgB;AAChB,EAAAA,iBAAA,iBAAc;AACd,EAAAA,iBAAA,oBAAiB;AACjB,EAAAA,iBAAA,UAAO;AANI,SAAAA;AAAA,GAAA;AAaL,IAAM,gBAAN,MAAoB;AAAA,EAC1B;AAAA,EAEA,cAAc;AACb,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA;AAAA,QAEC,EAAE,uBAAwB,oCAA8B,mCAA4B;AAAA,QACpF,EAAE,uBAAwB,sCAA+B,qCAA6B;AAAA,QACtF,EAAE,uBAAwB,kCAA6B,iCAA2B;AAAA,QAClF,EAAE,uBAAwB,wCAAgC,uCAA8B;AAAA;AAAA,QAGxF,EAAE,qCAA8B,mBAAsB,uBAAuB;AAAA,QAC7E,EAAE,uCAA+B,mBAAsB,uBAAuB;AAAA,QAC9E,EAAE,mCAA6B,mBAAsB,uBAAuB;AAAA,QAC5E,EAAE,yCAAgC,mBAAsB,uBAAuB;AAAA;AAAA,QAG/E,EAAE,qCAA8B,kCAA6B,qBAAsB;AAAA,QACnF,EAAE,uCAA+B,kCAA6B,qBAAsB;AAAA,QACpF,EAAE,mCAA6B,kCAA6B,qBAAsB;AAAA,QAClF,EAAE,yCAAgC,kCAA6B,qBAAsB;AAAA;AAAA,QAGrF,EAAE,yBAAyB,kCAA6B,qBAAsB;AAAA;AAAA,QAG9E,EAAE,yBAAyB,oCAA8B,mCAA4B;AAAA,QACrF,EAAE,yBAAyB,sCAA+B,qCAA6B;AAAA,QACvF,EAAE,yBAAyB,kCAA6B,iCAA2B;AAAA,QACnF,EAAE,yBAAyB,wCAAgC,uCAA8B;AAAA;AAAA,QAGzF,EAAE,uBAAwB,kCAA6B,qBAAsB;AAAA,QAC7E,EAAE,qCAA8B,oCAA8B,mCAA4B;AAAA,QAC1F,EAAE,uCAA+B,sCAA+B,qCAA6B;AAAA,QAC7F,EAAE,mCAA6B,kCAA6B,iCAA2B;AAAA,QACvF,EAAE,yCAAgC,wCAAgC,uCAA8B;AAAA,MACjG;AAAA,IACD;AAAA,EACD;AAAA,EAEA,WAA4B;AAC3B,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA,EAEA,SAAS,OAA8B;AACtC,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AAAA;AAAA;AAAA;AAAA,EAKA,OAAO,UAAoC,QAGxC,SAAwB;AAC1B,UAAM,EAAE,GAAG,EAAE,IAAI;AACjB,UAAM,EAAE,MAAM,MAAM,MAAM,MAAM,cAAc,IAAI;AAElD,QAAI,SAAS;AACZ,WAAK,SAAS,iBAAoB;AAClC;AAAA,IACD;AAGA,UAAM,WAAW,IAAI,OAAO;AAC5B,UAAM,YAAY,IAAI,OAAO;AAC7B,UAAM,aAAa,IAAI,OAAO;AAC9B,UAAM,UAAU,IAAI,OAAO;AAE3B,QAAI,UAAU;AACb,WAAK,SAAS,kCAA4B;AAAA,IAC3C,WAAW,WAAW;AACrB,WAAK,SAAS,oCAA6B;AAAA,IAC5C,WAAW,SAAS;AACnB,WAAK,SAAS,gCAA2B;AAAA,IAC1C,WAAW,YAAY;AACtB,WAAK,SAAS,sCAA8B;AAAA,IAC7C,OAAO;AACN,WAAK,SAAS,gCAA2B;AAAA,IAC1C;AAAA,EACD;AACD;;;ACjGA,IAAM,iBAAoC;AAAA,EACzC,OAAO;AAAA,EACP,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,SAAS;AAAA,EACT,eAAe;AAChB;AAEA,IAAM,2BAA2B,uBAAO,IAAI,4BAA4B;AAUxE,IAAM,mBAAN,MAAiD;AAAA,EAGhD,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,wBAAwB;AAC9D,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,aAAa,KAAK;AACxB,YAAM,UAAU,KAAK;AACrB,YAAM,UAAU,QAAQ;AAExB,UAAI,CAAC,QAAQ,KAAK;AACjB,gBAAQ,MAAM,IAAI,cAAc;AAAA,MACjC;AAEA,YAAM,SAAU,WAAW,iBAAiB;AAC5C,UAAI,KAAK,aAAa,UAAU,GAAG;AAClC,aAAK,mBAAmB,QAAQ,OAAO;AACvC,cAAM,OAAO,KAAK,UAAU,QAAQ,MAAM;AAC1C,cAAM,QAAQ,KAAK,UAAU,gBAAgB,MAAM;AACnD,YAAI,QAAQ,OAAO;AAClB,gBAAMC,UAAS,mBAAmB,OAAO;AACzC,kBAAQ,IAAI;AAAA,YACX,EAAE,GAAG,KAAK,SAAS,CAAC,GAAG,GAAG,KAAK,SAAS,CAAC,EAAE;AAAA,YAC3C;AAAA,cACC,MAAMA,QAAO;AAAA,cACb,MAAMA,QAAO;AAAA,cACb,MAAMA,QAAO;AAAA,cACb,MAAMA,QAAO;AAAA,cACb,eAAe,QAAQ;AAAA,YACxB;AAAA,YACA,MAAM,oBAAoB;AAAA,UAC3B;AACA;AAAA,QACD;AAAA,MACD;AAEA,UAAI,CAAC,WAAW,KAAM;AACtB,YAAM,UAAU,KAAK,WAAW,YAAY,OAAO;AACnD,YAAM,MAAM,WAAW,KAAK,YAAY;AACxC,YAAM,SAAS,mBAAmB,OAAO;AACzC,cAAQ,IAAI;AAAA,QACX,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE;AAAA,QACrB;AAAA,UACC,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,MAAM,OAAO;AAAA,UACb,eAAe,QAAQ;AAAA,QACxB;AAAA,QACA;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,mBAAmB,QAAgB,SAAkC;AAC5E,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,SAAK,UAAU,iBAAiB,QAAQ;AAAA,MACvC,OAAO,QAAQ;AAAA,MACf,QAAQ,QAAQ;AAAA,MAChB,SAAS,QAAQ;AAAA,MACjB,SAAS,QAAQ;AAAA,MACjB,eAAe,QAAQ;AAAA,IACxB,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEQ,WAAW,QAAa,SAAqC;AACpE,UAAM,OAAO,OAAO;AACpB,QAAI,CAAC,KAAM,QAAO;AAElB,UAAM,MAAM,KAAK,YAAY;AAC7B,UAAM,SAAS,mBAAmB,OAAO;AACzC,UAAM,SAAS,YAAY,EAAE,GAAG,IAAI,GAAG,GAAG,IAAI,EAAE,GAAG,MAAM;AAEzD,QAAI,OAAO,SAAS;AACnB,WAAK,eAAe,EAAE,GAAG,OAAO,GAAG,GAAG,OAAO,GAAG,GAAG,IAAI,EAAE,GAAG,IAAI;AAAA,IACjE;AAEA,WAAO,OAAO;AAAA,EACf;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AAiBO,IAAM,qBAAqB,eAAe;AAAA,EAChD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI,iBAAiB,IAAI,aAAa,MAAM,IAAI,gBAAgB;AAClE,CAAC;","names":["ScreenWrapState","ScreenWrapEvent","bounds"]}
@@ -2,10 +2,10 @@ import {
2
2
  GroundProbe3D,
3
3
  getGroundAnchorOffsetY,
4
4
  getGroundSnapTargetY
5
- } from "./chunk-SVLNF434.js";
5
+ } from "./chunk-JXMF4NUI.js";
6
6
  import {
7
7
  setVelocityIntent
8
- } from "./chunk-RTVRUNYR.js";
8
+ } from "./chunk-OTLSUYQQ.js";
9
9
  import {
10
10
  SyncStateMachine,
11
11
  t
@@ -48,7 +48,7 @@ function createJumpState2D() {
48
48
  };
49
49
  }
50
50
 
51
- // src/lib/behaviors/jumper-2d/jumper-2d.behavior.ts
51
+ // src/lib/behaviors/jumper-2d/jumper-2d.system.ts
52
52
  function jumpVelocityFromHeight(gravity, height) {
53
53
  return Math.sqrt(2 * gravity * height);
54
54
  }
@@ -59,7 +59,7 @@ var Jumper2DTickEvent = /* @__PURE__ */ ((Jumper2DTickEvent3) => {
59
59
  Jumper2DTickEvent3["Land"] = "land";
60
60
  return Jumper2DTickEvent3;
61
61
  })(Jumper2DTickEvent || {});
62
- var Jumper2DBehavior = class {
62
+ var Jumper2DSystem = class {
63
63
  tick(config, input, ctx, state) {
64
64
  const dtMs = ctx.dt * 1e3;
65
65
  const now = performance.now();
@@ -218,11 +218,6 @@ var defaultOptions = {
218
218
  debugGroundProbe: false
219
219
  };
220
220
  var JUMPER_2D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:jumper-2d");
221
- var JUMPER_2D_OFFSETS = [
222
- { x: 0, z: 0 },
223
- { x: 0.35, z: 0 },
224
- { x: -0.35, z: 0 }
225
- ];
226
221
  function isJumper2DGrounded(params) {
227
222
  if (params.velocityY > 0) {
228
223
  return false;
@@ -237,22 +232,22 @@ function shouldSnapJumper2DToGround(support, options, velocityY) {
237
232
  }
238
233
  var Jumper2DBehaviorSystem = class {
239
234
  constructor(world, scene, wasmStage, getBehaviorLinks) {
240
- this.world = world;
241
235
  this.scene = scene;
242
236
  this.wasmStage = wasmStage;
243
237
  this.getBehaviorLinks = getBehaviorLinks;
244
238
  this.groundProbe = new GroundProbe3D(world);
245
239
  }
246
- world;
247
240
  scene;
248
241
  wasmStage;
249
242
  getBehaviorLinks;
250
- behavior = new Jumper2DBehavior();
243
+ behavior = new Jumper2DSystem();
251
244
  groundProbe;
252
245
  initializedEntities = /* @__PURE__ */ new Set();
253
246
  timeSinceGroundedMs = /* @__PURE__ */ new Map();
254
247
  wasJumpHeld = /* @__PURE__ */ new Map();
255
248
  attachedRuntimeSlots = /* @__PURE__ */ new Set();
249
+ /** Reused per-entity per-frame instead of spreading a fresh input object. */
250
+ effectiveInput = createJumpInput2D();
256
251
  update(_ecs, delta) {
257
252
  const links = this.getBehaviorLinks?.(JUMPER_2D_BEHAVIOR_KEY);
258
253
  if (!links) return;
@@ -292,7 +287,6 @@ var Jumper2DBehaviorSystem = class {
292
287
  const probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);
293
288
  const support = this.groundProbe.probeSupport(gameEntity, {
294
289
  rayLength,
295
- offsets: JUMPER_2D_OFFSETS,
296
290
  mode: "any",
297
291
  debug: options.debugGroundProbe ?? false,
298
292
  scene: this.scene,
@@ -328,11 +322,11 @@ var Jumper2DBehaviorSystem = class {
328
322
  const state = gameEntity.jumper2dState;
329
323
  const previousHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;
330
324
  const held = input.jumpHeld === true;
331
- const effectiveInput = {
332
- ...input,
333
- jumpPressed: input.jumpPressed || held && !previousHeld,
334
- jumpReleased: input.jumpReleased || !held && previousHeld
335
- };
325
+ const effectiveInput = this.effectiveInput;
326
+ effectiveInput.jumpPressed = input.jumpPressed || held && !previousHeld;
327
+ effectiveInput.jumpHeld = held;
328
+ effectiveInput.jumpReleased = input.jumpReleased || !held && previousHeld;
329
+ effectiveInput.fastFall = input.fastFall;
336
330
  const store = gameEntity.transformStore;
337
331
  const ctx = {
338
332
  dt: delta,
@@ -376,7 +370,7 @@ var Jumper2DBehaviorSystem = class {
376
370
  this.attachedRuntimeSlots.clear();
377
371
  }
378
372
  };
379
- var Jumper2D = defineBehavior({
373
+ var Jumper2DBehavior = defineBehavior({
380
374
  name: "jumper-2d",
381
375
  defaultOptions,
382
376
  systemFactory: (ctx) => new Jumper2DBehaviorSystem(ctx.world, ctx.scene, ctx.wasmStage ?? null, ctx.getBehaviorLinks),
@@ -397,10 +391,10 @@ export {
397
391
  createJumpInput2D,
398
392
  createJumpState2D,
399
393
  Jumper2DTickEvent,
400
- Jumper2DBehavior,
394
+ Jumper2DSystem,
401
395
  Jumper2DState,
402
396
  Jumper2DEvent,
403
397
  Jumper2DFSM,
404
- Jumper2D
398
+ Jumper2DBehavior
405
399
  };
406
- //# sourceMappingURL=chunk-SS7ELMJ7.js.map
400
+ //# sourceMappingURL=chunk-GTLA6LOJ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/jumper-2d/components.ts","../src/lib/behaviors/jumper-2d/jumper-2d.system.ts","../src/lib/behaviors/jumper-2d/jumper-2d-fsm.ts","../src/lib/behaviors/jumper-2d/jumper-2d.descriptor.ts"],"sourcesContent":["export interface JumpConfig2D {\n\tjumpHeight: number;\n\tgravity: number;\n\tmaxFallSpeed?: number;\n\tmaxJumps: number;\n\tresetJumpsOnGround: boolean;\n\tcoyoteTimeMs: number;\n\tjumpBufferMs: number;\n\tminTimeBetweenJumpsMs?: number;\n\tvariableJump?: {\n\t\tenabled: boolean;\n\t\tcutGravityMultiplier: number;\n\t\tmaxHoldMs?: number;\n\t};\n}\n\nexport function createJumpConfig2D(\n\toptions: Partial<JumpConfig2D> = {},\n): JumpConfig2D {\n\treturn {\n\t\tjumpHeight: options.jumpHeight ?? 2.5,\n\t\tgravity: options.gravity ?? 20,\n\t\tmaxFallSpeed: options.maxFallSpeed,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tresetJumpsOnGround: options.resetJumpsOnGround ?? true,\n\t\tcoyoteTimeMs: options.coyoteTimeMs ?? 100,\n\t\tjumpBufferMs: options.jumpBufferMs ?? 80,\n\t\tminTimeBetweenJumpsMs: options.minTimeBetweenJumpsMs,\n\t\tvariableJump: options.variableJump,\n\t};\n}\n\nexport interface JumpInput2D {\n\tjumpPressed: boolean;\n\tjumpHeld: boolean;\n\tjumpReleased: boolean;\n\tfastFall?: boolean;\n}\n\nexport function createJumpInput2D(): JumpInput2D {\n\treturn {\n\t\tjumpPressed: false,\n\t\tjumpHeld: false,\n\t\tjumpReleased: false,\n\t\tfastFall: false,\n\t};\n}\n\nexport interface JumpContext2D {\n\tdt: number;\n\tvelocityY: number;\n\tisGrounded: boolean;\n\ttimeSinceGroundedMs: number;\n\tsetVerticalVelocity(y: number): void;\n}\n\nexport interface JumpState2D {\n\tjumpsUsed: number;\n\tlastJumpTimeMs: number;\n\tbufferedJumpMs: number;\n\tcoyoteMs: number;\n\tisJumping: boolean;\n\tjumpHoldMs: number;\n\tjumpCutApplied: boolean;\n}\n\nexport function createJumpState2D(): JumpState2D {\n\treturn {\n\t\tjumpsUsed: 0,\n\t\tlastJumpTimeMs: 0,\n\t\tbufferedJumpMs: 0,\n\t\tcoyoteMs: 0,\n\t\tisJumping: false,\n\t\tjumpHoldMs: 0,\n\t\tjumpCutApplied: false,\n\t};\n}\n","import type {\n\tJumpConfig2D,\n\tJumpContext2D,\n\tJumpInput2D,\n\tJumpState2D,\n} from './components';\n\nfunction jumpVelocityFromHeight(gravity: number, height: number): number {\n\treturn Math.sqrt(2 * gravity * height);\n}\n\nexport enum Jumper2DTickEvent {\n\tNone = 'none',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface Jumper2DTickResult {\n\tevent: Jumper2DTickEvent;\n}\n\nexport class Jumper2DSystem {\n\ttick(\n\t\tconfig: JumpConfig2D,\n\t\tinput: JumpInput2D,\n\t\tctx: JumpContext2D,\n\t\tstate: JumpState2D,\n\t): Jumper2DTickResult {\n\t\tconst dtMs = ctx.dt * 1000;\n\t\tconst now = performance.now();\n\t\tlet event = Jumper2DTickEvent.None;\n\t\tlet jumpedThisFrame = false;\n\n\t\tif (ctx.isGrounded) {\n\t\t\tstate.coyoteMs = config.coyoteTimeMs;\n\t\t\tif (config.resetJumpsOnGround) {\n\t\t\t\tstate.jumpsUsed = 0;\n\t\t\t}\n\t\t\tif (state.isJumping) {\n\t\t\t\tstate.isJumping = false;\n\t\t\t\tstate.jumpCutApplied = false;\n\t\t\t\tevent = Jumper2DTickEvent.Land;\n\t\t\t}\n\t\t} else {\n\t\t\tstate.coyoteMs = Math.max(0, state.coyoteMs - dtMs);\n\t\t}\n\n\t\tif (input.jumpPressed) {\n\t\t\tstate.bufferedJumpMs = config.jumpBufferMs;\n\t\t} else {\n\t\t\tstate.bufferedJumpMs = Math.max(0, state.bufferedJumpMs - dtMs);\n\t\t}\n\n\t\tif (state.isJumping && input.jumpHeld) {\n\t\t\tstate.jumpHoldMs += dtMs;\n\t\t}\n\n\t\tconst wantsJump = state.bufferedJumpMs > 0;\n\t\tconst isFirstJump =\n\t\t\tctx.isGrounded || (state.coyoteMs > 0 && state.jumpsUsed === 0);\n\t\tconst hasAirJumps = state.jumpsUsed > 0 && state.jumpsUsed < config.maxJumps;\n\t\tconst canJump = isFirstJump || hasAirJumps;\n\t\tconst cooldownOk =\n\t\t\tconfig.minTimeBetweenJumpsMs == null ||\n\t\t\tnow - state.lastJumpTimeMs >= config.minTimeBetweenJumpsMs;\n\n\t\tif (wantsJump && canJump && cooldownOk) {\n\t\t\tctx.setVerticalVelocity(jumpVelocityFromHeight(config.gravity, config.jumpHeight));\n\t\t\tjumpedThisFrame = true;\n\t\t\tstate.jumpsUsed++;\n\t\t\tstate.lastJumpTimeMs = now;\n\t\t\tstate.bufferedJumpMs = 0;\n\t\t\tstate.coyoteMs = 0;\n\t\t\tstate.isJumping = true;\n\t\t\tstate.jumpHoldMs = 0;\n\t\t\tstate.jumpCutApplied = false;\n\t\t\tevent = Jumper2DTickEvent.Jump;\n\t\t}\n\n\t\tif (!ctx.isGrounded || jumpedThisFrame) {\n\t\t\tlet gravityMul = 1;\n\t\t\tconst isFalling = ctx.velocityY < 0;\n\n\t\t\tif (isFalling) {\n\t\t\t\tgravityMul = input.fastFall ? 1.75 : 1;\n\t\t\t\tif (event !== Jumper2DTickEvent.Land) {\n\t\t\t\t\tevent = Jumper2DTickEvent.Fall;\n\t\t\t\t}\n\t\t\t} else if (state.isJumping) {\n\t\t\t\tconst variableJump = config.variableJump;\n\t\t\t\tif (variableJump?.enabled && !state.jumpCutApplied) {\n\t\t\t\t\tconst holdExpired =\n\t\t\t\t\t\tvariableJump.maxHoldMs != null &&\n\t\t\t\t\t\tstate.jumpHoldMs >= variableJump.maxHoldMs;\n\t\t\t\t\tif (!input.jumpHeld || holdExpired) {\n\t\t\t\t\t\tgravityMul = variableJump.cutGravityMultiplier;\n\t\t\t\t\t\tstate.jumpCutApplied = true;\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tlet newVelocityY = ctx.velocityY - config.gravity * gravityMul * ctx.dt;\n\t\t\tif (config.maxFallSpeed != null) {\n\t\t\t\tnewVelocityY = Math.max(-config.maxFallSpeed, newVelocityY);\n\t\t\t}\n\t\t\tctx.setVerticalVelocity(newVelocityY);\n\t\t} else {\n\t\t\tctx.setVerticalVelocity(0);\n\t\t}\n\n\t\treturn { event };\n\t}\n}\n","import { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { JumpState2D } from './components';\n\nexport enum Jumper2DState {\n\tGrounded = 'grounded',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n}\n\nexport enum Jumper2DEvent {\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface Jumper2DFSMContext {\n\tstate: JumpState2D;\n}\n\nexport class Jumper2DFSM {\n\tmachine: SyncStateMachine<Jumper2DState, Jumper2DEvent, never>;\n\n\tconstructor(private ctx: Jumper2DFSMContext) {\n\t\tthis.machine = new SyncStateMachine<Jumper2DState, Jumper2DEvent, never>(\n\t\t\tJumper2DState.Grounded,\n\t\t\t[\n\t\t\t\tt(Jumper2DState.Grounded, Jumper2DEvent.Jump, Jumper2DState.Jumping),\n\t\t\t\tt(Jumper2DState.Grounded, Jumper2DEvent.Fall, Jumper2DState.Falling),\n\t\t\t\tt(Jumper2DState.Grounded, Jumper2DEvent.Land, Jumper2DState.Grounded),\n\t\t\t\tt(Jumper2DState.Jumping, Jumper2DEvent.Fall, Jumper2DState.Falling),\n\t\t\t\tt(Jumper2DState.Jumping, Jumper2DEvent.Land, Jumper2DState.Grounded),\n\t\t\t\tt(Jumper2DState.Jumping, Jumper2DEvent.Jump, Jumper2DState.Jumping),\n\t\t\t\tt(Jumper2DState.Falling, Jumper2DEvent.Land, Jumper2DState.Grounded),\n\t\t\t\tt(Jumper2DState.Falling, Jumper2DEvent.Jump, Jumper2DState.Jumping),\n\t\t\t\tt(Jumper2DState.Falling, Jumper2DEvent.Fall, Jumper2DState.Falling),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Jumper2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Jumper2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisJumping(): boolean {\n\t\treturn this.getState() === Jumper2DState.Jumping;\n\t}\n\n\tisGrounded(): boolean {\n\t\treturn this.getState() === Jumper2DState.Grounded;\n\t}\n\n\tgetJumpsUsed(): number {\n\t\treturn this.ctx.state.jumpsUsed;\n\t}\n\n\tapplyTickEvent(tickEvent: string, isGrounded: boolean): void {\n\t\tswitch (tickEvent) {\n\t\t\tcase 'jump':\n\t\t\t\tthis.dispatch(Jumper2DEvent.Jump);\n\t\t\t\tbreak;\n\t\t\tcase 'fall':\n\t\t\t\tthis.dispatch(Jumper2DEvent.Fall);\n\t\t\t\tbreak;\n\t\t\tcase 'land':\n\t\t\t\tthis.dispatch(Jumper2DEvent.Land);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\tif (!isGrounded && this.getState() === Jumper2DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper2DEvent.Fall);\n\t\t\t\t}\n\t\t\t\tif (isGrounded && this.getState() !== Jumper2DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper2DEvent.Land);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n","import { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { setVelocityIntent } from '../../capabilities/velocity-intents';\nimport {\n\tGroundProbe3D,\n\tgetGroundAnchorOffsetY,\n\tgetGroundSnapTargetY,\n\ttype GroundProbeSupportHit,\n} from '../shared/ground-probe-3d';\nimport {\n\tcreateJumpConfig2D,\n\tcreateJumpInput2D,\n\tcreateJumpState2D,\n\ttype JumpConfig2D,\n\ttype JumpContext2D,\n\ttype JumpInput2D,\n\ttype JumpState2D,\n} from './components';\nimport {\n\tJumper2DSystem,\n\tJumper2DTickEvent,\n} from './jumper-2d.system';\nimport { Jumper2DFSM, Jumper2DState } from './jumper-2d-fsm';\nimport type { WasmStageRuntime } from '../../runtime/wasm-stage-runtime';\n\nexport interface Jumper2DBehaviorOptions {\n\tjumpHeight?: number;\n\tgravity?: number;\n\tmaxFallSpeed?: number;\n\tmaxJumps?: number;\n\tresetJumpsOnGround?: boolean;\n\tcoyoteTimeMs?: number;\n\tjumpBufferMs?: number;\n\tminTimeBetweenJumpsMs?: number;\n\tvariableJump?: JumpConfig2D['variableJump'];\n\tgroundRayLength?: number;\n\tsnapToGroundDistance?: number;\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Jumper2DBehaviorOptions = {\n\tjumpHeight: 2.5,\n\tgravity: 20,\n\tmaxJumps: 1,\n\tresetJumpsOnGround: true,\n\tcoyoteTimeMs: 100,\n\tjumpBufferMs: 80,\n\tgroundRayLength: 1,\n\tdebugGroundProbe: false,\n};\n\nconst JUMPER_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:jumper-2d');\n\nexport function isJumper2DGrounded(params: {\n\tnearGround?: boolean;\n\tvelocityY: number;\n\tsupportToi?: number | null;\n\tsnapToGroundDistance?: number;\n}): boolean {\n\tif (params.velocityY > 0) {\n\t\treturn false;\n\t}\n\n\tif (params.snapToGroundDistance != null) {\n\t\treturn (\n\t\t\tparams.supportToi != null &&\n\t\t\tparams.supportToi <= params.snapToGroundDistance\n\t\t);\n\t}\n\n\treturn params.nearGround === true;\n}\n\nfunction shouldSnapJumper2DToGround(\n\tsupport: GroundProbeSupportHit | null,\n\toptions: Jumper2DBehaviorOptions,\n\tvelocityY: number,\n): support is GroundProbeSupportHit {\n\treturn (\n\t\tsupport != null &&\n\t\toptions.snapToGroundDistance != null &&\n\t\tvelocityY <= 0 &&\n\t\tsupport.toi <= options.snapToGroundDistance\n\t);\n}\n\nexport interface Jumper2DEntity {\n\tuuid: string;\n\tbody: import('@dimforge/rapier3d-compat').RigidBody | null;\n\ttransformStore: import('../../capabilities/transform-store').TransformState;\n\tjumper2d: JumpConfig2D;\n\t$jumper2d: JumpInput2D;\n\tjumper2dState: JumpState2D;\n}\n\nclass Jumper2DBehaviorSystem implements BehaviorSystem {\n\tprivate behavior = new Jumper2DSystem();\n\tprivate groundProbe: GroundProbe3D;\n\tprivate initializedEntities = new Set<string>();\n\tprivate timeSinceGroundedMs = new Map<string, number>();\n\tprivate wasJumpHeld = new Map<string, boolean>();\n\tprivate attachedRuntimeSlots = new Set<number>();\n\t/** Reused per-entity per-frame instead of spreading a fresh input object. */\n\tprivate effectiveInput: JumpInput2D = createJumpInput2D();\n\n\tconstructor(\n\t\tworld: import('../../collision/world').ZylemWorld | null,\n\t\tprivate scene: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.groundProbe = new GroundProbe3D(world);\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(JUMPER_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 jumperRef = link.ref as any;\n\n\t\t\tconst options = jumperRef.options as Jumper2DBehaviorOptions;\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst input = gameEntity.$jumper2d as JumpInput2D | undefined;\n\t\t\t\tconst jumpPressed = !!(input?.jumpPressed || input?.jumpHeld);\n\t\t\t\tthis.wasmStage.setJumper2DInput(handle, jumpPressed);\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\n\t\t\tif (!gameEntity.jumper2d) {\n\t\t\t\tgameEntity.jumper2d = createJumpConfig2D(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$jumper2d) {\n\t\t\t\tgameEntity.$jumper2d = createJumpInput2D();\n\t\t\t}\n\t\t\tif (!gameEntity.jumper2dState) {\n\t\t\t\tgameEntity.jumper2dState = createJumpState2D();\n\t\t\t}\n\t\t\tif (!this.initializedEntities.has(gameEntity.uuid)) {\n\t\t\t\tthis.initializedEntities.add(gameEntity.uuid);\n\t\t\t\tgameEntity.body.setGravityScale(0, true);\n\t\t\t}\n\t\t\tif (!jumperRef.fsm && gameEntity.jumper2dState) {\n\t\t\t\tjumperRef.fsm = new Jumper2DFSM({\n\t\t\t\t\tstate: gameEntity.jumper2dState,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tconst rayLength = options.groundRayLength ?? 1;\n\t\t\tconst bodyVelocity = gameEntity.body.linvel();\n\t\t\tconst probeOriginYOffset = 0.05 - getGroundAnchorOffsetY(gameEntity);\n\t\t\tconst support = this.groundProbe.probeSupport(gameEntity, {\n\t\t\t\trayLength,\n\t\t\t\tmode: 'any',\n\t\t\t\tdebug: options.debugGroundProbe ?? false,\n\t\t\t\tscene: this.scene,\n\t\t\t\toriginYOffset: probeOriginYOffset,\n\t\t\t});\n\t\t\tconst nearGround = support != null;\n\t\t\tconst isGrounded = isJumper2DGrounded({\n\t\t\t\tnearGround,\n\t\t\t\tvelocityY: bodyVelocity.y,\n\t\t\t\tsupportToi: support?.toi ?? null,\n\t\t\t\tsnapToGroundDistance: options.snapToGroundDistance,\n\t\t\t});\n\t\t\tif (shouldSnapJumper2DToGround(support, options, bodyVelocity.y)) {\n\t\t\t\tconst currentPosition = gameEntity.body.translation();\n\t\t\t\tgameEntity.body.setTranslation(\n\t\t\t\t\t{\n\t\t\t\t\t\tx: currentPosition.x,\n\t\t\t\t\t\ty: getGroundSnapTargetY(gameEntity, support),\n\t\t\t\t\t\tz: currentPosition.z,\n\t\t\t\t\t},\n\t\t\t\t\ttrue,\n\t\t\t\t);\n\t\t\t}\n\n\t\t\tlet timeSinceGroundedMs = this.timeSinceGroundedMs.get(gameEntity.uuid) ?? 0;\n\t\t\tif (isGrounded) {\n\t\t\t\ttimeSinceGroundedMs = 0;\n\t\t\t} else {\n\t\t\t\ttimeSinceGroundedMs += delta * 1000;\n\t\t\t}\n\t\t\tthis.timeSinceGroundedMs.set(gameEntity.uuid, timeSinceGroundedMs);\n\n\t\t\tconst config: JumpConfig2D = gameEntity.jumper2d;\n\t\t\tconst input: JumpInput2D = gameEntity.$jumper2d;\n\t\t\tconst state: JumpState2D = gameEntity.jumper2dState;\n\t\t\tconst previousHeld = this.wasJumpHeld.get(gameEntity.uuid) ?? false;\n\t\t\tconst held = input.jumpHeld === true;\n\t\t\tconst effectiveInput = this.effectiveInput;\n\t\t\teffectiveInput.jumpPressed = input.jumpPressed || (held && !previousHeld);\n\t\t\teffectiveInput.jumpHeld = held;\n\t\t\teffectiveInput.jumpReleased = input.jumpReleased || (!held && previousHeld);\n\t\t\teffectiveInput.fastFall = input.fastFall;\n\n\t\t\tconst store = gameEntity.transformStore;\n\t\t\tconst ctx: JumpContext2D = {\n\t\t\t\tdt: delta,\n\t\t\t\tvelocityY: bodyVelocity.y,\n\t\t\t\tisGrounded,\n\t\t\t\ttimeSinceGroundedMs,\n\t\t\t\tsetVerticalVelocity: (y: number) => {\n\t\t\t\t\tsetVelocityIntent(\n\t\t\t\t\t\tstore,\n\t\t\t\t\t\t'jumper-2d',\n\t\t\t\t\t\t{ y },\n\t\t\t\t\t\t{ mode: 'replace', priority: 20 },\n\t\t\t\t\t);\n\t\t\t\t\tctx.velocityY = y;\n\t\t\t\t},\n\t\t\t};\n\n\t\t\tconst result = this.behavior.tick(config, effectiveInput, ctx, state);\n\t\t\tthis.wasJumpHeld.set(gameEntity.uuid, held);\n\n\t\t\tif (jumperRef.fsm) {\n\t\t\t\t(jumperRef.fsm as Jumper2DFSM).applyTickEvent(result.event, isGrounded);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Jumper2DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst jumpForce = Math.sqrt(2 * (options.gravity ?? 20) * (options.jumpHeight ?? 2.5));\n\t\tthis.wasmStage.attachJumper2D(handle, {\n\t\t\tjumpForce,\n\t\t\tmaxJumps: options.maxJumps ?? 1,\n\t\t\tjumpBufferTime: (options.jumpBufferMs ?? 80) / 1000,\n\t\t\tcoyoteTime: (options.coyoteTimeMs ?? 100) / 1000,\n\t\t\tgravity: options.gravity ?? 20,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.groundProbe.destroy();\n\t\tthis.initializedEntities.clear();\n\t\tthis.timeSinceGroundedMs.clear();\n\t\tthis.wasJumpHeld.clear();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\nexport const Jumper2DBehavior = defineBehavior<\n\tJumper2DBehaviorOptions,\n\t{\n\t\tgetState: () => Jumper2DState;\n\t\tisJumping: () => boolean;\n\t\tgetJumpsUsed: () => number;\n\t\tgetJumpsRemaining: () => number;\n\t},\n\tJumper2DEntity\n>({\n\tname: 'jumper-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Jumper2DBehaviorSystem(ctx.world, ctx.scene, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: (ref) => ({\n\t\tgetState: () =>\n\t\t\t(ref.fsm as Jumper2DFSM | undefined)?.getState() ??\n\t\t\tJumper2DState.Grounded,\n\t\tisJumping: () =>\n\t\t\t(ref.fsm as Jumper2DFSM | undefined)?.isJumping() ?? false,\n\t\tgetJumpsUsed: () =>\n\t\t\t(ref.fsm as Jumper2DFSM | undefined)?.getJumpsUsed() ?? 0,\n\t\tgetJumpsRemaining: () => {\n\t\t\tconst maxJumps = ref.options.maxJumps ?? 1;\n\t\t\tconst used =\n\t\t\t\t(ref.fsm as Jumper2DFSM | undefined)?.getJumpsUsed() ?? 0;\n\t\t\treturn maxJumps - used;\n\t\t},\n\t}),\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAgBO,SAAS,mBACf,UAAiC,CAAC,GACnB;AACf,SAAO;AAAA,IACN,YAAY,QAAQ,cAAc;AAAA,IAClC,SAAS,QAAQ,WAAW;AAAA,IAC5B,cAAc,QAAQ;AAAA,IACtB,UAAU,QAAQ,YAAY;AAAA,IAC9B,oBAAoB,QAAQ,sBAAsB;AAAA,IAClD,cAAc,QAAQ,gBAAgB;AAAA,IACtC,cAAc,QAAQ,gBAAgB;AAAA,IACtC,uBAAuB,QAAQ;AAAA,IAC/B,cAAc,QAAQ;AAAA,EACvB;AACD;AASO,SAAS,oBAAiC;AAChD,SAAO;AAAA,IACN,aAAa;AAAA,IACb,UAAU;AAAA,IACV,cAAc;AAAA,IACd,UAAU;AAAA,EACX;AACD;AAoBO,SAAS,oBAAiC;AAChD,SAAO;AAAA,IACN,WAAW;AAAA,IACX,gBAAgB;AAAA,IAChB,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,WAAW;AAAA,IACX,YAAY;AAAA,IACZ,gBAAgB;AAAA,EACjB;AACD;;;ACrEA,SAAS,uBAAuB,SAAiB,QAAwB;AACxE,SAAO,KAAK,KAAK,IAAI,UAAU,MAAM;AACtC;AAEO,IAAK,oBAAL,kBAAKA,uBAAL;AACN,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AACP,EAAAA,mBAAA,UAAO;AAJI,SAAAA;AAAA,GAAA;AAWL,IAAM,iBAAN,MAAqB;AAAA,EAC3B,KACC,QACA,OACA,KACA,OACqB;AACrB,UAAM,OAAO,IAAI,KAAK;AACtB,UAAM,MAAM,YAAY,IAAI;AAC5B,QAAI,QAAQ;AACZ,QAAI,kBAAkB;AAEtB,QAAI,IAAI,YAAY;AACnB,YAAM,WAAW,OAAO;AACxB,UAAI,OAAO,oBAAoB;AAC9B,cAAM,YAAY;AAAA,MACnB;AACA,UAAI,MAAM,WAAW;AACpB,cAAM,YAAY;AAClB,cAAM,iBAAiB;AACvB,gBAAQ;AAAA,MACT;AAAA,IACD,OAAO;AACN,YAAM,WAAW,KAAK,IAAI,GAAG,MAAM,WAAW,IAAI;AAAA,IACnD;AAEA,QAAI,MAAM,aAAa;AACtB,YAAM,iBAAiB,OAAO;AAAA,IAC/B,OAAO;AACN,YAAM,iBAAiB,KAAK,IAAI,GAAG,MAAM,iBAAiB,IAAI;AAAA,IAC/D;AAEA,QAAI,MAAM,aAAa,MAAM,UAAU;AACtC,YAAM,cAAc;AAAA,IACrB;AAEA,UAAM,YAAY,MAAM,iBAAiB;AACzC,UAAM,cACL,IAAI,cAAe,MAAM,WAAW,KAAK,MAAM,cAAc;AAC9D,UAAM,cAAc,MAAM,YAAY,KAAK,MAAM,YAAY,OAAO;AACpE,UAAM,UAAU,eAAe;AAC/B,UAAM,aACL,OAAO,yBAAyB,QAChC,MAAM,MAAM,kBAAkB,OAAO;AAEtC,QAAI,aAAa,WAAW,YAAY;AACvC,UAAI,oBAAoB,uBAAuB,OAAO,SAAS,OAAO,UAAU,CAAC;AACjF,wBAAkB;AAClB,YAAM;AACN,YAAM,iBAAiB;AACvB,YAAM,iBAAiB;AACvB,YAAM,WAAW;AACjB,YAAM,YAAY;AAClB,YAAM,aAAa;AACnB,YAAM,iBAAiB;AACvB,cAAQ;AAAA,IACT;AAEA,QAAI,CAAC,IAAI,cAAc,iBAAiB;AACvC,UAAI,aAAa;AACjB,YAAM,YAAY,IAAI,YAAY;AAElC,UAAI,WAAW;AACd,qBAAa,MAAM,WAAW,OAAO;AACrC,YAAI,UAAU,mBAAwB;AACrC,kBAAQ;AAAA,QACT;AAAA,MACD,WAAW,MAAM,WAAW;AAC3B,cAAM,eAAe,OAAO;AAC5B,YAAI,cAAc,WAAW,CAAC,MAAM,gBAAgB;AACnD,gBAAM,cACL,aAAa,aAAa,QAC1B,MAAM,cAAc,aAAa;AAClC,cAAI,CAAC,MAAM,YAAY,aAAa;AACnC,yBAAa,aAAa;AAC1B,kBAAM,iBAAiB;AAAA,UACxB;AAAA,QACD;AAAA,MACD;AAEA,UAAI,eAAe,IAAI,YAAY,OAAO,UAAU,aAAa,IAAI;AACrE,UAAI,OAAO,gBAAgB,MAAM;AAChC,uBAAe,KAAK,IAAI,CAAC,OAAO,cAAc,YAAY;AAAA,MAC3D;AACA,UAAI,oBAAoB,YAAY;AAAA,IACrC,OAAO;AACN,UAAI,oBAAoB,CAAC;AAAA,IAC1B;AAEA,WAAO,EAAE,MAAM;AAAA,EAChB;AACD;;;AC9GO,IAAK,gBAAL,kBAAKC,mBAAL;AACN,EAAAA,eAAA,cAAW;AACX,EAAAA,eAAA,aAAU;AACV,EAAAA,eAAA,aAAU;AAHC,SAAAA;AAAA,GAAA;AAML,IAAK,gBAAL,kBAAKC,mBAAL;AACN,EAAAA,eAAA,UAAO;AACP,EAAAA,eAAA,UAAO;AACP,EAAAA,eAAA,UAAO;AAHI,SAAAA;AAAA,GAAA;AAUL,IAAM,cAAN,MAAkB;AAAA,EAGxB,YAAoB,KAAyB;AAAzB;AACnB,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA,QACC,EAAE,2BAAwB,mBAAoB,uBAAqB;AAAA,QACnE,EAAE,2BAAwB,mBAAoB,uBAAqB;AAAA,QACnE,EAAE,2BAAwB,mBAAoB,yBAAsB;AAAA,QACpE,EAAE,yBAAuB,mBAAoB,uBAAqB;AAAA,QAClE,EAAE,yBAAuB,mBAAoB,yBAAsB;AAAA,QACnE,EAAE,yBAAuB,mBAAoB,uBAAqB;AAAA,QAClE,EAAE,yBAAuB,mBAAoB,yBAAsB;AAAA,QACnE,EAAE,yBAAuB,mBAAoB,uBAAqB;AAAA,QAClE,EAAE,yBAAuB,mBAAoB,uBAAqB;AAAA,MACnE;AAAA,IACD;AAAA,EACD;AAAA,EAfoB;AAAA,EAFpB;AAAA,EAmBA,WAA0B;AACzB,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA,EAEA,SAAS,OAA4B;AACpC,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AAAA,EAEA,YAAqB;AACpB,WAAO,KAAK,SAAS,MAAM;AAAA,EAC5B;AAAA,EAEA,aAAsB;AACrB,WAAO,KAAK,SAAS,MAAM;AAAA,EAC5B;AAAA,EAEA,eAAuB;AACtB,WAAO,KAAK,IAAI,MAAM;AAAA,EACvB;AAAA,EAEA,eAAe,WAAmB,YAA2B;AAC5D,YAAQ,WAAW;AAAA,MAClB,KAAK;AACJ,aAAK,SAAS,iBAAkB;AAChC;AAAA,MACD,KAAK;AACJ,aAAK,SAAS,iBAAkB;AAChC;AAAA,MACD,KAAK;AACJ,aAAK,SAAS,iBAAkB;AAChC;AAAA,MACD;AACC,YAAI,CAAC,cAAc,KAAK,SAAS,MAAM,2BAAwB;AAC9D,eAAK,SAAS,iBAAkB;AAAA,QACjC;AACA,YAAI,cAAc,KAAK,SAAS,MAAM,2BAAwB;AAC7D,eAAK,SAAS,iBAAkB;AAAA,QACjC;AACA;AAAA,IACF;AAAA,EACD;AACD;;;AC1CA,IAAM,iBAA0C;AAAA,EAC/C,YAAY;AAAA,EACZ,SAAS;AAAA,EACT,UAAU;AAAA,EACV,oBAAoB;AAAA,EACpB,cAAc;AAAA,EACd,cAAc;AAAA,EACd,iBAAiB;AAAA,EACjB,kBAAkB;AACnB;AAEA,IAAM,yBAAyB,uBAAO,IAAI,0BAA0B;AAE7D,SAAS,mBAAmB,QAKvB;AACX,MAAI,OAAO,YAAY,GAAG;AACzB,WAAO;AAAA,EACR;AAEA,MAAI,OAAO,wBAAwB,MAAM;AACxC,WACC,OAAO,cAAc,QACrB,OAAO,cAAc,OAAO;AAAA,EAE9B;AAEA,SAAO,OAAO,eAAe;AAC9B;AAEA,SAAS,2BACR,SACA,SACA,WACmC;AACnC,SACC,WAAW,QACX,QAAQ,wBAAwB,QAChC,aAAa,KACb,QAAQ,OAAO,QAAQ;AAEzB;AAWA,IAAM,yBAAN,MAAuD;AAAA,EAUtD,YACC,OACQ,OACA,WACA,kBACP;AAHO;AACA;AACA;AAER,SAAK,cAAc,IAAI,cAAc,KAAK;AAAA,EAC3C;AAAA,EALS;AAAA,EACA;AAAA,EACA;AAAA,EAbD,WAAW,IAAI,eAAe;AAAA,EAC9B;AAAA,EACA,sBAAsB,oBAAI,IAAY;AAAA,EACtC,sBAAsB,oBAAI,IAAoB;AAAA,EAC9C,cAAc,oBAAI,IAAqB;AAAA,EACvC,uBAAuB,oBAAI,IAAY;AAAA;AAAA,EAEvC,iBAA8B,kBAAkB;AAAA,EAWxD,OAAO,MAAe,OAAqB;AAC1C,UAAM,QAAQ,KAAK,mBAAmB,sBAAsB;AAC5D,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,aAAa,KAAK;AACxB,YAAM,YAAY,KAAK;AAEvB,YAAM,UAAU,UAAU;AAC1B,YAAM,SAAU,WAAW,iBAAiB;AAC5C,UAAI,KAAK,aAAa,UAAU,GAAG;AAClC,aAAK,mBAAmB,QAAQ,OAAO;AACvC,cAAMC,SAAQ,WAAW;AACzB,cAAM,cAAc,CAAC,EAAEA,QAAO,eAAeA,QAAO;AACpD,aAAK,UAAU,iBAAiB,QAAQ,WAAW;AACnD;AAAA,MACD;AAEA,UAAI,CAAC,WAAW,KAAM;AAEtB,UAAI,CAAC,WAAW,UAAU;AACzB,mBAAW,WAAW,mBAAmB,OAAO;AAAA,MACjD;AACA,UAAI,CAAC,WAAW,WAAW;AAC1B,mBAAW,YAAY,kBAAkB;AAAA,MAC1C;AACA,UAAI,CAAC,WAAW,eAAe;AAC9B,mBAAW,gBAAgB,kBAAkB;AAAA,MAC9C;AACA,UAAI,CAAC,KAAK,oBAAoB,IAAI,WAAW,IAAI,GAAG;AACnD,aAAK,oBAAoB,IAAI,WAAW,IAAI;AAC5C,mBAAW,KAAK,gBAAgB,GAAG,IAAI;AAAA,MACxC;AACA,UAAI,CAAC,UAAU,OAAO,WAAW,eAAe;AAC/C,kBAAU,MAAM,IAAI,YAAY;AAAA,UAC/B,OAAO,WAAW;AAAA,QACnB,CAAC;AAAA,MACF;AAEA,YAAM,YAAY,QAAQ,mBAAmB;AAC7C,YAAM,eAAe,WAAW,KAAK,OAAO;AAC5C,YAAM,qBAAqB,OAAO,uBAAuB,UAAU;AACnE,YAAM,UAAU,KAAK,YAAY,aAAa,YAAY;AAAA,QACzD;AAAA,QACA,MAAM;AAAA,QACN,OAAO,QAAQ,oBAAoB;AAAA,QACnC,OAAO,KAAK;AAAA,QACZ,eAAe;AAAA,MAChB,CAAC;AACD,YAAM,aAAa,WAAW;AAC9B,YAAM,aAAa,mBAAmB;AAAA,QACrC;AAAA,QACA,WAAW,aAAa;AAAA,QACxB,YAAY,SAAS,OAAO;AAAA,QAC5B,sBAAsB,QAAQ;AAAA,MAC/B,CAAC;AACD,UAAI,2BAA2B,SAAS,SAAS,aAAa,CAAC,GAAG;AACjE,cAAM,kBAAkB,WAAW,KAAK,YAAY;AACpD,mBAAW,KAAK;AAAA,UACf;AAAA,YACC,GAAG,gBAAgB;AAAA,YACnB,GAAG,qBAAqB,YAAY,OAAO;AAAA,YAC3C,GAAG,gBAAgB;AAAA,UACpB;AAAA,UACA;AAAA,QACD;AAAA,MACD;AAEA,UAAI,sBAAsB,KAAK,oBAAoB,IAAI,WAAW,IAAI,KAAK;AAC3E,UAAI,YAAY;AACf,8BAAsB;AAAA,MACvB,OAAO;AACN,+BAAuB,QAAQ;AAAA,MAChC;AACA,WAAK,oBAAoB,IAAI,WAAW,MAAM,mBAAmB;AAEjE,YAAM,SAAuB,WAAW;AACxC,YAAM,QAAqB,WAAW;AACtC,YAAM,QAAqB,WAAW;AACtC,YAAM,eAAe,KAAK,YAAY,IAAI,WAAW,IAAI,KAAK;AAC9D,YAAM,OAAO,MAAM,aAAa;AAChC,YAAM,iBAAiB,KAAK;AAC5B,qBAAe,cAAc,MAAM,eAAgB,QAAQ,CAAC;AAC5D,qBAAe,WAAW;AAC1B,qBAAe,eAAe,MAAM,gBAAiB,CAAC,QAAQ;AAC9D,qBAAe,WAAW,MAAM;AAEhC,YAAM,QAAQ,WAAW;AACzB,YAAM,MAAqB;AAAA,QAC1B,IAAI;AAAA,QACJ,WAAW,aAAa;AAAA,QACxB;AAAA,QACA;AAAA,QACA,qBAAqB,CAAC,MAAc;AACnC;AAAA,YACC;AAAA,YACA;AAAA,YACA,EAAE,EAAE;AAAA,YACJ,EAAE,MAAM,WAAW,UAAU,GAAG;AAAA,UACjC;AACA,cAAI,YAAY;AAAA,QACjB;AAAA,MACD;AAEA,YAAM,SAAS,KAAK,SAAS,KAAK,QAAQ,gBAAgB,KAAK,KAAK;AACpE,WAAK,YAAY,IAAI,WAAW,MAAM,IAAI;AAE1C,UAAI,UAAU,KAAK;AAClB,QAAC,UAAU,IAAoB,eAAe,OAAO,OAAO,UAAU;AAAA,MACvE;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,mBAAmB,QAAgB,SAAwC;AAClF,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,UAAM,YAAY,KAAK,KAAK,KAAK,QAAQ,WAAW,OAAO,QAAQ,cAAc,IAAI;AACrF,SAAK,UAAU,eAAe,QAAQ;AAAA,MACrC;AAAA,MACA,UAAU,QAAQ,YAAY;AAAA,MAC9B,iBAAiB,QAAQ,gBAAgB,MAAM;AAAA,MAC/C,aAAa,QAAQ,gBAAgB,OAAO;AAAA,MAC5C,SAAS,QAAQ,WAAW;AAAA,IAC7B,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,YAAY,QAAQ;AACzB,SAAK,oBAAoB,MAAM;AAC/B,SAAK,oBAAoB,MAAM;AAC/B,SAAK,YAAY,MAAM;AACvB,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AAEO,IAAM,mBAAmB,eAS9B;AAAA,EACD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI,uBAAuB,IAAI,OAAO,IAAI,OAAO,IAAI,aAAa,MAAM,IAAI,gBAAgB;AAAA,EAC7F,cAAc,CAAC,SAAS;AAAA,IACvB,UAAU,MACR,IAAI,KAAiC,SAAS;AAAA,IAEhD,WAAW,MACT,IAAI,KAAiC,UAAU,KAAK;AAAA,IACtD,cAAc,MACZ,IAAI,KAAiC,aAAa,KAAK;AAAA,IACzD,mBAAmB,MAAM;AACxB,YAAM,WAAW,IAAI,QAAQ,YAAY;AACzC,YAAM,OACJ,IAAI,KAAiC,aAAa,KAAK;AACzD,aAAO,WAAW;AAAA,IACnB;AAAA,EACD;AACD,CAAC;","names":["Jumper2DTickEvent","Jumper2DState","Jumper2DEvent","input"]}
@@ -1,20 +1,18 @@
1
1
  import {
2
2
  createFirstPersonInputComponent
3
- } from "./chunk-LD5SMIB3.js";
3
+ } from "./chunk-QVCMJ4Q2.js";
4
4
  import {
5
5
  createJumpInput3D
6
- } from "./chunk-DYLH6K7B.js";
6
+ } from "./chunk-4WH2ZN7P.js";
7
7
 
8
8
  // src/lib/coordinators/first-person-shooter.coordinator.ts
9
9
  var FirstPersonShooterCoordinator = class {
10
- constructor(entity, fpsController, jumper) {
10
+ constructor(entity, fpsController) {
11
11
  this.entity = entity;
12
12
  this.fpsController = fpsController;
13
- this.jumper = jumper;
14
13
  }
15
14
  entity;
16
15
  fpsController;
17
- jumper;
18
16
  update(input) {
19
17
  if (!this.entity.$fps) {
20
18
  this.entity.$fps = createFirstPersonInputComponent();
@@ -38,7 +36,6 @@ var FirstPersonShooterCoordinator = class {
38
36
  input.moveZ,
39
37
  this.fpsController.getYaw()
40
38
  );
41
- void this.jumper.getState();
42
39
  }
43
40
  getMoveDirWorld(moveX, moveZ, yaw) {
44
41
  const sinYaw = Math.sin(yaw);
@@ -64,4 +61,4 @@ var FirstPersonShooterCoordinator = class {
64
61
  export {
65
62
  FirstPersonShooterCoordinator
66
63
  };
67
- //# sourceMappingURL=chunk-VAN4WW3F.js.map
64
+ //# sourceMappingURL=chunk-H4TJ7X4O.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/first-person-shooter.coordinator.ts"],"sourcesContent":["import {\n\tcreateFirstPersonInputComponent,\n\ttype FirstPersonInputComponent,\n} from '../behaviors/first-person/components';\nimport {\n\tcreateJumpInput3D,\n\ttype JumpInput3D,\n} from '../behaviors/jumper-3d/components';\n\nexport interface FirstPersonShooterInput {\n\tmoveX: number;\n\tmoveZ: number;\n\tlookX: number;\n\tlookY: number;\n\tsprint: boolean;\n\tjumpPressed: boolean;\n\tjumpHeld: boolean;\n\tjumpReleased: boolean;\n\tfastFall?: boolean;\n}\n\ninterface FirstPersonShooterControllerHandle {\n\tgetYaw(): number;\n}\n\nexport type FirstPersonShooterEntity = {\n\t$fps?: FirstPersonInputComponent;\n\t$jumper?: JumpInput3D;\n};\n\n/**\n * Coordinator that fans a single input snapshot out to the first-person\n * behavior (`$fps`) and the Jumper3D behavior (`$jumper`), converting local\n * move axes into the camera-relative world direction the jumper uses for\n * air control.\n */\nexport class FirstPersonShooterCoordinator {\n\tconstructor(\n\t\tprivate entity: FirstPersonShooterEntity,\n\t\tprivate fpsController: FirstPersonShooterControllerHandle,\n\t) {}\n\n\tpublic update(input: FirstPersonShooterInput): void {\n\t\tif (!this.entity.$fps) {\n\t\t\tthis.entity.$fps = createFirstPersonInputComponent();\n\t\t}\n\t\tif (!this.entity.$jumper) {\n\t\t\tthis.entity.$jumper = createJumpInput3D();\n\t\t}\n\n\t\tconst fpsInput = this.entity.$fps;\n\t\tfpsInput.moveX = input.moveX;\n\t\tfpsInput.moveZ = input.moveZ;\n\t\tfpsInput.lookX = input.lookX;\n\t\tfpsInput.lookY = input.lookY;\n\t\tfpsInput.sprint = input.sprint;\n\n\t\tconst jumperInput = this.entity.$jumper;\n\t\tjumperInput.jumpPressed = input.jumpPressed;\n\t\tjumperInput.jumpHeld = input.jumpHeld;\n\t\tjumperInput.jumpReleased = input.jumpReleased;\n\t\tjumperInput.fastFall = input.fastFall ?? false;\n\t\tjumperInput.moveDirWorld = this.getMoveDirWorld(\n\t\t\tinput.moveX,\n\t\t\tinput.moveZ,\n\t\t\tthis.fpsController.getYaw(),\n\t\t);\n\t}\n\n\tprivate getMoveDirWorld(\n\t\tmoveX: number,\n\t\tmoveZ: number,\n\t\tyaw: number,\n\t): { x: number; y: number; z: number } | undefined {\n\t\tconst sinYaw = Math.sin(yaw);\n\t\tconst cosYaw = Math.cos(yaw);\n\t\tconst rightX = cosYaw;\n\t\tconst rightZ = -sinYaw;\n\t\tconst forwardX = -sinYaw;\n\t\tconst forwardZ = -cosYaw;\n\t\tconst worldX = rightX * moveX + forwardX * -moveZ;\n\t\tconst worldZ = rightZ * moveX + forwardZ * -moveZ;\n\t\tconst length = Math.hypot(worldX, worldZ);\n\t\tif (length <= 1e-6) {\n\t\t\treturn undefined;\n\t\t}\n\n\t\treturn {\n\t\t\tx: worldX / length,\n\t\t\ty: 0,\n\t\t\tz: worldZ / length,\n\t\t};\n\t}\n}\n"],"mappings":";;;;;;;;AAoCO,IAAM,gCAAN,MAAoC;AAAA,EAC1C,YACS,QACA,eACP;AAFO;AACA;AAAA,EACN;AAAA,EAFM;AAAA,EACA;AAAA,EAGF,OAAO,OAAsC;AACnD,QAAI,CAAC,KAAK,OAAO,MAAM;AACtB,WAAK,OAAO,OAAO,gCAAgC;AAAA,IACpD;AACA,QAAI,CAAC,KAAK,OAAO,SAAS;AACzB,WAAK,OAAO,UAAU,kBAAkB;AAAA,IACzC;AAEA,UAAM,WAAW,KAAK,OAAO;AAC7B,aAAS,QAAQ,MAAM;AACvB,aAAS,QAAQ,MAAM;AACvB,aAAS,QAAQ,MAAM;AACvB,aAAS,QAAQ,MAAM;AACvB,aAAS,SAAS,MAAM;AAExB,UAAM,cAAc,KAAK,OAAO;AAChC,gBAAY,cAAc,MAAM;AAChC,gBAAY,WAAW,MAAM;AAC7B,gBAAY,eAAe,MAAM;AACjC,gBAAY,WAAW,MAAM,YAAY;AACzC,gBAAY,eAAe,KAAK;AAAA,MAC/B,MAAM;AAAA,MACN,MAAM;AAAA,MACN,KAAK,cAAc,OAAO;AAAA,IAC3B;AAAA,EACD;AAAA,EAEQ,gBACP,OACA,OACA,KACkD;AAClD,UAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,UAAM,SAAS,KAAK,IAAI,GAAG;AAC3B,UAAM,SAAS;AACf,UAAM,SAAS,CAAC;AAChB,UAAM,WAAW,CAAC;AAClB,UAAM,WAAW,CAAC;AAClB,UAAM,SAAS,SAAS,QAAQ,WAAW,CAAC;AAC5C,UAAM,SAAS,SAAS,QAAQ,WAAW,CAAC;AAC5C,UAAM,SAAS,KAAK,MAAM,QAAQ,MAAM;AACxC,QAAI,UAAU,MAAM;AACnB,aAAO;AAAA,IACR;AAEA,WAAO;AAAA,MACN,GAAG,SAAS;AAAA,MACZ,GAAG;AAAA,MACH,GAAG,SAAS;AAAA,IACb;AAAA,EACD;AACD;","names":[]}
@@ -7,7 +7,7 @@ import {
7
7
  } from "./chunk-OHCPV2NT.js";
8
8
  import {
9
9
  StageBoundaryDim
10
- } from "./chunk-Y6UJOUE5.js";
10
+ } from "./chunk-4V7TIRO2.js";
11
11
  import {
12
12
  SyncStateMachine,
13
13
  t
@@ -135,12 +135,10 @@ function createWorldBoundary3DHandle(ref) {
135
135
  };
136
136
  }
137
137
  var WorldBoundary3DSystem = class {
138
- constructor(world, wasmStage, getBehaviorLinks) {
139
- this.world = world;
138
+ constructor(wasmStage, getBehaviorLinks) {
140
139
  this.wasmStage = wasmStage;
141
140
  this.getBehaviorLinks = getBehaviorLinks;
142
141
  }
143
- world;
144
142
  wasmStage;
145
143
  getBehaviorLinks;
146
144
  attachedRuntimeSlots = /* @__PURE__ */ new Set();
@@ -197,7 +195,7 @@ var WorldBoundary3DSystem = class {
197
195
  var WorldBoundary3DBehavior = defineBehavior({
198
196
  name: "world-boundary-3d",
199
197
  defaultOptions,
200
- systemFactory: (ctx) => new WorldBoundary3DSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),
198
+ systemFactory: (ctx) => new WorldBoundary3DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),
201
199
  createHandle: createWorldBoundary3DHandle
202
200
  });
203
201
 
@@ -209,4 +207,4 @@ export {
209
207
  WorldBoundary3DFSM,
210
208
  WorldBoundary3DBehavior
211
209
  };
212
- //# sourceMappingURL=chunk-4I6E6DGV.js.map
210
+ //# sourceMappingURL=chunk-HNESASBB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/behaviors/world-boundary-3d/world-boundary-3d-fsm.ts","../src/lib/behaviors/world-boundary-3d/world-boundary-3d.descriptor.ts"],"sourcesContent":["import { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport {\n\tclampPointToBounds3D,\n\tcomputeBounds3DHits,\n\tconstrainMovementToBounds3D,\n\thasAnyBounds3DHit,\n\tinsetBounds3D,\n\ttype Bounds3DPaddingInput,\n\ttype Bounds3DPoint,\n} from '../shared/bounds-3d';\n\nexport type WorldBoundary3DHit =\n\t| 'top'\n\t| 'bottom'\n\t| 'left'\n\t| 'right'\n\t| 'back'\n\t| 'front';\n\nexport type WorldBoundary3DHits = Record<WorldBoundary3DHit, boolean>;\n\nexport interface WorldBoundary3DPosition extends Bounds3DPoint {}\n\nexport interface WorldBoundary3DBounds {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n\tback: number;\n\tfront: number;\n}\n\nexport enum WorldBoundary3DState {\n\tInside = 'inside',\n\tTouching = 'touching',\n}\n\nexport enum WorldBoundary3DEvent {\n\tEnterInside = 'enter-inside',\n\tTouchBoundary = 'touch-boundary',\n}\n\nexport function computeWorldBoundary3DHits(\n\tposition: WorldBoundary3DPosition,\n\tbounds: WorldBoundary3DBounds,\n\tpadding?: Bounds3DPaddingInput,\n): WorldBoundary3DHits {\n\treturn computeBounds3DHits(position, insetBounds3D({\n\t\tminX: bounds.left,\n\t\tmaxX: bounds.right,\n\t\tminY: bounds.bottom,\n\t\tmaxY: bounds.top,\n\t\tminZ: bounds.back,\n\t\tmaxZ: bounds.front,\n\t}, padding));\n}\n\nexport function hasAnyWorldBoundary3DHit(hits: WorldBoundary3DHits): boolean {\n\treturn hasAnyBounds3DHit(hits);\n}\n\nexport class WorldBoundary3DFSM {\n\tpublic readonly machine: SyncStateMachine<WorldBoundary3DState, WorldBoundary3DEvent, never>;\n\n\tprivate lastHits: WorldBoundary3DHits = {\n\t\ttop: false,\n\t\tbottom: false,\n\t\tleft: false,\n\t\tright: false,\n\t\tback: false,\n\t\tfront: false,\n\t};\n\tprivate lastPosition: WorldBoundary3DPosition | null = null;\n\tprivate lastClampedPosition: WorldBoundary3DPosition | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<WorldBoundary3DState, WorldBoundary3DEvent, never>(\n\t\t\tWorldBoundary3DState.Inside,\n\t\t\t[\n\t\t\t\tt(WorldBoundary3DState.Inside, WorldBoundary3DEvent.TouchBoundary, WorldBoundary3DState.Touching),\n\t\t\t\tt(WorldBoundary3DState.Touching, WorldBoundary3DEvent.EnterInside, WorldBoundary3DState.Inside),\n\t\t\t\tt(WorldBoundary3DState.Inside, WorldBoundary3DEvent.EnterInside, WorldBoundary3DState.Inside),\n\t\t\t\tt(WorldBoundary3DState.Touching, WorldBoundary3DEvent.TouchBoundary, WorldBoundary3DState.Touching),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): WorldBoundary3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tgetLastHits(): WorldBoundary3DHits {\n\t\treturn this.lastHits;\n\t}\n\n\tgetMovement(\n\t\tmoveX: number,\n\t\tmoveY: number,\n\t\tmoveZ: number,\n\t): { moveX: number; moveY: number; moveZ: number } {\n\t\treturn constrainMovementToBounds3D(this.lastHits, moveX, moveY, moveZ);\n\t}\n\n\tgetLastPosition(): WorldBoundary3DPosition | null {\n\t\treturn this.lastPosition;\n\t}\n\n\tgetLastClampedPosition(): WorldBoundary3DPosition | null {\n\t\treturn this.lastClampedPosition;\n\t}\n\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\tupdate(\n\t\tposition: WorldBoundary3DPosition,\n\t\tbounds: WorldBoundary3DBounds,\n\t\tpadding?: Bounds3DPaddingInput,\n\t): WorldBoundary3DHits {\n\t\tconst effectiveBounds = insetBounds3D({\n\t\t\tminX: bounds.left,\n\t\t\tmaxX: bounds.right,\n\t\t\tminY: bounds.bottom,\n\t\t\tmaxY: bounds.top,\n\t\t\tminZ: bounds.back,\n\t\t\tmaxZ: bounds.front,\n\t\t}, padding);\n\n\t\tconst hits = computeBounds3DHits(position, effectiveBounds);\n\t\tthis.lastHits = hits;\n\t\tthis.lastPosition = { x: position.x, y: position.y, z: position.z };\n\t\tthis.lastClampedPosition = clampPointToBounds3D(position, effectiveBounds);\n\t\tthis.lastUpdatedAtMs = Date.now();\n\n\t\tif (hasAnyWorldBoundary3DHit(hits)) {\n\t\t\tthis.dispatch(WorldBoundary3DEvent.TouchBoundary);\n\t\t} else {\n\t\t\tthis.dispatch(WorldBoundary3DEvent.EnterInside);\n\t\t}\n\n\t\treturn hits;\n\t}\n\n\tprivate dispatch(event: WorldBoundary3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","import { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport type { Bounds3DPaddingInput } from '../shared/bounds-3d';\nimport {\n\tWorldBoundary3DFSM,\n\ttype WorldBoundary3DBounds,\n\ttype WorldBoundary3DHits,\n\ttype WorldBoundary3DPosition,\n} from './world-boundary-3d-fsm';\nimport {\n\tStageBoundaryDim,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport interface WorldBoundary3DOptions {\n\tboundaries: WorldBoundary3DBounds;\n\tpadding?: Bounds3DPaddingInput;\n}\n\nexport interface WorldBoundary3DHandle {\n\tgetLastHits(): WorldBoundary3DHits | null;\n\tgetMovement(\n\t\tmoveX: number,\n\t\tmoveY: number,\n\t\tmoveZ: number,\n\t): { moveX: number; moveY: number; moveZ: number };\n\tgetLastClampedPosition(): WorldBoundary3DPosition | null;\n}\n\nconst defaultOptions: WorldBoundary3DOptions = {\n\tboundaries: { top: 0, bottom: 0, left: 0, right: 0, back: 0, front: 0 },\n\tpadding: 0,\n};\n\nconst WORLD_BOUNDARY_3D_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:world-boundary-3d',\n);\n\nfunction createWorldBoundary3DHandle(\n\tref: BehaviorRef<WorldBoundary3DOptions>,\n): WorldBoundary3DHandle {\n\treturn {\n\t\tgetLastHits: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getLastHits() ?? null;\n\t\t},\n\t\tgetMovement: (moveX: number, moveY: number, moveZ: number) => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getMovement(moveX, moveY, moveZ) ?? { moveX, moveY, moveZ };\n\t\t},\n\t\tgetLastClampedPosition: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getLastClampedPosition() ?? null;\n\t\t},\n\t};\n}\n\nclass WorldBoundary3DSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(WORLD_BOUNDARY_3D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst boundaryRef = link.ref as any;\n\t\t\tconst options = boundaryRef.options as WorldBoundary3DOptions;\n\t\t\tif (!boundaryRef.fsm) {\n\t\t\t\tboundaryRef.fsm = new WorldBoundary3DFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst query = this.wasmStage.queryWorldBoundary(handle);\n\t\t\t\tif (query) {\n\t\t\t\t\tboundaryRef.fsm.update(\n\t\t\t\t\t\t{ x: query.clamped[0], y: query.clamped[1], z: query.clamped[2] },\n\t\t\t\t\t\toptions.boundaries,\n\t\t\t\t\t\toptions.padding,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tboundaryRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y, z: pos.z },\n\t\t\t\toptions.boundaries,\n\t\t\t\toptions.padding,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: WorldBoundary3DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst padding = typeof options.padding === 'number' ? options.padding : 0;\n\t\tthis.wasmStage.attachWorldBoundary(handle, StageBoundaryDim.Three, {\n\t\t\ttop: options.boundaries.top,\n\t\t\tbottom: options.boundaries.bottom,\n\t\t\tleft: options.boundaries.left,\n\t\t\tright: options.boundaries.right,\n\t\t\tfront: options.boundaries.front,\n\t\t\tback: options.boundaries.back,\n\t\t\tpadding,\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\nexport const WorldBoundary3DBehavior = defineBehavior({\n\tname: 'world-boundary-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew WorldBoundary3DSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createWorldBoundary3DHandle,\n});\n"],"mappings":"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","names":["WorldBoundary3DState","WorldBoundary3DEvent"]}
1
+ {"version":3,"sources":["../src/lib/behaviors/world-boundary-3d/world-boundary-3d-fsm.ts","../src/lib/behaviors/world-boundary-3d/world-boundary-3d.descriptor.ts"],"sourcesContent":["import { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport {\n\tclampPointToBounds3D,\n\tcomputeBounds3DHits,\n\tconstrainMovementToBounds3D,\n\thasAnyBounds3DHit,\n\tinsetBounds3D,\n\ttype Bounds3DPaddingInput,\n\ttype Bounds3DPoint,\n} from '../shared/bounds-3d';\n\nexport type WorldBoundary3DHit =\n\t| 'top'\n\t| 'bottom'\n\t| 'left'\n\t| 'right'\n\t| 'back'\n\t| 'front';\n\nexport type WorldBoundary3DHits = Record<WorldBoundary3DHit, boolean>;\n\nexport interface WorldBoundary3DPosition extends Bounds3DPoint {}\n\nexport interface WorldBoundary3DBounds {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n\tback: number;\n\tfront: number;\n}\n\nexport enum WorldBoundary3DState {\n\tInside = 'inside',\n\tTouching = 'touching',\n}\n\nexport enum WorldBoundary3DEvent {\n\tEnterInside = 'enter-inside',\n\tTouchBoundary = 'touch-boundary',\n}\n\nexport function computeWorldBoundary3DHits(\n\tposition: WorldBoundary3DPosition,\n\tbounds: WorldBoundary3DBounds,\n\tpadding?: Bounds3DPaddingInput,\n): WorldBoundary3DHits {\n\treturn computeBounds3DHits(position, insetBounds3D({\n\t\tminX: bounds.left,\n\t\tmaxX: bounds.right,\n\t\tminY: bounds.bottom,\n\t\tmaxY: bounds.top,\n\t\tminZ: bounds.back,\n\t\tmaxZ: bounds.front,\n\t}, padding));\n}\n\nexport function hasAnyWorldBoundary3DHit(hits: WorldBoundary3DHits): boolean {\n\treturn hasAnyBounds3DHit(hits);\n}\n\nexport class WorldBoundary3DFSM {\n\tpublic readonly machine: SyncStateMachine<WorldBoundary3DState, WorldBoundary3DEvent, never>;\n\n\tprivate lastHits: WorldBoundary3DHits = {\n\t\ttop: false,\n\t\tbottom: false,\n\t\tleft: false,\n\t\tright: false,\n\t\tback: false,\n\t\tfront: false,\n\t};\n\tprivate lastPosition: WorldBoundary3DPosition | null = null;\n\tprivate lastClampedPosition: WorldBoundary3DPosition | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<WorldBoundary3DState, WorldBoundary3DEvent, never>(\n\t\t\tWorldBoundary3DState.Inside,\n\t\t\t[\n\t\t\t\tt(WorldBoundary3DState.Inside, WorldBoundary3DEvent.TouchBoundary, WorldBoundary3DState.Touching),\n\t\t\t\tt(WorldBoundary3DState.Touching, WorldBoundary3DEvent.EnterInside, WorldBoundary3DState.Inside),\n\t\t\t\tt(WorldBoundary3DState.Inside, WorldBoundary3DEvent.EnterInside, WorldBoundary3DState.Inside),\n\t\t\t\tt(WorldBoundary3DState.Touching, WorldBoundary3DEvent.TouchBoundary, WorldBoundary3DState.Touching),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): WorldBoundary3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tgetLastHits(): WorldBoundary3DHits {\n\t\treturn this.lastHits;\n\t}\n\n\tgetMovement(\n\t\tmoveX: number,\n\t\tmoveY: number,\n\t\tmoveZ: number,\n\t): { moveX: number; moveY: number; moveZ: number } {\n\t\treturn constrainMovementToBounds3D(this.lastHits, moveX, moveY, moveZ);\n\t}\n\n\tgetLastPosition(): WorldBoundary3DPosition | null {\n\t\treturn this.lastPosition;\n\t}\n\n\tgetLastClampedPosition(): WorldBoundary3DPosition | null {\n\t\treturn this.lastClampedPosition;\n\t}\n\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\tupdate(\n\t\tposition: WorldBoundary3DPosition,\n\t\tbounds: WorldBoundary3DBounds,\n\t\tpadding?: Bounds3DPaddingInput,\n\t): WorldBoundary3DHits {\n\t\tconst effectiveBounds = insetBounds3D({\n\t\t\tminX: bounds.left,\n\t\t\tmaxX: bounds.right,\n\t\t\tminY: bounds.bottom,\n\t\t\tmaxY: bounds.top,\n\t\t\tminZ: bounds.back,\n\t\t\tmaxZ: bounds.front,\n\t\t}, padding);\n\n\t\tconst hits = computeBounds3DHits(position, effectiveBounds);\n\t\tthis.lastHits = hits;\n\t\tthis.lastPosition = { x: position.x, y: position.y, z: position.z };\n\t\tthis.lastClampedPosition = clampPointToBounds3D(position, effectiveBounds);\n\t\tthis.lastUpdatedAtMs = Date.now();\n\n\t\tif (hasAnyWorldBoundary3DHit(hits)) {\n\t\t\tthis.dispatch(WorldBoundary3DEvent.TouchBoundary);\n\t\t} else {\n\t\t\tthis.dispatch(WorldBoundary3DEvent.EnterInside);\n\t\t}\n\n\t\treturn hits;\n\t}\n\n\tprivate dispatch(event: WorldBoundary3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","import { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport type { Bounds3DPaddingInput } from '../shared/bounds-3d';\nimport {\n\tWorldBoundary3DFSM,\n\ttype WorldBoundary3DBounds,\n\ttype WorldBoundary3DHits,\n\ttype WorldBoundary3DPosition,\n} from './world-boundary-3d-fsm';\nimport {\n\tStageBoundaryDim,\n\ttype WasmStageRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport interface WorldBoundary3DOptions {\n\tboundaries: WorldBoundary3DBounds;\n\tpadding?: Bounds3DPaddingInput;\n}\n\nexport interface WorldBoundary3DHandle {\n\tgetLastHits(): WorldBoundary3DHits | null;\n\tgetMovement(\n\t\tmoveX: number,\n\t\tmoveY: number,\n\t\tmoveZ: number,\n\t): { moveX: number; moveY: number; moveZ: number };\n\tgetLastClampedPosition(): WorldBoundary3DPosition | null;\n}\n\nconst defaultOptions: WorldBoundary3DOptions = {\n\tboundaries: { top: 0, bottom: 0, left: 0, right: 0, back: 0, front: 0 },\n\tpadding: 0,\n};\n\nconst WORLD_BOUNDARY_3D_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:world-boundary-3d',\n);\n\nfunction createWorldBoundary3DHandle(\n\tref: BehaviorRef<WorldBoundary3DOptions>,\n): WorldBoundary3DHandle {\n\treturn {\n\t\tgetLastHits: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getLastHits() ?? null;\n\t\t},\n\t\tgetMovement: (moveX: number, moveY: number, moveZ: number) => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getMovement(moveX, moveY, moveZ) ?? { moveX, moveY, moveZ };\n\t\t},\n\t\tgetLastClampedPosition: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary3DFSM | undefined;\n\t\t\treturn fsm?.getLastClampedPosition() ?? null;\n\t\t},\n\t};\n}\n\nclass WorldBoundary3DSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: WasmStageRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(WORLD_BOUNDARY_3D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst boundaryRef = link.ref as any;\n\t\t\tconst options = boundaryRef.options as WorldBoundary3DOptions;\n\t\t\tif (!boundaryRef.fsm) {\n\t\t\t\tboundaryRef.fsm = new WorldBoundary3DFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\t\tconst query = this.wasmStage.queryWorldBoundary(handle);\n\t\t\t\tif (query) {\n\t\t\t\t\tboundaryRef.fsm.update(\n\t\t\t\t\t\t{ x: query.clamped[0], y: query.clamped[1], z: query.clamped[2] },\n\t\t\t\t\t\toptions.boundaries,\n\t\t\t\t\t\toptions.padding,\n\t\t\t\t\t);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!gameEntity.body) continue;\n\t\t\tconst pos = gameEntity.body.translation();\n\t\t\tboundaryRef.fsm.update(\n\t\t\t\t{ x: pos.x, y: pos.y, z: pos.z },\n\t\t\t\toptions.boundaries,\n\t\t\t\toptions.padding,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: WorldBoundary3DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst padding = typeof options.padding === 'number' ? options.padding : 0;\n\t\tthis.wasmStage.attachWorldBoundary(handle, StageBoundaryDim.Three, {\n\t\t\ttop: options.boundaries.top,\n\t\t\tbottom: options.boundaries.bottom,\n\t\t\tleft: options.boundaries.left,\n\t\t\tright: options.boundaries.right,\n\t\t\tfront: options.boundaries.front,\n\t\t\tback: options.boundaries.back,\n\t\t\tpadding,\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\nexport const WorldBoundary3DBehavior = defineBehavior({\n\tname: 'world-boundary-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew WorldBoundary3DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createWorldBoundary3DHandle,\n});\n"],"mappings":"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","names":["WorldBoundary3DState","WorldBoundary3DEvent"]}
@@ -27,4 +27,4 @@ export {
27
27
  createThrusterInputComponent,
28
28
  createThrusterStateComponent
29
29
  };
30
- //# sourceMappingURL=chunk-KS72RZHX.js.map
30
+ //# sourceMappingURL=chunk-IZJFUAXB.js.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/lib/behaviors/thruster/components.ts"],"sourcesContent":["/**\n * Thruster-specific ECS Components\n * \n * These components are specific to the thruster movement system.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterMovementComponent (capability / hardware spec)\n// Defines the thrust capabilities of an entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterMovementComponent {\n\t/** Linear thrust force in Newtons (or scaled units) */\n\tlinearThrust: number;\n\t/** Angular thrust torque scalar */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n\t/** Optional angular damping override */\n\tangularDamping?: number;\n}\n\nexport function createThrusterMovementComponent(\n\tlinearThrust: number,\n\tangularThrust: number,\n\toptions?: { linearDamping?: number; angularDamping?: number }\n): ThrusterMovementComponent {\n\treturn {\n\t\tlinearThrust,\n\t\tangularThrust,\n\t\tlinearDamping: options?.linearDamping,\n\t\tangularDamping: options?.angularDamping,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterInputComponent (intent)\n// Written by: Player controller, AI controller, FSM adapter\n// Read by: ThrusterMovementBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterInputComponent {\n\t/** Forward thrust intent: 0..1 */\n\tthrust: number;\n\t/** Rotation intent: -1..1 */\n\trotate: number;\n\t/** World-space X thrust intent: -1..1 */\n\tthrustX: number;\n\t/** World-space Y thrust intent: -1..1 */\n\tthrustY: number;\n}\n\nexport function createThrusterInputComponent(): ThrusterInputComponent {\n\treturn {\n\t\tthrust: 0,\n\t\trotate: 0,\n\t\tthrustX: 0,\n\t\tthrustY: 0,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterStateComponent (optional but useful)\n// Useful for: damage, EMP, overheating, UI feedback\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterStateComponent {\n\t/** Whether the thruster is enabled */\n\tenabled: boolean;\n\t/** Current thrust after FSM/gating */\n\tcurrentThrust: number;\n}\n\nexport function createThrusterStateComponent(): ThrusterStateComponent {\n\treturn {\n\t\tenabled: true,\n\t\tcurrentThrust: 0,\n\t};\n}\n"],"mappings":";AAsBO,SAAS,gCACf,cACA,eACA,SAC4B;AAC5B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,eAAe,SAAS;AAAA,IACxB,gBAAgB,SAAS;AAAA,EAC1B;AACD;AAmBO,SAAS,+BAAuD;AACtE,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACV;AACD;AAcO,SAAS,+BAAuD;AACtE,SAAO;AAAA,IACN,SAAS;AAAA,IACT,eAAe;AAAA,EAChB;AACD;","names":[]}
1
+ {"version":3,"sources":["../src/lib/behaviors/thruster/components.ts"],"sourcesContent":["/**\n * Thruster-specific ECS Components\n * \n * These components are specific to the thruster movement system.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterMovementComponent (capability / hardware spec)\n// Defines the thrust capabilities of an entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterMovementComponent {\n\t/** Linear thrust force in Newtons (or scaled units) */\n\tlinearThrust: number;\n\t/** Angular thrust torque scalar */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n\t/** Optional angular damping override */\n\tangularDamping?: number;\n}\n\nexport function createThrusterMovementComponent(\n\tlinearThrust: number,\n\tangularThrust: number,\n\toptions?: { linearDamping?: number; angularDamping?: number }\n): ThrusterMovementComponent {\n\treturn {\n\t\tlinearThrust,\n\t\tangularThrust,\n\t\tlinearDamping: options?.linearDamping,\n\t\tangularDamping: options?.angularDamping,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterInputComponent (intent)\n// Written by: Player controller, AI controller, FSM adapter\n// Read by: ThrusterSystem\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterInputComponent {\n\t/** Forward thrust intent: 0..1 */\n\tthrust: number;\n\t/** Rotation intent: -1..1 */\n\trotate: number;\n\t/** World-space X thrust intent: -1..1 */\n\tthrustX: number;\n\t/** World-space Y thrust intent: -1..1 */\n\tthrustY: number;\n}\n\nexport function createThrusterInputComponent(): ThrusterInputComponent {\n\treturn {\n\t\tthrust: 0,\n\t\trotate: 0,\n\t\tthrustX: 0,\n\t\tthrustY: 0,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterStateComponent (optional but useful)\n// Useful for: damage, EMP, overheating, UI feedback\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterStateComponent {\n\t/** Whether the thruster is enabled */\n\tenabled: boolean;\n\t/** Current thrust after FSM/gating */\n\tcurrentThrust: number;\n}\n\nexport function createThrusterStateComponent(): ThrusterStateComponent {\n\treturn {\n\t\tenabled: true,\n\t\tcurrentThrust: 0,\n\t};\n}\n"],"mappings":";AAsBO,SAAS,gCACf,cACA,eACA,SAC4B;AAC5B,SAAO;AAAA,IACN;AAAA,IACA;AAAA,IACA,eAAe,SAAS;AAAA,IACxB,gBAAgB,SAAS;AAAA,EAC1B;AACD;AAmBO,SAAS,+BAAuD;AACtE,SAAO;AAAA,IACN,QAAQ;AAAA,IACR,QAAQ;AAAA,IACR,SAAS;AAAA,IACT,SAAS;AAAA,EACV;AACD;AAcO,SAAS,+BAAuD;AACtE,SAAO;AAAA,IACN,SAAS;AAAA,IACT,eAAe;AAAA,EAChB;AACD;","names":[]}
@@ -1,5 +1,5 @@
1
1
  // src/lib/behaviors/shared/ground-probe-3d.ts
2
- import { Cuboid } from "@dimforge/rapier3d-compat";
2
+ import { Ball, Capsule, Cuboid } from "@dimforge/rapier3d-compat";
3
3
  import {
4
4
  BoxGeometry,
5
5
  Mesh,
@@ -86,6 +86,42 @@ function extractShapeData(shapeType, internal) {
86
86
  }
87
87
 
88
88
  // src/lib/behaviors/shared/ground-probe-3d.ts
89
+ function inspectLiveColliderShape(entity) {
90
+ const shape = entity.collider?.shape;
91
+ if (!shape) return null;
92
+ if (shape instanceof Capsule) {
93
+ return {
94
+ halfBelow: shape.halfHeight + shape.radius,
95
+ halfX: shape.radius,
96
+ halfZ: shape.radius
97
+ };
98
+ }
99
+ if (shape instanceof Cuboid) {
100
+ return {
101
+ halfBelow: shape.halfExtents.y,
102
+ halfX: shape.halfExtents.x,
103
+ halfZ: shape.halfExtents.z
104
+ };
105
+ }
106
+ if (shape instanceof Ball) {
107
+ return {
108
+ halfBelow: shape.radius,
109
+ halfX: shape.radius,
110
+ halfZ: shape.radius
111
+ };
112
+ }
113
+ return null;
114
+ }
115
+ function getLiveColliderCenterOffsetY(entity) {
116
+ const collider = entity.collider;
117
+ const body = entity.body;
118
+ if (!collider || !body) return 0;
119
+ try {
120
+ return collider.translation().y - body.translation().y;
121
+ } catch {
122
+ return 0;
123
+ }
124
+ }
89
125
  var GROUND_SNAP_EPSILON = 1e-3;
90
126
  var CENTER_BOX_HALF = 0.05;
91
127
  var DEFAULT_BOX_THICKNESS = 0.05;
@@ -130,7 +166,6 @@ var GroundProbe3D = class {
130
166
  z: shapePos.z
131
167
  },
132
168
  origin: { ...shapePos },
133
- rayIndex: 0,
134
169
  colliderUuid: hit.collider?._parent?.userData?.uuid
135
170
  };
136
171
  }
@@ -195,6 +230,14 @@ function deriveDefaultBox(entity, mode) {
195
230
  if (mode === "center") {
196
231
  return { x: CENTER_BOX_HALF, y: CENTER_BOX_HALF, z: CENTER_BOX_HALF };
197
232
  }
233
+ const live = inspectLiveColliderShape(entity);
234
+ if (live) {
235
+ return {
236
+ x: Math.max(live.halfX * 0.7, CENTER_BOX_HALF),
237
+ y: DEFAULT_BOX_THICKNESS,
238
+ z: Math.max(live.halfZ * 0.7, CENTER_BOX_HALF)
239
+ };
240
+ }
198
241
  const runtimeColliderDesc = entity?.colliderDesc;
199
242
  if (runtimeColliderDesc) {
200
243
  const inspected = inspectColliderDesc(runtimeColliderDesc);
@@ -226,6 +269,11 @@ function deriveDefaultBox(entity, mode) {
226
269
  return { x: 0.35, y: DEFAULT_BOX_THICKNESS, z: 0.35 };
227
270
  }
228
271
  function getGroundAnchorOffsetY(entity) {
272
+ const live = inspectLiveColliderShape(entity);
273
+ if (live) {
274
+ const centerY2 = getLiveColliderCenterOffsetY(entity);
275
+ return live.halfBelow - centerY2;
276
+ }
229
277
  const runtimeColliderDesc = entity?.colliderDesc;
230
278
  if (runtimeColliderDesc) {
231
279
  const inspected = inspectColliderDesc(runtimeColliderDesc);
@@ -258,4 +306,4 @@ export {
258
306
  getGroundAnchorOffsetY,
259
307
  getGroundSnapTargetY
260
308
  };
261
- //# sourceMappingURL=chunk-SVLNF434.js.map
309
+ //# sourceMappingURL=chunk-JXMF4NUI.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/shared/ground-probe-3d.ts","../src/lib/physics/collider-desc-inspect.ts"],"sourcesContent":["import { Ball, Capsule, Cuboid, type Collider, type RigidBody } from '@dimforge/rapier3d-compat';\nimport {\n\tBoxGeometry,\n\tMesh,\n\tMeshBasicMaterial,\n} from 'three';\nimport type { ZylemWorld } from '../../collision/world';\nimport { inspectColliderDesc } from '../../physics/collider-desc-inspect';\n\n/**\n * Probe sizing hint.\n *\n * - `center`: prefer a tiny box that behaves close to a single downward ray\n * (used by `Jumper3DBehavior` for thin/single-point ground tests).\n * - `any`: prefer a box sized from the entity's collider footprint\n * (used by `Platformer3D` so the character \"feels\" the ground under any\n * foot, not just dead-center).\n *\n * The actual box dimensions are still configurable via `options.box`.\n */\nexport type GroundProbeMode = 'center' | 'any';\n\nexport interface GroundProbeEntity {\n\tuuid: string;\n\tbody: RigidBody | null;\n\t/** Live Rapier collider; when present, probe geometry is derived from its shape. */\n\tcollider?: Collider | null;\n}\n\n/**\n * Geometry extracted from a live Rapier collider shape: the half-extent of\n * the shape below its center (`halfBelow`) and the horizontal half-extents\n * of its footprint. First-priority source for probe sizing/anchoring — works\n * for any entity with a live collider, engine metadata or not.\n */\ninterface LiveShapeInfo {\n\thalfBelow: number;\n\thalfX: number;\n\thalfZ: number;\n}\n\nfunction inspectLiveColliderShape(entity: GroundProbeEntity): LiveShapeInfo | null {\n\tconst shape = entity.collider?.shape;\n\tif (!shape) return null;\n\n\tif (shape instanceof Capsule) {\n\t\treturn {\n\t\t\thalfBelow: shape.halfHeight + shape.radius,\n\t\t\thalfX: shape.radius,\n\t\t\thalfZ: shape.radius,\n\t\t};\n\t}\n\tif (shape instanceof Cuboid) {\n\t\treturn {\n\t\t\thalfBelow: shape.halfExtents.y,\n\t\t\thalfX: shape.halfExtents.x,\n\t\t\thalfZ: shape.halfExtents.z,\n\t\t};\n\t}\n\tif (shape instanceof Ball) {\n\t\treturn {\n\t\t\thalfBelow: shape.radius,\n\t\t\thalfX: shape.radius,\n\t\t\thalfZ: shape.radius,\n\t\t};\n\t}\n\treturn null;\n}\n\n/**\n * Vertical offset of the collider center relative to the body origin\n * (0 when either translation is unavailable).\n */\nfunction getLiveColliderCenterOffsetY(entity: GroundProbeEntity): number {\n\tconst collider = entity.collider;\n\tconst body = entity.body;\n\tif (!collider || !body) return 0;\n\ttry {\n\t\treturn collider.translation().y - body.translation().y;\n\t} catch {\n\t\treturn 0;\n\t}\n}\n\nexport interface GroundProbeOptions {\n\t/** Maximum cast distance in world units. */\n\trayLength: number;\n\t/** Selects the default box size when `box` is not explicitly provided. */\n\tmode?: GroundProbeMode;\n\t/** Render a wireframe of the cast box for debugging. Requires `scene`. */\n\tdebug?: boolean;\n\t/** Three.js scene used to host the debug wireframe mesh. */\n\tscene?: any;\n\t/**\n\t * Vertical offset added to the body translation when computing the cast\n\t * origin. Allows callers to shift the probe to e.g. just above the\n\t * capsule's bottom face.\n\t */\n\toriginYOffset?: number;\n\t/**\n\t * Half-extents of the cast cuboid. When omitted the probe derives a\n\t * sensible default from the entity's collider:\n\t * - capsule: `(r * 0.7, 0.05, r * 0.7)` — a thin slab smaller than the\n\t * capsule footprint, so side walls don't register as ground.\n\t * - cuboid: shrink the bottom face by 30% so the probe sits well inside\n\t * the collider footprint.\n\t * - `mode: 'center'`: forces a tiny `(0.05, 0.05, 0.05)` box.\n\t */\n\tbox?: { x: number; y: number; z: number };\n}\n\nexport interface GroundProbeSupportHit {\n\ttoi: number;\n\tpoint: {\n\t\tx: number;\n\t\ty: number;\n\t\tz: number;\n\t};\n\torigin: {\n\t\tx: number;\n\t\ty: number;\n\t\tz: number;\n\t};\n\tcolliderUuid?: string;\n}\n\nexport const GROUND_SNAP_EPSILON = 0.001;\n\n/**\n * Tiny box used when the caller selects `mode: 'center'` — close enough to a\n * point cast that it preserves the old single-ray behavior of `Jumper3DBehavior`.\n */\nconst CENTER_BOX_HALF = 0.05;\n\n/**\n * Default vertical thickness of the cast cuboid. The box is intentionally thin\n * vertically so the impact distance closely matches a downward ray's TOI;\n * existing snap-to-ground math (which assumes `point.y ≈ origin.y - toi`)\n * keeps working with negligible drift.\n */\nconst DEFAULT_BOX_THICKNESS = 0.05;\n\n/**\n * GroundProbe3D — single-cast cuboid probe used by 3D platformer and jumper\n * behaviors to detect the ground beneath an entity.\n *\n * Replaces the previous 5-ray probe: a single shape-cast handles edge cases\n * (corner of a platform, narrow gap) more robustly and produces a single,\n * intuitive debug visualization (a wireframe box).\n */\nexport class GroundProbe3D {\n\tprivate debugMeshes = new Map<string, Mesh>();\n\n\tconstructor(private world: ZylemWorld | null) {}\n\n\tprobeSupport(\n\t\tentity: GroundProbeEntity,\n\t\toptions: GroundProbeOptions,\n\t): GroundProbeSupportHit | null {\n\t\tif (!this.world?.world || !entity.body) return null;\n\n\t\tconst translation = entity.body.translation();\n\t\tconst originYOffset = options.originYOffset ?? 0;\n\t\tconst box = options.box ?? deriveDefaultBox(entity, options.mode ?? 'any');\n\n\t\tconst shapePos = {\n\t\t\tx: translation.x,\n\t\t\ty: translation.y + originYOffset,\n\t\t\tz: translation.z,\n\t\t};\n\t\tconst shapeRot = { x: 0, y: 0, z: 0, w: 1 };\n\t\tconst shapeVel = { x: 0, y: -1, z: 0 };\n\n\t\tconst shape = new Cuboid(box.x, box.y, box.z);\n\t\tconst hit = this.world.world.castShape(\n\t\t\tshapePos,\n\t\t\tshapeRot,\n\t\t\tshapeVel,\n\t\t\tshape,\n\t\t\toptions.rayLength,\n\t\t\ttrue,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tundefined,\n\t\t\tentity.body,\n\t\t);\n\n\t\tlet support: GroundProbeSupportHit | null = null;\n\t\tif (hit) {\n\t\t\tsupport = {\n\t\t\t\ttoi: hit.toi,\n\t\t\t\tpoint: {\n\t\t\t\t\tx: shapePos.x,\n\t\t\t\t\ty: shapePos.y - hit.toi - box.y,\n\t\t\t\t\tz: shapePos.z,\n\t\t\t\t},\n\t\t\t\torigin: { ...shapePos },\n\t\t\t\tcolliderUuid: (hit.collider as any)?._parent?.userData?.uuid,\n\t\t\t};\n\t\t}\n\n\t\tif (options.debug && options.scene) {\n\t\t\tthis.updateDebugMesh(\n\t\t\t\tentity.uuid,\n\t\t\t\tshapePos,\n\t\t\t\tbox,\n\t\t\t\toptions.rayLength,\n\t\t\t\tBoolean(support),\n\t\t\t\toptions.scene,\n\t\t\t);\n\t\t} else {\n\t\t\tthis.disposeDebugMesh(entity.uuid);\n\t\t}\n\n\t\treturn support;\n\t}\n\n\tdetect(entity: GroundProbeEntity, options: GroundProbeOptions): boolean {\n\t\treturn this.probeSupport(entity, options) != null;\n\t}\n\n\tdestroyEntity(uuid: string): void {\n\t\tthis.disposeDebugMesh(uuid);\n\t}\n\n\tdestroy(): void {\n\t\tfor (const uuid of this.debugMeshes.keys()) {\n\t\t\tthis.disposeDebugMesh(uuid);\n\t\t}\n\t\tthis.debugMeshes.clear();\n\t}\n\n\tprivate updateDebugMesh(\n\t\tuuid: string,\n\t\tshapePos: { x: number; y: number; z: number },\n\t\tbox: { x: number; y: number; z: number },\n\t\trayLength: number,\n\t\thasGround: boolean,\n\t\tscene: any,\n\t): void {\n\t\tlet mesh = this.debugMeshes.get(uuid);\n\t\tif (!mesh) {\n\t\t\tconst geometry = new BoxGeometry(2 * box.x, 2 * box.y, 2 * box.z);\n\t\t\tconst material = new MeshBasicMaterial({\n\t\t\t\twireframe: true,\n\t\t\t\tcolor: 0xff0000,\n\t\t\t});\n\t\t\tmesh = new Mesh(geometry, material);\n\t\t\tscene.add(mesh);\n\t\t\tthis.debugMeshes.set(uuid, mesh);\n\t\t}\n\n\t\t// Position the box at the cast midpoint so users can see the swept volume.\n\t\tmesh.position.set(\n\t\t\tshapePos.x,\n\t\t\tshapePos.y - rayLength * 0.5,\n\t\t\tshapePos.z,\n\t\t);\n\t\tmesh.scale.set(1, Math.max(rayLength / Math.max(2 * box.y, 1e-6), 1), 1);\n\t\t(mesh.material as MeshBasicMaterial).color.setHex(\n\t\t\thasGround ? 0x00ff00 : 0xff0000,\n\t\t);\n\t}\n\n\tprivate disposeDebugMesh(uuid: string): void {\n\t\tconst mesh = this.debugMeshes.get(uuid);\n\t\tif (!mesh) return;\n\n\t\tmesh.removeFromParent();\n\t\tmesh.geometry.dispose();\n\t\t(mesh.material as MeshBasicMaterial).dispose();\n\n\t\tthis.debugMeshes.delete(uuid);\n\t}\n}\n\nfunction deriveDefaultBox(\n\tentity: GroundProbeEntity,\n\tmode: GroundProbeMode,\n): { x: number; y: number; z: number } {\n\tif (mode === 'center') {\n\t\treturn { x: CENTER_BOX_HALF, y: CENTER_BOX_HALF, z: CENTER_BOX_HALF };\n\t}\n\n\t// First priority: derive from the live Rapier collider shape. Works for\n\t// any consumer (e.g. the web harness) without engine collider metadata.\n\tconst live = inspectLiveColliderShape(entity);\n\tif (live) {\n\t\treturn {\n\t\t\tx: Math.max(live.halfX * 0.7, CENTER_BOX_HALF),\n\t\t\ty: DEFAULT_BOX_THICKNESS,\n\t\t\tz: Math.max(live.halfZ * 0.7, CENTER_BOX_HALF),\n\t\t};\n\t}\n\n\tconst runtimeColliderDesc = (entity as any)?.colliderDesc;\n\tif (runtimeColliderDesc) {\n\t\tconst inspected = inspectColliderDesc(runtimeColliderDesc);\n\t\tif (inspected.shape === 'capsule' && inspected.dimensions.length >= 2) {\n\t\t\tconst radius = inspected.dimensions[1] ?? 0.5;\n\t\t\tconst half = Math.max(radius * 0.7, CENTER_BOX_HALF);\n\t\t\treturn { x: half, y: DEFAULT_BOX_THICKNESS, z: half };\n\t\t}\n\t\tif (inspected.shape === 'cuboid' && inspected.dimensions.length >= 3) {\n\t\t\tconst halfX = (inspected.dimensions[0] ?? 0.5) * 0.7;\n\t\t\tconst halfZ = (inspected.dimensions[2] ?? 0.5) * 0.7;\n\t\t\treturn {\n\t\t\t\tx: Math.max(halfX, CENTER_BOX_HALF),\n\t\t\t\ty: DEFAULT_BOX_THICKNESS,\n\t\t\t\tz: Math.max(halfZ, CENTER_BOX_HALF),\n\t\t\t};\n\t\t}\n\t}\n\n\tconst collisionSize =\n\t\t(entity as any)?.options?.collision?.size ??\n\t\t(entity as any)?.options?.collisionSize ??\n\t\t(entity as any)?.options?.size;\n\tif (collisionSize) {\n\t\tconst halfX = ((collisionSize.x ?? 1) / 2) * 0.7;\n\t\tconst halfZ = ((collisionSize.z ?? collisionSize.x ?? 1) / 2) * 0.7;\n\t\treturn {\n\t\t\tx: Math.max(halfX, CENTER_BOX_HALF),\n\t\t\ty: DEFAULT_BOX_THICKNESS,\n\t\t\tz: Math.max(halfZ, CENTER_BOX_HALF),\n\t\t};\n\t}\n\n\treturn { x: 0.35, y: DEFAULT_BOX_THICKNESS, z: 0.35 };\n}\n\nexport function getGroundAnchorOffsetY(entity: any): number {\n\t// First priority: live Rapier collider shape (see inspectLiveColliderShape).\n\tconst live = inspectLiveColliderShape(entity);\n\tif (live) {\n\t\tconst centerY = getLiveColliderCenterOffsetY(entity);\n\t\treturn live.halfBelow - centerY;\n\t}\n\n\tconst runtimeColliderDesc = entity?.colliderDesc;\n\tif (runtimeColliderDesc) {\n\t\tconst inspected = inspectColliderDesc(runtimeColliderDesc);\n\t\tconst centerY = inspected.translation?.[1] ?? 0;\n\t\tif (inspected.shape === 'capsule' && inspected.dimensions.length >= 2) {\n\t\t\tconst halfCylinder = inspected.dimensions[0] ?? 0;\n\t\t\tconst radius = inspected.dimensions[1] ?? 0;\n\t\t\treturn halfCylinder + radius - centerY;\n\t\t}\n\t\tif (inspected.shape === 'cuboid' && inspected.dimensions.length >= 2) {\n\t\t\tconst halfHeight = inspected.dimensions[1] ?? 0;\n\t\t\treturn halfHeight - centerY;\n\t\t}\n\t}\n\n\tconst collisionSize =\n\t\tentity?.options?.collision?.size ??\n\t\tentity?.options?.collisionSize ??\n\t\tentity?.options?.size;\n\tconst height = collisionSize?.y ?? 0;\n\tif (height <= 0) {\n\t\treturn 0;\n\t}\n\n\tconst collisionPosition =\n\t\tentity?.options?.collision?.position ??\n\t\tentity?.options?.collisionPosition;\n\tconst centerY = collisionPosition?.y ?? height / 2;\n\treturn (height / 2) - centerY;\n}\n\nexport function getGroundSnapTargetY(\n\tentity: any,\n\tsupport: GroundProbeSupportHit,\n\tepsilon: number = GROUND_SNAP_EPSILON,\n): number {\n\treturn support.point.y + getGroundAnchorOffsetY(entity) + epsilon;\n}\n","import type { ColliderDesc } from '@dimforge/rapier3d-compat';\n\n/**\n * Subset of Rapier shape kinds that game-lib introspects for sizing decisions\n * (e.g. ground probes derive their cast box from the entity collider). Mirrors\n * the public Rapier shape vocabulary, kept as string literals so callers can\n * branch without depending on Rapier's internal shape enum.\n */\nexport type ColliderShapeKind =\n\t| 'ball'\n\t| 'cuboid'\n\t| 'capsule'\n\t| 'cone'\n\t| 'cylinder'\n\t| 'heightfield'\n\t| 'convexHull'\n\t| 'trimesh';\n\n/**\n * Plain, runtime-agnostic snapshot of a {@link ColliderDesc}'s shape and\n * placement. Only contains the fields game-lib needs today; extend as new\n * call sites appear.\n *\n * - `shape` — discriminator for the underlying Rapier shape.\n * - `dimensions` — shape-specific numbers (see {@link inspectColliderDesc}).\n * - `translation` — local offset relative to the parent body, when set.\n * - `vertices` / `indices` — raw geometry payload for `convexHull` and\n * `trimesh` shapes; tagged via `__zylemShapeData` at construction time.\n */\nexport interface ColliderDescInspection {\n\tshape: ColliderShapeKind;\n\tdimensions: number[];\n\ttranslation?: [number, number, number];\n\tvertices?: number[];\n\tindices?: number[];\n}\n\n/**\n * Read the shape kind, dimensions, and local translation off a Rapier\n * {@link ColliderDesc} without going through the WASM-backed accessors that\n * cannot cross worker boundaries.\n *\n * Layout per shape (matches Rapier's primitive constructors):\n * - `ball` → `[radius]`\n * - `cuboid` → `[halfX, halfY, halfZ]`\n * - `capsule` → `[halfHeight, radius]`\n * - `cone` → `[halfHeight, radius]`\n * - `cylinder` → `[halfHeight, radius]`\n * - `heightfield` → flattened heights array\n * - `convexHull` → flattened vertex array (set via the `__zylemShapeData` tag)\n * - `trimesh` → flattened vertex array (indices ignored here)\n */\nexport function inspectColliderDesc(desc: ColliderDesc): ColliderDescInspection {\n\tconst internal = desc as any;\n\tconst customShapeData = internal.__zylemShapeData as\n\t\t| { shape: 'convexHull'; vertices: number[] }\n\t\t| { shape: 'trimesh'; vertices: number[]; indices: number[] }\n\t\t| undefined;\n\n\tif (customShapeData?.shape === 'convexHull') {\n\t\tconst result = finalize('convexHull', [], internal);\n\t\tresult.vertices = [...customShapeData.vertices];\n\t\treturn result;\n\t}\n\tif (customShapeData?.shape === 'trimesh') {\n\t\tconst result = finalize('trimesh', [], internal);\n\t\tresult.vertices = [...customShapeData.vertices];\n\t\tresult.indices = [...customShapeData.indices];\n\t\treturn result;\n\t}\n\n\tconst shapeType = internal.shape?.type ?? internal.shapeType ?? 0;\n\tconst { shape, dimensions } = extractShapeData(shapeType, internal);\n\treturn finalize(shape, dimensions, internal);\n}\n\nfunction finalize(\n\tshape: ColliderShapeKind,\n\tdimensions: number[],\n\tinternal: any,\n): ColliderDescInspection {\n\tconst result: ColliderDescInspection = { shape, dimensions };\n\tconst t = internal.translation;\n\tif (t && (t.x !== 0 || t.y !== 0 || t.z !== 0)) {\n\t\tresult.translation = [t.x, t.y, t.z];\n\t}\n\treturn result;\n}\n\n/**\n * Rapier shape type enum values (from rapier internals):\n * 0 = Ball, 1 = Cuboid, 2 = Capsule, 6 = Cone, 7 = Cylinder, 11 = HeightField\n */\nfunction extractShapeData(\n\tshapeType: number,\n\tinternal: any,\n): { shape: ColliderShapeKind; dimensions: number[] } {\n\tswitch (shapeType) {\n\t\tcase 0:\n\t\t\treturn {\n\t\t\t\tshape: 'ball',\n\t\t\t\tdimensions: [internal.shape?.radius ?? internal.halfExtents?.x ?? 1],\n\t\t\t};\n\t\tcase 1:\n\t\t\treturn {\n\t\t\t\tshape: 'cuboid',\n\t\t\t\tdimensions: [\n\t\t\t\t\tinternal.shape?.halfExtents?.x ?? internal.halfExtents?.x ?? 0.5,\n\t\t\t\t\tinternal.shape?.halfExtents?.y ?? internal.halfExtents?.y ?? 0.5,\n\t\t\t\t\tinternal.shape?.halfExtents?.z ?? internal.halfExtents?.z ?? 0.5,\n\t\t\t\t],\n\t\t\t};\n\t\tcase 2:\n\t\t\treturn {\n\t\t\t\tshape: 'capsule',\n\t\t\t\tdimensions: [\n\t\t\t\t\tinternal.shape?.halfHeight ?? 0.5,\n\t\t\t\t\tinternal.shape?.radius ?? 0.5,\n\t\t\t\t],\n\t\t\t};\n\t\tcase 6:\n\t\t\treturn {\n\t\t\t\tshape: 'cone',\n\t\t\t\tdimensions: [\n\t\t\t\t\tinternal.shape?.halfHeight ?? 1,\n\t\t\t\t\tinternal.shape?.radius ?? 1,\n\t\t\t\t],\n\t\t\t};\n\t\tcase 7:\n\t\t\treturn {\n\t\t\t\tshape: 'cylinder',\n\t\t\t\tdimensions: [\n\t\t\t\t\tinternal.shape?.halfHeight ?? 1,\n\t\t\t\t\tinternal.shape?.radius ?? 1,\n\t\t\t\t],\n\t\t\t};\n\t\tcase 11: {\n\t\t\tconst heights = internal.shape?.heights;\n\t\t\treturn {\n\t\t\t\tshape: 'heightfield',\n\t\t\t\tdimensions: heights ? Array.from(heights) : [],\n\t\t\t};\n\t\t}\n\t\tdefault:\n\t\t\treturn { shape: 'cuboid', dimensions: [0.5, 0.5, 0.5] };\n\t}\n}\n"],"mappings":"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","names":["centerY"]}