@zylem/game-lib 0.6.3 → 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 (74) hide show
  1. package/dist/actions.d.ts +5 -5
  2. package/dist/actions.js +196 -32
  3. package/dist/actions.js.map +1 -1
  4. package/dist/behavior/jumper-2d.d.ts +114 -0
  5. package/dist/behavior/jumper-2d.js +711 -0
  6. package/dist/behavior/jumper-2d.js.map +1 -0
  7. package/dist/behavior/platformer-3d.d.ts +14 -14
  8. package/dist/behavior/platformer-3d.js +347 -104
  9. package/dist/behavior/platformer-3d.js.map +1 -1
  10. package/dist/behavior/ricochet-2d.d.ts +4 -3
  11. package/dist/behavior/ricochet-2d.js +53 -22
  12. package/dist/behavior/ricochet-2d.js.map +1 -1
  13. package/dist/behavior/ricochet-3d.d.ts +117 -0
  14. package/dist/behavior/ricochet-3d.js +443 -0
  15. package/dist/behavior/ricochet-3d.js.map +1 -0
  16. package/dist/behavior/screen-visibility.d.ts +79 -0
  17. package/dist/behavior/screen-visibility.js +358 -0
  18. package/dist/behavior/screen-visibility.js.map +1 -0
  19. package/dist/behavior/screen-wrap.d.ts +4 -3
  20. package/dist/behavior/screen-wrap.js +100 -49
  21. package/dist/behavior/screen-wrap.js.map +1 -1
  22. package/dist/behavior/shooter-2d.d.ts +79 -0
  23. package/dist/behavior/shooter-2d.js +180 -0
  24. package/dist/behavior/shooter-2d.js.map +1 -0
  25. package/dist/behavior/thruster.d.ts +5 -4
  26. package/dist/behavior/thruster.js +133 -75
  27. package/dist/behavior/thruster.js.map +1 -1
  28. package/dist/behavior/top-down-movement.d.ts +56 -0
  29. package/dist/behavior/top-down-movement.js +125 -0
  30. package/dist/behavior/top-down-movement.js.map +1 -0
  31. package/dist/behavior/world-boundary-2d.d.ts +4 -3
  32. package/dist/behavior/world-boundary-2d.js +90 -36
  33. package/dist/behavior/world-boundary-2d.js.map +1 -1
  34. package/dist/behavior/world-boundary-3d.d.ts +76 -0
  35. package/dist/behavior/world-boundary-3d.js +274 -0
  36. package/dist/behavior/world-boundary-3d.js.map +1 -0
  37. package/dist/{behavior-descriptor-BWNWmIjv.d.ts → behavior-descriptor-BXnVR8Ki.d.ts} +22 -5
  38. package/dist/{blueprints-BWGz8fII.d.ts → blueprints-DmbK2dki.d.ts} +2 -2
  39. package/dist/camera-4XO5gbQH.d.ts +905 -0
  40. package/dist/camera.d.ts +1 -1
  41. package/dist/camera.js +876 -289
  42. package/dist/camera.js.map +1 -1
  43. package/dist/{composition-DrzFrbqI.d.ts → composition-BASvMKrW.d.ts} +1 -1
  44. package/dist/{core-DAkskq6Y.d.ts → core-CARRaS55.d.ts} +57 -14
  45. package/dist/core.d.ts +9 -8
  46. package/dist/core.js +4519 -1255
  47. package/dist/core.js.map +1 -1
  48. package/dist/{entities-DC9ce_vx.d.ts → entities-ChFirVL9.d.ts} +22 -28
  49. package/dist/entities.d.ts +4 -4
  50. package/dist/entities.js +1231 -314
  51. package/dist/entities.js.map +1 -1
  52. package/dist/{entity-BpbZqg19.d.ts → entity-vj-HTjzU.d.ts} +80 -11
  53. package/dist/{global-change-Dc8uCKi2.d.ts → global-change-2JvMaz44.d.ts} +1 -1
  54. package/dist/main.d.ts +718 -19
  55. package/dist/main.js +12129 -5959
  56. package/dist/main.js.map +1 -1
  57. package/dist/physics-pose-DCc4oE44.d.ts +25 -0
  58. package/dist/physics-protocol-BDD3P5W2.d.ts +200 -0
  59. package/dist/physics-worker.d.ts +21 -0
  60. package/dist/physics-worker.js +306 -0
  61. package/dist/physics-worker.js.map +1 -0
  62. package/dist/physics.d.ts +205 -0
  63. package/dist/physics.js +577 -0
  64. package/dist/physics.js.map +1 -0
  65. package/dist/{stage-types-BFsm3qsZ.d.ts → stage-types-C19IhuzA.d.ts} +253 -89
  66. package/dist/stage.d.ts +9 -8
  67. package/dist/stage.js +3782 -1041
  68. package/dist/stage.js.map +1 -1
  69. package/dist/sync-state-machine-CZyspBpj.d.ts +16 -0
  70. package/dist/{thruster-DhRaJnoL.d.ts → thruster-23lzoPZd.d.ts} +16 -8
  71. package/dist/world-DfgxoNMt.d.ts +105 -0
  72. package/package.json +25 -1
  73. package/dist/camera-B5e4c78l.d.ts +0 -468
  74. package/dist/world-Be5m1XC1.d.ts +0 -31
