Skip to content

RUNE-1000: Story File Format Proposed

Updated2026-08-23

Summary

Rune.Story packages use one .story.yaml entry script, optional .sprite.yaml Composite definitions, and package-local .hlsl files as source-defined Shader Sprite Types. All authoring formats use the canonical Sibling Object projection and package-confined, file-relative resolution. The Story canvas additionally defines world and screen root composition spaces.

Sibling Object Syntax

yaml
type@name: primaryValue
field1: value1
field2: value2
PartMeaning
typeType ID resolved in the descriptor domain expected by the containing context
@nameOptional descriptor-defined object/result name
header valuePrimary value
remaining entriesField Initializers

Global Type IDs MUST use / namespaces. Dot is reserved for member access:

text
charts/table        # global Type ID
cell.flip           # Action on Sprite identity cell
cell.opacity        # property on Sprite identity cell

Explicit local source-defined Type IDs MUST start with ./ or ../. Exact .sprite.yaml and .hlsl paths are valid; extensionless local paths remain the legacy Composite fallback.

Primary binds only through the resolved descriptor's Primary. Sibling Initializers bind named Fields. A mapping Primary MUST remain one value and MUST NOT be expanded into Initializers. Binding applies Primary first and Initializers in source order; an Initializer for the Primary Field overrides the Primary contribution.

Exact duplicate YAML keys MUST fail during syntax parsing. Distinct keys that resolve to one Field, including case or alias variants, MUST fail during semantic binding.

Strict Story YAML and Design Layer

The Story profile accepts mappings, sequences, Sibling Objects, and ordinary scalars only. Anchors, aliases, merge keys, unknown tags, and tags on mappings/sequences MUST be rejected. !bind is the sole accepted tag and MUST wrap one unquoted direct $path. Universal references are $path and $design:name; compact persistent bindings are =path. Bare identifiers in wait slots are zero-to-many selectors; $name selects one named completion facet.

Story-local design: is an ordered extensionless .//../ import list; conversion maps it to extends in an implicit design: v1beta1 document. Local tokens: and styles: are the final layer. Imports are DFS/post-order, package-confined, and deduplicated. Style expansion is compile-time: style fields and StyleRef bases resolve after layering, instance fields and Primary values take precedence, and runtime scenes retain no design registry or deferred style state. See knowledge/story/design-system.md for typed Sprite Field and source-origin rules.

Local Source-Defined Types

Explicit local Type references MUST be package-confined and selected lexically:

  • ./foo.sprite.yaml resolves that exact Composite file;
  • ./foo.hlsl resolves that exact HLSL Shader file;
  • extensionless ./foo or ../foo remains the legacy Composite fallback to foo.sprite.yaml;
  • unsupported explicit extensions MUST fail instead of falling back to global lookup.

A .hlsl file acts as a source-defined Shader Sprite Type. Its required logical size is Primary, inputs is the built-in ordered image[] field, and reflected RuneParameters float/float2/float3/float4 entries become typed Story Fields. Input count MUST match the shader root D2D_INPUT_COUNT, while built-in rune_* cbuffer fields remain engine-supplied and hidden from Story.

Unified Story Value Type Contract

StoryValueTypeDescriptor and StoryValueTypeRegistry MUST be the sole compile-time authority for Design tokens, generated Fields, Composite Fields/Action inputs, styles, and endpoint compatibility. Each descriptor owns one ordinal, case-sensitive Story value Type ID and CLR representation, context-aware syntax/resolved-value conversion, nullability, tri-state intrinsic-default policy, constraints, immutable freeze policy, and documentation. Metadata and runtime objects MUST carry only the validated exact StoryTypeId; they MUST NOT retain the registry.

Generated CLR Fields MUST infer only canonical compatibility IDs for unambiguous/common types: double maps to number, string maps to string, and semantically specialized duration and font-family properties MUST explicitly annotate duration and fontFamily. A projected Primary MUST inherit its Field's ID. Story Type IDs use ordinal equality, while authoring Field names remain case-insensitive.

