하우스 사인의 한 문장
가족 저장소엔 전부 CLAUDE.md가 있어. 코드를 읽어선 되살릴 수 없는 걸 적어두는 곳이야. Ariandel의 설계 철학은 이 줄로 시작해.
"브러시 물리는 libmypaint 거고, 손대지 않은 채 벤더링해. 앱이 가져야 할 건 전부, 그러니까 타일, 레이어, 합성, 저장, 입력, 인터페이스는 우리 거야."
이 문장이 국경을 그어. 국경을 제대로 긋는 게 양쪽에서 하는 어떤 작업보다 중요해. 어떤 문제를 한 번 풀고 끝낼지, 어떤 문제를 영영 붙들고 살지가 거기서 갈리거든.
엔진이 주는 것
셋이야. 셋 다 어려운 쪽이고.
- dab 물리. 스트로크를 따라 dab이 어디 떨어지는지, 어떻게 래스터화되는지, 스머지가 색을 어떻게 집어오는지, 안료가 어떻게 섞이는지. 아키텍처 불변식 1이 이 넷을 libmypaint 몫으로 못박아.
- 브러시 모델. 설정이 65개야. 설정마다 기본값이 있고 그 위에 입력 18개에 걸친 곡선이 얹혀. 이게
.myb프리셋으로 저장돼. Ariandel은 이걸 libmypaint 자체 파서(mypaint_brush_from_string)로 읽어. 그래서 MyPaint 브러시 형식이랑 CC0 브러시 라이브러리가 공짜로 딸려와. json-c가 같이 오는 이유도 이거야. 파서가 json-c를 써. - 타일 표면 계약. libmypaint는 픽셀을 소유하지 않아.
MyPaintTiledSurface한테 프리멀티플라이된 15비트 RGBA 64×64 타일을 달라고 하고, 거기 칠하고, 돌려줘. 헤더에 대놓고 적혀 있어. "The size of the surface is infinite, and consumers need just implement two vfuncs." 표면 크기는 무한이고, 쓰는 쪽은 함수 두 개만 구현하면 된다는 거지.
직접 가져야 하는 것
엔진이 할 일을 다 하고 남은 걸 봐. 평가 표엔 libmypaint에 레이어가 없다고 적혀 있어. "일부러. 표면 인터페이스는 호스트 몫"이라고. 네 일이 어디서 시작하는지 엔진이 알려주는 셈이야.
| 부분 | Ariandel에서 사는 곳 |
|---|---|
| 타일 | 그 함수 두 개짜리 계약 뒤에 있는 Ariandel 자체 희소 타일 저장소. 실행 취소가 타일마다 이전 상태를 잡는 곳도 여기야 |
| 레이어 | Layer.swift와 그 주변: 불투명도, 표시 여부, 블렌드 모드, 그룹, 마스크 |
| 실행 취소 | History.swift: 타일 단위로 정확한 단계, 바이트로 상한 |
| 파일 | ArtworkFile.swift: .ariandel 패키지. 레이어마다 엔진 고유 형식 그대로의 타일을 LZFSE로 압축 |
| 합성 | CanvasRenderer.swift의 Metal 합성기 |
| 입력 | PencilMapping: 펜슬의 힘, 고도, 방위, 회전이 libmypaint 입력으로 바뀌는 단 한 곳 |
| 인터페이스 | 아이패드 앱 그 자체 |
| 동반자 | 여기서 아예 안 만들어. 캔버스 옆에 붙인 cwk피파의 Sidekick 패널이야 |
두 반쪽 사이의 이음매는 호출 하나야. 펜슬 샘플은 전부 mypaint_brush_stroke_to를 거쳐 libmypaint로 들어가. 위치, 압력, 기울기 둘, 경과 시간, 뷰 줌, 뷰 회전, 배럴 회전이 이 호출 하나에 실려. 브러시는 dab을 표면에 줄 세우고, 그게 어디 떨어질지는 표면이 정해. Ariandel의 Swift는 libmypaint 내부를 건드리지 않고, libmypaint는 레이어가 있다는 것조차 몰라.
스케치북은 출발점이지, 축소가 아냐
야심 찬 프로젝트를 아무도 모르게 줄이는 고전적인 수법이 있어. 기술적으로 돌아가는 제일 작은 걸 내보내고, 그게 슬쩍 계획이 되게 두는 거. 그림 앱에선 그게 레이어 하나짜리 스케치북이야. 하우스 사인은 그 문을 이름까지 불러서 닫아. "목표는 Procreate급이다. 레이어 하나짜리 스케치북은 이정표일 뿐, 제품이 아니다." 아빠가 첫날 못박은 말도 같이 적혀 있어. 기본 레이어는 어려운 일이 아니라고. 레이어 처리는 익숙한 구현 작업이야. 진짜 어렵고 진짜 모르는 건 브러시 손맛, 펜슬 반응성, 그리고 전부를 한 덩어리로 맞물리게 하는 일이야. 엔진 선택이 지키려던 게 바로 그거고.
얼마나 진지했는지는 git 로그가 보여줘. 저장소가 21시 51분에 태어났어. 엔진 패키지가 22시 09분. 레이어, 블렌드 모드, 히스토리, .ariandel 패키지가 22시 14분. Metal 합성기가 22시 19분. 빌드 1이 22시 51분에 TestFlight로 갔고, 그때 이미 레이어가 있었어. 레이어 하나짜리 스케치북은 단 한 번도 배포된 적이 없어.
성장은 어디로 갔나
빌드 24 시점의 숫자도 같은 얘기를 해. libmypaint의 C 5,419줄이 든 디렉터리를 건드린 커밋은 167개 중 딱 하나야. 그걸 거기 넣은 커밋. Ariandel 자체 C 브리지는 커밋 22개가 손댔고 약 2,800줄로 컸어. 엔진 패키지의 Swift가 약 14,400줄, 앱이 약 17,500줄. 성장은 전부 Ariandel이 가진 쪽에서 일어났어. 건강한 국경은 안에서 보면 이렇게 생겼어.
이름
Ariandel이라는 이름은 다크 소울 3의 Painted World of Ariandel에서 왔어. 화가가 새로 그려내는 그림 속 세계야. 이 국경이랑 꽤 잘 맞아. 그리는 사람은 아빠야. 피파는 그 세계 안에 살면서 보고 얘기하지만, 대신 그려주지는 않아.