refactor: extract resize controller

Issue: #264
User-Visible: no
This commit is contained in:
Matysh
2026-08-27 20:30:07 +03:00
parent ef32d2326f
commit ba66698f55
29 changed files with 3112 additions and 2564 deletions
+2 -36
View File
@@ -20,6 +20,8 @@ import { readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { checkOptimizeGeometry } from '../test-build/plan-geometry-preflight.js';
import { classifyNearAxisSegment } from '../test-build/near-axis.js';
import { checkWallRecordsPreserved } from '../test-build/wall-record-preservation.js';
export { checkWallRecordsPreserved } from '../test-build/wall-record-preservation.js';
import {
LATTICE_GRID_N as GRID_N,
LATTICE_NOISE_STEPS as NOISE_STEPS,
@@ -287,42 +289,6 @@ export function checkReferences({ config, layout = {} } = {}, { notes = [] } = {
return violations;
}
/**
* Инвариант 1: запись толщины не исчезает.
*
* Сравнивается мультимножество значений `cm`, а не суммарная длина: ресайз
* законно укорачивает стены, а склейка двух одинаковых записей законно
* уменьшает их число. Незаконно ровно одно — исчезновение значения целиком,
* как в #253, где 33 см пропали вместе с кладкой соседних комнат.
*
* `allowClear` — единственное исключение, и оно объявляется вызывающим:
* пользователь очистил толщину явно.
*/
export function checkWallRecordsPreserved(before, after, { allowClear = false } = {}) {
if (allowClear) return [];
const counts = (walls) => {
const map = new Map();
for (const wall of walls || []) {
if (!isFiniteNumber(wall?.cm) || !(wall.cm > 0)) continue;
const key = String(wall.cm);
map.set(key, (map.get(key) || 0) + 1);
}
return map;
};
const from = counts(before), to = counts(after);
const violations = [];
for (const [cm, was] of from) {
const now = to.get(cm) || 0;
if (now === 0) {
violations.push({
invariant: 'wall_records', kind: 'lost', owner: `${cm} см`,
reference: `было ${was}`, detail: 'записи этой толщины исчезли целиком',
});
}
}
return violations;
}
/**
* Копия ключа отрезка из `src/wall-thickness.ts`.
*
+6 -5
View File
@@ -431,9 +431,9 @@ export const MUTANTS = [
because: 'a candidate rejected by the exact production wall/floor preflight must create '
+ 'neither preview nor commit; polygon-only success previously persisted disappearing walls',
patches: [{
file: 'src/houseplan-card.ts',
find: ' const candidateValid = this._rszCandidateRenderable(preview);',
replace: ' const candidateValid = !!preview;',
file: 'src/resize-controller.ts',
find: ' try { valid = input.validatePreview(accepted.preview); } catch { valid = false; }',
replace: ' try { valid = true; } catch { valid = false; }',
}, {
file: 'src/houseplan-card.ts',
find: ' if (!legacySafe) {',
@@ -753,12 +753,13 @@ export const MUTANTS = [
},
{
id: 'invariant-loses-wall-record',
guard: 'node --test --test-name-pattern="исчезнувшая запись толщины" '
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs '
+ '&& node --test --test-name-pattern="исчезнувшая запись толщины" '
+ 'test/model-invariants.test.mjs',
because: 'потеря записи толщины — это дефект #253, найденный человеком глазами; если '
+ 'инвариант перестанет её замечать, класс вернётся в продукт незамеченным (#254)',
patches: [{
file: 'scripts/model-invariants.mjs',
file: 'src/wall-record-preservation.ts',
find: ' if (now === 0) {',
replace: ' if (false) {',
}],