From e00f62dca005ab80ee357c77bf915bed0fae3cfd Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 30 Sep 2026 23:29:27 +0300 Subject: [PATCH] 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 Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd --- test/summary-panel.test.mjs | 38 +++++++++++++++++++++++++++++++------ 1 file changed, 32 insertions(+), 6 deletions(-) diff --git a/test/summary-panel.test.mjs b/test/summary-panel.test.mjs index 6db33144..270677e3 100644 --- a/test/summary-panel.test.mjs +++ b/test/summary-panel.test.mjs @@ -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)'); });