← 모든 글

픽셀 아트 인터페이스: 텍스트 상자, 폰트, 일본어

Pokémon Emerald의 대화 상자는 가로 30타일, 세로 6타일의 테두리로, 화면 너비 240픽셀 전체와 높이의 30%를 차지하며, 그 안의 27×4타일 텍스트 창에 16픽셀 폰트로 두 줄이 들어갑니다. Crystal의 상자는 20×6타일이고, 18자짜리 줄이 두 줄입니다.123 Emerald는 속도 설정에 따라 8, 4, 1프레임마다 한 글자를 찍으며, 60헤르츠에서 초당 7.5, 15, 60자입니다. Crystal의 가장 빠른 설정과 Final Fantasy VI도 한 프레임에 한 글자를 찍습니다.14 Emerald의 작가들은 모든 줄을 손으로 끊었습니다. 스크립트 텍스트 24,100줄 가운데 가장 넓은 줄은 216픽셀 창에 대해 208픽셀이고, 넘치는 줄은 하나도 없습니다.1 표준 문자 집합을 다루는 일본어 픽셀 폰트는 약 7,000자를 그립니다. JIS X 0208은 6,879자이고, Misaki는 그 전부를 8×8 셀에 그리며, PixelMplus10은 코드 포인트 세 개를 뺀 전부를 10픽셀 그리드에 그립니다. Final Fantasy Pixel Remaster의 프로듀서는 팀이 새로 만든 일본어 폰트를 “around 7,500 characters”(약 7,500자)라고 말했습니다.5678 제 Mac에서 폰트 스무딩을 끄자, Core Text는 그리드가 베이스라인 위에 있고 어드밴스가 정수 픽셀인 픽셀 TrueType 폰트(Misaki, PixelMplus10, Press Start 2P)를 그리드의 정수배에서는 중간 톤 픽셀 없이 그렸고, 그 사이 크기에서는 잉크가 칠해진 픽셀의 19.4%에서 82.2%를 번지게 했습니다. 그리드가 베이스라인에서 어긋난 PixelMplus12, 아웃라인에 벗어난 점이 있는 Silkscreen, 그리드가 없는 DotGothic16은 측정한 모든 크기에서 중간 톤이 나왔고, k8x12도 글리프를 정확한 위치에 놓자 모든 크기에서 중간 톤이 나왔습니다. 그리기 컨텍스트의 기본 스무딩에서는 PixelMplus10을 제 크기인 10픽셀로 그려도 잉크가 칠해진 272픽셀 중 178픽셀이 중간 톤이었습니다.79 3배 화면에서 10픽셀 em이 정수 기기 픽셀에 맞는 것은 iOS의 Body 크기 12단계 중 40포인트 하나뿐입니다.710 현재 Kiradex World는 모든 글자를 시스템 고정폭 서체로 조판하고, 영어만 말하며, VoiceOver에는 걷기 테스트용 좌표를 넘깁니다.11 그래서 원전의 상자, 폰트, 속도를 디컴파일에서 측정하고, 일본어 픽셀 폰트 다섯 종과 라틴 문자 픽셀 폰트 두 종을 Core Text에서 측정하고, 브리프를 썼습니다. 두 언어에 쓰는 라이선스된 서체 하나, 폰트의 그리드에 맞춰 반올림한 Dynamic Type, 포지가 그린 테두리, 두 줄과 페이지 넘김, 그리고 VoiceOver가 설명할 수 있는 무대입니다.

TL;DR

  • 상자는 화면 아래 3분의 1이고, 두 줄입니다. Emerald의 대화 테두리는 30×6타일(240×48픽셀, 화면 높이의 30%)이고, 그 안에 16픽셀 줄 두 개가 들어가는 27×4타일 창이 있습니다. Crystal의 상자는 20×6타일로 높이의 33%이며 18자짜리 줄이 두 줄이고, Red도 같은 20×6입니다. Emerald의 상자에는 이름 칸이 없으며, 스크립트 문단 14,412개 중 934개(6.5%)가 본문 안에서 화자의 이름을 밝힙니다.12312
  • 속도는 프레임 수입니다. Emerald와 FireRed는 한 글자에 8, 4, 1프레임(60헤르츠에서 초당 7.5, 15, 60자)이 걸리고, 찍는 도중에 A나 B를 한 번 누른 뒤에는 누르고 있는 동안 한 글자에 1프레임입니다. Crystal과 Red는 5, 3, 1프레임(초당 12, 20, 60자)을 기다립니다. Final Fantasy VI는 필드 대화를 한 프레임에 한 글자씩 그리며, 이에 대한 설정은 없습니다(6단계 메시지 속도는 전투 메시지가 기다리는 시간입니다). Emerald의 넘김 화살표는 쉬는 위치에서 0, 1, 2, 1픽셀로 9프레임마다 한 단계씩 움직이며, 한 주기는 0.6초입니다. Crystal의 ▼는 테두리 타일과 번갈아 깜빡입니다.1131415164
  • 게임에 필요한 일본어는 이미 무료 폰트 다섯 종이 그려 두었습니다. Misaki는 JIS X 0208의 6,879자 전부를 8×8로 그리며, 한자는 모두 7×7 안에 들어갑니다. k8x12는 8×12로 그립니다. PixelMplus12와 PixelMplus10은 코드 포인트 세 개를 뺀 전부를 12픽셀과 10픽셀로 그립니다. DotGothic16은 전부를 그리지만 픽셀 그리드 위에 있지 않습니다. Misaki, k8x12, PixelMplus의 세 문장짜리 라이선스는 사용, 복제, 배포를 “either commercially or noncommercially”(상업적이든 비상업적이든) 허락합니다.5671718
  • 픽셀 폰트가 선명한 것은 폰트 픽셀 하나가 정수 개의 기기 픽셀일 때뿐이며, 그것도 파일이 깨끗할 때만입니다. iPhone이 아니라 macOS 27.0의 Core Text에서, 폰트 스무딩을 끄고 측정했습니다. 그리드가 베이스라인 위에 있고 어드밴스가 정수 픽셀인 폰트(Misaki, PixelMplus10, Press Start 2P)는 그리드의 정수배에서 중간 톤 픽셀이 0.0%, 그 사이에서 19.4%에서 82.2%였습니다.7 그리기 컨텍스트의 기본 스무딩에서는 PixelMplus10을 제 크기인 10픽셀로 그려도 잉크가 칠해진 272픽셀 중 178픽셀이 중간 톤이었으므로, 정수배에서 깨끗하다는 결과는 스무딩을 껐을 때만 성립합니다.9 PixelMplus12의 그리드는 베이스라인보다 0.39픽셀 아래에 있어, 스무딩을 꺼도 모든 크기에서 번집니다. k8x12의 가나 어드밴스는 8.01픽셀이라 글리프를 정확한 위치에 놓으면 번집니다.719
  • Dynamic Type은 그대로 따르는 것이 아니라 반올림해야 합니다. iOS의 Body 크기 12단계(14에서 53포인트) 중 3배 화면에서 10픽셀 em이 정수가 되는 것은 40포인트뿐이고, 8픽셀 em은 16과 40, 12픽셀 em은 16, 28, 40입니다. Apple은 커스텀 폰트도 시스템 폰트가 가진 동작(Dynamic Type 포함)을 구현하라고 요구하며, 앱의 텍스트는 이상적으로 최소 200퍼센트까지 키울 수 있게 하라고 요구합니다.71020
  • 일본어 줄 바꿈은 태그가 없으면 엄격하고, 태그가 있으면 느슨합니다. macOS의 Core Text에서 131자짜리 문단을 나누었더니, 태그가 없을 때는 측정한 어느 너비에서도 프로브의 행두 목록에 있는 문자로 시작하는 줄이 없었습니다. 이 목록은 부분적인 것으로, 대부분 JLREQ의 문자이고 그중 일곱 자는 전각형입니다. ja 태그를 붙이면 Hiragino Sans, Misaki, PixelMplus, DotGothic16에서 너비 7개 중 6개에서 작은 가나가 줄 첫머리에 왔습니다. W3C의 일본어 조판 요구 사항이 설명하는 덜 엄격한 규칙입니다.721
  • Kiradex에 주는 것. 만든 것이 아니라 브리프입니다. 세계 위에 그리는 모든 글자에 PixelMplus10을 씁니다(일본어, 깨끗한 그리드, 정수 픽셀 어드밴스를 갖춘 측정된 두 서체 Misaki와 PixelMplus10 중 큰 쪽). 크기는 폰트 픽셀 하나가 정수 개의 기기 픽셀이 되도록 반올림합니다(3배 iPhone의 기본 크기에서 16.67포인트, 2배에서 15). 포지가 직접 그린 테두리, 두 줄과 페이지 넘김, 초당 30자 표시와 단계적으로 움직이는 표시. iOS 27.0 시뮬레이터가 나열하는 가장 좁은 iPhone에, 좌우 인셋이 없을 때 들어가는 한 줄 말풍선. 들어가지 않는 줄은 상자로 보내기. 일본어 줄은 일본어 화자가 검토하기. 그리고 VoiceOver가 좌표가 아니라 말로 설명하는 무대. 각각에 스크립트, 로그 한 줄, 캡처 중 하나로 합격과 불합격을 가를 수 있는 검사를 붙입니다. 예외는 수동 VoiceOver 검사 하나뿐입니다.2211

1. Emerald와 FireRed: 화면 너비 전체의 테두리, 두 줄, 손으로 끊은 줄

이 시리즈는 세계에서 시작해 안쪽으로 들어왔습니다. iPhone에서 만드는 픽셀 아트 세계는 타일 시스템과 RealityKit 레시피를 정했고, 텍스트에 대해서는 “Text is art”(텍스트는 아트다)라는 규칙 하나와 계획 한 문장을 내놓았습니다. “m5x7 at integer multiples in a 24-by-24 nine-slice box, two lines, typewriter reveal, a two-frame advance arrow.”(m5x7을 정수배로, 24×24 나인 슬라이스 상자에 두 줄, 타자기식 표시, 2프레임 넘김 화살표)입니다.23 그 뒤의 글들에서 수집가, 건물, 움직임, 마을의 생활, 방, 길이 만들어졌습니다. 텍스트는 그 한 문장에 머물러 있었습니다. 이 글은 그 숫자들이 어디서 오는지 측정하고, 세계 편 글이 틀렸거나 부정확한 곳 네 군데를 찾아내며, 그 글에 없던 것을 더합니다. 8, 10, 12도트의 일본어, Core Text가 픽셀 TrueType 폰트에 실제로 하는 일, Dynamic Type과 픽셀 그리드를 어떻게 맞출지, 일본어 줄 바꿈, 그리고 RealityKit 무대 위의 VoiceOver입니다.

Kiradex World의 텍스트는 지금 소박합니다. 글자는 모두 SwiftUI로, 테마의 시스템 고정폭 서체로 되어 있습니다. 수집가 머리 위의 말풍선은 키트의 caption 역할을 굵게 한 한 줄이며, 크림색 종이에 2포인트 잉크 테두리가 있고, 대사가 나온 지 4초가 지나기 전까지 표시됩니다. 장소의 설명은 화면 아래쪽 크림색 텍스트 상자에 들어 있습니다. 앱은 영어 전용이고, 프리셋 대사는 서버에 영어로 있으며, 포지의 간판은 글자 없이 잉크로 휘갈긴 자국만 있는 12×11 판입니다. VoiceOver는 무대를 하나의 요소로 읽으며, 그 값은 걷기 테스트의 걸음 기록입니다.11242526272829 자세한 내용은 10절에 있습니다. 12절의 브리프는 이 상태와의 차이로 쓰였습니다.

아래의 Pokémon 숫자는 모두 pret 디컴파일의 얕은 클론, pokeemerald 731ad5b(2026년 10월 1일), pokefirered 037335f, pokecrystal 5beda23, pokered d2704a6에서 Python 스크립트 하나, measure_gen_text.py로 측정했으며, 출력은 스크립트 옆에 저장해 두었습니다. Final Fantasy VI의 숫자는 everything8215/ff6 디스어셈블리에 대한 두 번째 스크립트에서 나왔습니다. 둘 다 몇 초 만에 다시 실행할 수 있으며, 각주에 숫자마다 근거가 된 소스 파일을 적었습니다.3014

폰트

Emerald는 폰트를 표 하나, src/text.c의 sFontInfos에서 선언하며, 각각에 글자의 최대 너비와 높이를 픽셀로 적어 둡니다. 스몰 폰트는 5×12, 대화 상자가 쓰는 노멀 폰트는 6×16, 쇼트 폰트는 6×14로 네 번 선언되어 있습니다(자신과 사본 셋). 내로 폰트는 5×16, 스몰 내로 폰트는 5×8, 볼드 폰트는 8×8, 점자 폰트는 8×16에 줄 간격 8픽셀입니다. 나머지 폰트는 모두 줄 간격이 0으로, 16픽셀 셀이 곧 한 줄입니다.113

노멀 라틴 문자 폰트는 비례폭입니다. 게임의 charmap.txt를 거쳐 src/fonts.c의 gFontNormalLatinGlyphWidths를 찾아보면, 소문자 a에서 z는 평균 5.77픽셀 너비(4에서 6), 대문자 A에서 Z와 숫자 0에서 9는 모두 6, 공백과 마침표는 3, 느낌표는 4입니다.131 실제 문장에서는, 아래에서 다룰 게임 스크립트의 모든 줄로 측정해 한 글자당 평균 5.25픽셀이 됩니다.1

속도는 프레임 수입니다

Emerald의 텍스트 속도는 프레임 단위의 지연입니다. 옵션 화면의 세 속도는 src/menu.c의 sTextSpeedFrameDelays를 통해 8(느림), 4(보통), 1(빠름)의 지연에 대응합니다. src/text.c의 AddTextPrinter는 그 지연에서 1을 뺀 값을 프린터의 속도로 저장합니다. 프린터는 글자를 찍을 때 카운터를 그 저장된 속도로 설정하고 한 프레임에 1씩 줄이며, 카운터가 0이 된 다음 프레임에 다음 글자를 찍습니다. 그래서 한 글자에 걸리는 것은 지연 그 자체, 8, 4, 1프레임입니다. 60헤르츠에서 초당 7.5, 15, 60자입니다.1213 이 셈은 프린터의 상태 기계(src/text.c의 AddTextPrinter와 RENDER_STATE_HANDLE_CHAR 케이스)에 대한 제 해석이며, 스크립트는 이를 실행하지 않고 단언합니다.1 Game Boy Advance는 정확히 60헤르츠로 돌지 않습니다. GBATEK은 프레임을 “Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz”(총 228라인, 16.743밀리초, 280896사이클, 약 59.737헤르츠)로 적으며, 그중 160라인이 보이고 각 라인은 240도트 너비입니다. 따라서 실기에서 가장 빠른 설정은 초당 약 59.7자입니다.3233

버튼을 누르고 있으면 느린 설정이 빨라집니다. 글자의 지연을 세는 동안 A나 B를 새로 누르면, 이를 허용하는 메시지(gTextFlags.canABSpeedUpPrint)에서는 hasPrintBeenSpedUp이 켜집니다. 그 뒤로 프린터는 JOY_HELD(A_BUTTON | B_BUTTON) && subStruct->hasPrintBeenSpedUp을 확인하고, 참이면 카운터를 0으로 떨어뜨립니다. 한 프레임에 한 글자, 초당 60자로, 가장 빠른 설정과 같은 속도입니다.131

자동 스크롤 텍스트에는 고유한 지연이 있습니다. gTextFlags.autoScroll이 켜져 있으면 프린터는 카운터를 저장된 속도가 아니라 3으로 설정해 한 글자에 4프레임을 씁니다. 단, 녹화된 배틀은 예외이며, 판정은 if (!(gBattleTypeFlags & BATTLE_TYPE_RECORDED) && gTextFlags.autoScroll)입니다.131 배틀 메시지는 통신 배틀과 녹화된 배틀에서 자동 스크롤을 켜고, messageautoscroll 스크립트 명령도 자동 스크롤을 켜며 보통 속도를 강제합니다.34 그래서 지연 3은 통신 배틀의 다른 배틀 창과 스크립트의 자동 스크롤 메시지에 적용될 수 있으며, 녹화된 배틀에서는 절대 적용되지 않습니다. 녹화된 배틀의 메시지는 프린터가 받은 속도로 찍힙니다. 배틀 메시지라면 녹화와 함께 저장된 텍스트 속도이지만, 녹화된 통신 배틀만은 통신 배틀처럼 메시지 창에 1이 주어집니다.341 제 해석으로는, 카운터는 프린터에 저장된 속도가 0보다 클 때만 돌아가며, 통신 배틀의 메시지 창에는 지연 1이 주어지므로 자동 스크롤이 켜져 있어도 그 텍스트는 한 프레임에 한 글자로 남습니다.1334

세 가지 줄 바꿈 코드는 각기 다른 일을 합니다. \n은 프린터를 둘째 줄로 내립니다. \l은 넘김 화살표 아래에서 버튼이 눌리기를 기다린 뒤 상자를 한 줄 위로 스크롤합니다. \p는 같은 방식으로 기다린 뒤 상자를 지우고 맨 위에서 다시 시작합니다.1335 스크롤할 때 16픽셀 줄은 속도 설정에 따라 한 프레임에 1, 2, 4픽셀씩 올라가므로(sWindowVerticalScrollSpeeds), 한 번 스크롤하는 데 16, 8, 4프레임이 걸립니다.113

60헤르츠에서의 초당 글자 수를 나타낸 가로 막대그래프: Emerald 느림 7.5, 보통 15, 빠름 60, A나 B를 누르고 있을 때 60. Crystal과 Red 느림 12, 보통 20, 빠름 60. Final Fantasy VI 필드 60. Kiradex 브리프가 제안하는 Slow 12와 Normal 30.

측정한 모든 게임의 텍스트 속도와 브리프가 제안하는 두 설정. 측정 출력에서 figures/make_figures.py로 그렸습니다.

화살표

페이지가 가득 차면 상자는 작은 화살표 아래에서 기다립니다. Emerald는 8×16 화살표 셀을 { 0, 1, 2, 1 }픽셀의 세로 오프셋(sDownArrowYCoords)으로 그리고, 8로 초기화되는 지연이 다 되면 다시 그립니다. 즉 9프레임마다 한 단계, 한 주기는 36프레임, 60헤르츠에서 0.60초입니다.113 화살표는 깜빡이지 않습니다. 세 위치를 오가며 위아래로 흔들립니다.

상자와 그 테두리

Emerald의 필드 텍스트 상자는 src/menu.c의 sStandardTextBox_WindowTemplates에 정의되어 있습니다. 텍스트 창은 타일 열 2, 행 15에 있고 크기는 27×4타일, 216×32픽셀로, 16픽셀 노멀 폰트 두 줄이 들어갑니다. 그 테두리는 WindowFunc_DrawDialogueFrame이 그리며, 스크립트가 그 창에 대해 평가해 보니 열 0에서 29, 행 14에서 19에 걸쳐 있었습니다. 30×6타일, 240×48픽셀로, 화면 너비 전체이자 160픽셀 높이의 30%입니다.12

이 테두리는 나인 슬라이스가 아닙니다. LoadMessageBoxGfx는 56×16픽셀, 14타일짜리 띠인 message_box.png에서 테두리를 불러오며, 테두리는 여덟 개의 타일 위치로 그려집니다. 맨 아래 줄은 맨 위 줄을 BG_TILE_V_FLIP으로 상하 반전한 것입니다.1362 스크립트가 측정하는 것은 테두리의 범위와 PNG의 크기이고, 반전은 채우기 호출에서 읽은 것입니다.1

플레이어가 고를 수 있는 테두리는 다른 창, 즉 메뉴와 예/아니요 상자를 위한 것입니다. WINDOW_FRAMES_COUNT는 20이고, 스무 가지 테두리는 각각 24×24픽셀, 8픽셀 타일 3×3의 PNG입니다. 옵션 화면의 FRAME 항목이 저장한 테두리 종류에 따라 LoadUserWindowBorderGfx가 불러옵니다.136

이로써 세계 편 글이 바로잡힙니다. 그 글의 규칙 10은 “Pokémon Emerald’s text-window frame is a 24-by-24 image, three by three hardware tiles, which is a nine-slice”(Pokémon Emerald의 텍스트 창 테두리는 24×24 이미지, 하드웨어 타일 3×3이며, 이것이 나인 슬라이스다)라고 말합니다.23 graphics/text_window/에 있는 24×24 이미지 스무 장은 메뉴 창에서 고를 수 있는 테두리입니다. 대화 상자, 즉 플레이어가 기억하는 텍스트 상자는 56×16픽셀 message_box.png에서 아래 가장자리를 반전한 30×6타일 테두리로 그려지며, 24×24 이미지의 나인 슬라이스가 아닙니다.136 나인 슬라이스라는 생각은 유효하고 메뉴는 그것을 씁니다. 대화 상자에 연결한 것이 잘못이었습니다.

같은 축척으로 8×8픽셀 타일의 그리드로 그린 두 화면: Emerald의 30×20타일에서 대화 테두리가 아래쪽 6행을 덮고, 그 안에 열 2, 행 15부터 27×4 텍스트 창이 있다. Crystal의 20×18타일에서 아래쪽에 20×6 상자가 있고, 그 안쪽이 18×4.

두 대화 상자를 타일 그리드로 그린 것으로, 게임 아트는 들어 있지 않습니다. figures/make_figures.py로 그렸으며, 각 범위를 measure_gen_text.out과 대조합니다.

텍스트는 상자에 맞춰 쓰였습니다

