@forgeax/engine-physics 0.1.26 → 0.1.28

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.
package/dist/index.mjs CHANGED
@@ -1,5 +1,5 @@
1
1
  import { defineComponent, defineSystemSet } from '@forgeax/engine-ecs';
2
- import { PhysicsError } from '@forgeax/engine-types';
2
+ import { err, ok, PhysicsError } from '@forgeax/engine-types';
3
3
  export { PHYSICS_ERROR_HINTS, PhysicsError } from '@forgeax/engine-types';
4
4
 
5
5
  // src/collision-event.ts
@@ -85,6 +85,419 @@ function registerPhysicsComponents(world) {
85
85
  for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();
86
86
  };
87
87
  }
88
+ var DERIVED_PHYSICS_LIMITS = Object.freeze({
89
+ maxCandidates: 32,
90
+ maxShapesPerCandidate: 64,
91
+ maxConstraintsPerCandidate: 64,
92
+ maxCellsPerCandidate: 262144,
93
+ maxCandidateBytes: 8 * 1024 * 1024
94
+ });
95
+ var DerivedPhysicsError = class extends Error {
96
+ code;
97
+ expected;
98
+ hint;
99
+ detail;
100
+ constructor(code, expected, hint, detail = {}) {
101
+ super(`${code}: ${expected}`);
102
+ this.name = "DerivedPhysicsError";
103
+ this.code = code;
104
+ this.expected = expected;
105
+ this.hint = hint;
106
+ this.detail = Object.freeze({ code, ...detail });
107
+ }
108
+ };
109
+ var ID_RE = /\S/;
110
+ function finite(value) {
111
+ return Number.isFinite(value);
112
+ }
113
+ function vectorFinite(vector, length) {
114
+ return vector.length === length && vector.every(finite);
115
+ }
116
+ function rotateVectorByQuaternion(vector, rotation) {
117
+ const [x, y, z] = vector;
118
+ const [qx, qy, qz, qw] = rotation;
119
+ const tx = 2 * (qy * z - qz * y);
120
+ const ty = 2 * (qz * x - qx * z);
121
+ const tz = 2 * (qx * y - qy * x);
122
+ return [
123
+ x + qw * tx + qy * tz - qz * ty,
124
+ y + qw * ty + qz * tx - qx * tz,
125
+ z + qw * tz + qx * ty - qy * tx
126
+ ];
127
+ }
128
+ function normalizedQuaternion(rotation) {
129
+ if (!vectorFinite(rotation, 4)) return void 0;
130
+ const length = Math.hypot(rotation[0], rotation[1], rotation[2], rotation[3]);
131
+ if (!finite(length) || length < 1e-6) return void 0;
132
+ return [rotation[0] / length, rotation[1] / length, rotation[2] / length, rotation[3] / length];
133
+ }
134
+ function copyCells(cells) {
135
+ if (cells instanceof Int32Array) return new Int32Array(cells);
136
+ const result = new Int32Array(cells.length * 3);
137
+ for (let index = 0; index < cells.length; index += 1) {
138
+ const cell = cells[index];
139
+ if (cell === void 0 || cell.length !== 3 || cell.some((value) => !Number.isInteger(value))) {
140
+ return void 0;
141
+ }
142
+ result[index * 3] = cell[0] ?? 0;
143
+ result[index * 3 + 1] = cell[1] ?? 0;
144
+ result[index * 3 + 2] = cell[2] ?? 0;
145
+ }
146
+ return result;
147
+ }
148
+ function normalizeVoxelShapeInput(input) {
149
+ if (typeof input.id !== "string" || !ID_RE.test(input.id)) {
150
+ return err(
151
+ new DerivedPhysicsError(
152
+ "derived-shape-invalid",
153
+ "voxel shape id is a non-empty stable string",
154
+ "provide a stable shape identity from the consumer state",
155
+ { shapeId: input.id }
156
+ )
157
+ );
158
+ }
159
+ if (!Number.isInteger(input.revision) || input.revision < 0) {
160
+ return err(
161
+ new DerivedPhysicsError(
162
+ "derived-shape-invalid",
163
+ "voxel shape revision is a non-negative integer",
164
+ "increment the shape revision when its cells or transform changes",
165
+ { shapeId: input.id, actual: input.revision }
166
+ )
167
+ );
168
+ }
169
+ const cells = copyCells(input.cells);
170
+ if (cells === void 0 || cells.length === 0 || cells.length % 3 !== 0) {
171
+ return err(
172
+ new DerivedPhysicsError(
173
+ "derived-shape-invalid",
174
+ "voxel cells contain at least one complete integer x/y/z triple",
175
+ "supply a non-empty Int32Array or cell tuple list",
176
+ { shapeId: input.id, actual: cells?.length }
177
+ )
178
+ );
179
+ }
180
+ const voxelSize = input.voxelSize;
181
+ if (!vectorFinite(voxelSize, 3) || voxelSize.some((value) => value <= 0)) {
182
+ return err(
183
+ new DerivedPhysicsError(
184
+ "derived-shape-invalid",
185
+ "voxelSize contains finite positive components",
186
+ "choose a finite positive voxel size for every axis",
187
+ { shapeId: input.id, actual: voxelSize }
188
+ )
189
+ );
190
+ }
191
+ for (const value of cells) {
192
+ if (!Number.isInteger(value)) {
193
+ return err(
194
+ new DerivedPhysicsError(
195
+ "derived-shape-invalid",
196
+ "voxel coordinates are integers",
197
+ "quantize cells before submitting a physics candidate",
198
+ { shapeId: input.id, actual: value }
199
+ )
200
+ );
201
+ }
202
+ }
203
+ const origin = input.origin ?? [0, 0, 0];
204
+ if (!vectorFinite(origin, 3)) {
205
+ return err(
206
+ new DerivedPhysicsError(
207
+ "derived-shape-invalid",
208
+ "voxel origin contains finite coordinates",
209
+ "supply a finite local origin",
210
+ { shapeId: input.id, actual: origin }
211
+ )
212
+ );
213
+ }
214
+ const rotation = normalizedQuaternion(input.rotation ?? [0, 0, 0, 1]);
215
+ if (rotation === void 0) {
216
+ return err(
217
+ new DerivedPhysicsError(
218
+ "derived-shape-invalid",
219
+ "voxel rotation is a finite non-degenerate quaternion",
220
+ "normalize the local voxel orientation before submitting it",
221
+ { shapeId: input.id, actual: input.rotation }
222
+ )
223
+ );
224
+ }
225
+ for (const [name, value] of [
226
+ ["friction", input.friction],
227
+ ["restitution", input.restitution],
228
+ ["density", input.density]
229
+ ]) {
230
+ if (value !== void 0 && (!finite(value) || value < 0)) {
231
+ return err(
232
+ new DerivedPhysicsError(
233
+ "derived-shape-invalid",
234
+ `${name} is finite and non-negative`,
235
+ `repair the ${name} input before native preparation`,
236
+ { shapeId: input.id, actual: value }
237
+ )
238
+ );
239
+ }
240
+ }
241
+ return ok(
242
+ Object.freeze({
243
+ ...input,
244
+ cells,
245
+ voxelSize: [voxelSize[0], voxelSize[1], voxelSize[2]],
246
+ origin: [origin[0], origin[1], origin[2]],
247
+ rotation
248
+ })
249
+ );
250
+ }
251
+ function validateMassProperties(properties) {
252
+ if (properties === void 0 || properties.mode === "automatic") {
253
+ if (properties?.density !== void 0 && (!finite(properties.density) || properties.density <= 0)) {
254
+ return err(
255
+ new DerivedPhysicsError(
256
+ "derived-mass-invalid",
257
+ "automatic density is finite and positive",
258
+ "omit density to use backend density or provide a positive density",
259
+ { actual: properties.density }
260
+ )
261
+ );
262
+ }
263
+ return ok(properties);
264
+ }
265
+ if (!finite(properties.mass) || properties.mass <= 0 || !vectorFinite(properties.centerOfMass, 3) || !vectorFinite(properties.principalInertia, 3) || properties.principalInertia.some((value) => !finite(value) || value <= 0)) {
266
+ return err(
267
+ new DerivedPhysicsError(
268
+ "derived-mass-invalid",
269
+ "explicit mass, center of mass, and principal inertia are finite and non-degenerate",
270
+ "provide positive mass/inertia and a finite center of mass",
271
+ { actual: properties }
272
+ )
273
+ );
274
+ }
275
+ const frame = normalizedQuaternion(properties.principalInertiaLocalFrame ?? [0, 0, 0, 1]);
276
+ if (frame === void 0) {
277
+ return err(
278
+ new DerivedPhysicsError(
279
+ "derived-mass-invalid",
280
+ "principal inertia local frame is a finite non-degenerate quaternion",
281
+ "normalize the inertia frame before submitting it",
282
+ { actual: properties.principalInertiaLocalFrame }
283
+ )
284
+ );
285
+ }
286
+ return ok(
287
+ Object.freeze({
288
+ ...properties,
289
+ centerOfMass: [...properties.centerOfMass],
290
+ principalInertia: [...properties.principalInertia],
291
+ principalInertiaLocalFrame: frame
292
+ })
293
+ );
294
+ }
295
+ function preserveCenterOfMassVelocity(linearVelocity, angularVelocity, previousWorldCom, nextWorldCom) {
296
+ const dx = nextWorldCom[0] - previousWorldCom[0];
297
+ const dy = nextWorldCom[1] - previousWorldCom[1];
298
+ const dz = nextWorldCom[2] - previousWorldCom[2];
299
+ return [
300
+ linearVelocity[0] + angularVelocity[1] * dz - angularVelocity[2] * dy,
301
+ linearVelocity[1] + angularVelocity[2] * dx - angularVelocity[0] * dz,
302
+ linearVelocity[2] + angularVelocity[0] * dy - angularVelocity[1] * dx
303
+ ];
304
+ }
305
+ function cloneDerivedPhysicsInput(input) {
306
+ if (!Number.isInteger(input.entity) || input.entity < 0) {
307
+ return err(
308
+ new DerivedPhysicsError(
309
+ "derived-candidate-invalid",
310
+ "candidate entity is a non-negative ECS entity value",
311
+ "submit a live entity from the same World",
312
+ { entity: input.entity }
313
+ )
314
+ );
315
+ }
316
+ if (!Number.isInteger(input.revision) || input.revision < 0) {
317
+ return err(
318
+ new DerivedPhysicsError(
319
+ "derived-candidate-invalid",
320
+ "candidate revision is a non-negative integer",
321
+ "advance the consumer topology revision monotonically",
322
+ { entity: input.entity, actual: input.revision }
323
+ )
324
+ );
325
+ }
326
+ if (typeof input.sourceKey !== "string" || !ID_RE.test(input.sourceKey)) {
327
+ return err(
328
+ new DerivedPhysicsError(
329
+ "derived-candidate-invalid",
330
+ "candidate sourceKey is a non-empty stable producer identity",
331
+ "carry the producer sourceKey with every derived body revision",
332
+ { entity: input.entity, actual: input.sourceKey }
333
+ )
334
+ );
335
+ }
336
+ if (input.shapes.length === 0 || input.shapes.length > DERIVED_PHYSICS_LIMITS.maxShapesPerCandidate) {
337
+ return err(
338
+ new DerivedPhysicsError(
339
+ "derived-candidate-budget-exceeded",
340
+ `one candidate contains between one and ${DERIVED_PHYSICS_LIMITS.maxShapesPerCandidate} derived shapes`,
341
+ "split the consumer operation at a body boundary and retry",
342
+ { entity: input.entity, actual: input.shapes.length }
343
+ )
344
+ );
345
+ }
346
+ const seen = /* @__PURE__ */ new Set();
347
+ const shapes = [];
348
+ for (const shape of input.shapes) {
349
+ if (seen.has(shape.id)) {
350
+ return err(
351
+ new DerivedPhysicsError(
352
+ "derived-shape-duplicate",
353
+ "one candidate has one identity per derived shape",
354
+ "merge or rename duplicate shape inputs before preparation",
355
+ { entity: input.entity, shapeId: shape.id }
356
+ )
357
+ );
358
+ }
359
+ seen.add(shape.id);
360
+ const normalized = normalizeVoxelShapeInput(shape);
361
+ if (!normalized.ok) return normalized;
362
+ shapes.push(normalized.value);
363
+ }
364
+ const mass = validateMassProperties(input.massProperties);
365
+ if (!mass.ok) return mass;
366
+ if (input.motion !== void 0 && (!vectorFinite(input.motion.centerOfMass, 3) || !vectorFinite(input.motion.linearVelocity, 3) || !vectorFinite(input.motion.angularVelocity, 3))) {
367
+ return err(
368
+ new DerivedPhysicsError(
369
+ "derived-candidate-invalid",
370
+ "optional movement state contains finite world-space COM and velocity vectors",
371
+ "capture or provide three finite components for centerOfMass, linearVelocity, and angularVelocity",
372
+ { entity: input.entity, actual: input.motion }
373
+ )
374
+ );
375
+ }
376
+ const constraints = input.constraints ?? [];
377
+ if (constraints.length > DERIVED_PHYSICS_LIMITS.maxConstraintsPerCandidate) {
378
+ return err(
379
+ new DerivedPhysicsError(
380
+ "derived-candidate-budget-exceeded",
381
+ `one candidate contains at most ${DERIVED_PHYSICS_LIMITS.maxConstraintsPerCandidate} constraint updates`,
382
+ "submit a bounded constraint set for this body",
383
+ { entity: input.entity, actual: constraints.length }
384
+ )
385
+ );
386
+ }
387
+ const seams = input.seams ?? [];
388
+ const shapeById = new Map(shapes.map((shape) => [shape.id, shape]));
389
+ for (const seam of seams) {
390
+ const a = shapeById.get(seam.shapeA);
391
+ const b = shapeById.get(seam.shapeB);
392
+ const aRotation = a?.rotation ?? [0, 0, 0, 1];
393
+ const bRotation = b?.rotation ?? [0, 0, 0, 1];
394
+ const aOrigin = a?.origin ?? [0, 0, 0];
395
+ const bOrigin = b?.origin ?? [0, 0, 0];
396
+ const quaternionDot = aRotation.reduce(
397
+ (sum, value, index) => sum + value * (bRotation[index] ?? 0),
398
+ 0
399
+ );
400
+ const localOriginDelta = [
401
+ (bOrigin[0] ?? 0) - (aOrigin[0] ?? 0),
402
+ (bOrigin[1] ?? 0) - (aOrigin[1] ?? 0),
403
+ (bOrigin[2] ?? 0) - (aOrigin[2] ?? 0)
404
+ ];
405
+ const sharedGridDelta = rotateVectorByQuaternion(localOriginDelta, [
406
+ -aRotation[0],
407
+ -aRotation[1],
408
+ -aRotation[2],
409
+ aRotation[3]
410
+ ]);
411
+ const alignedOrigins = a !== void 0 && b !== void 0 && seam.offset.every(
412
+ (value, index) => Math.abs((sharedGridDelta[index] ?? 0) / (a?.voxelSize[index] ?? 1) - value) <= 1e-5
413
+ );
414
+ if (a === void 0 || b === void 0 || a.id === b.id || !vectorFinite(seam.offset, 3) || seam.offset.some((value) => !Number.isInteger(value)) || a.voxelSize.some((value, index) => Math.abs(value - (b.voxelSize[index] ?? 0)) > 1e-6) || Math.abs(Math.abs(quaternionDot) - 1) > 1e-5 || !alignedOrigins) {
415
+ return err(
416
+ new DerivedPhysicsError(
417
+ "derived-seam-invalid",
418
+ "a voxel seam joins same-grid shapes with integer offset in the shared rotated grid frame",
419
+ "rotate the local origin delta into the shared grid frame and use an integer grid offset",
420
+ { entity: input.entity, shapeId: seam.shapeA }
421
+ )
422
+ );
423
+ }
424
+ }
425
+ const seenConstraints = /* @__PURE__ */ new Set();
426
+ for (const constraint of constraints) {
427
+ if (seenConstraints.has(constraint.id)) {
428
+ return err(
429
+ new DerivedPhysicsError(
430
+ "derived-constraint-invalid",
431
+ "one candidate contains one update per constraint identity",
432
+ "merge duplicate constraint updates before preparation",
433
+ { entity: input.entity, constraintId: constraint.id }
434
+ )
435
+ );
436
+ }
437
+ seenConstraints.add(constraint.id);
438
+ for (const dependency of [constraint.bodyASource, constraint.bodyBSource]) {
439
+ if (typeof dependency.sourceKey !== "string" || !ID_RE.test(dependency.sourceKey) || !Number.isInteger(dependency.revision) || dependency.revision < 0) {
440
+ return err(
441
+ new DerivedPhysicsError(
442
+ "derived-constraint-invalid",
443
+ "constraint endpoint sourceKey and revision are stable and non-negative",
444
+ "refresh both endpoint dependencies before preparing the constraint",
445
+ { entity: input.entity, constraintId: constraint.id }
446
+ )
447
+ );
448
+ }
449
+ }
450
+ }
451
+ const cellCount = shapes.reduce((sum, shape) => sum + shape.cells.length / 3, 0);
452
+ if (cellCount > DERIVED_PHYSICS_LIMITS.maxCellsPerCandidate) {
453
+ return err(
454
+ new DerivedPhysicsError(
455
+ "derived-candidate-budget-exceeded",
456
+ `one candidate contains at most ${DERIVED_PHYSICS_LIMITS.maxCellsPerCandidate} cells`,
457
+ "reduce the voxel input or split it at a body boundary",
458
+ { entity: input.entity, actual: cellCount }
459
+ )
460
+ );
461
+ }
462
+ if (estimateDerivedPhysicsInputBytes({ ...input, shapes, constraints, seams }) > DERIVED_PHYSICS_LIMITS.maxCandidateBytes) {
463
+ return err(
464
+ new DerivedPhysicsError(
465
+ "derived-candidate-budget-exceeded",
466
+ `one candidate stages at most ${DERIVED_PHYSICS_LIMITS.maxCandidateBytes} bytes`,
467
+ "reduce cells and constraint metadata before preparing the candidate",
468
+ { entity: input.entity }
469
+ )
470
+ );
471
+ }
472
+ return ok(
473
+ Object.freeze({
474
+ ...input,
475
+ shapes: Object.freeze(shapes),
476
+ seams: Object.freeze(
477
+ seams.map((seam) => ({ ...seam, offset: [...seam.offset] }))
478
+ ),
479
+ ...mass.value === void 0 ? {} : { massProperties: mass.value },
480
+ ...input.motion === void 0 ? {} : {
481
+ motion: Object.freeze({
482
+ centerOfMass: [...input.motion.centerOfMass],
483
+ linearVelocity: [...input.motion.linearVelocity],
484
+ angularVelocity: [...input.motion.angularVelocity]
485
+ })
486
+ },
487
+ constraints: Object.freeze([...constraints]),
488
+ velocityPolicy: input.velocityPolicy ?? "preserve"
489
+ })
490
+ );
491
+ }
492
+ function estimateDerivedPhysicsInputBytes(input) {
493
+ const shapeBytes = input.shapes.reduce(
494
+ (sum, shape) => sum + (shape.cells instanceof Int32Array ? shape.cells.length / 3 : shape.cells.length) * 32 + 128,
495
+ 0
496
+ );
497
+ const seamBytes = (input.seams?.length ?? 0) * 64;
498
+ const constraintBytes = (input.constraints?.length ?? 0) * 192;
499
+ return shapeBytes + seamBytes + constraintBytes + 256;
500
+ }
88
501
 
