mirror of
https://github.com/Matysh/houseplan-card
synced 2026-09-29 03:09:36 +00:00
@@ -1,9 +1,9 @@
|
||||
# #306 — Нулевые стены вместо виртуальных границ
|
||||
|
||||
- **Issue:** [#306](https://github.com/Matysh/houseplan-card/issues/306)
|
||||
- **Статус документа:** готово к независимому ревью ТЗ
|
||||
- **Статус документа:** актуализировано после #282; готово к независимому ревью ТЗ
|
||||
- **Приоритет / тип:** P1 / feature
|
||||
- **Целевая версия модели:** `PLAN_MODEL_VERSION = 8`
|
||||
- **Целевая версия модели:** `PLAN_MODEL_VERSION = 9`
|
||||
- **Пользовательское изменение:** да
|
||||
|
||||
## 1. Сценарий
|
||||
@@ -33,9 +33,13 @@
|
||||
В редакторе остаётся одна модель:
|
||||
|
||||
- `cm > 0` — стена с физическим телом;
|
||||
- `cm = 0` — топологическая стена без физического тела;
|
||||
- отсутствие записи `walls[]` в старом разреженном формате — legacy-физическая
|
||||
осевая стена, а **не** нулевая стена.
|
||||
- `cm = 0` — топологическая стена без физического тела.
|
||||
|
||||
После #282 каждый contour atom уже хранится в authoritative-каталоге
|
||||
`space.wall_segments[]` со стабильным `id`, точными `a/b` и явным `cm`.
|
||||
Происхождение нулевого атома не сохраняется и не влияет на результат: прежняя
|
||||
физическая ось без тела, бывшая виртуальная граница и новая стена, нарисованная
|
||||
с толщиной 0, становятся одной и той же сущностью.
|
||||
|
||||
Пользователь рисует и изменяет оба вида инструментами **«Стены»** и
|
||||
**«Толщина»**. Настройка пространства определяет, показываются ли все нулевые
|
||||
@@ -55,12 +59,17 @@
|
||||
| `solid` | сплошная линия | блокируется | блокируются | не соединяет световые зоны |
|
||||
|
||||
5. Отсутствующее/неизвестное значение настройки читается как `dashed`.
|
||||
Это правило применяется ко **всем** `cm:0`, включая атомы, которые #282
|
||||
создала из старых физических осей без толстого тела. В результате часть
|
||||
старых планов после обновления неизбежно изменит вид или светопроницаемость;
|
||||
это принятое владельцем поведение, а не ошибка миграции.
|
||||
6. Нулевая стена в обоих режимах остаётся частью топологии, но не создаёт
|
||||
физическое тело, бумагу, тоннель проёма и вычет площади.
|
||||
7. Новый проём на нулевой стене запрещён. Перевод участка с проёмом в `0`
|
||||
отклоняется до записи; проём автоматически не удаляется и не деактивируется.
|
||||
8. Legacy `open_spans`, а при их отсутствии legacy `open_to`, мигрируют lossless
|
||||
в явные атомы `cm: 0`. Простое чтение конфигурации ничего не записывает.
|
||||
8. Legacy `open_spans`, а при их отсутствии legacy `open_to`, переводят
|
||||
покрытые атомы v8 в `cm:0`; все остальные уже существующие `cm:0` остаются
|
||||
неотличимыми от них. Простое чтение конфигурации ничего не записывает.
|
||||
9. После канонической записи старые поля больше не пишутся. Downgrade на версию,
|
||||
не понимающую `cm:0`, не поддерживается; восстановление выполняется из backup.
|
||||
10. Plan editor остаётся desktop-first. View и kiosk на touch входят в
|
||||
@@ -84,22 +93,28 @@
|
||||
носителя и `cm`. Нельзя сливать `outer(A)` с `shared(A,B)`, разные пары shared,
|
||||
room-wall с independent wall или пересекать узел/проём.
|
||||
|
||||
### 5.2 Три состояния разреженного room-wall слоя
|
||||
### 5.2 Authoritative wall model v8
|
||||
|
||||
Пока не реализована целевая объектная модель #282, `space.walls[]` остаётся
|
||||
разреженным слоем поверх осей комнат:
|
||||
#282 уже реализовала Stage 1 модели стен:
|
||||
|
||||
- явная запись `cm: 1..100` — положительная толщина;
|
||||
- явная запись `cm: 0` — нулевая стена;
|
||||
- нет записи на интервале — legacy-физическая ось без толстого тела.
|
||||
- `space.wall_segments[]` содержит **каждый** contour atom и является
|
||||
authoritative для identity и толщины;
|
||||
- `rooms[].wall_ids[]` ссылается на эти атомы в порядке обхода `poly`;
|
||||
- `space.walls[]` — только compatibility-проекция положительных толщин и не
|
||||
является источником истины;
|
||||
- `partitions[]` и `room_drafts[].segments[]` сохраняют собственные стабильные
|
||||
ID и явный `cm`.
|
||||
|
||||
Любой resolver обязан проверять **наличие явного атома**, а не подставлять `0`
|
||||
из-за отсутствия записи. Функции физического тела фильтруют `cm > 0`; функции
|
||||
топологии и редактора сохраняют и видят `cm = 0`.
|
||||
#306 не создаёт второй каталог и не возвращает midpoint-key identity. Она
|
||||
расширяет уже существующий v8-инвариант: `cm:0` получает единый пользовательский
|
||||
смысл и диапазон записи `0..100` на всех segment-based write paths. Функции
|
||||
физического тела фильтруют `cm > 0`; функции топологии и редактора сохраняют и
|
||||
видят `cm = 0`. При замыкании draft нулевой сегмент наследует ID по lineage-
|
||||
правилам #282.
|
||||
|
||||
Для независимых стен те же значения хранятся в `partitions[].cm` и
|
||||
`room_drafts[].segments[].cm`. При замыкании draft его нулевые сегменты
|
||||
переносятся в явные `walls[]` атомы созданной комнаты.
|
||||
Никаких `zero_kind`, `legacy_origin`, скрытых compatibility-флагов или эвристик
|
||||
по происхождению сегмента не добавляется. Два `cm:0` с одинаковой ролью и
|
||||
геометрией всегда эквивалентны.
|
||||
|
||||
### 5.3 Единый resolver
|
||||
|
||||
@@ -107,10 +122,11 @@ room-wall с independent wall или пересекать узел/проём.
|
||||
контракта) является единственной точкой для:
|
||||
|
||||
- `resolveZeroWallMode(space): { style, transmitsLight }`;
|
||||
- проекции legacy `open_spans/open_to` в runtime-атомы;
|
||||
- проекции legacy `open_spans/open_to` на authoritative v8 atoms;
|
||||
- классификации явных нулевых атомов по роли;
|
||||
- получения линий световых барьеров и derived room connectivity;
|
||||
- миграции runtime-проекции в канонический persisted candidate.
|
||||
- миграции v8 candidate в канонический v9 document без смены stable ID там,
|
||||
где carrier не разрезается.
|
||||
|
||||
Glow и солнце не имеют собственных проверок `dashed/solid`. Оба используют
|
||||
результат этого resolver. Визуальный renderer получает тот же `style`, но не
|
||||
@@ -125,17 +141,19 @@ type ZeroWallStyle = 'dashed' | 'solid';
|
||||
|
||||
interface SpaceConfig {
|
||||
zero_wall_style?: ZeroWallStyle; // missing/unknown read fallback = dashed
|
||||
walls?: Array<{ key: string; cm: number; a?: number[]; b?: number[] }>;
|
||||
room_drafts?: Array<{ segments: Array<{ cm: number }> }>;
|
||||
wall_segments: Array<{ id: string; a: number[]; b: number[]; cm: number }>;
|
||||
walls?: Array<{ key: string; cm: number; a: number[]; b: number[] }>;
|
||||
room_drafts?: Array<{ segments: Array<{ id: string; cm: number }> }>;
|
||||
partitions?: Array<{ id: string; a: number[]; b: number[]; cm: number }>;
|
||||
}
|
||||
```
|
||||
|
||||
Backend принимает `cm: 0..100` для `walls[]`, `room_drafts[].segments[]` и
|
||||
`partitions[]`; `wall_columns[].cm` остаётся `1..150`. Новые/переписанные room
|
||||
wall records обязаны иметь точные `a/b`; key-only `cm:0` новая версия не пишет.
|
||||
Backend принимает `cm: 0..100` для `wall_segments[]`,
|
||||
`room_drafts[].segments[]` и `partitions[]`; `wall_columns[].cm` остаётся
|
||||
`1..150`. Compatibility `walls[]` генерируется только из `cm>0`, всегда имеет
|
||||
точные `a/b` и никогда не содержит zero record.
|
||||
|
||||
`PLAN_MODEL_VERSION` повышается с `7` до `8` только при фактической канонической
|
||||
`PLAN_MODEL_VERSION` повышается с `8` до `9` только при фактической канонической
|
||||
записи хотя бы одного затронутого пространства. Открытие карточки и
|
||||
неструктурные изменения версию не повышают.
|
||||
|
||||
@@ -145,11 +163,12 @@ wall records обязаны иметь точные `a/b`; key-only `cm:0` но
|
||||
|
||||
- временно принимаются backend/import reader для старых документов;
|
||||
- сохраняются без мутации при read-only загрузке;
|
||||
- проецируются в runtime как `cm:0` при fallback `dashed`;
|
||||
- проецируются в runtime поверх v8 catalog: покрытые интервалы имеют `cm:0`, а
|
||||
остальные существующие `cm:0` не получают отдельного происхождения;
|
||||
- удаляются из затронутого пространства одной транзакцией после успешной
|
||||
канонической миграции;
|
||||
- никогда не создаются новым frontend;
|
||||
- в экспорте канонического v8 отсутствуют;
|
||||
- в экспорте канонического v9 отсутствуют;
|
||||
- регистрируются в `docs/CONFIG-COMPATIBILITY.md` и
|
||||
`scripts/config-field-registry.mjs` существующими статусами
|
||||
`deprecated-read` для compatibility-read и `migrate-on-write` для
|
||||
@@ -187,9 +206,10 @@ Unknown sibling-поля сохраняются на read/write как сейч
|
||||
|
||||
### 7.3 Рисование по существующей оси
|
||||
|
||||
Рисование `cm:0` поверх доказанного участка room wall изменяет/атомизирует этот
|
||||
участок. Оно не создаёт совпадающую `partition`. Повторное рисование того же
|
||||
интервала — semantic no-op и не добавляет Undo-команду.
|
||||
Рисование `cm:0` поверх доказанного участка room wall изменяет/атомизирует
|
||||
authoritative `wall_segments[]` через identity barrier #282. Оно не создаёт
|
||||
совпадающую `partition`. Повторное рисование того же интервала — semantic no-op
|
||||
и не добавляет Undo-команду.
|
||||
|
||||
На совпадающей independent partition меняется существующий объект только если
|
||||
есть ровно один однозначный carrier. T/X-узел, несколько carrier или конфликт
|
||||
@@ -250,14 +270,15 @@ Unknown sibling-поля сохраняются на read/write как сейч
|
||||
|
||||
### 8.4 Нормализация и Optimize
|
||||
|
||||
- Общий `normalizeWallIntervals()` сохраняет явные нули и объединяет только
|
||||
role-equivalent соседние атомы.
|
||||
- Identity writer #282 сохраняет stable ID и явные нули; соседние атомы
|
||||
объединяются только при совпадении role/owners/cm и по документированным
|
||||
lineage-правилам (survivor ID детерминирован, ссылки opening остаются валидны).
|
||||
- Ни один helper не должен применять `clampWallCm(0) → 1`.
|
||||
- Optimize preview отдельно считает `legacy virtual spans migrated` и
|
||||
`zero-wall atoms merged`.
|
||||
- После Confirm все пространства мигрируют атомарно; ошибка/лимит в одном
|
||||
пространстве отменяет весь candidate и оставляет one-deep Optimize Undo.
|
||||
- Повторный Optimize канонического v8 — byte/semantic no-op.
|
||||
- Повторный Optimize канонического v9 — byte/semantic no-op.
|
||||
|
||||
## 9. Свет и отображение
|
||||
|
||||
@@ -295,30 +316,42 @@ solid блокирует пересечение луча. Нулевая outer w
|
||||
- Hover/hit target остаётся доступным минимумом текущего wall editor и не
|
||||
ограничивается визуальной толщиной линии.
|
||||
|
||||
## 10. Алгоритм миграции v7 → v8
|
||||
## 10. Алгоритм миграции v8 → v9
|
||||
|
||||
Миграция pure, deterministic, idempotent и возвращает либо полный candidate,
|
||||
либо typed blocker.
|
||||
либо typed blocker. Документ старее v8 сначала проходит уже реализованный
|
||||
identity barrier #282 и только затем этот алгоритм; отдельный второй каталог
|
||||
стен не строится.
|
||||
|
||||
1. Валидировать rooms, walls, openings, limits и координаты без изменения input.
|
||||
1. Валидировать v8 `rooms[].poly/wall_ids`, `wall_segments`, openings, limits и
|
||||
координаты без изменения input. Для pre-v8 input получить полный v8 candidate
|
||||
существующим `commitWallSegmentModel()`.
|
||||
2. Если есть валидные `open_spans`, использовать их. Иначе построить полные
|
||||
shared intervals по `open_to` через существующий `sharedBoundary` resolver.
|
||||
3. Нормализовать и clip spans строго к доказанным room-wall carriers. Висящий,
|
||||
неоднозначный или non-finite span — blocker, не silently drop.
|
||||
4. Собрать breakpoints из концов carrier, legacy spans, положительных wall
|
||||
records, room ownership/role changes, nodes и opening intervals.
|
||||
5. Разрезать carrier на атомы. Интервал, покрытый legacy span, получает явную
|
||||
запись `cm:0`; положительный остаток сохраняет точный `cm`; legacy-физический
|
||||
остаток без записи остаётся отсутствующей записью.
|
||||
6. Проверить конфликт проёмов. Любое пересечение мигрируемого нулевого атома с
|
||||
opening host — blocker всего пространства.
|
||||
7. Role-aware merge объединяет только эквивалентные соседние нулевые атомы.
|
||||
8. Удалить `space.open_spans` и `rooms[].open_to` только из готового candidate.
|
||||
Если новое поле отсутствует, оставить его отсутствующим: read fallback
|
||||
`dashed` уже сохраняет поведение.
|
||||
9. Прогнать geometry preflight и backend schema. При превышении 500 `walls[]`
|
||||
или любого лимита отказать целиком; усечение запрещено.
|
||||
10. При записи установить `model_version: 8`. Повторный запуск возвращает no-op.
|
||||
3. Нормализовать и clip legacy spans строго к доказанным contour carriers.
|
||||
Висящий, неоднозначный или non-finite span — blocker, не silently drop.
|
||||
4. Убедиться, что endpoints каждого legacy span являются границами атомов.
|
||||
Если v8 catalog уже атомизирован #282, stable IDs сохраняются. Если требуется
|
||||
дополнительный split, применяется lineage #282: один доказанный survivor
|
||||
сохраняет исходный ID, остальные получают новые ID, а `room.wall_ids` и
|
||||
wall-hosted references обновляются атомарно.
|
||||
5. Каждый атом, покрытый legacy virtual span, получает `cm:0`. Положительный
|
||||
остаток сохраняет точный `cm`. Любой атом, который уже имел `cm:0`, остаётся
|
||||
нулевым без отдельной метки происхождения и получает тот же style/light mode.
|
||||
6. Проверить проёмы на **всех** итоговых `cm:0`, а не только на бывших virtual
|
||||
spans. Zero opening host — blocker всего пространства: opening и исходный v8
|
||||
документ сохраняются, молчаливое удаление запрещено.
|
||||
7. Role-aware normalization объединяет только эквивалентные соседние нулевые
|
||||
атомы и сохраняет валидную identity/host lineage.
|
||||
8. Перегенерировать compatibility `walls[]` только из `cm>0`. Удалить
|
||||
`space.open_spans` и `rooms[].open_to` только из полностью готового candidate.
|
||||
9. Не добавлять `zero_wall_style`, если пользователь его не сохранял: runtime
|
||||
fallback `dashed` применяется ко всем `cm:0`. Это намеренно может изменить
|
||||
вид и свет старых bodyless-физических осей после обновления.
|
||||
10. Прогнать geometry preflight, model invariants и backend schema. При
|
||||
превышении 500 `wall_segments[]` или любого лимита отказать целиком;
|
||||
усечение запрещено.
|
||||
11. При записи установить `model_version: 9`. Повторный запуск возвращает no-op.
|
||||
|
||||
### 10.1 Когда выполняется write
|
||||
|
||||
@@ -331,7 +364,7 @@ solid блокирует пересечение луча. Нулевая outer w
|
||||
- **«Оптимизировать планы»** строит preview и мигрирует все legacy-пространства
|
||||
одной подтверждённой транзакцией.
|
||||
- Full/space import старого документа проецирует и валидирует migration в
|
||||
preview; Apply сохраняет canonical v8 candidate либо отказывает целиком.
|
||||
preview; Apply сохраняет canonical v9 candidate либо отказывает целиком.
|
||||
|
||||
### 10.2 Масштаб и round-trip
|
||||
|
||||
@@ -342,11 +375,11 @@ config/layout round-trip не восстанавливают deprecated fields.
|
||||
|
||||
## 11. Backend, import/export и concurrency
|
||||
|
||||
- `custom_components/houseplan/validation.py` принимает новый enum и `cm:0`, но
|
||||
различает новый explicit zero и отсутствие record.
|
||||
- Validation запрещает key-only zero on new write, zero opening host и
|
||||
non-finite/negative cm; legacy document допускается только через compatibility
|
||||
read/import path.
|
||||
- `custom_components/houseplan/validation.py` принимает новый enum и сохраняет
|
||||
существующий v8 диапазон `wall_segments[].cm = 0..100`.
|
||||
- Validation v9 запрещает zero opening host, non-finite/negative cm, нарушение
|
||||
catalog/room projection и canonical наличие `open_spans/open_to`; legacy
|
||||
document допускается только через compatibility read/import path.
|
||||
- `import_export.py` включает `zero_wall_style`, умеет preview старой миграции и
|
||||
выдаёт локализуемый/машиночитаемый blocker code.
|
||||
- Config revision/CAS и атомарная config+layout запись остаются обязательными;
|
||||
@@ -358,17 +391,22 @@ config/layout round-trip не восстанавливают deprecated fields.
|
||||
|
||||
### 12.1 Forward compatibility
|
||||
|
||||
Старые конфигурации отображаются прежним пунктиром и пропускают свет до любой
|
||||
записи. Миграция не изменяет area, positive wall thickness, openings или
|
||||
координаты. Backend сохраняет compatibility-reader минимум весь релизный цикл
|
||||
v1.68.x; удаление reader требует отдельного issue и данных telemetry/fixtures.
|
||||
Старые `open_spans/open_to` до записи продолжают читаться. Одновременно runtime
|
||||
применяет единый zero-wall mode ко всем v8 `cm:0`: при отсутствующей настройке
|
||||
это `dashed`, поэтому ранее bodyless-физические оси могут стать пунктирными и
|
||||
начать пропускать свет уже после обновления. Этот переход намеренно не является
|
||||
lossless по визуалу/свету; координаты, stable IDs, комнаты, положительные
|
||||
толщины и сами opening records не меняются. Backend сохраняет compatibility-
|
||||
reader минимум весь релизный цикл v1.68.x; удаление reader требует отдельного
|
||||
issue и данных telemetry/fixtures.
|
||||
|
||||
### 12.2 Downgrade / rollback
|
||||
|
||||
Старая версия клиента не понимает explicit `cm:0`, поэтому downgrade после
|
||||
canonical write не поддерживается. До первой структурной записи пользователь
|
||||
может сделать полный backup. После Optimize доступен существующий one-deep Undo
|
||||
в текущей сессии; надёжный откат между версиями — импорт backup.
|
||||
Версия до #306 понимает v8 `cm:0`, но не понимает их новую единую семантику и
|
||||
может снова записать `open_spans/open_to`. Поэтому downgrade после canonical v9
|
||||
write не поддерживается. До первой структурной записи пользователь может сделать
|
||||
полный backup. После Optimize доступен существующий one-deep Undo в текущей
|
||||
сессии; надёжный откат между версиями — импорт backup.
|
||||
|
||||
Релизный rollback до первой beta выполняется revert коммита. После beta нельзя
|
||||
возвращать старую schema в stable; исправление выпускается forward-only с
|
||||
@@ -383,7 +421,7 @@ canonical write не поддерживается. До первой струк
|
||||
fingerprint/cache включают `space id + geometry revision + zero_wall_style`.
|
||||
- Смена HA state не пересобирает wall atoms, connectivity или sun barriers.
|
||||
- Лимит записей остаётся 500; результат не truncates.
|
||||
- Benchmark на large-house fixture сравнивает v7 projection и v8 canonical:
|
||||
- Benchmark на large-house fixture сравнивает v8 compatibility projection и v9 canonical:
|
||||
p95 построения barrier/first render не должен регрессировать более чем на 10%,
|
||||
steady HA-state render — более чем на 5% относительно baseline ветки.
|
||||
- Нулевые line barriers не превращаются в полигоны, что ограничивает рост
|
||||
@@ -437,7 +475,8 @@ canonical write не поддерживается. До первой струк
|
||||
- `src/light-visibility.ts`, `src/sun.ts`, `src/iso-walls.ts` — единый light mode
|
||||
и visual parity;
|
||||
- `src/plan-optimizer.ts`, `src/plan-geometry-preflight.ts`,
|
||||
`src/coordinate-canonicalization.ts` — migration v8, limits/idempotence;
|
||||
`src/wall-segment-model.ts`, `src/coordinate-canonicalization.ts` — migration
|
||||
v9 поверх identity barrier #282, limits/idempotence;
|
||||
- `custom_components/houseplan/const.py`, `validation.py`, `import_export.py`,
|
||||
`coordinate_canonicalization.py` — model version, schema и import/export;
|
||||
- `src/i18n/en.json`, `src/i18n/ru.json`;
|
||||
@@ -469,12 +508,16 @@ golden основной панели до/после.
|
||||
**Доказательство:** table-driven unit `test/wall-thickness.test.mjs` и smoke с
|
||||
Undo/Redo.
|
||||
|
||||
### AC3. Отсутствующая запись не становится нулевой
|
||||
### AC3. Все `cm:0` имеют одну семантику
|
||||
|
||||
Legacy room edge без `walls[]` record остаётся физической осевой стеной и не
|
||||
получает dashed/open semantics. Только explicit `cm:0` является нулевой стеной.
|
||||
Для v8 plan с двумя нулевыми contour atoms — один получен из прежней физической
|
||||
оси без тела, второй совпадает с `open_spans` — runtime и v9 migration не
|
||||
различают происхождение. Оба следуют одному `zero_wall_style`; persisted
|
||||
`zero_kind`/compatibility-marker отсутствует. При default `dashed` оба становятся
|
||||
пунктирными и пропускают свет.
|
||||
|
||||
**Доказательство:** migration unit + backend round-trip regression на v7 fixture.
|
||||
**Доказательство:** migration/runtime unit на смешанной v8 fixture + source-
|
||||
contract, запрещающий discriminator происхождения.
|
||||
|
||||
### AC4. Физическая геометрия и площадь не получают тело
|
||||
|
||||
@@ -504,15 +547,18 @@ area byte/number equal между стилями; golden solid.
|
||||
### AC7. Смена настройки работает без reload
|
||||
|
||||
Переключение `dashed ↔ solid` сразу меняет line style, Glow, sun и connectivity;
|
||||
старые barrier/render caches не используются, координаты и `walls[]` не меняются.
|
||||
старые barrier/render caches не используются, координаты и `wall_segments[]` не
|
||||
меняются.
|
||||
|
||||
**Доказательство:** unit fingerprint/cache test + browser smoke в одном session.
|
||||
|
||||
### AC8. Миграция `open_spans` lossless и idempotent
|
||||
### AC8. Миграция `open_spans` сохраняет данные и idempotent
|
||||
|
||||
Полный/частичный/соседний span, разные positive residues и shared/outer role
|
||||
преобразуются в exact `cm:0` atoms; deprecated fields удаляются только в
|
||||
candidate, model становится v8. Второй запуск no-op.
|
||||
преобразуются в exact `cm:0` atoms; stable IDs сохраняются по lineage #282,
|
||||
deprecated fields удаляются только в candidate, model становится v9. Второй
|
||||
запуск no-op. Координаты/комнаты/opening records не теряются; визуальная и
|
||||
световая losslessness для прежних bodyless `cm:0` намеренно не обещается.
|
||||
|
||||
**Доказательство:** `test/zero-wall-migration.test.mjs` fixture matrix и snapshot
|
||||
до/после/после второго запуска.
|
||||
@@ -553,7 +599,8 @@ space мигрирует его в той же CAS transaction.
|
||||
### AC13. Optimize и import/export атомарны
|
||||
|
||||
Optimize preview показывает counts и мигрирует все spaces после Confirm; one-deep
|
||||
Undo возвращает v7 data в сессии. Full/space v7 import создаёт v8 candidate.
|
||||
Undo возвращает исходные v8/legacy data в сессии. Full/space v8 или более старый
|
||||
import создаёт v9 candidate через последовательные identity и zero-wall barriers.
|
||||
Лимит 500, invalid span, opening conflict или revision conflict отклоняет весь
|
||||
candidate без truncation/partial apply.
|
||||
|
||||
@@ -571,8 +618,9 @@ smoke `show_borders`.
|
||||
|
||||
### AC15. Backend и compatibility registry согласованы
|
||||
|
||||
Backend принимает canonical v8 `cm:0` с exact endpoints, отклоняет negative,
|
||||
non-finite, key-only zero и zero opening host. Старый v7 документ читается.
|
||||
Backend принимает canonical v9 `wall_segments[].cm:0` с exact endpoints,
|
||||
отклоняет negative, non-finite, zero opening host и legacy virtual fields в v9.
|
||||
Старый v8/pre-v8 документ читается через compatibility path.
|
||||
`docs/CONFIG-COMPATIBILITY.md` описывает оба deprecated поля и downgrade.
|
||||
|
||||
**Доказательство:** backend parameterized tests + docs review.
|
||||
@@ -587,7 +635,7 @@ best effort и не блокирует.
|
||||
|
||||
### AC17. Производительность и кэши
|
||||
|
||||
Large-house v7 projection/v8 canonical проходят бюджеты §13; HA state tick не
|
||||
Large-house v8 projection/v9 canonical проходят бюджеты §13; HA state tick не
|
||||
запускает atomization/barrier rebuild; style toggle запускает ровно одну
|
||||
инвалидацию требуемых geometry/light caches.
|
||||
|
||||
@@ -618,7 +666,7 @@ Code-review/release gate дополнительно выполняет:
|
||||
- Glow и sun deterministic golden matrix;
|
||||
- flat/isometric/show_borders golden review;
|
||||
- large-house performance capture с бюджетами §13;
|
||||
- полный импорт v7 full/space fixtures и v8 round-trip;
|
||||
- полный импорт v8 и pre-v8 full/space fixtures и v9 round-trip;
|
||||
- сверку `dist/houseplan-card.js` и
|
||||
`custom_components/houseplan/frontend/houseplan-card.js`.
|
||||
|
||||
@@ -650,7 +698,7 @@ Golden принимаются только через действующую pol
|
||||
| Glow и sun разойдутся | единый resolver, совместная матрица AC5/AC6 |
|
||||
| `0` превратится в `1` старым clamp | отдельный zero-aware parser + AC2/AC11 |
|
||||
| Atomization превысит лимит | atomic failure, no truncation |
|
||||
| Старый клиент испортит v8 | документированный unsupported downgrade + backup |
|
||||
| Старый клиент вернёт legacy-поля в v9 | документированный unsupported downgrade + backup |
|
||||
| Cache переживёт style toggle | style in fingerprint + AC7/AC17 |
|
||||
| Две модели продолжат писаться | source-contract запрещает production legacy writers |
|
||||
|
||||
@@ -662,12 +710,13 @@ Golden принимаются только через действующую pol
|
||||
- #173 — единый инструмент «Стены»;
|
||||
- #199 — geometry preflight;
|
||||
- #224/#299 — canonical coordinates и role-aware records;
|
||||
- #282 — будущая объектная модель стен. #306 не ждёт #282, но не вводит вторую
|
||||
новую сущность: explicit zero является совместимым промежуточным атомом.
|
||||
- #282 — реализованная обязательная основа: stable `wall_segments[].id`,
|
||||
`rooms[].wall_ids` и единый structural identity barrier. #306 не дублирует и
|
||||
не обходит этот writer.
|
||||
|
||||
### Вне скоупа
|
||||
|
||||
- стабильные wall ids и полный graph rewrite #282;
|
||||
- Stages 2–4 ADR #282 (integer lattice, единый planar graph, closed-form junctions);
|
||||
- разный style/light mode у отдельных zero walls;
|
||||
- проёмы в нулевых стенах и «неактивные» сохранённые проёмы;
|
||||
- создание внешней световой зоны;
|
||||
|
||||
Reference in New Issue
Block a user