ピクセルアートのインターフェース:テキストボックス、フォント、日本語
『ポケットモンスター エメラルド』の会話ボックスは横30×縦6タイルの枠で、画面の幅240ピクセルいっぱいと高さの30%を占め、その内側の27×4タイルのテキストウィンドウに16ピクセルのフォントで2行が入ります。『ポケットモンスター クリスタルバージョン』のボックスは20×6タイルで、18文字の行が2行です。123 エメラルドは速さの設定に応じて8、4、1フレームごとに1文字を表示し、60ヘルツでは毎秒7.5、15、60文字になります。クリスタルの最速の設定と『ファイナルファンタジーVI』も1フレームに1文字です。14 エメラルドのライターはすべての行を手で改行しました。スクリプトの24,100行のうち最も幅のある行は216ピクセルのウィンドウに対して208ピクセルで、はみ出す行はひとつもありません。1 標準の文字集合をカバーする日本語のピクセルフォントは、約7,000字を描いています。JIS X 0208は6,879字、Misakiはそのすべてを8×8のセルに描き、PixelMplus10は3つのコードポイントを除くすべてを10ピクセルのグリッドに描いています。『ファイナルファンタジー ピクセルリマスター』のプロデューサーは、チームが新しく作った日本語フォントを「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の12段階のBodyサイズのうち40ポイントの1つだけです。710 現在のKiradex Worldは、すべての文字をシステムの等幅書体で組み、英語しか話さず、VoiceOverには歩行テスト用の座標を渡しています。11 そこで、原典のボックス、フォント、速さを逆コンパイルから実測し、日本語5書体とラテン文字2書体のピクセルフォントをCore Textで測り、ブリーフを書きました。両言語に使うライセンス済みの書体1つ、フォントのグリッドに丸めたDynamic Type、フォージが描く枠、2行とページ送り、そしてVoiceOverが説明できるステージです。
TL;DR
- ボックスは画面の下3分の1で、2行です。 エメラルドの会話の枠は30×6タイル(240×48ピクセル、画面の高さの30%)で、その内側に16ピクセルの行が2行入る27×4タイルのウィンドウがあります。クリスタルのボックスは20×6タイルで高さの33%、18文字の行が2行、赤も同じ20×6です。エメラルドのボックスには名前欄がなく、スクリプトの14,412段落のうち934段落(6.5%)は本文の中で話し手の名前を出しています。12312
- 速さはフレーム数です。 エメラルドとファイアレッドは1文字に8、4、1フレームかけ(60ヘルツで毎秒7.5、15、60文字)、表示中にAかBを押したあとは、押し続けているあいだ1フレームに1文字になります。クリスタルと赤の待ちは5、3、1フレーム(毎秒12、20、60文字)です。ファイナルファンタジーVIはフィールドの会話を1フレームに1文字ずつ描き、その速さの設定はありません(6段階のメッセージ速度は、戦闘メッセージを待つ時間です)。エメラルドの送り矢印は静止位置から0、1、2、1ピクセルと9フレームごとに1段ずつ動き、1周期は0.6秒です。クリスタルの▼は枠のタイルと交互に点滅します。1131415164
- ゲームに必要な日本語は、すでに5つの無料フォントが描いています。 MisakiはJIS X 0208の6,879字すべてを8×8で描き、漢字はどれも7×7の内側に収まります。k8x12は8×12で描きます。PixelMplus12とPixelMplus10は、3つのコードポイントを除くすべてを12ピクセルと10ピクセルで描きます。DotGothic16はすべてを描きますが、ピクセルのグリッドには乗っていません。Misaki、k8x12、PixelMplusの3文からなるライセンスは、使用、複製、配布を「either commercially or noncommercially」(商用か非商用かを問わず)認めています。5671718
- ピクセルフォントがくっきり見えるのは、1フォントピクセルが整数のデバイスピクセルになるときだけで、しかもファイルがきれいな場合に限ります。 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の12段階のBodyサイズ(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の文字、うち7文字は全角形です。
jaのタグを付けると、Hiragino Sans、Misaki、PixelMplus、DotGothic16では、7つの幅のうち6つで小書きのかなが行頭に来ました。W3Cの日本語組版処理の要件が述べる、より緩い規則です。721 - Kiradexにとっての意味。 作ったものではなくブリーフです。世界の上に描くすべての文字にPixelMplus10を使います(日本語、きれいなグリッド、整数ピクセルのアドバンスを備えた測定済みの2書体、MisakiとPixelMplus10のうち大きいほう)。サイズは1フォントピクセルが整数のデバイスピクセルになるよう丸めます(3倍のiPhoneの既定サイズで16.67ポイント、2倍では15)。独自にフォージで描いた枠、2行とページ送り、毎秒30文字の表示と段階的に動くマーク。iOS 27.0シミュレータが挙げる最も狭いiPhoneに、左右のインセットがないときに収まる1行の吹き出し。収まらない行はボックスへ回すこと。日本語の行は日本語話者がレビューすること。そしてVoiceOverが座標ではなく言葉で説明するステージ。それぞれに、スクリプト、ログの1行、キャプチャのいずれかで合否を判定できるチェックを付けます。例外は手動のVoiceOverチェック1つだけです。2211
1. エメラルドとファイアレッド:画面幅いっぱいの枠、2行、手作業の改行
このシリーズは世界から始まり、内側へと進んできました。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行、タイプライター表示、2フレームの送り矢印)です。23 その後の記事で、コレクター、建物、動き、町の暮らし、部屋、道ができました。テキストはその一文のまま残っていました。本記事では、その数字がどこから来るのかを測り、世界編の記事が誤っているか不正確な箇所を4つ見つけ、あの記事になかったものを加えます。8、10、12ドットの日本語、Core TextがピクセルTrueTypeフォントに実際に何をするか、Dynamic Typeとピクセルのグリッドをどう両立させるか、日本語の行分割、そしてRealityKitのステージの上のVoiceOverです。
Kiradex Worldのテキストは、今のところ素朴なものです。文字はすべてSwiftUIで、テーマのシステム等幅書体で組まれています。コレクターの頭上の吹き出しは、キットのcaptionロールの太字1行で、クリーム色の紙に2ポイントのインクの縁取りがあり、発言から4秒未満のあいだ表示されます。その場所の説明文は、画面下部のクリーム色のテキストボックスに入っています。アプリは英語のみで、定型文はサーバー上に英語で置かれ、フォージの看板は文字のない12×11の板にインクの走り書きがあるだけです。VoiceOverはステージを1つの要素として読み、その値は歩行テストのステップの記録です。11242526272829 詳細は第10節にあります。第12節のブリーフは、この状態との差分として書いています。
以下のポケモンの数字はすべて、pretの逆コンパイルのシャロークローン、pokeemerald 731ad5b(2026年10月1日)、pokefirered 037335f、pokecrystal 5beda23、pokered d2704a6から、1本のPythonスクリプトmeasure_gen_text.pyで測ったもので、出力はスクリプトの横に保存してあります。ファイナルファンタジーVIの数字は、everything8215/ff6の逆アセンブルに対する2本目のスクリプトによるものです。どちらも数秒で再実行でき、脚注にそれぞれの数字の元になったソースファイルを記しています。3014
フォント
エメラルドはフォントを1つの表、src/text.cのsFontInfosで宣言しており、それぞれに文字の最大幅と高さがピクセル単位で書かれています。スモールフォントは5×12、会話ボックスが使うノーマルフォントは6×16、ショートフォントは6×14で4回宣言されています(本体と3つのコピー)。ナローフォントは5×16、スモールナローフォントは5×8、ボールドフォントは8×8、点字フォントは8×16で行間が8ピクセルです。ほかのフォントの行間はすべて0で、16ピクセルのセルがそのまま1行です。113
ノーマルのラテン文字フォントはプロポーショナルです。ゲームのcharmap.txtを通してsrc/fonts.cのgFontNormalLatinGlyphWidthsを引くと、小文字のaからzは平均5.77ピクセル幅(4から6)、大文字のAからZと数字の0から9はすべて6、スペースとピリオドは3、感嘆符は4です。131 実際の文章では、後で述べるゲームのスクリプト全行で測って、1文字あたり平均5.25ピクセルになります。1
速さはフレーム数
エメラルドの文字の速さは、フレーム単位の待ち時間です。オプション画面の3つの速さは、src/menu.cのsTextSpeedFrameDelaysを通して、8(おそい)、4(ふつう)、1(はやい)の待ちに対応します。src/text.cのAddTextPrinterは、その待ちから1を引いた値をプリンタの速さとして保存します。プリンタは1文字を表示するとカウンタをその保存値に設定し、1フレームに1ずつ減らし、カウンタが0になった次のフレームで次の文字を表示します。つまり1文字にかかるのは待ちそのもの、8、4、1フレームです。60ヘルツでは毎秒7.5、15、60文字です。1213 この数え方は、プリンタの状態機械(src/text.cのAddTextPrinterとRENDER_STATE_HANDLE_CHARのケース)についての私の読みで、スクリプトはこれを実行せずに表明しています。1 ゲームボーイアドバンスはちょうど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に落とします。1フレームに1文字、毎秒60文字で、最速の設定と同じ速さです。131
自動スクロールのテキストには独自の待ちがあります。gTextFlags.autoScrollがオンのとき、プリンタはカウンタを保存した速さではなく3に設定し、1文字に4フレームかけます。ただし記録された対戦は別で、判定はif (!(gBattleTypeFlags & BATTLE_TYPE_RECORDED) && gTextFlags.autoScroll)です。131 戦闘メッセージは通信対戦と記録された対戦で自動スクロールをオンにし、messageautoscrollスクリプトコマンドも自動スクロールをオンにして「ふつう」の速さを強制します。34 そのため3の待ちは、通信対戦でのほかの戦闘ウィンドウと、スクリプトによる自動スクロールのメッセージに当てはまることがあり、記録された対戦では決して当てはまりません。記録された対戦のメッセージは、プリンタに与えられた速さで表示されます。戦闘メッセージであれば記録と一緒に保存された文字の速さですが、記録された通信対戦だけは、通信対戦と同じくメッセージウィンドウに1が与えられます。341 私の読みでは、カウンタが動くのはプリンタの保存した速さが0より大きいあいだだけで、通信対戦のメッセージウィンドウには待ち1が与えられるため、自動スクロールがオンでもそのテキストは1フレームに1文字のままです。1334
3つの改行コードは、それぞれ違うことをします。\nはプリンタを2行目へ下げます。\lは送り矢印の下でボタンが押されるのを待ってから、ボックスを1行上へスクロールします。\pは同じように待ってから、ボックスを消して最上段から始め直します。1335 スクロールでは、16ピクセルの行が速さの設定に応じて1フレームに1、2、4ピクセルずつ上がる(sWindowVerticalScrollSpeeds)ので、1回のスクロールに16、8、4フレームかかります。113
測ったすべてのゲームの文字の速さと、ブリーフが提案する2つの設定。測定結果からfigures/make_figures.pyで描画。
矢印
ページがいっぱいになると、ボックスは小さな矢印の下で待ちます。エメラルドは8×16の矢印のセルを{ 0, 1, 2, 1 }ピクセルの縦オフセット(sDownArrowYCoords)で描き、8にリセットされる待ちが尽きるたびに描き直します。つまり9フレームごとに1段、1周期は36フレーム、60ヘルツで0.60秒です。113 矢印は点滅しません。3つの位置を行き来して、上下に揺れるのです。
ボックスとその枠
エメラルドのフィールドのテキストボックスは、src/menu.cのsStandardTextBox_WindowTemplatesで定義されています。テキストウィンドウはタイルの列2、行15にあり、大きさは27×4タイル、216×32ピクセルで、16ピクセルのノーマルフォントが2行入ります。その枠はWindowFunc_DrawDialogueFrameが描き、スクリプトがそのウィンドウについて評価したところ、列0から29、行14から19に広がっていました。30×6タイル、240×48ピクセルで、画面の幅いっぱい、高さ160ピクセルの30%です。12
この枠はナインスライスではありません。LoadMessageBoxGfxは56×16ピクセル、14タイルの帯であるmessage_box.pngから枠を読み込み、枠は8つのタイル位置から描かれます。下端の行は、上端の行をBG_TILE_V_FLIPで上下反転したものです。1362 スクリプトが測っているのは枠の範囲とPNGの大きさで、反転は塗りつぶしの呼び出しから読み取ったものです。1
プレイヤーが選べる枠は、ほかのウィンドウ、つまりメニューと「はい・いいえ」のボックスのためのものです。WINDOW_FRAMES_COUNTは20で、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」(エメラルドのテキストウィンドウの枠は24×24の画像で、ハードウェアのタイル3×3、つまりナインスライスである)とあります。23 graphics/text_window/にある20枚の24×24の画像は、メニューのウィンドウで選べる枠です。会話ボックス、つまりプレイヤーの記憶に残っているテキストボックスは、56×16ピクセルのmessage_box.pngから、下端を反転した30×6タイルの枠として描かれており、24×24の画像のナインスライスではありません。136 ナインスライスという考え方は有効で、メニューはそれを使っています。会話ボックスに結びつけた点が誤りでした。
2つの会話ボックスをタイルのグリッドとして描いたもので、ゲームのアートは含みません。figures/make_figures.pyで描画し、それぞれの範囲をmeasure_gen_text.outと照合しています。
テキストはボックスに合わせて書かれていた
ボックスがどれだけ埋まっているかを見るため、スクリプトはエメラルドのスクリプトテキストを1行残らず読みました。504ファイル、data/text/*.incの36ファイルと468マップのscripts.incで、プレースホルダーを含まない行が24,100行、含む行が3,000行です。ゲームの3つの改行コード\n、\l、\pで分割しました。行の中央値は29文字、150ピクセル、95パーセンタイルは38文字、196ピクセル、最長は53文字、最大幅は208ピクセルです。216ピクセルのウィンドウより広い行はひとつもなく、平均の文字幅は5.25ピクセルです。137
1文字5.25ピクセルなら、ウィンドウには41文字入ります。ライターたちは95%の行を38文字で止めていました。331 改行はすべて手で置いたコードです。ゲームは行を折り返すことがなく、ライターを信頼しています。
ボックスにはウィンドウが1つだけあり、名前欄はありません。話し手を示したいとき、ライターは行の中に名前とコロンを打ち込みました。ミシロタウンの「MOM: {PLAYER}, we’re here, honey!」のようにです。14,412段落のうち934段落、6.5%が、大文字の名前とコロンで始まっています。137 独立した名前欄がないというのは、ウィンドウのテンプレートについての私の読みです。テンプレートには2行のテキストの場所しかなく、ほかには何もありません。2
英語版の中の日本語
英語版のエメラルドにも、日本のカートリッジから来たポケモンやトレーナーの名前のために、日本語のグリフが入っています。プリンタは制御コード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セル。そしてファイアレッドのjapanese_frlg_male.pngはエメラルドのツリーにもあり、256×512ピクセルで16×16のセルに分かれ、512セルのうち269セルが描かれ、幅の表は512項目で0から10ピクセル、うち408項目が10です。138 pretがビルドしているのは英語版のゲームなので、これらは英語版ROMで名前に使うシートです。日本語版ROM自体のボックス、速さ、フォントは読んでいません(第11節)。30
つまり原典自身の尺度で言えば、16ピクセルの行の中で日本語の1文字は、エメラルドでは幅8ピクセル、ファイアレッドのシートではおおむね10ピクセルで、ラテン文字の4から6ピクセルに対してこの幅です。1
ファイアレッド
ファイアレッドはエメラルドの待ち、8、4、1を受け継いでおり、エメラルドと同じく、src/text_printer.cの独自のAddTextPrinterが1を引いて保存します。標準のテキストウィンドウは列2にある26×4タイル(src/new_menu_helpers.c)で、エメラルドより1タイル狭くなっています。139 プリンタにはエメラルドと同じ押し続けの処理もあります。if (JOY_HELD(A_BUTTON | B_BUTTON) && subStruct->hasPrintBeenSpedUp)でカウンタを0に落とし、文字の待ちをカウントダウンしているあいだにAかBを新たに押すと、それを許可しているメッセージではhasPrintBeenSpedUpが立ちます。14
2. クリスタルと赤:18文字と点滅するマーク
ゲームボーイのボックスは、ゲームボーイアドバンスのボックスを低い解像度にした祖先です。クリスタルの画面は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のフォントなら1行に18文字です。テキストはボックスの2行目から始まり(TEXTBOX_INNERY = TEXTBOX_Y + 2)、行と行は2行ずつ離れているので、ボックスには間に空白の行を挟んで2行が表示されます。2行のレイアウトというのは、これらの定数についての私の読みです。13 赤のメッセージボックスはdb MESSAGE_BOX, 0, 12, 19, 17で、下部の同じ20×6タイルです。112
この2本も速さをフレームで数えます。待ちはTEXT_DELAY_FASTが1、中間が3、遅いが5フレームで1文字、60ヘルツで毎秒60、20、12文字です。クリスタルのPrintLetterDelayははっきりこう書いています。「The text speed setting in wOptions is actually a frame count」(wOptionsの文字の速さの設定は、実はフレーム数である)。AかBを押し続けているあいだは、1文字に1フレーム待ちます。115 つまりクリスタルの最速はエメラルドの最速と同じ毎秒60文字で、エメラルドでAかBを押し続けたときも同じ速さです。クリスタルの中間と遅い設定、20と12は、エメラルドの15と7.5より速くなっています。1 Pan Docsはゲームボーイのフレームを「One frame: 70224 dots @ 59.7 fps」(1フレーム:70224ドット、59.7fps)とし、1フレームが「takes ~16.74 ms instead of ~16.67」(約16.67ミリ秒ではなく約16.74ミリ秒かかる)と注記しています。40
クリスタルでは、プレイヤーがFRAME_1からFRAME_8までの8種類の枠から選べます。どれもTEXTBOX_FRAME_TILES、1ビットのタイル6枚で、24×16ピクセルのPNGとして保存されています。9番目の枠もROMにありますが、使われていません。フォントのシートgfx/font/font.pngは128×64ピクセル、8×8のセルが128個で、赤のものも同じ大きさです。141
マークは点滅する
クリスタルの送りマークは枠の一部です。WaitPressAorB_BlinkCursorがAかBを待つあいだ、BlinkCursorは枠自身の最下行にある(18, 17)のタイルを、'▼'と枠のタイル'─'とで入れ替えます。16 2つの状態はコードから読み取りましたが、点滅の周期は導いていません(第11節)。
これが世界編の記事の2つ目の不正確な点です。その記事のテキストの計画は「a two-frame advance arrow」(2フレームの送り矢印)を求めています。23 2状態のマークはクリスタルの点滅です。その計画のほかの部分(16ピクセルの行、下部のボックス)が従っているエメラルドの矢印は点滅しません。静止位置から0、1、2、1ピクセルと3つの位置を36フレームの周期で動きます(コードはビットマップの読み出し元の行をずらしており、私の読みでは画面上でくさびが持ち上がりますが、実行はしていません)。113 第12節のブリーフは、エメラルドの段の刻み方には従い、向きには従いません。
3. ファイナルファンタジーVI:塗り替えられるウィンドウ、折り返すフォント
ファイナルファンタジーVIは、ウィンドウをプレイヤーのものとして扱います。ここでの数字は、everything8215/ff6の逆アセンブル、8130132(2026年9月14日)によるものです。これはROMのグラフィックではなくコードと抽出リストを収めているので、大きさはバイトの範囲と、それを読むコードから求めています。304
8つのウィンドウ、それぞれ7色
英語版と日本語版の抽出リストはどちらも、ウィンドウのグラフィックを0xED0000から0xED1BFFの7,168バイトとしています。896バイトのスタイルが8つで、それぞれ1ピクセル4ビットの8×8タイル28枚です。パレットは0xED1C00から0xED1CFFに続き、16色のパレットが8つです。442 コンフィグ画面には8つのウィンドウパターンが並びます(ConfigWindowNumPos)。443
ウィンドウの色は、プレイヤーが決めるものです。LoadWindowPalはパターンの色を$1d57のセーブメモリから1パターン14バイトずつ、つまり2バイトの色を7色読み、コンフィグ画面はそれをR、G、Bのバーで編集します(ConfigColorBarTextList)。フォント用のもう1色は$1d55にあります。443 刻み幅はコードから、3つのチャンネルはバーのリストから読み取りました。4
コンフィグ画面のメッセージ速度は1から6の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バイトで、3つのチャンネルすべてに強度0から7の固定色を与えます。最初の並びは72個の値で、7から始まり、0まで下がり、また7まで上がります。445 ウィンドウは画面の上か下の近くに開きます。event.asmのコメントでは「dialog window y position (1 or 18)」(会話ウィンドウのy位置、1か18)です。45 この表を1走査線に1値ずつ消費するというのはコメントから読んだもので、追跡はしていません。グラデーションがどう見えるかも、逆アセンブルにピクセルがないので見ていません(第11節)。
フォント
会話のフォントは1ピクセル1ビット、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
1行、折り返し、1フレームに1文字
会話の1行はx 4から最大x $e0、224までで、220ピクセル、12ピクセルの日本語なら18文字です。446 英語のテキストはコードで単語ごとに折り返します。CalcTextWidthが次の単語の幅を足し、最大を超えそうならNewLineを呼ぶので、英語のスクリプトに手で置いた改行は要りません。スクリプトは呼び出しを表明しており、読み取りはUpdateDlgTextOneLineのものです。446
テキストは1フレームに1文字ずつ表示されます。フィールドのループは垂直ブランクを待ち、1周に1回UpdateDlgTextを呼び、UpdateDlgTextOneLineは1文字を描いて戻ります。60ヘルツで毎秒60文字です。448 1フレームに1周というのは、このループについての私の読みです。1つのソースから両方のROMをビルドしているので、2つの言語はこのループを共有しており、私の読みでは、英語と日本語で毎秒の文字数は同じです。48
マップのタイトルは中央揃え(($e0 - width) / 2)で、100フレーム、1.67秒表示されます。446
エメラルドと並べると、ファイナルファンタジーVIは同じボックスのもう1つの作り方を示しています。エメラルドはすべての改行を手で書き、プレイヤーに選ばせる枠はメニューのものだけで、会話ボックスの枠は選ばせません。ファイナルファンタジーVIはコードで折り返し、プレイヤーにウィンドウを塗り替えさせ、ハードウェアで陰影をつけます。エメラルドのフィールドのボックスは下部に、ファイナルファンタジーVIのものは上か下にあり、どちらも決まったフレーム数で表示します。
クロノ・トリガー
クロノ・トリガーもファイナルファンタジーVIと並べるつもりでしたが、できませんでした。公開の逆アセンブルはクローンしておらず、Data CrystalにあるこのゲームのROMマップは取得時にHTTP 403を返したので、本記事のテキストボックスに関する記述はどれもそこから来ていません(第11節)。49
4. 8、10、12ドットの日本語
Kiradexはカードコレクターのためのアプリで、デックスではすでに日本語のカード名を、下に英語を添えてシステムフォントで表示しています。50 世界のテキストにも日本語が必要になるので、日本語を描けないピクセルフォントは候補になりません。問題は、漢字をどこまで小さくできるか、そして7,000字をすでに描いた人がいるかどうかです。
仕事の大きさ
JIS X 0208は日本語のピクセルフォントのカバー範囲を測る基準になる文字集合で、Unicodeコンソーシアムの対応表では6,879字です。非漢字が524字で、そのうち169字が4区と5区のかな、第1水準漢字が16区から47区の2,965字、第2水準漢字が48区から84区の3,390字です。576 この表はバージョン2.0で、コンソーシアムは廃止された対応表の中に置いていますが、フォントのカバー範囲を数えるには今でも最も扱いやすいリストです。5
Misaki:8×8
門真なむ(Num Kadoma)によるMisaki(美咲フォント)は、最も小さいフォントです。そのページは「8×8 ドットの日本語ビットマップフォント」で「JIS 第一・第二水準」に対応すると述べ、「一部の記号を除いた全ての文字は 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の2水準の漢字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 ドットの日本語ビットマップフォント」で、JIS第1・第2水準です。変種が2つあり、かなを縦長に描いたk8x12Lと、非漢字の高さを8ドットにしたk8x12Sです。後者はページによれば昔のゲーム機の制約を意識したもので、「8×8 ドット単位でグラフィックを扱うような、昔のゲーム機等の制限を意識しています」とあります。52
BDFから測ると、k8x12は8×12のセルでアセント10、ディセント2、7,170字で、4ピクセルが168字、8ピクセルが7,002字です。セルを埋める漢字はなく、セルを埋める全角グリフはk8x12とk8x12Lで53個、k8x12Sで45個です。3つともJIS X 0208の6,879字をすべて描いています。6
PixelMplus:10と12、アウトラインとして
itouhiroによるPixelMplus(2013年)は、もう1つのやり方です。ビットマップフォントに見えるTrueTypeのアウトラインで、「埋め込みビットマップは なし」、「JIS第1・第2水準のすべての漢字を収録」し、ISO-8859-1も含みます。レギュラーとボールドの2ウェイト、PixelMplus12とPixelMplus10の2サイズがあり、M+ BITMAP FONTSからアウトライン化されています。18 Core Textで測ると、どれも7,253グリフで、JIS X 0208の6,879字のうち6,876字を描いています。対応していない3つはU+301C 〜、U+2016 ‖、U+2212 −です。7 これらはUnicodeへの対応がコンソーシアムのJIS0208.TXTとほかのベンダーの表とで異なる文字なので、私の読みでは、フォントはおそらく別のコードポイントにそのグリフを持っています。確かめてはいません(第11節)。5
DotGothic16:グリッドのないドットフォント
フォントワークスの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単位の隙間があります。これに合う整数ピクセルのグリッドはなく、プローブは4から40ピクセル毎emのどこにもグリッドを見つけませんでした。197 約62単位のピッチは、書体名の数字どおり1emあたり16ドットに近いので、プローブは名目上の16ドット毎emとその倍数で描いています。これは私が選んだサイズで、書体が宣言するグリッドではありません。macOS 27.0のCore Textは、これを16ピクセルで82.3%、64ピクセルで37.3%中間調に描き、測ったすべてのサイズで中間調が出ました(第6節)。7
無料フォントの実測値
表の「デザイングリッド」は、サンプルのグリフのアウトラインの点の95%が0.04ピクセル以内に乗る、最小の1emあたりピクセル数です。各軸に一定のオフセットを1つ認めています。これを求めるスクリプトmeasure_coretext.swiftはswiftc -Oでコンパイルし、macOS 27.0(ビルド26A428)で実行したので、これらはiPhoneではなくMacのCore Textの数字です。7
| フォント | グリフ数 | ビットマップのテーブル | デザイングリッド(1emあたりピクセル) | ラテン文字/かなのアドバンス(ピクセル) | 描いている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までで、1ピクセルが83.3単位のグリッドなので、ベースラインはグリッドの行の中の0.396ピクセルの位置に来ます。プローブの推定値0.39は、座標の端数を50分の1ピクセル幅のビンに振り分けて求めたもので、以下のレンダリングではこの値だけずらしました。19733
測った日本語対応のフォントでは、どれも日本語の1文字がラテン文字の2倍です。Misakiとk8x12では8対4、PixelMplusでは10対5と12対6です。7 原典も同じ範囲にあります。エメラルドの日本語は16ピクセルの行で幅8ピクセル、ファイアレッドのシートはおおむね10、ファイナルファンタジーVIは12です。14
ライセンス
これらはすべて、きわめて平易な条件で無料です。作者のフォントのページにあるMisakiとk8x12のライセンスは3文です。「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は同じ3文です。18 DotGothic16はSIL Open Font License 1.1で、条件は同梱のOFL.txtにあります。53 私の読みでは、3文のライセンスは見返りを何も求めていません。それでも作者のクレジットを記すのが正しく、ブリーフもそうしています(第12節)。これらは私の読みであり、法的助言ではありません。
読みやすさの代償
Misakiのかなは幅8フォントピクセル、PixelMplus12では12です。iPhone 18 Pro Maxで世界そのものの密度、つまり1テクセルが8デバイスピクセルのとき、Misakiで描いたボックスの1行にはラテン文字36字か日本語18字が入ります。11 日本語の行は1文字あたりの情報量が多いので、同じことを言うのにボックスに同じ文字数は要りません。これは私の読みで、測定ではありません。
5. ラテン文字のピクセルフォントと、測れなかったもの
日本語のフォントと並べて、Google Fontsのラテン文字のピクセルフォントを2つ測りました。30
Press Start 2PはCodeMan38によるもので、SIL Open Font License 1.1、「with Reserved Font Name “Press Start 2P”」(予約フォント名”Press Start 2P”付き)です。54 658グリフをベースライン上の8ピクセルのグリッドに持ち、ラテン文字はすべて幅8ピクセルで、印字可能なASCII 95文字をすべて含みます。かなと漢字はありません。JIS X 0208のうち描いているのは非漢字150字(ギリシャ文字、キリル文字、記号)で、漢字6,355字は1字もありません。7
SilkscreenはJason Kottkeによるもので、SIL Open Font License 1.1、予約フォント名なしです。55 228グリフを8ピクセルのグリッドに持ちますが、サンプルしたアウトライン座標588個のうち10個がグリッドから外れています(Aとoには、125単位のグリッドに対してy値495と130があります)。Aの幅は6ピクセルです。JIS X 0208のうち描いているのは非漢字21字です。719
ピクセルゲームによく勧められるフォントがあと3つありますが、測っていません。記憶で説明するのではなく、測っていないとここに書いておきます。
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を返し、フォントはダウンロードも測定もしていません。ここに書いたことはすべて、その1ページによるものです。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
これが世界編の記事の3つ目の不正確な点です。その記事のテキストの計画は、書体としてm5x7を挙げています。23 ページ自身の説明では、m5x7はラテン文字のフォントで、かなや漢字を持つとはページのどこにも書かれていません。日本語を表示するアプリは、世界のテキストをこれで組むことはできません。57 ブリーフでは書体をPixelMplus10に変えます。日本語、きれいなグリッド、整数ピクセルのアドバンスを備えた測定済みの2書体の1つ(もう1つはMisaki)で、2つのうち大きいほうです(第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ファイルをそれぞれ読み込み、サンプルの1行(ラテン文字のフォントには「Nice card! Which set? 0123」、日本語のフォントには「カードを見せて! Nice card!」)を、各フォントのグリッドの整数倍と非整数倍のサイズで8ビットのグレーのビットマップに描き、インクの載ったピクセルのうち中間調のもの、つまり255のうち8から247で、完全なインクとも空白とも3%以上離れているものを数えます。各サイズを3通りに描きます。Core Textの既定のアンチエイリアス、アンチエイリアスのオフ、そしてサブピクセル位置決めをオンにして量子化をオフにし、グリフがアドバンスの示す位置に正確に置かれるようにしたものです。7
数字の前に、2つの限界が重要です。実行したのはmacOS 27.0、ビルド26A428の私のMacで、iOSではありません。描画もSwiftUIのTextではなく、Core Textでビットマップのコンテキストに描いています。iPhoneが何を描くかは、ブリーフのキャプチャのチェックをiPhoneで実行するまでは予測にすぎません(第11節)。7 そしてスクリプトは、フォントスムージングをオフにして描いています(setAllowsFontSmoothing(false))。下の図をビットマップのコンテキストの既定で描いたところ、PixelMplus10を10ピクセルにした「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
macOS 27.0のCore Textで、1フォントピクセルを1デバイスピクセルとして描いた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 グリッドがベースライン上にある8書体(Misaki 2種、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 にじみはサイズとともに単調に減るわけではありません。8書体のどれでも、グリッドの1.75倍は1.5倍よりにじみます(Misakiは14ピクセルで65.7%、12ピクセルで57.7%)。同じ端数どうしなら減ります。グリッドの1.5、2.5、3.5倍では、8書体すべてが段階ごとに低くなります(Misakiは57.7%、39.9%、30.4%)。整数倍の間で測ったどのレンダリングでも、0にはなりませんでした。私の読みでは、端数を固定すると減るのは、グリフの縁での半ピクセルの誤差が、大きいグリフほど全体に占める割合が小さくなるからです。7
アンチエイリアスをオフにする(setShouldAntialias(false))と、どのサイズでもすべてのピクセルが完全なインクか空白になりますが、形は不均等に再標本化されます。Misakiの8ピクセルでのインクの載った191ピクセルは、10ピクセルでは377になります。各方向に均等に1.25倍すれば約298のはずです。733 2倍になる行や列とならない行や列があり、1ピクセル幅だった線が1ピクセルにも2ピクセルにもなります。
サイズ別の中間調のピクセル。iOSではなくmacOS 27.0のCore Textで、フォントスムージングはオフ。measure_coretext.outからfigures/make_figures.pyで描画。
くっきりしたサイズだけでは足りない:ファイルがきれいでなければならない
フォントを選ぶうえでは、倍率よりも目立たない2つの失敗のほうが重要です。
ベースラインからずれたグリッドは、すべてのサイズでにじみます。 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 私の読みでは、そのときかなは1文字ごとに、前の文字より100分の1ピクセルずつグリッドから遠ざかっていきます。
アウトラインの外れた点は少し残ります。 Silkscreenのグリッド外の座標10個は、8ピクセルで4.5%、32で1.2%の中間調として現れます。7
ドットフォントは決して揃いません。 DotGothic16には倍率をとるべきグリッドがないので、プローブは名目上の16ドット毎emと、その1.25倍から4倍で描いています(第4節)。アンチエイリアスをオン、スムージングをオフにすると、16ピクセルで82.3%、32で52.5%、48で48.8%、64で37.3%が中間調で、16の整数倍を含め、16から64ピクセルまでの9つのサイズすべてで中間調が出ます。ドットがどのピクセルのグリッドにも乗っていないのです(第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倍の画面では、1フォントピクセルが整数のデバイスピクセルになるのは、ポイントサイズの3倍をフォントピクセル単位のemで割った値が整数になるときだけです。12段階のBodyサイズのうち、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
3倍の画面で整数のデバイスピクセルに乗るBodyサイズと、ブリーフの丸め。measure_coretext.outとmeasure_kiradex_text.outからfigures/make_figures.pyで描画。
つまりピクセルフォントは、Dynamic Typeにそのまま従うことはできません。システムのサイズに合わせて連続的に拡大すると、整数になる少数のサイズを除いてグリッドから外れます。10ピクセルのemでは12段階のBodyサイズのうち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の幅に組み、2行目以降の各行の先頭の文字を、独自の行頭禁則文字のリストと照合します。リストに入っているのは、終わり括弧の一部、読点と句点、中点、コロンとセミコロン、疑問符と感嘆符、長音記号、小書きのかな、そして々です。7 このリストはJLREQの規則が挙げるクラスの部分集合ではなく、重なりがあるだけです。40文字のうち7文字、,.:;?!と)は、JLREQがASCIIの文字を挙げているところで日本語の文章が使う全角形で、残りの33文字はJLREQ自身の文字です。終わり括弧からは」』】〉》〕と全角の)を含み、’ ” ] } » 〟 ⦆ 〙 〗とASCIIの)を含みません。小書きのかな40字のうち22字を含み、ゕ、ゖ、ㇰからㇿを含みません。ハイフン類(cl-03)は、‐、〜、゠、enダッシュを含めて入っておらず、cl-04の‼ ⁇ ⁈ ⁉も、々以外の繰返し記号(cl-09)、たとえばヽ、ヾ、ゝ、ゞ、〻も入っていません。721 以下の結果はプローブのリストについてのもので、どの文字が行頭に来うるかは、テスト用の段落の文章が決めます。
タグなしでは、システムの日本語書体であるHiragino Sans(対照として実行)でも、8つのピクセル書体のどれでも、どの幅でも、プローブのリストの文字で始まる行はありませんでした。8書体とはMisaki Gothic、Misaki Gothic 2nd、k8x12、k8x12S、PixelMplus10 RegularとBold、PixelMplus12、そして名目上の16ドットで組んだDotGothic16です。7
kCTLanguageAttributeNameをjaにしてタグを付けると、規則が緩みました。Hiragino Sans、Misaki 2種、PixelMplus10の2種、PixelMplus12、DotGothic16では、7つの幅のうち6つ、18em以外のすべての幅で、小書きのかな(っ、ョ、ァ)で始まる行がありました。7 k8x12とk8x12Sでは、7つの幅のうち4つで小書きのかな(っ、ョ、ょ)で始まる行があり、長音記号ーで始まる行が2つの幅、10em(ョと同じ幅)と18emにあって、18emが5つ目の幅になりました。7
2つの引用に照らしたこれらの数についての私の読みでは、jaのタグはJLREQが述べる緩い規則、UAX #14がCSSのnormalな行分割と呼ぶものを与え、タグなしの実行は厳格に振る舞います。2161 タグは言語を示すものなので、予想とは逆でした。ブリーフがキャプチャなしにボックスのテキストにタグを付けないのは、このためです。
測定そのものについて、スクリプトを出力と突き合わせて読んでわかった注意が2つあります。幅はフォントの全サイズであるemで表しているので、かなが12ピクセルのemの中で8ピクセルのk8x12では、各幅に入るかなは数字が示す数の1.5倍になります。k8x12の段落は8emで12行になり、ほかは17行でした。7 そしてグリッドのないDotGothic16は、名目上の16ドットの2倍、1emあたり32ピクセルで組みました。このサイズではどの幅でもHiragino Sansと同じ行数(8emの17行から20emの7行まで)になり、タグを付けると同じ小書きのかなが行頭に来ました。7
8. その後のピクセルゲーム:ボックス、吹き出し、日本語と中国語
Stardew Valley:2つの話し方
Stardew Valleyは、Kiradexがすでに持っている2つの形をどちらも備えています。会話ボックスはコマンド$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 ボックスは顔を添えた会話のため、吹き出しは歩いて入ったときに町が話しかけてくる言葉のためです。
ラテン文字以外の言語には、ビットマップフォントを使っています。Wikiのカスタム言語のページ(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
ファイナルファンタジー ピクセルリマスター:フォントこそが仕事だった
ピクセルリマスターのコンソール版はピクセルフォントのオプションを追加し、プロデューサーの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
これが世界編の記事の4つ目の不正確な点です。その記事は同じ数字、「about 7,500 Japanese characters」(日本語約7,500字)を挙げ、その作業についてのGoNintendoの記事を出典にしていました。一次資料は、プロデューサー自身がSiliconeraに語ったインタビューで、そこでは「around 7,500 characters」とあります。238 数字は同じで、出典がよくなりました。
7,500字は、通常のカバー範囲の目標であるJIS X 0208の6,879字とおおむね同じ規模です。この比較は私によるものです。533 出荷前にすべての行が確定しているゲームなら、ファイナルファンタジーVIの日本語版ROMが会話用に1,248字で済ませたように(第3節)、もっと少ない字数で済みます。その場合は、すべての文字列をフォントと照合しなければなりません。全集合をカバーするフォントなら、そこから描くどの文字列にもその照合は要りません。4 小さなアプリにとっての教訓は、第4節の無料フォントが反対側から教えるものと同じです。これからも増えていくテキストにとって、日本語のピクセル書体が解決済みの問題になるのは、誰かがすでに7,000字を描き終えていて、ライセンスがそれを出荷することを認めている場合だけです。
現代のピクセルゲームが出荷しているもの
2026年10月5日時点のSteamでは、Stardew Valleyは12言語で、そのうちに日本語、簡体字中国語、韓国語があります。Celesteは9言語で日本語と簡体字中国語を含み、Sea of Starsは11言語で日本語、韓国語、簡体字と繁体字の両方の中国語を含み、Eastwardは7言語で中国語2種と日本語を含みます。64 4本すべてが日本語を出荷しています。
Celeste、Sea of Stars、Eastwardが日本語と中国語のテキストをどう描いているかは、開発者による資料では見つけられなかったので、本記事では言語のリスト以上のことは書きません(第11節)。
Kiradexの2つの話し方
KiradexにはすでにStardewの2つの形が両方あります。画面下部のWorldHUDのボックスと、広場で頭上に出る吹き出しです。2625 欠けているのは、その中に入る原典のタイポグラフィと、VoiceOverがどちらも聞けるようにする方法です。吹き出しのコードにあるコメントが、目指していたものを語っています。「A line said, as the old games drew it: a cream box with a dark border.」(昔のゲームが描いたような、話されたひと言。暗い縁取りのクリーム色の箱)。25
9. 技法を、数字つきのルールとして
測定が裏づけるルールを、それぞれの出どころとともに挙げます。測定した事実を推奨に変えているルールでは、推奨は私のものであり、そのことは動詞が示しています。
| 要素 | ルール | 出典 |
|---|---|---|
| ボックス | ボックスは画面の下3分の1で、2行です。下に置き、下を必要とするゲームなら上へ動かします。 | エメラルドの枠は30×6タイルで幅いっぱい、高さの30%、その内側に16ピクセルの行2行の27×4タイルのウィンドウ。クリスタルは20×6で33%、18文字の行が2行、赤も同じ20×6。ファイナルファンタジーVIのウィンドウは行1か18 |
| ラテン文字のグリフ | 高さ8から16ピクセルのセルに、幅4から12ピクセル。 | エメラルドの6×16のセルで、実際の文章では1文字5.25ピクセル。ファイナルファンタジーVIの16×11のセルに4から12。クリスタルの固定の8×8 |
| 日本語のグリフ | 8から12ピクセルで、同じフォントのラテン文字のおよそ2倍。 | Misakiは8×8でインクは7×7。エメラルドは16の行で幅8。ファイアレッドのシートとPixelMplus10は10。ファイナルファンタジーVIとPixelMplus12は12。測った日本語対応のフォントはどれも8対4、10対5、12対6 |
| 行の長さ | 1行18から41文字。幅を埋めるのではなく、幅に合わせて書きます。 | クリスタルは18。エメラルドの216ピクセルのウィンドウには5.25ピクセルで41文字入り、ライターは95%の行を38で止めた。ファイナルファンタジーVIの220ピクセルの行には12ピクセルの日本語が18文字 |
| 改行 | ライターがすべての行を改行するか、コードが単語ごとに折り返すか。両方は決してしません。 | エメラルドの手で改行した24,100行は、どれも208ピクセル以下。ファイナルファンタジーVIのCalcTextWidthとNewLine |
| 速さ | 文字数を時間で数えます。上限は毎秒60(エメラルドとクリスタルの最速の設定と、ファイナルファンタジーVIの1フレームに1文字)、中間の設定は15から20、遅い設定は7.5から12。 | エメラルドは7.5、15、60、押し続けで60。クリスタルと赤は12、20、60。ファイナルファンタジーVIは60 |
| 送りマーク | マークはゆっくりした時計で動かします。150ミリ秒ごとに1段、1周期は約0.6秒。 | エメラルドの0、1、2、1ピクセル、9フレームごとに1段、1周期36フレーム。クリスタルの▼は枠のタイルと交互に点滅 |
| 枠 | 枠は絵ではなくタイルです。角、辺、中央を繰り返します。プレイヤーにいくつかから選ばせます。 | エメラルドの選べる24×24のメニューの枠20種と、下端を反転した14タイルの会話の帯。クリスタルの1ビットのタイル6枚の枠8種。ファイナルファンタジーVIの28タイルのスタイル8種と、編集できる7色 |
| くっきりした文字 | ピクセルフォントがくっきりするのは、1フォントピクセルが整数のデバイスピクセルになるときだけで、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が整数になるのは12段階のBodyサイズのうち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つ)で小書きのかなを行頭に許した |
| 話し手 | 話し手の名前は、独立した欄や頭上ではなく、行の中で示します。 | エメラルドのボックスには名前欄がなく、段落の6.5%が名前とコロンで始まる |
| カバー範囲 | ライターが確定していない日本語のテキストを出荷する前に、標準の文字集合、約7,000字をカバーします。確定したスクリプトなら、文字列ごとに照合したうえで、もっと少なくて済みます。 | JIS X 0208の6,879字。ピクセルリマスターの「around 7,500」。ファイナルファンタジーVIの日本語版ROMの会話用1,248字。Misaki、k8x12、DotGothic16はJIS X 0208をすべて、PixelMplusは3つのコードポイントを除くすべてを描く |
| ライセンス | 書体を出荷するのは、ライセンスの本文を手元に置き、条件を読んでからです。 | Misaki、k8x12、PixelMplusの3文のライセンス。DotGothic16、Press Start 2P、SilkscreenのSIL Open Font License、Press Start 2Pの予約フォント名 |
| 2つの形 | ボックスと吹き出し、両方の形を保ちます。どちらが何を言うかはゲームが選ぶことです。Kiradexでの私の選択は、場所が言うことはボックス、人が言うことは吹き出しです。 | Stardew Valleyは両方を持つ。ボックスは会話に、吹き出しはプレイヤーが場所に入ったときにNPCが言うことに |
表の数字はすべて第1節から第8節に由来し、そこに脚注があります。1467921862
10. Appleのやり方:書体、サイズ、改行、VoiceOver
Kiradex WorldはRealityKitのシーンを2Dのレンダラーとして使っています。正射影のカメラを1テクセルあたり整数のデバイスピクセルに置き、人が読むものはすべてその上のSwiftUIで描きます。23 世界編の記事は、会話のテキストは「lives in SwiftUI at UI scale rather than in the world」(世界の中ではなく、UIのスケールでSwiftUIの中に置く)と決めました。本節はその決定を保ったまま、APIを埋めていきます。Appleがカスタム書体に求めること、同梱したピクセルフォントをどう組み込むか、丸めによってDynamic Typeをどう尊重するか、日本語をどう改行するか、そして1つのRealityKitビューであるステージをVoiceOverがどう読めるか、です。以下の対応バージョンはすべて、2026年10月5日に保存したAppleのドキュメントのページによるものです。コードは1行もビルドしておらず、本節のコストはどれもスマートフォンで測っていません。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に合わせて値を拡大する方法が3つと、拡大を止める方法が1つあります。
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:)を持ちます。67Font.custom(_:size:relativeTo:):SwiftUIで「that scales relative to the giventextStyle」(指定したtextStyleに対して相対的に拡大する)カスタムフォントを作ります。iOS 14.0からです。68ScaledMetric:「A dynamic property that scales a numeric value」(数値を拡大する動的プロパティ)で、iOS 14.0からです。69Font.custom(_:fixedSize:):「that does not scale with Dynamic Type」(Dynamic Typeで拡大しない)カスタムフォントを作ります。iOS 14.0からです。68
ピクセルフォントには、1つ目か3つ目と、最後の1つを組み合わせて使うのが向いています。システムが使うはずのサイズを読み、フォントのグリッドに丸め、丸めたサイズを固定サイズとして設定して、システムがそれ以上拡大しないようにするのです。丸めそのものは算数で、第12節のブリーフにあります。1フォントピクセルをmデバイスピクセルにし、10ピクセルのemならm = max(m₀, ⌊size × displayScale / 10 + 0.5⌋)、つまり0.5を切り上げた最も近い整数です。m₀は11ポイント以上になる最小のmで、3倍の画面で4、2倍で3です。ポイントサイズは10 × m / displayScaleです。3倍の画面では、12段階のBodyサイズがmの4、5、5、5、6、6、7、8、10、12、14、16に対応します(15ポイントのSmallがちょうど0.5になる唯一のサイズで、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 ブリーフが制限するのは、1行に保たなければならない吹き出しだけで、ボックスは決して制限しません。
フォントの同梱
フォントはアプリのバンドルに入れて出荷し、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で枠を描き、元画像の1ピクセルがちょうどm×mデバイスピクセルになるようにします。私の読みでは、.tileを付けたresizable(capInsets:)でも同じように使えるのは、そのタイル配置が整数のデバイスピクセルに乗る場合だけで、それは誰も測っていません。
Text.baselineOffset(_:)は「Sets the vertical offset for the text relative to its baseline」(ベースラインに対するテキストの縦のオフセットを設定する、iOS 13.0)もので、PixelMplus12のようにグリッドがベースラインからずれた書体のための道具です。第6節で測ったとおり、ずらしてもそうした書体が直るのは1つのサイズだけで、これがブリーフでその書体を使わない理由の1つです。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)には、名前の付いた戦略が3つあります。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
- 書体は1つ、システムのもの。 テーマのフォントファミリーは
.systemDesign(.monospaced)で、世界のビューはキットのロールを使っています。captionが4ファイルの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(2つの固定色を持つアートのサーフェス)の上にインクで描き、キットのsmallの間隔で左右に、extra-smallで上下に余白をとり、2ポイントのインクの縁取りと.fixedSize()で1行です。rig.headPointからlargeの間隔1段分上に置かれ、発言から4秒未満のあいだ表示され、ヒットテストをオフにしたオーバーレイの中で"Said: \(text)"というラベルが付きます。このオーバーレイはステージの内側にあり、ステージがシーン全体に重ねる2つのオーバーレイ、つまり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語ずつのリストから選んだ2語と4桁の数字で、最大20文字です(「Glacier Shuffle 0000」)。コレクターの頭上に描かれるのは吹き出しだけで、ハンドルはタップで開くパネルに表示されます。288911 このシリーズの町の暮らしの記事も同じ23行を数え、サーバーの上限をプレイヤー1人あたり2.0秒に1行としていました。9091
- フォントなし、言語は1つ。 フォントファイルは同梱されていません(リポジトリにある
.ttfはスクリーンショットツール用のscripts/screenshots/MartianMono-latin.ttfだけです)。プロジェクトのknownRegionsはBaseとenで、.xcstringsや.lprojのファイルはありません。1127 - VoiceOverに聞こえるのは座標。 ステージは1つの要素で、
.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の看板)で、その面は文字ではなく2行のインクの印です。広場のマップには看板のタイルSが1つあります。地面に指を置くと、プレイヤーはすぐにその下のタイルへ歩き、指を追ってタイルを渡ります。コレクターをタップすると指を離したときにそのパネルが開き、キオスクをタップすると町の掲示板が開きます。看板のタイルはほかのタイルと同じ地面で、独自のタップはありません。2992 - 世界の縮尺。
PlazaRig.fitのルール、ビューのピクセル単位の高さを320で割って切り捨てた値によって、1テクセルは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テクセル未満のフォント(1テクセル2ポイントで11ポイント)だけで、k = 6での8テクセルのemは16ポイントです。1133 UIのスケールでSwiftUIにテキストを置くという結論は、別の理由で成り立ちます。世界の密度では、1テクセル8デバイスピクセルのスマートフォンで10ピクセルのemは26.67ポイントになり、18 Pro Maxではボックスの1行にラテン文字が29字しか残りません。11 タイル単位で測ると、原典のボックスは狭いのです。ハードウェアのタイル16枚分、128ピクセル幅のボックスには、8×8のフォントで16字、エメラルドの平均5.25ピクセルのラテン文字で約24字(最悪の6ピクセルで21字)、エメラルドの8ピクセルの日本語で16字、ファイナルファンタジーVIの12ピクセルの日本語で10字が入ります。Kiradexの16テクセルのタイル16枚は256テクセルで、世界そのものの密度ではPixelMplus12のラテン文字42字とかな21字、Misakiなら64字と32字が入ります。11 そしてKiradex自身のタイルで測ったボックスは、測った5つのビューのうち3つに収まりません。16テクセルのタイル16枚は、1テクセル8デバイスピクセルで683ポイントになり、iPhone 18 Pro Max(440ポイント)やDuoの内側ディスプレイの縦向き(669)より広く、6では512ポイントで、Duoの外側ディスプレイの縦向き(466)より広くなります。収まるのは横向きの2つのビューだけです。内側ディスプレイの951に512ポイント、そして1テクセル4デバイスピクセルで341ポイントが外側ディスプレイの678に入ります。11 Kiradexのボックスは、タイルではなくポイントで大きさを決めます。
同じ算数をブリーフのサイズでやってみます。m = 5、16.67ポイントのPixelMplus10は、縦向きの18 Pro Maxのボックスで、現在の48ポイントのインセット(左右それぞれマージン16ポイントとパディング8ポイント)を全幅から引いたあと、左右のインセットがないと仮定して(測ってはいません)、1行にラテン文字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は4つのpretの逆コンパイルに対して実行しました。エメラルドのフォントの表(10項目、7フォント)、フレーム単位と毎秒文字数での文字の速さ、スクロール、送り矢印、27×4のウィンドウと30×6の枠、選べる20種の枠、ラテン文字のグリフ幅、文字数とピクセルで測ったスクリプトの24,100行、話し手の名前を出している14,412段落のうちの934段落、日本語のグリフのシート、ファイアレッドの待ちとウィンドウ、クリスタルと赤の画面、ボックス、待ち、枠、フォントのシートです。1 measure_ff6.pyはff6の逆アセンブルに対して実行しました。ウィンドウのスタイルとパレット、会話フォントのセルと幅、日本語フォントの大きさと幅の並び、220ピクセルの行、1フレームに1文字、100フレームのタイトル、単語ごとの折り返し、コンフィグ画面、ウィンドウのグラデーションの表です。4 measure_bdf.pyはMisakiとk8x12のBDFファイルに対して実行しました。6 measure_coretext.swiftとmeasure_outline_coords.swiftはmacOS 27.0でコンパイルして実行し、10個のTrueTypeファイルと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 以上で13本のスクリプトです。14本目の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がビルドしているのは英語版のゲームです。日本語版エメラルド自身のボックス、速さ、フォントは読んでいません。第1節の日本語のシートは、英語版ROMが名前のために持っているものです。日本語版のクリスタルと赤も読んでいません。30
- ファイナルファンタジーVIのグラフィック。 逆アセンブルにあるのは範囲とコードで、ピクセルはありません。ウィンドウのタイルの配置とグラデーションの見た目は見ていません。英語版の大きいフォントの範囲、5,120バイトは、22バイトの文字の整数個にならず、説明できていません。グラデーションの表を1走査線に1値ずつ消費するというのはコメントによるもので、追跡していません。4
- クリスタルの点滅の周期は導いていません。2つの状態はコードから読み取りました。16
- クロノ・トリガーはまったく測っていません。公開の逆アセンブルはクローンしておらず、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の対応していない3つのJISのコードポイントは、別のコードポイントでは確かめていません。7
- DotGothic16のサイズ。 ピクセルのグリッドがないので、名目上の16ドット毎emとその倍数で描画と組版をしました。これは名前とドットのピッチから私が選んだもので、ほかの名目サイズは試しておらず、第6節と第7節の数字はそのサイズでのものだけです。719
- フォントスムージング。 第6節のレンダリングはすべてフォントスムージングを許可していません。ビットマップのコンテキストの既定で描いたのは、1つの書体の1つのサイズ、10ピクセルのPixelMplus10だけで、インクの載った272ピクセルのうち178個が中間調でした。ほかの書体とサイズは、スムージングをオンにして描いていません。9
- 読んでいるあいだにアプリが変わりました。 Kiradexは、本記事を書いていた10月5日にも編集されていました。その日の早い時間にステージのタップ処理が書き直されて行が30から144ずれ、世界のSwiftファイルのうち3つはまだどのコミットにも入っていませんでした。私が読んでから14分後には、ステージとリグがまた変わっていました。本記事が読んでいるのは、コミット
334fbfeに、私が読んだ時点でのその日のコミット前の編集が加わったものです。アプリのファイルは太平洋時間12時28分(サーバーのコレクターと町のマップは12時33分)、それを測るスクリプトは12時34分と12時36分です。脚注には、その読み取りでの各ファイルの行を記しています。ブリーフは行番号ではなくシンボルで名指ししており、名指ししたシンボルがすべてまだあることを12時42分に確かめました。11 - アプリが動くiPhone。 33機種のiPhone(うち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の丸め、ボックスより1段小さく、ビューごとに決める上限の下に抑えた吹き出し、32ポイントのマージン、ボックスの待ち行列とその3行、ボックスのパディングの代わりに枠がとる8フォントピクセル、150ミリ秒のマークの段、4種の枠、アナウンスの6セルは提案であり、事実ではありません。
本記事が世界編の記事と異なる点。 第1、2、5、8節が、それぞれその記事の記述を1つずつ、測定とともに訂正しています。サイズの算数は正しいものでした(第10節)。シリーズのほかの記事から本記事が受け取った3つの事実は、アプリと照合しました。町の23行の定型文はlines.pyと一致します。キャラクター編の記事の、吹き出しはまだセルの高さから位置を決めているという注記は、歩く者の位置にシートのフレームの高さの半分と4を足すPlazaRig.headPointと一致します。そして吹き出しの下にいるのは、その記事の更新が述べている、32×40のセルで高さ30テクセルの人物で、アプリのマニフェスト、rig.py、体のシートがそれを裏づけます。その記事が幅を示しているのは以前の26テクセルの人物についてだけで、肩幅は約14でした。現在の人物の幅は、第12節でシートから測っています。90100289396
たどり着けなかった出典。 Data Crystalのクロノ・トリガーの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太字の1行の吹き出し、下部のWorldHUDのボックス、英語の定型文をサーバーに置いた英語のみのアプリ、文字のない看板のスプライト、そしてVoiceOverが座標として読むステージです。2211 世界編の記事の方向性は変わりません。テキストはアートである。会話は世界のテクセルではなく、UIのスケールでSwiftUIの中に置く。ボックスはタイルから作る。2行。タイプライター。送りマーク。23 変わるのは書体(第5節が述べる理由による)と算数(Dynamic Typeをフォントのグリッドに丸める必要があるため)です。7 ここでは、どのフランチャイズのグリフ、枠、マーク、言葉も描きません。すべてフォージが描くか、第4節にライセンスを引用したフォントで組みます。法律についての注記は私の読みであり、法的助言ではありません。まだ何も作っていません。
以下のチェックはどれも、スクリプト、テストのログの1行、キャプチャのいずれかで合否を判定できます。例外は、項目8にある、日付を記してデバイスで行う手動のチェック1つです。チェックの結果がデバイス、文字サイズ、選ぶ行、ボックスがほかに何を表示しているかに左右される場合は、それを明記しています。読んでいるあいだにコードが動いたので、ブリーフはアプリのコードを行番号ではなくシンボルで名指ししています。脚注には、私が読んだときの行を、コミットと時刻とともに記しています。項目の根拠が私のMacでの測定で、チェックがiPhoneかそのシミュレータで実行される場合は、Macではなくチェックが決めます。
1. 両言語に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のプレートです。アプリのほかの部分、デックス、スキャナー、設定はシステムの書体のままにします。
これを選ぶ理由。 第9節のルールが求める3つ、日本語、きれいなグリッド、整数ピクセルのアドバンスをすべて備えた測定済みの書体は2つです。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を選びます。私の選択です。2つのうち大きいほう、Misakiの8に対して10ピクセルであり、日本語の読者にとっての読みやすさが最も疑わしいのはMisakiの8×8の漢字です(第11節)。そして測定済みのBoldが付いているので、太字を合成する必要がありません。退けたものは、それぞれ退ける根拠となった測定とともに挙げます。
- PixelMplus12(M+ FONT LICENSE):グリッドがベースラインより0.39ピクセル下にあり、macOS 27.0のCore Textはすべてのサイズでにじませました。7
- k8x12(littlelimit.netの3文のライセンス、https://littlelimit.net/font.htm#license):かなのアドバンスが8.01ピクセルで、ラテン文字は幅4ピクセルです。717
- Misaki(同じライセンス、https://littlelimit.net/font.htm#license):3つとも備えていますが、測った中で最も小さい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)に通しており、そのカスタムフォントの経路はフォントを連続的に拡大します。そうするとこの書体は12段階のBodyサイズのうち11でグリッドから外れ、MacのCore Textがにじませた条件になります。887 世界はすでに、独自の2色を持つ宣言済みのアートのサーフェスWorldInkです。25 Kiradex内にPixelTextモディファイアを加え、そのアートのサーフェスの例外としてコメント付きで宣言し、キットのロールを受け取って項目2の丸めを適用させます。キットのファミリーのケースとして上流に提案するのは、出荷してからにします。
合成ウェイトはなし。 太字のテキストにはPixelMplus10-Boldを使います。ピクセル書体に.weight(_:)は付けません。合成しなければならないウェイトにCore Textが何をするかは測っていないからです(第11節)。
チェック。
- Core TextはiOSにもあるので、ユニットテストで同梱の2ファイルを読み込み、次を表明します。1emあたり1,000ユニット。世界の文字列と定型文が両言語で使うすべてのグリフのアドバンスが100ユニットの倍数であること。サンプルのグリフのアウトラインの点が100ユニットのグリッドから4ユニット以内(1フォントピクセルの0.04、第4節のプローブが使った許容差)にあること。不合格の書体は出荷できません。
- 世界が表示しうるすべての文字列(定型文、String Catalogの世界のキー、看板の文字)が、フォールバックのフォントなしでその書体のグリフに対応することのテスト。
- ビルドしたアプリのバンドルにLICENSE_Eがあることを確かめるビルドフェーズのスクリプトと、設定の新しい謝辞のセクションにフォントの名前、作者、ライセンスがあることを確かめるUIテスト。
2. サイズ:グリッドに丸めたDynamic Type
内容。 1フォントピクセルは常に整数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倍の画面では、ボックスは12段階のBodyサイズを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シミュレータが3機種のiPhoneについて挙げる2倍の画面(項目4)では、m₀は3、15ポイントで、12段階のBodyサイズは3、3、3、3、4、4、5、6、7、8、9、11になり、既定サイズで15ポイントです。9711 吹き出しは1段小さくし、m₀を下回らず、ビューごとに決める上限を超えないようにします(項目4)。
配置。 ピクセル書体がくっきりするのは、ベースラインがデバイスピクセルに乗るときだけです(第6節)。世界のオーバーレイで計算するすべての原点を、PlazaStage.bubblesの.positionも含めてデバイスピクセルのグリッドに丸め((x × scale).rounded() / scale)、テキストのビューには整数のデバイスピクセルの幅を、ボックスには整数のフォントピクセルの幅と高さを与えます(項目3)。
行送り。 PixelMplus10は上に9フォントピクセル、下に2フォントピクセルです。行送りを12フォントピクセルにし、行の間に空白の行を1つ挟みます。エメラルドの15ピクセルの日本語のグリフと16ピクセルのラテン文字のセルが16ピクセルの行に収まっているのと同じです。71 m = 5では1行20ポイントです。33
チェック。
- 丸めの純粋関数のテストを、すべてのDynamicTypeSizeについて、表示倍率2と3で行います。結果は1フォントピクセルあたり整数のデバイスピクセルで、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)。そしてテキストを切り出し、インクと紙の2色に対する中間調のピクセルを数えるスクリプトで、グリフの枠の内側、吹き出しの中とボックスの中で0であることを確かめます。どのキャプチャにも広場のサーバーの起動(KIRADEX_PLAZA=1)が必要で、プレイヤーが「Say」メニューから現在の最長の行を言ってから4秒以内に撮ります。行はすべて部屋から戻ってきてから表示され102、吹き出しは行が4秒未満のあいだしか表示されないからです。25103 同じスクリプトを現在のビルドで実行し、置き換える前の基準値を記録します。第6節の0.0%はフォントスムージングをオフにしたMacのCore Textのもので、iPhoneが同じように描くかどうかを決めるのはこのキャプチャです。
3. ボックス:フォージが描く枠、2行、ページ
内容。 WorldHUDのボックスは今の位置を保ちます。下部、セーフエリアの内側、Duoが半開きのときは折り目の広いほうの側です。26 2ポイントの線で描いた長方形は、フォージが描く枠に変わります。frame-paper.pngは24×24フォントピクセル、8のタイル3×3(角、辺、中央)で、WorldInkの2色に陰の色を1つ加えたもの、Kiradex独自のデザインです。PixelFrameビューがこれをCanvasに描き、元画像の1ピクセルをm×mデバイスピクセルの正方形にします。四隅には角のタイルを置き、辺と中央のタイルは左上の角から外側へ繰り返し、向こう側の端に達したところで最後のタイルを切ります。こうして枠のピクセルがテキストのピクセルと揃います。元画像のすべてのピクセルを整数の正方形にするため、ボックスの幅は、WorldHUDが現在レイアウトしているセーフエリアの幅から左右それぞれ16ポイントを引き、整数のフォントピクセル、つまりmデバイスピクセルの倍数に切り捨てたものにします。高さも整数のフォントピクセルにします。縦向きの18 Pro Maxでm = 5、左右のインセットなし(仮定であり、測っていません)なら、1,224デバイスピクセルは244.8フォントピクセルなので、1,220、244フォントピクセルに切り捨てます。1194 テキストは枠の内側、ボックスの縁からどの辺でも8フォントピクセルのところに置き、現在の8ポイントのパディングの代わりとします(私の選択)。そうするとそこでは1行に228フォントピクセル、ラテン文字45字か全角22字が残ります。11 場所のタイトルはどのページでも最上段の専用の行に置き、その下に本文を、エメラルドやクリスタルと同じく一度に2行ずつ表示します。1 2行より長い本文はページを送り、各ページは送りマークの下で待ちます(項目6)。ページの終わりがスペースなら、そのスペースは捨てます。WorldHUDのbox(WorldHUD.swift内)は、1つの状態の文字列の代わりにページを受け取ります。
待ち行列。 ボックスが表示する内容が状態の長さに左右されないようにするための、私のルールです。
- 状態のページはボックス自身のものです。状態が変わると(広場では、その中の何かが変わったとき、つまり接続の状態、そこにいる人数、時間帯、季節、獲得したリボン)、そのページは置き換えられ、ボックスは新しい状態を最初のページから表示します。ただし次のルールのページを表示中なら、そちらが優先されます。
- 看板の文字(項目5)と、大きいサイズで近くで話された行(項目4)は、状態より前に来るページです。すぐに表示され、それぞれ、すでに待っているそうしたページの後ろに、来た順に並びます。その最後のページのあとは、また状態を最初のページから表示します。ボックスが保持するのは、最新の3行までのページです。4行目が来たら、待っている行のうち、プレイヤー自身のものではない最も古い行を捨てます(プレイヤー自身の最も古い行を捨てるのは、待っている行がすべてそのプレイヤーのものの場合だけです)。こうして、1行を言ったプレイヤーがその行を失うことはありません。新しい看板は、まだ待っている看板のページを置き換えます。
- 状態のテキストは、どのページを表示していても、現在と同じく状態全体をアクセシビリティのラベルとして保持します。そうすればVoiceOverは状態全体を読み、
plaza-status、room-status、hall-statusを読む既存のUIテスト(冒頭の言葉、そこにいる人数、部屋の最初の言葉)も、今と同じものを読みます。表示中のページは、表示し終えたところまでが、隠れた要素の値になります。広場ではplaza-pageです(<name>-page、私の命名)。その値は場所のタイトルを除いたページの本文の行で、この要素は歩行テストの起動引数-worldTraceのもとでだけ存在します(項目8)。以下のチェックはこれを読み、どのチェックも-worldTraceを渡します。103
選べる枠。 エメラルドはメニューの枠を20種から選ばせ(会話ボックスの枠は1つのまま)、ファイナルファンタジーVIはウィンドウを8種から選ばせて色も編集させます。14 設定に、フォージが描く枠を4種、すべて無料で用意し、最初の3種は最初から、4種目はレベルで解放します。枠を売ることは決してしません。サポーターへの特典として枠を出すかどうかは、まだ決めていません。
チェック。 frame-paper.pngとそれぞれの別の枠が24×24で、パレットのインデックスだけを使い、デザインがそうと定めるところでは向かい合う辺が同一であることを確かめるフォージのチェック。iPhone 18 Pro Maxシミュレータで、m = 4(xSmall)とm = 5(既定サイズ)のボックスをキャプチャし、スクリプトで、ボックスの幅と高さがデバイスピクセルでmの倍数であることを確かめ、その枠、つまりどの辺でも外側の8フォントピクセルを、同じルール(四隅に角、辺と中央のタイルを左上から繰り返し、向こう側の端で切る)でキャプチャしたボックスの大きさに合わせてPNGから自ら組み立てた枠と、元画像の各ピクセルをm×mの正方形として1ピクセルずつ比べます。PNGが透明にしているピクセル、つまり世界が透けて見えるところは除きます。さらに同じシミュレータの既定サイズで、行が話されないよう広場のサーバーなしに、-worldTraceと-worldBoxSample、そして改行でつないだ30から40文字の3行(どれもそこで1行に入る45字以内)の状態を与えて起動するUIテストを行います。このテストは、plaza-pageが空白を除いて最初の2行に等しくなるのを待ち、そのあとで初めて、最初のページが表示しきった状態でボックスをタップし、3行目を見つけます。最初のページのキャプチャでは、スクリプトがボックスの右下にマークを見つけます。11 -worldBoxSampleは私の命名によるテスト用の引数で、-plazaTell(そのtellOnArrival)と同じようにPlazaViewで読み、広場自身の状態の代わりにWorldHUDへ状態として渡します。92
4. 吹き出し:1行、名前なし、両言語
内容。 吹き出しの形は保ちます。クリーム色、インクの縁取り、頭上、4秒未満。25 吹き出しのmでピクセル書体を使って組み、線で描いた長方形の代わりに枠の角のピクセルを使って、吹き出しの左右それぞれ8ポイントのパディングの内側に描きます。こうすれば現在の縁取りと同じく、幅は増えません。吹き出しの中にも上にも名前は出しません。話し手は吹き出しが載っている頭であり、VoiceOverがその名前を読みます(項目8)。定型文は決して折り返さず、.fixedSize()は残します。
走査線の上へ。 現在、吹き出しはステージの内側のオーバーレイで、ステージの走査線(3ポイントごとに不透明度16%の黒い線、3倍の画面では9デバイスピクセルごとに3デバイスピクセルの帯)とビネットの下にあります。一方、ボックスはステージ全体の上に重なっています。2526 これらの層の下では吹き出しの紙がグリフごと帯状に暗くなるので、どのサイズでもテキストがインクと紙の2色になりません。ブリーフでは、項目2のチェックをボックスだけに限るのではなく、吹き出しのオーバーレイを、ボックスがすでにある走査線とビネットのオーバーレイの上へ移します。私の選択で、ピクセル書体がくっきりするのはインクと紙の整数のデバイスピクセルとしてだけだという第6節のルールを、2つの話し方に同じように当てはめたものです。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で、セーフエリアに左右のインセットがなければ1行の吹き出しに収まります。それを保証するのが、下に述べるサーバーの上限です。インセットがあると、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の幅と3つのインセットの36通りのうち、現在の行で7通り、上限の日本語の行で8通り、ラテン文字の行で10通りです。そこではその行は吹き出しにならずボックスへ回り、短い行は吹き出しのままです(後述)。11 アプリはiOS 27.0以降のiPhone専用にビルドされており、私のMacのXcodeと一緒にインストールされたiOS 27.0シミュレータのランタイムは33機種のiPhoneを挙げています。3倍で最も狭いのは幅360ポイント(iPhone 12 miniと13 mini)で、3機種が2倍、第2世代と第3世代のiPhone SEが375ポイント、iPhone 11が414ポイントです。97 これはSDKがシミュレートするデバイスのリストで、iOS 27が対応するiPhoneについてのApple自身のリストは取得していません。吹き出しの余地は、セーフエリアの幅から、収める処理が左右に16ずつ確保する32ポイントを引いたものです。縦向きのスマートフォンで、左右のインセットがなければ(仮定であり、どれも測っていません)、このシリーズが測った5つのビューで最も狭い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のうち7機種が390ポイント、4機種が393ポイント)、ほかはそのままです。1197 アプリは、PlazaViewが渡すセーフな幅とサーバーが送る行から上限を計算するので、セーフエリアが狭くなったり行が長くなったり、たとえば日本語の行が加わったりすれば、上限は下がりえます。上限はすべての行に共通の1つの値です。上限がないところでどうするかは行ごとに決めます。私の選択です。最長の行に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 スマートフォンの縦向きの左右のインセットは測っていません。それが現れるとすれば、吹き出しがセーフエリアの内側にあることを表明する、下のキャプチャです。
3倍の画面での、サイズ別の吹き出しとしての最長の定型文と、各ビューの余地。measure_kiradex_text.outからfigures/make_figures.pyで描画。
大きい文字。 吹き出しのmはボックスのmに1段遅れて従い、m₀を下回らず、ビューの上限を超えません。18 Pro Maxでは、ボックスがxLarge(m = 6)のときに5に達し、ボックスのmが7以上になるxxxLarge以上では5のままです。上限によって吹き出しがボックスより2段以上小さく抑えられるところ(18 Pro MaxではxxxLarge以上、360ポイントのスマートフォンではxLarge以上)では、プレイヤーから6セル以内で話された行を、読む人はボックスでも、ボックスのサイズの独自のページとして受け取ります。話し手のハンドル、コロン、そしてその行で、エメラルドが話し手の名前を行の中で示すのと同じです(項目3がそのページを送り、項目8がアナウンスします)。部屋の行を保持し、現在ボックスの状態を組み立てているPlazaViewが、そのページを組み立て、項目3の待ち行列に従って状態より前に置きます。すぐに表示し、すでに待っている行のページの後ろに並べ、保持するのは最大3行です。行のページは、その吹き出しを加えるのと同じ更新でボックスの待ち行列に入れるので、そうした行の吹き出しが画面にあれば、そのページは待ち行列に入っています。1ページで済むとはかぎりません。ハンドルは14から20文字なので、「handle: line」は19から62文字になり、項目3の枠では、18 Pro Maxのボックスの1行にはラテン文字がxxxLargeで31字、AX3で17字、AX5で12字入ります。names.pyが作れるすべてのハンドルとすべての定型文について、xxxLargeからAX3までは1ページか2ページ、AX4とAX5では最大3ページ、そして360ポイントと2倍のスマートフォンの最大のサイズと、測ったレールがあるときのDuoの外側ディスプレイのAX4とAX5では最大4ページです。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のアドバンスの表(どちらかの言語で定型文が使う文字ごとに1項目)が、同梱したフォントと一致することも表明します。フォントのパーサーを持たないサーバーのテストスイート(依存関係は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のシミュレータの外側ディスプレイで、それぞれ4枚(英語と日本語、それぞれ既定サイズとAX3)をキャプチャします。広場のサーバーを起動し(KIRADEX_PLAZA=1)、アプリは-worldTraceで起動し、どのキャプチャでもプレイヤーがその言語の最長の行を言います。スクリプトは各キャプチャで吹き出しのインクの縁取りを見つけ、それがテキスト1行分の高さで、幅がセーフエリアの幅から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で、1行にラテン文字17字が入ります。広場のサーバー(KIRADEX_PLAZA=1)が必要で、ほかのプレイヤーは要りません。-worldTraceと、項目5が名前を挙げるworld.textSpeedキーによるInstantの文字の速さ(項目6)で起動するので、どのページも開いた瞬間に全体が表示され、ボックスを1回タップするとちょうど1ページ進みます。テストは「Me」タブからプレイヤーのハンドルを読み、「Say」メニューから「Hi!」を選び、広場に仲間がいることを確かめる既存のテストが認める5秒以内に吹き出しSaid: Hi!を待ちます。これで行は部屋から戻ってきており、項目4のルールにより、状態の長さにかかわらず、そのページは状態より前にボックスの待ち行列に入っています。町の4人のコレクターはそれぞれ最短でも20秒に1行しか言いませんが、その行が先に並んでいることはあります。待ち行列が保持するのは最大3行で、プレイヤーの1行を捨てることはないので(項目3)、先に並ぶのは最大2行、そこではそれぞれ最大2ページで、あとから言われた行はその後ろで待ちます。テストは各タップの前にplaza-pageを読み、ページがハンドル、コロン、スペース、「Hi!」でないあいだだけ、1秒おきに最大4回ボックスをタップし、いずれかの読み取りでそのページが見つかれば合格です。最後の読み取りは4回目のタップの1秒後です。この行を指定するのは、names.pyが作れるどのハンドルでも「handle: Hi!」はそこで1ページになるのに対し、最長の行は2ページになりうるからです。テストは読んだページをすべてログに残します。11103102
5. 看板と名前
看板。 看板のアートはkit.pyが描くとおり文字なしのままにし、1つのアトラスで両言語をまかないます。29 看板のタイルをタップすると、現在のようにプレイヤーをそこへ歩かせるのではなく、看板の文字を状態より前のページとして(項目3)WorldHUDのボックスに表示します。現在、ステージはDragGestureでタッチを読んでいます。touched(_:in:)は、コレクターやキオスクの上(これらは指が離れるのを待ちます)を除いて、指の下の地面へプレイヤーをすぐに歩かせ、lifted(_:in:)がその離れた瞬間をタップとして読みます。看板のタイルは、その両方でコレクターとキオスクの仲間に加わります。指が離れるのを待ち、lifted(_:in:)はキオスクに対してonKioskを呼ぶのと同じように、看板に対してonSignクロージャを呼びます(名前は私のものです)。92 看板の文字はマップと一緒に置きます。town.jsonに、セルをキーとするsigns: { "7,10": { "en": ..., "ja": ... } }を加え、フォージのチェックは両言語のそろっていない看板のセルを拒否します。
名前。 頭上に常設の名前ラベルは出しません。原典のボックスは、話し手の名前を行の中で示します。エメラルドでは段落の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:)から1つのメソッドに切り出した同じ処理にそのセルを送ります。iPhone 18 Pro Maxシミュレータの既定サイズで、行が話されないよう広場のサーバーなしに、テストは項目3の-worldBoxSampleも1ページの状態「Sample.」で渡し、起動引数の設定キーでInstantの文字の速さ(項目6)を指定します(キーworld.textSpeedは私のもので、スティックのworld.stickと並びます)。テストはplaza-pageが「Sample.」から看板の最初のページに変わるのを最大5秒待ってから読み、ボックスをタップしてまた読みます。これをplaza-pageが「Sample.」になるまで、看板の文字の単語ごと(日本語では1文字ごと)に1回、さらに1回を加えた回数を上限に繰り返し、それまでのページを順につなげたものが、空白を除いて看板の英語の文字と等しいことを確かめます。こうすれば、ページを送るほど長い看板の文字も短いものと同じく合格できます。アプリの言語を日本語にして起動し直し、日本語の文字についても確かめます。92 フォージのチェックは、どちらかの言語が欠けたSのセルを持つtown.jsonを拒否します。
6. 文字送りの速さと送りマーク
内容。 数えるのがフレームではなく時間であるタイプライター表示です。shown = floor(elapsed × cps)は、経過時間、速さ、2つの設定(Reduce MotionとVoiceOverの実行中かどうか)の純粋関数なので、60ヘルツでも120ヘルツでも同じ速さで表示されます。設定には、Slow(毎秒12文字、クリスタルの遅い設定)、Normal(毎秒30文字、既定、原典の中間の設定であるエメラルドの15とクリスタルの20と、最速の60の間をとった私の選択)、Instantを用意します。表示中にタップするとそのページを最後まで表示し、表示しきったページでタップするとページを送ります。1 そのためにボックスは、表示中か、表示しているページの後ろにページがあるあいだはタップを受け付けます。最後のページを表示しきったら、現在常にそうしているように、タップを下の地面に通します。26 両言語で速さは同じです。ファイナルファンタジーVIが両方を1フレームに1文字で表示するのと同じです。4 Reduce MotionがオンのときやVoiceOverの実行中は、関数はページ全体をすぐに返します。これは、Reduce Motionでは「UI should avoid large animations」(UIは大きなアニメーションを避けるべき)というドキュメントの一文にもとづく私の選択です。83
マーク。 Kiradex独自のフォージが描くマークで、5×3フォントピクセルの下向きのくさびを、ボックスの右下に置きます。150ミリ秒ごとに0、1、2、1フォントピクセル下へ段階的に動き(向きは私の選択で、私の読みではエメラルドのものは上がります)、1周期は600ミリ秒、60ヘルツでのエメラルドの9フレームの段です。133
チェック。 毎秒30文字での表示関数のユニットテスト。t = 0で0文字、1.000秒で30文字です。経過時間は、1ティックごとの差分を足し合わせるのではなく、ティック数をレートで割って求め、毎秒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文字の2行、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」(末尾にだけ追加する)でインデックスの順序を保ち、各行は英語と日本語の文字列になります。サーバーは両方を送り、各クライアントはユーザーが読む言語を表示するので、言語の混ざった広場で話された1行は、1つのインデックスから2つの言語で読まれます。28 私の読みでは、これがこのアプリで定型文を使う最良の理由です。世界の文字列、つまりWorldHUDのタイトルと状態、クエストのタイトルは、jaを含むString Catalogに移します。看板の文字はマップと一緒に置いたままにします(項目5)。日本語の文字列はすべて日本語話者が書くかレビューします。機械翻訳でレビューのないものは1つも出荷しません。
行分割。 定型文は決して折り返しません(項目4)。ボックスのテキストは、エメラルドのスクリプトが手で改行されていたのと同じく、既定サイズで1ページ2行に収まるように書きます。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の状態として渡し、そこで1行に入る全角22字のところで改行が小書きのかなの前に来るように書いておきます。キャプチャで、段落の最初の行より後のどの行も、JLREQの行頭リストの文字やその互換形で始まらないことを確かめます。11 このリストは完全なもので、3.1.7の規則が挙げる10のクラスの83項目(3つの文字は2つのクラスに属します)をテストのターゲットに入れておきます。第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項目のうち、)、〕、]の3つは2つのクラスに載っているので51文字、それに24の互換形)。98 スクリプトは各行の最初のグリフのセルを、同梱のフォントからCTFontGetGlyphsForCharactersでキャプチャのmで描いたその75文字のグリフと1ピクセルずつ比べ、一致がないことを確かめます。ピクセルでリストの文字と段落のほかの文字を区別できるのは、書体がそれらを違う形に描いている場合だけです。10ピクセルで描くと、75文字のうち2文字が、書体の中にピクセル単位で同一の双子を持っています。‐(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からの空気のプローブの報告があり、数分後には、その日に加えられた歩行デモからの4つ目の書き込みがありました。移す先は、アプリが-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. プレイヤーから6セル以内で話された行は、AccessibilityNotification.Announcementとして低い優先度で送ります(たとえばGlacier Shuffle 4471: Nice card!)。頻度は最大2秒に1回で、サーバー自身の1行あたりのレートと同じです。8291
5. 開いたボックスのページは、ポイント4ですでにアナウンスした行を繰り返すもの(項目4の大きい文字のページがそうです)でないかぎり、全体をアナウンスします。
チェック。 -worldTraceなしではステージの値に座標が含まれないことを確かめるUIテスト。iOS 27のシミュレータで、XCUIVoiceOverServiceを使ってVoiceOverを有効にし、ステージへ移動させ、currentSpeech()が返すものにステージのラベルが含まれ、ステップの記録のx,yの形の数字の組が含まれないことを表明するテスト。87 VoiceOverをオンにしたデバイスで、コレクターのエンティティに到達でき、読み上げられることを確かめる手動のチェック(日付とビルド番号を記録します)。仮想カメラを持つRealityViewがAccessibilityComponentの要素を公開するかどうかは確かめていないからです(第11節)。そしてアナウンスの間引きのユニットテストで、2秒以内の2行がアナウンス1回になることを確かめます。
9. 書体とウィンドウについて、決してしないこと
- どのゲームのROMや逆コンパイルからも、グリフ、枠、矢印、ウィンドウのグラフィックをアプリやフォージに入れません。pretとff6のシートは、ここで測るだけで、決してコピーしません。Kiradexの枠、マーク、看板はフォージが描き、書体はライセンス済みのフォントです。
- フランチャイズのキャラクターや合字を使いません。エメラルドのcharmapには、フランチャイズの言葉のための特殊なグリフがあります。そうしたものは描かず、どの文字列もフランチャイズのものの名前を使いません。31
- ライセンスファイルが手元にないフォントは使いません。書体を出荷するのは、ライセンスの本文を第4節か第5節に引用し、証拠と並べて保存した場合だけです。
- 改変したOFLの書体を、その予約フォント名のまま使いません。Press Start 2Pは予約フォント名を宣言しています。OFLの書体を改変することがあれば、名前を変えます。54
- 世界のアートにテキストを焼き込みません。看板、店先、掲示板は文字なしのままにし、アトラスが言語ごとになることがないようにします。
チェック。 スクリプトが、フォージが書き出し、アセットカタログが保持するすべての画像について、SHA-256のダイジェストを2つ、ファイルのバイトのものとデコードしたピクセルのものを計算し、ここで測ったそれぞれのPNG(pretのgraphics/fontsとgraphics/text_window、クリスタルの枠、クリスタルと赤のgfx/font/font.png)の同じ2つのダイジェストと比べます。画像ではなくダイジェストのリストを、scripts/forge/canon_digests.txt(私が選んだ名前で、フォージのkit.pyの横)としてリポジトリに入れるので、チェックは証拠フォルダなしで動き、一致が1つでもあればビルドは失敗します。チェック自身のテストは、ゲームのファイルを必要としない陰性対照です。フォージの出力1つのダイジェストをリストのコピーに加え、そのときスクリプトが失敗することを表明します。同じスクリプトは、フォージの出力の8×8のセルのうち、1ビットのマスクかその反転がそれらのシートのセルと一致するものを、失敗にはせずにレビュー用に列挙します。画像全体のダイジェストではそうしたセルを組み立て直せないので、リストにはシートの異なるセルマスクすべてのSHA-256ダイジェストも入れます。ルールは1つで(私のもの)、フォージの出力にも同じように当てはめます。画像の左上のピクセルと色が異なるピクセルを立てる。セルは左上の角から8×8で区切り、端の不完全なセルは捨てる。空白のセルと塗りつぶしのセルは、シートに多くあるので両側とも除外する(japanese_normal.pngだけでも1,024セルのうち430セルが空白です)。マスクは64ビットを行ごとに並べた8バイトとする。50枚のシートで、残るセルは7,746個、異なるダイジェストは1,484個です。99 色を変えたり、拡大縮小したり、描き直したりしたコピーはダイジェストを通り抜けます。ルール1を支えているのは、フォージがすべてのアセットをコードから描くことと、そのレビューであって、チェックだけではありません。そして、このシリーズのこれまでのブリーフが提案している文字列のチェックを、エメラルドのcharmapにあるフランチャイズの略語にまで広げます。105
このブリーフに含まないもの
- アプリ全体のピクセル書体。 デックス、スキャナー、設定はシステムの書体のままです。ピクセル書体は世界のためのものです。
- 中国語と韓国語。 調べた現代のピクセルゲーム4本はすべて中国語を出荷しており、そのうち2本、Stardew ValleyとSea of Starsは韓国語も出荷していますが、ここではどちらの言語についてもピクセルフォントを測っていません。64
- 本文を読ませるためのMisakiや、ほかの8ドットの書体。 測った中で最も小さい漢字で、そのサイズでの日本語の読者にとっての読みやすさは測っていません。6
- 頭上の名前ラベルとボックスの名前欄。1
- 誰もレビューしていない機械翻訳の日本語。
- ピクセル書体の合成の太字や斜体。
- 有料の枠。 枠はすべて無料です。サポーター向けの枠は、決定を待っています。
- RealityKitのシーンの中に描くテキスト。 世界編の記事の理由と第10節の算数の両方が、テキストをSwiftUIに置いておくことを支持しています。2311
重要なポイント
アートを描く人へ
- 枠はタイルから作ります。角、辺、中央です。エメラルドのメニューは24×24ピクセル、タイル3×3の枠20種から選び、会話ボックスは下端を反転した14タイルの帯から描き、クリスタルの枠はそれぞれ1ビットのタイル6枚です。1
- 送りマークにはゆっくりした時計を与えます。エメラルドの矢印は静止位置から0、1、2、1ピクセルと、9フレームごとに1段、1周期0.6秒で動きます。1
- 世界のアートに言葉を入れません。文字のない看板なら、1つのアトラスですべての言語をまかなえます。言葉はボックスに入れます。29
- 日本語は8から12ピクセルで描き、かなはラテン文字の2倍になると考えます。測った日本語対応のピクセルフォントはどれも8対4、10対5、12対6です。7
エンジンを作る人へ
- ピクセルフォントは名前ではなくファイルで選びます。グリッド、ベースラインからのずれ、アドバンスを確かめます。macOSのCore Textでは、ベースラインから0.39ピクセルずれたPixelMplus12のグリッドはすべてのサイズでにじみ、k8x12の8.01ピクセルのかなのアドバンスは、グリフを正確な位置に置くとにじみました。7
- 描くのは、1フォントピクセルが整数のデバイスピクセルになるときだけにします。フォントスムージングをオフにしたMacのCore Textでは、グリッドがベースライン上にありアドバンスが整数ピクセルのフォント(Misaki、PixelMplus10、Press Start 2P)は、整数倍で中間調0.0%、その間で19.4%から82.2%でした。コンテキストの既定のスムージングでは、PixelMplus10を本来のサイズで描いてもインクの載った272ピクセルのうち178個が中間調だったので、整数倍は必要条件であって十分条件ではありません。iPhoneでどちらの結果を信じるにも、その前にキャプチャします。79
- Dynamic Typeはグリッドに丸めて尊重します。3倍の画面で10ピクセルのemが整数になるのは、12段階のBodyサイズのうち1つです。拡大したサイズを、1フォントピクセルあたり整数のデバイスピクセルに丸め、固定サイズとして設定します。768
- 文字の速さはフレームではなく時間で数え、60ヘルツでも120ヘルツでも同じように表示されるようにします。測ったどのゲームでも最速は毎秒60文字で、中間の設定があるゲームではそれが15から20です。14
- VoiceOverには記録ではなく文を与えます。
accessibilityValueはラベルが言わないことのためのもので、AccessibilityComponentはエンティティを説明し、XCUIVoiceOverServiceはiOS 27.0からVoiceOverが読み上げる内容をテストできます。798487
ゲームのループを設計する人へ
- 一度に2行、下部に。エメラルドのボックスは画面の30%、クリスタルは33%で、どちらも2行です。1
- 幅に合わせて書きます。エメラルドのライターは、41文字入るウィンドウで24,100行の95%を38文字以内に収め、ウィンドウを超える行はひとつもありませんでした。1
- 話し手の名前は頭上ではなく行の中で示します。エメラルドのボックスには名前欄がありません。1
- 日本語は最初から計画に入れます。調べた現代のピクセルゲーム4本のうち4本が出荷しています。JIS X 0208をカバーするフォントは約7,000字を描きます。スクリプトが確定したゲームなら、ファイナルファンタジーVIの日本語版ROMが1,248字で済ませたように少なくできますが、そのときは新しい文字列をすべてフォントと照合しなければなりません。64584
よくある質問
ピクセルアートのゲームのテキストボックスは、どのくらいの大きさにすべきですか?
携帯機のポケモンのゲームでは、画面の下3分の1で2行です。エメラルドの会話の枠は30×6タイル、240×48ピクセルで、ゲームボーイアドバンスの画面の高さの30%を占め、その内側に、16ピクセルのフォントで2行が入る27×4タイルのテキストウィンドウがあります。クリスタルのボックスは20×6タイルでゲームボーイの画面の33%、18文字の行が2行で、赤も同じ20×6です。ファイナルファンタジーVIは、ウィンドウを画面の上か下に開きます。12345
タイプライター風のテキスト表示は、どのくらいの速さにすべきですか?
原典はフレームを数えています。エメラルドとファイアレッドは8、4、1フレームごとに1文字、60ヘルツで毎秒7.5、15、60文字を表示し、表示中にボタンを押したあとは、AかBを押し続けているあいだ1フレームに1文字です。クリスタルと赤は毎秒12、20、60文字です。ファイナルファンタジーVIはフィールドの会話を1フレームに1文字、毎秒60文字で表示し、その設定はありません(6段階のメッセージ速度は、戦闘メッセージを待つ時間を決めるものです)。現代のスマートフォンでは、フレームではなく時間で文字を数え、120ヘルツでも速さが変わらないようにします。測ったどのゲームも最速は毎秒60文字で、エメラルドとクリスタルの中間の設定は15と20です。下のブリーフは既定として30を提案していますが、これは中間の設定と最速の60の間をとった私の選択で、測った設定ではありません。1144
日本語に対応したピクセルフォントはどれですか?
測ったもののうち、Misaki(8×8ドット)、k8x12(8×12)、DotGothic16はJIS X 0208の6,879字すべてを、PixelMplus10とPixelMplus12は3つのコードポイントを除くすべてを描きます。Misaki、k8x12、PixelMplusは、使用、複製、配布を「either commercially or noncommercially」(商用か非商用かを問わず)認める3文の無料ライセンスで、DotGothic16はSIL Open Font License 1.1です。macOS 27.0のCore Textで測ると、ベースライン上のきれいなグリッドと整数ピクセルのアドバンスを持つのは、8ピクセルのMisakiと10ピクセルのPixelMplus10の2つです。PixelMplus12のグリッドはベースラインより0.39ピクセル下にあり、k8x12のかなのアドバンスは8.01ピクセル、DotGothic16のドットはどのピクセルのグリッドにも乗っていません。67171853
iPhoneでピクセルフォントがぼやけて見えるのはなぜですか?
たいていは、1フォントピクセルが整数のデバイスピクセルになっていないからです。フォントスムージングをオフにしたmacOS 27.0のCore Textでは、ベースライン上のきれいなグリッドと整数ピクセルのアドバンスを持つピクセルTrueTypeフォント(Misaki、PixelMplus10、Press Start 2P)は、グリッドの整数倍では中間調のピクセルなしで描かれ、その間ではインクの載ったピクセルの19.4%から82.2%がにじみました。描画コンテキストの既定のスムージングでは、整数倍でもぼやけました。PixelMplus10を本来の10ピクセルで描いて、インクの載った272ピクセルのうち178個が中間調でした。9 フォントファイルの2つの欠陥は、すべてのサイズでぼやけを生みます。PixelMplus12のように0.39ピクセルずれた、ベースラインから外れたグリッドと、k8x12の8.01ピクセルのかなのアドバンスのように、グリフを正確な位置に置いたときに整数にならないアドバンスです。測定はMacで行ったもので、iPhoneで決着をつけるのは、iPhoneでのキャプチャです。7
ピクセルフォントはDynamic Typeに対応できますか?
丸めることによってのみ対応できます。iOSの12段階のBodyサイズ(14から53ポイント)のうち、3倍の画面で10ピクセルのemが整数のデバイスピクセルに乗るのは40ポイントだけなので、Font.custom(_:size:relativeTo:)で連続的に拡大するピクセルフォントは、残りの11段階でグリッドから外れます。MacのCore Textでは、グリッドから外れることはにじむことを意味しました。iPhoneでSwiftUIが何を描くかは、iPhoneでのキャプチャが決めます。拡大したサイズをUIFontMetricsかScaledMetricで読み、1フォントピクセルあたり整数のデバイスピクセルに丸め、Font.custom(_:fixedSize:)で設定します。Appleは、カスタムフォントにもシステムフォントと同じ振る舞い(Dynamic Typeを含む)を実装すること、そしてアプリでは理想的には少なくとも200パーセントまでテキストを拡大できるようにすることを求めています。710206768
Core Textは日本語の行分割の規則に従いますか?
macOS 27.0での私の測定では、言語のタグを付けないときは、はい、厳格に従いました。131文字の段落を7つの幅で分割したところ、Hiragino Sansと8つのピクセル書体で、プローブの行頭リストのどの文字で始まる行もありませんでした。このリストは部分的なもので、大半はJLREQの文字、うち7文字は全角形です。jaのタグを付けると、W3Cの日本語組版処理の要件が述べる緩い規則に緩み、大半の書体で7つの幅のうち6つで小書きのかなが行頭に来ました。デバイスの言語が日本語のiPhoneでSwiftUIがどうするかは測っていません。721
日本語のピクセルフォントには何文字必要ですか?
通常のカバー範囲の目標であるJIS X 0208は6,879字で、非漢字524字、第1水準漢字2,965字、第2水準漢字3,390字です。ファイナルファンタジー ピクセルリマスターのプロデューサーはSiliconeraに、その日本語のピクセルフォントには「around 7,500 characters」(約7,500字)が必要で、欧米の言語では「a few hundred」(数百字)だったと語りました。スクリプトが確定したゲームなら少なくて済みます。ファイナルファンタジーVIの日本語版ROMの会話フォントは1,248字です。584
本記事の数字はすべて、保存したソースに対して再実行できるスクリプト、2026年10月5日に保存したページ、または日付を記したKiradexのソースの読み取りによるもので、どれによるかは脚注に記しています。Core Textの数字はiOSではなくmacOS 27.0のものです。第12節のブリーフは私の提案であり、そのように明記しています。まだ何も作っていません。
このサイトの関連記事:iPhoneでつくるピクセルアートの世界はこのシリーズの最初の記事で、RealityKitのレシピと、本記事が測ったテキストの方向性があります。ピクセルアートの人物:iPhoneのキャラクターとクリエイターは、吹き出しが頭上に載るコレクターの記事です。ピクセルアートの建物:iPhoneの家、ホール、室内には、看板を文字なしのままにするフォージがあります。ピクセルアートの動き:iPhoneでの歩行、カメラ、ドアには、表示のタイミングの基準となる60ヘルツと120ヘルツの時計があります。誰もオンラインにいなくても生きているピクセルの町には、定型文とそのレートがあります。ピクセルアートの部屋とピクセルアートの道は、本記事がそのチェックを広げたブリーフです。昔のポケモンのゲームに、借りずに敬意を払うは、原典を測っても決してコピーしない理由です。そしてXcode 27.1 beta:iPhone Duoシミュレータで自分のアプリを動かすは、吹き出しのキャプチャを実行するシミュレータについての記事です。
出典
-
著者による測定、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(空白でないセル)。pokefirered037335fからsrc/new_menu_helpers.c。pokecrystal5beda23からconstants/hardware.inc、constants/text_constants.asm、home/print_text.asm、gfx/font.asm、枠のPNG、gfx/font/font.png。pokeredd2704a6からdata/text_boxes.asm、constants/ram_constants.asm、gfx/font/font.png。毎秒の文字数は、60を1文字あたりのフレーム数で割ったもの。エメラルドのプリンタの読み(待ちはAddTextPrinterが1を引いて保存し、それをカウントダウンするので、1文字に待ちのフレーム数がかかる)、src/battle_message.cとsrc/scrcmd.cによる自動スクロールの待ちの条件、クリスタルの定数の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 ↩↩↩↩↩↩↩ -
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_WIDTH20とSCREEN_HEIGHT18)、2026年10月5日閲覧、https://github.com/pret/pokecrystal/blob/master/constants/text_constants.asm ↩↩↩↩ -
著者による測定、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)。1フレームに1文字と、グラデーションの1走査線に1値は、コードの読みであり、表明しているだけで実行はしていない。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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字。 ↩↩↩↩↩↩↩↩ -
著者による測定、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の文字を報告する。 ↩↩↩↩↩↩↩↩↩↩↩ -
著者による測定、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までの最小の1emあたりピクセル数。各軸に一定のオフセットを1つ認める)、ラテン文字とかなのアドバンス、ASCIIとJIS X 0208のカバー範囲、サンプルの1行をグリッドの1から4倍で8ビットのグレーのビットマップに描いたときのインクの載ったピクセルに占める中間調(255のうち8から247)の割合(アンチエイリアスのオン、オフ、そしてオンでサブピクセル位置決めをオン・量子化をオフにしたもの。フォントスムージングは許可しない)、PixelMplus12についてはベースラインをオフセット分動かした同じもの、Human Interface GuidelinesのBodyとCaption 1のサイズのうち3倍で1フォントピクセルが整数になるもの、そしてCTFramesetterで8から20emに組んだ131文字の日本語の段落の各行の最初の文字(タグなしとkCTLanguageAttributeName“ja”)を行頭禁則文字のリストと照合した結果を報告する。DotGothic16にはデザイングリッドがないので、名目上の16ドット毎em(1ドット62.5ユニット)とその1.25から4倍で描き、1emあたり32ピクセルで組んだ。このサイズは名前とmeasure_outline_coords.outのドットのピッチから選んだもので、出力に印字される。すべてのレンダリングはフォントスムージングを許可しない(setAllowsFontSmoothing(false))。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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/ ↩↩↩↩↩↩
-
著者による測定、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。 ↩↩↩↩↩↩↩↩↩↩ -
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 ↩↩↩↩↩↩↩
-
著者による測定、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語ずつのリストから選んだ2語と4桁の数字のハンドル、最大20文字)、Kiradex/World/*.swiftのすべての.kit941Font呼び出し(4ファイルで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ポイントのインセットのあとのボックスの1行あたりの文字数、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になり、1行にラテン文字45字か全角22字)。names.pyの400の語の組すべてと23行すべての「handle: line」のページを、12段階のBodyサイズと5つのビューで(1ページ2行、スペースでの貪欲な改行。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から、町の4人のコレクター、そのハンドル、18 Pro MaxのAX3でのその行のページ(最大2)、項目4のテストのタップの上限、(3 − 1)行 × 2ページ、Instantで1ページ1タップ: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。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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 ↩↩ -
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 ↩↩↩↩↩↩↩↩↩↩↩ -
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 ↩↩↩ -
pret、
pokecrystal/home/print_text.asm、コミット5beda23(4行目「The text speed setting in wOptions is actually a frame count」と、AかBを押し続けているあいだ1文字に1フレーム待つPrintLetterDelay)とpokered/constants/ram_constants.asmのd2704a6(38行目から40行目:TEXT_DELAY_FAST1、TEXT_DELAY_MEDIUM3、TEXT_DELAY_SLOW5)、2026年10月5日閲覧、https://github.com/pret/pokecrystal/blob/master/home/print_text.asm ↩↩ -
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 。点滅の周期は導いていない。 ↩↩↩ -
門真なむ(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 ↩↩↩↩↩
-
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の3文)、コミットd89b95f、2026年10月5日閲覧、https://github.com/itouhiro/PixelMplus/blob/master/misc/mplus_bitmap_fonts/LICENSE_E ↩↩↩↩↩ -
著者による測定、2026年10月5日:
measure_outline_coords.swift(本記事用の著者の証拠フォルダ内)、macOS 27.0でコンパイルして実行、出力はmeasure_outline_coords.out:PixelMplus12 RegularとDotGothic16のH、A、o、あと、SilkscreenのH、A、oの、1emあたり1,000ユニットでの生のxとyのアウトライン座標。 ↩↩↩↩↩↩↩ -
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 ↩↩↩
-
W3C「Requirements for Japanese Text Layout」、Working Group Note、2020年8月11日、3.1.7節(行頭禁則の規則と、より緩い規則についての注記)、2026年10月5日取得、https://www.w3.org/TR/jlreq/ ↩↩↩↩↩↩↩↩
-
本記事のための著者の調査資料、2026年10月5日(非公開):上記の測定、ケーススタディ、Kiradexのためのブリーフの初稿。 ↩↩↩
-
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」、エメラルドの24×24のテキストウィンドウの枠についての文)、k = 8とk = 6でのテキストのサイズの算数、日本語約7,500字についてGoNintendoを出典とするピクセルリマスターの段落、テキストの計画(「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 。その記事はここで引用しており、変更していない。 ↩↩↩↩↩↩↩↩↩↩
-
Kiradexのソース、
Kiradex/KiradexTheme.swift、12行目(let fontFamily: Kit941.FontFamily = .systemDesign(.monospaced))、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。 ↩↩ -
Kiradexのソース、
Kiradex/World/PlazaStage.swift、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ):146行目、155行目、189行目(stepTraceを、RealityViewのmakeクロージャでビルド失敗に、rig.onStepで各ステップに、-airProbeのもとで.taskの中で空気の報告に設定する。12時42分に同じファイルを読むと4つ目、新しい-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行目(その上にステージが重ねる2つのオーバーレイ: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)"))。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩ -
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:時間帯の言葉、そこにいる人数と助言の1行、獲得したリボン、季節の言葉)。キットの間隔は941KitのSources/Kit941/Tokens/Spacing.swiftで、iPhoneではxxs2、xs4、sm8、md16、lg24ポイント。 ↩↩↩↩↩↩↩ -
Kiradexのソース、
Kiradex.xcodeproj/project.pbxproj、1138行目から1141行目(knownRegions = (Base, en))、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。12時42分にはファイルの行が増えていた。 ↩↩ -
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.」。 ↩↩↩↩ -
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のセルが1つ、x 7、行10。12時33分にPythonのjsonモジュールで読み取り)。 ↩↩↩↩ -
方法。pretの逆コンパイル。シャロークローンを読んだのは、pokeemerald
731ad5b(2026年10月1日)、pokefirered037335f(2026年9月26日)、pokecrystal5beda23(2026年9月29日)、pokeredd2704a6(2026年9月22日)、https://github.com/pret 。ファイナルファンタジー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日に取得・保存。本記事用の著者の証拠フォルダにある13本のスクリプト(Pythonが10本、Swiftが3本)と、本記事の図と一緒に置いたもう1本のSwiftのスクリプトdump_glyphs.swiftが、測ったすべての数字を出しており、どれも出力をその横に保存している。pretがビルドしているのは英語版のゲームで、日本語版ROMは読んでいない。 ↩↩↩↩↩↩ -
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 ↩↩ -
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 ↩
-
測定値にもとづく著者の計算:216 ÷ 5.25 = 41文字。59.737 ÷ 1 = 毎秒59.7文字。191 × 1.25² = 298ピクセル。PixelMplus12の−33ユニット ÷ 1ピクセル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ポイント ÷ 1テクセル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ミリ秒、4段で600ミリ秒。3ポイント × 3 = 走査線の間隔9デバイスピクセル、各線は1ポイント × 3 = 高さ3デバイスピクセル。 ↩↩↩↩↩↩↩↩↩↩↩↩↩↩↩
-
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 ↩↩↩ -
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を1行下げるCHAR_NEWLINE)と1102行目から1109行目(2つのプロンプトがRENDER_STATE_CLEARとRENDER_STATE_SCROLL_STARTに入る)、2026年10月5日閲覧、https://github.com/pret/pokeemerald/blob/master/charmap.txt ↩ -
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 ↩↩↩ -
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 ↩↩ -
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 ↩↩ -
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 ↩ -
Pan Docs「Rendering」(「One frame: 70224 dots @ 59.7 fps」。1フレームは「takes ~16.74 ms instead of ~16.67」)、gbdev.io、2026年10月5日取得、https://gbdev.io/pandocs/Rendering.html ↩
-
pret、
pokecrystal/gfx/font.asm、コミット5beda23(枠FRAME_1からFRAME_8、それぞれ1ビットのタイル6枚のTEXTBOX_FRAME_TILES、未使用の9番目の枠)とgfx/font/font.png、2026年10月5日閲覧、https://github.com/pret/pokecrystal/blob/master/gfx/font.asm ↩ -
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 ↩ -
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 ↩↩↩ -
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/以下には1つもない。2026年10月5日閲覧、https://github.com/everything8215/ff6/blob/master/src/btlgfx/gfx_cmd.asm ↩ -
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 ↩↩↩ -
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 ↩↩↩↩↩ -
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 ↩ -
everything8215、ff6、
src/field/reset.asm(フィールドのループ:垂直ブランクを待ち、1周に1回UpdateDlgTextを呼ぶ)、コミット8130132、2026年10月5日閲覧、https://github.com/everything8215/ff6/blob/master/src/field/reset.asm 。この逆アセンブルは、1つのソースから英語版と日本語版の両方のROMをビルドする。 ↩↩ -
Data Crystal「Chrono Trigger/ROM map」、datacrystal.tcrf.net、2026年10月5日取得:HTTP 403、未読、https://datacrystal.tcrf.net/wiki/Chrono_Trigger/ROM_map ↩↩↩
-
Kiradex、
docs/TestFlight.md、手順5(192行目から194行目:デックスで日本語を選ぶと、セットとカードが日本語の名前で表示され、下に英語が添えられる)、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。 ↩ -
門真なむ(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 ↩↩↩↩
-
門真なむ(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 ↩
-
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 ↩↩↩↩ -
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 ↩↩↩ -
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 ↩↩ -
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を返した。単一の出典。 ↩↩↩↩
-
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 。フォントファイルはダウンロードしていない。 ↩↩↩↩↩↩
-
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 。フォントファイルはダウンロードしていない。 ↩↩↩↩↩
-
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 ↩
-
著者による測定、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)。 ↩↩↩↩↩ -
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/ ↩↩
-
Stardew Valley Wiki「Modding:Dialogue」、2026年5月20日最終更新(
$b、ポートレートのコマンド、Strings\SpeechBubbles)、2026年10月5日取得、https://stardewvalleywiki.com/Modding:Dialogue ↩↩↩ -
Stardew Valley Wiki「Modding:Custom languages」、2026年1月3日最終更新(
UseLatinFont、.fntファイル、推奨の基準値1.5のFontPixelZoom、FontApplyYOffset)、2026年10月5日取得、https://stardewvalleywiki.com/Modding:Custom_languages ↩↩ -
著者による測定、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日取得。 ↩↩↩↩↩ -
著者による測定、2026年10月5日:
measure_apple_docs.py(本記事用の著者の証拠フォルダ内)、出力はmeasure_apple_docs.out:第10節で引用した、保存したAppleのドキュメントの各ページのタイトル、宣言、概要、プラットフォーム、最初の段落。 ↩↩ -
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 ↩
-
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 ↩↩↩
-
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日取得。 ↩↩↩↩↩ -
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 ↩↩
-
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(:) ↩↩ -
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() ↩↩
-
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 ↩↩
-
Apple Developer Documentation「CTFontManagerRegisterFontsForURL(:::)」(iOS 4.1)、2026年10月5日取得、https://developer.apple.com/documentation/coretext/ctfontmanagerregisterfontsforurl(:::) ↩
-
Apple Developer Documentation「interpolation(:)」(Image。iOS 13.0)、2026年10月5日取得、https://developer.apple.com/documentation/swiftui/image/interpolation(:) ↩
-
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:) ↩
-
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(:) ↩
-
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:) ↩↩
-
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 ↩ -
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日取得。 ↩↩↩ -
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:) ↩↩
-
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 ↩
-
Apple Developer Documentation「AccessibilityNotification.Announcement」(iOS 17.0。
accessibilitySpeechAnnouncementPriority属性)、2026年10月5日取得、https://developer.apple.com/documentation/accessibility/accessibilitynotification/announcement ↩↩ -
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 ↩↩
-
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 ↩↩↩↩
-
Apple Developer Documentation「Improving the Accessibility of RealityKit Apps」(「set the properties on each visible
Entityin your scene」)、2026年10月5日取得、https://developer.apple.com/documentation/realitykit/improving-the-accessibility-of-realitykit-apps ↩ -
Apple Developer Documentation「RealityView」(iOS 18.0)、2026年10月5日取得、https://developer.apple.com/documentation/realitykit/realityview ↩
-
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 ↩↩↩
-
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日閲覧。非公開リポジトリ。 ↩↩ -
Kiradexのソース、
server/app/names.py(7行目から14行目、それぞれ20語のFIRSTSとSECONDS、大文字で始まる2語と4桁の数字からなるハンドルのパターン)、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。 ↩↩ -
Blake Crosley「A Pixel Town Alive When Nobody Is Online: NPCs and Ghosts」、blakecrosley.com、2026年10月5日(23行の定型文、うちプレイヤー用14行と町のコレクター用9行、そしてプレイヤー1人あたり2.0秒に1行)、https://blakecrosley.com/blog/pixel-art-town-alive-when-nobody-is-online ↩↩
-
Kiradex、
docs/WORLD.md、第5節「The stack」、524行目と525行目(レート制限:「eight moves a second, a line every two seconds」)、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。 ↩↩ -
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行目、歩行テストがステージのフレームの割合の位置をタップするところ。 ↩↩↩↩ -
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分にはファイルがまた変わっていたが、どちらの関数もそのときは変わっていなかった。 ↩↩ -
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行目に同じ2つのモディファイアがある。Kiradex/でsafeAreaInsetsを検索すると、CardViewerの行だけが見つかった。 ↩↩↩↩↩ -
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 ↩↩↩↩
-
著者による測定、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。 ↩↩↩ -
著者による測定、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)を読む:33機種のiPhone、iPhone 12 miniと13 mini(3倍で1,080×2,340ピクセル、360×780ポイント)からiPhone 16、17、18 Pro Max(440×956ポイント)まで、うち3機種が2倍で、iPhone SE(第2世代と第3世代、375×667ポイント)とiPhone 11(414×896)。これはシミュレータのデバイスのリストであり、Appleの互換性のページではない(そのページは取得していない)。コミット334fbfeのKiradexのプロジェクトファイルからも読み取りのみで:アプリのターゲットの両方の構成でのTARGETED_DEVICE_FAMILY = 1(iPhone)(12時28分の読み取りで1559行目と1705行目)と、プロジェクトの2つの構成でのIPHONEOS_DEPLOYMENT_TARGET = 27.0(1608行目と1670行目)。アプリのターゲットはこれを上書きしていない。 ↩↩↩↩↩↩↩ -
著者による測定、2026年10月5日:
probe_line_start_glyphs.swift(本記事用の著者の証拠フォルダ内)、swiftc -Oでコンパイルし、iOSではなくmacOS 27.0(ビルド26A428)で実行、出力はprobe_line_start_glyphs.out。保存した仕様の付録の表から、JLREQ 3.1.7の10の行頭クラスを読む(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は、項目を数えている)。 ↩↩↩↩ -
著者による測定、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、空白と塗りつぶしのセルは除外し、マスクのダイジェストは8つの行のバイトのSHA-256(左端のピクセルが最上位ビット)。japanese_normal.png、128×512ピクセル:1,024セル、空白430、残り594、異なるもの478。50枚のシート全体:残るセル7,746、異なるダイジェスト1,484。 ↩↩ -
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 ↩↩
-
コミット
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行目(コメント:ステージは1つのアクセシビリティ要素で、「The plaza」とプレイヤーの最後の一歩を値として持ち、場所が重なっているあいだは要素なし)、525行目(content.accessibilityRepresentation {)、526行目(if !covered {)、533行目(.accessibilityLabel("The plaza"))、534行目(.accessibilityValue(trace))。本記事のほかのKiradexの読み取りは10月5日のもので、日付を記している。 ↩ -
Kiradexのソース、
server/app/npcs.py、2026年10月5日太平洋時間12時33分に、コミット334fbfeで読み取り(ファイルの日付は10月3日。非公開リポジトリ):DEFINITIONSの町の4人のコレクター(36行目から58行目)、Moss Keeper 1999、Prism Scout 2024、Comet Pocket 3030、Dawn Sprout 4040で、その行はLINESへのインデックス。それぞれ8から20秒後に最初の行を言い(77行目)、その後は20から40秒ごとに1行(108行目)。server/app/main.pyの231行目は、プレイヤーの行をそのプレイヤーを含む部屋全体に送る(broadcastのexcept_idは未設定。rooms.pyの182行目)、12時33分に読み取り。 ↩↩ -
Kiradexのソース、
KiradexUITests/KiradexUITests.swift、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。ファイルの日付は10時32分で、読み取りのあいだ変化しなかった:228行目(「Me」タブのme-handleがハンドル、2語と数字を保持する)、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」が5以上になるのを待つtestTheTownHasCollectors(672行目から676行目)、そして689行目からのtestThePlazaHasCompany(KIRADEX_PLAZA=1、つまりサーバーがなければスキップされ、スキップのメッセージによればほかのプレイヤーも必要、690行目と691行目。plaza-statusで人数を待ち、「Say」メニューから「Hi!」を選び、吹き出しSaid: Hi!を5秒待つ、714行目)。 ↩↩↩↩ -
Kiradexのソース、
server/requirements.txt(fastapi、uvicorn[standard]、websockets、httpx、pytest、フォントのライブラリはなし)とserver/tests/(test_rooms.py)、2026年10月5日太平洋時間12時28分に、コミット334fbfeにその日のコミット前の編集が加わった状態で、その日編集中だった作業ツリーから読み取り(非公開リポジトリ)。 ↩ -
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 ↩