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
11 KiB
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.