The layer, frame and shape lists still had their add and delete
buttons scattered below the list, each in its own layout. They now
live in the shared SortableList, so every list works the same way.
- SortableList gets an optional header with the title on the left
and a + on the right, plus an optional x per row next to the eye.
- The keepOne option hides the x on the last remaining row, so a
pixel art image and a tilemap always keep one layer.
- Pixel art layers and frames and tilemap layers use header, + and
x. Only Merge down and Duplicate stay below the lists.
- Vector art shapes get the x but no +. Add shape stays below the
list and Delete shapes stays for a multi-selection.
- The section headings in the pixel art column use the same style
as the list titles and the inspector.
Findings:
- The wait loop added to check 47i for #196 only waited for the
right half of the shape. If the texture landed between the two
reads, the left half was still blue and the check failed. It now
waits for both halves.
Tests:
- ./check.sh: 138 files, 2052 tests, exit 0.
- GM_STEPS=39,40,47,48 (fresh): ALL PASSED, 156 checks (was 154).
- GM_STEPS=47,48 (fresh, final): ALL PASSED, 86 checks.
- Two new e2e checks: x on a shape (47) and x on a frame (48). The
tilemap check in 40 now uses the x instead of Delete layer.
- Counter-check: with the x not reporting to its owner, exactly the
two new checks fail.
- Check total 1266 in README and wiki is computed (1264 + 2).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Reordering shapes by dragging worked well in the vector tab. Pixel
art layers and frames and tilemap layers still used up/down buttons.
All three lists now share one widget built from the shape list.
- SortableList in the UI toolkit, with the drag rules next to it in
src/ui/sortable.ts. A row has an optional thumbnail, a name, a hint
on the right and an eye toggle. Every drop is one undo step.
- The shape list is a thin wrapper around it. shapeRows.ts is gone,
its tests moved to the new rules.
- Pixel art layers show the layer alone on the current frame, frames
show the whole frame. Up/Down, Hide, Earlier and Later are gone.
- Tilemap layers get the eye instead of the checkbox. The per-row
up, down and delete buttons are replaced by Delete layer next to
Add layer.
- A row is selected on release instead of press. Selecting rebuilt
the inspector or tilemap panel and cancelled a drag that started
on a row not yet selected.
Tests:
- ./check.sh: 138 files, 2052 tests, exit 0 (before 137 / 2051).
- GM_STEPS=39,40,47,48 (fresh seed): ALL PASSED, 154 checks.
- 5 new e2e checks. Check total 1264 in the README is computed
(1259 + 5), CI never measured 1259.
- Counter-checks bite: dropMove ignoring reversed fails a unit test,
a tilemap drop not applied fails the drag check, a layer thumbnail
showing all layers fails the thumbnail check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Issue #193 asked where friction, bounciness and density of a tile
live. Tiles had a fixed surface (friction 0.5, bounciness 0) and a
collider could not use the outline drawn on a texture frame (#141).
Both gaps are closed here.
- A collider has a fifth shape "Frame collision" with a Frame row.
The frame is picked like a sprite's texture and stored as the
value 'frame'.
- The shape is linked, not copied. Redrawing the outline in the
texture tab changes every collider using that frame, in the editor
and on the next play.
- The frame centre sits on the collider origin, one image pixel is
one unit. The shape turns, scales and mirrors like any other.
- Every outline ring blocks. Each ring is split into convex parts the
same way a polygon is.
- Without a frame, with a whole texture or with a frame that has no
outline, the collider is drawn red as a box and blocks nothing.
- Move, Rapier, raycast, detection, hit test, drawing and footprint
all resolve the frame. At runtime an entity looks the frame up
live in the current project.
- A tile has Friction and Bounciness next to Solid and One-way. The
rows only show when Solid is on. Defaults and mixing rules match
the collider. Density is left out because a tilemap never moves.
- Tilemap cells are grouped by surface as well as by one-way angle.
Ice and stone never merge into one rectangle, and every cell brings
its surface to Move and Rapier.
Findings:
- A tile with a collision scale below 1 is no longer flush with a
neighbour of another surface. It is inset there like at an open
side, the same as next to one-way tiles today.
- A bounce test first measured 0.84. The ball had landed on the hero
at the origin, not on the tile. On the tile it measures 0.993.
Tests:
- ./check.sh: 130 files, 1931 tests, exit 0 (was 129 / 1917).
- 14 new unit tests in tests/core/surface.test.ts. The Move tests run
against the editor run and the physics system.
- 3 new e2e checks: 40l (surface rows follow Solid), 42e (collider
has frame size, a walker climbs the slope in the frame).
- GM_STEPS=1-8,39-43 freshly seeded: ALL PASSED, 376 checks.
- Three counter-probes bite: Move ignores the tile surface (2 fail),
Rapier ignores it (1 fails), frame shape falls back to a box
(5 fail).
- Check sum 1198 is computed (1195 + 3), the full run belongs to CI.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>