【西遊記デバッグ】なぜ孫悟空は世界の果てでガタガタ震え出し、五行山に押し潰されるに至ったのか?
プロローグ:大雷音寺の決闘
西方、霊山大雷音寺。
天界で暴れ回り、十万の天兵さえ退けた石猿・孫悟空が、ついに釈迦如来の御前に引き据えられた。
悟空は如来を見上げ、傲然と言い放った。
「やい如来! 俺様は斉天大聖だ。変化の術も使えるし、觔斗雲で十万八千里も飛べる。 弱っちい天帝を追い出して、俺様にその位を寄越せと言ってるんだ!」
釈迦如来は、慈悲と威厳を湛えた眼差しで猿を見下ろし、静かに、しかし雷の如き声で痛罵した。
「増上慢の畜生め。人間界で修行しただけの猿風情が、無限のクロックサイクルを生きる天帝の位を奪おうとは。……よい、ならばその力を見せてみよ」
如来は右手を差し出した。
「朕の右の手のひらから飛び出すことができたなら、望み通り天帝の座をくれてやろう。だができねば、下界へ戻り修行し直すがよい」
「へっ、たかが手っぺら一つ! 俺様の觔斗雲なら一飛びよ!」悟空は鼻を鳴らし、飛翔の構えを取った。
如来は背後に控える見習い僧アーナンダに目配せをした。
「アーナンダよ。汝に法力『SinCos』の実装を命ずる。この猿を制御し、我が掌の理を教え込むのだ。」
「ぎょ、御意!法界の構築完了。直ちに変数の宣言と初期化に入ります!」
【コラム】智慧第一・サーリプッタの「SinCos」補講──回転とは、情報の変換である
我らが同胞、アーナンダが如来のO J Tにて脂汗を流しているようだが、見かねたので少し補足しておこう。私はサーリプッタ。釈尊の弟子にして、教団のシステムアーキテクチャ担当である。
さて、其の方らは 「SinCos ライブラリ」を、単なる「波を作る道具」や「三角形の辺を測る定規」だと思ってはいないか?
このライブラリは 「一次元の『量』を、二次元の『方向』に変換する法輪」である。
計算機の世界を見よ。時間は 0 から MAX_INT へと一直線に進む。メモリ番地も、配列のインデックスも、すべては直線(リニア)だ。
このままでは、世界は発散し、メモリはいずれ枯渇し、熱的死を迎える。これが「直線の苦しみ」である。
だが、釈迦の説く法、そして我々の住む物理世界はそうではない。季節は巡り、星は軌道を描き、因果は応報する。これらはすべて 「円環(サイクル)」 の理で動いている。
では、直線の理しか持たないコンピュータの中に、どうやって円環の理を持ち込むのか?それが、Sin と Cos という名の界面(インターフェース) だ。
入力: Angle(ラジアン)。これは「直線の上の距離、時間、そして量」だ。
変換: ライブラリ内部で、この直線を円に巻きつける。すなわち 「法輪を回す(転法輪)」 のだ。
出力: Vector(座標)。これにより、直線上の点は「円周上の位置」という、永遠に循環する意味を獲得する。
つまり、SinCosライブラリとは、「直線の迷い」を断ち切り、世界を「法輪の回転」へと接続する、聖なる通訳者 なのだ。
これからアーナンダが繰り出す数々の失敗は、この「翻訳」の厳密さを甘く見たからに他ならない。通貨(単位)を間違えれば価値が変わるように、型(セマンティクス)を間違えれば法輪は回らず、世界は崩壊するだろう。
ゆめゆめ忘れるな。我々はコードを書くとき、単に計算させているのではない。直線という不自由な檻の中に、回転という自由な魂を吹き込んでいる のだということを。

