@zylem/game-lib 0.6.2 → 0.6.4

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 (82) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +9 -16
  3. package/dist/actions.d.ts +30 -21
  4. package/dist/actions.js +793 -146
  5. package/dist/actions.js.map +1 -1
  6. package/dist/behavior/jumper-2d.d.ts +114 -0
  7. package/dist/behavior/jumper-2d.js +711 -0
  8. package/dist/behavior/jumper-2d.js.map +1 -0
  9. package/dist/behavior/platformer-3d.d.ts +296 -0
  10. package/dist/behavior/platformer-3d.js +761 -0
  11. package/dist/behavior/platformer-3d.js.map +1 -0
  12. package/dist/behavior/ricochet-2d.d.ts +275 -0
  13. package/dist/behavior/ricochet-2d.js +425 -0
  14. package/dist/behavior/ricochet-2d.js.map +1 -0
  15. package/dist/behavior/ricochet-3d.d.ts +117 -0
  16. package/dist/behavior/ricochet-3d.js +443 -0
  17. package/dist/behavior/ricochet-3d.js.map +1 -0
  18. package/dist/behavior/screen-visibility.d.ts +79 -0
  19. package/dist/behavior/screen-visibility.js +358 -0
  20. package/dist/behavior/screen-visibility.js.map +1 -0
  21. package/dist/behavior/screen-wrap.d.ts +87 -0
  22. package/dist/behavior/screen-wrap.js +246 -0
  23. package/dist/behavior/screen-wrap.js.map +1 -0
  24. package/dist/behavior/shooter-2d.d.ts +79 -0
  25. package/dist/behavior/shooter-2d.js +180 -0
  26. package/dist/behavior/shooter-2d.js.map +1 -0
  27. package/dist/behavior/thruster.d.ts +11 -0
  28. package/dist/behavior/thruster.js +292 -0
  29. package/dist/behavior/thruster.js.map +1 -0
  30. package/dist/behavior/top-down-movement.d.ts +56 -0
  31. package/dist/behavior/top-down-movement.js +125 -0
  32. package/dist/behavior/top-down-movement.js.map +1 -0
  33. package/dist/behavior/world-boundary-2d.d.ts +142 -0
  34. package/dist/behavior/world-boundary-2d.js +235 -0
  35. package/dist/behavior/world-boundary-2d.js.map +1 -0
  36. package/dist/behavior/world-boundary-3d.d.ts +76 -0
  37. package/dist/behavior/world-boundary-3d.js +274 -0
  38. package/dist/behavior/world-boundary-3d.js.map +1 -0
  39. package/dist/behavior-descriptor-BXnVR8Ki.d.ts +159 -0
  40. package/dist/{blueprints-Cq3Ko6_G.d.ts → blueprints-DmbK2dki.d.ts} +2 -2
  41. package/dist/camera-4XO5gbQH.d.ts +905 -0
  42. package/dist/camera.d.ts +3 -2
  43. package/dist/camera.js +1653 -377
  44. package/dist/camera.js.map +1 -1
  45. package/dist/composition-BASvMKrW.d.ts +218 -0
  46. package/dist/{core-bO8TzV7u.d.ts → core-CARRaS55.d.ts} +110 -69
  47. package/dist/core.d.ts +11 -6
  48. package/dist/core.js +10766 -5626
  49. package/dist/core.js.map +1 -1
  50. package/dist/{entities-DvByhMGU.d.ts → entities-ChFirVL9.d.ts} +133 -29
  51. package/dist/entities.d.ts +5 -3
  52. package/dist/entities.js +4679 -3202
  53. package/dist/entities.js.map +1 -1
  54. package/dist/entity-vj-HTjzU.d.ts +1169 -0
  55. package/dist/global-change-2JvMaz44.d.ts +25 -0
  56. package/dist/main.d.ts +1118 -16
  57. package/dist/main.js +17538 -8499
  58. package/dist/main.js.map +1 -1
  59. package/dist/physics-pose-DCc4oE44.d.ts +25 -0
  60. package/dist/physics-protocol-BDD3P5W2.d.ts +200 -0
  61. package/dist/physics-worker.d.ts +21 -0
  62. package/dist/physics-worker.js +306 -0
  63. package/dist/physics-worker.js.map +1 -0
  64. package/dist/physics.d.ts +205 -0
  65. package/dist/physics.js +577 -0
  66. package/dist/physics.js.map +1 -0
  67. package/dist/stage-types-C19IhuzA.d.ts +731 -0
  68. package/dist/stage.d.ts +11 -7
  69. package/dist/stage.js +8024 -3852
  70. package/dist/stage.js.map +1 -1
  71. package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
  72. package/dist/thruster-23lzoPZd.d.ts +180 -0
  73. package/dist/world-DfgxoNMt.d.ts +105 -0
  74. package/package.json +53 -13
  75. package/dist/behaviors.d.ts +0 -854
  76. package/dist/behaviors.js +0 -1209
  77. package/dist/behaviors.js.map +0 -1
  78. package/dist/camera-CeJPAgGg.d.ts +0 -116
  79. package/dist/moveable-B_vyA6cw.d.ts +0 -67
  80. package/dist/stage-types-Bd-KtcYT.d.ts +0 -375
  81. package/dist/transformable-CUhvyuYO.d.ts +0 -67
  82. package/dist/world-C8tQ7Plj.d.ts +0 -774
