@zylem/behaviors 0.4.1 → 0.5.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/README.md +13 -1
  2. package/dist/{behavior-system-y8SGrGWy.d.ts → behavior-system-CeJXzNvr.d.ts} +305 -17
  3. package/dist/{chunk-GFARAJV2.js → chunk-2PAHQZZD.js} +44 -73
  4. package/dist/chunk-2PAHQZZD.js.map +1 -0
  5. package/dist/chunk-52FJIDGL.js +30 -0
  6. package/dist/chunk-52FJIDGL.js.map +1 -0
  7. package/dist/{chunk-UYFOLI2X.js → chunk-5A5AYP2C.js} +1 -1
  8. package/dist/{chunk-UYFOLI2X.js.map → chunk-5A5AYP2C.js.map} +1 -1
  9. package/dist/{chunk-A4CA35DQ.js → chunk-5EBIIYZD.js} +25 -45
  10. package/dist/chunk-5EBIIYZD.js.map +1 -0
  11. package/dist/{chunk-4L36OPY2.js → chunk-5NJGASUJ.js} +81 -135
  12. package/dist/chunk-5NJGASUJ.js.map +1 -0
  13. package/dist/{chunk-27UPCP5B.js → chunk-5UMCWV5A.js} +22 -13
  14. package/dist/chunk-5UMCWV5A.js.map +1 -0
  15. package/dist/chunk-5WRI5ZAA.js +31 -0
  16. package/dist/chunk-5WRI5ZAA.js.map +1 -0
  17. package/dist/{chunk-5SM5SH5O.js → chunk-7JLWZXPA.js} +2 -2
  18. package/dist/{chunk-Z72F2ENM.js → chunk-AEBDZWCP.js} +17 -26
  19. package/dist/chunk-AEBDZWCP.js.map +1 -0
  20. package/dist/chunk-AS3HR7AS.js +188 -0
  21. package/dist/chunk-AS3HR7AS.js.map +1 -0
  22. package/dist/chunk-E4X4YJZB.js +841 -0
  23. package/dist/chunk-E4X4YJZB.js.map +1 -0
  24. package/dist/{chunk-VMQHNJJI.js → chunk-F6ESSHQK.js} +17 -23
  25. package/dist/chunk-F6ESSHQK.js.map +1 -0
  26. package/dist/chunk-GQAPBNGT.js +210 -0
  27. package/dist/chunk-GQAPBNGT.js.map +1 -0
  28. package/dist/chunk-JGXRQUTC.js +191 -0
  29. package/dist/chunk-JGXRQUTC.js.map +1 -0
  30. package/dist/{chunk-BXP4K7RF.js → chunk-JVADRLWX.js} +57 -186
  31. package/dist/chunk-JVADRLWX.js.map +1 -0
  32. package/dist/{chunk-ZA43LPRM.js → chunk-KIYUUSPK.js} +2 -2
  33. package/dist/{chunk-QDCJLJUV.js → chunk-KOWGILCN.js} +22 -13
  34. package/dist/chunk-KOWGILCN.js.map +1 -0
  35. package/dist/{chunk-WWVD5DR5.js → chunk-M3QDKKDF.js} +2 -8
  36. package/dist/chunk-M3QDKKDF.js.map +1 -0
  37. package/dist/{chunk-FTDZAKZW.js → chunk-NQPYMUDF.js} +7 -5
  38. package/dist/{chunk-FTDZAKZW.js.map → chunk-NQPYMUDF.js.map} +1 -1
  39. package/dist/{chunk-XP3ASVGJ.js → chunk-NW4SE5XW.js} +2 -2
  40. package/dist/{chunk-KDY5N6J4.js → chunk-OPZ2LVS2.js} +1 -22
  41. package/dist/{chunk-KDY5N6J4.js.map → chunk-OPZ2LVS2.js.map} +1 -1
  42. package/dist/{chunk-5XZWTFGF.js → chunk-PLIXVF7B.js} +3 -3
  43. package/dist/chunk-PLIXVF7B.js.map +1 -0
  44. package/dist/{chunk-Y7YIAWPW.js → chunk-SGHIGTVZ.js} +1 -11
  45. package/dist/{chunk-Y7YIAWPW.js.map → chunk-SGHIGTVZ.js.map} +1 -1
  46. package/dist/{chunk-UL6Q5HDP.js → chunk-TBUKJKY6.js} +3 -25
  47. package/dist/chunk-TBUKJKY6.js.map +1 -0
  48. package/dist/chunk-VFDUKGRZ.js +155 -0
  49. package/dist/chunk-VFDUKGRZ.js.map +1 -0
  50. package/dist/chunk-VT3H43QA.js +288 -0
  51. package/dist/chunk-VT3H43QA.js.map +1 -0
  52. package/dist/chunk-W26PHBME.js +309 -0
  53. package/dist/chunk-W26PHBME.js.map +1 -0
  54. package/dist/components-BANNjAZP.d.ts +30 -0
  55. package/dist/cooldown.d.ts +2 -2
  56. package/dist/cooldown.js +1 -0
  57. package/dist/coordinators/boundary-ricochet-3d.d.ts +4 -4
  58. package/dist/coordinators/boundary-ricochet-3d.js +1 -0
  59. package/dist/coordinators/boundary-ricochet.d.ts +4 -4
  60. package/dist/coordinators/boundary-ricochet.js +3 -2
  61. package/dist/coordinators/first-person-shooter.js +1 -0
  62. package/dist/coordinators/multidirectional-space-shooter.d.ts +3 -3
  63. package/dist/coordinators/multidirectional-space-shooter.js +5 -3
  64. package/dist/coordinators/top-down-shooter.d.ts +3 -3
  65. package/dist/coordinators/top-down-shooter.js +5 -3
  66. package/dist/core.d.ts +288 -8
  67. package/dist/core.js +480 -13
  68. package/dist/core.js.map +1 -1
  69. package/dist/destructible-3d.d.ts +32 -4
  70. package/dist/destructible-3d.js +2 -1
  71. package/dist/destructible-prebake.js +1 -0
  72. package/dist/{entity-types-CmtQ-IBp.d.ts → entity-types-DhfsZbSK.d.ts} +5 -3
  73. package/dist/first-person.d.ts +27 -38
  74. package/dist/first-person.js +8 -8
  75. package/dist/index.d.ts +14 -15
  76. package/dist/index.js +37 -52
  77. package/dist/jumper-2d.d.ts +3 -17
  78. package/dist/jumper-2d.js +4 -9
  79. package/dist/jumper-3d.d.ts +5 -39
  80. package/dist/jumper-3d.js +5 -10
  81. package/dist/{moveable-Cl9h-_w7.d.ts → moveable-XhzWPAw5.d.ts} +21 -4
  82. package/dist/particle-emitter/presets.js +1 -0
  83. package/dist/particle-emitter.d.ts +3 -3
  84. package/dist/particle-emitter.js +3 -2
  85. package/dist/platformer-2d.d.ts +13 -82
  86. package/dist/platformer-2d.js +4 -5
  87. package/dist/platformer-3d.d.ts +13 -78
  88. package/dist/platformer-3d.js +4 -5
  89. package/dist/ricochet-2d.d.ts +3 -3
  90. package/dist/ricochet-2d.js +4 -3
  91. package/dist/ricochet-3d.d.ts +3 -3
  92. package/dist/ricochet-3d.js +4 -3
  93. package/dist/runtime-2d.d.ts +2 -2
  94. package/dist/runtime-2d.js +1 -0
  95. package/dist/runtime-2d.js.map +1 -1
  96. package/dist/screen-visibility.d.ts +2 -2
  97. package/dist/screen-visibility.js +1 -0
  98. package/dist/screen-wrap.d.ts +2 -2
  99. package/dist/screen-wrap.js +4 -3
  100. package/dist/shooter-2d.d.ts +3 -3
  101. package/dist/shooter-2d.js +4 -3
  102. package/dist/thruster.d.ts +5 -34
  103. package/dist/thruster.js +4 -6
  104. package/dist/top-down-movement.d.ts +9 -27
  105. package/dist/top-down-movement.js +6 -6
  106. package/dist/{vector-Bm65-FOG.d.ts → vector-DjhLUrak.d.ts} +2 -4
  107. package/dist/world-boundary-2d.d.ts +2 -2
  108. package/dist/world-boundary-2d.js +5 -4
  109. package/dist/world-boundary-3d.d.ts +2 -2
  110. package/dist/world-boundary-3d.js +4 -3
  111. package/package.json +2 -3
  112. package/dist/chunk-27UPCP5B.js.map +0 -1
  113. package/dist/chunk-4L36OPY2.js.map +0 -1
  114. package/dist/chunk-4URF65BR.js +0 -44
  115. package/dist/chunk-4URF65BR.js.map +0 -1
  116. package/dist/chunk-5XZWTFGF.js.map +0 -1
  117. package/dist/chunk-67XLJ26X.js +0 -536
  118. package/dist/chunk-67XLJ26X.js.map +0 -1
  119. package/dist/chunk-75FJIUIG.js +0 -240
  120. package/dist/chunk-75FJIUIG.js.map +0 -1
  121. package/dist/chunk-A4CA35DQ.js.map +0 -1
  122. package/dist/chunk-BXP4K7RF.js.map +0 -1
  123. package/dist/chunk-F72WX4MH.js +0 -422
  124. package/dist/chunk-F72WX4MH.js.map +0 -1
  125. package/dist/chunk-GFARAJV2.js.map +0 -1
  126. package/dist/chunk-JXMF4NUI.js +0 -309
  127. package/dist/chunk-JXMF4NUI.js.map +0 -1
  128. package/dist/chunk-OTLSUYQQ.js +0 -29
  129. package/dist/chunk-OTLSUYQQ.js.map +0 -1
  130. package/dist/chunk-Q75LKHDF.js +0 -56
  131. package/dist/chunk-Q75LKHDF.js.map +0 -1
  132. package/dist/chunk-QDCJLJUV.js.map +0 -1
  133. package/dist/chunk-RDHSHEZM.js +0 -479
  134. package/dist/chunk-RDHSHEZM.js.map +0 -1
  135. package/dist/chunk-UIG7MWS2.js +0 -518
  136. package/dist/chunk-UIG7MWS2.js.map +0 -1
  137. package/dist/chunk-UL6Q5HDP.js.map +0 -1
  138. package/dist/chunk-VMQHNJJI.js.map +0 -1
  139. package/dist/chunk-WWVD5DR5.js.map +0 -1
  140. package/dist/chunk-Z72F2ENM.js.map +0 -1
  141. package/dist/components-BWG1GkZA.d.ts +0 -20
  142. package/dist/use-behavior-B85d5u7-.d.ts +0 -13
  143. /package/dist/{chunk-5SM5SH5O.js.map → chunk-7JLWZXPA.js.map} +0 -0
  144. /package/dist/{chunk-ZA43LPRM.js.map → chunk-KIYUUSPK.js.map} +0 -0
  145. /package/dist/{chunk-XP3ASVGJ.js.map → chunk-NW4SE5XW.js.map} +0 -0