상자가 얼마나 차는지 보기 위해, 스크립트는 Emerald의 스크립트 텍스트를 한 줄도 빠짐없이 읽었습니다. 504개 파일, data/text/*.inc의 36개와 468개 맵의 scripts.inc이며, 플레이스홀더가 없는 줄이 24,100줄, 있는 줄이 3,000줄입니다. 게임의 세 줄 바꿈 코드 \n, \l, \p에서 나누었습니다. 줄의 중앙값은 29자, 150픽셀, 95번째 백분위수는 38자, 196픽셀, 가장 긴 줄은 53자, 가장 넓은 줄은 208픽셀입니다. 216픽셀 창보다 넓은 줄은 하나도 없으며, 평균 글자 너비는 5.25픽셀입니다.137

한 글자 5.25픽셀이면 창에 41자가 들어갑니다. 작가들은 95%의 줄을 38자에서 멈췄습니다.331 줄 바꿈은 모두 손으로 넣은 코드입니다. 게임은 줄을 접어 넘기지 않고, 작가를 믿습니다.

상자에는 창이 하나뿐이고 이름 칸이 없습니다. 화자를 밝히고 싶을 때 작가들은 줄 안에 이름과 콜론을 쳐 넣었습니다. Littleroot Town의 “MOM: {PLAYER}, we’re here, honey!”처럼 말입니다. 문단 14,412개 중 934개, 6.5%가 대문자 이름과 콜론으로 시작합니다.137 별도의 이름 칸이 없다는 것은 창 템플릿에 대한 제 해석입니다. 템플릿에는 텍스트 두 줄의 자리만 있고 그 밖에는 아무것도 없습니다.2

영어판 속의 일본어

영어판 Emerald에도 일본 카트리지에서 온 Pokémon과 트레이너의 이름을 위해 일본어 글리프가 들어 있습니다. 프린터는 제어 코드 EXT_CTRL_CODE_JPN에서 글리프 세트를 바꾸고 EXT_CTRL_CODE_ENG에서 되돌리며, 그런 이름에는 이 코드가 붙어 있습니다(international_string_util.c, contest.c, battle_tower.c가 넣습니다). 이 모드에서 노멀 폰트는 모든 글자가 너비 8픽셀, 높이 15픽셀입니다(DecompressGlyph_Normal, 그리고 GetGlyphWidth_Normal은 8을 돌려줍니다).11338

영어판 ROM이 빌드에 쓰는 시트는 다음과 같습니다. japanese_normal.png는 128×512픽셀로 8×16 셀로 나뉘며, 셀 512개 중 302개가 그려져 있습니다. japanese_small.png는 같은 크기에 307개. japanese_bold.png는 128×256으로, 256개 중 246개. 그리고 Emerald 트리에도 있는 FireRed의 japanese_frlg_male.png는 256×512픽셀로 16×16 셀로 나뉘며 512개 중 269개가 그려져 있고, 너비 표는 0에서 10픽셀까지 512개 항목이며 그중 408개가 10입니다.138 pret이 빌드하는 것은 영어판 게임이므로, 이것들은 영어판 ROM에서 이름에 쓰는 시트입니다. 일본어판 ROM 자체의 상자, 속도, 폰트는 읽지 않았습니다(11절).30

그러니 원전 자신의 기준으로 보면, 16픽셀 줄 안에서 일본어 한 글자는 Emerald에서 너비 8픽셀, FireRed의 시트에서는 대체로 10픽셀이며, 라틴 문자의 4에서 6에 비해 이만큼입니다.1

FireRed

FireRed는 Emerald의 지연 8, 4, 1을 이어받으며, Emerald와 마찬가지로 src/text_printer.c의 자체 AddTextPrinter가 1을 빼서 저장합니다. 표준 텍스트 창은 열 2에 있는 26×4타일(src/new_menu_helpers.c)로, Emerald보다 한 타일 좁습니다.139 프린터에는 Emerald와 같은 누르고 있기 처리도 있습니다. if (JOY_HELD(A_BUTTON | B_BUTTON) && subStruct->hasPrintBeenSpedUp)이 카운터를 0으로 떨어뜨리며, 글자의 지연을 세는 동안 A나 B를 새로 누르면, 이를 허용하는 메시지에서는 hasPrintBeenSpedUp이 켜집니다.14

2. Crystal과 Red: 18자와 깜빡이는 표시

Game Boy의 상자는 Game Boy Advance 상자의 조상을 더 낮은 해상도로 만든 것입니다. Crystal의 화면은 20×18타일(SCREEN_WIDTH 20, SCREEN_HEIGHT 18), 160×144픽셀입니다. 텍스트 상자는 TEXTBOX_WIDTH = SCREEN_WIDTH × TEXTBOX_HEIGHT 6, 즉 너비 전체에 높이의 3분의 1, 33%입니다. BORDER_WIDTH 2이므로 안쪽은 18×4타일이고, 8×8 폰트라면 한 줄에 18자입니다. 텍스트는 상자 안 두 번째 행에서 시작하고(TEXTBOX_INNERY = TEXTBOX_Y + 2), 줄과 줄은 두 행씩 떨어져 있으므로, 상자에는 사이에 빈 행을 둔 두 줄이 보입니다. 두 줄 배치라는 것은 이 상수들에 대한 제 해석입니다.13 Red의 메시지 상자는 db MESSAGE_BOX, 0, 12, 19, 17로, 아래쪽의 같은 20×6타일입니다.112

두 게임도 속도를 프레임으로 셉니다. 지연은 TEXT_DELAY_FAST 1, 중간 3, 느림 5프레임으로 한 글자이며, 60헤르츠에서 초당 60, 20, 12자입니다. Crystal의 PrintLetterDelay는 이를 분명히 말합니다. “The text speed setting in wOptions is actually a frame count”(wOptions의 텍스트 속도 설정은 사실 프레임 수다). A나 B를 누르고 있는 동안에는 한 글자에 1프레임을 기다립니다.115 그러니 Crystal의 가장 빠른 설정은 Emerald의 가장 빠른 설정과 같은 초당 60자이고, Emerald에서 A나 B를 누르고 있을 때도 같은 속도입니다. Crystal의 중간과 느림 설정, 20과 12는 Emerald의 15와 7.5보다 빠릅니다.1 Pan Docs는 Game Boy의 프레임을 “One frame: 70224 dots @ 59.7 fps”(한 프레임: 70224도트, 59.7fps)로 적고, 한 프레임이 “takes ~16.74 ms instead of ~16.67”(약 16.67밀리초가 아니라 약 16.74밀리초 걸린다)라고 덧붙입니다.40

Crystal은 플레이어가 FRAME_1부터 FRAME_8까지 여덟 가지 테두리 중에서 고르게 합니다. 모두 TEXTBOX_FRAME_TILES, 1비트 타일 6개이며, 24×16픽셀 PNG로 저장되어 있습니다. 아홉 번째 테두리도 ROM에 있지만 쓰이지 않습니다. 폰트 시트 gfx/font/font.png는 128×64픽셀, 8×8 셀 128개이며, Red의 것도 같은 크기입니다.141

표시는 깜빡입니다

Crystal의 넘김 표시는 테두리의 일부입니다. WaitPressAorB_BlinkCursor가 A나 B를 기다리는 동안, BlinkCursor는 테두리 자체의 맨 아래 행에 있는 (18, 17)의 타일을 '▼'와 테두리 타일 '─'로 번갈아 바꿉니다.16 두 상태는 코드에서 읽었지만, 깜빡임의 주기는 도출하지 않았습니다(11절).

이것이 세계 편 글의 두 번째 부정확한 점입니다. 그 글의 텍스트 계획은 “a two-frame advance arrow”(2프레임 넘김 화살표)를 요구합니다.23 두 상태짜리 표시는 Crystal의 깜빡임입니다. 그 계획의 나머지(16픽셀 줄, 아래쪽 상자)가 따르는 Emerald의 화살표는 깜빡이지 않습니다. 쉬는 위치에서 0, 1, 2, 1픽셀로 세 위치를 36프레임 주기로 움직입니다(코드는 비트맵의 소스 행을 옮기는데, 제 해석으로는 화면에서 쐐기가 올라갑니다. 실행해 보지는 않았습니다).113 12절의 브리프는 Emerald의 단계는 따르되 방향은 따르지 않습니다.

3. Final Fantasy VI: 다시 칠하는 창, 줄을 접는 폰트

Final Fantasy VI는 창을 플레이어의 것으로 다룹니다. 여기의 숫자는 everything8215/ff6 디스어셈블리 8130132(2026년 9월 14일)에서 나왔습니다. 이 디스어셈블리는 ROM의 그래픽이 아니라 코드와 추출 목록을 담고 있으므로, 크기는 바이트 범위와 그것을 읽는 코드에서 구했습니다.304

창 여덟 개, 각각 일곱 색

영어판과 일본어판 추출 목록은 모두 창 그래픽을 0xED0000에서 0xED1BFF까지 7,168바이트로 적습니다. 896바이트짜리 스타일 여덟 개이며, 각각 픽셀당 4비트인 8×8 타일 28개입니다. 팔레트는 0xED1C00에서 0xED1CFF까지 이어지며, 16색 팔레트 여덟 개입니다.442 설정 화면에는 창 패턴 8개가 나열됩니다(ConfigWindowNumPos).443

창의 색은 플레이어가 정합니다. LoadWindowPal은 패턴의 색을 저장 메모리 $1d57에서 패턴당 14바이트, 즉 2바이트 색 일곱 개씩 읽고, 설정 화면은 이를 R, G, B 막대로 편집합니다(ConfigColorBarTextList). 폰트용 색 하나가 더 $1d55에 있습니다.443 간격은 코드에서, 세 채널은 막대 목록에서 읽었습니다.4

설정 화면의 메시지 속도는 1에서 6까지 여섯 단계이고(ChangeConfigOption_02는 $05에서 멈춥니다), 새 저장은 menu_sram.asm의 주석대로 “message speed = 3, battle speed = 3”(메시지 속도 3, 전투 속도 3)으로 시작합니다.443 이것은 전투 메시지 속도이며, config.asm의 주석도 “battle speed, battle message speed, magic order, and window pattern”(전투 속도, 전투 메시지 속도, 마법 순서, 창 패턴)이라고 부릅니다. 설정 화면 밖에서 설정 바이트 $1d4d의 해당 비트를 읽는 코드는 src/btlgfx/gfx_cmd.asm의 mess_wait뿐입니다. 이것은 MsgSpeedTbl, 즉 “wait duration for each battle message speed”(전투 메시지 속도별 대기 시간)에서 설정을 찾아 그만큼의 프레임을 기다립니다. 설정 1에서 6까지 32, 48, 64, 80, 96, 112프레임입니다. 필드 코드에는 $1d4d를 읽는 파일이 하나도 없으므로, 필드 대화에는 속도 설정이 없습니다. 아래에서 다룰 하나의 속도로 찍힙니다.444

타일 없이 음영을 넣은 창

대화 창은 하드웨어의 색 연산으로 위에서 아래로 음영이 들어갑니다. src/field/hdma.asm의 DlgWindowFixedColorTbl은 $E0에서 $E7까지의 200바이트로, 세 채널 모두에 강도 0에서 7의 고정 색을 줍니다. 첫 구간은 값 72개로, 7에서 시작해 0까지 내려갔다가 다시 7까지 올라갑니다.445 창은 화면 위나 아래 근처에서 열립니다. event.asm의 주석으로는 “dialog window y position (1 or 18)”(대화 창의 y 위치, 1 또는 18)입니다.45 이 표를 스캔라인마다 값 하나씩 소비한다는 것은 주석에서 읽은 것이고 추적하지는 않았습니다. 그라데이션이 어떻게 보이는지도, 디스어셈블리에 픽셀이 없으므로 보지 못했습니다(11절).

폰트

대화 폰트는 픽셀당 1비트, 한 글자에 22바이트로, 16비트 행이 11행인 너비 16픽셀, 높이 11픽셀의 셀입니다. 그림자는 그려져 있지 않습니다. LoadLetterGfx가 각 행을 1픽셀 밀어서(lsr) 만듭니다.446 FontWidth에 따른 영어 글자 너비는 비례폭으로, 대문자는 평균 8.04픽셀(5에서 12), 소문자는 7.73(4에서 12), 숫자는 7.80(5에서 9), 공백은 5입니다.446

일본어판 ROM의 대화 폰트는 추출 목록에서 0xC494C0에서 0xC4FFFF까지 27,456바이트, 22바이트짜리 글자 1,248자입니다. 너비 표는 1,280개 항목으로, 12픽셀이 1,056개, 8이 180개, 12가 44개로 이어지며, 디스어셈블리의 문자 표에는 한자 1,010자와 가나 173자가 실려 있습니다.447 영어판 큰 폰트의 범위는 5,120바이트로, 22바이트 글자의 정수 개가 되지 않습니다(232.7). 이것은 디스어셈블리로는 설명할 수 없습니다(11절).4

한 줄, 줄 접기, 한 프레임에 한 글자

대화 한 줄은 x 4에서 최대 x $e0, 224까지로 220픽셀이며, 12픽셀 일본어라면 18자입니다.446 영어 텍스트는 코드로 단어 단위로 줄을 접습니다. CalcTextWidth가 다음 단어의 너비를 더하고, 최대를 넘을 것 같으면 NewLine을 부르므로, 영어 스크립트에는 손으로 넣은 줄 바꿈이 필요 없습니다. 스크립트는 호출을 단언하며, 해석은 UpdateDlgTextOneLine에 대한 것입니다.446

텍스트는 한 프레임에 한 글자씩 찍힙니다. 필드 루프는 수직 블랭크를 기다리고 한 바퀴에 한 번 UpdateDlgText를 부르며, UpdateDlgTextOneLine은 글자 하나를 그리고 돌아옵니다. 60헤르츠에서 초당 60자입니다.448 한 프레임에 한 바퀴라는 것은 이 루프에 대한 제 해석입니다. 하나의 소스에서 두 ROM을 모두 빌드하므로 두 언어는 이 루프를 공유하며, 제 해석으로는 영어와 일본어에서 초당 글자 수가 같습니다.48

맵 제목은 가운데 정렬되고(($e0 - width) / 2), 100프레임, 1.67초 동안 표시됩니다.446

Emerald와 나란히 놓으면, Final Fantasy VI는 같은 상자를 만드는 또 다른 방법을 보여 줍니다. Emerald는 모든 줄 바꿈을 손으로 쓰고, 플레이어에게 메뉴의 테두리만 고르게 하며 대화 상자의 테두리는 고르게 하지 않습니다. Final Fantasy VI는 코드로 줄을 접고, 플레이어가 창을 다시 칠하게 하며, 하드웨어로 음영을 넣습니다. Emerald의 필드 상자는 아래쪽에, Final Fantasy VI의 것은 위나 아래에 있으며, 둘 다 정해진 프레임 수로 찍습니다.

Chrono Trigger

Chrono Trigger도 Final Fantasy VI 옆에 놓으려 했지만 그러지 못했습니다. 공개된 디스어셈블리는 클론하지 않았고, Data Crystal의 이 게임 ROM 맵은 가져오려 하자 HTTP 403을 돌려주었으므로, 이 글의 텍스트 상자에 관한 내용은 어느 것도 거기서 오지 않았습니다(11절).49

4. 8, 10, 12도트의 일본어

Kiradex는 카드 수집가를 위한 앱이며, Dex는 이미 일본어 카드 이름을 아래에 영어를 곁들여 시스템 폰트로 보여 줍니다.50 세계의 텍스트에도 일본어가 필요할 것이므로, 일본어를 그리지 못하는 픽셀 폰트는 후보가 되지 못합니다. 문제는 한자를 얼마나 작게 만들 수 있는지, 그리고 7,000자를 이미 그려 둔 사람이 누구인지입니다.

일의 규모

JIS X 0208은 일본어 픽셀 폰트의 지원 범위를 재는 기준이 되는 문자 집합으로, Unicode 컨소시엄의 대응표에 따르면 6,879자입니다. 비한자 524자(그중 169자가 4구와 5구의 가나), 16구에서 47구의 제1수준 한자 2,965자, 48구에서 84구의 제2수준 한자 3,390자입니다.576 이 표는 버전 2.0이며 컨소시엄은 폐기된 대응표 사이에 두고 있지만, 폰트의 지원 범위를 세기에는 여전히 가장 다루기 쉬운 목록입니다.5

Misaki: 8×8

Num Kadoma가 만든 Misaki(美咲フォント)는 가장 작은 폰트입니다. 그 페이지는 이 폰트를 “8×8 ドットの日本語ビットマップフォント”(8×8도트 일본어 비트맵 폰트)라고 부르며 “JIS 第一・第二水準”(JIS 제1·제2수준)을 지원한다고 하고, “一部の記号を除いた全ての文字は 7×7 ドットの範囲に収まっている”(일부 기호를 뺀 모든 글자가 7×7도트 안에 들어간다)고 말합니다. 그래서 글자를 나란히 놓아도 줄 간격과 자간이 유지된다고 페이지는 이어 갑니다.51 페이지의 연혁에 따르면, PC-E500 포켓 컴퓨터의 일본어 표시 드라이버를 위해 만들어졌습니다.51

2021-05-05 릴리스의 BDF 파일로 측정하면, Misaki Gothic은 7,171자로, 반각 168자(너비 4픽셀)와 전각 7,003자(너비 8픽셀)이며, 8×8 셀에 어센트 6, 디센트 2입니다. U+4E00에서 U+9FFF까지의 글리프 6,356개(코드 범위이므로 JIS의 두 수준 한자 6,355자와는 수가 다릅니다) 중 어느 방향으로든 8도트 셀을 채우는 것은 하나도 없고, 전각 글리프 53개, 즉 기호들은 채웁니다. JIS X 0208의 6,879자를 모두 그립니다.6 Misaki Mincho는 페이지에 따르면 Misaki Gothic의 비한자를 명조체풍으로 다시 그린 것으로, 모든 수치가 같게 나왔습니다. Misaki Gothic 2nd는 페이지에 따르면 반각 글자에 3×7픽셀, 전각 가나에 7×7을 꽉 채워 쓰는 것으로, 7,170자(반각 167자), 어센트 7, 디센트 1이며 나머지는 같게 나왔습니다.651

k8x12: 8×12

같은 작가의 k8x12는 8도트 너비를 유지하면서 높이를 더합니다. “8×12 ドットの日本語ビットマップフォント”(8×12도트 일본어 비트맵 폰트)이며 JIS 제1·제2수준입니다. 변형이 둘 있는데, 가나를 세로로 길게 그린 k8x12L과, 비한자의 높이를 8도트로 한 k8x12S입니다. 후자는 페이지에 따르면 옛 게임기의 제약을 의식해 만든 것으로, “8×8 ドット単位でグラフィックを扱うような、昔のゲーム機等の制限を意識しています”(8×8도트 단위로 그래픽을 다루던 옛 게임기 등의 제약을 의식했다)라고 적혀 있습니다.52

BDF로 측정하면 k8x12는 8×12 셀에 어센트 10, 디센트 2이며, 7,170자로 4픽셀이 168자, 8픽셀이 7,002자입니다. 셀을 채우는 한자는 없고, 셀을 채우는 전각 글리프는 k8x12와 k8x12L에서 53개, k8x12S에서 45개입니다. 세 가지 모두 JIS X 0208의 6,879자를 전부 그립니다.6

PixelMplus: 10과 12, 아웃라인으로

itouhiro가 만든 PixelMplus(2013년)는 또 다른 접근입니다. 비트맵 폰트처럼 보이는 TrueType 아웃라인으로, 임베디드 비트맵이 없고(“埋め込みビットマップは なし”), JIS 제1·제2수준의 모든 한자를 담았으며(“JIS第1・第2水準のすべての漢字を収録”), ISO-8859-1도 포함합니다. 레귤러와 볼드 두 굵기, PixelMplus12와 PixelMplus10 두 크기가 있으며, M+ BITMAP FONTS에서 아웃라인화한 것입니다.18 Core Text로 측정하면 각각 글리프 7,253개이며, JIS X 0208의 6,879자 중 6,876자를 그립니다. 매핑하지 않은 세 개는 U+301C 〜, U+2016 ‖, U+2212 −입니다.7 이 문자들은 컨소시엄의 JIS0208.TXT와 다른 벤더의 표에서 Unicode 매핑이 서로 다른 문자이므로, 제 해석으로는 폰트가 아마 다른 코드 포인트에 그 글리프를 갖고 있을 것입니다. 확인하지는 않았습니다(11절).5

DotGothic16: 그리드 없는 도트 폰트

Fontworks의 DotGothic16은 Google Fonts에서 SIL Open Font License 1.1, 예약 폰트 이름 없이 공개되어 있으며, 서브셋은 cyrillic, japanese, latin, latin-ext이고, 2020-12-15에 추가되었습니다.53 글리프 9,362개를 갖고 JIS X 0208의 6,879자를 모두 그립니다.7 하지만 픽셀이 아니라 떨어진 도트로 그려져 있습니다. A의 아웃라인의 x 방향 가장자리는 쌍으로 나타나며, 1,000단위 중 91과 102, 153과 163, 214와 225, 276과 287, 338과 349, 400과 410입니다. 도트는 약 62단위 간격이고 그 사이에 10이나 11단위의 틈이 있습니다. 여기에 맞는 정수 픽셀 그리드는 없으며, 프로브는 em당 4에서 40픽셀 사이에서 그리드를 하나도 찾지 못했습니다.197 약 62단위의 피치는 서체 이름의 숫자대로 em당 16도트에 가까우므로, 프로브는 명목상 em당 16도트와 그 배수로 그립니다. 이것은 제가 고른 크기이지 서체가 선언한 그리드가 아닙니다. macOS 27.0의 Core Text는 이것을 16픽셀에서 82.3%, 64픽셀에서 37.3% 중간 톤으로 그렸고, 측정한 모든 크기에서 중간 톤이 나왔습니다(6절).7

무료 폰트의 측정값

표의 디자인 그리드는 샘플 글리프의 아웃라인 점 95%가 0.04픽셀 이내에 놓이는 em당 최소 픽셀 수이며, 축마다 일정한 오프셋 하나를 허용합니다. 이를 찾는 스크립트 measure_coretext.swift는 swiftc -O로 컴파일해 macOS 27.0(빌드 26A428)에서 실행했으므로, 이것은 iPhone이 아니라 Mac의 Core Text 수치입니다.7

폰트 글리프 수 비트맵 테이블 디자인 그리드(em당 픽셀) 라틴 문자 / 가나 어드밴스(픽셀) 그리는 JIS X 0208
Misaki Gothic 7,174 EBDT, EBLC 8, 베이스라인 위 4 / 8 6,879
k8x12 7,173 EBDT, EBLC 12, 베이스라인 위 4 / 8.01 6,879
PixelMplus10 Regular와 Bold 각 7,253 없음 10, 베이스라인 위 5 / 10 6,876
PixelMplus12 7,253 없음 12, 베이스라인보다 0.39픽셀 아래 6 / 12 6,876
DotGothic16 9,362 없음 없음(명목상 16도트로 그림) 8 / 16 명목 도트 6,879

이 기준으로 PixelMplus10 Regular는 샘플한 아웃라인 좌표 1,032개 중 10픽셀 그리드에서 벗어난 것이 0개, Bold는 1,000개 중 0개입니다. Misaki Gothic은 848개 중 0개, k8x12는 988개 중 0개, PixelMplus12는 0.39픽셀 오프셋을 허용하면 1,192개 중 0개이고, DotGothic16은 1,544개 전부가 벗어납니다.7 PixelMplus12의 오프셋은 원래 좌표에 드러납니다. H는 1,000단위 중 y −33에서 717까지이고, 픽셀 하나가 83.3단위인 그리드이므로, 베이스라인은 그리드 행 안의 0.396픽셀 지점에 놓입니다. 프로브의 추정치 0.39는 좌표의 소수 부분을 50분의 1픽셀 폭의 구간으로 나누어 구한 것으로, 아래 렌더링에서 이 값만큼 옮겼습니다.19733

측정한 일본어 지원 폰트에서는 모두 일본어 한 글자가 라틴 문자의 두 배입니다. Misaki와 k8x12에서 8 대 4, PixelMplus에서 10 대 5와 12 대 6입니다.7 원전도 같은 범위에 있습니다. Emerald의 일본어는 16픽셀 줄에서 너비 8픽셀, FireRed의 시트는 대체로 10, Final Fantasy VI는 12입니다.14

라이선스

이 모든 것은 아주 평이한 조건으로 무료입니다. 작가의 폰트 페이지에 있는 Misaki와 k8x12의 라이선스는 세 문장입니다. “These fonts are free software. Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially. THESE FONTS ARE PROVIDED “AS IS” WITHOUT WARRANTY.”(이 폰트들은 자유 소프트웨어다. 수정 여부와 상업적이든 비상업적이든 관계없이 사용, 복제, 배포를 무제한으로 허락한다. 이 폰트들은 보증 없이 “있는 그대로” 제공된다.) 각 압축 파일의 readme도 조금 다른 말로 같은 조건을 담고 있으며, Misaki 페이지는 자신이 쓰는 라이선스가 M+ FONTS 라이선스라고 밝힙니다.1751 PixelMplus는 M+ FONT LICENSE이며, 그 영문 LICENSE_E는 같은 세 문장입니다.18 DotGothic16은 SIL Open Font License 1.1이며, 조건은 동봉된 OFL.txt에 있습니다.53 제 해석으로는 세 문장짜리 라이선스는 대가로 아무것도 요구하지 않습니다. 그래도 작가를 밝히는 것이 옳으며, 브리프도 그렇게 합니다(12절). 이것들은 제 해석이며 법률 자문이 아닙니다.

가독성의 대가

Misaki의 가나는 너비 8폰트 픽셀, PixelMplus12에서는 12입니다. iPhone 18 Pro Max에서 세계 자체의 밀도, 즉 텍셀 하나가 기기 픽셀 8개일 때, Misaki로 그린 상자 한 줄에는 라틴 문자 36자나 일본어 18자가 들어갑니다.11 일본어 줄은 글자 하나에 담는 정보가 더 많으므로, 같은 말을 하는 데 상자에 같은 글자 수가 필요하지 않습니다. 이것은 제 해석이지 측정이 아닙니다.

5. 라틴 문자 픽셀 폰트, 그리고 측정하지 못한 것들

일본어 폰트와 함께 Google Fonts의 라틴 문자 픽셀 폰트 두 개를 측정했습니다.30

Press Start 2P는 CodeMan38이 만들었고, SIL Open Font License 1.1 “with Reserved Font Name “Press Start 2P”“(예약 폰트 이름 “Press Start 2P” 포함)입니다.54 베이스라인 위의 8픽셀 그리드에 글리프 658개를 갖고, 라틴 문자는 모두 너비 8픽셀이며, 출력 가능한 ASCII 95자를 모두 담고 있습니다. 가나와 한자는 없습니다. JIS X 0208 중 그리는 것은 비한자 150자(그리스 문자, 키릴 문자, 기호)이고, 한자 6,355자는 하나도 그리지 않습니다.7

Silkscreen은 Jason Kottke가 만들었고, SIL Open Font License 1.1, 예약 폰트 이름 없음입니다.55 8픽셀 그리드에 글리프 228개를 갖지만, 샘플한 아웃라인 좌표 588개 중 10개가 그리드에서 벗어납니다(A와 o에는 125단위 그리드에 대해 y 값 495와 130이 있습니다). A의 너비는 6픽셀입니다. JIS X 0208 중 그리는 것은 비한자 21자입니다.719

픽셀 게임에 자주 추천되는 폰트가 세 개 더 있지만 측정하지 않았습니다. 기억에 기대어 설명하는 대신, 측정하지 않았다고 밝혀 둡니다.

Pixel Operator는 Jayvee Enaguas가 만들었습니다. DaFont에 있는 작가의 메모에는 “Changed licence to Creative Commons Zero (CC0) 1.0”(라이선스를 CC0 1.0으로 변경)이라고 적혀 있고, 서체는 “licensed under a Creative Commons Zero (CC0) 1.0”(CC0 1.0으로 라이선스됨)이라고 되어 있습니다. DaFont는 이를 “Bitmap (16 px)”로 분류하며, 폰트 파일 15개, 글리프 241개라고 적습니다. 메모가 알려 주는 NotABug의 작가 저장소는 HTTP 404를 돌려주었고, 폰트는 내려받지도 측정하지도 않았습니다. 여기 적은 것은 모두 그 한 페이지에서 나온 것입니다.56

m5x7은 Daniel Linssen이 만들었습니다. itch.io 페이지에는 “use font size 16, 32, 48, etc”(폰트 크기는 16, 32, 48 등을 쓸 것), “free to use but attribution appreciated”(무료로 쓸 수 있지만 출처 표기를 해 주면 고맙겠다)라고 적혀 있고, “Asset license Creative Commons Zero v1.0 Universal”이 표시되어 있으며, 평균 너비 5, 어센더 7, 디센더 2, x하이트 5라고 합니다. 글자에 대해서는 “uppercase and lowercase letters, including many accented letters, numbers and most common symbols”(많은 악센트 문자를 포함한 대문자와 소문자, 숫자, 흔히 쓰는 기호 대부분)라고 설명합니다.57

m3x6는 같은 작가의 것으로, “use font size 16, 32, 48, etc”와 “free to use with attribution”(출처를 표기하면 무료로 쓸 수 있다)이라고 적혀 있고, 페이지에 에셋 라이선스 항목이 없습니다.58 두 파일 모두 원하는 금액을 내는 다운로드 뒤에 있어서 내려받지 않았으므로, 둘 다 측정하지 않았습니다.5758

이것이 세계 편 글의 세 번째 부정확한 점입니다. 그 글의 텍스트 계획은 서체로 m5x7을 지목합니다.23 페이지 자신의 설명에 따르면 m5x7은 라틴 문자 폰트이며, 가나나 한자가 있다는 말은 페이지 어디에도 없습니다. 일본어를 보여 줄 앱은 세계의 텍스트를 이것으로 조판할 수 없습니다.57 브리프는 서체를 PixelMplus10으로 바꿉니다. 일본어, 깨끗한 그리드, 정수 픽셀 어드밴스를 갖춘 측정된 두 서체 중 하나(다른 하나는 Misaki)이며, 둘 중 큰 쪽입니다(6절과 12절). m5x7 자체는 여전히 측정되지 않은 상태입니다.

6. Core Text가 픽셀 폰트에 하는 일

세계 편 글은 “Bitmap fonts render at integer multiples only”(비트맵 폰트는 정수배로만 렌더링된다)라고 단언했습니다.23 픽셀 아트 안내서마다 되풀이하는 규칙이며, 저는 그것을 Apple 자신의 텍스트 엔진에서 숫자와 함께 확인하고 싶었습니다.

측정

Core Text는 Apple이 “Create text layouts, optimize font handling, and access font metrics and glyph data”(텍스트 레이아웃을 만들고, 폰트 처리를 최적화하고, 폰트 메트릭과 글리프 데이터에 접근하는) 계층으로, iOS 3.2와 macOS 10.8부터 쓸 수 있습니다.59 measure_coretext.swift는 후보 TrueType 파일을 하나씩 불러와, 샘플 한 줄(라틴 문자 폰트에는 “Nice card! Which set? 0123”, 일본어 폰트에는 “カードを見せて! Nice card!”)을 각 폰트 그리드의 정수배와 비정수배 크기로 8비트 회색 비트맵에 그리고, 잉크가 칠해진 픽셀 중 중간 톤인 것, 즉 255 중 8에서 247로 완전한 잉크나 빈 곳에서 3% 넘게 떨어진 것을 셉니다. 크기마다 세 가지 방식으로 그립니다. Core Text의 기본 안티앨리어싱, 안티앨리어싱 끔, 그리고 서브픽셀 위치 지정을 켜고 양자화를 꺼서 글리프가 어드밴스가 가리키는 위치에 정확히 놓이게 한 것입니다.7

숫자를 보기 전에 두 가지 한계가 중요합니다. 실행 환경은 iOS가 아니라 제 Mac의 macOS 27.0, 빌드 26A428이며, SwiftUI의 Text가 아니라 Core Text를 통해 비트맵 컨텍스트에 그립니다. iPhone이 무엇을 그릴지는 브리프의 캡처 검사를 iPhone에서 실행하기 전까지는 예측입니다(11절).7 그리고 스크립트는 폰트 스무딩을 끈 채로 그립니다(setAllowsFontSmoothing(false)). 아래 그림을 비트맵 컨텍스트의 기본값으로 그렸더니, 10픽셀 PixelMplus10으로 쓴 “Nice card!”의 잉크 픽셀 272개 중 178개가 중간 톤이었습니다. 스무딩을 껐을 때는 94개 중 0개였습니다.9 폰트 스무딩은 macOS의 설정입니다. iOS에도 비슷한 것이 적용되는지는 캡처 검사가 정할 일입니다.

정수배는 선명합니다

그리드의 정수배에서, 안티앨리어싱을 켜고(컨텍스트의 기본값) 폰트 스무딩을 끄면, Misaki의 8, 16, 24, 32픽셀, PixelMplus10 Regular와 Bold의 10, 20, 30, 40픽셀, Press Start 2P의 8, 16, 24, 32픽셀은 모두 중간 톤 픽셀 0.0%로 렌더링됩니다.7 모든 픽셀이 잉크 아니면 종이입니다. 이것은 스무딩 설정에 달려 있습니다. 비트맵 컨텍스트의 기본 스무딩에서는 PixelMplus10을 제 크기인 10픽셀로 그려도 “Nice card!”의 잉크 픽셀 272개 중 178개가 중간 톤이었으므로, Mac에서는 정수배만으로 텍스트가 선명해지지 않았습니다.9

PixelMplus10 Regular를 10픽셀로, 안티앨리어싱을 끄고 그려 그리드 위에서 아홉 배로 확대한 것: 50×11폰트 픽셀의 Nice card! 줄과 80×11의 일본어 줄 カードを見せて!. 베이스라인은 맨 아래에서 두 행 위에 있다.

macOS 27.0의 Core Text로, 폰트 픽셀 하나를 기기 픽셀 하나로 그린 PixelMplus10 Regular. 안티앨리어싱을 끈 상태로 보여 줍니다. 안티앨리어싱을 켜고 폰트 스무딩을 끄면 “Nice card!”의 잉크 픽셀 94개 중 중간 톤은 0개이고, 비트맵 컨텍스트의 기본 스무딩에서는 272개 중 178개입니다. 이 폰트는 M+ FONT LICENSE로, 복제와 배포가 허락됩니다. figures/dump_glyphs.swift와 figures/make_figures.py로 그렸습니다.

그 사이에서는 번집니다

정수배 사이에서는 같은 폰트들이 번집니다. 프로브 전체와 마찬가지로 스무딩은 꺼져 있습니다. Misaki는 12픽셀(그리드의 1.5배)에서 57.7%, 20(2.5배)에서 39.9%, 10(1.25배)에서 82.2%가 중간 톤입니다. PixelMplus10은 15픽셀에서 60.1%입니다. Press Start 2P는 12픽셀에서 44.0%, 20에서 29.1%, 28에서 21.7%입니다.7 그리드가 베이스라인 위에 있는 여덟 서체(Misaki 두 종, k8x12와 k8x12S, PixelMplus10 Regular와 Bold, Press Start 2P, Silkscreen)를 그리드의 1.25, 1.5, 1.75, 2.5, 3.5배로 그린 렌더링 40개 전체에서, 중간 톤 비율은 19.4%(PixelMplus10 Bold, 35픽셀)에서 82.2%(Misaki, 10픽셀)까지입니다.7 번짐은 크기에 따라 꾸준히 줄지 않습니다. 여덟 서체 모두에서 그리드의 1.75배가 1.5배보다 더 번집니다(Misaki는 14픽셀에서 65.7%, 12픽셀에서 57.7%). 같은 소수 부분끼리라면 줄어듭니다. 그리드의 1.5, 2.5, 3.5배에서는 여덟 서체 모두 단계마다 낮아집니다(Misaki는 57.7%, 39.9%, 30.4%). 정수배 사이에서 측정한 어떤 렌더링에서도 0에 이르지 않았습니다. 제 해석으로는, 소수 부분을 고정했을 때 줄어드는 것은 글리프 가장자리의 반 픽셀 오차가 큰 글리프에서 차지하는 비율이 더 작기 때문입니다.7

안티앨리어싱을 끄면(setShouldAntialias(false)) 모든 크기에서 모든 픽셀이 완전한 잉크나 빈 곳이 되지만, 모양은 고르지 않게 다시 샘플링됩니다. Misaki의 8픽셀에서의 잉크 픽셀 191개는 10픽셀에서 377개가 됩니다. 각 방향으로 고르게 1.25배 하면 약 298개여야 합니다.733 두 배가 되는 행이나 열과 그렇지 않은 행이나 열이 있어서, 1픽셀 너비였던 획이 1픽셀이 되기도 하고 2픽셀이 되기도 합니다.

폰트 스무딩을 끄고, 중간 톤 픽셀 비율을 각 폰트 그리드에 대한 배율 1에서 4에 걸쳐 나타낸 꺾은선그래프: Misaki, PixelMplus10 Regular, Press Start 2P는 정수배마다 0%로 떨어지고, 그 사이에서 21.7%(Press Start 2P, 3.5배)에서 82.2%(Misaki, 1.25배)까지 오른다. PixelMplus12는 0%에 이르지 않으며, 1배의 66.6%에서 4배의 23.6%까지. 글리프를 정확히 놓은 k8x12도 0%에 이르지 않으며, 1배의 62.3%에서 4배의 25.9%까지.

크기별 중간 톤 픽셀. iOS가 아니라 macOS 27.0의 Core Text에서, 폰트 스무딩을 끈 상태. measure_coretext.out에서 figures/make_figures.py로 그렸습니다.

선명한 크기만으로는 부족합니다: 파일이 깨끗해야 합니다

폰트를 고를 때는 배율보다 덜 눈에 띄는 두 가지 실패가 더 중요합니다.

베이스라인에서 어긋난 그리드는 모든 크기에서 번집니다. PixelMplus12의 아웃라인은 베이스라인 그리드보다 0.39픽셀 아래에 있어, 제 크기인 12픽셀에서 66.6%, 24에서 40.0%, 36에서 29.8%가 중간 톤으로 렌더링됩니다. 서브픽셀 위치 지정을 켜고 컨텍스트의 변환으로 베이스라인을 오프셋만큼 옮기면 12픽셀은 0.0%가 되지만, 24에서는 14.3%, 36에서는 19.0%가 남습니다. 옮기는 양을 크기에 맞춰 키웠는데도 그렇습니다(24에서 0.78픽셀, 36에서 1.17).7 키운 이동량이 왜 큰 크기를 깨끗하게 하지 못하는지는 출력이 말해 주지 않으며, 저도 추적하지 않았습니다.

그리드에서 벗어난 어드밴스는 글리프를 정확한 위치에 놓으면 번집니다. k8x12의 가나 어드밴스는 8이 아니라 8.01픽셀입니다. 글리프를 정수 픽셀에 놓는 비트맵 컨텍스트의 기본 설정에서는 12, 24, 36픽셀에서 중간 톤 0.0%로 렌더링됩니다. 서브픽셀 위치 지정을 켜고 양자화를 끄면, 같은 크기들이 62.3%, 42.8%, 32.6% 중간 톤이 됩니다.7 제 해석으로는, 그때 가나는 한 글자씩 갈 때마다 앞 글자보다 100분의 1픽셀씩 그리드에서 멀어집니다.

아웃라인의 벗어난 점은 조금 남습니다. Silkscreen의 그리드 밖 좌표 10개는 8픽셀에서 4.5%, 32에서 1.2%의 중간 톤으로 나타납니다.7

도트 폰트는 절대 맞춰지지 않습니다. DotGothic16에는 배수를 잡을 그리드가 없으므로, 프로브는 명목상 em당 16도트와 그 1.25에서 4배로 그립니다(4절). 안티앨리어싱을 켜고 스무딩을 끄면 16픽셀에서 82.3%, 32에서 52.5%, 48에서 48.8%, 64에서 37.3%가 중간 톤이며, 16의 정수배를 포함해 16에서 64픽셀까지 아홉 크기 모두에서 중간 톤이 나옵니다. 도트가 어떤 픽셀 그리드에도 놓여 있지 않습니다(4절).719

Misaki와 k8x12는 임베디드 비트맵 테이블(EBDT와 EBLC, CTFontCopyAvailableTables로 읽음)을 갖고 있습니다. 위의 측정 결과는 Core Text가 그린 것이지만, 그 비트맵을 그렸는지 아웃라인을 그렸는지는 확인하지 않았습니다(11절).7

Misaki, PixelMplus10, Press Start 2P에는 덜 눈에 띄는 결함이 둘 다 없습니다. 여기서 나오는 규칙은 크기만큼이나 폰트 파일에 관한 것입니다. 정수배, 베이스라인 위의 그리드, 정수 픽셀 어드밴스입니다.

Dynamic Type과 그리드

Apple의 Human Interface Guidelines는 iOS의 기본 텍스트 크기를 17포인트, 최소를 11포인트로 정하고, Dynamic Type의 모든 크기 표를 싣고 있습니다. Body는 xSmall에서 AX5까지 14, 15, 16, 17, 19, 21, 23, 28, 33, 40, 47, 53포인트로 나아갑니다. Caption 1은 11에서 43입니다.6010

3배 화면에서 폰트 픽셀 하나가 정수 개의 기기 픽셀이 되는 것은, 포인트 크기에 3을 곱해 폰트 픽셀 단위의 em으로 나눈 값이 정수일 때뿐입니다. Body 크기 12단계 중 12픽셀 em이 정수가 되는 것은 3개(16, 28, 40포인트), 8픽셀 em은 2개(16과 40), 10픽셀 em은 1개(40)입니다. Caption 1의 12단계 중에서는 12픽셀 em이 정수가 되는 것이 3개(12, 16, 32)입니다.760

xSmall에서 AX5까지, 14에서 53포인트인 iOS Body 크기 12단계의 그리드: 3배 화면에서 8픽셀 em이 정수가 되는 곳(16과 40포인트), 10픽셀 em(40만), 12픽셀 em(16, 28, 40)을 채운 점으로 표시. 그 아래에 PixelMplus10에 대해 브리프가 반올림한 배율 m(0.5는 올림) 4, 5, 5, 5, 6, 6, 7, 8, 10, 12, 14, 16과, 그것이 주는 포인트 크기 13.33에서 53.33.

3배 화면에서 정수 기기 픽셀에 맞는 Body 크기와 브리프의 반올림. measure_coretext.out과 measure_kiradex_text.out에서 figures/make_figures.py로 그렸습니다.

그러니 픽셀 폰트는 Dynamic Type을 그대로 따를 수 없습니다. 시스템의 크기에 맞춰 연속적으로 키우면, 정수가 되는 몇몇 크기를 빼고는 모두 그리드에서 벗어납니다. 10픽셀 em이면 Body 크기 12단계 중 11개, 12픽셀 em이면 12개 중 9개입니다.733 그리드에서 벗어나면, 안티앨리어싱을 켠 Mac의 Core Text는 측정한 모든 서체를 번지게 했습니다. iPhone의 SwiftUI도 그러할지는 그 렌더링과 배치에 달려 있으며, 그것을 정하는 것은 브리프의 캡처입니다. 시스템이 요청하는 크기를 받아 그리드에 맞춰 반올림해야 하며, 10절은 이를 API 레시피로, 12절은 브리프의 규칙으로 만듭니다.

7. 일본어 줄 바꿈

짧은 프리셋 대사는 줄을 바꾸지 않습니다. 상자에 들어가는 글은 줄을 바꾸며, 일본어에는 어디서 바꿀지에 대한 규칙이 있습니다.

규칙

W3C의 Requirements for Japanese Text Layout(2020년 8월 11일의 Working Group Note)은 3.1.7에서 행두 규칙을 정합니다. “In principle, no line should begin with closing brackets (cl-02), hyphens (cl-03), dividing punctuation marks (cl-04), middle dots (cl-05), full stops (cl-06), commas (cl-07), iteration marks (cl-09), a prolonged sound mark (cl-10), small kana (cl-11) or warichu closing brackets (cl-29)”(원칙적으로 닫는 괄호, 하이픈, 구분 문장 부호, 가운뎃점, 마침표, 쉼표, 반복 부호, 장음 부호, 작은 가나, 와리추 닫는 괄호로 줄을 시작하지 않는다). 더 느슨한 관행도 기록합니다. “Not a small number of books adopt a less strict set of rules which allow IDEOGRAPHIC ITERATION MARK “々” (one of the iteration marks (cl-09)), prolonged sound mark (cl-10) and small kana (cl-11) to start a line.”(적지 않은 책이 々, 장음 부호, 작은 가나가 줄을 시작하도록 허용하는 덜 엄격한 규칙을 쓴다.)21

Unicode의 줄 바꿈 알고리즘 UAX #14(리비전 57, Unicode 18.0.0용, 2026-09-01 자)는 작은 히라가나와 가타카나, 장음 부호를 클래스 CJ에 넣고 이렇게 말합니다. “Treating characters of class CJ as class NS will give CSS strict line breaking; treating them as class ID will give CSS normal breaking.”(클래스 CJ 문자를 클래스 NS로 다루면 CSS strict 줄 바꿈이 되고, 클래스 ID로 다루면 CSS normal 줄 바꿈이 된다.)61

Core Text가 한 일

measure_coretext.swift는(iOS가 아니라 macOS 27.0에서 실행) 131자짜리 일본어 문단 하나를 CTFramesetter로 8, 10, 12, 14, 16, 18, 20em 너비에 조판하고, 둘째 줄부터 각 줄의 첫 글자를 자체 행두 금칙 문자 목록과 대조합니다. 목록에 든 것은 닫는 괄호 일부, 일본어 쉼표와 마침표, 가운뎃점, 콜론과 세미콜론, 물음표와 느낌표, 장음 부호, 작은 가나, 그리고 々입니다.7 이 목록은 JLREQ 규칙이 꼽는 클래스의 부분집합이 아니라, 그것과 겹칠 뿐입니다. 40자 중 일곱 자, ,.:;?!와 )는 JLREQ가 ASCII 문자를 꼽는 자리에 일본어 글이 쓰는 전각형이고, 나머지 33자는 JLREQ 자신의 문자입니다. 닫는 괄호 중에서는 」』】〉》〕와 전각 )를 담고, ’ ” ] } » 〟 ⦆ 〙 〗와 ASCII )는 빠져 있습니다. 작은 가나 40자 중 22자를 담고, ゕ, ゖ, ㇰ에서 ㇿ까지는 빠져 있습니다. 하이픈(cl-03)은 ‐, 〜, ゠, en 대시를 포함해 빠져 있고, cl-04의 ‼ ⁇ ⁈ ⁉도, 々 말고는 반복 부호(cl-09) 전부, 이를테면 ヽ, ヾ, ゝ, ゞ, 〻도 빠져 있습니다.721 아래 결과는 프로브의 목록에 대한 것이며, 어떤 문자가 줄 첫머리에 올 수 있는지는 테스트 문단의 글이 정합니다.

태그가 없을 때는, 시스템의 일본어 서체인 Hiragino Sans(대조군으로 실행)에서도, 여덟 픽셀 서체 어디에서도, 어느 너비에서도 프로브 목록의 문자로 시작하는 줄이 없었습니다. 여덟 서체는 Misaki Gothic, Misaki Gothic 2nd, k8x12, k8x12S, PixelMplus10 Regular와 Bold, PixelMplus12, 그리고 명목상 16도트로 조판한 DotGothic16입니다.7

kCTLanguageAttributeName을 ja로 설정해 태그를 붙이자 규칙이 느슨해졌습니다. Hiragino Sans, Misaki 두 종, PixelMplus10 두 종, PixelMplus12, DotGothic16에서는 너비 7개 중 6개, 즉 18em을 뺀 모든 너비에서 작은 가나(っ, ョ, ァ)로 시작하는 줄이 있었습니다.7 k8x12와 k8x12S에서는 너비 7개 중 4개에서 작은 가나(っ, ョ, ょ)로 시작하는 줄이 있었고, 장음 부호 ー로 시작하는 줄이 두 너비, 10em(ョ와 같은 너비)과 18em에 있어서, 18em이 다섯 번째 너비가 되었습니다.7

두 인용문에 비추어 본 이 숫자들에 대한 제 해석으로는, ja 태그는 JLREQ가 설명하는 덜 엄격한 규칙, UAX #14가 CSS normal 줄 바꿈이라고 부르는 것을 주고, 태그가 없는 실행은 엄격하게 동작합니다.2161 태그는 언어를 지정하는 것이므로 제 예상과는 반대였고, 브리프가 캡처 없이 상자 텍스트에 태그를 붙이지 않는 이유가 이것입니다.

측정 자체에 대해, 스크립트를 출력과 맞대어 읽으며 알게 된 주의점이 둘 있습니다. 너비는 폰트의 전체 크기인 em 단위이므로, 가나가 12픽셀 em 안에서 8픽셀인 k8x12에서는 각 너비에 들어가는 가나가 숫자가 말하는 것의 1.5배입니다. k8x12의 문단은 8em에서 12줄이 되었고 다른 서체들은 17줄이었습니다.7 그리고 그리드가 없는 DotGothic16은 명목상 16도트의 두 배, em당 32픽셀로 조판했습니다. 그 크기에서는 모든 너비에서 Hiragino Sans와 같은 줄 수(8em의 17줄에서 20em의 7줄까지)가 되었고, 태그를 붙이자 같은 작은 가나가 줄 첫머리에 왔습니다.7

8. 그 뒤의 픽셀 게임들: 상자, 말풍선, 일본어와 중국어

Stardew Valley: 두 가지 말하기 방식

Stardew Valley는 Kiradex가 이미 가진 두 형태를 모두 갖추고 있습니다. 대화 상자는 명령 $b로 페이지를 넘기는데, Stardew Valley Wiki의 대화 모딩 페이지(2026년 5월 20일 마지막 수정)는 이를 “Indicates pauses in dialogue, where the player will need to click for the next part to load in a new dialogue box”(대화의 멈춤을 나타내며, 플레이어가 클릭해야 다음 부분이 새 대화 상자에 불러와진다)라고 설명합니다. 초상화 명령은 “set the portrait shown in the dialogue box for the current line of dialogue”(현재 대사에 대해 대화 상자에 보이는 초상화를 설정한다)는 것입니다.62 그리고 말풍선 Strings\SpeechBubbles가 있는데, 이는 “dialogue spoken by NPCs through speech bubbles when the player enters a given location”(플레이어가 특정 장소에 들어설 때 NPC가 말풍선으로 하는 대사)을 위한 것입니다.62 상자는 얼굴을 앞세운 대화를 위한 것이고, 말풍선은 걸어 들어설 때 마을이 건네는 말을 위한 것입니다.

라틴 문자가 아닌 언어에는 비트맵 폰트를 씁니다. 위키의 사용자 지정 언어 페이지(2026년 1월 3일 마지막 수정)에서는 새 언어가 UseLatinFont를 false로 두고 .fnt 파일을 제공하며, “the Chinese, Japanese, Korean, and Russian fonts in the Fonts folder”(Fonts 폴더의 중국어, 일본어, 한국어, 러시아어 폰트)를 본보기로 삼습니다. FontPixelZoom은 “A factor by which to multiply the font size. The recommended baseline is 1.5”(폰트 크기에 곱하는 계수. 권장 기준값은 1.5)이고, FontApplyYOffset은 “Whether to shift the font up by four pixels”(폰트를 4픽셀 위로 올릴지 여부)를 정하며, “languages with larger characters like Chinese and Japanese”(중국어와 일본어처럼 글자가 큰 언어)를 맞추기 위한 것입니다.63 1.5배는 정수배가 아니지만, 페이지는 이를 모더가 추가하는 언어의 출발점으로 제시하는 것이지, 출시된 중국어, 일본어, 한국어 폰트가 쓰는 값으로 제시하는 것이 아닙니다. 또한 배율이 화면에서 다시 샘플링되는지는 게임의 기기 픽셀에 대한 배율에도 달려 있습니다. 그러니 Stardew가 자신의 일본어를 어떻게 그리는지에 대해서는 확실하게 말해 주는 것이 없습니다.63

Final Fantasy Pixel Remaster: 폰트가 곧 작업이었습니다

Pixel Remaster의 콘솔판은 픽셀 폰트 옵션을 추가했고, 프로듀서 Naofumi Takuma가 Siliconera(Jenni Lada, 2023년 6월 2일)에 그 제작 과정을 이야기했습니다. 서양 언어는 사내에서 만든 폰트 데이터를 찾아 썼습니다. 일본어는 이렇습니다. “We didn’t have any alternative data saved on file in-house for the Japanese language font, so we decided to create a new one.”(일본어 폰트는 사내에 대체할 데이터가 저장되어 있지 않아서 새로 만들기로 했다.) 그리고 “While the Western language font required us to create only a few hundred characters, with the Japanese font, we had to create around 7,500 characters.”(서양 언어 폰트는 수백 자만 만들면 됐지만, 일본어 폰트는 약 7,500자를 만들어야 했다.)8

이것이 세계 편 글의 네 번째 부정확한 점입니다. 그 글은 같은 숫자, “about 7,500 Japanese characters”(일본어 약 7,500자)를 들며 그 작업에 대한 GoNintendo의 보도를 출처로 삼았습니다. 1차 출처는 프로듀서가 직접 Siliconera와 한 인터뷰이며, 거기에는 “around 7,500 characters”라고 되어 있습니다.238 숫자는 같고, 출처가 나아졌습니다.

7,500자는 일반적인 지원 목표인 JIS X 0208의 6,879자와 대략 같은 규모입니다. 이 비교는 제 것입니다.533 출시 전에 모든 대사가 확정되는 게임이라면, Final Fantasy VI의 일본어판 ROM이 대화용으로 1,248자로 해결했듯이(3절) 더 적은 글자로 충분하며, 그 경우에는 모든 문자열을 폰트와 대조해야 합니다. 전체 집합을 다루는 폰트라면 거기서 그리는 어떤 문자열에도 그런 대조가 필요 없습니다.4 작은 앱에 주는 교훈은 4절의 무료 폰트들이 반대편에서 가르쳐 주는 것과 같습니다. 계속 늘어날 텍스트에 대해 일본어 픽셀 서체가 해결된 문제가 되는 것은, 누군가가 이미 7,000자를 다 그려 두었고 라이선스가 그것을 출시하도록 허락할 때뿐입니다.

현대 픽셀 게임들이 출시하는 것

2026년 10월 5일 기준 Steam에서 Stardew Valley는 12개 언어를 나열하며 그중에 일본어, 중국어 간체, 한국어가 있습니다. Celeste는 9개 언어로 일본어와 중국어 간체를, Sea of Stars는 11개 언어로 일본어, 한국어, 중국어 간체와 번체를 모두, Eastward는 7개 언어로 두 가지 중국어와 일본어를 포함합니다.64 네 게임 모두 일본어를 출시합니다.

Celeste, Sea of Stars, Eastward가 일본어와 중국어 텍스트를 어떻게 그리는지는 개발자 자료에서 찾지 못했으므로, 이 글은 언어 목록 이상으로는 아무 말도 하지 않습니다(11절).

Kiradex의 두 가지 말하기 방식

Kiradex에는 이미 Stardew의 두 형태가 모두 있습니다. 화면 아래쪽의 WorldHUD 상자와 광장에서 머리 위에 뜨는 말풍선입니다.2625 없는 것은 그 안에 들어갈 원전의 타이포그래피와, VoiceOver가 둘 다 들을 수 있게 하는 방법입니다. 말풍선 코드에 붙은 주석이 무엇을 노렸는지 말해 줍니다. “A line said, as the old games drew it: a cream box with a dark border.”(옛 게임들이 그렸던 방식의 대사 한 줄: 짙은 테두리의 크림색 상자.)25

9. 기법을, 숫자가 붙은 규칙으로

측정이 뒷받침하는 규칙을, 각각의 출처와 함께 적습니다. 측정된 사실을 권고로 바꾸는 규칙에서는 권고가 제 것이며, 규칙의 동사가 그것을 드러냅니다.

요소 규칙 출처
상자 상자는 화면의 아래 3분의 1이고 두 줄입니다. 아래쪽에 두고, 아래쪽이 필요한 게임이라면 위로 옮깁니다. Emerald의 테두리는 30×6타일로 너비 전체, 높이의 30%이며, 그 안에 16픽셀 줄 두 개의 27×4타일 창. Crystal은 20×6으로 33%, 18자짜리 두 줄이며, Red도 같은 20×6. Final Fantasy VI의 창은 행 1이나 18
라틴 문자 글리프 높이 8에서 16픽셀의 셀에 너비 4에서 12픽셀. Emerald의 6×16 셀, 실제 글에서 한 글자 5.25픽셀. Final Fantasy VI의 16×11 셀에 4에서 12. Crystal의 고정 8×8
일본어 글리프 8에서 12픽셀, 같은 폰트의 라틴 문자의 약 두 배. Misaki 8×8에 잉크 7×7. Emerald는 16 줄에서 너비 8. FireRed의 시트와 PixelMplus10은 10. Final Fantasy VI와 PixelMplus12는 12. 측정한 일본어 지원 폰트는 모두 8 대 4, 10 대 5, 12 대 6
줄 길이 한 줄에 18에서 41자. 너비를 채우기보다 너비에 맞춰 씁니다. Crystal 18. Emerald의 216픽셀 창에는 5.25픽셀로 41자가 들어가고, 작가들은 95%의 줄을 38에서 멈춤. Final Fantasy VI의 220픽셀 줄에는 12픽셀 일본어 18자
줄 바꿈 작가가 모든 줄을 끊거나, 코드가 단어 단위로 접거나 둘 중 하나입니다. 둘 다는 절대 하지 않습니다. Emerald의 손으로 끊은 24,100줄, 모두 208픽셀 이하. Final Fantasy VI의 CalcTextWidth와 NewLine
속도 글자를 시간으로 셉니다. 상한은 초당 60(Emerald와 Crystal의 가장 빠른 설정, Final Fantasy VI의 한 프레임에 한 글자), 중간 설정은 15에서 20, 느린 설정은 7.5에서 12. Emerald 7.5, 15, 60, 누르고 있으면 60. Crystal과 Red 12, 20, 60. Final Fantasy VI 60
넘김 표시 표시는 느린 시계로 움직입니다. 150밀리초마다 한 단계, 한 주기 약 0.6초. Emerald의 0, 1, 2, 1픽셀, 9프레임마다 한 단계, 한 주기 36프레임. Crystal의 ▼는 테두리 타일과 번갈아 깜빡임
테두리 테두리는 그림이 아니라 타일입니다. 모서리, 가장자리, 가운데를 반복합니다. 플레이어가 몇 가지 중에서 고르게 합니다. Emerald에서 고를 수 있는 24×24 메뉴 테두리 20종과, 아래 가장자리를 반전한 14타일 대화 띠. Crystal의 1비트 타일 6개짜리 테두리 8종. Final Fantasy VI의 28타일 스타일 8종과 편집 가능한 7색
선명한 글자 픽셀 폰트가 선명한 것은 폰트 픽셀 하나가 정수 개의 기기 픽셀일 때뿐이며, Mac에서는 폰트 스무딩이 꺼져 있을 때뿐입니다. iPhone에서 어느 쪽이 성립하는지는 캡처해서 확인합니다. macOS 27.0의 Core Text, 폰트 스무딩 끔: 그리드가 베이스라인 위에 있고 어드밴스가 정수 픽셀인 폰트(Misaki, PixelMplus10, Press Start 2P)는 정수배에서 중간 톤 0.0%, 그 사이에서 19.4%에서 82.2%. 컨텍스트의 기본 스무딩에서는 PixelMplus10을 제 크기 10픽셀로 그려 잉크 픽셀 272개 중 178개가 중간 톤
깨끗한 파일 폰트를 고르기 전에 그리드, 베이스라인에서의 어긋남, 어드밴스를 확인합니다. macOS 27.0의 Core Text: PixelMplus12의 그리드는 베이스라인에서 0.39픽셀 어긋나 모든 크기에서 번짐. k8x12의 8.01픽셀 가나 어드밴스는 글리프를 정확히 놓으면 번짐. Silkscreen의 벗어난 점 10개는 1.2%에서 4.5%
Dynamic Type 사용자의 크기는 그리드에 맞춰 반올림해서 존중합니다. 절대 연속적으로 키우지 않고, 11포인트 아래로도 절대 내리지 않습니다. 3배 화면에서 10픽셀 em은 Body 크기 12단계 중 1개에서, 12픽셀 em은 3개에서, 8픽셀 em은 2개에서 정수. HIG의 최소 11포인트
일본어 줄 바꿈 상자 텍스트에는 엄격한 행두 규칙을 유지합니다. 프리셋 대사는 절대 줄이 바뀌지 않을 만큼 짧게 유지합니다. JLREQ 3.1.7. macOS 27.0의 Core Text는 태그가 없을 때 측정한 모든 너비에서 프로브의 부분적인 행두 목록에 대해 엄격한 규칙을 지켰고, ja 태그를 붙이면 대부분의 서체에서 너비 7개 중 6개(k8x12와 k8x12S에서는 7개 중 4개)에서 작은 가나가 줄을 시작하도록 허용
화자 화자의 이름은 별도의 칸이나 머리 위가 아니라 줄 안에서 밝힙니다. Emerald의 상자에는 이름 칸이 없고, 문단의 6.5%가 이름과 콜론으로 시작
지원 범위 작가가 확정하지 않은 일본어 텍스트를 출시하기 전에 표준 집합, 약 7,000자를 지원합니다. 확정된 스크립트라면 문자열마다 대조해서 더 적게 출시할 수 있습니다. JIS X 0208의 6,879자. Pixel Remaster의 “around 7,500”. Final Fantasy VI 일본어판 ROM의 대화 글자 1,248자. Misaki, k8x12, DotGothic16은 JIS X 0208 전부를, PixelMplus는 코드 포인트 세 개를 뺀 전부를 그림
라이선스 서체는 라이선스 전문을 손에 쥐고 조건을 읽은 뒤에만 출시합니다. Misaki, k8x12, PixelMplus의 세 문장짜리 라이선스. DotGothic16, Press Start 2P, Silkscreen의 SIL Open Font License와 Press Start 2P의 예약 폰트 이름
두 형태 상자와 말풍선, 두 형태를 모두 유지합니다. 어느 쪽이 무엇을 말할지는 게임이 정합니다. Kiradex에서 제가 고른 것은, 장소가 하는 말은 상자, 사람이 하는 말은 말풍선입니다. Stardew Valley는 둘 다 가짐. 상자는 대화에, 말풍선은 플레이어가 장소에 들어설 때 NPC가 하는 말에

표의 숫자는 모두 1절에서 8절에서 왔으며, 거기에 각주가 있습니다.1467921862

10. Apple의 방식: 서체, 크기, 줄 바꿈, VoiceOver

Kiradex World는 RealityKit 장면을 2D 렌더러로 씁니다. 직교 카메라를 텍셀당 정수 개의 기기 픽셀에 두고, 사람이 읽는 모든 것은 그 위의 SwiftUI로 그립니다.23 세계 편 글은 대화 텍스트가 “lives in SwiftUI at UI scale rather than in the world”(세계 안이 아니라 UI 스케일로 SwiftUI 안에 산다)고 정했습니다. 이 절은 그 결정을 유지하면서 API를 채웁니다. Apple이 커스텀 서체에 요구하는 것, 번들에 넣은 픽셀 폰트를 어떻게 들여올지, 반올림으로 Dynamic Type을 어떻게 존중할지, 일본어를 어떻게 줄 바꿈할지, 그리고 RealityKit 뷰 하나인 무대를 VoiceOver가 어떻게 읽을 수 있을지입니다. 아래의 지원 버전은 모두 2026년 10월 5일에 저장한 Apple 문서 페이지에서 나왔습니다. 코드는 하나도 빌드하지 않았으며, 이 절의 비용은 모두 휴대폰에서 측정하지 않았습니다.65

Apple이 텍스트에 요구하는 것

Human Interface Guidelines의 Typography 페이지는 iOS와 iPadOS의 기본 텍스트 크기를 17포인트, 최소를 11포인트로 정하고, 커스텀 폰트와 시스템 폰트 모두에 대해 앱이 “Follow the recommended default and minimum text sizes for each platform”(플랫폼별 권장 기본 크기와 최소 크기를 따를 것)하라고 요구합니다.1060 시스템 폰트는 Dynamic Type과 굵은 텍스트 같은 손쉬운 사용 기능을 스스로 지원합니다. 나머지에 대해 페이지는 이렇게 말합니다. “If you use a custom font, make sure it implements the same behaviors.”(커스텀 폰트를 쓴다면 같은 동작을 구현하도록 할 것.)10

Accessibility 페이지는 일의 규모를 정합니다. “Ideally, give people the option to enlarge text by at least 200 percent (or 140 percent in watchOS apps).”(이상적으로는 텍스트를 최소 200퍼센트, watchOS 앱에서는 140퍼센트까지 키울 수 있는 선택지를 줄 것.)20 게임 페이지는 여기에 가독성을 더합니다. “To keep text comfortably legible on each device, ensure that it contrasts well with the background and uses at least the recommended minimum text size in each platform.”(각 기기에서 텍스트를 편하게 읽을 수 있도록, 배경과 대비가 좋고 플랫폼별 권장 최소 크기 이상을 쓰게 할 것.)66

Body의 범위 14에서 53포인트는 기본값 17의 약 82%에서 약 312%입니다. 3배 화면에서 상자의 반올림된 크기 13.33에서 53.33포인트는 자기 기본값 16.67의 80%에서 320%로, Accessibility 페이지가 요구하는 200%를 넘습니다.601133

커스텀 폰트의 크기 조절

Apple에는 Dynamic Type에 맞춰 값을 키우는 방법이 셋, 키우지 않게 막는 방법이 하나 있습니다.

  • UIFontMetrics: “A utility object for obtaining custom fonts that scale to support Dynamic Type”(Dynamic Type을 지원하도록 크기가 바뀌는 커스텀 폰트를 얻기 위한 유틸리티 객체)으로, iOS 11.0부터 쓸 수 있습니다. scaledFont(for:), scaledFont(for:maximumPointSize:), 그리고 “Scales an arbitrary layout value based on the current Dynamic Type settings”(현재 Dynamic Type 설정에 따라 임의의 레이아웃 값을 키우는) scaledValue(for:)가 있습니다.67
  • Font.custom(_:size:relativeTo:): SwiftUI에서 “that scales relative to the given textStyle“(주어진 textStyle에 상대적으로 크기가 바뀌는) 커스텀 폰트를 만듭니다. iOS 14.0부터입니다.68
  • ScaledMetric: “A dynamic property that scales a numeric value”(숫자 값의 크기를 바꾸는 동적 프로퍼티)로, iOS 14.0부터입니다.69
  • Font.custom(_:fixedSize:): “that does not scale with Dynamic Type”(Dynamic Type에 따라 크기가 바뀌지 않는) 커스텀 폰트를 만듭니다. iOS 14.0부터입니다.68

픽셀 폰트에는 첫째나 셋째를 마지막 것과 함께 쓰는 것이 맞습니다. 시스템이 쓸 크기를 읽고, 폰트의 그리드에 맞춰 반올림하고, 반올림한 크기를 고정 크기로 설정해 시스템이 다시 키우지 않게 하는 것입니다. 반올림 자체는 산수이며 12절의 브리프에 있습니다. 폰트 픽셀 하나를 기기 픽셀 m개로 하고, 10픽셀 em이라면 m = max(m₀, ⌊size × displayScale / 10 + 0.5⌋), 즉 0.5를 올리는 가장 가까운 정수입니다. m₀은 11포인트 이상이 되는 가장 작은 m으로, 3배에서 4, 2배에서 3이며, 포인트 크기는 10 × m / displayScale입니다. 3배에서는 Body 크기 12단계가 m 4, 5, 5, 5, 6, 6, 7, 8, 10, 12, 14, 16에 대응합니다(15포인트인 Small이 정확히 절반이 되는 유일한 크기로, 4.5가 5로 올라갑니다). 기본 크기에서 16.67포인트, AX5에서 53.33포인트입니다.11

뷰 안에서 크기를 제한하려면 dynamicTypeSize(_:)(iOS 15.0)가 Dynamic Type 크기를 설정하거나 범위를 정하며, 그 페이지는 이렇게 권합니다. “When limiting the Dynamic Type size, consider if adding a large content view with accessibilityShowsLargeContentViewer() would be appropriate.”(Dynamic Type 크기를 제한할 때는 accessibilityShowsLargeContentViewer()로 큰 콘텐츠 뷰를 더하는 것이 적절한지 검토할 것.)70 그 수정자(역시 iOS 15.0)에는 자체 경고가 붙어 있습니다. “Don’t use the large content viewer as a replacement for proper Dynamic Type support.”(큰 콘텐츠 뷰어를 제대로 된 Dynamic Type 지원의 대체물로 쓰지 말 것.)71 브리프가 제한하는 것은 한 줄로 남아야 하는 말풍선뿐이며, 상자는 절대 제한하지 않습니다.

폰트 번들링

폰트는 앱 번들에 넣어 출시하고, UIAppFonts, 즉 “App-specific font files located in the bundle and that the system loads at runtime”(번들 안에 있고 시스템이 런타임에 불러오는 앱 전용 폰트 파일, iOS 3.2)에 이름을 적거나, 코드에서 CTFontManagerRegisterFontsForURL(_:_:_:)(iOS 4.1)로 등록합니다.7273

테두리 그리기와 베이스라인 배치

몇 개의 타일로 그린 테두리는 스무딩 없이 크기를 바꾼 이미지입니다. SwiftUI의 Image.interpolation(_:)는 보간 품질을 설정하고(iOS 13.0부터), “Sets the mode by which SwiftUI resizes an image to fit its space”(SwiftUI가 이미지를 공간에 맞게 크기 조절하는 방식을 설정하는) Image.resizable(capInsets:resizingMode:)는 모서리를 뺀 모든 부분을 늘이거나 타일로 깔아 줍니다(역시 iOS 13.0부터).7475 브리프는 대신 Canvas에 테두리를 그려, 원본 픽셀 하나가 정확히 m×m 기기 픽셀이 되게 합니다. 제 해석으로는 .tile을 쓴 resizable(capInsets:)도 그 타일 배치가 정수 기기 픽셀에 떨어질 때만 똑같이 쓸 수 있으며, 그것은 아무도 측정하지 않았습니다.

Text.baselineOffset(_:)는 “Sets the vertical offset for the text relative to its baseline”(베이스라인에 대한 텍스트의 세로 오프셋을 설정하는, iOS 13.0) 것으로, PixelMplus12처럼 그리드가 베이스라인에서 어긋난 서체를 위한 도구입니다. 6절에서 측정했듯이 옮겨도 그런 서체가 고쳐지는 것은 한 크기뿐이며, 이것이 브리프가 그 서체를 쓰지 않는 이유 중 하나입니다.767

SwiftUI와 UIKit의 일본어 줄 바꿈

typesettingLanguage(_:isEnabled:)(iOS 17.0)는 “may contain text of a particular language which doesn’t match the device UI language. In that case it’s useful to specify a language so line height, line breaking and spacing will respect the script used for that language”(기기 UI 언어와 맞지 않는 특정 언어의 텍스트를 담을 수 있다. 그럴 때 언어를 지정하면 줄 높이, 줄 바꿈, 간격이 그 언어의 문자 체계를 따른다) Text를 위한 것입니다. 페이지는 이렇게 덧붙입니다. “this language does not affect text localization.”(이 언어는 텍스트 현지화에 영향을 주지 않는다.)77

UIKit의 NSParagraphStyle.LineBreakStrategy(iOS 9.0)에는 이름 붙은 전략이 셋 있습니다. standard(iOS 14.0)는 “uses the same configuration of line-break strategies that it uses for standard UI labels”(표준 UI 레이블과 같은 줄 바꿈 전략 구성을 쓰며) “may be unsuitable for large amounts of text”(많은 양의 텍스트에는 맞지 않을 수 있다)고 되어 있습니다. pushOut(iOS 9.0)은 “an orphan word on the last line”(마지막 줄에 홀로 남은 단어)을 피하기 위한 것이고, hangulWordPriority(iOS 14.0)는 한국어를 위한 것입니다.78

7절에서 측정했듯이 macOS의 Core Text는 ja 태그를 붙이면 대부분의 서체에서 너비 7개 중 6개(k8x12와 k8x12S에서는 7개 중 4개)에서 작은 가나가 줄을 시작하도록 허용했고, 태그가 없으면 허용하지 않았습니다.7 기기 언어가 일본어일 때 SwiftUI의 Text가 일본어를 어떻게 다루는지, 그리고 typesettingLanguage가 측정에서 설정한 것과 같은 속성을 설정하는지는 측정하지 않았습니다(11절). 그래서 브리프는 태그를 붙여도 엄격한 규칙이 지켜진다는 것을 캡처가 보여 주지 않는 한 상자 텍스트에 태그를 붙이지 않습니다.

무대 위의 VoiceOver

SwiftUI의 손쉬운 사용 수정자는 뷰를 설명합니다. accessibilityLabel(_:)이 뷰에 이름을 붙이고, accessibilityValue(_:)가 그 값을 더하며, 후자의 문서에는 규칙이 있습니다. “Use this method to describe the value represented by a view, but only if that’s different than the view’s label.”(이 메서드는 뷰가 나타내는 값을 설명하는 데 쓰되, 그것이 뷰의 레이블과 다를 때만 쓸 것.) 둘 다 페이지가 나열하는 LocalizedStringResource 형태로는 iOS 16.0입니다.79

게임의 무대에는 레이블 이상이 필요합니다. accessibilityDirectTouch(_:options:)(iOS 17.0)는 사용자가 활성화한 뒤, “Direct touch areas passthrough touch events to the app rather than being handled through an assistive technology, such as VoiceOver”(다이렉트 터치 영역은 터치 이벤트를 VoiceOver 같은 보조 기술이 처리하게 하지 않고 앱에 그대로 넘긴다) 영역을 만듭니다.80 AccessibilityTraits.updatesFrequently(iOS 13.0)는 변하는 요소를 위한 것입니다. “Use this trait when you want an assistive technology to poll for changes when it needs updated information.”(갱신된 정보가 필요할 때 보조 기술이 변화를 폴링하게 하고 싶다면 이 특성을 쓸 것.)81 AccessibilityNotification.Announcement(iOS 17.0)는 음성 안내를 보내며, accessibilitySpeechAnnouncementPriority 속성으로 다른 음성에 대해 어떻게 대기열에 설지를 정합니다.82 그리고 accessibilityReduceMotion(iOS 13.0)은 Reduce Motion 설정을 알려 주며, 페이지에 따르면 이는 “UI should avoid large animations”(UI는 큰 애니메이션을 피해야 한다)는 뜻입니다.83

RealityKit은 엔티티 자체를 설명할 수 있습니다. AccessibilityComponent(iOS 17.0)는 “A component that stores accessibility information for an entity”(엔티티의 손쉬운 사용 정보를 저장하는 컴포넌트)이며, 그 이름은 “VoiceOver says aloud when it describes the entity”(VoiceOver가 엔티티를 설명할 때 소리 내어 말하는) 것입니다.84 Apple의 글 “Improving the Accessibility of RealityKit Apps”는 “set the properties on each visible Entity in your scene”(장면에서 보이는 각 Entity에 프로퍼티를 설정하라)고 합니다. Entity 자체의 손쉬운 사용 프로퍼티인 isAccessibilityElement, accessibilityLabel, accessibilityDescription이며, 글은 이것들을 iOS 14부터로 적지만 isAccessibilityElement를 뺀 나머지의 페이지는 지금 사용 중단으로 표시되어 있습니다(페이지에는 현지화된 문자열 키로서의 레이블 accessibilityLabelKey도 실려 있습니다). 컴포넌트 옆에 있는 더 오래된 경로입니다.85 Kiradex의 무대가 쓰는 SwiftUI 뷰 RealityView는 iOS 18.0입니다.86 Kiradex처럼 가상의 직교 카메라를 설정한 RealityView가 그 엔티티들을 VoiceOver에 노출하는지는 그 페이지들에 적혀 있지 않으며, 테스트하지도 않았습니다(11절).

이제 그 테스트는 자동화할 수 있습니다. XCUIVoiceOverService는 “Provides programmatic control of VoiceOver for UI testing”(UI 테스트를 위해 VoiceOver를 프로그램으로 제어한다)으로, iOS 27.0부터 쓸 수 있으며 XCUIDevice의 voiceOverService 프로퍼티로 접근합니다.87

현재의 Kiradex

2026년 10월 5일에 저장소에서 읽은 것으로, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태이며, 저는 실행하거나 바꾸지 않았습니다. 제가 읽는 동안에도 앱은 편집되고 있었습니다(11절).11

  • 서체는 하나, 시스템의 것. 테마의 폰트 패밀리는 .systemDesign(.monospaced)이고, 세계의 뷰들은 키트의 역할을 씁니다. caption이 파일 네 개의 10곳, label이 3곳, body가 3곳, title이 3곳입니다.2411
  • 연속적으로 크기가 바뀌는 커스텀 폰트 경로. 공유 키트의 Kit941.FontFamily.custom(named:)은 display, title, body, label, caption에 대해 34, 22, 17, 13, 12포인트의 Font.custom(_:size:relativeTo:)가 됩니다.88 픽셀 폰트라면 이것이 모든 Dynamic Type 크기를 거쳐 커지게 되고, 6절의 셈으로는 12픽셀 em이면 Body 크기 12단계 중 9개, 10픽셀 em이면 12개 중 11개에서 그리드를 벗어납니다.7
  • 말풍선. Text(text).kit941Font(.caption, weight: .bold)로, WorldInk.paper(고정 색 두 개를 가진 아트 표면) 위에 잉크로 그리며, 키트의 small 간격으로 좌우, extra-small로 위아래 여백을 두고, 2포인트 잉크 테두리와 .fixedSize()로 한 줄입니다. rig.headPoint에서 large 간격 한 단계 위에 놓이고, 대사가 나온 지 4초가 지나기 전까지 표시되며, 히트 테스트를 끈 오버레이 안에서 "Said: \(text)"라는 레이블이 붙습니다. 그 오버레이는 무대 안쪽에 있으며, 무대가 장면 전체에 덮는 두 오버레이, 즉 3포인트마다 불투명도 16%의 검은 선을 긋는 스캔라인과 비네트의 아래에 있습니다.2511
  • 상자. WorldHUD는 장소의 제목(caption, 굵게, 대문자)과 상태(caption, 세미볼드)를 같은 2포인트 테두리가 있는 크림색 상자에 넣어 화면 아래쪽에 둡니다. Duo를 반쯤 열었을 때는 접힌 선의 넓은 쪽에 머뭅니다. 탭을 받지 않으며(.allowsHitTesting(false), “Taps go through to the ground under it”(탭은 그 아래 땅으로 통과한다)), 무대 전체 위, 스캔라인과 비네트보다도 위에 덮여 있습니다.26
  • 말해지는 것. 서버에 프리셋 대사 23줄이 있으며, 3에서 40자, 중앙값 16입니다. 가장 긴 것은 “Anyone have a card from the first years?”입니다. 핸들은 20개와 20개 단어 목록에서 고른 두 단어와 네 자리 숫자로, 최대 20자입니다(“Glacier Shuffle 0000”). 수집가 머리 위에 그려지는 것은 말풍선뿐이고, 핸들은 탭하면 열리는 패널에 나옵니다.288911 이 시리즈의 마을 생활 글도 같은 23줄을 세었고, 서버의 제한을 플레이어당 2.0초에 한 줄로 적었습니다.9091
  • 폰트 없음, 언어 하나. 번들에 넣은 폰트 파일이 없습니다(저장소에 있는 .ttf는 스크린숏 도구용 scripts/screenshots/MartianMono-latin.ttf뿐입니다). 프로젝트의 knownRegions는 Base와 en이며, .xcstrings나 .lproj 파일은 없습니다.1127
  • VoiceOver가 듣는 것은 좌표. 무대는 하나의 요소이며, .accessibilityLabel("The plaza")와 .accessibilityValue(stepTrace)입니다. stepTrace는 마지막 걸음을 “x,y facing” 형태로 나타낸 것으로, “the walks read it, since nothing else the simulator logs reaches them”(시뮬레이터가 기록하는 다른 어떤 것도 걷기 테스트에 닿지 않으므로 걷기 테스트가 이것을 읽는다) 때문에 남아 있습니다. AccessibilityComponent, accessibilityDirectTouch, AccessibilityNotification, typesettingLanguage, UIFontMetrics, ScaledMetric, dynamicTypeSize는 앱 소스 어디에도 나오지 않습니다.2511
  • 글자 없는 간판. 포지의 sign()은 “A hanging sign on a bracket, 12 × 11”(까치발에 매단 12×11 간판)이며, 그 면은 글자가 아니라 두 줄의 잉크 자국입니다. 광장 맵에는 간판 타일 S가 하나 있습니다. 땅에 손가락을 대면 플레이어가 곧바로 그 아래 타일로 걸어가고 손가락을 따라 타일을 건너갑니다. 수집가를 탭하면 손가락을 뗄 때 그 패널이 열리고, 키오스크를 탭하면 마을 게시판이 열립니다. 간판 타일은 다른 타일과 똑같은 땅이며, 자체 탭이 없습니다.2992
  • 세계의 배율. PlazaRig.fit의 규칙, 뷰의 픽셀 높이를 320으로 나눈 값의 내림에 따라, 텍셀 하나는 iPhone 18 Pro Max(440×956포인트)와 Duo 안쪽 디스플레이 세로 방향에서 기기 픽셀 8개, Duo 바깥쪽 디스플레이 세로 방향과 안쪽 가로 방향에서 6개, 바깥쪽 가로 방향에서 4개입니다. 2.67, 2.00, 1.33포인트입니다.9311

텍스트를 세계 밖에 두는 이유

이 점에 대한 세계 편 글의 산수는 맞습니다. “At k = 8 on a 3× display a five-texel font is 13.3 points and at k = 6 it is 10 points, under the HIG’s 11-point minimum”(3배 화면에서 k = 8이면 5텍셀 폰트는 13.3포인트이고, k = 6이면 10포인트로 HIG의 최소 11포인트보다 작다).23 이것이 묶는 것은 k = 6에서 5.5텍셀 미만인 폰트(텍셀당 2포인트로 11포인트)뿐이며, k = 6에서 8텍셀 em은 16포인트입니다.1133 UI 스케일로 SwiftUI에 텍스트를 둔다는 결론은 다른 이유로 성립합니다. 세계의 밀도에서는 텍셀당 기기 픽셀 8개인 휴대폰에서 10픽셀 em이 26.67포인트가 되며, 18 Pro Max에서는 상자 한 줄에 라틴 문자 29자만 남습니다.11 타일로 재면 원전의 상자는 좁습니다. 하드웨어 타일 16개, 128픽셀 너비의 상자에는 8×8 폰트로 16자, Emerald의 평균 5.25픽셀 라틴 문자로 약 24자(최악의 6픽셀로 21자), Emerald의 8픽셀 일본어로 16자, Final Fantasy VI의 12픽셀 일본어로 10자가 들어갑니다. Kiradex의 16텍셀 타일 16개는 256텍셀이며, 세계 자체의 밀도에서 PixelMplus12의 라틴 문자 42자와 가나 21자, Misaki라면 64자와 32자가 들어갑니다.11 그리고 Kiradex 자신의 타일로 잰 상자는 측정한 다섯 뷰 중 셋에 들어가지 않습니다. 16텍셀 타일 16개는 텍셀당 기기 픽셀 8개에서 683포인트로, iPhone 18 Pro Max(440포인트)와 Duo 안쪽 디스플레이 세로 방향(669)보다 넓고, 6에서는 512포인트로 Duo 바깥쪽 디스플레이 세로 방향(466)보다 넓습니다. 들어가는 것은 가로 방향의 두 뷰뿐입니다. 안쪽 디스플레이의 951에 512포인트, 그리고 텍셀당 기기 픽셀 4개에서 341포인트가 바깥쪽 디스플레이의 678에 들어갑니다.11 Kiradex의 상자는 타일이 아니라 포인트로 크기를 정합니다.

브리프의 크기로 같은 산수를 해 봅니다. m = 5, 16.67포인트의 PixelMplus10은 세로 방향 18 Pro Max의 상자에서, 현재의 48포인트 인셋(좌우 각각 여백 16포인트와 패딩 8포인트)을 전체 너비에서 빼고, 좌우 인셋이 없다고 가정하면(측정하지 않은 가정입니다) 한 줄에 라틴 문자 47자나 일본어 23자가 들어갑니다. 상자는 안전 영역 안에 있으므로, 세로 방향 Duo 바깥쪽 디스플레이에서는 그 글자 수가 좌우 인셋에 달려 있습니다. 인셋이 없으면 50자나 25자, 이 시리즈가 닫힌 디스플레이에서 측정한 84포인트 뒤쪽 인셋, 즉 사이드 레일이 있으면 40자나 20자입니다(11절). 브리프의 테두리(12절 항목 3)에서는 패딩 대신 테두리가 좌우 각각 8폰트 픽셀을 차지하고 상자를 정수 폰트 픽셀로 내림하므로, 같은 줄에 18 Pro Max에서는 45자나 22자, Duo에서는 48자나 24자, 레일이 있으면 38자나 19자가 들어갑니다.119495

비용

이 중 어느 것도 기기에서 측정하지 않았습니다. 글리프 7,253개짜리 폰트를 번들에 넣으면 그 파일만큼 앱이 커집니다.7 커스텀 폰트로 SwiftUI 텍스트를 그리는 비용은 다른 Text와 같습니다. 그리고 브리프의 테두리는 크기가 바뀌면 다시 그리는 Canvas입니다. 제 해석으로는 이 중 어느 것도 RealityKit 장면에 비하면 크지 않지만, 그것은 해석일 뿐이며, 확인하는 곳은 브리프의 캡처입니다.

11. 측정한 것과 측정하지 않은 것

이 글의 숫자는 모두 저장한 소스에 대해 다시 실행할 수 있는 스크립트, 2026년 10월 5일에 저장한 페이지, 또는 날짜를 적은 Kiradex 소스 읽기 중 하나에서 나왔으며, 어느 것인지는 각주에 적었습니다. 이 절은 그것으로 다루지 못하는 것의 목록입니다.30

소스에서 측정한 것, 다시 실행할 수 있는 것. measure_gen_text.py는 pret 디컴파일 네 개를 대상으로 했습니다. Emerald의 폰트 표(항목 10개, 폰트 7종), 프레임과 초당 글자 수로 본 텍스트 속도, 스크롤, 넘김 화살표, 27×4 창과 30×6 테두리, 고를 수 있는 테두리 20종, 라틴 문자 글리프 너비, 글자 수와 픽셀로 잰 스크립트 24,100줄, 화자를 밝히는 문단 14,412개 중 934개, 일본어 글리프 시트, FireRed의 지연과 창, Crystal과 Red의 화면, 상자, 지연, 테두리, 폰트 시트입니다.1 measure_ff6.py는 ff6 디스어셈블리를 대상으로 했습니다. 창 스타일과 팔레트, 대화 폰트의 셀과 너비, 일본어 폰트의 크기와 너비 구간, 220픽셀 줄, 한 프레임에 한 글자, 100프레임 제목, 단어 단위 줄 접기, 설정 화면, 창의 그라데이션 표입니다.4 measure_bdf.py는 Misaki와 k8x12의 BDF 파일을 대상으로 했습니다.6 measure_coretext.swift와 measure_outline_coords.swift는 macOS 27.0에서 컴파일하고 실행해 TrueType 파일 10개와 Hiragino Sans를 측정했습니다.719 measure_kiradex_text.py는 커밋 334fbfe 시점에 디스크에 있던 Kiradex를 대상으로 했고(10월 15일에 현재 트리에 대해 다시 실행했으며, 이전 줄들은 바뀌지 않았습니다), measure_figure_width.py는 그 몸체 시트를 대상으로 했습니다.1196 measure_hig.py, measure_steam_languages.py, measure_apple_docs.py는 저장한 페이지를 대상으로 했습니다.606465 measure_ios27_iphones.py는 iOS 27.0 시뮬레이터 런타임의 iPhone 목록을, probe_line_start_glyphs.swift는 macOS 27.0에서 컴파일하고 실행해 JLREQ의 클래스 표와 PixelMplus10을 대상으로 했습니다.9798 measure_cell_masks.py는 저장한 pret 시트를 대상으로 하며, 브리프의 셀 마스크 참조를 만듭니다.99 여기까지 스크립트 열세 개입니다. 열네 번째인 dump_glyphs.swift는 이 글의 그림과 함께 두었으며, PixelMplus10을 제 크기로 폰트 스무딩을 켜고 끄고 그렸습니다.9

측정하지 않은 것, 또는 보기보다 약한 것.

  • macOS의 Core Text가 iOS를 대신합니다. 6절과 7절의 렌더링과 줄 바꿈 숫자는 모두 macOS 27.0의 Core Text가 폰트 스무딩을 끄고 비트맵 컨텍스트에 그린 것이며, iPhone의 SwiftUI에서 나온 것이 아닙니다. 의미 있는 측정은 브리프의 캡처 검사이며, 그것을 실행하기 전까지 iPhone이 무엇을 그릴지는 예측입니다.79
  • 비트맵 스트라이크. Misaki와 k8x12는 EBDT와 EBLC 테이블을 갖고 있습니다. 8픽셀과 12픽셀에서 Core Text가 그 비트맵을 그렸는지 아웃라인을 그렸는지는 확인하지 않았습니다.7
  • SwiftUI 자체의 배치. SwiftUI가 Text의 원점을 정수 기기 픽셀에 놓는지, 그리고 기기 언어가 일본어일 때 일본어에 어떤 줄 바꿈 규칙을 적용하는지는 측정하지 않았습니다.
  • 굵기. 측정한 굵은 서체는 PixelMplus10 Bold뿐입니다. 픽셀 서체의 합성 굵기는 측정하지 않았습니다.7
  • 가독성. 그리드와 지원 범위는 측정했지만, 16.67포인트의 10×10 한자가 일본어 독자에게 잘 읽히는지는 측정하지 않았으며, 기기에서 일본어 독자가 확인해야 합니다.
  • 일본어판 ROM. pret이 빌드하는 것은 영어판 게임입니다. 일본어판 Emerald 자체의 상자, 속도, 폰트는 읽지 않았습니다. 1절의 일본어 시트는 영어판 ROM이 이름을 위해 지닌 것입니다. 일본어판 Crystal과 Red도 읽지 않았습니다.30
  • Final Fantasy VI의 그래픽. 디스어셈블리에 있는 것은 범위와 코드이지 픽셀이 아닙니다. 창 타일의 배치와 그라데이션의 모습은 보지 못했습니다. 영어판 큰 폰트의 범위 5,120바이트는 22바이트 글자의 정수 개가 되지 않으며 설명되지 않았습니다. 그라데이션 표를 스캔라인마다 값 하나씩 소비한다는 것은 주석에서 나온 것이며 추적하지 않았습니다.4
  • Crystal의 깜빡임 주기는 도출하지 않았습니다. 두 상태는 코드에서 읽었습니다.16
  • Chrono Trigger는 전혀 측정하지 않았습니다. 공개 디스어셈블리는 클론하지 않았고, Data Crystal의 ROM 맵은 HTTP 403을 돌려주었습니다.49
  • Celeste, Sea of Stars, Eastward. Steam의 언어 목록뿐입니다. 일본어와 중국어 텍스트를 어떻게 그리는지는 개발자 자료에서 찾지 못했습니다.64
  • Pixel Operator, m5x7, m3x6는 내려받지도 측정하지도 않았으며, Pixel Operator 자체의 저장소는 HTTP 404를 돌려주었습니다. 라이선스는 각 페이지가 밝힌 그대로이며, Pixel Operator의 것은 DaFont 하나에서만 나왔습니다.565758
  • PixelMplus가 매핑하지 않은 JIS 코드 포인트 세 개는 다른 코드 포인트에서 확인하지 않았습니다.7
  • DotGothic16의 크기. 픽셀 그리드가 없으므로 명목상 em당 16도트와 그 배수로 그리고 조판했습니다. 이름과 도트 피치에서 제가 고른 것이며, 다른 명목 크기는 시도하지 않았고, 6절과 7절의 숫자는 그 크기들에서만의 것입니다.719
  • 폰트 스무딩. 6절의 렌더링은 모두 폰트 스무딩을 허용하지 않습니다. 비트맵 컨텍스트의 기본값으로 그린 것은 서체 하나의 크기 하나, 10픽셀 PixelMplus10뿐이며, 잉크 픽셀 272개 중 178개가 중간 톤이었습니다. 다른 서체와 크기는 스무딩을 켜고 그리지 않았습니다.9
  • 제가 읽는 동안 앱이 바뀌었습니다. Kiradex는 이 글을 쓰던 10월 5일에도 편집되고 있었습니다. 그날 일찍 무대의 탭 처리가 다시 작성되어 줄이 30에서 144만큼 밀렸고, 세계의 Swift 파일 중 세 개는 아직 어떤 커밋에도 들어 있지 않았습니다. 제가 읽고 14분 뒤에는 무대와 리그가 또 바뀌어 있었습니다. 이 글이 읽는 것은 커밋 334fbfe에, 제가 읽은 시점의 그날의 커밋되지 않은 편집이 더해진 상태입니다. 앱 파일은 태평양 시간 12시 28분(서버의 수집가와 마을 맵은 12시 33분), 그것을 측정하는 스크립트는 12시 34분과 12시 36분입니다. 각주에는 그 읽기에서의 각 파일의 줄을 적었습니다. 브리프는 줄 번호가 아니라 심볼로 지목하며, 지목한 심볼이 모두 그대로 있는지 12시 42분에 확인했습니다.11
  • 앱이 실행되는 iPhone. iPhone 33종(그중 3종이 2배)의 목록은 제 Mac의 Xcode에서 읽은 iOS 27.0 시뮬레이터 런타임의 것입니다. iOS 27이 지원하는 iPhone에 대한 Apple의 페이지는 가져오지 않았습니다.97
  • 안전 영역 인셋. 이 시리즈의 iPhone Duo 시뮬레이터 글은 닫힌 바깥쪽 디스플레이를 너비 466포인트로 측정했으며, 상태 막대, 도구 막대, 탭 막대를 보인 프로브에서 콘텐츠는 84포인트 뒤쪽 인셋, 즉 사이드 레일 안쪽에서 너비 382포인트였습니다.95 Kiradex의 세계는 상태 막대와 홈 인디케이터를 숨기고 막대를 보이지 않습니다. 코드는 세계의 크롬을 안전 영역 안에 두며 그것이 카메라와 사이드 레일을 피하게 해 준다고 말하지만, 상태 막대를 숨겼을 때 인셋이 얼마나 넓은지는 코드도 그 측정도 말해 주지 않습니다. 그래서 10절과 12절의 Duo 숫자는 좌우 인셋이 없을 때와 측정한 84포인트일 때 두 가지로 적었습니다. 다른 iPhone의 세로 방향 좌우 인셋은 측정하지 않았으며, 그에 대한 브리프의 숫자는 인셋이 없다고 가정합니다.94
  • Kiradex 구성에서의 RealityKit 손쉬운 사용. AccessibilityComponent는 iOS 17.0으로 문서화되어 있지만, 가상 직교 카메라를 둔 RealityView가 엔티티 요소를 VoiceOver에 노출하는지는 확인되지 않았습니다.84
  • 브리프의 숫자. m의 반올림, 상자보다 한 단계 작고 뷰마다 정한 상한 아래로 묶인 말풍선, 32포인트 여백, 상자의 대기열과 그 세 줄, 상자의 패딩 대신 테두리가 차지하는 8폰트 픽셀, 150밀리초의 표시 단계, 테두리 네 가지, 안내의 여섯 셀은 제안이지 사실이 아닙니다.

이 글이 세계 편 글과 다른 점. 1, 2, 5, 8절이 각각 그 글의 진술 하나씩을 측정과 함께 바로잡습니다. 크기 산수는 맞았습니다(10절). 이 글이 시리즈의 다른 글에서 가져온 세 가지 사실은 앱과 대조했습니다. 마을의 프리셋 대사 23줄은 lines.py와 일치합니다. 말풍선이 아직 셀의 높이에서 자리를 정한다는 캐릭터 편 글의 메모는, 걷는 이의 위치에 시트 프레임 높이의 절반과 4를 더하는 PlazaRig.headPoint와 일치합니다. 그리고 말풍선 아래의 인물은 그 글의 업데이트가 설명하는, 32×40 셀에서 키 30텍셀인 인물이며, 앱의 매니페스트, rig.py, 몸체 시트가 이를 확인해 줍니다. 그 글이 너비를 밝힌 것은 이전의 26텍셀 인물에 대해서뿐으로, 어깨에서 약 14였습니다. 현재 인물의 너비는 12절에서 시트로 측정합니다.90100289396

닿지 못한 출처. Data Crystal의 Chrono Trigger ROM 맵(HTTP 403), NotABug의 Pixel Operator 저장소(HTTP 404), m5x7과 m3x6 폰트 파일(원하는 금액을 내는 다운로드 뒤에 있으며, 시도하지 않았습니다), 그리고 Celeste, Sea of Stars, Eastward의 일본어와 중국어 텍스트에 대한 개발자 자료(찾지 못했습니다)입니다.49565758

12. 브리프: Kiradex가 만들 것과 통과해야 할 검사

그 뒤로 앱은 앞으로 나아갔습니다. 10월 15일의 커밋 fc9d4876에서는 무대의 레이블과 값이 StageAccessibility 뷰 수정자 안에서 설정되며(PlazaStage.swift, 373번째 줄에서 적용, 520번째 줄부터 정의), 레이블은 여전히 “The plaza”, 값은 걸음 기록이고, 장소가 무대를 덮고 있는 동안에는 요소가 사라집니다. 아래 검사는 10월 5일 빌드를 읽은 것이며, “현재”나 “오늘”은 모두 그 빌드를 뜻합니다. 항목 8은 둘 다에 적용됩니다.101

저자의 종합. 아래는 제 제안이며, 1절에서 11절까지를 바탕으로 2026년 10월 5일 시점의 Kiradex, 즉 커밋 334fbfe에 그날의 커밋되지 않은 편집이 작업 트리에 더해진 상태(11절)와의 차이로 썼습니다. 그 상태란 세계의 모든 뷰에서의 시스템 고정폭 텍스트, caption 굵게 한 줄짜리 말풍선, 아래쪽의 WorldHUD 상자, 서버에 영어 프리셋 대사를 둔 영어 전용 앱, 글자 없는 간판 스프라이트, 그리고 VoiceOver가 좌표로 읽는 무대입니다.2211 세계 편 글의 방향은 그대로입니다. 텍스트는 아트다. 대화는 세계의 텍셀이 아니라 UI 스케일로 SwiftUI 안에 산다. 상자는 타일로 짓는다. 두 줄. 타자기. 넘김 표시.23 바뀌는 것은 서체(5절이 밝힌 이유로)와 산수(Dynamic Type을 폰트의 그리드에 맞춰 반올림해야 하므로)입니다.7 여기서는 어떤 프랜차이즈의 글리프, 테두리, 표시, 단어도 그리지 않습니다. 모두 포지가 그리거나, 4절에 라이선스를 인용한 폰트로 조판합니다. 법적인 메모는 제 해석이며 법률 자문이 아닙니다. 아직 아무것도 만들지 않았습니다.

아래의 검사는 모두 스크립트, 테스트의 로그 한 줄, 캡처 중 하나로 합격과 불합격을 가를 수 있습니다. 예외는 항목 8에 있는, 날짜를 적고 기기에서 하는 수동 검사 하나입니다. 검사의 결과가 기기, 텍스트 크기, 고르는 대사, 상자가 그 밖에 무엇을 보여 주고 있는지에 달려 있다면 그것을 밝혀 둡니다. 제가 읽는 동안 코드가 움직였으므로, 브리프는 앱의 코드를 줄 번호가 아니라 심볼로 지목합니다. 각주에는 제가 읽었을 때의 줄을 커밋과 시각과 함께 적었습니다. 항목의 근거가 제 Mac에서의 측정이고 검사가 iPhone이나 그 시뮬레이터에서 실행된다면, 결정하는 것은 Mac이 아니라 검사입니다.

1. 두 언어에 서체 하나: PixelMplus10

내용. PixelMplus10-Regular.ttf와 PixelMplus10-Bold.ttf(itouhiro 작, M+ FONTS PROJECT의 M+ BITMAP FONTS에서. 라이선스: M+ FONT LICENSE, https://github.com/itouhiro/PixelMplus/blob/master/misc/mplus_bitmap_fonts/LICENSE_E)를 UIAppFonts로 번들에 넣고, 번들에 LICENSE_E를 넣으며, 지금은 없는 감사의 글 섹션을 설정에 추가해 itouhiro와 M+ FONTS PROJECT의 이름을 적습니다.1872 세계 위에 그리는 모든 글자에 씁니다. 말풍선, WorldHUD 상자, 간판 글자, 퀘스트 게시판, show-and-tell 명판입니다. 앱의 나머지, Dex, 스캐너, 설정은 시스템 서체를 유지합니다.

이것을 고른 이유. 9절의 규칙이 요구하는 세 가지, 일본어, 깨끗한 그리드, 정수 픽셀 어드밴스를 모두 갖춘 측정된 서체는 둘입니다. Misaki Gothic과 Misaki Gothic 2nd는 JIS X 0208의 6,879자 전부를 베이스라인 위의 8픽셀 그리드에 그리며, 샘플한 아웃라인 좌표 중 벗어난 것이 848개 중 0개와 860개 중 0개, 어드밴스는 4와 8입니다. PixelMplus10은 6,879자 중 6,876자, 두 수준의 모든 한자를 베이스라인 위의 10픽셀 그리드에 그리며, 샘플한 아웃라인 좌표 중 벗어난 것이 Regular에서 1,032개 중 0개, Bold에서 1,000개 중 0개, 어드밴스는 라틴 문자 5, 가나 10입니다.7 브리프는 PixelMplus10을 고릅니다. 제 선택입니다. 둘 중 큰 쪽, Misaki의 8에 대해 10픽셀이며, 일본어 독자에게 가독성이 가장 의심스러운 것은 Misaki의 8×8 한자입니다(11절). 그리고 측정된 Bold가 함께 있어서 굵은 텍스트를 합성할 필요가 없습니다. 물리친 것들은 각각 그것을 물리친 측정과 함께 적습니다.

  • PixelMplus12(M+ FONT LICENSE): 그리드가 베이스라인보다 0.39픽셀 아래에 있고, macOS 27.0의 Core Text는 모든 크기에서 이를 번지게 했습니다.7
  • k8x12(littlelimit.net의 세 문장짜리 라이선스, https://littlelimit.net/font.htm#license): 가나 어드밴스가 8.01픽셀이고, 라틴 문자는 너비 4픽셀입니다.717
  • Misaki(같은 라이선스, https://littlelimit.net/font.htm#license): 세 가지를 모두 갖췄지만 측정한 것 중 가장 작은 8×8 한자이며, 일본어 독자에게 가독성이 가장 의심스러운 것입니다(11절).617
  • DotGothic16(SIL Open Font License 1.1, https://github.com/google/fonts/blob/main/ofl/dotgothic16/OFL.txt): 도트가 어떤 그리드에도 놓여 있지 않으며, macOS 27.0의 Core Text는 16에서 64픽셀에서 37.3%에서 82.3%를 중간 톤으로 그렸습니다.753
  • Press Start 2P(예약 폰트 이름이 있는 SIL Open Font License 1.1, https://github.com/google/fonts/blob/main/ofl/pressstart2p/OFL.txt)와 Silkscreen(SIL Open Font License 1.1, https://github.com/google/fonts/blob/main/ofl/silkscreen/OFL.txt): 일본어가 없습니다.75455
  • m5x7(그 페이지: “Creative Commons Zero v1.0 Universal”, https://managore.itch.io/m5x7)과 m3x6(그 페이지: “free to use with attribution”, 라이선스 항목 없음, https://managore.itch.io/m3x6): 측정하지 않았습니다. 페이지 자신의 설명으로는 라틴 문자 전용이며, 원하는 금액을 내는 다운로드 뒤에 있습니다.5758
  • Pixel Operator(DaFont에 있는 작가의 메모로는 CC0 1.0, https://www.dafont.com/pixel-operator.font): 측정하지 않았습니다. DaFont에서 글리프 241개로, 일본어에는 턱없이 부족합니다.56

키트. 제 앱들의 공유 키트는 모든 텍스트 스타일을 .kit941Font(role)로 거치게 하며, 그 커스텀 폰트 경로는 폰트를 연속적으로 키웁니다. 그러면 이 서체는 Body 크기 12단계 중 11개에서 그리드를 벗어나게 되며, 그것이 Mac의 Core Text가 번지게 한 조건입니다.887 세계는 이미 자체 두 색을 가진, 선언된 아트 표면 WorldInk입니다.25 Kiradex 내부에 PixelText 수정자를 더하고, 그 아트 표면의 예외로 주석과 함께 선언하며, 키트의 역할을 받아 항목 2의 반올림을 적용하게 합니다. 키트 패밀리의 케이스로 상위에 제안하는 것은 출시한 뒤에 합니다.

합성 굵기는 없음. 굵은 텍스트에는 PixelMplus10-Bold를 씁니다. 픽셀 서체에 .weight(_:)는 쓰지 않습니다. 합성해야 하는 굵기에 Core Text가 무엇을 하는지는 측정하지 않았기 때문입니다(11절).

검사. - Core Text는 iOS에도 있으므로, 단위 테스트로 번들에 넣은 두 파일을 불러와 다음을 단언합니다. em당 1,000단위. 세계의 문자열과 프리셋 대사가 두 언어에서 쓰는 모든 글리프의 어드밴스가 100단위의 배수일 것. 샘플 글리프의 아웃라인 점이 100단위 그리드에서 4단위 이내(폰트 픽셀의 0.04, 4절의 프로브가 쓴 허용 오차)에 있을 것. 불합격한 서체는 출시할 수 없습니다. - 세계가 보여 줄 수 있는 모든 문자열(프리셋 대사, String Catalog의 세계 키, 간판 글자)이 대체 폰트 없이 그 서체의 글리프에 대응하는지에 대한 테스트. - 빌드한 앱 번들에서 LICENSE_E를 찾는 빌드 단계 스크립트와, 설정의 새 감사의 글 섹션에서 폰트의 이름, 작가, 라이선스를 찾는 UI 테스트.

2. 크기: 그리드에 맞춰 반올림한 Dynamic Type

내용. 폰트 픽셀 하나는 언제나 정수 m개의 기기 픽셀입니다. 역할마다 시스템이 쓸 Dynamic Type 크기를 읽고(UIFontMetrics(forTextStyle:).scaledValue(for:), 또는 역할의 기준 크기에 대한 @ScaledMetric(relativeTo:)), 그 크기를 100분의 1포인트로 반올림합니다. 표의 크기에 아주 조금 못 미치는 키운 값, 이를테면 15에 대한 14.999…가 표의 크기처럼 반올림되게 하기 위해서입니다. 그런 다음 m = max(m₀, ⌊size × displayScale / 10 + 0.5⌋), 즉 0.5를 올리는 가장 가까운 정수로 하고, m₀ = ⌈11 × displayScale / 10⌉는 11포인트 이상이 되는 가장 작은 m, pointSize = 10 × m / displayScale로 하여, 시스템이 다시 키우지 않도록 Font.custom(_:fixedSize:)로 적용합니다.676968 상자의 제목과 텍스트는 지금 쓰는 caption 역할에서 body 역할로 옮기며, 그러면 3배에서 상자는 Body 크기 12단계를 m = 4, 5, 5, 5, 6, 6, 7, 8, 10, 12, 14, 16에 대응시킵니다. 기본 크기에서 16.67포인트, AX5에서 53.33포인트입니다.2611 가장 작은 3배의 m₀ = 4는 13.33포인트로, 최소 11포인트보다 큽니다.1033 iOS 27.0 시뮬레이터가 iPhone 세 종에 대해 나열하는 2배 화면(항목 4)에서는 m₀이 3, 15포인트이고, Body 크기 12단계는 3, 3, 3, 3, 4, 4, 5, 6, 7, 8, 9, 11이 되어 기본 크기에서 15포인트입니다.9711 말풍선은 한 단계 작게 하되, m₀ 아래로 내려가지 않고, 뷰마다 정하는 상한을 넘지 않게 합니다(항목 4).

배치. 픽셀 서체는 베이스라인이 기기 픽셀에 떨어질 때만 선명합니다(6절). 세계 오버레이에서 계산하는 모든 원점을, PlazaStage.bubbles의 .position을 포함해 기기 픽셀 그리드에 반올림하고((x × scale).rounded() / scale), 텍스트 뷰에는 정수 기기 픽셀의 너비를, 상자에는 정수 폰트 픽셀의 너비와 높이를 줍니다(항목 3).

줄 간격. PixelMplus10은 위로 9폰트 픽셀, 아래로 2폰트 픽셀입니다. 줄 간격을 12폰트 픽셀로 해서 줄 사이에 빈 행 하나를 둡니다. Emerald의 15픽셀 일본어 글리프와 16픽셀 라틴 문자 셀이 16픽셀 줄에 들어가는 것과 같습니다.71 m = 5에서는 한 줄 20포인트입니다.33

검사. - 반올림의 순수 함수 테스트를 모든 DynamicTypeSize에 대해 표시 배율 2와 3에서 합니다. 결과는 폰트 픽셀당 정수 개의 기기 픽셀이며, 11포인트 아래가 되지 않고, 더 큰 크기에서 더 작아지지 않습니다. 테스트는 함수에 scaledValue(for:)에서 읽은 값이 아니라 HIG 표의 Body 크기를 직접 넣습니다. 전자의 정확한 출력을 저는 보지 못했기 때문입니다. 3배에서는 12개가 위의 12개 값이 되고 Small의 4.5는 5로 올라가며, 2배에서는 2배의 12개 값이 됩니다. 각 크기에서 10억분의 1포인트를 빼도, 100분의 1로의 반올림을 거쳐 같은 m이 나옵니다.11 - iPhone 18 Pro Max 시뮬레이터의 Large, xxxLarge, AX3와, 2배인 iPhone SE(3세대) 시뮬레이터의 Large에서 말풍선과 상자가 있는 광장을 캡처합니다. 말풍선 오버레이는 무대의 스캔라인과 비네트 오버레이 위로 옮겨 둡니다(항목 4). 그리고 텍스트를 잘라 잉크와 종이 두 색에 대한 중간 톤 픽셀을 세는 스크립트로, 글리프 상자 안쪽, 말풍선 안과 상자 안에서 0임을 확인합니다. 모든 캡처에는 광장 서버 실행(KIRADEX_PLAZA=1)이 필요하며, 플레이어가 Say 메뉴에서 현재 가장 긴 대사를 말한 뒤 4초 안에 찍습니다. 모든 대사는 방에서 되돌아온 뒤에 표시되고102, 말풍선은 대사가 나온 지 4초가 지나기 전에만 보이기 때문입니다.25103 같은 스크립트를 현재 빌드에서 실행해 대체될 기준값을 기록합니다. 6절의 0.0%는 폰트 스무딩을 끈 Mac의 Core Text의 것이며, iPhone이 똑같이 그리는지를 결정하는 것은 이 캡처입니다.

3. 상자: 포지가 그린 테두리, 두 줄, 페이지

내용. WorldHUD의 상자는 지금의 자리를 지킵니다. 아래쪽, 안전 영역 안, Duo가 반쯤 열렸을 때는 접힌 선의 넓은 쪽입니다.26 2포인트 선으로 그린 사각형은 포지가 그리는 테두리가 됩니다. frame-paper.png는 24×24폰트 픽셀, 8짜리 타일 3×3(모서리, 가장자리, 가운데)이며, WorldInk의 두 색에 음영 하나를 더한 Kiradex 고유의 디자인입니다. PixelFrame 뷰가 이를 Canvas에 그리며, 원본 픽셀 하나를 m×m 기기 픽셀의 정사각형으로 만듭니다. 네 모서리에는 모서리 타일을 놓고, 가장자리와 가운데 타일은 왼쪽 위 모서리에서 바깥쪽으로 반복하며, 반대편 끝에 닿는 곳에서 마지막 타일을 자릅니다. 그래서 테두리의 픽셀이 텍스트의 픽셀과 맞습니다. 모든 원본 픽셀이 정수 정사각형이 되도록, 상자의 너비는 WorldHUD가 지금 배치하는 안전 영역의 너비에서 좌우 각각 16포인트를 빼고 정수 폰트 픽셀, 즉 기기 픽셀 m개의 배수로 내림한 값으로 하며, 높이도 정수 폰트 픽셀로 합니다. 세로 방향 18 Pro Max에서 m = 5이고 좌우 인셋이 없다면(측정하지 않은 가정입니다), 1,224기기 픽셀은 244.8폰트 픽셀이므로 1,220, 즉 244폰트 픽셀로 내림합니다.1194 텍스트는 테두리 안쪽, 상자 가장자리에서 모든 변으로 8폰트 픽셀 떨어진 곳에 두어 지금의 8포인트 패딩을 대신합니다(제 선택). 그러면 그곳에서는 한 줄에 228폰트 픽셀, 라틴 문자 45자나 전각 22자가 남습니다.11 장소의 제목은 모든 페이지의 맨 위 자기 줄에 두고, 그 아래로 텍스트를 Emerald와 Crystal처럼 한 번에 두 줄씩 보여 줍니다.1 두 줄보다 긴 텍스트는 페이지를 넘기며, 각 페이지는 넘김 표시 아래에서 기다립니다(항목 6). 페이지가 공백에서 끝나면 그 공백은 버립니다. WorldHUD의 box(WorldHUD.swift 안)는 상태 문자열 하나 대신 페이지들을 받습니다.

대기열. 상자가 보여 주는 것이 상태의 길이에 좌우되지 않도록 하는 제 규칙입니다.

  • 상태의 페이지는 상자 자신의 것입니다. 상태가 바뀌면(광장에서는 그 안의 무언가가 바뀔 때, 즉 연결 상태, 여기 있는 사람 수, 시간대, 계절, 얻은 리본), 그 페이지들은 교체되고 상자는 새 상태를 첫 페이지부터 보여 줍니다. 단, 다음 규칙의 페이지가 표시 중이라면 예외입니다.
  • 간판의 글자(항목 5)와 큰 크기에서 근처에서 말해진 대사(항목 4)는 상태보다 앞서는 페이지입니다. 곧바로 표시되며, 각각 이미 기다리고 있는 그런 페이지 뒤에, 들어온 순서대로 섭니다. 그 마지막 페이지 다음에는 다시 상태를 첫 페이지부터 보여 줍니다. 상자는 가장 최근 대사 세 개까지의 페이지를 보관합니다. 네 번째가 오면, 기다리는 것 중 플레이어 자신의 것이 아닌 가장 오래된 대사를 버립니다(플레이어 자신의 가장 오래된 대사를 버리는 것은 기다리는 대사가 모두 그 플레이어의 것일 때뿐입니다). 그래서 대사 하나를 말한 플레이어가 그것을 잃는 일은 없습니다. 새 간판은 아직 기다리고 있는 간판의 페이지를 대체합니다.
  • 상태 텍스트는 어떤 페이지를 보여 주든 지금처럼 상태 전체를 손쉬운 사용 레이블로 지닙니다. 그래서 VoiceOver는 상태 전체를 읽고, plaza-status, room-status, hall-status를 읽는 기존 UI 테스트(첫 구절, 여기 있는 사람 수, 방의 첫 단어)는 지금 읽는 것을 그대로 읽습니다. 표시된 페이지는, 드러난 데까지, 숨겨진 요소의 값이 됩니다. 광장에서는 plaza-page입니다(<name>-page, 제가 붙인 이름). 그 값은 장소의 제목을 뺀 페이지의 텍스트 줄들이며, 이 요소는 걷기 테스트의 실행 인자 -worldTrace 아래에서만 존재합니다(항목 8). 아래 검사들은 이것을 읽으며, 모두 -worldTrace를 넘깁니다.103

고를 수 있는 테두리. Emerald는 메뉴의 테두리를 20종 중에서 고르게 하고(대화 상자는 테두리 하나를 유지합니다), Final Fantasy VI는 창을 8종 중에서 고르고 색도 편집하게 합니다.14 설정에 포지가 그린 테두리 네 가지를 모두 무료로 제공하며, 처음 셋은 처음부터, 넷째는 레벨에 도달하면 열립니다. 테두리는 절대 팔지 않습니다. 후원자를 위한 보상으로 테두리를 줄지는 아직 정하지 않았습니다.

검사. frame-paper.png와 각 대체 테두리가 24×24이고, 팔레트 인덱스만 쓰며, 디자인이 그렇다고 정한 곳에서는 마주 보는 가장자리가 같은지 확인하는 포지 검사. iPhone 18 Pro Max 시뮬레이터에서 m = 4(xSmall)와 m = 5(기본 크기)의 상자를 캡처하고, 스크립트로 상자의 너비와 높이가 기기 픽셀로 m의 배수인지 확인하며, 그 테두리, 즉 모든 변의 바깥쪽 8폰트 픽셀을, 같은 규칙(모서리에 모서리 타일, 가장자리와 가운데 타일을 왼쪽 위에서 반복하고 반대편 끝에서 자름)으로 캡처한 상자 크기에 맞춰 PNG에서 직접 조립한 테두리와, 원본 픽셀 하나를 m×m 정사각형으로 하여 픽셀 단위로 비교합니다. PNG가 투명하게 둔 픽셀, 즉 세계가 비쳐 보이는 곳은 뺍니다. 그리고 같은 시뮬레이터의 기본 크기에서, 대사가 말해지지 않도록 광장 서버 없이, -worldTrace와 -worldBoxSample, 그리고 줄 바꿈으로 이은 30에서 40자짜리 세 줄(모두 그곳에서 한 줄에 들어가는 45자 이내)의 상태로 실행하는 UI 테스트를 합니다. 이 테스트는 plaza-page가 공백을 빼고 처음 두 줄과 같아질 때까지 기다리고, 그런 뒤에야, 첫 페이지가 다 드러난 상태에서 상자를 탭해 셋째 줄을 찾습니다. 첫 페이지를 캡처한 것에서는 스크립트가 상자 오른쪽 아래에서 표시를 찾습니다.11 -worldBoxSample은 제가 붙인 이름의 테스트 인자로, -plazaTell(그 tellOnArrival)처럼 PlazaView에서 읽어, 광장 자체의 상태 대신 WorldHUD에 상태로 넘깁니다.92

4. 말풍선: 한 줄, 이름 없음, 두 언어

내용. 말풍선의 형태는 유지합니다. 크림색, 잉크 테두리, 머리 위, 4초 미만.25 말풍선의 m으로 픽셀 서체를 써서 조판하고, 선으로 그린 사각형 대신 테두리의 모서리 픽셀을 써서 말풍선의 좌우 8포인트 패딩 안쪽에 그립니다. 그러면 지금의 테두리처럼 너비가 늘지 않습니다. 말풍선 안에도 위에도 이름은 넣지 않습니다. 화자는 말풍선이 떠 있는 머리이며, VoiceOver가 그 이름을 말합니다(항목 8). 프리셋 대사는 절대 줄을 바꾸지 않으며, .fixedSize()는 그대로 둡니다.

스캔라인 위로. 지금 말풍선은 무대 안쪽의 오버레이로, 무대의 스캔라인(3포인트마다 불투명도 16%의 검은 선, 3배에서는 기기 픽셀 9개마다 3개 높이의 띠)과 비네트 아래에 있으며, 상자는 무대 전체 위에 덮여 있습니다.2526 그 층들 아래에서는 말풍선의 종이가 글리프를 가로지르며 띠 모양으로 어두워지므로, 어떤 크기에서도 텍스트가 잉크와 종이가 되지 않습니다. 브리프는 항목 2의 검사를 상자로 한정하는 대신, 말풍선 오버레이를 상자가 이미 있는 스캔라인과 비네트 오버레이 위로 옮깁니다. 제 선택이며, 픽셀 서체는 잉크와 종이의 정수 기기 픽셀일 때만 선명하다는 6절의 규칙을 두 말하기 방식에 똑같이 적용한 것입니다. PlazaStage.swift에서는 무대의 GeometryReader 안 마지막 수정자인 말풍선의 .overlay를 비네트의 .overlay(RadialGradient) 뒤로 옮기고, 뷰 크기를 위한 자체 GeometryReader를 줍니다.25

화면 안에. 지금 말풍선은 화자의 머리 위 가운데에 놓이며, 화면 안에 머물게 하는 장치가 없어서 가장자리 근처의 화자는 말풍선 일부를 화면 밖으로 밀어냅니다.25 말풍선의 프레임을 안전 영역에서 모든 변으로 16포인트(아래 너비 규칙이 남기는 여백)를 뺀 범위 안에 가두고, 머리 위 가운데에서는 가두는 데 필요한 만큼만 옮깁니다. 뷰가 아니라 안전 영역입니다. 무대는 안전 영역을 무시하므로(.ignoresSafeArea()), 무대 자체의 지오메트리는 안전 영역이 비워 두는 것, Duo에서는 카메라와 사이드 레일 아래까지 뻗어 있습니다. WorldHUD의 주석도 안전 영역 안에 있는 자기 자리에 대해 그렇게 말합니다.94 지금 무대에 인셋을 넘겨 주는 것은 없습니다. PlazaView가 무대 밖에서 인셋을 읽어 넘깁니다. CardViewer가 이미 카드에 대해 그렇게 하는 것과 같습니다(proxy.safeAreaInsets를 CardStage로). 가두는 일은 순수 함수 BubblePlacement.frame(head:size:view:insets:)가 하며, PlazaStage.bubbles(of:in:)에서 부르고, 같은 인셋으로 아래의 말풍선 상한도 정합니다(이름은 제 것입니다). 세계는 상태 막대와 홈 인디케이터를 숨깁니다(루트 뷰의 .statusBarHidden()과 .persistentSystemOverlays(.hidden)). 그것이 인셋을 좁히는지는 코드에 없으므로, 앱은 인셋을 가정하지 않고 읽습니다.94

너비 규칙. 모든 대사는 두 언어 모두, 앱이 실행될 수 있는 모든 iPhone에서, 안전 영역에 좌우 인셋이 없을 때 한 줄 말풍선으로 들어갑니다. 그것을 보장하는 것이 아래의 서버 한도입니다. 인셋이 있으면 m₀조차 들어갈 자리가 남지 않을 수 있습니다. 현재 가장 긴 대사의 경우, iOS 27.0 시뮬레이터가 나열하는 모든 너비 중에서, Duo의 84포인트 인셋이 있을 때 393포인트까지의 모든 3배 너비와 두 2배 너비, 그리고 좌우 16포인트씩일 때 375포인트 2배 휴대폰이 그렇습니다. 서버 한도에 꼭 맞는 대사는 더 많은 경우에 들어가지 않습니다. 일본어 대사는 Duo의 인셋이 있을 때 402포인트 휴대폰에서도 들어가지 않고(자리 286포인트에 대해 m₀에서 296), 라틴 문자 대사도 거기서(302.67), 인셋이 있을 때 414포인트 3배 휴대폰(298), 좌우 16포인트씩일 때 360포인트 휴대폰(296)에서 들어가지 않습니다. 너비 12개와 인셋 세 가지의 36가지 경우 중, 현재 대사로 7가지, 한도의 일본어 대사로 8가지, 라틴 문자 대사로 10가지입니다. 그런 곳에서 그 대사는 말풍선이 되지 않고 상자로 가며, 더 짧은 대사들은 말풍선을 유지합니다(아래).11 앱은 iOS 27.0 이상의 iPhone 전용으로 빌드되며, 제 Mac의 Xcode와 함께 설치된 iOS 27.0 시뮬레이터 런타임은 iPhone 33종을 나열합니다. 3배에서 가장 좁은 것은 너비 360포인트(iPhone 12 mini와 13 mini)이고, 세 종이 2배로, 2세대와 3세대 iPhone SE가 375포인트, iPhone 11이 414포인트입니다.97 이것은 SDK가 시뮬레이션하는 기기의 목록이며, iOS 27이 지원하는 iPhone에 대한 Apple 자체의 목록은 가져오지 않았습니다. 말풍선의 자리는 안전 영역의 너비에서, 가두기가 좌우 16씩 확보하는 32포인트를 뺀 것입니다. 세로 방향 휴대폰에서 좌우 인셋이 없다면(가정이며, 어느 것도 측정하지 않았습니다), 이 시리즈가 측정한 다섯 뷰 중 가장 좁은 iPhone 18 Pro Max에서 408이지만, 360포인트 휴대폰에서는 328, 375포인트 2배 휴대폰에서는 343입니다. 세로 방향 Duo 바깥쪽 디스플레이에서는 좌우 인셋이 없으면 434, 세계가 이 시리즈가 닫힌 디스플레이에서 측정한 84포인트 뒤쪽 인셋, 즉 사이드 레일을 유지한다면 350입니다(11절).11979533 현재 가장 긴 대사, 40자의 “Anyone have a card from the first years?”는 3배에서 말풍선으로 하면, 글리프에 지금의 좌우 8포인트 패딩을 더해 m = 4에서 너비 283포인트, 5에서 349, 6에서 416, 8에서 549입니다. 2포인트 테두리는 말풍선 안쪽에 그려져 너비를 늘리지 않습니다. 2배에서는 같은 대사가 m = 3에서 316포인트, 4에서 416입니다.1125 그래서 말풍선의 m에는 뷰마다 상한이 있습니다. 어느 언어든 가장 긴 프리셋 대사가 자리에 들어가는 가장 큰 m입니다. 현재 대사(모두 영어)로는 18 Pro Max(408에 349)와 나열된 390에서 440포인트의 모든 3배 iPhone에서 5, 360과 375포인트 3배 휴대폰에서 4, 2배 휴대폰에서는 2배가 허용하는 가장 작은 3입니다. 세로 방향 Duo 바깥쪽 디스플레이에서는 좌우 인셋이 없으면 6(434에 416), 측정한 레일이 있으면 5(350에 349.33)입니다. 아래 서버 한도에 꼭 맞는 일본어 대사, 전각 21자, 어드밴스 210폰트 픽셀이 있으면 Duo의 상한은 5와 4로, 390과 393포인트 휴대폰의 상한은 4로 내려가며(나열된 iPhone 중 일곱 종이 390포인트, 네 종이 393포인트), 나머지는 그대로입니다.1197 앱은 PlazaView가 넘기는 안전한 너비와 서버가 보내는 대사로 상한을 계산하므로, 안전 영역이 더 좁아지거나 대사가 더 길어지면, 이를테면 일본어 대사가 더해지면 상한이 내려갈 수 있습니다. 상한은 모든 대사에 걸친 값 하나입니다. 상한이 없는 곳에서 어떻게 할지는 대사마다 정합니다. 제 선택입니다. 가장 긴 대사에 m₀조차 들어가지 않는 곳에서는, m₀으로 들어가는 대사는 m₀으로 말풍선을 유지하고, 들어가지 않는 대사는 말풍선 없이 큰 크기에서처럼 자기 페이지로 상자에 갑니다(아래). 그 대사의 말풍선을 m₀으로 남기면 가두기가 깨집니다. 84포인트 뒤쪽 인셋이 있는 360포인트 뷰에서는 자리가 244포인트이고, 현재 가장 긴 대사는 m₀에서 282.67이기 때문입니다.11 서버는 서버대로, 모든 대사를 좌우 인셋 없이 각 배율에서 가장 좁은 휴대폰에 가장 작은 크기로 들어가는 길이로 제한합니다. 어드밴스로 최대 218폰트 픽셀, 라틴 문자 43자나 전각 21자이며, 3배의 (360 − 48) × 3 ÷ 4 = 234와 2배의 (375 − 48) × 2 ÷ 3 = 218 중 작은 쪽입니다. 그 한도에 꼭 맞는 일본어 대사는 3배의 m = 4에서 말풍선으로 296포인트, 2배의 m = 3에서 331입니다. 라틴 문자 대사는 302.67과 338.50입니다. 둘 다 레일이 있는 Duo의 350에 들어갑니다.1133 휴대폰의 세로 방향 좌우 인셋은 측정하지 않았습니다. 그것이 드러난다면, 말풍선이 안전 영역 안에 있음을 단언하는 아래의 캡처에서일 것입니다.

PixelMplus10을 3배로 쓴 한 줄 말풍선으로서의 가장 긴 프리셋 대사의 너비를 나타낸 가로 막대그래프. 말풍선의 자리(안전 영역의 너비에서 32를 뺀 것, 휴대폰은 세로 방향에 좌우 인셋이 없다고 가정)를 점선으로 표시: m 4에서 283포인트로, 360포인트 iPhone의 자리 328포인트 안. m 5에서 349로, 328을 넘지만 84포인트 레일이 있는 세로 방향 iPhone Duo 바깥쪽 디스플레이의 350과 iPhone 18 Pro Max의 408 안. m 6에서 416으로, 408을 넘지만 좌우 인셋이 없는 Duo 바깥쪽 디스플레이의 434 안. m 8에서 549로, 넷 모두를 넘는다.

3배에서 크기별로 말풍선이 된 가장 긴 프리셋 대사와 각 뷰의 자리. measure_kiradex_text.out에서 figures/make_figures.py로 그렸습니다.

더 큰 텍스트. 말풍선의 m은 상자의 m을 한 단계 뒤에서 따르며, m₀ 아래로 내려가지 않고 뷰의 상한을 넘지 않습니다. 18 Pro Max에서는 상자가 xLarge(m = 6)일 때 5에 이르고, 상자의 m이 7 이상이 되는 xxxLarge부터는 5에 머뭅니다. 상한 때문에 말풍선이 상자보다 두 단계 이상 작게 묶이는 곳(18 Pro Max에서는 xxxLarge부터, 360포인트 휴대폰에서는 xLarge부터)에서는, 플레이어에게서 여섯 칸 안에서 말해진 대사를 읽는 사람이 상자에서도 상자 크기의 자기 페이지로 받습니다. 화자의 핸들, 콜론, 그리고 그 대사이며, Emerald가 화자의 이름을 줄 안에서 밝히는 것과 같습니다(항목 3이 그것을 넘기고, 항목 8이 안내합니다). 방의 대사를 지니고 지금 상자의 상태를 짜는 PlazaView가 그 페이지들을 짜서, 항목 3의 대기열에 따라 상태보다 앞에 둡니다. 곧바로 보여 주고, 이미 기다리는 대사의 페이지 뒤에 세우며, 최대 세 대사를 보관합니다. 대사의 페이지는 그 말풍선을 더하는 것과 같은 업데이트에서 상자의 대기열에 넣으므로, 그런 대사의 말풍선이 화면에 있다면 그 페이지들은 대기열에 들어가 있습니다. 늘 한 페이지인 것은 아닙니다. 핸들은 14에서 20자이므로 “handle: line”은 19에서 62자가 되며, 항목 3의 테두리에서 18 Pro Max 상자의 한 줄에는 라틴 문자가 xxxLarge에서 31자, AX3에서 17자, AX5에서 12자 들어갑니다. names.py가 만들 수 있는 모든 핸들과 모든 프리셋 대사에 대해, xxxLarge에서 AX3까지는 한두 페이지, AX4와 AX5에서는 최대 세 페이지, 그리고 360포인트와 2배 휴대폰의 가장 큰 크기와 측정한 레일이 있는 Duo 바깥쪽 디스플레이의 AX4와 AX5에서는 최대 네 페이지입니다.1189 상자 자체는 절대 제한하지 않습니다. 줄을 바꾸고 페이지를 넘깁니다. dynamicTypeSize(_:) 페이지는 크기를 제한한다면 큰 콘텐츠 뷰를 권하지만, 브리프는 그것에 기대지 않습니다. 그 뷰어 자신의 페이지가 “as a replacement for proper Dynamic Type support”(제대로 된 Dynamic Type 지원의 대체물로) 쓰지 말라고 경고하며, 말풍선처럼 히트 테스트가 꺼진 오버레이에서 그것이 열리는지는 어느 페이지도 말하지 않기 때문입니다.707125 제 조사 노트는 말풍선을 m = 8에서 멈추자고 제안했지만, 가장 긴 대사는 8에서 너비 549포인트로(노트는 말풍선 안에 있는 테두리까지 세어 553), 18 Pro Max(440포인트)와 세로 방향 Duo 바깥쪽 디스플레이(466)보다 넓습니다. 게다가 노트는 더 좁은 것이 440인 18 Pro Max인데도 Duo 바깥쪽 디스플레이를 기준으로 한도를 쟀습니다. 여기의 브리프는 너비를 따릅니다.2211

검사. - 앱의 폰트 테스트(항목 1)는, 저장소에 넣은 PixelMplus10 어드밴스 표(어느 언어로든 프리셋 대사가 쓰는 문자마다 항목 하나)가 번들에 넣은 폰트와 일치한다는 것도 단언합니다. 폰트 파서가 없는 서버 테스트 모음(요구 사항은 FastAPI, uvicorn, websockets, httpx, pytest)은 그 표를 읽고, 표에 없는 문자를 담은 대사와, 어느 언어로든 어드밴스 합이 위의 한도인 218폰트 픽셀을 넘는 대사를 거부합니다.10411 - 상한의 단위 테스트. 상한은 안전한 너비, 표시 배율, 대사들의 어드밴스의 순수 함수입니다. 3배에서 현재 가장 긴 대사로는, 나열된 390에서 440포인트의 각 너비에서 5, 360과 375에서 4, 466에서 6, 382에서 5(Duo 바깥쪽 디스플레이의, 측정한 레일이 없을 때와 있을 때)이고, 2배에서는 375와 414에서 3입니다. 전각 210폰트 픽셀의 일본어 대사가 더해지면 466에서 5, 382에서 4, 390과 393에서 4입니다. 그리고 3배에서 현재 가장 긴 대사로 244포인트 자리(360포인트에서 84포인트 인셋과 32를 뺀 것)에서는 상한 없음입니다. 아울러 PlazaView의 조립기 테스트로, 그 자리에서 현재 가장 긴 대사는 말풍선을 그리지 않고 그 페이지를 상자에 넣으며, “Hi!”는 m₀ 말풍선(244 안에 너비 36포인트)으로 그린다는 것을 확인합니다.11 - BubblePlacement의 단위 테스트. 너비 360, 375, 440, 466포인트의 뷰에서, 안전 영역 인셋이 없을 때, 앞뒤 양쪽에 16포인트씩일 때, Duo의 84포인트 뒤쪽 인셋이 있을 때 각각, 화자의 머리를 각 변, 각 모서리, 가운데에 두고, 그 경우의 안전한 너비에서 32를 뺀 값까지(360에서 84와 32를 뺀 244포인트에서 434까지) 여러 너비의 말풍선을 놓아, 모든 말풍선이 안전 영역에서 모든 변으로 16포인트를 뺀 범위 안에 있고, 머리의 x가 그 띠 안에 있을 때는 말풍선이 머리의 x에 걸쳐 있음을 확인합니다.11 - iPhone 18 Pro Max, iPhone 13 mini, iPhone SE(3세대) 시뮬레이터와 iPhone Duo 시뮬레이터의 바깥쪽 디스플레이에서 각각 네 장(영어와 일본어, 각각 기본 크기와 AX3)을 캡처합니다. 광장 서버를 실행하고(KIRADEX_PLAZA=1) 앱은 -worldTrace로 실행하며, 캡처마다 플레이어가 그 언어의 가장 긴 대사를 말합니다. 스크립트는 각 캡처에서 말풍선의 잉크 테두리를 찾아, 그것이 텍스트 한 줄 높이이고, 너비가 안전 영역의 너비에서 32포인트를 뺀 값 이하이며, 안전 영역에서 모든 변으로 16포인트를 뺀 범위 안에 있음을 단언합니다. 스크립트는 인셋을 캡처를 만드는 UI 테스트에서 받으며, UI 테스트는 -worldTrace가 켜는 숨겨진 요소(항목 8)에서 그것을 읽고, 거기에는 PlazaView가 읽은 인셋을 씁니다(제 설계). 스크립트는 또 글리프의 픽셀에서 말풍선의 m을 구해, 그것이 상자의 m에서 1을 뺀 값(m₀ 아래로 내려가지 않음)과, 빌드에 든 두 언어의 대사로 계산한 그 안전한 너비의 상한 중 작은 쪽임을 단언합니다. 그러므로 상한과 같아지는 것은 상한이 제한하는 곳에서만입니다. 기본 크기에서는 상한과 관계없이 3배 휴대폰과 Duo에서 4, 2배 휴대폰에서 3입니다. AX3에서는 상한이며, Duo에서는 인셋이 정합니다. 현재 영어 대사로는 좌우 인셋이 없으면 6, 레일이 있으면 5, 서버 한도에 꼭 맞는 일본어 대사가 있으면 5와 4입니다.119597 - iPhone 18 Pro Max 시뮬레이터의 AX3에서 하는 UI 테스트. 그곳에서 상자의 m은 12이고 한 줄에 라틴 문자 17자가 들어갑니다. 광장 서버(KIRADEX_PLAZA=1)가 필요하고 다른 플레이어는 필요 없으며, -worldTrace와, 항목 5가 이름 붙인 world.textSpeed 키를 통한 Instant 텍스트 속도(항목 6)로 실행하므로, 모든 페이지는 열리는 순간 다 보이고 상자를 한 번 탭하면 정확히 한 페이지가 넘어갑니다. 테스트는 Me 탭에서 플레이어의 핸들을 읽고, Say 메뉴에서 “Hi!”를 고른 뒤, 광장에 동행이 있는지 보는 기존 테스트가 허용하는 5초 안에 말풍선 Said: Hi!를 기다립니다. 그러면 대사가 방에서 되돌아온 것이고, 항목 4의 규칙에 따라 상태의 길이와 관계없이 그 페이지들은 상태보다 앞서 상자의 대기열에 있습니다. 마을의 수집가 넷은 각각 길어야 20초에 한 번 대사를 하지만, 그 대사가 앞에 서 있을 수는 있습니다. 대기열은 최대 세 대사를 보관하고 플레이어의 대사 하나를 버리는 일이 없으므로(항목 3), 앞에 서는 것은 최대 두 대사이고 그곳에서 각각 최대 두 페이지이며, 나중에 말해진 대사는 그 뒤에서 기다립니다. 테스트는 탭하기 전마다 plaza-page를 읽고, 페이지가 핸들, 콜론, 공백, “Hi!”가 아닐 때만 1초 간격으로 최대 네 번 상자를 탭하며, 읽기 중 하나라도 그 페이지를 찾으면 합격입니다. 마지막 읽기는 네 번째 탭 1초 뒤입니다. 이 대사를 지정하는 것은, names.py가 만들 수 있는 어떤 핸들이든 “handle: Hi!”는 그곳에서 한 페이지인 반면 가장 긴 대사는 두 페이지가 될 수 있기 때문입니다. 테스트는 읽은 모든 페이지를 기록합니다.11103102

5. 간판과 이름

간판. 간판 아트는 kit.py가 그리는 대로 글자 없이 두어, 아틀라스 하나가 두 언어를 모두 맡게 합니다.29 간판 타일을 탭하면 지금처럼 플레이어를 그곳으로 걷게 하는 대신, 간판의 글자를 상태보다 앞서는 페이지로(항목 3) WorldHUD 상자에 엽니다. 지금 무대는 DragGesture로 터치를 읽습니다. touched(_:in:)은 수집가나 키오스크 위(이들은 손가락이 떨어지기를 기다립니다)를 빼고는 손가락 아래 땅으로 플레이어를 곧바로 걷게 하고, lifted(_:in:)이 그 떼는 순간을 탭으로 읽습니다. 간판 타일은 그 두 곳 모두에서 수집가와 키오스크에 합류합니다. 손가락이 떨어지기를 기다리고, lifted(_:in:)은 키오스크에 대해 onKiosk를 부르듯 간판에 대해 onSign 클로저를 부릅니다(이름은 제 것입니다).92 간판 글자는 맵과 함께 둡니다. town.json에 셀을 키로 하는 signs: { "7,10": { "en": ..., "ja": ... } }를 더하고, 포지의 검사는 두 언어가 다 있지 않은 간판 셀을 거부합니다.

이름. 머리 위에 늘 떠 있는 이름표는 두지 않습니다. 원전의 상자는 화자의 이름을 줄 안에서 밝히며, Emerald에서는 문단의 6.5%입니다.1 Kiradex의 핸들은 최대 20자입니다. 말풍선의 가장 작은 크기, 3배에서 m = 4, 2배에서 3일 때, 20자 핸들은 3배에서 너비 133포인트, 2배에서 150포인트입니다. 3배에서 그것이 얹히는 것은 카메라를 보면 너비 16텍셀, 옆을 보면 13텍셀(키 30, 32×40 셀)인 인물로, 18 Pro Max에서 42.7포인트, 또는 34.7포인트입니다.119610033 핸들은 지금처럼 탭하면 열리는 패널에 나오고, VoiceOver가 읽습니다(항목 8).

검사. 카메라가 플레이어를 따라가므로, UI 테스트는 간판 셀이 화면 어디에 그려지는지 알 수 없습니다. 그래서 테스트는 광장을 -worldTrace와 -plazaTapCell 7,10으로 실행합니다. 후자는 제가 붙인 이름의 테스트 인자로, -plazaFacingDemo처럼 PlazaStage의 body에서(.task 안에서) 읽으며, lifted(_:in:)에서 메서드 하나로 떼어 낸 같은 처리로 그 셀을 보냅니다. iPhone 18 Pro Max 시뮬레이터의 기본 크기에서, 대사가 말해지지 않도록 광장 서버 없이, 테스트는 항목 3의 -worldBoxSample도 한 페이지짜리 상태 “Sample.”로 넘기고, 실행 인자의 설정 키로 Instant 텍스트 속도(항목 6)를 지정합니다(키 world.textSpeed는 제 것이며, 스틱의 world.stick 옆에 둡니다). 테스트는 plaza-page가 “Sample.”에서 간판의 첫 페이지로 바뀌기를 최대 5초 기다린 뒤 읽고, 상자를 탭하고 다시 읽기를, plaza-page가 “Sample.”이 될 때까지, 간판 글자의 단어마다(일본어에서는 글자마다) 한 번에 한 번을 더한 횟수를 넘지 않게 되풀이하며, 그 앞의 페이지들을 순서대로 이은 것이 공백을 빼고 간판의 영어 글자와 같음을 확인합니다. 그래서 페이지를 넘길 만큼 긴 간판 글자도 짧은 것처럼 합격할 수 있습니다. 앱의 언어를 일본어로 해서 다시 실행하면 일본어 글자에 대해서도 확인합니다.92 포지 검사는 어느 한 언어가 빠진 S 셀을 가진 town.json을 거부합니다.

6. 텍스트 속도와 넘김 표시

내용. 세는 단위가 프레임이 아니라 시간인 타자기식 표시입니다. shown = floor(elapsed × cps)는 경과 시간, 속도, 두 설정(Reduce Motion과 VoiceOver 실행 여부)의 순수 함수이므로, 60헤르츠와 120헤르츠가 같은 속도로 드러냅니다. 설정에는 Slow(초당 12자, Crystal의 느림), Normal(30, 기본값, 원전의 중간 설정인 Emerald의 15와 Crystal의 20과, 빠른 설정 60 사이에서 고른 제 선택), Instant를 둡니다. 드러나는 중에 탭하면 그 페이지를 끝까지 보여 주고, 다 드러난 페이지에서 탭하면 넘깁니다.1 그러려면 상자는 드러나는 중이거나 보여 주는 페이지 뒤에 페이지가 있는 동안 탭을 받습니다. 마지막 페이지가 다 드러나면, 지금 늘 그렇듯 탭을 아래 땅으로 통과시킵니다.26 두 언어에서 속도는 같습니다. Final Fantasy VI가 둘 다 한 프레임에 한 글자로 찍는 것과 같습니다.4 Reduce Motion이 켜져 있거나 VoiceOver가 실행 중이면 함수는 페이지 전체를 곧바로 돌려줍니다. 이것은 Reduce Motion일 때 “UI should avoid large animations”(UI는 큰 애니메이션을 피해야 한다)라는 문서의 한 줄에 따른 제 선택입니다.83

표시. Kiradex 고유의, 포지가 그린 표시로, 5×3폰트 픽셀의 아래쪽을 향한 쐐기를 상자 오른쪽 아래에 둡니다. 150밀리초마다 0, 1, 2, 1폰트 픽셀 아래로 단계적으로 움직이며(방향은 제 선택이고, 제 해석으로는 Emerald의 것은 올라갑니다), 한 주기는 600밀리초, 60헤르츠에서 Emerald의 9프레임 단계입니다.133

검사. 초당 30자에서 표시 함수의 단위 테스트. t = 0에서 0자, 1.000초에서 30자입니다. 경과 시간은 틱마다의 차이를 더해서가 아니라 틱 수를 비율로 나누어 구하며, 초당 60틱과 120틱으로 시뮬레이션합니다(1/120의 차이 120개를 배정밀도로 더하면 0.9999999999999989가 되고, 30을 곱하면 29.999999999999968이며, 내림하면 29입니다). Reduce Motion이 켜졌을 때, 그리고 따로 VoiceOver가 실행 중일 때, 같은 함수가 t = 0에서 페이지 전체를 돌려주는지. iPhone 18 Pro Max 시뮬레이터의 기본 크기와 Slow에서, 광장 서버 없이, -worldTrace와 항목 3의 -worldBoxSample 상태로 실행하는 UI 테스트. 상태의 첫 페이지는 30에서 40자짜리 두 줄로, 60자 이상이므로 초당 12자에서 적어도 5초 동안 드러납니다. 드러나기 시작하고 1초 뒤에 상자를 탭하면 0.5초 안에 plaza-page가 페이지 전체가 되는지 확인합니다. 그때까지 드러나기만 했다면 18자였을 것입니다(탭 시점에 12자). 경과 시간의 함수로서 표시 오프셋의 단위 테스트. 0, 150, 300, 450, 600밀리초에서 0, 1, 2, 1, 0폰트 픽셀입니다. 그리고 시뮬레이터에서 표시를 2초 동안 화면 녹화해, 그 오프셋이 반복을 빼면 0, 1, 2, 1의 순환 순서로 흐르는지 확인합니다.33

7. 일본어

내용. 먼저 세계의 텍스트를 일본어로 하고, 앱의 나머지는 나중에 합니다. server/app/lines.py는 주석대로 “add at the end only”(끝에만 더할 것)로 인덱스 순서를 지키며, 각 대사는 영어와 일본어 문자열이 됩니다. 서버는 둘 다 보내고, 각 클라이언트는 사용자가 읽는 언어를 보여 주므로, 언어가 섞인 광장에서 말해진 대사 하나는 인덱스 하나에서 두 언어로 읽힙니다.28 제 해석으로는 이것이 이 앱에서 프리셋 대사를 쓰는 가장 좋은 근거입니다. 세계의 문자열, 즉 WorldHUD의 제목과 상태, 퀘스트 제목은 ja를 담은 String Catalog로 옮깁니다. 간판 글자는 맵과 함께 둡니다(항목 5). 일본어 문자열은 모두 일본어 화자가 쓰거나 검토합니다. 기계 번역하고 검토하지 않은 것은 하나도 출시하지 않습니다.

줄 바꿈. 프리셋 대사는 절대 줄을 바꾸지 않습니다(항목 4). 상자 텍스트는 Emerald의 스크립트를 손으로 끊었던 것처럼, 기본 크기에서 한 페이지 두 줄에 들어가도록 씁니다.1 꼭 줄을 바꿔야 하는 곳에서는 엄격한 규칙을 유지합니다. 태그를 붙여도 엄격한 규칙이 지켜진다는 것을 캡처가 보여 주지 않는 한 상자 텍스트에 ja 조판 언어 태그를 붙이지 않습니다. macOS의 Core Text는 ja 태그를 붙이면 대부분의 서체에서 너비 7개 중 6개(k8x12와 k8x12S에서는 7개 중 4개)에서 작은 가나가 줄을 시작하도록 허용했기 때문입니다.777 그래도 시스템 기본값이 느슨한 규칙을 적용한다면, 적어 둔 해결책은 그것이 필요한 몇몇 문자열에서 작은 가나 앞에 U+2060 WORD JOINER를 두는 것입니다.

검사. 모든 대사와 세계의 String Catalog의 모든 키가 두 언어를 갖는지 확인하는 테스트. 항목 4의 서버 테스트(218폰트 픽셀 한도, 전각 21자를 넘는 일본어 프리셋 대사를 거부합니다). 그리고 iPhone 18 Pro Max 시뮬레이터의 기본 크기에서 기기 언어를 일본어로 하고, 광장 서버 없이 Instant 속도로, -worldTrace로 실행해, 상자에 넣은 테스트 문단의 각 페이지를 캡처합니다. 문단은 항목 3의 -worldBoxSample 상태로 넘기며, 그곳에서 한 줄에 들어가는 전각 22자에서 줄이 작은 가나 앞에서 바뀌도록 써 둡니다. 캡처에서 문단의 첫 줄 이후 어떤 줄도 JLREQ의 행두 목록에 있는 문자나 그 호환 형태로 시작하지 않는지 확인합니다.11 이 목록은 완전한 것으로, 3.1.7 규칙이 꼽는 열 클래스의 83개 항목(세 문자는 두 클래스에 속합니다)을 테스트 타깃에 넣어 둡니다. 7절의 프로브는 부분적인 목록을 썼습니다(7절). 일본어 조판 소프트웨어는 대개 그 자리에, Unicode가 옛 표준과의 왕복을 위해 인코딩한 호환 형태를 조판합니다. JLREQ는 여는 괄호에 대해 이렇게 적습니다. “the compatibility counterpart U+FF08 FULLWIDTH LEFT PARENTHESIS is used in the Japanese context in most Japanese text layout software”(대부분의 일본어 조판 소프트웨어에서는 일본어 맥락에서 호환 문자 U+FF08 FULLWIDTH LEFT PARENTHESIS가 쓰인다).21 그래서 목록에는 Unicode Halfwidth and Fullwidth Forms 블록의 문자 중 호환 정규화(NFKC) 결과가 83개 중 하나인 것 전부, 24자를 더합니다. !(U+FF01), ?(U+FF1F), )(U+FF09), :, ;, ,, ., 그리고 반각 。, 、, ー와 ァ(U+FF67)부터의 작은 가타카나 등입니다. 그리고 같은 클래스의 물결 대시 〜 옆에, 전각 물결표 ~(U+FF5E)를 제 판단으로 더합니다. 그 정규화 결과는 ASCII 물결표이고 목록에 없기 때문입니다. 모두 25자입니다.98 PixelMplus10은 83개 항목 중 54개와 25개 형태 중 24개를 그립니다. 나머지 29개 항목, 이를테면 〜(U+301C), ゠, en 대시, ‼, U+31F0부터의 작은 가타카나와 전각 ⦆(U+FF60)은 그리지 않으며, 항목 1의 지원 범위 테스트가 그것들을 모두 세계의 문자열에서 막으므로, 캡처에 나타날 수 있는 것은 그 서체가 그리는 75자뿐입니다(54개 항목 중 ), 〕, ] 셋은 두 클래스에 실려 있으므로 51자, 그리고 24개 형태).98 스크립트는 각 줄의 첫 글리프 셀을, 번들 폰트에서 CTFontGetGlyphsForCharacters로 캡처의 m에 맞춰 그린 그 75자의 글리프와 픽셀 단위로 비교해, 일치하는 것이 없음을 확인합니다. 픽셀로 목록의 문자와 문단의 다른 문자를 구별할 수 있는 것은 서체가 그것들을 다르게 그릴 때뿐입니다. 10픽셀로 그리면 75자 중 두 자가 서체 안에 픽셀 단위로 같은 쌍둥이를 갖습니다. ‐(U+2010)은 줄 바꿈 없는 하이픈 U+2011과 같고, 콜론은 수식 문자 콜론 U+A789와 같습니다. 24개 형태에는 쌍둥이가 없습니다. 그래서 테스트 문단은 두 쌍둥이를 모두 쓰지 않습니다. ー(U+30FC)와 한자 一(U+4E00)는 달라서 m = 5에서 잉크 기기 픽셀이 200 대 250이므로, 一는 문단 어디에 두어도 됩니다.9821 7절의 결과는 Mac의 Core Text의 것이며, iOS의 SwiftUI가 어떻게 하는지는 이 캡처가 정합니다.

8. 무대 위의 VoiceOver

현재. VoiceOver가 듣는 것은 “The plaza”와 그 뒤의 걷기 테스트 기록, 이를테면 “12,4 down”입니다.25 이 값은 테스트를 위한 것이지 사람을 위한 것이 아닙니다.

내용. 1. 무대의 레이블은 “The plaza”나 방의 이름으로 두고, 값은 사람이 쓸 수 있는 문장이 됩니다. 수집가가 랜드마크 기준으로 어디에 서 있는지, 근처에 누가 있는지, 무엇을 들어 보이는지(예를 들어 By the pond. Three collectors nearby. Holding up a card.)이며, 값은 레이블이 말하지 않는 것을 말한다는 accessibilityValue의 규칙을 따릅니다.79 지금 stepTrace를 타고 무대의 값에 들어가는 것은, 무엇이 쓰든 모두 옮깁니다. 제가 PlazaStage.swift를 읽었을 때는 rig.onStep에서 오는 걸음 기록, RealityView의 make 클로저에서 오는 빌드 실패 메시지, -airProbe를 읽는 .task에서 오는 공기 프로브 보고가 있었고, 몇 분 뒤에는 그날 추가된 걷기 데모에서 오는 네 번째 쓰기가 있었습니다. 옮길 곳은 앱이 -worldTrace(제가 붙인 이름)로 실행될 때만 존재하는 숨겨진 요소로, 걷기 테스트가 이것을 넘기며, 상자의 페이지(항목 3)와 PlazaView가 읽은 인셋(항목 4) 옆에 둡니다. 무대의 값을 읽는 테스트들, 그중 testTheCollectorFacesTheWayItWalks와 value CONTAINS 'smoke 18 '를 기다리는 testTheChimneysSmoke는 대신 그 요소를 읽습니다.25103 2. 무대에 accessibilityDirectTouch(_:options:)를 붙여, VoiceOver 사용자가 활성화한 뒤 탭으로 걸을 수 있게 합니다(iOS 17.0).80 3. 각 수집가의 엔티티에 핸들을 레이블로, 들고 있는 것을 값으로 하는 AccessibilityComponent를 주어, VoiceOver가 그들을 설명할 수 있게 합니다(iOS 17.0).84 4. 플레이어에게서 여섯 칸 안에서 말해진 대사는 AccessibilityNotification.Announcement로 낮은 우선순위로 보냅니다(예를 들어 Glacier Shuffle 4471: Nice card!). 빈도는 최대 2초에 한 번으로, 서버 자체의 대사 비율과 같습니다.8291 5. 열린 상자 페이지는, 4번이 이미 안내한 대사를 되풀이하는 것(항목 4의 큰 텍스트 페이지가 그렇습니다)이 아니라면 전체를 안내합니다.

검사. -worldTrace 없이는 무대의 값에 좌표가 들어 있지 않음을 확인하는 UI 테스트. iOS 27 시뮬레이터에서 XCUIVoiceOverService로 VoiceOver를 켜고, 무대로 옮기고, currentSpeech()가 돌려주는 것에 무대의 레이블이 있고 걸음 기록의 x,y 형태 숫자 쌍이 없음을 단언하는 테스트.87 VoiceOver를 켠 기기에서, 수집가 엔티티에 도달할 수 있고 읽히는지 확인하는 수동 검사(날짜와 빌드 번호를 적습니다). 가상 카메라를 둔 RealityView가 AccessibilityComponent 요소를 노출하는지는 확인되지 않았기 때문입니다(11절). 그리고 안내 조절의 단위 테스트로, 2초 안의 두 대사가 안내 한 번이 되는지 확인합니다.

9. 서체와 창에 대해 절대 하지 않을 것

  1. 어떤 게임의 ROM이나 디컴파일에서 나온 글리프, 테두리, 화살표, 창 그래픽도 앱이나 포지에 넣지 않습니다. pret과 ff6의 시트는 여기서 측정만 하고 절대 복사하지 않습니다. Kiradex의 테두리, 표시, 간판은 포지가 그리고, 서체는 라이선스된 폰트입니다.
  2. 프랜차이즈 캐릭터나 합자를 쓰지 않습니다. Emerald의 charmap에는 프랜차이즈 단어를 위한 특수 글리프가 있습니다. 그런 것은 그리지 않으며, 어떤 문자열도 프랜차이즈가 쓰는 사물의 이름을 쓰지 않습니다.31
  3. 라이선스 파일이 손에 없는 폰트는 쓰지 않습니다. 서체는 4절이나 5절에 라이선스 전문을 인용하고 증거와 함께 저장했을 때만 출시합니다.
  4. 수정한 OFL 서체를 그 예약 폰트 이름으로 쓰지 않습니다. Press Start 2P는 예약 폰트 이름을 선언합니다. OFL 서체를 수정하는 일이 생기면 이름을 바꿉니다.54
  5. 세계 아트에 텍스트를 새기지 않습니다. 간판, 가게 앞, 게시판은 글자 없이 두어, 아틀라스가 언어마다 따로 생기는 일이 없게 합니다.

검사. 스크립트가 포지가 쓰고 에셋 카탈로그가 지닌 모든 이미지에 대해 SHA-256 다이제스트 두 개, 파일 바이트의 것과 디코딩한 픽셀의 것을 계산하고, 여기서 측정한 각 PNG(pret의 graphics/fonts와 graphics/text_window, Crystal의 테두리, Crystal과 Red의 gfx/font/font.png)의 같은 두 다이제스트와 비교합니다. 이미지가 아니라 다이제스트 목록을 scripts/forge/canon_digests.txt(제가 고른 이름으로, 포지의 kit.py 옆)로 저장소에 넣으므로, 검사는 증거 폴더 없이 돌아가며, 하나라도 일치하면 빌드가 실패합니다. 검사 자체의 테스트는 게임 파일이 필요 없는 음성 대조군입니다. 포지 출력 하나의 다이제스트를 목록 사본에 더하고, 그러면 스크립트가 실패함을 단언합니다. 같은 스크립트는 포지 출력의 8×8 셀 중 1비트 마스크나 그 반전이 그 시트들의 셀과 일치하는 것을, 실패시키지 않고 검토용으로 나열합니다. 이미지 전체의 다이제스트로는 그런 셀을 다시 만들 수 없으므로, 목록에는 시트의 서로 다른 셀 마스크 모두의 SHA-256 다이제스트도 담습니다. 규칙은 하나이며(제 것), 포지의 출력에도 똑같이 적용합니다. 이미지의 왼쪽 위 픽셀과 색이 다른 픽셀을 켠다. 셀은 왼쪽 위 모서리부터 8×8로 나누고, 가장자리의 불완전한 셀은 버린다. 빈 셀과 꽉 찬 셀은 시트에 많으므로 양쪽 모두에서 뺀다(japanese_normal.png 하나만 해도 셀 1,024개 중 430개가 비어 있습니다). 마스크는 64비트를 행마다 늘어놓은 8바이트로 한다. 시트 50장에서 남는 셀은 7,746개, 서로 다른 다이제스트는 1,484개입니다.99 색을 바꾸거나, 크기를 바꾸거나, 다시 그린 사본은 다이제스트를 통과합니다. 규칙 1을 떠받치는 것은 포지가 모든 에셋을 코드로 그린다는 것과 그 검토이지, 검사만이 아닙니다. 그리고 이 시리즈의 앞선 브리프들이 제안하는 문자열 검사를 Emerald의 charmap에 있는 프랜차이즈 약어까지 넓힙니다.105

이 브리프에 들어 있지 않은 것

  • 앱 전체를 위한 픽셀 서체. Dex, 스캐너, 설정은 시스템 서체를 유지합니다. 픽셀 서체는 세계를 위한 것입니다.
  • 중국어와 한국어. 확인한 현대 픽셀 게임 네 개가 모두 중국어를 출시하고, 그중 둘, Stardew Valley와 Sea of Stars는 한국어도 출시하지만, 여기서는 어느 쪽에 대해서도 픽셀 폰트를 측정하지 않았습니다.64
  • 본문을 읽히는 데 쓰는 Misaki나 다른 8도트 서체. 측정한 것 중 가장 작은 한자이며, 그 크기에서 일본어 독자에게의 가독성은 측정하지 않았습니다.6
  • 머리 위 이름표와 상자의 이름 칸.1
  • 아무도 검토하지 않은 기계 번역 일본어.
  • 픽셀 서체의 합성 굵게나 기울임.
  • 판매용 테두리. 테두리는 모두 무료입니다. 후원자용 테두리는 결정을 기다립니다.
  • RealityKit 장면 안에 그리는 텍스트. 세계 편 글의 이유와 10절의 산수가 모두 텍스트를 SwiftUI에 둡니다.2311

핵심 요약

아트를 그리는 분께

  • 테두리는 타일로 짓습니다. 모서리, 가장자리, 가운데입니다. Emerald의 메뉴는 24×24픽셀, 타일 3×3짜리 테두리 20종 중에서 고르고, 대화 상자는 아래 가장자리를 반전한 14타일 띠에서 그리며, Crystal의 테두리는 각각 1비트 타일 6개입니다.1
  • 넘김 표시에는 느린 시계를 줍니다. Emerald의 화살표는 쉬는 위치에서 0, 1, 2, 1픽셀로, 9프레임마다 한 단계, 한 주기 0.6초로 움직입니다.1
  • 세계 아트에 글자를 넣지 않습니다. 글자 없는 간판은 아틀라스 하나로 모든 언어를 맡습니다. 글자는 상자에 넣습니다.29
  • 일본어는 8에서 12픽셀로 그리고, 가나는 라틴 문자의 두 배가 된다고 생각합니다. 측정한 일본어 지원 픽셀 폰트는 모두 8 대 4, 10 대 5, 12 대 6입니다.7

엔진을 만드는 분께

  • 픽셀 폰트는 이름이 아니라 파일로 고릅니다. 그리드, 베이스라인에서의 어긋남, 어드밴스를 확인합니다. macOS의 Core Text에서, 베이스라인에서 0.39픽셀 어긋난 PixelMplus12의 그리드는 모든 크기에서 번졌고, k8x12의 8.01픽셀 가나 어드밴스는 글리프를 정확히 놓자 번졌습니다.7
  • 폰트 픽셀 하나가 정수 개의 기기 픽셀일 때만 그립니다. 폰트 스무딩을 끈 Mac의 Core Text에서, 그리드가 베이스라인 위에 있고 어드밴스가 정수 픽셀인 폰트(Misaki, PixelMplus10, Press Start 2P)는 정수배에서 중간 톤 0.0%, 그 사이에서 19.4%에서 82.2%였습니다. 컨텍스트의 기본 스무딩에서는 PixelMplus10을 제 크기로 그려도 잉크 픽셀 272개 중 178개가 중간 톤이었으므로, 정수배는 필요조건이지 충분조건이 아닙니다. iPhone에서 어느 결과든 믿기 전에 캡처합니다.79
  • Dynamic Type은 그리드에 맞춰 반올림해서 존중합니다. 3배에서 10픽셀 em이 정수가 되는 것은 Body 크기 12단계 중 하나입니다. 키운 크기를 폰트 픽셀당 정수 개의 기기 픽셀로 반올림하고 고정 크기로 설정합니다.768
  • 텍스트 속도는 프레임이 아니라 시간으로 세어, 60헤르츠와 120헤르츠가 똑같이 드러나게 합니다. 측정한 모든 게임에서 가장 빠른 속도는 초당 60자이고, 중간 설정이 있는 게임에서는 그것이 15에서 20입니다.14
  • VoiceOver에는 기록이 아니라 문장을 줍니다. accessibilityValue는 레이블이 말하지 않는 것을 위한 것이고, AccessibilityComponent는 엔티티를 설명하며, XCUIVoiceOverService는 iOS 27.0부터 VoiceOver가 읽는 내용을 테스트할 수 있습니다.798487

게임 루프를 설계하는 분께

  • 한 번에 두 줄, 아래쪽에. Emerald의 상자는 화면의 30%, Crystal은 33%이며, 둘 다 두 줄입니다.1
  • 너비에 맞춰 씁니다. Emerald의 작가들은 41자가 들어가는 창에서 24,100줄의 95%를 38자 이하로 두었고, 창을 넘는 줄은 하나도 없었습니다.1
  • 화자의 이름은 머리 위가 아니라 줄 안에서 밝힙니다. Emerald의 상자에는 이름 칸이 없습니다.1
  • 일본어는 처음부터 계획합니다. 확인한 현대 픽셀 게임 네 개 중 네 개가 출시합니다. JIS X 0208을 지원하는 폰트는 약 7,000자를 그립니다. 스크립트가 확정된 게임은 Final Fantasy VI의 일본어판 ROM이 1,248자로 해결했듯이 더 적게 출시할 수 있지만, 그러면 새 문자열마다 폰트와 대조해야 합니다.64584

자주 묻는 질문

픽셀 아트 게임의 텍스트 상자는 얼마나 커야 합니까?

휴대용 Pokémon 게임에서는 화면의 아래 3분의 1에 두 줄입니다. Emerald의 대화 테두리는 30×6타일, 240×48픽셀로 Game Boy Advance 화면 높이의 30%이며, 그 안에 16픽셀 폰트로 두 줄이 들어가는 27×4타일 텍스트 창이 있습니다. Crystal의 상자는 20×6타일로 Game Boy 화면의 33%, 18자짜리 두 줄이며, Red도 같은 20×6입니다. Final Fantasy VI는 창을 화면 위나 아래에 엽니다.12345

타자기식 텍스트 효과는 얼마나 빨라야 합니까?

원전은 프레임을 셉니다. Emerald와 FireRed는 8, 4, 1프레임마다 한 글자, 60헤르츠에서 초당 7.5, 15, 60자를 찍으며, 찍는 도중 한 번 누른 뒤에는 A나 B를 누르고 있는 동안 한 프레임에 한 글자입니다. Crystal과 Red는 초당 12, 20, 60자로 찍습니다. Final Fantasy VI는 필드 대화를 한 프레임에 한 글자, 초당 60자로 찍으며 이에 대한 설정이 없습니다(6단계 메시지 속도는 전투 메시지가 얼마나 기다릴지를 정합니다). 현대 휴대폰에서는 프레임이 아니라 시간으로 글자를 세어, 120헤르츠에서도 속도가 바뀌지 않게 합니다. 측정한 모든 게임의 최고 속도는 초당 60자이고, Emerald와 Crystal의 중간 설정은 15와 20입니다. 아래 브리프는 기본값으로 30을 제안하는데, 이것은 그 중간 설정과 빠른 60 사이에서 고른 제 선택이지 측정된 설정이 아닙니다.1144

일본어를 지원하는 픽셀 폰트는 무엇입니까?

측정한 것 중 Misaki(8×8도트), k8x12(8×12), DotGothic16은 JIS X 0208의 6,879자를 모두 그리고, PixelMplus10과 PixelMplus12는 코드 포인트 세 개를 뺀 전부를 그립니다. Misaki, k8x12, PixelMplus는 사용, 복제, 배포를 “either commercially or noncommercially”(상업적이든 비상업적이든) 허락하는 세 문장짜리 무료 라이선스이고, DotGothic16은 SIL Open Font License 1.1입니다. macOS 27.0의 Core Text로 측정하면, 베이스라인 위의 깨끗한 그리드와 정수 픽셀 어드밴스를 가진 것은 8픽셀의 Misaki와 10픽셀의 PixelMplus10 둘입니다. PixelMplus12의 그리드는 베이스라인보다 0.39픽셀 아래에 있고, k8x12의 가나 어드밴스는 8.01픽셀이며, DotGothic16의 도트는 어떤 픽셀 그리드에도 놓여 있지 않습니다.67171853

iPhone에서 제 픽셀 폰트가 흐려 보이는 이유는 무엇입니까?

대개는 폰트 픽셀 하나가 정수 개의 기기 픽셀이 아니기 때문입니다. 폰트 스무딩을 끈 macOS 27.0의 Core Text에서, 베이스라인 위의 깨끗한 그리드와 정수 픽셀 어드밴스를 가진 픽셀 TrueType 폰트(Misaki, PixelMplus10, Press Start 2P)는 그리드의 정수배에서 중간 톤 픽셀 없이 그려졌고, 그 사이에서는 잉크 픽셀의 19.4%에서 82.2%가 번졌습니다. 그리기 컨텍스트의 기본 스무딩에서는 정수배에서도 흐려졌습니다. PixelMplus10을 제 크기인 10픽셀로 그려 잉크 픽셀 272개 중 178개가 중간 톤이었습니다.9 폰트 파일의 두 결함은 모든 크기에서 흐림을 만듭니다. PixelMplus12처럼 0.39픽셀 어긋난, 베이스라인에서 벗어난 그리드와, k8x12의 8.01픽셀 가나 어드밴스처럼 글리프를 정확히 놓으면 정수가 아닌 어드밴스입니다. 측정은 Mac에서 했으며, iPhone에서 결론을 내리는 것은 iPhone에서의 캡처입니다.7

픽셀 폰트가 Dynamic Type을 지원할 수 있습니까?

반올림해서만 가능합니다. iOS의 Body 크기 12단계(14에서 53포인트) 중 3배에서 10픽셀 em이 정수 기기 픽셀에 맞는 것은 40포인트뿐이므로, Font.custom(_:size:relativeTo:)로 연속적으로 커지는 픽셀 폰트는 나머지 11단계에서 그리드를 벗어납니다. Mac의 Core Text에서는 그리드를 벗어나면 번졌으며, iPhone에서 SwiftUI가 무엇을 그리는지는 iPhone에서의 캡처가 정합니다. 키운 크기를 UIFontMetrics나 ScaledMetric으로 읽고, 폰트 픽셀당 정수 개의 기기 픽셀로 반올림하고, Font.custom(_:fixedSize:)로 설정합니다. Apple은 커스텀 폰트도 시스템 폰트가 가진 동작(Dynamic Type 포함)을 구현하고, 앱이 이상적으로 텍스트를 최소 200퍼센트까지 키울 수 있게 하라고 요구합니다.710206768

Core Text는 일본어 줄 바꿈 규칙을 따릅니까?

macOS 27.0에서 제가 측정한 바로는, 텍스트에 언어 태그를 붙이지 않았을 때는 그렇습니다. 엄격하게 따릅니다. 131자짜리 문단을 일곱 너비에서 나누었더니, Hiragino Sans와 픽셀 서체 여덟 개에서 프로브의 행두 목록에 있는 어떤 문자로도 시작하는 줄이 없었습니다. 이 목록은 부분적인 것으로, 대부분 JLREQ의 문자이고 그중 일곱 자는 전각형입니다. ja 태그를 붙이면 W3C의 일본어 조판 요구 사항이 설명하는 느슨한 규칙으로 풀어져, 대부분의 서체에서 너비 7개 중 6개에서 작은 가나가 줄을 시작했습니다. 기기 언어가 일본어인 iPhone에서 SwiftUI가 어떻게 하는지는 측정하지 않았습니다.721

일본어 픽셀 폰트에는 몇 자가 필요합니까?

일반적인 지원 목표인 JIS X 0208은 6,879자로, 비한자 524자, 제1수준 한자 2,965자, 제2수준 한자 3,390자입니다. Final Fantasy Pixel Remaster의 프로듀서는 Siliconera에 그 일본어 픽셀 폰트에 “around 7,500 characters”(약 7,500자)가 들었고, 서양 언어는 “a few hundred”(수백 자)였다고 말했습니다. 스크립트가 확정된 게임은 더 적게 출시할 수 있습니다. Final Fantasy VI의 일본어판 ROM은 대화 폰트에 1,248자를 담고 있습니다.584

이 글의 숫자는 모두 저장한 소스에 대해 다시 실행할 수 있는 스크립트, 2026년 10월 5일에 저장한 페이지, 또는 날짜를 적은 Kiradex 소스 읽기에서 나왔으며, 각주가 어느 것인지 밝힙니다. Core Text 숫자는 iOS가 아니라 macOS 27.0의 것입니다. 12절의 브리프는 제 제안이며 그렇게 표시해 두었습니다. 아직 아무것도 만들지 않았습니다.

이 사이트의 관련 글: iPhone에서 만드는 픽셀 아트 세계는 이 시리즈의 첫 글로, RealityKit 레시피와 이 글이 측정한 텍스트 방향이 있습니다. 픽셀 아트 인물: iPhone의 캐릭터와 크리에이터는 말풍선이 그 위에 뜨는 수집가의 글입니다. 픽셀 아트 건축: iPhone의 집, 홀, 실내에는 간판을 글자 없이 두는 포지가 있습니다. 픽셀 아트의 움직임: iPhone에서의 걷기, 카메라, 문에는 표시의 시간 기준이 되는 60헤르츠와 120헤르츠 시계가 있습니다. 아무도 접속하지 않을 때도 살아 있는 픽셀 마을에는 프리셋 대사와 그 비율이 있습니다. 픽셀 아트 방과 픽셀 아트 길은 이 글이 그 검사를 넓힌 브리프입니다. 옛 Pokémon 게임을 빌리지 않고 기리기는 원전을 측정하되 절대 복사하지 않는 이유입니다. 그리고 Xcode 27.1 베타: iPhone Duo 시뮬레이터에서 앱 실행하기는 말풍선 캡처를 실행하는 시뮬레이터를 다룹니다.

출처


  1. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_gen_text.py, 출력은 measure_gen_text.out. 입력은 pret pokeemerald 731ad5b에서 src/text.c(sFontInfos, RENDER_STATE_HANDLE_CHAR 케이스, sWindowVerticalScrollSpeeds, sDownArrowYCoords, 일본어 글리프 함수), src/menu.c(sTextSpeedFrameDelays, sStandardTextBox_WindowTemplates, 표준 창에 대해 평가한 WindowFunc_DrawDialogueFrame), src/fonts.c와 charmap.txt(글리프 너비), graphics/text_window/*.png(크기), include/text_window.h, data/text/*.inc와 data/maps/*/scripts.inc의 모든 줄(\n, \l, \p에서 나누어 너비 표로 측정. 파일 504개, 플레이스홀더 없는 줄 24,100개, 있는 줄 3,000개, 건너뛴 것 0), graphics/fonts/japanese_*.png(비어 있지 않은 셀). pokefirered 037335f에서 src/new_menu_helpers.c. pokecrystal 5beda23에서 constants/hardware.inc, constants/text_constants.asm, home/print_text.asm, gfx/font.asm, 테두리 PNG, gfx/font/font.png. pokered d2704a6에서 data/text_boxes.asm, constants/ram_constants.asm, gfx/font/font.png. 초당 글자 수는 60을 글자당 프레임 수로 나눈 것. Emerald 프린터에 대한 해석(지연은 AddTextPrinter가 1을 빼서 저장한 뒤 거꾸로 세므로, 한 글자에 지연만큼의 프레임이 걸림), src/battle_message.c와 src/scrcmd.c에 따른 자동 스크롤 지연의 조건, Crystal 상수의 두 줄 해석은 스크립트가 단언한 것이며 실행하지 않음. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  2. pret, pokeemerald/src/menu.c, 커밋 731ad5b(77번째 줄의 sTextSpeedFrameDelays, 84번째 줄의 sStandardTextBox_WindowTemplates, 212번째와 213번째 줄의 LoadMessageBoxGfx와 LoadUserWindowBorderGfx, 218번째 줄에서 호출되는 WindowFunc_DrawDialogueFrame과 378번째 줄부터 BG_TILE_V_FLIP을 쓰는 채우기), 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/menu.c ↩↩↩↩↩↩↩

  3. pret, pokecrystal/constants/text_constants.asm, 커밋 5beda23(24번째에서 32번째 줄: BORDER_WIDTH EQU 2, TEXTBOX_WIDTH EQU SCREEN_WIDTH, TEXTBOX_HEIGHT EQU 6, TEXTBOX_Y EQU SCREEN_HEIGHT - TEXTBOX_HEIGHT, TEXTBOX_INNERY EQU TEXTBOX_Y + 2)와 constants/hardware.inc(922번째와 923번째 줄, SCREEN_WIDTH 20과 SCREEN_HEIGHT 18), 2026년 10월 5일 열람, https://github.com/pret/pokecrystal/blob/master/constants/text_constants.asm ↩↩↩↩

  4. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_ff6.py, 출력은 measure_ff6.out. 입력은 everything8215/ff6 8130132에서 tools/rip_list_en.json과 tools/rip_list_jp.json(창 그래픽, 팔레트, 폰트의 바이트 범위), src/menu/config.asm(ConfigWindowNumPos, LoadWindowPal, ConfigColorBarTextList, ChangeConfigOption_02), src/menu/menu_sram.asm, src/btlgfx/gfx_cmd.asm(MsgSpeedTbl, mess_wait), $1d4d를 읽는 src/ 아래 모든 파일, src/field/text.asm(LoadLetterGfx, 줄의 x 범위, CalcTextWidth, NewLine, UpdateDlgTextOneLine, 맵 제목), src/gfx/font_gfx.asm(FontWidth), src/field/reset.asm(필드 루프), src/field/hdma.asm(DlgWindowFixedColorTbl), tools/char_table/(kanji.json, kana.json). 한 프레임에 한 글자와 그라데이션의 스캔라인당 값 하나는 코드에 대한 해석으로, 단언했을 뿐 실행하지 않음. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  5. Unicode 컨소시엄, JIS0208.TXT, JIS X 0208에서 Unicode로의 대응표, 버전 2.0, OBSOLETE/EASTASIA에 수록, 2026년 10월 5일 가져와 저장, https://www.unicode.org/Public/MAPPINGS/OBSOLETE/EASTASIA/JIS/JIS0208.TXT. measure_coretext.swift와 measure_bdf.py로 센 결과: 6,879자. 비한자 524자(그중 169자가 4구와 5구의 가나), 16구에서 47구의 제1수준 한자 2,965자, 48구에서 84구의 제2수준 한자 3,390자. ↩↩↩↩↩↩↩↩

  6. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_bdf.py, 출력은 measure_bdf.out. 입력: Misaki Gothic, Misaki Gothic 2nd, Misaki Mincho와 k8x12, k8x12L, k8x12S의 BDF 파일(2021-05-05 릴리스)과 저장한 JIS0208.TXT. 각 폰트의 픽셀 크기, 경계 상자, 어센트와 디센트, 글자 수, 어드밴스 너비, 어느 방향으로든 잉크가 셀을 채우는 전각 글리프, 한자(U+4E00에서 U+9FFF)에 대한 같은 것, 그리는 JIS X 0208 문자를 보고함. ↩↩↩↩↩↩↩↩↩↩↩

  7. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_coretext.swift, swiftc -O로 컴파일해 iOS가 아니라 macOS 27.0(빌드 26A428)에서 실행, 출력은 measure_coretext.out. 입력: Misaki Gothic, Misaki Gothic 2nd, k8x12, k8x12S(2021-05-05 TTF 릴리스), PixelMplus10 Regular와 Bold, PixelMplus12 Regular(PixelMplus d89b95f), DotGothic16, Press Start 2P, Silkscreen(google/fonts 9710da1), 시스템 대조군으로 Hiragino Sans W3, 저장한 JIS0208.TXT. 각 폰트의 글리프 수와 비트맵 테이블(CTFontCopyAvailableTables), 디자인 그리드(샘플 글리프의 아웃라인 점 95%가 0.04픽셀 이내에 놓이는, 4에서 40 사이 em당 최소 픽셀 수. 축마다 일정한 오프셋 하나 허용), 라틴 문자와 가나 어드밴스, ASCII와 JIS X 0208 지원 범위, 샘플 한 줄을 그리드의 1에서 4배로 8비트 회색 비트맵에 그렸을 때 잉크 픽셀 중 중간 톤(255 중 8에서 247)의 비율(안티앨리어싱 켬, 끔, 그리고 켠 상태에서 서브픽셀 위치 지정 켬과 양자화 끔. 폰트 스무딩은 허용하지 않음), PixelMplus12에 대해서는 베이스라인을 오프셋만큼 옮긴 같은 것, Human Interface Guidelines의 Body와 Caption 1 크기 중 3배에서 폰트 픽셀이 정수가 되는 것, 그리고 CTFramesetter로 8에서 20em에 조판한 131자 일본어 문단의 각 줄 첫 글자(태그 없음과 kCTLanguageAttributeName “ja”)를 행두 금칙 문자 목록과 대조한 결과를 보고함. DotGothic16에는 디자인 그리드가 없으므로 명목상 em당 16도트(도트당 62.5단위)와 그 1.25에서 4배로 그리고, em당 32픽셀로 조판함. 이 크기는 이름과 measure_outline_coords.out의 도트 피치에서 고른 것으로, 출력에 찍힘. 모든 렌더링은 폰트 스무딩을 허용하지 않음(setAllowsFontSmoothing(false)). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  8. Jenni Lada, “Interview: Preparing Final Fantasy Pixel Remaster for the Switch and PS4”, Siliconera, 2023년 6월 2일, 새 폰트 옵션에 대한 프로듀서 Naofumi Takuma의 말(“We didn’t have any alternative data saved on file in-house for the Japanese language font, so we decided to create a new one”, “a few hundred”에 대해 “around 7,500 characters”), 2026년 10월 5일 가져옴, https://www.siliconera.com/interview-preparing-final-fantasy-pixel-remaster-for-the-switch-and-ps4/ ↩↩↩↩↩↩

  9. 저자의 측정, 2026년 10월 5일: 이 글의 초안 폴더에 있는 figures/dump_glyphs.swift, swiftc -O로 컴파일해 macOS 27.0(26A428)에서 실행, 출력은 figures/glyphs.txt: Core Text로 10픽셀에 그린 PixelMplus10 Regular. “Nice card!”는 폰트 스무딩을 허용하지 않을 때(measure_coretext.swift가 그리는 방식) 잉크 픽셀 94개 중 중간 톤 0개, 비트맵 컨텍스트의 기본값으로는 272개 중 178개. 일본어 샘플 줄은 164개 중 0개와 451개 중 287개. ↩↩↩↩↩↩↩↩↩↩

  10. Apple, Human Interface Guidelines, “Typography”(iOS와 iPadOS의 기본 및 최소 텍스트 크기, 17포인트와 11포인트. “Follow the recommended default and minimum text sizes for each platform”, “If you use a custom font, make sure it implements the same behaviors”, iOS Dynamic Type 표), 2026년 10월 5일 가져옴, https://developer.apple.com/design/human-interface-guidelines/typography ↩↩↩↩↩↩↩

  11. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_kiradex_text.py, 출력은 measure_kiradex_text.out. 태평양 시간 12시 34분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 Kiradex, 그날 편집 중이던 작업 트리에 대해 실행. 스크립트는 파일을 줄이 아니라 심볼로, 통째로 읽음. 입력은 디스크에 있던 Kiradex 저장소에서 읽기만 함: server/app/lines.py(23줄, 3에서 40자, 중앙값 16), server/app/names.py(20개씩의 목록에서 고른 두 단어와 네 자리 숫자의 핸들, 최대 20자), Kiradex/World/*.swift의 모든 .kit941Font 호출(파일 네 개에서 caption 10, label 3, body 3, title 3), 테마의 폰트 패밀리, 말풍선의 수정자, 무대의 손쉬운 사용 수정자, 앱 소스에서 AccessibilityComponent, accessibilityDirectTouch, updatesFrequently, AccessibilityNotification, typesettingLanguage, UIFontMetrics, ScaledMetric, dynamicTypeSize 검색(모두 0), knownRegions(Base, en), .xcstrings와 .lproj 파일 수(0). 이 시리즈가 Xcode 27 시뮬레이터 프로필에서 측정한 기기 크기(iPhone 18 Pro Max 440×956포인트, iPhone Duo 바깥쪽 466×678, 안쪽 669×951, 모두 3배)에 대한 PlazaRig.fit 규칙에 따른 세계의 배율. 그리고 measure_coretext.out에서 각 후보 폰트의 em과 어드밴스를 가져와, 48포인트 인셋 이후 상자 한 줄당 글자 수, m = 4, 5, 6, 8에서 가장 긴 프리셋 대사의 말풍선 너비(283, 349, 416, 549포인트: 그 크기에서의 어드밴스에 말풍선의 좌우 8포인트 패딩을 더한 것. 테두리는 말풍선 안쪽에 그려짐), 10픽셀 em에 대해 m으로 반올림한 Body 크기(0.5는 올림)를 구함. 10월 5일 나중에 추가(이전 스크립트와 출력은 그 옆에 보관): 11포인트 이상이 되는 가장 작은 m(3배에서 4, 2배에서 3)으로 한 2배 반올림. 브리프의 테두리에서의 상자(너비에서 32포인트를 빼고 정수 폰트 픽셀로 내림, 테두리는 좌우 각각 8폰트 픽셀. 18 Pro Max에서 m = 5일 때 1,224기기 픽셀이 1,220이 되고, 한 줄에 라틴 문자 45자나 전각 22자). names.py의 단어 쌍 400개 전부와 23줄 전부에 대한 “handle: line”의 페이지를 Body 크기 12단계와 뷰 다섯 개에서(한 페이지 두 줄, 공백에서의 탐욕적 줄 바꿈. probe_line_start_glyphs.out에 따라 출력 가능한 ASCII 문자는 모두 너비 5폰트 픽셀). measure_ios27_iphones.out의 각 iPhone에서의 말풍선 상한. 218폰트 픽셀의 서버 한도. 그리고 1/120을 120번 더한 합. 그날 더 나중에 추가(이전 스크립트와 출력은 그 옆에 보관하고 출력의 이전 줄은 모두 그대로): 안전한 너비에서의 자리와 상한. 휴대폰은 좌우 인셋 없음, 세로 방향 Duo 바깥쪽 디스플레이는 인셋 없음과 시리즈의 Duo 글이 측정한 84포인트 뒤쪽 인셋 있음 둘 다로, 그 글에서 가져온 것(안전한 너비 382, 자리 350, 349.33포인트에서 상한 5. 기본 크기의 상자는 테두리 아래에서 라틴 문자 38자나 전각 19자, 현재 인셋 이후 40자나 20자). 그 안전한 너비에서의 “handle: line” 페이지. 배치 테스트를 위한 너비. 그 자리에서의 서버 한도(m = 4에서 296과 302.67포인트, 350 안). 그리고 server/app/npcs.py에서 마을의 수집가 넷, 그 핸들, 18 Pro Max의 AX3에서 그 대사의 페이지(최대 2), 항목 4 테스트의 탭 한도, (3 − 1)대사 × 2페이지, Instant에서 페이지당 탭 한 번: 4. 10월 15일에 그 마지막 줄을 지금 모습의 테스트에 맞춰, 출력이 보여 주는 한 같은 입력으로 다시 계산함(무대 파일은 12시 28분에 읽은 10월 5일 사본, lines.py, names.py, npcs.py는 10월 2일과 3일 이후 그대로, 나머지 입력은 커밋 fc9d4876의 현재 트리에서 읽었으며 같은 줄을 찍음). 이전 스크립트와 출력은 그 옆에 보관했으며, 출력의 다른 줄은 모두 전과 같이 찍힘. 10월 15일 더 나중에 추가(이전 스크립트와 출력은 그 옆에 보관하고 이전 줄은 모두 그대로): 현재 가장 긴 대사가 허용되는 어떤 m에서도 들어가지 않는 배치 테스트의 경우(84포인트 인셋이 있는 3배의 360과 375포인트, 2배의 375, 그리고 좌우 16씩인 2배의 375: 첫째 경우에서는 244포인트 자리에 대해 m₀에서 282.67포인트). 서버 한도에 꼭 맞는 일본어 대사, 21 × 10 = 210폰트 픽셀이 있을 때의 상한(18 Pro Max에서 5, Duo에서 레일 없음과 있음으로 5와 4, 3배의 360포인트에서 4, 2배에서 3). 그리고 캡처에서 나와야 할 말풍선의 m, 즉 상자의 m에서 1을 뺀 값(m₀ 아래로 내려가지 않음)과 두 언어에 걸친 상한 중 작은 쪽(3배 뷰의 기본 크기에서 4, 2배에서 3. AX3에서는 상한). 같은 저녁에 마찬가지로 추가: measure_ios27_iphones.out이 나열하는 모든 너비를, 좌우 인셋 없음, 좌우 16포인트씩, 84포인트 뒤쪽 인셋으로, 현재 영어 상한, 한도의 일본어 대사가 있을 때의 상한, 그리고 둘 다와 함께(인셋 없음: 360과 375에서 4, 일본어 대사가 있으면 390과 393에서 4, 402에서 440까지 5, 2배에서 3). 현재 가장 긴 대사가 허용되는 어떤 m에서도 들어가지 않는 경우(84포인트 인셋으로 3배의 360, 375, 390, 393포인트와 2배의 375와 414. 좌우 16씩으로 2배의 375). 그리고 m₀에서의 “Hi!”, 3배에서 36포인트, 2배에서 38.50포인트로, 모든 경우에 들어감. 그 뒤에 추가: 같은 표에 한도의 라틴 문자 대사, 43 × 5 = 215폰트 픽셀을 현재 대사와 일본어 대사 옆에 두고, 각각이 허용되는 어떤 m에서도 들어가지 않는 경우: 36개 중 7, 8, 10. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  12. pret, pokered/data/text_boxes.asm, 커밋 d2704a6, 10번째 줄(db MESSAGE_BOX, 0, 12, 19, 17), 2026년 10월 5일 열람, https://github.com/pret/pokered/blob/master/data/text_boxes.asm ↩↩

  13. pret, pokeemerald/src/text.c, 커밋 731ad5b(119번째 줄부터의 sFontInfos. 75번째 줄의 sDownArrowYCoords { 0, 1, 2, 1 }와 76번째 줄의 sWindowVerticalScrollSpeeds. 281번째 줄에서 textSpeed = speed를 설정하고 296번째 줄에서 --sTempTextPrinter.textSpeed;를 하는 AddTextPrinter. 832번째 줄의 화살표 downArrowDelay = 8. RENDER_STATE_HANDLE_CHAR 케이스에서 944번째 줄의 if (JOY_HELD(A_BUTTON | B_BUTTON) && subStruct->hasPrintBeenSpedUp), 947번째 줄의 카운트다운 if (textPrinter->delayCounter && textPrinter->textSpeed), 950번째에서 953번째 줄의 canABSpeedUpPrint와 A나 B의 새 누름에 따른 hasPrintBeenSpedUp 설정, 958번째에서 961번째 줄에서 카운터를 3으로, 아니면 textSpeed로 설정하는 if (!(gBattleTypeFlags & BATTLE_TYPE_RECORDED) && gTextFlags.autoScroll). 1094번째 줄의 EXT_CTRL_CODE_JPN. 1853번째 줄부터의 DecompressGlyph_Normal과 1887번째 줄부터의 GetGlyphWidth_Normal), 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/text.c ↩↩↩↩↩↩↩↩↩↩↩

  14. pret, pokefirered/src/text.c, 커밋 037335f, 639번째에서 647번째 줄(RENDER_STATE_HANDLE_CHAR 케이스에서 카운터를 0으로 하는 if (JOY_HELD(A_BUTTON | B_BUTTON) && subStruct->hasPrintBeenSpedUp)과, 지연을 세는 동안 hasPrintBeenSpedUp을 설정하는 gTextFlags.canABSpeedUpPrint && JOY_NEW(A_BUTTON | B_BUTTON)), 2026년 10월 5일 열람, https://github.com/pret/pokefirered/blob/master/src/text.c ↩↩↩

  15. pret, pokecrystal/home/print_text.asm, 커밋 5beda23(4번째 줄, “The text speed setting in wOptions is actually a frame count”, 그리고 A나 B를 누르고 있는 동안 한 글자에 한 프레임을 기다리는 PrintLetterDelay)와 pokered/constants/ram_constants.asm, d2704a6(38번째에서 40번째 줄: TEXT_DELAY_FAST 1, TEXT_DELAY_MEDIUM 3, TEXT_DELAY_SLOW 5), 2026년 10월 5일 열람, https://github.com/pret/pokecrystal/blob/master/home/print_text.asm ↩↩

  16. pret, pokecrystal/home/joypad.asm, 커밋 5beda23(342번째 줄의 WaitPressAorB_BlinkCursor. 442번째 줄의 BlinkCursor는 hlcoord 18, 17의 타일을 '▼'와 '─'로 번갈아 바꿈), 2026년 10월 5일 열람, https://github.com/pret/pokecrystal/blob/master/home/joypad.asm. 깜빡임의 주기는 도출하지 않음. ↩↩↩

  17. Num Kadoma, littlelimit.net, “font”, Misaki, k8x12와 작가의 다른 폰트에 대한 라이선스 절(“These fonts are free software. Unlimited permission is granted to use, copy, and distribute them, with or without modification, either commercially or noncommercially.”), 2026년 10월 5일 가져옴, https://littlelimit.net/font.htm#license ↩↩↩↩↩

  18. itouhiro, PixelMplus, README.md(아웃라인 전용 TrueType, “埋め込みビットマップは なし”. “JIS第1・第2水準のすべての漢字を収録”과 ISO-8859-1. PixelMplus12와 PixelMplus10. M+ BITMAP FONTS에서 만듦. 라이선스 M+ FONT LICENSE)와 misc/mplus_bitmap_fonts/LICENSE_E(M+ FONT LICENSE의 세 문장), 커밋 d89b95f, 2026년 10월 5일 열람, https://github.com/itouhiro/PixelMplus/blob/master/misc/mplus_bitmap_fonts/LICENSE_E ↩↩↩↩↩

  19. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_outline_coords.swift, macOS 27.0에서 컴파일하고 실행, 출력은 measure_outline_coords.out: PixelMplus12 Regular와 DotGothic16의 H, A, o, あ와 Silkscreen의 H, A, o의 원래 x, y 아웃라인 좌표, em당 1,000단위. ↩↩↩↩↩↩↩

  20. Apple, Human Interface Guidelines, “Accessibility”(“Ideally, give people the option to enlarge text by at least 200 percent (or 140 percent in watchOS apps).”), 2026년 10월 5일 가져옴, https://developer.apple.com/design/human-interface-guidelines/accessibility ↩↩↩

  21. W3C, “Requirements for Japanese Text Layout”, Working Group Note, 2020년 8월 11일, 3.1.7절(행두 금칙 규칙과 덜 엄격한 규칙에 대한 메모), 2026년 10월 5일 가져옴, https://www.w3.org/TR/jlreq/ ↩↩↩↩↩↩↩↩

  22. 이 글을 위한 저자의 조사 자료, 2026년 10월 5일(비공개): 위의 측정, 사례 연구, Kiradex를 위한 브리프의 초안. ↩↩↩

  23. Blake Crosley, “Pixel-Art Worlds on iPhone: What the 16-Bit Masters Knew”, blakecrosley.com, 2026년 10월 3일: 규칙 10(“Text is art”, m5x7 페이지의 “use font size 16, 32, 48, etc”, Emerald의 24×24 텍스트 창 테두리에 대한 문장), k = 8과 k = 6에서의 텍스트 크기 산수, 일본어 약 7,500자에 대해 GoNintendo를 출처로 든 Pixel Remaster 문단, 텍스트 계획(“m5x7 at integer multiples in a 24-by-24 nine-slice box, two lines, typewriter reveal, a two-frame advance arrow”), 2026년 10월 5일 열람, https://blakecrosley.com/blog/pixel-art-world-on-iphone. 그 글은 여기서 인용했으며 바꾸지 않음. ↩↩↩↩↩↩↩↩↩↩

  24. Kiradex 소스, Kiradex/KiradexTheme.swift, 12번째 줄(let fontFamily: Kit941.FontFamily = .systemDesign(.monospaced)), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). ↩↩

  25. Kiradex 소스, Kiradex/World/PlazaStage.swift, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): 146, 155, 189번째 줄(stepTrace를 RealityView의 make 클로저에서 빌드 실패로, rig.onStep에서 각 걸음으로, -airProbe 아래 .task에서 공기 보고로 설정. 12시 42분에 같은 파일을 읽으니 넷째, 새 -plazaLapDemo 아래의 stepTrace = "lap done"이 있었음), 58번째에서 61번째 줄(stepTrace, “The player’s last step, “x,y facing”, as the stage’s accessibility value: the walks read it, since nothing else the simulator logs reaches them.”), 313번째에서 315번째 줄(.accessibilityElement(), .accessibilityLabel("The plaza"), .accessibilityValue(stepTrace)), 289번째에서 293번째 줄(말풍선, 121번째에서 294번째 줄의 무대 GeometryReader 안 마지막 .overlay), 295번째에서 309번째 줄(그 위에 무대가 덮는 두 오버레이: 3포인트마다 높이 1포인트인 .black.opacity(0.16) 스캔라인과 RadialGradient 비네트), 402번째에서 418번째 줄(bubbles(of:in:): timeline.date.timeIntervalSince(said.at) < 4인 동안 보이는 대사로, 머리의 뷰 지점에서 Kit941.Spacing.lg를 뺀 곳에 놓이며 가두기 없음, 히트 테스트 끔), 444번째에서 459번째 줄(Bubble 뷰, .padding(.horizontal, Kit941.Spacing.sm) 다음에 .overlay(Rectangle().strokeBorder(...)), 그 주석 “A line said, as the old games drew it: a cream box with a dark border.”, caption 굵게, WorldInk.paper, Kit941.Spacing.xxs 잉크 테두리, .fixedSize(), .accessibilityLabel("Said: \(text)")). ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  26. Kiradex 소스, Kiradex/World/WorldHUD.swift, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). 파일 날짜는 10월 4일이며 읽는 동안 바뀌지 않음: 31번째에서 38번째 줄(WorldInk, “The world’s own colours, as the old games drew a text box: cream paper”와 그 종이 색), 40번째에서 44번째 줄(장소의 설명은 “in a text box at the bottom. Half open, the box keeps to the wider side of the fold.”), 61번째에서 79번째 줄(GeometryReader 안의 크롬, .padding(Kit941.Spacing.md)로 인셋, 상자는 그 너비 전체, 반쯤 열렸을 때는 넓은 쪽 창의 너비에서 좌우 각각 Kit941.Spacing.md를 뺀 너비), 84번째에서 101번째 줄(상자: 제목은 caption 굵게 대문자, 상태는 caption 세미볼드에 식별자 <name>-status, 광장에서는 plaza-status, 종이 배경, Kit941.Spacing.xxs 잉크 테두리, 주석 “Taps go through to the ground under it.” 아래의 .allowsHitTesting(false)). 그리고 Kiradex/World/PlazaStage.swift의 581번째와 582번째 줄, PlazaView가 words를 상태로 하여 무대 전체 위에 WorldHUD를 덮는 곳, 725번째에서 736번째 줄(words: 시간대의 문구, 여기 있는 사람 수와 조언 한 줄, 얻은 리본, 계절의 문구). 키트의 간격은 941Kit의 Sources/Kit941/Tokens/Spacing.swift로, iPhone에서 xxs 2, xs 4, sm 8, md 16, lg 24포인트. ↩↩↩↩↩↩↩

  27. Kiradex 소스, Kiradex.xcodeproj/project.pbxproj, 1138번째에서 1141번째 줄(knownRegions = (Base, en)), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). 12시 42분에는 파일의 줄이 늘어 있었음. ↩↩

  28. Kiradex 소스, server/app/lines.py, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): docstring “The preset lines: everything anyone can say in the plaza.”, LINES의 항목 23개(5번째 줄부터), 그리고 인덱스 14 앞, 20번째 줄의 주석 “Indices are fixed; add at the end only, the app shows them by number.” ↩↩↩↩

  29. Kiradex 소스, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): scripts/forge/kit.py, 529번째와 530번째 줄(def sign():, “A hanging sign on a bracket, 12 × 11.”), Kiradex/World/TileMap.swift, 52번째 줄(맵 문자 "S": .sign), Kiradex/Resources/World/town.json(S 셀 하나, x 7, 행 10. 12시 33분에 Python의 json 모듈로 읽음). ↩↩↩↩

  30. 방법. pret 디컴파일, 얕은 클론을 읽은 시점은 pokeemerald 731ad5b(2026년 10월 1일), pokefirered 037335f(2026년 9월 26일), pokecrystal 5beda23(2026년 9월 29일), pokered d2704a6(2026년 9월 22일), https://github.com/pret. Final Fantasy VI는 everything8215/ff6 디스어셈블리 813013276c952fdd27edcf7b8b86f17291542cbf(2026년 9월 14일)에서. 이것은 ROM의 그래픽이 아니라 코드와 추출 목록을 담고 있음, https://github.com/everything8215/ff6. PixelMplus는 d89b95f04eeb95bf98bb09cb9234a2f99d186df5(2013년 6월 2일)에서 얕은 클론. Misaki와 k8x12는 2021-05-05 릴리스의 TTF와 BDF로, 작가의 사이트에서 내려받음. Press Start 2P, Silkscreen, DotGothic16(폰트 파일, OFL.txt, METADATA.pb)은 google/fonts 9710da1(2026년 9월 30일)에서. 웹 페이지는 2026년 10월 5일에 가져와 저장. 이 글을 위한 저자의 증거 폴더에 있는 스크립트 열세 개(Python 열 개, Swift 세 개)와, 이 글의 그림과 함께 둔 Swift 스크립트 하나 dump_glyphs.swift가 측정한 모든 숫자를 내며, 각각 출력을 그 옆에 저장함. pret이 빌드하는 것은 영어판 게임이며, 일본어판 ROM은 읽지 않음. ↩↩↩↩↩↩

  31. pret, pokeemerald/src/fonts.c(148번째 줄부터의 gFontNormalLatinGlyphWidths)와 pokeemerald/charmap.txt(50번째에서 52번째 줄, 프랜차이즈 단어를 위한 특수 글리프), 커밋 731ad5b, 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/fonts.c ↩↩

  32. Martin Korth, GBATEK, “LCD Dimensions and Timings”(“Total 228 lines, 16.743 ms, 280896 cycles - ca. 59.737 Hz”. 보이는 라인 160개. 보이는 라인당 240도트), 2026년 10월 5일 가져옴, https://problemkaputt.de/gbatek-lcd-dimensions-and-timings.htm ↩

  33. 측정값에 대한 저자의 계산: 216 ÷ 5.25 = 41자. 59.737 ÷ 1 = 초당 59.7자. 191 × 1.25² = 298픽셀. PixelMplus12의 −33단위 ÷ 픽셀당 83.33단위 = −0.396픽셀(프로브는 그 구간의 중심인 −0.39를 보고함). 6,879에 대해 7,500. 정수가 되는 1과 3의 여집합으로 Body 크기 12개 중 11개와 12개 중 9개가 그리드를 벗어남. 14 ÷ 17 = 82%, 53 ÷ 17 = 312%. 13.33 ÷ 16.67 = 80%, 53.33 ÷ 16.67 = 320%. 11포인트 ÷ 텍셀당 2포인트 = 5.5텍셀. 4 × 10 ÷ 3 = 13.33포인트. 12폰트 픽셀 × 5 ÷ 3 = 20포인트. 440 − 32 = 408, 360 − 32 = 328, 375 − 32 = 343포인트. 2배에서 40 × 5 × 3 ÷ 2 + 16 = 316포인트. ⌈11 × 3 ÷ 10⌉ = 4, ⌈11 × 2 ÷ 10⌉ = 3, 10 × 3 ÷ 2 = 15포인트. (360 − 48) × 3 ÷ 4 = 234, (375 − 48) × 2 ÷ 3 = 218폰트 픽셀, 218 ÷ 5 = 라틴 문자 43자, 218 ÷ 10 = 전각 21자. 21 × 10 × 4 ÷ 3 + 16 = 296, 21 × 10 × 3 ÷ 2 + 16 = 331포인트. (440 − 32) × 3 = 1,224기기 픽셀, ÷ 5 = 244.8이므로 244폰트 픽셀(1,220기기 픽셀), 2 × 8을 빼서 228, ÷ 5 = 라틴 문자 45자, ÷ 10 = 전각 22자. 15 × 3 ÷ 10 = 4.5를 5로 올림. 1/120을 배정밀도로 120번 더하면 0.9999999999999989, × 30 = 29.999999999999968, 내림해서 29. 1.5초 × 12 = 18자. 20 × 5 × 4 ÷ 3 = 133포인트, 2배에서 20 × 5 × 3 ÷ 2 = 150포인트. 466 − 84 = 382, 382 − 32 = 350포인트, 466 − 32 = 434. 40 × 5 × 5 ÷ 3 + 16 = 349.33포인트. (382 − 32) × 3 ÷ 5 = 210폰트 픽셀, 2 × 8을 빼서 194, ÷ 5 = 라틴 문자 38자, ÷ 10 = 전각 19자. (382 − 48) ÷ (5 × 5 ÷ 3) = 40.08, ÷ (10 × 5 ÷ 3) = 20.04. 43 × 5 × 4 ÷ 3 + 16 = 302.67포인트. 360 − 84 − 32 = 244포인트. (3 − 1) × 2 × 1 = 탭 4번. 16텍셀 × 2.67포인트 = 42.7포인트, 13 × 2.67 = 34.7. 9프레임 ÷ 60 = 150밀리초, 네 단계에 600밀리초. 3포인트 × 3 = 스캔라인 간격 9기기 픽셀, 각 선은 1포인트 × 3 = 높이 3기기 픽셀. ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩

  34. pret, pokeemerald/src/battle_message.c, 커밋 731ad5b(3002번째와 3003번째 줄: gBattleTypeFlags & (BATTLE_TYPE_LINK | BATTLE_TYPE_RECORDED)일 때 자동 스크롤 켬. 3010번째 줄, 통신 배틀 메시지 창의 speed = 1. 3012번째 줄, 녹화된 배틀의 speed = sRecordedBattleTextSpeeds[GetTextSpeedInRecordedBattle()], 1957번째 줄의 표 {8, 4, 1, 0})와 pokeemerald/src/scrcmd.c(1286번째 줄부터의 ScrCmd_messageautoscroll, 1292번째와 1293번째 줄에서 autoScroll과 forceMidTextSpeed 설정), 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/battle_message.c ↩↩↩

  35. pret, pokeemerald/charmap.txt, 커밋 731ad5b, 1087번째에서 1089번째 줄('\l' = FA @ scroll up window text, '\p' = FB @ new paragraph, '\n' = FE @ new line)과 include/constants/characters.h, 175번째와 176번째 줄(CHAR_PROMPT_SCROLL, “waits for button press and scrolls dialog”. CHAR_PROMPT_CLEAR, “waits for button press and clears dialog”), 그리고 src/text.c의 968번째에서 971번째 줄(currentY를 한 줄 내리는 CHAR_NEWLINE)과 1102번째에서 1109번째 줄(두 프롬프트가 RENDER_STATE_CLEAR와 RENDER_STATE_SCROLL_START로 들어감), 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/charmap.txt ↩

  36. pret, pokeemerald/src/text_window.c(53번째 줄의 message_box.png, 93번째 줄의 LoadMessageBoxGfx, 110번째 줄의 LoadUserWindowBorderGfx), pokeemerald/include/text_window.h(4번째 줄의 #define WINDOW_FRAMES_COUNT 20), pokeemerald/src/option_menu.c(FRAME 옵션), pokeemerald/graphics/text_window/, 커밋 731ad5b, 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/text_window.c ↩↩↩

  37. pret, pokeemerald/data/text/와 각 맵의 scripts.inc, 커밋 731ad5b, 그중 data/maps/LittlerootTown/scripts.inc, 854번째 줄(.string "MOM: {PLAYER}, we're here, honey!\p"), 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/data/maps/LittlerootTown/scripts.inc ↩↩

  38. pret, pokeemerald/src/international_string_util.c, src/contest.c, src/battle_tower.c(이름 안의 EXT_CTRL_CODE_JPN과 EXT_CTRL_CODE_ENG 코드)와 graphics/fonts/(japanese_normal.png, japanese_small.png, japanese_bold.png, japanese_frlg_male.png), 커밋 731ad5b, 2026년 10월 5일 열람, https://github.com/pret/pokeemerald/blob/master/src/international_string_util.c ↩↩

  39. pret, pokefirered/src/new_menu_helpers.c, 커밋 037335f(34번째 줄부터의 sStandardTextBox_WindowTemplates, 40번째 줄의 .width = 26, 텍스트 속도 지연)와 pokefirered/src/text_printer.c(AddTextPrinter, 78번째 줄의 textSpeed = speed와 93번째 줄의 --sTempTextPrinter.textSpeed;), 커밋 037335f, 2026년 10월 5일 열람, https://github.com/pret/pokefirered/blob/master/src/new_menu_helpers.c ↩

  40. Pan Docs, “Rendering”(“One frame: 70224 dots @ 59.7 fps”. 한 프레임은 “takes ~16.74 ms instead of ~16.67”), gbdev.io, 2026년 10월 5일 가져옴, https://gbdev.io/pandocs/Rendering.html ↩

  41. pret, pokecrystal/gfx/font.asm, 커밋 5beda23(테두리 FRAME_1에서 FRAME_8, 각각 1비트 타일 6개인 TEXTBOX_FRAME_TILES, 쓰이지 않는 아홉 번째 테두리)와 gfx/font/font.png, 2026년 10월 5일 열람, https://github.com/pret/pokecrystal/blob/master/gfx/font.asm ↩

  42. everything8215, ff6, tools/rip_list_en.json과 tools/rip_list_jp.json, 커밋 8130132(0xED0000에서 0xED1BFF의 assets/gfx/window/window_%s.4bpp, 0xED1C00에서 0xED1CFF의 window_%s.pal), 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/tools/rip_list_en.json ↩

  43. everything8215, ff6, src/menu/config.asm(ConfigWindowNumPos, $1d57에서 읽는 LoadWindowPal, $1d55의 폰트 색, ConfigColorBarTextList, ChangeConfigOption_02)와 src/menu/menu_sram.asm(102번째 줄, “message speed = 3, battle speed = 3”), 커밋 8130132, 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/src/menu/config.asm ↩↩↩

  44. everything8215, ff6, 커밋 8130132: src/btlgfx/gfx_cmd.asm의 1101번째에서 1103번째 줄(MsgSpeedTbl, “wait duration for each battle message speed”, $20,$30,$40,$50,$60,$70,$80,$90)과 1151번째에서 1165번째 줄(mess_wait, $1d4d의 비트 4에서 6을 인덱스로 읽고 그 횟수만큼 WaitFrame을 부름). src/menu/config.asm의 1499번째 줄(“battle speed, battle message speed, magic order, and window pattern”)과 933번째 줄부터의 ChangeConfigOption_02. src/menu/menu_text.en.inc의 346번째 줄(1에서 6으로 그려지는 설정). 코드가 $1d4d를 건드리는 파일: src/battle/init.asm(명령 설정, 전투 모드, 전투 속도), src/btlgfx/gfx_cmd.asm, src/menu/config.asm, src/menu/field_menu.asm, src/menu/menu_sram.asm. src/field/ 아래에는 하나도 없음. 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/src/btlgfx/gfx_cmd.asm ↩

  45. everything8215, ff6, src/field/hdma.asm(DlgWindowFixedColorTbl)과 src/field/event.asm(1591번째 줄, “dialog window y position (1 or 18)”), 커밋 8130132, 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/src/field/hdma.asm ↩↩↩

  46. everything8215, ff6, src/field/text.asm(LoadLetterGfx, x 4에서 $e0까지의 대화 줄, CalcTextWidth, NewLine, UpdateDlgTextOneLine, 100프레임 동안 보이는 가운데 정렬 맵 제목)과 src/gfx/font_gfx.asm(FontWidth), 커밋 8130132, 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/src/field/text.asm ↩↩↩↩↩

  47. everything8215, ff6, tools/rip_list_jp.json(0xC494C0에서 0xC4FFFF의 large_font.jp.1bpp)과 tools/char_table/, 커밋 8130132, 2026년 10월 5일 열람, https://github.com/everything8215/ff6/tree/master/tools/char_table ↩

  48. everything8215, ff6, src/field/reset.asm(필드 루프: 수직 블랭크를 기다리고 한 바퀴에 한 번 UpdateDlgText를 부름), 커밋 8130132, 2026년 10월 5일 열람, https://github.com/everything8215/ff6/blob/master/src/field/reset.asm. 이 디스어셈블리는 하나의 소스에서 영어판과 일본어판 ROM을 모두 빌드함. ↩↩

  49. Data Crystal, “Chrono Trigger/ROM map”, datacrystal.tcrf.net, 2026년 10월 5일 가져옴: HTTP 403, 읽지 못함, https://datacrystal.tcrf.net/wiki/Chrono_Trigger/ROM_map ↩↩↩

  50. Kiradex, docs/TestFlight.md, 5단계(192번째에서 194번째 줄: Dex에서 일본어를 고르면 세트와 카드가 일본어 이름으로 나오고 아래에 영어가 붙음), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). ↩

  51. Num Kadoma, littlelimit.net, “8×8ドット日本語フォント「美咲フォント」”(개요의 8×8 셀, JIS 제1·제2수준, 7×7 잉크. Misaki Mincho와 Misaki Gothic 2nd의 설명. M+ FONTS 라이선스에 대한 메모. PC-E500의 일본어 표시 드라이버를 위해 만든 경위), 2026년 10월 5일 가져옴, https://littlelimit.net/misaki.htm ↩↩↩↩

  52. Num Kadoma, littlelimit.net, “8×12ドット日本語フォント「k8x12」”(8×12 셀, JIS 제1·제2수준, L과 S 변형, 옛 게임기의 8×8 그래픽에 대한 S 변형의 메모), 2026년 10월 5일 가져옴, https://littlelimit.net/k8x12.htm ↩

  53. Google Fonts, ofl/dotgothic16/OFL.txt(SIL Open Font License 1.1, Copyright 2020 The DotGothic16 Project Authors, 예약 폰트 이름 없음)와 METADATA.pb(디자이너 Fontworks Inc., 서브셋 cyrillic, japanese, latin, latin-ext, 2020-12-15 추가), 9710da1, 2026년 10월 5일 열람, https://github.com/google/fonts/blob/main/ofl/dotgothic16/OFL.txt ↩↩↩↩

  54. Google Fonts, ofl/pressstart2p/OFL.txt(SIL Open Font License 1.1, “with Reserved Font Name “Press Start 2P”“)와 METADATA.pb(디자이너 CodeMan38), 9710da1, 2026년 10월 5일 열람, https://github.com/google/fonts/blob/main/ofl/pressstart2p/OFL.txt ↩↩↩

  55. Google Fonts, ofl/silkscreen/OFL.txt(SIL Open Font License 1.1, 예약 폰트 이름 없음)와 METADATA.pb(디자이너 Jason Kottke), 9710da1, 2026년 10월 5일 열람, https://github.com/google/fonts/blob/main/ofl/silkscreen/OFL.txt ↩↩

  56. DaFont, “Pixel Operator Font”(Jayvee Enaguas 작. “Bitmap (16 px)”. 폰트 파일 15개. 글리프 241개. 작가의 메모 “Changed licence to Creative Commons Zero (CC0) 1.0”과 “licensed under a Creative Commons Zero (CC0) 1.0”), 2026년 10월 5일 가져옴, https://www.dafont.com/pixel-operator.font. 메모가 가리키는 저장소 https://notabug.org/HarvettFox96/ttf-pixeloperator 는 2026년 10월 5일에 HTTP 404를 돌려줌. 단일 출처. ↩↩↩↩

  57. Daniel Linssen, “m5x7”, itch.io(“use font size 16, 32, 48, etc”. “free to use but attribution appreciated”. “Asset license Creative Commons Zero v1.0 Universal”. 평균 너비 5, 어센더 7, 디센더 2, x하이트 5. 글자에 대한 설명 “uppercase and lowercase letters, including many accented letters, numbers and most common symbols”. 원하는 금액을 내는 다운로드), 2026년 10월 5일 가져옴, https://managore.itch.io/m5x7. 폰트 파일은 내려받지 않음. ↩↩↩↩↩↩

  58. Daniel Linssen, “m3x6”, itch.io(“use font size 16, 32, 48, etc”. “free to use with attribution”. 에셋 라이선스 항목 없음. 원하는 금액을 내는 다운로드), 2026년 10월 5일 가져옴, https://managore.itch.io/m3x6. 폰트 파일은 내려받지 않음. ↩↩↩↩↩

  59. Apple Developer Documentation, “Core Text”(“Create text layouts, optimize font handling, and access font metrics and glyph data”. iOS 3.2, macOS 10.8), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/coretext ↩

  60. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_hig.py, 출력은 measure_hig.out: 저장한 Typography 페이지의 표, 기본 및 최소 크기, xSmall에서 AX5까지 iOS Dynamic Type의 모든 표(Body 14, 15, 16, 17, 19, 21, 23, 28, 33, 40, 47, 53포인트. Caption 1 11, 11, 11, 12, 14, 16, 18, 22, 26, 32, 37, 43). ↩↩↩↩↩

  61. Unicode 컨소시엄, “Unicode Standard Annex #14: Unicode Line Breaking Algorithm”, 리비전 57, Unicode 18.0.0, 2026-09-01(CJ 클래스와 “Treating characters of class CJ as class NS will give CSS strict line breaking; treating them as class ID will give CSS normal breaking.”), 2026년 10월 5일 가져옴, https://www.unicode.org/reports/tr14/ ↩↩

  62. Stardew Valley Wiki, “Modding:Dialogue”, 2026년 5월 20일 마지막 수정($b, 초상화 명령, Strings\SpeechBubbles), 2026년 10월 5일 가져옴, https://stardewvalleywiki.com/Modding:Dialogue ↩↩↩

  63. Stardew Valley Wiki, “Modding:Custom languages”, 2026년 1월 3일 마지막 수정(UseLatinFont, .fnt 파일, 권장 기준값 1.5인 FontPixelZoom, FontApplyYOffset), 2026년 10월 5일 가져옴, https://stardewvalleywiki.com/Modding:Custom_languages ↩↩

  64. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_steam_languages.py, 출력은 measure_steam_languages.out. 저장한 Steam 상점 페이지의 언어 표를 읽음: Stardew Valley(https://store.steampowered.com/app/413150/), Celeste(https://store.steampowered.com/app/504230/), Sea of Stars(https://store.steampowered.com/app/1244090/), Eastward(https://store.steampowered.com/app/977880/), 2026년 10월 5일 가져옴. ↩↩↩↩↩

  65. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_apple_docs.py, 출력은 measure_apple_docs.out: 10절에서 인용한, 저장한 Apple 문서 각 페이지의 제목, 선언, 개요, 플랫폼, 첫 문단. ↩↩

  66. Apple, Human Interface Guidelines, “Designing for games”(“To keep text comfortably legible on each device, ensure that it contrasts well with the background and uses at least the recommended minimum text size in each platform.”), 2026년 10월 5일 가져옴, https://developer.apple.com/design/human-interface-guidelines/designing-for-games ↩

  67. Apple Developer Documentation, “UIFontMetrics”(“A utility object for obtaining custom fonts that scale to support Dynamic Type”. iOS 11.0), “scaledFont(for:)”, “scaledFont(for:maximumPointSize:)”, “scaledValue(for:)”(“Scales an arbitrary layout value based on the current Dynamic Type settings”. iOS 11.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/uikit/uifontmetrics ↩↩↩

  68. Apple Developer Documentation, “custom(:size:relativeTo:)”(“that scales relative to the given textStyle” 커스텀 폰트. iOS 14.0), https://developer.apple.com/documentation/swiftui/font/custom(:size:relativeto:), 그리고 “custom(:fixedSize:)”(“that does not scale with Dynamic Type” 커스텀 폰트. iOS 14.0), https://developer.apple.com/documentation/swiftui/font/custom(:fixedsize:), 2026년 10월 5일 가져옴. ↩↩↩↩↩

  69. Apple Developer Documentation, “ScaledMetric”(“A dynamic property that scales a numeric value”. iOS 14.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/scaledmetric ↩↩

  70. Apple Developer Documentation, “dynamicTypeSize(:)”(iOS 15.0. “When limiting the Dynamic Type size, consider if adding a large content view with accessibilityShowsLargeContentViewer() would be appropriate.”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/view/dynamictypesize(:) ↩↩

  71. Apple Developer Documentation, “accessibilityShowsLargeContentViewer()”(iOS 15.0. “Don’t use the large content viewer as a replacement for proper Dynamic Type support.”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/view/accessibilityshowslargecontentviewer() ↩↩

  72. Apple Developer Documentation, “UIAppFonts”(“App-specific font files located in the bundle and that the system loads at runtime”. iOS 3.2), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/bundleresources/information-property-list/uiappfonts ↩↩

  73. Apple Developer Documentation, “CTFontManagerRegisterFontsForURL(:::)”(iOS 4.1), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/coretext/ctfontmanagerregisterfontsforurl(:::) ↩

  74. Apple Developer Documentation, “interpolation(:)”(Image. iOS 13.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/image/interpolation(:) ↩

  75. Apple Developer Documentation, “resizable(capInsets:resizingMode:)”(“Sets the mode by which SwiftUI resizes an image to fit its space”. iOS 13.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/image/resizable(capinsets:resizingmode:) ↩

  76. Apple Developer Documentation, “baselineOffset(:)”(Text. “Sets the vertical offset for the text relative to its baseline”. iOS 13.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/text/baselineoffset(:) ↩

  77. Apple Developer Documentation, “typesettingLanguage(:isEnabled:)”(iOS 17.0. 기기 UI 언어와 맞지 않는 언어의 텍스트에 대한 대목. “this language does not affect text localization.”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/view/typesettinglanguage(:isenabled:) ↩↩

  78. Apple Developer Documentation, “NSParagraphStyle.LineBreakStrategy”(iOS 9.0)와 그 프로퍼티 standard(iOS 14.0), pushOut(iOS 9.0), hangulWordPriority(iOS 14.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/uikit/nsparagraphstyle/linebreakstrategy-swift.struct ↩

  79. Apple Developer Documentation, “accessibilityValue(:)”(“Use this method to describe the value represented by a view, but only if that’s different than the view’s label.”. LocalizedStringResource 형태로 iOS 16.0), https://developer.apple.com/documentation/swiftui/view/accessibilityvalue(:), 그리고 “accessibilityLabel(:)”(iOS 16.0), https://developer.apple.com/documentation/swiftui/view/accessibilitylabel(:), 2026년 10월 5일 가져옴. ↩↩↩

  80. Apple Developer Documentation, “accessibilityDirectTouch(:options:)”(iOS 17.0. “Direct touch areas passthrough touch events to the app rather than being handled through an assistive technology, such as VoiceOver.”. 영역은 먼저 활성화해야 함), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/view/accessibilitydirecttouch(:options:) ↩↩

  81. Apple Developer Documentation, “updatesFrequently”(AccessibilityTraits. iOS 13.0. “Use this trait when you want an assistive technology to poll for changes when it needs updated information.”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/accessibilitytraits/updatesfrequently ↩

  82. Apple Developer Documentation, “AccessibilityNotification.Announcement”(iOS 17.0. accessibilitySpeechAnnouncementPriority 속성), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/accessibility/accessibilitynotification/announcement ↩↩

  83. Apple Developer Documentation, “accessibilityReduceMotion”(iOS 13.0. “If this property’s value is true, UI should avoid large animations”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/swiftui/environmentvalues/accessibilityreducemotion ↩↩

  84. Apple Developer Documentation, “AccessibilityComponent”(RealityKit. “A component that stores accessibility information for an entity”. 이름은 “VoiceOver says aloud when it describes the entity”. iOS 17.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/realitykit/accessibilitycomponent ↩↩↩↩

  85. Apple Developer Documentation, “Improving the Accessibility of RealityKit Apps”(“set the properties on each visible Entity in your scene”), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/realitykit/improving-the-accessibility-of-realitykit-apps ↩

  86. Apple Developer Documentation, “RealityView”(iOS 18.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/realitykit/realityview ↩

  87. Apple Developer Documentation, “XCUIVoiceOverService”(“Provides programmatic control of VoiceOver for UI testing”. iOS 27.0), 2026년 10월 5일 가져옴, https://developer.apple.com/documentation/xcuiautomation/xcuivoiceoverservice ↩↩↩

  88. 941Kit 소스, Sources/Kit941/Tokens/Typography/FontFamily.swift, 커밋 50bc657(2026년 9월 28일), 21번째에서 63번째 줄(case custom(named: String), display, title, body, label, caption에 대해 고유 크기 34, 22, 17, 13, 12포인트로 Font.custom(_:size:relativeTo:)를 통해 렌더링), 2026년 10월 5일 열람. 비공개 저장소. ↩↩

  89. Kiradex 소스, server/app/names.py(7번째에서 14번째 줄, 각각 20단어인 FIRSTS와 SECONDS, 대문자로 시작하는 두 단어와 네 자리 숫자의 핸들 패턴), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). ↩↩

  90. Blake Crosley, “A Pixel Town Alive When Nobody Is Online: NPCs and Ghosts”, blakecrosley.com, 2026년 10월 5일(프리셋 대사 23줄, 그중 플레이어용 14줄과 마을 수집가용 9줄, 그리고 플레이어당 2.0초에 한 줄), https://blakecrosley.com/blog/pixel-art-town-alive-when-nobody-is-online ↩↩

  91. Kiradex, docs/WORLD.md, 5절 “The stack”, 524번째와 525번째 줄(속도 제한: “eight moves a second, a line every two seconds”), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). ↩↩

  92. Kiradex 소스, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): Kiradex/World/PlazaStage.swift, 199번째에서 203번째 줄(무대의 .gesture(DragGesture(minimumDistance: 0)), 바뀔 때 touched를, 끝날 때 lifted를 부름), 329번째에서 359번째 줄(touched(_:in:): 썸스틱이 없으면 손가락 아래 땅으로 곧바로 걸어가고 손가락을 따라 타일을 건너감. 수집가나 키오스크 타일 위의 터치는 거기서 돌아와 떼기를 기다림), 364번째에서 380번째 줄(lifted(_:in:): 걷거나 조종하지 않은 터치는 탭. 다른 수집가 위면 tapped를 설정하고, 키오스크 타일 위면 onKiosk를 부르며, 그 밖에는 rig.walk(to: tile)을 실행), 31번째와 32번째 줄(onKiosk, “The kiosk tapped: the town’s board.”), 82번째 줄(stickKey, "world.stick"), 테스트 실행 인자 -plazaFacingDemo(255번째에서 270번째 줄의 .task), -plazaTell을 읽는 PlazaView의 tellOnArrival(515번째에서 522번째 줄). KiradexUITests/KiradexUITests.swift의 384번째와 387번째 줄, 걷기 테스트가 무대 프레임의 비율 위치를 탭하는 곳. ↩↩↩↩

  93. Kiradex 소스, Kiradex/World/PlazaRig.swift, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): 432번째 줄의 fit(viewSize:displayScale:tilesTall:fitsWhole:)(pixelScale은 뷰의 픽셀 높이를 tilesTall × TileMap.tileSize, 20 × 16 = 320으로 나눈 값의 내림, 439번째 줄. TileMap.tileSize는 TileMap.swift 60번째 줄에서 16)와 479번째 줄의 headPoint(of:)(걷는 이의 위치에 시트 프레임 높이의 절반을 더하고 4를 더함, 482번째와 483번째 줄). 12시 42분에는 파일이 또 바뀌어 있었지만, 두 함수는 그때도 그대로였음. ↩↩

  94. Kiradex 소스, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소): Kiradex/World/PlazaStage.swift 311번째 줄(무대의 .ignoresSafeArea(). 그 GeometryReader, 121번째에서 294번째 줄이 말풍선 오버레이를 담음). 파일 어디에도 안전 영역 인셋을 읽는 곳이 없음. Kiradex/World/WorldHUD.swift 26번째와 27번째 줄(세계의 루트인 WorldView의 .statusBarHidden()과 .persistentSystemOverlays(.hidden))과 40번째에서 42번째 줄(크롬은 “over its stage and inside the safe area, which on the Duo keeps it clear of the cameras and the side rail”). 그리고 Kiradex/Views/Card/CardViewer.swift 19번째에서 24번째 줄, 카드 무대 밖의 GeometryReader가 안전 영역을 무시하는 CardStage에 proxy.safeAreaInsets를 넘겨, “the card rests inside the safe area, clear of the Duo’s outer camera and side rail”이 되는 곳으로, 56번째와 57번째 줄에 같은 두 수정자가 있음. Kiradex/에서 safeAreaInsets를 검색하면 CardViewer의 그 줄만 나옴. ↩↩↩↩↩

  95. Blake Crosley, “Xcode 27.1 Beta: Your App in the iPhone Duo Simulator”, blakecrosley.com, 2026년 9월 21일, 2026년 10월 2일 업데이트(iPhone Duo 시뮬레이터의 바깥쪽과 안쪽 디스플레이에서 앱 실행. 닫힌 상태, 27.1 SDK에 링크, 466×678포인트 창에서 내비게이션 스택 안의 콘텐츠는 382×562포인트, 안전 영역 인셋은 위 82, 아래 34, 뒤쪽 84로 레일이며, 그쪽 가장자리에 도구 막대와 탭 막대가 있고 상태 막대를 보인 프로브), https://blakecrosley.com/blog/xcode-27-1-beta-iphone-duo-simulator ↩↩↩↩

  96. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_figure_width.py, 출력은 measure_figure_width.out. 태평양 시간 12시 36분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 Kiradex에 대해 다시 실행했으며, 출력은 그대로. 입력은 Kiradex에서 읽기만 함: Kiradex/Resources/World/characters.json("cell": [32, 40], 프레임과 방향), scripts/forge/rig.py(53번째 줄, CELL_W, CELL_H = 32, 40), 몸체 시트 fig-body.png(1,376×160픽셀, 43프레임 × 4방향). 서 있는 프레임 idle_a에서 인물은 모든 방향에서 키 30텍셀, 가장 넓은 행의 너비는 아래나 위를 볼 때 16, 왼쪽이나 오른쪽을 볼 때 13. ↩↩↩

  97. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 measure_ios27_iphones.py, 출력은 measure_ios27_iphones.out. 태평양 시간 12시 36분에 다시 실행했으며, 출력은 그대로. 저자의 Mac에 Xcode 27.0과 함께 설치된 iOS 27.0 시뮬레이터 런타임(24A434)에 대해 xcrun simctl list runtimes -j를 읽고, 각 iPhone 기기 유형의 capabilities.plist(main-screen-width, main-screen-height, main-screen-scale)를 읽음: iPhone 33종, iPhone 12 mini와 13 mini(3배에서 1,080×2,340픽셀, 360×780포인트)부터 iPhone 16, 17, 18 Pro Max(440×956포인트)까지이며, 그중 세 종이 2배로 iPhone SE(2세대와 3세대, 375×667포인트)와 iPhone 11(414×896). 이것은 시뮬레이터의 기기 목록이지 Apple의 호환성 페이지가 아니며, 그 페이지는 가져오지 않음. 커밋 334fbfe의 Kiradex 프로젝트 파일에서도 읽기만 함: 앱 타깃의 두 구성 모두의 TARGETED_DEVICE_FAMILY = 1(iPhone)(12시 28분 읽기에서 1559번째와 1705번째 줄)과 프로젝트의 두 구성의 IPHONEOS_DEPLOYMENT_TARGET = 27.0(1608번째와 1670번째 줄). 앱 타깃은 이를 덮어쓰지 않음. ↩↩↩↩↩↩↩

  98. 저자의 측정, 2026년 10월 5일: 이 글을 위한 저자의 증거 폴더에 있는 probe_line_start_glyphs.swift, swiftc -O로 컴파일해 iOS가 아니라 macOS 27.0(빌드 26A428)에서 실행, 출력은 probe_line_start_glyphs.out. 저장한 명세의 부록 표에서 JLREQ 3.1.7의 행두 클래스 열 개를 읽음(항목 83개: cl-02 16, cl-03 4, cl-04 6, cl-05 3, cl-06 2, cl-07 2, cl-09 6, cl-10 1, cl-11 40, cl-29 3). PixelMplus10 Regular가 어느 것을 매핑하는지 Core Text에 물음(CTFontGetGlyphsForCharacters, 대체 없음): 54개. 매핑하지 않은 것은 cl-03의 〜 U+301C, ゠ U+30A0, en 대시, cl-02의 ⦆, 〙, 〗, cl-04의 ‼ ⁇ ⁈ ⁉, cl-09의 〻, cl-11의 작은 가나 18자(ゕ, ゖ, U+31F0에서 U+31FF). 서체가 매핑하는 코드 포인트 7,223개를 모두 10픽셀로, 안티앨리어싱과 폰트 스무딩을 끄고 그려, 픽셀 단위로 같은 셀끼리 묶음: 54개 중 ‐ U+2010은 U+2011과, : U+003A는 U+A789와 일치하며, 다른 것에는 쌍둥이가 없음. 50픽셀(m = 5)에서 안티앨리어싱을 켜고 스무딩을 끄면, ー U+30FC는 왼쪽 끝에 갈고리가 있는 모양으로 잉크 기기 픽셀 200개, 一 U+4E00는 250개로, 같지 않으며 둘 다 중간 톤 픽셀은 0. 출력 가능한 ASCII 문자는 모두 어드밴스 5폰트 픽셀. 10월 5일 나중에 확장(이전 스크립트와 출력은 그 옆에 보관하고 출력의 이전 줄은 모두 그대로): Halfwidth and Fullwidth Forms 블록, U+FF01에서 U+FFEF의 문자 중 호환 정규화(NFKC) 결과가 목록의 문자 하나인 24자(! ) , . : ; ? ] } ⦆ 。 」 、 ・ ァ ィ ゥ ェ ォ ャ ュ ョ ッ ー)와 저자가 더한 ~ U+FF5E: 서체는 25개 중 ⦆ U+FF60을 뺀 24개를 매핑하며, 전각은 너비 10픽셀, 반각은 5이고, 24개 중 어느 것도 서체의 코드 포인트 가운데 픽셀 단위로 같은 쌍둥이를 갖지 않음. 따라서 비교 집합은 75자: 54개 항목은 51자(), 〕, ]가 cl-02와 cl-29 양쪽에 실려 있으므로)이고, 24개 형태를 더해 75(프로브 자신의 마지막 줄의 54 + 24 = 78은 항목을 센 것). ↩↩↩↩

  99. 저자의 측정, 2026년 10월 15일: 이 글을 위한 저자의 증거 폴더에 있는 measure_cell_masks.py, 출력은 measure_cell_masks.out과 다이제스트 목록 canon_cell_masks.txt. 이 글을 위해 저장한 pret 파일(pokeemerald의 graphics/fonts 시트 17장과 graphics/text_window 21장, pokecrystal의 gfx/frames 10장, pokecrystal과 pokered의 gfx/font/font.png)에 대해 실행. 각 시트를 RGBA로 디코딩하고, 시트의 왼쪽 위 픽셀과 다른 픽셀을 켜며, 셀은 왼쪽 위 모서리부터 8×8, 빈 셀과 꽉 찬 셀은 빼고, 마스크의 다이제스트는 여덟 행 바이트의 SHA-256(가장 왼쪽 픽셀이 최상위 비트). japanese_normal.png, 128×512픽셀: 셀 1,024개, 빈 셀 430개, 남은 것 594개, 서로 다른 것 478개. 시트 50장 전체: 남은 셀 7,746개, 서로 다른 다이제스트 1,484개. ↩↩

  100. Blake Crosley, “Pixel-Art People: Characters and a Creator on iPhone”, blakecrosley.com, 2026년 10월 3일, 2026년 10월 5일 열람: 빌드 21에서 33의 인물에 대해 “The cell is 32 by 32; the figure is 26 tall to the crown of the head, 27 with most hair (28 with buns, spikes or the puff), and about 14 wide at the shoulders”, 빌드 34에 대한 업데이트에서 “The cell is 32 by 40 now, with eight rows of headroom and two under the feet, and the figure 30 pixels”, 그리고 말풍선과 3D 카드를 위한 매니페스트의 머리와 손 앵커에 대해 “though both still take their place from the cell’s height for now”, https://blakecrosley.com/blog/pixel-art-characters-on-iphone ↩↩

  101. 커밋 fc9d4876(refs/heads/main)의 Kiradex 작업 트리에 대한 저자의 읽기, 2026년 10월 15일 태평양 시간 22시 29분: Kiradex/World/PlazaStage.swift(그날 17시 13분에 수정), 373번째 줄(.modifier(StageAccessibility(covered: covered, trace: stepTrace))), 520번째 줄(private struct StageAccessibility: ViewModifier), 511번째에서 513번째 줄(주석: 무대는 하나의 손쉬운 사용 요소로, “The plaza”와 플레이어의 마지막 걸음을 값으로 가지며, 장소가 덮고 있는 동안에는 없음), 525번째 줄(content.accessibilityRepresentation {), 526번째 줄(if !covered {), 533번째 줄(.accessibilityLabel("The plaza")), 534번째 줄(.accessibilityValue(trace)). 이 글의 다른 Kiradex 읽기는 10월 5일의 것이며 날짜를 적었음. ↩

  102. Kiradex 소스, server/app/npcs.py, 2026년 10월 5일 태평양 시간 12시 33분에, 커밋 334fbfe에서 읽음(파일 날짜 10월 3일. 비공개 저장소): DEFINITIONS의 마을 수집가 넷(36번째에서 58번째 줄), Moss Keeper 1999, Prism Scout 2024, Comet Pocket 3030, Dawn Sprout 4040으로, 그 대사는 LINES의 인덱스. 각자 8에서 20초 뒤에 첫 대사를 하고(77번째 줄), 그 뒤로는 20에서 40초마다 한 줄(108번째 줄). server/app/main.py 231번째 줄은 플레이어의 대사를 그 플레이어를 포함한 방 전체에 보냄(broadcast의 except_id는 설정되지 않음. rooms.py 182번째 줄), 12시 33분에 읽음. ↩↩

  103. Kiradex 소스, KiradexUITests/KiradexUITests.swift, 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). 파일 날짜는 10시 32분이며 읽는 동안 바뀌지 않음: 228번째 줄(Me 탭의 me-handle이 핸들, 두 단어와 숫자를 담음), 432번째 줄부터의 testTheCollectorFacesTheWayItWalks(방향마다 무대의 값을 읽음), 502번째 줄부터의 testThePlazaKeepsTime(plaza-status를 읽고 그 접두사를 확인, 509번째 줄), 523번째 줄부터의 testTheChimneysSmoke(-airProbe로 실행해 무대의 value CONTAINS 'smoke 18 '를 기다림, 531번째 줄), room-status의 첫 단어를 기다리는 testVisitingARoomIsAVisit(469번째에서 472번째 줄)와 testYourRoomHasYourBinders(604번째에서 607번째 줄), hall-status를 기다리는 testTheShowHallHasEveryonesCards(490번째에서 494번째 줄), plaza-status에서 “here”가 다섯 이상이 되기를 기다리는 testTheTownHasCollectors(672번째에서 676번째 줄), 그리고 689번째 줄부터의 testThePlazaHasCompany(KIRADEX_PLAZA=1, 즉 서버가 없으면 건너뛰며, 건너뛰기 메시지로는 다른 플레이어도 필요함, 690번째와 691번째 줄. plaza-status에서 사람 수를 기다리고, Say 메뉴에서 “Hi!”를 골라, 말풍선 Said: Hi!를 5초 기다림, 714번째 줄). ↩↩↩↩

  104. Kiradex 소스, server/requirements.txt(fastapi, uvicorn[standard], websockets, httpx, pytest, 폰트 라이브러리 없음)와 server/tests/(test_rooms.py), 2026년 10월 5일 태평양 시간 12시 28분에, 커밋 334fbfe에 그날의 커밋되지 않은 편집이 더해진 상태로, 그날 편집 중이던 작업 트리에서 읽음(비공개 저장소). ↩

  105. Blake Crosley, “Pixel-Art Routes: Map Connections and Districts on iPhone”, blakecrosley.com, 2026년 10월 5일, 7절, 검사 13(모든 지역 이름, 간판, 문자열이 통과해야 하는, 저장소 안의 거부 목록과 그 테스트), https://blakecrosley.com/blog/pixel-art-world-beyond-the-plaza ↩

관련 게시물

픽셀 아트 인물: iPhone의 캐릭터와 크리에이터

뛰어난 픽셀 아트 게임은 인물을 어떻게 만들고 입히는지 실측하고, Kiradex의 수집가, 옷장, 크리에이터 화면을 iPhone에서 어떻게 다시 만들었는지 정리했습니다.

68 분 소요

픽셀 아트 건물: iPhone에서 짓는 집, 홀, 실내

Pokémon, SNES RPG, 스타듀 밸리가 집과 실내와 층을 어떻게 짓는지 소스에서 측정하고, Kiradex의 iPhone 마을에 진짜 건물이 들어서기까지를 정리했습니다.

64 분 소요

App Review는 무엇을 보는가, 알려진 범위 안에서: 2026년 10월

2026년 10월 기준 Apple의 App Review를 공개 기록으로 읽습니다. 누가 심사하고, 얼마나 걸리고, 무엇이 거절을 부르며 그 문구는 어떻게 쓰여 있는지, 2025~2026년의 규칙 변경, 그리고 첫 제…

45 분 소요