Unit 0:法界の定義と型システム──混同を防ぐための思考モデル
混沌としたメモリ空間
霊山大雷音寺のシステム管理室。アーナンダは震える手でエディタを開き、コードを記述し始めた。
釈迦如来がモニターに映し出された孫悟空の挙動を見つめ、静かに眉をひそめている。
悟空は座標 $${(0, 0)}$$ でビクンと痙攣したかと思うと、次の瞬間にはありえない速度で視界が明滅し、数メートル先へワープを繰り返した。
「あががが! 足が! 勝手に! 痺れるゥゥゥ! 如来が金縛りの術でもかけやがったか!」
「アーナンダよ」釈迦の声が響いた。
「見よ。あの哀れな猿を。其の方が定義した変数は、あまりにも無秩序である」
アーナンダは冷や汗を流しながら答えた。
「申し訳ございません、世尊。すべて float 型で宣言いたしましたゆえ、回転量も、座標も、速度も、すべて同じ器に入っております」
「愚かなり。コンパイラにとっては同じ32ビットの浮動小数点数であろうとも、セマンティクスにおいては天地ほどの隔たりがある。回転に座標を足せば、理は崩壊し、猿は因果の彼方へ吹き飛ぶであろう」
「……ご教示ください、世尊。私はどのように世界を記述すべきでしょうか」
「心せよ。幾何学の法界を構築するには、まず三つの『型(Type)』を厳密に分別せねばならぬ」
第一の法:Angle(回転の理)「円環を巡るもの」
釈迦は空中に円を描いた。
「第一の型は Angle。これは回転や周期を表す量である」
「単位は『度(Degree)』でしょうか?」アーナンダが問うた。
「否。それは人間界の俗な単位である。天界においては、『ラジアン(Radian)』 こそが真の言葉。半径1の円において、ラジアンは弧の長さがそのまま角度の値にするため、微分や積分の計算を劇的に単純化する、美しき単位よ」
「この型には 『周期性』 という業がある。値が $${2\pi}$$(約6.28)増えるたびに、意味は元の状態に戻る。これは、直線的に増え続ける距離や重さとは、決定的に異なる性質である」
「ゆめゆめ、距離と角度を足し算するでない。それは『天竺までの距離』と『今朝の読経の回数』を足すようなもの。全くのナンセンスである」
第二の法:Dir(指し示す理)「長さを持たぬ、純粋な意志」
次に釈迦は、虚空を指差した。 「第二の型は Dir(方向ベクトル)。これは『向き』だけを表す」
「構造としては $${x, y}$$ を持つ Vector2 ですが……」
「形に惑わされるな。ここには強力な戒律がある。『長さは常に 1.0 でなければならぬ』 という不変条件だ」
「長さが1、でございますか?」
「左様。其の方が『あちらを見よ』と指差すとき、腕の長さは重要か? 否、重要なのは向きのみ。 もし長さが 1.2 のベクトルを Dir (向き)として扱えばどうなるか。そのベクトルを基底として悟空を動かせば、悟空は意図したより 1.2倍 速く動き、世界は 1.2倍 に膨張するであろう」
「なんという恐ろしき……」
「計算の過程で長さがズレたならば、直ちに 『正規化(Normalize)』 の法力を用い、長さを1に戻すのだ。それがこの型の運用ルールである」
第三の法:Vec(実存の理)「物理的な実体を持つ量」
「最後、第三の型は Vec(一般ベクトル)。これは空間上の『位置』や、物体が持つ『速度』などを表す」
「これには制約はないのですか?」
「うむ。位置は原点からどれだけ離れていてもよい。速度はどれだけ速くても構わぬ。 多くの場合、この Vec は、先の Dir(向き)とスカラー量(大きさ)の積として表される」
$$
\text{Velocity} = \text{Speed} \times \text{Direction}
$$
「なるほど。純粋な『向き』に、現世の『量』を掛け合わせることで、物理的な実体が生まれるのですね」
実地試験:45度の悟り(Hello World)
「ではアーナンダよ。これら三つの法が正しく実装されているか、悟空を用いてテストを行う」 釈迦はモニターの中の悟空をリセットした。
「この猿に『右斜め上(45度)』を向かせ、その状態から再び『角度』を読み取ってみよ。 入力した角度と、読み取った角度が一致すれば、其の方の脳内に『行って戻る回路』が完成した証左となる」
ステップ 1: 俗世の言葉を翻訳する
アーナンダはキーボードに向かった。 「まずは人間界の言葉『45度』を定義し、それを天界の言葉『ラジアン』に翻訳します」
// 俗世の言葉「45.0」
float angle_deg = 45.0;
// 天界の言葉に翻訳(deg2rad)
// PI / 180.0 は、二つの世界をつなぐ経典(係数)です
float angle_rad = angle_deg * (PI / 180.0);
// これで angle_rad は約 0.7853... となりました
// 悟空にはこの数値が「右斜め上」として伝わります
ステップ 2: 角度を意志に変える(SinCosの顕現)
「次に、我が法力 SinCos を用い、実体のない『角度』を、具体的な『方向(Dir)』に物質化させます」
// fromAngle: 角度から方向ベクトルを作る秘術
// x成分に cos, y成分に sin を宿らせます
Vector2 dir;
dir.x = cos(angle_rad);
dir.y = sin(angle_rad);
モニターの中の悟空が、カッと目を見開いた。
「ぬ? なんだ? 体が……勝手に右斜め上へ向きやがる! 止まれ! 俺様はあっちへ行きたくねえ!」
悟空は必死に地面を踏ん張るが、見えない万力で首ごと捻り上げられるように、強制的に45度方向へ固定されていく。
「釈迦如来、何をしやがった! 金縛りか!?」
「世尊、ご覧ください。悟空は必死に抵抗しておりますが、その身体は cos と sin によって完全に制御されております」
「左様。法力とは、対象の意志に関わらず物理法則を書き換える絶対の理。 $${x}$$ と $${y}$$ の値は共に 0.7071...。 その長さは $${\sqrt{0.7071^2 + 0.7071^2} = 1.0}$$。 正しく Dir 型の戒律(長さ1)を満たしており、悟空はいかなる怪力を用いてもこの向きから逃れることはできぬ」
ステップ 3: 意志から角度を読み解く(逆変換)
「よろしい。では最後に、その強制固定された悟空の向き(ベクトル)から、元の角度を復元してみよ。 正しく制御できているかを確認するための、単なる検算である」
// toAngle: 方向ベクトルから角度を読み解く逆変換
// ここでは atan2(y, x) という両眼を開く法を使います
float recovered_angle_rad = atan2(dir.y, dir.x);
// recovered_angle_rad は 0.7853... になりました
// 翻訳ステップで作ったラジアン値と一致しています
ステップ 4: 答え合わせ
「最後に、天界の言葉を俗世の言葉に戻し、検算を行います」
// ラジアンを度に戻して確認
float recovered_angle_deg = recovered_angle_rad * (180.0 / PI);
// 結果: 45.0
「おお……! 45度に戻りました! 45度から出発し、ラジアンの海を渡り、ベクトルの森を抜け、無事に45度へ帰還いたしました!」
釈迦は満足げに頷いた。
「見事だアーナンダ。 その『行って戻ってくる回路』こそが、幾何学を自在に操るためのパスポートである。 以後、変数を宣言するときは、常にこの三つの型のいずれであるかを意識し、決して混同することなかれ」
「謹んで承ります、世尊!」
こうして、システム管理室のモニターには、悲鳴を上げながらも一ミリたりともズレることなく、美しく45度を向き続ける孫悟空の姿があった。

