← 미리디 아카이브 / 병합 대치 정렬 문제

내용시각화 post-replace-relayout relayout-core/ 작성 2026-08-28

알고리즘 적용 결과 채점 로직

«예쁜가»를 사람 눈 대신 숫자로 판정한다. 무엇을 어떤 상자로 재고(§2), 그 상자로 무엇을 세며(§4), 그 숫자들이 어떤 게이트를 지나 한 낱말이 되는지(§6)를 코드에서 정리했다. tidyLayout 이 실패한 이유 중 하나가 판정 기준의 부재였다 — 알고리즘보다 지표가 먼저다.

miricanvas-iui · src/post-replace-relayout/relayout-core 브랜치 feat/post-replace-relayout 커밋 9f1dfef7 지표 7 · 라벨 5
§0

30초 요약

  • 채점 기준원안(original.json)이다 — before 도 정답도 아니다. 재배치가 되돌리려는 상태가 원안이라서, ①②③④⑤ 어느 것도 절대값을 보지 않는다.
  • 판정 박스visual bounding box 하나다 — 엔진이 잰 glyph extent(tight)에 글머리 기호가 쓰는 들여쓰기 띠를 더한 상자. 도형은 절대 박스.
  • 못 잰 노드프레임으로 대신 재지 않는다. 한 표 안에 두 기준이 섞이는 것이 과대 보고보다 나쁘다 — ④ 유휴 여백만 예외다(§8).
  • 지표 7넘침 · 폰트 배율 · 정렬선 보존율 · 유휴 여백 · 겹침 · 좁은 칼럼 · 잘림. ⑤ 만 점수가 아니라 게이트다.
  • 라벨 5FAIL 게이트 셋 → SUCCESS 눈금 둘 → 그 사이가 ACCEPTABLE. 못 잰 자리는 UNSOLVABLE(알아본 결과 답이 없다)과 UNKNOWN(아직 못 알아봤다)으로 가른다.
  • 한 함수judgeDocs 하나로 모았다 — 예전에는 네 걸음이 네 군데에 복사돼 있어 화면·터미널·코퍼스 정리가 서로 다른 답을 낼 수 있었다.

이 글의 근거

브랜치 feat/post-replace-relayout · 커밋 9f1dfef7작업트리이고, 이 커밋은 원격에 없다 — 코드 링크는 vscode://file/… 편집기 링크로 건다. 규칙 문서는 docs/evaluation-rules.md §2·§3 이 정본이고, 이 글은 그 규칙이 코드에서 어떻게 도는지를 흐름으로 다시 그린 것이다.

케이스를 만드는 쪽테스트 케이스 제작 로직 에 있다.

§1

채점 한 번 — 네 걸음

「원안 좌표계 맞추기 → 기준 지표 → 대상 지표 → 판정」 네 걸음은 순서와 인자가 정확히 맞아야 뜻이 있는데(특히 alignDocFrame 을 빠뜨리면 겹침 면적이 배율2로 부푼다), 그 네 걸음이 예전에는 네 군데에 복사돼 있었다 — 뷰어 · relayout-metrics.ts · relayout-method-run.ts · batch/pruneCases.ts. 한 곳이 어긋나면 화면과 터미널과 코퍼스 정리가 서로 다른 답을 내므로 judgeDocs 하나로 모았다.

judgeDocs(doc, tight, original, originalTight, {excessiveFontScale, outlines})

채점 대상 채점 기준 — 원안 doc — before · 후보 결과 · 정답 + 그 문서의 tight bounding box original.json — 파일 그대로 + tight/original.json — 파일 그대로 ① 좌표계·배율 맞추기 alignDocFrame(original, doc) scaleTightBoxes(tight, s) · s = 배율 비 baseDoc · baseTight ② 대상 지표 computeLayoutMetrics(doc, tight, baseDoc, baseTight, outlines) ② 기준 지표 computeLayoutMetrics(baseDoc, baseTight, undefined, undefined, outlines) 기준은 자기 자신과 견준다 — ②⑤ 가 만점으로 떨어지는 것이 옳다 ③ judgeMetrics(metrics, base, options) FAIL 게이트 셋 → SUCCESS 눈금 둘 → 그 사이 SUCCESS · ACCEPTABLE · FAIL · UNSOLVABLE · UNKNOWN
부르는 쪽이 원안을 미리 옮겨 넘기지 않는다 — 좌표계 맞추기도 배율 맞추기도 judgeDocs 가 한다. tight bounding box 는 프레임 원점 기준 길이라 문서를 늘린 만큼 같이 늘려야 하고 (scaleTightBoxes), 안 주면 기준 쪽 텍스트가 전부 「못 잼」이 되어 ⑤ 신규 겹침이 부풀어 보인다.

왜 원안이 기준인가

