post-replace-relayout · idle-area 설계

유휴 여백 재배치 설계

대치 후 재배치가 남기는 FAIL을 줄이기 위해, 「텍스트 오버플로우가 있을 때만」이던 발동 조건을 유휴 여백 기준으로 일반화하고, 페이지의 빈 공간을 이동 예산으로 쓰는 후보 설계.

2026-08-27 · feat/post-replace-relayout-idle-area · 실측 = relayout-metrics 98건(원안 있는 케이스)

before (대치 직후)
F 87 / S 0 · A 11
현행 최선 합본 3+1+2+5+4+6
F 18 / S 51 · A 29
잔존 FAIL의 게이트
겹침 15 · 넘침 7 (중복 4)
18건 중 정답도 못 넘긴 것
13 UNSOLVABLE

코퍼스는 살아 있다(오늘 2건 제외돼 96건으로 이동 중) — 절대값은 낡으니, 구현 후 평가는 같은 시점의 코퍼스에서 베이스라인과 함께 다시 잰다.

§1무엇을 고치려는 판인가

현행 최선 합본은 넘침의 87%를 지우고 판정을 S51/A29/F18까지 끌어올린다. 남은 것을 갈라 보면 판이 세 덩어리다.

덩어리규모실체이 설계의 목표
이길 수 있는 FAIL4~5건정답(expected)이 SUCCESS·ACCEPTABLE에 도달한 케이스 — 5933332(카드 넘침), 6690122, 5647291, 6780937라벨 뒤집기 — A·B의 1차 표적
정답도 못 푼 FAIL13건expected 제작 과정도 신규 겹침 게이트를 못 넘겨 UNSOLVABLE로 남긴 케이스라벨 대신 ⑤-b 쌍·면적 감소 — 라벨이 그대로여도 눈에 보이는 겹침이 줄면 서비스 품질은 좋아진다
ACCEPTABLE 29건29건전원 단일 사유 「유휴 여백 > 원안」 — 대치로 글이 짧아져 글리프 면적이 준 것이번 라운드 목표가 아니다 — 평행이동은 점유율을 못 바꾸고(§3), 사람이 만든 정답도 18건이 같은 사유로 ACCEPTABLE에 머문다

§2남는 원인 — 실제 케이스 세 장

잔존 FAIL의 공통 구조: 결함(겹침)은 있는데 그 주변에 빈 공간이 있고, 알고리즘은 그 빈 공간을 모른다. 잔존 18건 중 15건이 유휴 여백 ≥ 0.5인 페이지다.

← 페이지 왼쪽 절반이 비어 있다 (유휴 여백 0.752) 신규 겹침 18쌍
5546858 — 대치로 길어진 글이 좁은 칼럼(폭 228px)을 타고 427~855px 높이로 흘러내려 아래 텍스트들을 덮었다. 세로로 벌리자니 띠가 통째로 묶여 있고(pushApart는 대칭 절반씩 한 쌍만), 옆의 빈 공간은 §4.1(가로 밀기 금지)에 막혀 아무도 못 쓴다. 정답 제작도 이 케이스는 못 넘겼다(UNSOLVABLE) — 목표는 쌍 수 감소다.
한 열에 쌓인 스택 3개 — 신규 겹침 3쌍 유휴 여백 0.962 — 페이지가 거의 비었다
5647291 — 폭 72px짜리 좁은 칼럼 텍스트 셋이 같은 x에 쌓여 겹쳤다. 「최소 침투축이 가로」로 읽혀 method 4가 세로 밀기를 거부하는 분류 오류(§9 ⓒ1)의 전형 — 가로 침투가 상자의 전체 폭이면 가로는 분리축이 아니다. 유휴 여백이 96%인데도 알고리즘은 이 빈 공간을 쓸 방법이 없다.
텍스트 4개가 카드 위변을 13~27px 넘었다 (넘침 4노드)
5933332 — 겹침이 아니라 넘침만 남은 3건 중 하나. 카드끼리의 간격과 패널 아래 여백은 남아 있는데 hug·재분배가 게이트·정렬 계획에 막혀 다 못 썼다. 사람이 만든 정답은 ACCEPTABLE에 도달한다 — 이길 수 있는 판이고, 평가 단계에서 원인을 케이스 단위로 판다.
텍스트 (판정 박스) 배경 도형 원안에 없던 겹침 — 실선·점선 좌표는 rendered.json 실측