Unit 1:ラジアンと単位の戒律──なぜコンピュータは「度」を嫌うのか
右を向かない猿
霊山大雷音寺、システム管理室。
Unit 0 の研修を終えたアーナンダは、次なる課題に取り組んでいた。
「アーナンダよ」釈迦如来がモニターを指差した。
「次は、この猿の姿勢制御を行う。悟空を『右(90度)』に向けよ」
「御意、世尊。それしきのこと、造作もございません」
アーナンダは自信満々にキーボードを叩いた。右といえば直角、直角といえば90度である。
// アーナンダのコード
// 悟空よ、右を向け(意図:90度)
float angle = 90.0;
// 向きを計算して適用
goku.direction = fromAngle(angle);
【悟空の視点】
「お? なんだ? 釈迦の如来がこっち見ろってか? へいへい、右だな……」
悟空が首を巡らそうとした瞬間、視界が猛烈な勢いで回転した。
「ぬおおお!? 止まらねぇ! 首が! 首がねじ切れるゥゥゥ!」
悟空はその場でコマのように高速回転し、目を回して地面に激突した。最後に止まったとき、その顔は右(真横)ではなく、斜め右下(約25度) の中途半端な方向を向いて泡を吹いていた。
「な、なんと……!?」
アーナンダは狼狽した。「90度を指定しました! なぜ悟空はあのように荒ぶり、そしてあさっての方向を向いているのですか!」
釈迦は静かに告げた。「デバッガを見よ。cos(90.0) の値は何か」
「はっ…… -0.448... と出ております。0ではありません」
「左様。其の方は『半径1メートルの円周上を、90メートル走れ』と命令したのだ。それは周回コースを14周して、さらに1.7メートルほど進んだ地点である」
戒律:度数法(Degree)の否定
「世尊、納得がいきません!人間界では直角といえば90度。なぜ法界は、これほど分かりにくい数値を要求するのですか?」
釈迦は威厳を持って答えた。
「360という数字は、古代バビロニアの人間が『1年がだいたい360日だから』という理由で決めた、俗世の定数に過ぎぬ。
対して、法界が使う 『ラジアン(Radian)』 は、宇宙の物理法則そのものから導き出された真理の単位である」
理由A: 「変化のコスト」をゼロにする(数学的理由)
「アーナンダよ、物理シミュレーションの真髄は『変化(微分)』にある。 角度の単位がラジアンであれば、三角関数の変化率は驚くほど美しくなる」
$$
\frac{d}{dx} \sin(x) = \cos(x)
$$
「サインの変化率はコサインになる。係数は何もつかぬ。1対1の完全なる対応である。 しかし、もしこれが俗世の『度数法』であったなら、どうなるか?」
$$
\frac{d}{dx} \sin(x^\circ) = \frac{\pi}{180} \cos(x^\circ) \approx 0.01745 \cos(x^\circ)
$$
「微分するたびに、$${\frac{\pi}{180}}$$ という面倒な『消費税』のごとき係数が飛び出してくる。
もし物理エンジンが度数法で作られておれば、あらゆる運動方程式にこの $${0.01745...}$$ がこびりつき、計算は複雑怪奇になり、バグの温床となるであろう」
「なんと……計算のたびに税金を取られるとは……」
「ラジアンを採用することは、この税を免除し、計算コストを最小化するための慈悲なのだ」
理由B: シリコンの負担を減らす(実装的理由)
「さらに、ハードウェア(シリコン)の叫びを聞くがよい。 CPUやGPUの内部で sin(x) を計算するとき、彼らは円を描いているわけではない。「マクローリン展開(テイラー展開)」と呼ばれる近似を使って、多項式の足し算と掛け算だけで値を求めているのだ」
$$
\sin(x) \approx x - \frac{x^3}{6} + \frac{x^5}{120} - \dots
$$
「この美しき近似式は、$${x}$$ がラジアンであるという前提条件の上でのみ 成立する。 もし度数法を使うなら、入力された $${x}$$ をまず $${\frac{\pi}{180}}$$ 倍して変換せねばならぬ。 世界中の何億台というデバイスで、毎秒何兆回と実行される計算において、毎回『360』という人間都合の数字のために変換コストを払うのは、計算資源の浪費である」
「……恐れ入りました。360度という数字が、これほど計算機を苦しめていたとは」
実践:通貨の両替(Unit Conversion)
「では世尊、私は人間界の『度』と、法界の『ラジアン』をどう共存させればよいのでしょうか」
「『通貨の両替』 と同じと思え。 人間界のUI(入力画面)や設定ファイルは『人間の国』ゆえ、度数法を使ってよい。 だが、データを読み込んだ瞬間に、『関所(変換層)』 を設けよ」
アーキテクチャ・ルール:国境で変換せよ
UI層(入国審査): ユーザーが「90度」と入力する場所。ここは度数法でよい。
変換の壁(両替所): データを読み込んだ瞬間に、deg2rad() の法力をもってラジアンに変換せよ。 これより内側では、一切『度』という概念を持ち込んではならぬ。 変数名に _rad をつけるなどして、厳格に管理せよ。
ロジック層(機械の国): 移動、回転、当たり判定。ここは完全なる「ラジアン」の浄土である。
レンダリング層(出国審査): デバッグ表示や画面出力のときだけ、rad2deg() で人間に読める形に戻してやるがよい。
「悪い例を見せよう」釈迦は空中におぞましいコードを投影した。
❌ 破戒のコード(計算のたびに両替している)
void update_goku() {
// 内部状態として「度」を持ってしまっている
// 毎フレーム計算のたびに変換コスト(税金)が発生!
float rad = deg2rad(goku.angle_deg);
goku.x += cos(rad);
// 他の計算でもまた変換... 徳が下がっていく...
}
⭕ 悟りのコード(入国時に両替済み)
void init_goku() {
// 読み込み時に一度だけ変換
goku.angle = deg2rad(config.start_angle_deg);
}
void update_goku() {
// ロジック内はすべてラジアンで統一。変換コストゼロ。
goku.x += cos(goku.angle);
}
天界のランドマーク
「最後に、ラジアンを直感的に扱うための智慧を授けよう。 毎回 $${3.14...}$$ を計算する必要はない。円周上の主要な地点を『定数』として頭に刻むのだ」
TAU ($${2\pi \approx 6.28}$$): 一周。
輪廻転生。プログラム上で angle > TAU となったら、一周回って元に戻ったことを意味する。
PI ($${\pi \approx 3.14}$$): 半周(180度)。
「後ろを向く」という操作は、angle + PI である。
HALF_PI ($${\pi/2 \approx 1.57}$$): 直角(90度)。
$${(0, 1)}$$ つまり $${y}$$ 軸方向(真上)である。
QUARTER_PI ($${\pi/4 \approx 0.785}$$): 斜め45度。
$${(0.707, 0.707)}$$ 方向である。
「90度と言いたいとき、脳内で瞬時に HALF_PI という定数が浮かぶようになれば、其の方はもうバイリンガルである」
アーナンダは深く合掌した。
「よく分かりました世尊。これより直ちにコードを修正し、悟空の目を回さぬようにいたします」
// 修正後のコード
float angle = 90.0 * DEG_TO_RAD; // HALF_PI に変換
goku.direction = fromAngle(angle);
モニターの中の悟空は、今度こそビシッと真右(X軸方向)を向き、満足げに鼻を鳴らした。
「おう、やっと止まったか。やれやれ、如来も人使いが荒いぜ」