Every Design/style consumption MUST request the concrete target Field/Primary facet. Symbolic style values MUST remain unresolved through layering and resolve only when that target is known. Equal CLR types MUST NOT imply semantic compatibility. Cross-semantic conversion is permitted only through an explicit typed construction such as brush@panel: $accent, which invokes the target descriptor's converter; assigning a color facet directly to a brush Field MUST fail.

Normal compilation MUST pass the caller's StoryCompileContext through conversion, then validate, freeze, and revalidate the result. Registry composition MUST canonicalize published descriptors, validate ID/CLR/kind and constructor conflicts (including reserved structural IDs), and seal the registry before Design parsing or scope construction.

Story Entry Schema

yaml
story: v1beta1
name: q1-results
canvas: 1920x1080
fps: 60

tokens:
  - tween@panelReveal: 0.3s
  - windows@narrator: en-US-JennyNeural

script:
  - create@cell:
      ./sprites/split-flap: "A"
      x: 120
    with: $fade

  - create@caption:
      text: "Screen-fixed"
      x: 50%
      y: 90%
    space: screen
FieldRequiredDefaultPurpose
story Primaryyes-Version tag v1beta1
namenonullDisplay/output name
canvasno1920x1080Logical Story coordinate system
fpsno60Target frame rate
tokensnobuilt-in facetsAnimation, voice, and typed design tokens
scriptyes-Ordered root Action list

Unknown root Fields MUST fail compilation. The obsolete nested video root is invalid; authors use top-level canvas and fps.

Top-level animations: and voice: registries are invalid. Concrete spring@/tween@ animation constructors and windows@/azure@/plugin voice constructors are tokens: entries. The host adds lowest-priority animation facets default, smooth, snappy, fade, linear and voice facets default, male, female. Omitted motion.with uses animation $default; omitted speak.voice uses voice $default. Platform voice resolution is strict for locale/gender and reports a diagnostic rather than silently falling back.

Logical canvas size does not force output resolution. Output size, scale, path, and safety limits remain renderer/CLI options.

Logical Canvas Composition

Root-created Sprites belong to one immutable composition space:

SpaceSelectionTransformOrder
worlddefault, or create.space: worldcamera, then direct worldfirst
screencreate.space: screenidentity in Story coordinateslast

Nested Layer and Composite children inherit their root ancestor's space. Composite-private creates MUST NOT specify space; choosing an overlay is an explicit root concern. Root Sprite names are shared across both spaces, and camera/world are reserved root names.

Both branches receive the static host output mapping. Implementations MUST apply one final clip equal to the host-mapped logical canvas. They MUST NOT pre-clip world content before camera/world transforms: initially off-canvas world content may enter the viewport. Final world and screen output MUST NOT draw into letterbox or pillarbox regions.

Composite Definition Schema

yaml
sprite: v1beta1
name: split-flap
description: A reusable split-flap cell.
canvas: 52x76

members:
  - field@character: string
    default: " "

  - field@perspective: number
    default: 760
    minExclusive: 0
    animatable: true

  - action@flip:
      - field@to: string
        required: true
      - field@duration: duration
        default: 0.083167s
    script:
      - set:
          label.text: $to

script:
  - create@label:
      text: $character
FieldRequiredPurpose
sprite PrimaryyesVersion tag v1beta1
namenoDiagnostic display name
descriptionnoHuman-readable definition summary
canvasyesFixed local canvas
membersnoOrdered semantic public Field and Action declarations
scriptyesOne-time private initialization script

Composite canvas dimensions MUST be finite, positive, and absolute. Percentages, wildcards, auto, zero, and negative values are invalid. The canvas defines local coordinates and percentage resolution but MUST NOT clip overflow.

Composite scripts cannot resolve root built-in camera or world property objects. The space of a Composite's complete private subtree is inherited from the root-created CompositeSprite.

The first declared Composite Field receives construction Primary. The first Field in an Action declaration receives invocation Primary. Reordering either is a public schema break. Public Composite Fields join inherited Sprite Fields in the construction/property namespace; private children and Actions are not public style targets.

