mirror of
https://github.com/Matysh/houseplan-card
synced 2026-10-02 12:49:56 +00:00
docs: spec #331 — boundary precision of the junction limits
Quantised node keys with -0 normalisation and node incidence at the quantum, zero-degree wedges become visible, an iterative maximal-branch wall run, arc collinearity measured against the chain base, fail-closed candidate checks, and a narrow except in the python mirror. Every reproduction in §1 was verified by execution on current dev after #330. Issue: #331 User-Visible: no
This commit is contained in:
@@ -0,0 +1,142 @@
|
||||
# Issue #331 — пограничная точность ограничений стыков (#329)
|
||||
|
||||
Статус: ревизия 1. Родители: #329 (правила), #330 (производительность —
|
||||
слита, код проверок актуален по её итогу). Сёстры вне скоупа: #333
|
||||
(optimize/import-лазейка), #339 (смешанные толщины у острия).
|
||||
|
||||
## 1. Сценарий и пользовательский результат
|
||||
|
||||
Класс симптома «легитимная запись отклонена / нелегитимная пропущена» уже
|
||||
дважды бил по бетам (#316, #319). Репро на `dev` после #330 (все —
|
||||
исполнением):
|
||||
|
||||
- пара узлов `−1e-8` и `0` (плавающий мусор ресайза до канонизации) — **два
|
||||
ложных нарушения П4** с `actual = 0.000012 см`: ключи узлов `toFixed(6)`
|
||||
дают `"-0.000000"` и `"0.000000"`;
|
||||
- точный дубль стены (клин ровно 0°) не ловится ни П1, ни П4 — худший
|
||||
вырожденный случай невидим, тогда как 0.5° блокируется;
|
||||
- цепочка из ~9000 коллинеарных атомов — `RangeError` (обе реализации;
|
||||
на бэке ушло бы клиенту как `unknown_error` с трейсбеком);
|
||||
- при двух коллинеарных продолжениях в узле `.find` берёт первое — вторая
|
||||
ветвь молча теряется, длина стены занижается → ложный отказ П3;
|
||||
- исключение при проверке КАНДИДАТА пропускает запись (fail-open) — в
|
||||
противоход установленному fail-closed гарду #278;
|
||||
- `except Exception` в `_migrated_spaces` маскирует настоящие баги миграции;
|
||||
- дуга из атомов по ~0.9°/шаг накапливает произвольный поворот, считаясь
|
||||
одной «стеной» П3.
|
||||
|
||||
Результат: на границах вердикты честные и одинаковые в обоих зеркалах;
|
||||
переполнений стека нет; отказ проверки — это отказ записи, не пропуск.
|
||||
|
||||
## 2. Решение (нормативно, оба зеркала симметрично)
|
||||
|
||||
### 2.1 Квантованный ключ узла и инцидентность
|
||||
|
||||
Ключ узла — координаты, квантованные к **1e-7** (`round(v·1e7)/1e7`) с
|
||||
нормализацией `−0 → 0`; формат строки ключа одинаков в TS и python. Порог
|
||||
выбран на два порядка грубее канонической сетки хранения (1e-9) и на
|
||||
порядки тоньше любого осмысленного зазора плана (минимальный порог правил —
|
||||
5 см ≈ 4e-4 в норм. координатах): плавающий мусор сливается, реальная
|
||||
геометрия неразличимой не становится.
|
||||
|
||||
В П4 пары узлов с евклидовой дистанцией ≤ **1e-7** считаются ОДНИМ узлом
|
||||
(инцидентность, не нарушение) — закрывает случай «через границу кванта»
|
||||
(например `−5.1e-8` и `5.1e-8`).
|
||||
|
||||
### 2.2 П1: ноль градусов — честное нарушение
|
||||
|
||||
Фильтр `degrees > EPS` снимается: дельта ~0° между лучами узла — нарушение
|
||||
угла с `actual ≈ 0`. Легальных 0°-клиньев не существует: коллинеарный стык
|
||||
атомов одной прямой стены даёт в узле 180°-пару, а 0° — всегда дубль или
|
||||
наложение стен. Точный дубль сегмента становится видим (два одинаковых
|
||||
луча из одного узла).
|
||||
|
||||
### 2.3 Итеративный прогон П3 с максимальной ветвью
|
||||
|
||||
`collinearRunLengthUnits` / `collinear_run_length_units` переписываются
|
||||
итеративно (явный стек, без рекурсии — глубина входа больше не
|
||||
ограничивает). При развилке из нескольких коллинеарных продолжений одной
|
||||
толщины берётся **максимальная** ветвь (DFS по вариантам, visited на рёбрах)
|
||||
— вторая ветвь больше не теряется молча.
|
||||
|
||||
### 2.4 Коллинеарность — к базе цепочки
|
||||
|
||||
Допуск ±1° меряется к направлению ПЕРВОГО сегмента прогона, а не к
|
||||
предыдущему атому — накопление поворота по дуге исчезает. Прямая стена с
|
||||
легальными микро-изломами (<1° суммарно) остаётся одной стеной; дуга,
|
||||
набравшая >1° от базы, — нет.
|
||||
|
||||
### 2.5 Fail-closed на кандидате
|
||||
|
||||
Исключение при вычислении нарушений КАНДИДАТА в `_junctionLimitsIntroduced`
|
||||
— отказ записи с тостом (ключ `junction.limit_check_failed`, i18n en+ru), по
|
||||
образцу fail-closed гарда #278. Baseline остаётся fail-open (недоказуемое
|
||||
наследование не повод отклонять запись) — асимметрия сознательная и
|
||||
комментируется в коде.
|
||||
|
||||
### 2.6 Узкий except в зеркале
|
||||
|
||||
`_migrated_spaces`: `except Exception` → `except (WallSegmentMigrationError,
|
||||
ValueError)` + `_LOGGER.debug` с причиной. TypeError/RecursionError и прочие
|
||||
баги миграции всплывают в общий обработчик WS честной ошибкой.
|
||||
|
||||
## 3. Границы
|
||||
|
||||
- Пороги правил П1–П5 (#329) не меняются; меняются вердикты только
|
||||
пограничных классов: плавающий мусор (было: ложный отказ), 0°-дубль
|
||||
(было: невидим), развилка/глубокая цепочка (было: занижение/краш), дуга
|
||||
(было: одна «стена»).
|
||||
- Спека #329 §2 дополняется абзацем о квантовании ключей и нуле градусов
|
||||
(той же правкой, отдельной ревизией её файла).
|
||||
- Вне скоупа: #333, #339, любые изменения производительности (#330 —
|
||||
бюджеты бенча обязаны остаться зелёными).
|
||||
|
||||
## 4. Acceptance criteria
|
||||
|
||||
- **AC1 (ключи/инцидентность).** Пара `−1e-8`/`0` — ноль нарушений П4;
|
||||
пара «через границу кванта» (`−5.1e-8`/`5.1e-8`) — ноль; узлы на
|
||||
дистанции 4 см — по-прежнему нарушение; ровно 5 см — нет. Оба зеркала,
|
||||
через паритет-набор.
|
||||
- **AC2 (0°).** Точный дубль стены — нарушение П1 с `actual < 0.001°`;
|
||||
прямая стена из двух атомов через узел (180°-пара) — по-прежнему чиста;
|
||||
T-стык — чист. Оба зеркала.
|
||||
- **AC3 (итеративность/ветви).** Цепочка 10 000 атомов — ответ без
|
||||
переполнения (обе реализации); развилка двух коллинеарных ветвей — длина
|
||||
= максимальной ветви (юниты с точным ожиданием), короткий доборный атом
|
||||
перепада толщин остаётся законным (существующий AC3b #329 зелёный).
|
||||
- **AC4 (дуга).** Дуга 30×0.9° не считается одной стеной (первый атом
|
||||
короче 20 см в сумме с коллинеарной БАЗЕ частью — отказ П3); прямая с
|
||||
одним изломом 0.5° — считается.
|
||||
- **AC5 (fail-closed).** Синтетическое исключение в проверке кандидата
|
||||
(мутант/подмена) — запись отклонена с тостом `junction.limit_check_failed`;
|
||||
исключение на baseline — запись проходит. Смок/юнит.
|
||||
- **AC6 (except).** Битая форма документа (TypeError в миграции) — честная
|
||||
ошибка WS, не тихое «нарушений нет»; `WallSegmentMigrationError` —
|
||||
прежний фолбэк «нет базы». Backend-юниты.
|
||||
- **AC7 (паритет и регресс).** Паритет-набор расширен всеми классами выше;
|
||||
все существующие юниты/смоки/мутанты #329+#330 и бенч #330 зелёные без
|
||||
изменения ожиданий (кроме прямо описанных в §3 пограничных вердиктов).
|
||||
|
||||
## 5. План тестов
|
||||
|
||||
Юниты границ на каждый пункт §2 в обоих зеркалах + расширение паритета;
|
||||
смок AC5 (тост отказа при сломанной проверке — через подмену в смоке);
|
||||
мутанты: `junction-limit-zero-wedge-invisible` (возврат фильтра
|
||||
`degrees > EPS`), `junction-limit-key-precision-lost` (возврат toFixed(6)),
|
||||
`junction-limit-branch-dropped` (возврат `.find` первой ветви),
|
||||
`junction-limit-candidate-fail-open` (возврат `catch { return []; }`).
|
||||
Бенч #330 — без правок бюджетов.
|
||||
|
||||
## 6. Обязательные разделы
|
||||
|
||||
- **i18n**: один новый ключ `junction.limit_check_failed` (en+ru).
|
||||
- **Touch**: не задет.
|
||||
- **Риски**: (1) квантование ключей меняет вердикты классов входов —
|
||||
закрыто явным перечнем §3 и паритетом; (2) выбор максимальной ветви может
|
||||
легализовать ранее отклонявшиеся конфигурации — это исправление
|
||||
занижения, фиксируется юнитом с точным ожиданием; (3) 0°-нарушение
|
||||
может всплыть на легаси-планах с дублями — наследование #329 §3
|
||||
по-прежнему пропускает несвязанные правки (инвариант не трогается).
|
||||
- **Release**: обычная бета; CHANGELOG (en+ru) одной строкой user-visible
|
||||
(«точность проверок стыков на границах: ложные отказы устранены, дубли
|
||||
стен видимы»); откат — чистый revert.
|
||||
Reference in New Issue
Block a user