Unit 2:回転ロジックと精度維持──変数の独立性と浮動小数点の罠
回転の試練(スパイラル・バグ)
霊山大雷音寺、システム管理室。
Unit 1 で角度の単位を学んだアーナンダは、次なるステップとして「回転のアニメーション」に挑んでいた。
「アーナンダよ」釈迦如来が告げた。
「次は、この猿をその場で回転させ続けよ。永遠に回り続ける円環の理を実装するのだ」
「御意、世尊。回転の公式は予習済みでございます」
アーナンダは自信を持ってコードを記述した。
// アーナンダのコード
// 毎フレーム、悟空を少しずつ回転させる
void update_goku() {
float t = deg2rad(1.0); // 1度ずつ回す
// 公式通りに実装
goku.x = goku.x * cos(t) - goku.y * sin(t);
goku.y = goku.x * sin(t) + goku.y * cos(t); // 事故発生地点
}
「けっ、今度は何だ? ぐるぐる回るだけか? 楽勝だぜ!」
悟空はその場でくるくると回転を始めた。最初は順調に見えた。
しかし、数秒後、悟空の体に異変が起きた。
「お? なんか体が……引き伸ばされる気がするぞ?」
回転するにつれて、悟空の身体は斜めにひしゃげ、飴細工のように薄く、長くなっていく。
「おい! 止まれ! 俺様の金剛不壊の体が! ペラペラになっちまうゥゥゥ!」
「な、なんと……!?」
アーナンダは悲鳴を上げた。「公式通りに記述しました! なぜ悟空はあのように歪み、次元の隙間に挟まったような姿になるのですか!」
釈迦は静かにモニターを指差した。「アーナンダよ。其の方は『因果(時間)』を軽んじている」
第一の魔物:ロジックの汚染(変数の上書き)
「因果、でございますか?」
「左様。コードの1行目を見るがよい」
goku.x = goku.x * cos(t) - goku.y * sin(t);
「ここで goku.x は、回転後の新しい値に書き換えられた。すなわち『未来のX』になったのだ」「はい、その通りです」
「では、2行目を見よ」
goku.y = goku.x * sin(t) + goku.y * cos(t);
「ここで使われている goku.x は、『過去のX』か? それとも『未来のX』か?」
アーナンダはハッとした。「ああっ! 1行目で書き換えたばかりの『未来のX』を使っています!」
「その通り。回転の理とは、過去のXとYを用いて、同時に未来の姿を導くこと。
未来のXと過去のYを混ぜて計算すれば、それは回転ではなく 『せん断(Shear)』 という別の変形になる。ゆえに猿はペラペラになったのだ」
解決策:時を止める(一時変数)
「では世尊、いかがすれば……」
「『一時変数(Temp Variable)』 という仮の器を用意せよ。 過去の姿をそこに退避させ、因果が混線せぬように計らうのだ」
// 修正後のコード
void update_goku() {
float t = deg2rad(1.0);
// 過去の姿を仮の器に退避(Backup)
float old_x = goku.x;
float old_y = goku.y;
// 退避させた「過去の値」を使って計算
goku.x = old_x * cos(t) - old_y * sin(t);
goku.y = old_x * sin(t) + old_y * cos(t);
}
悟空の体は元の形に戻り、再び綺麗な円を描いて回り始めた。
「ふぅ……危ないところだったぜ。今度こそ完璧だな?」
第二の魔物:無限の彼方へ散る誤差
悟空は回り続けた。1分、10分、1時間……。
アーナンダは安心して休憩を取ろうとしたが、釈迦はモニターから目を離さなかった。
「まだ終わっておらぬ。見よ」
モニターの中の悟空が、またしてもおかしくなっていた。今度は歪んでいるのではない。 太っているのだ。
回転するたびに、悟空の体はわずかに、しかし確実に巨大化していた。最初は微々たる変化だったが、数万回回った今、悟空はパンパンに膨れ上がり、画面からはみ出そうとしていた。
「うおおお! 体が重い! 俺様は何も食ってねえぞ! なんで太るんだァァァ!」
「せ、世尊! ロジックは完璧なはず! なぜ悟空は膨張するのですか!」
釈迦は厳かに告げた。
「これはアーナンダの過ちではない。計算機という箱庭の宿命(限界) である」
浮動小数点の業
「此岸には、真の『数』は存在せぬ。あるのは有限のビットで近似された『浮動小数点数』のみ。 ゆえに、cos や sin の値、そして掛け算の結果には、必ず$${10^{-7}}$$ 程度の微小な 『丸め誤差(Rounding Error)』 が付着する」
「誤差……でございますか」
「たった一回の計算ならば、その汚れは目に見えぬ。だが、其の方はループの中で、その汚れを何万回も積み重ねた。たとえ $${1.000001}$$ 倍の拡大であっても、20万回繰り返せば $${1.2}$$ 倍となり、100万回繰り返せば世界を飲み込む巨体となる。これを 『誤差の蓄積』 と呼ぶ」
実践:不変条件の調律(Normalize)
「なんと恐ろしい……。計算を続ける限り、誤差からは逃れられぬということですか。悟空は破裂する運命にあると?」
「否。逃れられぬなら、浄化すればよい。アーナンダよ。Dir 型(方向ベクトル)における絶対の戒律を思い出せ」
「はっ! 『長さ(ノルム)は常に 1.0 でなければならぬ』 でございます!」
「左様。計算によって長さが $${1.000001}$$ に歪んだのなら、それを強制的に $${1.0}$$ に引き戻せばよい。 これを 『正規化(Normalize)』 と呼ぶ。 定期的にこの法力を用い、積もりゆく誤差(カルマ)を洗い流すのだ」
アーナンダは直ちにコードを追加した。
// 修正後のコード(完全版)
void update_goku() {
float t = deg2rad(1.0);
// 1. 退避 (Backup)
float old_x = goku.x;
float old_y = goku.y;
// 2. 計算 (Calculate)
goku.x = old_x * cos(t) - old_y * sin(t);
goku.y = old_x * sin(t) + old_y * cos(t);
// 3. 調律 (Normalize)
// 膨張した体を、長さ1.0の型枠に押し戻す
float len = sqrt(goku.x * goku.x + goku.y * goku.y);
goku.x /= len;
goku.y /= len;
}
「ぐぐぐ……体が張り裂けそうだ……お? おおっ?」
突如、悟空の体から余分な肉が削ぎ落とされ、引き締まった元の姿に戻った。 回転は続き、何万回回っても、もはや悟空が太ることも痩せることもなかった。
「へへっ、体が軽いぜ! これなら永遠に回ってられるな!」
三つの工程
釈迦は頷いた。
「よいかアーナンダ。回転の実装とは、単なる計算ではない。 『退避(Backup)』『計算(Calculate)』『調律(Normalize)』。 この三つを揃えて初めて、円環の理は保たれる。 ゆめゆめ、計算機を過信するな。数値は放っておけば腐る(劣化する)ものと心得よ」
アーナンダは深く合掌した。「肝に銘じます。常に変数を清め、誤差を祓いながら実装いたします」
モニターの中で、悟空は永遠の回転を続けている。その軌道には、一点の淀みもなかった。