구조적 원인 네 가지

#원인코드의 자리
1발동이 결함(넘침) 중심 — 넘침이 없으면 method 1·3은 no-op. 겹침 후보(4·5)는 돌지만 공간 지식이 없다method1.ts 조기 반환 · method3.ts site 조건
2여백 지식이 전역 스칼라 하나 — 「얼마나 비었나」(④)만 있고 「어디가 비었나」가 없다metrics.ts idleMarginRatio · architecture.md §9.5
3짝 밀기가 대칭·단발 — 절반씩만 밀고, 첫 게이트 실패에서 문서 전체를 포기method4.ts pushApart
4가로가 전면 금지 — 「이웃을 민다」와 「빈 공간으로 간다」를 구분하지 못한다§4.1 규칙

§3데이터가 설계를 바꾼 세 가지

① 「폰트 복원」 후보는 기각했다. 초기 후보 1순위는 「오토핏으로 줄어든 폰트를 유휴 여백으로 복원」이었다. 실측 결과 현 코퍼스의 rendered.json은 98건 전부 폰트가 원안과 동일하다(렌더본을 오토핏 끄고 굽는 현행 방식). 복원할 것이 없다. case.jsonproblems: shrink 79건은 옛 굽기 기준의 낡은 라벨이다 — scripts/label-test-cases.ts 재실행을 별도 정리 작업으로 권고한다.

② FAIL 18건 중 13건은 정답도 못 넘겼다(UNSOLVABLE). 라벨 개선의 천장은 4~5건이다. 그래서 A·B의 주 지표를 라벨이 아니라 ⑤-b 신규 겹침 쌍·면적 감소로 잡는다 — 못박는 것은 앞 합본과의 차이와 잔존 케이스 목록의 변화다(relayout_logics.md §10의 원칙 그대로).

③ ACCEPTABLE 29건(유휴 > 원안)은 이동으로 물리적으로 안 바뀐다. 점유율은 leaf 박스들의 union인데 평행이동은 union 면적을 바꾸지 못한다. 잃어버린 글리프 면적을 채우려면 무언가를 키워야 하고(컨테이너 확대 = 사실상 리디자인), 사람이 만든 정답조차 18건이 같은 사유로 ACCEPTABLE에 머문다. 이 덩어리는 명시적으로 이번 범위 밖에 둔다.

§4후보 세 개 — A·B는 구현, C는 설계

A · METHOD 7이번에 구현

세로 스프레드

겹침 클러스터를 세로 빈 구간(코리도) 안에서 placeRow 정확해로 다시 쌓는다. pushApart의 일반화 — 비대칭·N유닛·클러스터 단위 되돌림.

  • 발동: 신규 겹침 존재 ∧ 유휴 ≥ τ(0.5) — 오버플로우 불필요
  • : 예측 0 · 크기 불변 · 정렬 대가 없음 · 기존 부품 재사용
  • : 세로 한정 — 코리도보다 큰 클러스터는 실현불가로 남김
B · IDLESPACE + METHOD 8이번에 구현

유휴 여백 지도 · 가로 탈출

「어디가 비었나」를 답하는 공용 모듈(4방향 slack, A와 공용) + 가로로 파고든 겹침을 빈 공간으로만 옮겨 푸는 후보.

  • 발동: 가로 침투가 진짜인 쌍(ⓒ2) ∧ 유휴 ≥ τ
  • : §4.1을 고치지 않고 지킴(이웃 아닌 빈 자리로) · 이중 방어(지도+게이트)
  • : 좌변 정렬(③) 대가 가능 — 최소 변위 우선으로 상쇄
C · SELECT설계만 · A·B 이후

결정론적 후보 선택기

후보 전부를 돌리고 사전순 규칙(라벨 → ⑤-b → ① → ② → ④ → ③ → 변위)으로 케이스별 최선을 고른다. 학습·가중치 없음.

  • : 고정 순서 합본의 조합 한계 해소 · 구현 0에 가까움(기존 함수 조합)
  • : 실행 N배 · 생성력이 아니라 선택력
  • 채택 게이트: 베이스라인 JSON으로 상한 선계산 후 결정

