Empty a deleted tile's cells on every tilemap (#268)
Deleting a tile left the cells painted with it in the tilemaps. They drew nothing, blocked nothing and did not show in the delete dialog, but still read as a tile for "Tile at" and stopped a fill. - DeleteAssetCommand empties the cells of a deleted tile on every layer of every tilemap in the same step. Undo puts the tile and its cells back together. - The delete dialog counts them, for example "3 cell(s) on 1 tilemap(s) are painted with it and become empty." (cellsPainted). - presentTile treats a cell naming a missing tile as empty. "Tile at" (World.tileAt) and floodFill use it, so cells already in existing files read and fill as empty. There is no migration. - The brush, the rectangle and the fill check the tile in hand. A tile deleted after it was picked no longer paints new invisible cells, the editor says "Choose a tile to paint with." instead. - Unchanged on purpose: the picker still reports such a cell as naming a tile that no longer exists, and erasing removes it. Tests: - ./check.sh: 195 files, 2823 tests, CHECK EXIT 0 (+6). - GM_STEPS=39,40: ALL PASSED, 100 checks (4 new in 40t). - Full e2e sum is now 1590 (1586 confirmed by CI on round 8, plus 4). - Counter-checks, all fail as expected: 5 in unit tests (delete without emptying, undo without cells, fill and "Tile at" without presentTile, wrong map count) and 3 in the browser (dialog without count, delete without emptying, brush without the check). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
14972d057f
commit
9b5eeaab57
11 files changed
+310
-38
No files matched your search
@@ -1291,6 +1291,101 @@ export async function tilemap(t) {
|
||||
__gm.state.select(null)`);
|
||||
await settle();
|
||||
|
||||
// 40t. Deleting a tile that is painted (#268): the dialog counts its cells, the same step empties
|
||||
// them, a brush still holding the tile paints nothing, and an undo brings tile and cells back
|
||||
// together. A tile of its own on the empty bottom row, so the cells above stay as they are.
|
||||
await mutate(`
|
||||
const assets = __gm.state.project.assets;
|
||||
const doomed = structuredClone(assets.find((a) => a.id === 'at-grass'));
|
||||
Object.assign(doomed, { id: 'tm-doomed', name: 'Doomed' });
|
||||
assets.push(doomed);`);
|
||||
const selectMap = async () => {
|
||||
await select({ kind: 'object', holderId: 'scene1', objectIds: [MAP_OBJECT], objectId: MAP_OBJECT });
|
||||
await evaluate(`__gm.lookAt(0, ${LANE})`);
|
||||
await settle();
|
||||
};
|
||||
const doomedCells = () =>
|
||||
json(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(made.id)}).layers` +
|
||||
".flatMap((l) => Object.keys(l.cells).filter((key) => l.cells[key] === 'tm-doomed')).sort().join(' ')",
|
||||
);
|
||||
await selectMap();
|
||||
await clickInPanel(t, '__gm.tilemap.tileButtonAt("tm-doomed")');
|
||||
await clickInPanel(t, '__gm.tilemap.toolAt("brush")');
|
||||
await settle();
|
||||
const rowStart = await json(`__gm.tilemap.cellAt(${JSON.stringify(MAP_OBJECT)}, 0, 3)`);
|
||||
const rowEnd = await json(`__gm.tilemap.cellAt(${JSON.stringify(MAP_OBJECT)}, 2, 3)`);
|
||||
await drag(rowStart.x, rowStart.y, rowEnd.x, rowEnd.y);
|
||||
await settle();
|
||||
const paintedRow = await doomedCells();
|
||||
const doomedRow = await json("__gm.assetRow('tm-doomed')");
|
||||
await click(doomedRow.x, doomedRow.y, 'right');
|
||||
await settle();
|
||||
const deleteEntry = await t.locate('Delete…', true);
|
||||
await click(deleteEntry.x, deleteEntry.y);
|
||||
await settle();
|
||||
const counted = await t.has('3 cell(s) on 1 tilemap(s) are painted with it and become empty.');
|
||||
await shot('40-delete-painted-tile');
|
||||
check(
|
||||
'the delete dialog of a painted tile counts its cells and the maps they lie on',
|
||||
paintedRow === '0,3 1,3 2,3' && counted,
|
||||
JSON.stringify({ paintedRow, overlay: await json('__gm.overlayTexts()') }),
|
||||
);
|
||||
const confirm = await t.locate('Delete', true);
|
||||
await click(confirm.x, confirm.y);
|
||||
await settle();
|
||||
const afterDelete = {
|
||||
tile: await json("__gm.state.project.assets.some((a) => a.id === 'tm-doomed')"),
|
||||
cells: await doomedCells(),
|
||||
};
|
||||
check(
|
||||
'deleting the tile empties its cells in the same step',
|
||||
afterDelete.tile === false && afterDelete.cells === '',
|
||||
JSON.stringify(afterDelete),
|
||||
);
|
||||
// The brush still holds the deleted tile; a click with it on a cell never painted must leave no
|
||||
// such cell behind.
|
||||
await selectMap();
|
||||
await clickInPanel(t, '__gm.tilemap.toolAt("brush")');
|
||||
await settle();
|
||||
const inHand = await json('__gm.tilemap.paint()');
|
||||
const untouched = await json(`__gm.tilemap.cellAt(${JSON.stringify(MAP_OBJECT)}, 4, 3)`);
|
||||
await click(untouched.x, untouched.y);
|
||||
await settle();
|
||||
const afterClick = {
|
||||
inHand,
|
||||
cell: await json(
|
||||
`__gm.state.project.assets.find((a) => a.id === ${JSON.stringify(made.id)}).layers.map((l) => l.cells['4,3'] ?? '')`,
|
||||
),
|
||||
notice: await json('__gm.state.notice'),
|
||||
};
|
||||
check(
|
||||
'a brush still holding the deleted tile paints nothing and asks for a tile',
|
||||
inHand.tool === 'brush' &&
|
||||
inHand.tileId === 'tm-doomed' &&
|
||||
afterClick.cell.every((id) => id === '') &&
|
||||
afterClick.notice === 'Choose a tile to paint with.',
|
||||
JSON.stringify(afterClick),
|
||||
);
|
||||
await undo();
|
||||
await settle();
|
||||
const restored = {
|
||||
tile: await json("__gm.state.project.assets.some((a) => a.id === 'tm-doomed')"),
|
||||
cells: await doomedCells(),
|
||||
};
|
||||
check(
|
||||
'an undo brings the tile and its cells back together',
|
||||
restored.tile === true && restored.cells === '0,3 1,3 2,3',
|
||||
JSON.stringify(restored),
|
||||
);
|
||||
// The stroke goes the same way, and the tile the mutate brought in goes as it came.
|
||||
await undo();
|
||||
await settle();
|
||||
await mutate(`
|
||||
const p = __gm.state.project;
|
||||
p.assets = p.assets.filter((a) => a.id !== 'tm-doomed');`);
|
||||
await settle();
|
||||
|
||||
// Leave the editor as the rest of the suite expects to find it.
|
||||
await evaluate("__gm.state.setTilemapPaint({ tool: 'select' })");
|
||||
await evaluate('__gm.state.select(null)');
|
||||
|
||||
Reference in new issue
Block a user