Let a texture frame set where a sprite stands for Y sorting (#228)

A sprite sorted by Y stood on the bottom of its box. With a shadow or
empty rows under the feet that is too low, so a figure slipped in
front of a tree it should stand behind. The line belongs to the frame,
not the texture, because one texture can hold several sprite sheets.

- New field `sortLine` on a frame: whole image pixels up from the
  frame's bottom edge, 0 to the frame height. 0 is stored as nothing,
  so old projects stay the same. Shrinking a frame keeps the line
  inside it.
- `feetOf` in drawOrder.ts lifts a sprite by the same share of its
  box. Scene view, game view, play and picking all use it. In an
  animation the line of the frame shown right now counts.
- The texture tab draws the selected frame's line dashed in yellow
  with a knob left of the frame. At 0 the line shares the bottom edge
  with the nine-slice border and a resize handle, so the knob is
  tested first. A line inside the frame can be dragged directly,
  tested after the nine-slice lines.
- The inspector has a new "Sorting" section with a "Sort line"
  field. Dragging or typing is one undo step each.

Limit: frames of one walk cycle need the same value, set by hand.
Otherwise the figure changes its place in the order while walking.

Tests:
- ./check.sh: 160 files, 2356 tests, CHECK EXIT 0 (before 2350).
- 6 new unit tests in tests/viewport/sort-by-y.test.ts.
- e2e 37 and 39-43: ALL PASSED, 162 checks. New check 37b-ii drags
  the knob up 6 px and undoes it, plus the inspector row.
- Counter checks: if feetOf ignores the line, exactly the 3 order
  tests fail. If the knob cannot be grabbed, 37b-ii fails.
- The e2e sum in the README is 1380, counted, not yet seen in CI.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-09-24 19:34:37 +02:00
1 parent 50854389bb
commit 89048413f1
11 files changed
+334 -11

No files matched your search

+23
View File
@@ -167,6 +167,29 @@ export function dragBorder(border: FrameBorders, edge: BorderEdge, dx: number, d
return moved;
}
/** The Y a frame's sort line (#228) runs at, in image pixels. */
export function sortLineY(rect: FrameRect, line: number): number {
return rect.y + rect.h - line;
}
/**
* What a press grabs of the sort line: its knob left of the frame, reachable wherever the line sits,
* or the line itself inside the frame. `reach` is how far left of the frame the knob goes, in image
* pixels. The press tests the knob before the frame's handles and the line after its border lines,
* which at 0 all lie on the bottom edge with it.
*/
export function sortLineAt(
rect: FrameRect,
line: number,
point: Vec2,
tolerance: number,
reach: number,
): 'knob' | 'line' | null {
if (Math.abs(point.y - sortLineY(rect, line)) > tolerance) return null;
if (point.x >= rect.x - reach && point.x < rect.x - tolerance) return 'knob';
return point.x >= rect.x && point.x <= rect.x + rect.w ? 'line' : null;
}
export function borderCursor(edge: BorderEdge): string {
return edge === 'left' || edge === 'right' ? 'ew-resize' : 'ns-resize';
}