23 KiB
SPEC-REVIEW-230-r1
- Issue: https://github.com/Matysh/houseplan-card/issues/230
- ТЗ:
docs/specs/230-hatch-density-normalization.md - Ветка / SHA:
issue/230-hatch-density-normalization@2396044 - Этап: spec (PROCESS.md §2.4) · трек:
small(метка на issue) - Заход: r1 · блокирующих циклов израсходовано 0/4 до этого вердикта
- Ревьюер: Claude, роль «ревьюер ТЗ»
Скоуп
Оценивалось ТЗ #230 — нормализация плотности штриховки тела стены к
физическим сантиметрам плана и удаление зумовой компенсации паттерна. Код не
менялся и не оценивался (реализации нет, этап S4). Проверялись: соответствие
docs/SCOPE.md, фактическая точность ссылок на код и на golden-фикстуры,
однозначность и доказуемость AC1–AC10, согласованность Scope/Contract/AC между
собой.
Как проверялось
- Прочитаны
docs/SCOPE.md,PROCESS.mdцеликом (первый цикл, правило «по дельте» §2.10 не применяется). - Прочитано тело issue #230 и единственный комментарий («ТЗ готово, на
ревью»); в истории issue нет отдельного
S2-analysis-комментария с шаблонной «Оценкой» (§7.2) — трекsmallи меткиbug/polishвидны только как текущее состояние. - Прочитан файл ТЗ целиком.
- Построчно проверены все фактические ссылки на код:
src/wall-thickness.ts:40-73(wallBodyNeedsSolid,WALL_HATCH_MIN_PX = 3,clampWallCm,wallCmToUnits) — совпадает с ТЗ;src/houseplan-card.ts:6163-6166(_cellCm, дефолт 5 при нечисловом/≤0 значении) — совпадает;src/houseplan-card.ts:11079-11093(_wallHatchDefs,const inv = ...,patternTransform="rotate(45) scale(${inv})") — совпадает буквально;src/space-render.ts:509-513(тот же<pattern>безscale, толькоrotate(45)) — подтверждает заявленное расхождение рендереров уже сегодня;src/space-geometry.ts:11,197,199(NORM_W=1000,GRID_N=240,GRID_PITCH=NORM_W/GRID_N≈4.1667) — подтверждает таблицу в §3 ТЗ;src/houseplan-card.ts:285-286,src/plan-optimizer.ts:32-33(CELL_CM_MIN=0.1,CELL_CM_MAX=1000) — подтверждает диапазонcell_cmиз §8.3.
- Алгебраически проверены формулы AC1–AC6:
wallHatchStepUnits(cellCm) = 8·(5/cellCm); отношениеwallCmToUnits(cm,cell,GP)/wallHatchStepUnits(cell) = cm·GP/40не зависит отcell, то есть AC2 доказывает именно заявленную инвариантность, а дляcm=30противcm=15отношение ровно вдвое (AC3). Клампы[0.5, 80]алгебраически покрывают весь диапазонcell_cm0.1…1000без разрывов (AC5). - Проверена схема вызова
wallBodyNeedsSolidв обоих рендерерах (houseplan-card.ts:11312,space-render.ts:473) — точка интеграции для новогоwallHatchNeedsSolid(§8.4) существует и однозначна. - Проверены golden-фикстуры, а не только текст ТЗ о них:
demo/fixtures/large-house.mjs:209-218(perf-floor-1,cell_cm: 5, стены 15 см,show_borders: true) иdemo/golden/matrix.mjs:328-331(сценарииlarge-house-zoom-040-dark—zoom: 0.4,large-house-zoom-250-dark—zoom: 2.5, оба наfixture: 'large',space: 'perf-floor-1') — см. High-1. - Проверено, что
src/space-card.tsне является третьим независимым потребителем паттерна: он рендерит черезrenderSpaceStaticизspace-render.ts(уже в скоупе §6), CSS-класс.wallbody{fill:url(#hp-wall-hatch)}лишь ссылается на тот же<pattern>, который создаёт вызванный рендерер — не находка. - Проверено
docs/WALL-THICKNESS.md§3 (Body render) — текущая редакция не описывает ни зависимость шага паттерна отcell_cm, ни зумовую компенсацию, то есть менять там формально нечего, но раздел про решение «убрать зумовую компенсацию» отсутствует — см. Low-1. - Проверено
docs/USER-GUIDE.ru.md:442— существующая строка про штриховку («на малом масштабе штриховка скрывается, тело остаётся») не противоречит планируемой правке §15. - Код не запускался, гейты не гонялись — реализации нет, это ожидаемо на этапе ревью ТЗ.
Находки
High-1 — риск-анализ и AC10 построены на неверной посылке о golden-фикстурах
Файл: docs/specs/230-hatch-density-normalization.md, §11 «Риски», п.2
(Golden) и §12, AC10.
Формулировка ТЗ: «Все golden-фикстуры используют cell_cm: 5
(demo/fixtures/*.mjs, demo/golden/harness.mjs) и не меняют зум, поэтому
эталоны не должны измениться ни в одном пикселе. Это проверяемое утверждение:
golden:verify обязан пройти без переснятия.»
Проблема. Первая половина утверждения верна: оба файла фикстур
(demo/fixtures/large-house.mjs, demo/fixtures/visual-matrix.mjs) задают
cell_cm: 5 для каждого пространства. Вторая половина — «не меняют зум» —
фактически неверна: demo/golden/matrix.mjs:328-331 определяет ровно два
сценария на фикстуре large (пространство perf-floor-1, стены 15 см,
show_borders: true, то есть тело стены рисуется):
{ id: 'large-house-zoom-040-dark', fixture: 'large', space: 'perf-floor-1',
mode: 'view', zoom: 0.4, theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
{ id: 'large-house-zoom-250-dark', fixture: 'large', space: 'perf-floor-1',
mode: 'view', zoom: 2.5, theme: 'dark', viewport: { width: 1180, height: 900 }, ...stage },
demo/golden/harness.mjs:440-441 применяет scenario.zoom через
card._applyView(scenario.zoom, 500, 500) — то есть зум реально
устанавливается перед снимком, а не игнорируется.
Это не мелкая деталь, а прямое противоречие продуктовому решению владельца из
того же ТЗ (§4, п.2): «Зумовая компенсация убирается... Число полос внутри
стены одинаково на любом зуме». Сегодня inv = max(0.4, 1/max(zoom,0.4)) при
zoom=0.4 даёт inv=2.5, при zoom=2.5 даёт inv=0.4 — то есть паттерн
hp-wall-hatch в этих двух golden-сценах сегодня отрисован с ненулевым
масштабом, а после правки patternTransform вообще не содержит scale
(§8.2 ТЗ, дословно: «Множителя 1/zoom больше нет ни в одном рендерере»). Это
ровно та точка, где линии паттерна на экране видимо переместятся — если тело
стены на этих экранах не провалилось в solid (порог WALL_HATCH_MIN_PX=3),
разница будет пикселем, особенно на zoom: 2.5 (зум внутрь, то есть
больше пикселей на юнит, а не меньше — вероятность попасть в solid-режим
там ниже, чем на zoom: 0.4).
Почему это находка, а не техническая деталь. Владелец явно одобрил именно
это изменение видимого поведения при зуме (§4, п.2) — то есть расхождение
этих двух golden-сцен, если оно произойдёт, не является «правка задела
больше заявленного» из формулировки риска в самом ТЗ; это прямое и ожидаемое
следствие продуктового решения, зафиксированного в том же документе. ТЗ
противоречит самому себе: §4 обещает видимое изменение при любом зуме ≠
эталонного поведения на экране, а §11/AC10 обещают, что ни одна golden-сцена
не изменится ни на пиксель. Оба обещания не могут быть верны одновременно для
сцен, снятых при zoom ≠ 1.
Почему это блокирует, а не варьируется по ходу реализации. AC10 —
единственная строка ТЗ, которая должна поймать регрессию из риска №3
(расхождение карты и статического рендерера) и защитить от случайного
охвата больше заявленного. Если формулировка AC10 неверна, разработчик либо
(а) увидит красный golden:verify там, где всё сделано правильно, и не будет
знать, чинить код или переснимать эталон — ровно то самое решение, которое
процесс явно запрещает принимать в одиночку («переснятие ради зелёного CI»,
§12 PROCESS.md), либо (б) продавит переснятие двух сцен без разбора, чтобы
пройти AC10, что легитимно только через golden:accept -- --reviewed, но ТЗ
не называет этот путь и не готовит к нему код-ревью.
Что нужно. Явно разобрать оба сценария large-house-zoom-*-dark в §11/§12:
- посчитать/оговорить, действительно ли тело стены
perf-floor-1(15 см,cell_cm: 5) остаётся в режимеhatch(неsolid) на этих двух зумах при ихviewport; и - если остаётся — заранее объявить, что эти две сцены ожидаемо меняются
(это следствие §4 п.2, не побочный эффект) и должны быть переснятые через
golden:accept -- --reviewedс отдельной пометкой в хендоффе, ЛИБО сузить AC10 формулировкой вида «golden:verifyпроходит без переснятия для всех сцен, кромеlarge-house-zoom-040-dark/large-house-zoom-250-dark, которые меняются предсказуемо и переснимаются по правилу §12 PROCESS.md».
Medium-1 — контракт «оба рендерера» не имеет собственного доказательства для статического рендерера
Файл: docs/specs/230-hatch-density-normalization.md, §6, §8.2, AC7, AC8,
§13.
Проблема. §6 (Scope) и §8.2 (Contract) требуют, чтобы оба рендерера —
houseplan-card.ts (интерактивная карта) и space-render.ts (статический
рендерер Room View card / киоск) — строили <pattern> через
wallHatchStepUnits(cellCm). Но AC7 и AC8 явно говорят «в разметке карты» /
«на реальной карте», и запланированный смок (§13, demo/smoke_wall_hatch_density.mjs)
описан как проверка «на реальной карте при cell_cm 5 и 25» — то есть тоже
только интерактивный рендерер. Ни один AC не проверяет разметку паттерна
space-render.ts при cell_cm ≠ 5.
AC10 (golden:verify) не закрывает этот пробел: как показано в High-1, все
golden-сцены используют cell_cm: 5, а при cell_cm === 5
wallHatchStepUnits(5) === 8 — то есть числовое совпадение со старой
константой width="8" height="8". Если разработчик поправит только
houseplan-card.ts и оставит в space-render.ts:509 старый жёстко заданный
width="8" height="8", все AC1–AC10 в текущей формулировке останутся
зелёными, а требование §6/§8.2 «тот же паттерн через ту же функцию» останется
невыполненным и непроверенным ни одним автотестом — регрессия того самого
расхождения рендереров, которое ТЗ называет риском №3 и обещает зафиксировать
тестом.
Что нужно. Одна из двух правок: расширить формулировку AC7/AC8 (или
добавить AC7b) так, чтобы она явно требовала проверки разметки паттерна
space-render.ts при cell_cm ≠ 5 — например, через прямой вызов
renderSpaceStatic/buildSpaceDevices с фикстурой cell_cm: 25 и разбор
получившейся строки <pattern>, не обязательно в браузере. Это устраняет
пробел без изменения продуктового контракта.
Low-1 — канонический документ подсистемы не входит в release-артефакты
Файл: docs/specs/230-hatch-density-normalization.md, §15.
docs/WALL-THICKNESS.md §3 («Body render») — канонический документ ровно той
подсистемы, которую меняет эта задача (штриховка тела стены), и в этом же
документе уже принято фиксировать точечные решения такого рода инлайн-ссылками
на issue (#197, #198, #201 в тексте §3/§8). §15 ТЗ называет только оба
CHANGELOG и строку в docs/USER-GUIDE.ru.md, не упоминая обновление
WALL-THICKNESS.md фразой про новую формулу шага и про то, что зумовая
компенсация паттерна больше не существует ни в одном рендерере. Не блокирует
и не влияет на проверяемость AC — снимаю с рекомендацией добавить одну фразу
в §3 канона в том же коммите, что и код (по аналогии с уже принятой в
документе практикой).
Что проверено и корректно
- Обязательные разделы §7.1 присутствуют по существу: сценарий и персона (§1), видимое изменение до/после (§2), причина (§3), продуктовые решения владельца (§4), цели (§5), scope/не-scope (§6–7), контракт (§8), данные/i18n (§9), performance (§10), риски (§11), AC1–AC10 (§12), план тестов (§13), мутационный гейт (§14), release-артефакты (§15), откат (§16), явный блок предположений (§17).
- Продуктовая рамка (§1–2) отвечает на оба обязательных вопроса — персона
(«ведёт планы двух домов» из
docs/SCOPE.mdJ4/J6), поверхность (десктопный просмотр плана), объём видимого изменения одной фразой без терминов реализации. - Главный технический вывод ТЗ верен и лучше исходного issue. Формула из
тела issue (
cell_cm/5) действительно усугубляет разброс, а не устраняет его — проверено алгебраическим пересчётом (см. «Как проверялось», п.5) тем же способом, каким ТЗ его приводит. Обратный множитель5/cellCm— корректное решение поставленной владельцем задачи «привязать плотность к сантиметрам плана». - AC1, AC4, AC5, AC6, AC9 однозначны, проверяемы и указывают способ
доказательства (
unit); тривиально вычисляются вручную и дают ожидаемый автором результат — не оставляют пространства для интерпретации при написании теста. - AC2/AC3 математически доказывают именно то, что заявлено целью (§5):
независимость числа полос от
cell_cmи пропорциональность толщине стены, а не декларируют это без доказательства. - Обращение к геттеру
_cellCm(нечисловой/нулевой/отрицательный → 5) в §8.1 зафиксировано с точной ссылкой на существующий прецедент (houseplan-card.ts:6163) — не самостоятельно изобретённое правило. - §7 («Не входит в задачу») корректно исключает угол штриховки, порог
WALL_HATCH_MIN_PX, изометрию (_renderProjection === 'iso'— подтверждено чтениемhouseplan-card.ts:11303) и штриховку прочих сущностей — все четыре исключения проверены по коду, а не заявлены на веру. Touch editor: not exposed— корректное значение: задача не добавляет ни одного взаимодействия, только характер отрисовки уже существующего слоя.- Мутационный гейт (§14) целится в реальные, а не декоративные ошибки:
hatch-step-invertedпрямо воспроизводит ошибку из формулировки issue,hatch-zoom-compensation-backзащищает именно от регрессии, названной в риске №3. - Откат (§16) реалистичен: чистая функция, конфиг не меняется, revert безопасен.
Продуктовые вопросы владельцу
Не требуются. Единственная точка, где решение владельца уже расходится с текстом того же ТЗ (High-1), решается не новым продуктовым вопросом, а уточнением формулировки риска/AC — владелец уже высказался по существу (§4, п.2), автору нужно привести §11/AC10 в соответствие с этим решением, а не получить новое.
Чего не проверял
- Реализацию — её не существует на этом этапе.
- Действительно ли
large-house-zoom-040-dark/large-house-zoom-250-darkсегодня попадают вsolid-режим поWALL_HATCH_MIN_PX=3или показывают видимую штриховку — для этого нужно посчитатьpxPerUnitчерез реальныйviewport/_baseVb/_applyViewэтой фикстуры или прогнатьgolden:verifyпо факту. Не требуется для вывода High-1: сам факт, что ТЗ безусловно утверждает «зум не меняется» при наличии двух сцен сzoom ≠ 1, уже делает риск-анализ и AC10 недостоверными независимо от точного числового исхода. - Соответствие метки
smallфактической сложности задачи (три модуля, golden-риск, требующий отдельного расчёта) — не поднимаю как находку: ТЗ существует как полноценный файлdocs/specs/230-*.mdнезависимо от метки трека, содержание оценивается по существу, а не по тому, в какой контейнер его положили. demo/golden/harness.mjsцеликом и остальные ~40 golden-сцен построчно — проверено выборочно (grepнаzoom:/cell_cm), достаточно для вывода High-1; полный построчный обзор оставляю код-ревью при реализации.
Вердикт
Одна блокирующая находка (High-1): риск-анализ и AC10 построены на проверяемо неверной посылке о golden-фикстурах, что делает главный защитный AC задачи ненадёжным как написано. Medium-1 и Low-1 в скоупе задачи и чинятся правкой текста ТЗ, без изменения продуктового решения владельца. Итог — жёлтый: возврат автору на правку ТЗ, повторный цикл ревью по дельте (PROCESS.md §2.10).