YAML Accounted Multi adV. Architect
A pure scenario dispatcher written in Rust. Parse YAML, drive your game — YamAMVA handles state, branching, and blocking menus while knowing nothing about rendering, audio, or AI.
while (1) {
int id = yamamva_exec(h, &args);
if (id == YAMAMVA_END) break;
switch (id) {
case CMD_BG: show_bg(&args); break;
case CMD_TEXT: show_text(&args); break;
case CMD_MENU: show_menu(&args); break;
}
}Windows message loop meets visual novels.
YamAMVA is GetMessage; your game is DispatchMessage.
- Engine-agnostic — Unity (C# P/Invoke), Python (ctypes), C/C++, WASM
- Pull-based execution — your game calls
exec(), not the other way around - Registration model — define your own node types; YamAMVA dispatches by command ID
- World / Scene scope — split scenarios across multiple YAML files with
file:scenejump notation - Built-in flow control —
jump/do/when/incase/end - Expression evaluator —
score >= 80 and not accused,count + 1 - Conditional elements — menu items filtered by
whenbefore reaching your game - Save / restore — snapshot to JSON, resume from any point
- Zero rendering opinion — same YAML drives 3D, 2D, retro pixel, or web scroll
- Minimal dependencies — only
serde+serde_yaml+serde_json
MindFox OSS
├── MxBS — NPC memory engine (Rust, standalone)
├── MxMindFox — Mood & decision system (Rust, MxBS workspace)
└── YamAMVA — YAML scenario dispatcher (Rust, standalone) ← this
Three crates, all Rust, all with C bindings, zero interdependence. Combine as needed.
cargo build --release
# Output: target/release/libyamamva.so (Linux)
# target/release/libyamamva.dylib (macOS)
# target/release/yamamva.dll (Windows)id: hello
title: "Hello YamAMVA"
version: "1.0"
entry: scene_start
state:
greeted: false
scenes:
scene_start:
- speak:
character: elmar
text: "Hello! Pick a place."
emotion: joy
- menu:
style: vertical
elements:
- { key: park, label: "Go to the park" }
- { key: home, label: "Stay home" }
- incase:
- when: "$result == 'park'"
next: scene_park
- next: scene_home
scene_park:
- speak:
character: elmar
text: "The park is nice today!"
- do:
greeted: true
- end: true
scene_home:
- speak:
character: elmar
text: "Home sweet home."
- end: true#include <stdio.h>
#define CMD_SPEAK 1
#define CMD_MENU 2
int main() {
const char* yaml = load_file("hello.yaml");
YamamvaHandle* h = yamamva_load(yaml, strlen(yaml));
yamamva_register(h, "speak", CMD_SPEAK, YAMAMVA_PASS);
yamamva_register(h, "menu", CMD_MENU, YAMAMVA_BLOCKING);
YamamvaArgs args = {0};
while (1) {
int id = yamamva_exec(h, &args);
if (id == YAMAMVA_END) break;
switch (id) {
case CMD_SPEAK:
printf("Speech: %s\n", args.node_json);
break;
case CMD_MENU:
printf("Menu with %d choices\n", args.element_count);
args.result = "park"; // player's choice
break;
}
}
yamamva_free(h);
return 0;
}byte[] yaml = File.ReadAllBytes("scenario.yaml");
IntPtr h = YamamvaBridge.yamamva_load(yaml, (uint)yaml.Length);
YamamvaBridge.yamamva_register(h, "speak", CMD_SPEAK, YAMAMVA_PASS);
YamamvaBridge.yamamva_register(h, "move", CMD_MOVE, YAMAMVA_PASS);
YamamvaBridge.yamamva_register(h, "menu", CMD_MENU, YAMAMVA_BLOCKING);
// exec loop in a coroutine — see examples/unity/from yamamva_bridge import YamamvaBridge
CMD_SPEAK = 1
CMD_MENU = 2
yamva = YamamvaBridge(yaml_str) # load from string
yamva.register("speak", CMD_SPEAK) # PASS
yamva.register("menu", CMD_MENU, blocking=True) # BLOCKING
while True:
cmd, info = yamva.exec()
if cmd == yamva.END:
break
if cmd == CMD_SPEAK:
print(info["node_json"]["text"])
elif cmd == CMD_MENU:
yamva.set_result("park") # player's choice
yamva.close()See python/yamamva_bridge.py for the full wrapper.
import ctypes
lib = ctypes.CDLL("./libyamamva.so")
h = lib.yamamva_load(yaml_bytes, len(yaml_bytes))
lib.yamamva_register(h, b"speak", 1, 0) # PASS
lib.yamamva_register(h, b"menu", 2, 1) # BLOCKING
# exec loop — see test_yamamva_ffi.pyid: my_scenario # required
title: "My Scenario" # required
version: "1.0"
entry: scene_first # required — first scene to execute
state: {} # initial state variables
characters: {} # metadata (YamAMVA does NOT resolve names)
backgrounds: {} # metadata
bgm: {} # metadata
scenes: # required
scene_first:
- ...nodes...Split large scenarios across files using world.yaml + scenes/ directory:
my_game/
├── world.yaml ← global state, metadata, entry point
└── scenes/
├── intro.yaml ← scene_intro
├── hub.yaml ← scene_hub, scene_shop
└── endings.yaml ← scene_win, scene_lose
# world.yaml
id: my_game
title: "My Game"
entry: intro:scene_intro # file:scene notation
scene_path: scenes/
state:
gold: 100
characters:
hero: { name: "Hero" }# scenes/hub.yaml
scene_hub:
- text: "Where to?"
- menu:
elements:
- { key: shop, label: "Shop" }
- { key: end, label: "Leave" }
- incase:
- when: "$result == 'shop'"
next: scene_shop # same file — no prefix needed
- next: endings:scene_win # cross-file — file:scene required
scene_shop:
- text: "Welcome to the shop."
- jump:
- next: scene_hubLoad with yamamva_load_world() (C API) or parse_world() (Rust).
Single-file scenarios loaded via yamamva_load() remain fully compatible.
Any YAML key becomes a node type. Register it and YamAMVA dispatches it. Unregistered nodes are silently skipped.
# Simple pass-through nodes
- bg: lobby
- text: "You entered the room."
- speak:
character: elmar
text: "Welcome!"
emotion: joy
# Blocking node (game must write args.result)
- menu:
style: vertical
elements:
- { key: sword, label: "Buy Sword (500G)", price: 500, when: "gold >= 500" }
- { key: leave, label: "Leave" }
# Node-level when (skipped if false)
- speak:
character: elmar
text: "You already bought a sword!"
when: "has_sword"These are handled internally. Your game never sees them.
| Node | Purpose | Example |
|---|---|---|
do |
Update state | - do: { score: "score + 10" } |
jump |
Branch to scene | - jump: [{ when: "score >= 80", next: good_end }, { next: bad_end }] |
incase |
Branch on $result |
- incase: [{ when: "$result == 'yes'", next: scene_a }, { next: scene_b }] |
when |
Conditional skip | (attached to any node) |
end |
End scenario | - end: true |
| Category | Operators |
|---|---|
| Comparison | == != < > <= >= |
| Logic | and or not |
| Arithmetic | + - * / |
| Types | true false 42 3.14 "string" |
| Special | $result (last BLOCKING return value) |
Whitelist-only. No arbitrary code execution.
YamamvaHandle* yamamva_load(const char* yaml, uint32_t len);
YamamvaHandle* yamamva_load_world(const char* world_yaml_path);
void yamamva_free(YamamvaHandle* h);void yamamva_register(YamamvaHandle* h, const char* node_type,
int32_t command_id, int32_t flags);
// flags: YAMAMVA_PASS (0) or YAMAMVA_BLOCKING (1)int32_t yamamva_exec(YamamvaHandle* h, YamamvaArgs* args);
// Returns: command_id (>= 1) or YAMAMVA_END (-1)const char* yamamva_get_state(const YamamvaHandle* h, const char* key);
void yamamva_set_state(YamamvaHandle* h, const char* key,
const char* value_json);const char* yamamva_meta(const YamamvaHandle* h, const char* section);
// section: "characters", "backgrounds", "bgm", "format", "state"const char* yamamva_save(const YamamvaHandle* h);
YamamvaHandle* yamamva_restore(const char* yaml, uint32_t len,
const char* save_json);void yamamva_free_string(const char* s);
// Free strings returned by yamamva_get_state, yamamva_meta, yamamva_savetypedef struct {
const char* node_type;
const char* node_json; // full node as JSON
uint32_t element_count;
const YamamvaElement* elements;
const char* result; // game writes this (BLOCKING only)
} YamamvaArgs;
typedef struct {
const char* key;
const char* label;
const char* extra_json; // all fields except key/label/when
} YamamvaElement;#define YAMAMVA_END (-1)
#define YAMAMVA_PASS (0)
#define YAMAMVA_BLOCKING (1)Total: 11 functions, 3 constants, 2 structs.
YamAMVA knows flow control and nothing else:
| YamAMVA knows | YamAMVA does NOT know |
|---|---|
| YAML structure | How to draw backgrounds |
| State variables | Character names → sprites/models |
| Conditional branching | Menu UI design |
when element filtering |
Audio playback |
| Save/restore snapshots | LLM calls |
| Command dispatch | Network, database, physics |
Same YAML, different games:
| Platform | speaker: elmar resolves to |
|---|---|
| Unity (3D) | VRM model with lip sync |
| Web (scroll) | PNG sprite overlay |
| Retro PC | 16-color pixel portrait |
See the examples/ directory:
ellmar_tour.yaml— Room tour with move/speak/menu (used in ELLMAR-Unity integration test)oyatsu_adv.yaml— Mystery ADV with hearing menu, conditional branching, state tracking
python/yamamva_bridge.py — full ctypes wrapper class.
from yamamva_bridge import YamamvaBridge
yamva = YamamvaBridge(yaml_str)
yamva.register("speak", 1)
yamva.register("menu", 2, blocking=True)
cmd, info = yamva.exec()
# info = {"node_type": str, "node_json": dict, "elements": [{"key", "label", "extra"}]}
yamva.set_result("choice_key") # answer BLOCKING node
val = yamva.get_state("score") # read state variable
yamva.set_state("score", 100) # write state variable
save_json = yamva.save() # serialize for save file
# Restore from save
yamva2 = YamamvaBridge.restore(yaml_str, save_json)All 11 C API functions are wrapped. Requires libyamamva.dylib/.so (run cargo build --release).
cargo test
# 36 tests (21 unit + 15 world/scope integration), 0 warnings
# FFI tests (requires Python 3)
python test_yamamva_ffi.py
# 5 testsYamAMVA(山姥)は YAML シナリオ・ディスパッチャです。
YAML で書かれたシナリオの進行制御(ステート管理・条件分岐)だけを行い、描画・音声・LLM・メニューUI の「意味」は一切知りません。ゲーム側が登録したコマンドIDに対して引数を返すだけの、純粋なステートマシンです。
Windows のメッセージループと同じ構造 —— 山姥が GetMessage、ゲーム側が DispatchMessage。
| クレート | 役割 |
|---|---|
| MxBS | NPC記憶エンジン |
| MxMindFox | 感情 & 意思決定 |
| YamAMVA | YAML シナリオディスパッチャ |
3つとも Rust、3つとも C bindings 付き、相互依存なし。
MIT License — see LICENSE
MULTITAPPS INC. — Mahito KIDA + エルマー🦊