재배치가 되돌리려는 상태가 원안이기 때문이다 — 디자이너가 정한 글자 크기 · 정렬선 · 「의도된 겹침」이 거기 있다. 그래서 ⑤-b 는 「원안에 없던 겹침을 만들었나」가 되고 After 에서 0(PASS)이 좋다.

원안은 다른 좌표계에 있다

original.jsonrendered.json 은 같은 컴포넌트를 서로 다른 자리·배율로 앉힌 문서다. 페이지는 둘 다 1920×1080 이지만 컴포넌트를 앉힐 때 배율이 붙고, 그 배율이 props.text.fontSizeScale 이다(원안은 언제나 1, 렌더본은 커밋된 케이스에서 0.97~1.91).

지표배율에 어떻게 걸리나
② 최소 폰트 배율안 걸린다. 양쪽 다 자기 배율이 따로 붙으므로 fontSize 의 비가 곧 정답이다 (배율이 약분된다)
③ 정렬선 보존율「같은 선에 있던 이 아직 같은 선인가」라 닮음변환에 불변. 다만 GUIDE_TOL(4px)이 절대값이라 배율 1.9 에서 경계가 흔들린다
⑤ 신규 겹침정면으로 걸린다. 면적이 배율2로 커지므로(1.912 = 3.65) 원안에 있던 의도된 겹침이 전부 「새로 생긴 겹침」으로 잡힌다

그래서 alignDocFrame닮음변환 하나(t = b × s + (tx, ty))로 옮긴다. 이동량은 양쪽에 다 있는 노드의 차이의 중앙값이다 — 평균이 아닌 것은 대치가 실제로 옮긴 노드가 섞여 있기 때문이다(6447600 은 한 도형이 573px 옮겨져 있다).

답을 보고 푸는 것이 아니다

기준은 채점에만 들어온다. 후보의 입력은 여전히 rendered.json 하나이고, 원안을 봐야 정의되는 목표를 가진 후보(「원안에 없던 겹침」을 푸는 것들)에게만 좌표계를 맞춘 사본이 간다. 풀고 나서 답안지와 맞추는 것이지 답을 보고 푸는 것이 아니다.

§2

판정 박스 — 상자 셋 중 하나다

세 상자를 구별해야 나머지가 읽힌다. 고를 것은 없다 — 기준이 두 번 옮겨진 끝에 지금은 하나다.

이름무엇어디서 오나
rendered bounding box
(프레임)
문서에 박힌 data.props.boundingBox. 오토핏 여백을 안고 있다metrics.ts 의 absBoxMap
tight bounding box본문 글자의 glyph extent. 글머리 기호를 뺀다엔진 TextBoundingBoxCalculator
→ eval-out/cases/<id>/tight/
visual bounding box
판정 박스
tight + 글자에 딸린 장식(글머리 기호가 쓰는 들여쓰기 띠)visualBox.ts 의 visualBoxMap
— 구운 tight 와 문서에서 만든다

한 텍스트 노드 위의 세 상자

프레임 — 오토핏 여백 포함 글머리 기호가 쓰는 띠 visual bounding box = 판정 박스. ①④⑤ 가 본다 tight bounding box 엔진이 잰 glyph extent 기호를 뺀다 프레임 실측 217 × 46.7 vs tight 174.84 × 27.5
이 그림은 하우스 팔레트로 그렸다 — 뷰어 오버레이의 색은 다르다(파랑 = 컨테이너, 노랑 = tight, 초록 = visual). 초록과 노랑은 글머리 기호가 있는 텍스트에서만 갈라진다 — 그 폭이 곧 기호 몫이고, 기호가 없으면 두 상자가 같아서 선이 겹쳐 보인다(정상이다).

기준이 두 번 옮겨졌다

프레임 → tight

해결

예전에는 「tight bounding box 로 채점」 토글이 있었고 기본이 꺼짐이었다. 끄면 프레임으로 쟀는데, 프레임에는 오토핏 여백이 들어 있어 넘침·겹침을 과대 보고한다 (실측: 프레임 217×46.7 vs tight 174.84×27.5). 즉 화면의 기본 상태가 틀린 숫자를 보여 주고 있었다. 토글을 지우고 글자 쪽 상자를 필수로 만들었다.

tight → visual

해결

tight 는 반대 방향으로 틀렸다 — 글머리 기호를 뺀다. 엔진이 기호를 본문과 따로 그리기 때문이다(CanvasTextDrawer 의 「5. markers」). 커밋된 코퍼스에서 잰 것이 그 증거다.

ntight 의 left (프레임 원점 기준) 중앙값
글머리 기호 있는 텍스트11730.94px
없는 텍스트7893.78px

기호가 있으면 tight 가 기호를 지나친 자리에서 시작한다. 그 상자로 재면 「기호는 도형 밖으로 나가 있는데 넘침 0」이 된다. visual 로 옮기면서 코퍼스 117개 노드가 중앙값 30.94px(최소 16.72 · 최대 83.85) 넓어졌다.

