Commit Graph
18 Commits
Author SHA1 Message Date
adminandClaude Opus 5.5 487b3aa5b4 Painting over identical tiles no longer adds an empty undo step (#212)
game-maker / gate (push) Successful in 2m45s
game-maker / suite (push) Successful in 8m43s
A rectangle drawn over cells that already hold exactly that tile
pushed a command that changed nothing. Undo then seemed to do nothing
and the project was marked as modified. PaintTilesCommand.changes()
was meant to prevent that, but nothing called it since #128.

- New paintTiles() in tilemapCommands.ts returns the command for a
  finished stroke or fill, or null when no cell changes. It is the
  first caller of PaintTilesCommand.changes().
- All three paint paths in the Scene View go through it: brush and
  eraser, rectangle (both in endStroke) and flood fill (fillFrom).
- Flood fill was not affected, because floodFill already returns []
  for the same tile. The guard is there anyway, as the issue asked.
- A rectangle that changes even one cell stays a normal undo step.

Tests:
- ./check.sh: 145 files, 2190 tests, CHECK EXIT 0 (before 2187).
- 3 new unit tests in tests/core/tilemap.test.ts: same rectangle
  twice gives no command, a wider rectangle still does, a fill that
  finds nothing gives no command.
- 1 new e2e check in 40d: the same rectangle again, then one undo
  must take 9 cells to 0. A redo restores them for later blocks.
- GM_STEPS=39,40 on the final build: ALL PASSED, 81 checks.
- Counter-probe with changes(...) || true: exactly the two unit
  tests fail, and in the browser exactly the new check fails with
  {"afterUndo":9}. Original restored via cmp (IDENTICAL), rebuilt.
- Editor suite check total: CI measured 1312 at 0e85856, new total
  1313 (calculated) in README and wiki.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-23 08:56:17 +02:00
adminandClaude Opus 5 8c5846f956 Right-click in the scene view opens the object menu (#204)
The object menu lived only on the hierarchy rows. Now a right-click in
the scene view opens the same menu for the object under the cursor.

- The menu moves out of HierarchyPanel into src/editor/objectMenu.ts.
  Hierarchy and scene view call the same function, so their entries
  cannot drift apart. Only the hierarchy adds "Expand all" and
  "Collapse all".
- Which object the menu is for follows the press rule from #203. A
  selected object under the cursor, even a hidden one, keeps the
  selection and the menu acts on all of it. Otherwise the topmost
  object is selected first.
- The rule is the pure function menuTargetOf in src/viewport/pick.ts,
  next to pressOn.
- Empty space, including an empty tilemap cell, opens no menu.
- The container tab gets the same, because ContainerCanvas extends the
  scene view.

Tests:
- ./check.sh: 142 files, 2094 tests, exit 0 (was 2090)
- e2e 39,40 fresh: ALL PASSED, 80 checks
- e2e 1-16,39,40 fresh: ALL PASSED, 565 checks
- New block 40r (3 checks): menu on a hidden selected crate, topmost
  object without a selection, no menu on an empty cell.
- Counter-check: a right-click that always takes the topmost object
  fails 2 unit tests and the hidden-crate e2e check.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 19:47:31 +02:00
adminandClaude Opus 5 ad2a2d7921 Clicks in the scene view pick what is drawn under the cursor (#203)
Pixi's own hit test decided what a click in the scene view selected.
A tilemap caught clicks over its whole area, even on empty cells and
over sprites sorted above one of its layers. A press always grabbed
the topmost object, so a hidden selected object could not be dragged,
and there was no way to reach an object underneath another one.

- New pure rule in src/viewport/pick.ts. It lists the objects under
  the cursor front to back, in the order they are drawn.
- A tilemap counts only where it has a tile. Its depth there is the Z
  of the highest layer with a tile at that spot. An empty cell is
  empty space, so a click clears the selection or starts a marquee.
- Pressing on any selected object, hidden ones included, drags the
  selection. Otherwise the press selects the topmost object and drags
  it, as before.
- A click without drag on a selected object selects the next one
  underneath. After the lowest it starts again at the top.
- New debug hook __gm.sceneStack(x, y) returns that list.

Decisions:
- With a paint tool the selected tilemap paints anywhere on its grid,
  even under a sprite. With empty cells as empty space an empty cell
  could not be painted otherwise.
- Icons such as colliders and cameras stay in front. A second click
  now reaches the sprite under a collider.

Tests:
- ./check.sh: 142 files, 2090 tests, exit 0 (was 141 / 2080)
- 10 new unit tests in tests/viewport/pick.test.ts
- GM_STEPS=39,40: ALL PASSED, 77 checks
- GM_STEPS=1-16,29,39-43: ALL PASSED, 626 checks
- 4 new checks in block 40q: sprite over a lower layer, empty cell,
  cycling crate, map, crate, dragging the hidden selected crate.
- Counter test: whole-area tilemap hits and always-topmost presses
  fail 4 unit tests and all 4 new checks.
- Check total 1290 (1286 + 4), computed, not yet counted by CI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-22 19:09:29 +02:00
adminandClaude Opus 5 bbbf34109b Lists duplicate, rename and edit values right in their rows
game-maker / gate (push) Successful in 2m20s
game-maker / suite (push) Successful in 8m25s
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>
2026-09-22 06:02:47 +02:00
adminandClaude Opus 5 ae0247d86f Lists add and delete items right in the list header and rows
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>
2026-09-21 21:18:26 +02:00
adminandClaude Opus 5 5b9b57fd19 Layer, frame and shape lists all reorder by drag and drop
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>
2026-09-21 18:48:50 +02:00
adminandClaude Opus 5 f4ec23d3d3 Colliders take a frame's outline and tiles have their own surface (#193)
game-maker / gate (push) Successful in 2m11s
game-maker / suite (push) Successful in 7m33s
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>
2026-09-21 07:29:26 +02:00
adminandClaude Opus 5 47eea2f6e4 Each tilemap layer has its own Z: an offset added to the Z of the placed tilemap, like a child's Z to its parent, default 0 keeps the previous order (layers right after their map, in list order), a negative Z sorts a layer below the map, on a tie layers keep their list order and follow the hierarchy against other objects; editor scene and game use the same order through the new pure function drawSlots, a tilemap draws into one container per layer, and the tilemap panel gains a Z field for the selected layer with one undo step per change (#174)
game-maker / gate (push) Successful in 1m58s
game-maker / suite (push) Successful in 6m50s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-19 14:03:20 +02:00
adminandClaude Opus 5 bffa508ee7 A tile gains Collision scale: below 1 each blocking cell pulls in on the sides without a blocking neighbour and stays flush towards its neighbours, so a wall of bushes becomes a narrower wall without gaps and the inner corner of an L closes cleanly; above 1 the cell grows at its open sides; on several layers the largest value counts, and the merged rectangles work in fractions of a cell with exactly the old result at 1 (#140)
game-maker / gate (push) Successful in 1m43s
game-maker / suite (push) Successful in 6m27s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 15:00:07 +02:00
adminandClaude Opus 5 4728f4fab5 A placed tilemap grows and shrinks at every edge: the new GRID EDGES block in the inspector adds or removes one column or row at Left, Top, Right or Bottom as one undo step, shifting the cells of all layers and moving the placement through its own matrix so the content stays in place on screen; removed painted cells are kept outside the grid and return when the edge grows again, and the Columns and Rows fields of a placement keep the content in place too (#136)
game-maker / gate (push) Successful in 1m43s
game-maker / suite (push) Successful in 6m8s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 13:58:03 +02:00
adminandClaude Opus 5 0b32d3cbf3 The tilemap panel gains Fill and Pick: Fill floods the connected cells of the active layer that carry the same tile (edge neighbours only, bounded by the grid) as one undo step, Pick takes the tile of the clicked cell on the active layer, and holding Alt picks while any tool is active (#133)
game-maker / gate (push) Successful in 1m41s
game-maker / suite (push) Successful in 6m7s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 12:31:19 +02:00
adminandClaude Opus 5 5046ddb810 The tilemap painting tools move out of the inspector into their own panel beside the scene view, shown only while exactly one placed tilemap is selected in the scene tab; the free viewports keep their content in place on screen when they are resized (#142)
game-maker / gate (push) Canceled after 0s
game-maker / suite (push) Canceled after 0s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 11:46:23 +02:00
adminandClaude Opus 5 f53279d065 The active tilemap layer can be renamed from the painting section, and a Show setting fades or hides the other layers in the scene view while the tilemap is selected (#138)
game-maker / gate (push) Successful in 1m40s
game-maker / suite (push) Successful in 6m4s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 10:56:32 +02:00
adminandClaude Opus 5 bfbcbe604c The tile list in the tilemap section is a palette of pictures: each tile shows its lone tile (the four convex corners for floor and wall, the picture itself for none) with its name below, laid out with the image-picker grid (#139)
game-maker / gate (push) Successful in 1m41s
game-maker / suite (push) Successful in 6m15s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 10:15:51 +02:00
adminandClaude Opus 5 4c411c1ff3 Tilemaps can block movement: a tile gains a Solid switch, the solid cells of all layers are merged into rectangles and the placed tilemap takes part in the collision system as one body, reported as other in collision events (#132)
game-maker / gate (push) Successful in 1m41s
game-maker / suite (push) Successful in 6m3s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 09:47:03 +02:00
adminandClaude Opus 5 10b49fac46 Tilemap cells are stored compactly in the project file: each layer keeps a tile list, an origin and one run-length encoded string per row; older projects with a cell map still load (#134)
game-maker / gate (push) Successful in 1m40s
game-maker / suite (push) Successful in 5m59s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 09:17:49 +02:00
adminandClaude Opus 5 732f6518ae The autotile asset is renamed to tile and gains a plain None (1 × 1) kind, which is now the default; older projects load their autotiles as floor tiles (#131)
game-maker / gate (push) Successful in 1m40s
game-maker / suite (push) Successful in 5m56s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 08:01:07 +02:00
adminandClaude Opus 5 33a7b1b63e Tilemaps are a new placeable asset type that is painted with autotiles in the scene view, with layers, brush, eraser and rectangle, and is drawn at runtime and in the export (#128)
game-maker / gate (push) Successful in 1m38s
game-maker / suite (push) Successful in 5m51s
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-18 06:41:47 +02:00