@ignifx/physics-2d 0.2.0 → 0.3.0

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.d.ts CHANGED
@@ -82,20 +82,9 @@ export declare function defaultPhysics2DSettings(): Physics2DSettings;
82
82
  //#endregion
83
83
  //#region src/material.d.ts
84
84
  /**
85
- * The `ignifx.physicsmaterial` asset, read by the 2D extension.
86
- *
87
- * ## Why the format is the 3D one
88
- *
89
- * `11-2d-toolkit.md` §8 lists a `PhysicsMaterial2D`, and `09-physics.md` §2.4 already defines
90
- * `{ "format": "ignifx.physicsmaterial", "friction": …, "staticFriction": …, "restitution": … }`.
91
- * Inventing a second document for the same two numbers would give artists two files to keep in
92
- * step, so this package reads **the same format and the same `.physicsmaterial.json` extension**,
93
- * ignoring `staticFriction` (Rapier 2D has one friction coefficient, `geometry/collider.d.ts`).
94
- * `@ignifx/physics` and `@ignifx/physics-2d` never run in one world — `IGX-1101` — so the two
95
- * loaders never collide.
96
- *
97
- * The 2D-only knobs, how two surfaces' friction and restitution are **combined**, are fields on the
98
- * collider rather than on the asset, so the document stays byte-identical to the 3D one.
85
+ * Read the shared `.physicsmaterial.json` format, ignoring `staticFriction` because Rapier has one
86
+ * friction coefficient. Combine rules stay on colliders. Only one physics extension runs per world,
87
+ * so the 2D and 3D loaders do not compete.
99
88
  */
100
89
  /**
101
90
  * The asset type name `.physicsmaterial.json` addresses resolve to.
@@ -184,23 +173,10 @@ export declare function parsePhysicsMaterial2D(address: string, document: JsonVa
184
173
  //#endregion
185
174
  //#region src/lite/rapier/world.d.ts
186
175
  /**
187
- * The Rapier 2D adapter (`docs/architecture/11-2d-toolkit.md` §8, ADR-0006). Everything the runtime
188
- * needs from `@dimforge/rapier2d-compat` is a function here, so the rest of `src/` never imports the
189
- * backend (`ignifx/no-lite-outside-adapter`, coding standards §4).
190
- *
191
- * ## Facts measured against `@dimforge/rapier2d-compat@0.20.0` (spike S6.2)
192
- *
193
- * - **The broadphase is only built by `World.step`.** Before the first step, `castRay` returns
194
- * `null` and `KinematicCharacterController.computeColliderMovement` reports the full requested
195
- * motion with no obstacles; a collider created after the last step is invisible until the next
196
- * one. {@link primeWorld} runs a zero-length step, which builds the broadphase without
197
- * integrating — but it is *not* free of side effects on a running simulation (a settled pile
198
- * hashes differently), so the runtime primes only when the collider set changed.
199
- * - **`InteractionGroups` is one 32-bit number**: membership in the high 16 bits, filter in the low
200
- * 16 (`geometry/interaction_groups.d.ts`). Sixteen layers, not thirty-two.
201
- * - **`ActiveCollisionTypes.DEFAULT` excludes non-dynamic pairs**, so a kinematic character never
202
- * reports entering a static sensor. Colliders that want events are given
203
- * `ActiveCollisionTypes.ALL`.
176
+ * Keep Rapier calls inside the adapter (ADR-0006).
177
+ * Queries need a built broadphase. Prime after collider-set changes only: a zero-length step can
178
+ * still affect a running simulation. Interaction groups support sixteen layers; event-enabled
179
+ * colliders include non-dynamic pairs so characters can enter static sensors.
204
180
  */
205
181
  /**
206
182
  * Rapier's physics world.
@@ -265,29 +241,9 @@ type RapierShape2D = {
265
241
  //#endregion
266
242
  //#region src/components/collider.d.ts
267
243
  /**
268
- * The 2D collider contract (`docs/architecture/11-2d-toolkit.md` §8). Every collider shares an
269
- * offset, the trigger flag, a surface, a layer override, and the one-way platform flag, and answers
270
- * one question the body builder asks: which Rapier shapes it stands for.
271
- *
272
- * ## Corrections to §8
273
- *
274
- * - **The material is two fields, not a union.** A schema field has exactly one kind (ADR-0004), so
275
- * {@link Collider2D.material} (an asset reference) and {@link Collider2D.inlineMaterial} (a
276
- * record) are separate; the asset wins, the inline record is the fallback, and
277
- * `physics2d.defaultMaterial` is the last resort. This is the same split `@ignifx/physics` makes.
278
- * - **Combine rules are collider fields.** Rapier combines two surfaces' friction and restitution
279
- * with a per-collider rule (`dynamics/coefficient_combine_rule.d.ts`); the 3D
280
- * `ignifx.physicsmaterial` document has no such field, so the knob lives here rather than
281
- * forking the asset format.
282
- * - **A collider may stand for several Rapier colliders.** `TilemapCollider2D` produces one polygon
283
- * per merged tile run, so {@link Collider2D.collectShapes} fills a list rather than returning one
284
- * shape.
285
- *
286
- * ## Sizes and scale
287
- *
288
- * Sizes are authored in local metres and multiplied by the entity's lossy scale when the shapes are
289
- * built. Rapier cannot rescale a built shape, so a later scale change needs
290
- * {@link Collider2D.rebuild}.
244
+ * Build shapes from local metres multiplied by entity scale; call `rebuild` after changing scale.
245
+ * Material assets take precedence over inline values, then project defaults. Friction and restitution
246
+ * combine rules belong to each collider. A tilemap collider can produce several backend shapes.
291
247
  */