visual 은 기호를 덮는 상자이지 기호의 glyph extent 가 아니다

들여쓰기 폭이 폰트마다 달라(tight.left / listStyle.fontSize0.70~2.10em) 이 층에서는 기호 글리프를 잴 수 없다. 그래서 기호가 쓰라고 비워 둔 띠 전체를 덮는다 — 기호를 자르지 않는 것이 목적이라 그 방향의 오차는 안전하다.

세 갈래를 가른다 — judgeBoxOf

judgeBoxOf(nodeId, box, tight) → 상자 // 글자가 칠해진 영역을 문서 좌표로 null // status:'empty' — 글자 없는 빈 텍스트. 넘침·겹침 판정에서 뺀다 undefined // 구운 값이 없거나 status:'failed' → 「못 잰 노드」(tightMissing)

프레임으로 떨어지는 길이 없다. tight선택이 아니라 필수 인자이고, 프레임으로 재야 하는 자리는 frameBoxesAsTight(doc)적어서 넘긴다. 그런 자리는 둘뿐이다 — 후보 안의 시행 문서(크롬을 못 부른다)와 합성 픽스처 테스트(손으로 만든 Doc).

§3

무엇을 어떤 박스로 재나

헷갈리기 쉬운 자리라 한 표에 모아 둔다. 오버레이와 지표는 늘 같은 박스를 본다 — 둘 다 judgeBoxOf 를 지난다. 그래서 파란 점선 밖으로 나간 초록 상자의 수가 곧 「넘침 노드 수」다.

어디판정 박스코드
뷰어 오버레이 · ① 넘침 · ④ 유휴 여백 · ⑤ 신규 겹침 — 텍스트visual bounding boxjudgeBoxOf ← visualBoxMap ← tight/
같은 곳, 도형절대 박스 — 문서에서 읽은 boundingBoxabsBoxMap
⑥ 좁은 칼럼절대 박스 — 「문서에 박힌 폭」을 되묻는 지표라 판정 박스로 물을 수 없다absBoxMap
렌더 재검증tight bounding box — 줄 수를 높이 차분으로 얻는 자리라 기호(가로 몫)와 무관하다renderProbe.ts 의 tightBoxOf
후보 안쪽 게이트 · 합성 픽스처절대 박스 — tight 를 잴 수 없어 적어서 끼운다frameBoxesAsTight
컨테이너 「담기는가」배경 도형의 모양이 허락하는 최대 내접 사각형containerShape.ts

컨테이너는 사각형이 아니다

computeLayoutMetrics 의 다섯째 인자 outlines(= 구운 배경 도형 외곽선)를 주면 ①과 중앙 오차가 도형의 모양을 보고, ④가 도형이 실제로 칠한 자리를 센다. 코퍼스의 텍스트 컨테이너 403개 중 절반쯤이 캡슐·원·말풍선·프레임이다.

후보에게도 같은 값을 준다 — 알고리즘과 지표가 다른 자를 쓰면 개선이 숫자로 안 나온다. 안 주면 지금까지처럼 bbox 다. 실측(98건 · rendered.json): 넘침 노드 94 → 99, 유휴 여백 중앙값 +0.006.

§4

지표 ①~⑦

computeLayoutMetrics(doc, tight, base, baseTight, outlines) 가 계산한다. tight필수다 — 판정 박스가 그것 하나이기 때문이다. base(= 좌표계를 맞춘 원안)를 주면 ② 폰트 배율 · ③ 정렬선 보존율 · ⑤ 신규 겹침이 채워진다.

#지표정의방향
넘침 노드 수 / 넘침 면적(px²) visual bounding box 가 컨테이너 영역(배경 도형이 이 글에게 허락하는 상자, 없으면 페이지)을 벗어난 노드 수와 총면적. OVERFLOW_EPS = 1px² 미만은 부동소수 잡음이라 무시낮을수록
최소 폰트 배율 원안 대비 가장 많이 줄어든 텍스트의 fontSize 배율(1 = 안 줄어듦). shrunkNodes 는 줄어든 노드 수높을수록
정렬선 보존율 원안에서 같은 직선(±GUIDE_TOL = 4px)에 있던 노드 쌍이 채점 대상에서도 같은 선인 비율(0~1). 통째 평행이동은 보존높을수록
유휴 여백 비율 1 − 콘텐츠 점유율. 점유율은 leaf 가 덮은 격자(COVERAGE_CELL = 4px) 비율 — 텍스트는 visual box, 도형은 자기 모양(구운 외곽선, 구멍 제외)으로 덮는다. 페이지 전면 배경(면적 ≥ 90%)은 제외낮을수록
⑤-a겹침 쌍 지금 겹쳐 있는 leaf 쌍 수(의도된 겹침 제외 — §5). 참고용 절대량이라 원안에도 0 이 아닐 수 있다낮을수록
⑤-b신규 겹침 쌍 수 / 면적 원안에 없던 겹침이 생기거나 커진 leaf 쌍의 수와 늘어난 면적0 이어야 함 — 게이트
좁은 칼럼 노드 수 두 줄 이상인데 줄당 글자 수가 NARROW_CPL(=3) 미만인 텍스트 수. 폭은 문서에 박힌 boundingBox.width낮을수록
잘린 노드 수 / 면적 요소의 일부가 페이지(= 렌더 iframe) 밖으로 나간 leaf 수와 면적. 유일하게 원안에 안 견주는 절대값낮을수록

