사이트 첫 화면에는 카라바조의 「성 베드로의 부인」(1610)이 걸려 있다. 내 이름은 거기 없다. 처음 2초 동안은. 이름 자리에는 터미널 글리프 — 0-9 A-F · : / — 로 만든 안개가 떠 있고, 안개는 왼쪽부터 152px Fraunces 활자로 응결된다. 인물들 뒤에서. 이 글은 그 한 장면을 만든 기록이다. 수치는 전부 실제 값이다.
문제 — 이름이 그림에 먹힌다
히어로는 그림 전면 확대다. 1440×900 스테이지에 유화가 가득 차고, 그 위 상단 밴드에 이름 OH YOUNGWOONG이 놓인다. Fraunces 420, opsz 144, 152px.
쉬운 답은 두 개였고, 둘 다 틀렸다.
글자를 인물 앞에 올리면 읽기는 쉽다. 대신 스티커가 된다. 그림과 글자가 서로를 모른 채 붙어 있는 병치. 글자를 인물 뒤로 넣으면 그림과 한 몸이 된다. 대신 먹힌다. 하녀의 머리가 U를 삼키고 베드로가 O를 삼킨다.
여기서 오너 교정이 들어왔다. 이름은 반드시 인물 뒤. 레이어를 뒤집어서 판독을 사면 안 된다. 판독은 커닝으로 다시 벌어라. 그래서 규칙이 하나 남았다 — 인물은 항상 글자를 가린다. 가려도 읽히게 만드는 것이 이 장면의 일이다.
가리는 쪽을 먼저 실측했다. 스테이지 px 기준으로 벽이 세 개다.
- 병사의 투구: x 246–404
- 하녀의 머리: x 502–660
- 베드로의 머리: x 1052–1250
이 세 벽 사이에 열두 글리프를 앉혀야 한다.

