feat: full mitre at every node — the #249 chamfer retires (#302, decision #5)

Владелец, осмотрев первые эталоны сета, отменил дневное решение о сохранении
фаски: `junction-y-60-equal50` показывал вырез, `junction-acute30-mixed` —
торчащие углы. По визуальному сравнению трёх вариантов принято: узлы
смыкаются полным mitre, как обычное пересечение стен на чертеже.

Итоговое правило веера (одно на все случаи):

- mitre принимается, когда он В СЕКТОРЕ пары (вперёд по лучам для обычной
  пары, назад — для рефлексной: наружный угол между крайними лучами, где и
  жил вырез Y-60), в пределах классического `MITRE_LIMIT` и не дальше конца
  толстого саппорта (#271);
- рефлекс без валидного mitre замыкается плоской хордой между гранями;
- обычная пара без mitre — локальный бевел: ход по граням ограничен толстым
  саппортом, лимитом и двойной толщиной пары, чтобы хорда осталась деталью
  угла. Гигантские бевел-«бабочки» и mitre вне сектора — две реальные ошибки
  промежуточных версий, обе пойманы на сценах сета до пуша.

Слой `bevelMultiWallBody` сохранён только как АДРЕСНЫЙ латеральный трим для
узлов с вырожденно-коротким толстым саппортом (#271); все прочие узлы — чисто
аддитивные, следы трима на них исчезли. `bevelMultiWallPaper` из бумаги
удалён. Обе записи CHANGELOG приведены к финальному контракту.

Тесты: юниты §302 усилены; площадь фикстуры #197 +0.6 юнита²; мутанты
переякорены, краснота каждого проверена исполнением.

Issue: #302
User-Visible: yes
This commit is contained in:
Codex
2026-08-25 17:34:19 +03:00
parent 8af5ebefd2
commit aac501516e
17 changed files with 453 additions and 348 deletions
+23 -12
View File
@@ -2211,16 +2211,16 @@ export const MUTANTS = [
},
{
id: 'junction-fans-disabled',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="issue 302" test/wall-thickness.test.mjs',
guard: 'node demo/smoke_junction_holes.mjs',
because: 'без вееров сектор между соседними полосами узла остаётся дырой — '
+ 'это и есть класс артефактов #302',
patches: [{
file: 'src/wall-thickness.ts',
find: ' if (mitre && mitreWithinStrips',
replace: ' if (false && mitre && mitreWithinStrips',
find: ' if (mitre) {\n push([[P[0], P[1]], EA, mitre, EB]);\n continue;\n }',
replace: ' if (mitre) {\n continue;\n }',
}, {
file: 'src/wall-thickness.ts',
find: ' push(out.fans, [[P[0], P[1]], EA, A2, B2, EB]);',
find: ' push([[P[0], P[1]], EA, A2, B2, EB]);',
replace: ' void A2; void B2;',
}],
},
@@ -2238,23 +2238,34 @@ export const MUTANTS = [
{
id: 'junction-fan-ignores-thick-length',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="issue 302" test/wall-thickness.test.mjs',
because: 'веер, шагающий за короткий толстый саппорт, рисует латеральный '
because: 'mitre, шагнувший за короткий толстый саппорт, рисует латеральный '
+ 'фантом рядом с тонким продолжением (#271)',
patches: [{
file: 'src/wall-thickness.ts',
find: ' mitreWithinStrips = tA <= A.thickLength && tB <= B.thickLength;',
replace: ' mitreWithinStrips = true;',
find: ' : tA > 1e-9 && tA <= A.thickLength && tB <= B.thickLength;',
replace: ' : tA > 1e-9;',
}],
},
{
id: 'junction-reflex-sector-fanned',
id: 'junction-reflex-outer-mitre-missing',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="issue 302" test/wall-thickness.test.mjs',
because: 'рефлексный сектор — внешность выпуклого угла; веер там — лишний '
+ 'фасадный материал (контракт вогнутого Split)',
because: 'рефлексный сектор — наружный угол между крайними лучами; без '
+ 'обратного mitre там остаётся вырез Y-60 из отчёта владельца',
patches: [{
file: 'src/wall-thickness.ts',
find: ' if (sector > Math.PI + 1e-9) continue;',
replace: ' void sector;',
find: ' const directionOk = reflex\n ? tA <= 1e-9 && tB <= 1e-9',
replace: ' const directionOk = reflex\n ? false',
}],
},
{
id: 'junction-fan-limit-back-to-249',
guard: 'npx tsc -p tsconfig.test.json && node scripts/fix-test-build.mjs && node --test --test-name-pattern="issue 302" test/wall-thickness.test.mjs',
because: 'лимит веера 1.25·h — это отставка решения №5: узлы снова с '
+ 'вырезами и ступеньками вместо полного mitre',
patches: [{
file: 'src/wall-thickness.ts',
find: ' const limit = MITRE_LIMIT * Math.max(A.halfDepth, B.halfDepth);\n // Facing strip edges',
replace: ' const limit = node.limit;\n // Facing strip edges',
}],
},
{