シミュレーションとコンピュータグラフィックス
シミュレーションは「状態遷移の正しさ」、CGは「観測の見え方」を担います。世界モデルではこの2層を分けて考えることが実装上の要点です。
動きと見え方は、別の層で作られている
物体がどう動くかを決めるのはシミュレーションで、その結果がセンサーや画像としてどう見えるかを決めるのは描画側です。世界モデルを作るとき、この二つを混ぜると原因の切り分けが難しくなります。
ここでは放物運動のごく小さな例を使って、状態更新と 2D グリッドへの描画を分けて見ます。やりたいのは CG を学ぶことそのものではなく、『物理の間違い』と『見え方の間違い』を別々に扱う感覚を持つことです。
反発係数ひとつでも、物理と観測は役割が違う
vy *= -0.4 は床にぶつかったあとの跳ね返り方を決める物理側の設定です。一方で canvas に * を置く処理は、その状態をどんな観測へ変換するかという表示側の設定です。二つは同じ誤差に見えても、直す場所が違います。
この notebook の見どころ
最初の状態列、グリッド上の軌跡、床反射の挙動を順に見ながら、どこまでが遷移でどこからがレンダリングかを切り分けます。
現実へ持っていくときに効くのも、この分離です。sim-to-real でズレたとき、『世界の動きが違うのか、見え方が違うのか』をまず切り分けないと調整方針が立ちません。
丸めが生む別種の誤差
連続座標をグリッドに落とすと、物理は合っていても見た目が粗くなります。これは遷移モデルの失敗ではなく観測化の副作用で、学習器には別種のノイズとして入ります。
読み方の軸
この notebook は物理エンジンや本格レンダラの代わりではありません。状態更新と観測生成を分けて設計する理由を、最小例で確かめるためのものです。
状態を時間発展させる
まずは位置と速度を更新し続けて、世界の側で何が起きているかを数値として追います。
import numpy as np
dt = 0.1
g = -9.8
x, y = 0.0, 0.0
vx, vy = 2.0, 6.0
traj = []
for _ in range(25):
x += vx * dt
y += vy * dt
vy += g * dt
if y < 0:
y = 0
vy *= -0.4
traj.append((x, y))
print('first 5 simulated states =', [(round(a,2), round(b,2)) for a,b in traj[:5]])
数値の軌跡を見える形にする
次に、その状態列を粗いキャンバスへ写して、観測として何が見えているかを確かめます。
# CG: 連続状態をグリッドに投影して可視化
W, H = 40, 12
canvas = [['.' for _ in range(W)] for _ in range(H)]
for x, y in traj:
px = min(W - 1, max(0, int(x * 3)))
py = min(H - 1, max(0, int(y * 1.5)))
canvas[H - 1 - py][px] = '*'
for row in canvas:
print(''.join(row))
この分離を保っておくと、ズレが出たときに『物理を直すのか、描画を直すのか』を判断しやすくなります。世界モデルの実装では、この切り分け自体がかなり重要です。