先日公開した 2D 配置エンジン「Kimetron Isle」の Qiita 記事のコメント欄で、Albertn さん(@Albertn)とやり取りをしました。その議論がそのまま1個の OSS になったので、経緯を含めて3回に分けて書きます。
出来上がったのは、ユニット移動の仲裁を担う中間層「SAGBAL(サグバル)」です。MIT ライセンスで公開しています。
この第1回は「なぜ層を作ることにしたか」という設計の話です。
発端: エンジンは何を返すべきか
Kimetron Isle は「配置」をデータとして扱う 2D エンジンです。tryMove という API で移動を試みて、成否と拒否理由を返します。記事で紹介したデモでは、ユニットが建物内を徘徊していました。
そこでコメント欄で話題になったのが、**「移動が拒否されたとき、エンジンはどこまで報告すべきか」**という問題です。議論の中で確立した線引きはこうでした:
- エンジンが出せるのは一時点の事実だけ。「この移動は成立する/しない」「X がそれを塞いでいる」
- 「連続 N 回遮られた」「この場所に滞留している」は解釈であって事実ではない。組み立ては外側でやる
- 拒否情報は二系統で出す。理由コード(機械的分類)とブロッカー id(意味論参照)。id さえあれば、利用側はそれが「壁なのか他のユニットなのか、あのうるさい隣人なのか」を再構成できる
- 利用側の語彙をエンジンの enum に逆流させない。
custom+tag: stringで素通しさせる
最後のやつが効きます。「ドア」「敵」「優先度高いユニット」といったアプリ固有の語彙をエンジンに教え込んでしまうと、エンジンはそのアプリ専用になっていきます。語彙を逆流させないために、エンジン側は「素通しできる窓」を用意する——この発想はその後の設計全体を支配しました。
問題: 事実と意味の間に何もない
ここで構図を整理します。
App ──何をすればいい?──▶ Kimetron
(「隣人が3回目だから怒る」 (「tryMove は失敗。
という判断がしたい) ブロッカーは uE」)
エンジンから上がってくるのは刻々の事実、アプリが欲しいのは時間をまたいだ意味。間に何もないと、アプリ側は毎回「事実 → 意味」の組み立てを自前で書くことになります。しかもこれ、移動するユニットを持つゲームなら全部が書いているやつです。譲り合い、詰まり検出、行き詰まりの解消——ゲームごとに微妙に違う実装が散らばっているはずです。
じゃあ真ん中に層を作ればいい。それが SAGBAL の出発点でした。
三層の線引き
App ──API──▶ SAGBAL ──API──▶ Kimetron
(意味・ポリシー) (事象の層) (物理の真実)
| 層 | 担当 | 例 |
|---|---|---|
| Kimetron | 幾何的事実 | tryMove 成否、拒否理由+ブロッカーid、objectsIn |
| SAGBAL | 仕組み | 意図の収集、競合の裁定、譲り、行き詰まり検出、拡散 |
| App | ポリシー | 重みづけ、誰が譲るか、行き先の意味、勝敗 |
重要なのは、中間層は「交渉の仕組み」までで「交渉の優劣」を持たないことです。「プレイヤーが一番偉い」とか「このユニットは頑固」はアプリ側の値であって、層は数値を受け取って機械的に裁くだけです。
設計原則は自分にも適用する
コメント欄で確立した規則は、作った層自身にも再帰的に適用しました。
- SAGBAL も事実を飲み込まない。生のエンジンイベント・ブロッカー id は App まで素通し。層が情報のチョークポイントになると、二系統報告の冗長性が死ぬ
- SAGBAL の出力は「より濃い事実」。「ユニット U は N ティック連続で遮られた」「U と V で互い違いの意図が検出された」——そういう事象として出す。「U はイライラしている」みたいな解釈語彙は出さない。解釈は App の仕事
- SAGBAL の語彙を Kimetron core に入れない。意図・交渉・swap は層の言葉。依存は SAGBAL → Kimetron の一方向だけ
「事実は上へ、指示は下へ」という流れです。
デモ: スライムの門前渋滞
v0.1.0 のデモでは、プレイヤー P と6体の自律徘徊スライムが建物内を動き回り、中央の門(幅がユニット2体分ほど)で渋滞が起きます。門を抜けたいスライム同士が競合し、譲り合い、行き詰まり、横にどき合います。
何が起きたかは右のイベントログに全部流れます。Kimetron の生の事実(灰)、SAGBAL が組み立てた事象(橙)、App の解釈(緑)で3色に色分けしています。層ごとに違う色で見えるので、設計通りに動いているかが一目で分かる検証装置でもあります。
名前の由来
SAGBAL はシュメール語の sag(前・先)× bal(交差・移し替え)の合成語。「先に通るもの」を「交差」させる層——行き違いそのものが字面に内蔵されています。Kimetron が ki(場)× metron(尺度)で「測る場」なら、その場の上で起きる交差の層です。島(Isle)に積む地層(stratum)、という位置づけを名付けに反映しています。
つづく
第2回は中身の話です。毎ティック集まる「この1歩を取りたい」という意図をどう裁くか、譲ったユニットをどう扱うか、行き詰まった2体をどうほどくか、耐えきれなくなったユニットがどう「爆発」するか——仲裁機構の設計を書きます。