292
248
  /**
293
249
  * How two surfaces' coefficients are combined when they touch, mirroring Rapier's
@@ -390,26 +346,8 @@ export declare abstract class Collider2D extends Component implements ComponentH
390
346
  //#endregion
391
347
  //#region src/events.d.ts
392
348
  /**
393
- * The payloads the five physics callbacks receive in 2D (`docs/architecture/11-2d-toolkit.md` §8,
394
- * `09-physics.md` §4).
395
- *
396
- * ## The callbacks are the 3D names
397
- *
398
- * `11-2d-toolkit.md` §8 writes them as `onCollisionEnter2D`/`onTriggerEnter2D`. The kernel's
399
- * `PhysicsCallbackName` union has five members and no `2D` variants
400
- * (`packages/core/src/lifecycle/callbacks.ts`), and a world uses either `physics()` or
401
- * `physics2d()` — never both, which is what `IGX-1101` enforces. So a 2D game implements
402
- * `onCollisionEnter`, `onCollisionStay`, `onCollisionExit`, `onTriggerEnter`, and `onTriggerExit`,
403
- * and receives the 2D payloads below. §8 is corrected accordingly.
404
- *
405
- * ## Identities are native
406
- *
407
- * Rapier reports **both** colliders of every event (`pipeline/event_queue.d.ts`,
408
- * `drainCollisionEvents`), so `other` is never `null` for a live body and the ADR-0013 waiver that
409
- * 3D physics needs has no counterpart here.
410
- *
411
- * Every payload is **pooled**: the same instances are reused for every event of a step, so a script
412
- * that needs a value after its callback returns must copy it out (coding standards §7).
349
+ * 2D physics uses the same five collision and trigger callback names as 3D, without a `2D` suffix.
350
+ * Rapier supplies both collider identities. Payloads are pooled; copy values needed after a callback.
413
351
  */
414
352
  /**
415
353
  * One contact point of a 2D collision. Pooled with its owning {@link Collision2D}.
@@ -488,24 +426,9 @@ interface CharacterCollision2D {
488
426
  //#endregion
489
427
  //#region src/components/rigidbody.d.ts
490
428
  /**
491
- * The `Rigidbody2D` component (`docs/architecture/11-2d-toolkit.md` §8): what makes an entity's 2D
492
- * colliders a simulated, kinematic, or explicitly static Rapier body.
493
- *
494
- * ## Decisions §8 leaves open
495
- *
496
- * - **`mass` is exact, and `0` means "weigh the colliders".** Rapier derives a body's mass and
497
- * angular inertia from each collider's *density* and area (`geometry/collider.d.ts`,
498
- * `setDensity`), and `setAdditionalMass` adds mass without inertia. So a non-zero `mass` is
499
- * applied by scaling every collider's density until the body weighs exactly that, which keeps the
500
- * inertia distribution the shapes imply; `mass: 0` leaves Rapier's own density of 1 kg/m² alone.
501
- * - **`freezeRotation` is one boolean, not three.** A 2D body has one rotational degree of freedom
502
- * (`dynamics/rigid_body.d.ts`, `lockRotations`).
503
- * - **Damping is per body here, unlike 3D.** Havok has no per-body damping; Rapier does
504
- * (`setLinearDamping`, `setAngularDamping`), so `linearDamping` and `angularDamping` are real
505
- * fields rather than upstream requests.
506
- * - **`collisionEvents` is a three-valued enum**, exactly as in `@ignifx/physics`: a schema field
507
- * has one kind (ADR-0004), and `"auto"` recomputes from `ctx.entityImplements` whenever a
508
- * component joins or leaves the entity.
429
+ * A nonzero mass rescales collider densities while preserving their inertia distribution.
430
+ * Zero keeps the default density of 1 kg/m². Rotation has one freeze flag; damping is per body.
431
+ * Automatic collision events follow callbacks as components join or leave the entity.
509
432
  */
