Files
houseplan-card/docs/reviews/SPEC-REVIEW-302-r1.md
T
2026-08-25 17:34:18 +03:00

155 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# SPEC-REVIEW-302-r1
- Issue: [#302](https://github.com/Matysh/houseplan-card/issues/302) — «Артефакты на стыках стен: дыры в union-геометрии. Переработка механизма стыков + полноценный сет скриншот-тестов»
- ТЗ: `docs/specs/302-junction-node-material.md`
- Ветка: `issue/302-junction-node-material`, ревьюемый SHA: `3b19111aa5894f624e608b9f7fef31736a4ecdf5` (родитель `4b8f17ba`, ровно вершина `dev` на момент написания ТЗ)
- Заход: r1 · блокирующих циклов израсходовано 0/4
- Класс изменения: A (продукт), трек: обычный (не `small`) — корректно, задача явно затрагивает perf-бюджет и переработку подсистемы, критерии лёгкого трека не выполняются
- Вердикт: **зелёный**
## Скоуп ревью
Первый заход, разбор полный (§2.9 не применяется — нет r0). Проверялись:
тело issue #302 и единственный комментарий автора, `docs/specs/302-junction-node-material.md`
целиком, `docs/SCOPE.md`, `PROCESS.md` §1–§9, `docs/WALL-THICKNESS.md` целиком,
исходник `src/wall-thickness.ts` (4145 строк) — выборочно, по каждому
идентификатору и утверждению ТЗ, требующему проверки на существование/точность.
## Как проверялось
1. **Продуктовая рамка.** `docs/SCOPE.md` — задача улинии с миссией
(«live spatial overview») через качество геометрии плана; это не новая
фича, а устранение искажений в самом визуальном представлении дома —
прямое следствие J1. Конфликта со SCOPE нет.
2. **Процесс.** Issue помечен `S4-spec-review`, что и запускает этот этап;
размер задачи (обычный) обоснован — трогает `src/wall-thickness.ts`
целиком, влияет на perf-бюджет, не подходит под критерии `small` (§5
PROCESS.md требует «нет влияния на производительность» — здесь влияние
прямо названо в §10 ТЗ).
3. **Фактчекинг утверждений о коде и истории**, чтобы отделить подтверждённые
факты от гипотез, выданных за решение (обязательный пункт инструкции):
- `MITRE_LIMIT`, `openEps`, `buildMultiWallNodeMap`, `wallIntervals`,
`multiWallNodeAt`, `outsetContour`/`insetContour`,
`bevelMultiWallBody`/`bevelMultiWallPaper`, `unionJunctionPatches`,
`linearWallJoinPatches`, `stableJunctionPatch`, `wallBodiesGeometry`,
`wallBodiesUnionPath` — все существуют в `src/wall-thickness.ts` ровно с
тем поведением, которое им приписывает ТЗ (grep + прочтение сигнатур).
- `EPS_NODE = openEps × 4` — совпадает буквально с вызовом в
`multiWallNodesForGeometry` (`wall-thickness.ts:2035-2036`).
- `wallBodiesUnionPath` действительно единственная точка входа для обоих
рендереров: `src/houseplan-card.ts:11687` (Full/Plan) и
`src/space-render.ts:432` (Static) — вызывают именно её. Утверждение §2
ТЗ «оба рендерера получают результат из одного и того же кода» не
догадка, а факт.
- Ссылки на прежние ТЗ (#141, #172, #197, #229, #249, #271, #272, #275,
#279, #288, #290, #296) сверены с содержанием `docs/WALL-THICKNESS.md` —
каждая деталь («R = 1.25×H», `NEAR_AXIS_MAX_DEGREES = 0.25`,
«near-orthogonal», «short ray handoff», сращивание коллинеарных равных
стен из #229) находит буквальное соответствие в каноническом документе
подсистемы либо в исходнике (`src/near-axis.ts:6-7`).
- Все девять смоков из AC9 (`smoke_wall_junctions`,
`smoke_junction_patch_resilience`, `smoke_split_corner_wall`,
`smoke_zero_divider_taper`, `smoke_wall_thickness*`) и
`smoke_render_perf` (AC8) существуют в `demo/`.
- `demo/golden/matrix.mjs` существует и это действительно data-only
каталог golden-фикстур (`GOLDEN_MATRIX_VERSION`), куда естественно
ложится новый сет из §13.
- Нового «детектора дыр» и `isPointInPath`-инструментария в репозитории
сейчас нет (`grep` пусто) — заявка «новый чистый модуль» корректна, это
не дублирование существующего.
- SHA `4b8f17b`, на котором получены цифры 359/360 дыр, существует в
истории и совпадает с прямым родителем ревьюемого коммита — воспроизвести
довод «дыры родились в базовой фазе» можно на том же дереве.
4. **Смысловая проверка контракта §8**, а не только буквенная. Рассмотрены
вырожденные и граничные случаи: узел из 2 лучей (веер строится на обеих
угловых секторах — внутренней и внешней стороне, поскольку у двух лучей при
круговом обходе ровно два соседства); дегенерат mitre при малом угле
(обрезается `MITRE_LIMIT`, переходит в bevel — предотвращает уход материала
в бесконечность); связь с историческим классом дефектов «короткий луч
короче радиуса ремонта» (#271/#288) — новая механика по построению не
продолжает луч вдоль его оси вовсе («конец полосы» — точка ровно у узла,
офсетная по перпендикуляру, а не отложенная на расстояние `MITRE_LIMIT`
вдоль луча), поэтому класс дефектов «репэйр перерос короткий луч» не имеет
аналога в новой механике — не пробел ТЗ, а архитектурное упрощение, которое
стоит явно назвать в фиксации, но не блокирует ревью.
5. **Проверка DoR-состава** (§2.5, §7.1 PROCESS.md): сценарий ✓, «что человек
увидит» ✓, проблема/причина ✓ (§3 с исполненным доказательством), скоуп и
не-скоуп ✓ (§6–7), контракт поведения ✓ (§8), данные/i18n/a11y/privacy ✓
(§9, всё «не меняется» — обосновано отсутствием миграции конфига), perf ✓
(§10, бюджет назван), риски ✓ (§11, три риска с конкретными мерами), AC ✓
(§12, 9 штук, пронумерованы), план автотестов ✓ (§13–14), откат ✓ (§16),
release-артефакты ✓ (§15).
## Находки
Ни одной находки уровня High или Medium. Два пункта Low — решение ревьюера:
сняты с записью, правки не требуют.
1. **Low — способ доказательства AC не продублирован буквально на каждой
строке §12.** AC1–AC5, AC8 описывают ожидаемый результат словами
(«детектор §8.4», «попиксельное совпадение», «смок сверяет пути»), а не
явным тегом `unit`/`golden`/`smoke` при каждом пункте, как того просит
буква §2.5 PROCESS.md. Решение: снимается без правки — §13 однозначно
называет механику («каждая сцена: golden крупным планом + детектор §8.4»,
«юниты: чистые функции веера... и детектора»), и разработчик/ревьюер кода
не может трактовать способ доказательства иначе. Формальный тег ничего не
добавил бы к однозначности.
2. **Low — нет отдельного раздела «UX».** Шаблон §7.1 называет UX отдельным
разделом; в этом ТЗ его содержание разнесено между шапкой («Touch editor:
not exposed») и §2 («Что человек увидит до и после»). Решение: снимается —
задача не меняет ни одного интерактивного пути (только геометрия отрисовки
тел стен), содержательно вопрос закрыт, отдельный заголовок был бы пустой
формальностью.
Открытых продуктовых вопросов к владельцу нет и это обоснованно: контракт
поведения продиктован самим владельцем в комментарии к issue (кандидат-алгоритм
из тела issue почти буквально формализован в §8.2), формальные константы
детектора отмечены как техническое предположение в §17 с направлением
уточнения («только в сторону строгости»), стадийность демонтажа старых слоёв —
там же, помечено как свободное для реализации техническое решение. Это
редкий случай, когда молчание не является риском: сам факт задачи и её решение
исходят от одного и того же человека.
## Что проверено и корректно
- Соответствие SCOPE.md — задача в рамках мандата, не расширяет функциональность.
- Соответствие процессу — статус, класс изменения, трек, обязательные разделы,
трейлеры/release-артефакты названы верно.
- Все технические идентификаторы, числа и ссылки на историю, на которые
опирается контракт §8, проверены на существование и точность прямым чтением
кода и `docs/WALL-THICKNESS.md`, а не приняты на слово.
- Контракт §8.2–8.4 логически замкнут: покрывает все перечисленные в §8.3
вырожденные случаи, не оставляет геометрического механизма для дыры между
соседними веерами (общая вершина — узел, дуги секторов покрывают полный
оборот), и делает объективно проверяемым единственное новое утверждение
(«нет дыр») через детектор с собственной самопроверкой на дырявой фикстуре
(мутант `hole-detector-blind`).
- AC9 корректно называет регрессионный периметр — все перечисленные смоки
существуют и по названию действительно покрывают стыковую механику.
- Откат (§16), release-артефакты (§15) и риски golden-переснятия (§11.1)
учитывают процессные уроки #230/#235 именно так, как того требует PROCESS.md.
## Чего не проверял
- Я не запускал никакие гейты (`tsc`, `npm test`, `npm run build`,
`golden:verify`, смоки) — на этапе ревью ТЗ кода ещё нет, гейты этапа
относятся к код-ревью (§2.7 PROCESS.md), а не к ревью ТЗ (§2.4).
- Не проверял геометрию mitre/bevel численно (не строил прототип и не считал
вручную пересечения офсетных прямых) — на этапе ТЗ это не требуется: контракт
верифицируется исполнением на код-ревью через детектор §8.4 и golden, а не
через ручной пересчёт геометрии ревьюером ТЗ.
- Не читал полностью весь `src/wall-thickness.ts` (4145 строк) построчно —
прочитаны и сверены только фрагменты, релевантные утверждениям ТЗ
(сигнатуры функций, константы, точки вызова `wallBodiesUnionPath`).
- Не проверял содержание единственного комментария к issue построчно на
предмет более ранних версий ТЗ — комментарий один, зафиксированное решение
соответствует финальному ТЗ.
## Итог
`docs/specs/302-junction-node-material.md` готово к переходу в `S5-ready`.
Продуктовая рамка ясна, контракт поведения проверяем объективным детектором и
golden-эталонами, риски демонтажа старого стека названы и покрыты
регрессионным периметром (AC9), технические предположения отделены от решения
и помечены как свободные к изменению.