Name, opacity and Z each sat in a separate field below or beside
the list. You had to select a row first and then look elsewhere to
change it. Duplicating was a separate button or not possible at all.
Now the row itself carries everything.
- SortableList gets three optional callbacks. onDuplicate shows a
copy button between the eye and the delete button. onRename opens
a text field on double click, Enter or a click outside commits,
Esc cancels, an empty name is ignored. value puts a NumberField
into the row.
- Dragging on the number changes the value and never moves the row.
A whole drag is one undo step, the canvas follows live.
- Copy and delete only show on the hovered and the selected row.
Their space stays reserved, so nothing jumps on hover.
- Pixel layers: copy right above with the same pixels on all
frames, rename, opacity in percent. The Name and Opacity fields
below the list are gone.
- Pixel frames: copy right after, replaces the Duplicate button.
- Tilemap layers: copy with cells, Z and visibility, rename, Z in
the row. The Name and Z fields in the panel are gone.
- Vector shapes: copy at the same place and select it, rename.
- New in core: DuplicateVectorShapeCommand,
DuplicateTilemapLayerCommand, withLayerDuplicated and copyName
("X copy", "X copy 2"). copyName lives in src/core/util/names.ts
because the model never imports from commands/.
Findings:
- Double clicking a shape that is not selected yet only selects it.
The first click moves the list up, so the second click misses the
name. Module 47 selects the row first.
Tests:
- ./check.sh: 138 files, 2055 tests, CHECK EXIT 0 (before 2052).
- GM_STEPS=47,48 (fresh, twice): ALL PASSED, 91 checks (before 86).
- GM_STEPS=39,40,47,48: module 40 green, 47 died on the rename
check, which was a test fault and is fixed.
- Counter probes: a constant copyName fails the shape and pixel
layer tests. A dead copy hit fails the copy check in 47.
- Check total 1272 in README and wiki, computed (1266 + 6).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
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>
CI has been red since 66d6aaf (tasks 221 and 223, two failures each).
Both failures come from the tests added with #130, not from the
editor.
- Module 04 still expected 19 asset types in the + menu. Pixel art
and Palette make it 21.
- Module 48 died with "reading 'assets'". The password change in
module 45 ends every other session of the e2e account, including
the one the tools had cached, so api() got a 401. api() now drops
the cached session and signs in once more on a 401.
Tests:
- GM_STEPS=4,45,48 (fresh seed): ALL PASSED, 102 checks.
- Counter-check: without the retry module 48 dies with exactly the
CI error.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
After signing out and back in, a texture fill stayed in its base
colour until something else redrew the vector tab. The texture
library is a singleton that outlives the editor. Four widgets
subscribed to it and never unsubscribed. After a new sign-in the
first loaded texture called a destroyed canvas, which threw and
stopped the loop before the vector tab heard about the image.
- TextureCanvas, TileCanvas, ShaderPreview and WorldViewport now
unsubscribe in destroy(). This is the pattern FontPreview and
VectorArtCanvas already use. WorldViewport leaves both the
texture and the font library.
- FileLibrary.notifyChange catches errors per listener. A broken
listener no longer starves the others. The error is still
logged as "[textures] a listener failed".
- e2e 47i no longer forces a redraw. It waits up to 3 s for the
tab to show the texture on its own.
Findings:
- The trigger was e2e module 45 (sign out, sign in), not the
texture fill. My note in #196 that the browser showed no
exception was wrong. The counter-check shows it clearly.
Tests:
- ./check.sh: 137 files, 2051 tests, exit 0 (before 136 / 2049)
- 2 new unit tests in tests/viewport/file-library.test.ts
- GM_STEPS=45,47 after a fresh seed: ALL PASSED, 71 checks
- Counter-check with the old source and the new check: 1 FAIL
(shape stays #0000ff) plus the TypeError. Old FileLibrary.ts
fails the first unit test.
- No full e2e run locally. The check count is unchanged.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Textures are uploaded files named by their content, so they cannot
be edited in place. Pixel art is therefore its own asset type, like
vector art. Its pixels live in the project and the tab rasters them
into a texture that sprites, tiles and animations can use.
- New asset types Pixel art and Palette. The first pixel art of a
project gets a palette with the 16 PICO-8 colours in the same undo
step. Every pixel is a palette index, 0 is transparent.
- Each layer on each frame is its own file data/pixels/<sha256>.px.
The project only holds the names, so undo swaps names. The browser
computes the hash and uploads new cels before the project is saved.
- New route POST ?r=pixels in api/lib/PixelStore.php. It answers 201
and 422 for broken data. data/pixels/ is git-ignored.
- Tools: pencil, eraser, fill (contiguous or all pixels of a colour),
eyedropper, line, rectangle and ellipse (outline or filled), brush
size 1 to 8.
- Layers can be added, deleted, renamed, reordered, hidden, faded and
merged down. Frames can be added, duplicated, deleted and reordered,
with onion skin and a small playing preview.
- Zoom 1x to 32x in whole steps, grid from 8x, a 3x3 tile preview and
Resize... with a 3x3 anchor.
- All frames sit side by side in one image. Each frame is a region
like a cut texture frame, so texture rows offer "Asset / Frame 2"
and the context menu has Create animation from frames.
Decisions:
- Merging down drops the opacity of the upper layer. Indexed colours
know no blends.
- Only the last palette colour can be removed. Otherwise the indices
of every image using the palette would shift.
- Undo stores the whole layer per stroke instead of pixel diffs.
- Alt+click picks a colour. It is a mouse gesture, not a shortcut.
Follow-up issues: #197 import from texture, #198 selection and
flipping, #199 PNG export, #200 more tools.
Tests:
- ./check.sh: 136 files, 2049 tests (before 135 / 2011). 34 new in
tests/core/pixel-art.test.ts, 4 from the icon tables.
- e2e module 48 fresh: ALL PASSED, 29 checks, console empty.
- Counter-probes bite: five on the unit tests, upload order (2 tests
fail) and cels not uploaded in the browser (1 check fails).
- Check total 1259 in README is computed (1232 measured in CI + 27).
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
CI run 217 for 592ea94 failed three checks in block 47i. In the full
run the texture list is longer than in a single module run, so the
picker click missed "halves" and the texture stayed empty.
- The check now types "halves" into the menu search field and picks
it with Enter, independent of the list length.
- FIND_HALVES looks the image up by name. Matching only by size had
picked the 32 px image "wedge" in the full run.
- The texture fill check forces a redraw before reading pixels. After
the work of earlier modules the vector tab does not redraw once the
texture has loaded. That bug is tracked in #196, and the forced
redraw goes away with it.
Tests:
- ./check.sh: CHECK EXIT 0
- npm run build: BUILD EXIT 0
- GM_STEPS=47 (fresh seed): ALL PASSED, 54 checks
- Full run not repeated locally. CI decides.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A vector shape could only be filled and outlined with one flat colour.
Shading, glows and patterned surfaces had to be faked with stacks of
shapes. Every shape now has a paint type for its fill and its outline.
- Fill type: solid colour, linear gradient, radial gradient or
texture. Line type offers the same without texture.
- Colour stops are edited on a bar in the inspector, as agreed in the
issue. A click on the bar adds a stop in the colour the gradient
already has there, so adding never changes the picture. At least two
stops remain.
- The new Paint tool in the tab shows two handles for the selected
shape: axis start and end, centre and radius, or the corners of one
texture tile. "Handles: Fill / Outline" picks which paint is edited.
- Moving a whole shape moves its gradients and tile with it. Every
gesture is one undo step.
- A texture fill always tiles, because Pixi turns a non-repeating
pattern into a repeating one anyway. When switched on, one tile
covers the whole shape.
- Only uploaded textures and frames can be a fill, never a drawing.
A drawing filled with itself would rasterise itself mid-raster.
- The fingerprint of a drawing includes the texture and frame a fill
points to, so the picture redraws when the image is swapped.
Findings:
- graphics.destroy(true) also destroyed the textures of the fills.
With a texture fill it deleted the tile slice and the next redraw
crashed on reading 'scaleMode' of null. It is now destroy().
- The gradient cache destroyed old gradients itself, which gave 20
Pixi warnings "destroyed while still bound". They are now handed to
Pixi's texture GC with autoGarbageCollect switched on. The console
of module 47 is empty again, the last CI log had 0 of them.
Tests:
- ./check.sh: 135 files, 2011 tests, exit 0 (before 134 / 1986)
- npm run build: exit 0
- GM_STEPS=47, freshly seeded: ALL PASSED, 54 checks (before 44),
0 console warnings
- 25 new unit tests, 10 new e2e checks in block 47i
- Four counter-checks bite, originals restored and verified with cmp
- Check sum 1232 in README and wiki, calculated (1222 + 10)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Building a shape out of simpler ones meant drawing the result point by
point. The Vector Art tab now selects several shapes at once and
combines them with a boolean operation into one ordinary shape.
- Ctrl/Cmd+click in the tab and Ctrl or Shift+click in the shape list
add a shape to the selection. Shift stays pan in the canvas.
- A COMBINE block in the inspector offers Union, Subtract, Intersect
and Exclude. Subtract removes every upper shape from the lowest one.
- The result replaces the lowest selected shape and keeps its name,
colours, stroke and place in the order. It is one undo step.
- Curves are flattened to 0.2 canvas pixels, a circle gets about 48
points. Open lines and single shapes cannot be combined, the buttons
are off and a hint gives the reason. An empty result is rejected.
- Delete now removes every selected shape, and the selection drops
shapes that no longer exist.
- The geometry comes from clipper2-ts 2.0.1, pinned. It has no
dependencies and speaks even-odd directly. Only the engine module
is imported, the bundle grows by 55 kB instead of 110 kB.
Findings:
- clipper2-js was tried and dropped. It pulls in Angular 15 with a
high npm audit finding and no fix.
- Pixi finds holes by containment, not by even-odd depth. An island
inside a hole would be drawn as a hole again. Result rings are
emitted outer ring before its islands, and a test mirrors Pixi's
rule.
- A depth sort turned out to be dead code in a counter-check and was
removed. A pixel check in 47h stayed green without the subtract
and now checks both colours.
Tests:
- ./check.sh: 134 files, 1986 tests, exit 0 (before 133 / 1968)
- npm run build: exit 0
- GM_STEPS=47, freshly seeded: ALL PASSED, 44 checks (before 39)
- 18 new unit tests, 5 new e2e checks in block 47h
- Five counter-checks bite, originals restored and verified with cmp
- Check sum 1222 in README and wiki, calculated (1217 + 5)
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Until now only the editor toolbar could pause a running game. A pause
menu inside the game had no way to stop the world while its own graph
kept running, and there was no slow motion.
- Five new nodes: Pause game, Resume game, Is game paused, Set time
scale and Time scale. Pause and resume take effect on the next frame.
- A game pause stops world time, physics, animations, particles and
shader time. Only behaviors with the new "Run while paused" switch
keep running.
- Set time scale 0 freezes the world but graphs keep running, as
agreed in the issue.
- Play and Stop reset the time scale to 1, Load scene keeps it. The
pause belongs to the scene, so Load scene and Restart unpause.
- The toolbar pause stays separate and also freezes the menu. When the
game pauses itself the status line reads "The game paused itself".
- While paused, collision events are not reported, sounds keep
playing, and the editor run ignores both nodes with a log line.
Findings:
- The first status line was longer than any before and got cut with
an ellipsis. It is shorter now.
- The e2e harness did not know the key "p". It does now.
Tests:
- ./check.sh: 133 files, 1968 tests, exit 0 (before 132 / 1952)
- npm run build: exit 0
- GM_STEPS=18 fresh: ALL PASSED, 33 checks (before 26)
- 16 new unit tests: 8 catalog cases, game-pause.test.ts, editor run
- 7 new e2e checks in block 18i, driven by real keys in the game
- Six counter-checks all bite: behavior filter, collision lock, world
time, editor run, clock reset and the game clock in the browser
- Check total 1217 in README is computed (1210 + 7), CI measures it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Changing min, max or default of a property rebuilt the slider rows of
the shader preview, but the rows stayed at 0 x 0. Only the labels were
visible until the preview was resized by hand.
- Widget.resize only lays out when the size changes. The row count
stays the same on such an edit, so the row stack kept its height and
never laid out its new children.
- syncParams now relayouts the row stack itself after the column
around it.
Tests:
- e2e 28n: a new max rebuilds the slider at 136 x 24, a click at its
right end sets level 2. Red against the old build (w 0, h 0).
- GM_STEPS=28,31 fresh: ALL PASSED, 106 checks (was 105)
- ./check.sh: 132 files, 1952 tests, exit 0
- Check sum 1210 in README, computed (1209 + 1), CI measures it.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A shader could only be judged by placing it on a sprite and running
the scene. The shader tab now shows its effect next to the graph, on
the image the user picked last, without a picker to maintain.
- The preview follows the last selection that has an image: a placed
sprite with its overrides, size and tint, a sprite asset, a texture
or a frame. Clicking the shader, a node, a behavior or a scene keeps
the picture.
- Without such a selection, or when the picked image is deleted, a
built-in 64 px test image is shown. Its colour runs with u and v, a
checkerboard shows where a UV lands and the border fades for alpha.
- Each property gets a slider. The sliders belong to the preview and
never write to the project. They start at the sprite's values when
it carries this shader, otherwise at the default.
- The preview has its own clock. Hold and Restart are only enabled
when a Time node is in the graph.
- The graph | splitter | panel layout of the particles tab moves into
PreviewSplit, and both tabs use it.
- A nine-sliced frame is previewed unsliced. This is documented.
Findings:
- The slider row stayed squeezed to a few pixels when the properties
changed while the tab was hidden. The layout was triggered one level
too high. The slider check in block 28n holds this.
- The graph canvas is narrower now, 385 px at a 1280 px window, the
same as in the particles tab. Block 28b clicked at a fixed x+420 and
now uses the measured width.
Tests:
- ./check.sh: 132 files, 1952 tests (was 131 / 1941), exit 0
- npm run build: exit 0
- GM_STEPS=28,31 freshly seeded: ALL PASSED, 105 checks
- 11 new unit tests in tests/editor/shader-preview.test.ts
- 7 new e2e checks in block 28n
- Counter-probes: start values ignoring the sprite fail 1 unit test,
a preview ignoring the selection fails 2 e2e checks
- Check total 1209 in README is computed (1202 + 7), CI measures it
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drawing an outline by hand for every frame of a sheet is slow, and the
outline mostly follows the opaque part of the image anyway. The texture
tab now traces it: one click, a corner budget, and every Frame
collision collider and tile using that frame blocks with the result.
- "Generate collision..." sits in the texture tab header and is off
while the texture has no frames.
- The dialog has Max points (default 8, 3 to 64), Solid from ... %
alpha (default 50) and All frames of the texture. A live line says
what Generate will write, and Generate stays off when nothing is
solid enough.
- traceCollision walks each island of solid pixels along the pixel
edges, then drops the least important corners until at most N are
left and no stair step remains. A slope becomes one straight edge.
- An outline never crosses itself. Where the image would force that,
the island's convex hull is taken instead.
- Crumbs under 1 % of the frame area are dropped unless they are the
only island. Holes are not reproduced.
- Existing outlines are replaced. One frame or all frames is a single
undo step.
- TextureLibrary hands out the sheet's RGBA data, and a pure function
cuts a frame's region from it.
Findings:
- The wedge in the e2e fixture gets 4 corners, not 3. Its tip is one
pixel wide in the image, and that real edge stays while corners are
still free.
Tests:
- ./check.sh: 131 files, 1941 tests, CHECK EXIT 0 (was 130 / 1931).
- 10 new unit tests in tests/core/trace-collision.test.ts, including
150 random shapes that must all be valid and within the budget.
- GM_STEPS=42, freshly seeded: ALL PASSED, 20 checks (was 16).
- Three counter-probes bite: no hull fallback, no stair threshold,
dialog ignoring the alpha threshold.
- Check sum 1202 in README is computed (1198 + 4), CI measures it.
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>
The pen only pulled out the outgoing handle of a new point, because
the opposite handle was mirrored only if it already existed. The
first segment of every dragged point was therefore half a curve.
Beyond that bug, a point had no way to say how its handles relate.
- A vector point has an optional mode: straight, corner, smooth or
symmetric. Missing means corner. The mode is not part of the
fingerprint, because it changes no pixel, only the next drag.
- Dragging a handle follows the mode. Corner leaves the other handle
alone, smooth turns it along and keeps its length, symmetric
mirrors it exactly.
- The pen pulls out both handles mirrored and marks the point
symmetric. A plain click still sets a point without handles.
- The tab header has a "Point: ..." menu for the last clicked or set
point. That point is drawn filled. Changing its type is one undo
step. Text instead of four icons, because the UI kit has no
tooltips.
- Switching a point without handles to a curved type first grows
handles along its neighbours. Smooth and symmetric line the
handles up and keep the rough direction of the curve.
- A point inserted on a curve is smooth. Symmetric neighbours become
smooth, because the split shortens one of their handles.
- Alt-click still toggles between straight and symmetric.
Found on the way: the old Alt rounding took the point at the other
end of an open line as a neighbour, so its handles came out skewed.
Fixed and held by a unit test.
Existing points with handles now count as corner. Before, every drag
mirrored. No pixel changes, only the next drag behaves differently.
Tests:
- ./check.sh: 129 files, 1917 tests, exit 0 (before 1903)
- GM_STEPS=47, freshly seeded: ALL PASSED, 39 checks (before 32)
- Check sum 1195 in README and wiki is computed (1188 + 7), CI
measures it
- Counter-checks: pen without 'symmetric' fails 3 checks, no smooth
branch fails 1 unit test and 1 e2e check
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The snap toggle was a checkbox with the word "Snap" next to Fit, and
the vector art tab had its own "Grid" button for the same idea. Both
now share one square icon button, so snapping looks and works the
same wherever it exists.
- SnapToggle is a square toggle button with a magnet icon and no
text. It lights up blue when snapping is on, like the other toggle
buttons.
- The magnet is drawn with cubic curves instead of arcs, because the
Pixi SVG parser draws arcs as jagged edges.
- The vector art tab drops its Grid button and uses the same
SnapToggle. It still snaps to 8 px, the graph tabs to 24 px.
- SnapToggle accepts anything with setSnapping(on). The vector art
canvas method is renamed from setSnap to setSnapping for that.
- Every tab exposes the button as __gm.<tab>.snapButton(). Modules
06, 26 and 36 click through that hook instead of the label "Snap"
plus 30 px.
Tests:
- ./check.sh: 129 files, 1903 tests (was 128 / 1897)
- 3 new tests in tests/editor/snap-toggle.test.ts, 3 in the icon tests
- GM_STEPS=1-6,26,36,47 fresh: ALL PASSED, 346 checks
- GM_STEPS=47: ALL PASSED, 32 checks (was 29)
- Counter-checks: toggle not switching its target fails 2 unit
tests, the canvas ignoring the switch fails module 47
- Check total 1188 in README and wiki is computed (1184 + 4), the
full run is left to CI
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage 5 closes the loop that the earlier stages left open: every check so
far put the shapes into the project with mutate, because the tab that
draws them came later. Block 47f now walks the path a user walks - the +
menu, the asset panel, the tab, the picture picker - so a regression in
any of those four doors is caught by the suite instead of by you.
- The + menu offers "Vector art" among the types that only live in the
library, and the entry makes an asset that is selected and stands in
the panel under its own heading.
- A double-click on the row opens the Vector art tab on that asset,
which is what stage 3 built and no check measured until now.
- The Texture row of a sprite offers the drawing by name, sorted among
the other pictures, and picking it there makes the sprite wear it -
measured at the pixel in the scene, not at the row.
One finding: the picker listed three entries where I expected more, and
the reason is sound - a fresh fixture holds one texture and the drawing,
"None" being the third. The check names all three instead of counting,
so a lost entry says which one it lost.
Tests:
./check.sh 128 files, 1897 tests, exit 0
node tools/e2e.mjs (all modules) ALL PASSED - 1184 checks (was 1179)
GM_STEPS=47 (fresh seed) ALL PASSED - 29 checks (was 24)
Counter-checks: both hold - no generator registered fails the picker
check, and opening the tab without the asset fails the double-click
check.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stage 4 of #129. Until now the inspector showed the rows of the asset
when a shape was selected, so there was no way to give a shape a colour,
an outline or a place in the drawing order without editing the project
file. The rows for a shape now sit where every other selection puts its
rows.
- SHAPES lists every shape of the asset, topmost row drawn last, the
same way a layer list reads. Selecting the asset itself shows the same
list below its own rows, with no shape selected.
- Each row paints its shape with the commands the rasterizer uses
(shapeDrawing plus paintDrawing) into a 22 px swatch, the whole canvas
scaled in, so the row also says where on the canvas the shape sits.
- The four order buttons from the issue (forward, to front, backward, to
back) sit in two rows of two, and a row can be dragged to another
place with a marker line. They are disabled when there is nothing to
move.
- VECTOR SHAPE carries name, fill, opacity and an outline checkbox, the
checkbox adding and removing the stroke instead of hiding it; line
colour and width appear with it. A dragged colour slider is one undo
step, the style rows going through previewingHandlers like every other
row.
- A line width of 0 is no stroke, which the command rejects, so the row
clamps at 0.1.
Findings:
- locate('Outline') aims beside the control, because it gives the centre
of the label text and a wider label pushes a fixed offset out of the
field. __gm.inspectorField(label) knows the centre of the control.
- A click into a text field sets the cursor and selects nothing, so a
typed hex landed appended to the old one and parseHex returned null
without anything failing - typeText only checks that the text is
contained. A Ctrl+A goes first now.
- Four buttons side by side cut "Backward" to "Backw...", which only the
screenshot showed. Two rows of two now, as in the tilemap panel.
Tests: ./check.sh 128 files, 1897 tests (was 127/1883). Full e2e suite
locally instead of only CI because the change sits in the inspector and
touches every selection: ALL PASSED - 1179 checks (was 1172), module 47
at 24 checks (was 17). Three counter-checks hold: not reversing the list
fails 1 unit test and 2 e2e checks, dropping the gap correction when
dropping a row fails 2 unit tests and the drag check, and a constant
signature kills module 47 because the outline rows never appear.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Stages 1 and 2 gave the asset a model and a picture, but the only way
to put a shape into it was a test calling mutate. The tab closes that
gap: what you draw there is what the sprite in the scene carries, with
no extra step, because the fingerprint changes and the picture registry
of #185 redraws.
- The tab has a header with Select, Pen, Grid and Fit, a chequerboard
behind the canvas and the canvas itself as a frame. It paints with
the same commands as the rasterizer, paintDrawing being exported
from viewport/vectorArtPicture.ts for it, so the tab cannot drift
away from the picture.
- Pen sets points on a click and pulls the handles out on a drag, both
of them mirrored; a click on the first point closes the ring and Esc
ends an open line.
- Select drags a point, a handle or a whole shape, a click on an edge
puts a point there, a double click takes one away, and Alt turns a
corner round and back. Delete removes the selected shape.
- The rules grew inside polygonEdit.ts instead of a second module, as
the plan says, so a collider polygon and a frame outline get the
same behaviour where it applies.
- A point on a curved edge splits the curve by de Casteljau instead of
cutting it: both halves together draw exactly what was there before.
- A curved edge is walked instead of measured over its chord, or a
click on the bow of a curve would grab nothing; the same walk carries
the test for the inside of a ring.
- One gesture is one undo step. The preview writes the model directly
and the old state is put back on release before the command runs,
which is the pattern the frame outlines of #141 already use.
- state.ts gained a selection kind "shape". The inspector shows the
rows of the asset for it, the shape list with colour, opacity and
stroke being stage 4.
One counter-check was no counter-check: disabling the pen's closing
tolerance by multiplying it with 0 left the run green, and rightly so,
because the closing click lands on exactly the screen coordinate point
0 came from, so the distance is 0 and <= 0 is true. Only an
unsatisfiable condition really switched it off, and then 2 checks fail.
A second trap on the way: a false && in front of the guard breaks the
TS narrowing, npm run build bails out, and two runs went against the
old bundle.
Tests: check.sh 127 files, 1878 tests, EXIT 0 (was 126 / 1860).
Full e2e suite ALL PASSED - 1172 checks, module 47 alone 17 (was 10).
18 new unit tests in tests/editor/ring-edit.test.ts and 7 new e2e
checks in blocks 47c and 47d. Two counter-checks hold: no de
Casteljau fails 1 unit test, measuring a curve over its chord fails
another. Originals checked back with cmp and rebuilt.