feedback7 R23-DRAGSNAP: a dragged operator stands on the tile under the pointer while that is a legal target (a board tile or a bench slot: render/drag.js dragStandTile), as in the official deploy drag (the owner's recording of 2026-10-09), and is held under the pointer elsewhere (an illegal tile, the shop bar); the pick, highlight, tileHover, wheel and drop stay the pointer's tile — the report in PR #403 by @bubububaoshe (not merged); §27.62; golden: unchanged

This commit is contained in:
sganggs committed 2026-10-09 01:38:57 +08:00
1 parent 9bacd9ad45
commit 9405aae60c
11 files changed
+228 -53

No files matched your search

+54 -2
View File
@@ -1,9 +1,10 @@
// test/render/drag.test.js — drop target resolution and the pointer state machine (no DOM, fake timers).
// test/render/drag.test.js — drop target resolution, the pointer state machine (no DOM, fake timers) and the tile a
// dragged unit is drawn standing on (dragStandTile; the harness's 100 px grid stands in for the camera's tile pick).
import { test, describe } from 'node:test';
import assert from 'node:assert/strict';
import { GEO } from '../../shared/constants.js';
import { tileSlot, pieceSlot, pieceTile, resolveDrop, isLegal, sameSlot, createDragController } from '../../public/js/render/drag.js';
import { tileSlot, pieceSlot, pieceTile, resolveDrop, isLegal, sameSlot, createDragController, dragStandTile } from '../../public/js/render/drag.js';
describe('slots & targets', () => {
test('tileSlot classifies hand, temp, board and everything else', () => {
@@ -293,3 +294,54 @@ describe('drag controller', () => {
} finally { console.error = orig; }
});
});
// The official deploy drag (the owner's recording of 2026-10-09; the report in PR #403): the dragged unit is drawn
// standing on the tile under the pointer while that is a legal target, and held under the pointer elsewhere. The tile
// is the drag target itself, so the pick, tileHover, the highlight and the drop stay the pointer's tile.
describe('the tile a dragged unit stands on (dragStandTile)', () => {
test('a legal board tile or bench slot is stood on; an illegal tile, a temp slot, no target hold it under the pointer', () => {
assert.deepEqual(dragStandTile({ target: { area: 'board', row: 10, col: 3 }, legal: true }), { row: 10, col: 3, bench: false });
assert.deepEqual(dragStandTile({ target: { area: 'hand', idx: 6 }, legal: true }), { row: GEO.HAND_ROW, col: 6, bench: true });
assert.equal(dragStandTile({ target: { area: 'board', row: 10, col: 3 }, legal: false }), null, 'an illegal tile');
assert.equal(dragStandTile({ target: { area: 'hand', idx: 6 }, legal: false }), null, 'the own bench slot / an illegal slot');
assert.equal(dragStandTile({ target: { area: 'temp', idx: 1 }, legal: true }), null, 'a temp slot is never a target');
assert.equal(dragStandTile({ target: { area: 'outside', clientX: 1, clientY: 2 }, legal: true }), null);
assert.equal(dragStandTile({ target: null, legal: false }), null, 'off the grid, over the shop bar');
assert.equal(dragStandTile({ target: { area: 'board', row: 10, col: 3 }, legal: 1 }), null, 'legal must be true');
assert.equal(dragStandTile({ target: { area: 'board', row: 9.5, col: 3 }, legal: true }), null);
assert.equal(dragStandTile({ target: { area: 'hand' }, legal: true }), null);
assert.equal(dragStandTile(null), null);
assert.equal(dragStandTile(undefined), null);
assert.deepEqual(dragStandTile({ target: { area: 'hand', idx: 2 }, legal: true }, { HAND_ROW: 3 }), { row: 3, col: 2, bench: true }, 'geo is a parameter');
});
test('over the drag path: one tile while the pointer moves inside it, the next legal tile at once, none where no drop lands (mouse and touch)', () => {
for (const pointerType of ['mouse', 'touch']) {
const h = harness({ domCover: true, canPlace: (p, r, c) => !(r === 11 && c === 4) });
h.c.setEditable(true);
const stand = () => dragStandTile(h.events.filter((e) => e[0] === 'pieceDragMove').pop()[1]);
const at = (x, y) => { h.ev('pointerMove', x, y, { pointerType }); return stand(); };
h.ev('pointerDown', 50, 550, { pointerType });
assert.equal(at(50, 530), null, `${pointerType}: on its own bench slot (no target) it is held under the pointer`);
assert.ok(h.c.dragging);
// the pointer anywhere on (9,4) (x 400–499, y 300–399 in the harness grid): the unit stands on (9,4) the whole time
const inside = [[410, 390], [450, 350], [495, 305], [402, 301]].map(([x, y]) => at(x, y));
assert.deepEqual(inside, Array(4).fill({ row: 9, col: 4, bench: false }), `${pointerType}: moving inside the tile leaves it there`);
assert.deepEqual(at(450, 299), { row: 10, col: 4, bench: false }, `${pointerType}: the next tile takes it at once`);
assert.equal(at(450, 150), null, `${pointerType}: an illegal tile (11,4)`);
assert.equal(at(550, 450), null, `${pointerType}: a temp slot`);
assert.deepEqual(at(250, 550), { row: GEO.HAND_ROW, col: 2, bench: true }, `${pointerType}: another bench slot`);
assert.equal(at(250, 950), null, `${pointerType}: over the shop bar (DOM over the canvas)`);
assert.equal(at(-20, 300), null, `${pointerType}: off the grid`);
assert.deepEqual(at(350, 250), { row: 10, col: 3, bench: false });
h.ev('pointerUp', 350, 250, { pointerType });
assert.deepEqual(h.events.find((e) => e[0] === 'pieceDrop')[1].target, { area: 'board', row: 10, col: 3 }, `${pointerType}: dropped on the tile it stood on`);
// a board piece lifts on its own tile (a legal drop: the direction wheel re-orients it in place)
h.events.length = 0;
h.ev('pointerDown', 450, 250, { pointerType });
assert.deepEqual(at(470, 230), { row: 10, col: 4, bench: false }, `${pointerType}: a board piece stands on its own tile`);
h.ev('pointerUp', 470, 230, { pointerType });
assert.deepEqual(h.events.find((e) => e[0] === 'pieceDrop')[1].target, { area: 'board', row: 10, col: 4 });
}
});
});