@@ -0,0 +1,25 @@
1
+ interface PhysicsVector3 {
2
+ x: number;
3
+ y: number;
4
+ z: number;
5
+ }
6
+ interface PhysicsQuaternion {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ w: number;
11
+ }
12
+ interface PhysicsPose {
13
+ position: PhysicsVector3;
14
+ rotation: PhysicsQuaternion;
15
+ }
16
+ interface PhysicsPoseHistory {
17
+ previous: PhysicsPose;
18
+ current: PhysicsPose;
19
+ }
20
+ interface PhysicsPoseReadable {
21
+ translation(): PhysicsVector3;
22
+ rotation(): PhysicsQuaternion;
23
+ }
24
+
25
+ export type { PhysicsPoseHistory as P, PhysicsPose as a, PhysicsPoseReadable as b };
@@ -0,0 +1,200 @@
1
+ /**
2
+ * Message protocol for communication between the main thread and the
3
+ * physics Web Worker.
4
+ *
5
+ * Data flows in two directions:
6
+ * Main → Worker: {@link PhysicsCommand} (init, entity CRUD, per-frame step)
7
+ * Worker → Main: {@link PhysicsEvent} (ready, step results, errors)
8
+ *
9
+ * All types are plain JSON-serializable objects so they can cross the
10
+ * postMessage boundary without structured-clone issues.
11
+ */
12
+ /** Rigid body type mirroring Rapier's RigidBodyType enum values. */
13
+ type SerializableBodyType = 'dynamic' | 'fixed' | 'kinematicPositionBased' | 'kinematicVelocityBased';
14
+ /**
15
+ * Plain-object representation of a Rapier RigidBodyDesc.
16
+ * Created on the main thread and reconstructed in the worker.
17
+ */
18
+ interface SerializableBodyDesc {
19
+ type: SerializableBodyType;
20
+ translation: [number, number, number];
21
+ gravityScale: number;
22
+ canSleep: boolean;
23
+ ccdEnabled: boolean;
24
+ lockTranslations?: boolean;
25
+ lockRotations?: boolean;
26
+ }
27
+ /** Collider shape kind. Matches the factory functions in collision-factories. */
28
+ type ColliderShapeKind = 'cuboid' | 'ball' | 'capsule' | 'cone' | 'cylinder' | 'trimesh' | 'heightfield';
29
+ /**
30
+ * Plain-object representation of a Rapier ColliderDesc.
31
+ * Shape-specific dimensions are stored in {@link dimensions}.
32
+ */
33
+ interface SerializableColliderDesc {
34
+ shape: ColliderShapeKind;
35
+ /** Shape-specific numeric dimensions (e.g. halfExtents, radius, halfHeight). */
36
+ dimensions: number[];
37
+ /** For trimesh: flattened xyz vertex array. */
38
+ vertices?: number[];
39
+ /** For trimesh: triangle indices referencing {@link vertices}. */
40
+ indices?: number[];
41
+ /** Collider offset translation relative to the body. */
42
+ translation?: [number, number, number];
43
+ sensor?: boolean;
44
+ collisionGroups?: number;
45
+ activeCollisionTypes?: number;
46
+ /** For heightfield: number of rows and columns. */
47
+ heightfieldMeta?: {
48
+ nrows: number;
49
+ ncols: number;
50
+ };
51
+ }
52
+ /** Describes a character controller to be created alongside the body. */
53
+ interface SerializableCharacterController {
54
+ offset: number;
55
+ maxSlopeClimbAngle: number;
56
+ minSlopeSlideAngle: number;
57
+ snapToGroundDistance: number;
58
+ slideEnabled: boolean;
59
+ applyImpulsesToDynamic: boolean;
60
+ characterMass: number;
61
+ }
62
+ type BodyCommand = {
63
+ kind: 'setLinvel';
64
+ uuid: string;
65
+ x: number;
66
+ y: number;
67
+ z: number;
68
+ } | {
69
+ kind: 'setAngvel';
70
+ uuid: string;
71
+ x: number;
72
+ y: number;
73
+ z: number;
74
+ } | {
75
+ kind: 'setTranslation';
76
+ uuid: string;
77
+ x: number;
78
+ y: number;
79
+ z: number;
80
+ } | {
81
+ kind: 'setRotation';
82
+ uuid: string;
83
+ x: number;
84
+ y: number;
85
+ z: number;
86
+ w: number;
87
+ } | {
88
+ kind: 'applyImpulse';
89
+ uuid: string;
90
+ x: number;
91
+ y: number;
92
+ z: number;
93
+ } | {
94
+ kind: 'applyTorqueImpulse';
95
+ uuid: string;
96
+ x: number;
97
+ y: number;
98
+ z: number;
99
+ } | {
100
+ kind: 'lockTranslations';
101
+ uuid: string;
102
+ locked: boolean;
103
+ } | {
104
+ kind: 'lockRotations';
105
+ uuid: string;
106
+ locked: boolean;
107
+ } | {
108
+ kind: 'addTranslation';
109
+ uuid: string;
110
+ dx: number;
111
+ dy: number;
112
+ dz: number;
113
+ } | {
114
+ kind: 'setLinearDamping';
115
+ uuid: string;
116
+ damping: number;
117
+ } | {
118
+ kind: 'setGravityScale';
119
+ uuid: string;
120
+ scale: number;
121
+ };
122
+ type PhysicsCommand = PhysicsInitCommand | PhysicsAddBodyCommand | PhysicsRemoveBodyCommand | PhysicsStepCommand | PhysicsDisposeCommand;
123
+ interface PhysicsInitCommand {
124
+ type: 'init';
125
+ gravity: [number, number, number];
126
+ physicsRate: number;
127
+ }
128
+ interface PhysicsAddBodyCommand {
129
+ type: 'addBody';
130
+ uuid: string;
131
+ body: SerializableBodyDesc;
132
+ colliders: SerializableColliderDesc[];
133
+ characterController?: SerializableCharacterController;
134
+ }
135
+ interface PhysicsRemoveBodyCommand {
136
+ type: 'removeBody';
137
+ uuid: string;
138
+ }
139
+ interface PhysicsStepCommand {
140
+ type: 'step';
141
+ delta: number;
142
+ commands: BodyCommand[];
143
+ }
144
+ interface PhysicsDisposeCommand {
145
+ type: 'dispose';
146
+ }
147
+ type PhysicsEvent = PhysicsReadyEvent | PhysicsStepResultEvent | PhysicsErrorEvent;
148
+ interface PhysicsReadyEvent {
149
+ type: 'ready';
150
+ }
151
+ /**
152
+ * Result of a physics step sent from the worker each frame.
153
+ *
154
+ * `transforms` is a flat Float32Array with 13 floats per body:
155
+ * [posX, posY, posZ, rotX, rotY, rotZ, rotW, linvelX, linvelY, linvelZ, angvelX, angvelY, angvelZ]
156
+ *
157
+ * The order matches {@link bodyOrder} — an array of UUIDs in the same
158
+ * index order as the transform buffer.
159
+ */
160
+ interface PhysicsStepResultEvent {
161
+ type: 'stepResult';
162
+ /** Flat buffer: 13 floats per body (pos3, rot4, linvel3, angvel3). */
163
+ transforms: Float32Array;
164
+ /** UUID order matching the transforms buffer. */
165
+ bodyOrder: string[];
166
+ /** Collision pairs detected this frame. */
167
+ collisions: CollisionPair[];
168
+ /** Interpolation alpha for rendering (0..1). */
169
+ interpolationAlpha: number;
170
+ }
171
+ interface PhysicsErrorEvent {
172
+ type: 'error';
173
+ message: string;
174
+ }
175
+ /** A collision pair detected during the physics step. */
176
+ interface CollisionPair {
177
+ uuidA: string;
178
+ uuidB: string;
179
+ contactType: 'contact' | 'intersection';
180
+ }
181
+ /** Number of floats per body in the transforms buffer. */
182
+ declare const FLOATS_PER_BODY = 13;
183
+ /** Offsets into the per-body segment of the transforms buffer. */
184
+ declare const TransformOffset: {
185
+ readonly POS_X: 0;
186
+ readonly POS_Y: 1;
187
+ readonly POS_Z: 2;
188
+ readonly ROT_X: 3;
189
+ readonly ROT_Y: 4;
190
+ readonly ROT_Z: 5;
191
+ readonly ROT_W: 6;
192
+ readonly LINVEL_X: 7;
193
+ readonly LINVEL_Y: 8;
194
+ readonly LINVEL_Z: 9;
195
+ readonly ANGVEL_X: 10;
196
+ readonly ANGVEL_Y: 11;
197
+ readonly ANGVEL_Z: 12;
198
+ };
199
+
200
+ export { type BodyCommand as B, type CollisionPair as C, FLOATS_PER_BODY as F, type PhysicsStepResultEvent as P, type SerializableColliderDesc as S, TransformOffset as T, type SerializableBodyDesc as a, type SerializableCharacterController as b, type PhysicsCommand as c, type PhysicsEvent as d, type PhysicsReadyEvent as e, type PhysicsErrorEvent as f, type SerializableBodyType as g, type ColliderShapeKind as h };
@@ -0,0 +1,21 @@
1
+ import { ColliderDesc } from '@dimforge/rapier3d-compat';
2
+ import { S as SerializableColliderDesc } from './physics-protocol-BDD3P5W2.js';
3
+
4
+ /**
5
+ * Physics Web Worker.
6
+ *
7
+ * Owns the Rapier world and runs the fixed-timestep simulation loop.
8
+ * Communicates with the main thread via {@link PhysicsCommand} /
9
+ * {@link PhysicsEvent} messages defined in physics-protocol.ts.
10
+ *
11
+ * Lifecycle:
12
+ * 1. Main sends `init` → worker creates Rapier world.
13
+ * 2. Main sends `addBody` / `removeBody` to manage entities.
14
+ * 3. Main sends `step` each frame → worker runs accumulator,
15
+ * collects transforms + collisions, replies with `stepResult`.
16
+ * 4. Main sends `dispose` → worker frees resources.
17
+ */
18
+
19
+ declare function reconstructColliderDesc(desc: SerializableColliderDesc): ColliderDesc;
20
+
21
+ export { reconstructColliderDesc };
@@ -0,0 +1,306 @@
1
+ // src/lib/physics/physics-worker.ts
2
+ import RAPIER, { RigidBodyDesc, RigidBodyType, ColliderDesc } from "@dimforge/rapier3d-compat";
3
+
4
+ // src/lib/physics/physics-protocol.ts
5
+ var FLOATS_PER_BODY = 13;
6
+ var TransformOffset = {
7
+ POS_X: 0,
8
+ POS_Y: 1,
9
+ POS_Z: 2,
10
+ ROT_X: 3,
11
+ ROT_Y: 4,
12
+ ROT_Z: 5,
13
+ ROT_W: 6,
14
+ LINVEL_X: 7,
15
+ LINVEL_Y: 8,
16
+ LINVEL_Z: 9,
17
+ ANGVEL_X: 10,
18
+ ANGVEL_Y: 11,
19
+ ANGVEL_Z: 12
20
+ };
21
+
22
+ // src/lib/physics/physics-worker.ts
23
+ var world = null;
24
+ var fixedTimestep = 1 / 60;
25
+ var accumulator = 0;
26
+ var MAX_STEPS_PER_FRAME = 5;
27
+ var bodyMap = /* @__PURE__ */ new Map();
28
+ var bodyOrder = [];
29
+ var controllerMap = /* @__PURE__ */ new Map();
30
+ if (typeof self !== "undefined") {
31
+ self.onmessage = (e) => {
32
+ const cmd = e.data;
33
+ switch (cmd.type) {
34
+ case "init":
35
+ handleInit(cmd.gravity, cmd.physicsRate);
36
+ break;
37
+ case "addBody":
38
+ handleAddBody(cmd.uuid, cmd.body, cmd.colliders, cmd.characterController);
39
+ break;
40
+ case "removeBody":
41
+ handleRemoveBody(cmd.uuid);
42
+ break;
43
+ case "step":
44
+ handleStep(cmd.delta, cmd.commands);
45
+ break;
46
+ case "dispose":
47
+ handleDispose();
48
+ break;
49
+ }
50
+ };
51
+ }
52
+ async function handleInit(gravity, physicsRate) {
53
+ try {
54
+ await RAPIER.init();
55
+ world = new RAPIER.World({ x: gravity[0], y: gravity[1], z: gravity[2] });
56
+ fixedTimestep = 1 / physicsRate;
57
+ world.integrationParameters.dt = fixedTimestep;
58
+ accumulator = 0;
59
+ self.postMessage({ type: "ready" });
60
+ } catch (err) {
61
+ self.postMessage({
62
+ type: "error",
63
+ message: `Failed to init physics worker: ${err}`
64
+ });
65
+ }
66
+ }
67
+ function handleAddBody(uuid, desc, colliderDescs, charCtrl) {
68
+ if (!world) return;
69
+ const bodyDesc = reconstructBodyDesc(desc);
70
+ const body = world.createRigidBody(bodyDesc);
71
+ body.userData = { uuid };
72
+ for (const cd of colliderDescs) {
73
+ const colliderDesc = reconstructColliderDesc(cd);
74
+ world.createCollider(colliderDesc, body);
75
+ }
76
+ if (charCtrl) {
77
+ const controller = world.createCharacterController(charCtrl.offset);
78
+ controller.setMaxSlopeClimbAngle(charCtrl.maxSlopeClimbAngle);
79
+ controller.setMinSlopeSlideAngle(charCtrl.minSlopeSlideAngle);
80
+ controller.enableSnapToGround(charCtrl.snapToGroundDistance);
81
+ controller.setSlideEnabled(charCtrl.slideEnabled);
82
+ controller.setApplyImpulsesToDynamicBodies(charCtrl.applyImpulsesToDynamic);
83
+ controller.setCharacterMass(charCtrl.characterMass);
84
+ controllerMap.set(uuid, controller);
85
+ body.lockRotations(true, true);
86
+ }
87
+ bodyMap.set(uuid, body);
88
+ bodyOrder.push(uuid);
89
+ }
90
+ function handleRemoveBody(uuid) {
91
+ if (!world) return;
92
+ const ctrl = controllerMap.get(uuid);
93
+ if (ctrl) {
94
+ try {
95
+ ctrl.free();
96
+ } catch {
97
+ }
98
+ controllerMap.delete(uuid);
99
+ }
100
+ const body = bodyMap.get(uuid);
101
+ if (body) {
102
+ world.removeRigidBody(body);
103
+ bodyMap.delete(uuid);
104
+ bodyOrder = bodyOrder.filter((id) => id !== uuid);
105
+ }
106
+ }
107
+ function handleStep(delta, commands) {
108
+ if (!world) return;
109
+ applyBodyCommands(commands);
110
+ accumulator += delta;
111
+ const maxAccum = fixedTimestep * MAX_STEPS_PER_FRAME;
112
+ accumulator = Math.min(accumulator, maxAccum);
113
+ const collisions = [];
114
+ while (accumulator >= fixedTimestep) {
115
+ world.step();
116
+ accumulator -= fixedTimestep;
117
+ }
118
+ collectCollisions(collisions);
119
+ const interpolationAlpha = accumulator / fixedTimestep;
120
+ const transforms = packTransforms();
121
+ const result = {
122
+ type: "stepResult",
123
+ transforms,
124
+ bodyOrder: [...bodyOrder],
125
+ collisions,
126
+ interpolationAlpha
127
+ };
128
+ self.postMessage(result, [transforms.buffer]);
129
+ }
130
+ function handleDispose() {
131
+ for (const ctrl of controllerMap.values()) {
132
+ try {
133
+ ctrl.free();
134
+ } catch {
135
+ }
136
+ }
137
+ controllerMap.clear();
138
+ bodyMap.clear();
139
+ bodyOrder = [];
140
+ if (world) {
141
+ try {
142
+ world.free();
143
+ } catch {
144
+ }
145
+ world = null;
146
+ }
147
+ }
148
+ function applyBodyCommands(commands) {
149
+ for (const cmd of commands) {
150
+ const body = bodyMap.get(cmd.uuid);
151
+ if (!body) continue;
152
+ switch (cmd.kind) {
153
+ case "setLinvel":
154
+ body.setLinvel({ x: cmd.x, y: cmd.y, z: cmd.z }, true);
155
+ break;
156
+ case "setAngvel":
157
+ body.setAngvel({ x: cmd.x, y: cmd.y, z: cmd.z }, true);
158
+ break;
159
+ case "setTranslation":
160
+ body.setTranslation({ x: cmd.x, y: cmd.y, z: cmd.z }, true);
161
+ break;
162
+ case "setRotation":
163
+ body.setRotation({ x: cmd.x, y: cmd.y, z: cmd.z, w: cmd.w }, true);
164
+ break;
165
+ case "applyImpulse":
166
+ body.applyImpulse({ x: cmd.x, y: cmd.y, z: cmd.z }, true);
167
+ break;
168
+ case "applyTorqueImpulse":
169
+ body.applyTorqueImpulse({ x: cmd.x, y: cmd.y, z: cmd.z }, true);
170
+ break;
171
+ case "lockTranslations":
172
+ body.lockTranslations(cmd.locked, true);
173
+ break;
174
+ case "lockRotations":
175
+ body.lockRotations(cmd.locked, true);
176
+ break;
177
+ case "addTranslation": {
178
+ const t = body.translation();
179
+ body.setTranslation({ x: t.x + cmd.dx, y: t.y + cmd.dy, z: t.z + cmd.dz }, true);
180
+ break;
181
+ }
182
+ case "setLinearDamping":
183
+ body.setLinearDamping(cmd.damping);
184
+ break;
185
+ case "setGravityScale":
186
+ body.setGravityScale(cmd.scale, true);
187
+ break;
188
+ }
189
+ }
190
+ }
191
+ function collectCollisions(out) {
192
+ if (!world) return;
193
+ for (const [uuid, body] of bodyMap) {
194
+ const collider = body.collider(0);
195
+ if (!collider) continue;
196
+ world.contactsWith(collider, (otherCollider) => {
197
+ const otherBody = otherCollider.parent();
198
+ if (!otherBody) return;
199
+ const otherUuid = otherBody.userData?.uuid;
200
+ if (otherUuid) {
201
+ out.push({ uuidA: uuid, uuidB: otherUuid, contactType: "contact" });
202
+ }
203
+ });
204
+ world.intersectionsWith(collider, (otherCollider) => {
205
+ const otherBody = otherCollider.parent();
206
+ if (!otherBody) return;
207
+ const otherUuid = otherBody.userData?.uuid;
208
+ if (otherUuid) {
209
+ out.push({ uuidA: uuid, uuidB: otherUuid, contactType: "intersection" });
210
+ }
211
+ });
212
+ }
213
+ }
214
+ function packTransforms() {
215
+ const buf = new Float32Array(bodyOrder.length * FLOATS_PER_BODY);
216
+ for (let i = 0; i < bodyOrder.length; i++) {
217
+ const body = bodyMap.get(bodyOrder[i]);
218
+ if (!body) continue;
219
+ const offset = i * FLOATS_PER_BODY;
220
+ const pos = body.translation();
221
+ const rot = body.rotation();
222
+ const linvel = body.linvel();
223
+ const angvel = body.angvel();
224
+ buf[offset + TransformOffset.POS_X] = pos.x;
225
+ buf[offset + TransformOffset.POS_Y] = pos.y;
226
+ buf[offset + TransformOffset.POS_Z] = pos.z;
227
+ buf[offset + TransformOffset.ROT_X] = rot.x;
228
+ buf[offset + TransformOffset.ROT_Y] = rot.y;
229
+ buf[offset + TransformOffset.ROT_Z] = rot.z;
230
+ buf[offset + TransformOffset.ROT_W] = rot.w;
231
+ buf[offset + TransformOffset.LINVEL_X] = linvel.x;
232
+ buf[offset + TransformOffset.LINVEL_Y] = linvel.y;
233
+ buf[offset + TransformOffset.LINVEL_Z] = linvel.z;
234
+ buf[offset + TransformOffset.ANGVEL_X] = angvel.x;
235
+ buf[offset + TransformOffset.ANGVEL_Y] = angvel.y;
236
+ buf[offset + TransformOffset.ANGVEL_Z] = angvel.z;
237
+ }
238
+ return buf;
239
+ }
240
+ function reconstructBodyDesc(desc) {
241
+ const typeMap = {
242
+ dynamic: RigidBodyType.Dynamic,
243
+ fixed: RigidBodyType.Fixed,
244
+ kinematicPositionBased: RigidBodyType.KinematicPositionBased,
245
+ kinematicVelocityBased: RigidBodyType.KinematicVelocityBased
246
+ };
247
+ const bodyDesc = new RigidBodyDesc(typeMap[desc.type] ?? RigidBodyType.Dynamic).setTranslation(desc.translation[0], desc.translation[1], desc.translation[2]).setGravityScale(desc.gravityScale).setCanSleep(desc.canSleep).setCcdEnabled(desc.ccdEnabled);
248
+ return bodyDesc;
249
+ }
250
+ function reconstructColliderDesc(desc) {
251
+ let colliderDesc;
252
+ const d = desc.dimensions;
253
+ switch (desc.shape) {
254
+ case "cuboid":
255
+ colliderDesc = ColliderDesc.cuboid(d[0], d[1], d[2]);
256
+ break;
257
+ case "ball":
258
+ colliderDesc = ColliderDesc.ball(d[0]);
259
+ break;
260
+ case "capsule":
261
+ colliderDesc = ColliderDesc.capsule(d[0], d[1]);
262
+ break;
263
+ case "cone":
264
+ colliderDesc = ColliderDesc.cone(d[0], d[1]);
265
+ break;
266
+ case "cylinder":
267
+ colliderDesc = ColliderDesc.cylinder(d[0], d[1]);
268
+ break;
269
+ case "trimesh":
270
+ colliderDesc = ColliderDesc.trimesh(
271
+ new Float32Array(desc.vertices ?? []),
272
+ new Uint32Array(desc.indices ?? [])
273
+ );
274
+ colliderDesc.__zylemShapeData = {
275
+ shape: "trimesh",
276
+ vertices: [...desc.vertices ?? []],
277
+ indices: [...desc.indices ?? []]
278
+ };
279
+ break;
280
+ case "heightfield": {
281
+ const meta = desc.heightfieldMeta;
282
+ const heights = new Float32Array(d);
283
+ colliderDesc = ColliderDesc.heightfield(meta.nrows, meta.ncols, heights, { x: 1, y: 1, z: 1 });
284
+ break;
285
+ }
286
+ default:
287
+ colliderDesc = ColliderDesc.cuboid(0.5, 0.5, 0.5);
288
+ }
289
+ if (desc.translation) {
290
+ colliderDesc.setTranslation(desc.translation[0], desc.translation[1], desc.translation[2]);
291
+ }
292
+ if (desc.sensor) {
293
+ colliderDesc.setSensor(true);
294
+ }
295
+ if (desc.collisionGroups !== void 0) {
296
+ colliderDesc.setCollisionGroups(desc.collisionGroups);
297
+ }
298
+ if (desc.activeCollisionTypes !== void 0) {
299
+ colliderDesc.activeCollisionTypes = desc.activeCollisionTypes;
300
+ }
301
+ return colliderDesc;
302
+ }
303
+ export {
304
+ reconstructColliderDesc
305
+ };
306
+ //# sourceMappingURL=physics-worker.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/lib/physics/physics-worker.ts","../src/lib/physics/physics-protocol.ts"],"sourcesContent":["/**\n * Physics Web Worker.\n *\n * Owns the Rapier world and runs the fixed-timestep simulation loop.\n * Communicates with the main thread via {@link PhysicsCommand} /\n * {@link PhysicsEvent} messages defined in physics-protocol.ts.\n *\n * Lifecycle:\n * 1. Main sends `init` → worker creates Rapier world.\n * 2. Main sends `addBody` / `removeBody` to manage entities.\n * 3. Main sends `step` each frame → worker runs accumulator,\n * collects transforms + collisions, replies with `stepResult`.\n * 4. Main sends `dispose` → worker frees resources.\n */\n\nimport RAPIER, { RigidBody, RigidBodyDesc, RigidBodyType, ColliderDesc, World, ActiveCollisionTypes } from '@dimforge/rapier3d-compat';\nimport type {\n\tPhysicsCommand,\n\tPhysicsStepResultEvent,\n\tBodyCommand,\n\tSerializableBodyDesc,\n\tSerializableColliderDesc,\n\tSerializableCharacterController,\n\tCollisionPair,\n} from './physics-protocol';\nimport { FLOATS_PER_BODY, TransformOffset } from './physics-protocol';\n\n// ─── Internal State ────────────────────────────────────────────────────────\n\nlet world: World | null = null;\nlet fixedTimestep = 1 / 60;\nlet accumulator = 0;\nconst MAX_STEPS_PER_FRAME = 5;\n\n/** Map from entity UUID to Rapier RigidBody handle. */\nconst bodyMap = new Map<string, RigidBody>();\n\n/** Ordered list of UUIDs for deterministic transform packing. */\nlet bodyOrder: string[] = [];\n\n/** Character controllers keyed by entity UUID. */\nconst controllerMap = new Map<string, RAPIER.KinematicCharacterController>();\n\n// ─── Message Handler ───────────────────────────────────────────────────────\n\nif (typeof self !== 'undefined') {\n\tself.onmessage = (e: MessageEvent<PhysicsCommand>) => {\n\t\tconst cmd = e.data;\n\t\tswitch (cmd.type) {\n\t\t\tcase 'init':\n\t\t\t\thandleInit(cmd.gravity, cmd.physicsRate);\n\t\t\t\tbreak;\n\t\t\tcase 'addBody':\n\t\t\t\thandleAddBody(cmd.uuid, cmd.body, cmd.colliders, cmd.characterController);\n\t\t\t\tbreak;\n\t\t\tcase 'removeBody':\n\t\t\t\thandleRemoveBody(cmd.uuid);\n\t\t\t\tbreak;\n\t\t\tcase 'step':\n\t\t\t\thandleStep(cmd.delta, cmd.commands);\n\t\t\t\tbreak;\n\t\t\tcase 'dispose':\n\t\t\t\thandleDispose();\n\t\t\t\tbreak;\n\t\t}\n\t};\n}\n\n// ─── Command Handlers ──────────────────────────────────────────────────────\n\nasync function handleInit(gravity: [number, number, number], physicsRate: number) {\n\ttry {\n\t\tawait RAPIER.init();\n\t\tworld = new RAPIER.World({ x: gravity[0], y: gravity[1], z: gravity[2] });\n\t\tfixedTimestep = 1 / physicsRate;\n\t\tworld.integrationParameters.dt = fixedTimestep;\n\t\taccumulator = 0;\n\t\t(self as unknown as Worker).postMessage({ type: 'ready' });\n\t} catch (err) {\n\t\t(self as unknown as Worker).postMessage({\n\t\t\ttype: 'error',\n\t\t\tmessage: `Failed to init physics worker: ${err}`,\n\t\t});\n\t}\n}\n\nfunction handleAddBody(\n\tuuid: string,\n\tdesc: SerializableBodyDesc,\n\tcolliderDescs: SerializableColliderDesc[],\n\tcharCtrl?: SerializableCharacterController,\n) {\n\tif (!world) return;\n\n\tconst bodyDesc = reconstructBodyDesc(desc);\n\tconst body = world.createRigidBody(bodyDesc);\n\tbody.userData = { uuid };\n\n\tfor (const cd of colliderDescs) {\n\t\tconst colliderDesc = reconstructColliderDesc(cd);\n\t\tworld.createCollider(colliderDesc, body);\n\t}\n\n\tif (charCtrl) {\n\t\tconst controller = world.createCharacterController(charCtrl.offset);\n\t\tcontroller.setMaxSlopeClimbAngle(charCtrl.maxSlopeClimbAngle);\n\t\tcontroller.setMinSlopeSlideAngle(charCtrl.minSlopeSlideAngle);\n\t\tcontroller.enableSnapToGround(charCtrl.snapToGroundDistance);\n\t\tcontroller.setSlideEnabled(charCtrl.slideEnabled);\n\t\tcontroller.setApplyImpulsesToDynamicBodies(charCtrl.applyImpulsesToDynamic);\n\t\tcontroller.setCharacterMass(charCtrl.characterMass);\n\t\tcontrollerMap.set(uuid, controller);\n\t\tbody.lockRotations(true, true);\n\t}\n\n\tbodyMap.set(uuid, body);\n\tbodyOrder.push(uuid);\n}\n\nfunction handleRemoveBody(uuid: string) {\n\tif (!world) return;\n\n\tconst ctrl = controllerMap.get(uuid);\n\tif (ctrl) {\n\t\ttry { ctrl.free(); } catch { /* noop */ }\n\t\tcontrollerMap.delete(uuid);\n\t}\n\n\tconst body = bodyMap.get(uuid);\n\tif (body) {\n\t\tworld.removeRigidBody(body);\n\t\tbodyMap.delete(uuid);\n\t\tbodyOrder = bodyOrder.filter((id) => id !== uuid);\n\t}\n}\n\nfunction handleStep(delta: number, commands: BodyCommand[]) {\n\tif (!world) return;\n\n\tapplyBodyCommands(commands);\n\n\taccumulator += delta;\n\tconst maxAccum = fixedTimestep * MAX_STEPS_PER_FRAME;\n\taccumulator = Math.min(accumulator, maxAccum);\n\n\tconst collisions: CollisionPair[] = [];\n\n\twhile (accumulator >= fixedTimestep) {\n\t\tworld.step();\n\t\taccumulator -= fixedTimestep;\n\t}\n\n\tcollectCollisions(collisions);\n\n\tconst interpolationAlpha = accumulator / fixedTimestep;\n\n\tconst transforms = packTransforms();\n\n\tconst result: PhysicsStepResultEvent = {\n\t\ttype: 'stepResult',\n\t\ttransforms,\n\t\tbodyOrder: [...bodyOrder],\n\t\tcollisions,\n\t\tinterpolationAlpha,\n\t};\n\n\t(self as unknown as Worker).postMessage(result, [transforms.buffer]);\n}\n\nfunction handleDispose() {\n\tfor (const ctrl of controllerMap.values()) {\n\t\ttry { ctrl.free(); } catch { /* noop */ }\n\t}\n\tcontrollerMap.clear();\n\tbodyMap.clear();\n\tbodyOrder = [];\n\n\tif (world) {\n\t\ttry { world.free(); } catch { /* noop */ }\n\t\tworld = null;\n\t}\n}\n\n// ─── Body Command Application ──────────────────────────────────────────────\n\nfunction applyBodyCommands(commands: BodyCommand[]) {\n\tfor (const cmd of commands) {\n\t\tconst body = bodyMap.get(cmd.uuid);\n\t\tif (!body) continue;\n\n\t\tswitch (cmd.kind) {\n\t\t\tcase 'setLinvel':\n\t\t\t\tbody.setLinvel({ x: cmd.x, y: cmd.y, z: cmd.z }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'setAngvel':\n\t\t\t\tbody.setAngvel({ x: cmd.x, y: cmd.y, z: cmd.z }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'setTranslation':\n\t\t\t\tbody.setTranslation({ x: cmd.x, y: cmd.y, z: cmd.z }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'setRotation':\n\t\t\t\tbody.setRotation({ x: cmd.x, y: cmd.y, z: cmd.z, w: cmd.w }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'applyImpulse':\n\t\t\t\tbody.applyImpulse({ x: cmd.x, y: cmd.y, z: cmd.z }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'applyTorqueImpulse':\n\t\t\t\tbody.applyTorqueImpulse({ x: cmd.x, y: cmd.y, z: cmd.z }, true);\n\t\t\t\tbreak;\n\t\t\tcase 'lockTranslations':\n\t\t\t\tbody.lockTranslations(cmd.locked, true);\n\t\t\t\tbreak;\n\t\t\tcase 'lockRotations':\n\t\t\t\tbody.lockRotations(cmd.locked, true);\n\t\t\t\tbreak;\n\t\t\tcase 'addTranslation': {\n\t\t\t\tconst t = body.translation();\n\t\t\t\tbody.setTranslation({ x: t.x + cmd.dx, y: t.y + cmd.dy, z: t.z + cmd.dz }, true);\n\t\t\t\tbreak;\n\t\t\t}\n\t\t\tcase 'setLinearDamping':\n\t\t\t\tbody.setLinearDamping(cmd.damping);\n\t\t\t\tbreak;\n\t\t\tcase 'setGravityScale':\n\t\t\t\tbody.setGravityScale(cmd.scale, true);\n\t\t\t\tbreak;\n\t\t}\n\t}\n}\n\n// ─── Collision Collection ──────────────────────────────────────────────────\n\nfunction collectCollisions(out: CollisionPair[]) {\n\tif (!world) return;\n\n\tfor (const [uuid, body] of bodyMap) {\n\t\tconst collider = body.collider(0);\n\t\tif (!collider) continue;\n\n\t\tworld.contactsWith(collider, (otherCollider) => {\n\t\t\tconst otherBody = otherCollider.parent();\n\t\t\tif (!otherBody) return;\n\t\t\tconst otherUuid = (otherBody.userData as any)?.uuid as string | undefined;\n\t\t\tif (otherUuid) {\n\t\t\t\tout.push({ uuidA: uuid, uuidB: otherUuid, contactType: 'contact' });\n\t\t\t}\n\t\t});\n\n\t\tworld.intersectionsWith(collider, (otherCollider) => {\n\t\t\tconst otherBody = otherCollider.parent();\n\t\t\tif (!otherBody) return;\n\t\t\tconst otherUuid = (otherBody.userData as any)?.uuid as string | undefined;\n\t\t\tif (otherUuid) {\n\t\t\t\tout.push({ uuidA: uuid, uuidB: otherUuid, contactType: 'intersection' });\n\t\t\t}\n\t\t});\n\t}\n}\n\n// ─── Transform Packing ─────────────────────────────────────────────────────\n\nfunction packTransforms(): Float32Array {\n\tconst buf = new Float32Array(bodyOrder.length * FLOATS_PER_BODY);\n\n\tfor (let i = 0; i < bodyOrder.length; i++) {\n\t\tconst body = bodyMap.get(bodyOrder[i]);\n\t\tif (!body) continue;\n\n\t\tconst offset = i * FLOATS_PER_BODY;\n\t\tconst pos = body.translation();\n\t\tconst rot = body.rotation();\n\t\tconst linvel = body.linvel();\n\t\tconst angvel = body.angvel();\n\n\t\tbuf[offset + TransformOffset.POS_X] = pos.x;\n\t\tbuf[offset + TransformOffset.POS_Y] = pos.y;\n\t\tbuf[offset + TransformOffset.POS_Z] = pos.z;\n\n\t\tbuf[offset + TransformOffset.ROT_X] = rot.x;\n\t\tbuf[offset + TransformOffset.ROT_Y] = rot.y;\n\t\tbuf[offset + TransformOffset.ROT_Z] = rot.z;\n\t\tbuf[offset + TransformOffset.ROT_W] = rot.w;\n\n\t\tbuf[offset + TransformOffset.LINVEL_X] = linvel.x;\n\t\tbuf[offset + TransformOffset.LINVEL_Y] = linvel.y;\n\t\tbuf[offset + TransformOffset.LINVEL_Z] = linvel.z;\n\n\t\tbuf[offset + TransformOffset.ANGVEL_X] = angvel.x;\n\t\tbuf[offset + TransformOffset.ANGVEL_Y] = angvel.y;\n\t\tbuf[offset + TransformOffset.ANGVEL_Z] = angvel.z;\n\t}\n\n\treturn buf;\n}\n\n// ─── Descriptor Reconstruction ─────────────────────────────────────────────\n\nfunction reconstructBodyDesc(desc: SerializableBodyDesc): RigidBodyDesc {\n\tconst typeMap: Record<string, RigidBodyType> = {\n\t\tdynamic: RigidBodyType.Dynamic,\n\t\tfixed: RigidBodyType.Fixed,\n\t\tkinematicPositionBased: RigidBodyType.KinematicPositionBased,\n\t\tkinematicVelocityBased: RigidBodyType.KinematicVelocityBased,\n\t};\n\n\tconst bodyDesc = new RigidBodyDesc(typeMap[desc.type] ?? RigidBodyType.Dynamic)\n\t\t.setTranslation(desc.translation[0], desc.translation[1], desc.translation[2])\n\t\t.setGravityScale(desc.gravityScale)\n\t\t.setCanSleep(desc.canSleep)\n\t\t.setCcdEnabled(desc.ccdEnabled);\n\n\treturn bodyDesc;\n}\n\nexport function reconstructColliderDesc(desc: SerializableColliderDesc): ColliderDesc {\n\tlet colliderDesc: ColliderDesc;\n\tconst d = desc.dimensions;\n\n\tswitch (desc.shape) {\n\t\tcase 'cuboid':\n\t\t\tcolliderDesc = ColliderDesc.cuboid(d[0], d[1], d[2]);\n\t\t\tbreak;\n\t\tcase 'ball':\n\t\t\tcolliderDesc = ColliderDesc.ball(d[0]);\n\t\t\tbreak;\n\t\tcase 'capsule':\n\t\t\tcolliderDesc = ColliderDesc.capsule(d[0], d[1]);\n\t\t\tbreak;\n\t\tcase 'cone':\n\t\t\tcolliderDesc = ColliderDesc.cone(d[0], d[1]);\n\t\t\tbreak;\n\t\tcase 'cylinder':\n\t\t\tcolliderDesc = ColliderDesc.cylinder(d[0], d[1]);\n\t\t\tbreak;\n\t\tcase 'trimesh':\n\t\t\tcolliderDesc = ColliderDesc.trimesh(\n\t\t\t\tnew Float32Array(desc.vertices ?? []),\n\t\t\t\tnew Uint32Array(desc.indices ?? []),\n\t\t\t);\n\t\t\t(colliderDesc as any).__zylemShapeData = {\n\t\t\t\tshape: 'trimesh',\n\t\t\t\tvertices: [...(desc.vertices ?? [])],\n\t\t\t\tindices: [...(desc.indices ?? [])],\n\t\t\t};\n\t\t\tbreak;\n\t\tcase 'heightfield': {\n\t\t\tconst meta = desc.heightfieldMeta!;\n\t\t\tconst heights = new Float32Array(d);\n\t\t\tcolliderDesc = ColliderDesc.heightfield(meta.nrows, meta.ncols, heights, { x: 1, y: 1, z: 1 });\n\t\t\tbreak;\n\t\t}\n\t\tdefault:\n\t\t\tcolliderDesc = ColliderDesc.cuboid(0.5, 0.5, 0.5);\n\t}\n\n\tif (desc.translation) {\n\t\tcolliderDesc.setTranslation(desc.translation[0], desc.translation[1], desc.translation[2]);\n\t}\n\n\tif (desc.sensor) {\n\t\tcolliderDesc.setSensor(true);\n\t}\n\n\tif (desc.collisionGroups !== undefined) {\n\t\tcolliderDesc.setCollisionGroups(desc.collisionGroups);\n\t}\n\n\tif (desc.activeCollisionTypes !== undefined) {\n\t\tcolliderDesc.activeCollisionTypes = desc.activeCollisionTypes as ActiveCollisionTypes;\n\t}\n\n\treturn colliderDesc;\n}\n","/**\n * Message protocol for communication between the main thread and the\n * physics Web Worker.\n *\n * Data flows in two directions:\n * Main → Worker: {@link PhysicsCommand} (init, entity CRUD, per-frame step)\n * Worker → Main: {@link PhysicsEvent} (ready, step results, errors)\n *\n * All types are plain JSON-serializable objects so they can cross the\n * postMessage boundary without structured-clone issues.\n */\n\n// ─── Serializable Descriptor Types ─────────────────────────────────────────\n\n/** Rigid body type mirroring Rapier's RigidBodyType enum values. */\nexport type SerializableBodyType = 'dynamic' | 'fixed' | 'kinematicPositionBased' | 'kinematicVelocityBased';\n\n/**\n * Plain-object representation of a Rapier RigidBodyDesc.\n * Created on the main thread and reconstructed in the worker.\n */\nexport interface SerializableBodyDesc {\n\ttype: SerializableBodyType;\n\ttranslation: [number, number, number];\n\tgravityScale: number;\n\tcanSleep: boolean;\n\tccdEnabled: boolean;\n\tlockTranslations?: boolean;\n\tlockRotations?: boolean;\n}\n\n/** Collider shape kind. Matches the factory functions in collision-factories. */\nexport type ColliderShapeKind =\n\t| 'cuboid'\n\t| 'ball'\n\t| 'capsule'\n\t| 'cone'\n\t| 'cylinder'\n\t| 'trimesh'\n\t| 'heightfield';\n\n/**\n * Plain-object representation of a Rapier ColliderDesc.\n * Shape-specific dimensions are stored in {@link dimensions}.\n */\nexport interface SerializableColliderDesc {\n\tshape: ColliderShapeKind;\n\t/** Shape-specific numeric dimensions (e.g. halfExtents, radius, halfHeight). */\n\tdimensions: number[];\n\t/** For trimesh: flattened xyz vertex array. */\n\tvertices?: number[];\n\t/** For trimesh: triangle indices referencing {@link vertices}. */\n\tindices?: number[];\n\t/** Collider offset translation relative to the body. */\n\ttranslation?: [number, number, number];\n\tsensor?: boolean;\n\tcollisionGroups?: number;\n\tactiveCollisionTypes?: number;\n\t/** For heightfield: number of rows and columns. */\n\theightfieldMeta?: { nrows: number; ncols: number };\n}\n\n/** Describes a character controller to be created alongside the body. */\nexport interface SerializableCharacterController {\n\toffset: number;\n\tmaxSlopeClimbAngle: number;\n\tminSlopeSlideAngle: number;\n\tsnapToGroundDistance: number;\n\tslideEnabled: boolean;\n\tapplyImpulsesToDynamic: boolean;\n\tcharacterMass: number;\n}\n\n// ─── Per-Body Commands (batched inside a step) ─────────────────────────────\n\nexport type BodyCommand =\n\t| { kind: 'setLinvel'; uuid: string; x: number; y: number; z: number }\n\t| { kind: 'setAngvel'; uuid: string; x: number; y: number; z: number }\n\t| { kind: 'setTranslation'; uuid: string; x: number; y: number; z: number }\n\t| { kind: 'setRotation'; uuid: string; x: number; y: number; z: number; w: number }\n\t| { kind: 'applyImpulse'; uuid: string; x: number; y: number; z: number }\n\t| { kind: 'applyTorqueImpulse'; uuid: string; x: number; y: number; z: number }\n\t| { kind: 'lockTranslations'; uuid: string; locked: boolean }\n\t| { kind: 'lockRotations'; uuid: string; locked: boolean }\n\t| { kind: 'addTranslation'; uuid: string; dx: number; dy: number; dz: number }\n\t| { kind: 'setLinearDamping'; uuid: string; damping: number }\n\t| { kind: 'setGravityScale'; uuid: string; scale: number };\n\n// ─── Main → Worker Commands ────────────────────────────────────────────────\n\nexport type PhysicsCommand =\n\t| PhysicsInitCommand\n\t| PhysicsAddBodyCommand\n\t| PhysicsRemoveBodyCommand\n\t| PhysicsStepCommand\n\t| PhysicsDisposeCommand;\n\nexport interface PhysicsInitCommand {\n\ttype: 'init';\n\tgravity: [number, number, number];\n\tphysicsRate: number;\n}\n\nexport interface PhysicsAddBodyCommand {\n\ttype: 'addBody';\n\tuuid: string;\n\tbody: SerializableBodyDesc;\n\tcolliders: SerializableColliderDesc[];\n\tcharacterController?: SerializableCharacterController;\n}\n\nexport interface PhysicsRemoveBodyCommand {\n\ttype: 'removeBody';\n\tuuid: string;\n}\n\nexport interface PhysicsStepCommand {\n\ttype: 'step';\n\tdelta: number;\n\tcommands: BodyCommand[];\n}\n\nexport interface PhysicsDisposeCommand {\n\ttype: 'dispose';\n}\n\n// ─── Worker → Main Events ──────────────────────────────────────────────────\n\nexport type PhysicsEvent =\n\t| PhysicsReadyEvent\n\t| PhysicsStepResultEvent\n\t| PhysicsErrorEvent;\n\nexport interface PhysicsReadyEvent {\n\ttype: 'ready';\n}\n\n/**\n * Result of a physics step sent from the worker each frame.\n *\n * `transforms` is a flat Float32Array with 13 floats per body:\n * [posX, posY, posZ, rotX, rotY, rotZ, rotW, linvelX, linvelY, linvelZ, angvelX, angvelY, angvelZ]\n *\n * The order matches {@link bodyOrder} — an array of UUIDs in the same\n * index order as the transform buffer.\n */\nexport interface PhysicsStepResultEvent {\n\ttype: 'stepResult';\n\t/** Flat buffer: 13 floats per body (pos3, rot4, linvel3, angvel3). */\n\ttransforms: Float32Array;\n\t/** UUID order matching the transforms buffer. */\n\tbodyOrder: string[];\n\t/** Collision pairs detected this frame. */\n\tcollisions: CollisionPair[];\n\t/** Interpolation alpha for rendering (0..1). */\n\tinterpolationAlpha: number;\n}\n\nexport interface PhysicsErrorEvent {\n\ttype: 'error';\n\tmessage: string;\n}\n\n/** A collision pair detected during the physics step. */\nexport interface CollisionPair {\n\tuuidA: string;\n\tuuidB: string;\n\tcontactType: 'contact' | 'intersection';\n}\n\n// ─── Transform Buffer Layout ───────────────────────────────────────────────\n\n/** Number of floats per body in the transforms buffer. */\nexport const FLOATS_PER_BODY = 13;\n\n/** Offsets into the per-body segment of the transforms buffer. */\nexport const TransformOffset = {\n\tPOS_X: 0,\n\tPOS_Y: 1,\n\tPOS_Z: 2,\n\tROT_X: 3,\n\tROT_Y: 4,\n\tROT_Z: 5,\n\tROT_W: 6,\n\tLINVEL_X: 7,\n\tLINVEL_Y: 8,\n\tLINVEL_Z: 9,\n\tANGVEL_X: 10,\n\tANGVEL_Y: 11,\n\tANGVEL_Z: 12,\n} as const;\n"],"mappings":"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","names":[]}