# Матрица настроек света устройства Актуально для локальной реализации issues [#84](https://github.com/Matysh/houseplan-card/issues/84) и [#88](https://github.com/Matysh/houseplan-card/issues/88). Каноническая модель геометрии и распространения света остаётся в [`LIGHT.md`](LIGHT.md). ## Обозначения - **A** — режим «Авто» нашёл у устройства собственный пространственный источник. - **S** — у устройства есть собственная управляемая `light.*`/`switch.*`, способная дать состояние и принять service call. - **Источник** — устройство участвует в Glow, заливке «Свет», карточке и статистике комнаты. - **Live** — доступен режим цвета «Из источника». - **Ручн.** — доступны ручной цвет и ручная яркость. - **R** — доступен локальный радиус Glow. - `Авто → фикс.` — сохранённый режим не переписывается, но в UI и runtime пассивный источник получает безопасный fallback: общий цвет, яркость 100%. Комбинация `A=да, S=нет` приведена для полноты контракта и мутационных тестов, но недостижима штатным resolver: автоматически найденный источник всегда имеет реальную `light.*`. Остальные 27 строк достижимы. ## Полная матрица role × A × S × режим Glow | # | Роль | A | S | Сохранённый режим | Источник | Пассивный | Live | Ручн. | R | Эффективный режим | |---:|---|:---:|:---:|---|:---:|:---:|:---:|:---:|:---:|---| | 1 | Авто | нет | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled | | 2 | Авто | нет | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled | | 3 | Авто | нет | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled | | 4 | Авто | нет | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled | | 5 | Авто | нет | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled | | 6 | Авто | нет | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled | | 7 | Авто | да | нет | Из источника | да | да | нет | да | да | Авто → фикс. (теоретическая) | | 8 | Авто | да | нет | Задать цвет | да | да | нет | да | да | Задать цвет (теоретическая) | | 9 | Авто | да | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость (теоретическая) | | 10 | Авто | да | да | Из источника | да | нет | да | да | да | Из источника | | 11 | Авто | да | да | Задать цвет | да | нет | да | да | да | Задать цвет | | 12 | Авто | да | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость | | 13 | Всегда | нет | нет | Из источника | да | да | нет | да | да | Авто → фикс. | | 14 | Всегда | нет | нет | Задать цвет | да | да | нет | да | да | Задать цвет | | 15 | Всегда | нет | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость | | 16 | Всегда | нет | да | Из источника | да | нет | да | да | да | Из источника | | 17 | Всегда | нет | да | Задать цвет | да | нет | да | да | да | Задать цвет | | 18 | Всегда | нет | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость | | 19 | Всегда | да | нет | Из источника | да | да | нет | да | да | Авто → фикс. (теоретическая) | | 20 | Всегда | да | нет | Задать цвет | да | да | нет | да | да | Задать цвет (теоретическая) | | 21 | Всегда | да | нет | Цвет + яркость | да | да | нет | да | да | Цвет + яркость (теоретическая) | | 22 | Всегда | да | да | Из источника | да | нет | да | да | да | Из источника | | 23 | Всегда | да | да | Задать цвет | да | нет | да | да | да | Задать цвет | | 24 | Всегда | да | да | Цвет + яркость | да | нет | да | да | да | Цвет + яркость | | 25 | Никогда | нет | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled | | 26 | Никогда | нет | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled | | 27 | Никогда | нет | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled | | 28 | Никогда | нет | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled | | 29 | Никогда | нет | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled | | 30 | Никогда | нет | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled | | 31 | Никогда | да | нет | Из источника | нет | нет | нет | нет | нет | Из источника, disabled (теоретическая) | | 32 | Никогда | да | нет | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled (теоретическая) | | 33 | Никогда | да | нет | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled (теоретическая) | | 34 | Никогда | да | да | Из источника | нет | нет | нет | нет | нет | Из источника, disabled | | 35 | Никогда | да | да | Задать цвет | нет | нет | нет | нет | нет | Задать цвет, disabled | | 36 | Никогда | да | да | Цвет + яркость | нет | нет | нет | нет | нет | Цвет + яркость, disabled | Ручной цвет при stateful-источнике оставляет живую яркость. Режим «Цвет + яркость» фиксирует оба значения. Для пассивного источника живой яркости нет, поэтому «Задать цвет» означает яркость 100%, а «Цвет + яркость» использует сохранённое значение. ## Ведущая сущность (#88) | Роль | Управляемых собственных сущностей | Сохранённый `light_entity` | UI и runtime | |---|---:|---|---| | Авто / Никогда | любое число | любое значение | Селектор скрыт; поле сохраняется без изменения, но на текущую роль не влияет | | Всегда | 0 | отсутствует | Пассивный источник, селектор скрыт | | Всегда | 1 | отсутствует | Единственная сущность выбирается автоматически, селектор скрыт | | Всегда | 2+ | отсутствует | Селектор показан; fallback `binding → primary → первая управляемая` | | Всегда | 1+ | валидное значение | Выбранная сущность даёт state и service target | | Всегда | любое число | сущность исчезла | Предупреждение; временный fallback, сохранённая ссылка не стирается | Выбор не зависит от текущего `on/off/unavailable`: capability берётся из binding и реестра HA, а live state обрабатывается отдельно. ## Связи «Управляет другими источниками света» (#84) | Цель в `controls` | Состояние цели | Service call контроллера | Glow и статистика | |---|---|---|---| | `light.*` / `switch.*` | Фактическое состояние entity; группа — `any(on)` | Только реальные entity IDs | Реальный отдельный marker владеет позицией; иначе цель участвует без отдельного пятна у контроллера | | `marker:` stateful | Состояние ведущей сущности цели | Ведущая сущность цели, с дедупликацией | Позиция, комната, цвет и радиус принадлежат marker-цели | | `marker:` passive, один controller | Его реальные targets, иначе собственная ведущая entity | Сам `marker:*` никогда не отправляется в HA | Пассивная лампа следует controller и светит в своей позиции | | Passive, несколько controllers | OR всех активных driver entities | По каждому действию — только его реальные targets | Один источник и один голос комнаты, без дублей | | Exact `virtual` + «Всегда» + Toggle, есть links | OR всех активных driver entities; ручной bit временно не участвует | Клик controller — его группа; клик лампы — deduplicated union всех её drivers | HA state едино управляет Glow, заливкой, статистикой и обоими marker | | Exact `virtual` + «Всегда» + Toggle, links отсутствуют | Operational Store #107; отсутствие записи = `on` | Клик лампы меняет только operational state, без HA service | Ручное состояние общее для full/static и сохраняется после restart | | Passive без сохранённых links, не exact-режим #107 | Всегда `on` | Нет собственного вызова | Постоянный Glow и `1 из 1` | | Links есть, но все drivers скрыты/disabled/удалены | `off` / dormant | Нет вызова по битой цели | Ссылка сохраняется, пятна нет | | Прямая entity + `marker:` на тот же stateful source | Одно effective состояние | Один service target | Один источник и один голос | | Target переведён из «Всегда» в «Авто без источника»/«Никогда» | Dormant | Нет marker-service | Link сохраняется и оживает при возврате «Всегда» | | Target скрыт или disabled в HA | Dormant | Нет marker-service | Не рисуется и не влияет на комнату | | Target удалён с плана | Ссылка удаляется атомарно | Нет | Устройство снова можно добавить заново | | Broken legacy ref | Игнорируется с диагностикой | Нет | Open → Save не уничтожает ссылку | | Self-link / новый цикл | Запрещено | — | UI не предлагает, backend отклоняет запись/import | Связи разрешены между комнатами и пространствами одного плана. При экспорте одного пространства внутренние `marker:`-ссылки ремапятся вместе с marker ID, а внешние отбрасываются с предупреждением в preview. ## Независимые переключатели отображения | Настройка | Влияние на световую модель | |---|---| | «Значок + состояние» | Подложка устройства показывает его resolved working state; Glow определяется матрицей выше | | «Значок + состояние и активность» | Дополнительно показывает короткую или постоянную пульсацию; источник света не меняется | | «Значение + состояние» | Меняет только содержимое маркера; источник света не меняется | | «Всегда статичный значок» | Блокирует динамику самого значка/подложки, но не отменяет отдельно настроенные Glow, заливку «Свет» и статистику комнаты | | Glow выключен у пространства | Пятна не рисуются, но источник, состояние комнаты и заливка «Свет» продолжают вычисляться | | Маркер скрыт / HA-disabled | Маркер и его собственный источник не участвуют в плане независимо от остальных настроек | ## Проверяемый контракт - Все 36 строк первой таблицы проверяются pure unit-тестом. - Выбор ведущей сущности, stale fallback, passive OR, dormant links, alias-dedupe, lifecycle и запрет передачи `marker:*` в HA покрыты отдельными unit-тестами. - Контекстный селектор, passive-гейтинг и plan-source picker покрыты browser smoke. - Backend отдельно проверяет новые ссылки, циклы и перенос между пространствами.