見出し画像

【西遊記デバッグ】なぜ孫悟空は世界の果てでガタガタ震え出し、五行山に押し潰されるに至ったのか?

プロローグ:大雷音寺の決闘

西方、霊山大雷音寺。

天界で暴れ回り、十万の天兵さえ退けた石猿・孫悟空が、ついに釈迦如来の御前に引き据えられた。

悟空は如来を見上げ、傲然と言い放った。

「やい如来! 俺様は斉天大聖だ。変化の術も使えるし、觔斗雲きんとうんで十万八千里も飛べる。 弱っちい天帝を追い出して、俺様にその位管理者権限を寄越せと言ってるんだ!」

釈迦如来は、慈悲と威厳を湛えた眼差しで猿を見下ろし、静かに、しかし雷の如き声で痛罵した。

「増上慢の畜生め。人間界で修行しただけの猿風情が、無限のクロックサイクルを生きる天帝の位を奪おうとは。……よい、ならばその力を見せてみよ」

如来は右手を差し出した。

わたしの右の手のひらから飛び出すことができたなら、望み通り天帝の座をくれてやろう。だができねば、下界へ戻り修行し直すがよい」

「へっ、たかが手っぺら一つ! 俺様の觔斗雲きんとうんなら一飛びよ!」悟空は鼻を鳴らし、飛翔の構えを取った。

如来は背後に控える見習い僧アーナンダに目配せをした。

「アーナンダよ。汝に法力『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(入力画面)や設定ファイルは『人間の国』ゆえ、度数法を使ってよい。 だが、データを読み込んだ瞬間に、『関所(変換層)』 を設けよ」

アーキテクチャ・ルール:国境で変換せよ

  1. UI層(入国審査): ユーザーが「90度」と入力する場所。ここは度数法でよい。

  2. 変換の壁(両替所): データを読み込んだ瞬間に、deg2rad() の法力をもってラジアンに変換せよ。 これより内側では、一切『度』という概念を持ち込んではならぬ。 変数名に _rad をつけるなどして、厳格に管理せよ。

  3. ロジック層(機械の国): 移動、回転、当たり判定。ここは完全なる「ラジアン」の浄土である。

  4. レンダリング層(出国審査): デバッグ表示や画面出力のときだけ、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年のコード・フリーズ反省期間に入ったのである。

いいなと思ったら応援しよう!