@zylem/behaviors 0.3.1 → 0.4.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (139) hide show
  1. package/dist/behavior-system-y8SGrGWy.d.ts +580 -0
  2. package/dist/{chunk-ARJWUNRE.js → chunk-27UPCP5B.js} +10 -10
  3. package/dist/{chunk-ARJWUNRE.js.map → chunk-27UPCP5B.js.map} +1 -1
  4. package/dist/{chunk-YUO6SKJD.js → chunk-4L36OPY2.js} +33 -23
  5. package/dist/chunk-4L36OPY2.js.map +1 -0
  6. package/dist/{chunk-DYLH6K7B.js → chunk-4WH2ZN7P.js} +1 -1
  7. package/dist/{chunk-DYLH6K7B.js.map → chunk-4WH2ZN7P.js.map} +1 -1
  8. package/dist/{chunk-ZPSAXX73.js → chunk-5SM5SH5O.js} +26 -26
  9. package/dist/chunk-5SM5SH5O.js.map +1 -0
  10. package/dist/{chunk-STCCNLPC.js → chunk-5XZWTFGF.js} +5 -3
  11. package/dist/chunk-5XZWTFGF.js.map +1 -0
  12. package/dist/{chunk-JAN34G6S.js → chunk-67XLJ26X.js} +152 -168
  13. package/dist/chunk-67XLJ26X.js.map +1 -0
  14. package/dist/{chunk-2CLT36VB.js → chunk-75FJIUIG.js} +22 -23
  15. package/dist/chunk-75FJIUIG.js.map +1 -0
  16. package/dist/{chunk-OYWFPIHF.js → chunk-A4CA35DQ.js} +20 -17
  17. package/dist/chunk-A4CA35DQ.js.map +1 -0
  18. package/dist/{chunk-SS7ELMJ7.js → chunk-F72WX4MH.js} +49 -33
  19. package/dist/chunk-F72WX4MH.js.map +1 -0
  20. package/dist/{chunk-RTRVBEIB.js → chunk-FTDZAKZW.js} +5 -18
  21. package/dist/chunk-FTDZAKZW.js.map +1 -0
  22. package/dist/{chunk-XIJNE67Y.js → chunk-GFARAJV2.js} +3 -5
  23. package/dist/{chunk-XIJNE67Y.js.map → chunk-GFARAJV2.js.map} +1 -1
  24. package/dist/{chunk-VAN4WW3F.js → chunk-H4TJ7X4O.js} +4 -7
  25. package/dist/chunk-H4TJ7X4O.js.map +1 -0
  26. package/dist/{chunk-KS72RZHX.js → chunk-IZJFUAXB.js} +1 -1
  27. package/dist/{chunk-KS72RZHX.js.map → chunk-IZJFUAXB.js.map} +1 -1
  28. package/dist/{chunk-SVLNF434.js → chunk-JXMF4NUI.js} +51 -3
  29. package/dist/chunk-JXMF4NUI.js.map +1 -0
  30. package/dist/chunk-KFFETUAL.js +40 -0
  31. package/dist/chunk-KFFETUAL.js.map +1 -0
  32. package/dist/chunk-LL4FLHIY.js +12 -0
  33. package/dist/chunk-LL4FLHIY.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-Y6UJOUE5.js → chunk-Q75LKHDF.js} +14 -2
  37. package/dist/chunk-Q75LKHDF.js.map +1 -0
  38. package/dist/{chunk-KOZUNIIE.js → chunk-QDCJLJUV.js} +10 -10
  39. package/dist/{chunk-KOZUNIIE.js.map → chunk-QDCJLJUV.js.map} +1 -1
  40. package/dist/{chunk-LD5SMIB3.js → chunk-QVCMJ4Q2.js} +1 -1
  41. package/dist/{chunk-LD5SMIB3.js.map → chunk-QVCMJ4Q2.js.map} +1 -1
  42. package/dist/{chunk-F3KRX2V4.js → chunk-RDHSHEZM.js} +46 -24
  43. package/dist/chunk-RDHSHEZM.js.map +1 -0
  44. package/dist/chunk-UIG7MWS2.js +518 -0
  45. package/dist/chunk-UIG7MWS2.js.map +1 -0
  46. package/dist/{chunk-6C6NXNEW.js → chunk-UL6Q5HDP.js} +25 -3
  47. package/dist/chunk-UL6Q5HDP.js.map +1 -0
  48. package/dist/{chunk-XG2YMCJW.js → chunk-VMQHNJJI.js} +6 -8
  49. package/dist/{chunk-XG2YMCJW.js.map → chunk-VMQHNJJI.js.map} +1 -1
  50. package/dist/{chunk-GCNRXAKH.js → chunk-XP3ASVGJ.js} +13 -10
  51. package/dist/chunk-XP3ASVGJ.js.map +1 -0
  52. package/dist/{chunk-ID5QJYF5.js → chunk-Y7YIAWPW.js} +11 -1
  53. package/dist/chunk-Y7YIAWPW.js.map +1 -0
  54. package/dist/{chunk-4I6E6DGV.js → chunk-Z72F2ENM.js} +6 -8
  55. package/dist/{chunk-4I6E6DGV.js.map → chunk-Z72F2ENM.js.map} +1 -1
  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 +17 -39
  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 +9 -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 +6 -5
  82. package/dist/jumper-3d.d.ts +12 -11
  83. package/dist/jumper-3d.js +7 -6
  84. package/dist/{moveable-BCRpjAQb.d.ts → moveable-Cl9h-_w7.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 +24 -0
  89. package/dist/platformer-3d.d.ts +74 -87
  90. package/dist/platformer-3d.js +5 -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-B85d5u7-.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 +7 -5
  114. package/dist/behavior-system-DYbx-x_-.d.ts +0 -307
  115. package/dist/chunk-2C3Z5GE7.js +0 -176
  116. package/dist/chunk-2C3Z5GE7.js.map +0 -1
  117. package/dist/chunk-2CLT36VB.js.map +0 -1
  118. package/dist/chunk-3PYGRHAL.js +0 -35
  119. package/dist/chunk-3PYGRHAL.js.map +0 -1
  120. package/dist/chunk-6C6NXNEW.js.map +0 -1
  121. package/dist/chunk-F3KRX2V4.js.map +0 -1
  122. package/dist/chunk-FPTWVCPN.js.map +0 -1
  123. package/dist/chunk-GCNRXAKH.js.map +0 -1
  124. package/dist/chunk-ID5QJYF5.js.map +0 -1
  125. package/dist/chunk-JAN34G6S.js.map +0 -1
  126. package/dist/chunk-K3UNLQ7W.js.map +0 -1
  127. package/dist/chunk-OYWFPIHF.js.map +0 -1
  128. package/dist/chunk-RTRVBEIB.js.map +0 -1
  129. package/dist/chunk-RTVRUNYR.js.map +0 -1
  130. package/dist/chunk-SS7ELMJ7.js.map +0 -1
  131. package/dist/chunk-STCCNLPC.js.map +0 -1
  132. package/dist/chunk-SVLNF434.js.map +0 -1
  133. package/dist/chunk-VAN4WW3F.js.map +0 -1
  134. package/dist/chunk-Y6UJOUE5.js.map +0 -1
  135. package/dist/chunk-YUO6SKJD.js.map +0 -1
  136. package/dist/chunk-ZPSAXX73.js.map +0 -1
  137. package/dist/runtime-pong.d.ts +0 -92
  138. package/dist/runtime-pong.js +0 -38
  139. /package/dist/{runtime-pong.js.map → platformer-2d.js.map} +0 -0
@@ -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":";;;;;;;;;;;;;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,OACA,WACA,kBACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EALD,uBAAuB,oBAAI,IAAY;AAAA,EAQ/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,OAAO,IAAI,aAAa,MAAM,IAAI,gBAAgB;AAC7E,CAAC;","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":"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","names":["ScreenWrapState","ScreenWrapEvent","bounds"]}
@@ -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":[]}
@@ -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"]}
@@ -0,0 +1,40 @@
1
+ import {
2
+ getBounds3DNormalFromHits
3
+ } from "./chunk-OHCPV2NT.js";
4
+ import {
5
+ updateBoundaryRicochet
6
+ } from "./chunk-LL4FLHIY.js";
7
+
8
+ // src/lib/coordinators/boundary-ricochet-3d.coordinator.ts
9
+ var BoundaryRicochet3DCoordinator = class {
10
+ constructor(entity, boundary, ricochet) {
11
+ this.entity = entity;
12
+ this.boundary = boundary;
13
+ this.ricochet = ricochet;
14
+ }
15
+ entity;
16
+ boundary;
17
+ ricochet;
18
+ update() {
19
+ return updateBoundaryRicochet({
20
+ getLastHits: () => this.boundary.getLastHits(),
21
+ hasAnyHit: (hits) => hits.left || hits.right || hits.top || hits.bottom || hits.back || hits.front,
22
+ getNormal: (hits) => getBounds3DNormalFromHits(hits),
23
+ snapToClamped: () => {
24
+ const clamped = this.boundary.getLastClampedPosition();
25
+ if (clamped && this.entity.body) {
26
+ this.entity.body.setTranslation(clamped, true);
27
+ }
28
+ },
29
+ applyRicochet: (normal) => this.ricochet.applyRicochet({
30
+ entity: this.entity,
31
+ contact: { normal }
32
+ })
33
+ });
34
+ }
35
+ };
36
+
37
+ export {
38
+ BoundaryRicochet3DCoordinator
39
+ };
40
+ //# sourceMappingURL=chunk-KFFETUAL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/boundary-ricochet-3d.coordinator.ts"],"sourcesContent":["import type { MoveableEntity } from '../capabilities/moveable';\nimport type { BaseEntityInterface } from '../types/entity-types';\nimport type { Ricochet3DHandle } from '../behaviors/ricochet-3d/ricochet-3d.descriptor';\nimport type { WorldBoundary3DHandle } from '../behaviors/world-boundary-3d/world-boundary-3d.descriptor';\nimport { getBounds3DNormalFromHits } from '../behaviors/shared/bounds-3d';\nimport { updateBoundaryRicochet } from './shared/boundary-ricochet';\n\n/** Entity shape required by the 3D boundary-ricochet coordinator. */\nexport type BoundaryRicochet3DEntity = BaseEntityInterface & MoveableEntity;\n\n/**\n * Coordinator that bridges WorldBoundary3DBehavior and Ricochet3DBehavior.\n * Snaps the body back to the boundary's clamped position before reflecting,\n * so fast entities can't tunnel outside the box between frames.\n */\nexport class BoundaryRicochet3DCoordinator {\n\tconstructor(\n\t\tprivate entity: BoundaryRicochet3DEntity,\n\t\tprivate boundary: WorldBoundary3DHandle,\n\t\tprivate ricochet: Ricochet3DHandle,\n\t) {}\n\n\tpublic update(): boolean {\n\t\treturn updateBoundaryRicochet({\n\t\t\tgetLastHits: () => this.boundary.getLastHits(),\n\t\t\thasAnyHit: (hits) =>\n\t\t\t\thits.left || hits.right || hits.top || hits.bottom || hits.back || hits.front,\n\t\t\tgetNormal: (hits) => getBounds3DNormalFromHits(hits),\n\t\t\tsnapToClamped: () => {\n\t\t\t\tconst clamped = this.boundary.getLastClampedPosition();\n\t\t\t\tif (clamped && this.entity.body) {\n\t\t\t\t\tthis.entity.body.setTranslation(clamped, true);\n\t\t\t\t}\n\t\t\t},\n\t\t\tapplyRicochet: (normal) =>\n\t\t\t\tthis.ricochet.applyRicochet({\n\t\t\t\t\tentity: this.entity,\n\t\t\t\t\tcontact: { normal },\n\t\t\t\t}),\n\t\t});\n\t}\n}\n"],"mappings":";;;;;;;;AAeO,IAAM,gCAAN,MAAoC;AAAA,EAC1C,YACS,QACA,UACA,UACP;AAHO;AACA;AACA;AAAA,EACN;AAAA,EAHM;AAAA,EACA;AAAA,EACA;AAAA,EAGF,SAAkB;AACxB,WAAO,uBAAuB;AAAA,MAC7B,aAAa,MAAM,KAAK,SAAS,YAAY;AAAA,MAC7C,WAAW,CAAC,SACX,KAAK,QAAQ,KAAK,SAAS,KAAK,OAAO,KAAK,UAAU,KAAK,QAAQ,KAAK;AAAA,MACzE,WAAW,CAAC,SAAS,0BAA0B,IAAI;AAAA,MACnD,eAAe,MAAM;AACpB,cAAM,UAAU,KAAK,SAAS,uBAAuB;AACrD,YAAI,WAAW,KAAK,OAAO,MAAM;AAChC,eAAK,OAAO,KAAK,eAAe,SAAS,IAAI;AAAA,QAC9C;AAAA,MACD;AAAA,MACA,eAAe,CAAC,WACf,KAAK,SAAS,cAAc;AAAA,QAC3B,QAAQ,KAAK;AAAA,QACb,SAAS,EAAE,OAAO;AAAA,MACnB,CAAC;AAAA,IACH,CAAC;AAAA,EACF;AACD;","names":[]}
@@ -0,0 +1,12 @@
1
+ // src/lib/coordinators/shared/boundary-ricochet.ts
2
+ function updateBoundaryRicochet(update) {
3
+ const hits = update.getLastHits();
4
+ if (!hits || !update.hasAnyHit(hits)) return false;
5
+ update.snapToClamped?.();
6
+ return update.applyRicochet(update.getNormal(hits));
7
+ }
8
+
9
+ export {
10
+ updateBoundaryRicochet
11
+ };
12
+ //# sourceMappingURL=chunk-LL4FLHIY.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/shared/boundary-ricochet.ts"],"sourcesContent":["/**\n * Shared boundary→ricochet glue used by the 2D and 3D boundary-ricochet\n * coordinators. Reads the boundary behavior's last hits, optionally snaps the\n * body back inside the bounds, and applies the ricochet reflection.\n */\nexport interface BoundaryRicochetUpdate<THits, TNormal> {\n\t/** Boundary behavior handle (or any source of last-frame hits). */\n\tgetLastHits(): THits | null;\n\t/** Whether any boundary face was hit this frame. */\n\thasAnyHit(hits: THits): boolean;\n\t/** Convert hits into a reflection normal. */\n\tgetNormal(hits: THits): TNormal;\n\t/** Optional pre-reflection snap (e.g. clamp the body back inside bounds). */\n\tsnapToClamped?(): void;\n\t/** Apply the reflection; returns true when a ricochet occurred. */\n\tapplyRicochet(normal: TNormal): boolean;\n}\n\nexport function updateBoundaryRicochet<THits, TNormal>(\n\tupdate: BoundaryRicochetUpdate<THits, TNormal>,\n): boolean {\n\tconst hits = update.getLastHits();\n\tif (!hits || !update.hasAnyHit(hits)) return false;\n\n\tupdate.snapToClamped?.();\n\treturn update.applyRicochet(update.getNormal(hits));\n}\n"],"mappings":";AAkBO,SAAS,uBACf,QACU;AACV,QAAM,OAAO,OAAO,YAAY;AAChC,MAAI,CAAC,QAAQ,CAAC,OAAO,UAAU,IAAI,EAAG,QAAO;AAE7C,SAAO,gBAAgB;AACvB,SAAO,OAAO,cAAc,OAAO,UAAU,IAAI,CAAC;AACnD;","names":[]}
@@ -1,4 +1,4 @@
1
- // src/lib/actions/capabilities/velocity-intents.ts
1
+ // src/lib/capabilities/velocity-intents.ts
2
2
  function setVelocityIntent(store, sourceId, vector, options = {}) {
3
3
  const prev = store.velocityChannels[sourceId];
4
4
  const mode = options.mode ?? prev?.mode ?? "replace";
@@ -26,4 +26,4 @@ export {
26
26
  setVelocityIntent,
27
27
  clearVelocityIntent
28
28
  };
29
- //# sourceMappingURL=chunk-RTVRUNYR.js.map
29
+ //# sourceMappingURL=chunk-OTLSUYQQ.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/capabilities/velocity-intents.ts"],"sourcesContent":["import { TransformState, VelocityIntentMode } from './transform-store';\n\nexport interface VelocityIntentOptions {\n\tmode?: VelocityIntentMode;\n\tpriority?: number;\n}\n\nexport interface VelocityIntentVector {\n\tx?: number;\n\ty?: number;\n\tz?: number;\n}\n\nexport function setVelocityIntent(\n\tstore: TransformState,\n\tsourceId: string,\n\tvector: VelocityIntentVector,\n\toptions: VelocityIntentOptions = {},\n): void {\n\tconst prev = store.velocityChannels[sourceId];\n\tconst mode = options.mode ?? prev?.mode ?? 'replace';\n\tconst priority = options.priority ?? prev?.priority ?? 0;\n\tconst isAdditiveMerge = mode === 'add' && prev?.mode === 'add';\n\n\tconst x =\n\t\tvector.x == null\n\t\t\t? prev?.x\n\t\t\t: isAdditiveMerge\n\t\t\t\t? (prev?.x ?? 0) + vector.x\n\t\t\t\t: vector.x;\n\tconst y =\n\t\tvector.y == null\n\t\t\t? prev?.y\n\t\t\t: isAdditiveMerge\n\t\t\t\t? (prev?.y ?? 0) + vector.y\n\t\t\t\t: vector.y;\n\tconst z =\n\t\tvector.z == null\n\t\t\t? prev?.z\n\t\t\t: isAdditiveMerge\n\t\t\t\t? (prev?.z ?? 0) + vector.z\n\t\t\t\t: vector.z;\n\n\tstore.velocityChannels[sourceId] = {\n\t\tx,\n\t\ty,\n\t\tz,\n\t\tmode,\n\t\tpriority,\n\t};\n\tstore.dirty.velocityChannels = true;\n}\n\nexport function clearVelocityIntent(store: TransformState, sourceId: string): void {\n\tif (!(sourceId in store.velocityChannels)) return;\n\tdelete store.velocityChannels[sourceId];\n\tstore.dirty.velocityChannels = true;\n}\n"],"mappings":";AAaO,SAAS,kBACf,OACA,UACA,QACA,UAAiC,CAAC,GAC3B;AACP,QAAM,OAAO,MAAM,iBAAiB,QAAQ;AAC5C,QAAM,OAAO,QAAQ,QAAQ,MAAM,QAAQ;AAC3C,QAAM,WAAW,QAAQ,YAAY,MAAM,YAAY;AACvD,QAAM,kBAAkB,SAAS,SAAS,MAAM,SAAS;AAEzD,QAAM,IACL,OAAO,KAAK,OACT,MAAM,IACN,mBACE,MAAM,KAAK,KAAK,OAAO,IACxB,OAAO;AACZ,QAAM,IACL,OAAO,KAAK,OACT,MAAM,IACN,mBACE,MAAM,KAAK,KAAK,OAAO,IACxB,OAAO;AACZ,QAAM,IACL,OAAO,KAAK,OACT,MAAM,IACN,mBACE,MAAM,KAAK,KAAK,OAAO,IACxB,OAAO;AAEZ,QAAM,iBAAiB,QAAQ,IAAI;AAAA,IAClC;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACD;AACA,QAAM,MAAM,mBAAmB;AAChC;AAEO,SAAS,oBAAoB,OAAuB,UAAwB;AAClF,MAAI,EAAE,YAAY,MAAM,kBAAmB;AAC3C,SAAO,MAAM,iBAAiB,QAAQ;AACtC,QAAM,MAAM,mBAAmB;AAChC;","names":[]}
@@ -32,6 +32,15 @@ var StageEventType = {
32
32
  Wrapped: 8,
33
33
  CooldownFired: 9
34
34
  };
35
+ var STAGE_INPUT_BUTTON_JUMP = 1;
36
+ var STAGE_INPUT_BUTTON_RUN = 2;
37
+ function readBehaviorSnapshot(ref) {
38
+ const backref = ref;
39
+ const wasmStage = backref.__wasmStage;
40
+ const handle = backref.__entity?.runtimeHandle ?? -1;
41
+ if (!wasmStage || handle < 0) return null;
42
+ return wasmStage.readSnapshot(handle);
43
+ }
35
44
 
36
45
  export {
37
46
  StageBodyKind,
@@ -39,6 +48,9 @@ export {
39
48
  StageRicochetDim,
40
49
  StageRicochetReflection,
41
50
  StageTopDownPlane,
42
- StageEventType
51
+ StageEventType,
52
+ STAGE_INPUT_BUTTON_JUMP,
53
+ STAGE_INPUT_BUTTON_RUN,
54
+ readBehaviorSnapshot
43
55
  };
44
- //# sourceMappingURL=chunk-Y6UJOUE5.js.map
56
+ //# sourceMappingURL=chunk-Q75LKHDF.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/runtime/wasm-stage-runtime.ts"],"sourcesContent":["export const StageBodyKind = {\n\tDynamic: 0,\n\tStatic: 1,\n\tKinematicPosition: 2,\n\tKinematicVelocity: 3,\n} as const;\n\nexport const StageBoundaryDim = {\n\tTwo: 2,\n\tThree: 3,\n} as const;\n\nexport const StageRicochetDim = {\n\tTwo: 2,\n\tThree: 3,\n} as const;\n\nexport const StageRicochetReflection = {\n\tMirror: 0,\n\tAngled: 1,\n} as const;\n\nexport const StageTopDownPlane = {\n\tXy: 0,\n\tXz: 1,\n} as const;\n\nexport const StageEventType = {\n\tCollisionStarted: 1,\n\tCollisionStopped: 2,\n\tBoundaryHit: 3,\n\tRicochet: 4,\n\tRegionEntered: 5,\n\tJumpStarted: 6,\n\tLanded: 7,\n\tWrapped: 8,\n\tCooldownFired: 9,\n} as const;\n\nexport type StageBoundaryDimValue = (typeof StageBoundaryDim)[keyof typeof StageBoundaryDim];\nexport type StageRicochetDimValue = (typeof StageRicochetDim)[keyof typeof StageRicochetDim];\nexport type StageRicochetReflectionValue =\n\t(typeof StageRicochetReflection)[keyof typeof StageRicochetReflection];\nexport type StageTopDownPlaneValue = (typeof StageTopDownPlane)[keyof typeof StageTopDownPlane];\n\n/** Position + rotation snapshot for one runtime slot. */\nexport interface StagePose {\n\tposition: [number, number, number];\n\trotation: [number, number, number, number];\n}\n\nexport interface StageWorldBoundaryOptions {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n\tfront?: number;\n\tback?: number;\n\tpadding?: number;\n}\n\nexport interface StageScreenWrapOptions {\n\twidth: number;\n\theight: number;\n\tcenterX?: number;\n\tcenterY?: number;\n\tedgeThreshold?: number;\n}\n\nexport interface StageRicochetOptions {\n\tminSpeed: number;\n\tmaxSpeed: number;\n\tspeedMultiplier: number;\n\tmaxAngleDeg: number;\n\treflectionMode: StageRicochetReflectionValue;\n}\n\nexport interface StageThrusterOptions {\n\tmaxSpeed: number;\n\tacceleration: number;\n\tturnRateRadPerSec: number;\n\tlinearDamping: number;\n}\n\nexport interface StageTopDownOptions {\n\tplane: StageTopDownPlaneValue;\n\tspeed: number;\n\tfaceMovement?: boolean;\n}\n\nexport interface StageShooter2DOptions {\n\tfireRateHz: number;\n\tmuzzleOffsetX: number;\n\tmuzzleOffsetY: number;\n\tmuzzleSpeed: number;\n}\n\nexport interface StageJumper2DOptions {\n\tjumpForce: number;\n\tmaxJumps: number;\n\tjumpBufferTime: number;\n\tcoyoteTime: number;\n\tgravity: number;\n}\n\nexport interface StageJumper3DOptions {\n\tjumpForce: number;\n\tmaxJumps: number;\n\tgravity: number;\n\t/** Coyote time in seconds (default 0.1). */\n\tcoyoteTime?: number;\n\t/** Jump buffer window in seconds (default 0.08). */\n\tjumpBufferTime?: number;\n}\n\nexport interface StagePlatformer3DOptions {\n\thalfHeight: number;\n\tradius: number;\n\twalkSpeed: number;\n\trunSpeed: number;\n\tjumpForce: number;\n\tmaxJumps: number;\n\tgravity: number;\n\t/** Coyote time in seconds (default 0.1). */\n\tcoyoteTime?: number;\n\t/** Jump buffer window in seconds (default 0.1). */\n\tjumpBufferTime?: number;\n\t/** Velocity multiplier applied on early jump release (default 0.5). */\n\tjumpCutMultiplier?: number;\n\t/** Seconds before a multi-jump becomes available (default 0.15). */\n\tmultiJumpWindow?: number;\n\t/** Max slope angle in degrees the KCC can climb (default 50). */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default 40). */\n\tslopeSlideAngle?: number;\n\t/** Autostep max height in world units; 0 disables (default 0.3). */\n\tautostepHeight?: number;\n\t/** Minimum unobstructed width for an autostep (default 0.1). */\n\tautostepMinWidth?: number;\n\t/** Snap-to-ground distance in world units; 0 disables (default 0.3). */\n\tsnapToGroundDistance?: number;\n\t/** KCC collision offset between collider and environment (default 0.05). */\n\tcharacterOffset?: number;\n}\n\nexport interface StagePlatformer2DOptions {\n\thalfHeight: number;\n\tradius: number;\n\twalkSpeed: number;\n\trunSpeed: number;\n\tjumpForce: number;\n\tmaxJumps: number;\n\tgravity: number;\n\t/** Coyote time in seconds (default 0.1). */\n\tcoyoteTime?: number;\n\t/** Jump buffer window in seconds (default 0.1). */\n\tjumpBufferTime?: number;\n\t/** Velocity multiplier applied on early jump release (default 0.5). */\n\tjumpCutMultiplier?: number;\n\t/** Seconds before a multi-jump becomes available (default 0.15). */\n\tmultiJumpWindow?: number;\n\t/** Max slope angle in degrees the KCC can climb (default 50). */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default 40). */\n\tslopeSlideAngle?: number;\n\t/** Autostep max height in world units; 0 disables (default 0.25). */\n\tautostepHeight?: number;\n\t/** Minimum unobstructed width for an autostep (default 0.05). */\n\tautostepMinWidth?: number;\n\t/** Snap-to-ground distance in world units; 0 disables (default 0.2). */\n\tsnapToGroundDistance?: number;\n\t/** KCC collision offset between collider and environment (default 0.05). */\n\tcharacterOffset?: number;\n}\n\nexport interface StageFirstPersonOptions {\n\twalkSpeed: number;\n\trunSpeed: number;\n\teyeHeight: number;\n\tlookSensitivity: number;\n}\n\nexport interface StageFirstPersonInput {\n\tmoveX: number;\n\tmoveZ: number;\n\tlookYawDelta: number;\n\tlookPitchDelta: number;\n\trun: boolean;\n}\n\n/** Button bits for the batched input buffer's bitmask field. */\nexport const STAGE_INPUT_BUTTON_JUMP = 1;\nexport const STAGE_INPUT_BUTTON_RUN = 2;\n\n/**\n * Decoded per-slot behavior snapshot written by the runtime after every\n * step: movement fields come from whichever character behavior the entity\n * has (platformer 3D/2D, jumper 3D/2D); first-person fills yaw/pitch.\n * Values reflect the end of the previous `step()` (one-frame read lag).\n */\nexport interface StageBehaviorSnapshot {\n\tgrounded: boolean;\n\tfsmState: number;\n\tjumpCount: number;\n\tverticalVelocity: number;\n\tspeed: number;\n\tyaw: number;\n\tpitch: number;\n}\n\n/**\n * Wasm fast-path backrefs a behavior system stores on its `BehaviorRef` every\n * frame so descriptor handles can read the runtime snapshot buffer instead of\n * a duplicate TS FSM (or hardcoded defaults).\n */\nexport interface WasmSnapshotBackref {\n\t__wasmStage?: WasmStageRuntime | null;\n\t__entity?: { runtimeHandle?: number } | null;\n}\n\n/**\n * Read the behavior snapshot for the entity a `BehaviorRef` is linked to.\n * Returns `null` off the wasm fast path (no stage runtime / no\n * `runtimeHandle` / no snapshot yet), in which case handles should fall back\n * to their TS FSM.\n */\nexport function readBehaviorSnapshot(ref: object): StageBehaviorSnapshot | null {\n\tconst backref = ref as WasmSnapshotBackref;\n\tconst wasmStage = backref.__wasmStage;\n\tconst handle = backref.__entity?.runtimeHandle ?? -1;\n\tif (!wasmStage || handle < 0) return null;\n\treturn wasmStage.readSnapshot(handle);\n}\n\n/**\n * Typed FFI surface of the wasm unified Stage runtime that behaviors rely on.\n *\n * The concrete implementation lives in `@zylem/game-lib`\n * (`wasm-stage-runtime.ts`); this contract mirrors the slice of its methods\n * that behavior systems call so descriptors stay runtime-agnostic.\n */\nexport interface WasmStageRuntime {\n\tgetPose(handle: number, out?: StagePose): StagePose | null;\n\n\t// ─── Batched input / snapshot buffers (preferred fast path) ─────────\n\t// Field-scoped writes into the slot-indexed input buffer, consumed by\n\t// the runtime in one pass at the start of each step. Behaviors composed\n\t// on one entity each write only their part of the slot.\n\twriteInputAxes(handle: number, axisA: number, axisB: number): boolean;\n\twriteInputLook(handle: number, yawDelta: number, pitchDelta: number): boolean;\n\twriteInputButton(handle: number, buttonMask: number, pressed: boolean): boolean;\n\t/** Read the per-slot behavior snapshot written at the end of the previous step. */\n\treadSnapshot(handle: number, out?: StageBehaviorSnapshot): StageBehaviorSnapshot | null;\n\n\tattachThruster(handle: number, opts: StageThrusterOptions): boolean;\n\tsetThrusterInput(handle: number, thrust: number, rotate: number): void;\n\n\tattachTopDown(handle: number, opts: StageTopDownOptions): boolean;\n\tsetTopDownInput(handle: number, moveX: number, moveY: number): void;\n\n\tattachShooter2D(handle: number, opts: StageShooter2DOptions): boolean;\n\n\tattachScreenWrap(handle: number, opts: StageScreenWrapOptions): boolean;\n\tqueryScreenWrap(handle: number): { fsm: number; lastWrappedAxes: number } | null;\n\n\tattachWorldBoundary(\n\t\thandle: number,\n\t\tdim: StageBoundaryDimValue,\n\t\topts: StageWorldBoundaryOptions,\n\t): boolean;\n\tqueryWorldBoundary(handle: number): { hits: number; clamped: [number, number, number] } | null;\n\n\tattachRicochet(handle: number, dim: StageRicochetDimValue, opts: StageRicochetOptions): boolean;\n\n\tattachPlatformer3D(handle: number, opts: StagePlatformer3DOptions): boolean;\n\tsetPlatformer3DInputAxes(handle: number, moveX: number, moveZ: number): void;\n\tsetPlatformer3DInputButtons(handle: number, jump: boolean, run: boolean): void;\n\n\tattachPlatformer2D(handle: number, opts: StagePlatformer2DOptions): boolean;\n\tsetPlatformer2DInputAxes(handle: number, moveX: number): void;\n\tsetPlatformer2DInputButtons(handle: number, jump: boolean, run: boolean): void;\n\n\tattachFirstPerson(handle: number, opts: StageFirstPersonOptions): boolean;\n\tsetFirstPersonInput(handle: number, input: StageFirstPersonInput): void;\n\n\tattachJumper2D(handle: number, opts: StageJumper2DOptions): boolean;\n\tsetJumper2DInput(handle: number, jumpPressed: boolean): void;\n\n\tattachJumper3D(handle: number, opts: StageJumper3DOptions): boolean;\n\tsetJumper3DInput(handle: number, jumpPressed: boolean): void;\n}\n\n/** Default options used when constructing a wasm-backed Stage runtime. */\nexport interface WasmStageRuntimeOptions {\n\tinitialCapacity?: number;\n\tgravity?: readonly [number, number, number];\n\timports?: WebAssembly.Imports;\n}\n"],"mappings":";AAAO,IAAM,gBAAgB;AAAA,EAC5B,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,mBAAmB;AAAA,EACnB,mBAAmB;AACpB;AAEO,IAAM,mBAAmB;AAAA,EAC/B,KAAK;AAAA,EACL,OAAO;AACR;AAEO,IAAM,mBAAmB;AAAA,EAC/B,KAAK;AAAA,EACL,OAAO;AACR;AAEO,IAAM,0BAA0B;AAAA,EACtC,QAAQ;AAAA,EACR,QAAQ;AACT;AAEO,IAAM,oBAAoB;AAAA,EAChC,IAAI;AAAA,EACJ,IAAI;AACL;AAEO,IAAM,iBAAiB;AAAA,EAC7B,kBAAkB;AAAA,EAClB,kBAAkB;AAAA,EAClB,aAAa;AAAA,EACb,UAAU;AAAA,EACV,eAAe;AAAA,EACf,aAAa;AAAA,EACb,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,eAAe;AAChB;AA0JO,IAAM,0BAA0B;AAChC,IAAM,yBAAyB;AAkC/B,SAAS,qBAAqB,KAA2C;AAC/E,QAAM,UAAU;AAChB,QAAM,YAAY,QAAQ;AAC1B,QAAM,SAAS,QAAQ,UAAU,iBAAiB;AAClD,MAAI,CAAC,aAAa,SAAS,EAAG,QAAO;AACrC,SAAO,UAAU,aAAa,MAAM;AACrC;","names":[]}
@@ -1,11 +1,11 @@
1
- import {
2
- StageRicochetDim,
3
- StageRicochetReflection
4
- } from "./chunk-Y6UJOUE5.js";
5
1
  import {
6
2
  SyncStateMachine,
7
3
  t
8
4
  } from "./chunk-4URF65BR.js";
5
+ import {
6
+ StageRicochetDim,
7
+ StageRicochetReflection
8
+ } from "./chunk-Q75LKHDF.js";
9
9
  import {
10
10
  defineBehavior
11
11
  } from "./chunk-EZNCCO4L.js";
@@ -312,7 +312,9 @@ function createRicochet2DHandle(ref) {
312
312
  entity.transformStore.velocity.x = result.velocity.x;
313
313
  entity.transformStore.velocity.y = result.velocity.y;
314
314
  entity.transformStore.velocity.z = result.velocity.z ?? 0;
315
- entity.transformStore.dirty.velocity = true;
315
+ entity.transformStore.dirty.velocityX = true;
316
+ entity.transformStore.dirty.velocityY = true;
317
+ entity.transformStore.dirty.velocityZ = true;
316
318
  }
317
319
  return true;
318
320
  },
@@ -338,12 +340,10 @@ function createRicochet2DHandle(ref) {
338
340
  };
339
341
  }
340
342
  var Ricochet2DSystem = class {
341
- constructor(world, wasmStage, getBehaviorLinks) {
342
- this.world = world;
343
+ constructor(wasmStage, getBehaviorLinks) {
343
344
  this.wasmStage = wasmStage;
344
345
  this.getBehaviorLinks = getBehaviorLinks;
345
346
  }
346
- world;
347
347
  wasmStage;
348
348
  getBehaviorLinks;
349
349
  elapsedMs = 0;
@@ -400,7 +400,7 @@ var Ricochet2DSystem = class {
400
400
  var Ricochet2DBehavior = defineBehavior({
401
401
  name: "ricochet-2d",
402
402
  defaultOptions,
403
- systemFactory: (ctx) => new Ricochet2DSystem(ctx.world, ctx.wasmStage ?? null, ctx.getBehaviorLinks),
403
+ systemFactory: (ctx) => new Ricochet2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),
404
404
  createHandle: createRicochet2DHandle
405
405
  });
406
406
 
@@ -410,4 +410,4 @@ export {
410
410
  Ricochet2DFSM,
411
411
  Ricochet2DBehavior
412
412
  };
413
- //# sourceMappingURL=chunk-KOZUNIIE.js.map
413
+ //# sourceMappingURL=chunk-QDCJLJUV.js.map