SDL2 と VM の境界設計 — 「VM は一切いじらない」
AI との対話から生まれた設計文書(原文)
ChatGPT との対話から生まれた設計文書(2026年2月25日) 自作 VM(Kaede Script Code)と SDL2 の接続設計。Switch 移植のアーキテクチャを決めた一枚
了解。VM が完成していて、さらにクラス実装の"受け"まで整っているなら、SDL2 側は VM に寄せ切る のが最短です。やることは「VM が要求する副作用(I/O)を SDL2 で実装する」だけになります。
ゴール
- VM は一切いじらない(原則)
- SDL2 は Host(実行環境) として、VM の命令を受けて描画・音・入力・ファイルを処理する
- 追加機能は「命令セット(API)」追加で回す
1) まず固定する"境界": Host API(これが全て)
VM ↔ SDL2 の境界は次の 2つだけ に絞るのが安定です。
A. VM → Host(副作用要求)
- spawn / destroy
- transform(move / scale / rotate)
- render(sprite / text / rect)
- audio(bgm / se)
- storage(load / save)
- time(wait / sleep)
- debug / log
B. Host → VM(入力イベント)
- key down / up
- mouse move / down / up / wheel
- gamepad
- window resize / quit
ここを インターフェース(C++ なら純仮想、TS なら型)で固定 します。 VM はこのインターフェースだけ知っていて、SDL2 実装はその具象。
2) 実装の最短手順(VM が完成してる前提)
Step 1: "命令の副作用"を全部イベント化する
VM が今「命令を実行して直接何かする」形でも、外へ出すのはこうします。
- VM 実行 →
HostRequestを吐く(またはコールバックを呼ぶ) - Host が処理して結果を VM へ返す(同期/非同期どっちでも)
重要: SDL2 のメインループと VM の逐次実行を衝突させないこと。 解決は「VM は1フレームに実行していい命令数/時間を制限」か「yield 命令で止める」。
Step 2: 命令を「即時」と「待ち」に分ける
- 即時: spawnSprite, setTexture, setText, playSE
- 待ち: wait(t), moveTo(..., duration), fade(..., duration)
待ち系は VM 側が既に yield できるなら、
- 命令実行で Task / Coroutine を作る
- Host は進捗だけ返す
が一番綺麗です。
3) クラス実装準備ができてるなら: Host API も"オブジェクト指向"で揃える
クラス導入で効くのは「シーン/エンティティ/コンポーネント」を VM 側で表現できる点です。
ただし SDL2 側は凝らない。SDL2 は以下だけ持てばいい:
NodeId(整数)TransformDrawable(Sprite / Text / Rect)Z(描画順)Visible
これを SceneGraph っぽく持って、VM は NodeId を参照するだけ。
4) "移植しやすい命令セット"の形(推奨フォーマット)
将来、SDL2 以外(WebGL、Metal、Switch 向け独自)に替えるために、命令はこういう形が強いです。
- 命令は データ(struct / JSON / バイナリ)で表現
- Host は 命令を解釈して実行
- VM は 命令を生成して流す
例(概念):
CMD_SPAWN_SPRITE { nodeId, assetId, x, y }
CMD_TWEEN { nodeId, prop, from, to, duration, easing }
命令がデータなら、ログ・リプレイ・テストが強くなります(ここが AI 実装とも相性良い)。
5) ここから先、Claude Code に投げる"具体タスク"
VM が完成している前提で、最初のタスクはこれが最短です。
- SDL2 プロジェクト雛形(CMake)
- Host インターフェース作成(上の A/B を型として固定)
SdlHost実装(画像1枚 + テキスト表示 + 入力イベント送出)- VM をリンクし、固定スクリプト1本を毎回実行できるようにする
- spawnSprite → 表示
- moveTo(…, 1.0) → 1秒移動
- wait(0.5) → 停止
- fade(…, 0.5) → フェード
ここまで行けば、あとは命令を増やしていくだけ になります。
6) VM の実行モデルによる分岐
VM が「yield / コルーチン」をどう表現しているかで、Host 側の作りが決まります。
-
パターン A: VM が wait 等で自分で停止し、dt で再開できる → Host は単純(毎フレーム
vm.update(dt)) -
パターン B: VM は停止できない(全部同期実行) → Host 側で命令キュー+タイムラインを持つ必要あり(少し重くなる)
必要なら、既存 VM の「実行モデル(update / yield / 命令キュー)」を前提にして、Host API(インターフェース定義)と命令セット初版(20〜30個)をそのまま Claude Code に投げられる形で出します。