Scene editing: transform rows and the matrix chain; Colliders: oriented boxes; 758 checks (#76)

admin committed 2026-09-13 17:25:09 +02:00
1 parent 288e156e68
commit 359b366f4b
5 files changed
+35 -14

No files matched your search

+2 -2
@@ -95,7 +95,7 @@ shown on the node and editable in the inspector.
- **Node set** (`src/core/graph/nodes/`, one file per node): events `On start`, `On enable`, `On update`, `On disable`, `On destroy` (see *Lifecycle*), `On key down/up/held`,
`On timer`, `On collision enter/stay/exit`, `On click` (the mouse pressed on the owner — anywhere when it has none), `On animation finished`, `Start` · flow `Branch`, `Sequence`, `Repeat`, `Run behavior`, `End` · data `Self`, `Owner`, `Parent`, `Object by name`, `Name of`,
`Is asset` (is this object an instance of asset X — how you tell what you collided with),
`Variables`, `Properties`, `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`, `Add/Subtract/Multiply/Divide`,
`Variables`, `Properties` (every property row of the target's type, the transform rows `scale`, `rotation`, `mirrorX`, `mirrorY` included — how a graph turns a sprite), `Get position` (local and world), `Is enabled`, literals `Number`/`Text`/`Boolean`/`Texture`, `Add/Subtract/Multiply/Divide`,
`Min/Max/Abs/Sqrt/Clamp`, `Floor/Ceil/Round`, `Modulo`, `Random`, `Time`, `To text`, `To number`, `Join text`,
`Substring`, `Find text`, `Equals`,
lists `List` (a constant), `Fill` (one value repeated — how an empty board is made), `Count`,
@@ -104,7 +104,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` ·
actions `Move` (dx, dy, scale, dt — connect an event's dt for per-second movement), `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), `Set position`, `Spawn`
(asset setting + x/y/z, optional parent → outputs the new object), `Destroy`, `Log`, `Stop game`, `Restart game`. 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
+10 -4
@@ -6,8 +6,13 @@ sprite, parented at its centre. Colliders can be reused across objects, overridd
and stacked (hitbox + hurtbox on one sprite; colliders of the same body never collide with each
other). They are editor symbols: green outlines in edit mode, invisible in play mode.
A collider takes the transform of its body: a box turns and grows with the sprite it sits on, a
circle grows (see *Transform* in [Scene editing](Scene-editing)). A turned box is tested as the
box it is — separating axes, not its upright frame — so a wall turned by 45 degrees blocks along
its slant.
Play runs a `CollisionSystem` (`src/runtime/systems/CollisionSystem.ts`) before the behaviors:
every frame it tests all collider pairs (world positions applied; touching edges do not count)
every frame it tests all collider pairs (world transforms applied; touching edges do not count)
and sorts them into *enter*, *stay* and *exit*. Detection never pushes anything apart. A
behavior receives `On collision enter/stay/exit` for the colliders of its **owner** (its
parent; itself when top-level): the outputs are `other` (the other body), `other collider`
@@ -16,9 +21,10 @@ 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 and, after each axis, pushes
its *solid* colliders back to just touch any solid collider they ran into — so the other axis
still moves and the object slides along walls. Its `blocked x` / `blocked y` outputs tell which
(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). 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
+1 -1
@@ -6,7 +6,7 @@
| **Shader** | A node graph that becomes the GLSL a sprite is drawn with, edited in its own tab. A sprite wears it through `shader`; the numbers the graph declares are rows below that dropdown. See [Shaders](Shaders). |
| **Text** | An asset that draws a line of text (`text`, `fontSize`, `color`, `align`) — titles, HUD readouts, button labels. Its content is an ordinary property, so a `Properties` node of type Text with `text` switched to Set changes it while playing. |
| **Enabled** | Every placeable asset has the switch `enabled` (default on). A disabled object is not drawn, does not collide and its behavior does not run — **and neither does anything parented below it**. Switching one instance off is a normal override, which makes a container a *screen*: switch it on to show it, off to hide it. From a graph, `Properties` with `enabled` switched to Set does it, and `Is enabled` asks (including the parents); a behavior hears the switch as `On enable` / `On disable`. |
| **Scene object** | An asset placed into a scene: a link to the asset + a **position** (`x`, `y`, `z`) + sparse **overrides** of the asset's properties + an optional **parent** (another object of the scene). Scenes cannot be placed. |
| **Scene object** | An asset placed into a scene: a link to the asset + a **position** (`x`, `y`, `z`) + sparse **overrides** of the asset's properties + an optional **parent** (another object of the scene). Drawn objects also carry **transform** rows (scale, rotation, mirror) — properties like any other, and they cascade to children the way a position does (see [Scene editing](Scene-editing)). Scenes cannot be placed. |
| **Container** | A reusable group ("prefab"): the asset holds a **template** of objects (asset link, local position, overrides, parent links) centred on its origin. Placing a container stores the container object **and nothing else** — its content is resolved from the template on every read, so a template edit reaches every instance at once. **Create container** turns the current selection into such an asset: the selected objects move into the template and the scene keeps one placed container at their centre (world positions unchanged). The template is edited in the **Container** tab (see [Containers](Containers)) or from an instance through **Apply to container**. |
| **Selection** | One or more scene objects. Click selects, `Ctrl`/`Shift`-click adds/removes, dragging on empty scene space draws a rubber band (objects whose position lies inside are selected). Dragging a selected object moves the whole selection; `Delete` removes it; the inspector offers **Create container** and **Remove** for several objects. |
| **Behavior** | Logic as an asset: a **node graph** plus the **variables** it declares. Placed like anything else (a diamond symbol in the scene); its parent is the **owner** the graph acts on. |
+3 -3
@@ -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 # 742 editor checks, screenshots in /tmp/gm-e2e-*.png
node tools/seed-e2e.mjs && node tools/e2e.mjs # 758 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
@@ -63,7 +63,7 @@ to touch it.
The e2e run is a list of steps in `tools/e2e/` (hierarchy, inspector, play, assets, drawing,
graph, parenting, colliders, containers, text/enabled, menus, textures, frames, overflow, aspect,
container tab, run-in-editor, pause, nested behaviors, key repeat, list tab, struct variable,
variable nodes, property nodes, animations, state machines, console, shaders) that build on each other's
variable nodes, property nodes, animations, state machines, console, shaders, transforms) that build on each other's
scene state and share the CDP driver in `tools/lib/harness.mjs` — `press`, `undo`, `play`,
`object(id)`, `world()`, `mutate(js)` and friends, so a check reads like the interaction it
exercises.
@@ -151,4 +151,4 @@ widths to find click points. The **render device is not in this repo** — `/dev
and a 1 GB `/dev/shm` come from `container.options` in the runner's own `config.yaml`, which
applies to every job on that runner. Without them chromium falls back to llvmpipe, which gets
WebGL 1.0 only. Measured in the job container: `ANGLE (Intel, Vulkan 1.3.230, HD Graphics 630)`,
WebGL 2.0, and the same 742 checks as here.
WebGL 2.0, and the same 758 checks as here.
+19 -4
@@ -20,12 +20,27 @@ Scene tab and the frame names of the Texture tab — read that size from one tok
`z` is drawn on top; objects with equal `z` keep their hierarchy (tree) order. It is edited in the
inspector next to X/Y, undoable like any move, and applies in edit and play mode alike.
**Transform.** Sprites, texts, colliders and containers carry four more rows next to their
position: **Scale** (one factor, default 1), **Rotation** (degrees, positive turns clockwise on
screen), **Mirror X** and **Mirror Y**. They are ordinary properties — set on the asset, overridden
per object, gold when they deviate, writable from a graph through `Properties` — and they
**cascade like a position**: a child is placed inside its parent's turn and size, so a whole group
turns around its parent's origin (`src/core/properties/transform.ts` composes the chain as
matrices; `src/core/model/matrix.ts` is the arithmetic). Cameras and behaviors have no rows of
their own — they are placed by the chain but never turned or sized (the camera's view stays
upright) — and the editor's icons stay upright and unscaled too. The selection outline follows
the corners; dragging an object moves it in its parent's frame, so a child under a parent turned
by a quarter walks down the screen when dragged along its own x. `Move` and `Set position` work
in the object's own axes as well.
**Hierarchy (parenting).** Every scene object may have a parent in the same scene
(`SceneObject.parentId`). Positions — including `z` — are **local to the parent**; the world
position is the sum along the chain (`src/core/model/hierarchy.ts`). Children move with their
parent and are removed with it (also when the parent's asset is deleted); undo brings the whole
subtree back. Re-parenting keeps the world position by recomputing the local one, and cycles
are rejected. In the **Scene objects** panel children are indented below their parent; drag a
position is the parent's transform applied to the local one, all the way up (`z` simply adds).
Children move with their parent and are removed with it (also when the parent's asset is
deleted); undo brings the whole subtree back. Re-parenting keeps the world position *and look*:
the local position is recomputed, and when the new parent chain is turned or sized differently
the object's transform rows absorb the difference (between two upright parents nothing is
written). Cycles are rejected. In the **Scene objects** panel children are indented below their parent; drag a
row onto another row to parent it, onto the empty space below the rows (or right-click →
Unparent) to move it to the top level; the inspector offers the same as a **Parent** menu.
Dragging into the **gap** above or below a row — its outer quarter, marked with a line instead of