Physics: Move slides along the contact normal with friction, outputs hit/normal in Behaviors, sliding and friction in Colliders and Physics, check total 1057 (#150)

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
adminandClaude Opus 5 committed 2026-09-18 21:52:11 +02:00
1 parent d6190ec3ee
commit e1e8bbdc98
4 files changed
+28 -14

No files matched your search

+1 -1
@@ -108,7 +108,7 @@ shown on the node and editable in the inspector.
`Length`, `Distance`, `Normalize`, `Axis input` (four keys → a
normalized direction, so diagonals are not faster), `Compare`, `And/Or/Not`, `Key is down`,
`Mouse position`, `Mouse button`, `Is hovered`, `Is touching`, `Get velocity`, `Current state` (name and id), `Is in state` (one boolean per state) ·
actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement; in the object's own axes, which a turned parent turns too), `Set position`, `Spawn`
actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement; in the object's own axes, which a turned parent turns too; with *Collide with solids* it slides along what it hits, less friction, and outputs `blocked x/y`, `hit` and the contact `normal x/y`, see [Colliders](Colliders)), `Set position`, `Spawn`
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Emit` (starts a particle source over, see *Particles*),
`Play sound` / `Stop sound` (see [Sounds](Sounds): the asset says how loud and whether it loops, the node's
`volume` multiplies that and its `loop` either leaves it alone or overrules it in either
+17 -6
@@ -24,12 +24,23 @@ object stands for its collider children, a collider for itself. The geometry liv
`src/core/properties/collider.ts` so editor and runtime agree on it.
**Blocking** is opt-in per movement: the `Move` node has a **Collide with solids** setting
(default off). When enabled it moves an object one axis at a time — its own axes, which a turned
parent turns as well — and, after each axis, pushes its *solid* colliders back along that axis to
just touch any solid collider they ran into (`exitDistance` in `collider.ts`: the travel at which
the shapes stop overlapping) — so the other axis still moves and the object slides along walls. Its `blocked x` / `blocked y` outputs tell which
axes hit (always `false` with the setting off). In Play a dynamic body that stops the move is pushed
along (see [Physics](Physics)). A collider with
(default off). When enabled the object's *solid* colliders move along (dx, dy) — in its own axes,
which a turned parent turns as well — until one runs into a solid collider; there the object stops
flush (`exitDistance` in `collider.ts`: the travel at which the shapes stop overlapping). What is left
of the move then goes on **along the surface**: the part into it is dropped, and the part along it
is shortened by **friction × the part into it** (Coulomb; the pair's friction is the average of both
colliders, a tile counts 0.5). The contact's normal decides which way "along" is, so a circle that
runs into an edge rolls up over it, an object pressed onto a slope slides down it unless the pair
is rough enough to hold it (friction ≥ tan of the slope), and one pressed against a wall slides
along it the slower the harder it presses. Up to four contacts are followed per move (a corner
between floor and wall is two). There is no bounce: `Move` sets a distance, not a speed – a
dynamic body bounces (see [Physics](Physics)). The outputs: `blocked x` / `blocked y` tell which of
the object's own axes something stood against (a floor blocks y, a wall x, a slope each axis the
move went along);
`hit` whether anything was met; `normal x` / `normal y` which way the first surface faces, in the
world, pointing away from it — enough to build a bounce or a wall jump in the graph. All are `false`
/ 0 with the setting off. An object without a solid collider of its own moves freely. In Play a
dynamic body that stops the move is pushed into the contact (see [Physics](Physics)). A collider with
**Trigger (no blocking)** enabled (`isTrigger`, drawn amber instead of green) still produces
collision events but never blocks. An overlap that already exists is never resolved (no jolt
when objects start stacked or inside a wall) but also never deepened, so such an object can
+1 -1
@@ -12,7 +12,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
# code is lost (tail's is reported), so a red run reads as green
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1055 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1057 editor checks, screenshots in /tmp/gm-e2e-*.png
GM_STEPS=20 node tools/e2e.mjs # or 1-12, or 6,20 — a subset while working on one step
./e2e.sh start [GM_STEPS] # the same run detached, see below
./e2e.sh status # live progress, or the last run's result
+9 -6
@@ -69,9 +69,11 @@ Whether two of them touch is decided by the same overlap test as before – touc
count, only overlapping does – the engine only proposes which pairs to test. A placed tilemap is
static ground made of its merged solid cells (see [Tilemaps](Tilemaps)). `Move` with
*Collide with solids* works as described in [Colliders](Colliders): the object stops flush at what
is in its way and slides along it.
is in its way and goes on along its surface, less what friction takes – the same friction a
dynamic body feels, combined the same way (the average), and the same in Play and the editor run.
A dynamic body that stops such a move gets **pushed** along the move, as one body meeting another
A dynamic body that stops such a move gets **pushed** into the contact – along its normal, so a
crate hit at its corner is shoved away from the corner, not along the move – as one body meeting another
without bounce: the speed it is pushed with is the move spread over the frame, and how much of it
the body takes depends on the two masses – a mover as heavy as a crate shoves it along, a crate
forty times heavier hardly shifts (measured: 133 of 161 units in a second, and 0.2). The mover
@@ -110,9 +112,10 @@ are placed, because what the editor run moves is saved. The nodes above do nothi
## What is not there yet
- `Move` walking up slopes and over steps (#150). Rapier's character controller was measured
for it in #147 and not taken: it caught on the seams between boxes lying side by side (10 of 100
steps along a floor of 12 crates) and kept a gap of up to 1.7 units, where `Move` catches on
nothing and stops flush.
- `Move` stepping up onto a ledge, or a limit on how steep a slope it walks up – that is for the
graph, with `hit` and `normal` (#150). `Move` does not use Rapier's character controller: in #147
it caught on the seams between boxes lying side by side (10 of 100 steps along a floor of 12
crates), kept a gap of up to 1.7 units and knows no friction; `Move` works out its contacts itself,
in double precision, and slides over the same seams without catching (#150).
- The simulation in the editor run (#148), and `On collision stay` with contact data (#149).
- Capsules and polygons, collision layers, raycasts, joints, one-way platforms (#144).