Field Declarations

yaml
- field@name: number
  default: 10
  required: false
  description: Public value.
  animatable: true
  minExclusive: 0

The Field header Primary is its Story value Type ID. Metadata is expressed through Initializers. required: true MUST NOT be combined with default. Without either, the value type supplies its intrinsic value when defined; a type without one requires an explicit declaration choice.

Composite Field IDs MUST resolve only through the sealed Story value registry. Object descriptors MUST NOT be accepted as arbitrary fallback value types. CompositeValueTypeDescriptor is only a view over the registered descriptor; declared defaults, style values, construction values, and Action inputs MUST share its conversion, default, constraint, and freeze policies. Structural array MUST deep-freeze YAML sequences, and structural ref MUST produce immutable StoryRefValue rather than an unvalidated string or object.

Composite Fields are persistent runtime properties. Action Fields are immutable values in one invocation. Phase 1 MUST reject animatable on Action Fields and on Composite Fields whose type is not number.

Package and Local Resolution

The package root is the directory containing the entry .story.yaml. For an explicit local source-defined Type ID, the implementation MUST:

  1. Resolve ./ or ../ relative to the file containing that Type ID.
  2. Accept an exact .sprite.yaml Composite path or an exact .hlsl Shader path.
  3. Treat an extensionless local path as the legacy Composite fallback and append .sprite.yaml.
  4. Reject any other explicit extension.
  5. Canonicalize the path, reject anything outside the package root, and cache definitions by canonical path.

Missing files, malformed definitions, dependency cycles, and unsupported explicit extensions MUST produce diagnostics. Diagnostics MUST render file names as package-relative paths with / separators.

Intentional ../ reuse is allowed only while the canonical target remains in the package:

yaml
# sprites/board.sprite.yaml
- create@cell:
    ../shared/cell: A

Immutable Source Origins

Every compiled definition and script-template node MUST retain an immutable origin containing:

  • canonical package root;
  • canonical defining file;
  • defining file base directory;
  • package-relative display path.

Nested definitions MUST receive their own origins. Parent compilation MUST NOT overwrite child base directories. Cached definitions MUST NOT contain mutable per-instance resolver state.

Runtime resource resolution for images, video, SVG, and nested Composite references MUST use the origin of the node that declared the resource while enforcing the same package root. Materializing fresh Action graphs MUST preserve template origins and source spans.

Value Prefixes

Unquoted scalar prefixes are semantic:

SyntaxMeaning
$pathEvaluate current model value once
=pathInstall a persistent one-way binding
bare identifierCompletion selector in a compatible wait/timing slot
!bind $pathExplicit tagged form of a direct $path binding
alias@name: $sourceExact-facet alias
alias@name: !semanticType $sourceExact selected facet alias
type@name: $sourceTarget constructor Primary/converter

Quoted "$path" and "=path" are literal strings. ${...} is reserved.

Built-in Animation and Voice Facets

$default, $smooth, $snappy, $fade, and $linear are built-in animation facets and may be overridden by a same-name spring@ or tween@ token. $default, $male, and $female also resolve target-directed voice facets where applicable. AnimationRef and VoiceRef are frozen during compilation; bake has no named animation/voice registries. none is the only bare animation keyword and means immediate assignment.

Tween tokens may consume a compile-time duration token (duration@fast: 0.2s, then tween@intro: $fast). Runtime completion names and selectors are invalid in token definitions.

Out of Scope

Standalone Action files, imports, inline primitive definitions, slots, anchors, events, iteration, independently playable nested stories, and Composite morph are outside Phase 1.

Rationale

One syntax model across entry scripts, Composite definitions, Actions, and Sprites keeps binding strict and source-aware. Immutable per-file origins make recursive cached definitions deterministic, while package confinement permits reusable sibling directories without arbitrary file access. A fixed logical canvas and explicit world/screen spaces support camera movement without moving HUD.

Rune Project brings Rune Story authoring together with the Rune Engine rendering foundation.