@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,443 @@
1
+ // src/lib/core/utility/sync-state-machine.ts
2
+ import {
3
+ StateMachine as BaseStateMachine
4
+ } from "typescript-fsm";
5
+ import { t } from "typescript-fsm";
6
+ var SyncStateMachine = class extends BaseStateMachine {
7
+ constructor(init, transitions = [], logger = console) {
8
+ super(init, transitions, logger);
9
+ }
10
+ dispatch(_event, ..._args) {
11
+ throw new Error("SyncStateMachine does not support async dispatch.");
12
+ }
13
+ syncDispatch(event, ...args) {
14
+ const found = this.transitions.some((transition) => {
15
+ if (transition.fromState !== this._current || transition.event !== event) {
16
+ return false;
17
+ }
18
+ const current = this._current;
19
+ this._current = transition.toState;
20
+ if (!transition.cb) {
21
+ return true;
22
+ }
23
+ try {
24
+ transition.cb(...args);
25
+ return true;
26
+ } catch (error) {
27
+ this._current = current;
28
+ this.logger.error("Exception in callback", error);
29
+ throw error;
30
+ }
31
+ });
32
+ if (!found) {
33
+ const errorMessage = this.formatErr(this._current, event);
34
+ this.logger.error(errorMessage);
35
+ }
36
+ return found;
37
+ }
38
+ };
39
+
40
+ // src/lib/behaviors/ricochet-3d/ricochet-3d-fsm.ts
41
+ var Ricochet3DState = /* @__PURE__ */ ((Ricochet3DState2) => {
42
+ Ricochet3DState2["Idle"] = "idle";
43
+ Ricochet3DState2["Ricocheting"] = "ricocheting";
44
+ return Ricochet3DState2;
45
+ })(Ricochet3DState || {});
46
+ var Ricochet3DEvent = /* @__PURE__ */ ((Ricochet3DEvent2) => {
47
+ Ricochet3DEvent2["StartRicochet"] = "start-ricochet";
48
+ Ricochet3DEvent2["EndRicochet"] = "end-ricochet";
49
+ return Ricochet3DEvent2;
50
+ })(Ricochet3DEvent || {});
51
+ function clamp(value, min, max) {
52
+ return Math.max(min, Math.min(max, value));
53
+ }
54
+ function length3(x, y, z) {
55
+ return Math.sqrt(x * x + y * y + z * z);
56
+ }
57
+ var Ricochet3DFSM = class {
58
+ machine;
59
+ lastResult = null;
60
+ lastUpdatedAtMs = null;
61
+ currentTimeMs = 0;
62
+ listeners = /* @__PURE__ */ new Set();
63
+ constructor() {
64
+ this.machine = new SyncStateMachine(
65
+ "idle" /* Idle */,
66
+ [
67
+ t("idle" /* Idle */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */),
68
+ t("ricocheting" /* Ricocheting */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
69
+ t("idle" /* Idle */, "end-ricochet" /* EndRicochet */, "idle" /* Idle */),
70
+ t("ricocheting" /* Ricocheting */, "start-ricochet" /* StartRicochet */, "ricocheting" /* Ricocheting */)
71
+ ]
72
+ );
73
+ }
74
+ addListener(callback) {
75
+ this.listeners.add(callback);
76
+ return () => this.listeners.delete(callback);
77
+ }
78
+ getState() {
79
+ return this.machine.getState();
80
+ }
81
+ getLastResult() {
82
+ return this.lastResult;
83
+ }
84
+ getLastUpdatedAtMs() {
85
+ return this.lastUpdatedAtMs;
86
+ }
87
+ setCurrentTimeMs(timeMs) {
88
+ this.currentTimeMs = timeMs;
89
+ }
90
+ isOnCooldown(cooldownMs = 50) {
91
+ if (this.lastUpdatedAtMs === null) return false;
92
+ return this.currentTimeMs - this.lastUpdatedAtMs < cooldownMs;
93
+ }
94
+ resetCooldown() {
95
+ this.lastUpdatedAtMs = null;
96
+ }
97
+ computeRicochet(ctx, options = {}) {
98
+ const {
99
+ minSpeed = 2,
100
+ maxSpeed = 20,
101
+ speedMultiplier = 1.05,
102
+ reflectionMode = "angled",
103
+ maxAngleDeg = 45
104
+ } = options;
105
+ const {
106
+ selfVelocity,
107
+ selfPosition,
108
+ otherPosition,
109
+ otherSize
110
+ } = this.extractDataFromEntities(ctx);
111
+ if (!selfVelocity) {
112
+ this.dispatch("end-ricochet" /* EndRicochet */);
113
+ return null;
114
+ }
115
+ const speed = length3(selfVelocity.x, selfVelocity.y, selfVelocity.z);
116
+ if (speed === 0) {
117
+ this.dispatch("end-ricochet" /* EndRicochet */);
118
+ return null;
119
+ }
120
+ const normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);
121
+ if (!normal) {
122
+ this.dispatch("end-ricochet" /* EndRicochet */);
123
+ return null;
124
+ }
125
+ const unitNormal = this.normalizeVector(normal);
126
+ let reflected = this.computeBasicReflection(selfVelocity, unitNormal);
127
+ if (reflectionMode === "angled") {
128
+ reflected = this.computeAngledDeflection(
129
+ reflected,
130
+ selfVelocity,
131
+ unitNormal,
132
+ speed,
133
+ maxAngleDeg,
134
+ speedMultiplier,
135
+ selfPosition,
136
+ otherPosition,
137
+ otherSize,
138
+ ctx.contact.position
139
+ );
140
+ } else {
141
+ reflected = {
142
+ x: reflected.x * speedMultiplier,
143
+ y: reflected.y * speedMultiplier,
144
+ z: reflected.z * speedMultiplier
145
+ };
146
+ }
147
+ reflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);
148
+ const result = {
149
+ velocity: reflected,
150
+ speed: length3(reflected.x, reflected.y, reflected.z),
151
+ normal: unitNormal
152
+ };
153
+ this.lastResult = result;
154
+ this.lastUpdatedAtMs = this.currentTimeMs;
155
+ this.dispatch("start-ricochet" /* StartRicochet */);
156
+ this.emitToListeners(result);
157
+ return result;
158
+ }
159
+ clearRicochet() {
160
+ this.dispatch("end-ricochet" /* EndRicochet */);
161
+ }
162
+ emitToListeners(result) {
163
+ for (const callback of this.listeners) {
164
+ try {
165
+ callback(result);
166
+ } catch (error) {
167
+ console.error("[Ricochet3DFSM] Listener error:", error);
168
+ }
169
+ }
170
+ }
171
+ extractDataFromEntities(ctx) {
172
+ let selfVelocity = ctx.selfVelocity;
173
+ let selfPosition = ctx.selfPosition;
174
+ let otherPosition = ctx.otherPosition;
175
+ let otherSize = ctx.otherSize;
176
+ if (ctx.entity?.body) {
177
+ const velocity = ctx.entity.body.linvel();
178
+ selfVelocity = selfVelocity ?? { x: velocity.x, y: velocity.y, z: velocity.z };
179
+ const position = ctx.entity.body.translation();
180
+ selfPosition = selfPosition ?? { x: position.x, y: position.y, z: position.z };
181
+ }
182
+ if (ctx.otherEntity?.body) {
183
+ const position = ctx.otherEntity.body.translation();
184
+ otherPosition = otherPosition ?? { x: position.x, y: position.y, z: position.z };
185
+ }
186
+ if (ctx.otherEntity && "size" in ctx.otherEntity) {
187
+ const size = ctx.otherEntity.size;
188
+ if (size && typeof size.x === "number") {
189
+ otherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };
190
+ }
191
+ }
192
+ return { selfVelocity, selfPosition, otherPosition, otherSize };
193
+ }
194
+ computeNormalFromPositions(selfPosition, otherPosition) {
195
+ if (!selfPosition || !otherPosition) return null;
196
+ const delta = {
197
+ x: selfPosition.x - otherPosition.x,
198
+ y: selfPosition.y - otherPosition.y,
199
+ z: selfPosition.z - otherPosition.z
200
+ };
201
+ const absX = Math.abs(delta.x);
202
+ const absY = Math.abs(delta.y);
203
+ const absZ = Math.abs(delta.z);
204
+ if (absX >= absY && absX >= absZ) {
205
+ return { x: delta.x >= 0 ? 1 : -1, y: 0, z: 0 };
206
+ }
207
+ if (absY >= absX && absY >= absZ) {
208
+ return { x: 0, y: delta.y >= 0 ? 1 : -1, z: 0 };
209
+ }
210
+ return { x: 0, y: 0, z: delta.z >= 0 ? 1 : -1 };
211
+ }
212
+ normalizeVector(vector) {
213
+ const magnitude = length3(vector.x, vector.y, vector.z);
214
+ if (magnitude <= 1e-6) {
215
+ return { x: 0, y: 1, z: 0 };
216
+ }
217
+ return {
218
+ x: vector.x / magnitude,
219
+ y: vector.y / magnitude,
220
+ z: vector.z / magnitude
221
+ };
222
+ }
223
+ dot(a, b) {
224
+ return a.x * b.x + a.y * b.y + a.z * b.z;
225
+ }
226
+ computeBasicReflection(velocity, normal) {
227
+ const dotProduct = this.dot(velocity, normal);
228
+ return {
229
+ x: velocity.x - 2 * dotProduct * normal.x,
230
+ y: velocity.y - 2 * dotProduct * normal.y,
231
+ z: velocity.z - 2 * dotProduct * normal.z
232
+ };
233
+ }
234
+ computeAngledDeflection(reflected, incoming, normal, speed, maxAngleDeg, speedMultiplier, selfPosition, otherPosition, otherSize, contactPosition) {
235
+ const baseDirection = this.normalizeVector(reflected);
236
+ const tangentVector = this.computeHitOffsetVector(
237
+ incoming,
238
+ normal,
239
+ selfPosition,
240
+ otherPosition,
241
+ otherSize,
242
+ contactPosition
243
+ );
244
+ const offsetStrength = clamp(
245
+ length3(tangentVector.x, tangentVector.y, tangentVector.z),
246
+ 0,
247
+ 1
248
+ );
249
+ if (offsetStrength === 0) {
250
+ return {
251
+ x: baseDirection.x * speed * speedMultiplier,
252
+ y: baseDirection.y * speed * speedMultiplier,
253
+ z: baseDirection.z * speed * speedMultiplier
254
+ };
255
+ }
256
+ const tangentDirection = this.normalizeVector(tangentVector);
257
+ const angle = offsetStrength * (maxAngleDeg * Math.PI / 180);
258
+ const cosAngle = Math.cos(angle);
259
+ const sinAngle = Math.sin(angle);
260
+ const direction = this.normalizeVector({
261
+ x: baseDirection.x * cosAngle + tangentDirection.x * sinAngle,
262
+ y: baseDirection.y * cosAngle + tangentDirection.y * sinAngle,
263
+ z: baseDirection.z * cosAngle + tangentDirection.z * sinAngle
264
+ });
265
+ const newSpeed = speed * speedMultiplier;
266
+ return {
267
+ x: direction.x * newSpeed,
268
+ y: direction.y * newSpeed,
269
+ z: direction.z * newSpeed
270
+ };
271
+ }
272
+ computeHitOffsetVector(velocity, normal, selfPosition, otherPosition, otherSize, contactPosition) {
273
+ if (otherPosition && otherSize) {
274
+ const faceAxis = this.getDominantAxis(normal);
275
+ if (faceAxis === "x") {
276
+ return {
277
+ x: 0,
278
+ y: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),
279
+ z: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6)
280
+ };
281
+ }
282
+ if (faceAxis === "y") {
283
+ return {
284
+ x: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),
285
+ y: 0,
286
+ z: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6)
287
+ };
288
+ }
289
+ return {
290
+ x: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),
291
+ y: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),
292
+ z: 0
293
+ };
294
+ }
295
+ const tangent = {
296
+ x: velocity.x - this.dot(velocity, normal) * normal.x,
297
+ y: velocity.y - this.dot(velocity, normal) * normal.y,
298
+ z: velocity.z - this.dot(velocity, normal) * normal.z
299
+ };
300
+ const tangentLength = length3(tangent.x, tangent.y, tangent.z);
301
+ if (tangentLength <= 1e-6) {
302
+ return { x: 0, y: 0, z: 0 };
303
+ }
304
+ return {
305
+ x: tangent.x / tangentLength,
306
+ y: tangent.y / tangentLength,
307
+ z: tangent.z / tangentLength
308
+ };
309
+ }
310
+ getDominantAxis(vector) {
311
+ const absX = Math.abs(vector.x);
312
+ const absY = Math.abs(vector.y);
313
+ const absZ = Math.abs(vector.z);
314
+ if (absX >= absY && absX >= absZ) return "x";
315
+ if (absY >= absX && absY >= absZ) return "y";
316
+ return "z";
317
+ }
318
+ applySpeedClamp(velocity, minSpeed, maxSpeed) {
319
+ const currentSpeed = length3(velocity.x, velocity.y, velocity.z);
320
+ if (currentSpeed === 0) return velocity;
321
+ const targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);
322
+ const scale = targetSpeed / currentSpeed;
323
+ return {
324
+ x: velocity.x * scale,
325
+ y: velocity.y * scale,
326
+ z: velocity.z * scale
327
+ };
328
+ }
329
+ dispatch(event) {
330
+ if (this.machine.can(event)) {
331
+ this.machine.syncDispatch(event);
332
+ }
333
+ }
334
+ };
335
+
336
+ // src/lib/behaviors/behavior-descriptor.ts
337
+ function defineBehavior(config) {
338
+ return {
339
+ key: /* @__PURE__ */ Symbol.for(`zylem:behavior:${config.name}`),
340
+ defaultOptions: config.defaultOptions,
341
+ systemFactory: config.systemFactory,
342
+ createHandle: config.createHandle
343
+ };
344
+ }
345
+
346
+ // src/lib/behaviors/ricochet-3d/ricochet-3d.descriptor.ts
347
+ var defaultOptions = {
348
+ minSpeed: 2,
349
+ maxSpeed: 20,
350
+ speedMultiplier: 1.05,
351
+ reflectionMode: "angled",
352
+ maxAngleDeg: 45
353
+ };
354
+ var RICOCHET_3D_BEHAVIOR_KEY = /* @__PURE__ */ Symbol.for("zylem:behavior:ricochet-3d");
355
+ function createRicochet3DHandle(ref) {
356
+ return {
357
+ getRicochet: (ctx) => {
358
+ const fsm = ref.fsm;
359
+ if (!fsm) return null;
360
+ return fsm.computeRicochet(ctx, ref.options);
361
+ },
362
+ applyRicochet: (ctx) => {
363
+ const fsm = ref.fsm;
364
+ if (!fsm || fsm.isOnCooldown()) return false;
365
+ const result = fsm.computeRicochet(ctx, ref.options);
366
+ if (!result) return false;
367
+ const entity = ctx.entity;
368
+ if (entity?.transformStore) {
369
+ entity.transformStore.velocity.x = result.velocity.x;
370
+ entity.transformStore.velocity.y = result.velocity.y;
371
+ entity.transformStore.velocity.z = result.velocity.z;
372
+ entity.transformStore.dirty.velocity = true;
373
+ }
374
+ return true;
375
+ },
376
+ getLastResult: () => {
377
+ const fsm = ref.fsm;
378
+ return fsm?.getLastResult() ?? null;
379
+ },
380
+ onRicochet: (callback) => {
381
+ const fsm = ref.fsm;
382
+ if (!fsm) {
383
+ if (!ref.pendingListeners) {
384
+ ref.pendingListeners = [];
385
+ }
386
+ ref.pendingListeners.push(callback);
387
+ return () => {
388
+ const pending = ref.pendingListeners;
389
+ const index = pending.indexOf(callback);
390
+ if (index >= 0) pending.splice(index, 1);
391
+ };
392
+ }
393
+ return fsm.addListener(callback);
394
+ }
395
+ };
396
+ }
397
+ var Ricochet3DSystem = class {
398
+ constructor(world, getBehaviorLinks) {
399
+ this.world = world;
400
+ this.getBehaviorLinks = getBehaviorLinks;
401
+ }
402
+ elapsedMs = 0;
403
+ update(_ecs, delta) {
404
+ this.elapsedMs += delta * 1e3;
405
+ const links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);
406
+ if (!links) return;
407
+ for (const link of links) {
408
+ const ref = link.ref;
409
+ if (!ref.fsm) {
410
+ ref.fsm = new Ricochet3DFSM();
411
+ const pending = ref.pendingListeners;
412
+ if (pending) {
413
+ for (const callback of pending) {
414
+ ref.fsm.addListener(callback);
415
+ }
416
+ ref.pendingListeners = [];
417
+ }
418
+ }
419
+ ref.fsm.setCurrentTimeMs(this.elapsedMs);
420
+ }
421
+ }
422
+ destroy(_ecs) {
423
+ const links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);
424
+ if (!links) return;
425
+ for (const link of links) {
426
+ const ref = link.ref;
427
+ ref.fsm?.resetCooldown();
428
+ }
429
+ }
430
+ };
431
+ var Ricochet3DBehavior = defineBehavior({
432
+ name: "ricochet-3d",
433
+ defaultOptions,
434
+ systemFactory: (ctx) => new Ricochet3DSystem(ctx.world, ctx.getBehaviorLinks),
435
+ createHandle: createRicochet3DHandle
436
+ });
437
+ export {
438
+ Ricochet3DBehavior,
439
+ Ricochet3DEvent,
440
+ Ricochet3DFSM,
441
+ Ricochet3DState
442
+ };
443
+ //# sourceMappingURL=ricochet-3d.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../../src/lib/core/utility/sync-state-machine.ts","../../src/lib/behaviors/ricochet-3d/ricochet-3d-fsm.ts","../../src/lib/behaviors/behavior-descriptor.ts","../../src/lib/behaviors/ricochet-3d/ricochet-3d.descriptor.ts"],"sourcesContent":["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","import { SyncStateMachine, t } from '../../core/utility/sync-state-machine';\nimport { BaseEntityInterface } from '../../types/entity-types';\n\nexport interface Ricochet3DResult {\n\tvelocity: { x: number; y: number; z: number };\n\tspeed: number;\n\tnormal: { x: number; y: number; z: number };\n}\n\nexport interface Ricochet3DCollisionContext {\n\tentity?: BaseEntityInterface;\n\totherEntity?: BaseEntityInterface;\n\tselfVelocity?: { x: number; y: number; z: number };\n\tcontact: {\n\t\tnormal?: { x: number; y: number; z: number };\n\t\tposition?: { x: number; y: number; z: number };\n\t};\n\tselfPosition?: { x: number; y: number; z: number };\n\totherPosition?: { x: number; y: number; z: number };\n\totherSize?: { x: number; y: number; z: number };\n}\n\nexport enum Ricochet3DState {\n\tIdle = 'idle',\n\tRicocheting = 'ricocheting',\n}\n\nexport enum Ricochet3DEvent {\n\tStartRicochet = 'start-ricochet',\n\tEndRicochet = 'end-ricochet',\n}\n\nexport type Ricochet3DCallback = (result: Ricochet3DResult) => void;\n\nfunction clamp(value: number, min: number, max: number): number {\n\treturn Math.max(min, Math.min(max, value));\n}\n\nfunction length3(x: number, y: number, z: number): number {\n\treturn Math.sqrt(x * x + y * y + z * z);\n}\n\nexport class Ricochet3DFSM {\n\tpublic readonly machine: SyncStateMachine<Ricochet3DState, Ricochet3DEvent, never>;\n\n\tprivate lastResult: Ricochet3DResult | null = null;\n\tprivate lastUpdatedAtMs: number | null = null;\n\tprivate currentTimeMs = 0;\n\tprivate listeners = new Set<Ricochet3DCallback>();\n\n\tconstructor() {\n\t\tthis.machine = new SyncStateMachine<Ricochet3DState, Ricochet3DEvent, never>(\n\t\t\tRicochet3DState.Idle,\n\t\t\t[\n\t\t\t\tt(Ricochet3DState.Idle, Ricochet3DEvent.StartRicochet, Ricochet3DState.Ricocheting),\n\t\t\t\tt(Ricochet3DState.Ricocheting, Ricochet3DEvent.EndRicochet, Ricochet3DState.Idle),\n\t\t\t\tt(Ricochet3DState.Idle, Ricochet3DEvent.EndRicochet, Ricochet3DState.Idle),\n\t\t\t\tt(Ricochet3DState.Ricocheting, Ricochet3DEvent.StartRicochet, Ricochet3DState.Ricocheting),\n\t\t\t],\n\t\t);\n\t}\n\n\taddListener(callback: Ricochet3DCallback): () => void {\n\t\tthis.listeners.add(callback);\n\t\treturn () => this.listeners.delete(callback);\n\t}\n\n\tgetState(): Ricochet3DState {\n\t\treturn this.machine.getState();\n\t}\n\n\tgetLastResult(): Ricochet3DResult | null {\n\t\treturn this.lastResult;\n\t}\n\n\tgetLastUpdatedAtMs(): number | null {\n\t\treturn this.lastUpdatedAtMs;\n\t}\n\n\tsetCurrentTimeMs(timeMs: number): void {\n\t\tthis.currentTimeMs = timeMs;\n\t}\n\n\tisOnCooldown(cooldownMs: number = 50): boolean {\n\t\tif (this.lastUpdatedAtMs === null) return false;\n\t\treturn (this.currentTimeMs - this.lastUpdatedAtMs) < cooldownMs;\n\t}\n\n\tresetCooldown(): void {\n\t\tthis.lastUpdatedAtMs = null;\n\t}\n\n\tcomputeRicochet(\n\t\tctx: Ricochet3DCollisionContext,\n\t\toptions: {\n\t\t\tminSpeed?: number;\n\t\t\tmaxSpeed?: number;\n\t\t\tspeedMultiplier?: number;\n\t\t\treflectionMode?: 'simple' | 'angled';\n\t\t\tmaxAngleDeg?: number;\n\t\t} = {},\n\t): Ricochet3DResult | null {\n\t\tconst {\n\t\t\tminSpeed = 2,\n\t\t\tmaxSpeed = 20,\n\t\t\tspeedMultiplier = 1.05,\n\t\t\treflectionMode = 'angled',\n\t\t\tmaxAngleDeg = 45,\n\t\t} = options;\n\n\t\tconst {\n\t\t\tselfVelocity,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t} = this.extractDataFromEntities(ctx);\n\n\t\tif (!selfVelocity) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst speed = length3(selfVelocity.x, selfVelocity.y, selfVelocity.z);\n\t\tif (speed === 0) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst normal = ctx.contact.normal ?? this.computeNormalFromPositions(selfPosition, otherPosition);\n\t\tif (!normal) {\n\t\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t\t\treturn null;\n\t\t}\n\n\t\tconst unitNormal = this.normalizeVector(normal);\n\t\tlet reflected = this.computeBasicReflection(selfVelocity, unitNormal);\n\n\t\tif (reflectionMode === 'angled') {\n\t\t\treflected = this.computeAngledDeflection(\n\t\t\t\treflected,\n\t\t\t\tselfVelocity,\n\t\t\t\tunitNormal,\n\t\t\t\tspeed,\n\t\t\t\tmaxAngleDeg,\n\t\t\t\tspeedMultiplier,\n\t\t\t\tselfPosition,\n\t\t\t\totherPosition,\n\t\t\t\totherSize,\n\t\t\t\tctx.contact.position,\n\t\t\t);\n\t\t} else {\n\t\t\treflected = {\n\t\t\t\tx: reflected.x * speedMultiplier,\n\t\t\t\ty: reflected.y * speedMultiplier,\n\t\t\t\tz: reflected.z * speedMultiplier,\n\t\t\t};\n\t\t}\n\n\t\treflected = this.applySpeedClamp(reflected, minSpeed, maxSpeed);\n\n\t\tconst result: Ricochet3DResult = {\n\t\t\tvelocity: reflected,\n\t\t\tspeed: length3(reflected.x, reflected.y, reflected.z),\n\t\t\tnormal: unitNormal,\n\t\t};\n\n\t\tthis.lastResult = result;\n\t\tthis.lastUpdatedAtMs = this.currentTimeMs;\n\t\tthis.dispatch(Ricochet3DEvent.StartRicochet);\n\t\tthis.emitToListeners(result);\n\t\treturn result;\n\t}\n\n\tclearRicochet(): void {\n\t\tthis.dispatch(Ricochet3DEvent.EndRicochet);\n\t}\n\n\tprivate emitToListeners(result: Ricochet3DResult): void {\n\t\tfor (const callback of this.listeners) {\n\t\t\ttry {\n\t\t\t\tcallback(result);\n\t\t\t} catch (error) {\n\t\t\t\tconsole.error('[Ricochet3DFSM] Listener error:', error);\n\t\t\t}\n\t\t}\n\t}\n\n\tprivate extractDataFromEntities(ctx: Ricochet3DCollisionContext) {\n\t\tlet selfVelocity = ctx.selfVelocity;\n\t\tlet selfPosition = ctx.selfPosition;\n\t\tlet otherPosition = ctx.otherPosition;\n\t\tlet otherSize = ctx.otherSize;\n\n\t\tif (ctx.entity?.body) {\n\t\t\tconst velocity = ctx.entity.body.linvel();\n\t\t\tselfVelocity = selfVelocity ?? { x: velocity.x, y: velocity.y, z: velocity.z };\n\t\t\tconst position = ctx.entity.body.translation();\n\t\t\tselfPosition = selfPosition ?? { x: position.x, y: position.y, z: position.z };\n\t\t}\n\n\t\tif (ctx.otherEntity?.body) {\n\t\t\tconst position = ctx.otherEntity.body.translation();\n\t\t\totherPosition = otherPosition ?? { x: position.x, y: position.y, z: position.z };\n\t\t}\n\n\t\tif (ctx.otherEntity && 'size' in ctx.otherEntity) {\n\t\t\tconst size = (ctx.otherEntity as any).size;\n\t\t\tif (size && typeof size.x === 'number') {\n\t\t\t\totherSize = otherSize ?? { x: size.x, y: size.y, z: size.z };\n\t\t\t}\n\t\t}\n\n\t\treturn { selfVelocity, selfPosition, otherPosition, otherSize };\n\t}\n\n\tprivate computeNormalFromPositions(\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } | null {\n\t\tif (!selfPosition || !otherPosition) return null;\n\n\t\tconst delta = {\n\t\t\tx: selfPosition.x - otherPosition.x,\n\t\t\ty: selfPosition.y - otherPosition.y,\n\t\t\tz: selfPosition.z - otherPosition.z,\n\t\t};\n\t\tconst absX = Math.abs(delta.x);\n\t\tconst absY = Math.abs(delta.y);\n\t\tconst absZ = Math.abs(delta.z);\n\n\t\tif (absX >= absY && absX >= absZ) {\n\t\t\treturn { x: delta.x >= 0 ? 1 : -1, y: 0, z: 0 };\n\t\t}\n\t\tif (absY >= absX && absY >= absZ) {\n\t\t\treturn { x: 0, y: delta.y >= 0 ? 1 : -1, z: 0 };\n\t\t}\n\t\treturn { x: 0, y: 0, z: delta.z >= 0 ? 1 : -1 };\n\t}\n\n\tprivate normalizeVector(vector: { x: number; y: number; z: number }) {\n\t\tconst magnitude = length3(vector.x, vector.y, vector.z);\n\t\tif (magnitude <= 1e-6) {\n\t\t\treturn { x: 0, y: 1, z: 0 };\n\t\t}\n\t\treturn {\n\t\t\tx: vector.x / magnitude,\n\t\t\ty: vector.y / magnitude,\n\t\t\tz: vector.z / magnitude,\n\t\t};\n\t}\n\n\tprivate dot(\n\t\ta: { x: number; y: number; z: number },\n\t\tb: { x: number; y: number; z: number },\n\t): number {\n\t\treturn a.x * b.x + a.y * b.y + a.z * b.z;\n\t}\n\n\tprivate computeBasicReflection(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tconst dotProduct = this.dot(velocity, normal);\n\t\treturn {\n\t\t\tx: velocity.x - 2 * dotProduct * normal.x,\n\t\t\ty: velocity.y - 2 * dotProduct * normal.y,\n\t\t\tz: velocity.z - 2 * dotProduct * normal.z,\n\t\t};\n\t}\n\n\tprivate computeAngledDeflection(\n\t\treflected: { x: number; y: number; z: number },\n\t\tincoming: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t\tspeed: number,\n\t\tmaxAngleDeg: number,\n\t\tspeedMultiplier: number,\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t\totherSize?: { x: number; y: number; z: number },\n\t\tcontactPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tconst baseDirection = this.normalizeVector(reflected);\n\t\tconst tangentVector = this.computeHitOffsetVector(\n\t\t\tincoming,\n\t\t\tnormal,\n\t\t\tselfPosition,\n\t\t\totherPosition,\n\t\t\totherSize,\n\t\t\tcontactPosition,\n\t\t);\n\n\t\tconst offsetStrength = clamp(\n\t\t\tlength3(tangentVector.x, tangentVector.y, tangentVector.z),\n\t\t\t0,\n\t\t\t1,\n\t\t);\n\t\tif (offsetStrength === 0) {\n\t\t\treturn {\n\t\t\t\tx: baseDirection.x * speed * speedMultiplier,\n\t\t\t\ty: baseDirection.y * speed * speedMultiplier,\n\t\t\t\tz: baseDirection.z * speed * speedMultiplier,\n\t\t\t};\n\t\t}\n\n\t\tconst tangentDirection = this.normalizeVector(tangentVector);\n\t\tconst angle = offsetStrength * (maxAngleDeg * Math.PI / 180);\n\t\tconst cosAngle = Math.cos(angle);\n\t\tconst sinAngle = Math.sin(angle);\n\t\tconst direction = this.normalizeVector({\n\t\t\tx: baseDirection.x * cosAngle + tangentDirection.x * sinAngle,\n\t\t\ty: baseDirection.y * cosAngle + tangentDirection.y * sinAngle,\n\t\t\tz: baseDirection.z * cosAngle + tangentDirection.z * sinAngle,\n\t\t});\n\t\tconst newSpeed = speed * speedMultiplier;\n\n\t\treturn {\n\t\t\tx: direction.x * newSpeed,\n\t\t\ty: direction.y * newSpeed,\n\t\t\tz: direction.z * newSpeed,\n\t\t};\n\t}\n\n\tprivate computeHitOffsetVector(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tnormal: { x: number; y: number; z: number },\n\t\tselfPosition?: { x: number; y: number; z: number },\n\t\totherPosition?: { x: number; y: number; z: number },\n\t\totherSize?: { x: number; y: number; z: number },\n\t\tcontactPosition?: { x: number; y: number; z: number },\n\t): { x: number; y: number; z: number } {\n\t\tif (otherPosition && otherSize) {\n\t\t\tconst faceAxis = this.getDominantAxis(normal);\n\t\t\tif (faceAxis === 'x') {\n\t\t\t\treturn {\n\t\t\t\t\tx: 0,\n\t\t\t\t\ty: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),\n\t\t\t\t\tz: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6),\n\t\t\t\t};\n\t\t\t}\n\t\t\tif (faceAxis === 'y') {\n\t\t\t\treturn {\n\t\t\t\t\tx: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),\n\t\t\t\t\ty: 0,\n\t\t\t\t\tz: ((selfPosition?.z ?? contactPosition?.z ?? otherPosition.z) - otherPosition.z) / Math.max(otherSize.z / 2, 1e-6),\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn {\n\t\t\t\tx: ((selfPosition?.x ?? contactPosition?.x ?? otherPosition.x) - otherPosition.x) / Math.max(otherSize.x / 2, 1e-6),\n\t\t\t\ty: ((selfPosition?.y ?? contactPosition?.y ?? otherPosition.y) - otherPosition.y) / Math.max(otherSize.y / 2, 1e-6),\n\t\t\t\tz: 0,\n\t\t\t};\n\t\t}\n\n\t\tconst tangent = {\n\t\t\tx: velocity.x - this.dot(velocity, normal) * normal.x,\n\t\t\ty: velocity.y - this.dot(velocity, normal) * normal.y,\n\t\t\tz: velocity.z - this.dot(velocity, normal) * normal.z,\n\t\t};\n\t\tconst tangentLength = length3(tangent.x, tangent.y, tangent.z);\n\t\tif (tangentLength <= 1e-6) {\n\t\t\treturn { x: 0, y: 0, z: 0 };\n\t\t}\n\t\treturn {\n\t\t\tx: tangent.x / tangentLength,\n\t\t\ty: tangent.y / tangentLength,\n\t\t\tz: tangent.z / tangentLength,\n\t\t};\n\t}\n\n\tprivate getDominantAxis(vector: { x: number; y: number; z: number }): 'x' | 'y' | 'z' {\n\t\tconst absX = Math.abs(vector.x);\n\t\tconst absY = Math.abs(vector.y);\n\t\tconst absZ = Math.abs(vector.z);\n\t\tif (absX >= absY && absX >= absZ) return 'x';\n\t\tif (absY >= absX && absY >= absZ) return 'y';\n\t\treturn 'z';\n\t}\n\n\tprivate applySpeedClamp(\n\t\tvelocity: { x: number; y: number; z: number },\n\t\tminSpeed: number,\n\t\tmaxSpeed: number,\n\t): { x: number; y: number; z: number } {\n\t\tconst currentSpeed = length3(velocity.x, velocity.y, velocity.z);\n\t\tif (currentSpeed === 0) return velocity;\n\n\t\tconst targetSpeed = clamp(currentSpeed, minSpeed, maxSpeed);\n\t\tconst scale = targetSpeed / currentSpeed;\n\t\treturn {\n\t\t\tx: velocity.x * scale,\n\t\t\ty: velocity.y * scale,\n\t\t\tz: velocity.z * scale,\n\t\t};\n\t}\n\n\tprivate dispatch(event: Ricochet3DEvent): void {\n\t\tif (this.machine.can(event)) {\n\t\t\tthis.machine.syncDispatch(event);\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","import type { IWorld } from 'bitecs';\nimport { defineBehavior, type BehaviorRef } from '../behavior-descriptor';\nimport type { BehaviorEntityLink, BehaviorSystem } from '../behavior-system';\nimport {\n\tRicochet3DFSM,\n\ttype Ricochet3DResult,\n\ttype Ricochet3DCollisionContext,\n\ttype Ricochet3DCallback,\n} from './ricochet-3d-fsm';\n\nexport type { Ricochet3DResult };\n\nexport interface Ricochet3DOptions {\n\tminSpeed: number;\n\tmaxSpeed: number;\n\tspeedMultiplier: number;\n\treflectionMode: 'simple' | 'angled';\n\tmaxAngleDeg: number;\n}\n\nexport interface Ricochet3DHandle {\n\tgetRicochet(ctx: Ricochet3DCollisionContext): Ricochet3DResult | null;\n\tapplyRicochet(ctx: Ricochet3DCollisionContext): boolean;\n\tgetLastResult(): Ricochet3DResult | null;\n\tonRicochet(callback: Ricochet3DCallback): () => void;\n}\n\nconst defaultOptions: Ricochet3DOptions = {\n\tminSpeed: 2,\n\tmaxSpeed: 20,\n\tspeedMultiplier: 1.05,\n\treflectionMode: 'angled',\n\tmaxAngleDeg: 45,\n};\n\nconst RICOCHET_3D_BEHAVIOR_KEY = Symbol.for('zylem:behavior:ricochet-3d');\n\nfunction createRicochet3DHandle(\n\tref: BehaviorRef<Ricochet3DOptions>,\n): Ricochet3DHandle {\n\treturn {\n\t\tgetRicochet: (ctx: Ricochet3DCollisionContext) => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm) return null;\n\t\t\treturn fsm.computeRicochet(ctx, ref.options);\n\t\t},\n\t\tapplyRicochet: (ctx: Ricochet3DCollisionContext): boolean => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm || fsm.isOnCooldown()) return false;\n\n\t\t\tconst result = fsm.computeRicochet(ctx, ref.options);\n\t\t\tif (!result) return false;\n\n\t\t\tconst entity = ctx.entity as any;\n\t\t\tif (entity?.transformStore) {\n\t\t\t\tentity.transformStore.velocity.x = result.velocity.x;\n\t\t\t\tentity.transformStore.velocity.y = result.velocity.y;\n\t\t\t\tentity.transformStore.velocity.z = result.velocity.z;\n\t\t\t\tentity.transformStore.dirty.velocity = true;\n\t\t\t}\n\n\t\t\treturn true;\n\t\t},\n\t\tgetLastResult: () => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\treturn fsm?.getLastResult() ?? null;\n\t\t},\n\t\tonRicochet: (callback: Ricochet3DCallback) => {\n\t\t\tconst fsm = ref.fsm as Ricochet3DFSM | undefined;\n\t\t\tif (!fsm) {\n\t\t\t\tif (!(ref as any).pendingListeners) {\n\t\t\t\t\t(ref as any).pendingListeners = [];\n\t\t\t\t}\n\t\t\t\t(ref as any).pendingListeners.push(callback);\n\t\t\t\treturn () => {\n\t\t\t\t\tconst pending = (ref as any).pendingListeners as Ricochet3DCallback[];\n\t\t\t\t\tconst index = pending.indexOf(callback);\n\t\t\t\t\tif (index >= 0) pending.splice(index, 1);\n\t\t\t\t};\n\t\t\t}\n\t\t\treturn fsm.addListener(callback);\n\t\t},\n\t};\n}\n\nclass Ricochet3DSystem implements BehaviorSystem {\n\tprivate elapsedMs = 0;\n\n\tconstructor(\n\t\tprivate world: any,\n\t\tprivate getBehaviorLinks?: (key: symbol) => Iterable<BehaviorEntityLink>,\n\t) {}\n\n\tupdate(_ecs: IWorld, delta: number): void {\n\t\tthis.elapsedMs += delta * 1000;\n\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\n\t\tfor (const link of links) {\n\t\t\tconst ref = link.ref as any;\n\t\t\tif (!ref.fsm) {\n\t\t\t\tref.fsm = new Ricochet3DFSM();\n\t\t\t\tconst pending = ref.pendingListeners as Ricochet3DCallback[] | undefined;\n\t\t\t\tif (pending) {\n\t\t\t\t\tfor (const callback of pending) {\n\t\t\t\t\t\tref.fsm.addListener(callback);\n\t\t\t\t\t}\n\t\t\t\t\tref.pendingListeners = [];\n\t\t\t\t}\n\t\t\t}\n\n\t\t\tref.fsm.setCurrentTimeMs(this.elapsedMs);\n\t\t}\n\t}\n\n\tdestroy(_ecs: IWorld): void {\n\t\tconst links = this.getBehaviorLinks?.(RICOCHET_3D_BEHAVIOR_KEY);\n\t\tif (!links) return;\n\t\tfor (const link of links) {\n\t\t\tconst ref = link.ref as any;\n\t\t\tref.fsm?.resetCooldown();\n\t\t}\n\t}\n}\n\nexport const Ricochet3DBehavior = defineBehavior({\n\tname: 'ricochet-3d',\n\tdefaultOptions,\n\tsystemFactory: (ctx) =>\n\t\tnew Ricochet3DSystem(ctx.world, ctx.getBehaviorLinks),\n\tcreateHandle: createRicochet3DHandle,\n});\n"],"mappings":"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","names":["Ricochet3DState","Ricochet3DEvent"]}
@@ -0,0 +1,79 @@
1
+ import { c as BehaviorDescriptor } from '../behavior-descriptor-BXnVR8Ki.js';
2
+ import { I as GameEntity } from '../entity-vj-HTjzU.js';
3
+ import 'bitecs';
4
+ import 'three';
5
+ import '@dimforge/rapier3d-compat';
6
+ import 'mitt';
7
+
8
+ interface ScreenVisibilitySnapshot {
9
+ initialized: boolean;
10
+ visible: boolean;
11
+ justEntered: boolean;
12
+ justExited: boolean;
13
+ visibleCameraNames: string[];
14
+ }
15
+ declare class ScreenVisibilityFSM {
16
+ private initialized;
17
+ private visible;
18
+ private justEntered;
19
+ private justExited;
20
+ private visibleCameraNames;
21
+ update(visible: boolean, visibleCameraNames: readonly string[]): void;
22
+ isInitialized(): boolean;
23
+ isVisible(): boolean;
24
+ wasJustEntered(): boolean;
25
+ wasJustExited(): boolean;
26
+ getVisibleCameraNames(): string[];
27
+ getState(): ScreenVisibilitySnapshot;
28
+ }
29
+
30
+ interface ScreenVisibilitySize {
31
+ x: number;
32
+ y: number;
33
+ z: number;
34
+ }
35
+ interface ScreenVisibilityChangeContext {
36
+ entity: GameEntity<any>;
37
+ visible: boolean;
38
+ wasVisible: boolean;
39
+ justEntered: boolean;
40
+ justExited: boolean;
41
+ visibleCameraNames: string[];
42
+ cameraName: string | null;
43
+ }
44
+ interface ScreenVisibilityOptions {
45
+ /**
46
+ * Restrict visibility checks to one active camera by name.
47
+ * When omitted, any active camera can make the entity visible.
48
+ */
49
+ cameraName: string | null;
50
+ /**
51
+ * Require the full entity bounds to remain inside the camera frustum.
52
+ * Defaults to partial visibility (any intersection counts).
53
+ */
54
+ requireFullyVisible: boolean;
55
+ /**
56
+ * Expand or shrink the computed bounds in world units.
57
+ * Positive values make visibility checks more generous.
58
+ */
59
+ padding: number;
60
+ /**
61
+ * Optional bounds to use when the entity has no renderable mesh/group.
62
+ * Falls back to the entity's configured size when available.
63
+ */
64
+ fallbackSize: ScreenVisibilitySize | null;
65
+ onChange?: (context: ScreenVisibilityChangeContext) => void;
66
+ onEnter?: (context: ScreenVisibilityChangeContext) => void;
67
+ onExit?: (context: ScreenVisibilityChangeContext) => void;
68
+ }
69
+ interface ScreenVisibilityHandle {
70
+ isVisible(): boolean;
71
+ isOffscreen(): boolean;
72
+ wasJustEntered(): boolean;
73
+ wasJustExited(): boolean;
74
+ getVisibleCameraNames(): string[];
75
+ getState(): ScreenVisibilitySnapshot | null;
76
+ }
77
+ declare const ScreenVisibilityBehavior: BehaviorDescriptor<ScreenVisibilityOptions, ScreenVisibilityHandle, unknown>;
78
+
79
+ export { ScreenVisibilityBehavior, type ScreenVisibilityChangeContext, ScreenVisibilityFSM, type ScreenVisibilityHandle, type ScreenVisibilityOptions, type ScreenVisibilitySize, type ScreenVisibilitySnapshot };