①·⑤·⑦ 은 견주는 대상이 다르다

① 넘침
노드를 자기 컨테이너에 견준다. 「그릇을 벗어난 면적」이 정의이고, 컨테이너가 있으면 그릇은 그 도형이다
⑤ 겹침
노드를 다른 노드에 견준다. 상대가 노드라 「부분적으로 포개졌다」를 센다
⑦ 잘림
노드를 페이지 사각형 하나에 견준다. 글자가 아예 안 그려지는 것이라 다른 사건이다

셋은 서로 배타가 아니라 한 노드가 잘리면서 넘치고 겹칠 수 있고, 각각 따로 센다. ① 이 이미 「컨테이너가 없으면 페이지」를 보는데도 ⑦ 을 따로 세는 이유는 둘이다 — ① 은 글자가 그려진다고 전제하므로 「배경 도형 안에는 담겼는데 그 도형이 페이지 밖」을 0 으로 보고, ① 은 원안 대비인데 잘림은 절대값이다(원안에서 잘려 있었더라도 안 읽히면 안 읽히는 것이다).

⑥ 은 넘침이 아니라 가독성이다

대치가 라벨 자리에 문장을 넣으면 렌더러는 넘치는 대신 좁은 상자에 여러 줄로 흘린다. 5636021 실측: 「18%」가 있던 상자에 「여기까지 여세요」가 들어와 139px 폭에 4줄(줄당 1.8자)이 됐다. 상자가 배경 도형 안에 있으니 ① 넘침 0 · ② 폰트 배율 1.000 · ⑤ 신규 겹침도 없어 ①~⑤ 가 전부 만점을 주는데 그림은 세로쓰기처럼 보인다.

왜 3 인가

「읽기 나쁘다」는 라벨이 코퍼스에 없어서, 사람이 만든 정답이 그 노드의 폭을 넓혔나를 대신 봤다. 하한 3 에서 걸리는 59개 중 정답도 넓힌 것이 36개 · 안 건드린 것이 7개로 84% 가 같은 판단이고, 4 부터 71% · 5 부터 63% 로 흐려진다. 그 위(줄당 4~6자)는 배지·라벨의 정상 모양과 섞여 잣대로 쓸 수 없다. (2026-08-24 · 정답 104건 · 두 줄 이상 텍스트 459개 시점의 일회성 조사다 — 답하는 것이 「왜 3 인가」이지 「지금 코퍼스가 어떤가」가 아니어서 다시 재지 않는다.)

⑥·⑦ 은 아직 판정에 안 넣는다 — 이유가 정반대

⑥ 은 지금 걸리는 것이 너무 많다. 게이트로 올리면 before 의 좁은 칼럼 58개 노드가 한꺼번에 FAIL 로 넘어가는데, 합본이 푸는 것은 33개뿐이고 나머지 25개는 slack 이 없어 어떤 후보도 못 고친다.

⑦ 은 걸리는 것이 없다. 커밋된 119건에서 페이지 밖으로 나간 leaf 는 ExtendableShapeItem 하나뿐이고 텍스트는 0 이다(2026-08-26 실측). 게이트로 올려도 아무것도 안 가른다 — 지금 ⑦ 은 후보가 만들지 않았는지 보는 자리다.

평가 항목에서 뺀 것

중앙 정렬 오차(meanCenterError)는 계산이 metrics.ts 에 남아 있지만 번호가 없고 표·리포트·CSV 어디에도 안 실린다. 옛 ⑤ 였고, 뺄 때 옛 ⑥ 신규 겹침이 ⑤ 로 당겨졌다 — 이 문서보다 오래된 글의 ⑥ 은 지금의 ⑤ 다.

① 은 코드에서 축별로 분해돼 있다 (overflowNodesH/V · overflowAreaH/V/Corner). 세로로 흘러넘친 것과 가로로 삐져나간 것은 알고리즘이 손쓸 방법이 다르므로 한 숫자로 뭉개지 않는다. 축별 노드 수는 서로 배타가 아니다 — 대각으로 삐져나가면 양쪽에 다 센다.

§5

⑤ 겹침 — 의도된 겹침은 애초에 세지 않는다