package/README.md CHANGED
@@ -1,6 +1,18 @@
1
1
  # Zylem Behaviors
2
2
 
3
- Tree-shakable Zylem behavior descriptors, systems, coordinators, and shared behavior helpers.
3
+ The simulation layer of the Zylem framework: tree-shakable behavior descriptors, FSMs, coordinators, and the `Simulation` API that owns the `@zylem/runtime` WASM module (ECS + Rapier physics).
4
+
5
+ `@zylem/behaviors` sits between the game framework and the runtime:
6
+
7
+ ```
8
+ @zylem/game-lib → @zylem/behaviors → @zylem/runtime (WASM)
9
+ ```
10
+
11
+ - **`createSimulation()` / `Simulation`** — loads and owns the WASM stage, exposes `spawn`/`despawn` with opaque `EntityHandle`s, a fixed-timestep `update(delta)` loop, snapshot/pose accessors, raycasts, debug rendering, and translated `SimulationEvent`s (collision start/stop with handles instead of raw slots).
12
+ - **Behavior descriptors** (`Platformer3DBehavior`, `TopDownMovementBehavior`, …) — attach WASM-side controllers to entities and forward per-frame input through batched buffers; FSM state is read back from snapshots.
13
+ - **`MockBehaviorRuntime`** — in-memory `BehaviorRuntime` implementation for unit-testing behaviors without loading WASM.
14
+
15
+ Physics simulation is WASM-only; there is no TypeScript physics fallback. Disposal chains downward: `Simulation.dispose()` destroys the WASM stage.
4
16
 
5
17
  Use per-behavior entrypoints when you want bundlers to pull only the behavior family you import:
6
18
 
@@ -1,4 +1,4 @@
1
- import { RigidBody, Collider, World } from '@dimforge/rapier3d-compat';
1
+ import { StageWasmExports } from '@zylem/runtime';
2
2
 
3
3
  /**
4
4
  * BehaviorDescriptor
@@ -199,8 +199,9 @@ declare class GameEntity<TOptions = unknown> {
199
199
  group: any;
200
200
  mesh?: any;
201
201
  materials?: any[];
202
- body: RigidBody | null;
203
- collider: Collider | null;
202
+ /** Opaque host-owned physics references (never inspected by behaviors). */
203
+ body: unknown | null;
204
+ collider: unknown | null;
204
205
  colliderDesc?: any;
205
206
  colliderDescs: any[];
206
207
  bodyDesc?: any;