@@ -0,0 +1,292 @@
1
+ // src/lib/behaviors/thruster/components.ts
2
+ function createThrusterMovementComponent(linearThrust, angularThrust, options) {
3
+ return {
4
+ linearThrust,
5
+ angularThrust,
6
+ linearDamping: options?.linearDamping,
7
+ angularDamping: options?.angularDamping
8
+ };
9
+ }
10
+ function createThrusterInputComponent() {
11
+ return {
12
+ thrust: 0,
13
+ rotate: 0,
14
+ thrustX: 0,
15
+ thrustY: 0
16
+ };
17
+ }
18
+ function createThrusterStateComponent() {
19
+ return {
20
+ enabled: true,
21
+ currentThrust: 0
22
+ };
23
+ }
24
+
25
+ // src/lib/core/utility/sync-state-machine.ts
26
+ import {
27
+ StateMachine as BaseStateMachine
28
+ } from "typescript-fsm";
29
+ import { t } from "typescript-fsm";
30
+ var SyncStateMachine = class extends BaseStateMachine {
31
+ constructor(init, transitions = [], logger = console) {
32
+ super(init, transitions, logger);
33
+ }
34
+ dispatch(_event, ..._args) {
35
+ throw new Error("SyncStateMachine does not support async dispatch.");
36
+ }
37
+ syncDispatch(event, ...args) {
38
+ const found = this.transitions.some((transition) => {
39
+ if (transition.fromState !== this._current || transition.event !== event) {
40
+ return false;
41
+ }
42
+ const current = this._current;
43
+ this._current = transition.toState;
44
+ if (!transition.cb) {
45
+ return true;
46
+ }
47
+ try {
48
+ transition.cb(...args);
49
+ return true;
50
+ } catch (error) {
51
+ this._current = current;
52
+ this.logger.error("Exception in callback", error);
53
+ throw error;
54
+ }
55
+ });
56
+ if (!found) {
57
+ const errorMessage = this.formatErr(this._current, event);
58
+ this.logger.error(errorMessage);
59
+ }
60
+ return found;
61
+ }
62
+ };
63
+
64
+ // src/lib/behaviors/thruster/thruster-fsm.ts
65
+ var ThrusterState = /* @__PURE__ */ ((ThrusterState2) => {
66
+ ThrusterState2["Idle"] = "idle";
67
+ ThrusterState2["Active"] = "active";
68
+ ThrusterState2["Boosting"] = "boosting";
69
+ ThrusterState2["Disabled"] = "disabled";
70
+ ThrusterState2["Docked"] = "docked";
71
+ return ThrusterState2;
72
+ })(ThrusterState || {});
73
+ var ThrusterEvent = /* @__PURE__ */ ((ThrusterEvent2) => {
74
+ ThrusterEvent2["Activate"] = "activate";
75
+ ThrusterEvent2["Deactivate"] = "deactivate";
76
+ ThrusterEvent2["Boost"] = "boost";
77
+ ThrusterEvent2["EndBoost"] = "endBoost";
78
+ ThrusterEvent2["Disable"] = "disable";
79
+ ThrusterEvent2["Enable"] = "enable";
80
+ ThrusterEvent2["Dock"] = "dock";
81
+ ThrusterEvent2["Undock"] = "undock";
82
+ return ThrusterEvent2;
83
+ })(ThrusterEvent || {});
84
+ var ThrusterFSM = class {
85
+ constructor(ctx) {
86
+ this.ctx = ctx;
87
+ this.machine = new SyncStateMachine(
88
+ "idle" /* Idle */,
89
+ [
90
+ // Core transitions
91
+ t("idle" /* Idle */, "activate" /* Activate */, "active" /* Active */),
92
+ t("active" /* Active */, "deactivate" /* Deactivate */, "idle" /* Idle */),
93
+ t("active" /* Active */, "boost" /* Boost */, "boosting" /* Boosting */),
94
+ t("active" /* Active */, "disable" /* Disable */, "disabled" /* Disabled */),
95
+ t("active" /* Active */, "dock" /* Dock */, "docked" /* Docked */),
96
+ t("boosting" /* Boosting */, "endBoost" /* EndBoost */, "active" /* Active */),
97
+ t("boosting" /* Boosting */, "disable" /* Disable */, "disabled" /* Disabled */),
98
+ t("disabled" /* Disabled */, "enable" /* Enable */, "idle" /* Idle */),
99
+ t("docked" /* Docked */, "undock" /* Undock */, "idle" /* Idle */),
100
+ // Self-transitions (no-ops for redundant events)
101
+ t("idle" /* Idle */, "deactivate" /* Deactivate */, "idle" /* Idle */),
102
+ t("active" /* Active */, "activate" /* Activate */, "active" /* Active */)
103
+ ]
104
+ );
105
+ }
106
+ machine;
107
+ /**
108
+ * Get current state
109
+ */
110
+ getState() {
111
+ return this.machine.getState();
112
+ }
113
+ /**
114
+ * Dispatch an event to transition state
115
+ */
116
+ dispatch(event) {
117
+ if (this.machine.can(event)) {
118
+ this.machine.syncDispatch(event);
119
+ }
120
+ }
121
+ /**
122
+ * Update FSM state based on player input.
123
+ * Auto-transitions between Idle/Active to report current state.
124
+ * Does NOT modify input - just observes and reports.
125
+ */
126
+ update(playerInput) {
127
+ const state = this.machine.getState();
128
+ const hasInput = Math.abs(playerInput.thrust) > 0.01 || Math.abs(playerInput.rotate) > 0.01 || Math.abs(playerInput.thrustX ?? 0) > 0.01 || Math.abs(playerInput.thrustY ?? 0) > 0.01;
129
+ if (hasInput && state === "idle" /* Idle */) {
130
+ this.dispatch("activate" /* Activate */);
131
+ } else if (!hasInput && state === "active" /* Active */) {
132
+ this.dispatch("deactivate" /* Deactivate */);
133
+ }
134
+ }
135
+ };
136
+
137
+ // src/lib/behaviors/shared/direction-2d.ts
138
+ var DIRECTION_EPSILON = 1e-6;
139
+ function normalizeDirection2D(x, y) {
140
+ const length = Math.hypot(x, y);
141
+ if (length <= DIRECTION_EPSILON) {
142
+ return null;
143
+ }
144
+ return {
145
+ x: x / length,
146
+ y: y / length
147
+ };
148
+ }
149
+ function getRotationAngle2D(rotation) {
150
+ return Math.atan2(
151
+ 2 * (rotation.w * rotation.z + rotation.x * rotation.y),
152
+ 1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z)
153
+ );
154
+ }
155
+
156
+ // src/lib/behaviors/thruster/thruster-movement.behavior.ts
157
+ var ThrusterMovementBehavior = class {
158
+ constructor(world) {
159
+ this.world = world;
160
+ }
161
+ /**
162
+ * Update a single thruster-enabled entity.
163
+ */
164
+ updateEntity(gameEntity, _dt) {
165
+ if (!gameEntity.physics?.body || !gameEntity.thruster || !gameEntity.$thruster) {
166
+ return;
167
+ }
168
+ const e = {
169
+ physics: gameEntity.physics,
170
+ thruster: gameEntity.thruster,
171
+ $thruster: gameEntity.$thruster
172
+ };
173
+ const body = e.physics.body;
174
+ const thruster = e.thruster;
175
+ const input = e.$thruster;
176
+ if (thruster.linearDamping != null && typeof body.setLinearDamping === "function") {
177
+ body.setLinearDamping(thruster.linearDamping);
178
+ }
179
+ const rotationZ = getRotationAngle2D(body.rotation());
180
+ const currentVel = body.linvel();
181
+ const vectorThrust = normalizeDirection2D(input.thrustX, input.thrustY);
182
+ if (vectorThrust) {
183
+ const thrustScale = Math.min(1, Math.hypot(input.thrustX, input.thrustY));
184
+ const thrustAmount = thruster.linearThrust * thrustScale * 0.1;
185
+ body.setLinvel({
186
+ x: currentVel.x + vectorThrust.x * thrustAmount,
187
+ y: currentVel.y + vectorThrust.y * thrustAmount,
188
+ z: currentVel.z
189
+ }, true);
190
+ } else if (input.thrust !== 0) {
191
+ const forwardX = Math.sin(-rotationZ);
192
+ const forwardY = Math.cos(-rotationZ);
193
+ const thrustAmount = thruster.linearThrust * input.thrust * 0.1;
194
+ body.setLinvel({
195
+ x: currentVel.x + forwardX * thrustAmount,
196
+ y: currentVel.y + forwardY * thrustAmount,
197
+ z: currentVel.z
198
+ }, true);
199
+ }
200
+ if (input.rotate !== 0) {
201
+ body.setAngvel({ x: 0, y: 0, z: -thruster.angularThrust * input.rotate }, true);
202
+ } else {
203
+ const angVel = body.angvel();
204
+ body.setAngvel({ x: angVel.x, y: angVel.y, z: 0 }, true);
205
+ }
206
+ }
207
+ update(dt) {
208
+ if (!this.world?.collisionMap) return;
209
+ for (const [, entity] of this.world.collisionMap) {
210
+ this.updateEntity(entity, dt);
211
+ }
212
+ }
213
+ };
214
+
215
+ // src/lib/behaviors/behavior-descriptor.ts
216
+ function defineBehavior(config) {
217
+ return {
218
+ key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
219
+ defaultOptions: config.defaultOptions,
220
+ systemFactory: config.systemFactory,
221
+ createHandle: config.createHandle
222
+ };
223
+ }
224
+
225
+ // src/lib/behaviors/thruster/thruster.descriptor.ts
226
+ var defaultOptions = {
227
+ linearThrust: 10,
228
+ angularThrust: 5,
229
+ linearDamping: void 0
230
+ };
231
+ var THRUSTER_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:thruster");
232
+ var ThrusterBehaviorSystem = class {
233
+ constructor(world, getBehaviorLinks) {
234
+ this.world = world;
235
+ this.getBehaviorLinks = getBehaviorLinks;
236
+ this.movementBehavior = new ThrusterMovementBehavior(world);
237
+ }
238
+ movementBehavior;
239
+ update(_ecs, delta) {
240
+ const links = this.getBehaviorLinks?.(THRUSTER_BEHAVIOR_KEY);
241
+ if (!links) return;
242
+ for (const link of links) {
243
+ const gameEntity = link.entity;
244
+ const thrusterRef = link.ref;
245
+ if (!gameEntity.body) continue;
246
+ const options = thrusterRef.options;
247
+ if (!gameEntity.thruster) {
248
+ gameEntity.thruster = createThrusterMovementComponent(
249
+ options.linearThrust,
250
+ options.angularThrust,
251
+ { linearDamping: options.linearDamping }
252
+ );
253
+ }
254
+ if (!gameEntity.$thruster) {
255
+ gameEntity.$thruster = createThrusterInputComponent();
256
+ }
257
+ if (!gameEntity.physics) {
258
+ gameEntity.physics = { body: gameEntity.body };
259
+ }
260
+ if (!thrusterRef.fsm && gameEntity.$thruster) {
261
+ thrusterRef.fsm = new ThrusterFSM({ input: gameEntity.$thruster });
262
+ }
263
+ if (thrusterRef.fsm && gameEntity.$thruster) {
264
+ thrusterRef.fsm.update({
265
+ thrust: gameEntity.$thruster.thrust,
266
+ rotate: gameEntity.$thruster.rotate,
267
+ thrustX: gameEntity.$thruster.thrustX,
268
+ thrustY: gameEntity.$thruster.thrustY
269
+ });
270
+ }
271
+ this.movementBehavior.updateEntity(gameEntity, delta);
272
+ }
273
+ }
274
+ destroy(_ecs) {
275
+ }
276
+ };
277
+ var ThrusterBehavior = defineBehavior({
278
+ name: "thruster",
279
+ defaultOptions,
280
+ systemFactory: (ctx) => new ThrusterBehaviorSystem(ctx.world, ctx.getBehaviorLinks)
281
+ });
282
+ export {
283
+ ThrusterBehavior,
284
+ ThrusterEvent,
285
+ ThrusterFSM,
286
+ ThrusterMovementBehavior,
287
+ ThrusterState,
288
+ createThrusterInputComponent,
289
+ createThrusterMovementComponent,
290
+ createThrusterStateComponent
291
+ };
292
+ //# sourceMappingURL=thruster.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/behaviors/thruster/components.ts","../../src/lib/core/utility/sync-state-machine.ts","../../src/lib/behaviors/thruster/thruster-fsm.ts","../../src/lib/behaviors/shared/direction-2d.ts","../../src/lib/behaviors/thruster/thruster-movement.behavior.ts","../../src/lib/behaviors/behavior-descriptor.ts","../../src/lib/behaviors/thruster/thruster.descriptor.ts"],"sourcesContent":["/**\n * Thruster-specific ECS Components\n * \n * These components are specific to the thruster movement system.\n */\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterMovementComponent (capability / hardware spec)\n// Defines the thrust capabilities of an entity\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterMovementComponent {\n\t/** Linear thrust force in Newtons (or scaled units) */\n\tlinearThrust: number;\n\t/** Angular thrust torque scalar */\n\tangularThrust: number;\n\t/** Optional linear damping override */\n\tlinearDamping?: number;\n\t/** Optional angular damping override */\n\tangularDamping?: number;\n}\n\nexport function createThrusterMovementComponent(\n\tlinearThrust: number,\n\tangularThrust: number,\n\toptions?: { linearDamping?: number; angularDamping?: number }\n): ThrusterMovementComponent {\n\treturn {\n\t\tlinearThrust,\n\t\tangularThrust,\n\t\tlinearDamping: options?.linearDamping,\n\t\tangularDamping: options?.angularDamping,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterInputComponent (intent)\n// Written by: Player controller, AI controller, FSM adapter\n// Read by: ThrusterMovementBehavior\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterInputComponent {\n\t/** Forward thrust intent: 0..1 */\n\tthrust: number;\n\t/** Rotation intent: -1..1 */\n\trotate: number;\n\t/** World-space X thrust intent: -1..1 */\n\tthrustX: number;\n\t/** World-space Y thrust intent: -1..1 */\n\tthrustY: number;\n}\n\nexport function createThrusterInputComponent(): ThrusterInputComponent {\n\treturn {\n\t\tthrust: 0,\n\t\trotate: 0,\n\t\tthrustX: 0,\n\t\tthrustY: 0,\n\t};\n}\n\n// ─────────────────────────────────────────────────────────────────────────────\n// ThrusterStateComponent (optional but useful)\n// Useful for: damage, EMP, overheating, UI feedback\n// ─────────────────────────────────────────────────────────────────────────────\n\nexport interface ThrusterStateComponent {\n\t/** Whether the thruster is enabled */\n\tenabled: boolean;\n\t/** Current thrust after FSM/gating */\n\tcurrentThrust: number;\n}\n\nexport function createThrusterStateComponent(): ThrusterStateComponent {\n\treturn {\n\t\tenabled: true,\n\t\tcurrentThrust: 0,\n\t};\n}\n","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","/**\n * ThrusterFSM\n * \n * State machine controller for thruster behavior.\n * FSM does NOT touch physics or ThrusterMovementBehavior - 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","export interface Direction2D {\n\tx: number;\n\ty: number;\n}\n\nconst DIRECTION_EPSILON = 1e-6;\n\nexport function normalizeDirection2D(x: number, y: number): Direction2D | null {\n\tconst length = Math.hypot(x, y);\n\tif (length <= DIRECTION_EPSILON) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: x / length,\n\t\ty: y / length,\n\t};\n}\n\nexport function angleFromDirection2D(direction: Direction2D): number {\n\treturn Math.atan2(-direction.x, direction.y);\n}\n\nexport function directionFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.sin(-angle),\n\t\ty: Math.cos(-angle),\n\t};\n}\n\nexport function rightFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.cos(angle),\n\t\ty: Math.sin(angle),\n\t};\n}\n\nexport function rotateLocalOffset2D(\n\toffset: Direction2D,\n\tangle: number,\n): Direction2D {\n\tconst right = rightFromAngle2D(angle);\n\tconst forward = directionFromAngle2D(angle);\n\treturn {\n\t\tx: right.x * offset.x + forward.x * offset.y,\n\t\ty: right.y * offset.x + forward.y * offset.y,\n\t};\n}\n\nexport function getRotationAngle2D(\n\trotation: { x: number; y: number; z: number; w: number },\n): number {\n\treturn Math.atan2(\n\t\t2 * (rotation.w * rotation.z + rotation.x * rotation.y),\n\t\t1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z),\n\t);\n}\n","/**\n * ThrusterMovementBehavior\n * \n * This is the heart of the thruster movement system - a pure, stateless force generator.\n * Works identically for player, AI, and replay.\n */\n\nimport type { ZylemWorld } from '../../collision/world';\nimport type { PhysicsBodyComponent } from '../components';\nimport type { ThrusterMovementComponent, ThrusterInputComponent } from './components';\nimport {\n\tgetRotationAngle2D,\n\tnormalizeDirection2D,\n} from '../shared/direction-2d';\n\n/**\n * Zylem-style Behavior interface\n */\nexport interface Behavior {\n\tupdate(dt: number): void;\n}\n\n/**\n * Entity with thruster components\n */\nexport interface ThrusterEntity {\n\tphysics: PhysicsBodyComponent;\n\tthruster: ThrusterMovementComponent;\n\t$thruster: ThrusterInputComponent;\n}\n\n/**\n * ThrusterMovementBehavior - Force generator for thruster-equipped entities\n * \n * Responsibilities:\n * - Query entities with PhysicsBody, ThrusterMovement, and ThrusterInput components\n * - Apply velocities based on thrust input (2D mode)\n * - Apply angular velocity based on rotation input\n */\nexport class ThrusterMovementBehavior implements Behavior {\n\tconstructor(private world: ZylemWorld) {}\n\n\t/**\n\t * Update a single thruster-enabled entity.\n\t */\n\tupdateEntity(gameEntity: any, _dt: number): void {\n\t\tif (!gameEntity.physics?.body || !gameEntity.thruster || !gameEntity.$thruster) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst e: ThrusterEntity = {\n\t\t\tphysics: gameEntity.physics,\n\t\t\tthruster: gameEntity.thruster,\n\t\t\t$thruster: gameEntity.$thruster,\n\t\t};\n\n\t\tconst body = e.physics.body;\n\t\tconst thruster = e.thruster;\n\t\tconst input = e.$thruster;\n\n\t\tif (thruster.linearDamping != null && typeof body.setLinearDamping === 'function') {\n\t\t\tbody.setLinearDamping(thruster.linearDamping);\n\t\t}\n\n\t\t// Get Z rotation from quaternion (for 2D sprites)\n\t\tconst rotationZ = getRotationAngle2D(body.rotation());\n\t\tconst currentVel = body.linvel();\n\t\tconst vectorThrust = normalizeDirection2D(input.thrustX, input.thrustY);\n\n\t\t// --- Linear thrust ---\n\t\tif (vectorThrust) {\n\t\t\tconst thrustScale = Math.min(1, Math.hypot(input.thrustX, input.thrustY));\n\t\t\tconst thrustAmount = thruster.linearThrust * thrustScale * 0.1;\n\t\t\tbody.setLinvel({\n\t\t\t\tx: currentVel.x + vectorThrust.x * thrustAmount,\n\t\t\t\ty: currentVel.y + vectorThrust.y * thrustAmount,\n\t\t\t\tz: currentVel.z,\n\t\t\t}, true);\n\t\t} else if (input.thrust !== 0) {\n\t\t\tconst forwardX = Math.sin(-rotationZ);\n\t\t\tconst forwardY = Math.cos(-rotationZ);\n\t\t\tconst thrustAmount = thruster.linearThrust * input.thrust * 0.1;\n\t\t\tbody.setLinvel({\n\t\t\t\tx: currentVel.x + forwardX * thrustAmount,\n\t\t\t\ty: currentVel.y + forwardY * thrustAmount,\n\t\t\t\tz: currentVel.z\n\t\t\t}, true);\n\t\t}\n\n\t\t// --- Angular thrust (Z-axis for 2D) ---\n\t\tif (input.rotate !== 0) {\n\t\t\tbody.setAngvel({ x: 0, y: 0, z: -thruster.angularThrust * input.rotate }, true);\n\t\t} else {\n\t\t\t// Stop rotation when no input\n\t\t\tconst angVel = body.angvel();\n\t\t\tbody.setAngvel({ x: angVel.x, y: angVel.y, z: 0 }, true);\n\t\t}\n\t}\n\n\tupdate(dt: number): void {\n\t\tif (!this.world?.collisionMap) return;\n\t\tfor (const [, entity] of this.world.collisionMap) {\n\t\t\tthis.updateEntity(entity, dt);\n\t\t}\n\t}\n}\n","/**\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: BehaviorSystemFactory;\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: BehaviorRef<O>) => 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","/**\n * Thruster Behavior Descriptor\n * \n * Type-safe descriptor for the thruster behavior system using the new entity.use() API.\n * This wraps the existing ThrusterMovementBehavior and components.\n */\n\nimport type { IWorld } from 'bitecs';\nimport { defineBehavior } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport { ThrusterMovementBehavior, ThrusterEntity } from './thruster-movement.behavior';\nimport { ThrusterFSM } from './thruster-fsm';\nimport {\n\tcreateThrusterInputComponent,\n\tcreateThrusterMovementComponent,\n\ttype ThrusterMovementComponent,\n\ttype ThrusterInputComponent,\n} from './components';\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 wraps ThrusterMovementBehavior as a BehaviorSystem.\n * \n * This bridges the entity.use() pattern with the ECS component-based approach:\n * - Reads options from entity behaviorRefs\n * - Initializes ThrusterMovementComponent and ThrusterInputComponent on entities\n * - Creates FSM lazily and attaches to BehaviorRef for access via handle.getFSM()\n * - Delegates physics to ThrusterMovementBehavior\n * \n * NOTE: Input is controlled by the user via entity.onUpdate() - set entity.input.thrust/rotate\n */\nclass ThrusterBehaviorSystem implements BehaviorSystem {\n\tprivate movementBehavior: ThrusterMovementBehavior;\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {\n\t\tthis.movementBehavior = new ThrusterMovementBehavior(world);\n\t}\n\n\tupdate(_ecs: IWorld, 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\t\t\tif (!gameEntity.body) continue;\n\n\t\t\tconst options = thrusterRef.options as ThrusterBehaviorOptions;\n\n\t\t\t// Ensure entity has thruster components (initialized once)\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\tif (!gameEntity.physics) {\n\t\t\t\tgameEntity.physics = { body: gameEntity.body };\n\t\t\t}\n\n\t\t\t// Create FSM lazily and attach to the BehaviorRef for handle.getFSM()\n\t\t\tif (!thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm = new ThrusterFSM({ input: gameEntity.$thruster });\n\t\t\t}\n\n\t\t\t// Update FSM to sync state with input (auto-transitions)\n\t\t\tif (thrusterRef.fsm && gameEntity.$thruster) {\n\t\t\t\tthrusterRef.fsm.update({\n\t\t\t\t\tthrust: gameEntity.$thruster.thrust,\n\t\t\t\t\trotate: gameEntity.$thruster.rotate,\n\t\t\t\t\tthrustX: gameEntity.$thruster.thrustX,\n\t\t\t\t\tthrustY: gameEntity.$thruster.thrustY,\n\t\t\t\t});\n\t\t\t}\n\n\t\t\tthis.movementBehavior.updateEntity(gameEntity, delta);\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\t// Cleanup if needed\n\t}\n}\n\n/**\n * ThrusterBehavior - typed descriptor for thruster movement.\n * \n * Uses the existing ThrusterMovementBehavior under the hood.\n * \n * @example\n * ```typescript\n * import { ThrusterBehavior } from \"@zylem/game-lib\";\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.world, ctx.getBehaviorLinks),\n});\n"],"mappings":"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","names":["ThrusterState","ThrusterEvent"]}
@@ -0,0 +1,56 @@
1
+ import { c as BehaviorDescriptor } from '../behavior-descriptor-BXnVR8Ki.js';
2
+ import 'bitecs';
3
+
4
+ interface TopDownMovementComponent {
5
+ moveSpeed: number;
6
+ }
7
+ declare function createTopDownMovementComponent(options?: Partial<TopDownMovementComponent>): TopDownMovementComponent;
8
+ interface TopDownMovementInputComponent {
9
+ moveX: number;
10
+ moveY: number;
11
+ faceX: number;
12
+ faceY: number;
13
+ }
14
+ declare function createTopDownMovementInputComponent(): TopDownMovementInputComponent;
15
+ interface TopDownMovementStateComponent {
16
+ facingAngle: number;
17
+ moving: boolean;
18
+ }
19
+ declare function createTopDownMovementStateComponent(): TopDownMovementStateComponent;
20
+
21
+ interface TopDownMovementEntity {
22
+ body: {
23
+ linvel(): {
24
+ x: number;
25
+ y: number;
26
+ z: number;
27
+ };
28
+ };
29
+ transformStore?: {
30
+ velocity: {
31
+ x: number;
32
+ y: number;
33
+ z: number;
34
+ };
35
+ dirty: {
36
+ velocity: boolean;
37
+ };
38
+ };
39
+ setRotationZ?: (angle: number) => void;
40
+ topDownMovement: TopDownMovementComponent;
41
+ $topDownMovement: TopDownMovementInputComponent;
42
+ topDownMovementState: TopDownMovementStateComponent;
43
+ }
44
+ declare class TopDownMovementRuntimeBehavior {
45
+ updateEntity(entity: TopDownMovementEntity, _delta: number): void;
46
+ }
47
+
48
+ interface TopDownMovementBehaviorOptions {
49
+ moveSpeed?: number;
50
+ }
51
+ interface TopDownMovementHandle {
52
+ getFacingAngle(): number;
53
+ }
54
+ declare const TopDownMovementBehavior: BehaviorDescriptor<TopDownMovementBehaviorOptions, TopDownMovementHandle, TopDownMovementEntity>;
55
+
56
+ export { TopDownMovementBehavior, type TopDownMovementBehaviorOptions, type TopDownMovementComponent, type TopDownMovementEntity, type TopDownMovementHandle, type TopDownMovementInputComponent, TopDownMovementRuntimeBehavior, type TopDownMovementStateComponent, createTopDownMovementComponent, createTopDownMovementInputComponent, createTopDownMovementStateComponent };
@@ -0,0 +1,125 @@
1
+ // src/lib/behaviors/top-down-movement/components.ts
2
+ function createTopDownMovementComponent(options = {}) {
3
+ return {
4
+ moveSpeed: options.moveSpeed ?? 8
5
+ };
6
+ }
7
+ function createTopDownMovementInputComponent() {
8
+ return {
9
+ moveX: 0,
10
+ moveY: 0,
11
+ faceX: 0,
12
+ faceY: 0
13
+ };
14
+ }
15
+ function createTopDownMovementStateComponent() {
16
+ return {
17
+ facingAngle: 0,
18
+ moving: false
19
+ };
20
+ }
21
+
22
+ // src/lib/behaviors/shared/direction-2d.ts
23
+ var DIRECTION_EPSILON = 1e-6;
24
+ function normalizeDirection2D(x, y) {
25
+ const length = Math.hypot(x, y);
26
+ if (length <= DIRECTION_EPSILON) {
27
+ return null;
28
+ }
29
+ return {
30
+ x: x / length,
31
+ y: y / length
32
+ };
33
+ }
34
+ function angleFromDirection2D(direction) {
35
+ return Math.atan2(-direction.x, direction.y);
36
+ }
37
+
38
+ // src/lib/behaviors/top-down-movement/top-down-movement.behavior.ts
39
+ var TopDownMovementRuntimeBehavior = class {
40
+ updateEntity(entity, _delta) {
41
+ if (!entity.body || !entity.transformStore || !entity.topDownMovement || !entity.$topDownMovement || !entity.topDownMovementState) {
42
+ return;
43
+ }
44
+ const movement = entity.topDownMovement;
45
+ const input = entity.$topDownMovement;
46
+ const state = entity.topDownMovementState;
47
+ const moveDirection = normalizeDirection2D(input.moveX, input.moveY);
48
+ const velocity = entity.body.linvel();
49
+ state.moving = moveDirection !== null;
50
+ entity.transformStore.velocity.x = (moveDirection?.x ?? 0) * movement.moveSpeed;
51
+ entity.transformStore.velocity.y = (moveDirection?.y ?? 0) * movement.moveSpeed;
52
+ entity.transformStore.velocity.z = velocity.z;
53
+ entity.transformStore.dirty.velocity = true;
54
+ const facingDirection = normalizeDirection2D(input.faceX, input.faceY);
55
+ if (facingDirection) {
56
+ state.facingAngle = angleFromDirection2D(facingDirection);
57
+ }
58
+ entity.setRotationZ?.(state.facingAngle);
59
+ }
60
+ };
61
+
62
+ // src/lib/behaviors/behavior-descriptor.ts
63
+ function defineBehavior(config) {
64
+ return {
65
+ key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
66
+ defaultOptions: config.defaultOptions,
67
+ systemFactory: config.systemFactory,
68
+ createHandle: config.createHandle
69
+ };
70
+ }
71
+
72
+ // src/lib/behaviors/top-down-movement/top-down-movement.descriptor.ts
73
+ var defaultOptions = {
74
+ moveSpeed: 8
75
+ };
76
+ var TOP_DOWN_MOVEMENT_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for(
77
+ "zylem:behavior:top-down-movement"
78
+ );
79
+ function createTopDownMovementHandle(ref) {
80
+ return {
81
+ getFacingAngle: () => ref.__entity?.topDownMovementState?.facingAngle ?? 0
82
+ };
83
+ }
84
+ var TopDownMovementBehaviorSystem = class {
85
+ constructor(getBehaviorLinks) {
86
+ this.getBehaviorLinks = getBehaviorLinks;
87
+ }
88
+ behavior = new TopDownMovementRuntimeBehavior();
89
+ update(_ecs, delta) {
90
+ const links = this.getBehaviorLinks?.(TOP_DOWN_MOVEMENT_BEHAVIOR_KEY);
91
+ if (!links) return;
92
+ for (const link of links) {
93
+ const entity = link.entity;
94
+ const ref = link.ref;
95
+ if (!entity.body || !entity.transformStore) continue;
96
+ if (!entity.topDownMovement) {
97
+ entity.topDownMovement = createTopDownMovementComponent(ref.options);
98
+ }
99
+ if (!entity.$topDownMovement) {
100
+ entity.$topDownMovement = createTopDownMovementInputComponent();
101
+ }
102
+ if (!entity.topDownMovementState) {
103
+ entity.topDownMovementState = createTopDownMovementStateComponent();
104
+ }
105
+ ref.__entity = entity;
106
+ this.behavior.updateEntity(entity, delta);
107
+ }
108
+ }
109
+ destroy(_ecs) {
110
+ }
111
+ };
112
+ var TopDownMovementBehavior = defineBehavior({
113
+ name: "top-down-movement",
114
+ defaultOptions,
115
+ systemFactory: (ctx) => new TopDownMovementBehaviorSystem(ctx.getBehaviorLinks),
116
+ createHandle: createTopDownMovementHandle
117
+ });
118
+ export {
119
+ TopDownMovementBehavior,
120
+ TopDownMovementRuntimeBehavior,
121
+ createTopDownMovementComponent,
122
+ createTopDownMovementInputComponent,
123
+ createTopDownMovementStateComponent
124
+ };
125
+ //# sourceMappingURL=top-down-movement.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/behaviors/top-down-movement/components.ts","../../src/lib/behaviors/shared/direction-2d.ts","../../src/lib/behaviors/top-down-movement/top-down-movement.behavior.ts","../../src/lib/behaviors/behavior-descriptor.ts","../../src/lib/behaviors/top-down-movement/top-down-movement.descriptor.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","export interface Direction2D {\n\tx: number;\n\ty: number;\n}\n\nconst DIRECTION_EPSILON = 1e-6;\n\nexport function normalizeDirection2D(x: number, y: number): Direction2D | null {\n\tconst length = Math.hypot(x, y);\n\tif (length <= DIRECTION_EPSILON) {\n\t\treturn null;\n\t}\n\n\treturn {\n\t\tx: x / length,\n\t\ty: y / length,\n\t};\n}\n\nexport function angleFromDirection2D(direction: Direction2D): number {\n\treturn Math.atan2(-direction.x, direction.y);\n}\n\nexport function directionFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.sin(-angle),\n\t\ty: Math.cos(-angle),\n\t};\n}\n\nexport function rightFromAngle2D(angle: number): Direction2D {\n\treturn {\n\t\tx: Math.cos(angle),\n\t\ty: Math.sin(angle),\n\t};\n}\n\nexport function rotateLocalOffset2D(\n\toffset: Direction2D,\n\tangle: number,\n): Direction2D {\n\tconst right = rightFromAngle2D(angle);\n\tconst forward = directionFromAngle2D(angle);\n\treturn {\n\t\tx: right.x * offset.x + forward.x * offset.y,\n\t\ty: right.y * offset.x + forward.y * offset.y,\n\t};\n}\n\nexport function getRotationAngle2D(\n\trotation: { x: number; y: number; z: number; w: number },\n): number {\n\treturn Math.atan2(\n\t\t2 * (rotation.w * rotation.z + rotation.x * rotation.y),\n\t\t1 - 2 * (rotation.y * rotation.y + rotation.z * rotation.z),\n\t);\n}\n","import {\n\tangleFromDirection2D,\n\tnormalizeDirection2D,\n} from '../shared/direction-2d';\nimport type {\n\tTopDownMovementComponent,\n\tTopDownMovementInputComponent,\n\tTopDownMovementStateComponent,\n} from './components';\n\nexport interface TopDownMovementEntity {\n\tbody: {\n\t\tlinvel(): { x: number; y: number; z: number };\n\t};\n\ttransformStore?: {\n\t\tvelocity: { x: number; y: number; z: number };\n\t\tdirty: { velocity: boolean };\n\t};\n\tsetRotationZ?: (angle: number) => void;\n\ttopDownMovement: TopDownMovementComponent;\n\t$topDownMovement: TopDownMovementInputComponent;\n\ttopDownMovementState: TopDownMovementStateComponent;\n}\n\nexport class TopDownMovementRuntimeBehavior {\n\tupdateEntity(entity: TopDownMovementEntity, _delta: number): void {\n\t\tif (\n\t\t\t!entity.body\n\t\t\t|| !entity.transformStore\n\t\t\t|| !entity.topDownMovement\n\t\t\t|| !entity.$topDownMovement\n\t\t\t|| !entity.topDownMovementState\n\t\t) {\n\t\t\treturn;\n\t\t}\n\n\t\tconst movement = entity.topDownMovement;\n\t\tconst input = entity.$topDownMovement;\n\t\tconst state = entity.topDownMovementState;\n\t\tconst moveDirection = normalizeDirection2D(input.moveX, input.moveY);\n\t\tconst velocity = entity.body.linvel();\n\n\t\tstate.moving = moveDirection !== null;\n\t\tentity.transformStore.velocity.x = (moveDirection?.x ?? 0) * movement.moveSpeed;\n\t\tentity.transformStore.velocity.y = (moveDirection?.y ?? 0) * movement.moveSpeed;\n\t\tentity.transformStore.velocity.z = velocity.z;\n\t\tentity.transformStore.dirty.velocity = true;\n\n\t\tconst facingDirection = normalizeDirection2D(input.faceX, input.faceY);\n\t\tif (facingDirection) {\n\t\t\tstate.facingAngle = angleFromDirection2D(facingDirection);\n\t\t}\n\n\t\tentity.setRotationZ?.(state.facingAngle);\n\t}\n}\n","/**\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: BehaviorSystemFactory;\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: BehaviorRef<O>) => 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","import type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tcreateTopDownMovementComponent,\n\tcreateTopDownMovementInputComponent,\n\tcreateTopDownMovementStateComponent,\n} from './components';\nimport {\n\tTopDownMovementRuntimeBehavior,\n\ttype TopDownMovementEntity,\n} from './top-down-movement.behavior';\n\nexport interface TopDownMovementBehaviorOptions {\n\tmoveSpeed?: number;\n}\n\nexport interface TopDownMovementHandle {\n\tgetFacingAngle(): number;\n}\n\nconst defaultOptions: TopDownMovementBehaviorOptions = {\n\tmoveSpeed: 8,\n};\n\nconst TOP_DOWN_MOVEMENT_BEHAVIOR_KEY = Symbol.for(\n\t'zylem:behavior:top-down-movement',\n);\n\nfunction createTopDownMovementHandle(\n\tref: BehaviorRef<TopDownMovementBehaviorOptions>,\n): TopDownMovementHandle {\n\treturn {\n\t\tgetFacingAngle: () =>\n\t\t\t(ref as any).__entity?.topDownMovementState?.facingAngle ?? 0,\n\t};\n}\n\nclass TopDownMovementBehaviorSystem implements BehaviorSystem {\n\tprivate behavior = new TopDownMovementRuntimeBehavior();\n\n\tconstructor(\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: IWorld, delta: number): void {\n\t\tconst links = this.getBehaviorLinks?.(TOP_DOWN_MOVEMENT_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst entity = link.entity as any;\n\t\t\tconst ref = link.ref as any;\n\t\t\tif (!entity.body || !entity.transformStore) continue;\n\n\t\t\tif (!entity.topDownMovement) {\n\t\t\t\tentity.topDownMovement = createTopDownMovementComponent(ref.options);\n\t\t\t}\n\t\t\tif (!entity.$topDownMovement) {\n\t\t\t\tentity.$topDownMovement = createTopDownMovementInputComponent();\n\t\t\t}\n\t\t\tif (!entity.topDownMovementState) {\n\t\t\t\tentity.topDownMovementState = createTopDownMovementStateComponent();\n\t\t\t}\n\n\t\t\tref.__entity = entity;\n\t\t\tthis.behavior.updateEntity(entity, delta);\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {}\n}\n\nexport const TopDownMovementBehavior = defineBehavior<\n\tTopDownMovementBehaviorOptions,\n\tTopDownMovementHandle,\n\tTopDownMovementEntity\n>({\n\tname: 'top-down-movement',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew TopDownMovementBehaviorSystem(ctx.getBehaviorLinks),\n\tcreateHandle: createTopDownMovementHandle,\n});\n"],"mappings":"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","names":[]}