Issue: #242 User-Visible: no
19 KiB
CODE-REVIEW-242-r2
- Issue: #242 — «Проём в толстой стене рисуется у произвольной грани, а не по центру толщины»
- Этап: code (PROCESS.md §2.7)
- Заход: r2 (блокирующих циклов израсходовано 1 из 4; CODE-REVIEW-242-r1 — жёлтый)
- Диапазон полного цикла:
origin/dev..HEAD=0033138..1885c77 - Предыдущий раунд: CODE-REVIEW-242-r1, вердикт жёлтый, получен на SHA
5e13e34 - Дельта этого раунда:
git diff 5e13e34..HEAD= 2 коммита:a65db39fix(openings): preserve gate flip turn direction(User-Visible: yes) — правка H11885c77docs: refresh opening screenshot provenance(User-Visible: no) — обновление фингерпринта скриншотов после правки, тронувшейsrc/**
- Вердикт: зелёный
Скоуп
r1 закрыл жёлтым вердиктом с одной блокирующей находкой (H1, High, в скоупе) и
одной находкой в скоупе (M1, Medium): flip_v у ворот не менял знак 10°
поворота на общей стене двух комнат и на независимой (partition) стене —
только на однокомнатной внешней стене; и заявленный автором golden-охват
(«2 сцены») был занижен на порядок (фактически ~48). Разбор этого раунда
ограничен дельтой 5e13e34..HEAD, потому что дельта локальна: два коммита,
оба прицельно адресуют H1 и M1, не трогают резолверы wall-thickness.ts /
partition-openings.ts (AC1/AC2/AC7) и не меняют контракт flip_v.
Закрытие раунда r1
| Находка | Чем закрыта | Где это видно |
|---|---|---|
H1 (High) — flip_v не менял знак 10° поворота ворот на общей стене и на независимой стене |
a65db39 убирает вторую зеркальную инверсию sy для ворот в renderOpeningVisibleGeometry (src/render/opening-symbol.ts) и в leafBasis/buildIsoOpeningBasis (src/iso-openings.ts): gateAngle = spec.face.side * 10 * amount вместо spec.face.side * sy * 10 * amount. face.side уже несёт знак flip_v через faceFlipV в houseplan-card.ts/space-render.ts — второе умножение на sy его гасило |
src/render/opening-symbol.ts:107-117, src/iso-openings.ts:80-118; подтверждено моим независимым прогоном demo/smoke_wall_thickness.mjs (sharedGateFlipReversesTurn: true, partitionGateFlipReversesTurn: true) и demo/smoke_isometric_contract.mjs (isoGateFlipReversesTurn: true), плюс ad hoc browser-проверкой третьего (однокомнатного внешнего) пути, который новые тесты не покрывают напрямую (см. «Как проверялось») |
| M1 (Medium, в скоупе) — заявленный golden-охват занижен на порядок; §12.3 сцены не добавлены | a65db39 добавляет 4 обязательные семантические сцены (opening-symbol-room-wall-light, opening-symbol-diagonal-partition-dark, opening-symbol-flip-pairs-light, isometric-opening-symbol-parity-dark) с pre-PNG semantic guard в demo/golden/run.mjs (assertOpeningSymbolContract), который до сравнения PNG проверяет центр, jamb-глубину, op-glass, отсутствие scaleY-инверсии у ворот и противоположный знак поворота в паре flip_v. Отдельно документирует точный список из 67 существующих golden-сцен, задетых общими фикстурами (docs/TESTING.md, OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT в demo/golden/matrix.mjs, застрахован unit-тестом) |
demo/golden/matrix.mjs (OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT, длина 67), demo/golden/run.mjs:486-600 (assertOpeningSymbolContract), docs/TESTING.md раздел «Opening symbol centreline (#242)», test/golden-matrix.test.mjs (новый тест «opening symbol goldens lock…»). Частично не сходится с фактом — см. L1 ниже |
Унаследовано из r1
Из CODE-REVIEW-242-r1 (документ на SHA 5e13e34) принято без повторной
проверки, так как дельта 5e13e34..HEAD не касается этого кода:
- AC1/AC2 для room-wall и partition путей (центр стены, независимость от
порядка комнат/направления оси) —
src/wall-thickness.ts,src/partition-openings.ts,src/opening-symbol-placement.tsдельтой не тронуты. - AC3 (
flip_v: trueу door/window, окно целиком едет однимtranslate) — логика для non-gate типов не изменена (renderSy/syдля door/window идентичны до и послеa65db39; подтверждено тем, что затронутые тестыtest/opening-symbol.test.mjsдля door/window не менялись в дельте, и полныйnpm testзелёный). - Jambs full-depth —
jambHalfвopeningVisibleMetricsдельтой не тронут. - AC7 (без миграции/схемы) —
OpeningCfg.flip_vне тронут этой дельтой. - Три копии бандла и mutation-gate (кроме нового мутанта) — методология подтверждения не изменилась.
Как проверялось
Прочитан git diff 5e13e34..HEAD построчно: src/render/opening-symbol.ts,
src/iso-openings.ts (продуктовый код), demo/golden/harness.mjs,
demo/golden/matrix.mjs, demo/golden/run.mjs, demo/smoke_wall_thickness.mjs,
demo/smoke_isometric_contract.mjs, scripts/mutation-gate.mjs,
test/opening-symbol.test.mjs, test/iso-openings.test.mjs,
test/golden-matrix.test.mjs, docs/TESTING.md, docs/CHANGELOG*.md,
docs/images/screenshots.json. Дистрибутивные копии бандла (dist/**,
demo/srv/assets/**, custom_components/**/frontend/**) не читались построчно
— сверены по npm run build (см. ниже).
Гейты, реально прогнанные в этой сессии:
| Гейт | Результат |
|---|---|
npx tsc --noEmit |
green |
npm test |
green, 1096/1096 |
npm run build + сверка dist / demo/srv/assets / custom_components/.../frontend |
green, все три sha256 = fc19a1dce77b39b7bf67008f165980d59705b84a844f723fb84abfb185d32d16, git status чист |
node scripts/check-docs.mjs |
green (7 файлов, 10 внешних ссылок) |
node scripts/mutation-gate.mjs --check |
green, включая новый мутант opening-gate-flip-cancels-turn |
node scripts/smoke-select.mjs --base origin/dev --head HEAD |
«Зарегистрированная связь» (2): smoke_isometric_contract.mjs, smoke_wall_thickness.mjs — вывод инструмента приведён ниже |
node demo/smoke_wall_thickness.mjs |
green: sharedGateFlipReversesTurn: true, partitionGateFlipReversesTurn: true (и весь остальной набор), OK |
node demo/smoke_isometric_contract.mjs |
green: isoGateFlipReversesTurn: true (и весь остальной набор), OK |
npm run golden:verify на HEAD (1885c77) |
92 сцены: 16 passed, 68 different, 8 missing-baseline; сравнено построчно с origin/dev |
npm run golden:verify на origin/dev (5bf1868, отдельный git worktree) |
22 different / 66 passed / 8... фактически другой набор — использован для сравнения actualSha256, не для приёмки |
Вывод smoke-select.mjs:
Изменено файлов src/**: 4 · символов проекта на изменённых строках: 6
Матрица: 168 смоков · порог «широкого» символа: больше 33 смоков
Зарегистрированная связь (2):
demo/smoke_isometric_contract.mjs ← openingSymbolOffset
demo/smoke_wall_thickness.mjs ← openingSymbolOffset
Оба прогнаны (см. таблицу). Полная матрица (168 смоков) не прогонялась:
дельта — два файла рендера ворот плюс golden/mutation-инфраструктура, не
задевающая несвязанные подсистемы; выбор smoke-select уже покрывает ровно
тот геометрический путь, который правит a65db39.
Дополнительная независимая проверка (не покрыта новыми тестами автора).
Новые тесты (test/opening-symbol.test.mjs, test/iso-openings.test.mjs,
demo/smoke_wall_thickness.mjs, demo/smoke_isometric_contract.mjs,
golden-фикстура opening-symbol-flip-pairs-light) проверяют знак поворота
ворот только на общей стене двух комнат и на независимой стене. Ни
один из них не воспроизводит третий путь из r1 (однокомнатная внешняя
стена, available[0].side вместо константы -1) — единственный, который в
r1 уже работал корректно, и который эта дельта могла случайно сломать, так
как формула в обеих затронутых функциях изменена безусловно для всех ворот,
а не только для «сломанных» путей. Я independently прогнал ad hoc
browser-скрипт (тот же харнесс demo/serve.mjs, что и существующие
demo/smoke_*, не закоммичен — репозиторий трогать нельзя): толстая внешняя
стена одной комнаты (_wallThickClick([50,250]), cm=20), двое ворот
flip_v: false/true. Результат: extGateDefaultTurn: 10,
extGateFlippedTurn: -10, extGateFlipReversesTurn: true — путь
по-прежнему работает корректно, регрессии нет.
Проверка «умеет ли тест падать». Для новых unit-тестов —
scripts/mutation-gate.mjs'ный мутант opening-gate-flip-cancels-turn
восстанавливает старую формулу (spec.face.side * sy * 10 * amount) и гейт
подтверждает, что таргетный тест opening-symbol.test.mjs его ловит
(--check прогнан и зелёный, что означает: без патча тест проходит, с
патчем — нет; это и есть механика mutation-gate.mjs). Для golden semantic
guard — код assertOpeningSymbolContract явно бросает
semantic golden gate flip does not reverse turn при
pair[0].turn + pair[1].turn) > epsilon, что структурно не может не упасть
при регрессии знака. Для двух browser-смоков — проверено чтением: до
a65db39 эти же строки (sharedGateFlipReversesTurn,
partitionGateFlipReversesTurn, isoGateFlipReversesTurn) не существовали;
их ассерты === -default буквально требуют разных знаков, что при откате
патча (см. мутант выше) стало бы false.
Находки
L1 (Low) — точный список из 67 существующих golden-сцен на самом деле содержит 2 сцены, не задетые этой правкой, и не хватает 0 сцен
Что не так. docs/TESTING.md и OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT в
demo/golden/matrix.mjs заявляют «измерено сравнением actualSha256 для
HEAD и origin/dev» и дают список ровно из 67 id. Я повторил это сравнение
буквально: прогнал npm run golden:verify на HEAD (1885c77) и на
origin/dev (5bf1868 — это ровно git merge-base HEAD origin/dev, тот же
SHA, на который должен был ссылаться автор) в отдельном git worktree, и
сравнил artifacts/golden/golden-report.json.results[].actualSha256
попарно по id. Из 69 сцен с расхождением по 4 новым opening-symbol-*
исключены (их не с чем сравнивать на dev — они там не существуют); из
оставшихся 65 всё совпадает с декларированным списком, кроме двух:
junction-patch-resilience-plan-dark и large-house-zoom-250-dark заявлены
как часть списка, но их actualSha256 идентичен на HEAD и на dev
(проверено: оба совпадающих хэша процитированы в логе) — то есть эти два
кадра не изменились из-за правки #242, у них обоих статус different
относительно baseline по совсем другой, уже существующей на dev причине
(та самая «unrelated pending pre-release candidates», о которой
предупреждает сам документ). Не хватает ни одной сцены — то есть под-учёта
(того, что было проблемой в M1 r1) больше нет; есть только два лишних пункта.
Почему это Low, а не Medium. Направление ошибки безопасное: список
переоценивает влияние (2 лишних пункта из 67), а не недооценивает его — риск
пропущенной регрессии не растёт. Расхождение не влияет ни на один AC и не
меняет видимое поведение; unit-тест (test/golden-matrix.test.mjs) фиксирует
именно то число и тот список, что сейчас в коде, поэтому сам гейт не
"врёт" сам себе — врёт только английская/русская проза документа и
сопроводительный комментарий о методе измерения.
Серьёзность. Low. Не блокирует. Можно поправить точечно (убрать два id
из OPENING_SYMBOL_EXISTING_GOLDEN_IMPACT и синхронизировать
docs/TESTING.md) в любой последующей правке этой области; отдельный issue
не нужен ни по объёму, ни по риску.
Что проверено и корректно
- H1 закрыт end-to-end на всех трёх путях host (общая стена, независимая
стена, однокомнатная внешняя стена) и в Iso — см. «Как проверялось» и
таблицу «Закрытие раунда r1». Trailers
Issue/User-Visibleнаa65db39корректны, оба changelog обновлены в этом же коммите. - M1 практически закрыт: 4 обязательные по §12.3 golden-сцены добавлены с
pre-PNG semantic guard, точный (за вычетом L1) список существующих
задетых сцен задокументирован и застрахован unit-тестом,
GOLDEN_MATRIX_VERSIONподнят до 37 и это отражено в тесте на sun-ray сценарий.docs/TESTING.mdявно запрещает локальныйgolden:acceptдля этих 71 сцены (4 новых + 67 существующих) — только через reviewed Linux-артефакт. 1885c77— точечное обновлениеdocs/images/screenshots.jsonи одного PNG (05-plan-context-tray.png) после того, какsourceFingerprintустарел из-за правкиsrc/**;check-docs.mjsзелёный, только один скриншот реально сменил пиксели (гейт ворот виден в тестовом фрейме), что соответствует масштабу правки.- Отсутствие регрессии для door/window.
renderSy/syвrender/opening-symbol.tsиsyвiso-openings.tsдля типов, отличных отgate, не изменились;npm testзелёный без единого затронутого door/window-теста в дельте. - Три копии бандла и
git status— синхронны и дерево чисто после build.
Чего не проверял
- Полный Linux golden-артефакт и его reviewed-приёмка — вне цикла
код-ревью (§13), как и в r1. Локальный
golden:verifyобслуживает только проверку L1/M1-охвата, не приёмку эталонов. python -m pytest tests_backend— не прогонял:custom_components/**/*.pyдельтой5e13e34..HEADне тронут (только скопированный бандлcustom_components/houseplan/frontend/houseplan-card.js, сверенный по sha256).- Performance-профили — не прогонял: дельта не меняет асимптотику,
только константу (одно сравнение типа вместо перемножения на
sy). - Полная browser-smoke матрица (168 сценариев) — не прогонял всю, кроме
двух названных
smoke-select; см. обоснование выше. Дополнительно прогнал вручную (не закоммичено) третий host-путь ворот, который выборsmoke-selectне called напрямую. - Унаследованные из r1 пункты — см. раздел «Унаследовано из r1»; они не передоказывались, так как дельта их не касается.