Z-order via position.z and reset-to-default for asset properties

- Position is now { x, y, z }; higher z draws on top, ties keep hierarchy order
- Viewport sorts the objects layer deterministically (syncDrawOrder)
- Inspector shows a Z field; asset properties that differ from the schema
  default are marked bold with a reset button (ResetAssetPropertyCommand)
- isModified() helper, unit tests, E2E checks and seed updated

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
rootandClaude Fable 5.1 committed 2026-09-07 11:42:18 +02:00
1 parent aaa8154adf
commit d4b1ffa051
18 files changed
+232 -63

No files matched your search

+46
View File
@@ -284,6 +284,52 @@ try {
check('camera symbol is drawn in the overlay layer above sprites', layers === '["overlay","objects","objects"]', layers);
await shot('13-symbol-on-top');
// 11. Z-order: put both crates on the origin. With equal z the hierarchy order wins
// (o1 before o2 → o2 on top); raising o1's z draws it above o2; undo restores it.
await evaluate(`(() => {
const o2 = __gm.state.activeScene.objects.find(o => o.id === 'o2');
o2.position.x = 0; o2.position.y = 0;
__gm.state.preview();
})()`);
await sleep(200);
const orderBefore = await evaluate(`JSON.stringify(__gm.drawOrder().filter(id => id === 'o1' || id === 'o2'))`);
check('equal z keeps hierarchy order', orderBefore === '["o1","o2"]', orderBefore);
await evaluate(`__gm.state.select({ kind: 'object', sceneId: __gm.state.activeScene.id, objectId: 'o1' })`);
await sleep(200);
const zLabel = await locate('Z');
await click(zLabel.x + 160, zLabel.y); // plain click → text editing with everything selected
await typeText('5');
await keyPress('Enter', 'Enter', { windowsVirtualKeyCode: 13 });
await sleep(200);
const zNow = await evaluate(`__gm.state.activeScene.objects.find(o => o.id === 'o1').position.z`);
check('Z field sets position.z', zNow === 5, `z=${zNow}`);
const orderAfter = await evaluate(`JSON.stringify(__gm.drawOrder().filter(id => id === 'o1' || id === 'o2'))`);
check('higher z is drawn on top', orderAfter === '["o2","o1"]', orderAfter);
await shot('14-z-order');
await keyPress('z', 'KeyZ', { modifiers: 2, windowsVirtualKeyCode: 90 });
await sleep(200);
const zUndone = await evaluate(`__gm.state.activeScene.objects.find(o => o.id === 'o1').position.z`);
check('undo restores z', zUndone === 0, `z=${zUndone}`);
// 12. Asset defaults: the Crate asset has height 60 (schema default 100), width and
// color at their defaults → exactly one reset button is visible. Clicking it puts
// height back to 100 and hides the button; undo brings 60 back.
await evaluate(`__gm.state.select({ kind: 'asset', assetId: 'crate' })`);
await sleep(200);
const resetButton = await evaluate(`__gm.locate('↺')`);
check('asset inspector shows a reset for the non-default height', Boolean(resetButton));
await click(resetButton.x, resetButton.y);
await sleep(200);
const heightReset = await evaluate(`__gm.state.project.assets.find(a => a.id === 'crate').props.height`);
check('reset puts the asset property back to its default', heightReset === 100, `height=${heightReset}`);
check('reset button hidden once the value is at default', !(await evaluate(`__gm.locate('↺')`)));
await shot('15-asset-reset');
await keyPress('z', 'KeyZ', { modifiers: 2, windowsVirtualKeyCode: 90 });
await sleep(200);
const heightUndone = await evaluate(`__gm.state.project.assets.find(a => a.id === 'crate').props.height`);
check('undo restores the asset value', heightUndone === 60, `height=${heightUndone}`);
check('reset button visible again after undo', Boolean(await evaluate(`__gm.locate('↺')`)));
check('no exceptions during run', !consoleLog.some((l) => l.startsWith('EXCEPTION')), consoleLog.filter((l) => l.startsWith('EXCEPTION')).slice(0, 3).join(' | '));
ws.close();
} catch (error) {
+3 -3
View File
@@ -22,9 +22,9 @@ const project = {
name: 'Level 1',
props: { backgroundColor: 0x22252c },
objects: [
{ id: 'o1', assetId: 'crate', name: '', position: { x: -150, y: 0 }, overrides: {} },
{ id: 'o2', assetId: 'crate', name: 'Blue crate', position: { x: 120, y: -80 }, overrides: { width: 140, color: 0x4488ff } },
{ id: 'o3', assetId: 'cam', name: '', position: { x: 0, y: 0 }, overrides: {} },
{ id: 'o1', assetId: 'crate', name: '', position: { x: -150, y: 0, z: 0 }, overrides: {} },
{ id: 'o2', assetId: 'crate', name: 'Blue crate', position: { x: 120, y: -80, z: 0 }, overrides: { width: 140, color: 0x4488ff } },
{ id: 'o3', assetId: 'cam', name: '', position: { x: 0, y: 0, z: 0 }, overrides: {} },
],
},
{ id: 'crate', type: 'sprite', name: 'Crate', props: { width: 100, height: 60, color: 0xe0553f } },