Physics stage 1b: Moving bodies from a graph and pushing with Move in Physics, the new nodes in Behaviors, blocking in Colliders, Architecture, check total 1055 (#147)

admin committed 2026-09-18 19:19:22 +02:00
1 parent f6904445d8
commit d6190ec3ee
5 files changed
+42 -10

No files matched your search

+2 -1
@@ -86,7 +86,8 @@ src/runtime/ World, Entity, System interface, Input tracker, Collision
Audio (the AudioSink a run plays into — the counterpart of Input on the way out)
src/runtime/physics/ engine (loads Rapier once, reports its state, converts between pixels and metres),
PhysicsSystem (Play's collisions: one engine body per rigidbody, free collider and tilemap,
the fixed step, the poses handed back, the contact pairs sorted into events)
the fixed step, the poses handed back, the contact pairs sorted into events; the push a
`Move` gives a dynamic body, and what the physics nodes reach through `NodeContext.physics`)
src/ui/ widget toolkit (generic, knows nothing about assets)
src/viewport/ view (transforms), items (what to draw), symbols (how to draw it),
FileLibrary (load once, never retry a failure, tell the listeners) ← TextureLibrary, FontLibrary;
+3 -2
@@ -107,14 +107,15 @@ shown on the node and editable in the inspector.
`Join lists`, `Index of`, structs `Pack`/`Expand`,
`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`, `Current state` (name and id), `Is in state` (one boolean per state) ·
`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`
(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
direction; every play is a voice of its own, so two in a row overlap, and the voice ends with the
behavior that started it unless `detached` says otherwise),
`Load scene` (swaps the running scene once the frame is over — see [Scenes](Scenes)), `Log`, `Stop game`, `Restart game`. A node type is one file exporting a `NodeDef`
`Load scene` (swaps the running scene once the frame is over — see [Scenes](Scenes)), `Log`, `Stop game`, `Restart game`,
`Apply force`, `Apply impulse`, `Set velocity`, `Set angular velocity`, `Set gravity` (see [Physics](Physics)). A node type is one file exporting a `NodeDef`
(ports, settings, `run`), listed in `nodes/index.ts`; palette, ports, validation and
inspector fields are generated from it. A port may declare a `default` for its inline value
(e.g. `Move.scale = 1`), shown on the node and in the inspector.
+2 -1
@@ -28,7 +28,8 @@ object stands for its collider children, a collider for itself. The geometry liv
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). A collider with
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
**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 # 1052 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1055 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
+34 -5
@@ -68,8 +68,35 @@ wherever the object is put, nothing pulls on them, and dynamic bodies bounce off
Whether two of them touch is decided by the same overlap test as before – touching edges do not
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); a dynamic body blocks it like a
wall.
*Collide with solids* works as described in [Colliders](Colliders): the object stops flush at what
is in its way and slides along it.
A dynamic body that stops such a move gets **pushed** 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
weighs what its rigidbody's **Mass** says, or else what its colliders weigh by their density; a
dynamic mover what the engine says. The mover stays flush in front of the crate and never gets
into it.
## Moving bodies from a graph
| Node | What it does |
|---|---|
| **Apply force** | Pushes with a force (units × kg per second²) for the length of the frame, so from `On update` it pushes steadily at any frame rate. A heavier body speeds up less. |
| **Apply impulse** | A kick at once (units × kg per second): the velocity changes by impulse ÷ mass. A jump, a hit, an explosion. |
| **Set velocity** | Sets the speed in units per second, whatever the body weighs. For one direction only, feed the other from `Get velocity`. |
| **Set angular velocity** | Sets the turning speed in degrees per second, counted like the rotation. A body with *Fixed rotation* does not turn, and does not start to later either. |
| **Get velocity** | Speed (x, y) and turning speed (degrees/s); 0 for an object without a dynamic body. |
| **Set gravity** | The scene's gravity for the rest of the run; the next scene starts with its own, and the project keeps its value. |
The object is the one the rigidbody sits on (the rigidbody itself works too). They act on a
**dynamic** body only; on anything else they do nothing, and the console says so once per object:
`Physics: "Player" has no dynamic rigidbody, so Apply force, Apply impulse and Set velocity do
nothing on it.` They work from `On start` on.
`Move` and `Set position` on a dynamic body put it somewhere else and it keeps its velocity; the
first time the console points to the nodes above, which move it with the physics.
## Play and the editor run
@@ -79,11 +106,13 @@ the console say why. There is no falling back to the old detection: a game would
differently.
A behavior with *Run in editor* sees collisions but no simulation: dynamic bodies stay where they
are placed, because what the editor run moves is saved.
are placed, because what the editor run moves is saved. The nodes above do nothing there.
## What is not there yet
- Forces, impulses and velocities from a graph (`Apply force`, `Apply impulse`, `Set velocity`, …)
and `Move` over the engine's character controller – slopes, steps, pushing boxes (#147).
- `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.
- 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).