
2026/04/07 4:25
**Show HN: TTF‑DOOM – TrueType フォントヒンティング内で実行されるレイキャスター**
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
プロジェクトは、ほんの小さなTrueTypeフォントファイル(約6.5 KB)が完全なDoomスタイルのレイキャスティングエンジンを埋め込み、3DグラフィックスをブラウザまたはPygameで直接レンダリングできることを示しています。
グリフ「A」には16本の縦棒輪郭が含まれており、その位置はSCFS命令によって各フレームごとにシフトされ、16×16タイルマップの遠近法ビューを生成します。JavaScriptは
(軸 MOVX、MOVY、TURN)を通じてプレイヤーの移動・敵・射撃を提供し、ブラウザは毎フレームグリフを再ヒントして壁描画を行います。font-variation-settingsカスタムDSLコンパイラは、簡略化されたCライク言語をTrueTypeヒントアセンブリ(FDEF、CALL、RS、WS、SCFS など)に変換し、正弦/余弦ルックアップテーブルとマップデータを .ttf ファイルに注入します。TrueType の
がMULを実行するため、コードは(a*b)/64後にDIV(a,1)を使用して真の乗算を達成します。ループは再帰的な FDEF 呼び出しとしてコンパイルされ(WHILE 命令は存在せず)、FreeType の約64フレーム呼び出しスタック制限に依存しています;レイキャスターの16列 × 14ステップはこの境界内に収まります。MULSCFS はピクセル座標で動作し、フォント単位ではありません。また、Chrome はヒント付けされたグリフをキャッシュする場合があり、変化軸の変更時に再ヒントがスキップされることがあります;毎フレーム揺れ(ジャイター)を追加して新鮮なヒント付けを強制します。
デモはローカルで実行できます (
)、フォントをビルドし、ブラウザまたはPygameホストのいずれかを起動します。操作は WASD が移動、矢印キーが回転、Space が射撃です;Tab を押すと実時間変化軸(MOVX、MOVY、TURN)のデバッグオーバーレイが表示されます。git clone https://github.com/4RH1T3CT0R7/ttf-doom.gitコードベースは
、compiler/、fontgen/に整理され、ブラウザおよび Python ホスト、テスト、および最終フォントgame/が含まれています。Apple の1991年版 TrueType ヒントバイトコードを使用し、Harfbuzz の WASM シェイパー(doom.ttfでのような)ではなくしています。プロジェクトは Apache 2.0 ライセンスで公開されています。llama.ttfこの作品は、非常に小さなフォントファイル内で複雑な3Dレンダリングを実現できることを示し、パフォーマンスクリティカルなグラフィックスや難読化の可能性を開き、TrueType エンジン間の差異が将来のウェブレンダリング戦略に影響を与える可能性があることを強調しています。
本文
TrueType フォントのヒンティングプログラム内で動作する DOOM‑スタイルのレイキャスター
これは何か?
TrueType フォントにはグリッドフィッティング(文字をピクセルに合わせる)用の仮想マシンが組み込まれています。
スタック、ストレージスロット、算術演算、条件分岐、関数呼び出しなどを備えており、実際にはチューリング完全です。私はこの VM を使って 3D グラフィックスを描画できるか試してみました。
フォントファイルに TrueType バイトコードで書かれた DDA レイキャスティングエンジンが埋め込まれています。
文字「A」は 16 本の垂直棒(縦線)を持ち、ヒンティングプログラムは 16 × 16 のタイルマップに対してレイキャストし、SCFS 命令でそれらの棒を再配置して 3‑D パースペクティブビューを作ります。全体は 6.5 KB に収まっています。
JavaScript が移動・敵・射撃を制御し、座標情報を
font-variation-settings 経由でフォントに渡します。フォントがレイキャストと壁描画を行い、Canvas 上に敵や武器、HUD を重ねて表示します。
動作原理
私はカスタム DSL(ドメイン固有言語)から TrueType ヒンティングアセンブリを出力する小さなコンパイラを書きました。
DSL は簡易 C のように見えます:
func raycast(col: int) -> int { var ra: int = player_angle + col * 3 - FOV_HALF; var dx: int = get_cos(ra); var dy: int = get_sin(ra); … }
コンパイラは TT バイトコード(
FDEF, CALL, RS, WS, SCFS 等)を生成し、sin/cos ルックアップテーブルとマップデータと共に .ttf ファイルへ挿入します。変数割り当ては TT ストレージスロット全てで行い、関数定義は
FDEF/ENDF として出力されます。また固定小数点(fixed‑point)演算もコンパイラが処理します。
パイプライン
doom.doom → lexer → parser → codegen → doom.ttf
TrueType の算術
-
はMUL
を計算します。(a*b)/64
内部では F26Dot6 固定小数点が使われるため、
となります。1 * 4 = 0
私は 2 日かけて
がDIV(a, 1)
を返すことを突き止め、その後a*64
と解決しました。MUL(a*64, b) = (a*64*b)/64 = a*b -
命令はありません。ループは再帰的なWHILE
にコンパイルされ、FreeType は呼び出しスタックを約 64 フレームに制限します。FDEF
16 列 × 14 ステップのレイはぎりぎり収まります。 -
再帰ループ内で
を使っても即座に抜けるわけではなく、値をプッシュして再帰が続くため、ヒットフラグで制御し直しました。return -
は F26Dot6 ピクセル座標(フォント単位ではなく)を受け取ります。これが原因で棒が極端に小さくなる問題を解決できました。SCFS
Chrome はヒンティングされたグリフをキャッシュし、軸が変わった際に再ヒンティングをスキップすることがあります。各フレームで少しずつ座標を揺らすことで回避しました。
が Y を選択し、SVTCA[0]
が X を選択します。SVTCA[1]
アーキテクチャ
- フォントが壁(レイキャスト結果)を描画します。
- JavaScript はそれ以外すべてを担当:プレイヤー位置/角度 → 3 つのフォント変動軸 (
,MOVX
,MOVY
) を更新し、フレームごとにブラウザがグリフを再ヒンティングして形状を変更します。TURN
デモで Tab キーを押すと、リアルタイムでフォント変動軸が表示されるデバッグオーバーレイが出ます。
使い方
git clone https://github.com/4RH1T3CT0R7/ttf-doom.git cd ttf-doom pip install fonttools freetype-py pygame pytest python game/build.py python -m http.server 8765
Chrome または Edge で
http://localhost:8765/hosts/browser/index.html を開きます。WASD で移動、矢印キーで向きを変え、Space で発射します。デバッグオーバーレイは Tab キー。
プロジェクト構成
| ディレクトリ | 用途 |
|---|---|
| DSL → TrueType アセンブリ(字句解析・構文解析・コード生成) |
| フォントビルダー、グリフ生成、sin/cos テーブル |
| Raycaster ソース () とビルドスクリプト |
| ブラウザデモ |
| Pygame ホスト(開発用) |
| 451 のテストケース |
| 実際にプレイできるフォント (6,580 バイト) |
比較
- llama.ttf はフォント内で計算を行いますが、HarfBuzz の WASM シェイパー(WebAssembly ランタイム)と連携しています。
- TTF‑DOOM は 1991 年に Apple が配布した TrueType ヒンティングバイトコード(グリッドフィッティング用 VM)をそのまま使用しており、全く別の仮想マシンです。
ライセンス
Apache 2.0