510
433
  /**
511
434
  * How a 2D body moves.
@@ -675,27 +598,9 @@ export declare class Rigidbody2D extends Component implements ComponentHooks {
675
598
  //#endregion
676
599
  //#region src/components/character-controller.d.ts
677
600
  /**
678
- * The `CharacterController2D` component (`docs/architecture/11-2d-toolkit.md` §8): Rapier's
679
- * `KinematicCharacterController` (`control/character_controller.d.ts`) driven from `fixedUpdate`.
680
- *
681
- * ## How it is driven
682
- *
683
- * Call {@link CharacterController2D.move} from `fixedUpdate`. The requested displacements of one
684
- * step are summed and handed to Rapier's `computeColliderMovement` by the step system, so two
685
- * scripts moving the same character compose instead of fighting. Gravity is **not** applied by this
686
- * component: a bare controller is purely kinematic, exactly as the 3D one is, and the toolkit
687
- * controller scripts integrate gravity themselves.
688
- *
689
- * After the step the controller owns the entity's world X and Y. Z and `rotation2D` stay
690
- * user-controlled.
691
- *
692
- * ## The one addition to §8: `shape`
693
- *
694
- * §8 describes the controller as a capsule. Measured against `@dimforge/rapier2d-compat@0.20.0`
695
- * (spike S6.2), Rapier's autostep clears a 0.3 m step with a **box** character and refuses anything
696
- * above about 0.15 m with a capsule of radius 0.2 — so `stepOffset` is only usable with a box.
697
- * {@link CharacterController2D.shape} therefore chooses between the two, defaulting to `"capsule"`
698
- * as §8 says, and the limitation is documented in the skill and in ADR-0006's validation section.
601
+ * Sum `move` displacements during `fixedUpdate`; game scripts supply gravity.
602
+ * After the step the controller owns world X/Y, while Z and rotation remain user-controlled.
603
+ * Box shapes support taller autosteps than capsules; see ADR-0006's validation results.
699
604
  */
700
605
  /**
701
606
  * The collision shape a 2D character controller uses.
@@ -779,6 +684,10 @@ export declare class CharacterController2D extends Component implements Componen
779
684
  /**
780
685
  * Emitted once per obstacle the character hit during a step.
781
686
  *
687
+ * @remarks
688
+ * Trigger colliders are not obstacles — the character walks straight through them — so a sensor
689
+ * never appears here. Listen for `onTriggerEnter`/`onTriggerExit` on the entity's scripts instead.
690
+ *
782
691
  * @returns The signal, created on first access.
783
692
  */
784
693
  get onCollided(): Signal<CharacterCollision2D>;
