KSC(コード記法)仕様書
Kaede Script の コード記法(KSC、.ksc) の詳細仕様書です。TypeScript に近い構文で、複雑なゲームロジックを手続き的に記述できます。
Kaede Script 全体像
kaedevn のスクリプト言語は 1 つ(Kaede Script)で、2 つの記法を提供します:
- タグ記法(KS、
.ks) — KS 仕様書。宣言的でシーン描写向き- コード記法(KSC、
.ksc) — 本仕様書。手続き的で複雑なロジック向き概要は Kaede Script 概要 を参照してください。
処理方式: インタプリタ
KSC は インタプリタ方式 で動作します。スクリプトを1行ずつ逐次解釈して直接実行します。TypeScript に近い構文でゲームのロジックを記述でき、フラグによる複雑な分岐、数値計算、関数の再利用などが可能です。
.ksc スクリプト
|
v
インタプリタ ── スクリプトを逐次解釈して直接実行
| TypeScript 風の構文を1行ずつ処理
v
HostAPI ────── 描画エンジンに命令を委譲
Web なら PixiJS、Switch なら SDL2
インタプリタ自体は画面描画や音声再生を一切行わず、HostAPI 経由で各プラットフォームの実装に委譲します。デバッグモード(ブレークポイント、変数ウォッチ、ステップ実行)やエラー候補提示(Levenshtein 距離による類似コマンドの提案)も搭載しています。
KS との比較
- 構文 — KS: TyranoScript 風(
@command) / KSC: TypeScript 風 - 処理方式 — KS: コンパイラ → Op[] / KSC: インタプリタ(逐次実行)
- 型システム — KS: なし / KSC: あり(型チェック)
- 制御フロー — KS:
@if/@choice/ KSC: if/else, for, while - 関数 — KS: なし / KSC: ユーザー定義関数(def / sub)
- 用途 — KS: シンプルなシナリオ / KSC: 高度なロジック
型システム
KSC は以下の型をサポートします。型の不一致はエラーとして検出されます。
- number — 数値(例:
affection = 5) - string — 文字列(例:
playerName = "太郎") - boolean — 真偽値(例:
isClear = false) - null — null 値(例:
result = null) - object — オブジェクト(例:
{hp: 100, mp: 50}) - array — 配列(例:
[1, 2, 3]) - union — ユニオン型(例:
number | string) - 関数 — 関数型(例:
def get_rank(score) { ... })
// 実行時にエラーを検出
affection = "高い" // Error: number型の変数にstringを代入できません
jump(123) // Error: jump()の引数はstringである必要があります
1. ラベルの定義
KSC におけるラベルは、ジャンプ先やサブルーチンの開始地点として機能します。行頭に * を付けて記述します。
// ラベルの定義
*start_scene
bg("room_day")
#hero
「おはよう!」
#
jump("next_part") // ラベルへジャンプ
*next_part
#sakura
「いい天気だね」
#
2. ダイアローグ(セリフ)ブロック
KSC では、セリフを #名前 と # で囲んで記述します。このブロックが終了すると、自動的にクリック待ちが発生します。
#hero
「今日はいい天気だね」
「どこかへ出かけようか」
#
#sakura
「賛成!図書館に行きたいな」
#
変数展開
セリフ内で {変数名} を使うと、変数の値が自動的に埋め込まれます。
#sakura
「{playerName}君、今の好感度は {affection} だよ!」
#
3. 変数と演算
数値、文字列、真偽値を自由に扱えます。
// 代入と演算
affection = 5
affection += 2 // 7になる
playerName = "太郎"
isClear = false
4. フロー制御
条件分岐 (if)
条件式により、物語の展開を変化させます。ジャンプ先には対応するラベルが必要です。
if (affection >= 10) {
jump("true_end")
} else if (affection >= 5) {
#sakura
「もっと仲良くなりたいな」
#
} else {
jump("bad_end")
}
*true_end
#sakura
「ずっと一緒にいようね!」
#
jump("story_end") // ← これがないと *bad_end も表示されてしまいます
*bad_end
#sakura
「さようなら...」
#
*story_end
#system
おわり
#
選択肢 (choice)
プレイヤーに選択を促し、結果を即座に反映します。
注意: ラベルは進行を止めません
ラベル自体にプログラムの実行を止める機能はありません。特定のラベル以降の処理だけを行いたい場合は、ブロックの最後に別のラベルへの
jump()を記述して、意図しない実行を防いでください。
choice {
"一緒に帰る" {
jump("go_home")
}
"デートに誘う" if (affection >= 10) {
jump("date_event")
}
}
*go_home
#system
二人で夕暮れの道を歩いた。
#
jump("day_end") // ← ここで飛ばさないと、下の *date_event も実行されてしまう
*date_event
#sakura
「えっ、デート!? 喜んで!」
#
jump("day_end")
*day_end
#system
こうして一日が終わった。
#
ループ (for / while)
KSC では KS にはないループ構文が使えます。
// for ループ
for (i = 0; i < 5; i += 1) {
#system
「{i}回目のループ」
#
}
// while ループ
count = 10
while (count > 0) {
count -= 1
}
5. 関数とサブルーチン
処理を共通化して再利用できます。
サブルーチン (sub)
戻り値のない処理のまとまりです。
sub show_status() {
#system
現在の好感度: {affection}
#
}
// 呼び出し
show_status()
関数 (def)
計算結果などを返すことができます。
def get_rank(score) {
if (score > 80) return "S"
return "A"
}
rank = get_rank(score)
call / ret
ラベルをサブルーチンとして呼び出すこともできます。
call("common_effect") // サブルーチン呼び出し
// ... 処理後ここに戻ってくる
*common_effect
se("sparkle")
wait(500)
ret() // 呼び出し元に戻る
6. 組み込みコマンド・リファレンス
KSC で利用可能な組み込みコマンド/関数の全一覧です。Interpreter.ts の isBuiltinFunction() で定義される 28 個です。各コマンドは IEngineAPI のメソッドを経由してプラットフォーム(Web = PixiJS、Switch = SDL2)に委譲されます。
| コマンド | シグネチャ | 非同期 | 役割 |
|---|---|---|---|
| 表示・演出 | |||
bg | (id, effect?) | async | 背景切替(IEngineAPI.setBg) |
ch | (id, pose, pos?, fadeMs?) | async | キャラ表示(showChar、pos: L / C / R) |
ch_anim | (id, pose, pos) | async | アニメ付きキャラ表示 |
ch_hide | (id, fadeMs?) | async | 特定キャラ消去 |
ch_clear | (fadeMs?) | async | 全キャラ消去 |
filter | (name, strength?) | sync | スクリーンフィルタ適用(46 種) |
filter_clear | () | sync | フィルタ解除 |
color_adjust | (b, c, s, t) | async | 明度・コントラスト・彩度・色温度 |
shake | (intensity?, ms?) | async | 画面揺れ |
| オーディオ | |||
bgm | (id, vol?, fadeMs?) | sync | BGM 再生(ループ) |
bgm_stop | (fadeMs?) | 条件付 | BGM 停止(fadeMs 指定時は async) |
se | (id, vol?) | sync | 効果音(1 回) |
voice | (id) | sync | ボイス再生 |
| 制御・待機 | |||
wait | (ms) | async | 指定 ms 待機 |
waitclick | () | async | クリック待機 |
jump | (label) | — | ラベルへ移動(スタック非消費) |
call | (label) | — | サブルーチン呼び出し(復帰位置を push) |
ret | () | — | サブルーチンから復帰 |
| 特殊演出 | |||
timeline / timeline_play | (id) | async | タイムライン演出実行 |
battle | (id) | async | バトル開始、'win' / 'lose' を返す |
map_load | (id, x?, y?, dir?) | async | マップ転送 |
map_exit | () | async | マップ終了 |
player_move | (dir, steps) | async | プレイヤー移動 |
event_move | (id, dx, dy, speed?) | async | イベント移動 |
item_add | (id, count) | sync | アイテム追加 |
item_remove | (id, count) | sync | アイテム削除 |
layout_set | (id) | async | レイアウト切替 |
組み込みユーティリティ関数
| 関数 | 戻り値 | 役割 |
|---|---|---|
print(value) | void | デバッグ出力(コンソール) |
random(min, max) | number | [min, max] の整数乱数(シード付き乱数で決定論的) |
toString(value) | string | 任意値を文字列化 |
len(array) | number | 配列/文字列/オブジェクトの要素数 |
詳細なパラメータ・エラー条件は packages/interpreter/docs/spec-builtin-commands.md を参照。
7. 式評価モデル
KSC の式評価は Evaluator.ts の再帰下降パーサーで実装されています。
7.1 リテラル
| 種類 | 例 |
|---|---|
| Number | 42、3.14、-0.5 |
| String | "hello"、'world'(エスケープ: \n / \t / \\ / \") |
| Boolean | true / false |
| null | null |
| Array | [1, 2, 3](trailing comma 可) |
| Object | {hp: 100, name: "hero"} / {"key": val} |
7.2 演算子と優先順位
高い → 低い順で以下の 9 段階:
| 段 | 演算子 | 結合 | 備考 |
|---|---|---|---|
| 1 | ( expr ) / リテラル / 識別子 / 関数呼び出し | — | Primary |
| 2 | .prop / [index] | 左 | プロパティ・インデックスアクセス(連鎖可) |
| 3 | !、-(単項) | — | 前置 |
| 4 | * / / / % | 左 | Multiplication |
| 5 | + / - | 左 | Addition(+ は文字列結合も) |
| 6 | > / >= / < / <= | 左 | Comparison |
| 7 | == / != | 左 | Equality |
| 8 | && | 左 | Logical AND(短絡評価) |
| 9 | || | 左 | Logical OR(短絡評価) |
代入は文レベルで = / += / -= / *= / /= が利用可能(プロパティ代入 obj.x = 5 も可)。
7.3 暗黙型変換
+演算子 — 片方が string なら文字列結合、そうでなければ数値加算- 他の算術・比較 — オペランドを数値に強制変換
- truthiness(if / while / && / ||)
false/0/""(空文字列)/null/undefined→ falsy- それ以外 → truthy
7.4 プロパティアクセスと永続変数
- ドット記法:
player.hp/flags.firstDate - ブラケット記法:
arr[0]/obj["key"] - 永続変数プレフィクス:
p.で始まる変数(例:p.ended_good)は自動で永続化マップに保存される。セーブ/ロード対象
8. エラー型
ランタイムエラーは [KSC ErrorType] Line N: メッセージ 形式で表示されます。
| ErrorType | 発生シーン | 典型例 |
|---|---|---|
SyntaxError | トークナイズ・パース失敗 | #キャラ名 が閉じていない、関数定義の構文ミス |
ReferenceError | 未定義変数/関数/ラベル参照 | undefined_var の読み取り、存在しないラベルへの jump |
TypeError | 型不一致 | choice の条件式が boolean でない、組み込み関数の引数型が合わない |
RuntimeError | 実行時の論理エラー | while の反復回数が上限(100,000)を超過、配列範囲外アクセス |
StackOverflow | 関数/call 再帰が深度上限(16)超過 | call ループ、再帰関数の非終端 |
FileNotFound | スクリプト・リソース未検出 | map_load で存在しないマップ |
Levenshtein 距離による類似候補提示
ReferenceError 発生時、編集距離 3 以下(かつ変数名長の半分以下)で最大 3 件の候補が自動提示されます。
[KSC ReferenceError] Line 15: 未定義の変数: afection
候補: affection, addition, actor
9. コメント構文
| 形式 | 構文 | ネスト |
|---|---|---|
| 単行コメント | // … | — |
| ブロックコメント | /* … */ | 不可(開始の /* と最初の */ が対応) |
セリフブロック(#キャラ名 ... #)の内部にコメントを書くと、そのままテキストとして表示されます(エスケープ扱いなし)。
インタプリタの役割
KSC インタプリタはスクリプトの「頭脳」です。以下を担当します:
- 逐次実行: スクリプトを1行ずつ解釈して直接実行
- 変数の保持: フラグや好感度などのゲーム状態を管理(グローバル/ローカルスコープ対応)
- フロー制御: if 分岐、ジャンプ、サブルーチン呼び出し(call / ret)、for / while ループ
- 選択肢の処理: プレイヤーの選択を受け取り、対応するラベルへ移動
- デバッグモード: ブレークポイント、変数ウォッチ、トレースログ、ステップ実行
- エラー候補提示: Levenshtein 距離による類似コマンドの提案(「
bggと書きましたが、bgのことですか?」)
インタプリタ自体は画面描画や音声再生を一切行いません。「背景を room に変えて」「BGM を再生して」といった命令を HostAPI 経由で発行するだけです。実際の描画は、各プラットフォームの実装(Web なら PixiJS、Switch なら SDL2)が担当します。
サンプルシナリオ
// === 学園シーン(KSC版) ===
*start
bg("classroom")
ch("sakura", "smile", "C")
#sakura
「おはよう!今日もいい天気だね」
#
affection = 5
ch("sakura", "thinking", "C")
#sakura
「放課後、どうする?」
#
choice {
"一緒に帰る" {
affection += 5
jump("go_home")
}
"図書館で勉強" {
jump("library")
}
"デートに誘う" if (affection >= 10) {
jump("date")
}
}
*go_home
ch("sakura", "smile", "C")
#sakura
「やったー!一緒に帰ろう」
#
jump("day_end")
*library
ch("sakura", "sad", "C")
#sakura
「そっか...じゃあまたね」
#
jump("day_end")
*date
ch("sakura", "smile", "C")
#sakura
「えっ、デート!? 喜んで!」
#
affection += 10
jump("day_end")
*day_end
def get_rank(score) {
if (score > 15) return "S"
if (score > 10) return "A"
return "B"
}
rank = get_rank(affection)
ch_hide("sakura", 500)
bg("sunset")
#system
好感度ランク: {rank}
こうして放課後が過ぎていった。
#
関連ドキュメント
- KS スクリプト仕様書 — タグベースのシンプルな形式
- ブロック型リファレンス — GUI ブロックの詳細
- エンジンの仕組み — インタプリタの仕組み
- バトルシステム — KSC で制御するバトル