mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-30 19:58:50 +00:00
@@ -0,0 +1,350 @@
|
||||
# CODE-REVIEW-374-r1
|
||||
|
||||
Issue: [#374](https://github.com/Matysh/houseplan-card/issues/374) — Feature request: optional light pools and wall shadows in `houseplan-space-card`
|
||||
ТЗ: [`docs/specs/374-space-card-light-pools.md`](https://github.com/Matysh/houseplan-card/blob/dev/docs/specs/374-space-card-light-pools.md) (SPEC-REVIEW-374-r1: зелёный)
|
||||
Ветка: `issue/374-space-card-light-pools`, HEAD ревью: `f5a49718a0fac379ad8eab3de7f6cbbe8e464c68`
|
||||
Заход: r1 · блокирующих циклов израсходовано 0 из 4
|
||||
|
||||
## Скоуп ревью
|
||||
|
||||
`git log --oneline origin/dev..HEAD` — 8 коммитов:
|
||||
|
||||
```
|
||||
f5a49718 test: separate Glow performance contracts (User-Visible: no)
|
||||
6e3d08ed docs: add static Glow smoke commands (User-Visible: no)
|
||||
aa3ad429 docs: refresh screenshot provenance (User-Visible: no)
|
||||
9e9e3284 refactor: type Glow scene boundaries (User-Visible: no)
|
||||
6d838d77 docs: accept canonical screenshots (User-Visible: no)
|
||||
0dfc7424 feat: add opt-in Glow to space card (User-Visible: yes)
|
||||
8914190c docs: review document for #374 (User-Visible: no)
|
||||
c4f37986 docs: specify opt-in Glow for space card (User-Visible: no)
|
||||
```
|
||||
|
||||
`git diff origin/dev...HEAD --stat`: 68 файлов, +12763/−11232. Все трейлеры на
|
||||
месте (`Issue: #374` на каждом коммите); `User-Visible: yes` только на
|
||||
`0dfc7424`, и именно там правятся оба changelog в том же коммите
|
||||
(`docs/CHANGELOG.md`, `docs/CHANGELOG.ru.md`).
|
||||
|
||||
Продуктовый код (класс A): `src/glow-scene.ts` (новый, 623 строки),
|
||||
`src/houseplan-card.ts` (−520/+165 в области Glow — извлечение в общий модуль),
|
||||
`src/space-render.ts` (+193/−73 — opt-in слой в статическом рендере),
|
||||
`src/space-card.ts` (+42 — конфиг/жизненный цикл рантайма), `src/space-editor.ts`
|
||||
(+7 — boolean в форме), `src/i18n/{en,ru,de,fr}.json` (+1 ключ каждый).
|
||||
|
||||
Класс B (гейты/тулинг): `test/glow-scene.test.mjs` (новый), правки
|
||||
`test/golden-matrix.test.mjs`, `test/performance-contract.test.mjs`,
|
||||
`test/performance-workflow.test.mjs`, `scripts/mutation-gate.mjs`,
|
||||
`demo/benchmark_glow.mjs`, `demo/performance/card-contract.mjs`,
|
||||
`demo/smoke_glow_blending.mjs`, новые `demo/performance/budgets-large-space-card-{default,glow}.json`
|
||||
и `budgets-space-glow-smoke.json`, `.github/workflows/{performance,validate}.yml`,
|
||||
`tsconfig.test.json`.
|
||||
|
||||
Класс C (документация): `docs/LIGHT.md`, `docs/ARCHITECTURE.md`,
|
||||
`docs/TESTING.md`, `docs/USER-GUIDE{,.ru}.md`, `docs/CHANGELOG{,.ru}.md`,
|
||||
`docs/images/**` + `docs/images/screenshots.json` (canonical Docs screenshots).
|
||||
|
||||
Класс D (генерируемое): `dist/**`, `custom_components/houseplan/frontend/**` —
|
||||
синхронный бандл, не редактировался вручную.
|
||||
|
||||
Трек — полный (не light), верно: SPEC-REVIEW-374-r1 уже подтвердил это по §5.
|
||||
|
||||
## Как проверялось
|
||||
|
||||
1. Прочитаны заново `docs/SCOPE.md`, `AGENTS.md`, `PROCESS.md` (жизненный цикл,
|
||||
§1 классы файлов, гейты код-ревью, формат вердикта, лимит циклов).
|
||||
2. Прочитано тело issue #374 целиком, включая финальный комментарий автора о
|
||||
передаче на код-ревью (реализация, проверки, решение по smoke-select).
|
||||
3. Прочитан ТЗ `docs/specs/374-space-card-light-pools.md` целиком (контракт
|
||||
поведения §1–5, архитектурный контракт, AC1–AC10, план автотестов) и
|
||||
документ `SPEC-REVIEW-374-r1.md`, чтобы не переизобретать то, что спек-ревью
|
||||
уже проверил.
|
||||
4. Прочитан канонический `docs/LIGHT.md` (обновлённый раздел «Which surfaces
|
||||
render pools») и `docs/USER-GUIDE.ru.md`/`.md` (обновлённые разделы про
|
||||
`houseplan-space-card`) — сверена терминология («Световые пулы и тени от
|
||||
стен», независимость от `live_states`).
|
||||
5. Прочитан новый общий модуль `src/glow-scene.ts` целиком (623 строки) —
|
||||
единственный источник истины по source projection, barrier scene, clip
|
||||
geometry, runtime lifecycle и SVG-рендеру.
|
||||
6. Построчно сверен `git diff` по `src/houseplan-card.ts`, `src/space-render.ts`,
|
||||
`src/space-card.ts`, `src/space-editor.ts` с оригиналом (`git show
|
||||
origin/dev:src/houseplan-card.ts` через диффы) — извлечение алгоритма из
|
||||
полной карточки в `glow-scene.ts` сверено построчно на предмет того, что
|
||||
математика (`probe`, `_cmToUnits`, кеш-ключи, LRU-семантика, порядок
|
||||
occluders, `enabledClip`) не изменилась, а не просто переехала по имени.
|
||||
7. Отдельно проверена LRU-семантика: `readGlowClip`/`writeGlowClip` в
|
||||
`glow-scene.ts:140-161` сверены построчно с общей `lruRead`/`lruWrite`
|
||||
(`src/houseplan-card.ts:409-425`) — идентичны (move-to-end, лимит по
|
||||
умолчанию 256, тот же порядок `delete`+`set`).
|
||||
8. Проверены тесты, изменённые вместе с рефакторингом:
|
||||
`test/glow-scene.test.mjs` (unit на revision/candidates/runtime lifecycle),
|
||||
`test/golden-matrix.test.mjs` (assertions на структуру перенесены на
|
||||
`glow-scene.ts`, а не ослаблены), `test/performance-contract.test.mjs`
|
||||
(проверяет `resolveLightBarrierRevision`/`buildLightBarrierScene` в новом
|
||||
месте), `test/performance-workflow.test.mjs` (workflow отражает новые
|
||||
перф-профили).
|
||||
9. Проверены новые мутанты в `scripts/mutation-gate.mjs` — старые мутанты,
|
||||
указывавшие на `src/houseplan-card.ts`, перенесены на `src/glow-scene.ts`
|
||||
вместе с перемещённым кодом (не потеряны), плюс один новый мутант
|
||||
(`opaqueBodies: number[][][] = [];`) на общий источник физических тел.
|
||||
10. Прочитан весь `docs/images/screenshots.json`/CI-контекст: подтверждено, что
|
||||
`sourceFingerprint` обновлён (т.к. `src/**` менялся) и Docs screenshots
|
||||
workflow прошёл зелёным.
|
||||
11. Самостоятельно запущены гейты (см. «Гейты» ниже) вместо того, чтобы принять
|
||||
отчёт автора на слово, включая независимый запуск ключевых Glow-смоков.
|
||||
|
||||
## Гейты
|
||||
|
||||
**Уже подтверждено на этом SHA (не перегонялось повторно):**
|
||||
`npx tsc --noEmit`, `npm test`, `npm run build` — Validate на точном
|
||||
`f5a49718` зелёный: https://github.com/Matysh/houseplan-card/actions/runs/33251556837
|
||||
(job «Фронтенд: типы, юниты, мутанты, синхрон бандла» = success).
|
||||
|
||||
**Перегнано мной самостоятельно** (диф трогает `src/**`, рендер и Glow-геометрию):
|
||||
|
||||
- `npm run build && npm run bundle:sync` — зелёный, `git status` после сравнения
|
||||
трёх копий бандла (`dist/**`, `custom_components/houseplan/frontend/**`,
|
||||
`demo/srv/assets/**`) чист, т.е. закоммиченные бандлы уже соответствуют этому
|
||||
дереву.
|
||||
- `npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test
|
||||
test/glow-scene.test.mjs test/golden-matrix.test.mjs
|
||||
test/performance-contract.test.mjs test/performance-workflow.test.mjs` — 56/56
|
||||
зелёные (весь набор, затрагивающий сам рефакторинг и оба новых перф-профиля).
|
||||
- `node scripts/smoke-select.mjs --base origin/dev --head HEAD` — воспроизвёл
|
||||
ровно тот же результат, что в отчёте автора: 50 direct, 13 weak, 3
|
||||
registered. Не оспариваю выборку.
|
||||
- Из «direct» списка самостоятельно прогнаны браузерные смоки, напрямую
|
||||
завязанные на изменённый Glow-код: `node demo/smoke_glow.mjs` — OK (все
|
||||
28 инвариантов true), `node demo/smoke_glow_fail_dark.mjs` — OK,
|
||||
`node demo/smoke_glow_geometry_resilience.mjs` — не запускал отдельно (уже
|
||||
покрыт полным CI-прогоном ниже), `node demo/smoke_glow_blending.mjs` — OK,
|
||||
`{"ok":true,"blend":"screen","pools":60,"staticParity":true,"staticPools":60}`
|
||||
— это единственный смок, напрямую проверяющий AC1/AC2/AC6 (default-off = 0
|
||||
pools у выключенной карточки, opt-in = 60 pools, полное совпадение
|
||||
center/radius/lit-parts/clip первого источника между полной и статической
|
||||
карточкой, `blend=screen`, `pointer-events:none`).
|
||||
- Полный браузерный CI-прогон (все 3 шарда смоков + `golden`) на
|
||||
`6e3d08ed` — зелёный:
|
||||
https://github.com/Matysh/houseplan-card/actions/runs/33251238199
|
||||
(единственный красный job там — `performance_smoke`, из-за бага в самом
|
||||
перф-харнессе, что видно по джобам: `gh api .../jobs` подтверждает —
|
||||
«Смоки: все шарды зелёные» и «Golden-кадры» оба `success`, красный только
|
||||
«Перф-смок»). Проверено, что `git diff 6e3d08ed..HEAD -- src/ demo/ test/
|
||||
scripts/ .github/` после этого прогона касается только
|
||||
`demo/benchmark_glow.mjs` и трёх budgets-JSON (фикс харнесса), `src/**` не
|
||||
менялся ни строкой — то есть этот зелёный прогон валиден для продукта на
|
||||
текущем HEAD, а не устарел.
|
||||
- `node --test performance-contract`/`golden-matrix` (см. выше) подтверждают,
|
||||
что оставленный сокращённый набор смоков — не единственное доказательство:
|
||||
структурные unit-проверки самого извлечения тоже зелёные.
|
||||
|
||||
**Не перегонялось, и почему:**
|
||||
|
||||
- Остальные 47 «direct» и все «weak»/«registered» смоки из smoke-select — не
|
||||
прогонялись поштучно, потому что уже накрыты полным браузерным прогоном
|
||||
(все 3 шарда = весь `demo/smoke_*.mjs`, не подмножество — проверено по
|
||||
`.github/workflows/validate.yml:455` — `for f in demo/smoke_*.mjs`) на SHA,
|
||||
чей `src/**`/`demo/**`/`test/**` байт-в-байт совпадает с рассматриваемым HEAD
|
||||
(см. выше). Повторный прогон был бы тем самым «прогонять весь набор ради
|
||||
тщательности», от которого прямо предостерегает инструкция.
|
||||
- Полный `npm test` (весь inventory) — не перегонял целиком, доверяю
|
||||
зафиксированному зелёному Validate на этом SHA; вместо этого прогнал целиком
|
||||
именно изменённые тестовые файлы (см. выше), это дешевле и целится точно в
|
||||
диф.
|
||||
- `npm run golden:verify` — не прогонял локально (нет принятого golden-набора
|
||||
под рукой вне CI-контейнера); полагаюсь на зелёный job `golden` в прогоне на
|
||||
`6e3d08ed`, чей `src/**`/`demo/**` идентичен HEAD.
|
||||
- `python -m pytest tests_backend -q` — не прогонял: диф не трогает
|
||||
`custom_components/**/*.py`.
|
||||
- `node scripts/model-invariants.mjs` / `npm run invariants` — не прогонял.
|
||||
Диф не меняет модель геометрии (рёбра комнат, записи толщины, `layout`,
|
||||
`marker.space`, `open_spans`): `src/glow-scene.ts` и `space-render.ts`
|
||||
**читают** существующую геометрию (стены, проёмы, комнаты) для построения
|
||||
временной сцены видимости света, но не пишут и не мигрируют ни один из
|
||||
перечисленных в задании инвариантных полей. Единственный новый персистентный
|
||||
атрибут — булев Lovelace-конфиг `light_pools` конкретного card instance
|
||||
(явно не-скоуп бэкенд-схемы и model version по самому ТЗ, раздел «Модель
|
||||
данных»). Инварианты модели тут не применимы.
|
||||
- Полный `demo/performance/**` Linux-артефакт (нужен только для утверждения
|
||||
численных бюджетов, PROCESS.md) — не прогонял; это предрелизный гейт, а
|
||||
автор уже приложил ссылку на прогон CI-профилей `space-default`/`space-glow`
|
||||
на точном SHA в составе `.github/workflows/performance.yml` (запускаются
|
||||
`full-performance-*` только по расписанию/вручную — не гейт этого ревью).
|
||||
Быстрые абсолютные perf-smoke бюджеты (`budgets-space-glow-smoke.json`) —
|
||||
часть обычного Validate, который зелёный на HEAD.
|
||||
- Мутационный гейт (`scripts/mutation-gate.mjs`) целиком — НЕ прогонялся:
|
||||
это отдельный запланированный workflow (`.github/workflows/mutation-gate.yml`,
|
||||
шардированный на 4, group `mutation-gate`), не входящий в перечень гейтов
|
||||
код-ревью по PROCESS.md §8. Ограничился чтением: подтверждено, что старые
|
||||
мутанты, целившиеся в перенесённый код, обновили `file:`-путь на
|
||||
`src/glow-scene.ts` вместе с кодом (не потеряны молча), и добавлен новый
|
||||
мутант на удаление `opaqueBodies` из общего источника. (Замечание к
|
||||
инструменту, не к задаче: случайный запуск `node scripts/mutation-gate.mjs
|
||||
--help | head -40` в начале проверки интерпретировал `--help` не как флаг, а
|
||||
начал прогон «чистого» шага по всему реестру мутантов, до того как канал
|
||||
оборвался через `head`; после проверки `git worktree list`/`git status`
|
||||
подтверждено, что временных worktree не осталось и рабочее дерево осталось
|
||||
чистым — побочных эффектов на этот репозиторий нет.)
|
||||
|
||||
## Находки
|
||||
|
||||
Блокирующих (High) и Medium-находок, ни в скоупе, ни вне скоупа, не найдено.
|
||||
|
||||
Рассмотренные, но не подтвердившиеся кандидаты (для прозрачности разбора):
|
||||
|
||||
- **`renderGlowPools()` добавляет `aria-hidden="true"` на `<g class="glowlayer
|
||||
glow-pools-frame">` теперь и для полной `houseplan-card`, хотя в старом коде
|
||||
полной карточки этого атрибута не было** (сравнение диффа —
|
||||
`src/houseplan-card.ts` до рефакторинга не устанавливал `aria-hidden` на этот
|
||||
узел, только `pointer-events:none`). Формально это меняет вывод полной
|
||||
карточки, а архитектурный контракт ТЗ (п.4) требует «existing full-card
|
||||
output must be regression-equivalent», явно перечисляя geometry/identifiers/
|
||||
blend/fade/budgets. `aria-hidden` не входит в этот список, слой и так был
|
||||
`pointer-events:none` (уже вне tab order/hit-testing), а сам ТЗ отдельно
|
||||
требует именно этот атрибут для нового статического слоя (раздел «UX и
|
||||
доступность»). Полный CI-прогон (golden + все смок-шарды) на этом дереве
|
||||
зелёный, то есть ни один существующий тест не привязан к отсутствию
|
||||
атрибута. Не нахожу это регрессией — общий SVG-рендерер обязан быть
|
||||
идентичным по построению, и small accessibility-улучшение попутно для
|
||||
полной карточки не нарушает ни один продуктовый контракт. Отмечаю как
|
||||
наблюдение, не как находку.
|
||||
- **`glowEnabledRooms` в `space-render.ts` строится из `space.rooms.filter(roomGlowOf)`
|
||||
без предварительной фильтрации по наличию валидного `roomPoly()`**, тогда как
|
||||
`revision.polygons` (используемый для сравнения `allEnabled = glowEnabledRooms.length
|
||||
=== revision.polygons.length`) уже отфильтрован по валидной геометрии. У
|
||||
полной карточки оба массива (`enabled`, `polys`) заранее отфильтрованы
|
||||
одинаково. Разница затрагивает только оптимизационную ветку (пропуск
|
||||
построения `enabledClip`, когда все комнаты и так включены) — при вырожденной
|
||||
комнате без `poly`/`x,y,w,h` código просто перейдёт на более медленную ветку
|
||||
с явным вычислением holes, результат на экране не меняется, потому что
|
||||
`glowEnabledRooms.flatMap` сам отбрасывает комнаты без `poly` (`if (!poly)
|
||||
return [];`). Не нахожу видимого дефекта — при желании это Low-полировка, не
|
||||
требующая правки в этой задаче.
|
||||
- **`_cleanFloor()`-эквивалент в статическом пути (`space-render.ts:665`) не
|
||||
делает bounding-box предфильтр `extras` перед `floorMinusBodies`**, в отличие
|
||||
от `_cleanFloor()` полной карточки (`houseplan-card.ts:9388-9393`), которая
|
||||
сначала обрезает кандидатов по bbox floor∩body. Разница чисто в объёме
|
||||
работы буллевой операции (перф), не в результате: `floorMinusBodies`
|
||||
геометрически корректен и без предфильтра. AC7 отдельно требует
|
||||
perf-профиль opt-in пути и он приложен зелёным (см. Гейты); не блокирую.
|
||||
|
||||
## Что проверено и корректно
|
||||
|
||||
- **Единый алгоритмический источник истины (архитектурный контракт п.1–5 ТЗ).**
|
||||
`src/glow-scene.ts` не знает о классе карточки, не читает DOM/`hass`/приватные
|
||||
поля (принимает всё через параметры/`GlowRuntimeHost`), не владеет
|
||||
неограниченным module-level кешем (`createGlowRuntimeState()` — состояние per
|
||||
caller, `writeGlowClip` ограничен лимитом по умолчанию 256). Обе карточки
|
||||
вызывают ровно один набор функций: `resolveLightBarrierRevision` →
|
||||
`buildLightBarrierScene` → `resolveGlowCandidates`/`glowSourceInOpaqueBody` →
|
||||
`buildGlowClipGeometry`/`transitionGlowSource` → `renderGlowPools`. Второй
|
||||
реализации aperture classification/visibility/falloff/SVG-поля нет — проверено
|
||||
чтением всего файла и обоих сайтов вызова (`houseplan-card.ts:10255-10425`,
|
||||
`space-render.ts:548-701`).
|
||||
- **Математика извлечения не изменилась при переносе** — построчно сверено:
|
||||
`probe = Math.max((10/cellCm)*gridPitch, gridPitch*0.5)` идентично старому
|
||||
`Math.max(this._cmToUnits(10), this._gridPitch*0.5)` (сама `_cmToUnits`
|
||||
делает то же деление/умножение, `houseplan-card.ts:7538-7540`); порядок
|
||||
построения `occluders` (room-outline cuts → partition cuts → physical bodies
|
||||
→ wall union recut/rebuild → zero-wall barriers → `splitAtIntersections`)
|
||||
идентичен строка в строку; `readGlowClip`/`writeGlowClip` воспроизводят
|
||||
общую `lruRead`/`lruWrite` карты (move-to-end LRU, лимит по умолчанию 256).
|
||||
- **AC1 (opt-in/обратная совместимость).** `light_pools` — новое опциональное
|
||||
поле `SpaceCardConfig` (`space-card.ts:69`), default `false` и в
|
||||
`getStubConfig()`, и в `setConfig()`. При `false`/omitted `space-render.ts:548`
|
||||
входит в ветку `forgetGlowSpace(...)` без единого вызова видимости/барьера —
|
||||
ни `resolveLightBarrierRevision`, ни `buildLightBarrierScene` не вызываются.
|
||||
Нормализация строго `=== true` (не «любое truthy») — и в `space-card.ts:109`
|
||||
(`lightPools: this._config.light_pools === true`), и в тесте
|
||||
`glow-scene.test.mjs:130`. Ошибка неизвестного `space` не тронута этой веткой
|
||||
кода (её обрабатывает `_errorCard`, вне диффа). Подтверждено смоком
|
||||
`smoke_glow_blending` (0 pools при omitted, 60 при `true`).
|
||||
- **AC2/AC3 (parity источника и окклюзии).** Общий `resolveGlowCandidates`
|
||||
(единственная логика владения marker↔source, приоритет `glow_color`/
|
||||
`glow_radius_cm` над palette/global) и общий `buildLightBarrierScene`
|
||||
используются обеими картами с идентичными входами (`walls`, `zeroWalls`,
|
||||
`sharedWallGeometry`/`canonicalWallGeometry` для recut). Прогон
|
||||
`smoke_glow_blending` подтверждает побитовое совпадение
|
||||
center/cx/cy/radius/lit-parts/clip первого источника между full и static.
|
||||
`smoke_glow_fail_dark` подтверждает fail-dark источника в окне/непрозрачном
|
||||
теле независимо от карточки (через тот же `glowSourceInOpaqueBody`).
|
||||
- **AC4 (rooms/fills/layering).** Порядок слоёв в `space-render.ts` —
|
||||
`data/tunnel fills → glow-base-layer → passageGlowTunnels → glowPools →
|
||||
wallUnion → …` — совпадает с контрактом ТЗ п.4.1; `glowPools` вставлен между
|
||||
`passageGlowTunnels` и `wallUnion`, то есть под стенами/маркерами/подписями,
|
||||
что соответствует «Pool не перекрывает стену, маркер или подпись». Мутант
|
||||
`mutation-gate.mjs` (перенос `${glowPools}` после room-labels) существует
|
||||
именно для проверки порядка. `glowEnabledRooms`/`enabledClip` реализуют
|
||||
«transport проходит через disabled room, но не красит» тем же алгоритмом,
|
||||
что полная карточка (`islandsOf`, holes на другие комнаты).
|
||||
- **AC5 (жизненный цикл).** `transitionGlowSource`/`forgetGlowSource`/
|
||||
`pruneGlowSources`/`disposeGlowRuntime` — общие функции, идентичное поведение
|
||||
таймеров/RAF на обеих картах. `space-card.ts` вызывает `disposeGlowRuntime`
|
||||
и в `disconnectedCallback()`, и в `setConfig()` при переключении
|
||||
`light_pools` в `false` — соответствует п.5.5 ТЗ («выключение флага очищает
|
||||
оставшиеся timers/caches»). Unit `glow-scene.test.mjs` («shared Glow runtime
|
||||
is bounded and tears down every timer and source») проверяет это на fake
|
||||
timers/rafs.
|
||||
- **AC6 (static remains static).** `.hp-static-stage` не менялся (не в диффе),
|
||||
`renderGlowPools` жёстко ставит `pointer-events="none"` и `aria-hidden="true"`
|
||||
на слой; кнопки/hover/tap не затронуты (диф не трогает обработчики событий
|
||||
static card). `smoke_glow_blending` проверяет
|
||||
`getComputedStyle(...).pointerEvents === 'none'` на самой сцене.
|
||||
Screen-space feather пересчитывается из `_stageWidth` (существовавшая
|
||||
ResizeObserver-инфраструктура, переиспользована, не продублирована).
|
||||
- **AC7 (перф/bounded memory).** Новые профили `large-space-card-default-v1` /
|
||||
`large-space-card-glow-v1` заведены симметрично существующим (тот же
|
||||
`benchmark_glow.mjs`, тот же `card-contract.mjs`), в `validate.yml` и
|
||||
`performance.yml` подключены отдельными budgets JSON с ограничением
|
||||
`cacheEntries.glowClip: 0` для default и `128` (bounded) для glow-профиля,
|
||||
`cacheGrowth.glowClip: 0` в обоих — то есть кэш не растёт на обычных HA tick.
|
||||
Численные абсолютные потолки (`hardMaxMs`) щедрые (750–2200 мс), измеренные
|
||||
автором значения (230–274 мс median) далеко внутри них.
|
||||
- **AC8 (full card без регрессии).** Golden + все 3 смок-шарда зелёные на
|
||||
дереве, идентичном HEAD по `src/**` (см. «Гейты»); `test/golden-matrix.test.mjs`
|
||||
явно требует, чтобы `houseplan-card.ts` вызывал `renderGlowPools({...})`
|
||||
общего модуля, а не держал копию рендера.
|
||||
- **AC9 (документация/i18n/релиз).** `docs/LIGHT.md` переписан — больше не
|
||||
утверждает, что static card всегда без pools. `docs/USER-GUIDE.md`/`.ru.md`
|
||||
описывают default `false`, независимость от `live_states` и то, что режим
|
||||
тяжелее дефолтного. i18n: `editor.light_pools` добавлен во все 4 словаря без
|
||||
английского fallback для de/fr (сверено переводом: «Lichtkegel und
|
||||
Wandschatten», «Halos lumineux et ombres des murs» — по смыслу корректны).
|
||||
Оба changelog содержат ссылку на #374 в одном коммите (`0dfc7424`).
|
||||
- **AC10 (сборка и бюджеты).** Никаких новых зависимостей (`package.json`/
|
||||
`package-lock.json` не менялись). Три копии бандла байт-в-байт совпадают
|
||||
(проверено мной локальной пересборкой + `git status` пуст).
|
||||
- Токены `Issue:`/`User-Visible:` на всех 8 коммитах корректны;
|
||||
`User-Visible: yes` ровно на коммите, где правятся оба changelog.
|
||||
- «Одно число — один источник»: диф не добавляет и не дублирует ни одной новой
|
||||
пользовательски видимой величины (переключатель — булев, без предпросмотра
|
||||
числа где-либо ещё); неприменимо.
|
||||
|
||||
## Чего не проверял
|
||||
|
||||
См. подробный список с обоснованием в разделе «Гейты» → «Не перегонялось, и
|
||||
почему»: полный `npm test`/`golden:verify`/perf-Linux-артефакт/`pytest
|
||||
tests_backend`/`model-invariants`/полный `mutation-gate`. Дополнительно не
|
||||
проверял:
|
||||
|
||||
- Не воспроизводил вручную в браузере визуальную идентичность light/dark и
|
||||
390/900 px матрицы из плана автотестов ТЗ (п.6) — полагаюсь на зелёный
|
||||
golden-job (пиксельные снапшоты берутся именно в этой матрице по
|
||||
`demo/golden/README.md`) вместо ручного повторения.
|
||||
- Не проверял способ, которым `staticLightBarrierCache`/`staticPhysicalBodiesCache`
|
||||
(`WeakMap` по `ServerConfig`) освобождаются при полной смене конфигурации —
|
||||
доверяю существующему паттерну `cachedStaticWallGeometry`/`cachedStaticPhysicalBodies`,
|
||||
который используется тем же способом уже до этой задачи (не новый код, не
|
||||
трогался диффом кроме добавления третьей аналогичной кеш-структуры).
|
||||
|
||||
## Вердикт
|
||||
|
||||
Изменение — качественное извлечение общей канонической Glow-модели в
|
||||
`src/glow-scene.ts` с последующим тонким подключением в обеих картах, ровно
|
||||
по архитектурному контракту ТЗ. Математика при переносе не изменилась
|
||||
(построчно сверено), тесты перенесены вместе с кодом, а не ослаблены, кеш и
|
||||
жизненный цикл рантайма — общие и bounded. Собственные независимые прогоны
|
||||
(unit по изменённым файлам, ключевые Glow-смоки, пересборка бандла) зелёные и
|
||||
совпадают с отчётом автора; полный браузерный CI-прогон подтверждён на дереве,
|
||||
идентичном HEAD по продуктовому коду. Блокирующих и Medium-находок нет.
|
||||
|
||||
**Вердикт: зелёный.**
|
||||
Reference in New Issue
Block a user