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

+27
View File
@@ -3,6 +3,7 @@ import {
RenameFrameCommand,
SetFrameRectCommand,
SetFrameSliceCommand,
SetFrameSortLineCommand,
} from '../../core/commands/frameCommands';
import type { FrameBorders, TextureAsset, TextureFrame } from '../../core/model/Asset';
import { type PropertyValue, textOf } from '../../core/model/Properties';
@@ -15,9 +16,12 @@ import {
type FrameRect,
frameSlice,
type FrameSlice,
frameSortLine,
normalizeFrameRect,
normalizeSortLine,
setFrameRect,
setFrameSlice,
setFrameSortLine,
} from '../../core/model/texture';
import { Button } from '../../ui';
import { createFieldRow, type FieldHandlers } from './fieldFactory';
@@ -70,6 +74,10 @@ export function buildFrameSection(host: InspectorHost, assetId: string, frameId:
fillRow(host, assetId, frameId);
addNote(host, 'All four at zero: the picture is drawn over the whole box, as it always was.');
addTitle(host, 'SORTING (IMAGE PIXELS)');
sortLineRow(host, assetId, frameId);
addNote(host, 'Where a sprite of this frame stands on a layer sorted by Y, up from the bottom edge.');
addGap(host);
host.body.add(
new Button('Delete frame', {
@@ -89,9 +97,28 @@ export function refreshFrameSection(host: InspectorHost, assetId: string, frameI
for (const component of BORDER_COMPONENTS)
host.fields.get(`border-${component.key}`)?.setValue(border[component.key]);
host.fields.get('fill')?.setValue(frameFill(frame));
host.fields.get('sortLine')?.setValue(frameSortLine(frame));
return true;
}
function sortLineRow(host: InspectorHost, assetId: string, frameId: string): void {
const frame = currentFrame(host.state.project, assetId, frameId);
const row = createFieldRow(
host,
{ key: 'sortLine', label: 'Sort line', kind: 'number', default: 0, min: 0, step: 1, decimals: 0 },
frame ? frameSortLine(frame) : 0,
previewingHandlers<TextureFrame, number>(host, {
target: () => currentFrame(host.state.project, assetId, frameId),
read: frameSortLine,
write: setFrameSortLine,
toModel: (input, frame) => normalizeSortLine(Number(input), frame.h),
commit: (line) => new SetFrameSortLineCommand(assetId, frameId, line),
}),
);
host.fields.set('sortLine', row);
host.body.add(row.row);
}
/**
* One border of the nine-slice, in pixels of the frame. The row keeps the other three and the fill
* as they are at that moment, so two rows edited one after the other do not undo each other.