Unit 3:逆変換と象限の眼力──失われた情報を復元する技術
追尾の試練
霊山大雷音寺、システム管理室。
Unit 2 で回転の理を学んだアーナンダは、次なるステップとして「自動追尾(ホーミング)」の実装に挑んでいた。
「アーナンダよ」釈迦如来が告げた。
「次は、この猿に『敵を見る』知恵を授けよ。空間上のあらゆる位置に出現する標的を、即座に見つけ出し、そちらを向くのだ」
「御意、世尊。標的の位置から角度を逆算すればよいのですね」
アーナンダは、テスト用として悟空の分身を出現させ、悟空にそれを追尾させるコードを記述した。
// アーナンダのコード
// 標的(target)への方向ベクトルを計算
Vector2 dir = normalize(target.pos - goku.pos);
// 方向ベクトルのX成分から角度を逆算し、そちらを向く
float angle = acos(dir.x);
goku.rotation = angle;
「けっ、今度は何だ? あの偽物を睨めばいいんだな? 楽勝だぜ!」
悟空は目の前に現れた分身をギロリと睨みつけた。
分身が右へ動けば、悟空も右を向く。左へ動けば、左を向く。
「へへっ、逃がさねぇぞ!」
しかし、分身が悟空の背後に回り込んだ瞬間、異変が起きた。
第三の魔物:見えざる背中(情報の欠損)
分身は悟空の真後ろ(角度 $${\pi}$$ 付近)にいる。しかし、悟空は決して振り向かない。 あろうことか、首を前に向けたまま、白目を剥いてガクガクと震え出した。
「おい! 後ろだ! 後ろにいやがるのに、首が動かねェ!」
悟空は必死に後ろを見ようとするが、見えない壁に阻まれたように、視線は前方の半円(上半分)に固定されている。
さらに、分身が真下($${-\pi/2}$$)に移動すると、悟空はなぜか真上($${+\pi/2}$$)を向き、空に向かって威嚇を始めた。
「こ、これは……!? 敵は下にいるのに、なぜ上を向くのですか!」
アーナンダはパニックに陥った。「数値は正しいはずです! acos は角度を返す関数ではないのですか!」
釈迦は静かにモニターを指差した。
「アーナンダよ。其の方は『片目』で世界を見ている」
第四の魔物:定義域の壁
「片目、でございますか?」
「左様。其の方が使った acos は、ベクトルの $${X}$$ 成分しか見ておらぬ」
釈迦は空中に図を描いた。
「単位円を思え。$${X}$$ が $${0.5}$$ となる角度は、上の $${60^\circ}$$ と、下の $${-60^\circ}$$ の二つある。 だが acos は $${Y}$$ 成分(上下の情報)を知らされぬゆえ、この二つを区別できぬ。
ゆえに、ブラフマー神は定めた。『acos は常に上半分($${0 \sim \pi}$$)の答えのみを返すべし』と」
「なんと……! では、下半分の世界は……」
「永遠に失われた。悟空が下を見られぬのも、背後の敵を正しく認識できぬのも、すべては『情報(Y成分)』を捨て去った其の方の業である」
「ぐぬぬ……! 下からつつかれてる! 尻が! 尻が危ねェ! 誰か俺様の首を下に向けろォォ!」
実践:両眼の開眼(atan2)
「世尊、いかがすれば悟空に全方位を見せることができるのでしょうか」
「両目を開け。『atan2』という法力を用いるのだ。 これは $${X}$$ と $${Y}$$、両方の業を見通す、千里眼の関数である」
「atan ではなく、atan2 ですか?」
「普通の atan(タンジェント)もまた、割り算によって符号を失う。 atan2(y, x) は、生の座標を二つ受け取り、その符号の組み合わせ(象限)を判定する。 右か左か、上か下か。四つの世界すべてを見通すことができるのだ」
アーナンダは直ちにコードを修正した。
// 修正後のコード
Vector2 dir = target.pos - goku.pos; // 正規化不要
// YとX、両方を渡して角度を仰ぐ
// 引数の順序は (y, x) であることに注意せよ
goku.rotation = atan2(dir.y, dir.x);
atan2 の内部ロジック(擬似コード)
atan2 は魔法の関数ではなく、愚直な条件分岐の塊である。東京大学の入試がごとく。
function atan2(y, x) {
if (x > 0) {
// 第1・第4象限:通常のarctanと同じ
// 右側にあるので、-90度から90度の範囲
return atan(y / x);
} else if (x < 0) {
// 第2・第3象限:左側にある
if (y >= 0) {
// 第2象限(左上):通常のarctanの結果に180度(PI)を足して補正
return atan(y / x) + PI;
} else {
// 第3象限(左下):通常のarctanの結果から180度(PI)を引いて補正
return atan(y / x) - PI;
}
} else {
// x == 0 (Y軸上) の例外処理:ゼロ除算を防ぐ
if (y > 0) return PI / 2; // 真上 (90度)
if (y < 0) return -PI / 2; // 真下 (-90度)
return 0; // x=0, y=0 (原点) は定義不能だが便宜上0などを返す
}
}
このように、atan2 は内部で $${x}$$ と $${y}$$ の符号に応じた場合分けを行い、$${-\pi}$$ から $${\pi}$$ までの(あるいは $${0}$$ から $${2\pi}$$ までの)360度全方位を正しく復元して返すものである。
死角なき追尾
修正コードが走った瞬間、悟空の首がカッ! と動き、真後ろにいた分身を正確に捉えた。
「見つけたぞコラァ!」
分身が上下左右、どこへ逃げようとも、悟空の視線は吸い付くように追従する。もはや死角はない。
「おお……! 悟空が、360度すべてを見ております!」
釈迦は頷いた。
「よいかアーナンダ。情報を圧縮する(Xだけ見る)ということは、可能性を捨てるということだ。 世界を正しく認識したくば、横着をせず、ありのままの座標(XとY)を関数に捧げよ。 さすれば、その知恵(戻り値)は円環のすべてを照らすであろう」
「肝に銘じます。これからは常に両眼(atan2)を開き、死角なき実装を心がけます」
モニターの中では、悟空が分身を追いかけ回し、楽しげに暴れ回っている。 「へへっ、どこに隠れようが無駄だぜ! 俺様の目は誤魔化せねェ!」

