Collision layers and masks: Physics and Colliders describe the layer and the mask of layers a collider meets, that a pair only meets when both masks name the other's layer, and where the names are edited - "Collision layers..." in the toolbar's Project menu, as one undo step, up to 16 of them; API names the project field collisionLayers, Architecture lists collisionLayers and CollisionLayersDialog, Concepts and Tilemaps name the two new inspector rows, Development carries the check sum 1138

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
adminandClaude Opus 5 committed 2026-09-20 15:31:31 +02:00
1 parent b6614c51e4
commit 0b7f0c2ff5
7 files changed
+44 -4

No files matched your search

+4
@@ -32,6 +32,10 @@ of admission. See [Accounts](Accounts).
Which editor plugins a project switches on is the project's own field `plugins` (#119); the server
has no switch of its own and hands out every plugin file it has.
The named collision layers are the project's field `collisionLayers` (#152), a list of up to 16
names of which the first one is layer 0. A project file without it has the single layer *Default*,
which is what every collider is on, so the field needs no format bump either.
Every project document carries `formatVersion` (`PROJECT_FORMAT_VERSION` in `core/model/Project.ts`,
currently 2). There are no load-time migrations: `serialize.ts` reads what it recognises and drops
the rest.
+1 -1
@@ -68,7 +68,7 @@
```
web/index.html Vite source page (dev) index.html built page (generated, served)
src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, and where one sits in the world), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead)
src/core/model/ Project (object holders), Asset + AssetTypes, SceneObject, Properties (value types), hierarchy, walk (generic parent/child walks), instance (container content resolved from its template), usages (where an asset is used), container (the cycle guard), placement (what may be dropped where), texture (frames, the slice grid, a frame's collision outlines), polygon (an outline checked, cut into convex pieces and clipped to a rectangle), tile (the three kinds and their block shapes, reading a tile's pictures, the animation step), autotile (which quarter tile a neighbourhood asks for, the 47/16 tiles a block produces, 1 for a plain tile), tilemap (the cells of a layer, which neighbours a cell is joined to, the cells a stroke or a rectangle covers), tilemapCollision (the solid cells of a map, drawn in or out by their tile's collision scale and merged into rectangles, the outlined cells as polygons — an autotile's outlines cut into its quarters and pieced together per cell like its picture —, and where one sits in the world), animation (which frame at time t, a row's value there, what a row may aim at, the players on an object; KeyRef and its lookups) + animationPreview (the root at a frame, written into copies), graphLookup (what a graph sees of the project: behaviors to run, state machines to read), Scope (where a state machine's current state lives: per instance or one for the run — variables are shared by living on a Variables asset instead), collisionLayers (which colliders may meet: the project's named layers, a collider's layer and mask, and the one rule `layersMeet` the engine, Solids and the event pair search all answer by)
src/core/properties/ PropertySchema, kinds (one row per value kind), listItems (an item's kind, read off its value), resolve (the only override merge), deviation (what is gold), collider/camera/bounds geometry (with the side a one-way collider blocks from), convex (capsules and polygons: rounded convex pieces against each other), ray (where a ray meets a shape, in double precision), rigidbody (what the engine reads off a rigidbody, a collider's material and the scene), aspect, enabled, editorRun, assetRef/textureRef (how a reference reads), declaredRows (the row a Property node declares in a shader or particle graph, and the params that node has in both)
src/core/commands/ undoable commands, CommandStack, SetBagValueCommand base, RenameCommand base (undone by renaming back: variable, struct field, port), one import command shape for texture/font/sound, require.ts (validated lookups, and what an instance may not change), naming.ts
src/core/graph/ graph model, NodeRegistry/NodeContext/values, interpreter, dropTarget, nested (behaviors as nodes),
+21
@@ -104,6 +104,27 @@ from above:
Tiles can be one-way as well (see [Tilemaps](Tilemaps#one-way-tiles)).
## Collision layers
A project has up to 16 named **collision layers** (#152), edited under **Project → Collision
layers…**; a new project has one, *Default*. Every collider and every tilemap names the layer it
sits on (**Layer**) and the layers it collides with (**Collides with**, a tick per layer).
**Two things meet only when each one names the layer of the other.** The rule is symmetric, as in
the physics engine itself, so there is no pair that collides only one way: giving the player's
shots their own layer and leaving the player out of their *Collides with* is enough — the player
does not have to be changed as well, but the shots have to name what they *should* hit.
- The default is layer 0 against every layer, which is what every collider written before there
were layers reads as. Nothing collides differently until a layer is named.
- One setting decides both: what does not collide reports no event either. To be notified without
being blocked, use **Trigger (no blocking)**.
- It filters everything alike — the engine in Play, `Move` with *Collide with solids*, and the
collision events of the editor run.
- A ray is not filtered by layers yet, that is #158.
- Only the *last* layer can be removed in the dialog: a collider names its layer by number, so
taking one out of the middle would move every collider below it onto another layer.
## Raycast
The **Raycast** node (#153) casts a ray from `from x/y` along `dir x/y` (any length, it is made a
+1
@@ -17,6 +17,7 @@
| **Variables asset** | Declarations and nothing else — no graph, no position, never placed. A `Variables` node naming it reads and writes **one value per key for the whole run**, however many graphs point at it. The values start at the declared ones on every Play and are never written back, so playing cannot change the project; while a run is on the asset's inspector shows them live and an edit there reaches the run at once. Two such assets may declare the same name without meeting. See [Behaviors](Behaviors). |
| **Run in editor** | A switch only behaviors have (default off). With it on the behavior keeps running while the project is **not** playing — and unlike Play it then edits the *saved* model: what it moves or writes is a real change, autosaved and not on the undo stack. See *Running without Play*. |
| **Collider** | A shape (box or circle) for collision detection, placed as a **child** of the object it belongs to (its *body*). Drawn as a green outline in edit mode only. |
| **Collision layer** | One of up to 16 named layers of the project (**Project → Collision layers…**). A collider or tilemap names the layer it is on and the layers it **collides with**; two of them meet only when each one names the layer of the other. A new project has one layer, *Default*, which every collider is on and meets — so nothing collides differently until a layer is named. See [Colliders](Colliders#collision-layers). |
| **Rigidbody** | Placed as a **child** of an object, it hands that object to the physics engine while playing: it falls with the scene's gravity, bounces and rests on colliders, pushed by what it meets. At most one per object. See [Physics](Physics). |
| **Override** | A per-object value that replaces the asset's value for one property. Only changed keys are stored; the inspector paints their label gold and offers a reset (↺) and an **apply** (↑) that writes the value into the asset instead. Both appear only where the value really differs from its base — an override carrying the asset's own value is none. |
| **Default** | Every property has a schema default. Asset values that differ from it are marked gold in the inspector and can be reset (↺) the same way an override can. The asset's name in the **Assets** panel carries the same gold while any of them does (`assetDeviates`, `src/core/properties/deviation.ts` — the inspector marks the rows of that same schema, so the list cannot claim a deviation the inspector then fails to show). A behavior's variables are not in it: their default is declared on the variable itself, so the asset value cannot differ from it. |
+1 -1
@@ -13,7 +13,7 @@ npm run check # typecheck + lint + format:check + tests, the gate CI runs
npm run build # writes dist/ and regenerates index.html
./build.sh # the same under `set -o pipefail`, for callers that pipe the output
php tools/make-user.php <name> <password> # an account; every tool signs in (see Accounts)
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1129 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 1138 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
+12 -2
@@ -120,6 +120,16 @@ body shot up from below comes out on top and lands, one sliding sideways goes th
falling at 2000 units per second lands with and without CCD. Collision events come on passing
through as well: overlapping is colliding.
## Collision layers in the engine
Rapier has the rule built in: every collider gets an **interaction group**, its layer in the high
16 bits and its *Collides with* mask in the low ones, set as both the collision group (what is
detected) and the solver group (what is pushed apart). The engine meets a pair when each side's
membership passes the other's filter, which is exactly what
[`layersMeet`](Colliders#collision-layers) says — so a pair that the layers keep apart is not
reported either, and nothing extra has to be filtered afterwards. Changing a layer rebuilds the
body's colliders, since the group sits on the collider itself.
## Moving bodies from a graph
| Node | What it does |
@@ -157,5 +167,5 @@ are placed, because what the editor run moves is saved. The nodes above do nothi
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).
- Collision layers (#152) and joints (#154). Raycasts (#153) and one-way platforms (#155) are
there, see [Colliders](Colliders#raycast).
- Joints (#154), and a ray filtered by collision layers (#158). Raycasts (#153), one-way platforms
(#155) and the collision layers themselves (#152) are there, see [Colliders](Colliders#raycast).
+4
@@ -150,6 +150,10 @@ that blocks (#173): where two rectangles or the pieces of outlined cells meet, n
so a wall of several rectangles or a lake of many pieces shows as one shape. Collision itself keeps
the pieces.
A placed tilemap carries **Layer** and **Collides with** like a collider does, for the whole map
(see [Colliders](Colliders#collision-layers)): ground only some things walk on is a map on its own
layer. Per layer of the map or per tile it is not — a cell belongs to the map.
### Collision scale
A tile's **Collision scale** (1 by default, 0.1 to 2) makes its cells block less or more than