@ignifx/physics-2d 0.0.0 → 0.1.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/LICENSE +201 -0
- package/dist/index.d.ts +1686 -1
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +3881 -0
- package/dist/index.js.map +1 -0
- package/package.json +31 -26
- package/skills/physics-2d/SKILL.md +249 -13
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ignifx/physics-2d",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.0",
|
|
4
4
|
"description": "ignifx 2D physics: Rigidbody2D, 2D colliders, CharacterController2D, queries.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"ignifx",
|
|
@@ -9,6 +9,11 @@
|
|
|
9
9
|
"homepage": "https://ignifx.com",
|
|
10
10
|
"license": "Apache-2.0",
|
|
11
11
|
"author": "Astrum Forge Studios",
|
|
12
|
+
"repository": {
|
|
13
|
+
"type": "git",
|
|
14
|
+
"url": "git+https://github.com/astrum-forge/ignifx.git",
|
|
15
|
+
"directory": "packages/physics-2d"
|
|
16
|
+
},
|
|
12
17
|
"files": [
|
|
13
18
|
"dist",
|
|
14
19
|
"skills"
|
|
@@ -24,37 +29,28 @@
|
|
|
24
29
|
},
|
|
25
30
|
"publishConfig": {
|
|
26
31
|
"access": "public",
|
|
27
|
-
"provenance":
|
|
28
|
-
},
|
|
29
|
-
"scripts": {
|
|
30
|
-
"build": "tsdown",
|
|
31
|
-
"typecheck": "tsc --build tsconfig.json tsconfig.test.json",
|
|
32
|
-
"test": "vitest run --project node --root ../.. packages/physics-2d",
|
|
33
|
-
"api-report": "api-extractor run --verbose",
|
|
34
|
-
"docs:api": "typedoc",
|
|
35
|
-
"api-report:update": "api-extractor run --local --verbose",
|
|
36
|
-
"pack-check": "publint && attw --pack . --profile esm-only"
|
|
32
|
+
"provenance": false
|
|
37
33
|
},
|
|
38
34
|
"dependencies": {
|
|
39
35
|
"@dimforge/rapier2d-compat": "^0.20.0"
|
|
40
36
|
},
|
|
41
37
|
"devDependencies": {
|
|
42
|
-
"@arethetypeswrong/cli": "
|
|
43
|
-
"@
|
|
44
|
-
"@
|
|
45
|
-
"@
|
|
46
|
-
"
|
|
47
|
-
"
|
|
48
|
-
"
|
|
49
|
-
"
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
"
|
|
53
|
-
"
|
|
38
|
+
"@arethetypeswrong/cli": "^0.18.5",
|
|
39
|
+
"@microsoft/api-extractor": "^7.59.0",
|
|
40
|
+
"@types/webxr": "^0.5.0",
|
|
41
|
+
"@webgpu/types": "^0.1.72",
|
|
42
|
+
"publint": "^0.3.24",
|
|
43
|
+
"tsdown": "^0.23.0",
|
|
44
|
+
"typedoc": "^0.28.20",
|
|
45
|
+
"typedoc-plugin-markdown": "^4.13.0",
|
|
46
|
+
"typescript-native": "npm:typescript@7.0.2",
|
|
47
|
+
"vitest": "^5.0.0",
|
|
48
|
+
"@ignifx/2d": "0.1.0",
|
|
49
|
+
"@ignifx/core": "0.1.0"
|
|
54
50
|
},
|
|
55
51
|
"peerDependencies": {
|
|
56
|
-
"@ignifx/
|
|
57
|
-
"@ignifx/
|
|
52
|
+
"@ignifx/core": "0.1.0",
|
|
53
|
+
"@ignifx/2d": "0.1.0"
|
|
58
54
|
},
|
|
59
55
|
"peerDependenciesMeta": {
|
|
60
56
|
"@ignifx/2d": {
|
|
@@ -70,5 +66,14 @@
|
|
|
70
66
|
],
|
|
71
67
|
"capabilities": [],
|
|
72
68
|
"skill": "./skills/physics-2d/SKILL.md"
|
|
69
|
+
},
|
|
70
|
+
"scripts": {
|
|
71
|
+
"build": "tsdown",
|
|
72
|
+
"typecheck": "tsc --build tsconfig.json tsconfig.test.json",
|
|
73
|
+
"test": "vitest run --project node --root ../.. packages/physics-2d",
|
|
74
|
+
"api-report": "api-extractor run --verbose",
|
|
75
|
+
"docs:api": "typedoc",
|
|
76
|
+
"api-report:update": "api-extractor run --local --verbose",
|
|
77
|
+
"pack-check": "publint && attw --pack . --profile esm-only"
|
|
73
78
|
}
|
|
74
|
-
}
|
|
79
|
+
}
|
|
@@ -1,46 +1,281 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: physics-2d
|
|
3
|
-
description: Adds 2D physics to an ignifx game with @ignifx/physics-2d: Rigidbody2D,
|
|
3
|
+
description: Adds 2D physics to an ignifx game with @ignifx/physics-2d: Rapier 2D simulation on the fixed loop, Rigidbody2D, box/circle/capsule/polygon/edge/tilemap colliders, physics materials, 2D triggers, CharacterController2D with slopes, autostep, snap-to-ground and one-way platforms, 2D raycasts and shape queries, the collision layer matrix, and render interpolation. Use when adding or editing 2D physics bodies, 2D colliders, platformer movement, 2D triggers, or 2D queries in an ignifx project, or when the user mentions @ignifx/physics-2d, Rigidbody2D, Rapier, or CharacterController2D.
|
|
4
4
|
license: Apache-2.0
|
|
5
5
|
metadata:
|
|
6
|
-
ignifx-version: "0.
|
|
6
|
+
ignifx-version: "0.1.0"
|
|
7
7
|
---
|
|
8
8
|
|
|
9
9
|
# @ignifx/physics-2d
|
|
10
10
|
|
|
11
|
-
> **Scaffold notice.** ignifx is in the planning phase and `@ignifx/physics-2d` is an empty package. Every section below is filled by Phase 6 of `docs/plan/engineering-plan.md`; until then this file makes no API claims (`CONSTITUTION.md` Article V).
|
|
12
|
-
|
|
13
11
|
## What this is / when to use
|
|
14
12
|
|
|
15
|
-
`@ignifx/physics-2d` is the ignifx extension that
|
|
13
|
+
`@ignifx/physics-2d` is the ignifx extension that simulates 2D rigid bodies with **Rapier 2D**
|
|
14
|
+
(`@dimforge/rapier2d-compat`, Apache-2.0, ADR-0006). Add a 2D collider to make an entity solid, add
|
|
15
|
+
a `Rigidbody2D` to make it move, and read the result off `entity.transform.position2D` /
|
|
16
|
+
`rotation2D` like any other pose.
|
|
17
|
+
|
|
18
|
+
Units are **metres and seconds**, +Y is up, and `rotation2D` is degrees counter-clockwise about +Z.
|
|
19
|
+
Pixels-per-unit affects only rendering (`docs/architecture/11-2d-toolkit.md` §3). The simulation is
|
|
20
|
+
stepped directly by ignifx's fixed loop — there is no Babylon Lite scene involved — so it advances
|
|
21
|
+
at a fixed rate whatever the frame rate does, and it runs headless with no GPU.
|
|
22
|
+
|
|
23
|
+
A world uses **either** `physics()` **or** `physics2d()`. Registering both throws `IGX-1101`.
|
|
16
24
|
|
|
17
25
|
## Environment
|
|
18
26
|
|
|
19
|
-
Engine
|
|
27
|
+
- Engine: ignifx `0.0.0-unreleased`; this package targets `@ignifx/core` `>=0.0.0 <1.0.0`.
|
|
28
|
+
- Dependency: `@dimforge/rapier2d-compat` `^0.20.0`. The `-compat` build **inlines** its WebAssembly
|
|
29
|
+
as base64, so nothing has to be served and Node, the browser, and Electron all take the same path.
|
|
30
|
+
- `@ignifx/2d` is an **optional** peer: you need it only for `TilemapCollider2D`'s data type, so a
|
|
31
|
+
game that draws its own graphics can use 2D physics on its own.
|
|
32
|
+
- Register it like any extension; nothing happens at import time.
|
|
33
|
+
|
|
34
|
+
```ts run
|
|
35
|
+
import { createApp } from "@ignifx/core";
|
|
36
|
+
import { physics2d } from "@ignifx/physics-2d";
|
|
37
|
+
|
|
38
|
+
const app = await createApp({ headless: true, extensions: [physics2d()] });
|
|
39
|
+
await app.start();
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
`ignifx.config.ts` carries the settings under `physics2d`:
|
|
43
|
+
|
|
44
|
+
| Setting | Default | Meaning |
|
|
45
|
+
| -------------------- | --------------- | ------------------------------------------------------------------ |
|
|
46
|
+
| `gravity` | `{x:0,y:-9.81}` | World gravity, m/s²; +Y is up |
|
|
47
|
+
| `collisionMatrix` | `{}` | `{ layerName: [layerNames] }`; an unlisted layer collides with all |
|
|
48
|
+
| `defaultMaterial` | friction `0.6` | The surface a collider with no material of its own uses |
|
|
49
|
+
| `velocityIterations` | `0` | Rapier solver iterations; `0` leaves Rapier's own default of 4 |
|
|
50
|
+
| `interpolation` | `true` | Whether dynamic bodies interpolate between fixed steps |
|
|
51
|
+
|
|
52
|
+
`fixedDeltaTime` is **not** here: the step comes from the core `time` section and `app.time`.
|
|
20
53
|
|
|
21
54
|
## Mental model
|
|
22
55
|
|
|
23
|
-
|
|
56
|
+
```
|
|
57
|
+
app.physics2d (Physics2DService) gravity · raycast · raycastAll · overlapCircle/Box · shapeCast · rapier
|
|
58
|
+
│
|
|
59
|
+
├─ Rigidbody2D dynamic | kinematic | static, one per entity
|
|
60
|
+
├─ Box/Circle/Capsule/Polygon/Edge/TilemapCollider2D several per entity form one compound body
|
|
61
|
+
└─ CharacterController2D kinematic capsule or box, collide-and-slide
|
|
62
|
+
|
|
63
|
+
per fixed step:
|
|
64
|
+
Systems(FixedUpdate, -100) restore the authoritative pose of interpolated bodies
|
|
65
|
+
scripts.fixedUpdate(dt) forces, velocities, controller.move()
|
|
66
|
+
Systems(FixedUpdate, 100) step Rapier · snapshot poses · dispatch collision and trigger events
|
|
67
|
+
Systems(PreRender, -500) write lerp(previous, current, time.fixedStepAlpha)
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
- Bodies and colliders are (re)built at the **start of the next fixed step**, never mid-frame, so
|
|
71
|
+
body creation order — and therefore Rapier's internal order — follows entity creation order.
|
|
72
|
+
- An entity with 2D colliders and no `Rigidbody2D` gets an implicit **static** body placed once.
|
|
73
|
+
Moving it afterwards logs `IGX-1151`; anything that moves needs a kinematic `Rigidbody2D`.
|
|
74
|
+
- A body's entity should be a **root** entity; a parented one logs `IGX-1157`.
|
|
75
|
+
- The callbacks are the **3D names** — `onCollisionEnter`, `onCollisionStay`, `onCollisionExit`,
|
|
76
|
+
`onTriggerEnter`, `onTriggerExit` — carrying the 2D payloads. There is no `onCollisionEnter2D`.
|
|
77
|
+
Type the parameter as the 2D payload — `onTriggerEnter(trigger: TriggerEvent2D): void` — rather
|
|
78
|
+
than accepting core's `unknown` and casting: method parameters are bivariant, so the narrow
|
|
79
|
+
signature satisfies `ScriptCallbacks`, and the cast trips `typescript/no-unsafe-type-assertion`
|
|
80
|
+
under the engine's own lint settings.
|
|
24
81
|
|
|
25
82
|
## First app
|
|
26
83
|
|
|
27
|
-
|
|
84
|
+
```ts run
|
|
85
|
+
import { createApp } from "@ignifx/core";
|
|
86
|
+
import { BoxCollider2D, physics2d, Rigidbody2D } from "@ignifx/physics-2d";
|
|
87
|
+
|
|
88
|
+
const app = await createApp({ headless: true, extensions: [physics2d()] });
|
|
89
|
+
await app.start();
|
|
90
|
+
|
|
91
|
+
const floor = app.world.createEntity("Floor");
|
|
92
|
+
floor.transform.position = { x: 0, y: -0.5, z: 0 };
|
|
93
|
+
floor.addComponent(BoxCollider2D, { size: { x: 20, y: 1 } });
|
|
94
|
+
|
|
95
|
+
const crate = app.world.createEntity("Crate");
|
|
96
|
+
crate.transform.position = { x: 0, y: 5, z: 0 };
|
|
97
|
+
crate.addComponent(BoxCollider2D, { size: { x: 1, y: 1 } });
|
|
98
|
+
const body = crate.addComponent(Rigidbody2D, { mass: 2 });
|
|
99
|
+
|
|
100
|
+
for (let index = 0; index < 240; index += 1) {
|
|
101
|
+
app.step(1 / 60);
|
|
102
|
+
}
|
|
103
|
+
app.log.info("crate y:", crate.transform.position.y, "mass:", body.computedMass);
|
|
104
|
+
```
|
|
28
105
|
|
|
29
106
|
## Core APIs
|
|
30
107
|
|
|
31
|
-
|
|
108
|
+
`Rigidbody2D` — `bodyType` (`"dynamic" | "kinematic" | "static"`), `mass` (exact kilograms; `0`
|
|
109
|
+
means "weigh the colliders at 1 kg/m²"), `gravityScale`, `linearDamping`, `angularDamping`,
|
|
110
|
+
`freezeRotation`, `interpolation`, `collisionEvents` (`"auto" | "on" | "off"`). At runtime:
|
|
111
|
+
`linearVelocity` (m/s), `angularVelocity` (**degrees**/s), `computedMass`, `addForce`, `addImpulse`,
|
|
112
|
+
`addTorque`, `teleport(position, rotationDegrees?)`, and the `rapier` escape hatch.
|
|
113
|
+
|
|
114
|
+
Colliders share `offset`, `isTrigger`, `material` (a `.physicsmaterial.json` asset), the
|
|
115
|
+
`inlineMaterial` fallback, `frictionCombine` / `restitutionCombine`, `oneWay`, and `layerOverride`:
|
|
116
|
+
|
|
117
|
+
| Collider | Geometry |
|
|
118
|
+
| ------------------- | ----------------------------------------------------------- |
|
|
119
|
+
| `BoxCollider2D` | `size` — an axis-aligned rectangle |
|
|
120
|
+
| `CircleCollider2D` | `radius`; the larger scale axis wins |
|
|
121
|
+
| `CapsuleCollider2D` | `radius`, `height` (tip to tip), `direction` (`"x" \| "y"`) |
|
|
122
|
+
| `PolygonCollider2D` | `points` → the **convex hull** of the list |
|
|
123
|
+
| `EdgeCollider2D` | `points` → an open chain; infinitely thin |
|
|
124
|
+
| `TilemapCollider2D` | `collisionData` from a `Tilemap` |
|
|
125
|
+
|
|
126
|
+
`CharacterController2D` — `shape` (`"capsule" | "box"`), `radius`, `height`, `offset`, `slopeLimit`
|
|
127
|
+
(degrees), `stepOffset`, `snapToGround`, `skinWidth`, `onOneWayPlatforms`, `pushBodies`,
|
|
128
|
+
`interpolation`. Drive it with `move(delta)` from `fixedUpdate`; read `isGrounded`, `velocity`,
|
|
129
|
+
`groundNormal`, and `onCollided`.
|
|
130
|
+
|
|
131
|
+
`app.physics2d` — `gravity`, `raycast`, `raycastAll`, `overlapCircle`, `overlapBox`, `shapeCast`,
|
|
132
|
+
and `rapier` (the unstable escape hatch). Full signatures:
|
|
133
|
+
`skills/ignifx/references/api/physics-2d.md`.
|
|
32
134
|
|
|
33
135
|
## Recipes
|
|
34
136
|
|
|
35
|
-
|
|
137
|
+
### A trigger zone
|
|
138
|
+
|
|
139
|
+
```ts run
|
|
140
|
+
import { Script } from "@ignifx/core";
|
|
141
|
+
import type { ScriptCallbacks } from "@ignifx/core";
|
|
142
|
+
import type { TriggerEvent2D } from "@ignifx/physics-2d";
|
|
143
|
+
|
|
144
|
+
export class Coin extends Script implements ScriptCallbacks {
|
|
145
|
+
static typeId = "mygame/Coin";
|
|
146
|
+
|
|
147
|
+
onTriggerEnter(trigger: TriggerEvent2D): void {
|
|
148
|
+
if (trigger.other?.name === "Player") {
|
|
149
|
+
this.entity.destroy();
|
|
150
|
+
}
|
|
151
|
+
}
|
|
152
|
+
}
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Give the coin a collider with `isTrigger: true`; give the player anything that moves. Both entities
|
|
156
|
+
receive the callback, and `trigger.other` and `trigger.otherCollider` are always the real objects —
|
|
157
|
+
Rapier reports both colliders, so 2D has none of the identity gaps 3D physics documents.
|
|
158
|
+
|
|
159
|
+
### Platformer movement
|
|
160
|
+
|
|
161
|
+
```ts run
|
|
162
|
+
import { Script, Vec2 } from "@ignifx/core";
|
|
163
|
+
import type { ScriptCallbacks } from "@ignifx/core";
|
|
164
|
+
import { CharacterController2D } from "@ignifx/physics-2d";
|
|
165
|
+
|
|
166
|
+
export class Platformer extends Script implements ScriptCallbacks {
|
|
167
|
+
static typeId = "mygame/Platformer";
|
|
168
|
+
|
|
169
|
+
/** Metres per second. */
|
|
170
|
+
speed = 5;
|
|
171
|
+
|
|
172
|
+
/** The vertical speed a jump starts at. */
|
|
173
|
+
jumpSpeed = 9;
|
|
174
|
+
|
|
175
|
+
readonly #velocity = new Vec2();
|
|
176
|
+
|
|
177
|
+
fixedUpdate(dt: number): void {
|
|
178
|
+
const controller = this.entity.requireComponent(CharacterController2D);
|
|
179
|
+
this.#velocity.x = this.speed;
|
|
180
|
+
if (controller.isGrounded) {
|
|
181
|
+
this.#velocity.y = this.jumpSpeed;
|
|
182
|
+
} else {
|
|
183
|
+
this.#velocity.y -= 9.81 * dt;
|
|
184
|
+
}
|
|
185
|
+
controller.move({ x: this.#velocity.x * dt, y: this.#velocity.y * dt });
|
|
186
|
+
}
|
|
187
|
+
}
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Gravity is **not** applied by the controller: a bare `CharacterController2D` is purely kinematic, so
|
|
191
|
+
the script owns the vertical velocity, coyote time, and variable jump height.
|
|
192
|
+
|
|
193
|
+
### A raycast
|
|
194
|
+
|
|
195
|
+
```ts run
|
|
196
|
+
import { createApp } from "@ignifx/core";
|
|
197
|
+
import { BoxCollider2D, physics2d } from "@ignifx/physics-2d";
|
|
198
|
+
|
|
199
|
+
const app = await createApp({ headless: true, extensions: [physics2d()] });
|
|
200
|
+
await app.start();
|
|
201
|
+
const ground = app.world.createEntity("Ground");
|
|
202
|
+
ground.addComponent(BoxCollider2D, { size: { x: 20, y: 1 } });
|
|
203
|
+
app.step(1 / 60);
|
|
204
|
+
|
|
205
|
+
const hit = app.physics2d.raycast({ x: 0, y: 5 }, { x: 0, y: -1 }, 20);
|
|
206
|
+
if (hit !== null) {
|
|
207
|
+
app.log.info("hit:", hit.entity.name, "y:", hit.point.y);
|
|
208
|
+
}
|
|
209
|
+
```
|
|
210
|
+
|
|
211
|
+
### Tilemap collision
|
|
212
|
+
|
|
213
|
+
```ts run
|
|
214
|
+
import { createApp } from "@ignifx/core";
|
|
215
|
+
import { physics2d, TilemapCollider2D } from "@ignifx/physics-2d";
|
|
216
|
+
|
|
217
|
+
const app = await createApp({ headless: true, extensions: [physics2d()] });
|
|
218
|
+
await app.start();
|
|
219
|
+
const map = app.world.createEntity("Map");
|
|
220
|
+
const collider = map.addComponent(TilemapCollider2D);
|
|
221
|
+
collider.collisionData = {
|
|
222
|
+
cellSize: 1,
|
|
223
|
+
chunkSize: 8,
|
|
224
|
+
version: 1,
|
|
225
|
+
chunks: [
|
|
226
|
+
{
|
|
227
|
+
chunkX: 0,
|
|
228
|
+
chunkY: 0,
|
|
229
|
+
polygons: [
|
|
230
|
+
[
|
|
231
|
+
{ x: -4, y: -1 },
|
|
232
|
+
{ x: 4, y: -1 },
|
|
233
|
+
{ x: 4, y: 0 },
|
|
234
|
+
{ x: -4, y: 0 },
|
|
235
|
+
],
|
|
236
|
+
],
|
|
237
|
+
oneWayEdges: [],
|
|
238
|
+
},
|
|
239
|
+
],
|
|
240
|
+
};
|
|
241
|
+
app.step(1 / 60);
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
Assign `tilemap.collisionData` from `@ignifx/2d` instead of building it by hand. The runtime watches
|
|
245
|
+
the data's `version` every fixed step and rebuilds the shapes when it changes, so a tile edit reaches
|
|
246
|
+
physics with no subscription of your own.
|
|
36
247
|
|
|
37
248
|
## File formats
|
|
38
249
|
|
|
39
|
-
|
|
250
|
+
`ignifx.physicsmaterial` — the **same** `.physicsmaterial.json` document 3D physics reads
|
|
251
|
+
(`skills/ignifx/references/formats/ignifx.physicsmaterial.md`): `{ "format":
|
|
252
|
+
"ignifx.physicsmaterial", "friction": 0.6, "restitution": 0 }`. `staticFriction` is ignored, because
|
|
253
|
+
Rapier 2D has one friction coefficient. The combine rules are collider fields, not asset fields.
|
|
254
|
+
|
|
255
|
+
Component field tables: `skills/ignifx/references/formats/components.md`.
|
|
40
256
|
|
|
41
257
|
## Gotchas
|
|
42
258
|
|
|
43
|
-
|
|
259
|
+
- **Only sixteen layers filter.** Rapier packs membership and filter into 16 bits each, so a
|
|
260
|
+
collider on layer 16 or above logs `IGX-1152` and falls back to layer 0. Keep the layers 2D
|
|
261
|
+
physics uses in the first sixteen of `ignifx.config.ts`.
|
|
262
|
+
- **Queries need one completed step.** Rapier builds its broadphase inside `step`, so a query before
|
|
263
|
+
the first fixed step throws `IGX-1153`. Call `app.step(1 / 60)` once first.
|
|
264
|
+
- **Autostep needs a box.** `stepOffset` clears a 0.3 m step with `shape: "box"`; with the default
|
|
265
|
+
capsule of radius 0.2 it clears about 0.15 m and no more (measured, ADR-0006 Validation). Give a
|
|
266
|
+
stair-climbing character `shape: "box"`.
|
|
267
|
+
- **One-way platforms are a character-controller feature.** `oneWay` changes what
|
|
268
|
+
`CharacterController2D` collides with; rigid bodies fall through such a collider in both
|
|
269
|
+
directions in the MVP.
|
|
270
|
+
- **`PolygonCollider2D` is convex.** Rapier builds the convex hull of the points, so a concave
|
|
271
|
+
outline is silently filled in. Use several polygons on one entity, or an `EdgeCollider2D`.
|
|
272
|
+
- **`EdgeCollider2D` is infinitely thin.** A fast body can tunnel through it and a body that starts
|
|
273
|
+
inside it is not pushed out. It is for static level geometry.
|
|
274
|
+
- **Determinism is same-machine only.** `@dimforge/rapier2d-compat` is Rapier's main build, which
|
|
275
|
+
promises local determinism, not cross-platform bit-exactness; the
|
|
276
|
+
`-deterministic-` builds are the ones that do (ADR-0006 Validation).
|
|
277
|
+
- **`angularVelocity` is degrees per second**, matching `Transform.rotation2D`, while Rapier's own
|
|
278
|
+
API is radians. The `rapier` escape hatch gives you radians.
|
|
44
279
|
|
|
45
280
|
## Deprecated (current window)
|
|
46
281
|
|
|
@@ -48,4 +283,5 @@ None (pre-1.0: no deprecation window; breaking changes are listed in the changel
|
|
|
48
283
|
|
|
49
284
|
## Where to look next
|
|
50
285
|
|
|
51
|
-
`skills/ignifx/SKILL.md` for the engine entry skill · `docs/architecture
|
|
286
|
+
`skills/ignifx/SKILL.md` for the engine entry skill · `docs/architecture/11-2d-toolkit.md` §8 and
|
|
287
|
+
ADR-0006 for design rationale.
|