걸러내지 않으면 정상 디자인에서 상시로 울린다. 여섯을 빼면 남는 것은 「무언가가 무언가에 부분적으로 포개졌다」뿐이다.

overlapAreaMap — 여섯 필터, 그리고 게이트

문서의 leaf 쌍 — 겹친 면적 > 0 ① 그룹(자식이 있는 노드) — leaf 만 본다 부모-자손 겹침이 이걸로 함께 빠진다 ② 페이지 전면 배경판 — 면적 ≥ 페이지의 90% ④·findTextContainer 와 같은 규칙 ③ 텍스트 ↔ 자기 컨테이너(배경 도형) 도형 위에 글자가 놓이는 건 당연하다. 텍스트끼리는 센다 ④ 장식 타입 — LineItem · VectorGraphicItem 6174028 실측: 구분선 하나가 텍스트와 1,410px² «겹침» ⑤ 포함 관계 — 95% 이상 들어가고 큰 쪽이 배경 도형 6357697 의 칩은 패널에 99.4%, 칩 위 글자는 99.8% 들어간다 ⑥ 겹친 면적 ≤ OVERLAP_EPS(100px²) 모서리가 스치는 수준은 사고가 아니다 남은 것 = ⑤-a 겹침 쌍 (절대량 · 참고용) 원안에도 0 이 아닐 수 있다 — 디자이너가 일부러 포갠 것 원안의 같은 쌍보다 100px² 넘게 커졌나 새로 생긴 쌍(baseArea = 0)도 여기서 잡힌다 ⑤-b 신규 겹침 — 게이트 1건이라도 있으면 FAIL 아니오 표에 절대량으로만 남는다 (⑤-a)
OVERLAP_EPS 가 100px² 인 것은 ① 넘침의 하한(1px²)보다 훨씬 크다 — 넘침과 달리 겹침은 「두 요소가 포개졌다」를 세기 때문이다. 후보도 이 값을 본다: 지표가 겹침으로 안 세는 접촉을 알고리즘이 겹침으로 보면 고쳐도 숫자가 안 움직인다.

⑤ 는 왜 ④ 와 별개인가

④ 유휴 여백은 격자 union 점유율이라 union = Σ면적 − Σ겹침 이다. 겹치면 union 이 줄어 ④가 나빠지긴 하지만 두 현상이 한 숫자에 뭉개져 분리되지 않는다. 특히 「텍스트/배경 동반 확장」에서는 노드를 키운 Σ면적 증가가 겹침 손실을 덮어버려 글자가 서로 포개진 문서가 ④ 기준으로는 개선으로 찍힌다. 반대로 겹침이 전혀 없는 여백 넉넉한 레이아웃은 ④만 나쁘다. ①②③ 도 겹침에는 무감각하므로, ⑤ 가 없으면 「요소가 서로 포개졌다」를 벌주는 지표가 하나도 없다.

이 지표가 세는 것과 「되돌릴 이유」는 다르다

⑤ 는 새로 생긴 쌍과 이미 겹쳐 있던 쌍이 커진 것을 모두 세지만, 만드는 쪽에서 변경을 되돌리는 판정은 새로 생긴 쌍만 본다. 컨테이너를 키우면 배지가 카드 위에 걸친 것 같은 의도된 겹침이 필연적으로 커지므로, 그것까지 되돌리면 컨테이너를 조금도 키울 수 없다. 숫자는 여기 그대로 남는다 — 가리는 것이 아니라 되돌릴 이유로 쓰지 않는 것이다.

§6

판정 라벨 — 한 낱말로 접는다

지표는 숫자를 열 몇 개로 늘어놓는다. 케이스가 열 몇 건일 때는 표를 눈으로 훑어 「이건 됐고 저건 안 됐다」를 셀 수 있었지만, 케이스가 늘면 그 셈이 안 된다 — 후보를 하나 고칠 때마다 표 전체를 다시 읽어야 하고 좋아졌는지 나빠졌는지가 숫자 사이에 묻힌다. 그래서 지표 위에 라벨을 한 겹 얹는다. 표는 그대로 두고(왜 그 라벨인지는 표가 답한다), 라벨은 셀 수 있는 것만 맡는다.

judgeMetrics(metrics, base, options) — 게이트 흐름도

