fix: seven small honesty fixes from the 29.08 audit (#369)

(a) documented: deleting a vacuum marker erases its server trail at once
and a re-added marker starts from scratch (VACUUM.md + both USER-GUIDEs).
(b) smoothVacPath reports dropped non-finite segments — one console warn
per call with the count — instead of hiding the whole trail silently on a
broken calibration matrix. (c) room climate (#317) now reaches legacy
markers whose exported config carries an ABSENT area key rather than an
explicit null: `== null` where the placement is decided. (d) the armed
furniture preview follows Shift without mouse movement — window
keydown/keyup listeners live exactly as long as the palette is armed,
detached at every palette teardown. (e) only the primary mouse button
places decor/furniture: a right or middle click with an armed tool is a
no-op, touch/pen untouched. (f) a device whose registry entities were ALL
deliberately disabled by the user no longer glows as an alive controller —
the #318 entityless-active rule now requires a genuinely empty roster.
(g) furniture-pack author corrected to Sergey Matyunin (Сергей Матюнин)
per the owner's decision — LICENSE.md, README.md, pack.json,
docs/FURNITURE.md, the provenance check in generate-furniture-assets and
its unit; the source archive bytes are unchanged and the README notes the
romanisation fix.

Proofs: units for (b)/(c)/(f) including the #318 regression pair; new
smoke_furniture_polish for (d)/(e) with listener add/remove counters and
both mouse buttons; five registry mutants, one per code change.

Issue: #369
User-Visible: yes
This commit is contained in:
Codex
2026-08-29 11:29:42 +03:00
parent a041934bc2
commit 8ee1c91fff
38 changed files with 551 additions and 212 deletions
+103
View File
@@ -0,0 +1,103 @@
// #369(д)(е): the armed furniture preview follows Shift without mouse motion,
// its window listeners come and go with the palette, and only the primary
// mouse button places objects — a right-click with an armed tool is a no-op.
import { launch, checkAll, finish } from './serve.mjs';
const { page, browser } = await launch();
const out = await page.evaluate(async () => {
const out = {};
const c = window.__card;
const sr = () => c.shadowRoot || c.renderRoot;
const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
const stageEl = () => sr().querySelector('.stage');
const toScreen = (x, y) => {
const r = stageEl().getBoundingClientRect();
const v = c._viewOr(c._baseVb());
return [r.left + ((x - v.x) / v.w) * r.width, r.top + ((y - v.y) / v.h) * r.width * (v.h / v.w) * (r.height / (r.width * (v.h / v.w)))];
};
// счётчики слушателей окна
const addReal = window.addEventListener.bind(window);
const removeReal = window.removeEventListener.bind(window);
let shiftAdds = 0; let shiftRemoves = 0;
window.addEventListener = (type, fn, opts) => {
if (type === 'keydown' || type === 'keyup') shiftAdds++;
return addReal(type, fn, opts);
};
window.removeEventListener = (type, fn, opts) => {
if (type === 'keydown' || type === 'keyup') shiftRemoves++;
return removeReal(type, fn, opts);
};
// в декор-режим, инструмент «мебель», выбрать символ
await c._requestMode('decor', false);
await c.updateComplete;
c._editorSecondary.openPalette?.();
c._decorTool = 'furniture';
c._furnPalette = null; c._furnCategory = null;
c.requestUpdate(); await c.updateComplete;
const pal = () => sr().querySelector('.furnpalette');
pal()?.querySelector('.furnitem[data-category="sofa"]')?.click();
await c.updateComplete;
pal()?.querySelector('.furnitem[data-symbol="sofa"]')?.click();
await c.updateComplete;
out.symbolArmed = !!c._furnPalette;
out.listenersAttached = shiftAdds >= 2;
// превью от движения мыши
const stage = stageEl();
const rect = stage.getBoundingClientRect();
const cx = rect.left + rect.width * 0.5;
const cy = rect.top + rect.height * 0.55;
stage.dispatchEvent(new PointerEvent('pointermove', {
bubbles: true, composed: true, pointerType: 'mouse', pointerId: 7,
clientX: cx, clientY: cy,
}));
await c.updateComplete;
out.previewVisible = !!c._furnPreviewInput;
out.previewStartsSnapped = c._furnPreviewInput?.free === false;
// (д): Shift БЕЗ движения мыши переводит превью в free и обратно
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Shift', bubbles: true }));
await c.updateComplete;
out.shiftDownGoesFree = c._furnPreviewInput?.free === true;
window.dispatchEvent(new KeyboardEvent('keyup', { key: 'Shift', bubbles: true }));
await c.updateComplete;
out.shiftUpBacksToSnap = c._furnPreviewInput?.free === false;
// (е): правый клик с armed-символом НЕ ставит мебель
const beforeCount = c._decorList.length;
stage.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true, composed: true, pointerType: 'mouse', pointerId: 8,
clientX: cx, clientY: cy, button: 2, buttons: 2,
}));
await c.updateComplete; await sleep(50);
out.rightClickPlacesNothing = c._decorList.length === beforeCount
&& !!c._furnPalette;
// левый клик — ставит (регресс)
stage.dispatchEvent(new PointerEvent('pointerdown', {
bubbles: true, composed: true, pointerType: 'mouse', pointerId: 9,
clientX: cx, clientY: cy, button: 0, buttons: 1,
}));
await c.updateComplete; await sleep(50);
out.leftClickPlaces = c._decorList.length === beforeCount + 1;
// (д): после установки палитра погашена — слушатели сняты симметрично
out.paletteDisarmed = !c._furnPalette;
out.listenersDetached = shiftRemoves >= 2 && shiftRemoves === shiftAdds;
// Shift после снятия — не трогает ничего (превью нет)
window.dispatchEvent(new KeyboardEvent('keydown', { key: 'Shift', bubbles: true }));
out.shiftAfterDetachIsInert = !c._furnPreviewInput;
window.dispatchEvent(new KeyboardEvent('keyup', { key: 'Shift', bubbles: true }));
window.addEventListener = addReal;
window.removeEventListener = removeReal;
// прибрать поставленный диван
const sp = c._curSpaceCfg;
sp.decor = sp.decor.slice(0, beforeCount);
c._decorSel = null;
c.requestUpdate(); await c.updateComplete;
return out;
});
checkAll(out);
await finish(browser, out);