From dffe77e32d125f4f50cabe8e1dc17f1e1011d115 Mon Sep 17 00:00:00 2001 From: Codex Date: Sun, 6 Sep 2026 14:03:51 +0300 Subject: [PATCH] docs: specify iso perf witnesses and diff-aware performance smoke User-Visible: no Issue: #473 --- .../specs/473-iso-perf-witnesses-and-smoke.md | 125 ++++++++++++++++++ docs/specs/README.md | 1 + 2 files changed, 126 insertions(+) create mode 100644 docs/specs/473-iso-perf-witnesses-and-smoke.md diff --git a/docs/specs/473-iso-perf-witnesses-and-smoke.md b/docs/specs/473-iso-perf-witnesses-and-smoke.md new file mode 100644 index 00000000..03c7d24c --- /dev/null +++ b/docs/specs/473-iso-perf-witnesses-and-smoke.md @@ -0,0 +1,125 @@ +# #473 — Свидетели перф-дельты #160 и диффозависимый перф-смок в Validate + +- **Issue:** https://github.com/Matysh/houseplan-card/issues/473 +- **Тип / приоритет:** infra, process / P2 +- **Трек:** полный — две поверхности: свидетели на продуктовом коде и workflow с бюджетами +- **Оценка:** ценность для разработки 8/10; сложность 3/10; риск 2/10 +- **Связано:** #160, #451, #458, #466, #467; `PROCESS.md` §8, §11.4 + +## 1. Проблема + +Первый кандидат `v1.73.0-beta.1` (`de215578`) упал на полных бенчмарках с +регрессией Stage 3 изометрии: первый кадр 1 516 → 9 870 мс, цикл 12 пространств +1 746 → 30 545 мс (прогон 34013995127). Два раунда код-ревью #160 её не +увидели — и не могли: ревью по §8 гоняет typecheck, unit и build, а +`performance_smoke` в Validate меряет только два glow-профиля. Изометрический +профиль с потолком первого кадра `hardMaxMs: 3500` существует, но в смоке не +участвует. + +Починено по §11.4 четырьмя коммитами (`f1b9bbf3`, `b69743c0`, `196fa096`, +`b3ca0ca2`, +301/−64 в `iso-overlays.ts`, `iso-scene-render.ts`, +`houseplan-card.ts`) без ревью. Двенадцать мутантов `stage3-w1…w12` защищают +визуальный контракт Stage 3; **ни один не патчит механизмы перф-дельты**: +LRU-кэш размещений с подписью, кэш по идентичности массива силуэтов, повторное +использование при зуме внутрь, AABB-отсечение, envelope без коллизий. + +Два дефекта одного класса: горячий код без свидетелей и гейт, который есть, но +не там, где нужен. + +## 2. Скоуп + +1. Свидетели на четыре механизма перф-дельты (§4) — через них проходит и + постфактум-ревью этого кода. +2. Диффозависимый набор профилей у существующего `performance_smoke` (§5). + +## 3. Не-скоуп + +- Изменение самих механизмов кэширования #160 — если свидетель найдёт дефект, + он заводится отдельным issue. +- Полные бенчмарки в код-ревью или в Validate. +- Пересмотр §11.4. +- Пороги полных профилей (`hardMaxMs` и регрессионные коэффициенты). + +## 4. Контракт свидетелей + +Каждый мутант — на один защитный контракт, с гардом-юнитом в +`test/iso-scene-render.test.mjs` / `test/iso-overlays.test.mjs`, проверенным +отрицательным прогоном штатным раннером. + +| Свидетель | Контракт | Мутация | Чем краснеет | +|---|---|---|---| +| `iso-placement-cache-ignores-selected` | подпись кэша (`iso-scene-render.ts:805-807`) включает все входы, меняющие размещение | `selected ? 1 : 0` выброшен из `shapeSignature` | плита с `selected=true` после `selected=false` при том же id обязана дать иное размещение, а не кэшированное | +| `iso-placement-cache-survives-silhouette-change` | кэш размещений привязан к **идентичности** массива силуэтов (`WeakMap`, `:783`), новая геометрия — новый ключ | ключ WeakMap заменён константным объектом | два вызова с разными массивами силуэтов при тех же плитах обязаны дать разные размещения там, где стена появилась | +| `iso-zoom-in-reuses-near-wall-plate` | повторное использование при зуме внутрь (`:812-822`) только для плит, не бывших у стены, либо очищенных в пределах cap | гард `!previous.nearWallBefore \|\|` снят | плита у стены при зуме внутрь обязана пройти точный резолвер, а не вернуть прежний объект | +| `iso-aabb-rejects-touching-wall` | AABB-отсечение (`iso-overlays.ts:342-345`) не отбрасывает силуэт, пересекающий плиту | `boundsNear` требует строгого перекрытия без `gap` и без равенства | плита, касающаяся стены ровно по границе с зазором `gap`, обязана считаться «у стены» | + +Пятый контракт — envelope без коллизий (`resolveCollisions === false`) даёт те +же границы, что точный резолвер до nudge, — юнитом без мутанта: его +нарушение меняет fit, а не картинку, и у него нет однозначной мутации. + +Все четыре гарда — чистые юниты на экспортах `buildIsoOverlayRenderScene` и +`resolveIsoOverlayPlacement`; фикстура — три комнаты, одна стена, одна плита у +стены и одна вдали, как в существующих тестах. + +## 5. Контракт перф-смока + +`performance_smoke` в `validate.yml` становится диффозависимым: + +- всегда — два glow-профиля, как сегодня; +- дифф задел `src/iso-*` — добавляется `large-house-isometric-v1`; +- дифф задел `src/live-*`, `src/render-*`, `src/houseplan-render-lifecycle.ts`, + `src/houseplan-card.ts` — добавляется `large-house-interaction-v1`. + +Классификацию делает `changes` (существующий job) двумя новыми выходами +`perf_iso` и `perf_interaction` по тем же `has(...)`-шаблонам. Профили идут с +`--variants=60 --samples=3 --warmups=1 --absolute-only` и **своими +smoke-бюджетами** `budgets-isometric-smoke.json`, `budgets-interaction-smoke.json` +(`minimumSamples: 3`, потолки — `hardMaxMs` из полных профилей). Регрессионные +коэффициенты в смоке не участвуют: сравнивать не с чем, абсолютный потолок +достаточен — 9 870 мс против 3 500 он ловит с одного образца. + +Ключ переиспользования `performance_smoke` (`reuse`) включает набор профилей: +кэшированный результат glow-only не засчитывается прогону, которому нужен +изометрический. + +Ревью-промпт не меняется: он уже принимает зелёный Validate на SHA как +подтверждение дешёвых гейтов, а смок — часть Validate. + +## 6. Совместимость и откат + +Продуктовый код не меняется. Откат §5 — одним коммитом убрать выходы и +условные шаги; свидетели §4 остаются, они безвредны. + +## 7. Критерии приёмки + +| AC | Критерий | Доказательство | +|---|---|---| +| AC1 | Четыре свидетеля §4 в реестре, каждый ловится: «тест покраснел, поймано 1 из 1» | отрицательный прогон `--id=` штатным раннером | +| AC2 | Envelope без коллизий равен точному до nudge | unit | +| AC3 | `changes` отдаёт `perf_iso` и `perf_interaction`; смок добавляет профиль ровно при своём выходе | контрактный тест на YAML | +| AC4 | Smoke-бюджеты новых профилей валидны для `benchmark:compare --absolute-only` и повторяют `hardMaxMs` полных | unit: чтение обоих файлов, сравнение потолков | +| AC5 | Ключ `reuse` для `performance_smoke` различает наборы профилей | контрактный тест на YAML | +| AC6 | Воспроизведение: на дереве `de215578` изометрический smoke-бюджет краснеет по первому кадру | замер один раз при реализации, результат в issue с командой | +| AC7 | На текущем `dev` оба новых профиля укладываются в потолки за 3 образца | прогон в CI этой ветки | + +## 8. Release-артефакты + +`User-Visible: no`; changelog не трогается. Документация: абзац в +`docs/PROCESS.md` §8 о диффозависимых профилях смока. + +## 9. Затронутые файлы + +- `scripts/mutation-gate.mjs` — четыре мутанта; +- `test/iso-scene-render.test.mjs`, `test/iso-overlays.test.mjs` — гарды и AC2; +- `.github/workflows/validate.yml` — выходы `changes`, условные шаги смока, ключ `reuse`; +- `demo/performance/budgets-isometric-smoke.json`, `budgets-interaction-smoke.json` — новые; +- `test/validate-workflow.test.mjs` — AC3, AC5; `test/performance-budget.test.mjs` — AC4; +- `PROCESS.md` §8. + +## 10. Принятые предположения + +- Изометрический профиль на 3 образцах укладывается в 20-минутный лимит job + вместе с glow-профилями; если нет — лимит поднимается в этой же задаче с + замером. +- Мутация AABB (`boundsNear` без `gap`) достижима существующей фикстурой «плита + у стены»; если фикстура даёт перекрытие, а не касание, добавляется касание. diff --git a/docs/specs/README.md b/docs/specs/README.md index 84ea7ae0..32214611 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -186,6 +186,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#460](https://github.com/Matysh/houseplan-card/issues/460) Детерминированное завершение кадра живого редактора | [460-live-editor-settlement.md](460-live-editor-settlement.md) | | [#461](https://github.com/Matysh/houseplan-card/issues/461) Быстрый commit промежуточной точки цепочки стен | [461-wall-draw-click-performance.md](461-wall-draw-click-performance.md) | | [#464](https://github.com/Matysh/houseplan-card/issues/464) Верхний контекстный слой Zigbee-топологии | [464-zigbee-topology-layer-order.md](464-zigbee-topology-layer-order.md) | +| [#473](https://github.com/Matysh/houseplan-card/issues/473) Свидетели перф-дельты #160 и диффозависимый перф-смок | [473-iso-perf-witnesses-and-smoke.md](473-iso-perf-witnesses-and-smoke.md) | ## P3