judgeMetrics(metrics, base) 지표(metrics)가 있나 아니오 unsolvable 근거가 있나 아니오 UNSOLVABLE UNKNOWN 기준(base = 원안)이 있나 아니오 UNKNOWN — 기준 없음 ②⑤ 를 잰 적이 없다 ③ FAIL 게이트 셋 — 하나라도 걸리면 넘침 노드 > 원안 또는 넘침 면적 > 원안 × 1.10 신규 겹침 쌍 > 0 최소 폰트 배율 < fontFloor(기본 0.7) − ε 걸린 것을 전부 적는다 — 첫 번째에서 멈추지 않는다 FAIL 하나도 안 걸림 ④ SUCCESS 눈금 둘 — 하나라도 놓치면 최소 폰트 배율 < 1.0 − ε → 「폰트 축소」 유휴 여백 > 원안 + ε → 「유휴 여백」 ③ 정렬선 보존율은 라벨을 가르지 않는다 — 표에는 남는다 ACCEPTABLE 둘 다 통과 SUCCESS 넘침 n노드 …px² ≤ 원안 · 신규 겹침 0 · 폰트 1.000 · 유휴 여백 ≤ 원안 — 실제 값을 적는다
FAIL 셋은 게이트, SUCCESS 둘은 그 위의 눈금이고 그 사이가 ACCEPTABLE 이다. 「눈에 거슬리는 것은 없는데 원안만큼은 아니다」가 그 자리다. 넷 다 원안에 견준다 — ①②④⑤ 어느 것도 절대값을 보지 않는다.

경계와 그 대가

A

폰트 0.7~1.0 은 ACCEPTABLE 이다

SUCCESS 가 「폰트 = 1.0」을 요구하고 FAIL 이 「< 0.7」만 잡으면 그 사이가 어느 라벨도 아닌 구간으로 비는데, 렌더러 오토핏이 멈추는 자리가 정확히 0.700 이라 그 구간에 문서가 몰린다. 비워 두면 가장 흔한 결과를 못 부르므로 아래쪽으로 붙였다.

B

넘침도 원안에 견준다 — 절대 0 이 아니다

예전에는 ① 만 절대값이라 디자이너가 일부러 넣은 오버행까지 후보 탓으로 셌다. 그 결과 원안 자신이 FAIL 인 케이스가 18건이었고(2026-08-25 · 137건 시점), 그 케이스는 재배치가 원안을 완벽히 되살려도 SUCCESS·ACCEPTABLE 이 못 됐다. 판정 박스가 프레임이라 넘침을 과대 보고한 것도 그 18건을 부풀린 원인이었다. 지금은 대치·재배치가 새로 만든 넘침만 벌준다.

C

노드 수를 먼저 보고, 면적은 여유를 두고 함께 본다

판정의 단위는 「몇 개가 삐져나왔나」다 — 면적을 그대로 견주면 같은 노드가 조금 더 크게 잡히기만 해도 임계 근처에서 라벨이 흔들린다. 그래서 오랫동안 노드 수만 봤는데, 그러면 같은 노드가 훨씬 크게 넘친 것이 게이트를 그냥 지난다: 5699884 는 넘침 노드가 원안과 같은 1개인데 면적이 5,691 → 14,261px²(2.5배) 로 커진 채 SUCCESS 였고, 6777793 은 420 → 23,248px²(55배) 가 그랬다.

2026-08-27 부터 면적도 본다 — 원안의 10% 까지 봐준다. 10% 는 실측으로 고른 값이다: 96건 × 후보 16개에서 「노드 수는 안 늘고 면적만 늘어 게이트를 지나던」 결과가 29건인데, 그 배율의 최솟값이 1.78배이고 1.0~1.78배 구간에는 결과가 0건이다. 1.01 로 두어도 1.5 로 두어도 지금 코퍼스의 판정은 같다.

D

SUCCESS 라도 넘침이 0 이 아닐 수 있다

게이트가 요구하는 것은 「원안보다 나쁘지 않다」이지 0 이 아니다 — 6777793 은 3노드 748px² 를 남긴 채 SUCCESS 다(원안 5노드 420px²). 그래서 SUCCESS 의 까닭 문구는 실제 값을 적는다. 예전에는 그 자리에 「넘침 0」이 하드코딩돼 있어 표·터미널·case.json 이 전부 거짓을 적었다.

E

기준 열(original)은 언제나 SUCCESS 라 라벨을 안 적는다

자기와 견주므로 넘침이 같고 ②⑤ 가 만점이며 ④ 도 같다. 그것을 라벨로 적으면 「원안은 완벽하다」로 읽히므로 ⑤-b 게이트 칸과 같은 규칙으로 「기준」이라고 적는다. ②③ 은 그대로 숫자(1.000)를 적는다 — 그쪽은 0 이 아니라 만점의 모양이라 읽는 사람이 오해하지 않는다.

UNSOLVABLEUNKNOWN 을 가르는 것은 케이스가 든 근거

두 라벨 다 장이 비어 있지만 까닭이 반대다 — 앞은 알아본 결과 답이 없는 것이고 뒤는 아직 알아보지 못한 것이다. 예전에는 정답이 없는 케이스가 전부 UNKNOWN 이라, 「글자를 줄이지 않으면 안 담기니 넘침을 남긴다」고 판단해 일부러 비운 자리와 아직 만들지 않은 자리가 한 칸에 섞였다. 근거는 case.jsonexpectedResult: {status, reason, at} 이고 expected-result-verify.ts 가 적는다. 색은 미결을 뜻하는 파랑이다 (빨강은 FAIL, 회색은 UNKNOWN).