89
502
  // src/load-rapier-backend.mjs
90
503
  function loadRapier3DBackend() {
@@ -159,6 +572,6 @@ function physicsPlugin(backend) {
159
572
  }
160
573
  var PhysicsSet = defineSystemSet({ name: "physics" });
161
574
 
162
- export { COLLIDER_SHAPE_CAPSULE, COLLIDER_SHAPE_CUBOID, COLLIDER_SHAPE_SPHERE, CharacterController, Collider, ColliderShapeValue, CollidingEntities, CollisionEvent, PhysicsSet, RIGID_BODY_TYPE_DYNAMIC, RIGID_BODY_TYPE_KINEMATIC, RIGID_BODY_TYPE_STATIC, RigidBody, RigidBodyTypeValue, colliderShapeFromF32, physicsPlugin, registerPhysicsComponents, rigidBodyTypeFromF32 };
575
+ export { COLLIDER_SHAPE_CAPSULE, COLLIDER_SHAPE_CUBOID, COLLIDER_SHAPE_SPHERE, CharacterController, Collider, ColliderShapeValue, CollidingEntities, CollisionEvent, DERIVED_PHYSICS_LIMITS, DerivedPhysicsError, PhysicsSet, RIGID_BODY_TYPE_DYNAMIC, RIGID_BODY_TYPE_KINEMATIC, RIGID_BODY_TYPE_STATIC, RigidBody, RigidBodyTypeValue, cloneDerivedPhysicsInput, colliderShapeFromF32, estimateDerivedPhysicsInputBytes, normalizeVoxelShapeInput, physicsPlugin, preserveCenterOfMassVelocity, registerPhysicsComponents, rigidBodyTypeFromF32, validateMassProperties };
163
576
  //# sourceMappingURL=index.mjs.map
164
577
  //# sourceMappingURL=index.mjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/collision-event.ts","../src/components.ts","../src/load-rapier-backend.mjs","../src/plugin-factory.ts","../src/system-set.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @forgeax/engine-physics — CollisionEvent ECS Event token placeholder.\n//\n// Emitted by physics tick systems during the Writeback phase.\n// Two states only: 'started' (new contact) and 'stopped' (separated).\n// No 'continued' event — use CollidingEntities component for ongoing contacts\n// (plan-strategy D-3).\n\nimport type { Vec3 } from '@forgeax/engine-math';\n\n/**\n * Collision event payload — per-contact-pair event emitted during Writeback.\n *\n * `type: 'started'` — two colliders just began touching.\n * `type: 'stopped'` — two colliders just separated.\n */\nexport interface CollisionEventPayload {\n type: 'started' | 'stopped';\n entityA: number;\n entityB: number;\n contactPoint: Vec3;\n contactNormal: Vec3;\n}\n\n/**\n * CollisionEvent constant — identifies the collision event type.\n * Backend systems push CollisionEventPayload instances into the event queue\n * during the Writeback phase; user systems drain via query.\n *\n * The backing ECS event infrastructure (Event<T> generic + world.drainEvent)\n * is deferred to a future feat. For M1, this is a type-only contract.\n */\nexport const CollisionEvent = '__CollisionEvent__' as const;\n\n/** Type-level identifier for the CollisionEvent event channel. */\nexport type CollisionEvent = typeof CollisionEvent;\n","// @forgeax/engine-physics — ECS Component schemas.\n//\n// RigidBody and Collider are the two user-facing entry points; AI users\n// spawn entities with these components to opt into physics simulation.\n// CollidingEntities is the runtime set-query component for continuous\n// collision status (started/stopped model, no 'continued' event).\n\nimport { type Component, defineComponent, type World } from '@forgeax/engine-ecs';\n\n/**\n * RigidBody motion type — 3-state discriminant mirroring Rapier's\n * Dynamic / Fixed / KinematicPositionBased triplet.\n *\n * `'static'`: infinite mass, never moves (Rapier Fixed).\n * `'dynamic'`: driven by forces, gravity, collisions (Rapier Dynamic).\n * `'kinematic'`: user-controlled position, velocity derived by engine\n * (Rapier KinematicPositionBased).\n */\nexport type RigidBodyType = 'static' | 'dynamic' | 'kinematic';\n\n/**\n * Collider shape discriminant — 3 AI-friendly shape names.\n *\n * `'cuboid'`: box shape defined by half-extents (x, y, z).\n * `'sphere'`: sphere defined by radius.\n * `'capsule'`: capsule defined by half-height + radius.\n *\n * Named `'sphere'` not `'ball'` per plan-strategy D-5: AI users see\n * the familiar geometric term; backend maps to Rapier `ColliderDesc.ball()`.\n */\nexport type ColliderShape = 'cuboid' | 'sphere' | 'capsule';\n\n// ─── D-3: numeric enum constants + narrowing helpers ─────────────────────\n//\n// Aligned with `packages/runtime/src/components/camera.ts:41-53`\n// `cameraProjectionFromF32` pattern. The ECS `enum` field maps to `number`\n// (Uint32Array column); these constants let AI users write\n// `{ type: RigidBodyTypeValue.dynamic }` instead of bare magic numbers,\n// and the narrowing helpers let backends switch cleanly on the string union.\n//\n// Declared BEFORE the RigidBody / Collider components so each component's enum\n// field descriptor can reference the SAME `*Value` map as its `labels`\n// (Derive, don't Duplicate — one object is both the AI-facing const AND the\n// schema-projected label map that `describeComponent` surfaces).\n\n/** Numeric value for static rigid body (Rapier Fixed). */\nexport const RIGID_BODY_TYPE_STATIC = 0;\n/** Numeric value for dynamic rigid body (Rapier Dynamic). */\nexport const RIGID_BODY_TYPE_DYNAMIC = 1;\n/** Numeric value for kinematic rigid body (Rapier KinematicPositionBased). */\nexport const RIGID_BODY_TYPE_KINEMATIC = 2;\n\nexport const RigidBodyTypeValue = {\n static: RIGID_BODY_TYPE_STATIC,\n dynamic: RIGID_BODY_TYPE_DYNAMIC,\n kinematic: RIGID_BODY_TYPE_KINEMATIC,\n} as const;\n\nexport function rigidBodyTypeFromF32(n: number): RigidBodyType {\n if (n === RIGID_BODY_TYPE_DYNAMIC) return 'dynamic';\n if (n === RIGID_BODY_TYPE_KINEMATIC) return 'kinematic';\n return 'static';\n}\n\n/** Numeric value for cuboid collider shape. */\nexport const COLLIDER_SHAPE_CUBOID = 0;\n/** Numeric value for sphere collider shape. */\nexport const COLLIDER_SHAPE_SPHERE = 1;\n/** Numeric value for capsule collider shape. */\nexport const COLLIDER_SHAPE_CAPSULE = 2;\n\nexport const ColliderShapeValue = {\n cuboid: COLLIDER_SHAPE_CUBOID,\n sphere: COLLIDER_SHAPE_SPHERE,\n capsule: COLLIDER_SHAPE_CAPSULE,\n} as const;\n\nexport function colliderShapeFromF32(n: number): ColliderShape {\n if (n === COLLIDER_SHAPE_SPHERE) return 'sphere';\n if (n === COLLIDER_SHAPE_CAPSULE) return 'capsule';\n return 'cuboid';\n}\n\n/**\n * ECS Component: rigid body physics properties.\n *\n * AI user entry point — spawn with `world.spawn(RigidBody({ type: 'dynamic' }))`\n * to opt an entity into physics simulation.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `type` | `RigidBodyType` | `'dynamic'` | Motion type discriminant |\n * | `mass` | `number` | `1.0` | Linear mass (> 0 for dynamic) |\n * | `linearDamping` | `number` | `0.0` | Velocity damping factor [0, 1] |\n * | `angularDamping` | `number` | `0.0` | Angular velocity damping [0, 1] |\n * | `gravityScale` | `number` | `1.0` | Per-body gravity multiplier |\n * | `ccdEnabled` | `boolean` | `false` | Continuous collision detection |\n *\n * `type` declares `labels: RigidBodyTypeValue` so `describeComponent` projects\n * the `static=0 / dynamic=1 / kinematic=2` map through the front door — an AI\n * learns the legal variants from the schema, not from engine source.\n */\nexport const RigidBody = defineComponent('RigidBody', {\n type: { type: 'enum', default: RIGID_BODY_TYPE_DYNAMIC, labels: RigidBodyTypeValue },\n mass: { type: 'f32', default: 1 },\n linearDamping: { type: 'f32', default: 0 },\n angularDamping: { type: 'f32', default: 0 },\n gravityScale: { type: 'f32', default: 1 },\n ccdEnabled: { type: 'bool', default: false },\n});\n\n/**\n * ECS Component: collision geometry.\n *\n * Spawn alongside RigidBody to give an entity a collision shape. Entities\n * with Collider but no RigidBody are treated as static colliders (Rapier\n * native behavior — collider without parent body is fixed): `physicsSyncBackend`\n * synthesizes an implicit static body for them, so a bare-Collider floor/wall is\n * simulated as immovable level geometry — the natural way to author static\n * scenery without a redundant `RigidBody{static}`.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `shape` | `ColliderShape` | — | Shape discriminant |\n * | `halfExtents` | `[number, number, number]` | `[0.5, 0.5, 0.5]` | Cuboid half-width/height/depth |\n * | `radius` | `number` | `0.5` | Sphere / capsule radius |\n * | `halfHeight` | `number` | `0.5` | Capsule half-height |\n * | `friction` | `number` | `0.5` | Coulomb friction coefficient |\n * | `restitution` | `number` | `0.0` | Elasticity (1.0 = perfect bounce) |\n * | `density` | `number` | `1.0` | Mass density (alternative to mass) |\n * | `isSensor` | `bool` | `false` | Sensor mode (detect, no physical response) |\n * | `collisionGroups` | `u32` | `0x0001_FFFF` | 32-bit packed membership/filter |\n * | `solverGroups` | `u32` | `0xFFFF_FFFF` | 32-bit packed constraint groups |\n */\nexport const Collider = defineComponent('Collider', {\n shape: { type: 'enum', default: COLLIDER_SHAPE_CUBOID, labels: ColliderShapeValue },\n // feat-20260709 M4: cuboid half-extents collapsed from 3 per-axis scalar\n // columns into one inline array<f32,3> column. Explicit layer-2 default\n // (the array layer-3 fallback is all-zero, which would give a degenerate\n // zero-size box). radius/halfHeight stay scalar (OOS-1: independent\n // sphere/capsule params, not part of the cuboid vec).\n halfExtents: { type: 'array<f32, 3>', default: new Float32Array([0.5, 0.5, 0.5]) },\n radius: { type: 'f32', default: 0.5 },\n halfHeight: { type: 'f32', default: 0.5 },\n friction: { type: 'f32', default: 0.5 },\n restitution: { type: 'f32', default: 0 },\n density: { type: 'f32', default: 1 },\n isSensor: { type: 'bool', default: false },\n collisionGroups: { type: 'u32', default: 0x0001_ffff },\n solverGroups: { type: 'u32', default: 0xffff_ffff },\n});\n\n/**\n * ECS Component: kinematic character controller tuning + output state.\n *\n * Spawn alongside a `RigidBody({ type: 'kinematic' })` + `Collider` to opt an\n * entity into collision-aware movement via `PhysicsWorld.moveAndSlide`. The\n * tuning fields are stable character properties; `grounded` is written back by\n * the engine after each `moveAndSlide` (game code reads it, never writes it).\n *\n * All fields are flat scalars in engine units (degrees, world-space distance) —\n * no Rapier types leak through. Slope angles use the `Deg` suffix to make the\n * unit explicit; the backend translates degrees to radians. A single field\n * carries both the on/off switch and the value: `autoStepMaxHeight === 0`\n * disables auto-step, `snapToGroundDist === 0` disables snap-to-ground.\n *\n * 2D and 3D reuse this same component (plan-strategy D-9): every field is a\n * dimension-agnostic scalar, so no separate 2D component is needed.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `offset` | `f32` | `0.01` | Skin thickness, prevents penetration |\n * | `maxSlopeClimbDeg` | `f32` | `45` | Max climbable slope angle (degrees) |\n * | `minSlopeSlideDeg` | `f32` | `30` | Slope angle past which sliding starts (degrees) |\n * | `autoStepMaxHeight` | `f32` | `0.3` | Max auto-step height (0 = off) |\n * | `autoStepMinWidth` | `f32` | `0.2` | Min step width to be steppable |\n * | `snapToGroundDist` | `f32` | `0.2` | Downhill ground-snap distance (0 = off) |\n * | `grounded` | `bool` | `false` | Engine-written: grounded after last move |\n */\nexport const CharacterController = defineComponent('CharacterController', {\n offset: { type: 'f32', default: 0.01 },\n maxSlopeClimbDeg: { type: 'f32', default: 45 },\n minSlopeSlideDeg: { type: 'f32', default: 30 },\n autoStepMaxHeight: { type: 'f32', default: 0.3 },\n autoStepMinWidth: { type: 'f32', default: 0.2 },\n snapToGroundDist: { type: 'f32', default: 0.2 },\n grounded: { type: 'bool', default: false, transient: true },\n});\n\n/**\n * ECS Component: set of entities currently colliding with the holder entity.\n *\n * Maintained by the physics tick systems — entities are added on collision\n * start (`CollisionEvent.started`) and removed on collision stop\n * (`CollisionEvent.stopped`). AI users query this component to know whose\n * colliders overlap right now without consuming per-frame events.\n *\n * This is the `'continued'` equivalent — no repeated per-frame events,\n * one component query per frame exposes the full active contact set\n * (plan-strategy D-3: CollidingEntities set-query mode).\n */\nexport const CollidingEntities = defineComponent(\n 'CollidingEntities',\n {\n entities: { type: 'array<entity>' },\n },\n { transient: true },\n);\n\nconst PHYSICS_COMPONENTS: readonly Component[] = [\n CharacterController,\n Collider,\n CollidingEntities,\n RigidBody,\n];\n\n/** Install the physics component vocabulary in a World and release its leases on teardown. */\nexport function registerPhysicsComponents(world: World): () => void {\n const leases = PHYSICS_COMPONENTS.map((component) =>\n world.components.register(component).unwrap(),\n );\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n","// Keep literal specifiers visible to bundlers without pulling backend declarations into the physics TypeScript graph.\nexport function loadRapier3DBackend() {\n return import('@forgeax/engine-physics-rapier3d');\n}\n\nexport function loadRapier2DBackend() {\n return import('@forgeax/engine-physics-rapier2d');\n}\n","// @forgeax/engine-physics -- physicsPlugin(backend) factory (M2 / w10, plan-strategy D-5 / D-7).\n//\n// physicsPlugin lives in @forgeax/engine-physics (the interface package, C-9)\n// and accepts an interface->backend dependency inversion: its async apply\n// dynamic-imports the rapier 2D / 3D backend on demand. The backends are\n// declared as devDependencies in this package's package.json (a regular\n// dependency would form a physics <-> rapier cycle since the backends depend on\n// the interface package); the consuming app declares the real runtime dep.\n//\n// charter awareness:\n// P3 explicit failure: WASM load failure rejects plugin activation and the\n// App boundary preserves the cause; it is never a silent skip.\n// P4 consistent abstraction: physicsPlugin shares the same Plugin shape as\n// transform / audio -- one mental model covers every wiring.\n\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { registerPhysicsComponents } from './components';\nimport { PhysicsError } from './errors';\nimport { loadRapier2DBackend, loadRapier3DBackend } from './load-rapier-backend.mjs';\nimport type { PhysicsWorld, PhysicsWorld2D } from './physics-world';\n\ninterface Rapier3DBackendModule {\n loadRapier3D(): Promise<unknown>;\n createRapier3DPhysicsWorld(rapier: unknown): PhysicsWorld;\n registerPhysicsSystems(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\ninterface Rapier2DBackendModule {\n loadRapier2D(): Promise<unknown>;\n createRapier2DPhysicsWorld(rapier: unknown): PhysicsWorld2D;\n registerPhysicsSystems2D(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\n/** Rapier backend selector. */\nexport type PhysicsBackend = 'rapier-2d' | 'rapier-3d';\n\nfunction normalizeWasmLoadFailure(backend: PhysicsBackend, cause: unknown): PhysicsError {\n if (cause instanceof PhysicsError && cause.code === 'wasm-load-failed') return cause;\n const reason = cause instanceof Error ? cause.message : String(cause);\n return new PhysicsError({\n code: 'wasm-load-failed',\n expected: `successful import and WASM initialization for ${backend}`,\n hint: `Rapier backend activation failed: ${reason}`,\n detail: { code: 'wasm-load-failed', reason },\n });\n}\n\ndeclare module '@forgeax/engine-plugin' {\n interface EngineContextServices {\n physics?: PhysicsWorld | PhysicsWorld2D;\n }\n}\n\n/**\n * physicsPlugin(backend) dynamically imports the Rapier backend,\n * loads the WASM module, creates the PhysicsWorld, inserts it as the\n * 'PhysicsWorld' world resource, and registers the three-phase tick systems.\n *\n * The resource is inserted before registering systems so moveAndSlide resolves\n * `PhysicsWorld` on the first tick. Cordis owns rollback if any later effect\n * fails.\n *\n * @param backend 'rapier-2d' or 'rapier-3d'\n */\nexport function physicsPlugin(backend: PhysicsBackend): Plugin {\n return {\n name: 'physics',\n inject: ['world'],\n provide: 'physics',\n async apply(ctx) {\n const world = ctx.world;\n let physics: PhysicsWorld | PhysicsWorld2D;\n let registerSystems: () => () => void;\n if (backend === 'rapier-3d') {\n let module: Rapier3DBackendModule;\n let rapier: unknown;\n try {\n module = (await loadRapier3DBackend()) as Rapier3DBackendModule;\n rapier = await module.loadRapier3D();\n } catch (cause) {\n throw normalizeWasmLoadFailure(backend, cause);\n }\n if (rapier instanceof PhysicsError) throw normalizeWasmLoadFailure(backend, rapier);\n const { createRapier3DPhysicsWorld, registerPhysicsSystems } = module;\n physics = createRapier3DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems(world);\n } else {\n let module: Rapier2DBackendModule;\n let rapier: unknown;\n try {\n module = (await loadRapier2DBackend()) as Rapier2DBackendModule;\n rapier = await module.loadRapier2D();\n } catch (cause) {\n throw normalizeWasmLoadFailure(backend, cause);\n }\n if (rapier instanceof PhysicsError) throw normalizeWasmLoadFailure(backend, rapier);\n const { createRapier2DPhysicsWorld, registerPhysicsSystems2D } = module;\n physics = createRapier2DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems2D(world);\n }\n ctx.effect(() => registerPhysicsComponents(world), 'physics/components');\n ctx.effect(() => {\n world.insertResource('PhysicsWorld', physics);\n return () => {\n world.removeResource('PhysicsWorld');\n physics.dispose();\n };\n }, 'physics/resource');\n ctx.effect(() => {\n const unregister = registerSystems();\n return () => unregister();\n }, 'physics/systems');\n ctx.provide('physics', physics);\n },\n };\n}\n","import { defineSystemSet } from '@forgeax/engine-ecs';\n\nexport const PhysicsSet = defineSystemSet({ name: 'physics' });\n"]}
1
+ {"version":3,"sources":["../src/collision-event.ts","../src/components.ts","../src/derived-physics.ts","../src/load-rapier-backend.mjs","../src/plugin-factory.ts","../src/system-set.ts"],"names":[],"mappings":"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","file":"index.mjs","sourcesContent":["// @forgeax/engine-physics — CollisionEvent ECS Event token placeholder.\n//\n// Emitted by physics tick systems during the Writeback phase.\n// Two states only: 'started' (new contact) and 'stopped' (separated).\n// No 'continued' event — use CollidingEntities component for ongoing contacts\n// (plan-strategy D-3).\n\nimport type { Vec3 } from '@forgeax/engine-math';\n\n/**\n * Collision event payload — per-contact-pair event emitted during Writeback.\n *\n * `type: 'started'` — two colliders just began touching.\n * `type: 'stopped'` — two colliders just separated.\n */\nexport interface CollisionEventPayload {\n type: 'started' | 'stopped';\n entityA: number;\n entityB: number;\n contactPoint: Vec3;\n contactNormal: Vec3;\n}\n\n/**\n * CollisionEvent constant — identifies the collision event type.\n * Backend systems push CollisionEventPayload instances into the event queue\n * during the Writeback phase; user systems drain via query.\n *\n * The backing ECS event infrastructure (Event<T> generic + world.drainEvent)\n * is deferred to a future feat. For M1, this is a type-only contract.\n */\nexport const CollisionEvent = '__CollisionEvent__' as const;\n\n/** Type-level identifier for the CollisionEvent event channel. */\nexport type CollisionEvent = typeof CollisionEvent;\n","// @forgeax/engine-physics — ECS Component schemas.\n//\n// RigidBody and Collider are the two user-facing entry points; AI users\n// spawn entities with these components to opt into physics simulation.\n// CollidingEntities is the runtime set-query component for continuous\n// collision status (started/stopped model, no 'continued' event).\n\nimport { type Component, defineComponent, type World } from '@forgeax/engine-ecs';\n\n/**\n * RigidBody motion type — 3-state discriminant mirroring Rapier's\n * Dynamic / Fixed / KinematicPositionBased triplet.\n *\n * `'static'`: infinite mass, never moves (Rapier Fixed).\n * `'dynamic'`: driven by forces, gravity, collisions (Rapier Dynamic).\n * `'kinematic'`: user-controlled position, velocity derived by engine\n * (Rapier KinematicPositionBased).\n */\nexport type RigidBodyType = 'static' | 'dynamic' | 'kinematic';\n\n/**\n * Collider shape discriminant — 3 AI-friendly shape names.\n *\n * `'cuboid'`: box shape defined by half-extents (x, y, z).\n * `'sphere'`: sphere defined by radius.\n * `'capsule'`: capsule defined by half-height + radius.\n *\n * Named `'sphere'` not `'ball'` per plan-strategy D-5: AI users see\n * the familiar geometric term; backend maps to Rapier `ColliderDesc.ball()`.\n */\nexport type ColliderShape = 'cuboid' | 'sphere' | 'capsule';\n\n// ─── D-3: numeric enum constants + narrowing helpers ─────────────────────\n//\n// Aligned with `packages/runtime/src/components/camera.ts:41-53`\n// `cameraProjectionFromF32` pattern. The ECS `enum` field maps to `number`\n// (Uint32Array column); these constants let AI users write\n// `{ type: RigidBodyTypeValue.dynamic }` instead of bare magic numbers,\n// and the narrowing helpers let backends switch cleanly on the string union.\n//\n// Declared BEFORE the RigidBody / Collider components so each component's enum\n// field descriptor can reference the SAME `*Value` map as its `labels`\n// (Derive, don't Duplicate — one object is both the AI-facing const AND the\n// schema-projected label map that `describeComponent` surfaces).\n\n/** Numeric value for static rigid body (Rapier Fixed). */\nexport const RIGID_BODY_TYPE_STATIC = 0;\n/** Numeric value for dynamic rigid body (Rapier Dynamic). */\nexport const RIGID_BODY_TYPE_DYNAMIC = 1;\n/** Numeric value for kinematic rigid body (Rapier KinematicPositionBased). */\nexport const RIGID_BODY_TYPE_KINEMATIC = 2;\n\nexport const RigidBodyTypeValue = {\n static: RIGID_BODY_TYPE_STATIC,\n dynamic: RIGID_BODY_TYPE_DYNAMIC,\n kinematic: RIGID_BODY_TYPE_KINEMATIC,\n} as const;\n\nexport function rigidBodyTypeFromF32(n: number): RigidBodyType {\n if (n === RIGID_BODY_TYPE_DYNAMIC) return 'dynamic';\n if (n === RIGID_BODY_TYPE_KINEMATIC) return 'kinematic';\n return 'static';\n}\n\n/** Numeric value for cuboid collider shape. */\nexport const COLLIDER_SHAPE_CUBOID = 0;\n/** Numeric value for sphere collider shape. */\nexport const COLLIDER_SHAPE_SPHERE = 1;\n/** Numeric value for capsule collider shape. */\nexport const COLLIDER_SHAPE_CAPSULE = 2;\n\nexport const ColliderShapeValue = {\n cuboid: COLLIDER_SHAPE_CUBOID,\n sphere: COLLIDER_SHAPE_SPHERE,\n capsule: COLLIDER_SHAPE_CAPSULE,\n} as const;\n\nexport function colliderShapeFromF32(n: number): ColliderShape {\n if (n === COLLIDER_SHAPE_SPHERE) return 'sphere';\n if (n === COLLIDER_SHAPE_CAPSULE) return 'capsule';\n return 'cuboid';\n}\n\n/**\n * ECS Component: rigid body physics properties.\n *\n * AI user entry point — spawn with `world.spawn(RigidBody({ type: 'dynamic' }))`\n * to opt an entity into physics simulation.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `type` | `RigidBodyType` | `'dynamic'` | Motion type discriminant |\n * | `mass` | `number` | `1.0` | Linear mass (> 0 for dynamic) |\n * | `linearDamping` | `number` | `0.0` | Velocity damping factor [0, 1] |\n * | `angularDamping` | `number` | `0.0` | Angular velocity damping [0, 1] |\n * | `gravityScale` | `number` | `1.0` | Per-body gravity multiplier |\n * | `ccdEnabled` | `boolean` | `false` | Continuous collision detection |\n *\n * `type` declares `labels: RigidBodyTypeValue` so `describeComponent` projects\n * the `static=0 / dynamic=1 / kinematic=2` map through the front door — an AI\n * learns the legal variants from the schema, not from engine source.\n */\nexport const RigidBody = defineComponent('RigidBody', {\n type: { type: 'enum', default: RIGID_BODY_TYPE_DYNAMIC, labels: RigidBodyTypeValue },\n mass: { type: 'f32', default: 1 },\n linearDamping: { type: 'f32', default: 0 },\n angularDamping: { type: 'f32', default: 0 },\n gravityScale: { type: 'f32', default: 1 },\n ccdEnabled: { type: 'bool', default: false },\n});\n\n/**\n * ECS Component: collision geometry.\n *\n * Spawn alongside RigidBody to give an entity a collision shape. Entities\n * with Collider but no RigidBody are treated as static colliders (Rapier\n * native behavior — collider without parent body is fixed): `physicsSyncBackend`\n * synthesizes an implicit static body for them, so a bare-Collider floor/wall is\n * simulated as immovable level geometry — the natural way to author static\n * scenery without a redundant `RigidBody{static}`.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `shape` | `ColliderShape` | — | Shape discriminant |\n * | `halfExtents` | `[number, number, number]` | `[0.5, 0.5, 0.5]` | Cuboid half-width/height/depth |\n * | `radius` | `number` | `0.5` | Sphere / capsule radius |\n * | `halfHeight` | `number` | `0.5` | Capsule half-height |\n * | `friction` | `number` | `0.5` | Coulomb friction coefficient |\n * | `restitution` | `number` | `0.0` | Elasticity (1.0 = perfect bounce) |\n * | `density` | `number` | `1.0` | Mass density (alternative to mass) |\n * | `isSensor` | `bool` | `false` | Sensor mode (detect, no physical response) |\n * | `collisionGroups` | `u32` | `0x0001_FFFF` | 32-bit packed membership/filter |\n * | `solverGroups` | `u32` | `0xFFFF_FFFF` | 32-bit packed constraint groups |\n */\nexport const Collider = defineComponent('Collider', {\n shape: { type: 'enum', default: COLLIDER_SHAPE_CUBOID, labels: ColliderShapeValue },\n // feat-20260709 M4: cuboid half-extents collapsed from 3 per-axis scalar\n // columns into one inline array<f32,3> column. Explicit layer-2 default\n // (the array layer-3 fallback is all-zero, which would give a degenerate\n // zero-size box). radius/halfHeight stay scalar (OOS-1: independent\n // sphere/capsule params, not part of the cuboid vec).\n halfExtents: { type: 'array<f32, 3>', default: new Float32Array([0.5, 0.5, 0.5]) },\n radius: { type: 'f32', default: 0.5 },\n halfHeight: { type: 'f32', default: 0.5 },\n friction: { type: 'f32', default: 0.5 },\n restitution: { type: 'f32', default: 0 },\n density: { type: 'f32', default: 1 },\n isSensor: { type: 'bool', default: false },\n collisionGroups: { type: 'u32', default: 0x0001_ffff },\n solverGroups: { type: 'u32', default: 0xffff_ffff },\n});\n\n/**\n * ECS Component: kinematic character controller tuning + output state.\n *\n * Spawn alongside a `RigidBody({ type: 'kinematic' })` + `Collider` to opt an\n * entity into collision-aware movement via `PhysicsWorld.moveAndSlide`. The\n * tuning fields are stable character properties; `grounded` is written back by\n * the engine after each `moveAndSlide` (game code reads it, never writes it).\n *\n * All fields are flat scalars in engine units (degrees, world-space distance) —\n * no Rapier types leak through. Slope angles use the `Deg` suffix to make the\n * unit explicit; the backend translates degrees to radians. A single field\n * carries both the on/off switch and the value: `autoStepMaxHeight === 0`\n * disables auto-step, `snapToGroundDist === 0` disables snap-to-ground.\n *\n * 2D and 3D reuse this same component (plan-strategy D-9): every field is a\n * dimension-agnostic scalar, so no separate 2D component is needed.\n *\n * | Field | Type | Default | Purpose |\n * |:--|:--|:--|:--|\n * | `offset` | `f32` | `0.01` | Skin thickness, prevents penetration |\n * | `maxSlopeClimbDeg` | `f32` | `45` | Max climbable slope angle (degrees) |\n * | `minSlopeSlideDeg` | `f32` | `30` | Slope angle past which sliding starts (degrees) |\n * | `autoStepMaxHeight` | `f32` | `0.3` | Max auto-step height (0 = off) |\n * | `autoStepMinWidth` | `f32` | `0.2` | Min step width to be steppable |\n * | `snapToGroundDist` | `f32` | `0.2` | Downhill ground-snap distance (0 = off) |\n * | `grounded` | `bool` | `false` | Engine-written: grounded after last move |\n */\nexport const CharacterController = defineComponent('CharacterController', {\n offset: { type: 'f32', default: 0.01 },\n maxSlopeClimbDeg: { type: 'f32', default: 45 },\n minSlopeSlideDeg: { type: 'f32', default: 30 },\n autoStepMaxHeight: { type: 'f32', default: 0.3 },\n autoStepMinWidth: { type: 'f32', default: 0.2 },\n snapToGroundDist: { type: 'f32', default: 0.2 },\n grounded: { type: 'bool', default: false, transient: true },\n});\n\n/**\n * ECS Component: set of entities currently colliding with the holder entity.\n *\n * Maintained by the physics tick systems — entities are added on collision\n * start (`CollisionEvent.started`) and removed on collision stop\n * (`CollisionEvent.stopped`). AI users query this component to know whose\n * colliders overlap right now without consuming per-frame events.\n *\n * This is the `'continued'` equivalent — no repeated per-frame events,\n * one component query per frame exposes the full active contact set\n * (plan-strategy D-3: CollidingEntities set-query mode).\n */\nexport const CollidingEntities = defineComponent(\n 'CollidingEntities',\n {\n entities: { type: 'array<entity>' },\n },\n { transient: true },\n);\n\nconst PHYSICS_COMPONENTS: readonly Component[] = [\n CharacterController,\n Collider,\n CollidingEntities,\n RigidBody,\n];\n\n/** Install the physics component vocabulary in a World and release its leases on teardown. */\nexport function registerPhysicsComponents(world: World): () => void {\n const leases = PHYSICS_COMPONENTS.map((component) =>\n world.components.register(component).unwrap(),\n );\n return () => {\n for (let index = leases.length - 1; index >= 0; index -= 1) leases[index]?.dispose();\n };\n}\n","import { err, ok, type Result } from '@forgeax/engine-types';\n\n/** A portable integer grid coordinate. */\nexport type VoxelCell = readonly [number, number, number];\n\n/** A portable quaternion used by derived-physics inputs. */\nexport type PhysicsQuaternion = readonly [number, number, number, number];\n\n/** A portable three-component vector used by derived-physics inputs. */\nexport type PhysicsVector = readonly [number, number, number];\n\n/**\n * One local voxel shape. `cells` contains contiguous x/y/z triples. The\n * backend copies it while preparing a candidate, so callers may reuse their\n * scratch buffer after the call returns.\n */\nexport interface VoxelShapeInput {\n readonly id: string;\n readonly revision: number;\n readonly cells: Int32Array | readonly VoxelCell[];\n readonly voxelSize: PhysicsVector;\n readonly origin?: PhysicsVector;\n readonly rotation?: PhysicsQuaternion;\n readonly friction?: number;\n readonly restitution?: number;\n readonly density?: number;\n readonly isSensor?: boolean;\n readonly collisionGroups?: number;\n readonly solverGroups?: number;\n}\n\n/** Stable producer identity and revision for one constraint endpoint. */\nexport interface PhysicsConstraintBodyDependency {\n readonly sourceKey: string;\n readonly revision: number;\n}\n\n/** Explicit or density-derived rigid-body mass properties. */\nexport type PhysicsMassProperties =\n | {\n readonly mode: 'automatic';\n readonly density?: number;\n }\n | {\n readonly mode: 'explicit';\n readonly mass: number;\n readonly centerOfMass: PhysicsVector;\n readonly principalInertia: PhysicsVector;\n readonly principalInertiaLocalFrame?: PhysicsQuaternion;\n };\n\n/** How a topology replacement carries the committed body's motion. */\nexport type PhysicsVelocityPolicy = 'preserve' | 'reset';\n\n/**\n * Consumer-visible movement state for one committed derived body.\n *\n * `centerOfMass` is world-space (the same frame as the body's transform),\n * while both velocity vectors are world-space. Keeping this as one POD\n * value makes snapshot/recovery carry the complete motion boundary instead\n * of exposing Rapier objects or asking consumers to infer velocity from\n * successive transforms.\n */\nexport interface DerivedPhysicsMotion {\n readonly centerOfMass: PhysicsVector;\n readonly linearVelocity: PhysicsVector;\n readonly angularVelocity: PhysicsVector;\n}\n\n/** Stable, consumer-owned constraint input. */\nexport type PhysicsConstraintInput =\n | {\n readonly id: string;\n readonly revision: number;\n readonly kind: 'spring';\n readonly bodyA: number;\n readonly bodyB: number;\n readonly bodyASource: PhysicsConstraintBodyDependency;\n readonly bodyBSource: PhysicsConstraintBodyDependency;\n readonly anchorA: PhysicsVector;\n readonly anchorB: PhysicsVector;\n readonly restLength: number;\n readonly stiffness: number;\n readonly damping: number;\n }\n | {\n readonly id: string;\n readonly revision: number;\n readonly kind: 'hinge';\n readonly bodyA: number;\n readonly bodyB: number;\n readonly bodyASource: PhysicsConstraintBodyDependency;\n readonly bodyBSource: PhysicsConstraintBodyDependency;\n readonly anchorA: PhysicsVector;\n readonly anchorB: PhysicsVector;\n readonly axis: PhysicsVector;\n readonly limits?: readonly [number, number];\n };\n\n/** All data needed to prepare one body's derived shape replacement. */\nexport interface DerivedPhysicsCandidateInput {\n readonly entity: number;\n readonly revision: number;\n readonly sourceKey: string;\n readonly shapes: readonly VoxelShapeInput[];\n readonly seams?: readonly DerivedShapeSeamInput[];\n readonly bodyType?: 'static' | 'dynamic' | 'kinematic';\n readonly massProperties?: PhysicsMassProperties;\n readonly velocityPolicy?: PhysicsVelocityPolicy;\n /** Optional committed motion to restore after native mass admission. */\n readonly motion?: DerivedPhysicsMotion;\n readonly constraints?: readonly PhysicsConstraintInput[];\n /** Optional World identity; it is checked when the backend is ECS-bound. */\n readonly worldIdentity?: object;\n}\n\n/** A same-body integer-grid seam maintained by the Rapier voxel backend. */\nexport interface DerivedShapeSeamInput {\n readonly shapeA: string;\n readonly shapeB: string;\n /** Grid-space origin of B relative to A; all components must be integers. */\n readonly offset: PhysicsVector;\n}\n\n/** Candidate lifecycle visible to a consumer. */\nexport type DerivedPhysicsCandidateState =\n | 'ready'\n | 'queued'\n | 'published'\n | 'cancelled'\n | 'invalidated'\n | 'failed';\n\n/** Opaque-enough prepared candidate. Native handles never cross this type. */\nexport interface DerivedPhysicsCandidate {\n readonly candidateId: string;\n readonly generation: number;\n /** Per-PhysicsWorld owner identity; candidate IDs are not global. */\n readonly owner: object;\n readonly input: Readonly<DerivedPhysicsCandidateInput>;\n readonly state: DerivedPhysicsCandidateState;\n}\n\n/** Stable receipt emitted when a candidate becomes the committed shape set. */\nexport interface DerivedPhysicsPublication {\n readonly candidateId: string;\n readonly entity: number;\n readonly revision: number;\n readonly fixedStep: number;\n readonly shapeIds: readonly string[];\n readonly generation: number;\n}\n\n/** Structured admission failure that leaves the prior publication queryable. */\nexport interface DerivedPhysicsFailure {\n readonly candidateId: string;\n readonly entity: number;\n readonly revision: number;\n readonly fixedStep: number;\n readonly error: DerivedPhysicsError;\n readonly recovery: 'old-state-retained' | 'rebuild-required';\n}\n\n/** Public projection of an active local shape. */\nexport interface DerivedShapeState {\n readonly id: string;\n readonly revision: number;\n readonly entity: number;\n readonly voxelSize: PhysicsVector;\n readonly origin: PhysicsVector;\n readonly rotation: PhysicsQuaternion;\n readonly generation: number;\n}\n\n/** Real backend contact observation, including stable shape identity when known. */\nexport interface PhysicsContactObservation {\n readonly phase: 'started' | 'stopped';\n readonly fixedStep: number;\n readonly entityA: number;\n readonly entityB: number;\n readonly shapeA?: string;\n readonly shapeB?: string;\n readonly point?: PhysicsVector;\n readonly normal?: PhysicsVector;\n}\n\n/** Portable recovery input produced from a committed derived state. */\nexport interface DerivedPhysicsSnapshot {\n readonly generation: number;\n readonly fixedStep: number;\n readonly bodies: readonly {\n readonly entity: number;\n readonly revision: number;\n readonly sourceKey: string;\n readonly shapes: readonly VoxelShapeInput[];\n readonly seams?: readonly DerivedShapeSeamInput[];\n readonly bodyType?: 'static' | 'dynamic' | 'kinematic';\n readonly massProperties?: PhysicsMassProperties;\n readonly velocityPolicy?: PhysicsVelocityPolicy;\n /** Committed world-space COM and velocities at capture time. */\n readonly motion?: DerivedPhysicsMotion;\n readonly constraints: readonly PhysicsConstraintInput[];\n }[];\n}\n\nexport type DerivedPhysicsErrorCode =\n | 'derived-physics-disposed'\n | 'derived-body-not-found'\n | 'derived-world-mismatch'\n | 'derived-candidate-not-found'\n | 'derived-candidate-stale'\n | 'derived-candidate-pending'\n | 'derived-candidate-cancelled'\n | 'derived-candidate-invalid'\n | 'derived-candidate-budget-exceeded'\n | 'derived-shape-invalid'\n | 'derived-shape-duplicate'\n | 'derived-seam-invalid'\n | 'derived-mass-invalid'\n | 'derived-constraint-invalid'\n | 'derived-constraint-not-found'\n | 'derived-constraint-stale'\n | 'derived-backend-failed'\n | 'derived-recovery-invalid';\n\n/** Bounded derived-physics preparation envelope. */\nexport const DERIVED_PHYSICS_LIMITS = Object.freeze({\n maxCandidates: 32,\n maxShapesPerCandidate: 64,\n maxConstraintsPerCandidate: 64,\n maxCellsPerCandidate: 262_144,\n maxCandidateBytes: 8 * 1024 * 1024,\n});\n\nexport interface DerivedPhysicsErrorDetail {\n readonly code: DerivedPhysicsErrorCode;\n readonly entity?: number;\n readonly candidateId?: string;\n readonly shapeId?: string;\n readonly constraintId?: string;\n readonly expected?: string;\n readonly actual?: unknown;\n readonly reason?: string;\n}\n\n/** Closed, structured failure for the public derived-physics surface. */\nexport class DerivedPhysicsError extends Error {\n readonly code: DerivedPhysicsErrorCode;\n readonly expected: string;\n readonly hint: string;\n readonly detail: DerivedPhysicsErrorDetail;\n\n constructor(\n code: DerivedPhysicsErrorCode,\n expected: string,\n hint: string,\n detail: Omit<DerivedPhysicsErrorDetail, 'code'> = {},\n ) {\n super(`${code}: ${expected}`);\n this.name = 'DerivedPhysicsError';\n this.code = code;\n this.expected = expected;\n this.hint = hint;\n this.detail = Object.freeze({ code, ...detail });\n }\n}\n\nconst ID_RE = /\\S/;\n\nfunction finite(value: number): boolean {\n return Number.isFinite(value);\n}\n\nfunction vectorFinite(vector: readonly number[], length: number): boolean {\n return vector.length === length && vector.every(finite);\n}\n\n/** Rotate a local origin delta into the shared grid frame used by a seam. */\nfunction rotateVectorByQuaternion(\n vector: readonly [number, number, number],\n rotation: readonly [number, number, number, number],\n): [number, number, number] {\n const [x, y, z] = vector;\n const [qx, qy, qz, qw] = rotation;\n const tx = 2 * (qy * z - qz * y);\n const ty = 2 * (qz * x - qx * z);\n const tz = 2 * (qx * y - qy * x);\n return [\n x + qw * tx + qy * tz - qz * ty,\n y + qw * ty + qz * tx - qx * tz,\n z + qw * tz + qx * ty - qy * tx,\n ];\n}\n\nfunction normalizedQuaternion(rotation: PhysicsQuaternion): PhysicsQuaternion | undefined {\n if (!vectorFinite(rotation, 4)) return undefined;\n const length = Math.hypot(rotation[0], rotation[1], rotation[2], rotation[3]);\n if (!finite(length) || length < 1e-6) return undefined;\n return [rotation[0] / length, rotation[1] / length, rotation[2] / length, rotation[3] / length];\n}\n\nfunction copyCells(cells: Int32Array | readonly VoxelCell[]): Int32Array | undefined {\n if (cells instanceof Int32Array) return new Int32Array(cells);\n const result = new Int32Array(cells.length * 3);\n for (let index = 0; index < cells.length; index += 1) {\n const cell = cells[index];\n if (cell === undefined || cell.length !== 3 || cell.some((value) => !Number.isInteger(value))) {\n return undefined;\n }\n result[index * 3] = cell[0] ?? 0;\n result[index * 3 + 1] = cell[1] ?? 0;\n result[index * 3 + 2] = cell[2] ?? 0;\n }\n return result;\n}\n\n/** Validate and snapshot one voxel input before native resources are created. */\nexport function normalizeVoxelShapeInput(input: VoxelShapeInput): Result<\n VoxelShapeInput & {\n readonly cells: Int32Array;\n readonly origin: PhysicsVector;\n readonly rotation: PhysicsQuaternion;\n },\n DerivedPhysicsError\n> {\n if (typeof input.id !== 'string' || !ID_RE.test(input.id)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel shape id is a non-empty stable string',\n 'provide a stable shape identity from the consumer state',\n { shapeId: input.id },\n ),\n );\n }\n if (!Number.isInteger(input.revision) || input.revision < 0) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel shape revision is a non-negative integer',\n 'increment the shape revision when its cells or transform changes',\n { shapeId: input.id, actual: input.revision },\n ),\n );\n }\n const cells = copyCells(input.cells);\n if (cells === undefined || cells.length === 0 || cells.length % 3 !== 0) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel cells contain at least one complete integer x/y/z triple',\n 'supply a non-empty Int32Array or cell tuple list',\n { shapeId: input.id, actual: cells?.length },\n ),\n );\n }\n const voxelSize = input.voxelSize;\n if (!vectorFinite(voxelSize, 3) || voxelSize.some((value) => value <= 0)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxelSize contains finite positive components',\n 'choose a finite positive voxel size for every axis',\n { shapeId: input.id, actual: voxelSize },\n ),\n );\n }\n for (const value of cells) {\n if (!Number.isInteger(value)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel coordinates are integers',\n 'quantize cells before submitting a physics candidate',\n { shapeId: input.id, actual: value },\n ),\n );\n }\n }\n const origin = input.origin ?? [0, 0, 0];\n if (!vectorFinite(origin, 3)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel origin contains finite coordinates',\n 'supply a finite local origin',\n { shapeId: input.id, actual: origin },\n ),\n );\n }\n const rotation = normalizedQuaternion(input.rotation ?? [0, 0, 0, 1]);\n if (rotation === undefined) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n 'voxel rotation is a finite non-degenerate quaternion',\n 'normalize the local voxel orientation before submitting it',\n { shapeId: input.id, actual: input.rotation },\n ),\n );\n }\n for (const [name, value] of [\n ['friction', input.friction],\n ['restitution', input.restitution],\n ['density', input.density],\n ] as const) {\n if (value !== undefined && (!finite(value) || value < 0)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-invalid',\n `${name} is finite and non-negative`,\n `repair the ${name} input before native preparation`,\n { shapeId: input.id, actual: value },\n ),\n );\n }\n }\n return ok(\n Object.freeze({\n ...input,\n cells,\n voxelSize: [voxelSize[0], voxelSize[1], voxelSize[2]] as PhysicsVector,\n origin: [origin[0], origin[1], origin[2]] as PhysicsVector,\n rotation,\n }),\n );\n}\n\n/** Validate explicit mass/inertia values before touching the backend. */\nexport function validateMassProperties(\n properties: PhysicsMassProperties | undefined,\n): Result<PhysicsMassProperties | undefined, DerivedPhysicsError> {\n if (properties === undefined || properties.mode === 'automatic') {\n if (\n properties?.density !== undefined &&\n (!finite(properties.density) || properties.density <= 0)\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-mass-invalid',\n 'automatic density is finite and positive',\n 'omit density to use backend density or provide a positive density',\n { actual: properties.density },\n ),\n );\n }\n return ok(properties);\n }\n if (\n !finite(properties.mass) ||\n properties.mass <= 0 ||\n !vectorFinite(properties.centerOfMass, 3) ||\n !vectorFinite(properties.principalInertia, 3) ||\n properties.principalInertia.some((value) => !finite(value) || value <= 0)\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-mass-invalid',\n 'explicit mass, center of mass, and principal inertia are finite and non-degenerate',\n 'provide positive mass/inertia and a finite center of mass',\n { actual: properties },\n ),\n );\n }\n const frame = normalizedQuaternion(properties.principalInertiaLocalFrame ?? [0, 0, 0, 1]);\n if (frame === undefined) {\n return err(\n new DerivedPhysicsError(\n 'derived-mass-invalid',\n 'principal inertia local frame is a finite non-degenerate quaternion',\n 'normalize the inertia frame before submitting it',\n { actual: properties.principalInertiaLocalFrame },\n ),\n );\n }\n return ok(\n Object.freeze({\n ...properties,\n centerOfMass: [...properties.centerOfMass] as PhysicsVector,\n principalInertia: [...properties.principalInertia] as PhysicsVector,\n principalInertiaLocalFrame: frame,\n }),\n );\n}\n\n/** The required linear velocity correction for a committed COM change. */\nexport function preserveCenterOfMassVelocity(\n linearVelocity: PhysicsVector,\n angularVelocity: PhysicsVector,\n previousWorldCom: PhysicsVector,\n nextWorldCom: PhysicsVector,\n): PhysicsVector {\n const dx = nextWorldCom[0] - previousWorldCom[0];\n const dy = nextWorldCom[1] - previousWorldCom[1];\n const dz = nextWorldCom[2] - previousWorldCom[2];\n return [\n linearVelocity[0] + angularVelocity[1] * dz - angularVelocity[2] * dy,\n linearVelocity[1] + angularVelocity[2] * dx - angularVelocity[0] * dz,\n linearVelocity[2] + angularVelocity[0] * dy - angularVelocity[1] * dx,\n ];\n}\n\n/** Snapshot an input without retaining caller-owned typed-array references. */\nexport function cloneDerivedPhysicsInput(\n input: DerivedPhysicsCandidateInput,\n): Result<DerivedPhysicsCandidateInput, DerivedPhysicsError> {\n if (!Number.isInteger(input.entity) || input.entity < 0) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-invalid',\n 'candidate entity is a non-negative ECS entity value',\n 'submit a live entity from the same World',\n { entity: input.entity },\n ),\n );\n }\n if (!Number.isInteger(input.revision) || input.revision < 0) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-invalid',\n 'candidate revision is a non-negative integer',\n 'advance the consumer topology revision monotonically',\n { entity: input.entity, actual: input.revision },\n ),\n );\n }\n if (typeof input.sourceKey !== 'string' || !ID_RE.test(input.sourceKey)) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-invalid',\n 'candidate sourceKey is a non-empty stable producer identity',\n 'carry the producer sourceKey with every derived body revision',\n { entity: input.entity, actual: input.sourceKey },\n ),\n );\n }\n if (\n input.shapes.length === 0 ||\n input.shapes.length > DERIVED_PHYSICS_LIMITS.maxShapesPerCandidate\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-budget-exceeded',\n `one candidate contains between one and ${DERIVED_PHYSICS_LIMITS.maxShapesPerCandidate} derived shapes`,\n 'split the consumer operation at a body boundary and retry',\n { entity: input.entity, actual: input.shapes.length },\n ),\n );\n }\n const seen = new Set<string>();\n const shapes: VoxelShapeInput[] = [];\n for (const shape of input.shapes) {\n if (seen.has(shape.id)) {\n return err(\n new DerivedPhysicsError(\n 'derived-shape-duplicate',\n 'one candidate has one identity per derived shape',\n 'merge or rename duplicate shape inputs before preparation',\n { entity: input.entity, shapeId: shape.id },\n ),\n );\n }\n seen.add(shape.id);\n const normalized = normalizeVoxelShapeInput(shape);\n if (!normalized.ok) return normalized;\n shapes.push(normalized.value);\n }\n const mass = validateMassProperties(input.massProperties);\n if (!mass.ok) return mass;\n if (\n input.motion !== undefined &&\n (!vectorFinite(input.motion.centerOfMass, 3) ||\n !vectorFinite(input.motion.linearVelocity, 3) ||\n !vectorFinite(input.motion.angularVelocity, 3))\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-invalid',\n 'optional movement state contains finite world-space COM and velocity vectors',\n 'capture or provide three finite components for centerOfMass, linearVelocity, and angularVelocity',\n { entity: input.entity, actual: input.motion },\n ),\n );\n }\n const constraints = input.constraints ?? [];\n if (constraints.length > DERIVED_PHYSICS_LIMITS.maxConstraintsPerCandidate) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-budget-exceeded',\n `one candidate contains at most ${DERIVED_PHYSICS_LIMITS.maxConstraintsPerCandidate} constraint updates`,\n 'submit a bounded constraint set for this body',\n { entity: input.entity, actual: constraints.length },\n ),\n );\n }\n const seams = input.seams ?? [];\n const shapeById = new Map(shapes.map((shape) => [shape.id, shape]));\n for (const seam of seams) {\n const a = shapeById.get(seam.shapeA);\n const b = shapeById.get(seam.shapeB);\n const aRotation = a?.rotation ?? [0, 0, 0, 1];\n const bRotation = b?.rotation ?? [0, 0, 0, 1];\n const aOrigin = a?.origin ?? [0, 0, 0];\n const bOrigin = b?.origin ?? [0, 0, 0];\n const quaternionDot = aRotation.reduce(\n (sum, value, index) => sum + value * (bRotation[index] ?? 0),\n 0,\n );\n const localOriginDelta: [number, number, number] = [\n (bOrigin[0] ?? 0) - (aOrigin[0] ?? 0),\n (bOrigin[1] ?? 0) - (aOrigin[1] ?? 0),\n (bOrigin[2] ?? 0) - (aOrigin[2] ?? 0),\n ];\n // `origin` is expressed in the body's frame while Rapier's seam shift is\n // expressed in shape A's voxel frame. Move the body-local delta through\n // the inverse of A's local-to-body rotation before quantizing it.\n const sharedGridDelta = rotateVectorByQuaternion(localOriginDelta, [\n -aRotation[0],\n -aRotation[1],\n -aRotation[2],\n aRotation[3],\n ]);\n const alignedOrigins =\n a !== undefined &&\n b !== undefined &&\n seam.offset.every(\n (value, index) =>\n Math.abs((sharedGridDelta[index] ?? 0) / (a?.voxelSize[index] ?? 1) - value) <= 1e-5,\n );\n if (\n a === undefined ||\n b === undefined ||\n a.id === b.id ||\n !vectorFinite(seam.offset, 3) ||\n seam.offset.some((value) => !Number.isInteger(value)) ||\n a.voxelSize.some((value, index) => Math.abs(value - (b.voxelSize[index] ?? 0)) > 1e-6) ||\n Math.abs(Math.abs(quaternionDot) - 1) > 1e-5 ||\n !alignedOrigins\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-seam-invalid',\n 'a voxel seam joins same-grid shapes with integer offset in the shared rotated grid frame',\n 'rotate the local origin delta into the shared grid frame and use an integer grid offset',\n { entity: input.entity, shapeId: seam.shapeA },\n ),\n );\n }\n }\n const seenConstraints = new Set<string>();\n for (const constraint of constraints) {\n if (seenConstraints.has(constraint.id)) {\n return err(\n new DerivedPhysicsError(\n 'derived-constraint-invalid',\n 'one candidate contains one update per constraint identity',\n 'merge duplicate constraint updates before preparation',\n { entity: input.entity, constraintId: constraint.id },\n ),\n );\n }\n seenConstraints.add(constraint.id);\n for (const dependency of [constraint.bodyASource, constraint.bodyBSource]) {\n if (\n typeof dependency.sourceKey !== 'string' ||\n !ID_RE.test(dependency.sourceKey) ||\n !Number.isInteger(dependency.revision) ||\n dependency.revision < 0\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-constraint-invalid',\n 'constraint endpoint sourceKey and revision are stable and non-negative',\n 'refresh both endpoint dependencies before preparing the constraint',\n { entity: input.entity, constraintId: constraint.id },\n ),\n );\n }\n }\n }\n const cellCount = shapes.reduce((sum, shape) => sum + shape.cells.length / 3, 0);\n if (cellCount > DERIVED_PHYSICS_LIMITS.maxCellsPerCandidate) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-budget-exceeded',\n `one candidate contains at most ${DERIVED_PHYSICS_LIMITS.maxCellsPerCandidate} cells`,\n 'reduce the voxel input or split it at a body boundary',\n { entity: input.entity, actual: cellCount },\n ),\n );\n }\n if (\n estimateDerivedPhysicsInputBytes({ ...input, shapes, constraints, seams }) >\n DERIVED_PHYSICS_LIMITS.maxCandidateBytes\n ) {\n return err(\n new DerivedPhysicsError(\n 'derived-candidate-budget-exceeded',\n `one candidate stages at most ${DERIVED_PHYSICS_LIMITS.maxCandidateBytes} bytes`,\n 'reduce cells and constraint metadata before preparing the candidate',\n { entity: input.entity },\n ),\n );\n }\n return ok(\n Object.freeze({\n ...input,\n shapes: Object.freeze(shapes),\n seams: Object.freeze(\n seams.map((seam) => ({ ...seam, offset: [...seam.offset] as PhysicsVector })),\n ),\n ...(mass.value === undefined ? {} : { massProperties: mass.value }),\n ...(input.motion === undefined\n ? {}\n : {\n motion: Object.freeze({\n centerOfMass: [...input.motion.centerOfMass] as PhysicsVector,\n linearVelocity: [...input.motion.linearVelocity] as PhysicsVector,\n angularVelocity: [...input.motion.angularVelocity] as PhysicsVector,\n }),\n }),\n constraints: Object.freeze([...constraints]),\n velocityPolicy: input.velocityPolicy ?? 'preserve',\n }),\n );\n}\n\n/** Estimate staged CPU/native input bytes for the bounded admission budget. */\nexport function estimateDerivedPhysicsInputBytes(input: DerivedPhysicsCandidateInput): number {\n const shapeBytes = input.shapes.reduce(\n (sum, shape) =>\n sum +\n (shape.cells instanceof Int32Array ? shape.cells.length / 3 : shape.cells.length) * 32 +\n 128,\n 0,\n );\n const seamBytes = (input.seams?.length ?? 0) * 64;\n const constraintBytes = (input.constraints?.length ?? 0) * 192;\n return shapeBytes + seamBytes + constraintBytes + 256;\n}\n","// Keep literal specifiers visible to bundlers without pulling backend declarations into the physics TypeScript graph.\nexport function loadRapier3DBackend() {\n return import('@forgeax/engine-physics-rapier3d');\n}\n\nexport function loadRapier2DBackend() {\n return import('@forgeax/engine-physics-rapier2d');\n}\n","// @forgeax/engine-physics -- physicsPlugin(backend) factory (M2 / w10, plan-strategy D-5 / D-7).\n//\n// physicsPlugin lives in @forgeax/engine-physics (the interface package, C-9)\n// and accepts an interface->backend dependency inversion: its async apply\n// dynamic-imports the rapier 2D / 3D backend on demand. The backends are\n// optional peerDependencies in this package's package.json (a regular\n// dependency would form a physics <-> rapier cycle since the backends depend on\n// the interface package); the consuming app declares the selected runtime dep.\n//\n// charter awareness:\n// P3 explicit failure: WASM load failure rejects plugin activation and the\n// App boundary preserves the cause; it is never a silent skip.\n// P4 consistent abstraction: physicsPlugin shares the same Plugin shape as\n// transform / audio -- one mental model covers every wiring.\n\nimport type { Plugin } from '@forgeax/engine-plugin';\nimport { registerPhysicsComponents } from './components';\nimport { PhysicsError } from './errors';\nimport { loadRapier2DBackend, loadRapier3DBackend } from './load-rapier-backend.mjs';\nimport type { PhysicsWorld, PhysicsWorld2D } from './physics-world';\n\ninterface Rapier3DBackendModule {\n loadRapier3D(): Promise<unknown>;\n createRapier3DPhysicsWorld(rapier: unknown): PhysicsWorld;\n registerPhysicsSystems(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\ninterface Rapier2DBackendModule {\n loadRapier2D(): Promise<unknown>;\n createRapier2DPhysicsWorld(rapier: unknown): PhysicsWorld2D;\n registerPhysicsSystems2D(world: import('@forgeax/engine-ecs').World): () => void;\n}\n\n/** Rapier backend selector. */\nexport type PhysicsBackend = 'rapier-2d' | 'rapier-3d';\n\nfunction normalizeWasmLoadFailure(backend: PhysicsBackend, cause: unknown): PhysicsError {\n if (cause instanceof PhysicsError && cause.code === 'wasm-load-failed') return cause;\n const reason = cause instanceof Error ? cause.message : String(cause);\n return new PhysicsError({\n code: 'wasm-load-failed',\n expected: `successful import and WASM initialization for ${backend}`,\n hint: `Rapier backend activation failed: ${reason}`,\n detail: { code: 'wasm-load-failed', reason },\n });\n}\n\ndeclare module '@forgeax/engine-plugin' {\n interface EngineContextServices {\n physics?: PhysicsWorld | PhysicsWorld2D;\n }\n}\n\n/**\n * physicsPlugin(backend) dynamically imports the Rapier backend,\n * loads the WASM module, creates the PhysicsWorld, inserts it as the\n * 'PhysicsWorld' world resource, and registers the three-phase tick systems.\n *\n * The resource is inserted before registering systems so moveAndSlide resolves\n * `PhysicsWorld` on the first tick. Cordis owns rollback if any later effect\n * fails.\n *\n * @param backend 'rapier-2d' or 'rapier-3d'\n */\nexport function physicsPlugin(backend: PhysicsBackend): Plugin {\n return {\n name: 'physics',\n inject: ['world'],\n provide: 'physics',\n async apply(ctx) {\n const world = ctx.world;\n let physics: PhysicsWorld | PhysicsWorld2D;\n let registerSystems: () => () => void;\n if (backend === 'rapier-3d') {\n let module: Rapier3DBackendModule;\n let rapier: unknown;\n try {\n module = (await loadRapier3DBackend()) as Rapier3DBackendModule;\n rapier = await module.loadRapier3D();\n } catch (cause) {\n throw normalizeWasmLoadFailure(backend, cause);\n }\n if (rapier instanceof PhysicsError) throw normalizeWasmLoadFailure(backend, rapier);\n const { createRapier3DPhysicsWorld, registerPhysicsSystems } = module;\n physics = createRapier3DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems(world);\n } else {\n let module: Rapier2DBackendModule;\n let rapier: unknown;\n try {\n module = (await loadRapier2DBackend()) as Rapier2DBackendModule;\n rapier = await module.loadRapier2D();\n } catch (cause) {\n throw normalizeWasmLoadFailure(backend, cause);\n }\n if (rapier instanceof PhysicsError) throw normalizeWasmLoadFailure(backend, rapier);\n const { createRapier2DPhysicsWorld, registerPhysicsSystems2D } = module;\n physics = createRapier2DPhysicsWorld(rapier);\n registerSystems = () => registerPhysicsSystems2D(world);\n }\n ctx.effect(() => registerPhysicsComponents(world), 'physics/components');\n ctx.effect(() => {\n world.insertResource('PhysicsWorld', physics);\n return () => {\n world.removeResource('PhysicsWorld');\n physics.dispose();\n };\n }, 'physics/resource');\n ctx.effect(() => {\n const unregister = registerSystems();\n return () => unregister();\n }, 'physics/systems');\n ctx.provide('physics', physics);\n },\n };\n}\n","import { defineSystemSet } from '@forgeax/engine-ecs';\n\nexport const PhysicsSet = defineSystemSet({ name: 'physics' });\n"]}