perf/fix v1.43.1: external audit P1 — render cost, drag threshold, geometry, backend

L1: memoized space model + open pairs (structural fingerprint key, epoch
bumped synchronously at mutation time, not inside the debounce); hoisted
per-room geometry out of the render loop; smoke asserts zero recomputation
across state pushes.

L4: openings get the 3 px drag threshold used by every other pipeline and
only write when the geometry actually changed — taps open the dialog again.

G2: interiorPoint() replaces the vertex mean, so island rooms inside
concave (U/L) parents are accepted and their evenodd holes render; traced
duplicates still are not containment.

G3: segKey rounds before ordering — one shared wall, one key.

B2: _check_write fails closed when the entry is unavailable.
B3: layout/set honours expected_rev and returns the new rev.
B4: config/set without expected_rev over a non-empty store logs a warning.
B5: coordinates reject NaN/Infinity; spaces/rooms/markers/decor/layout capped.

+2 unit tests (118), +2 backend tests (14), smoke_render_perf; docs
same-commit
This commit is contained in:
Matysh
2026-07-27 10:58:18 +03:00
parent 0fd0ba408d
commit 49b0cb4e05
16 changed files with 445 additions and 160 deletions
+23
View File
@@ -13,6 +13,7 @@ import {
migratePdfUrls,
roomFillModeOf,
contentUrl,
interiorPoint,
segmentCm, formatLength, roomEdges, roomPoly, pointOnBoundary, pointStrictlyInside, roomsOverlap,
mergeRooms, splitRoom, polygonArea, closestPointOnBoundary, isActiveState, snapToWall, openingAmount, fillColorsOf, lerpColor, roomFillStyle, stateIcon, lightColorOf, isAlarmState, parseRoomRef, diffNewDevices,
} from '../test-build/logic.js';
@@ -844,3 +845,25 @@ test('contentUrl: legacy static paths become authenticated ones (audit B1)', ()
assert.equal(contentUrl(''), '');
assert.equal(contentUrl(null), '');
});
test('interiorPoint / polyContainsPoly on concave rooms (audit G2)', () => {
// U-образная комната: среднее вершин лежит СНАРУЖИ
const u = [[0, 0], [10, 0], [10, 10], [9, 10], [9, 2], [1, 2], [1, 10], [0, 10]];
const mean = [u.reduce((s, p) => s + p[0], 0) / u.length, u.reduce((s, p) => s + p[1], 0) / u.length];
assert.equal(pointStrictlyInside(mean, u), false, 'предпосылка: среднее снаружи');
const ip = interiorPoint(u);
assert.ok(ip && pointStrictlyInside(ip, u), 'interiorPoint обязана быть внутри');
// вложенность вогнутых теперь распознаётся
const outer = [[-1, -1], [11, -1], [11, 11], [8.5, 11], [8.5, 2.5], [1.5, 2.5], [1.5, 11], [-1, 11]];
assert.equal(polyContainsPoly(outer, u), true);
assert.equal(roomsOverlap(outer, u), false);
// дубликат по-прежнему НЕ вложенность
assert.equal(polyContainsPoly(u, u), false);
});
test('segKey: one wall, one key at any precision (audit G3)', () => {
assert.equal(segKey([1.000001, 1], [1.000002, 2]), segKey([1.000002, 1], [1.000001, 2]));
assert.equal(segKey([0, 0], [1, 1], 3), segKey([1, 1], [0, 0], 3));
// разные стены — разные ключи
assert.notEqual(segKey([0, 0], [1, 1]), segKey([0, 0], [2, 2]));
});