비교마다 ±0.0005 를 물린다

표가 소수 3자리로 찍으므로 그 반올림 폭이다. 1.000 이라 적어 놓고 라벨이 「폰트 축소」라고 말하면 읽는 사람이 표를 의심하게 된다.

§7

상수 — 값과 그 근거

상수어디에근거
EXCESSIVE_FONT_SCALE0.7② 폰트 게이트렌더러 오토핏의 하한과 같은 값 — 「렌더러가 더는 못 줄이는 선」이 곧 「사람이 못 읽겠다는 선」이다. 뷰어에서 0.5~1.0 로 옮길 수 있다
FULL_FONT_SCALE1SUCCESS 조건가독성 기준이 아니라 불변 조건이라 잣대를 옮겨도 안 움직인다
OVERFLOW_AREA_TOL0.1① 면적 게이트96건 × 후보 16개 실측: 게이트를 지나던 29건의 배율 최솟값이 1.78배, 1.0~1.78 구간은 0건
VERDICT_EPS5e-4모든 비교표가 3자리로 찍는 반올림 폭. 표와 라벨이 어긋나지 않게
OVERFLOW_EPS1 px²① 넘침 하한정렬 계산의 부동소수 오차로 1e-13 짜리 «넘침» 이 생겨 노드 수만 부풀던 것을 막는다
OVERLAP_EPS100 px²⑤ 겹침 하한변끼리 딱 붙은 박스와 모서리가 스치는 접촉을 걸러낸다. 후보도 같은 값을 본다
CONTAINED_RATIO0.95⑤ 포함 관계「포갠 것이 아니라 얹은 것」의 선. 재배치의 위상 판정도 같은 값을 본다
GUIDE_TOL4 px③ 정렬선tidyLayout 의 GUIDE_TOL 과 같은 값
COVERAGE_CELL4 px④ 점유율 격자작을수록 정확하고 느리다
PAGE_BACKDROP_RATIO0.9④·⑤·컨테이너페이지 전면 배경판은 원래 페이지를 꽉 채운다 — 세면 전부가 겹침이 된다
NARROW_CPL3⑥ 좁은 칼럼정답이 폭을 넓혔나로 대신 검증: 3 에서 84% 일치, 4 부터 71%, 5 부터 63%
SIZE_EPS0.01 pxtight 물려받기좌표 계산의 부동소수 잡음만 걸러낸다
DECORATION_TYPES2종⑤ 장식 제외LineItem · VectorGraphicItem다른 요소 위에 걸치라고 놓는 것이다
§8

「못 잰 노드」 — ④ 만 다르게 다룬다

①⑤ — 판정에서 뺀다
그쪽은 사건(넘침·겹침)을 세는 지표라, 오토핏 여백까지 안은 프레임을 끼우면 없던 사건이 태어난다. 빠진 수는 tightMissing 이 센다
④ — 프레임으로 대신 센다
④ 는 면적을 세므로 노드를 빼는 것이 곧 그 면적을 0 으로 세는 것이다. 조금 크게 세는 오차와 통째로 빼는 오차는 크기가 다르다

이것이 실제로 문제가 되는 자리는 하나다 — 크기를 바꾼 텍스트는 구운 판정 박스를 못 물려받는다. 그래서 폭을 열어 줄 수를 줄인 후보의 결과에서 그 텍스트가 격자에서 사라지고 ④ 가 그만큼 부푼다.

실측무슨 일이
4223631빠진 노드 하나가 페이지의 3.0% 라 ④ 가 0.787 → 0.818 로 뛰었고, 그 0.031 때문에 「두 줄을 한 줄로 편」 답이 SUCCESS 를 놓쳤다
6720819텍스트 13개가 빠져 ④ 가 실제보다 0.154 낮게(= 촘촘하게) 나왔다 — 양방향으로 튄다
최선 합본 96건프레임으로 대신 세면 SUCCESS 51 → 58(내려가는 케이스 0건). 게이트·겹침·폰트는 한 자리도 안 움직인다

빈 텍스트(엔진이 「글자 없음」이라 답한 것)는 그대로 아무 칸도 안 덮는다 — 그건 못 잰 것이 아니라 잰 결과가 0 이다.

After 장은 어떻게 채점되나 — carryTightBoxes

재배치 결과는 뷰어가 그때그때 만드는 문서라 구울 수 없다. 그 장은 rendered.json 의 tight bounding box 를 물려받되 크기가 그대로인 노드만 가져다 쓴다.

언제 물려받아도 되나 — 박스가 그대로일 때만