합본에서의 자리는 셋이 같은 그림을 본다:

method 3 → 1 ↔ 2 → 5 → 4  (현행: 크기 → 유닛 안 → 유닛 사이)
                → 7 세로 스프레드   ← A. 4가 남긴 겹침을 코리도로
                → 8 가로 탈출       ← B. 7이 넘긴 가로 침투 쌍을 빈 공간으로
                → 6 폰트 축소       (마지막 수단, 그대로)

§5트리거 베이스라인 τ — 기본 0.5, 조정 전제

§6측정 계획과 성공 기준

구현과 평가 결과는 아래 §7이다(설계 당시 계획 그대로 덧붙였다).

§7구현 결과 (2026-08-27 · 96건)

결론 — A·B를 구현해 붙인 합본이 FAIL 17 → 12(−29%), 신규 겹침 59 → 42쌍(−29%)을 만들었고, 나빠진 케이스는 0건이다. 라벨을 딴 5건 중 5건 모두가 「정답 제작 과정도 게이트를 못 넘겨 UNSOLVABLE로 남았던」 케이스다 — §1에서 4~5건으로 잡았던 천장이 실제로는 더 물렀다.

코퍼스가 평가 중 98 → 96건으로 움직여(케이스 2건 제외) 모든 수치를 96건에서 다시 쟀다 — 베이스라인(현행 최선 합본)도 같은 판에서 S50/A29/F17이다.

후보SAF⑤-b 쌍⑤-b 면적정렬선 평균
before (대치 직후)011851651,509,7160.9276
현행 최선 3+1+2+5+4+650291759665,5210.8540
+ method 7 (A)54291346605,8790.8486
+ method 8 (B)51291655652,9280.8498
+ 7 + 8 (A+B)55291242593,2860.8444
expected (목표선)22185U 51652,9060.8228

구현이 설계에서 배운 것 — 회귀 한 건이 가르친 두 가지

첫 평가에서 5629794가 SUCCESS→FAIL로 떨어졌다. 원인은 자(판정 박스)의 불일치였다: 후보 안의 게이트는 프레임 대용 상자로, 성적표는 실측 tight(carryTightBoxes 승계)로 재서, 유령 겹침을 쫓은 이동이 바깥 채점에서 신규 겹침으로 태어났다. 고친 것 둘 —

잔존 FAIL 12건 — 왜 남았고 무엇이 다음인가

원인케이스다음 수
세로 여유 자체가 없다 — 코리도 실현불가 + 짝 나누기 여유 05546858(13쌍, 855px 텍스트 열) · 5638155 · 5647291(스택 764px vs 코리도 590px) · 6672017 · 6688247 · 6805703폭 열기(method 2·3)와의 결합 — 좁은 칼럼을 넓혀 스택 높이를 줄인 뒤 스프레드. 5647291이 교과서 케이스
넘침만 남았다 — 겹침이 없어 7·8의 발동 조건 밖5933332 · 6690122 · 5108314(+1쌍)expected는 SUCCESS·ACCEPTABLE에 도달 — method 1의 hug 계획/게이트를 케이스 단위로 파야 한다(이동이 아니라 크기의 문제)
빽빽한 페이지 — 유휴 0.36으로 τ 미만 + 가로 slack 06577997(가로 8쌍 · 그릇 4쌍)유휴 예산이 없는 판 — 스프레드 계열의 사정권 밖이 맞다(정답도 UNSOLVABLE)
τ 경계 — 유휴 0.46으로 0.5에 걸림6780937(세로 1쌍)τ=0.4 검토 대상(스윕상 부작용 없음)
가로 slack 부족6478618(가로 3쌍)비대칭·연쇄 가로 이동(placeRow의 가로판) — B의 2단계 확장
같은 유닛 안(method 5의 몫) · 그릇이 낀 쌍6805703(같은 유닛 2쌍) · 6577997(그릇 4쌍)method 5 강화 / 그릇 이동은 위상 전제를 바꾸는 별건

딸려 나온 정리 거리