Unit 4: 行列と基底の曼荼羅 ── 暗記不要の行列作成術
開発現場で起きる「マイナス符号の迷宮」
霊山大雷音寺、システム管理室。 Unit 3 で逆変換の理を修めたアーナンダは、いよいよ幾何学の深淵、「行列(Matrix)」 の実装に挑んでいた。
「アーナンダよ」釈迦如来が告げた。
「次は、回転の法力を『行列』という形に封じ込めよ。 行列を用いれば、回転だけでなく、拡大、縮小、せん断、あらゆる変形を悟空に適用できる」
「御意、世尊。回転行列の公式は……ええと、記憶しております」
$$
R(\theta) = \begin{bmatrix} \cos\theta & \mp\sin\theta \\ \pm\sin\theta & \cos\theta \end{bmatrix}
$$
アーナンダは記憶の糸を手繰り寄せた。
$${\cos}$$ と $${\sin}$$ が四つ並ぶ。それは覚えている。しかし、破壊の力(マイナス符号)($${-\sin}$$)がどこに付くのか? 右上か? 左下か? それとも両方か?
「ええい、ままよ! 多分、左下でしょう!」
// アーナンダのコード(うろ覚えの実装)
// マイナスを左下につけてみた
Matrix2x2 mat = {
cos(t), sin(t),
-sin(t), cos(t)
};
// 悟空に適用
goku.transform = mat;
「けっ、今度は何だ? 体が……お? おお?」
悟空が右手を上げると、なぜか左手が上がった。
右足を出すと、左足が出る。
「なんだこりゃ!? 鏡の中か!? 俺様の動きがアベコベだ!」
悟空が右(東)へ走ろうとすると、体は左(西)へと進んでいく。
脳の命令と体の動きが逆転し、悟空は自分の足に躓いて派手に転げ回った。
「痛ててて! なんで逆に行くんだ! 釈迦の仕業か!」
第五の魔物:暗記の迷宮
「世尊……悟空が鏡の国の住人になってしまいました……」
アーナンダは頭を抱えた。「教科書の公式通りに書いたつもりでした。しかし、マイナスの位置が逆だったようです。直ちに修正を……」
「待て、アーナンダ」 釈迦如来が制止した。
「修正する前に、問おう。其の方は、なぜマイナスがそこ(右上あるいは左下)に付くのか、理屈を知っておるか?」
「いえ……昔、学校で『コス・マイナスシン・シン・コス』と呪文のように教わりました」
「嘆かわしい。呪文で覚えるゆえに、忘却のたびにバグを生むのだ。 しかも、開発環境には『行優先(Row Major)、ゲームエンジン(UnityやUnreal)』と『列優先(Column Major)、シェーダー言語(HLSLやGLSL)』という二つの宗派がある。呪文の暗記など、宗派が変われば役に立たぬ」
「では、いかがすれば……」
「『基底(Basis)』 を見よ。 行列とは、数字の羅列ではない。『世界の基準となる矢印が、どこへ飛んでいったか』を記した、航海日誌である」
説法:基底の行方
釈迦は空中に、X軸(赤)とY軸(緑)の二本の矢印を浮かべた。
「アーナンダよ。二次元の世界を支える二本の柱(基底ベクトル)がある。 一本は X軸 $${(1, 0)}$$。右を指す矢印。 一本は Y軸 $${(0, 1)}$$。上を指す矢印だ。
行列を作るとは、この二本の矢印が、回転後に『どの座標へ移動したか』を書き留める作業に他ならぬ」
ステップ1:赤き矢印(X軸)の行方
「まず、右を向いている 基底X $${(1, 0)}$$ を、角度 $${\theta}$$ だけ回してみよ。 矢印の先端はどこにある?」
「はっ。それは SinCos の定義そのものです。 半径1の円周上を進むゆえ、座標は $${(\cos\theta, \sin\theta)}$$ でございます」
「左様。それが、行列の 1列目 となる」
$$
\text{1列目} = \begin{bmatrix} \cos\theta \\ \sin\theta \end{bmatrix}
$$
ステップ2:緑の矢印(Y軸)の行方
「次に、上を向いている 基底Y $${(0, 1)}$$ を回す。 公式を思い出そうとするな。図形を見よ。 Y軸とは、X軸から見て『左に90度』進んだ場所にある矢印だ。 ならば、その行き先もまた、さきほどのX軸の行き先 $${(\cos\theta, \sin\theta)}$$ を、90度回した場所になるはずではないか?」
アーナンダは脳内で矢印を回した。
「$${(\cos, \sin)}$$ を90度回す……。 右($${x}$$)を向いていた成分は、上($${y}$$)へ行きます。 上($${y}$$)を向いていた成分は、左($${-x}$$)へ倒れ込みます。 つまり…… $${(\cos\theta, \sin\theta)}$$ を90度回すと、$${(-\sin\theta, \cos\theta)}$$ です!」
「その通り。Y軸を左に倒せば、X座標はマイナスの領域(左側)に突入する。ゆえに $${x}$$ 成分にマイナスが付くのだ。 これが、行列の 2列目 となる」
$$
\text{2列目} = \begin{bmatrix} -\sin\theta \\ \cos\theta \end{bmatrix}
$$
実践:曼荼羅の完成
「1列目と2列目を並べよ。それが回転行列の真の姿だ」
$$
R(\theta) = \begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix}
$$
「おお……! マイナスは右上(1行2列目)でした! Y軸(2列目)のX座標(1行目)だから、右上にマイナス。理路整然としております!」
アーナンダは歓喜し、コードを修正した。
// 修正後のコード
// 基底の行き先を並べる
Matrix2x2 mat = {
// 1列目 (Xの行き先)
cos(t),
sin(t),
// 2列目 (Yの行き先)
-sin(t),
cos(t)
};
「お? おおっ! 直ったぞ!」
悟空の手足の動きが正常に戻った。右へ走れば右へ進み、左へ走れば左へ進む。
「へへっ、やっぱこうでねえとな! 釈迦の野郎、やっと真面目にやりやがったか」
深淵:行列とベクトルの契り(線形結合)
悟空が正常に戻ったのを見届け、釈迦はさらに深く説いた。
「アーナンダよ。行列が作れたところで、その法力が実際に何を行っているかを知るがよい。 其の方は『行列とベクトルを掛ける』という計算を、単なる数字の足し合わせだと思っておらぬか?」
「はい。行と列を掛けて足す……と習いました」
「それでは半分しか理解しておらぬ。 行列ベクトル積とは、『新しい軸の上を歩くこと』 である」
$$
\begin{bmatrix} \cos\theta & -\sin\theta \\ \sin\theta & \cos\theta \end{bmatrix} \begin{bmatrix} x \\ y \end{bmatrix} = x \begin{bmatrix} \cos\theta \\ \sin\theta \end{bmatrix} + y \begin{bmatrix} -\sin\theta \\ \cos\theta \end{bmatrix}
$$
「この式を言葉に翻訳せよ」
アーナンダは数式を見つめ、ハッとした。
「こ、これは…… 『新しいX軸(1列目) の方向に $${x}$$ 歩進んで、 新しいY軸(2列目) の方向に $${y}$$ 歩進め』 ……という意味になります!」
「左様。回転行列を掛けるとは、『世界ごと斜めに傾けて、その傾いた世界の上で、元の歩数だけ歩く』 ことと同義なのだ。 元の座標 $${(x, y)}$$ は、『右に $${x}$$、上に $${y}$$』という命令であった。軸が回転しても、この命令(レシピ)は変わらぬ。『(傾いた)右に $${x}$$、(傾いた)上に $${y}$$』進めば、自然と回転した位置にたどり着く」
「なんと……! 計算ではなく、空間的な移動そのものであったとは!」
一生使える智慧
釈迦は満足げに頷いた。
「この『基底の行き先を並べる』という作成術は、回転行列だけでなく、あらゆる線形変換に通じる万能の鍵である。
例えば、X軸を2倍、Y軸を半分に縮めたいならどうする?」
「はっ! 基底X $${(1,0)}$$ は $${(2,0)}$$ へ。 基底Y $${v(0,1)}$$ は $${(0, 0.5)}$$ へ。 並べれば $${\begin{bmatrix} 2 & 0 \\ 0 & 0.5 \end{bmatrix}}$$ となります!」
「Y軸だけを右に倒したい(せん断)なら?」
「基底X $${(1,0)}$$ はそのまま。 基底Y $${(0,1)}$$ は右にズレて $${(1, 1)}$$ へ。 並べれば $${\begin{bmatrix} 1 & 1 \\ 0 & 1 \end{bmatrix}}$$ です!」
「見事だ。もはやマイナス記号の場所ごときに迷うことはあるまい。 無人島で教科書がなくとも、あるいは行列の並び順が違う異世界(異なるゲームエンジン)に行こうとも、其の方は自力で正しい行列を導き出せるであろう」
モニターの中で、悟空は自在に回転し、宙返りを繰り返している。
その動きを支えているのは、もはやあやふやな記憶ではなく、強固な幾何学の理解であった。