@@ -233,12 +234,24 @@ declare class GameEntity<TOptions = unknown> {
233
234
  */
234
235
  type CreateEntityFn = (options?: Partial<GameEntityOptions>) => GameEntity<GameEntityOptions>;
235
236
 
237
+ /**
238
+ * `WasmStageRuntime` — owns one wasm `WebAssembly.Instance` of
239
+ * `@zylem/runtime` and exposes the unified-Stage FFI as typed TypeScript
240
+ * methods. This is the single simulation adapter for the whole framework:
241
+ * behavior systems talk to it through the {@link BehaviorRuntime} contract,
242
+ * and hosts (e.g. `@zylem/game-lib`) drive it through
243
+ * {@link createSimulation} in `./simulation`.
244
+ */
245
+
246
+ /** Sentinel handle for "no entity / failed allocation". Mirrors `INVALID_SLOT` in Rust. */
247
+ declare const STAGE_INVALID_SLOT = 4294967295;
236
248
  declare const StageBodyKind: {
237
249
  readonly Dynamic: 0;
238
250
  readonly Static: 1;
239
251
  readonly KinematicPosition: 2;
240
252
  readonly KinematicVelocity: 3;
241
253
  };
254
+ type StageBodyKindValue = (typeof StageBodyKind)[keyof typeof StageBodyKind];
242
255
  declare const StageBoundaryDim: {
243
256
  readonly Two: 2;
244
257
  readonly Three: 3;
@@ -426,7 +439,7 @@ interface StageBehaviorSnapshot {
426
439
  * a duplicate TS FSM (or hardcoded defaults).
427
440
  */
428
441
  interface WasmSnapshotBackref {
429
- __wasmStage?: WasmStageRuntime | null;
442
+ __wasmStage?: BehaviorRuntime | null;
430
443
  __entity?: {
431
444
  runtimeHandle?: number;
432
445
  } | null;
@@ -439,21 +452,24 @@ interface WasmSnapshotBackref {
439
452
  */
440
453
  declare function readBehaviorSnapshot(ref: object): StageBehaviorSnapshot | null;
441
454
  /**
442
- * Typed FFI surface of the wasm unified Stage runtime that behaviors rely on.
443
- *
444
- * The concrete implementation lives in `@zylem/game-lib`
445
- * (`wasm-stage-runtime.ts`); this contract mirrors the slice of its methods
446
- * that behavior systems call so descriptors stay runtime-agnostic.
455
+ * Runtime surface behavior systems rely on. `WasmStageRuntime` is the real
456
+ * wasm-backed implementation; `MockBehaviorRuntime` (see
457
+ * `./mock-behavior-runtime`) implements the same contract for unit tests, so
458
+ * descriptors stay runtime-agnostic.
447
459
  */
448
- interface WasmStageRuntime {
460
+ interface BehaviorRuntime {
449
461
  getPose(handle: number, out?: StagePose): StagePose | null;
462
+ setPosition(handle: number, x: number, y: number, z: number): boolean;
463
+ setRotation(handle: number, x: number, y: number, z: number, w: number): boolean;
450
464
  writeInputAxes(handle: number, axisA: number, axisB: number): boolean;
451
465
  writeInputLook(handle: number, yawDelta: number, pitchDelta: number): boolean;
466
+ /** Write reserved input slots (thruster world-space vector X/Y). */
467
+ writeInputReserved(handle: number, reservedA: number, reservedB: number): boolean;
452
468
  writeInputButton(handle: number, buttonMask: number, pressed: boolean): boolean;
453
469
  /** Read the per-slot behavior snapshot written at the end of the previous step. */
454
470
  readSnapshot(handle: number, out?: StageBehaviorSnapshot): StageBehaviorSnapshot | null;
455
471
  attachThruster(handle: number, opts: StageThrusterOptions): boolean;
456
- setThrusterInput(handle: number, thrust: number, rotate: number): void;
472
+ setThrusterInput(handle: number, thrust: number, rotate: number, vectorX?: number, vectorY?: number): void;
457
473
  attachTopDown(handle: number, opts: StageTopDownOptions): boolean;
458
474
  setTopDownInput(handle: number, moveX: number, moveY: number): void;
459
475
  attachShooter2D(handle: number, opts: StageShooter2DOptions): boolean;
@@ -486,20 +502,292 @@ interface WasmStageRuntimeOptions {
486
502
  initialCapacity?: number;
487
503
  gravity?: readonly [number, number, number];
488
504
  imports?: WebAssembly.Imports;
505
+ /**
506
+ * Custom wasm source. Defaults to the `zylem_runtime.wasm` bundled with
507
+ * `@zylem/runtime`.
508
+ */
509
+ source?: RequestInfo | URL | ArrayBuffer | Uint8Array;
510
+ }
511
+ type StageEventTypeValue = (typeof StageEventType)[keyof typeof StageEventType];
512
+ /** One decoded event drained from the runtime's per-step event buffer. */
513
+ interface StageEvent {
514
+ type: StageEventTypeValue;
515
+ primarySlot: number | null;
516
+ secondarySlot: number | null;
517
+ payload: [number, number, number];
518
+ }
519
+ /**
520
+ * Decoded view of one entity's slot in the unified render buffer.
521
+ *
522
+ * Mirrors the `STAGE_RENDER_STRIDE` (12-float) packing documented in
523
+ * `runtime/stage/render.rs`:
524
+ * `[0..3]` position, `[3..7]` rotation xyzw, `[7]` uniform scale,
525
+ * `[8..12]` custom0..custom3 (per-behavior shading hints).
526
+ */
527
+ interface StageRenderSlot {
528
+ position: [number, number, number];
529
+ rotation: [number, number, number, number];
530
+ scale: number;
531
+ custom: [number, number, number, number];
532
+ }
533
+ /**
534
+ * Read a single entity slot out of a unified-Stage render buffer view.
535
+ *
536
+ * @param view The `Float32Array` over wasm memory (e.g. `WasmStageRuntime.render`).
537
+ * @param slot The entity's wasm slot id (its `runtimeHandle`).
538
+ * @param out Optional target object to write into (avoids per-call allocation).
539
+ * @returns The decoded slot, or `null` when the slot is out of range.
540
+ */
541
+ declare function readStageRenderSlot(view: Float32Array, slot: number, out?: StageRenderSlot): StageRenderSlot | null;
542
+ interface StageBodyConfig {
543
+ kind: StageBodyKindValue;
544
+ position: readonly [number, number, number];
545
+ rotation: readonly [number, number, number, number];
546
+ linearDamping: number;
547
+ angularDamping: number;
548
+ gravityScale: number;
549
+ canSleep: boolean;
550
+ ccdEnabled: boolean;
551
+ lockRotation: readonly [boolean, boolean, boolean];
552
+ lockTranslation: readonly [boolean, boolean, boolean];
553
+ }
554
+ type StageColliderShape = {
555
+ type: 'box';
556
+ halfExtents: readonly [number, number, number];
557
+ } | {
558
+ type: 'sphere';
559
+ radius: number;
560
+ } | {
561
+ type: 'capsule';
562
+ halfHeight: number;
563
+ radius: number;
564
+ } | {
565
+ type: 'cylinder';
566
+ halfHeight: number;
567
+ radius: number;
568
+ } | {
569
+ type: 'cone';
570
+ halfHeight: number;
571
+ radius: number;
572
+ } | {
573
+ type: 'convexHull';
574
+ vertices: Float32Array;
575
+ } | {
576
+ type: 'trimesh';
577
+ vertices: Float32Array;
578
+ indices: Uint32Array;
579
+ } | {
580
+ type: 'heightfield';
581
+ rows: number;
582
+ cols: number;
583
+ scale: readonly [number, number, number];
584
+ heights: Float32Array;
585
+ };
586
+ interface StageColliderConfig {
587
+ shape: StageColliderShape;
588
+ offset: readonly [number, number, number];
589
+ friction: number;
590
+ restitution: number;
591
+ sensor: boolean;
592
+ /** Packed interaction-groups: high 16 bits = membership, low 16 bits = filter. */
593
+ collisionGroups: number;
594
+ }
595
+ /** Result of a physics raycast against the stage. */
596
+ interface StageRaycastHit {
597
+ slot: number;
598
+ distance: number;
599
+ normal: [number, number, number];
600
+ }
601
+ /** Body introspection payload for debug tooling. */
602
+ interface StageBodyInfo {
603
+ bodyType: number;
604
+ mass: number;
605
+ isSleeping: boolean;
606
+ isEnabled: boolean;
607
+ linearVelocity: [number, number, number];
489
608
  }
609
+ /** Line-list wireframe for physics debug rendering. */
610
+ interface StageDebugRender {
611
+ /** `[x, y, z]` per vertex, two vertices per line. */
612
+ vertices: Float32Array;
613
+ /** RGBA per vertex. */
614
+ colors: Float32Array;
615
+ }
616
+ /**
617
+ * Owns the wasm instance plus the live float-views over its render / event /
618
+ * pose / scratch buffers. One instance per logical stage; each stage gets
619
+ * its own `WebAssembly.Instance` so the singleton inside `stage_ffi.rs`
620
+ * stays single-stage scoped (today's `thread_local!` contract).
621
+ */
622
+ declare class WasmStageRuntime implements BehaviorRuntime {
623
+ readonly exports: StageWasmExports;
624
+ private readonly memory;
625
+ private renderView;
626
+ private inputView;
627
+ private snapshotView;
628
+ private eventView;
629
+ private poseView;
630
+ private vec3View;
631
+ private queryView;
632
+ private scratchView;
633
+ constructor(exports: StageWasmExports, options?: WasmStageRuntimeOptions);
634
+ /** Recreate typed views over wasm memory. Call after step() or whenever memory may have grown. */
635
+ refreshViews(): void;
636
+ /** Step the simulation by `dt` seconds. Refreshes views afterwards. */
637
+ step(dt: number): number;
638
+ dispose(): void;
639
+ get tickCount(): number;
640
+ get activeCount(): number;
641
+ setGravity(x: number, y: number, z: number): void;
642
+ createEntity(): number;
643
+ destroyEntity(slot: number): boolean;
644
+ attachBody(slot: number, config: StageBodyConfig): boolean;
645
+ addCollider(slot: number, config: StageColliderConfig): boolean;
646
+ private stageScratch;
647
+ private stageScratchIndices;
648
+ getPose(slot: number, out?: StagePose): StagePose | null;
649
+ setPosition(slot: number, x: number, y: number, z: number): boolean;
650
+ setRotation(slot: number, x: number, y: number, z: number, w: number): boolean;
651
+ setLinearVelocity(slot: number, x: number, y: number, z: number): boolean;
652
+ getLinearVelocity(slot: number, out?: [number, number, number]): [number, number, number] | null;
653
+ setAngularVelocity(slot: number, x: number, y: number, z: number): boolean;
654
+ getAngularVelocity(slot: number, out?: [number, number, number]): [number, number, number] | null;
655
+ applyImpulse(slot: number, x: number, y: number, z: number): boolean;
656
+ /** Cast a ray against the physics world. Returns the closest hit or `null`. */
657
+ castRay(origin: readonly [number, number, number], dir: readonly [number, number, number], maxDistance: number, solidOnly?: boolean): StageRaycastHit | null;
658
+ /** Body introspection for debug tooling. */
659
+ getBodyInfo(slot: number): StageBodyInfo | null;
660
+ /** Wireframe debug render of every collider (all shapes). */
661
+ getDebugRender(): StageDebugRender | null;
662
+ /** Read-only view over the render buffer; one slot occupies {@link STAGE_RENDER_STRIDE} floats. */
663
+ get render(): Float32Array;
664
+ get renderStride(): number;
665
+ /**
666
+ * Decode a single entity slot from the live render buffer. Convenience
667
+ * wrapper over {@link readStageRenderSlot} bound to this runtime's view.
668
+ */
669
+ readRenderSlot(slot: number, out?: StageRenderSlot): StageRenderSlot | null;
670
+ /** Drain the wasm event buffer into a fresh array of typed events. */
671
+ drainEvents(): StageEvent[];
672
+ /** Writable view over the slot-indexed input buffer ({@link STAGE_INPUT_STRIDE} floats/slot). */
673
+ get input(): Float32Array;
674
+ get inputStride(): number;
675
+ /** Read-only view over the per-slot behavior snapshot buffer ({@link STAGE_SNAPSHOT_STRIDE} floats/slot). */
676
+ get snapshot(): Float32Array;
677
+ get snapshotStride(): number;
678
+ /**
679
+ * Write the primary movement axes for a slot into the batched input
680
+ * buffer and mark the slot dirty. Field-scoped (doesn't touch look
681
+ * deltas or buttons) so multiple behaviors composed on one entity can
682
+ * each write their part.
683
+ */
684
+ writeInputAxes(slot: number, axisA: number, axisB: number): boolean;
685
+ /** Write the look deltas (first-person yaw/pitch) for a slot. */
686
+ writeInputLook(slot: number, yawDelta: number, pitchDelta: number): boolean;
687
+ /** Write reserved input slots (thruster world-space vector X/Y). */
688
+ writeInputReserved(slot: number, reservedA: number, reservedB: number): boolean;
689
+ /**
690
+ * Set or clear one button bit ({@link STAGE_INPUT_BUTTON_JUMP} /
691
+ * {@link STAGE_INPUT_BUTTON_RUN}) in a slot's input bitmask.
692
+ */
693
+ writeInputButton(slot: number, buttonMask: number, pressed: boolean): boolean;
694
+ /**
695
+ * Decode a slot's behavior snapshot from the buffer written at the end of
696
+ * the previous step. Returns `null` when the slot has no behavior data.
697
+ */
698
+ readSnapshot(slot: number, out?: StageBehaviorSnapshot): StageBehaviorSnapshot | null;
699
+ get queryScratch(): Float32Array;
700
+ attachCooldown(slot: number): boolean;
701
+ registerCooldown(slot: number, id: number, durationSeconds: number, immediate?: boolean): boolean;
702
+ fireCooldown(slot: number, id: number): boolean;
703
+ resetCooldown(slot: number, id: number): boolean;
704
+ cooldownRemaining(slot: number, id: number): number;
705
+ attachWorldBoundary(slot: number, dim: StageBoundaryDimValue, bounds: StageWorldBoundaryOptions): boolean;
706
+ queryWorldBoundary(slot: number): {
707
+ hits: number;
708
+ clamped: [number, number, number];
709
+ } | null;
710
+ attachScreenWrap(slot: number, opts: StageScreenWrapOptions): boolean;
711
+ queryScreenWrap(slot: number): {
712
+ fsm: number;
713
+ lastWrappedAxes: number;
714
+ } | null;
715
+ attachRicochet(slot: number, dim: StageRicochetDimValue, opts: StageRicochetOptions): boolean;
716
+ queryRicochet(slot: number): {
717
+ bounces: number;
718
+ lastKind: number;
719
+ } | null;
720
+ attachThruster(slot: number, opts: StageThrusterOptions): boolean;
721
+ setThrusterInput(slot: number, thrust: number, turn: number, vectorX?: number, vectorY?: number): void;
722
+ queryThruster(slot: number): {
723
+ headingRad: number;
724
+ speed: number;
725
+ } | null;
726
+ attachTopDown(slot: number, opts: StageTopDownOptions): boolean;
727
+ setTopDownInput(slot: number, axisA: number, axisB: number): void;
728
+ attachJumper2D(slot: number, opts: StageJumper2DOptions): boolean;
729
+ setJumper2DInput(slot: number, jumpPressed: boolean): void;
730
+ queryJumper2D(slot: number): {
731
+ grounded: boolean;
732
+ jumpCount: number;
733
+ coyote: number;
734
+ jumpBuffer: number;
735
+ } | null;
736
+ attachJumper3D(slot: number, opts: StageJumper3DOptions): boolean;
737
+ setJumper3DInput(slot: number, jump: boolean): void;
738
+ queryJumper3D(slot: number): {
739
+ grounded: boolean;
740
+ jumpCount: number;
741
+ verticalVelocity: number;
742
+ } | null;
743
+ attachShooter2D(slot: number, opts: StageShooter2DOptions): boolean;
744
+ fireShooter2D(slot: number, x: number, y: number, z: number, yaw: number): boolean;
745
+ queryShooter2D(slot: number): {
746
+ cooldown: number;
747
+ firedCount: number;
748
+ } | null;
749
+ attachPlatformer3D(slot: number, opts: StagePlatformer3DOptions): boolean;
750
+ setPlatformer3DInputAxes(slot: number, moveX: number, moveZ: number): void;
751
+ setPlatformer3DInputButtons(slot: number, jump: boolean, run: boolean): void;
752
+ queryPlatformer3D(slot: number): {
753
+ grounded: boolean;
754
+ jumpCount: number;
755
+ fsmState: number;
756
+ speed: number;
757
+ } | null;
758
+ attachPlatformer2D(slot: number, opts: StagePlatformer2DOptions): boolean;
759
+ setPlatformer2DInputAxes(slot: number, moveX: number): void;
760
+ setPlatformer2DInputButtons(slot: number, jump: boolean, run: boolean): void;
761
+ queryPlatformer2D(slot: number): {
762
+ grounded: boolean;
763
+ jumpCount: number;
764
+ fsmState: number;
765
+ verticalVelocity: number;
766
+ } | null;
767
+ attachFirstPerson(slot: number, opts: StageFirstPersonOptions): boolean;
768
+ setFirstPersonInput(slot: number, input: StageFirstPersonInput): void;
769
+ queryFirstPerson(slot: number): {
770
+ yaw: number;
771
+ pitch: number;
772
+ speed: number;
773
+ eyeHeight: number;
774
+ } | null;
775
+ }
776
+ /**
777
+ * Load the bundled `@zylem/runtime` wasm (or a custom `source`), instantiate
778
+ * it, and construct a `WasmStageRuntime` around the exports.
779
+ */
780
+ declare function createWasmStageRuntime(options?: WasmStageRuntimeOptions): Promise<WasmStageRuntime>;
490
781
 
491
782
  /**
492
783
  * Structural stand-in for the engine world wrapper. The engine's world
493
- * delegate exposes the raw Rapier world plus an entity lookup map; behaviors
494
- * only rely on this slice.
784
+ * delegate exposes an entity lookup map; behaviors only rely on this slice.
495
785
  *
496
786
  * The map value is intentionally `unknown` (not the `GameEntity` stub class):
497
787
  * the engine's concrete entity class is a different nominal type, and systems
498
788
  * that iterate the map narrow entities themselves.
499
789
  */
500
790
  interface ZylemWorld {
501
- /** Raw Rapier physics world (present when the Rapier path is active). */
502
- world?: World;
503
791
  collisionMap?: Map<string, unknown>;
504
792
  }
505
793
 
@@ -553,7 +841,7 @@ interface BehaviorSystemContext {
553
841
  /** Host scene wrapper (engine-specific); behaviors only call `add`/`remove`-style methods on it. */
554
842
  scene: any;
555
843
  /** Wasm-driven Stage runtime; omitted when the stage has no wasm module. */
556
- wasmStage?: WasmStageRuntime | null;
844
+ wasmStage?: BehaviorRuntime | null;
557
845
  /**
558
846
  * Returns live behavior links for a descriptor key.
559
847
  * O(1) lookup into a pre-built stage index.
@@ -577,4 +865,4 @@ interface BehaviorSystemContext {
577
865
  */
578
866
  type BehaviorSystemFactory<T extends BehaviorSystem = BehaviorSystem> = (stage: BehaviorSystemContext) => T;
579
867
 
580
- export { type StageScreenWrapOptions as A, type BaseBehaviorHandle as B, type CreateEntityFn as C, type DefineBehaviorConfig as D, type StageShooter2DOptions as E, type StageThrusterOptions as F, GameEntity as G, type StageTopDownOptions as H, StageTopDownPlane as I, type StageTopDownPlaneValue as J, type StageWorldBoundaryOptions as K, type VelocityIntentMode as L, type WasmStageRuntime as M, type WasmStageRuntimeOptions as N, createTransformStore as O, defineBehavior as P, readBehaviorSnapshot as Q, STAGE_INPUT_BUTTON_JUMP as S, type TransformState as T, type VelocityIntent as V, type WasmSnapshotBackref as W, type ZylemWorld as Z, type BehaviorDescriptor as a, type BehaviorEntityLink as b, type BehaviorHandle as c, type BehaviorRef as d, type BehaviorSystem as e, type BehaviorSystemContext as f, type BehaviorSystemFactory as g, type GameEntityOptions as h, STAGE_INPUT_BUTTON_RUN as i, type StageBehaviorSnapshot as j, StageBodyKind as k, StageBoundaryDim as l, type StageBoundaryDimValue as m, StageEventType as n, type StageFirstPersonInput as o, type StageFirstPersonOptions as p, type StageJumper2DOptions as q, type StageJumper3DOptions as r, type StagePlatformer2DOptions as s, type StagePlatformer3DOptions as t, type StagePose as u, StageRicochetDim as v, type StageRicochetDimValue as w, type StageRicochetOptions as x, StageRicochetReflection as y, type StageRicochetReflectionValue as z };
868
+ export { type WasmSnapshotBackref as $, type StageJumper2DOptions as A, type BaseBehaviorHandle as B, type CreateEntityFn as C, type DefineBehaviorConfig as D, type StageJumper3DOptions as E, type StagePlatformer2DOptions as F, GameEntity as G, type StagePlatformer3DOptions as H, type StagePose as I, type StageRaycastHit as J, type StageRenderSlot as K, StageRicochetDim as L, type StageRicochetDimValue as M, type StageRicochetOptions as N, StageRicochetReflection as O, type StageRicochetReflectionValue as P, type StageScreenWrapOptions as Q, type StageShooter2DOptions as R, STAGE_INPUT_BUTTON_JUMP as S, type StageThrusterOptions as T, type StageTopDownOptions as U, StageTopDownPlane as V, type StageTopDownPlaneValue as W, type StageWorldBoundaryOptions as X, type TransformState as Y, type VelocityIntent as Z, type VelocityIntentMode as _, type BehaviorDescriptor as a, WasmStageRuntime as a0, type WasmStageRuntimeOptions as a1, createTransformStore as a2, createWasmStageRuntime as a3, defineBehavior as a4, readBehaviorSnapshot as a5, readStageRenderSlot as a6, type BehaviorEntityLink as b, type BehaviorHandle as c, type BehaviorRef as d, type BehaviorRuntime as e, type BehaviorSystem as f, type BehaviorSystemContext as g, type BehaviorSystemFactory as h, type GameEntityOptions as i, STAGE_INPUT_BUTTON_RUN as j, STAGE_INVALID_SLOT as k, type StageBehaviorSnapshot as l, type StageBodyConfig as m, type StageBodyInfo as n, StageBodyKind as o, type StageBodyKindValue as p, StageBoundaryDim as q, type StageBoundaryDimValue as r, type StageColliderConfig as s, type StageColliderShape as t, type StageDebugRender as u, type StageEvent as v, StageEventType as w, type StageEventTypeValue as x, type StageFirstPersonInput as y, type StageFirstPersonOptions as z };
@@ -1,11 +1,10 @@
1
1
  import {
2
- createBounds2DRect,
3
- wrapPoint2D
4
- } from "./chunk-KDY5N6J4.js";
2
+ createBounds2DRect
3
+ } from "./chunk-OPZ2LVS2.js";
5
4
  import {
6
5
  SyncStateMachine,
7
- t
8
- } from "./chunk-4URF65BR.js";
6
+ import_typescript_fsm
7
+ } from "./chunk-AS3HR7AS.js";
9
8
  import {
10
9
  defineBehavior
11
10
  } from "./chunk-EZNCCO4L.js";
@@ -36,33 +35,33 @@ var ScreenWrapFSM = class {
36
35
  "center" /* Center */,
37
36
  [
38
37
  // From Center
39
- t("center" /* Center */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
40
- t("center" /* Center */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
41
- t("center" /* Center */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
42
- t("center" /* Center */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
38
+ (0, import_typescript_fsm.t)("center" /* Center */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
39
+ (0, import_typescript_fsm.t)("center" /* Center */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
40
+ (0, import_typescript_fsm.t)("center" /* Center */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
41
+ (0, import_typescript_fsm.t)("center" /* Center */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
43
42
  // From NearEdge to Wrapped
44
- t("near-edge-left" /* NearEdgeLeft */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
45
- t("near-edge-right" /* NearEdgeRight */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
46
- t("near-edge-top" /* NearEdgeTop */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
47
- t("near-edge-bottom" /* NearEdgeBottom */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
43
+ (0, import_typescript_fsm.t)("near-edge-left" /* NearEdgeLeft */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
44
+ (0, import_typescript_fsm.t)("near-edge-right" /* NearEdgeRight */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
45
+ (0, import_typescript_fsm.t)("near-edge-top" /* NearEdgeTop */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
46
+ (0, import_typescript_fsm.t)("near-edge-bottom" /* NearEdgeBottom */, "wrap" /* Wrap */, "wrapped" /* Wrapped */),
48
47
  // From NearEdge back to Center
49
- t("near-edge-left" /* NearEdgeLeft */, "enter-center" /* EnterCenter */, "center" /* Center */),
50
- t("near-edge-right" /* NearEdgeRight */, "enter-center" /* EnterCenter */, "center" /* Center */),
51
- t("near-edge-top" /* NearEdgeTop */, "enter-center" /* EnterCenter */, "center" /* Center */),
52
- t("near-edge-bottom" /* NearEdgeBottom */, "enter-center" /* EnterCenter */, "center" /* Center */),
48
+ (0, import_typescript_fsm.t)("near-edge-left" /* NearEdgeLeft */, "enter-center" /* EnterCenter */, "center" /* Center */),
49
+ (0, import_typescript_fsm.t)("near-edge-right" /* NearEdgeRight */, "enter-center" /* EnterCenter */, "center" /* Center */),
50
+ (0, import_typescript_fsm.t)("near-edge-top" /* NearEdgeTop */, "enter-center" /* EnterCenter */, "center" /* Center */),
51
+ (0, import_typescript_fsm.t)("near-edge-bottom" /* NearEdgeBottom */, "enter-center" /* EnterCenter */, "center" /* Center */),
53
52
  // From Wrapped back to Center
54
- t("wrapped" /* Wrapped */, "enter-center" /* EnterCenter */, "center" /* Center */),
53
+ (0, import_typescript_fsm.t)("wrapped" /* Wrapped */, "enter-center" /* EnterCenter */, "center" /* Center */),
55
54
  // From Wrapped to NearEdge (landed near opposite edge)
56
- t("wrapped" /* Wrapped */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
57
- t("wrapped" /* Wrapped */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
58
- t("wrapped" /* Wrapped */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
59
- t("wrapped" /* Wrapped */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
55
+ (0, import_typescript_fsm.t)("wrapped" /* Wrapped */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
56
+ (0, import_typescript_fsm.t)("wrapped" /* Wrapped */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
57
+ (0, import_typescript_fsm.t)("wrapped" /* Wrapped */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
58
+ (0, import_typescript_fsm.t)("wrapped" /* Wrapped */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */),
60
59
  // Self-transitions (no-ops for redundant events)
61
- t("center" /* Center */, "enter-center" /* EnterCenter */, "center" /* Center */),
62
- t("near-edge-left" /* NearEdgeLeft */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
63
- t("near-edge-right" /* NearEdgeRight */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
64
- t("near-edge-top" /* NearEdgeTop */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
65
- t("near-edge-bottom" /* NearEdgeBottom */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */)
60
+ (0, import_typescript_fsm.t)("center" /* Center */, "enter-center" /* EnterCenter */, "center" /* Center */),
61
+ (0, import_typescript_fsm.t)("near-edge-left" /* NearEdgeLeft */, "approach-left" /* ApproachLeft */, "near-edge-left" /* NearEdgeLeft */),
62
+ (0, import_typescript_fsm.t)("near-edge-right" /* NearEdgeRight */, "approach-right" /* ApproachRight */, "near-edge-right" /* NearEdgeRight */),
63
+ (0, import_typescript_fsm.t)("near-edge-top" /* NearEdgeTop */, "approach-top" /* ApproachTop */, "near-edge-top" /* NearEdgeTop */),
64
+ (0, import_typescript_fsm.t)("near-edge-bottom" /* NearEdgeBottom */, "approach-bottom" /* ApproachBottom */, "near-edge-bottom" /* NearEdgeBottom */)
66
65
  ]
67
66
  );
68
67
  }
@@ -130,41 +129,24 @@ var ScreenWrapSystem = class {
130
129
  wrapRef.fsm = new ScreenWrapFSM();
131
130
  }
132
131
  const handle = gameEntity.runtimeHandle ?? -1;
133
- if (this.wasmStage && handle >= 0) {
134
- this.ensureWasmAttached(handle, options);
135
- const pose = this.wasmStage.getPose(handle);
136
- const query = this.wasmStage.queryScreenWrap(handle);
137
- if (pose && query) {
138
- const bounds2 = createBounds2DRect(options);
139
- wrapRef.fsm.update(
140
- { x: pose.position[0], y: pose.position[1] },
141
- {
142
- minX: bounds2.minX,
143
- maxX: bounds2.maxX,
144
- minY: bounds2.minY,
145
- maxY: bounds2.maxY,
146
- edgeThreshold: options.edgeThreshold
147
- },
148
- query.lastWrappedAxes !== 0
149
- );
150
- continue;
151
- }
132
+ if (!this.wasmStage || handle < 0) continue;
133
+ this.ensureWasmAttached(handle, options);
134
+ const pose = this.wasmStage.getPose(handle);
135
+ const query = this.wasmStage.queryScreenWrap(handle);
136
+ if (pose && query) {
137
+ const bounds = createBounds2DRect(options);
138
+ wrapRef.fsm.update(
139
+ { x: pose.position[0], y: pose.position[1] },
140
+ {
141
+ minX: bounds.minX,
142
+ maxX: bounds.maxX,
143
+ minY: bounds.minY,
144
+ maxY: bounds.maxY,
145
+ edgeThreshold: options.edgeThreshold
146
+ },
147
+ query.lastWrappedAxes !== 0
148
+ );
152
149
  }
153
- if (!gameEntity.body) continue;
154
- const wrapped = this.wrapEntity(gameEntity, options);
155
- const pos = gameEntity.body.translation();
156
- const bounds = createBounds2DRect(options);
157
- wrapRef.fsm.update(
158
- { x: pos.x, y: pos.y },
159
- {
160
- minX: bounds.minX,
161
- maxX: bounds.maxX,
162
- minY: bounds.minY,
163
- maxY: bounds.maxY,
164
- edgeThreshold: options.edgeThreshold
165
- },
166
- wrapped
167
- );
168
150
  }
169
151
  }
170
152
  ensureWasmAttached(handle, options) {
@@ -178,17 +160,6 @@ var ScreenWrapSystem = class {
178
160
  });
179
161
  this.attachedRuntimeSlots.add(handle);
180
162
  }
181
- wrapEntity(entity, options) {
182
- const body = entity.body;
183
- if (!body) return false;
184
- const pos = body.translation();
185
- const bounds = createBounds2DRect(options);
186
- const result = wrapPoint2D({ x: pos.x, y: pos.y }, bounds);
187
- if (result.wrapped) {
188
- body.setTranslation({ x: result.x, y: result.y, z: pos.z }, true);
189
- }
190
- return result.wrapped;
191
- }
192
163
  destroy(_ecs) {
193
164
  this.attachedRuntimeSlots.clear();
194
165
  }
@@ -205,4 +176,4 @@ export {
205
176
  ScreenWrapFSM,
206
177
  ScreenWrapBehavior
207
178
  };
208
- //# sourceMappingURL=chunk-GFARAJV2.js.map
179
+ //# sourceMappingURL=chunk-2PAHQZZD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/behaviors/screen-wrap/screen-wrap-fsm.ts","../src/lib/behaviors/screen-wrap/screen-wrap.descriptor.ts"],"sourcesContent":["/**\n * ScreenWrapFSM\n * \n * State machine for screen wrap behavior.\n * Reports position relative to bounds edges.\n */\n\nimport { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM State Model\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapState {\n\tCenter = 'center',\n\tNearEdgeLeft = 'near-edge-left',\n\tNearEdgeRight = 'near-edge-right',\n\tNearEdgeTop = 'near-edge-top',\n\tNearEdgeBottom = 'near-edge-bottom',\n\tWrapped = 'wrapped',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// FSM Events\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport enum ScreenWrapEvent {\n\tEnterCenter = 'enter-center',\n\tApproachLeft = 'approach-left',\n\tApproachRight = 'approach-right',\n\tApproachTop = 'approach-top',\n\tApproachBottom = 'approach-bottom',\n\tWrap = 'wrap',\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ScreenWrapFSM\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport class ScreenWrapFSM {\n\tmachine: SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>;\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<ScreenWrapState, ScreenWrapEvent, never>(\n\t\t\tScreenWrapState.Center,\n\t\t\t[\n\t\t\t\t// From Center\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t\t\n\t\t\t\t// From NearEdge to Wrapped\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.Wrap, ScreenWrapState.Wrapped),\n\n\t\t\t\t// From NearEdge back to Center\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped back to Center\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\n\t\t\t\t// From Wrapped to NearEdge (landed near opposite edge)\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.Wrapped, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\n\t\t\t\t// Self-transitions (no-ops for redundant events)\n\t\t\t\tt(ScreenWrapState.Center, ScreenWrapEvent.EnterCenter, ScreenWrapState.Center),\n\t\t\t\tt(ScreenWrapState.NearEdgeLeft, ScreenWrapEvent.ApproachLeft, ScreenWrapState.NearEdgeLeft),\n\t\t\t\tt(ScreenWrapState.NearEdgeRight, ScreenWrapEvent.ApproachRight, ScreenWrapState.NearEdgeRight),\n\t\t\t\tt(ScreenWrapState.NearEdgeTop, ScreenWrapEvent.ApproachTop, ScreenWrapState.NearEdgeTop),\n\t\t\t\tt(ScreenWrapState.NearEdgeBottom, ScreenWrapEvent.ApproachBottom, ScreenWrapState.NearEdgeBottom),\n\t\t\t]\n\t\t);\n\t}\n\n\tgetState(): ScreenWrapState {\n\t\treturn this.machine.getState();\n\t}\n\n\tdispatch(event: ScreenWrapEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\n\t\t}\n\t}\n\n\t/**\n\t * Update FSM based on entity position relative to bounds\n\t */\n\tupdate(position: { x: number; y: number }, bounds: {\n\t\tminX: number; maxX: number; minY: number; maxY: number;\n\t\tedgeThreshold: number;\n\t}, wrapped: boolean): void {\n\t\tconst { x, y } = position;\n\t\tconst { minX, maxX, minY, maxY, edgeThreshold } = bounds;\n\n\t\tif (wrapped) {\n\t\t\tthis.dispatch(ScreenWrapEvent.Wrap);\n\t\t\treturn;\n\t\t}\n\n\t\t// Check if near edges\n\t\tconst nearLeft = x < minX + edgeThreshold;\n\t\tconst nearRight = x > maxX - edgeThreshold;\n\t\tconst nearBottom = y < minY + edgeThreshold;\n\t\tconst nearTop = y > maxY - edgeThreshold;\n\n\t\tif (nearLeft) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachLeft);\n\t\t} else if (nearRight) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachRight);\n\t\t} else if (nearTop) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachTop);\n\t\t} else if (nearBottom) {\n\t\t\tthis.dispatch(ScreenWrapEvent.ApproachBottom);\n\t\t} else {\n\t\t\tthis.dispatch(ScreenWrapEvent.EnterCenter);\n\t\t}\n\t}\n}\n","/**\n * ScreenWrapBehavior\n * \n * When an entity exits the defined 2D bounds, it wraps around to the opposite edge.\n * Asteroids-style screen wrapping with FSM for edge detection.\n */\n\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ScreenWrapFSM } from './screen-wrap-fsm';\nimport { createBounds2DRect } from '../shared/bounds-2d';\nimport type { BehaviorRuntime } from '../../runtime/wasm-stage-runtime';\n\n/**\n * Screen wrap options (typed for entity.use() autocomplete)\n */\nexport interface ScreenWrapOptions {\n\t/** Width of the wrapping area (default: 20) */\n\twidth: number;\n\t/** Height of the wrapping area (default: 15) */\n\theight: number;\n\t/** Center X position (default: 0) */\n\tcenterX: number;\n\t/** Center Y position (default: 0) */\n\tcenterY: number;\n\t/** Distance from edge to trigger NearEdge state (default: 2) */\n\tedgeThreshold: number;\n}\n\nconst defaultOptions: ScreenWrapOptions = {\n\twidth: 20,\n\theight: 15,\n\tcenterX: 0,\n\tcenterY: 0,\n\tedgeThreshold: 2,\n};\n\nconst SCREEN_WRAP_BEHAVIOR_KEY = Symbol.for('zylem:behavior:screen-wrap');\n\n/**\n * ScreenWrapSystem - Wraps entities around 2D bounds\n *\n * The Rust `screen_wrap` Stage behavior owns the wrap; this system attaches\n * it and mirrors the queried state into the local FSM so handles can observe\n * edge/wrap states.\n */\nclass ScreenWrapSystem 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?.(SCREEN_WRAP_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst gameEntity = link.entity as any;\n\t\t\tconst wrapRef = link.ref as any;\n\t\t\tconst options = wrapRef.options as ScreenWrapOptions;\n\n\t\t\tif (!wrapRef.fsm) {\n\t\t\t\twrapRef.fsm = new ScreenWrapFSM();\n\t\t\t}\n\n\t\t\tconst handle = (gameEntity.runtimeHandle ?? -1) as number;\n\t\t\tif (!this.wasmStage || handle < 0) continue;\n\n\t\t\tthis.ensureWasmAttached(handle, options);\n\t\t\tconst pose = this.wasmStage.getPose(handle);\n\t\t\tconst query = this.wasmStage.queryScreenWrap(handle);\n\t\t\tif (pose && query) {\n\t\t\t\tconst bounds = createBounds2DRect(options);\n\t\t\t\twrapRef.fsm.update(\n\t\t\t\t\t{ x: pose.position[0], y: pose.position[1] },\n\t\t\t\t\t{\n\t\t\t\t\t\tminX: bounds.minX,\n\t\t\t\t\t\tmaxX: bounds.maxX,\n\t\t\t\t\t\tminY: bounds.minY,\n\t\t\t\t\t\tmaxY: bounds.maxY,\n\t\t\t\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t\t\t\t},\n\t\t\t\t\tquery.lastWrappedAxes !== 0,\n\t\t\t\t);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate ensureWasmAttached(handle: number, options: ScreenWrapOptions): void {\n\t\tif (!this.wasmStage || this.attachedRuntimeSlots.has(handle)) return;\n\t\tthis.wasmStage.attachScreenWrap(handle, {\n\t\t\twidth: options.width,\n\t\t\theight: options.height,\n\t\t\tcenterX: options.centerX,\n\t\t\tcenterY: options.centerY,\n\t\t\tedgeThreshold: options.edgeThreshold,\n\t\t});\n\t\tthis.attachedRuntimeSlots.add(handle);\n\t}\n\n\tdestroy(_ecs: unknown): void {\n\t\tthis.attachedRuntimeSlots.clear();\n\t}\n}\n\n/**\n * ScreenWrapBehavior - Wraps entities around 2D bounds\n * \n * @example\n * ```typescript\n * import { ScreenWrapBehavior } from \"@zylem/game-lib/behavior\";\n * \n * const ship = createSprite({ ... });\n * const wrapRef = ship.use(ScreenWrapBehavior, { width: 20, height: 15 });\n * \n * // Access FSM to observe edge state\n * const fsm = wrapRef.getFSM();\n * console.log(fsm?.getState()); // 'center', 'near-edge-left', 'wrapped', etc.\n * ```\n */\nexport const ScreenWrapBehavior = defineBehavior({\n\tname: 'screen-wrap',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew ScreenWrapSystem(ctx.wasmStage ?? null, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ScreenWrapState","ScreenWrapEvent"]}
@@ -0,0 +1,30 @@
1
+ import {
2
+ getEntityFacingAngle2D,
3
+ resolveAim2D
4
+ } from "./chunk-TBUKJKY6.js";
5
+
6
+ // src/lib/coordinators/shared/shooter-fire.ts
7
+ function fireShooter2DIfRequested(entity, shooter, stage, input, aim) {
8
+ if (!input.shootPressed && !input.shootHeld) {
9
+ return false;
10
+ }
11
+ const sourcePosition = entity.body?.translation();
12
+ if (!sourcePosition) {
13
+ return false;
14
+ }
15
+ const resolved = aim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));
16
+ void shooter.fire({
17
+ source: entity,
18
+ stage,
19
+ target: {
20
+ x: sourcePosition.x + resolved.direction.x,
21
+ y: sourcePosition.y + resolved.direction.y
22
+ }
23
+ });
24
+ return true;
25
+ }
26
+
27
+ export {
28
+ fireShooter2DIfRequested
29
+ };
30
+ //# sourceMappingURL=chunk-52FJIDGL.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/coordinators/shared/shooter-fire.ts"],"sourcesContent":["import type {\n\tShooter2DHandle,\n\tShooter2DSourceEntity,\n\tShooter2DStageLike,\n} from '../../behaviors/shooter-2d/shooter-2d.descriptor';\nimport {\n\tgetEntityFacingAngle2D,\n\tresolveAim2D,\n\ttype ResolvedAim2D,\n} from './top-down-aim';\n\n/**\n * Aim + fire input fields shared by the 2D shooter coordinators\n * (top-down-shooter, multidirectional-space-shooter).\n */\nexport interface Shooter2DAimFireInput {\n\taimX: number;\n\taimY: number;\n\tshootPressed: boolean;\n\tshootHeld: boolean;\n}\n\n/**\n * Fire the entity's Shooter2D behavior when the input requests it.\n *\n * Resolves the 2D aim (explicit aim axes, falling back to the entity's\n * current facing) and fires toward a point one unit along the aim direction.\n * Pass a pre-resolved `aim` to avoid recomputing when the coordinator already\n * resolved it for rotation.\n *\n * @returns true when a fire attempt was issued\n */\nexport function fireShooter2DIfRequested(\n\tentity: Shooter2DSourceEntity,\n\tshooter: Shooter2DHandle,\n\tstage: Shooter2DStageLike,\n\tinput: Shooter2DAimFireInput,\n\taim?: ResolvedAim2D,\n): boolean {\n\tif (!input.shootPressed && !input.shootHeld) {\n\t\treturn false;\n\t}\n\n\tconst sourcePosition = entity.body?.translation();\n\tif (!sourcePosition) {\n\t\treturn false;\n\t}\n\n\tconst resolved =\n\t\taim ?? resolveAim2D(input.aimX, input.aimY, getEntityFacingAngle2D(entity));\n\n\tvoid shooter.fire({\n\t\tsource: entity,\n\t\tstage,\n\t\ttarget: {\n\t\t\tx: sourcePosition.x + resolved.direction.x,\n\t\t\ty: sourcePosition.y + resolved.direction.y,\n\t\t},\n\t});\n\treturn true;\n}\n"],"mappings":";;;;;;AAgCO,SAAS,yBACf,QACA,SACA,OACA,OACA,KACU;AACV,MAAI,CAAC,MAAM,gBAAgB,CAAC,MAAM,WAAW;AAC5C,WAAO;AAAA,EACR;AAEA,QAAM,iBAAiB,OAAO,MAAM,YAAY;AAChD,MAAI,CAAC,gBAAgB;AACpB,WAAO;AAAA,EACR;AAEA,QAAM,WACL,OAAO,aAAa,MAAM,MAAM,MAAM,MAAM,uBAAuB,MAAM,CAAC;AAE3E,OAAK,QAAQ,KAAK;AAAA,IACjB,QAAQ;AAAA,IACR;AAAA,IACA,QAAQ;AAAA,MACP,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,MACzC,GAAG,eAAe,IAAI,SAAS,UAAU;AAAA,IAC1C;AAAA,EACD,CAAC;AACD,SAAO;AACR;","names":[]}
@@ -21,4 +21,4 @@ export {
21
21
  createTransformComponent,
22
22
  createPhysicsBodyComponent
23
23
  };
24
- //# sourceMappingURL=chunk-UYFOLI2X.js.map
24
+ //# sourceMappingURL=chunk-5A5AYP2C.js.map