From 359b366f4b0ef2d66b39fb2b20b71600a82e0d85 Mon Sep 17 00:00:00 2001 From: meleki Date: Sun, 13 Sep 2026 17:25:09 +0200 Subject: [PATCH] Scene editing: transform rows and the matrix chain; Colliders: oriented boxes; 758 checks (#76) --- Behaviors.md | 4 ++-- Colliders.md | 14 ++++++++++---- Concepts.md | 2 +- Development.md | 6 +++--- Scene-editing.md | 23 +++++++++++++++++++---- 5 files changed, 35 insertions(+), 14 deletions(-) diff --git a/Behaviors.md b/Behaviors.md index b02cd40..cd55336 100644 --- a/Behaviors.md +++ b/Behaviors.md @@ -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 diff --git a/Colliders.md b/Colliders.md index a2ba41f..9021d47 100644 --- a/Colliders.md +++ b/Colliders.md @@ -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 diff --git a/Concepts.md b/Concepts.md index 71b951d..18fa3fe 100644 --- a/Concepts.md +++ b/Concepts.md @@ -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. | diff --git a/Development.md b/Development.md index 3ed2ec4..915a848 100644 --- a/Development.md +++ b/Development.md @@ -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. diff --git a/Scene-editing.md b/Scene-editing.md index 5ec81b1..1a94555 100644 --- a/Scene-editing.md +++ b/Scene-editing.md @@ -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