# アーキテクチャ

この章では全体像を示します。Kagari のゲームがソースファイルから実行中のゲームになるまでの流れ、ゲームの各部分を何で書くか、各部分がどのようにやりとりできるかです。個々の要素は後の章で厳密に定義します。

## ソースから実行中のゲームまで {#from-source-to-running-game}

<!-- diagram: architecture-pipeline (mermaid-ish)
flowchart LR
  subgraph project["kagari.toml プロジェクト"]
    src["src/: .kis, .kix"]
    assets["assets/: 画像、音声、フォント"]
  end
  project ==> build["kagari build: 検査、解析、生成、アセットのパック"]
  build ==> diags["診断 (kagari check、エディタ)"]
  subgraph out["target/v0/"]
    kagari["game.kagari (バイトコード)"]
    archives["*.kiarchive (アセットアーカイブ)"]
  end
  build ==> out
  out ==> runtime["デスクトップランタイム、プロファイル native_v0"]
  runtime <==> data["データディレクトリ: 設定、メタ、セーブ"]
  runtime ==> game["実行中のゲーム"]
-->
{{#include ../diagrams/architecture-pipeline.svg}}

**プロジェクト。** ゲームは一つの [プロジェクト](../tools/projects.md) です。`kagari.toml` マニフェスト、`src/` 以下のソースファイル、`assets/` 以下のアセットをもつディレクトリです。コンパイルは一つのルートファイル（既定では `src/main.kis`）から始まり、その `mod` 宣言をたどって他のすべてのソースファイルに至ります。ソースファイルは `.kis`（素の言語）か `.kix`（言語に `s!(…)` や `choice!{…}` などの [フォーム](../syntax/forms.md) を加えたもの）のどちらかです。拡張子が選ぶのは構文だけで、意味は変わりません。アセットはソース中で `const BG_ROOF: ImageAsset = asset("bg/roof.png");` のような定数で名前を付け、各ファイルが存在することをコンパイラが検査します。

**ビルド。** [`kagari build`](../tools/cli.md) はプログラム全体を一度にコンパイルします。すべてのファイルを構文解析して名前を解決し、型と [関数種別](../concepts/kinds.md) を検査し、定数とレンダーを評価し、物語とゲームシステムを解析し、プログラムを生成します。エラーが一つでもあればビルドは止まりますが、警告では止まりません。結果は `<out-dir>/<target>/`、既定では `target/v0/` に書き出されます。

| 出力 | 内容 |
| ---- | ---- |
| `game.kagari` | コンパイルされたプログラム：物語、レンダー、ゲームシステム |
| `*.kiarchive` | プログラムが参照するアセット。アセットディレクトリの最上位のディレクトリごとに一つのアーカイブ、アセットディレクトリ直下のファイルには `data.kiarchive`、ゲームが名前で指定したインストール済みフォントには `system-fonts.kiarchive`、使用する std のシェーダーには `std.kiarchive` |

ビルドは何かを書き出す前に、マニフェストに列挙されたすべてのランタイムプロファイルが、参照されるすべてのアセットをデコードできることを検査します。`kagari check` は出力を書かずに同じ診断を報告し、言語サーバーは入力中の [エディタ](../tools/editors.md) にそれを表示します。

ビルドはさらに、プログラムが使うすべてのシェーダーをそのロールに照らして検証し、検証に通らないシェーダーがあれば失敗します。

**ランタイム。** [デスクトップランタイム](../tools/runtime.md) `kagari-desktop-runtime` は、ビルドしたゲームを Windows、macOS、Linux で実行します。ゲームディレクトリ（既定では実行ファイルのあるディレクトリ。したがって出荷するゲームでは実行ファイルを `game.kagari` とアーカイブの隣に置きます）から `game.kagari` を読み込み、どの部分も実行する前にプログラムを検証し、`fn main` を一度実行してゲームシステムの記述を得て、開始ページを表示します。設定、メタ、セーブスロットは、ゲームディレクトリの外にあるデータディレクトリに保存されます。ランタイムが実装するのはランタイムプロファイル `native_v0` で、動画のアセットを除く標準ライブラリ全体です。[ヘッドレスモード](../tools/headless.md) では、同じゲームをウィンドウなしでスクリプトに従って実行でき、自動テストに使えます。

## ゲームの各部分を書く種別 {#what-each-part-of-a-game-is-written-in}

ゲームは三つの部分から成り、それぞれを一つの [種別](../concepts/kinds.md) の関数で書きます。

| 部分 | 種別 | 含むもの |
| ---- | ---- | -------- |
| 物語 | `pushdown fn` | シーン（`#[scene]`）、即時手続き、[物語変数](../concepts/story.md)（`static mut`）、プレイヤーが読み、選ぶ [ステップ](../concepts/steps.md) |
| 演出 | `const fn` | 各ステップで画面を変える [レンダー関数](../concepts/presentation.md)、キャラクターのレシピなどのステージング補助関数、テキスト効果、定数 |
| ゲームシステム | `fn` | `fn main`、タイトルページ、メニュー、セーブとロードのページ、設定、バックログ、ゲームビュー。物語を取り巻くすべて |

型、定数、設定とメタのための `static` ハンドル、アセット定数は、三つの部分すべてが共有する通常のアイテムです。一つのモジュールにどの種別を混ぜてもかまいません。種別はファイルではなく関数に属します。

<!-- diagram: architecture-layers (mermaid-ish)
flowchart TB
  system["ゲームシステム: fn (main、タイトル、メニュー、セーブとロード、設定、ゲームビュー)"]
  story["物語: pushdown fn (シーン、ステップ、選択肢、物語変数)"]
  pres["演出: const fn (レンダー、ステージングの補助関数、テキスト効果、定数)"]
  system ==>|"エントリーシーンを開始: nav::start、有限型の引数"| story
  system <==>|"Narrative ハンドル: 入力、台詞、セーブ"| story
  story ==>|"エンディングの値、書き込み専用のメタ"| system
  story ==>|"ステップごとにレンダーを選ぶ"| pres
  pres -.->|"決してない: レンダーは画面だけを見る。物語は画面を読まない"| story
  system ==>|"const fn を呼べる"| pres
-->
{{#include ../diagrams/architecture-layers.svg}}

どの矢印が存在するかは [呼び出し規則](../concepts/kinds.md#call-rules) が定めます。

- `fn` は `fn` と `const fn` を呼べます。`pushdown fn` を呼ぶことも、物語変数を読むこともできません。
- `pushdown fn` は `pushdown fn` と `const fn` を呼べ、`fn` は決して呼べません。実行時に保持する値はすべて [有限型](../concepts/kinds.md#finite-types) です。
- `const fn` は `const fn` しか呼べません。物語変数、設定、メタなど、引数以外のものは何も読めません。

関数ポインタとクロージャの型も種別をもつので、これらの規則はポインタやクロージャを経由しても成り立ちます。

## 入れ子になった二つの機械 {#two-nested-machines}

実行時のゲームは、一方が他方の内側にある二つの機械です。

- 外側の**システム機械**は [モード](../concepts/system.md) のスタックを保持します。底にはタイトルやギャラリーのようなルートページ、またはゲームモードがあり、その上に Config、Save、Backlog などのオーバーレイやダイアログが積まれます。入力は最上位のモードだけに届きます。
- **ゲームモード**は実行中の物語を一つ保持します。これが [物語機械](../concepts/story.md) です。物語機械はプッシュダウン機械で、その状態 σ は、シーン内の位置、実行中のシーンと即時手続きのコールスタック、物語変数から成ります。ゲームモードはこれに加えて、現在の画面と、プレイヤーの選択のトレースを保持します。

物語のコールスタックはゲームモードの内側にあり、モードのスタックに載ることはありません。したがって、セーブが保持するのは実行中の物語だけで、それを取り巻くメニューは何も含みません。セーブをロードすると、スタックに積むのではなくゲームモードが置き換わります。そして「ゲームが実行中である」とは、ちょうどスタックにゲームモードがあることを意味します。オーバーレイやダイアログがゲームモードの上にある間、物語は一時停止します。

| 状態 | 寿命 | セーブに含まれるか |
| ---- | ---- | ------------------ |
| 設定 | 永続、大域的 | いいえ |
| メタ：解放状況、既読テキスト、表示済みの画像 | 永続、大域的 | いいえ |
| セーブスロット | 永続 | これがセーブそのもの |
| モードのスタックと各モードの状態 | ゲーム終了まで | いいえ |
| 実行中の物語：σ と画面 | 物語が終わるか、別の物語に置き換わるまで | はい。そこに到達するトレースとして |

## 部分どうしのやりとり {#how-the-parts-talk}

情報は三つの部分を一方向に流れます。物語が何が起こるかを決め、演出がそれがどう見えるかを決め、ゲームシステムがその周りのすべてを決めます。どの部分も、自分の出力を後段がどう扱ったかを読み返しません。

### 物語から演出へ {#from-the-story-to-the-presentation}

各ステップは自分のレンダーを指定します（`s!(render = roof, …)`）。物語がそのステップに到達すると、エンジンは現在の画面にレンダーを適用し、結果を表示します。レンダーが受け取るのは画面だけなので、物語変数に依存できません。また `Screen` は有限型ではないので、物語は画面を読めません。物語に依存する演出は、分岐ごとに異なるレンダーを渡すことで、物語の側が選びます。

### ゲームシステムと物語の間 {#between-the-game-system-and-the-story}

ゲームシステムと物語が接するのは次の四か所だけです。

| 接点 | 方向 | 渡るもの |
| ---- | ---- | -------- |
| [エントリーシーン](../concepts/entry.md) | システムから物語へ | `nav::start(entry, args, on_exit)` は、有限型の引数で `#[entry]` シーンから物語を開始します。物語に入る方法はこれだけです |
| [`Narrative` ハンドル](../concepts/system.md#the-narrative-handle) | 双方向 | ゲームビューとゲームオーバーレイは、実行中の物語へのハンドルを受け取ります。これを通じて現在の台詞、選択肢、バックログを表示し、物語を進める、アームを選ぶ、セーブする、巻き戻す、といった操作をします。このハンドルから物語変数は読めません |
| [エンディング](../concepts/entry.md) | 物語からシステムへ | エントリーシーンが戻ると、その値は終了ハンドラ `on_exit` に渡されます。終了ハンドラは起こりうるすべてのエンディングを扱わなければなりません |
| メタ | 物語からシステムへ | 物語は `Meta::write` でメタに書き込み、実行中にそれを読み返すことはありません。システムはメタを読み、次の物語にエントリー引数として渡せます |

設定が物語に届くことはありません。設定に依存するレンダーは、その値を読むのではなく、エンティティを設定でゲートします。設定の値は、描画時にエンジンが適用します。

## ゲームの一回の実行 {#a-run-of-a-game}

1. ランタイムは `game.kagari` を読み込んで検証し、`fn main` を実行し、それが返す `System` レコードに指定された開始ページを表示します。
2. ページ、オーバーレイ、ダイアログはコンポーネント、つまり UI を組み立てる素の `fn` です。ボタンのハンドラは `nav::call(Config)` や `nav::start(…)` などのナビゲーション関数を呼びます。
3. `nav::start` はモードのスタックを、エントリーシーンを実行する新しいゲームモードで置き換えます。
4. 物語は最初のステップまで即座に実行されます。ステップのレンダーが画面を変え、ゲームビューが台詞を表示し、物語は待機します。
5. プレイヤーの入力が物語を再開させます。`s!` のステップを先へ進めるか、`choice!` のアームを選ぶかです。物語は次のステップまで実行されます。エンジンは選択肢ごとに自動セーブします。
6. エントリーシーンが戻ると、終了ハンドラがエンディングを受け取り、たとえばクレジットへ遷移します。

[コンパイラの解析](./philosophy.md) は、起こりうるすべての実行、つまりすべてのエントリーシーン、その引数のすべての値、すべての選択の列について、手順 3 から 6 を対象とします。
