작성 2026-08-28 · 브랜치 feat/post-replace-relayout-2 ·
커밋 2aed14ada · 코퍼스 1,177건 시점의 글이다.
이 글이 §5 에서 지적한 중복이 그 뒤 실제로 정리됐다 —
「짝 밀기 두 벌을 method 7 한 벌로 합친다」(bad3810f2) ·
「한 이름을 쓰던 두 뜻을 가른다」(9a4f52fb2).
현재판은 재배치 알고리즘 파이프라인 v0.7 이다.
내용 대치가 끝난 컴포넌트를 넘침·겹침·유휴 여백이 없도록 다시 배치한다. 연산자가 여덟이 됐고, 새로 붙은 둘은 페이지의 빈 공간을 이동 예산으로 쓴다. 이 판은 파이프라인을 흐름도로 펴고, 책임이 겹치는 자리·복잡도·선택 층을 코드로 따져 적었다.
penetrationOf 라는
같은 판별식으로 겹침 쌍을 서로 겹치지 않게 나눠 가진다 — 한 쌍은 반드시 한쪽에만 간다.
합본에서 둘 다 돌지만 같은 쌍을 두 번 건드리지 않는다. §4penetrationOf 가 두 파일에 글자 하나 다르지 않게 복제돼 있고
(그 동일성에 분업의 정확성이 걸려 있다), OVERLAP_EPS 는 같은 이름으로 1 과 100 두 뜻을 쓴다. §5O(L²) 는 사실상 상수다.
지배항은 method 6 — 배율 한 칸마다 앞의 다섯을 통째로 되부르고, 회귀 가드가 켜지면 그것이 두 번 돈다. §6.1betterOf 는 9.7%. §6.3betterOf 이고,
그건 스프레드 합본 3종에서 두 갈래만 고른다. 설계안 C 는 후보 전부가 대상이라 범위가 다르다. §7브랜치 feat/post-replace-relayout-2 · 커밋 2aed14ada 의
작업트리다(git status 비어 있음 — 줄 번호가 그대로 맞는다).
실측은 aee07d0b 에서 돌렸고, 그 뒤 HEAD 가 움직였지만
이 글이 인용한 후보 파일은 한 줄도 안 바뀌었다 — 바뀐 것은
metrics.ts(+59줄) · relayout-core/index.ts · 문서, 그리고 새 파일
separateOverlaps.ts 다(§5 에 반영). 인용한 15개 줄 번호는
현재 HEAD 에서 다시 확인했다.
이 커밋은 원격에 없다 — GitHub permalink 가 404가 되므로
코드 링크는 vscode://file/… 편집기 링크로 건다.
이전판은 v0.5 (2026-08-26 · 연산자 여섯 · 실측 111건) 이다.
본판 합본 method 3 + 1 + 2 + 5 + 4 + 7 + 8 + 6 이 한 문서를 어떻게 통과시키는지,
분기·되돌림·되부름까지 폈다. 왼쪽 여백은 그 단계가 거는 게이트와 되돌림 단위,
오른쪽 여백은 반복 상한이다.
betterOf 비교 자체를 건너뛴다.
회귀 가드는 공짜가 아니라 필요할 때만 치르는 비용이다(§6).freezeRelations 가 한 번 정하고 굳힌다. 옮기는 도중에 다시 재면 소속이 바뀌어
결과가 처리 순서에 의존한다. 이 층이 겹침 후보 넷을 가른다 — 움직이는 단위가 무엇이냐가 곧 무엇을 풀 수 있냐다.
위상까지 탐색 변수로 열면 배치는 조합 최적화가 되어 다항시간에 못 푼다. 굳히면 남는 것은 1차원 좌표 문제이고, 그건 닫힌 해가 있다. 음수 갭이 곧 겹침이라 「겹친 것을 떼어 놓기」는 「갭 하한을 0 으로 두고 다시 나누기」와 같은 말이다.
| 부품 | 무엇 | 비용 | 파일 |
|---|---|---|---|
| 여백 대수 | 갭을 델타 균등 + 하한 water-filling 으로 다시 나눈다 | O(g²) 최악 | gapAlgebra.ts |
| PlaceRow (Abacus) | 겹치지 않으면서 가장 덜 움직이는 배치 — 정렬 후 1패스 클러스터 스윕 | O(k log k) | placeRow.ts |
| 실현가능성 | 못 담으면 「몇 px 모자란가」를 남기고 던진다 | O(1) | feasibility.ts |
| 유휴 여백 지도 v0.6 신규 | 장애물 · 4방향 여유 · 세로 코리도 — 격자가 아니라 상자 스윕 | O(L) | idleSpace.ts |
| 겹침 지도 | 같은 그룹 안 leaf 쌍을 전부 견준다 — 지표·게이트의 심장 | O(L²) | metrics.ts |
| 게이트 | 무엇을 묻나 | 누가 쓰나 |
|---|---|---|
| ① 없던 쌍 | 자기 입력에 없던 겹침 쌍을 만들었나 | 1·2·3·4·5·7·8 |
| ② 목적 달성 | 원안 대비 신규 겹침(지표 ⑤-b)이 실제로 줄었나 | 4·5·7·8 |
| ③ 넘침 증가 | 넘침이 늘지 않았나 | 4·5·6·7·8 |
②가 겹침 후보에만 있는 이유 — 자리를 다시 나누는 연산은 한 쌍을 떼어놓느라 원래 있던 쌍을 키울 수 있는데 ①은 그것을 못 잡는다(쌍 자체는 있던 것이라서).
| 라벨 | 조건 |
|---|---|
| FAIL | ① 넘침이 원안보다 나쁘다(노드 수 ↑ 또는 면적이 OVERFLOW_AREA_TOL = 10% 넘게 ↑) · ⑤ 신규 겹침이 있다 · ② 폰트가 EXCESSIVE_FONT_SCALE = 0.7 미만 — 셋 중 하나라도 |
| SUCCESS | FAIL 아님 + 폰트 배율이 원안 그대로(=1) + ④ 유휴 여백이 원안 이하 |
| ACCEPTABLE | 그 사이 |
글자를 한 톨이라도 줄이면 SUCCESS 가 영영 불가능하다. 그래서 method 6 은 맨 뒤이고, 앞이 이미 판정을 넘긴 케이스에는 아예 발동하지 않는다.
| # | 손대는 것 | 층 | 축 | 크기 | 트리거 | 예측 |
|---|---|---|---|---|---|---|
| 1 | 세로 유휴 여백 재분배 | 노드·체인 | 세로 | 세로만 키움 | 넘침 | 0 |
| 2 | 가로 확장 | 노드 | 가로 | 넓힘 | 넘침·좁은 칼럼 | 줄바꿈 |
| 3 | 가로 넘침 해소 | 노드 | 가로 | 키우거나 좁힘 | 가로 넘침 | 줄 수 |
| 4 | 겹침 — 띠 쪼개기 + 대칭 절반 밀기 | 유닛 사이 | 세로 | 고정 | 신규 겹침 | 0 |
| 5 | 겹침 — 도형 안에서 열거나 재쌓기 | 유닛 안 | 가로·세로 | 바꿈 | 신규 겹침 | 일부 |
| 6 | 가독성 하한 폰트 축소 | 노드 | — | 글자 | 판정 미달 | 0 |
| 7 신규 | 겹침 — 코리도 재배치 + 비대칭 짝 밀기 | 유닛 사이 | 세로 | 고정 | 신규 겹침 ∧ 유휴 ≥ τ | 0 |
| 8 신규 | 겹침 — 빈 자리로 탈출 | 유닛 사이 | 가로 | 고정 | 신규 겹침 ∧ 유휴 ≥ τ | 0 |
겹침을 푸는 후보가 넷이고, 넷을 가르는 것은 두 축이다 — 층(유닛 안 / 유닛 사이)과 이동축(세로 / 가로). 4·7 이 같은 칸에 앉는 것이 §5 에서 따질 중복이다.
두 후보는 penetrationOf(a, b) 로 겹친 쌍의 세로 침투 iy · 가로 침투 ix ·
열 포함 여부를 재고, 그 결과로 서로 배타적인 조건을 건다.
한 쌍은 반드시 정확히 한쪽에만 간다 — 둘 다 처리하는 쌍도, 아무도 안 맡는 쌍도 없다.
P = (iy > ix ∧ ¬columnContained) 라 할 때
method 8 은 P, method 7 은 ¬P 를 맡는다.
그래서 합본에서 둘 다 돌아도 한 쌍을 두 번 건드리지 않는다.
각자 못 푼 것은 리포트로 세어 상대에게 넘긴다 — method 7 의 horizontalPairs,
method 8 의 verticalPairs 가 그 계량기다.method 8 은 method 7 이 이미 옮긴 뒤의 문서에서 쌍을 다시 분류한다.
method 7 이 다른 유닛을 세로로 옮기면 그 주변 쌍의 iy·ix 가 바뀌므로,
7 이전에 「가로」였던 쌍이 8 시점에는 아예 안 겹칠 수도, 반대로 새 쌍이 생길 수도 있다.
분업은 매 시점 다시 계산되는 분업이지 한 번 정해 둔 명단이 아니다.
(새로 태어난 쌍은 게이트 ①이 잡아 되돌린다.)
겹침 후보 넷 중 4 와 7 은 같은 칸에 앉아 있다(§3) — 같은 층(유닛 사이),
같은 축(세로), 같은 게이트 셋(①②③), 같은 MOVE_EPS·OVERLAP_EPS.
각각의 「짝 밀기」를 나란히 놓으면 관계가 분명해진다.
method 4 — 2단계 pushApart | method 7 — 2-b 짝 나누기 | |
|---|---|---|
| 배분 | 최소 침투 깊이의 절반씩 서로 반대로 | 비대칭 — 위 유닛은 위 여유만큼, 아래 유닛은 아래 여유만큼 |
| 여유를 보나 | 안 본다 — 밀고 나서 게이트가 잡는다 | directionalSlack(box).up / .down 을 먼저 잰다 |
| 부분 이동 | 없다 — 되면 하고 아니면 되돌린다 | 있다 — 여유가 침투보다 작으면 되는 데까지 |
| 대상 수 | 가장 깊은 한 쌍씩 | 클러스터 안의 쌍을 깊은 것부터 |
| 상한 | MAX_PUSHES = 20 | MAX_NUDGES = 20 |
| 트리거 | 신규 겹침만 | 신규 겹침 ∧ 유휴 ≥ τ |
위·아래 여유가 같을 때 비대칭 배분은 정확히 절반씩으로 떨어진다 — 즉 method 4 의 2단계는 method 7 2-b 의 특수한 경우다. 둘을 갈라 두는 유일한 이유는 τ 트리거다: method 7 은 유휴 여백이 0.5 미만인 빽빽한 페이지에서 아예 안 돈다.
단순화 제안 — 짝 밀기를 한 벌로 합치고 τ 를 그 단계의 파라미터로 내리면 (빽빽한 페이지에서는 여유가 작아 자연히 부분 이동조차 못 하므로) method 4 는 1단계 띠 쪼개기만 남고 세로 분리는 전부 method 7 이 맡는다. 두 벌의 상한·엡실론·게이트 배선이 한 벌이 된다.
penetrationOf 가 두 벌method7.ts 와 method8.ts 에 글자 하나 다르지 않게 복제돼 있고,
COLUMN_EPS = 1 도 각자 선언한다.
위험 — §4 의 분업은 두 판별식이 같다는 전제 위에 서 있다. 한쪽만 고치면 아무도 안 맡는 쌍이나 둘 다 손대는 쌍이 조용히 생긴다.
고칠 자리 — relayout-core 로 올린다. 쌍을 다루는 모듈(pairing.ts)이 이미 있다.
OVERLAP_EPS 가 두 뜻metrics.ts 의 것은 export 된 100 — 「이보다 작은 접촉은 겹침으로 안 센다」.
method 4·7·8 의 것은 로컬 1 — 「원안보다 늘었나」를 물을 때의 잡음 하한.
같은 이름 · 다른 뜻 · 100배 차이다. 한 파일에서 둘을 같이 읽을 일이 없어 지금은 안 터지지만, 부품을 코어로 올리는 순간 부딪힌다.
고칠 자리 — 뜻이 이름에 드러나게 OVERLAP_MIN_AREA / OVERLAP_GROWTH_EPS 처럼 가른다.
후보 쪽 둘(method 4·7) 말고 정답 제작 경로용이 하나 더 있다 —
relayout-core/separateOverlaps.ts(2026-08-28 신규).
「열을 아래로 흘리기」만 하는 최소판이고, 합치지 않은 이유를 스스로 적어 두었다:
후보는 자기 입력에 견주고 정답은 원안에 견주므로 기준이 다르다.
그 파일이 태어난 사연이 이 절의 논지를 그대로 뒷받침한다 — 원래 스크립트 안의 지역 함수였는데
사본을 뜨면 정책이 갈라진다는 이유로 코어에 올렸다. 주석이 든 전례가
「내접 판정이 세 군데로 갈려 원형 컨테이너 위반을 놓친 사고」다.
penetrationOf 두 벌이 바로 그 다음 후보다.
MOVE_EPS = 0.01 이 method 1·4·5·7 네 벌. 값이 같다는 사실 자체가 계약인데
그 계약을 적어 둔 자리가 없다.
unitComponents(연결 성분)는 topology.ts 에 같은 꼴이 있지만
module-private 이라 method 7 이 다시 짰다 — 주석이 그 사정을 적어 두고 있다.
내보내기만 하면 한 벌이 된다.
method 6 은 배율을 한 칸 줄일 때마다 앞의 다섯을 되부른다(options.relayout).
그런데 본판 합본이 넘기는 되부름 함수는 3+1+2+5+4 뿐이다 — 7·8 이 빠져 있다.
의도된 것일 수 있다 — 되부름을 한 겹으로 묶어 「배율 위에 배율」을 막는 규율이 있고,
7·8 을 넣으면 되부름 비용이 다시 배가 된다. 다만 결과적으로 축소된 판에서는 스프레드가 최신이 아니다.
betterOf 가 그 손해를 사후에 걸러내므로 안전하지만 최적은 아닌 구조다 — 적어 둘 값어치가 있는 트레이드다.
기호 — N = 전체 노드 수, L = leaf 노드 수, U = 유닛 수, P = 겹친 쌍 수, k = 한 행/클러스터의 셀 수, g = 한 체인의 갭 수, M = method 6 의 배율 칸 수(≤ 10).
코퍼스 1,177건을 직접 세면 leaf 노드 중앙값 12 · p90 26 · 최대 60이다.
그래서 O(L²) 는 최대 3,600 쌍 비교, 중앙값 144 — 사실상 상수다.
비용을 만드는 것은 지수가 아니라 그 O(L²)를 몇 번 부르느냐다.
| 단계 | 한 번의 비용 | 반복 | 합계 |
|---|---|---|---|
freezeRelations | O(L²) | 1 | O(L²) |
| method 3 | O(L) | 1 | O(L) |
| method 1 ↔ 2 | O(L + g²) | ≤ 3 라운드 | O(L + g²) |
| method 5 | O(L²) | 유닛당 | O(U·L²) 최악 |
| method 4 | O(L²) | ≤ 20 | 20·O(L²) |
| method 7 | O(L²) + O(k log k) | ≤ 20 | 20·O(L²) |
| method 8 | O(L²) + O(P·L) | ≤ 20 | 20·O(L²) |
| method 6 | 앞의 다섯 전체 + O(L²) | ≤ M ≈ 10 | M · (전체 체인) |
betterOf | 2 × O(L²) | 1 | O(L²) |
지배 요인은 문서 복제다. 모든 후보가 structuredClone(input) 으로 시작하고
(run(doc): Doc 계약이 「입력을 건드리지 않는다」이므로), 되풀이마다 trial 을 한 벌 더 뜬다.
실측으로 doc 한 벌이 JSON 기준 중앙값 39.1KB · p90 83.2KB · 최대 174.1KB 다
(객체 그래프는 그보다 몇 배).
| 무엇 | 공간 | 실측 크기 | 수명 |
|---|---|---|---|
문서 사본 structuredClone | O(N) | 39.1KB (중앙값) | 단계마다 1벌 + 시도마다 trial 1벌 |
absBoxMap | O(N) | 노드당 상자 4수 | 계산 1회분 |
overlapAreaMap | O(P) | 쌍 중앙값 2 · 최대 28 | 겹친 쌍만 담는다 — L² 가 아니다 |
freezeRelations 그래프 | O(N) | 유닛·띠·체인·프레임 | 후보 1회 실행 내내 |
| tight box map | O(텍스트 수) | 1.1KB (중앙값) | 케이스 적재 시부터 |
| outline (도형 실루엣) | O(도형 수) | 1.2KB · 최대 115KB | 모듈 적재 때 한 번 읽어 둔다 |
placeRow 정렬 사본 | O(k) | 셀 수십 | 한 클러스터 처리 중 |
idleSpace 장애물 배열 | O(L) | ≤ 60 | 질의 1회분 |
laid.doc · spread.doc · escaped.doc ·
shrunk.doc · plain.doc 을 동시에 들고 betterOf 로 들어간다 —
문서 대여섯 벌 + 지표 두 벌이 한 시점에 살아 있다.
중앙값이면 ~0.2MB, 최대 케이스면 ~1MB. 절대량은 작다.trial 을 뜨고(각 ≤ 20),
method 6 은 배율 칸마다 체인을 통째로 다시 돌린다 — 한 케이스에 doc 사본 수십 벌이 났다 사라진다.
뷰어가 케이스 수십 건을 한꺼번에 갈아 고를 때 화면이 잠깐 멈추는 것이 이 지점이다.복잡도의 상한과 실제 비용은 다르다. 코퍼스 전건에 후보를 태워 각 후보의 리포트를 집계했다 — 발동(그 후보가 할 일을 찾음)과 실제 조치(문서를 실제로 바꿈)를 나눠 셌다.
| 후보 | 단독 발동 | 단독 조치 | 합본 발동 | 합본 조치 | 최악 복잡도 |
|---|---|---|---|---|---|
| method 3 | 21.2% | 17.6% | 19.8% | 17.6% | O(L) |
| method 1 ↔ 2 | 66.7% / 73.7% | 53.3% / 46.4% | 100% | 51.6% | O(L + g²) |
| method 5 | 20.4% | 13.2% | 16.0% | 8.9% | O(U·L²) |
| method 4 | 73.9% | 39.2% | 44.5% | 32.5% | 20·O(L²) |
| method 7 | 64.3% | 34.2% | 21.7% | 7.7% | 20·O(L²) |
| method 8 | 64.3% | 2.8% | 18.3% | 2.3% | 20·O(L²) |
| method 6 | 96.9% | 38.1% | 43.2% | 22.3% | M · 전체 체인 |
betterOf | — | — | 9.7% | 9.7% | +1 × method 6 |
단독 = 후보 하나를 rendered.json 에 바로 태웠을 때. 합본 = 본판
3+1+2+5+4+7+8+6 안에서 그 패스가 한 일. 원안이 있는 1,177건 전건, 예외 0건.
⚠️ 단독 method 2 는 세 번째 트리거(collided)를 못 받는다 — 그 값은 합본이 만들어 넘기므로
단독 수치가 그만큼 낮게 잡힌다.
method 5 는 최악 복잡도가 O(U·L²) 로 가장 나쁘지만, 합본에서 16.0%만 발동하고 8.9%만 조치한다 —
여덟 중 가장 드물게 일하는 축이다. 게다가 U ≤ L ≤ 60 이라 최악값도 21만 연산 남짓이다.
실제 비용을 지배하는 것은 method 6 이다 — 43.2%에서 발동하고, 발동하면 배율 칸마다
앞의 다섯을 통째로 되부른다. 여기에 betterOf 가 9.7%에서 그 method 6 을 한 벌 더 돌린다.
「복잡도 × 빈도」로 읽으면 순위가 뒤집힌다.
§4 에서 코드로 보인 배타적 분업이 숫자로도 나온다. 각 후보는 자기 몫이 아닌 쌍을 세어서 넘긴다 — 그 계량기를 집계한 값이다.
| 넘기는 쪽 | 넘기는 것 | 케이스 | 비율 |
|---|---|---|---|
| method 4 | 유닛 안 겹침 → method 5 의 몫 | 253 | 21.5% |
| method 4 | 가로 침투 → method 8 의 몫 | 157 | 13.3% |
| method 7 | 가로 침투 → method 8 의 몫 | 93 | 7.9% |
| method 7 | 유닛 안 겹침 → method 5 의 몫 | 174 | 14.8% |
| method 8 | 세로로 풀 쌍 → method 4·7 의 몫 | 572 | 48.6% |
단독 실행에서 7·8 이 실제로 옮긴 케이스를 갈라 보면 — 7만 옮김 394건 · 8만 옮김 25건 · 둘 다 옮김 8건 · 아무도 안 옮김 750건. 「둘 다」 8건은 모순이 아니다 — 한 문서 안의 서로 다른 쌍을 각자 맡은 것이다. 분업은 케이스 단위가 아니라 쌍 단위다.
method 8 이 실제로 맡는 쌍은 중앙값 1쌍 · 최대 10쌍(75건에서만 대상이 생긴다). 가로 침투가 진짜인 겹침은 드물고, 그래서 method 8 의 조치율이 2.3%로 가장 낮다 — §5 에서 「7 이 4 를 포함한다」고 본 것과 달리, 8 은 대체 불가능하되 사정권이 좁은 특수 연산자다.
betterOf 와 후보 선택기는 다른 것이다저장소를 훑어도 선택기 구현이 없다(candidateSelector·selectBest 류 0건).
설계안 C 는 설계 문서(2026-08-27)로만 있고,
거기 적힌 채택 게이트가 「A·B 실측 이후」다.
구현된 것은 betterOf 뿐이고, 그것은 선택기가 아니라 회귀 가드다 —
method 7·8 전용도 아니고, 전 후보 대상도 아니다.
betterOf — 구현됨 | 결정론적 후보 선택기 — 설계만 | |
|---|---|---|
| 무엇을 고르나 | 두 갈래 — 스프레드 있음 / 없음 | 후보 전부 — pool = METHODS (단독 8 + 합본 8) |
| 어디서 도나 | 스프레드 합본 3종에서만 — …+7+6 · …+8+6 · …+7+8+6 | 후보 하나(key: 'select')로 등록해 드롭박스에 오르는 구조 |
| 비교 항목 | 6개 — ⑤-b 쌍·면적 → ① 노드·면적 → ② 폰트 → ③ 정렬선 | 7개 — 판정 라벨 랭크로 시작, ④ 유휴 차이와 총 변위·등록 순서로 종결 |
| 동률 처리 | 기존 경로(스프레드 없는 쪽)를 남긴다 | 총 변위 → 후보 등록 순서로 동률을 없앤다 |
| 비용 | method 6 을 한 번 더 (≈ 2배) | 후보 N배 실행 |
| 목적 | 안 나빠지게 — 나비효과 방어 | 더 좋아지게 — 케이스마다 최선 후보 |
4→7 이, 케이스 Y 에는 7→4 가 낫다」는 고정 순서 합본으로 담을 수 없다.
그 조합 한계를 푸는 것이 선택기의 몫이고, 설계안은 구현 전에 베이스라인 JSON 으로 상한을 먼저 재는 공짜 분석을 채택 게이트로 걸어 두었다.정리하면 — 선택 층은 지금 「두 갈래 · 스프레드 합본 한정」까지만 와 있다. 전 후보를 대상으로 하는 층은 설계로만 존재하고, 그 설계에서 method 7·8 은 선택 대상의 일부일 뿐이다.
지표 ④ idleMarginRatio 는 「여백이 얼마나 남았나」만 답하는 페이지 전역 스칼라였고,
후보들이 쓰는 여유는 전부 국소적이었다 — neighbors.ts 는 좌·우 이웃의 한계선, gapAlgebra.ts 는 한 체인의 1차원 갭.
「어디에 남았나」를 답하는 자리가 없어서 겹침을 옮겨 풀 후보가 밀 자리를 찾지 못했다.
넘침은 조건이 아니다. 이것이 v0.5 와의 가장 큰 차이다 — 기존 후보들은 「텍스트가 넘쳤을 때」 발동했고, 그래서 넘침 없이 겹침만 남은 케이스에는 아무도 손대지 않았다. 발동에 「신규 겹침 존재」가 결합돼 있어 원안부터 여백이 큰 미니멀 디자인을 멀쩡한데 건드리는 일은 없다 — τ 는 예산의 하한이지 목표가 아니다.
v0.6 에서 가장 값진 발견은 새 연산자가 아니라 「자(판정 박스)의 불일치」였다.
후보 안쪽에서는 엔진이 잰 값을 못 얻으므로 frameBoxesAsTight 로 노드 박스를 그 자리에 끼워 쓴다.
그런데 성적표는 실측 tight 로 잰다 — 두 자가 다르면 후보가 유령 겹침을 쫓는다.
| 자 | 무엇 | 누가 보나 |
|---|---|---|
| frameBoxesAsTight | 노드 박스를 tight 자리에 끼운 프레임 대용. 오토핏 여백만큼 과대 보고하지만 모든 후보가 같은 기준을 본다 | 후보 안쪽(기본) |
| carryTightBoxes | 케이스의 rendered.json + 그 tight 를 승계해 시행 문서에 옮긴다 — 채점과 같은 자 | 7·8 (tight 를 받았을 때) · betterOf |
| visualBox.ts | 엔진이 잰 tight + 글머리 기호 몫 = visual bounding box | 성적표(채점) |
replaceLayout 이 케이스의 실측 tight 와 원안 tight 를 후보까지 넘기도록
(RelayoutRunContext.tight / baseTight) 배관을 깔자, method 7·8 의 트리거·게이트가
성적표와 같은 자를 보게 됐다. 이 배관이 회귀만 막은 게 아니다 —
기준 쪽 겹침 부풀림에 가려 안 보이던 신규 쌍들이 드러나 새로 풀린 케이스가 생겼다.
method 6(·4·5)은 여전히 프레임 대용 자로 발동을 판정한다.
§5 의 중복 정리와 함께 묶어 보면, 「자를 어디서 정할 것인가」가 아직 한 곳에 모여 있지 않다 —
tight 를 받는 후보와 안 받는 후보가 섞여 있고, 그 차이가 §5 ③ 의 되부름 비대칭과 같은 뿌리다.
아래 두 성적표는 각각 다른 시점·다른 건수이고, 지금 코퍼스는 그 어느 쪽보다도 크다 — 그대로 견주면 안 된다.
eval-out/cases/*/case.json 을 직접 세어 얻은 값이다. before(대치 직후)의 FAIL 사유는
신규 겹침 843 · 넘침 709 · 유휴 11 로 갈린다(한 케이스가 여러 사유를 가진다).
| 열 | 넘침 노드 | 넘침 면적 | 게이트 PASS | 폰트 배율 | 정렬선 |
|---|---|---|---|---|---|
original (기준) | 45 | 1,086,451 | 기준 | 1.000 | 1.000 |
before | 139 | 4,107,470 | 34/111 | 1.000 | 0.932 |
합본 3+1+2 | 14 | 534,454 | 43/111 | 1.000 | 0.895 |
합본 3+1+2+4 | 14 | 534,454 | 71/111 | 1.000 | 0.885 |
합본 3+1+2+5+4 | 14 | 534,454 | 83/111 | 1.000 | 0.864 |
합본 3+1+2+5+4+6 | 11 | 368,345 | 97/111 | 0.982 | 0.861 |
expected (목표선) | 39 | 699,729 | 60/90 | 0.993 | 0.849 |
읽는 법 — 넘침은 3+1+2 에서 87% 가 사라진다. 거기서 게이트가 43/111 밖에 안 되는 것은
넘침과 겹침이 다른 문제이기 때문이다. +4 가 유닛 사이를, +5+4 가 유닛 안까지 푼다 —
그 셋은 넘침이 한 자리도 안 다르다(「넘침이 늘면 되돌린다」가 게이트라서).
마지막 +6 만 글자를 줄이므로 넘침까지 지우지만, 대가로 SUCCESS 는 한 건도 안 는다.
| 후보 | S | A | F | ⑤-b 쌍 | 정렬선 |
|---|---|---|---|---|---|
현행 최선 3+1+2+5+4+6 | 50 | 29 | 17 | 59 | 0.8540 |
| + method 7 (A) | 54 | 29 | 13 | 46 | 0.8486 |
| + method 8 (B) | 51 | 29 | 16 | 55 | 0.8498 |
| + 7 + 8 (A+B) | 55 | 29 | 12 | 42 | 0.8444 |
FAIL 17 → 12(−29%) · 신규 겹침 59 → 42쌍(−29%) · 나빠진 케이스 0건. §4 의 분업이 실측으로 확인된다 — 5628689 는 B 만이, 5611182·5694141·5698092·6377108 은 A 가 풀었고, A+B 는 정확히 둘의 합집합이다. 겹치는 일을 했다면 합집합보다 작았을 것이다. 자세한 설계·평가는 유휴 여백 재배치 설계 에 있다.
아래 링크는 vscode://file/… 편집기 링크다 —
이 커밋(aee07d0b)이 원격에 없어 GitHub permalink 가 서지 않는다.
| 무엇 | 파일 | 여기서 확인한 것 |
|---|---|---|
| 레지스트리 · 진입점 | relayout-methods/index.ts:69 | 후보 16개 · replaceLayout 의 6개 인자 |
| method 4 | relayout-methods/method4.ts:152 | MAX_PUSHES = 20 · 2단계 대칭 절반 밀기 |
| method 5 | relayout-methods/method5.ts:150 | 유닛 안 · MOVE_EPS 사본 |
| method 6 | relayout-methods/method6.ts:317 | SHRINK_STEP = 0.05 · relayout(trial) 되부름 |
| method 7 | relayout-methods/method7.ts:227 | penetrationOf 원본 · MAX_NUDGES = 20 · 비대칭 짝 밀기 |
| method 8 | relayout-methods/method8.ts:202 | penetrationOf 사본 · MAX_MOVES = 20 |
| 회귀 가드 | relayout-methods/betterOf.ts:43 | 6항목 사전순 · 동률 → 기존 경로 |
| 본판 합본 | relayout-methods/method3Method1Method2Method5Method4Method7Method8Method6.ts:55 | 되부름에 7·8 이 빠진 자리(§5 ③) |
| 위상 | relayout-core/topology.ts:57 | 유닛 · 띠 · 체인 · 프레임 · PIN_WEIGHT = 100 |
| 유휴 여백 지도 | relayout-core/idleSpace.ts:57 | IDLE_SPREAD_TRIGGER = 0.5 · 세 질의 |
| 겹침·지표 | relayout-core/metrics.ts:95 | OVERLAP_EPS = 100 — 후보 로컬의 1 과 같은 이름 |
| 정확해 부품 | relayout-core/placeRow.ts:112 | 정렬 후 1패스 — O(k log k) |
| 여백 대수 | relayout-core/gapAlgebra.ts:85 | water-filling guard ≤ orig.length |
| 판정 | relayout-core/verdict.ts:108 | EXCESSIVE_FONT_SCALE = 0.7 · OVERFLOW_AREA_TOL = 0.1 |
| 쌍짓기 | relayout-core/pairing.ts:33 | penetrationOf 를 올릴 자리 후보(§5 ②) |
코퍼스가 늘고 줄 때마다 깨질 뿐 후보에 대해 아무것도 말하지 않는다. 못박는 것은 앞 합본과의 차이(방향과 크기)와 남는 케이스의 목록이다.