@@ -970,25 +879,9 @@ export declare class EdgeCollider2D extends Collider2D {
970
879
  //#endregion
971
880
  //#region src/components/tilemap-collider.d.ts
972
881
  /**
973
- * `TilemapCollider2D` (`docs/architecture/11-2d-toolkit.md` §2.5, §8): the merged collision surface
974
- * of a `Tilemap`, turned into Rapier geometry.
975
- *
976
- * ## How it reaches the data
977
- *
978
- * `@ignifx/2d` is an **optional** peer of `@ignifx/physics-2d` (a physics-only 2D game needs no
979
- * sprite toolkit), so this component never imports the toolkit as a value — only the
980
- * `TilemapCollisionData` type. A game, or `@ignifx/2d`'s own tilemap loader, assigns
981
- * {@link TilemapCollider2D.collisionData}; the runtime compares the data's `version` at the start of
982
- * every fixed step and rebuilds the shapes when it changes, which is the same moment
983
- * `Tilemap.onCollisionChanged` fires and needs no signal subscription across the package boundary.
984
- *
985
- * ## Why closed polylines rather than polygons
986
- *
987
- * A merged run of solid tiles is not convex in general — an L of tiles merges into an L — and
988
- * Rapier's polygon shape is a **convex hull** (`geometry/shape.d.ts`, `ConvexPolygon`), which would
989
- * silently fill the notch. A closed `polyline` reproduces the outline exactly and is the shape
990
- * static level geometry wants; the trade-off is that a polyline is infinitely thin, so a body that
991
- * starts inside the tilemap is not pushed out.
882
+ * Read tile collision data through the optional 2D toolkit's type and rebuild when its version changes.
883
+ * Closed polylines preserve concave tile outlines that convex hulls would fill in. They are thin
884
+ * surfaces, so a body starting inside the map is not pushed out.
992
885
  */
993
886
  /**
994
887
  * The collision surface of a tilemap, as one static body's worth of Rapier shapes.
@@ -1446,7 +1339,7 @@ declare class Physics2DRuntime {
1446
1339
  teleportController(component: CharacterController2D, position: Vec2Like): void;
1447
1340
  /**
1448
1341
  * Restores the authoritative pose of every interpolated body, undoing the display pose the
1449
- * `PreRender` system wrote (`09-physics.md` §1).
1342
+ * `Update` system wrote at the top of the previous frame's `Update` (`09-physics.md` §1).
1450
1343
  */
1451
1344
  restorePoses(): void;
1452
1345
  /**
@@ -1596,31 +1489,9 @@ export declare class Physics2DService {
1596
1489
  //#endregion
1597
1490
  //#region src/extension.d.ts
1598
1491
  /**
1599
- * The `@ignifx/physics-2d` extension (`docs/architecture/11-2d-toolkit.md` §8,
1600
- * `04-extensions.md` §1).
1601
- *
1602
- * ## Why `@ignifx/2d` is optional rather than required
1603
- *
1604
- * `00-overview.md` §2 draws `@ignifx/physics-2d` above `@ignifx/2d`, and the layering rule allows
1605
- * the dependency. At runtime the only thing this package needs from the toolkit is the
1606
- * `TilemapCollisionData` **type**, which is erased at build time, so a platformer that draws its own
1607
- * graphics can register `physics2d()` on its own. The manifest therefore declares
1608
- * `requires: []` and `optional: ["@ignifx/2d"]`, and `package.json` makes `@ignifx/2d` an optional
1609
- * peer dependency.
1610
- *
1611
- * ## Why the world is built in `onStart`
1612
- *
1613
- * `register` runs before `createApp` builds the `World` (`packages/core/src/app/app.ts`,
1614
- * `initialize`), and the Rapier world needs the world's layer table; Rapier's WebAssembly module
1615
- * also has to be instantiated, which is asynchronous. So `register` declares everything against a
1616
- * {@link Physics2DHost} holder and `onStart` fills the holder in.
1617
- *
1618
- * ## One physics extension per world
1619
- *
1620
- * `11-2d-toolkit.md` §8: "A world uses either `physics()` or `physics2d()`; registering both throws
1621
- * `IGX-1101`". `@ignifx/physics` publishes its Havok simulation scene through
1622
- * `ExtensionContext.setSimulationScene`, so `app.world.lite.simulationScene !== null` at `onStart`
1623
- * is the test — and it needs no dependency on `@ignifx/physics`, which this package must not have.
1492
+ * The 2D toolkit is optional; physics only needs its tile-collision data type.
1493
+ * Create the Rapier world in `onStart`, once the world's layer table exists and WASM is ready.
1494
+ * Reject a world already using 3D physics with `IGX-1101`.
1624
1495
  */
1625
1496
  /**
1626
1497
  * What `physics2d()` accepts.
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","names":[],"sources":["../src/settings.ts","../src/material.ts","../src/lite/rapier/world.ts","../src/components/collider.ts","../src/events.ts","../src/components/rigidbody.ts","../src/components/character-controller.ts","../src/components/colliders.ts","../src/components/tilemap-collider.ts","../src/errors.ts","../src/queries.ts","../src/runtime/body-record.ts","../src/runtime/runtime.ts","../src/runtime/host.ts","../src/service.ts","../src/extension.ts","../src/schemas.ts"],"mappings":"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"}
1
+ {"version":3,"file":"index.d.ts","names":[],"sources":["../src/settings.ts","../src/material.ts","../src/lite/rapier/world.ts","../src/components/collider.ts","../src/events.ts","../src/components/rigidbody.ts","../src/components/character-controller.ts","../src/components/colliders.ts","../src/components/tilemap-collider.ts","../src/errors.ts","../src/queries.ts","../src/runtime/body-record.ts","../src/runtime/runtime.ts","../src/runtime/host.ts","../src/service.ts","../src/extension.ts","../src/schemas.ts"],"mappings":"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"}
package/dist/index.js CHANGED
@@ -16,24 +16,9 @@ const Physics2DHostKey = createServiceKey("@ignifx/physics-2d:host");
16
16
  //#endregion
17
17
  //#region src/components/rigidbody.ts
18
18
  /**
19
- * The `Rigidbody2D` component (`docs/architecture/11-2d-toolkit.md` §8): what makes an entity's 2D
20
- * colliders a simulated, kinematic, or explicitly static Rapier body.
21
- *
22
- * ## Decisions §8 leaves open
23
- *
24
- * - **`mass` is exact, and `0` means "weigh the colliders".** Rapier derives a body's mass and
25
- * angular inertia from each collider's *density* and area (`geometry/collider.d.ts`,
26
- * `setDensity`), and `setAdditionalMass` adds mass without inertia. So a non-zero `mass` is
27
- * applied by scaling every collider's density until the body weighs exactly that, which keeps the
28
- * inertia distribution the shapes imply; `mass: 0` leaves Rapier's own density of 1 kg/m² alone.
29
- * - **`freezeRotation` is one boolean, not three.** A 2D body has one rotational degree of freedom
30
- * (`dynamics/rigid_body.d.ts`, `lockRotations`).
31
- * - **Damping is per body here, unlike 3D.** Havok has no per-body damping; Rapier does
32
- * (`setLinearDamping`, `setAngularDamping`), so `linearDamping` and `angularDamping` are real
33
- * fields rather than upstream requests.
34
- * - **`collisionEvents` is a three-valued enum**, exactly as in `@ignifx/physics`: a schema field
35
- * has one kind (ADR-0004), and `"auto"` recomputes from `ctx.entityImplements` whenever a
36
- * component joins or leaves the entity.
19
+ * A nonzero mass rescales collider densities while preserving their inertia distribution.
20
+ * Zero keeps the default density of 1 kg/m². Rotation has one freeze flag; damping is per body.
21
+ * Automatic collision events follow callbacks as components join or leave the entity.
37
22
  */
38
23
  /**
39
24
  * How a 2D body moves.
@@ -233,27 +218,9 @@ function rigidbody2DSchema() {
233
218
  //#endregion
234
219
  //#region src/components/character-controller.ts
235
220
  /**
236
- * The `CharacterController2D` component (`docs/architecture/11-2d-toolkit.md` §8): Rapier's
237
- * `KinematicCharacterController` (`control/character_controller.d.ts`) driven from `fixedUpdate`.
238
- *
239
- * ## How it is driven
240
- *
241
- * Call {@link CharacterController2D.move} from `fixedUpdate`. The requested displacements of one
242
- * step are summed and handed to Rapier's `computeColliderMovement` by the step system, so two
243
- * scripts moving the same character compose instead of fighting. Gravity is **not** applied by this
244
- * component: a bare controller is purely kinematic, exactly as the 3D one is, and the toolkit
245
- * controller scripts integrate gravity themselves.
246
- *
247
- * After the step the controller owns the entity's world X and Y. Z and `rotation2D` stay
248
- * user-controlled.
249
- *
250
- * ## The one addition to §8: `shape`
251
- *
252
- * §8 describes the controller as a capsule. Measured against `@dimforge/rapier2d-compat@0.20.0`
253
- * (spike S6.2), Rapier's autostep clears a 0.3 m step with a **box** character and refuses anything
254
- * above about 0.15 m with a capsule of radius 0.2 — so `stepOffset` is only usable with a box.
255
- * {@link CharacterController2D.shape} therefore chooses between the two, defaulting to `"capsule"`
256
- * as §8 says, and the limitation is documented in the skill and in ADR-0006's validation section.
221
+ * Sum `move` displacements during `fixedUpdate`; game scripts supply gravity.
222
+ * After the step the controller owns world X/Y, while Z and rotation remain user-controlled.
223
+ * Box shapes support taller autosteps than capsules; see ADR-0006's validation results.
257
224
  */
258
225
  /**
259
226
  * The collision shape a 2D character controller uses.
@@ -349,6 +316,10 @@ var CharacterController2D = class CharacterController2D extends Component {
349
316
  /**
350
317
  * Emitted once per obstacle the character hit during a step.
351
318
  *
319
+ * @remarks
320
+ * Trigger colliders are not obstacles — the character walks straight through them — so a sensor
321
+ * never appears here. Listen for `onTriggerEnter`/`onTriggerExit` on the entity's scripts instead.
322
+ *
352
323
  * @returns The signal, created on first access.
353
324
  */
354
325
  get onCollided() {
@@ -521,20 +492,9 @@ function physics2DError(code, message, options) {
521
492
  //#endregion
522
493
  //#region src/material.ts
523
494
  /**
524
- * The `ignifx.physicsmaterial` asset, read by the 2D extension.
525
- *
526
- * ## Why the format is the 3D one
527
- *
528
- * `11-2d-toolkit.md` §8 lists a `PhysicsMaterial2D`, and `09-physics.md` §2.4 already defines
529
- * `{ "format": "ignifx.physicsmaterial", "friction": …, "staticFriction": …, "restitution": … }`.
530
- * Inventing a second document for the same two numbers would give artists two files to keep in
531
- * step, so this package reads **the same format and the same `.physicsmaterial.json` extension**,
532
- * ignoring `staticFriction` (Rapier 2D has one friction coefficient, `geometry/collider.d.ts`).
533
- * `@ignifx/physics` and `@ignifx/physics-2d` never run in one world — `IGX-1101` — so the two
534
- * loaders never collide.
535
- *
536
- * The 2D-only knobs, how two surfaces' friction and restitution are **combined**, are fields on the
537
- * collider rather than on the asset, so the document stays byte-identical to the 3D one.
495
+ * Read the shared `.physicsmaterial.json` format, ignoring `staticFriction` because Rapier has one
496
+ * friction coefficient. Combine rules stay on colliders. Only one physics extension runs per world,
497
+ * so the 2D and 3D loaders do not compete.
538
498
  */
539
499
  /**
540
500
  * The asset type name `.physicsmaterial.json` addresses resolve to.
@@ -681,29 +641,9 @@ function numberAt(object, key, fallback) {
681
641
  //#endregion
682
642
  //#region src/components/collider.ts
683
643
  /**
684
- * The 2D collider contract (`docs/architecture/11-2d-toolkit.md` §8). Every collider shares an
685
- * offset, the trigger flag, a surface, a layer override, and the one-way platform flag, and answers
686
- * one question the body builder asks: which Rapier shapes it stands for.
687
- *
688
- * ## Corrections to §8
689
- *
690
- * - **The material is two fields, not a union.** A schema field has exactly one kind (ADR-0004), so
691
- * {@link Collider2D.material} (an asset reference) and {@link Collider2D.inlineMaterial} (a
692
- * record) are separate; the asset wins, the inline record is the fallback, and
693
- * `physics2d.defaultMaterial` is the last resort. This is the same split `@ignifx/physics` makes.
694
- * - **Combine rules are collider fields.** Rapier combines two surfaces' friction and restitution
695
- * with a per-collider rule (`dynamics/coefficient_combine_rule.d.ts`); the 3D
696
- * `ignifx.physicsmaterial` document has no such field, so the knob lives here rather than
697
- * forking the asset format.
698
- * - **A collider may stand for several Rapier colliders.** `TilemapCollider2D` produces one polygon
699
- * per merged tile run, so {@link Collider2D.collectShapes} fills a list rather than returning one
700
- * shape.
701
- *
702
- * ## Sizes and scale
703
- *
704
- * Sizes are authored in local metres and multiplied by the entity's lossy scale when the shapes are
705
- * built. Rapier cannot rescale a built shape, so a later scale change needs
706
- * {@link Collider2D.rebuild}.
644
+ * Build shapes from local metres multiplied by entity scale; call `rebuild` after changing scale.
645
+ * Material assets take precedence over inline values, then project defaults. Friction and restitution
646
+ * combine rules belong to each collider. A tilemap collider can produce several backend shapes.
707
647
  */
708
648
  /**
709
649
  * How two surfaces' coefficients are combined when they touch, mirroring Rapier's
@@ -789,19 +729,9 @@ var Collider2D = class extends Component {
789
729
  //#endregion
790
730
  //#region src/components/colliders.ts
791
731
  /**
792
- * The concrete 2D colliders (`docs/architecture/11-2d-toolkit.md` §8). Each maps to one Rapier
793
- * shape (`geometry/shape.d.ts`), sized in local metres and multiplied by the entity's lossy scale.
794
- *
795
- * ## The two point-list colliders
796
- *
797
- * - `PolygonCollider2D` builds a **convex** shape (`ColliderDesc.convexHull`). Rapier 2D also ships
798
- * `convexDecomposition`, which needs a triangle mesh with an index buffer and runs V-HACD — far
799
- * too heavy for a component field. Concave outlines are expressed as several
800
- * `PolygonCollider2D`s on one entity, which is what a compound body is for, or as an
801
- * `EdgeCollider2D` when the outline is open.
802
- * - `EdgeCollider2D` builds a `polyline`, the open chain a platformer's ground contour wants. A
803
- * polyline is infinitely thin: a fast body can tunnel through it, and a character controller that
804
- * starts *inside* one is not pushed out. Static level geometry is what it is for.
732
+ * Polygon colliders use convex hulls; represent concave geometry with multiple colliders.
733
+ * Edge colliders are thin, open polylines for static contours: fast bodies can tunnel through them,
734
+ * and bodies starting inside are not pushed out.
805
735
  */
806
736
  /** A quarter turn in radians, which is how a horizontal capsule is expressed. */
807
737
  const QUARTER_TURN = Math.PI / 2;
@@ -1124,25 +1054,9 @@ function degenerate(entity, component, requirement) {
1124
1054
  //#endregion
1125
1055
  //#region src/components/tilemap-collider.ts
1126
1056
  /**
1127
- * `TilemapCollider2D` (`docs/architecture/11-2d-toolkit.md` §2.5, §8): the merged collision surface
1128
- * of a `Tilemap`, turned into Rapier geometry.
1129
- *
1130
- * ## How it reaches the data
1131
- *
1132
- * `@ignifx/2d` is an **optional** peer of `@ignifx/physics-2d` (a physics-only 2D game needs no
1133
- * sprite toolkit), so this component never imports the toolkit as a value — only the
1134
- * `TilemapCollisionData` type. A game, or `@ignifx/2d`'s own tilemap loader, assigns
1135
- * {@link TilemapCollider2D.collisionData}; the runtime compares the data's `version` at the start of
1136
- * every fixed step and rebuilds the shapes when it changes, which is the same moment
1137
- * `Tilemap.onCollisionChanged` fires and needs no signal subscription across the package boundary.
1138
- *
1139
- * ## Why closed polylines rather than polygons
1140
- *
1141
- * A merged run of solid tiles is not convex in general — an L of tiles merges into an L — and
1142
- * Rapier's polygon shape is a **convex hull** (`geometry/shape.d.ts`, `ConvexPolygon`), which would
1143
- * silently fill the notch. A closed `polyline` reproduces the outline exactly and is the shape
1144
- * static level geometry wants; the trade-off is that a polyline is infinitely thin, so a body that
1145
- * starts inside the tilemap is not pushed out.
1057
+ * Read tile collision data through the optional 2D toolkit's type and rebuild when its version changes.
1058
+ * Closed polylines preserve concave tile outlines that convex hulls would fill in. They are thin
1059
+ * surfaces, so a body starting inside the map is not pushed out.
1146
1060
  */
1147
1061
  /**
1148
1062
  * The collision surface of a tilemap, as one static body's worth of Rapier shapes.
@@ -1286,21 +1200,9 @@ function segment(from, to, scale) {
1286
1200
  //#endregion
1287
1201
  //#region src/lite/rapier/module.ts
1288
1202
  /**
1289
- * The one place `@dimforge/rapier2d-compat`'s WebAssembly is instantiated
1290
- * (`docs/standards/coding-standards.md` §4, `CONSTITUTION.md` §3.4). Rapier is not Babylon Lite,
1291
- * but it is a native/WASM backend under the same adapter rule, and the rule's allowed directory is
1292
- * `src/lite/**` — hence `src/lite/rapier/`.
1293
- *
1294
- * ## Why `-compat`
1295
- *
1296
- * The `-compat` build inlines the WebAssembly module as base64 inside `rapier.mjs`, so it needs no
1297
- * asset serving and works unchanged in Node and in the browser (spike S6.2, ADR-0006 Validation).
1298
- * `init()` is what decodes and instantiates it; every other Rapier call before that throws.
1299
- *
1300
- * The import is static: `world.ts` and `service.ts` also import Rapier's classes statically, so a
1301
- * dynamic import here alone would not move the 2 MB module out of an app's entry chunk (measured in
1302
- * Phase 6). Splitting it needs the adapter to read every class off the loaded module instead, which
1303
- * is the Phase 12 bundle work.
1203
+ * Initialise Rapier's embedded WebAssembly before making backend calls.
1204
+ * The compat build includes the binary, so both Node and browser apps need no separate asset.
1205
+ * Rapier classes are imported statically by the adapters; delaying `init` does not split that bundle.
1304
1206
  *
1305
1207
  * @internal
1306
1208
  */
@@ -2024,6 +1926,12 @@ var Physics2DHost = class {
2024
1926
  //#endregion
2025
1927
  //#region src/lite/rapier/character.ts
2026
1928
  /**
1929
+ * The obstacle filter every controller move uses. Sensors describe regions, not geometry, so they
1930
+ * are excluded from collide-and-slide; the pair is still reported by the world step, which is where
1931
+ * `onTriggerEnter`/`onTriggerExit` come from.
1932
+ */
1933
+ const OBSTACLE_FLAGS = QueryFilterFlags.EXCLUDE_SENSORS;
1934
+ /**
2027
1935
  * Creates a character controller.
2028
1936
  *
2029
1937
  * @param world - The world.
@@ -2071,6 +1979,10 @@ function tuneController(controller, tuning) {
2071
1979
  /**
2072
1980
  * Runs one collide-and-slide move.
2073
1981
  *
1982
+ * @remarks
1983
+ * Sensors are excluded from the obstacle set ({@link OBSTACLE_FLAGS}), so a character walks into and
1984
+ * out of a trigger volume at full speed and the world step raises the intersection events.
1985
+ *
2074
1986
  * @param controller - The controller.
2075
1987
  * @param collider - The character's own collider.
2076
1988
  * @param desired - The displacement to attempt, in metres.
@@ -2085,11 +1997,11 @@ function computeControllerMovement(controller, collider, desired, groups, filter
2085
1997
  if (filter === null) controller.computeColliderMovement(collider, {
2086
1998
  x: desired.x,
2087
1999
  y: desired.y
2088
- }, void 0, groups);
2000
+ }, OBSTACLE_FLAGS, groups);
2089
2001
  else controller.computeColliderMovement(collider, {
2090
2002
  x: desired.x,
2091
2003
  y: desired.y
2092
- }, void 0, groups, filter);
2004
+ }, OBSTACLE_FLAGS, groups, filter);
2093
2005
  controller.computedMovement(out);
2094
2006
  return controller.computedGrounded();
2095
2007
  }
@@ -2430,7 +2342,7 @@ var Physics2DRuntime = class {
2430
2342
  }
2431
2343
  /**
2432
2344
  * Restores the authoritative pose of every interpolated body, undoing the display pose the
2433
- * `PreRender` system wrote (`09-physics.md` §1).
2345
+ * `Update` system wrote at the top of the previous frame's `Update` (`09-physics.md` §1).
2434
2346
  */
2435
2347
  restorePoses() {
2436
2348
  for (let index = 0; index < this.#records.length; index += 1) {
@@ -2497,8 +2409,9 @@ var Physics2DRuntime = class {
2497
2409
  destroyWorld(this.#world);
2498
2410
  }
2499
2411
  /**
2500
- * Publishes the frame's counters and zeroes the accumulators. `PreRender` is where a frame's fixed
2501
- * steps are all done.
2412
+ * Publishes the frame's counters and zeroes the accumulators. The top of `Update`, where the
2413
+ * display pose is written, is the first point in a frame at which every fixed step of that frame
2414
+ * is done.
2502
2415
  */
2503
2416
  #publishCounters() {
2504
2417
  this.#counters.set(this.#counters.index("bodies"), this.#records.length);
@@ -2951,6 +2864,10 @@ var Physics2DRuntime = class {
2951
2864
  /**
2952
2865
  * Reports every obstacle a controller hit and returns the most upward-facing normal.
2953
2866
  *
2867
+ * @remarks
2868
+ * Sensors are filtered out of the move (`lite/rapier/character.ts`), so a trigger volume never
2869
+ * shows up here and never contributes a ground normal.
2870
+ *
2954
2871
  * @param rapierController - Rapier's controller.
2955
2872
  * @param component - The component.
2956
2873
  * @returns The ground normal, or `null` when nothing was hit.
@@ -3250,26 +3167,43 @@ function now() {
3250
3167
  //#endregion
3251
3168
  //#region src/runtime/systems.ts
3252
3169
  /**
3253
- * The three systems `docs/architecture/01-lifecycle-and-time.md` §3 step 4 and `09-physics.md` §1
3254
- * put physics in the frame with — identical in 2D, except that Rapier is stepped directly and there
3255
- * is no Babylon Lite simulation scene:
3170
+ * The three systems `docs/architecture/01-lifecycle-and-time.md` §3 and `09-physics.md` §1 put
3171
+ * physics in the frame with — identical in 2D, except that Rapier is stepped directly and there is
3172
+ * no Babylon Lite simulation scene:
3256
3173
  *
3257
3174
  * ```
3258
3175
  * Systems(FixedUpdate, -100) restore the authoritative pose of interpolated bodies
3259
3176
  * scripts.fixedUpdate(dt) forces, velocities, CharacterController2D.move()
3260
3177
  * Systems(FixedUpdate, 100) step Rapier, snapshot poses, dispatch collision and trigger events
3261
- * Systems(PreRender, -500) write lerp(prev, cur, time.fixedStepAlpha)
3178
+ * ── the fixed loop ends; time.fixedStepAlpha is final ──
3179
+ * Systems(Update, -900) write lerp(prev, cur, time.fixedStepAlpha)
3180
+ * scripts.update / lateUpdate, animation, rendering — all read the display pose
3262
3181
  * ```
3263
3182
  *
3183
+ * **Why the display pose is written at the top of `Update` and not in `PreRender`** (2026-09-08).
3184
+ * It used to be written in `PreRender`, after `lateUpdate`. `Camera2DFollow` and every hand-written
3185
+ * follow camera run in `update`/`lateUpdate`, so they framed the character where the last fixed step
3186
+ * left it while the renderer drew it at `lerp(prev, cur, alpha)`: up to one fixed step of relative
3187
+ * judder every frame, which a pixel-perfect camera turns into a visibly shaking sprite because the
3188
+ * two poses are quantised independently. Writing the pose before `scripts.update` means scripts,
3189
+ * animation, camera rigs and the render sync all see the pose the frame presents, which is Unity's
3190
+ * model. The simulation is unaffected: the restore system at `FixedUpdate −100` runs before
3191
+ * `scripts.fixedUpdate`, so `fixedUpdate` and Rapier still see the authoritative pose.
3192
+ *
3264
3193
  * Everything here is `@internal`.
3265
3194
  */
3266
3195
  /** The `FixedUpdate` order the restore system runs at. */
3267
3196
  const PHYSICS_2D_RESTORE_ORDER = -100;
3268
- /** The `PreRender` order the interpolation system runs at. */
3269
- const PHYSICS_2D_INTERPOLATE_ORDER = -500;
3270
3197
  /**
3271
- * Undoes the display pose the interpolation system wrote, so `fixedUpdate` and Rapier both see the
3272
- * authoritative pose.
3198
+ * The `Update` order the interpolation system runs at. Negative, so it lands in
3199
+ * `Systems(Update, order < 0)` — after the fixed loop and lifecycle flush B, before
3200
+ * `scripts.update` — and low enough that a game's own `Update` systems can still be ordered in
3201
+ * front of it if they must see the authoritative pose.
3202
+ */
3203
+ const PHYSICS_2D_INTERPOLATE_ORDER = -900;
3204
+ /**
3205
+ * Undoes the display pose the interpolation system wrote at the top of the previous `Update`, so
3206
+ * `fixedUpdate` and Rapier both see the authoritative pose.
3273
3207
  *
3274
3208
  * @internal
3275
3209
  */
@@ -3745,7 +3679,7 @@ const physics2d = defineExtension((options = {}) => {
3745
3679
  order: 100
3746
3680
  });
3747
3681
  ctx.registerSystem(new Physics2DInterpolationSystem(host), {
3748
- phase: Phase.PreRender,
3682
+ phase: Phase.Update,
3749
3683
  order: PHYSICS_2D_INTERPOLATE_ORDER
3750
3684
  });
3751
3685
  },