Grid density: a coarser board settles its tiles instead of stacking them

Stepping the grid coarser pulled tiles past the right edge back in and
left them wherever that put them — on top of one another, which the
board never allows under a drag. Tiles now settle in reading order: one
that would land on a tile already settled takes the first free spot, the
same rule a newly added tile follows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
This commit is contained in:
Waters Church
2026-09-22 11:26:05 -04:00
co-authored by Claude Fable 5.1
parent 2af7ef27ba
commit e383070a9e
2 changed files with 26 additions and 5 deletions
+22 -3
View File
@@ -296,8 +296,9 @@ function cellHeightFor(cols) {
}
/** Cut the board into `cols` columns. Tiles keep their grid units — the
board gets denser or coarser and everything on it changes size with it;
only a tile now past the right edge is pulled back in. */
board gets denser or coarser and everything on it changes size with it.
A tile now past the right edge is pulled back in, and one that would then
sit on another moves to the first free spot instead. */
function setGridCols(value, { save = true } = {}) {
const next = normalizeGridCols(value);
const changed = next !== COLS;
@@ -305,9 +306,16 @@ function setGridCols(value, { save = true } = {}) {
grid.style.gridTemplateColumns = `repeat(${COLS}, minmax(0, 1fr))`;
document.documentElement.style.setProperty('--cell-h', cellHeightFor(COLS) + 'px');
if (changed) {
for (const tile of tiles) {
// A coarser board may not have room for every tile where it was. Settle
// them in reading order: pull a tile past the right edge back in, and
// give one that would then land on a tile already settled the first free
// spot instead — tiles never overlap here, any more than under a drag.
const settled = [];
for (const tile of tiles.slice().sort((a, b) => a.y - b.y || a.x - b.x)) {
tile.w = Math.max(1, Math.min(COLS, tile.w));
tile.x = Math.max(0, Math.min(COLS - tile.w, tile.x));
if (settled.some((s) => overlaps(tile, s))) Object.assign(tile, spotAmong(tile.w, tile.h, settled));
settled.push(tile);
applyRect(tile);
notifyResize(tile);
}
@@ -386,6 +394,17 @@ function isFree(rect, excludeId) {
return isFreeExcept(rect, [excludeId]);
}
/** First spot, scanning top-to-bottom, left-to-right, clear of these tiles. */
function spotAmong(w, h, others) {
for (let y = 0; y < 400; y += 1) {
for (let x = 0; x <= COLS - w; x += 1) {
const rect = { x, y, w, h };
if (!others.some((o) => overlaps(rect, o))) return { x, y };
}
}
return { x: 0, y: 0 };
}
/** First free spot scanning top-to-bottom, left-to-right. */
function findSpot(w, h) {
for (let y = 0; y < 200; y += 1) {