test: обход последовательностей правок с инвариантами после каждого жеста

Все прежние гейты проверяют снимок модели. Дефекты геометрии рождаются в
редактировании: #289, #296 и #298 прошли решётку, кладку, роли, ключи и аудит
ручек, потому что такая геометрия снимок не портит — она портит следующий жест.

demo/smoke_edit_walk.mjs расшатывает реальный план продуктовыми жестами
(_rszEdgeDown/_rszMove/_rszUp, _confirmRoomDelete, optimizePlans) по фиксированному
семени и после каждого шага судит конфиг в node. Второго представления редактора
не появляется — принцип #292.

Подшаговый шум остаётся наблюдением, а не нарушением: координата пишется девятью
знаками, 304/240 = 1.266666667, отклонение 8e-8 шага неустранимо форматом
хранения и уйдёт на этапе 1 ADR #282. Судится только «вне сетки».

Таблица KNOWN работает в обе стороны: обход падает и когда находок больше, и
когда меньше. Молча позеленевший гейт не сообщает о починке — так
partition-mt2on9ou-0 прожил в плане владельца от беты 9 до rc.1.

Новый инвариант checkHiddenObstacles: перегородка на стене комнаты, незакрытый
контур на стене комнаты, черновик, который не может стать комнатой (#296).

Найдено сразу, в пяти прогонах из шести — на первом жесте: #298 (ресайз уводит
конец записи толщины мимо решётки и мимо ребра), #299 («Оптимизировать» и
удаление комнаты сливают записи через границу роли).

Issue: #297
User-Visible: no
This commit is contained in:
Claude
2026-08-24 21:14:30 +03:00
parent 8c03656412
commit 1816042a1a
5 changed files with 577 additions and 2 deletions
+38
View File
@@ -582,6 +582,44 @@ export const MUTANTS = [
replace: ' if (false) {',
}],
},
{
id: 'invariant-hidden-counts-corner-touch',
guard: 'node --test --test-name-pattern="касание углом" test/model-invariants.test.mjs',
because: 'перегородка, продолжающая стену за угол, законна: общего с ребром у неё ровно '
+ 'точка. Порог в шаг решётки — единственное, что отделяет находку от ложного '
+ 'срабатывания на каждом углу плана (#296)',
patches: [{
file: 'scripts/model-invariants.mjs',
find: ' if (overlap > GRID_STEP_N && (!best || overlap > best.overlap)) {',
replace: ' if (overlap >= 0 && (!best || overlap > best.overlap)) {',
}],
},
{
id: 'invariant-hidden-tolerates-two-point-draft',
guard: 'node --test --test-name-pattern="черновик из двух точек" '
+ 'test/model-invariants.test.mjs',
because: 'ровно это условие пропускает вырожденный черновик в align-grid: `points.length '
+ '>= 2` считает контур из двух точек годным, а комнатой он не станет никогда — '
+ 'и остаётся невидимым препятствием для ресайза (#296)',
patches: [{
file: 'scripts/model-invariants.mjs',
find: ' if (points.length < 3) {',
replace: ' if (points.length < 2) {',
}],
},
{
id: 'invariant-hidden-ignores-collinearity',
guard: 'node --test --test-name-pattern="перегородка поперёк комнаты" '
+ 'test/model-invariants.test.mjs',
because: 'перегородка поперёк комнаты — единственный смысл существования перегородки; '
+ 'без проверки поперечного отклонения проверка ловит её и будет отключена в первую '
+ 'неделю (#296)',
patches: [{
file: 'scripts/model-invariants.mjs',
find: ' if (across(c) > EDGE_TOLERANCE || across(d) > EDGE_TOLERANCE) return 0;',
replace: ' if (false) return 0;',
}],
},
{
id: 'invariant-roles-sample-endpoints',
guard: 'node --test --test-name-pattern="реальные планы проекта эту проверку" '