현진건, 1924 — 코드로 렌더한 4분 38초짜리 절차적 단편 영화에 대하여
▶ 영화 보기 4분 38초 · 16 시퀀스 · 65 샷 · 외부 에셋 0개 · 브라우저에서 바로 재생2026년 8월 2일, Andrej Karpathy가 실험 하나를 올렸다. Claude Opus 5에게 반지의 제왕 첫 문단과 100만 토큰 예산(약 $10)을 주고 "이걸 three.js로 렌더링해봐"라고만 시킨 것이다. Opus는 약 2시간 동안 혼자 돌면서 코드를 썼고, 92.6초짜리 절차적 단편 영화가 나왔다. 외부 에셋은 하나도 쓰지 않았다. 나무도 집도 인물도 전부 코드가 그 자리에서 만들어낸 폴리곤이다.
그가 이 실험에서 흥미로워한 건 결과물의 완성도가 아니었다. "제정신인 사람이라면 아무도 이렇게 커스텀한 걸 만드느라 시간을 쓰지 않을 텐데, LLM은 세상의 모든 끈기와 인내심을 갖고 있다"는 것 — "아무도 안 할 일"이 "$10이면 하지 뭐"로 바뀌는 지점이었다.
우리는 같은 실험을 한국 소설로 했다. 현진건의 「운수 좋은 날」(1924). 호비튼의 언덕 대신 1924년 비 내리는 경성, 빌보 대신 인력거꾼 김 첨지다. 결과물은 4분 38초, 16 시퀀스, 65 샷. 캡션 53줄은 전부 원문 그대로이고, 의존성은 여전히 three.js 하나, 외부 에셋은 여전히 0개다.
Karpathy의 원문과 답글에서만 드러난 사실들은 별도 페이지에 따로 정리했다.
전문은 한글 7,035음절이다. 낭독 속도 실측 5.41음절/초로 그대로 읽으면 29분이 나오고, 한 번의 작업 런에 들어가지 않는다. 그래서 전문의 15%인 1,097음절만 발췌했다. 4분 38초는 그 15%가 만들어낸 길이다.
다만 캡션은 한 글자도 고치지 않았다. 발췌란 문장을 건너뛰는 것이지 문장을 줄이는 게 아니다. 요약도, 현대어역도, 의역도 없다. 고른 문장은 1924년 표기 그대로 간다 — "내리었다", "첨지", "모주". 건너뛴 구간은 캡션 없는 무음 샷으로 넘긴다. 침묵도 연출이다.
도입부만 자르지 않은 이유는 분명하다. 「운수 좋은 날」은 마지막 대사가 전부인 소설이다. Karpathy처럼 첫 문단만 렌더했다면 비 오는 거리 스케치로 끝났을 것이고, 이 작품을 고른 의미가 사라진다. 그래서 7개 비트 — 비 오는 아침, 연달아 붙는 손님, 아픈 아내와 설렁탕, 인력거 질주, 선술집, 불안, 귀가 — 를 모두 통과하도록 문장을 골랐다.
설렁탕을 사다놓았는데 왜 먹지를 못하니, 왜 먹지를 못하니... 괴상하게도 오늘은! 운수가 좋더니만...
— 마지막 캡션. 원문 그대로.
narration.jsonl과 shots.json 둘뿐.
timing.js로 굽는다.
이 파일이 PASS 2의 정답지가 된다.
warp = authored / real로 재생 속도만 스케일해
실측 길이에 맞춘다. 애니메이션 곡선의 모양은 그대로 두고 속도만 바뀌므로 동작이 깨지지 않는다.
Karpathy는 그림을 먼저 만들고, ElevenLabs 나레이션을 나중에 붙인 뒤,
retime-to-narration.mjs로 샷 길이를 다시 맞췄다. 우리는 그 순서를 뒤집었다.
TTS를 먼저 만들어 길이를 못 박고, 그 길이에 그림을 맞춘다.
이유는 단순하다. 길이를 먼저 확정하면 모델이 두 시간 내내 "이 샷 몇 초짜리지"를 추측하지 않아도 된다. 추측이 사라진 만큼 토큰이 실제 지오메트리로 간다. 대신 잃는 것도 있다 — "한 줄 프롬프트로 어디까지 가나"라는 원래 실험의 순수성이다.
다섯 번째 단계가 필요한 이유는 Karpathy가 트윗의 마지막 문단에서 짚은 것과 같다. LLM은 영상을 네이티브하게 지각하지 못해서 자기가 만든 화면을 감사할 수 없다. 스크린샷은 그 구멍을 메우는 임시방편이지, 해결책이 아니다.
| 단계 | 모델 | 토큰 | 하한 전부 input |
상한 전부 output |
|---|---|---|---|---|
| 불러오는 중… | ||||
| 불러오는 중… |
Karpathy의 값은 그의 트윗과 공개 소스에서 온 것이다. 코드 줄 수의 5,500 / 6,933 차이는 트윗의 서술과 실제 소스를 직접 센 값의 차이다.
트윗 전문과 번역, 지표, 그리고 답글에서만 나온 사실들 — 오디오는 본인이 수동으로 만들었다는 것, three.js 공식계정의 InstancedMesh 지적을 다시 Opus에 먹였다는 것, 절차적 코드로 스토리보드를 잡고 video-to-video로 텍스처링하자는 후속 구상 — 을 별도 페이지에 정리했다.
Math.random()은 어디에도 쓰지 않는다. 새로고침해도 정확히 같은 화면이 나온다.renderer.compile()로 셰이더를 미리 굽는다. 첫 프레임에서 멈추지 않기 위해서다.