本指南僅有日文版(它是人寫的散文,這裡不做機械替換)。
Fullseye のほとんどの op は与えられた画像を測る側にいます。この族だけが
反対側にいて、画像を作ります。ただし作るのは「素材」ではなく、
真値が最初から付いている画像です。
3 つの入口があります。
| 入口 | 何が出るか | 何を主張するか |
|---|---|---|
| 錯視(13 op) | 1 枚の rgb | 目が否定する不変量(平行・等長・同値・共線)を、厳密に守っている |
| 無限描画(12 op) | 1 枚の rgb | 絵とは独立に成り立つ恒等式を持っている |
| 循環動画(2 op)+ 状態(3 op) | rgbvideo / table | 継ぎ目が無いことを、構成から保証している |
新しい型語は 1 つも作っていません。返りは既存の rgb(H,W,3)・
rgbvideo(T,H,W,3)・table だけです。「錯視画像」という特別な型を作らない
のが設計の中心で、作った絵に既存の 2,000 以上の op がそのまま掛かることが、
この族の値打ちそのものだからです。
装飾だからではありません。**錯視は「見た目は確かめの役に立たない」ことの、
最も純粋な実例**だからです。
illusion_cafe_wall の目地は厳密な水平線です。illusion_muller_lyer の
2 本の軸は厳密に等長です。illusion_checker_shadow の 2 マスは**浮動小数
として同じ値**です。見えているものが違う、というだけ。
そして面白いのはここから先で —— 錯視は人間の目だけを騙すのではありません。
illusion_muller_lyer の図で「行の黒い区間」を素朴に測ると、2 本の軸は
223 画素と 225 画素になります(矢羽根の反エイリアスの裾が軸の端に乗る)。
素朴な計測も 2 画素ずれる。だからこの族は、測る側の採点表として使えます。
Mermaid 図(ソース):
flowchart LR
G["illusion_cafe_wall<br/>illusion_checker_shadow<br/>…13 op"] -->|rgb| M["既存の計測 op<br/>(threshold / blob / measure1d …)"]
G -->|table| T["illusion_ground_truth<br/>目が否定する不変量"]
M --> C{一致するか}
T --> C
C -->|一致| OK["測る側は目に騙されていない"]
C -->|不一致| NG["測り方の欠陥が見えた"]
perpetual_ten_print(Commodore 64 の一行プログラム)、
perpetual_elementary_ca(Wolfram の 256 規則)、perpetual_langtons_ant、
perpetual_chaos_game、perpetual_apollonian、perpetual_harmonograph、
perpetual_ifs_attractor、perpetual_flow_field、
perpetual_reaction_diffusion、perpetual_plasma、perpetual_truchet ——
どれも「いつ止めても途中」なので、絵の見た目では採点できません。
そこで、絵とは独立に成り立つ主張を持つものだけを入れました。
perpetual_identities が 6 本まとめて実測で返します。
| 系 | 恒等式 | 実測の残差 |
|---|---|---|
| 規則 90 | 第 n 行 = C(n,k) mod 2(リュカの定理) | 0.000e+00 |
| ラングトンの蟻 | 周期 104 で同じ斜め (2,2) を繰り返す | 0.000e+00 |
| アポロニウス | (Σk)² = 2Σk²(デカルトの円定理) | 1.19e-16 |
| 流れ場 | div(curl ψ) = 0(非圧縮) | 6.95e-17 |
| 反応拡散 | 餌も死も 0 なら総量保存 | 1.98e-16 |
| プラズマ | 4 隅は最後まで書き換えられない | 0.000e+00 |
1 枚返す生成器だけでは「無限に描き続ける」ことになりません。だから
状態 → 進める → 描くの 3 本組を置いてあります ——
perpetual_state / perpetual_step / perpetual_render。
op の側は「いつ止めるか」を決めません。
import fullseye as fs
s = fs.ledger.perpetual_state("langtons_ant", size=301)
s = fs.ledger.perpetual_step(s, 12_000) # 好きなだけ回してよい
img = fs.ledger.perpetual_render(s) # 何歩目で描いてもよい
ident = fs.ledger.perpetual_identities() # 絵を見ずに採点する
print(dict(zip(ident["system"], ident["residual"])))
状態の宣言型は table ですが、table は広い型です。無関係な表を渡されたら
KeyError ではなく「これは perpetual の状態ではない」と明示的に拒否します
(fail-closed)。
perpetual_loop は継ぎ目の無い動画を返します。ここは作り方が要点で、
「最後に頭へ戻す」のではありません。時間依存の量をすべて θ の関数にして
θ = 2πt/T を回すと、t = T は t = 0 と同じ式になります —— つまり継ぎ目は
最初から存在しません。編集で消す種類のものではなく、構成から従います。
perpetual_loop_seam がそれを数で出します。読み方に注意が要ります:
> 比が 0 ではなく 1 に近いのが正解。 比 = (最後のまたぎの差) ÷ (ふつうの
> コマ間の差)。1 なら「最後のまたぎが、ほかのまたぎと見分けが付かない」。
> 0 なら継ぎ目が無いのではなく、動きが止まっているという意味になります。
v = fs.ledger.perpetual_loop("plasma_orbit", frames=48, size=360)
m = fs.ledger.perpetual_loop_seam(v)
print(float(m["ratio"][0])) # 0.99〜1.02 なら継ぎ目なし
向く: 測る側の採点(錯視図 + illusion_ground_truth)、尽きない試験入力、
周期境界を持つ時間方向 op の素材、真値つきの合成データ。
向かない: 「作品」としての完成度。この族は主張が検算できる図だけを
持っていて、美しいが検算できない図は入れていません
(illusion_ebbinghaus / illusion_zollner / illusion_ponzo /
illusion_poggendorff / illusion_fraser_spiral / illusion_kanizsa /
illusion_hermann_grid / illusion_scintillating_grid /
illusion_simultaneous_contrast はいずれも不変量を持ちます)。
どれも絵を見ても気づけない型でした。
1. 二項係数を int64 で積んでいて、C(62,31)×31 で黙って溢れた。
リュカの定理「C(n,k) が奇 ⟺ (n&k)==k」に置き換えて、桁あふれの余地を消した。
2. illusion_checker_shadow で対にするマスを間違えていた(同じ明暗のマスを
2 つ選んでいた。差 0.31)。対にすべきは「影の外の暗マス」と「影の中の明マス」。
3. 定数の配列でも std() は厳密に 0 になりません(平均を引く途中で 1 ulp 残る、
実測 5.6e-17)。「全部同じ値」を厳密に言うなら ptp(最大 − 最小)を使います。