From b94b1b93cd07a856e93c22ba8986d1a5a2803aa7 Mon Sep 17 00:00:00 2001 From: Sergey Matyunin Date: Sun, 16 Aug 2026 21:46:32 +0300 Subject: [PATCH] docs: specify sun north rotation fix Issue: #166 User-Visible: no --- docs/specs/166-sun-north-rotation.md | 331 +++++++++++++++++++++++++++ docs/specs/README.md | 3 +- 2 files changed, 333 insertions(+), 1 deletion(-) create mode 100644 docs/specs/166-sun-north-rotation.md diff --git a/docs/specs/166-sun-north-rotation.md b/docs/specs/166-sun-north-rotation.md new file mode 100644 index 00000000..22766437 --- /dev/null +++ b/docs/specs/166-sun-north-rotation.md @@ -0,0 +1,331 @@ +# Issue #166 — солнечные лучи зеркально учитывают направление севера + +Статус: **ТЗ на ревью** +Дата: 2026-08-16 +Тип: `bug` · приоритет: `P1` · user value: 8/10 · complexity: 4/10 · risk: 6/10 + +Issue: [#166](https://github.com/Matysh/houseplan-card/issues/166) +Ветка: `issue/166-sun-north-rotation` +Канонические документы: [SCOPE](../SCOPE.md), [SUN](../SUN.md), +[USER-GUIDE](../USER-GUIDE.md), [USER-GUIDE.ru](../USER-GUIDE.ru.md), +[TOUCH-SUPPORT](../TOUCH-SUPPORT.md), [TESTING](../TESTING.md), +[CONFIG-COMPATIBILITY](../CONFIG-COMPATIBILITY.md). + +## 1. Сценарий и продуктовый контекст + +Основная персона — владелец дома, который настроил план по реальной ориентации +здания и ежедневно смотрит его в Full View либо на kiosk-панели. Он поворачивает +стрелку N в настройках туда, где на плане действительно находится север, и +ожидает, что оконные лучи будут соответствовать текущему положению Солнца. + +Это прямой контракт SCOPE J1: House Plan должен правдиво показывать текущее +состояние дома. Выбор окна с неправильной стороны здания является фактически +ложной визуализацией, даже если сам эффект декоративный. + +## 2. Что человек увидит до и после + +**До:** если истинный север не совпадает с верхом холста, поворот стрелки N +зеркально поворачивает солнечное направление. Чтобы осветилось физически верное +окно, пользователю приходится намеренно ставить компас не по реальному северу. +При `north_deg = 0` ошибка незаметна. + +**После:** стрелка N буквально указывает истинный север на плане. House Plan +комбинирует это направление с азимутом `sun.sun`, выбирает окно на физически +правильной стороне и ведёт луч от него внутрь комнаты. Ручная зеркальная +компенсация больше не нужна. + +## 3. Подтверждённая причина + +1. `_compassPoint()` в `src/houseplan-card.ts` вычисляет угол через + `atan2(dx, -dy)`: верх холста = 0°, право = 90°, низ = 180°, лево = 270°. +2. `_renderCompass()` поворачивает стрелку N на сохранённый `north_deg`, поэтому + UI показывает именно направление истинного севера на холсте. +3. `docs/SUN.md` определяет `north_deg` тем же образом: градусы по часовой + стрелке от верха холста до истинного севера. +4. `planSunAngle()` в `src/sun.ts` вопреки этому вычисляет + `norm360(azimuth - northDeg)`. Вычитание отражает поворот относительно + нулевой оси; для заявленной семантики направления должны складываться. +5. `test/sun.test.mjs` закрепляет неверный знак ожиданием «east sun + + `north_deg=90` → up» и выбором верхнего окна. +6. `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` — направление **к Солнцу** на холсте. + +Каноническая формула: + +```text +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 + +В задачу входят: + +1. исправление преобразования `azimuth + north_deg` в единственной чистой + функции солнечного направления; +2. сохранение действующего контракта вектора к Солнцу и противоположного + вектора луча внутрь комнаты; +3. исправление unit-ожиданий, которые сейчас закрепляют неверный знак; +4. smoke-проверка выбора окон при ненулевом global `north_deg` и per-space + override; +5. детерминированная golden-сцена с ненулевым севером, чтобы знак поворота был + визуально различим; +6. исправление формулы и примеров в `docs/SUN.md` и пользовательских руководствах; +7. предупреждение о ранее компенсированных настройках в обоих changelog; +8. обычные 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()` продолжает возвращать единичный вектор к +Солнцу в координатах холста. + +Знак меняется только в композиции двух систем координат. Нельзя одновременно +инвертировать `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 matrix есть хотя бы одна детерминированная сцена с ненулевым +`north_deg` и асимметричным сочетанием азимута/окон, где ошибочный знак выбрал +бы другую сторону плана. + +**Доказательство:** обновлённый 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. План тестирования + +### В цикле реализации + +1. `typecheck`. +2. Полный unit-набор, включая обновлённый `test/sun.test.mjs`. +3. Production build. + +Точные команды берутся из актуального `package.json`/runbook; набор не +расширяется golden/smoke/performance на каждой итерации. + +### Перед бетой + +1. `demo/smoke_sun.mjs` и связанные sun smoke. +2. Golden matrix с новой ненулевой ориентацией и review diff. +3. Штатный performance gate, чтобы подтвердить отсутствие нового churn. +4. Остальные golden/smoke/performance проверки по release runbook. + +Полный HA harness на Windows не является каноном из-за `fcntl`; его +обязательное доказательство остаётся в Linux CI. + +## 12. Риски и защита от регрессии + +1. **Повторная инверсия не того вектора.** Защита: отдельно тестировать + `toSun`, выбранное окно и `ray.dir` внутрь комнаты. +2. **Тесты снова проходят только при north=0.** Защита: обязательный + `north_deg=90` и некардинальный wrap в unit, smoke и visual fixture. +3. **Незаметное изменение four-phase фона.** Защита: фон резолвится без + compass dependency и получает отдельный regression assertion. +4. **Сломанная настройка у пользователей обходного решения.** Защита: + никаких эвристик/миграции; явное предупреждение в обоих changelog и guide. +5. **Ослабление старых физических ограничений.** Защита: существующие тесты + 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. Принятые предположения + +1. `north_deg` всегда означает показанное стрелкой направление истинного + севера на холсте, а не угол, которым нужно повернуть сам план обратно к + северу. +2. Азимут Home Assistant интерпретируется текущим контрактом: 0° — север, + 90° — восток, рост по часовой стрелке. +3. Исправление не мигрирует намеренно неверные обходные значения, потому что + их невозможно надёжно отличить от корректных. +4. Отдельного продуктового решения по UX не требуется: пользователь уже дал + реальный север, меняется только неверный результат этой настройки. diff --git a/docs/specs/README.md b/docs/specs/README.md index 14e8aaaa..5b566457 100644 --- a/docs/specs/README.md +++ b/docs/specs/README.md @@ -1,6 +1,6 @@ # Спецификации задач P1 и P2 -Актуально на 2026-08-14. +Актуально на 2026-08-16. GitHub Issues и GitHub Projects (v2) остаются единственным каноническим backlog проекта. Этот каталог содержит развёрнутые ТЗ: каждое ТЗ ссылается на issue, а issue — на соответствующий файл. Статус, приоритет и факт завершения меняются только в GitHub. @@ -49,6 +49,7 @@ GitHub Issues и GitHub Projects (v2) остаются единственным | [#138](https://github.com/Matysh/houseplan-card/issues/138) Автозамыкание комнаты по существующей стене | [138-adjacent-room-autoclose.md](138-adjacent-room-autoclose.md) | | [#146](https://github.com/Matysh/houseplan-card/issues/146) Четырёхфазный фон «Следует за Солнцем» | [146-four-phase-sun-background.md](146-four-phase-sun-background.md) | | [#156](https://github.com/Matysh/houseplan-card/issues/156) Регрессии Full Performance перед v1.64.0 stable | [156-full-performance-regressions.md](156-full-performance-regressions.md) | +| [#166](https://github.com/Matysh/houseplan-card/issues/166) Солнечные лучи зеркально учитывают направление севера | [166-sun-north-rotation.md](166-sun-north-rotation.md) | ## P2