レジストリ
ビルダーページは /ja/6502/builders にある。
games.tinymachines.ai/builders でも今も応答する。何も止めていないからだ。
ここにある唯一の状態持ちであり、その境界こそが要点だ。 チップは手つかず:
すべてのリクエストは今もマシン全体を運び、公開された ROM を走らせることは今もそれを POST することだ。保存されるのはカタログ。SQLite ファイル一つ
(REGISTRY_DB)、一つの物に一行。
独立のサービスではない。カートリッジ鋳造所、コンソール仕様、レジストリは 同じ FastAPI アプリのルートだ。Python のサービスは一つであって、二つ ではない。
トークン
サインアップは無い。トークンは手で鋳造され、手渡され、ハンドルを取得する。一つのトークンに一人のビルダー。
python3 service/registry_admin.py mint --note "誰のためか" # 表示は一度きり
python3 service/registry_admin.py tokens
python3 service/registry_admin.py builders
python3 service/registry_admin.py revoke <token-or-hash>
python3 service/registry_admin.py grant <token> <handle> <name> # 予約名
これが今のところ認証の話のすべてであり、意図的にそうしてある。設計ではなく制約だ。
正しくできているのは、後から変えると痛い部分だ: トークンは一度だけ表示され、保存されるのは SHA-256 だけ。だからデータベースの写しは、全員の資格情報の写しにはならない。
このビルダーのものでないトークンには 403 ではなく 404: そのビルダーが存在するかどうかを知る筋合いが無い。失効はページと ROM に手を触れない。失効は資格情報についての操作だからだ。
残りを形づくる三つの規則
レジストリは信じる代わりに測る。 カートリッジは誰かが編集できるファイル
なので、その verify ブロックは作者による主張だ。公開時にカートリッジは展開され、ここで走らされる。横に印字されるサイズ、タイル数、フレーム費用は、その実行が作った値だ。フレームを完了しない ROM は、掲載される代わりに拒まれる。
テストは 12 半サイクルのフレームを主張するカートリッジを公開し、保存される数字が実測値であることを要求する。ページが自分のリクエストを読み返すとどうなるかはカートリッジを参照。
アートは常に '0'..'3' の行でしかない。 写真の変換はブラウザで起きる
ので、リクエスト経路に画像パーサは無く、ディスクに着地するのは CHR だ:
スプライトシートと同じエンコーディングなので、ビルダーページの肖像はゲームを描くのと同じ decodeCHR が描く。
PATCH は名指ししたものにしか触れない。 だから bio を保存するクライアント が、読み込んでもいないアバターを消すことはできない。
ページ
ページは /b/<handle> で、そこに載る ROM は /b/<handle>/<slug>。後者はそのカートリッジを読み込み済みのコンソールだ。どちらも自分のパスを読む静的文書で、nginx が引用符付きの正規表現ロケーションを builder.html と
index.html に向ける。だから公開された ROM はクエリ文字列ではなく、自分のアドレスを持つ。
正規表現に引用符が付いているのは、nginx が { をブロックの開始として読むからだ。{2,32} を含むロケーション正規表現は、パターンの真ん中を名指しした "unknown directive" で落ちる。
/ja/6502/manage がエディタだ: トークンを貼り、ページを編集し、.cart.gz を公開する。
いまは両方の半分が apex の下にある
読む半分。/ja/6502/builders がこの索引で、
/6502/builders/<handle> がページだ。/6502/b/<handle> はサービス自身が配るアドレスで、そこへリダイレクトする。あのリンクは壊してよいものではないからだ。どちらも同じ生きたレジストリを CORS 越しに読む。あのサービスの
Access-Control-Allow-Origin: * はそのためにあった。
書く半分は 2026-08-24 に続いた。それを堰き止めていたものは記録しておく価値がある。決定に見えて、ヘッダだったからだ。tinymachines.ai からのプリフライトはかつて 4 つのヘッダを受け付けて返し、その中に Authorization
は無かった。だから apex のブラウザはあのサービスに bearer トークンを一切送れなかった: ハンドルの取得も、ページの編集も、公開も。リクエストはそもそも送信されず、サーバのログには見るべきものが何も無かった。
tinymachines/6502#12 がそれを記録している。いまサービスの CORS ポリシーは
authorization とレジストリ自身の動詞 (PATCH、PUT、DELETE) を許し、
エディタはそれを相手にここで動いている。
書き込みを apex 自身の API 経由でプロキシしても動きはする。サーバ二つの間にブラウザは居らず、したがってプリフライトも無いからだ。それは意図的にやっていない。読み取り専用のサービススコープとアイデンティティの紐付けは、 games が apex の下に移れば内部の結合になる、という理由で一覧の仕事から外されたのであって、いま資格情報付きプロキシを建てることは、同じ境界を二度建てることになる。
ディザリングは選ばれたのではなく、測られた
写真はブラウザ内 (art.js) でダイの 4 色に変換され、画像としてではなく
'0'..'3' のグリッドとしてアップロードされる。
ディザリングはフロイド・スタインバーグを輝度ではなく RGB で行う。これも実測だ。Rec.709 で測るとパレットは 17、130、169、169。つまりポリシリコンとメタルは 255 分の 0.2 以内で同じ明るさで、違うのは色相だけだ。輝度の傾斜は 4 段ではなく 3 段になり、パレットの暖色の半分を捨ててしまう。
深さ 2 でしか現れなかったバグ
index.html は game.js を相対 src で読み込んでいた。/ で配られればそれは /game.js で、/b/tinymachines/die-runner で配られれば
/b/tinymachines/game.js、つまり 404 だ。
ページはそれでも描画された。 マークアップは静的で、欠けたのは JavaScript だけだったから、スクリプトが取得されなかったコンソールではなく、起動に失敗したコンソールに見えた。
いま文書の参照は絶対であり、game.js の中の二つの fetch はページではなく
import.meta.url に対して解決される。wasm のグルーと同じ手だ。ページを読んで見つけたのではなく、実物のページを走らせて見つけた。