2026-09-13 원본에서 평가 계약·구현·코퍼스가 달라진 것을 확인해 당시 기록으로 보관했습니다. 본문과 자동 동기화 표는 보관 당시 내용입니다.
현재 기준과 원본 보기 → · 보관 목록작성 2026-08-28 · 브랜치 feat/post-replace-relayout-2 ·
커밋 2aed14ada · 코퍼스가 1,177건이던 시점의 기록이다. 기술 내용과 수치는 당시 기준으로 보존하고, 문장은 2026-08-30에 다듬었다.
§5에서 다룬 중복 구현 중 일부는 이후 정리됐다.
짝 밀기를 method 7로 통합했으며(bad3810f2),
서로 다른 의미로 쓰이던 상수 이름을 구분했다(9a4f52fb2).
현재판은 재배치 알고리즘 파이프라인 v0.7 이다.
내용 대치가 끝난 컴포넌트의 넘침과 겹침을 줄이고, 남은 여백을 활용해 다시 배치한다. v0.6의 연산자는 8개이며, 새로 추가된 method 7·8은 페이지의 빈 공간을 활용해 요소를 이동한다. 이 문서는 실행 흐름과 각 연산자의 역할, 중복 구현, 실행 비용, 후보 비교 방식을 정리한다.
penetrationOf 라는
같은 판별식을 사용한다. 동일한 좌표에서 판별하면 세로 처리 대상과 가로 처리 대상이 구분된다.
각 단계는 실행 시점의 좌표로 다시 판별한다. §4penetrationOf 가 두 파일에 동일하게 중복 구현돼 있고
(두 구현이 일치해야 처리 대상을 같은 기준으로 구분할 수 있다), OVERLAP_EPS 는 같은 이름으로 1 과 100 두 뜻을 쓴다. §5betterOf 는 9.7%. §6.3betterOf 이고,
이는 여백 활용 단계를 포함한 합성 후보 3종에서 두 실행 경로를 비교한다. 설계안 C 는 후보 전부가 대상이라 범위가 다르다. §7브랜치 feat/post-replace-relayout-2 · 커밋 2aed14ada 의
당시 작업트리를 기준으로 작성했다(작성 시점에는 인용 파일의 커밋과 작업트리가 일치했다).
실측은 aee07d0b 에서 돌렸고, 이후 커밋이 추가됐지만
인용한 후보 파일은 문서 작성 시점까지 변경되지 않았다 — 바뀐 것은
metrics.ts(+59줄) · relayout-core/index.ts · 문서, 그리고 새 파일
separateOverlaps.ts 다(§5 에 반영). 인용한 15개 줄 번호는
당시 HEAD에서 다시 확인했다.
인용 커밋은 당시 원격에 없었다. 따라서 기존 코드 링크는 당시 작업트리의 vscode://file/… 주소를 보존했다. 해당 작업트리가 없거나 파일이 바뀌면 링크와 줄 번호가 맞지 않을 수 있다. 최신 코드 링크는 v0.7을 참고한다.
이전판은 v0.5 (2026-08-26 · 연산자 여섯 · 실측 111건) 이다.
대표 합성 후보 method 3 + 1 + 2 + 5 + 4 + 7 + 8 + 6 이 한 문서를 어떻게 통과시키는지,
분기, 되돌림, 재호출 과정까지 보여 준다. 왼쪽에는 단계별 검사 조건과 되돌림 단위를,
오른쪽에는 반복 상한을 표시했다.
betterOf 비교 자체를 건너뛴다.
회귀 방지 비교는 7·8이 실제로 이동한 경우에만 추가 실행한다(§6).freezeRelations 가 배치 단위와 관계를 정한다. 옮기는 도중에 다시 재면 소속이 바뀌어
결과가 처리 순서에 의존한다. 이 층이 겹침 후보 넷을 가른다 — 움직이는 단위가 무엇이냐가 곧 무엇을 풀 수 있냐다.
배치 관계와 좌표를 동시에 탐색하면 고려할 조합이 많아진다. 이 구현은 관계를 먼저 정한 뒤, 순서와 경계가 고정된 1차원 배치 문제를 푼다. 음수 갭이 곧 겹침이라 「겹친 것을 떼어 놓기」는 「갭 하한을 0 으로 두고 다시 나누기」와 같은 말이다.
| 부품 | 무엇 | 비용 | 파일 |
|---|---|---|---|
| 여백 대수 | 갭을 델타 균등 + 하한 water-filling 으로 다시 나눈다 | O(g²) 최악 | gapAlgebra.ts |
| PlaceRow (Abacus) | 겹치지 않으면서 가장 덜 움직이는 배치 — 정렬 후 한 차례 클러스터 스윕 | 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 — 당시 데이터에서는 비교 규모가 크지 않았다.
단, 점근 복잡도가 상수가 되는 것은 아니다. 같은 비교를 반복하는 횟수도 함께 살펴봐야 한다.
| 단계 | 한 번의 비용 | 반복 | 합계 |
|---|---|---|---|
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 사본을 추가로 만든다.
실측으로 문서 한 개의 크기는 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 로 들어간다 —
문서 사본 5~6개와 두 결과의 지표이 한 시점에 살아 있다.
중앙값이면 ~0.2MB, 최대 케이스면 ~1MB. 절대량은 작다.trial 을 뜨고(각 ≤ 20),
method 6 은 배율 칸마다 체인을 통째로 다시 돌린다 — 한 케이스에 문서 사본 수십 개가 생성됐다가 해제된다.
여러 케이스를 연속 실행할 때의 화면 지연은 복제와 렌더링 등을 함께 측정해 확인해야 한다.복잡도의 상한과 실제 비용은 다르다. 코퍼스 전체에 후보를 실행해 각 후보의 리포트를 집계했다 — 발동(그 후보가 할 일을 찾음)과 실제 조치(문서를 실제로 바꿈)를 나눠 셌다.
| 후보 | 단독 발동 | 단독 조치 | 합성 후보 발동 | 합성 후보 조치 | 최악 복잡도 |
|---|---|---|---|---|---|
| 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 은 대체 불가능하되 제한된 유형의 겹침을 처리하는 연산자다.
저장소를 훑어도 선택기 구현이 없다(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/… 편집기 링크다 —
당시 실행 환경의 주소를 보존한 것으로, 최신 파일의 줄 번호를 가리키지는 않는다.
| 무엇 | 파일 | 여기서 확인한 것 |
|---|---|---|
| 레지스트리 · 진입점 | 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 과 같은 이름 |
| 1차원 배치 계산 | relayout-core/placeRow.ts:112 | 정렬 후 한 차례 — 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 ②) |
코퍼스가 늘고 줄 때마다 깨질 뿐 후보에 대해 아무것도 말하지 않는다. 검증해야 할 것은 앞 합성 후보과의 차이(방향과 크기)와 남는 케이스의 목록이다.