multilinear-story 0.1.3

Game-facing story runtime over a multilinear net: presentation wiring and owner-routed callable events
Documentation

multilinear-story

A game-facing story runtime over a multilinear net.

It loads a multilinear net (a single .mld: aspect defaults before the first header, then events) together with a presentation wiring file, then routes the currently callable events to their owners and reads aspect state for presentation. This is the shared substrate behind the callable-events-as-options pattern: an interactable's options are the callable events wired to it, applying one is a transition, and the net re-derives availability on its own.

The split

  • Events are the actions (dialog options, interactions). An owner's options are its callable events — no variant selection, no conflict resolution. Multiple callable events simply mean multiple options.
  • Aspects are exclusive, readable state (clothing, mood, location). Read via value/value_name to present, never to decide which events exist.

Save and restore

Story::state() returns the current aspect state as raw value indices (a Vec<usize>), and Story::load_with_state(net, wiring, state) reloads a story with that state restored instead of starting fresh. Serialize the Vec<usize> however you like; only restore it against the same net.

Jumping to a situation

Story::set_value(aspect, value) sets an aspect by name, bypassing the events that would normally reach it, and Story::values(aspect) lists the value names an aspect can take. Together they let a debug console or a command-line flag start a session anywhere in the net ("give the player the sword, skip the tutorial"). Regular play still goes through options/play.

Wiring format

Header-based (via header-parsing); event names are header segments, so they may contain whitespace:

# events
## bibo talk
owner bibo
dialog bibo_talk

# idle
## bibo
dialog bibo_idle

Every wired event must exist in the net, or Story::load fails with LoadError::UnknownEvent. Net events without wiring are reported as warnings (reachable but never surfaced).

Example

use std::path::Path;
use multilinear_story::Story;

let mut story = Story::load(
    Path::new("assets/story.mld"),
    Path::new("assets/wiring"),
)?;

for event in story.options("bibo") {        // bibo's current options
    println!("{:?}", story.dialog(event));
}
if let Some(&choice) = story.options("bibo").first() {
    story.play(choice);                     // apply; net re-derives availability
}
println!("{:?}", story.value_name("companion bibo")); // present aspect state

AI-coding-friendly properties

  • Errors are an explicit thiserror enum (LoadError) with Display; load returns Result rather than silently falling back to an empty story.
  • No implicit state: routing is options/play, reading is value/value_name — no hidden coupling.
  • Aspect and Event are re-exported, so consumers need not depend on multilinear directly.