[eric] dashboard: spawn picks the EUCLIDEAN-nearest open gap, not the first ring-scan cell (3rd card flung to a far corner when a big browser blocked the near cells)

This commit is contained in:
ciregenz
2026-07-15 18:37:50 -07:00
parent 42f69e67d7
commit b062666bf1
@@ -386,8 +386,18 @@ export function findOpenSpotNear(
};
}
// Spiral by ring perimeter; right/down preference for stability.
// Ring order approximates distance but returns the first-in-scan cell, which flings a card to a
// far corner when the near cells are blocked (a big browser + expanded chats). Instead pick the
// cell CLOSEST to the anchor by real distance: scan outward, and once a ring yields a free cell,
// scan ONE more ring (a ring-r corner ~r*1.41 can lose to a ring-(r+1) edge) then take the nearest.
const MAX_RING = 32;
const spotDist = (col: number, row: number): number => {
const x = GRID_ORIGIN.x + col * cellW;
const y = GRID_ORIGIN.y + row * cellH;
return Math.hypot(x - anchorX, y - anchorY);
};
let best: { col: number; row: number; d: number } | null = null;
let firstHitRing = -1;
for (let r = 1; r <= MAX_RING; r++) {
for (let dy = -r; dy <= r; dy++) {
for (let dx = -r; dx <= r; dx++) {
@@ -395,14 +405,20 @@ export function findOpenSpotNear(
const col = baseCol + dx;
const row = baseRow + dy;
if (col < 0 || row < 0) continue;
if (cellFree(col, row)) {
return {
x: GRID_ORIGIN.x + col * cellW,
y: GRID_ORIGIN.y + row * cellH,
};
}
if (!cellFree(col, row)) continue;
const d = spotDist(col, row);
if (!best || d < best.d) best = { col, row, d };
}
}
if (best && firstHitRing === -1) firstHitRing = r;
// Scan one ring past the first hit (a ring-r corner can lose to a ring-(r+1) edge), then commit.
if (firstHitRing !== -1 && r >= firstHitRing + 1) break;
}
if (best) {
return {
x: GRID_ORIGIN.x + best.col * cellW,
y: GRID_ORIGIN.y + best.row * cellH,
};
}
// Pathological, full canvas occupied near anchor. Fall back to the global first-empty scan so we never return an overlap.