Method 8 — 유휴 여백 지도와 가로 탈출
「어디가 비었나」를 답하는 공용 모듈(idleSpace)과, 그 지도를 근거로 겹침을 빈 공간으로 옮겨 푸는 재배치 후보
§1한 줄 요약
지표 ④ idleMarginRatio는 「여백이 얼마나 남았나」만 답하는 페이지 전역 스칼라다 — 「어디에 남았나」를 답하는 코드는 저장소에 없다(docs/architecture.md §9.5). 설계안 B는 그 공백을 메우는 두 조각이다: ① relayout-core/idleSpace.ts — 상자 하나의 4방향 여유(slack)와 세로 코리도를 재는 순수 조회 모듈(method 7과 공용), ② method8 — 그 지도를 근거로, 세로로는 못 푸는 겹침을 가로 빈 공간으로 옮겨 푸는 후보.
§2왜 필요한가 — 지표와 액추에이터의 해상도가 다르다
| 지금 있는 「여유」 지식 | 범위 | 못 답하는 것 |
|---|---|---|
metrics.ts idleMarginRatio | 페이지 전역 스칼라 1개 | 어디가 비었나 |
neighbors.ts limitsOf | 좌·우 이웃이 정하는 한계선 (가로 · 체인 안) | 위·아래, 이웃 너머, 체인 밖 |
gapAlgebra.ts chainBudget | 한 체인 [from,to]의 1차원 갭 합 | 체인에 안 묶인 방향의 여유 |
그 결과가 §4.1의 전면 금지다 — 「이웃을 옆으로 밀지 않는다」. 옳은 규칙이지만, 현행 구현은 「이웃을 민다」와 「빈 공간으로 간다」를 구분하지 못해 후자까지 막고 있다. 잔존 FAIL의 ⓒ2(가로로만 파고든 겹침 14쌍, §9 기준)가 정확히 그 자리다.
§3① relayout-core/idleSpace.ts — 공용 조회 모듈
type Slack = { left: number; right: number; up: number; down: number };
obstaclesFor(doc, abs, opts?: { excludeIds?: Set<string> }): AbsBox[]
directionalSlack(box: AbsBox, obstacles: AbsBox[], bounds: AbsBox): Slack
verticalCorridor(box: AbsBox, obstacles: AbsBox[], bounds: AbsBox): { lo: number; hi: number }
장애물 규칙 — 지표·위상과 같은 것을 본다
- leaf만 본다. 그룹은 자식이 움직이면 따라온다(④·⑤·
freezeRelations와 동일). - 장식(
DECORATION_TYPES) 제외 — 겹침 판정이 장식 쌍을 안 세므로, 장식 위로 지나가도 게이트에 안 걸린다. 장애물로 세면 풀 수 있는 것을 못 푼다. - 페이지 배경판(≥
PAGE_BACKDROP_RATIO) 제외 — ④·findTextContainer·위상과 동일 규칙. - 자기 유닛의 노드 제외 — 유닛은 통째로 움직이므로 자기 멤버는 장애물이 아니다.
- 나를 포함하는 상자(그릇·프레임)는 막는 벽이 아니라 반대편 안쪽 변이 한계 — 그 밖으로 나가면 포함 예외(
CONTAINED_RATIO)가 깨져 없던 겹침 쌍이 태어난다. - 이미 부분 겹침인 비포함 상자는 그 방향 slack 0 — 더 파고드는 이동은 애초에 후보가 아니다(분리 상대는 질의에서 제외하고 잰다).
bounds는 담는 프레임 박스, 없으면 페이지 — ⑦ 잘림(페이지 밖) 절대 금지 규칙이 여기서 지켜진다.
왜 비트맵이 아니라 스윕인가 — ④의 4px 격자 비트맵을 재활용하는 안도 검토했다. 격자는 「전체 점유율」에는 맞지만 방향 질의에는 O(격자) 스캔 + 셀 경계 오차가 붙는다. 장애물이 수십 개 수준인 문서에서는 상자 스윕이 더 단순하고 정확하다(limitsOf가 이미 같은 방식이고, 이 모듈은 그 4방향 일반화다).
§4② method8 — 가로 탈출
0. 동결 · 쌍 검출 method 4·7과 동일 (원안에 없던 겹침 → 유닛 쌍)
1. 대상 고르기 가로 침투가 진짜인 쌍만: ix < iy 이고 ix < min(두 상자 폭)
(세로로 풀리는 쌍은 method 4·7의 몫 — 축으로 분업한다)
2. 이동 후보 열거 쌍 {A,B}에 대해 4가지: A←, A→, B←, B→ (필요량 δ = ix + ε)
+ 양쪽 분담(A← δa, B→ δb, δa+δb = δ) — slack 비례 배분
3. 지도 필터 directionalSlack ≥ 필요량 인 후보만 남긴다 → 총 변위가 작은 순으로 정렬
4. 시도 · 게이트 후보를 앞에서부터 시도 — 게이트는 method 4와 같은 셋
(입력에 없던 쌍 0 · 원안 대비 신규 겹침 실제 감소 · 넘침 불증)
통과한 첫 후보로 확정, 전부 실패하면 그 쌍은 그대로 (쌍 단위 all-or-nothing)
5. 반복 남은 쌍에 대해 반복 (MAX_MOVES 안전핀, method 4의 MAX_PUSHES와 같은 급)
마무리 refitGroupBoxes
§4.1(이웃을 옆으로 밀지 않는다)과의 관계 — 규칙을 고치지 않고 지킨다
§4.1이 금지한 것은 이웃을 밀어내는 것이다. method 8은 이웃을 한 픽셀도 건드리지 않는다 — 겹침 당사자 유닛만, slack이 보장하는 빈 자리로 옮긴다. 이동 후보가 이웃과 부딪히면 지도 필터(3단계)가 먼저 거르고, 혹시 지도가 놓쳐도 게이트(4단계)가 되돌린다. 이중 방어다.
§5파라미터 · 리포트
| 이름 | 기본 | 뜻 |
|---|---|---|
idleThreshold (τ) | 0.5 | method 7과 같은 정책 스위치 — 빽빽한 페이지에서의 가로 대이동을 막는다 |
MAX_MOVES | 20 | 한 문서에서 옮겨 보는 최대 횟수(method 4의 MAX_PUSHES와 같은 급의 안전핀) |
EDGE_MARGIN | 0 | 페이지·프레임 경계에 붙이는 여유 — 0이면 경계까지 허용(⑦ 잘림은 게이트·경계가 이미 막는다) |
리포트(Method8Report): 대상 쌍 수, 옮긴 쌍 수(방향별), slack 부족으로 못 옮긴 쌍, 게이트로 되돌린 쌍, 남은 쌍.
§6대가와 한계 — 미리 적는다
- 정렬선(③) 대가 — 가로 이동은 좌변 정렬 그룹을 깰 수 있다. 게이트에는 없지만(라벨을 가르지 않는 지표) 총 변위 최소 후보부터 시도해 대가를 줄이고, 리포트에 이동량을 남겨 평가에서 ③ 변화를 함께 본다.
- 회전 요소 — 절대 박스는
absBoxMap(=preferRotated: false) 관례를 그대로 쓴다. 지표·게이트와 같은 상자를 봐야 개선이 숫자로 나온다. - 같은 유닛 안 겹침(ⓑ) — 옮길 대상이 하나라 사정권 밖(method 5의 몫).
- 그릇·배경판이 낀 쌍(ⓕ) — 그릇 자신은 옮기지 않는다(위상 전제). 사정권 밖으로 명시.
- slack이 사방 0인 쌍 — 빽빽한 문서(예: 6577997, idle 0.356)는 옮길 곳 자체가 없다. 그대로 남기고 리포트한다 — 이런 케이스가 곧 UNSOLVABLE의 실체다.
천장 — method 7과 같은 사정: 잔존 FAIL 18건 중 13건은 정답 제작도 못 넘긴 UNSOLVABLE이다. method 8의 주 지표도 라벨보다 ⑤-b 쌍·면적 감소다.
§6.5구현 노트 (as-built, 2026-08-27)
- 모듈은 설계대로 —
relayout-core/idleSpace.ts:obstaclesFor·directionalSlack·verticalCorridor(코리도 안에 낀 장애물은interior로 돌려주고, method 7이 핀 셀로 앉힌다). τ 상수IDLE_SPREAD_TRIGGER = 0.5도 여기 산다. - 분리축 판별은 유닛 박스로(method 7과 같은 이유·같은 판별식).
- 게이트가 넷이다 — method 7과 동일하게 「어떤 쌍도 입력보다 커지면 안 된다」 추가.
- 채점과 같은 자(judge) — 실측 tight(rendered + 배율 맞춘 원안)를 배관해 트리거·게이트가 성적표와 같은 판정 박스를 본다. 합본에는
betterOf회귀 가드(완성본끼리 비교, 같으면 기존 경로).
§7합본에서의 자리와 측정 계획
… method 5 → method 4 → method 7(세로 스프레드) → method 8(가로 탈출) → method 6
- method 7 뒤: 세로가 싸고 안전하다(정렬 대가 없음). 세로로 안 풀린 것만 가로를 치른다.
- 등록:
method8단독 +…+method7+method8+method6합본. A·B 분리 기여는…+method7+method6vs…+method8+method6vs 둘 다로 잰다. - 본 지표: ⑤-b 쌍·면적(주), 판정 분포, 정렬선 보존율(대가), 잔존 FAIL 목록 변화, slack-부족 리포트(다음 설계의 입력).