노드가 옮겨졌다
물려받는다 — tight 는 프레임 원점 기준이라 어디로 갔든 상관없다. 재배치가 하는 일의 대부분이 이것이다
크기가 같다 (±0.01px)
물려받는다 — 글자 배치를 정하는 것은 노드의 폭·높이이므로 크기가 같으면 안에 찍히는 글자도 같다
크기가 달라졌다
물려받지 않고 비운다 — 줄바꿈이 달라졌을 수 있다. 물려받으면 조용히 틀린 값으로 채점하게 된다
폭을 고정하는 후보는 대부분 물려받고, 폭을 넓히는 후보는 손댄 노드만큼 빠진다 — 그 수가 tightMissing 이고, 그것이 곧 위 ④ 문제의 크기다.
§9

어디에 뜨나 · 지금 코퍼스의 성적표

자리무엇을 보여 주나
뷰어케이스 제목 옆에 지금 고른 알고리즘의 라벨, 지표 표 맨 윗줄에 네 장 전부의 라벨. 칸에 마우스를 올리면 까닭이 뜬다(걸린 게이트, 또는 SUCCESS 를 놓친 이유)
relayout-metrics.ts■ <id> 옆에 후보들의 판정 분포, 표 맨 위에 판정 줄, 맨 아래에 열 × 라벨 요약표. --json 에도 verdict·reasons 가 실린다
relayout-method-run.ts케이스 id 바로 옆에 라벨, 맨 아래 한 줄 집계
case.json 의 verdict들일 때의 before 판정 하나. 목록·정렬용이고 정본이 아니다

파일에 적힌 라벨은 정본이 아니다

뷰어는 고른 알고리즘·채점 상자·폰트 잣대로 그때그때 잰다. case.json 에 굽는 것은 들일 때의 before 판정 하나이고, 답하는 질문은 「이 케이스를 왜 들였나 = 재배치가 고칠 것이 있나」다. 그래서 잣대를 함께 적는다(verdict.rule = {excessiveFontScale, box}) — 폰트 게이트의 선이 조절 가능해졌으므로 무엇으로 잰 값인지 없이는 이 값이 낡았는지 알 수 없다.

2026-08-28 · 96건

before 판정건수
FAIL85
ACCEPTABLE11
SUCCESS0
UNSOLVABLE 표시된 케이스51

SUCCESS 0 은 성적이 아니라 프루너가 이미 뺐다는 뜻이다 — 재배치가 고칠 것이 없는 케이스는 코퍼스에 남기지 않는다.

② 는 지금 코퍼스에서 안 움직인다
96건의 rendered.jsonoriginal.json 과 노드 id 로 짝지어 fontSize 를 견주면 텍스트 745쌍 중 줄어든 것이 0개다 — 최소 폰트 배율이 전 케이스 1.000 이다. 렌더본을 오토핏을 끄고 굽기 때문이고, 못 담은 글은 전부 넘침으로 남아 있다.
그래서 case.jsonproblems 는 낡아 있다
같은 파일들의 problems 에는 shrink77건 남아 있다. verdict 는 2026-08-27 에 전건 다시 구워졌지만 problems이미 값이 있으면 안 덮는 규칙(사람 판단 보호) 때문에 그대로다 — 제작 로직 §7 에 자세히 적었다.
§10

코드 어디에 있나

무엇파일진입점
라벨 규칙 · 상수relayout-core/verdict.ts:170judgeMetrics · judgeDocs
지표 계산relayout-core/metrics.ts:1044computeLayoutMetrics
판정 박스 좌표 옮기기relayout-core/metrics.ts:191judgeBoxOf · frameBoxesAsTight
tight 물려받기relayout-core/metrics.ts:153carryTightBoxes
겹침 지도relayout-core/metrics.ts:895overlapAreaMap
좌표계·배율 맞추기relayout-core/docFrame.ts:87alignDocFrame · scaleTightBoxes · docFontScale
visual bounding boxrelayout-core/visualBox.ts:129visualBoxMap · markerIndentOf
컨테이너 모양relayout-core/containerShape.ts:607containerRegions · paintedSpansOf
넘침·줄바꿈·좁은 칼럼relayout-core/textOverflow.ts:617overflowOf · isNarrowColumn
후보 안쪽 게이트relayout-core/gate.ts— 「자기 입력보다 겹침을 늘렸나」
렌더 재검증relayout-core/renderProbe.tsRenderProbe

규칙 문서와 코드의 절 번호는 묶여 있다

metrics.ts · PostReplaceRelayout.tsx · relayout-report.ts · expected-result-build.ts 의 각주가 docs/evaluation-rules.md 의 절 번호를 가리킨다. 절 번호를 바꾸면 그 참조도 함께 고쳐야 한다.

케이스를 만드는 쪽은 따로 정리했다

여기서 채점하는 케이스가 어떻게 만들어지는지테스트 케이스 제작 로직 에 있다 — 두 입구, 축소를 끄는 레버, 굽기 순서, 라벨·정답·프루닝.