test(summary): #509 AC3 counts masonry passes instead of milliseconds (#721)

The large-house AC3 witness asserted an absolute 2.5 s budget for one
floor's clean-floor total. On a loaded 2-CPU machine the healthy path
took 2.7-4.3 s and the test went red while the code was fine.

The property #509 AC3 protects is structural: the summary panel builds
the space's wall masonry once and hands it to innerContourForRoom
(shared.roomGeom / shared.multiWallNodes); without it the masonry is
rebuilt for every room. Every masonry build walks the contours of all
rooms, so the test now counts reads of room.poly and compares the
floor total against one explicit spaceWallGeometry pass of the same
floor in the same run. Healthy code costs ~1.3 passes; the registered
mutant summary-area-recomputes-walls-per-room costs 21.3 and is red,
and so are the half-regressions that drop only one of the two shared
arguments (4.6 and 18.0 passes).

The count is deterministic, so machine load no longer matters.

Issue: #721
User-Visible: no
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
This commit is contained in:
Claude
2026-09-30 22:16:19 +00:00
committed by claude[bot]
parent bbcc88caf1
commit e00f62dca0
+32 -6
View File
@@ -2,6 +2,7 @@ import assert from 'node:assert/strict';
import test from 'node:test';
import { makeLargeHouseFixture } from '../demo/fixtures/large-house.mjs';
import { spaceModels } from '../test-build/space-geometry.js';
import { prepareSpacePhysicalGeometryInputs } from '../test-build/plan-geometry-preflight.js';
import { readFileSync } from 'node:fs';
import {
@@ -476,19 +477,44 @@ test('#509 AC3: площадь считает геометрию стен оди
test('#509 AC3: один этаж большого дома считается без пересборки кладки на каждой комнате', () => {
// Синтетический счётчик выше доказывает «один вызов на пространство», но не
// то, что результат этого вызова ДОШЁЛ до innerContourForRoom: без
// shared-аргументов та объединяет кладку заново для каждой комнаты, и
// единственный наблюдаемый признак — время (S2: 176 мс на комнату).
// Разрыв семикратный, поэтому порог грубый и не флейкует.
// shared-аргументов та объединяет кладку заново для каждой комнаты.
// Этот AC измеряет повторное построение кладки. Максимальные 250 лестниц
// отдельно входят в общий large-house benchmark и не должны превращать
// узкий wall-cache witness в тест другой подсистемы (#663).
//
// Меряется работа, а не миллисекунды: абсолютный порог по времени краснел
// на занятой машине (#721). Любая сборка кладки обходит контуры всех комнат
// пространства, поэтому счётчик чтений `room.poly` детерминированно
// отражает число таких сборок. Эталон — один явный проход кладки того же
// этажа в том же прогоне: с общей геометрией площадь этажа стоит ~1,3
// прохода (сам проход + подготовка + лёгкая работа по комнатам), с
// пересборкой на каждой комнате — ~21.
const fixture = makeLargeHouseFixture({ includeStairs: false });
const config = { ...fixture.config, spaces: fixture.config.spaces.slice(0, 1) };
const model = spaceModels(config);
assert.equal(model[0].rooms.length, 20, 'фикстура даёт этаж из 20 комнат');
const started = Date.now();
let contourReads = 0;
for (const room of model[0].rooms) {
let poly = room.poly;
assert.ok(poly?.length >= 3, `комната ${room.id} задана контуром`);
Object.defineProperty(room, 'poly', {
configurable: true, enumerable: true,
get() { contourReads += 1; return poly; },
set(next) { poly = next; },
});
}
const prepared = prepareSpacePhysicalGeometryInputs(config.spaces[0], model[0]);
const beforePass = contourReads;
spaceWallGeometry(model[0], prepared);
const onePass = contourReads - beforePass;
assert.ok(onePass >= model[0].rooms.length, `эталонный проход кладки прочитал ${onePass} контуров — счётчик ослеп`);
const beforeArea = contourReads;
const area = totalCleanFloorAreaM2(config, model);
const elapsed = Date.now() - started;
const areaWork = contourReads - beforeArea;
assert.ok(area > 0, String(area));
assert.ok(elapsed < 2500, `этаж из 20 комнат посчитан за ${elapsed} мс — кладка собирается заново на каждой комнате (#509)`);
const passes = areaWork / onePass;
assert.ok(passes < 2, `площадь этажа из 20 комнат стоила ${areaWork} чтений контуров = `
+ `${passes.toFixed(1)} прохода кладки (эталон ${onePass}); ожидается один общий проход — `
+ 'кладка собирается заново на каждой комнате (#509)');
});