@zylem/behaviors 0.5.8 → 0.8.8

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 (162) hide show
  1. package/dist/{behavior-system-HEt67el5.d.ts → behavior-system-DkFAwLb_.d.ts} +6 -0
  2. package/dist/{chunk-EIWQM6U3.js → chunk-2HKY6WF7.js} +0 -1
  3. package/dist/{chunk-IZJFUAXB.js → chunk-2HLZBWGD.js} +0 -1
  4. package/dist/{chunk-LL4FLHIY.js → chunk-2YF7J3H4.js} +0 -1
  5. package/dist/{chunk-SGHIGTVZ.js → chunk-3X3CL24Z.js} +0 -1
  6. package/dist/{chunk-HZMY3TT4.js → chunk-4DVDFCVY.js} +0 -1
  7. package/dist/{chunk-QVCMJ4Q2.js → chunk-4EWASZHB.js} +0 -1
  8. package/dist/{chunk-S5HFWDGM.js → chunk-55CFXXZH.js} +3 -4
  9. package/dist/{chunk-IRMJTAIK.js → chunk-57GLKGEX.js} +3 -4
  10. package/dist/{chunk-2PAHQZZD.js → chunk-7CE6OHLW.js} +43 -17
  11. package/dist/{chunk-WQUPSOFV.js → chunk-7GGR26YA.js} +3 -4
  12. package/dist/{chunk-3QUECYMT.js → chunk-7LHXAEOZ.js} +4 -5
  13. package/dist/{chunk-PLIXVF7B.js → chunk-7TIIRJKF.js} +3 -4
  14. package/dist/{chunk-DWVLGXMF.js → chunk-CB3C7CRD.js} +4 -5
  15. package/dist/{chunk-DGDNFX43.js → chunk-CTSYM56P.js} +5 -6
  16. package/dist/{chunk-NW4SE5XW.js → chunk-DD4U42FG.js} +2 -3
  17. package/dist/{chunk-BAXJ7XHJ.js → chunk-E2MOFIDH.js} +3 -4
  18. package/dist/{chunk-WOMHL23C.js → chunk-ERS6SNA5.js} +1 -2
  19. package/dist/{chunk-4WH2ZN7P.js → chunk-F7E23E26.js} +0 -1
  20. package/dist/{chunk-4GW6DCYF.js → chunk-GZ3DQIIZ.js} +0 -1
  21. package/dist/{chunk-5WRI5ZAA.js → chunk-JSBRDJBE.js} +0 -1
  22. package/dist/{chunk-5A5AYP2C.js → chunk-K6CWKFHL.js} +0 -1
  23. package/dist/{chunk-52FJIDGL.js → chunk-LAN7U3AK.js} +1 -2
  24. package/dist/{chunk-GL5JGXPR.js → chunk-LLDLVVG4.js} +4 -5
  25. package/dist/{chunk-M3QDKKDF.js → chunk-LWM346FW.js} +0 -1
  26. package/dist/{chunk-JACX7ZMJ.js → chunk-M25QJBDN.js} +4 -5
  27. package/dist/{chunk-GLXEJD2P.js → chunk-NDJO56BB.js} +3 -4
  28. package/dist/{chunk-AS3HR7AS.js → chunk-OBF6BIA2.js} +1 -2
  29. package/dist/{chunk-KIYUUSPK.js → chunk-OSPQFWQF.js} +2 -3
  30. package/dist/{chunk-ZOYA3TZG.js → chunk-OU2SW4U5.js} +3 -4
  31. package/dist/{chunk-H4TJ7X4O.js → chunk-QU6GQ657.js} +2 -3
  32. package/dist/{chunk-KFFETUAL.js → chunk-RR3XNDKG.js} +2 -3
  33. package/dist/{chunk-JVADRLWX.js → chunk-SHZZ2QRV.js} +2 -3
  34. package/dist/{chunk-D7N6SC4D.js → chunk-TK3AXXXI.js} +3 -4
  35. package/dist/{chunk-OPZ2LVS2.js → chunk-TZQQYJYK.js} +0 -1
  36. package/dist/{chunk-USFIJUDN.js → chunk-UF7QPYUX.js} +0 -1
  37. package/dist/{chunk-NQPYMUDF.js → chunk-UKGBQTHL.js} +3 -4
  38. package/dist/{chunk-5XPPLTC7.js → chunk-UUMH3DC3.js} +1 -2
  39. package/dist/{chunk-TBUKJKY6.js → chunk-VJ3UA6OD.js} +1 -2
  40. package/dist/{chunk-OHCPV2NT.js → chunk-WHKPWIQO.js} +0 -1
  41. package/dist/{chunk-EZNCCO4L.js → chunk-YDLO5M7S.js} +0 -1
  42. package/dist/{components-JZQoO2sU.d.ts → components--RTwuT4S.d.ts} +1 -1
  43. package/dist/cooldown.d.ts +1 -1
  44. package/dist/cooldown.js +3 -4
  45. package/dist/coordinators/boundary-ricochet-3d.d.ts +2 -2
  46. package/dist/coordinators/boundary-ricochet-3d.js +4 -5
  47. package/dist/coordinators/boundary-ricochet.d.ts +2 -2
  48. package/dist/coordinators/boundary-ricochet.js +4 -5
  49. package/dist/coordinators/first-person-shooter.js +4 -5
  50. package/dist/coordinators/multidirectional-space-shooter.d.ts +1 -1
  51. package/dist/coordinators/multidirectional-space-shooter.js +6 -7
  52. package/dist/coordinators/top-down-shooter.d.ts +1 -1
  53. package/dist/coordinators/top-down-shooter.js +6 -7
  54. package/dist/core.d.ts +4 -4
  55. package/dist/core.js +6 -7
  56. package/dist/destructible-3d.d.ts +1 -1
  57. package/dist/destructible-3d.js +4 -5
  58. package/dist/destructible-prebake.js +2 -3
  59. package/dist/first-person.d.ts +1 -1
  60. package/dist/first-person.js +7 -8
  61. package/dist/index.d.ts +3 -3
  62. package/dist/index.js +40 -41
  63. package/dist/jumper-2d.d.ts +1 -1
  64. package/dist/jumper-2d.js +5 -6
  65. package/dist/jumper-3d.d.ts +1 -1
  66. package/dist/jumper-3d.js +6 -7
  67. package/dist/{moveable-BdFmI3s1.d.ts → moveable-CFOHmblC.d.ts} +1 -1
  68. package/dist/particle-emitter/presets.js +2 -3
  69. package/dist/particle-emitter.d.ts +1 -1
  70. package/dist/particle-emitter.js +5 -6
  71. package/dist/platformer-2d.d.ts +1 -1
  72. package/dist/platformer-2d.js +5 -6
  73. package/dist/platformer-3d.d.ts +1 -1
  74. package/dist/platformer-3d.js +5 -6
  75. package/dist/ricochet-2d.d.ts +1 -1
  76. package/dist/ricochet-2d.js +5 -6
  77. package/dist/ricochet-3d.d.ts +1 -1
  78. package/dist/ricochet-3d.js +5 -6
  79. package/dist/runtime-2d.d.ts +1 -1
  80. package/dist/runtime-2d.js +2 -3
  81. package/dist/screen-visibility.d.ts +1 -1
  82. package/dist/screen-visibility.js +3 -4
  83. package/dist/screen-wrap.d.ts +1 -1
  84. package/dist/screen-wrap.js +5 -6
  85. package/dist/shooter-2d.d.ts +1 -1
  86. package/dist/shooter-2d.js +5 -6
  87. package/dist/thruster.d.ts +1 -1
  88. package/dist/thruster.js +5 -6
  89. package/dist/top-down-movement.d.ts +1 -1
  90. package/dist/top-down-movement.js +7 -8
  91. package/dist/world-boundary-2d.d.ts +1 -1
  92. package/dist/world-boundary-2d.js +6 -7
  93. package/dist/world-boundary-3d.d.ts +1 -1
  94. package/dist/world-boundary-3d.js +6 -7
  95. package/package.json +158 -161
  96. package/dist/chunk-2PAHQZZD.js.map +0 -1
  97. package/dist/chunk-3QUECYMT.js.map +0 -1
  98. package/dist/chunk-4GW6DCYF.js.map +0 -1
  99. package/dist/chunk-4WH2ZN7P.js.map +0 -1
  100. package/dist/chunk-52FJIDGL.js.map +0 -1
  101. package/dist/chunk-5A5AYP2C.js.map +0 -1
  102. package/dist/chunk-5WRI5ZAA.js.map +0 -1
  103. package/dist/chunk-5XPPLTC7.js.map +0 -1
  104. package/dist/chunk-AS3HR7AS.js.map +0 -1
  105. package/dist/chunk-BAXJ7XHJ.js.map +0 -1
  106. package/dist/chunk-D7N6SC4D.js.map +0 -1
  107. package/dist/chunk-DGDNFX43.js.map +0 -1
  108. package/dist/chunk-DWVLGXMF.js.map +0 -1
  109. package/dist/chunk-EIWQM6U3.js.map +0 -1
  110. package/dist/chunk-EZNCCO4L.js.map +0 -1
  111. package/dist/chunk-GL5JGXPR.js.map +0 -1
  112. package/dist/chunk-GLXEJD2P.js.map +0 -1
  113. package/dist/chunk-H4TJ7X4O.js.map +0 -1
  114. package/dist/chunk-HZMY3TT4.js.map +0 -1
  115. package/dist/chunk-IRMJTAIK.js.map +0 -1
  116. package/dist/chunk-IZJFUAXB.js.map +0 -1
  117. package/dist/chunk-JACX7ZMJ.js.map +0 -1
  118. package/dist/chunk-JVADRLWX.js.map +0 -1
  119. package/dist/chunk-KFFETUAL.js.map +0 -1
  120. package/dist/chunk-KIYUUSPK.js.map +0 -1
  121. package/dist/chunk-LL4FLHIY.js.map +0 -1
  122. package/dist/chunk-M3QDKKDF.js.map +0 -1
  123. package/dist/chunk-NQPYMUDF.js.map +0 -1
  124. package/dist/chunk-NW4SE5XW.js.map +0 -1
  125. package/dist/chunk-OHCPV2NT.js.map +0 -1
  126. package/dist/chunk-OPZ2LVS2.js.map +0 -1
  127. package/dist/chunk-PLIXVF7B.js.map +0 -1
  128. package/dist/chunk-QVCMJ4Q2.js.map +0 -1
  129. package/dist/chunk-S5HFWDGM.js.map +0 -1
  130. package/dist/chunk-SGHIGTVZ.js.map +0 -1
  131. package/dist/chunk-TBUKJKY6.js.map +0 -1
  132. package/dist/chunk-USFIJUDN.js.map +0 -1
  133. package/dist/chunk-WOMHL23C.js.map +0 -1
  134. package/dist/chunk-WQUPSOFV.js.map +0 -1
  135. package/dist/chunk-ZOYA3TZG.js.map +0 -1
  136. package/dist/cooldown.js.map +0 -1
  137. package/dist/coordinators/boundary-ricochet-3d.js.map +0 -1
  138. package/dist/coordinators/boundary-ricochet.js.map +0 -1
  139. package/dist/coordinators/first-person-shooter.js.map +0 -1
  140. package/dist/coordinators/multidirectional-space-shooter.js.map +0 -1
  141. package/dist/coordinators/top-down-shooter.js.map +0 -1
  142. package/dist/core.js.map +0 -1
  143. package/dist/destructible-3d.js.map +0 -1
  144. package/dist/destructible-prebake.js.map +0 -1
  145. package/dist/first-person.js.map +0 -1
  146. package/dist/index.js.map +0 -1
  147. package/dist/jumper-2d.js.map +0 -1
  148. package/dist/jumper-3d.js.map +0 -1
  149. package/dist/particle-emitter/presets.js.map +0 -1
  150. package/dist/particle-emitter.js.map +0 -1
  151. package/dist/platformer-2d.js.map +0 -1
  152. package/dist/platformer-3d.js.map +0 -1
  153. package/dist/ricochet-2d.js.map +0 -1
  154. package/dist/ricochet-3d.js.map +0 -1
  155. package/dist/runtime-2d.js.map +0 -1
  156. package/dist/screen-visibility.js.map +0 -1
  157. package/dist/screen-wrap.js.map +0 -1
  158. package/dist/shooter-2d.js.map +0 -1
  159. package/dist/thruster.js.map +0 -1
  160. package/dist/top-down-movement.js.map +0 -1
  161. package/dist/world-boundary-2d.js.map +0 -1
  162. package/dist/world-boundary-3d.js.map +0 -1
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../node_modules/.pnpm/typescript-fsm@1.6.0/node_modules/typescript-fsm/src/stateMachine.ts","../src/lib/core/utility/sync-state-machine.ts"],"sourcesContent":[null,"import {\n\tStateMachine as BaseStateMachine,\n\ttype ILogger,\n\ttype ITransition,\n\ttype SyncCallback,\n} from 'typescript-fsm';\n\nexport { t } from 'typescript-fsm';\nexport type { ILogger, ITransition, SyncCallback };\n\n/**\n * Local wrapper around typescript-fsm's SyncStateMachine.\n *\n * typescript-fsm@1.6.0 incorrectly reports callback-less transitions as\n * unhandled even after moving to the next state, which causes noisy\n * `No transition...` console errors for valid FSM dispatches.\n */\nexport class SyncStateMachine<\n\tSTATE extends string | number | symbol,\n\tEVENT extends string | number | symbol,\n\tCALLBACK extends Record<EVENT, SyncCallback> = Record<EVENT, SyncCallback>,\n> extends BaseStateMachine<STATE, EVENT, CALLBACK> {\n\tconstructor(\n\t\tinit: STATE,\n\t\ttransitions: ITransition<STATE, EVENT, CALLBACK[EVENT]>[] = [],\n\t\tlogger: ILogger = console,\n\t) {\n\t\tsuper(init, transitions, logger);\n\t}\n\n\tdispatch<E extends EVENT>(_event: E, ..._args: unknown[]): Promise<void> {\n\t\tthrow new Error('SyncStateMachine does not support async dispatch.');\n\t}\n\n\tsyncDispatch<E extends EVENT>(event: E, ...args: unknown[]): boolean {\n\t\tconst found = this.transitions.some((transition) => {\n\t\t\tif (transition.fromState !== this._current || transition.event !== event) {\n\t\t\t\treturn false;\n\t\t\t}\n\n\t\t\tconst current = this._current;\n\t\t\tthis._current = transition.toState;\n\n\t\t\tif (!transition.cb) {\n\t\t\t\treturn true;\n\t\t\t}\n\n\t\t\ttry {\n\t\t\t\ttransition.cb(...args);\n\t\t\t\treturn true;\n\t\t\t} catch (error) {\n\t\t\t\tthis._current = current;\n\t\t\t\tthis.logger.error('Exception in callback', error);\n\t\t\t\tthrow error;\n\t\t\t}\n\t\t});\n\n\t\tif (!found) {\n\t\t\tconst errorMessage = this.formatErr(this._current, event);\n\t\t\tthis.logger.error(errorMessage);\n\t\t}\n\n\t\treturn found;\n\t}\n}\n"],"mappings":"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","names":["t","SyncStateMachine","import_typescript_fsm","BaseStateMachine"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/thruster/thruster-fsm.ts","../src/lib/behaviors/thruster/thruster.descriptor.ts"],"sourcesContent":["/**\n * ThrusterFSM\n * \n * State machine controller for thruster behavior.\n * FSM does NOT touch physics or ThrusterSystem - it only writes ThrusterInputComponent.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { ThrusterInputComponent } from './components';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterState {\n\tIdle = 'idle',\n\tActive = 'active',\n\tBoosting = 'boosting',\n\tDisabled = 'disabled',\n\tDocked = 'docked',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ThrusterEvent {\n\tActivate = 'activate',\n\tDeactivate = 'deactivate',\n\tBoost = 'boost',\n\tEndBoost = 'endBoost',\n\tDisable = 'disable',\n\tEnable = 'enable',\n\tDock = 'dock',\n\tUndock = 'undock',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Context Object\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterFSMContext {\n\tinput: ThrusterInputComponent;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Player Input (raw input from controller/keyboard)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface PlayerInput {\n\tthrust: number;\n\trotate: number;\n\tthrustX?: number;\n\tthrustY?: number;\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterFSM Controller\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ThrusterFSM {\n\tmachine: SyncStateMachine<ThrusterState, ThrusterEvent, never>;\n\n\tconstructor(private ctx: ThrusterFSMContext) {\n\t\tthis.machine = new SyncStateMachine<ThrusterState, ThrusterEvent, never>(\n\t\t\tThrusterState.Idle,\n\t\t\t[\n\t\t\t\t// Core transitions\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Boost, ThrusterState.Boosting),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Dock, ThrusterState.Docked),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.EndBoost, ThrusterState.Active),\n\t\t\t\tt(ThrusterState.Boosting, ThrusterEvent.Disable, ThrusterState.Disabled),\n\t\t\t\tt(ThrusterState.Disabled, ThrusterEvent.Enable, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Docked, ThrusterEvent.Undock, ThrusterState.Idle),\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ThrusterState.Idle, ThrusterEvent.Deactivate, ThrusterState.Idle),\n\t\t\t\tt(ThrusterState.Active, ThrusterEvent.Activate, ThrusterState.Active),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Get current state\n\t */\n\tgetState(): ThrusterState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Dispatch an event to transition state\n\t */\n\tdispatch(event: ThrusterEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM state based on player input.\n\t * Auto-transitions between Idle/Active to report current state.\n\t * Does NOT modify input - just observes and reports.\n\t */\n\tupdate(playerInput: PlayerInput): void {\n\t\tconst state = this.machine.getState();\n\t\tconst hasInput =\n\t\t\tMath.abs(playerInput.thrust) > 0.01\n\t\t\t|| Math.abs(playerInput.rotate) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustX ?? 0) > 0.01\n\t\t\t|| Math.abs(playerInput.thrustY ?? 0) > 0.01;\n\n\t\t// Auto-transition to report state based on input\n\t\tif (hasInput && state === ThrusterState.Idle) {\n\t\t\tthis.dispatch(ThrusterEvent.Activate);\n\t\t} else if (!hasInput && state === ThrusterState.Active) {\n\t\t\tthis.dispatch(ThrusterEvent.Deactivate);\n\t\t}\n\t}\n}\n","/**\n * Thruster Behavior Descriptor\n *\n * Type-safe descriptor for the thruster behavior using the entity.use() API.\n * The wasm runtime owns the thruster simulation; this system attaches the\n * Rust behavior and forwards input into the slot-indexed input buffer.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tcreateThrusterInputComponent,\n\tcreateThrusterMovementComponent,\n\ttype ThrusterMovementComponent,\n\ttype ThrusterInputComponent,\n} from './components';\nimport type { BehaviorRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Entity with thruster components (input written by the game, state owned by\n * the wasm runtime).\n */\nexport interface ThrusterEntity {\n\tthruster: ThrusterMovementComponent;\n\t$thruster: ThrusterInputComponent;\n}\n\n/**\n * Thruster behavior options (typed for entity.use() autocomplete)\n */\nexport interface ThrusterBehaviorOptions {\n\t/** Forward thrust force (default: 10) */\n\tlinearThrust: number;\n\t/** Rotation torque (default: 5) */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n}\n\nconst defaultOptions: ThrusterBehaviorOptions = {\n\tlinearThrust: 10,\n\tangularThrust: 5,\n\tlinearDamping: undefined,\n};\n\nconst THRUSTER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:thruster');\n\n/**\n * Adapter that forwards thruster input into the wasm runtime.\n *\n * The Rust `thruster` Stage behavior is the single simulation path: this\n * system attaches it once per entity and writes `$thruster` input into the\n * slot-indexed input buffer each frame.\n */\nclass ThrusterBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(THRUSTER_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst thrusterRef = link.ref as any;\n\n\t\t\tconst options = thrusterRef.options as ThrusterBehaviorOptions;\n\n\t\t\tif (!gameEntity.thruster) {\n\t\t\t\tgameEntity.thruster = createThrusterMovementComponent(\n\t\t\t\t\toptions.linearThrust,\n\t\t\t\t\toptions.angularThrust,\n\t\t\t\t\t{ linearDamping: options.linearDamping },\n\t\t\t\t) as ThrusterMovementComponent;\n\t\t\t}\n\n\t\t\tif (!gameEntity.$thruster) {\n\t\t\t\tgameEntity.$thruster = createThrusterInputComponent() as ThrusterInputComponent;\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tconst input = gameEntity.$thruster as ThrusterInputComponent | undefined;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.thrust, input.rotate);\n\t\t\t\tthis.wasmStage.writeInputReserved(handle, input.thrustX, input.thrustY);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ThrusterBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachThruster(handle, {\n\t\t\tmaxSpeed: 50,\n\t\t\tacceleration: options.linearThrust,\n\t\t\tturnRateRadPerSec: options.angularThrust,\n\t\t\tlinearDamping: options.linearDamping ?? 0,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ThrusterBehavior - typed descriptor for thruster movement.\n *\n * @example\n * ```typescript\n * import { ThrusterBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ship = createSprite({ ... });\n * ship.use(ThrusterBehavior, { linearThrust: 15, angularThrust: 8 });\n * ```\n */\nexport const ThrusterBehavior = defineBehavior<ThrusterBehaviorOptions, Record<string, never>, ThrusterEntity>({\n\tname: 'thruster',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ThrusterBehaviorSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ThrusterState","ThrusterEvent"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/ricochet-2d/ricochet-2d-fsm.ts","../src/lib/behaviors/ricochet-2d/ricochet-2d.descriptor.ts"],"sourcesContent":["/**\n * Ricochet2DFSM\n *\n * FSM + extended state to track ricochet events and results.\n * The FSM state tracks whether a ricochet is currently occurring.\n */\nimport { BaseEntityInterface } from \"../../types/entity-types\";\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\nexport interface Ricochet2DResult {\n\t/** The reflected velocity vector */\n\tvelocity: { x: number; y: number; z?: number };\n\t/** The resulting speed after reflection */\n\tspeed: number;\n\t/** The collision normal used for reflection */\n\tnormal: { x: number; y: number; z?: number };\n}\n\nexport interface Ricochet2DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\t/** Current velocity of the entity (optional if entity is provided) */\n\tselfVelocity?: { x: number; y: number; z?: number };\n\t/** Contact information from the collision */\n\tcontact: {\n\t\t/** The collision normal */\n\t\tnormal?: { x: number; y: number; z?: number };\n\t\t/**\n\t\t * Optional position where the collision occurred.\n\t\t * If provided, used for precise offset calculation.\n\t\t */\n\t\tposition?: { x: number; y: number; z?: number };\n\t};\n\t/**\n\t * Optional position of the entity that owns this behavior.\n\t * Used with contact.position for offset calculations.\n\t */\n\tselfPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional position of the other entity in the collision.\n\t * Used for paddle-style deflection: offset = (contactY - otherY) / halfHeight.\n\t */\n\totherPosition?: { x: number; y: number; z?: number };\n\t/**\n\t * Optional size of the other entity (e.g., paddle size).\n\t * If provided, used to normalize the offset based on the collision face.\n\t */\n\totherSize?: { x: number; y: number; z?: number };\n}\n\n/** Structural view of a host-attached body (harness/simulation facade). */\ninterface RicochetBodyLike {\n\tlinvel(): { x: number; y: number; z: number };\n\ttranslation(): { x: number; y: number; z: number };\n}\n\nfunction bodyOf(entity: BaseEntityInterface | undefined): RicochetBodyLike | null {\n\tconst body = entity?.body as Partial<RicochetBodyLike> | null | undefined;\n\tif (body && typeof body.linvel === 'function' && typeof body.translation === 'function') {\n\t\treturn body as RicochetBodyLike;\n\t}\n\treturn null;\n}\n\nexport enum Ricochet2DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet2DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\n/**\n * Callback type for ricochet event listeners.\n */\nexport type RicochetCallback = (result: Ricochet2DResult) => void;\n\n/**\n * FSM wrapper with extended state (lastResult).\n * Systems or consumers call `computeRicochet(...)` when a collision occurs.\n */\nexport class Ricochet2DFSM {\n\tpublic readonly machine: SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>;\n\n\tprivate lastResult: Ricochet2DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\tprivate currentTimeMs: number = 0;\n\tprivate listeners: Set<RicochetCallback> = new Set();\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<Ricochet2DState, Ricochet2DEvent, never>(\n\t\t\tRicochet2DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(Ricochet2DState.Idle, Ricochet2DEvent.EndRicochet, Ricochet2DState.Idle),\n\t\t\t\tt(Ricochet2DState.Ricocheting, Ricochet2DEvent.StartRicochet, Ricochet2DState.Ricocheting),\n\t\t\t]\n\t\t);\n\t}\n\n\t/**\n\t * Add a listener for ricochet events.\n\t * @returns Unsubscribe function\n\t */\n\taddListener(callback: RicochetCallback): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\t/**\n\t * Emit result to all listeners.\n\t */\n\tprivate emitToListeners(result: Ricochet2DResult): void {\n\t\tfor (const callback of this.listeners) {\n\t\t\ttry {\n\t\t\t\tcallback(result);\n\t\t\t} catch (e) {\n\t\t\t\tconsole.error('[Ricochet2DFSM] Listener error:', e);\n\t\t\t}\n\t\t}\n\t}\n\n\tgetState(): Ricochet2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last computation.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Set current game time (called by system each frame).\n\t * Used for cooldown calculations.\n\t */\n\tsetCurrentTimeMs(timeMs: number): void {\n\t\tthis.currentTimeMs = timeMs;\n\t}\n\n\t/**\n\t * Check if ricochet is on cooldown (to prevent rapid duplicate applications).\n\t * @param cooldownMs Cooldown duration in milliseconds (default: 50ms)\n\t */\n\tisOnCooldown(cooldownMs: number = 50): boolean {\n\t\tif (this.lastUpdatedAtMs === null) return false;\n\t\treturn (this.currentTimeMs - this.lastUpdatedAtMs) < cooldownMs;\n\t}\n\n\t/**\n\t * Reset cooldown state (e.g., on entity respawn).\n\t */\n\tresetCooldown(): void {\n\t\tthis.lastUpdatedAtMs = null;\n\t}\n\n\t/**\n\t * Compute a ricochet result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t */\n\tcomputeRicochet(\n\t\tctx: Ricochet2DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {}\n\t): Ricochet2DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 60,\n\t\t} = options;\n\n\t\t// Extract data from entities if provided\n\t\tconst { selfVelocity, selfPosition, otherPosition, otherSize } = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = Math.hypot(selfVelocity.x, selfVelocity.y);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute or extract collision normal\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\t// Compute basic reflection\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, normal);\n\n\t\t// Apply angled deflection if requested\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\tselfVelocity,\n\t\t\t\tnormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position\n\t\t\t);\n\t\t}\n\n\t\t// Apply final speed constraints\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet2DResult = {\n\t\t\tvelocity: { x: reflected.x, y: reflected.y, z: 0 },\n\t\t\tspeed: Math.hypot(reflected.x, reflected.y),\n\t\t\tnormal: { x: normal.x, y: normal.y, z: 0 },\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = this.currentTimeMs;\n\t\tthis.dispatch(Ricochet2DEvent.StartRicochet);\n\t\tthis.emitToListeners(result);\n\n\t\treturn result;\n\t}\n\n\t/**\n\t * Extract velocity, position, and size data from entities or context.\n\t */\n\tprivate extractDataFromEntities(ctx: Ricochet2DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tconst selfBody = bodyOf(ctx.entity);\n\t\tif (selfBody) {\n\t\t\tconst vel = selfBody.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: vel.x, y: vel.y, z: vel.z };\n\t\t\tconst pos = selfBody.translation();\n\t\t\tselfPosition = selfPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tconst otherBody = bodyOf(ctx.otherEntity);\n\t\tif (otherBody) {\n\t\t\tconst pos = otherBody.translation();\n\t\t\totherPosition = otherPosition ?? { x: pos.x, y: pos.y, z: pos.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\t/**\n\t * Compute collision normal from entity positions using AABB heuristic.\n\t */\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number; z?: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst dx = selfPosition.x - otherPosition.x;\n\t\tconst dy = selfPosition.y - otherPosition.y;\n\n\t\t// Simple \"which face was hit\" logic for box collisions\n\t\tif (Math.abs(dx) > Math.abs(dy)) {\n\t\t\treturn { x: dx > 0 ? 1 : -1, y: 0, z: 0 };\n\t\t} else {\n\t\t\treturn { x: 0, y: dy > 0 ? 1 : -1, z: 0 };\n\t\t}\n\t}\n\n\t/**\n\t * Compute basic reflection using the formula: v' = v - 2(v·n)n\n\t */\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst vx = velocity.x;\n\t\tconst vy = velocity.y;\n\t\tconst dotProduct = vx * normal.x + vy * normal.y;\n\n\t\treturn {\n\t\t\tx: vx - 2 * dotProduct * normal.x,\n\t\t\ty: vy - 2 * dotProduct * normal.y,\n\t\t};\n\t}\n\n\t/**\n\t * Compute angled deflection for paddle-style reflections.\n\t */\n\tprivate computeAngledDeflection(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): { x: number; y: number } {\n\t\tconst maxAngleRad = (maxAngleDeg * Math.PI) / 180;\n\n\t\t// Tangent vector (perpendicular to normal)\n\t\tlet tx = -normal.y;\n\t\tlet ty = normal.x;\n\n\t\t// Ensure tangent points in positive direction of the deflection axis\n\t\t// so that positive offset (right/top) results in positive deflection\n\t\tif (Math.abs(normal.x) > Math.abs(normal.y)) {\n\t\t\t// Vertical face (Normal is X-aligned). Deflection axis is Y.\n\t\t\t// We want ty > 0.\n\t\t\tif (ty < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t} else {\n\t\t\t// Horizontal face (Normal is Y-aligned). Deflection axis is X.\n\t\t\t// We want tx > 0.\n\t\t\tif (tx < 0) {\n\t\t\t\ttx = -tx;\n\t\t\t\tty = -ty;\n\t\t\t}\n\t\t}\n\n\t\t// Compute offset based on hit position\n\t\tconst offset = this.computeHitOffset(\n\t\t\tvelocity,\n\t\t\tnormal,\n\t\t\tspeed,\n\t\t\ttx,\n\t\t\tty,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition\n\t\t);\n\n\t\tconst angle = clamp(offset, -1, 1) * maxAngleRad;\n\n\t\tconst cosA = Math.cos(angle);\n\t\tconst sinA = Math.sin(angle);\n\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: newSpeed * (normal.x * cosA + tx * sinA),\n\t\t\ty: newSpeed * (normal.y * cosA + ty * sinA),\n\t\t};\n\t}\n\n\t/**\n\t * Compute hit offset for angled deflection (-1 to 1).\n\t */\n\tprivate computeHitOffset(\n\t\tvelocity: { x: number; y: number },\n\t\tnormal: { x: number; y: number; z?: number },\n\t\tspeed: number,\n\t\ttx: number,\n\t\tty: number,\n\t\tselfPosition?: { x: number; y: number; z?: number },\n\t\totherPosition?: { x: number; y: number; z?: number },\n\t\totherSize?: { x: number; y: number; z?: number },\n\t\tcontactPosition?: { x: number; y: number; z?: number }\n\t): number {\n\t\t// Use position-based offset if available\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst useY = Math.abs(normal.x) > Math.abs(normal.y);\n\t\t\tconst halfExtent = useY ? otherSize.y / 2 : otherSize.x / 2;\n\n\t\t\tif (useY) {\n\t\t\t\tconst selfY = selfPosition?.y ?? contactPosition?.y ?? 0;\n\t\t\t\tconst paddleY = otherPosition.y;\n\t\t\t\treturn (selfY - paddleY) / halfExtent;\n\t\t\t} else {\n\t\t\t\tconst selfX = selfPosition?.x ?? contactPosition?.x ?? 0;\n\t\t\t\tconst paddleX = otherPosition.x;\n\t\t\t\treturn (selfX - paddleX) / halfExtent;\n\t\t\t}\n\t\t}\n\n\t\t// Fallback: use velocity-based offset\n\t\treturn (velocity.x * tx + velocity.y * ty) / speed;\n\t}\n\n\t/**\n\t * Apply speed constraints to the reflected velocity.\n\t */\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number\n\t): { x: number; y: number } {\n\t\tconst currentSpeed = Math.hypot(velocity.x, velocity.y);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t};\n\t}\n\n\t/**\n\t * Clear the ricochet state (call after consumer has applied the result).\n\t */\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet2DEvent.EndRicochet);\n\t}\n\n\tprivate dispatch(event: Ricochet2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","/**\n * Ricochet2DBehavior\n *\n * Computes 2D ricochet/reflection results for entities during collisions.\n * The behavior computes the result; the consumer decides how to apply it.\n *\n * Use `getRicochet(ctx)` on the behavior handle to compute reflection results.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Ricochet2DFSM, type Ricochet2DResult, type Ricochet2DCollisionContext, type RicochetCallback } from './ricochet-2d-fsm';\nimport {\n\tStageRicochetDim,\n\tStageRicochetReflection,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\nexport type { Ricochet2DResult };\n\nexport interface Ricochet2DOptions {\n\t/**\n\t * Minimum speed after reflection.\n\t * Default: 2\n\t */\n\tminSpeed: number;\n\n\t/**\n\t * Maximum speed after reflection.\n\t * Default: 20\n\t */\n\tmaxSpeed: number;\n\n\t/**\n\t * Speed multiplier applied during angled reflection.\n\t * Default: 1.05\n\t */\n\tspeedMultiplier: number;\n\n\t/**\n\t * Reflection mode:\n\t * - 'simple': Basic axis inversion\n\t * - 'angled': Paddle-style deflection based on contact point\n\t * Default: 'angled'\n\t */\n\treflectionMode: 'simple' | 'angled';\n\n\t/**\n\t * Maximum deflection angle in degrees for angled mode.\n\t * Default: 60\n\t */\n\tmaxAngleDeg: number;\n}\n\n/**\n * Handle methods provided by Ricochet2DBehavior\n */\nexport interface Ricochet2DHandle {\n\t/**\n\t * Compute a ricochet/reflection result from collision context.\n\t * Returns the result for the consumer to apply, or null if invalid input.\n\t *\n\t * @param ctx - Collision context with selfVelocity and contact normal\n\t * @returns Ricochet result with velocity, speed, and normal, or null\n\t */\n\tgetRicochet(ctx: Ricochet2DCollisionContext): Ricochet2DResult | null;\n\n\t/**\n\t * Compute ricochet and apply velocity directly via transformStore.\n\t * Emits to onRicochet listeners if successful.\n\t *\n\t * @param ctx - Collision context with entity and contact normal\n\t * @returns true if ricochet was computed and applied, false otherwise\n\t */\n\tapplyRicochet(ctx: Ricochet2DCollisionContext): boolean;\n\n\t/**\n\t * Get the last computed ricochet result, or null if none.\n\t */\n\tgetLastResult(): Ricochet2DResult | null;\n\n\t/**\n\t * Register a listener for ricochet events.\n\t * Called whenever a ricochet is computed (from getRicochet or applyRicochet).\n\t *\n\t * @param callback - Function to call with ricochet result\n\t * @returns Unsubscribe function\n\t */\n\tonRicochet(callback: RicochetCallback): () => void;\n}\n\nconst defaultOptions: Ricochet2DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 60,\n};\n\nconst RICOCHET_BEHAVIOR_KEY = Symbol.for('zylem:behavior:ricochet-2d');\n\n/**\n * Creates behavior-specific handle methods for Ricochet2DBehavior.\n */\nfunction createRicochet2DHandle(\n\tref: BehaviorRef<Ricochet2DOptions>\n): Ricochet2DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet2DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tapplyRicochet: (ctx: Ricochet2DCollisionContext): boolean => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) return false;\n\n\t\t\t// Skip if on cooldown (prevents rapid duplicate applications)\n\t\t\tif (fsm.isOnCooldown()) return false;\n\n\t\t\tconst result = fsm.computeRicochet(ctx, ref.options);\n\t\t\tif (!result) return false;\n\n\t\t\t// Apply velocity via transformStore\n\t\t\tconst entity = ctx.entity as any;\n\t\t\tif (entity?.transformStore) {\n\t\t\t\tentity.transformStore.velocity.x = result.velocity.x;\n\t\t\t\tentity.transformStore.velocity.y = result.velocity.y;\n\t\t\t\tentity.transformStore.velocity.z = result.velocity.z ?? 0;\n\t\t\t\tentity.transformStore.dirty.velocityX = true;\n\t\t\t\tentity.transformStore.dirty.velocityY = true;\n\t\t\t\tentity.transformStore.dirty.velocityZ = true;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t\tonRicochet: (callback: RicochetCallback): (() => void) => {\n\t\t\tconst fsm = ref.fsm as Ricochet2DFSM | undefined;\n\t\t\tif (!fsm) {\n\t\t\t\t// FSM not ready yet - queue callback for later\n\t\t\t\t// System will apply pending callbacks when FSM is created\n\t\t\t\tif (!(ref as any).pendingListeners) {\n\t\t\t\t\t(ref as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t\t(ref as any).pendingListeners.push(callback);\n\t\t\t\t\n\t\t\t\t// Return unsubscribe that removes from pending queue\n\t\t\t\treturn () => {\n\t\t\t\t\tconst pending = (ref as any).pendingListeners as RicochetCallback[];\n\t\t\t\t\tconst idx = pending.indexOf(callback);\n\t\t\t\t\tif (idx >= 0) pending.splice(idx, 1);\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn fsm.addListener(callback);\n\t\t},\n\t};\n}\n\n/**\n * Ricochet2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - lazily creates FSM instances for each entity\n *\n * Note: This behavior is consumer-driven. The system only manages FSM lifecycle.\n * Consumers call `getRicochet(ctx)` during collision callbacks to compute results.\n */\nclass Ricochet2DSystem implements BehaviorSystem {\n\tprivate elapsedMs: number = 0;\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tthis.elapsedMs += delta * 1000;\n\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst ricochetRef = link.ref as any;\n\t\t\tconst gameEntity = link.entity as any;\n\n\t\t\tif (!ricochetRef.fsm) {\n\t\t\t\tricochetRef.fsm = new Ricochet2DFSM();\n\n\t\t\t\tconst pending = (ricochetRef as any).pendingListeners as RicochetCallback[] | undefined;\n\t\t\t\tif (pending) {\n\t\t\t\t\tfor (const cb of pending) {\n\t\t\t\t\t\tricochetRef.fsm.addListener(cb);\n\t\t\t\t\t}\n\t\t\t\t\t(ricochetRef as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tricochetRef.fsm.setCurrentTimeMs(this.elapsedMs);\n\n\t\t\tconst handle = (gameEntity?.runtimeHandle ?? -1) as number;\n\t\t\tif (this.wasmStage && handle >= 0) {\n\t\t\t\tthis.ensureWasmAttached(handle, ricochetRef.options as Ricochet2DOptions);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Ricochet2DOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst reflectionMode = options.reflectionMode === 'simple'\n\t\t\t? StageRicochetReflection.Mirror\n\t\t\t: StageRicochetReflection.Angled;\n\t\tthis.wasmStage.attachRicochet(handle, StageRicochetDim.Two, {\n\t\t\tminSpeed: options.minSpeed,\n\t\t\tmaxSpeed: options.maxSpeed,\n\t\t\tspeedMultiplier: options.speedMultiplier,\n\t\t\tmaxAngleDeg: options.maxAngleDeg,\n\t\t\treflectionMode,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\tconst ricochetRef = link.ref as any;\n\t\t\t\tif (ricochetRef?.fsm) {\n\t\t\t\t\tricochetRef.fsm.resetCooldown();\n\t\t\t\t}\n\t\t\t}\n\t\t}\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Ricochet2DBehavior\n *\n * @example\n * ```ts\n * import { Ricochet2DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ball = createSphere({ ... });\n * const ricochet = ball.use(Ricochet2DBehavior, {\n * minSpeed: 3,\n * maxSpeed: 15,\n * reflectionMode: 'angled',\n * });\n *\n * ball.onCollision(({ entity, other }) => {\n * const velocity = entity.body.linvel();\n * const result = ricochet.getRicochet({\n * selfVelocity: velocity,\n * contact: { normal: { x: 1, y: 0 } }, // from collision data\n * });\n *\n * if (result) {\n * entity.body.setLinvel(result.velocity, true);\n * }\n * });\n * ```\n */\nexport const Ricochet2DBehavior = defineBehavior({\n\tname: 'ricochet-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Ricochet2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createRicochet2DHandle,\n});\n"],"mappings":"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","names":["Ricochet2DState","Ricochet2DEvent"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/first-person/first-person-fsm.ts","../src/lib/behaviors/first-person/first-person.system.ts","../src/lib/behaviors/first-person/first-person.descriptor.ts"],"sourcesContent":["/**\n * First Person Controller Finite State Machine\n *\n * Manages state transitions for first-person movement:\n * - Idle: Standing still\n * - Walking: Moving at walk speed\n * - Running: Moving at run/sprint speed\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { FirstPersonInputComponent, FirstPersonStateComponent } from './components';\n\n/**\n * First-person movement states\n */\nexport enum FirstPersonState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n}\n\n/**\n * Events that trigger state transitions\n */\nexport enum FirstPersonEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tStop = 'stop',\n}\n\n/**\n * Context for the FSM\n */\nexport interface FirstPersonContext {\n\tinput: FirstPersonInputComponent;\n\tstate: FirstPersonStateComponent;\n}\n\n/**\n * First Person Controller FSM\n */\nexport class FirstPersonFSM {\n\tmachine: SyncStateMachine<FirstPersonState, FirstPersonEvent, never>;\n\n\tconstructor(private ctx: FirstPersonContext) {\n\t\tthis.machine = new SyncStateMachine<FirstPersonState, FirstPersonEvent, never>(\n\t\t\tFirstPersonState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Idle, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Walking, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Walk, FirstPersonState.Walking),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Stop, FirstPersonState.Idle),\n\t\t\t\tt(FirstPersonState.Running, FirstPersonEvent.Run, FirstPersonState.Running),\n\t\t\t],\n\t\t);\n\t}\n\n\t/** Get the current state */\n\tgetState(): FirstPersonState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/** Dispatch an event to the FSM */\n\tdispatch(event: FirstPersonEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/** Get the current yaw from context */\n\tgetYaw(): number {\n\t\treturn this.ctx.state.yaw;\n\t}\n\n\t/** Get the current pitch from context */\n\tgetPitch(): number {\n\t\treturn this.ctx.state.pitch;\n\t}\n\n\t/**\n\t * Update FSM based on current input and state.\n\t */\n\tupdate(input: FirstPersonInputComponent, state: FirstPersonStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst hasMovement = Math.abs(input.moveX) > 0.1 || Math.abs(input.moveZ) > 0.1;\n\n\t\tif (hasMovement) {\n\t\t\tif (input.sprint) {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Run);\n\t\t\t} else {\n\t\t\t\tthis.dispatch(FirstPersonEvent.Walk);\n\t\t\t}\n\t\t} else {\n\t\t\tthis.dispatch(FirstPersonEvent.Stop);\n\t\t}\n\t}\n}\n","/**\n * First Person Presentation System\n *\n * Presentation-only companion to the wasm `first_person` Stage behavior:\n * - Syncs the FirstPersonPerspective eye position from the runtime pose\n * - Applies the runtime's yaw/pitch (from the behavior snapshot) to the\n * perspective so the camera matches the simulated look state\n * - Positions an optional viewmodel entity relative to the camera\n *\n * All movement/look simulation happens in the wasm runtime; this system only\n * reads poses and snapshots.\n */\n\nimport { Vector3, Quaternion, Euler, type Object3D } from 'three';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport type {\n\tBehaviorRuntime,\n\tStagePose,\n\tStageBehaviorSnapshot,\n} from '../../runtime/wasm-stage-runtime';\nimport type {\n\tFirstPersonMovementComponent,\n\tFirstPersonInputComponent,\n\tFirstPersonStateComponent,\n} from './components';\nimport type { TransformState } from '../../capabilities/transform-store';\n\n/**\n * Entity interface for the first-person controller.\n * Entities with this behavior have these components attached.\n */\nexport interface FirstPersonEntity {\n\tuuid: string;\n\ttransformStore: TransformState;\n\tfirstPerson: FirstPersonMovementComponent;\n\t$fps: FirstPersonInputComponent;\n\tfirstPersonState: FirstPersonStateComponent;\n}\n\n/**\n * Viewmodel configuration for an entity that follows the camera.\n */\nexport interface ViewmodelConfig {\n\t/** The entity whose visual group should be positioned relative to the camera. */\n\tentity: any;\n\t/** Offset from the eye position in camera-local space (right, down, forward). */\n\toffset: Vector3;\n\t/** Additional rotation applied to the viewmodel in local space (Euler angles in radians). */\n\trotation?: Euler;\n\t/**\n\t * When set, the viewmodel group is parented to this Object3D (typically the\n\t * Three.js camera) and kept at the local offset/rotation. This avoids\n\t * world-space teleport jitter from physics pose sync fighting the camera.\n\t */\n\tcameraObject?: Object3D;\n}\n// Reusable temporaries (avoid per-frame allocations)\nconst _viewmodelQuat = new Quaternion();\nconst _viewmodelLocalQuat = new Quaternion();\nconst _viewmodelEuler = new Euler();\nconst _offset = new Vector3();\nconst _pose: StagePose = { position: [0, 0, 0], rotation: [0, 0, 0, 1] };\nconst _snapshot = {} as StageBehaviorSnapshot;\n\n/**\n * Presentation system driven by runtime pose + snapshot reads.\n */\nexport class FirstPersonPresentation {\n\tprivate perspectives = new Map<string, FirstPersonPerspective>();\n\tprivate viewmodels = new Map<string, ViewmodelConfig>();\n\n\t/**\n\t * Associate a FirstPersonPerspective with an entity (by uuid).\n\t * Called once by the descriptor system when the behavior is initialized.\n\t */\n\tsetPerspective(entityUuid: string, perspective: FirstPersonPerspective): void {\n\t\tthis.perspectives.set(entityUuid, perspective);\n\t}\n\n\thasPerspective(entityUuid: string): boolean {\n\t\treturn this.perspectives.has(entityUuid);\n\t}\n\n\t/**\n\t * Attach a viewmodel (weapon/item) to an entity.\n\t * The viewmodel entity's group will be positioned relative to the camera each frame.\n\t */\n\tsetViewmodel(entityUuid: string, config: ViewmodelConfig): void {\n\t\tthis.viewmodels.set(entityUuid, config);\n\t}\n\n\thasViewmodel(entityUuid: string): boolean {\n\t\treturn this.viewmodels.has(entityUuid);\n\t}\n\n\t/**\n\t * Sync camera + viewmodel for one entity from the runtime state.\n\t */\n\tsyncEntity(\n\t\tentityUuid: string,\n\t\truntime: BehaviorRuntime,\n\t\thandle: number,\n\t\tmovement: FirstPersonMovementComponent,\n\t\tstate: FirstPersonStateComponent | undefined,\n\t): void {\n\t\tconst perspective = this.perspectives.get(entityUuid);\n\t\tconst pose = runtime.getPose(handle, _pose);\n\t\tconst snapshot = runtime.readSnapshot(handle, _snapshot);\n\n\t\tif (perspective && snapshot) {\n\t\t\tperspective.setLook?.(snapshot.yaw, snapshot.pitch);\n\t\t\tif (state) {\n\t\t\t\tstate.yaw = snapshot.yaw;\n\t\t\t\tstate.pitch = snapshot.pitch;\n\t\t\t\tstate.currentSpeed = snapshot.speed;\n\t\t\t}\n\t\t}\n\n\t\tif (perspective?.initialPosition && pose) {\n\t\t\tperspective.initialPosition.set(\n\t\t\t\tpose.position[0],\n\t\t\t\tpose.position[1] + movement.eyeHeight,\n\t\t\t\tpose.position[2],\n\t\t\t);\n\t\t}\n\n\t\tconst vm = this.viewmodels.get(entityUuid);\n\t\tif (vm && state && (vm.cameraObject || perspective?.initialPosition)) {\n\t\t\tthis.positionViewmodel(vm, perspective!, state, runtime);\n\t\t}\n\t}\n\n\t/**\n\t * Position a viewmodel entity relative to the camera's eye position and rotation.\n\t * Prefer `cameraObject` parenting for jitter-free follow during movement.\n\t */\n\tprivate positionViewmodel(\n\t\tvm: ViewmodelConfig,\n\t\tperspective: FirstPersonPerspective,\n\t\tstate: FirstPersonStateComponent,\n\t\truntime: BehaviorRuntime,\n\t): void {\n\t\tconst entity = vm.entity;\n\t\tconst group = entity?.group as Object3D | undefined;\n\n\t\t// Camera-parented path: local transform only — stays locked to the rendered view.\n\t\tif (vm.cameraObject && group) {\n\t\t\tif (entity) {\n\t\t\t\tentity.skipRenderPoseSync = true;\n\t\t\t}\n\t\t\tif (group.parent !== vm.cameraObject) {\n\t\t\t\tvm.cameraObject.add(group);\n\t\t\t}\n\t\t\tgroup.position.copy(vm.offset);\n\t\t\tif (vm.rotation) {\n\t\t\t\tgroup.rotation.copy(vm.rotation);\n\t\t\t} else {\n\t\t\t\tgroup.rotation.set(0, 0, 0);\n\t\t\t}\n\t\t\treturn;\n\t\t}\n\n\t\tconst eyePos = perspective.initialPosition;\n\t\tif (!eyePos) return;\n\n\t\t// Look rotation only for the offset (matches camera-parented path).\n\t\t_viewmodelQuat.setFromEuler(\n\t\t\t_viewmodelEuler.set(state.pitch, state.yaw, 0, 'YXZ'),\n\t\t);\n\t\t_offset.copy(vm.offset).applyQuaternion(_viewmodelQuat);\n\n\t\t// Mesh orientation: look * viewmodel local fix\n\t\tif (vm.rotation) {\n\t\t\t_viewmodelLocalQuat.setFromEuler(vm.rotation);\n\t\t\t_viewmodelQuat.multiply(_viewmodelLocalQuat);\n\t\t}\n\n\t\tconst wx = eyePos.x + _offset.x;\n\t\tconst wy = eyePos.y + _offset.y;\n\t\tconst wz = eyePos.z + _offset.z;\n\n\t\t// Move the runtime body (if registered) so physics stays in sync\n\t\tconst handle = (entity?.runtimeHandle ?? -1) as number;\n\t\tif (handle >= 0) {\n\t\t\truntime.setPosition(handle, wx, wy, wz);\n\t\t\truntime.setRotation(\n\t\t\t\thandle,\n\t\t\t\t_viewmodelQuat.x, _viewmodelQuat.y, _viewmodelQuat.z, _viewmodelQuat.w,\n\t\t\t);\n\t\t}\n\n\t\t// Also set the group directly for immediate visual update\n\t\tif (group) {\n\t\t\tgroup.position.set(wx, wy, wz);\n\t\t\tgroup.quaternion.copy(_viewmodelQuat);\n\t\t}\n\t}\n\n\t/** Cleanup */\n\tdestroy(): void {\n\t\tthis.perspectives.clear();\n\t\tthis.viewmodels.clear();\n\t}\n}\n","/**\n * First Person Controller Behavior Descriptor\n *\n * Type-safe descriptor for the first-person controller behavior system.\n * Provides entity.use() API for FPS movement and camera control.\n */\n\nimport { Euler, type Object3D } from 'three';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport type { FirstPersonPerspective } from '../../camera/perspectives/first-person-perspective';\nimport { Vec3Input, VEC3_ZERO, toThreeVector3 } from '../../core/vector';\nimport {\n\tFirstPersonPresentation,\n\ttype FirstPersonEntity,\n\ttype ViewmodelConfig,\n} from './first-person.system';\nimport { FirstPersonState } from './first-person-fsm';\nimport {\n\tcreateFirstPersonMovementComponent,\n\tcreateFirstPersonInputComponent,\n\tcreateFirstPersonStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\n/**\n * First-person controller behavior options (typed for entity.use() autocomplete).\n */\nexport interface FirstPersonBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 16) */\n\trunSpeed?: number;\n\t/** Look sensitivity multiplier (default: 2) */\n\tlookSensitivity?: number;\n\t/** Eye height offset above entity position (default: 1.7) */\n\teyeHeight?: number;\n\t/** The FirstPersonPerspective to drive (from camera.getPerspective()) */\n\tperspective?: FirstPersonPerspective;\n\t/** Optional viewmodel (weapon/item) to position relative to the camera */\n\tviewmodel?: {\n\t\tentity: any;\n\t\toffset: Vec3Input;\n\t\trotation?: Euler;\n\t\t/** Parent the viewmodel to this Object3D (e.g. the Three.js camera) for jitter-free follow. */\n\t\tcameraObject?: Object3D;\n\t};\n}\n\nconst defaultOptions: FirstPersonBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 16,\n\tlookSensitivity: 2,\n\teyeHeight: 1.7,\n};\n\nconst FIRST_PERSON_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:first-person-controller',\n);\n\n/**\n * Forwards look/move input into the wasm runtime and syncs the camera\n * perspective + optional viewmodel from the runtime's pose/snapshot\n * (presentation only — the Rust `first_person` behavior owns simulation).\n */\nclass FirstPersonBehaviorSystem implements BehaviorSystem {\n\tprivate presentation = new FirstPersonPresentation();\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(FIRST_PERSON_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 fpsRef = link.ref as any;\n\n\t\t\tconst options = fpsRef.options as FirstPersonBehaviorOptions;\n\n\t\t\t// Initialize components (once)\n\t\t\tif (!gameEntity.firstPerson) {\n\t\t\t\tgameEntity.firstPerson = createFirstPersonMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$fps) {\n\t\t\t\tgameEntity.$fps = createFirstPersonInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.firstPersonState) {\n\t\t\t\tgameEntity.firstPersonState = createFirstPersonStateComponent();\n\t\t\t}\n\n\t\t\t// Wire up perspective (once)\n\t\t\tif (options.perspective && !this.presentation.hasPerspective(gameEntity.uuid)) {\n\t\t\t\tconst eyeHeight = options.eyeHeight ?? defaultOptions.eyeHeight!;\n\t\t\t\toptions.perspective.setEyeHeight?.(eyeHeight);\n\t\t\t\tthis.presentation.setPerspective(gameEntity.uuid, options.perspective);\n\t\t\t}\n\n\t\t\t// Wire up viewmodel (once)\n\t\t\tif (options.viewmodel && !this.presentation.hasViewmodel(gameEntity.uuid)) {\n\t\t\t\tthis.presentation.setViewmodel(gameEntity.uuid, {\n\t\t\t\t\t...options.viewmodel,\n\t\t\t\t\toffset: toThreeVector3(options.viewmodel.offset, VEC3_ZERO),\n\t\t\t\t} as ViewmodelConfig);\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tfpsRef.__wasmStage = this.wasmStage;\n\t\t\tfpsRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$fps;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, input.moveZ ?? 0);\n\t\t\t\tthis.wasmStage.writeInputLook(handle, input.lookX ?? 0, input.lookY ?? 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.sprint);\n\t\t\t}\n\n\t\t\tthis.presentation.syncEntity(\n\t\t\t\tgameEntity.uuid,\n\t\t\t\tthis.wasmStage,\n\t\t\t\thandle,\n\t\t\t\tgameEntity.firstPerson,\n\t\t\t\tgameEntity.firstPersonState,\n\t\t\t);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: FirstPersonBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachFirstPerson(handle, {\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\teyeHeight: options.eyeHeight ?? defaultOptions.eyeHeight!,\n\t\t\tlookSensitivity: options.lookSensitivity ?? defaultOptions.lookSensitivity!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.presentation.destroy();\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * FirstPersonBehavior - typed descriptor for first-person movement and camera control.\n *\n * Provides complete first-person controller including:\n * - WASD movement relative to camera yaw\n * - Mouse look driving a FirstPersonPerspective\n * - Optional viewmodel positioning (weapon/item following camera)\n * - Walk/run state tracking via FSM\n *\n * @example\n * ```typescript\n * import { FirstPersonBehavior } from \"@zylem/game-lib/behavior\";\n * import { createCamera, Perspectives, FirstPersonPerspective } from \"@zylem/game-lib/core\";\n * import { createActor } from \"@zylem/game-lib/entity\";\n *\n * const fpsCamera = createCamera({ perspective: Perspectives.FirstPerson, ... });\n * const fps = fpsCamera.getPerspective<FirstPersonPerspective>();\n *\n * const player = createActor({ name: 'player', ... });\n * const controller = player.use(FirstPersonBehavior, {\n * perspective: fps,\n * walkSpeed: 8,\n * runSpeed: 16,\n * lookSensitivity: 2,\n * });\n *\n * // In update loop - write input, behavior handles the rest\n * player.onUpdate(({ inputs }) => {\n * player.$fps.moveX = inputs.p1.axes.Horizontal.value;\n * player.$fps.moveZ = inputs.p1.axes.Vertical.value;\n * player.$fps.lookX = inputs.p1.axes.SecondaryHorizontal.value;\n * player.$fps.lookY = inputs.p1.axes.SecondaryVertical.value;\n * player.$fps.sprint = inputs.p1.shoulders.LTrigger.held > 0;\n * });\n * ```\n */\nexport const FirstPersonBehavior = defineBehavior<\n\tFirstPersonBehaviorOptions,\n\t{\n\t\tgetState: () => FirstPersonState;\n\t\tgetYaw: () => number;\n\t\tgetPitch: () => number;\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => void;\n\t},\n\tFirstPersonEntity\n>({\n\tname: 'first-person-controller',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew FirstPersonBehaviorSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\t// Handles read the runtime snapshot buffer (previous-step values). The\n\t// Rust behavior is the single source of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (!snapshot) return FirstPersonState.Idle;\n\t\t\tif (snapshot.speed <= 0.01) return FirstPersonState.Idle;\n\t\t\tconst walkSpeed = ref.options.walkSpeed ?? defaultOptions.walkSpeed!;\n\t\t\treturn snapshot.speed > walkSpeed\n\t\t\t\t? FirstPersonState.Running\n\t\t\t\t: FirstPersonState.Walking;\n\t\t},\n\t\tgetYaw: () => readBehaviorSnapshot(ref)?.yaw ?? 0,\n\t\tgetPitch: () => readBehaviorSnapshot(ref)?.pitch ?? 0,\n\t\tattachViewmodel: (entity: any, offset: Vec3Input) => {\n\t\t\tif (ref.options) {\n\t\t\t\tref.options.viewmodel = { entity, offset };\n\t\t\t}\n\t\t},\n\t}),\n});\n"],"mappings":"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","names":["FirstPersonState","FirstPersonEvent"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/jumper-3d/jumper-3d-fsm.ts","../src/lib/behaviors/jumper-3d/jumper-3d.descriptor.ts"],"sourcesContent":["/**\n * Jumper 3D Finite State Machine\n *\n * Three high-level states driven by events emitted from the behavior tick:\n * Grounded ──Jump──▶ Jumping\n * Grounded ──Fall──▶ Falling (walked off a ledge)\n * Jumping ──Fall──▶ Falling (apex / descending)\n * Jumping ──Jump──▶ Jumping (multi-jump)\n * Jumping ──Land──▶ Grounded (rare — very short hop)\n * Falling ──Land──▶ Grounded\n * Falling ──Jump──▶ Jumping (air-jump while falling)\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { JumpState3D } from './components';\n\nexport enum Jumper3DState {\n\tGrounded = 'grounded',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n}\n\nexport enum Jumper3DEvent {\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n}\n\nexport interface Jumper3DFSMContext {\n\tstate: JumpState3D;\n}\n\nexport class Jumper3DFSM {\n\tmachine: SyncStateMachine<Jumper3DState, Jumper3DEvent, never>;\n\n\tconstructor(private ctx: Jumper3DFSMContext) {\n\t\tthis.machine = new SyncStateMachine<Jumper3DState, Jumper3DEvent, never>(\n\t\t\tJumper3DState.Grounded,\n\t\t\t[\n\t\t\t\t// Grounded\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Grounded, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\n\t\t\t\t// Jumping\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Jumping, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\n\t\t\t\t// Falling\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Land, Jumper3DState.Grounded),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Jump, Jumper3DState.Jumping),\n\t\t\t\tt(Jumper3DState.Falling, Jumper3DEvent.Fall, Jumper3DState.Falling),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Jumper3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Jumper3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisJumping(): boolean {\n\t\treturn this.getState() === Jumper3DState.Jumping;\n\t}\n\n\tisFalling(): boolean {\n\t\treturn this.getState() === Jumper3DState.Falling;\n\t}\n\n\tisGrounded(): boolean {\n\t\treturn this.getState() === Jumper3DState.Grounded;\n\t}\n\n\tgetJumpsUsed(): number {\n\t\treturn this.ctx.state.jumpsUsed;\n\t}\n\n\t/**\n\t * Translate a tick event into an FSM dispatch.\n\t * Also handles the implicit Grounded→Falling transition when the entity\n\t * is no longer grounded but no explicit event was emitted (e.g. walked\n\t * off a ledge without jumping).\n\t */\n\tapplyTickEvent(tickEvent: string, isGrounded: boolean): void {\n\t\tswitch (tickEvent) {\n\t\t\tcase 'jump':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Jump);\n\t\t\t\tbreak;\n\t\t\tcase 'fall':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\tbreak;\n\t\t\tcase 'land':\n\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\tbreak;\n\t\t\tdefault:\n\t\t\t\t// No explicit event — detect implicit fall\n\t\t\t\tif (!isGrounded && this.getState() === Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Fall);\n\t\t\t\t}\n\t\t\t\tif (isGrounded && this.getState() !== Jumper3DState.Grounded) {\n\t\t\t\t\tthis.dispatch(Jumper3DEvent.Land);\n\t\t\t\t}\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n","/**\n * Jumper 3D Behavior Descriptor\n *\n * Type-safe descriptor for the jumper-3d behavior system.\n * Provides `entity.use()` API for composable 3D jumping.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Jumper3DState } from './jumper-3d-fsm';\nimport {\n\tcreateJumpInput3D,\n\ttype JumpConfig3D,\n\ttype JumpInput3D,\n\ttype JumpState3D,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\treadBehaviorSnapshot,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Options exposed through entity.use()\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DBehaviorOptions {\n\t/** Desired peak height in world units (default: 2.5) */\n\tjumpHeight?: number;\n\t/** Gravity magnitude applied by the behavior (default: 20) */\n\tgravity?: number;\n\t/** Terminal fall speed (optional) */\n\tmaxFallSpeed?: number;\n\t/** Max concurrent jumps, 1 = single, 2 = double, etc. (default: 1) */\n\tmaxJumps?: number;\n\t/** Reset jump counter on ground (default: true) */\n\tresetJumpsOnGround?: boolean;\n\t/** Reset jump counter on wall contact (default: undefined) */\n\tresetJumpsOnWall?: boolean;\n\t/** Coyote time in ms (default: 100) */\n\tcoyoteTimeMs?: number;\n\t/** Jump buffer in ms (default: 80) */\n\tjumpBufferMs?: number;\n\t/** Min cooldown between jumps in ms (optional) */\n\tminTimeBetweenJumpsMs?: number;\n\t/** Variable jump height config (optional) */\n\tvariableJump?: JumpConfig3D['variableJump'];\n\t/** Planar control config (optional) */\n\tplanar?: JumpConfig3D['planar'];\n\t/** Fall tuning (optional) */\n\tfall?: JumpConfig3D['fall'];\n\t/** Ground-detection ray length (default: 1.0) */\n\tgroundRayLength?: number;\n\t/** Max support distance that counts as grounded and can snap to ground */\n\tsnapToGroundDistance?: number;\n\t/** Enable debug visualization for ground probe rays (default: false) */\n\tdebugGroundProbe?: boolean;\n}\n\nconst defaultOptions: Jumper3DBehaviorOptions = {\n\tjumpHeight: 2.5,\n\tgravity: 20,\n\tmaxJumps: 1,\n\tresetJumpsOnGround: true,\n\tcoyoteTimeMs: 100,\n\tjumpBufferMs: 80,\n\tgroundRayLength: 1.0,\n\tdebugGroundProbe: false,\n};\n\nconst JUMPER_BEHAVIOR_KEY = Symbol.for('zylem:behavior:jumper-3d');\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Entity shape expected by the system\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Jumper3DEntity {\n\tuuid: string;\n\ttransformStore: import('../../capabilities/transform-store').TransformState;\n\tjumper: JumpConfig3D;\n\t$jumper: JumpInput3D;\n\tjumperState: JumpState3D;\n}\n\nexport function isJumper3DGrounded(params: {\n\tsupportToi?: number | null;\n\tvelocityY: number;\n\tsnapToGroundDistance?: number;\n}): boolean {\n\tif (params.snapToGroundDistance != null) {\n\t\treturn (\n\t\t\tparams.supportToi != null &&\n\t\t\tparams.supportToi <= params.snapToGroundDistance &&\n\t\t\tparams.velocityY <= 0.25\n\t\t);\n\t}\n\n\treturn (\n\t\tparams.supportToi != null &&\n\t\tparams.velocityY > -2.0 &&\n\t\tparams.velocityY < 2.0\n\t);\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Behavior System adapter\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Forwards jump input into the wasm runtime. The Rust `jumper_3d` Stage\n * behavior (ground probe + jump core) is the single simulation path; handles\n * read its state back via the snapshot buffer.\n */\nclass Jumper3DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(JUMPER_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst jumperRef = link.ref as any;\n\n\t\t\tconst options = jumperRef.options as Jumper3DBehaviorOptions;\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tif (!gameEntity.$jumper) {\n\t\t\t\tgameEntity.$jumper = createJumpInput3D();\n\t\t\t}\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tjumperRef.__wasmStage = this.wasmStage;\n\t\t\tjumperRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$jumper as JumpInput3D;\n\t\t\tconst jumpPressed = !!(input.jumpPressed || input.jumpHeld);\n\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, jumpPressed);\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: Jumper3DBehaviorOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst jumpForce = Math.sqrt(2 * (options.gravity ?? 20) * (options.jumpHeight ?? 2.5));\n\t\tthis.wasmStage.attachJumper3D(handle, {\n\t\t\tjumpForce,\n\t\t\tmaxJumps: options.maxJumps ?? 1,\n\t\t\tgravity: options.gravity ?? 20,\n\t\t\tcoyoteTime: (options.coyoteTimeMs ?? 100) / 1000,\n\t\t\tjumpBufferTime: (options.jumpBufferMs ?? 80) / 1000,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Public descriptor\n// ─────────────────────────────────────────────────────────────────────────────\n\n/**\n * Jumper3DBehavior — composable 3D jump behavior.\n *\n * Provides height-based jumping, multi-jump, coyote time, jump buffering,\n * variable jump height, configurable fall gravity, and optional air control.\n *\n * Designed to compose with any movement controller (e.g. FirstPersonBehavior)\n * — the controller writes X/Z velocity and Jumper3DBehavior writes Y velocity.\n *\n * @example\n * ```typescript\n * import { Jumper3DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const jumper = player.use(Jumper3DBehavior, {\n * jumpHeight: 2.5,\n * gravity: 20,\n * maxJumps: 2,\n * coyoteTimeMs: 100,\n * jumpBufferMs: 80,\n * variableJump: { enabled: true, cutGravityMultiplier: 3 },\n * fall: { fallGravityMultiplier: 1.5 },\n * });\n *\n * player.onUpdate(({ inputs }) => {\n * player.$jumper.jumpPressed = inputs.p1.buttons.A.pressed;\n * player.$jumper.jumpHeld = inputs.p1.buttons.A.held > 0;\n * player.$jumper.jumpReleased = inputs.p1.buttons.A.released;\n *\n * const state = jumper.getState(); // Grounded | Jumping | Falling\n * });\n * ```\n */\nexport const Jumper3DBehavior = defineBehavior<\n\tJumper3DBehaviorOptions,\n\t{\n\t\tgetState: () => Jumper3DState;\n\t\tisJumping: () => boolean;\n\t\tgetJumpsUsed: () => number;\n\t\tgetJumpsRemaining: () => number;\n\t},\n\tJumper3DEntity\n>({\n\tname: 'jumper-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Jumper3DBehaviorSystem(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (previous-step values). The\n\t// Rust behavior is the single source of truth.\n\tcreateHandle: (ref) => {\n\t\tconst jumpsUsed = () => readBehaviorSnapshot(ref)?.jumpCount ?? 0;\n\t\treturn {\n\t\t\tgetState: () => {\n\t\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\t\tif (!snapshot) return Jumper3DState.Grounded;\n\t\t\t\tif (snapshot.grounded) return Jumper3DState.Grounded;\n\t\t\t\treturn snapshot.verticalVelocity > 0\n\t\t\t\t\t? Jumper3DState.Jumping\n\t\t\t\t\t: Jumper3DState.Falling;\n\t\t\t},\n\t\t\tisJumping: () => {\n\t\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\t\treturn snapshot ? !snapshot.grounded && snapshot.verticalVelocity > 0 : false;\n\t\t\t},\n\t\t\tgetJumpsUsed: jumpsUsed,\n\t\t\tgetJumpsRemaining: () => (ref.options.maxJumps ?? 1) - jumpsUsed(),\n\t\t};\n\t},\n});\n"],"mappings":"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","names":["Jumper3DState","Jumper3DEvent"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/top-down-movement/components.ts"],"sourcesContent":["export interface TopDownMovementComponent {\n\tmoveSpeed: number;\n}\n\nexport function createTopDownMovementComponent(\n\toptions: Partial<TopDownMovementComponent> = {},\n): TopDownMovementComponent {\n\treturn {\n\t\tmoveSpeed: options.moveSpeed ?? 8,\n\t};\n}\n\nexport interface TopDownMovementInputComponent {\n\tmoveX: number;\n\tmoveY: number;\n\tfaceX: number;\n\tfaceY: number;\n}\n\nexport function createTopDownMovementInputComponent(): TopDownMovementInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tmoveY: 0,\n\t\tfaceX: 0,\n\t\tfaceY: 0,\n\t};\n}\n\nexport interface TopDownMovementStateComponent {\n\tfacingAngle: number;\n\tmoving: boolean;\n}\n\nexport function createTopDownMovementStateComponent(): TopDownMovementStateComponent {\n\treturn {\n\t\tfacingAngle: 0,\n\t\tmoving: false,\n\t};\n}\n"],"mappings":";AAIO,SAAS,+BACf,UAA6C,CAAC,GACnB;AAC3B,SAAO;AAAA,IACN,WAAW,QAAQ,aAAa;AAAA,EACjC;AACD;AASO,SAAS,sCAAqE;AACpF,SAAO;AAAA,IACN,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,IACP,OAAO;AAAA,EACR;AACD;AAOO,SAAS,sCAAqE;AACpF,SAAO;AAAA,IACN,aAAa;AAAA,IACb,QAAQ;AAAA,EACT;AACD;","names":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/behavior-descriptor.ts"],"sourcesContent":["/**\n * BehaviorDescriptor\n *\n * Type-safe behavior descriptors that provide options inference.\n * Used with entity.use() to declaratively attach behaviors to entities.\n *\n * Each behavior can define its own handle type via `createHandle`,\n * providing behavior-specific methods with full type safety.\n */\n\nimport type { BehaviorSystemFactory } from './behavior-system';\n\n/**\n * Base handle returned by entity.use() for lazy access to behavior runtime.\n * FSM is null until entity is spawned and components are initialized.\n */\nexport interface BaseBehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** Get the FSM instance (null until entity is spawned) */\n getFSM(): any | null;\n /** Get the current options */\n getOptions(): O;\n /** Access the underlying behavior ref */\n readonly ref: BehaviorRef<O>;\n}\n\n/**\n * Reference to a behavior stored on an entity\n */\nexport interface BehaviorRef<\n O extends Record<string, any> = Record<string, any>,\n> {\n /** The behavior descriptor */\n descriptor: BehaviorDescriptor<O, any>;\n /** Merged options (defaults + overrides) */\n options: O;\n /** Optional FSM instance - set lazily when entity is spawned */\n fsm?: any;\n}\n\n/**\n * A typed behavior descriptor that associates a symbol key with:\n * - Default options (providing type inference)\n * - A system factory to create the behavior system\n * - An optional handle factory for behavior-specific methods\n */\nexport interface BehaviorDescriptor<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Unique symbol identifying this behavior */\n readonly key: symbol;\n /** Default options (used for type inference) */\n readonly defaultOptions: O;\n /** Factory to create the behavior system */\n readonly systemFactory: any;\n /**\n * Optional factory to create behavior-specific handle methods.\n * These methods are merged into the handle returned by entity.use().\n */\n readonly createHandle?: (ref: any) => H;\n}\n\n/**\n * The full handle type returned by entity.use().\n * Combines base handle with behavior-specific methods.\n */\nexport type BehaviorHandle<\n O extends Record<string, any> = Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n> = BaseBehaviorHandle<O> & H;\n\n/**\n * Configuration for defining a new behavior\n */\nexport interface DefineBehaviorConfig<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n> {\n /** Human-readable name for debugging */\n name: string;\n /** Default options - these define the type */\n defaultOptions: O;\n /** Factory function to create the system */\n systemFactory: BehaviorSystemFactory;\n /**\n * Optional factory to create behavior-specific handle methods.\n * The returned object is merged into the handle returned by entity.use().\n *\n * @example\n * ```typescript\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX, moveY) => ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * ```\n */\n createHandle?: (ref: BehaviorRef<O>) => H;\n}\n\n/**\n * Define a typed behavior descriptor.\n *\n * @example\n * ```typescript\n * export const WorldBoundary2DBehavior = defineBehavior({\n * name: 'world-boundary-2d',\n * defaultOptions: { boundaries: { top: 0, bottom: 0, left: 0, right: 0 } },\n * systemFactory: (ctx) => new WorldBoundary2DSystem(ctx.world),\n * createHandle: (ref) => ({\n * getLastHits: () => ref.fsm?.getLastHits() ?? null,\n * getMovement: (moveX: number, moveY: number) =>\n * ref.fsm?.getMovement(moveX, moveY) ?? { moveX, moveY },\n * }),\n * });\n *\n * // Usage - handle has getLastHits and getMovement with full types\n * const boundary = ship.use(WorldBoundary2DBehavior, { ... });\n * const hits = boundary.getLastHits(); // Fully typed!\n * ```\n */\nexport function defineBehavior<\n O extends Record<string, any>,\n H extends Record<string, any> = Record<string, never>,\n I = unknown,\n>(\n config: DefineBehaviorConfig<O, H, I>,\n): BehaviorDescriptor<O, H, I> {\n return {\n key: Symbol.for(`zylem:behavior:${config.name}`),\n defaultOptions: config.defaultOptions,\n systemFactory: config.systemFactory,\n createHandle: config.createHandle,\n };\n}\n"],"mappings":";AA4HO,SAAS,eAKd,QAC6B;AAC7B,SAAO;AAAA,IACL,KAAK,uBAAO,IAAI,kBAAkB,OAAO,IAAI,EAAE;AAAA,IAC/C,gBAAgB,OAAO;AAAA,IACvB,eAAe,OAAO;AAAA,IACtB,cAAc,OAAO;AAAA,EACvB;AACF;","names":[]}
@@ -1 +0,0 @@
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 BehaviorRuntime,\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: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(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) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tconst query = this.wasmStage.queryWorldBoundary(handle);\n\t\t\tif (query) {\n\t\t\t\tboundaryRef.fsm.update(\n\t\t\t\t\t{ x: query.clamped[0], y: query.clamped[1], z: query.clamped[2] },\n\t\t\t\t\toptions.boundaries,\n\t\t\t\t\toptions.padding,\n\t\t\t\t);\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"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/particle-emitter/particle-emitter.descriptor.ts","../src/lib/behaviors/particle-emitter/particle-effect.ts"],"sourcesContent":["import { Group, Object3D, Quaternion, Vector3 } from 'three';\n\nimport { Particles } from './gpu';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { VEC3_ZERO, type Vec3Input, normalizeVec3 } from '../../core/vector';\nfunction getRefGlobals(\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n): (() => any) | null {\n\treturn ((ref as any).__getGlobals ?? null) as (() => any) | null;\n}\nimport type { ParticleEffectDefinition } from './particle-effect';\n\nexport interface ParticleEmitterBehaviorOptions {\n\teffect: ParticleEffectDefinition;\n\tautoplay: boolean;\n\tlocalOffset?: Vec3Input;\n\tfollowPosition: boolean;\n\tfollowRotation: boolean;\n\tautoDestroy: boolean;\n}\n\nexport interface ParticleEmitterHandle {\n\tplay(): void;\n\tstop(): void;\n\tpause(): void;\n\trestart(): void;\n\tburst(): void;\n\tisPlaying(): boolean;\n\tgetSystem(): Particles | null;\n}\n\ntype DesiredParticleState = 'playing' | 'paused' | 'stopped';\n\ninterface ParticleEmitterControlState {\n\tdesiredState: DesiredParticleState;\n\tpendingRestart: boolean;\n\truntime: ParticleEmitterRuntime | null;\n}\n\ninterface ParticleEmitterRuntime {\n\tsystem: Particles;\n\tanchor: Group;\n\tservice: ParticleRendererService;\n\tinitialPosition: Vector3;\n\tinitialQuaternion: Quaternion;\n\t/** Whether the system has been started at least once in this lifecycle. */\n\tstarted: boolean;\n}\n\ninterface ParticleEmitterHostEntity {\n\tuuid: string;\n\tname?: string;\n\tbody?: {\n\t\ttranslation(): { x: number; y: number; z: number };\n\t\trotation?(): { x: number; y: number; z: number; w: number };\n\t} | null;\n\tgroup?: Object3D | null;\n\tmesh?: Object3D | null;\n\toptions?: {\n\t\tposition?: Vec3Input;\n\t};\n\tmarkedForRemoval?: boolean;\n\tnodeDestroy?(params: { me: unknown; globals: unknown }): void;\n\t__zylemParticleSystemEntity?: boolean;\n}\n\nconst PARTICLE_EMITTER_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:particle-emitter',\n);\n\nconst DEFAULT_PARTICLE_EFFECT: ParticleEffectDefinition = {\n\tcreate() {\n\t\tthrow new Error(\n\t\t\t'ParticleEmitterBehavior requires an effect definition created with particleEffect(...).',\n\t\t);\n\t},\n};\n\nconst defaultOptions: ParticleEmitterBehaviorOptions = {\n\teffect: DEFAULT_PARTICLE_EFFECT,\n\tautoplay: true,\n\tlocalOffset: undefined,\n\tfollowPosition: true,\n\tfollowRotation: true,\n\tautoDestroy: false,\n};\n\n/**\n * Ticks the GPU particle systems each frame.\n *\n * The previous backend (three.quarks) required a `BatchedRenderer` Object3D in\n * the scene graph; the WebGPU engine instead exposes `Particles` Object3Ds that\n * self-render and only need `update(delta)` called for CPU-side emission.\n */\nclass ParticleRendererService {\n\tprivate readonly systems = new Set<Particles>();\n\n\tconstructor(private readonly sceneRoot: Object3D) {}\n\n\t/** Scene node that emitter anchors are parented to. */\n\tget root(): Object3D {\n\t\treturn this.sceneRoot;\n\t}\n\n\tregister(system: Particles): void {\n\t\tthis.systems.add(system);\n\t}\n\n\tforget(system: Particles): void {\n\t\tthis.systems.delete(system);\n\t}\n\n\tupdate(delta: number): void {\n\t\tfor (const system of this.systems) {\n\t\t\tsystem.update(delta);\n\t\t}\n\t}\n\n\tdestroy(): void {\n\t\tthis.systems.clear();\n\t}\n}\n\nfunction getParticleControlState(\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n): ParticleEmitterControlState {\n\tconst runtimeState = (ref as any).__particleEmitterControlState as\n\t\t| ParticleEmitterControlState\n\t\t| undefined;\n\tif (runtimeState) {\n\t\treturn runtimeState;\n\t}\n\n\tconst nextState: ParticleEmitterControlState = {\n\t\tdesiredState: ref.options.autoplay ? 'playing' : 'stopped',\n\t\tpendingRestart: false,\n\t\truntime: null,\n\t};\n\t(ref as any).__particleEmitterControlState = nextState;\n\treturn nextState;\n}\n\nfunction shouldCreateRuntime(\n\tstate: ParticleEmitterControlState,\n): boolean {\n\treturn state.runtime == null\n\t\t&& (state.desiredState !== 'stopped' || state.pendingRestart);\n}\n\n// Module-level scratch objects reused every frame by readHostTransform /\n// getLocalOffset. This runs per emitter per frame, so allocating fresh\n// Vector3/Quaternion instances here was the library's worst GC hotspot.\n// Callers must copy/clone the returned values if they retain them.\nconst _scratchLocalOffset = new Vector3();\nconst _hostTransform = {\n\tposition: new Vector3(),\n\tquaternion: new Quaternion(),\n};\n\nfunction getLocalOffset(options: ParticleEmitterBehaviorOptions): Vector3 {\n\tconst offset = normalizeVec3(options.localOffset, VEC3_ZERO);\n\treturn _scratchLocalOffset.set(offset.x, offset.y, offset.z);\n}\n\nfunction readHostTransform(entity: ParticleEmitterHostEntity): {\n\tposition: Vector3;\n\tquaternion: Quaternion;\n} {\n\tconst wasmPose = (entity as any).getPose?.();\n\tif (wasmPose && (entity as any).runtimeHandle >= 0) {\n\t\t_hostTransform.position.set(wasmPose.position.x, wasmPose.position.y, wasmPose.position.z);\n\t\t_hostTransform.quaternion.set(\n\t\t\twasmPose.rotation.x,\n\t\t\twasmPose.rotation.y,\n\t\t\twasmPose.rotation.z,\n\t\t\twasmPose.rotation.w,\n\t\t);\n\t\treturn _hostTransform;\n\t}\n\n\tif (entity.body?.translation) {\n\t\tconst position = entity.body.translation();\n\t\tconst rotation = entity.body.rotation?.() ?? { x: 0, y: 0, z: 0, w: 1 };\n\t\t_hostTransform.position.set(position.x, position.y, position.z);\n\t\t_hostTransform.quaternion.set(rotation.x, rotation.y, rotation.z, rotation.w);\n\t\treturn _hostTransform;\n\t}\n\n\tconst visualRoot = entity.group ?? entity.mesh;\n\tif (visualRoot) {\n\t\tvisualRoot.getWorldPosition(_hostTransform.position);\n\t\tvisualRoot.getWorldQuaternion(_hostTransform.quaternion);\n\t\treturn _hostTransform;\n\t}\n\n\tconst position = normalizeVec3(entity.options?.position, VEC3_ZERO);\n\t_hostTransform.position.set(position.x, position.y, position.z);\n\t_hostTransform.quaternion.identity();\n\treturn _hostTransform;\n}\n\nfunction syncRuntimeTransform(\n\tentity: ParticleEmitterHostEntity,\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n\truntime: ParticleEmitterRuntime,\n): void {\n\tconst current = readHostTransform(entity);\n\tconst options = ref.options;\n\tconst basePosition = options.followPosition\n\t\t? current.position\n\t\t: runtime.initialPosition;\n\tconst baseQuaternion = options.followRotation\n\t\t? current.quaternion\n\t\t: runtime.initialQuaternion;\n\tconst offset = getLocalOffset(options);\n\n\tif (options.followRotation) {\n\t\toffset.applyQuaternion(baseQuaternion);\n\t}\n\n\truntime.anchor.position.copy(basePosition).add(offset);\n\truntime.anchor.quaternion.copy(baseQuaternion);\n}\n\nfunction applyRuntimeState(\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n): void {\n\tconst state = getParticleControlState(ref);\n\tconst runtime = state.runtime;\n\tif (!runtime) {\n\t\treturn;\n\t}\n\n\tif (state.pendingRestart) {\n\t\truntime.system.restart();\n\t\truntime.started = true;\n\t\tstate.pendingRestart = false;\n\n\t\tif (state.desiredState === 'paused') {\n\t\t\truntime.system.pause();\n\t\t} else if (state.desiredState === 'stopped') {\n\t\t\truntime.system.stop();\n\t\t\truntime.started = false;\n\t\t}\n\t\treturn;\n\t}\n\n\tif (state.desiredState === 'paused') {\n\t\truntime.system.pause();\n\t\treturn;\n\t}\n\n\tif (state.desiredState === 'stopped') {\n\t\truntime.system.stop();\n\t\truntime.started = false;\n\t\treturn;\n\t}\n\n\t// playing\n\tif (!runtime.started) {\n\t\truntime.system.start();\n\t\truntime.started = true;\n\t} else {\n\t\truntime.system.resume();\n\t}\n}\n\nfunction releaseRuntime(\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n\treason: 'manual' | 'system-destroyed',\n): void {\n\tconst state = getParticleControlState(ref);\n\tconst runtime = state.runtime;\n\tif (!runtime) {\n\t\treturn;\n\t}\n\n\tstate.runtime = null;\n\tstate.pendingRestart = false;\n\n\truntime.service.forget(runtime.system);\n\n\tif (reason === 'manual') {\n\t\truntime.system.dispose();\n\t}\n\n\truntime.anchor.removeFromParent();\n}\n\nfunction handleSystemDestroyed(\n\tentity: ParticleEmitterHostEntity,\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n): void {\n\treleaseRuntime(ref, 'system-destroyed');\n\tconst state = getParticleControlState(ref);\n\tstate.desiredState = 'stopped';\n\n\tif (\n\t\tref.options.autoDestroy\n\t\t&& entity.__zylemParticleSystemEntity\n\t\t&& !entity.markedForRemoval\n\t) {\n\t\tentity.nodeDestroy?.({ me: entity, globals: getRefGlobals(ref)?.() ?? {} });\n\t}\n}\n\nfunction createRuntime(\n\tentity: ParticleEmitterHostEntity,\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n\tservice: ParticleRendererService,\n): ParticleEmitterRuntime {\n\tconst system = ref.options.effect.create();\n\tconst anchor = new Group();\n\tconst initialTransform = readHostTransform(entity);\n\tconst runtime: ParticleEmitterRuntime = {\n\t\tsystem,\n\t\tanchor,\n\t\tservice,\n\t\tinitialPosition: initialTransform.position.clone(),\n\t\tinitialQuaternion: initialTransform.quaternion.clone(),\n\t\tstarted: false,\n\t};\n\n\tanchor.name = `${entity.name || entity.uuid}:particle-anchor`;\n\tanchor.add(system);\n\tservice.register(system);\n\tservice.root.add(anchor);\n\n\tsyncRuntimeTransform(entity, ref, runtime);\n\treturn runtime;\n}\n\nfunction createParticleEmitterHandle(\n\tref: BehaviorRef<ParticleEmitterBehaviorOptions>,\n): ParticleEmitterHandle {\n\treturn {\n\t\tplay: () => {\n\t\t\tconst state = getParticleControlState(ref);\n\t\t\tstate.desiredState = 'playing';\n\t\t\tapplyRuntimeState(ref);\n\t\t},\n\t\tstop: () => {\n\t\t\tconst state = getParticleControlState(ref);\n\t\t\tstate.desiredState = 'stopped';\n\t\t\tstate.pendingRestart = false;\n\t\t\tapplyRuntimeState(ref);\n\t\t},\n\t\tpause: () => {\n\t\t\tconst state = getParticleControlState(ref);\n\t\t\tstate.desiredState = 'paused';\n\t\t\tapplyRuntimeState(ref);\n\t\t},\n\t\trestart: () => {\n\t\t\tconst state = getParticleControlState(ref);\n\t\t\tstate.desiredState = 'playing';\n\t\t\tstate.pendingRestart = true;\n\t\t\tapplyRuntimeState(ref);\n\t\t},\n\t\tburst: () => {\n\t\t\tconst state = getParticleControlState(ref);\n\t\t\tstate.desiredState = 'playing';\n\t\t\tstate.pendingRestart = true;\n\t\t\tapplyRuntimeState(ref);\n\t\t},\n\t\tisPlaying: () => {\n\t\t\tconst runtime = getParticleControlState(ref).runtime;\n\t\t\tif (!runtime) {\n\t\t\t\treturn getParticleControlState(ref).desiredState === 'playing';\n\t\t\t}\n\t\t\treturn runtime.system.running;\n\t\t},\n\t\tgetSystem: () => getParticleControlState(ref).runtime?.system ?? null,\n\t};\n}\n\nclass ParticleEmitterBehaviorSystem implements BehaviorSystem {\n\tprivate rendererService: ParticleRendererService | null = null;\n\n\tconstructor(\n\t\tprivate readonly scene: { scene: Object3D },\n\t\tprivate readonly getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t\tprivate readonly getGlobals?: () => any,\n\t) {}\n\n\tprivate getRendererService(): ParticleRendererService {\n\t\tif (!this.rendererService) {\n\t\t\tthis.rendererService = new ParticleRendererService(this.scene.scene);\n\t\t}\n\t\treturn this.rendererService;\n\t}\n\n\tupdate(_ecs: unknown, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PARTICLE_EMITTER_BEHAVIOR_KEY);\n\t\tif (!links) {\n\t\t\treturn;\n\t\t}\n\n\t\tlet hasRuntime = false;\n\t\tlet rendererService = this.rendererService;\n\n\t\tfor (const link of links) {\n\t\t\tconst entity = link.entity as ParticleEmitterHostEntity;\n\t\t\tconst ref = link.ref as BehaviorRef<ParticleEmitterBehaviorOptions>;\n\t\t\t(ref as any).__getGlobals = this.getGlobals;\n\t\t\tconst state = getParticleControlState(ref);\n\n\t\t\tif (shouldCreateRuntime(state)) {\n\t\t\t\ttry {\n\t\t\t\t\trendererService ??= this.getRendererService();\n\t\t\t\t\tstate.runtime = createRuntime(entity, ref, rendererService);\n\t\t\t\t} catch (error) {\n\t\t\t\t\tstate.desiredState = 'stopped';\n\t\t\t\t\tstate.pendingRestart = false;\n\t\t\t\t\tconsole.error('Failed to create particle runtime:', error);\n\t\t\t\t\tcontinue;\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tif (!state.runtime) {\n\t\t\t\tcontinue;\n\t\t\t}\n\n\t\t\thasRuntime = true;\n\t\t\tsyncRuntimeTransform(entity, ref, state.runtime);\n\t\t\tapplyRuntimeState(ref);\n\n\t\t\tif (\n\t\t\t\tref.options.autoDestroy\n\t\t\t\t&& state.runtime.started\n\t\t\t\t&& state.desiredState === 'playing'\n\t\t\t\t&& state.runtime.system.isComplete()\n\t\t\t) {\n\t\t\t\thandleSystemDestroyed(entity, ref);\n\t\t\t}\n\t\t}\n\n\t\tif (hasRuntime && rendererService) {\n\t\t\trendererService.update(delta);\n\t\t}\n\t}\n\n\tdetach(link: BehaviorEntityLink): void {\n\t\treleaseRuntime(\n\t\t\tlink.ref as BehaviorRef<ParticleEmitterBehaviorOptions>,\n\t\t\t'manual',\n\t\t);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tconst links = this.getBehaviorLinks?.(PARTICLE_EMITTER_BEHAVIOR_KEY);\n\t\tif (links) {\n\t\t\tfor (const link of links) {\n\t\t\t\treleaseRuntime(\n\t\t\t\t\tlink.ref as BehaviorRef<ParticleEmitterBehaviorOptions>,\n\t\t\t\t\t'manual',\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\n\t\tthis.rendererService?.destroy();\n\t\tthis.rendererService = null;\n\t}\n}\n\nexport const ParticleEmitterBehavior = defineBehavior<\n\tParticleEmitterBehaviorOptions,\n\tParticleEmitterHandle\n>({\n\tname: 'particle-emitter',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ParticleEmitterBehaviorSystem(ctx.scene, ctx.getBehaviorLinks, ctx.getGlobals),\n\tcreateHandle: createParticleEmitterHandle,\n});\n","import { AdditiveBlending, Color, type ColorRepresentation } from 'three';\n\nimport { Particles } from './gpu';\nimport type { ParticleSettings, ParticlesConfig, RenderSettings } from './gpu';\nimport {\n\tparticleEffect,\n\ttype ParticleEffectDefinition,\n\ttype ParticlePresetMaterialOptions,\n} from './preset-builder';\nimport { electricityPresets, firePresets, gasPresets, magicPresets, waterPresets } from './presets';\n\nexport type {\n\tParticleEffectDefinition,\n\tParticleMagicAgency,\n\tParticleMagicAlignment,\n\tParticleMagicModifierInput,\n\tParticleMagicModifierOptions,\n\tParticleMagicOrder,\n\tParticleMagicRealityEffect,\n\tParticleMagicTemperament,\n\tParticlePresetMaterialOptions,\n\tSemanticParticlePresetOptions,\n} from './preset-builder';\n\nexport interface BurstParticlePresetOptions extends ParticlePresetMaterialOptions {\n\tcolor?: ColorRepresentation;\n\tcount?: number;\n\tduration?: number;\n\tlife?: number | readonly [number, number];\n\tsize?: number | readonly [number, number];\n\tspeed?: number | readonly [number, number];\n\t/**\n\t * Kept for API compatibility. Emitter follow behavior is controlled by the\n\t * particle-emitter behavior (followPosition/followRotation), so this flag is\n\t * not used by the GPU engine.\n\t */\n\tworldSpace?: boolean;\n}\n\ninterface BurstDefaults {\n\tduration: number;\n\tlife: [number, number];\n\tspeed: [number, number];\n\tsize: [number, number];\n\tcolor: ColorRepresentation;\n\tcount: number;\n\tintensity: number;\n}\n\nfunction toRange(\n\tvalue: number | readonly [number, number] | undefined,\n\tfallback: [number, number],\n): [number, number] {\n\tif (value === undefined) return fallback;\n\tif (Array.isArray(value)) {\n\t\treturn [value[0] ?? fallback[0], value[1] ?? fallback[1]];\n\t}\n\tconst single = value as number;\n\treturn [single, single];\n}\n\nfunction toHexColor(color: ColorRepresentation): string {\n\treturn `#${new Color(color).getHexString()}`;\n}\n\nfunction makeBurstConfig(\n\toptions: BurstParticlePresetOptions,\n\tdefaults: BurstDefaults,\n): ParticlesConfig {\n\tconst count = Math.max(1, Math.round(options.count ?? defaults.count));\n\tconst color = toHexColor(options.color ?? defaults.color);\n\tconst blending = (options.blending ?? AdditiveBlending) as RenderSettings['blendingMode'];\n\n\tconst particles: Partial<ParticleSettings> & Pick<ParticleSettings, 'count'> = {\n\t\tcount,\n\t\tintensity: options.opacity ?? defaults.intensity,\n\t\tcolors: [\n\t\t\t[\n\t\t\t\t{ color, stop: 0 },\n\t\t\t\t{ color, stop: 1 },\n\t\t\t],\n\t\t],\n\t\tfadeAlpha: [0.05, 0.7],\n\t\tfadeSize: [0.1, 0.85],\n\t};\n\tif (options.texture) {\n\t\tparticles.alphaMapsStart = [options.texture] as unknown as ParticleSettings['alphaMapsStart'];\n\t}\n\n\treturn {\n\t\tparticles,\n\t\temitter: {\n\t\t\tduration: options.duration ?? defaults.duration,\n\t\t\tloop: false,\n\t\t\tspawnMode: 'burst',\n\t\t\trate: count,\n\t\t\tstatic: 0,\n\t\t\tlifetime: toRange(options.life, defaults.life),\n\t\t\tspeed: toRange(options.speed, defaults.speed),\n\t\t\tsize: toRange(options.size, defaults.size),\n\t\t\tdirectionMin: [-1, -1, -1],\n\t\t\tdirectionMax: [1, 1, 1],\n\t\t},\n\t\trender: { blendingMode: blending },\n\t};\n}\n\nexport const particlePresets = {\n\tburst(options: BurstParticlePresetOptions = {}): ParticleEffectDefinition {\n\t\tconst config = makeBurstConfig(options, {\n\t\t\tduration: 0.25,\n\t\t\tlife: [0.3, 0.7],\n\t\t\tspeed: [1.5, 4],\n\t\t\tsize: [0.08, 0.18],\n\t\t\tcolor: '#ffffff',\n\t\t\tcount: 18,\n\t\t\tintensity: 1,\n\t\t});\n\t\treturn particleEffect(() => new Particles(config));\n\t},\n\tsmoke(options: BurstParticlePresetOptions = {}): ParticleEffectDefinition {\n\t\tconst config = makeBurstConfig(options, {\n\t\t\tduration: 1.1,\n\t\t\tlife: [0.8, 1.6],\n\t\t\tspeed: [0.2, 0.8],\n\t\t\tsize: [0.2, 0.5],\n\t\t\tcolor: '#999999',\n\t\t\tcount: 10,\n\t\t\tintensity: 0.85,\n\t\t});\n\t\treturn particleEffect(() => new Particles(config));\n\t},\n\tfire: firePresets,\n\twater: waterPresets,\n\tgas: gasPresets,\n\telectricity: electricityPresets,\n\tmagic: magicPresets,\n} as const;\n\nexport { particleEffect } from './preset-builder';\n"],"mappings":"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","names":["position"]}
@@ -1 +0,0 @@
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":[]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/destructible-prebake/source-geometry.ts","../src/lib/destructible-prebake/cache-key.ts","../src/lib/destructible-prebake/payload.ts"],"sourcesContent":["/**\n * Normalizes source mesh geometry for Voronoi prebake / fracture so runtime and\n * CLI tooling share one code path (float attributes, non-indexed, root-relative transform).\n */\n\nimport {\n\tBufferAttribute,\n\tBufferGeometry,\n\tMatrix4,\n\ttype Group,\n\ttype Mesh,\n} from 'three';\n\ntype ReadableGeometryAttribute = Pick<\n\tBufferAttribute,\n\t'count' | 'itemSize' | 'getComponent'\n>;\n\nfunction cloneAttributeAsFloat32(\n\tattribute: ReadableGeometryAttribute,\n): BufferAttribute {\n\tconst values = new Float32Array(attribute.count * attribute.itemSize);\n\tlet offset = 0;\n\tfor (let index = 0; index < attribute.count; index += 1) {\n\t\tfor (let component = 0; component < attribute.itemSize; component += 1) {\n\t\t\tvalues[offset] = attribute.getComponent(index, component);\n\t\t\toffset += 1;\n\t\t}\n\t}\n\n\treturn new BufferAttribute(values, attribute.itemSize);\n}\n\n/**\n * Clones geometry with float32 attributes and expands indexed meshes to non-indexed.\n */\nexport function normalizeFractureSourceGeometry(\n\tsourceGeometry: BufferGeometry,\n): BufferGeometry {\n\tconst geometry = sourceGeometry.clone();\n\tfor (const [name, attribute] of Object.entries(geometry.attributes)) {\n\t\tgeometry.setAttribute(name, cloneAttributeAsFloat32(attribute));\n\t}\n\n\tconst index = geometry.getIndex();\n\tif (!index) {\n\t\treturn geometry;\n\t}\n\n\tconst indices = new Uint32Array(index.count);\n\tfor (let cursor = 0; cursor < index.count; cursor += 1) {\n\t\tindices[cursor] = index.getX(cursor);\n\t}\n\n\tgeometry.setIndex(new BufferAttribute(indices, 1));\n\treturn geometry.toNonIndexed();\n}\n\n/**\n * Produces fracture-space geometry: normalized mesh vertices in root-local space.\n */\nexport function buildNormalizedFractureSourceGeometry(\n\tmesh: Mesh,\n\troot: Group,\n): BufferGeometry {\n\tconst geometry = normalizeFractureSourceGeometry(mesh.geometry);\n\troot.updateMatrixWorld(true);\n\tmesh.updateMatrixWorld(true);\n\n\tconst relativeMatrix = new Matrix4()\n\t\t.copy(root.matrixWorld)\n\t\t.invert()\n\t\t.multiply(mesh.matrixWorld);\n\n\tgeometry.applyMatrix4(relativeMatrix);\n\tgeometry.computeBoundingBox();\n\tgeometry.computeBoundingSphere();\n\treturn geometry;\n}\n","/**\n * Stable JSON key for matching prebaked fragment templates to fracture options.\n */\n\nimport type { FractureOptions as PinataFractureOptions } from '@dgreenheck/three-pinata';\nimport { Vector2, Vector3 } from 'three';\n\nfunction vector2ToTuple(value?: Vector2): [number, number] | null {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\treturn [value.x, value.y];\n}\n\nfunction vector3ToTuple(value?: Vector3): [number, number, number] | null {\n\tif (!value) {\n\t\treturn null;\n\t}\n\n\treturn [value.x, value.y, value.z];\n}\n\n/**\n * Serializes fracture options for prebake cache identity (mesh not included).\n */\nexport function buildFractureGeometryCacheKey(\n\toptions: PinataFractureOptions,\n): string {\n\treturn JSON.stringify({\n\t\tfractureMethod: options.fractureMethod,\n\t\tfragmentCount: options.fragmentCount,\n\t\tfracturePlanes: {\n\t\t\tx: options.fracturePlanes?.x ?? true,\n\t\t\ty: options.fracturePlanes?.y ?? true,\n\t\t\tz: options.fracturePlanes?.z ?? true,\n\t\t},\n\t\ttextureScale: vector2ToTuple(options.textureScale),\n\t\ttextureOffset: vector2ToTuple(options.textureOffset),\n\t\tseed: options.seed ?? null,\n\t\tvoronoiOptions: options.voronoiOptions\n\t\t\t? {\n\t\t\t\tmode: options.voronoiOptions.mode,\n\t\t\t\tseedPoints:\n\t\t\t\t\toptions.voronoiOptions.seedPoints?.map((point) => vector3ToTuple(point)),\n\t\t\t\tprojectionAxis: options.voronoiOptions.projectionAxis ?? null,\n\t\t\t\tprojectionNormal: vector3ToTuple(options.voronoiOptions.projectionNormal),\n\t\t\t\tuseApproximation: options.voronoiOptions.useApproximation ?? false,\n\t\t\t\tapproximationNeighborCount:\n\t\t\t\t\toptions.voronoiOptions.approximationNeighborCount ?? 12,\n\t\t\t}\n\t\t\t: null,\n\t});\n}\n","/**\n * Serializable payloads for destructible Voronoi prebake in a Web Worker.\n */\n\nimport { FractureOptions, type DestructibleMesh } from '@dgreenheck/three-pinata';\nimport {\n\tBufferAttribute,\n\tBufferGeometry,\n\tQuaternion,\n\tVector2,\n\tVector3,\n} from 'three';\n\nexport interface PlainBufferGeometryPayload {\n\tattributes: Record<\n\t\tstring,\n\t\t{\n\t\t\tarray: ArrayBuffer;\n\t\t\titemSize: number;\n\t\t\tnormalized?: boolean;\n\t\t}\n\t>;\n}\n\nexport type PlainFractureOptions = {\n\tfractureMethod: 'voronoi' | 'simple';\n\tfragmentCount: number;\n\tfracturePlanes: { x: boolean; y: boolean; z: boolean };\n\ttextureScale: [number, number];\n\ttextureOffset: [number, number];\n\tseed?: number;\n\tvoronoiOptions?: {\n\t\tmode: '3D' | '2.5D';\n\t\tseedPoints?: [number, number, number][];\n\t\timpactPoint?: [number, number, number];\n\t\timpactRadius?: number;\n\t\tprojectionAxis?: 'x' | 'y' | 'z' | 'auto' | null;\n\t\tprojectionNormal?: [number, number, number];\n\t\tuseApproximation: boolean;\n\t\tapproximationNeighborCount: number;\n\t};\n};\n\nexport type PlainWorkerFragmentPayload = {\n\tattributes: PlainBufferGeometryPayload['attributes'];\n\tposition: [number, number, number];\n\tquaternion: [number, number, number, number];\n\tscale: [number, number, number];\n};\n\nexport type PrebakeWorkerRequest = {\n\tgeometry: PlainBufferGeometryPayload;\n\toptions: PlainFractureOptions;\n};\n\nexport type PrebakeWorkerResponse = {\n\tfragments: PlainWorkerFragmentPayload[];\n};\n\nexport interface DestructiblePrebakeWorkerAPI {\n\tprebake(request: PrebakeWorkerRequest): Promise<PrebakeWorkerResponse>;\n}\n\n/**\n * Copies a buffer geometry into detached ArrayBuffers suitable for postMessage transfer.\n */\nexport function bufferGeometryToPlain(geometry: BufferGeometry): {\n\tpayload: PlainBufferGeometryPayload;\n\ttransferables: ArrayBuffer[];\n} {\n\tconst attributes: PlainBufferGeometryPayload['attributes'] = {};\n\tconst transferables: ArrayBuffer[] = [];\n\n\tfor (const name of Object.keys(geometry.attributes)) {\n\t\tconst attr = geometry.getAttribute(name);\n\t\tif (!attr || !(attr instanceof BufferAttribute)) {\n\t\t\tcontinue;\n\t\t}\n\t\tconst view = attr.array as ArrayBufferView;\n\t\tconst buffer = view.buffer.slice(\n\t\t\tview.byteOffset,\n\t\t\tview.byteOffset + view.byteLength,\n\t\t);\n\t\ttransferables.push(buffer);\n\t\tattributes[name] = {\n\t\t\tarray: buffer,\n\t\t\titemSize: attr.itemSize,\n\t\t\tnormalized: attr.normalized,\n\t\t};\n\t}\n\n\treturn { payload: { attributes }, transferables };\n}\n\n/**\n * Rebuilds a BufferGeometry from a plain payload (e.g. in a worker).\n */\nexport function plainToBufferGeometry(\n\tpayload: PlainBufferGeometryPayload,\n): BufferGeometry {\n\tconst geometry = new BufferGeometry();\n\n\tfor (const [name, spec] of Object.entries(payload.attributes)) {\n\t\tconst array = new Float32Array(spec.array);\n\t\tgeometry.setAttribute(\n\t\t\tname,\n\t\t\tnew BufferAttribute(array, spec.itemSize, spec.normalized),\n\t\t);\n\t}\n\n\treturn geometry;\n}\n\n/**\n * Serializes {@link FractureOptions} for structured clone / worker transfer.\n */\nexport function pinataFractureOptionsToPlain(\n\toptions: FractureOptions,\n): PlainFractureOptions {\n\tconst voronoi = options.voronoiOptions;\n\treturn {\n\t\tfractureMethod: options.fractureMethod,\n\t\tfragmentCount: options.fragmentCount,\n\t\tfracturePlanes: {\n\t\t\tx: options.fracturePlanes.x,\n\t\t\ty: options.fracturePlanes.y,\n\t\t\tz: options.fracturePlanes.z,\n\t\t},\n\t\ttextureScale: [options.textureScale.x, options.textureScale.y],\n\t\ttextureOffset: [options.textureOffset.x, options.textureOffset.y],\n\t\tseed: options.seed,\n\t\tvoronoiOptions: voronoi\n\t\t\t? {\n\t\t\t\tmode: voronoi.mode,\n\t\t\t\tseedPoints: voronoi.seedPoints?.map((p) => [p.x, p.y, p.z]),\n\t\t\t\timpactPoint: voronoi.impactPoint\n\t\t\t\t\t? [voronoi.impactPoint.x, voronoi.impactPoint.y, voronoi.impactPoint.z]\n\t\t\t\t\t: undefined,\n\t\t\t\timpactRadius: voronoi.impactRadius,\n\t\t\t\tprojectionAxis: voronoi.projectionAxis ?? null,\n\t\t\t\tprojectionNormal: voronoi.projectionNormal\n\t\t\t\t\t? [\n\t\t\t\t\t\tvoronoi.projectionNormal.x,\n\t\t\t\t\t\tvoronoi.projectionNormal.y,\n\t\t\t\t\t\tvoronoi.projectionNormal.z,\n\t\t\t\t\t]\n\t\t\t\t\t: undefined,\n\t\t\t\tuseApproximation: voronoi.useApproximation ?? false,\n\t\t\t\tapproximationNeighborCount:\n\t\t\t\t\tvoronoi.approximationNeighborCount ?? 12,\n\t\t\t}\n\t\t\t: undefined,\n\t};\n}\n\n/**\n * Reconstructs {@link FractureOptions} from a plain payload.\n */\nexport function plainFractureOptionsToPinata(\n\tplain: PlainFractureOptions,\n): FractureOptions {\n\treturn new FractureOptions({\n\t\tfractureMethod: plain.fractureMethod,\n\t\tfragmentCount: plain.fragmentCount,\n\t\tfracturePlanes: plain.fracturePlanes,\n\t\ttextureScale: new Vector2(plain.textureScale[0], plain.textureScale[1]),\n\t\ttextureOffset: new Vector2(plain.textureOffset[0], plain.textureOffset[1]),\n\t\tseed: plain.seed,\n\t\tvoronoiOptions: plain.voronoiOptions\n\t\t\t? {\n\t\t\t\tmode: plain.voronoiOptions.mode,\n\t\t\t\tseedPoints: plain.voronoiOptions.seedPoints?.map(\n\t\t\t\t\t([x, y, z]) => new Vector3(x, y, z),\n\t\t\t\t),\n\t\t\t\timpactPoint: plain.voronoiOptions.impactPoint\n\t\t\t\t\t? new Vector3(\n\t\t\t\t\t\tplain.voronoiOptions.impactPoint[0],\n\t\t\t\t\t\tplain.voronoiOptions.impactPoint[1],\n\t\t\t\t\t\tplain.voronoiOptions.impactPoint[2],\n\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t\t\timpactRadius: plain.voronoiOptions.impactRadius,\n\t\t\t\tprojectionAxis:\n\t\t\t\t\tplain.voronoiOptions.projectionAxis === null\n\t\t\t\t\t\t? undefined\n\t\t\t\t\t\t: plain.voronoiOptions.projectionAxis ?? undefined,\n\t\t\t\tprojectionNormal: plain.voronoiOptions.projectionNormal\n\t\t\t\t\t? new Vector3(\n\t\t\t\t\t\tplain.voronoiOptions.projectionNormal[0],\n\t\t\t\t\t\tplain.voronoiOptions.projectionNormal[1],\n\t\t\t\t\t\tplain.voronoiOptions.projectionNormal[2],\n\t\t\t\t\t)\n\t\t\t\t\t: undefined,\n\t\t\t\tuseApproximation: plain.voronoiOptions.useApproximation,\n\t\t\t\tapproximationNeighborCount:\n\t\t\t\t\tplain.voronoiOptions.approximationNeighborCount,\n\t\t\t}\n\t\t\t: undefined,\n\t});\n}\n\n/**\n * Converts worker response fragments into template parts for the main-thread cache.\n */\nexport function plainWorkerResultToTemplateParts(response: PrebakeWorkerResponse): Array<{\n\tgeometry: BufferGeometry;\n\tposition: Vector3;\n\tquaternion: Quaternion;\n\tscale: Vector3;\n}> {\n\treturn response.fragments.map((fragment) => {\n\t\tconst geometry = plainToBufferGeometry({ attributes: fragment.attributes });\n\t\tgeometry.computeBoundingBox();\n\t\tgeometry.computeBoundingSphere();\n\t\treturn {\n\t\t\tgeometry,\n\t\t\tposition: new Vector3(\n\t\t\t\tfragment.position[0],\n\t\t\t\tfragment.position[1],\n\t\t\t\tfragment.position[2],\n\t\t\t),\n\t\t\tquaternion: new Quaternion(\n\t\t\t\tfragment.quaternion[0],\n\t\t\t\tfragment.quaternion[1],\n\t\t\t\tfragment.quaternion[2],\n\t\t\t\tfragment.quaternion[3],\n\t\t\t),\n\t\t\tscale: new Vector3(\n\t\t\t\tfragment.scale[0],\n\t\t\t\tfragment.scale[1],\n\t\t\t\tfragment.scale[2],\n\t\t\t),\n\t\t};\n\t});\n}\n\n/**\n * Serializes fracture fragments for Comlink.transfer (used in the worker).\n */\nexport function destructibleFragmentsToPlainResponse(\n\tfragments: readonly DestructibleMesh[],\n): {\n\tresponse: PrebakeWorkerResponse;\n\ttransferables: ArrayBuffer[];\n} {\n\tconst out: PlainWorkerFragmentPayload[] = [];\n\tconst transferables: ArrayBuffer[] = [];\n\n\tfor (const fragment of fragments) {\n\t\tconst { payload, transferables: geoTransfer } = bufferGeometryToPlain(\n\t\t\tfragment.geometry,\n\t\t);\n\t\tfor (const buffer of geoTransfer) {\n\t\t\ttransferables.push(buffer);\n\t\t}\n\t\tout.push({\n\t\t\tattributes: payload.attributes,\n\t\t\tposition: [fragment.position.x, fragment.position.y, fragment.position.z],\n\t\t\tquaternion: [\n\t\t\t\tfragment.quaternion.x,\n\t\t\t\tfragment.quaternion.y,\n\t\t\t\tfragment.quaternion.z,\n\t\t\t\tfragment.quaternion.w,\n\t\t\t],\n\t\t\tscale: [fragment.scale.x, fragment.scale.y, fragment.scale.z],\n\t\t});\n\t\tfragment.geometry.dispose();\n\t}\n\n\treturn { response: { fragments: out }, transferables };\n}\n"],"mappings":"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","names":["BufferAttribute","BufferGeometry"]}
@@ -1 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/platformer-2d/components.ts","../src/lib/behaviors/platformer-2d/platformer-2d-fsm.ts","../src/lib/behaviors/platformer-2d/platformer-2d.descriptor.ts"],"sourcesContent":["/**\n * Platformer 2D Components\n *\n * Components for 2D platformer movement on the X/Y plane: walking/running on\n * X, jumping with multi-jump support, and falling/landing states.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DMovementComponent (capability / configuration)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DMovementComponent {\n\t/** Base walking speed */\n\twalkSpeed: number;\n\t/** Sprint/run speed */\n\trunSpeed: number;\n\t/** Initial jump force/velocity */\n\tjumpForce: number;\n\t/** Maximum number of jumps (1 = single jump, 2 = double jump, etc.) */\n\tmaxJumps: number;\n\t/** Gravity force */\n\tgravity: number;\n\t/** Coyote time in seconds (grace period after leaving ground) */\n\tcoyoteTime: number;\n\t/** Jump buffer time in seconds (queue jump input before landing) */\n\tjumpBufferTime: number;\n\t/** Velocity multiplier when releasing jump button early (0-1) */\n\tjumpCutMultiplier: number;\n\t/** Time in seconds before multi-jump becomes available (0 = after button release) */\n\tmultiJumpWindowTime: number;\n\t/** Maximum slope angle (degrees) the character can walk up */\n\tslopeClimbAngle: number;\n\t/** Slope angle (degrees) past which the character slides down */\n\tslopeSlideAngle: number;\n\t/** Autostep configuration for SNES-style step-ups, or false to disable */\n\tautostep: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance (keeps the character glued going downhill) */\n\tsnapToGroundDistance: number;\n\t/** Character controller offset (small gap kept between collider and environment) */\n\tcharacterOffset: number;\n}\n\nexport function createPlatformer2DMovementComponent(\n\toptions: Partial<Platformer2DMovementComponent> = {},\n): Platformer2DMovementComponent {\n\treturn {\n\t\twalkSpeed: options.walkSpeed ?? 8,\n\t\trunSpeed: options.runSpeed ?? 14,\n\t\tjumpForce: options.jumpForce ?? 12,\n\t\tmaxJumps: options.maxJumps ?? 1,\n\t\tgravity: options.gravity ?? 20,\n\t\tcoyoteTime: options.coyoteTime ?? 0.1,\n\t\tjumpBufferTime: options.jumpBufferTime ?? 0.1,\n\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? 0.5,\n\t\tmultiJumpWindowTime: options.multiJumpWindowTime ?? 0.15,\n\t\tslopeClimbAngle: options.slopeClimbAngle ?? 50,\n\t\tslopeSlideAngle: options.slopeSlideAngle ?? 40,\n\t\tautostep: options.autostep ?? { maxHeight: 0.25, minWidth: 0.05 },\n\t\tsnapToGroundDistance: options.snapToGroundDistance ?? 0.2,\n\t\tcharacterOffset: options.characterOffset ?? 0.05,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DInputComponent (intent)\n// Written by: Player controller, AI controller\n// Read by: Platformer2DBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DInputComponent {\n\t/** Horizontal movement input (-1 to 1) */\n\tmoveX: number;\n\t/** Jump button pressed */\n\tjump: boolean;\n\t/** Run/sprint button held */\n\trun: boolean;\n}\n\nexport function createPlatformer2DInputComponent(): Platformer2DInputComponent {\n\treturn {\n\t\tmoveX: 0,\n\t\tjump: false,\n\t\trun: false,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// Platformer2DStateComponent (runtime state)\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface Platformer2DStateComponent {\n\t/** Is the entity on the ground */\n\tgrounded: boolean;\n\t/** Is currently in a jump arc */\n\tjumping: boolean;\n\t/** Is falling (not jumping) */\n\tfalling: boolean;\n\t/** Current jump count (for multi-jump) */\n\tjumpCount: number;\n\t/** Y position when jump started */\n\tjumpStartHeight: number;\n\t/** Current movement speed being applied */\n\tcurrentSpeed: number;\n\t/** Last known ground Y position */\n\tlastGroundedY: number;\n\t/** Was jump button pressed last frame (for edge detection) */\n\tjumpPressedLastFrame: boolean;\n\t/** Time in seconds since last grounded */\n\ttimeSinceGrounded: number;\n\t/** Is a jump allowed due to buffer? */\n\tjumpBuffered: boolean;\n\t/** Time remaining in jump buffer */\n\tjumpBufferTimer: number;\n\t/** Is jump button currently held */\n\tjumpHeld: boolean;\n\t/** Has the jump cut been applied for this jump? */\n\tjumpCutApplied: boolean;\n\t/** Has jump button been released since last jump (required for multi-jump) */\n\tjumpReleasedSinceLastJump: boolean;\n\t/** Time since current jump started (for multi-jump window) */\n\ttimeSinceJump: number;\n\t/** True for the single frame on which a jump impulse was applied; prevents gravity from clobbering jump velocity. */\n\tjumpedThisFrame: boolean;\n\t/** Remaining seconds of jump-cut velocity ramp; while > 0, verticalVelocity lerps toward verticalVelocity * jumpCutMultiplier. */\n\tjumpCutRampTimer: number;\n\t/**\n\t * Controller-owned vertical velocity (units/second).\n\t * The body is kinematic, so `linvel()` is meaningless — this field is the\n\t * single source of truth for jump/gravity motion on the Y axis.\n\t */\n\tverticalVelocity: number;\n}\n\nexport function createPlatformer2DStateComponent(): Platformer2DStateComponent {\n\treturn {\n\t\tgrounded: false,\n\t\tjumping: false,\n\t\tfalling: false,\n\t\tjumpCount: 0,\n\t\tjumpStartHeight: 0,\n\t\tcurrentSpeed: 0,\n\t\tlastGroundedY: 0,\n\t\tjumpPressedLastFrame: false,\n\t\ttimeSinceGrounded: 0,\n\t\tjumpBuffered: false,\n\t\tjumpBufferTimer: 0,\n\t\tjumpHeld: false,\n\t\tjumpCutApplied: false,\n\t\tjumpReleasedSinceLastJump: true,\n\t\ttimeSinceJump: 0,\n\t\tjumpedThisFrame: false,\n\t\tjumpCutRampTimer: 0,\n\t\tverticalVelocity: 0,\n\t};\n}\n","/**\n * Platformer 2D Finite State Machine\n *\n * Manages state transitions for 2D platformer movement:\n * - Idle: Standing still on ground\n * - Walking: Moving on ground (slow)\n * - Running: Moving on ground (fast)\n * - Jumping: Ascending from jump\n * - Falling: Descending (gravity)\n * - Landing: Just touched ground\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport type { Platformer2DInputComponent, Platformer2DStateComponent } from './components';\n\n/**\n * Simplified collision context (normal in the X/Y plane).\n */\nexport interface Platformer2DCollisionContext {\n\tcontact: {\n\t\tnormal: { x: number; y: number };\n\t};\n}\n\nexport enum Platformer2DState {\n\tIdle = 'idle',\n\tWalking = 'walking',\n\tRunning = 'running',\n\tJumping = 'jumping',\n\tFalling = 'falling',\n\tLanding = 'landing',\n}\n\nexport enum Platformer2DEvent {\n\tWalk = 'walk',\n\tRun = 'run',\n\tJump = 'jump',\n\tFall = 'fall',\n\tLand = 'land',\n\tStop = 'stop',\n}\n\nexport interface Platformer2DContext {\n\tinput: Platformer2DInputComponent;\n\tstate: Platformer2DStateComponent;\n}\n\nexport class Platformer2DFSM {\n\tmachine: SyncStateMachine<Platformer2DState, Platformer2DEvent, never>;\n\n\tconstructor(private ctx: Platformer2DContext) {\n\t\tthis.machine = new SyncStateMachine<Platformer2DState, Platformer2DEvent, never>(\n\t\t\tPlatformer2DState.Idle,\n\t\t\t[\n\t\t\t\t// Idle transitions\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Walking transitions\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Walking, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Running transitions\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Jump, Platformer2DState.Jumping),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t\tt(Platformer2DState.Running, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\n\t\t\t\t// Jumping transitions\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Fall, Platformer2DState.Falling),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Jumping, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Multi-jump\n\n\t\t\t\t// Falling transitions\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Land, Platformer2DState.Landing),\n\t\t\t\tt(Platformer2DState.Falling, Platformer2DEvent.Jump, Platformer2DState.Jumping), // Air jump\n\n\t\t\t\t// Landing transitions\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Walk, Platformer2DState.Walking),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Run, Platformer2DState.Running),\n\t\t\t\tt(Platformer2DState.Landing, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\n\t\t\t\t// Self-transitions (no-ops)\n\t\t\t\tt(Platformer2DState.Idle, Platformer2DEvent.Stop, Platformer2DState.Idle),\n\t\t\t],\n\t\t);\n\t}\n\n\tgetState(): Platformer2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: Platformer2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\tisGrounded(): boolean {\n\t\tconst state = this.getState();\n\t\treturn (\n\t\t\tstate === Platformer2DState.Idle ||\n\t\t\tstate === Platformer2DState.Walking ||\n\t\t\tstate === Platformer2DState.Running ||\n\t\t\tstate === Platformer2DState.Landing\n\t\t);\n\t}\n\n\tgetJumpCount(): number {\n\t\treturn this.ctx.state.jumpCount;\n\t}\n\n\t/**\n\t * Update FSM based on current component state.\n\t */\n\tupdate(input: Platformer2DInputComponent, state: Platformer2DStateComponent): void {\n\t\tthis.ctx.input = input;\n\t\tthis.ctx.state = state;\n\n\t\tconst currentState = this.getState();\n\t\tconst hasInput = Math.abs(input.moveX) > 0.1;\n\t\tconst isRunning = input.run;\n\n\t\t// Handle landing: if we're in falling state and now grounded, land\n\t\tif (currentState === Platformer2DState.Falling && state.grounded) {\n\t\t\tthis.dispatch(Platformer2DEvent.Land);\n\t\t\t// Don't return - allow immediate transition to movement state\n\t\t}\n\n\t\tif (state.grounded) {\n\t\t\tif (hasInput) {\n\t\t\t\tif (isRunning) {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Run);\n\t\t\t\t} else {\n\t\t\t\t\tthis.dispatch(Platformer2DEvent.Walk);\n\t\t\t\t}\n\t\t\t} else {\n\t\t\t\tthis.dispatch(Platformer2DEvent.Stop);\n\t\t\t}\n\t\t} else if (state.falling) {\n\t\t\tthis.dispatch(Platformer2DEvent.Fall);\n\t\t}\n\n\t\t// Handle jump input (edge detection handled in the system)\n\t\tif (input.jump && !state.jumpPressedLastFrame) {\n\t\t\tthis.dispatch(Platformer2DEvent.Jump);\n\t\t}\n\t}\n}\n","/**\n * Platformer 2D Behavior Descriptor\n *\n * Type-safe descriptor for the platformer 2D behavior system.\n * Provides entity.use() API for 2D platformer movement on the X/Y plane.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { Platformer2DState } from './platformer-2d-fsm';\nimport {\n\tcreatePlatformer2DMovementComponent,\n\tcreatePlatformer2DInputComponent,\n\tcreatePlatformer2DStateComponent,\n\ttype Platformer2DMovementComponent,\n\ttype Platformer2DInputComponent,\n\ttype Platformer2DStateComponent,\n} from './components';\nimport {\n\tSTAGE_INPUT_BUTTON_JUMP,\n\tSTAGE_INPUT_BUTTON_RUN,\n\treadBehaviorSnapshot,\n\tStagePlatformerShapeSource,\n\ttype BehaviorRuntime,\n\ttype StagePlatformer2DOptions,\n} from '../../runtime/wasm-stage-runtime';\n\n/** Rust FSM index → TS state (shared `platformer_3d/fsm.rs` ordering). */\nconst FSM_STATE_BY_INDEX: readonly Platformer2DState[] = [\n\tPlatformer2DState.Idle,\n\tPlatformer2DState.Walking,\n\tPlatformer2DState.Running,\n\tPlatformer2DState.Jumping,\n\tPlatformer2DState.Falling,\n\tPlatformer2DState.Landing,\n];\n\n/**\n * Platformer 2D behavior options (typed for entity.use() autocomplete)\n */\nexport interface Platformer2DBehaviorOptions {\n\t/** Base walking speed (default: 8) */\n\twalkSpeed?: number;\n\t/** Sprint/run speed (default: 14) */\n\trunSpeed?: number;\n\t/** Initial jump force (default: 12) */\n\tjumpForce?: number;\n\t/** Maximum number of jumps (default: 1) */\n\tmaxJumps?: number;\n\t/** Gravity force (default: 20) */\n\tgravity?: number;\n\t/** Coyote time in seconds (default: 0.1) */\n\tcoyoteTime?: number;\n\t/** Jump buffer time in seconds (default: 0.1) */\n\tjumpBufferTime?: number;\n\t/** Velocity multiplier when releasing jump early (default: 0.5) */\n\tjumpCutMultiplier?: number;\n\t/** Maximum slope angle in degrees the character can walk up (default: 50) */\n\tslopeClimbAngle?: number;\n\t/** Slope angle in degrees past which the character slides (default: 40) */\n\tslopeSlideAngle?: number;\n\t/** Autostep over small step-ups, or false to disable (default: { maxHeight: 0.25, minWidth: 0.05 }) */\n\tautostep?: { maxHeight: number; minWidth: number } | false;\n\t/** Snap-to-ground distance for downhill/step-down stability (default: 0.2) */\n\tsnapToGroundDistance?: number;\n\t/** Character controller collision offset (default: 0.05) */\n\tcharacterOffset?: number;\n\t/** Explicit KCC capsule override (requires `useAttachedCollider: false`). */\n\tshape?: {\n\t\thalfHeight: number;\n\t\tradius: number;\n\t\toffset?: [number, number, number];\n\t};\n\t/** When true (default), KCC reads shape + offset from the attached Rapier collider. */\n\tuseAttachedCollider?: boolean;\n}\n\nconst defaultOptions: Platformer2DBehaviorOptions = {\n\twalkSpeed: 8,\n\trunSpeed: 14,\n\tjumpForce: 12,\n\tmaxJumps: 1,\n\tgravity: 20,\n\tcoyoteTime: 0.1,\n\tjumpBufferTime: 0.1,\n\tjumpCutMultiplier: 0.5,\n\tslopeClimbAngle: 50,\n\tslopeSlideAngle: 40,\n\tautostep: { maxHeight: 0.25, minWidth: 0.05 },\n\tsnapToGroundDistance: 0.2,\n\tcharacterOffset: 0.05,\n};\n\nconst PLATFORMER_2D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:platformer-2d');\n\nfunction resolvePlatformer2DAttachShape(\n\toptions: Platformer2DBehaviorOptions,\n\tgameEntity?: {\n\t\tcolliderDesc?: {\n\t\t\tshape?: { type?: string; halfHeight?: number; radius?: number };\n\t\t\toffset?: [number, number, number];\n\t\t};\n\t},\n): Pick<StagePlatformer2DOptions, 'halfHeight' | 'radius' | 'offsetX' | 'offsetY' | 'offsetZ' | 'shapeSource'> {\n\tconst fallback = {\n\t\thalfHeight: 0.9,\n\t\tradius: 0.4,\n\t\toffsetX: 0,\n\t\toffsetY: 0,\n\t\toffsetZ: 0,\n\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t};\n\n\tif (options.useAttachedCollider === false && options.shape) {\n\t\tconst offset = options.shape.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: options.shape.halfHeight,\n\t\t\tradius: options.shape.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Override,\n\t\t};\n\t}\n\n\tconst collider = gameEntity?.colliderDesc;\n\tif (collider?.shape?.type === 'capsule') {\n\t\tconst offset = collider.offset ?? [0, 0, 0];\n\t\treturn {\n\t\t\thalfHeight: collider.shape.halfHeight ?? fallback.halfHeight,\n\t\t\tradius: collider.shape.radius ?? fallback.radius,\n\t\t\toffsetX: offset[0],\n\t\t\toffsetY: offset[1],\n\t\t\toffsetZ: offset[2],\n\t\t\tshapeSource: StagePlatformerShapeSource.Auto,\n\t\t};\n\t}\n\n\treturn fallback;\n}\n\n/**\n * Entity with 2D platformer components (input written by the game; the wasm\n * runtime owns the kinematic character controller and FSM).\n */\nexport interface Platformer2DEntity {\n\tuuid: string;\n\tplatformer2D: Platformer2DMovementComponent;\n\t$platformer2D: Platformer2DInputComponent;\n\tplatformer2DState: Platformer2DStateComponent;\n}\n\n/**\n * Forwards 2D platformer input into the wasm runtime. Entities with a\n * `runtimeHandle` are attached once to the Stage runtime and receive input\n * via the slot-indexed input buffer each frame; the Rust `platformer_2d` system\n * is the single simulation path.\n */\nclass Platformer2DBehaviorSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(PLATFORMER_2D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst platformerRef = link.ref as any;\n\n\t\t\tconst options = platformerRef.options as Platformer2DBehaviorOptions;\n\n\t\t\tif (!gameEntity.platformer2D) {\n\t\t\t\tgameEntity.platformer2D = createPlatformer2DMovementComponent(options);\n\t\t\t}\n\t\t\tif (!gameEntity.$platformer2D) {\n\t\t\t\tgameEntity.$platformer2D = createPlatformer2DInputComponent();\n\t\t\t}\n\t\t\tif (!gameEntity.platformer2DState) {\n\t\t\t\tgameEntity.platformer2DState = createPlatformer2DStateComponent();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options, gameEntity);\n\t\t\tplatformerRef.__wasmStage = this.wasmStage;\n\t\t\tplatformerRef.__entity = gameEntity;\n\t\t\tconst input = gameEntity.$platformer2D;\n\t\t\tif (input) {\n\t\t\t\tthis.wasmStage.writeInputAxes(handle, input.moveX ?? 0, 0);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_JUMP, !!input.jump);\n\t\t\t\tthis.wasmStage.writeInputButton(handle, STAGE_INPUT_BUTTON_RUN, !!input.run);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(\n\t\thandle: number,\n\t\toptions: Platformer2DBehaviorOptions,\n\t\tgameEntity?: { colliderDesc?: { shape?: { type?: string; halfHeight?: number; radius?: number }; offset?: [number, number, number] } },\n\t): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tconst autostep = options.autostep ?? defaultOptions.autostep!;\n\t\tconst shape = resolvePlatformer2DAttachShape(options, gameEntity);\n\t\tthis.wasmStage.attachPlatformer2D(handle, {\n\t\t\t...shape,\n\t\t\twalkSpeed: options.walkSpeed ?? defaultOptions.walkSpeed!,\n\t\t\trunSpeed: options.runSpeed ?? defaultOptions.runSpeed!,\n\t\t\tjumpForce: options.jumpForce ?? defaultOptions.jumpForce!,\n\t\t\tmaxJumps: options.maxJumps ?? defaultOptions.maxJumps!,\n\t\t\tgravity: options.gravity ?? defaultOptions.gravity!,\n\t\t\tcoyoteTime: options.coyoteTime ?? defaultOptions.coyoteTime!,\n\t\t\tjumpBufferTime: options.jumpBufferTime ?? defaultOptions.jumpBufferTime!,\n\t\t\tjumpCutMultiplier: options.jumpCutMultiplier ?? defaultOptions.jumpCutMultiplier!,\n\t\t\tslopeClimbAngle: options.slopeClimbAngle ?? defaultOptions.slopeClimbAngle!,\n\t\t\tslopeSlideAngle: options.slopeSlideAngle ?? defaultOptions.slopeSlideAngle!,\n\t\t\tautostepHeight: autostep ? autostep.maxHeight : 0,\n\t\t\tautostepMinWidth: autostep ? autostep.minWidth : 0.05,\n\t\t\tsnapToGroundDistance: options.snapToGroundDistance ?? defaultOptions.snapToGroundDistance!,\n\t\t\tcharacterOffset: options.characterOffset ?? defaultOptions.characterOffset!,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * Platformer2DBehavior - typed descriptor for 2D platformer movement\n *\n * Provides complete 2D platformer physics on the X/Y plane including:\n * - Walking and running on X\n * - Jumping with buffer, coyote time, multi-jump, and jump-cut\n * - Gravity ownership and falling\n * - Kinematic character controller (collide-and-slide, autostep, snap-to-ground)\n *\n * @example\n * ```typescript\n * import { Platformer2DBehavior } from \"@zylem/behaviors/platformer-2d\";\n *\n * const platformer = player.use(Platformer2DBehavior, {\n * walkSpeed: 8,\n * runSpeed: 14,\n * jumpForce: 12,\n * maxJumps: 2, // Double jump!\n * });\n *\n * // In update loop\n * player.onUpdate(({ inputs }) => {\n * player.$platformer2D.moveX = inputs.p1.axes.Horizontal.value;\n * player.$platformer2D.jump = inputs.p1.buttons.A.held > 0;\n * player.$platformer2D.run = inputs.p1.shoulders.LTrigger.held > 0;\n *\n * const state = platformer.getState();\n * const grounded = platformer.isGrounded();\n * });\n * ```\n */\nexport const Platformer2DBehavior = defineBehavior<\n\tPlatformer2DBehaviorOptions,\n\t{\n\t\tgetState: () => Platformer2DState;\n\t\tisGrounded: () => boolean;\n\t\tgetJumpCount: () => number;\n\t},\n\tPlatformer2DEntity\n>({\n\tname: 'platformer-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Platformer2DBehaviorSystem(\n\t\t\tctx.wasmStage ?? null,\n\t\t\tctx.getBehaviorLinks,\n\t\t),\n\t// Handles read the runtime snapshot buffer (values are from the end of\n\t// the previous step — one-frame lag). The Rust FSM is the single source\n\t// of truth.\n\tcreateHandle: (ref) => ({\n\t\tgetState: () => {\n\t\t\tconst snapshot = readBehaviorSnapshot(ref);\n\t\t\tif (snapshot) return FSM_STATE_BY_INDEX[snapshot.fsmState] ?? Platformer2DState.Idle;\n\t\t\treturn Platformer2DState.Idle;\n\t\t},\n\t\tisGrounded: () => readBehaviorSnapshot(ref)?.grounded ?? false,\n\t\tgetJumpCount: () => readBehaviorSnapshot(ref)?.jumpCount ?? 0,\n\t}),\n});\n"],"mappings":"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","names":["Platformer2DState","Platformer2DEvent"]}
@@ -1 +0,0 @@
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 +0,0 @@
1
- {"version":3,"sources":["../src/lib/behaviors/world-boundary-2d/world-boundary-2d-fsm.ts","../src/lib/behaviors/world-boundary-2d/world-boundary-2d.descriptor.ts"],"sourcesContent":["/**\n * WorldBoundary2DFSM\n *\n * Minimal FSM + extended state to track which world boundaries were hit.\n *\n * Notes:\n * - \"Hit boundaries\" is inherently a *set* (can hit left+bottom in one frame),\n * so we store it as extended state (`lastHits`) rather than a single FSM state.\n * - The FSM state is still useful for coarse status like \"inside\" vs \"touching\".\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport {\n\tcomputeBounds2DHits,\n\tconstrainMovementToBounds2D,\n\thasAnyBounds2DHit,\n} from '../shared/bounds-2d';\n\nexport type WorldBoundary2DHit = 'top' | 'bottom' | 'left' | 'right';\nexport type WorldBoundary2DHits = Record<WorldBoundary2DHit, boolean>;\n\nexport interface WorldBoundary2DPosition {\n\tx: number;\n\ty: number;\n}\n\nexport interface WorldBoundary2DBounds {\n\ttop: number;\n\tbottom: number;\n\tleft: number;\n\tright: number;\n}\n\nexport enum WorldBoundary2DState {\n\tInside = 'inside',\n\tTouching = 'touching',\n}\n\nexport enum WorldBoundary2DEvent {\n\tEnterInside = 'enter-inside',\n\tTouchBoundary = 'touch-boundary',\n}\n\n/**\n * Compute which boundaries are being hit for a position and bounds.\n * This matches the semantics of the legacy `boundary2d` behavior:\n * - left hit if x <= left\n * - right hit if x >= right\n * - bottom hit if y <= bottom\n * - top hit if y >= top\n */\nexport function computeWorldBoundary2DHits(\n\tposition: WorldBoundary2DPosition,\n\tbounds: WorldBoundary2DBounds\n): WorldBoundary2DHits {\n\treturn computeBounds2DHits(position, {\n\t\tminX: bounds.left,\n\t\tmaxX: bounds.right,\n\t\tminY: bounds.bottom,\n\t\tmaxY: bounds.top,\n\t});\n}\n\nexport function hasAnyWorldBoundary2DHit(hits: WorldBoundary2DHits): boolean {\n\treturn hasAnyBounds2DHit(hits);\n}\n\n/**\n * FSM wrapper with \"extended state\" (lastHits / lastPosition).\n * Systems should call `update(...)` once per frame.\n */\nexport class WorldBoundary2DFSM {\n\tpublic readonly machine: SyncStateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>;\n\n\tprivate lastHits: WorldBoundary2DHits = { top: false, bottom: false, left: false, right: false };\n\tprivate lastPosition: WorldBoundary2DPosition | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<WorldBoundary2DState, WorldBoundary2DEvent, never>(\n\t\t\tWorldBoundary2DState.Inside,\n\t\t\t[\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\n\t\t\t\t// Self transitions (no-ops)\n\t\t\t\tt(WorldBoundary2DState.Inside, WorldBoundary2DEvent.EnterInside, WorldBoundary2DState.Inside),\n\t\t\t\tt(WorldBoundary2DState.Touching, WorldBoundary2DEvent.TouchBoundary, WorldBoundary2DState.Touching),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): WorldBoundary2DState {\n\t\treturn this.machine.getState();\n\t}\n\n\t/**\n\t * Returns the last computed hits (always available after first update call).\n\t */\n\tgetLastHits(): WorldBoundary2DHits {\n\t\treturn this.lastHits;\n\t}\n\n\t/**\n\t * Returns adjusted movement values based on boundary hits.\n\t * If the entity is touching a boundary and trying to move further into it,\n\t * that axis component is zeroed out.\n\t *\n\t * @param moveX - The desired X movement\n\t * @param moveY - The desired Y movement\n\t * @returns Adjusted { moveX, moveY } with boundary-blocked axes zeroed\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number } {\n\t\treturn constrainMovementToBounds2D(this.lastHits, moveX, moveY);\n\t}\n\n\t/**\n\t * Returns the last position passed to `update`, if any.\n\t */\n\tgetLastPosition(): WorldBoundary2DPosition | null {\n\t\treturn this.lastPosition;\n\t}\n\n\t/**\n\t * Best-effort timestamp (ms) of the last `update(...)` call.\n\t * This is optional metadata; systems can ignore it.\n\t */\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\t/**\n\t * Update FSM + extended state based on current position and bounds.\n\t * Returns the computed hits for convenience.\n\t */\n\tupdate(position: WorldBoundary2DPosition, bounds: WorldBoundary2DBounds): WorldBoundary2DHits {\n\t\tconst hits = computeWorldBoundary2DHits(position, bounds);\n\n\t\tthis.lastHits = hits;\n\t\tthis.lastPosition = { x: position.x, y: position.y };\n\t\tthis.lastUpdatedAtMs = Date.now();\n\n\t\tif (hasAnyWorldBoundary2DHit(hits)) {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.TouchBoundary);\n\t\t} else {\n\t\t\tthis.dispatch(WorldBoundary2DEvent.EnterInside);\n\t\t}\n\n\t\treturn hits;\n\t}\n\n\tprivate dispatch(event: WorldBoundary2DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n}\n","/**\n * WorldBoundary2DBehavior\n *\n * Tracks which 2D world boundaries an entity is touching.\n * Use `getMovement()` on the behavior handle to adjust movement based on hits.\n *\n * Source of truth: the WASM stage boundary query, consistent with other behaviors.\n */\n\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tWorldBoundary2DFSM,\n\ttype WorldBoundary2DBounds,\n\ttype WorldBoundary2DHits,\n} from './world-boundary-2d-fsm';\nimport {\n\tStageBoundaryDim,\n\ttype BehaviorRuntime,\n} from '../../runtime/wasm-stage-runtime';\n\nexport interface WorldBoundary2DOptions {\n\t/**\n\t * World boundaries (in world units).\n\t * - left hit if x <= left\n\t * - right hit if x >= right\n\t * - bottom hit if y <= bottom\n\t * - top hit if y >= top\n\t */\n\tboundaries: WorldBoundary2DBounds;\n}\n\n/**\n * Handle methods provided by WorldBoundary2DBehavior\n */\nexport interface WorldBoundary2DHandle {\n\t/**\n\t * Get the last computed boundary hits.\n\t * Returns null until entity is spawned and FSM is initialized.\n\t */\n\tgetLastHits(): WorldBoundary2DHits | null;\n\n\t/**\n\t * Get adjusted movement values based on boundary hits.\n\t * Zeros out movement into boundaries the entity is touching.\n\t */\n\tgetMovement(moveX: number, moveY: number): { moveX: number; moveY: number };\n}\n\nconst defaultOptions: WorldBoundary2DOptions = {\n\tboundaries: { top: 0, bottom: 0, left: 0, right: 0 },\n};\n\nconst WORLD_BOUNDARY_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:world-boundary-2d',\n);\n\n/**\n * Creates behavior-specific handle methods for WorldBoundary2DBehavior.\n */\nfunction createWorldBoundary2DHandle(\n\tref: BehaviorRef<WorldBoundary2DOptions>\n): WorldBoundary2DHandle {\n\treturn {\n\t\tgetLastHits: () => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getLastHits() ?? null;\n\t\t},\n\t\tgetMovement: (moveX: number, moveY: number) => {\n\t\t\tconst fsm = ref.fsm as WorldBoundary2DFSM | undefined;\n\t\t\treturn fsm?.getMovement(moveX, moveY) ?? { moveX, moveY };\n\t\t},\n\t};\n}\n\n/**\n * WorldBoundary2DSystem\n *\n * Stage-level system that:\n * - finds entities with this behavior attached\n * - computes and tracks boundary hits using the FSM\n *\n * When a {@link BehaviorRuntime} is present in `BehaviorSystemContext` and the\n * entity has been registered with the unified Stage (`runtimeHandle >= 0`), the\n * system uses the Rust `world_boundary` behavior as the source of truth and\n * mirrors its query into the local FSM so handle methods (`getLastHits`,\n * `getMovement`) return the same shape as the legacy code path.\n */\nclass WorldBoundary2DSystem implements BehaviorSystem {\n\tprivate attachedRuntimeSlots = new Set<number>();\n\n\tconstructor(\n\t\tprivate wasmStage: BehaviorRuntime | null,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: unknown, _delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(WORLD_BOUNDARY_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 WorldBoundary2DOptions;\n\n\t\t\tif (!boundaryRef.fsm) {\n\t\t\t\tboundaryRef.fsm = new WorldBoundary2DFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options.boundaries);\n\t\t\tconst query = this.wasmStage.queryWorldBoundary(handle);\n\t\t\tif (query) {\n\t\t\t\tboundaryRef.fsm.update(\n\t\t\t\t\t{ x: query.clamped[0], y: query.clamped[1] },\n\t\t\t\t\toptions.boundaries,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, bounds: WorldBoundary2DBounds): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachWorldBoundary(handle, StageBoundaryDim.Two, {\n\t\t\ttop: bounds.top,\n\t\t\tbottom: bounds.bottom,\n\t\t\tleft: bounds.left,\n\t\t\tright: bounds.right,\n\t\t\tfront: 0,\n\t\t\tback: 0,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * WorldBoundary2DBehavior\n *\n * @example\n * ```ts\n * import { WorldBoundary2DBehavior } from \"@zylem/game-lib/behavior\";\n *\n * const ship = createSprite({ ... });\n * const boundary = ship.use(WorldBoundary2DBehavior, {\n * boundaries: { left: -10, right: 10, bottom: -7.5, top: 7.5 },\n * });\n *\n * ship.onUpdate(({ me }) => {\n * let moveX = ..., moveY = ...;\n * const hits = boundary.getLastHits(); // Fully typed!\n * ({ moveX, moveY } = boundary.getMovement(moveX, moveY));\n * me.moveXY(moveX, moveY);\n * });\n * ```\n */\nexport const WorldBoundary2DBehavior = defineBehavior({\n\tname: 'world-boundary-2d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew WorldBoundary2DSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n\tcreateHandle: createWorldBoundary2DHandle,\n});\n"],"mappings":";;;;;;;;;;;;;;;;;AAiCO,IAAK,uBAAL,kBAAKA,0BAAL;AACN,EAAAA,sBAAA,YAAS;AACT,EAAAA,sBAAA,cAAW;AAFA,SAAAA;AAAA,GAAA;AAKL,IAAK,uBAAL,kBAAKC,0BAAL;AACN,EAAAA,sBAAA,iBAAc;AACd,EAAAA,sBAAA,mBAAgB;AAFL,SAAAA;AAAA,GAAA;AAaL,SAAS,2BACf,UACA,QACsB;AACtB,SAAO,oBAAoB,UAAU;AAAA,IACpC,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,IACb,MAAM,OAAO;AAAA,EACd,CAAC;AACF;AAEO,SAAS,yBAAyB,MAAoC;AAC5E,SAAO,kBAAkB,IAAI;AAC9B;AAMO,IAAM,qBAAN,MAAyB;AAAA,EACf;AAAA,EAER,WAAgC,EAAE,KAAK,OAAO,QAAQ,OAAO,MAAM,OAAO,OAAO,MAAM;AAAA,EACvF,eAA+C;AAAA,EAC/C,kBAAiC;AAAA,EAEzC,cAAc;AACb,SAAK,UAAU,IAAI;AAAA,MAClB;AAAA,MACA;AAAA,YACC,yBAAE,uBAA6B,sCAAoC,yBAA6B;AAAA,YAChG,yBAAE,2BAA+B,kCAAkC,qBAA2B;AAAA;AAAA,YAG9F,yBAAE,uBAA6B,kCAAkC,qBAA2B;AAAA,YAC5F,yBAAE,2BAA+B,sCAAoC,yBAA6B;AAAA,MACnG;AAAA,IACD;AAAA,EACD;AAAA,EAEA,WAAiC;AAChC,WAAO,KAAK,QAAQ,SAAS;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA,EAKA,cAAmC;AAClC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWA,YAAY,OAAe,OAAiD;AAC3E,WAAO,4BAA4B,KAAK,UAAU,OAAO,KAAK;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA,EAKA,kBAAkD;AACjD,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAAoC;AACnC,WAAO,KAAK;AAAA,EACb;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,OAAO,UAAmC,QAAoD;AAC7F,UAAM,OAAO,2BAA2B,UAAU,MAAM;AAExD,SAAK,WAAW;AAChB,SAAK,eAAe,EAAE,GAAG,SAAS,GAAG,GAAG,SAAS,EAAE;AACnD,SAAK,kBAAkB,KAAK,IAAI;AAEhC,QAAI,yBAAyB,IAAI,GAAG;AACnC,WAAK,SAAS,oCAAkC;AAAA,IACjD,OAAO;AACN,WAAK,SAAS,gCAAgC;AAAA,IAC/C;AAEA,WAAO;AAAA,EACR;AAAA,EAEQ,SAAS,OAAmC;AACnD,QAAI,KAAK,QAAQ,IAAI,KAAK,GAAG;AAC5B,WAAK,QAAQ,aAAa,KAAK;AAAA,IAChC;AAAA,EACD;AACD;;;AC3GA,IAAM,iBAAyC;AAAA,EAC9C,YAAY,EAAE,KAAK,GAAG,QAAQ,GAAG,MAAM,GAAG,OAAO,EAAE;AACpD;AAEA,IAAM,8BAA8B,uBAAO;AAAA,EAC1C;AACD;AAKA,SAAS,4BACR,KACwB;AACxB,SAAO;AAAA,IACN,aAAa,MAAM;AAClB,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,YAAY,KAAK;AAAA,IAC9B;AAAA,IACA,aAAa,CAAC,OAAe,UAAkB;AAC9C,YAAM,MAAM,IAAI;AAChB,aAAO,KAAK,YAAY,OAAO,KAAK,KAAK,EAAE,OAAO,MAAM;AAAA,IACzD;AAAA,EACD;AACD;AAeA,IAAM,wBAAN,MAAsD;AAAA,EAGrD,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,2BAA2B;AACjE,QAAI,CAAC,MAAO;AAEZ,eAAW,QAAQ,OAAO;AACzB,YAAM,aAAa,KAAK;AACxB,YAAM,cAAc,KAAK;AACzB,YAAM,UAAU,YAAY;AAE5B,UAAI,CAAC,YAAY,KAAK;AACrB,oBAAY,MAAM,IAAI,mBAAmB;AAAA,MAC1C;AAEA,YAAM,SAAU,WAAW,iBAAiB;AAC5C,UAAI,CAAC,KAAK,aAAa,SAAS,EAAG;AAEnC,WAAK,mBAAmB,QAAQ,QAAQ,UAAU;AAClD,YAAM,QAAQ,KAAK,UAAU,mBAAmB,MAAM;AACtD,UAAI,OAAO;AACV,oBAAY,IAAI;AAAA,UACf,EAAE,GAAG,MAAM,QAAQ,CAAC,GAAG,GAAG,MAAM,QAAQ,CAAC,EAAE;AAAA,UAC3C,QAAQ;AAAA,QACT;AAAA,MACD;AAAA,IACD;AAAA,EACD;AAAA,EAEQ,mBAAmB,QAAgB,QAAqC;AAC/E,QAAI,CAAC,KAAK,aAAa,KAAK,qBAAqB,IAAI,MAAM,EAAG;AAC9D,SAAK,UAAU,oBAAoB,QAAQ,iBAAiB,KAAK;AAAA,MAChE,KAAK,OAAO;AAAA,MACZ,QAAQ,OAAO;AAAA,MACf,MAAM,OAAO;AAAA,MACb,OAAO,OAAO;AAAA,MACd,OAAO;AAAA,MACP,MAAM;AAAA,IACP,CAAC;AACD,SAAK,qBAAqB,IAAI,MAAM;AAAA,EACrC;AAAA,EAEA,QAAQ,MAAqB;AAC5B,SAAK,qBAAqB,MAAM;AAAA,EACjC;AACD;AAsBO,IAAM,0BAA0B,eAAe;AAAA,EACrD,MAAM;AAAA,EACN;AAAA,EACA,eAAe,CAAC,QACf,IAAI,sBAAsB,IAAI,aAAa,MAAM,IAAI,gBAAgB;AAAA,EACtE,cAAc;AACf,CAAC;","names":["WorldBoundary2DState","WorldBoundary2DEvent"]}