Issue: #531 User-Visible: no
14 KiB
SPEC-REVIEW-531-r2
Issue: #531 — «Панорама плана в Firefox: viewBox переписывается на каждый
кадр жеста, композитор ждёт 13 vsync-тиков»
Этап: spec (ревью ТЗ, PROCESS.md §2.4)
Трек: полный (не изменился со времени r1 — критерий §5, который задача не
проходит, назван и подтверждён в r1: «нет влияния на производительность»)
Заход: r2 · блокирующих циклов израсходовано 1 из 4
Материал: тело issue #531 на текущем HEAD, dev b4a16ad7. Продуктовый
код не тронут: git diff af2081cb..b4a16ad7 -- src/live-viewport.ts src/live-interaction-runtime.ts src/houseplan-card.ts docs/ARCHITECTURE.md scripts/mutation-gate.mjs test/live-viewport.test.mjs demo/benchmark_large_house.mjs пуст. Единственный коммит между материалом r1
и этим раундом — b4a16ad7 docs: review document for #531 (класс C, публикация
самого документа r1), никакого влияния на технические утверждения r1.
Разбор по дельте (PROCESS.md §2.10)
Предыдущий раунд закончился жёлтым вердиктом с единственной находкой Medium
(в скоупе), остальное ТЗ принято. Автор ответил комментарием
(2026-09-11T09:58:44Z), назвав ровно три правки текста ТЗ: переписан К2,
добавлен AC2а, переформулирован риск 1. Код между раундами не менялся (см.
выше) — делать полный повторный разбор нет предмета: дельта строго локальна
(один контрактный пункт, один новый AC, одна строка риска), не является
ребейзом, не меняет контракт поведения за пределами уже поднятого вопроса и не
затрагивает новую подсистему. Разбор в этом раунде ограничен дельтой и всем, до
чего она дотягивается: К2, АС2/АС2а как пара, риск 1 и их согласованность с
остальным контрактом (К1, К5) и остальными AC (AC1, AC3-AC9), которые дельта не
меняет текстуально, но с которыми новый текст обязан не конфликтовать.
Закрытие раунда r1
| Находка r1 | Чем закрыта | Где это видно |
|---|---|---|
| Medium — второй триггер К2 (сдвиг) не имел ни ориентировочной константы, ни собственного AC; ни один из AC1-AC9 не мог покраснеть, если реализация вообще не построит сдвиговую ветку | К2 переписан как два явных условия с названными константами-ориентирами (LIVE_VIEWBOX_REFRESH_MS ≈100 мс, LIVE_VIEWBOX_REFRESH_SHIFT ≈0.15) и объяснением, зачем нужен второй; добавлен отдельный AC2а (unit), который с подменёнными часами проверяет сдвиговый путь независимо от временного: кадр со смещением выше порога обязан переписать viewBox немедленно, хотя LIVE_VIEWBOX_REFRESH_MS ещё не истёк, а кадр ниже порога — не должен переписывать |
Тело issue #531, раздел ### Контракт, пункт К2 (два подпункта «по времени» / «по сдвигу», абзац «Оба порога — константы модуля»); раздел ### AC, новый пункт AC2а |
| Low — обязательные разделы §7.1 присутствуют по содержанию, но не оформлены отдельными заголовками | Снято ревьюером r1 без правки, автор текст не трогал (подтверждено комментарием «Low ... снят ревьюером без правки — текст не трогал») | Не требует проверки — решение уже зафиксировано в SPEC-REVIEW-531-r1.md |
Проверка не ограничилась чтением заявления автора: ниже — построчная сверка, что новый текст АС2а действительно ловит отсутствие сдвиговой ветки, а не просто существует.
АС2а умеет покраснеть. Если реализация не построит путь «накопленный сдвиг
→ досрочная перезапись» и оставит только временной бюджет — кадр со смещением
выше LIVE_VIEWBOX_REFRESH_SHIFT, но с ещё не истёкшим
LIVE_VIEWBOX_REFRESH_MS, при отсутствующей сдвиговой ветке не перепишет
viewBox; AC2а прямо требует, чтобы он его переписал — assertion провалится.
Симметрично: если реализация триггерит по сдвигу слишком рано (например,
использует долю от неверной оси или не выполняет сброс накопленного смещения
после перезаписи), негативная часть AC2а («кадр со смещением ниже порога — не
переписывает») ловит и это. Это ровно тот сценарий, который в r1 не мог
провалиться ни на одном из AC1-AC9 — пробел закрыт предметно, а не
декларативно.
Согласованность с остальным контрактом не нарушена. К1 («каждый кадр — это только трансформ») и новое К2 не конфликтуют: кадр, на котором истёк один из двух бюджетов (временной или сдвиговый), — явно описанное исключение, а не нарушение К1, и после него «трансформ этих узлов возвращается в единицу в том же кадре» для обоих путей одинаково. АС2 (временной путь) и АС2а (сдвиговый путь) размечены как независимые сценарии и не пересекаются: АС2 говорит «по истечении бюджета», АС2а — «пока временной бюджет ещё не истёк». Риск 1 («пустая полоса на набегающем крае») теперь корректно ссылается на «оба порога К2» вместо одного, и вывод риска («пороги — константы модуля, их достаточно уменьшить») остаётся верным для обоих. AC5 (смок, суммарный счётчик перезаписей за 20 кадров) не переписан и не должен был: у него по-прежнему нет обязанности различать триггер — это ровно то, для чего существует АС2а. AC8 (таблица мутантов на К1/К4) не получил новой строки под сдвиговый триггер; это не пробел этого раунда — §2.7 (таблица «AC · чем доказан · чем краснеет») требование к код-ревью для защитных AC, а не к тексту спецификации, и АС2а сам по себе уже задаёт проверяемый негативный сценарий текстом.
Унаследовано из r1
Без повторной проверки в этом раунде приняты все выводы
docs/reviews/SPEC-REVIEW-531-r1.md (материал: тело issue #531 на dev
af2081cb), поскольку продуктовый код и остальной текст ТЗ, от которых эти
выводы зависят, между раундами не менялись (подтверждено пустым git diff
выше):
- продуктовая рамка и выбор полного трека (docs/SCOPE.md, J1; критерий §5);
- построчная сверка технических утверждений с
src/live-viewport.ts,src/live-interaction-runtime.ts,src/houseplan-card.ts(paintLiveViewportпишетviewBoxбезусловно;isIdentityLiveLayerProjectionуже существует у трансформа и отсутствует уviewBox;_floorView(view) === viewв неизометрическом режиме — факт, не приближение; тегиdata-hp-live-viewbox/data-hp-live-layerсуществуют и используются как описано, включая изометрическую ветку); - проверяемость AC1, AC3, AC4, AC6, AC9 и их привязка к существующей
инфраструктуре (
test/live-viewport.test.mjs,scripts/mutation-gate.mjs,docs/ARCHITECTURE.md:1908); - честность разделения «AC внутри репозитория доказывают механизм» / «приговор по скорости — профиль владельца» (AC7 golden, раздел «Приёмка по скорости»);
- риски 2-4, откат, touch-вывод («единственная точка входа
_liveVp(), Pointer Events унифицируют мышь и тач — отдельного контракта не требуется»); - статус трёх «Принятых технических предположений» как действительно технических, не подменяющих продуктовое решение;
- Low-находка о неформальной структуре разделов §7.1 — снята r1, автор текст, к которому она относится, не менял.
Находки
Нет. Единственная находка r1 (Medium) закрыта по существу — новый текст не просто добавляет константу, а даёт конкретный, способный провалиться сценарий для триггера, который раньше мог остаться нереализованным незамеченно. Новых находок дельта не вносит: правка локальна, не противоречит соседним пунктам контракта и не расширяет скоуп.
Чего не проверял
- Гейты
npx tsc --noEmit/npm test/npm run build/node scripts/check-docs.mjsне запускались — в материале этого раунда нет изменений класса A/B/D (диапазонorigin/dev..HEADпуст,src/**не менялся), прогонять их значило бы проверять пустое множество; та же логика, что в r1. - Реализуемость числовых ориентиров
LIVE_VIEWBOX_REFRESH_MS≈100 мсиLIVE_VIEWBOX_REFRESH_SHIFT≈0.15на живом железе владельца — по тексту ТЗ («Приёмка по скорости — отдельно и не здесь») это предмет профиля на этапе код-ревью/пре-релиза, не текста спецификации; унаследовано из r1 без повторной оценки. - Изометрический путь и поведение при одновременном зуме и панораме — унаследовано из r1 («детальная проверка изо-пути остаётся код-ревью по факту диффа»), дельта этого раунда К6 и зум не касается.
Вердикт
Зелёный · заход r2/4 · High: 0 · Medium: 0.
Единственная находка r1 закрыта предметно: К2 теперь называет константу для
обоих триггеров перезаписи viewBox, и новый AC2а даёт сценарий, который
красится именно на отсутствии сдвиговой ветки (проверено построчно выше, а не
принято на слово автора). Согласованность нового текста с остальным
контрактом (К1, К5, AC2, AC5, риск 1) проверена и дефектов не выявила. Код
между раундами не менялся, поэтому весь остальной корпус выводов r1
наследуется без повторной проверки (раздел «Унаследовано из r1»).
Следующий статус — «Готово к разработке» (S5-ready).
Материал раунда
- Ветка:
dev, коммитb4a16ad7043d— ребейз его осиротит, и это нормально: ниже якоря, которые ребейз не меняет. - Дерево материала:
9ac2a0132e8bb5d8db8eaab5fa14e1a714063cafgit log --all --format='%H %T' | grep 9ac2a0132e8b - Тело issue:
89e75138a50434ac52f42cef606d8af741a8ae3b022dba382c4b62c1f4887e8a - Вердикт конвейера:
green· High 0