재료 — 활자가 안개가 된다
응결 이전에 안개가 필요했다. 원칙은 하나. 안개는 글자 밖에서 빌려오지 않는다. 글자가 자기 자신의 물질로 만들어져야 한다.
파이프라인은 이렇다. 각 글리프를 오프스크린 캔버스에 렌더하고, 잉크 마스크를 5px 간격으로 샘플한다. 잉크가 있는 셀마다 터미널 글리프 한 자를 배정한다 — 0123456789ABCDEF·:/ 중 하나, 6.5–9px, 색은 활자와 같은 상아색(#e6d7b8). 셀은 자기 홈 좌표(글자 안의 원래 자리)를 기억한 채 무작위 방향으로 흩어져 시작한다.
응결은 왼쪽에서 오른쪽으로 지나가는 웨이브프런트다. 장면 진행도 P(0→1)에서 글리프 i의 진행도는:
// 응결 웨이브프런트 — 글리프 i의 진행도
q(i) = clamp((P − i·0.055) / 0.34, 0, 1)
// 안개 산란 반경 = 8.5px · (1−q)^1.9, 상시 드리프트 ±0.7px
// q > 0.62부터 크리스프 활자 페이드인, q ≥ 0.95에서 안개 소멸
글리프당 0.055씩 시차를 두고, 각자 0.34 폭의 창 안에서 응결을 끝낸다. 안개 셀은 산란 반경이 (1−q)^1.9로 줄며 홈 좌표로 수렴하고, q가 0.62를 넘는 순간부터 진짜 활자가 그 아래에서 올라온다. 크로스페이드 구간(0.62→0.95)에서는 먼지와 활자가 잠깐 겹친다. 그 겹침이 이 장면에서 제일 마음에 드는 프레임이다.


캘리브레이션 — 픽셀 옵티마이저와 물림 예산
가장 큰 이동은 밴드 자체였다. 원래 이름은 em-top 196에 있었고, 거기서는 글자가 인물 얼굴 한가운데를 지나갔다. 밴드를 위로 올려 em-top 80(잉크 밴드 대략 y 98–206)에 앉히자 구도가 바뀐다. 머리들이 글자를 자르는 게 아니라, 글자 하단을 무는 그림이 된다.
다음은 커닝. 두 단계로 풀었다.
1차는 1차원 솔버(solve_layout.py)다. 벽 세 개를 상수로 놓고, F=148에서 실측한 글리프 어드밴스(F에 선형)를 가지고 152부터 내려가며 최대 가능 활자 크기를 찾았다. 이때 물림 예산이 정해졌다 — H는 투구에 12px까지만 물리고, Y는 왼쪽 잉크의 42%까지만 내주고, W는 베드로에 22px까지만 물린다.
2차는 픽셀 옵티마이저(optimize.py)다. 1차원 벽은 근사일 뿐이라, 실제로는 글리프 잉크 마스크(3px 격자)를 인물 컷아웃의 알파 채널과 픽셀 단위로 겹쳐서 가시율을 계산했다. 판독 규칙은 이렇다: 가시율 42% 이상, 또는 26% 이상이면서 좌우 플랭크(잉크 양끝 25% 컬럼) 중 한쪽이 70% 이상 보일 것. 글자는 절반이 가려져도 한쪽 윤곽이 살아 있으면 읽힌다.
탐색은 좌표 하강 8스윕. 변수는 시작점 x0과 글리프별 갭 12개. 워드스페이스는 +64px로 미리 열어두고 시작했다 — 투구가 낱말 사이 공백에 들어와 앉게 하는 초기화다. 활자 크기 4개(152/156/160/164) × 밴드 높이 3개(80/88/96) = 후보 12개를 각각 하강시키고, 점수는 판독 실패당 1000점 벌점에 활자가 클수록 보너스. 같은 값이면 큰 활자가 이긴다.
확정값:
// 캘리브레이션 확정 — 1440×900 스테이지 px
const LAYOUT = {
F: 152, // Fraunces 420 · opsz 144 · 자간 -0.012em
x0: 59, // 펜 시작
top: 80, // em-top (잉크 밴드 ≈ y98–206)
// O H ␣ Y O U N G W O O N G
gaps: [ 20, 0, 64, 0, 32, -20, 0, 0, 0, 0, -20, 0, 0 ] // 글리프별 추가 커닝(px)
};
이 레이아웃의 글리프별 가시율(인물이 앞에 있는 상태):
| O | H | Y | O | U | N | G | W | O | O | N | G |
|---|---|---|---|---|---|---|---|---|---|---|---|
| 1.00 | .64 | 1.00 | .41 | .28 | 1.00 | 1.00 | 1.00 | .64 | .42 | .96 | 1.00 |
판독 실패 0. 제일 깊게 물린 글자는 U다 — 하녀가 72%를 가리지만 왼쪽 윤곽이 통째로 살아 있어서 판독 규칙을 통과한다. 그리고 베드로의 정수리는 WOONG의 두 번째 O 안으로 올라온다. 가시율 42%. 베드로는 세 번 부인한 사람이지만, 내 이름에 대해서는 O 하나를 58% 가리는 선에서 합의했다.


죽인 것들
매트릭스 비. 첫 버전의 안개 셀은 12Hz로 글자를 계속 바꿨다. 화면이 즉시 다른 영화가 됐다. 내리는 초록 코드의 인용이지, 이름의 응결이 아니다. 셀 글자는 생성 시 한 번만 배정하고 동결했다. 바뀌는 것은 위치뿐이다. 안개는 내리는 코드가 아니라 가라앉는 먼지여야 한다.
스크롤 스크럽. 목업에서는 휠이 응결을 문질렀다 — preventDefault로 스크롤을 가로채고 deltaY/360씩 P를 밀었다. 걸레로 김서린 유리를 닦는 느낌이라 꽤 좋았고, 그래서 오래 잡고 있었다. 그래도 죽였다. 폴드 아래로 가려는 사람의 스크롤을 애니메이션의 담보로 잡는 짓이다. 설치본의 규칙은 하나다: 스크롤 의도가 감지되면 이름은 그 자리에서 즉시 완성되고, 스크롤은 사용자에게 돌아간다. 기다린 사람에게는 응결을, 바쁜 사람에게는 이름을.
시네마 과잉. 오너 코멘트 한 줄 — “너무 시네마처럼 안 해도”. 산란 반경 8.5px, 드리프트 ±0.7px, 안개 알파 최대 0.82, 응결 1.1초. 전부 이 코멘트 이후의 값이다. 장면은 이벤트가 아니라 조판이 도착하는 과정이면 충분하다.
결과 — 정지 포스터가 판독 게이트를 통과한다
최종 타임라인. 로드하면 안개가 그림 페이드인(0.8초)에 실려 함께 들어온다. 2.0초 유휴가 지나면 장면이 스스로 응결을 시작하고, 1.1초(easeInOutCubic) 만에 이름이 선다. 완성 후에는 캔버스가 정지 포스터를 한 번 그리고 렌더 루프를 끝낸다. 쉬는 히어로가 태우는 프레임은 0이다.
경계 조건이 이 장면의 절반이다.
prefers-reduced-motion: 안개도 웨이브도 없다. 정지 포스터를 한 번 그리고 끝. 두 프레임을 찍어 비교했을 때 픽셀 델타 0.- 스크롤 선행: 유휴 2초를 기다리지 않고 스크롤하면 즉시 완성. 다시 올라와도 캔버스는 완성된 이름을 들고 있다.
- 모바일 390: 이름 2행 스택, 가로 스크롤 0.
- 이름은 캔버스와 무관하게
sr-onlyh1으로 항상 존재한다. 캔버스는 장식이고, 접근성 트리에는 처음부터 이름이 서 있다.
설치 인수는 스크립트로 닫았다(accept_install.py) — 정지 포스터 픽셀 일치, 유휴 2.0초/응결 1.1초 타이밍, 스크롤 즉시 완성, 1920/1366 폴드 규율, 콘솔 에러·워닝 0/0, reduced-motion 정지, 글리프당 가시율 게이트, 390 레이아웃. 여덟 항목. 이 장면의 진짜 시험은 마지막 프레임이다. 모션이 전부 빠진 정지 화면 한 장이 판독 게이트를 통과하는가. 통과한다 — 위의 표가 그 증거다.

이 장면이 사이트의 나머지를 감당한다고는 아직 말하지 않는다. 첫 화면 하나를 닫았다는 것까지만 말한다. 그림 제목이 「부인」이다. 이름까지 부인당하게 둘 수는 없었다.
출처·증거
- Caravaggio, The Denial of Saint Peter, 1610 — The Metropolitan Museum of Art, Public Domain
- 활자: Fraunces (variable), 안개 폴백: ui-monospace
- 레이아웃 솔버:
solve_layout.py(1D 벽),optimize.py(픽셀 가시율, 좌표 하강) · 인수:accept_install.py(8항목) - 본문 수치 전부 위 스크립트와
mock.html캘리브레이션 헤더에서 검증됨

