Волна 5 эпика #674, перенос ТЗ (класс C). Из 240 файлов `docs/specs/` в `legacy/specs/` уехали 219: на них не ссылается ни один живой файл (код, тесты, скрипты, workflow, документы вне архива и ревью). Остались 21 ТЗ — на которые ссылаются код, ADR, ISOMETRIC, SUN, RADAR, LIGHT (`docs/specs/067`), DECOR-EDITOR, support-relay, и те, на которые ссылаются они сами; README каталога объясняет, где искать остальное. Открытых issue с файлом ТЗ среди перенесённых нет. Относительные ссылки перенесённых файлов переписаны (`../X` → `../../docs/X`, соседние оставшиеся ТЗ → `../../docs/specs/…`) — все 26 резолвятся. Попутно: битая ссылка в `089-isometric-view-stage1.md:8` на удалённый `089-isometric-view.md` — теперь команда `git show` по истории. Строка в `legacy/README.md`. Issue: #682 User-Visible: no Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018qZfe7YS4rqEMKoVeS3GKd
24 KiB
Issue #166 — солнечные лучи зеркально учитывают направление севера
Статус: ТЗ принято, готово к реализации
Дата: 2026-08-16
Тип: bug · приоритет: P1 · user value: 8/10 · complexity: 4/10 · risk: 6/10
Issue: #166
Ветка: issue/166-sun-north-rotation
Канонические документы: SCOPE, SUN,
USER-GUIDE, USER-GUIDE.ru,
TOUCH-SUPPORT, TESTING,
CONFIG-COMPATIBILITY.
1. Сценарий и продуктовый контекст
Основная персона — владелец дома, который настроил план по реальной ориентации здания и ежедневно смотрит его в Full View либо на kiosk-панели. Он поворачивает стрелку N в настройках туда, где на плане действительно находится север, и ожидает, что оконные лучи будут соответствовать текущему положению Солнца.
Это прямой контракт SCOPE J1: House Plan должен правдиво показывать текущее состояние дома. Выбор окна с неправильной стороны здания является фактически ложной визуализацией, даже если сам эффект декоративный.
2. Что человек увидит до и после
До: если истинный север не совпадает с верхом холста, поворот стрелки N
зеркально поворачивает солнечное направление. Чтобы осветилось физически верное
окно, пользователю приходится намеренно ставить компас не по реальному северу.
При north_deg = 0 ошибка незаметна.
После: стрелка N буквально указывает истинный север на плане. House Plan
комбинирует это направление с азимутом sun.sun, выбирает окно на физически
правильной стороне и ведёт луч от него внутрь комнаты. Ручная зеркальная
компенсация больше не нужна.
3. Подтверждённая причина
_compassPoint()вsrc/houseplan-card.tsвычисляет угол черезatan2(dx, -dy): верх холста = 0°, право = 90°, низ = 180°, лево = 270°._renderCompass()поворачивает стрелку N на сохранённыйnorth_deg, поэтому UI показывает именно направление истинного севера на холсте.docs/SUN.mdопределяетnorth_degтем же образом: градусы по часовой стрелке от верха холста до истинного севера.planSunAngle()вsrc/sun.tsвопреки этому вычисляетnorm360(azimuth - northDeg). Вычитание отражает поворот относительно нулевой оси; для заявленной семантики направления должны складываться.test/sun.test.mjsзакрепляет неверный знак ожиданием «east sun +north_deg=90→ up» и выбором верхнего окна.demo/fixtures/visual-matrix.mjsиспользуетnorth_deg = 0, поэтому действующие golden-сцены не различают сложение и вычитание.
Репорт подтверждён по коду и является дефектом координатного преобразования.
4. Координатный контракт
Все углы нормализуются в [0, 360) и растут по часовой стрелке.
A(azimuth) — направление от истинного севера к Солнцу изsun.sun: N=0°, E=90°, S=180°, W=270°.N(north_deg) — направление истинного севера на холсте: верх=0°, право=90°, низ=180°, лево=270°.P— направление к Солнцу на холсте.
Каноническая формула:
P = norm360(N + A)
toSun = (sin(P), -cos(P))
awayFromSun = -toSun
Здесь ось X растёт вправо, ось Y — вниз. toSun используется для проверки,
какая внешняя нормаль окна смотрит на Солнце; awayFromSun остаётся
направлением хода света от окна внутрь комнаты.
Контрольные примеры:
| Север на холсте N | Азимут A | Направление к Солнцу P | Сторона холста |
|---|---|---|---|
| 0° (вверх) | 90° (восток) | 90° | справа |
| 90° (вправо) | 0° (север) | 90° | справа |
| 90° (вправо) | 90° (восток) | 180° | снизу |
| 270° (влево) | 90° (восток) | 0° | сверху |
| 350° | 20° | 10° | wrap через 360° |
5. Scope
В задачу входят:
- исправление преобразования
azimuth + north_degв единственной чистой функции солнечного направления; - сохранение действующего контракта вектора к Солнцу и противоположного вектора луча внутрь комнаты;
- исправление unit-ожиданий, которые сейчас закрепляют неверный знак;
- smoke-проверка выбора окон при ненулевом global
north_degи per-space override; - детерминированная golden-сцена с ненулевым севером, чтобы знак поворота был визуально различим;
- исправление формулы и примеров в
docs/SUN.mdи пользовательских руководствах; - предупреждение о ранее компенсированных настройках в обоих changelog;
- обычные bundle/release-артефакты пользовательского исправления.
6. Non-scope
В задачу не входят:
- изменение значений либо частоты обновления
sun.sun; - новая астрономическая модель, геолокация или расчёт положения Солнца внутри House Plan;
- изменение длины, цвета, opacity, fade, rim или clipping солнечных лучей;
- изменение определения внешнего окна либо взаимное затенение крыльями здания;
- изменение четырёхфазного фона
daynight, который по контракту #146 не зависит от компаса; - изменение UI компаса, его диапазона, шага, inheritance или доступности;
- изменение режима редакторов или добавление лучей в статическую
houseplan-space-card; - автоматическая миграция либо эвристическое распознавание пользовательской компенсации старого дефекта.
7. Функциональное поведение
7.1. Преобразование направления
planSunAngle(azimuth, northDeg) возвращает
norm360(azimuth + northDeg). Для любых конечных входов сохраняется текущая
нормализация угла. sunDirOnPlan() продолжает возвращать единичный вектор к
Солнцу в координатах холста.
Архитектурный инвариант для текущих и будущих потребителей, включая
SUN-CONTRAST: направление Солнца на плане берётся только из
planSunAngle(). Ни render-слой, ни contrast-логика не вводят вторую формулу,
зеркальное преобразование или дополнительную поправку знака.
Знак меняется только в композиции двух систем координат. Нельзя одновременно
инвертировать toSun, нормали окон или awayFromSun: это дало бы локально
правильный тест, но снова перепутало бы освещённую сторону либо направление
луча внутри комнаты.
7.2. Выбор окна и геометрия луча
windowLit() продолжает сравнивать внешнюю нормаль окна с toSun. После
исправления выбирается окно со стороны холста, соответствующей P. Геометрия
луча продолжает строиться вдоль -toSun, то есть от освещённого окна внутрь
принимающей комнаты. Все действующие правила exterior/interior, wall depth,
room clipping, physical obstacles, gradient и rim сохраняются.
Для контрольного плана с окнами на четырёх сторонах, north_deg=90 и
azimuth=90 должно выбираться нижнее окно холста, а не верхнее. Луч от нижнего
окна идёт вверх, внутрь комнаты.
7.3. Global и per-space
Правило наследования не меняется: явный north_deg пространства выигрывает у
global; пустой override наследует global; отсутствие обоих отключает оконные
лучи. И global, и override используют одну исправленную формулу. Preview
несохранённого значения в открытом диалоге должен давать тот же результат,
что и значение после сохранения.
7.4. Гейты и соседние режимы
Без изменений остаются:
- отсутствие/невалидность
sun.sunвыключает оконные лучи; elevation <= 0не создаёт геометрию, а 3°-порог и двухсекундный fade продолжают действовать по текущему контракту;sun_rays=false, editor mode и отсутствиеnorth_degвыключают слой;bg_mode=daynightи декоративный свет #146 не используютnorth_deg;- memo key уже содержит
azimuth,elevation, effective north и config epoch; его состав менять не требуется.
8. UX, accessibility, touch и i18n
Компас, number input, 1°/15° шаги, pointer capture, клавиатурный ввод и подписи остаются прежними. Исправляется результат существующей настройки, а не interaction.
Full View и kiosk сохраняют полный touch-контракт. Настройка компаса остаётся desktop-first поверхностью редактора настроек; новых жестов или hover-only действий нет. Новых i18n-ключей не требуется. EN/RU пользовательская документация должна однозначно сказать, что стрелка N направляется туда, где на плане находится истинный север.
9. Данные, migration и совместимость
Схема не меняется: north_deg остаётся integer 0–359 на global и per-space
уровнях. Backend validation, storage version, export/import и default null
не меняются.
Автомиграции north_deg -> 360 - north_deg нет. Она повредила бы корректно
сохранённые по документированному смыслу значения и не может отличить их от
намеренной компенсации. После обновления:
- значения, выставленные по реальному северу, начинают давать правильные лучи;
0°и180°визуально сохраняют прежнее направление;- пользователю, который зеркально компенсировал дефект, нужно один раз вернуть стрелку N к реальному северу. Это явно указывается в EN/RU changelog.
10. Acceptance criteria и доказательства
AC-01 — математический контракт
planSunAngle() использует сложение и нормализацию. Таблица из раздела 4,
включая wrap и некардинальный угол, проходит в test/sun.test.mjs.
Доказательство: unit-тесты точных углов и sunDirOnPlan() с допуском для
float.
AC-02 — семантика компаса совпадает с геометрией
Положения стрелки N сверху/справа/снизу/слева означают соответственно 0/90/180/270°, а одинаковое значение, заданное dial либо number input, даёт одинаковое направление лучей до и после сохранения.
Доказательство: действующие compass smoke-проверки плюс направленный smoke
с north_deg=90.
AC-03 — освещается физически правильное окно
На плане с четырьмя внешними окнами для north_deg=90, azimuth=90 и
положительной elevation выбирается нижнее окно холста; луч направлен от него
внутрь. Внутреннее окно не участвует.
Доказательство: unit computeSunRays() и production-bundle
demo/smoke_sun.mjs.
AC-04 — inheritance исправлен на обоих уровнях
Global north и per-space override используют новый знак; удаление override снова применяет global. Отсутствие обоих по-прежнему не рисует лучи.
Доказательство: unit наследования и smoke текущей full-card конфигурации.
AC-05 — соседнее поведение не изменено
Кардинальные направления при north_deg=0, night/elevation gates, exterior
window selection, clipping, wall thickness, obstacle subtraction, ray length,
fade/rim, editor gating и независимый four-phase background проходят
существующие тесты без ослабления assertions.
Доказательство: полный unit-набор и существующие sun smoke; отдельная
проверка, что изменение north_deg не меняет resolved day-cycle #146.
AC-06 — визуальная регрессия больше не маскируется
В GOLDEN_SCENARIOS есть хотя бы одна детерминированная сцена с ненулевым
north_deg и асимметричным сочетанием азимута/окон, где ошибочный знак выбрал
бы другую сторону плана. Добавление либо семантическое изменение этой сцены
сопровождается повышением GOLDEN_MATRIX_VERSION.
Доказательство: обновлённый manifest/diff golden; ручное принятие только изменённой ожидаемой сцены.
AC-07 — документация и release contract
docs/SUN.md содержит формулу norm360(north_deg + azimuth), а EN/RU guide
объясняет буквальную ориентацию стрелки N. Оба changelog называют исправление
и предупреждают пользователей старой ручной компенсации.
Доказательство: docs diff и terminal trailers Issue: #166,
User-Visible: yes в продуктовых коммитах реализации.
AC-08 — performance, security и поддерживаемые поверхности
Исправление не добавляет DOM, таймеров, сетевых вызовов, config writes либо новых пересчётов; геометрия по-прежнему memoized. View/kiosk работают на desktop и touch; security boundary не меняется.
Доказательство: typecheck/unit/build в цикле, штатный performance gate и smoke/golden перед бетой, без новых security exceptions.
11. План тестирования
В цикле реализации
typecheck.- Полный unit-набор, включая обновлённый
test/sun.test.mjs. - Production build.
Точные команды берутся из актуального package.json/runbook; набор не
расширяется golden/smoke/performance на каждой итерации.
Перед бетой
demo/smoke_sun.mjsи связанные sun smoke.- Golden matrix с новой ненулевой ориентацией и review diff.
- Штатный performance gate, чтобы подтвердить отсутствие нового churn.
- Остальные golden/smoke/performance проверки по release runbook.
Полный HA harness на Windows не является каноном из-за fcntl; его
обязательное доказательство остаётся в Linux CI.
Мутационный гейт
Перед передачей реализации на код-ревью автор вручную применяет каждый мутант, запускает указанный узкий тест и фиксирует, что тест красный; после возврата рабочей реализации тот же тест должен быть зелёным.
| Мутант | Обязательное доказательство |
|---|---|
Вернуть в planSunAngle() вычитание azimuth - northDeg |
Табличный unit AC-01 падает хотя бы на контрольных сочетаниях 90° + 90° и wrap через 360° |
Оставить сложение углов, но инвертировать toSun вместо исправления требуемой семантики вектора |
Таблица углов AC-01 остаётся зелёной, а направленный unit AC-03 выбора нижнего окна при north_deg=90, azimuth=90 падает |
Вернуть north_deg: 0 в новой golden-сцене |
Структурный тест golden matrix падает, потому что обязательная запись в GOLDEN_SCENARIOS больше не содержит ненулевой ориентации, а GOLDEN_MATRIX_VERSION не соответствует сценарию |
Гейт не считается доказанным только визуальным diff: для каждого мутанта нужен исполняемый тест с заранее определённой причиной падения.
12. Риски и защита от регрессии
- Повторная инверсия не того вектора. Защита: отдельно тестировать
toSun, выбранное окно иray.dirвнутрь комнаты. - Тесты снова проходят только при north=0. Защита: обязательный
north_deg=90и некардинальный wrap в unit, smoke и visual fixture. - Незаметное изменение four-phase фона. Защита: фон резолвится без compass dependency и получает отдельный regression assertion.
- Сломанная настройка у пользователей обходного решения. Защита: никаких эвристик/миграции; явное предупреждение в обоих changelog и guide.
- Ослабление старых физических ограничений. Защита: существующие тесты interior windows, elevation, grazing angle, clipping, wall depth и obstacles остаются обязательными.
13. Rollback
Rollback — единый revert формулы, тестов, golden fixture и документации. Данных для отката нет: schema/storage не меняются. Частичный rollback недопустим — нельзя оставить старую формулу с новыми тестами/документацией либо наоборот.
14. Release-артефакты
Реализация является user-visible и обязана в одном продуктовом коммите содержать:
- запись в
docs/CHANGELOG.md; - запись в
docs/CHANGELOG.ru.md; - исправление
docs/SUN.md; - синхронное уточнение
docs/USER-GUIDE.mdиdocs/USER-GUIDE.ru.md; - обновлённые unit и
demo/smoke_sun.mjs; - ненулевую compass-сцену в golden fixture/manifest и принятый diff;
- пересобранные поставляемые bundle-копии по release runbook;
- terminal trailers
Issue: #166иUser-Visible: yes.
15. Принятые предположения
north_degвсегда означает показанное стрелкой направление истинного севера на холсте, а не угол, которым нужно повернуть сам план обратно к северу.- Азимут Home Assistant интерпретируется текущим контрактом: 0° — север, 90° — восток, рост по часовой стрелке.
- Исправление не мигрирует намеренно неверные обходные значения, потому что их невозможно надёжно отличить от корректных.
- Отдельного продуктового решения по UX не требуется: пользователь уже дал реальный север, меняется только неверный результат этой настройки.