Unit 5:巨大入力と精度の彼岸──無限の彼方で何が起きるか
天の果てへの挑戦
霊山大雷音寺、システム管理室。
Unit 4 で行列の理を修めたアーナンダは、ついに最終試験に臨んでいた。
「アーナンダよ」釈迦如来が告げた。
「最後の試練である。この猿を、世界の果てまで飛ばしてみせよ。 数学的に正しい波を描きながら、永遠に彼方へ進ませるのだ」
「御意、世尊。波の計算など、もはや呼吸をするより容易きこと」
アーナンダは自信満々に、距離パラメータに極大の数値を入力した。
// アーナンダのコード
// 距離(dist)を無限大に進め、悟空を世界の果てへ送る
float huge_dist = 100000000.0; // 1億メートル
goku.y = sin(huge_dist);
「うおおお! 今度は遠出か! 望むところよ!」
悟空は觔斗雲の出力を最大にし、光の速さで地平線の彼方へと飛び去った。
「ふん、釈迦の掌など、一飛びで越えてくれるわ!」
悟空は飛び続けた。一万、十万、百万……。
やがて、肉色の雲を突き抜けてそびえ立つ、五本の巨大な天の柱が見えてきた。
「ここが世界の果てか。ふふん、造作もないことよ」
しかし、柱に近づこうとしたその時、異変が起きた。
第六の魔物:デジタル・グリッチ(身体崩壊)
「ぬ? なんだ? 手が……震える?」
悟空の指先が、ガタガタと激しく震え始めた。武者震いではない。存在そのものが、接触不良の映像のように激しく明滅しているのだ。
一歩進もうとすると、体が一瞬で数メートル先にワープする。滑らかさが完全に失われている。
「な、なんだコリャ!? 俺様の体が……カクカクする……!?」
「まやかしの術か!? い、一歩も動けない! 足場が消えていくゥゥ!」
悟空の視界にある五本の柱もまた、激しく座標ブレを起こし、ポリゴンが欠け、虚空が剥き出しになっていた。
世界そのものが、ノイズの海に沈もうとしている。
「ええい! ここは何処だ! 世界が壊れてやがる! 助けてくれェェ!」
説法:円周率の限界
「世尊! 悟空の座標が安定しません! 数学的には波は永遠に続くはずです!」
アーナンダは狼狽した。コードは間違っていない。sin は周期関数であり、どこまで行っても $${-1}$$ と $${1}$$ の間を滑らかに行き来するはずだ。
釈迦はモニターを見つめ、静かに告げた。
「アーナンダよ。其の方は『無限』という幻想に囚われている。 数学界には無限の精度があるが、シリコンにはない」
「精度、でございますか?」
「左様。sin 関数が何を行っているか知っておるか? 巨大な入力が来たとき、まず『円を何周したか』を計算し、その『余り』を取り出しているのだ」
$$
x_{\text{reduced}} = x \pmod{2\pi}
$$
「これを 『引数縮約(Argument Reduction)』 と呼ぶ。
だが、ここで使う円周率 $${\pi}$$ は、真の $${\pi}$$ ではない。有限のビットで近似された、不完全な値だ」
釈迦は空中に数式を投影した。
$$
\pi_{\text{computer}} = \pi_{\text{true}} + \epsilon \quad (\epsilon \text{は微小な誤差})
$$
「この微小な誤差 $${\epsilon}$$ は、入力 $${x}$$ が小さいときは問題にならぬ。 だが、1億という巨大な数を掛ければどうなる? 誤差もまた1億倍に増幅される」
「あっ……!」
「微々たるズレも、1億倍されれば致命的な亀裂となる。 今、悟空の座標計算においては、円周率の桁落ちにより『余り』がデタラメな値になっておる。 ゆえに波は波の体をなさず、ただの乱数と化したのだ」
実践:周期のリセット(Phase Reset)
「そ、そんな……。では、世界の果てには行けないのですか……」
「行こうとするな。『足るを知る』 のだ。 悟空を救いたくば、巨大な数を関数に渡してはならぬ。 手元で小さく回すのだ」
「手元で、とは?」
「『fmod』という法力を用い、周期をリセットせよ。 $${2\pi}$$ を超えたならば、即座に $${0}$$ に戻すのだ。さすれば入力は常に小さく保たれ、円周率の誤差が牙を剥くこともない」
アーナンダは震える手でコードを修正した。
// 修正後のコード
// 無限に増える距離を、有限の円環(0 ~ 2PI)に閉じ込める
float local_phase = fmod(huge_dist, 2 * PI);
// 小さな入力であれば、関数は正しい答えを返す
goku.y = sin(local_phase);
五行山の封印
修正コードが適用された瞬間、悟空の震えは止まった。 ノイズの海は消え去り、そこには静寂な元の世界(原点付近)が広がっていた。
「た、助かった……。もう遠くへ行くのは御免だ……」
悟空は力なくその場にへたり込んだ。
釈迦は静かに右手を翻した。「受けてみよ」
如来の手のひらが反転し、五本の指が山となって悟空の上に覆いかぶさった。
アーナンダは最後の仕上げとして、物理演算の拘束条件(Constraint)を入力する。
// 最後の仕上げ:五行山の実装
// 1. 衝突判定用の巨大メッシュ(五行山)を生成
Mesh mountain = loadMesh("Gogyo_Mountain");
mountain.position = goku.position;
// 2. 悟空の座標をクランプ(Clamp)して移動を封じる
// 上下左右、すべての自由度(DoF)をロック
goku.constraints = RigidbodyConstraints.FreezeAll;
// 3. 物理マテリアルを「封印」に設定
goku.physicsMaterial = "Sealed_Stone";
「ぎゃーーっ! 重てぇぇぇ! 体が動かねぇ!」
ズズズ……ン! 五行山という名の巨大な静的コライダ(Static Collider)の下に、悟空の座標は完全に固定された。
もはや微動だにしない悟空を見て、如来はアーナンダを振り返り、慈悲深くも厳かに告げた。
「アーナンダよ。見たか」
「は、はい……」
「この猿は、己が世界の果てまで飛んだと思い込んでいた。だが実際には、sin と cos が織りなす円環の理、すなわち精度の許す『掌』の中から、一歩も出てはいなかったのだ」
「……恐れ入りました、世尊」
「幾何学ライブラリを使いこなす者は、居ながらにして宇宙を手のひらに収める。だが一つ間違えば、世界そのものを崩壊させる。ゆめゆめ忘れるな」
アーナンダはデバッグコンソールの前で深く平伏した。
こうして、石猿・孫悟空は五行山の下で500年のコード・フリーズ期間に入ったのである。

