Files
storybook/docs/SBIR-v0.3.1-SPEC.md
Sienna Meridian Satterwhite 2c898347ee feat(lang): rename schedule keyword from extends to modifies
Changed the schedule composition keyword from "extends" to "modifies"
to better reflect the semantic meaning of schedule inheritance. When
a schedule modifies another, it inherits base blocks and can override
them by name or add new blocks.

This is a breaking change for all existing Storybook files that use
schedule composition. The migration is a simple find-and-replace:
  schedule X extends Y → schedule X modifies Y

Changes include:
- Grammar: Updated tree-sitter grammar and lexer token
- Parser: Updated lalrpop parser and AST field names
- Documentation: Updated all reference docs, tutorials, and specs
- Examples: Updated baker-family example schedules
- Tests: Updated all test cases and corpus files
- Testing: Added type system keywords to prop_tests exclusion list
- Tooling: Added xtask for workspace cleanup
- Version: Bumped to v0.3.1 (skipping v0.3.0)
- Spec: Created SBIR v0.3.1 spec documenting the change

BREAKING CHANGE: The "extends" keyword for schedules has been
replaced with "modifies". Update all schedule declarations.
2026-02-16 22:52:48 +00:00

31 KiB

Storybook Intermediate Representation (SBIR) v0.3.1 Specification

Version: 0.3.1 Status: Draft Date: February 2026


Table of Contents

  1. Introduction
  2. File Format Overview
  3. Section 1: Header
  4. Section 2: String Table
  5. Section 3: Types
  6. Section 4: Characters
  7. Section 5: Templates
  8. Section 6: Species
  9. Section 7: Behaviors
  10. Section 8: Schedules
  11. Section 9: Institutions
  12. Section 10: Relationships
  13. Section 11: Locations
  14. Section 12: Life Arcs
  15. Section 13: Enums
  16. Changelog

1. Introduction

1.1 Purpose

The Storybook Intermediate Representation (SBIR) is a binary format that represents compiled Storybook programs. It serves as the interchange format between the Storybook compiler and runtime engines.

1.2 Design Goals

  • Compact: Efficient binary encoding for large story worlds
  • Fast to load: Direct memory mapping when possible
  • Versioned: Clear version tracking for format evolution
  • Complete: Represents all semantic information from source
  • Runtime-ready: Minimal post-processing required

1.3 Changes in v0.3.1

Keyword changes:

  1. Schedule Composition - The extends keyword for schedule inheritance has been renamed to modifies
    • Old: schedule BakerSchedule extends WorkWeek { ... }
    • New: schedule BakerSchedule modifies WorkWeek { ... }
    • This is a find-and-replace change - no binary format changes required
    • The field name in the Schedule struct remains the same (just stores the base schedule name)

1.4 Changes in v0.3.0

Major additions:

  1. Type System - Concepts, sub-concepts (enum/record), and concept comparisons with pattern matching
  2. Species-Based Template Inheritance - Templates can declare a species base for field inheritance
  3. Life Arc Field Requirements - Life arcs can declare required fields with type annotations

Breaking changes:

  • TYPES section now populated with concept, sub_concept, and concept_comparison definitions
  • Value discriminants renamed: Int→Number, Float→Decimal, String→Text, Bool→Boolean
  • Expression discriminants renamed: IntLit→NumberLit, FloatLit→DecimalLit, StringLit→TextLit, BoolLit→BooleanLit
  • TEMPLATES section extended with species_base field
  • LIFE ARCS section extended with required_fields

1.5 Changes in v0.2.0

Major additions:

  1. Resource Linking System - Characters and institutions can link to behaviors and schedules with conditions and priorities
  2. Year-Long Schedule System - Schedules support temporal patterns (day-specific, seasonal, recurrence)
  3. Behavior Tree Enhancements - Named nodes, decorator parameters, keyword transformations

Breaking changes:

  • CHARACTERS section extended with behavior_links and schedule_links
  • INSTITUTIONS section extended with behavior_links and schedule_links
  • SCHEDULES section redesigned with patterns and inheritance
  • BEHAVIORS section extended with named nodes and parameterized decorators

2. File Format Overview

2.1 File Structure

[Header]
[String Table]
[Type Definitions]
[Characters Section]
[Templates Section]
[Species Section]
[Behaviors Section]
[Schedules Section]
[Institutions Section]
[Relationships Section]
[Locations Section]
[Life Arcs Section]
[Enums Section]

2.2 Primitive Types

Type Size Description
u8 1 byte Unsigned 8-bit integer
u16 2 bytes Unsigned 16-bit integer (little-endian)
u32 4 bytes Unsigned 32-bit integer (little-endian)
u64 8 bytes Unsigned 64-bit integer (little-endian)
i32 4 bytes Signed 32-bit integer (little-endian)
i64 8 bytes Signed 64-bit integer (little-endian)
f32 4 bytes IEEE 754 single-precision float
f64 8 bytes IEEE 754 double-precision float
bool 1 byte 0 = false, 1 = true
String Variable Length-prefixed UTF-8: u32 length + [u8; length]
StringRef 4 bytes Index into string table (u32)

2.3 Common Structures

Option

Optional values are encoded with a discriminant byte followed by the value if present:

u8 discriminant:
  0 = None  → No additional bytes, next field starts immediately
  1 = Some  → T data follows immediately after discriminant

Encoding:

  • None case: Just 1 byte (0x00), nothing else
  • Some case: 1 byte (0x01) + full T encoding

Examples:

Option<StringRef> when None:

0x00                           ← 1 byte total

Option<StringRef> when Some(42):

0x01                           ← discriminant (Some)
0x2A 0x00 0x00 0x00           ← StringRef = 42 (u32)
                               ← 5 bytes total

Option<Vec<Field>> when None:

0x00                           ← 1 byte total

Option<Vec<Field>> when Some([field1, field2]):

0x01                           ← discriminant (Some)
0x02 0x00 0x00 0x00           ← count = 2 (u32)
[Field 1 encoding]             ← First field
[Field 2 encoding]             ← Second field
                               ← 5+ bytes (depends on field sizes)

Vec

u32 count
[T; count]

2.4 Binary Format Conventions

IMPORTANT: SBIR is a packed binary format with no separators.

No Delimiter Bytes

All data is laid out sequentially in memory with no separator characters between:

  • Items in a Vec
  • Fields in a struct
  • Sections in the file
  • Values of any kind

The file is a continuous stream of bytes where each element's size determines where the next element begins.

Length-Prefix Pattern

All variable-length data uses a length-prefix pattern:

  1. Length/count field (u32) tells you "how much data is coming"
  2. Data bytes - read exactly that amount
  3. Next field starts immediately after (no gap, no separator)

Reading Variable-Length Vec

When T itself is variable-length, each item carries its own size information:

Example: Vec

u32 count = 3                    ← "There are 3 strings"
  String 1:
    u32 length = 5               ← "First string is 5 bytes"
    [u8; 5] = "hello"            ← Read 5 bytes, next item starts immediately
  String 2:
    u32 length = 5               ← "Second string is 5 bytes"
    [u8; 5] = "world"            ← Read 5 bytes
  String 3:
    u32 length = 3               ← "Third string is 3 bytes"
    [u8; 3] = "foo"              ← Read 3 bytes

Example: Vec (complex variable-length structs)

u32 count = 2                    ← "There are 2 characters"
  Character 1:
    u32 name_len = 6
    [u8; 6] = "Martha"
    u8 species_discriminant = 1  ← Option::Some
    u32 species_ref = 10
    u32 fields_count = 2
      Field 1: (name_ref, value_discriminant, value_data)
      Field 2: (name_ref, value_discriminant, value_data)
    u32 template_refs_count = 1
      StringRef = 15
    u32 behavior_links_count = 0
    u32 schedule_links_count = 0
  Character 2:
    (starts immediately after Character 1 ends)
    u32 name_len = 5
    [u8; 5] = "David"
    ... (continues)

Parsing Algorithm

The parser reads sequentially using each length field to know how many bytes to consume:

position = 0
while position < file_size:
    read_length_or_discriminant()
    read_exactly_that_many_bytes()
    position += bytes_read
    // Next field starts here (no seeking, no separator scanning)

Key Rules

  1. Fixed-size types (u8, u32, f64, etc.) take their exact size - no padding
  2. Variable-size types always start with a length prefix (u32)
  3. Option starts with a discriminant byte (0=None, 1=Some)
  4. Enums/discriminated unions start with a discriminant byte
  5. No alignment padding - all data is tightly packed

There are no:

  • No newline characters (\n)
  • No separators (,, ;, spaces)
  • No null terminators (except inside UTF-8 string data)
  • No padding bytes between fields (unless explicitly specified)
  • No section markers or headers (sections just follow each other)

The documentation uses newlines and indentation for readability only - the actual binary file is a continuous stream of bytes with no whitespace.


3. Section 1: Header

Magic: [u8; 4]     // "SBIR" (0x53 0x42 0x49 0x52)
Version: u16       // Major version (0x0003 for v0.3.0)
MinorVersion: u16  // Minor version (0x0000)
Flags: u32         // Reserved (0x00000000)
SectionCount: u32  // Number of sections (currently 13)

Version History:

  • v0.1.0: Implicit version (pre-release)
  • v0.2.0: First formal versioned release
  • v0.3.0: Type system, species inheritance, life arc requirements

4. Section 2: String Table

The string table stores all strings used in the SBIR file.

Count: u32
Strings: [String; Count]

Usage: All StringRef types reference an index in this table.

Encoding: UTF-8 with length prefix.

Example:

Count: 3
Strings:
  [0]: "Alice"
  [1]: "Wonderland"
  [2]: "rabbit_hole"

5. Section 3: Types

Note: New in v0.3.0. This section encodes the concept type system.

5.1 Structure

ConceptCount: u32
Concepts: [Concept; ConceptCount]
SubConceptCount: u32
SubConcepts: [SubConcept; SubConceptCount]
ConceptComparisonCount: u32
ConceptComparisons: [ConceptComparison; ConceptComparisonCount]

5.2 Concept Encoding

Concept:
  name: StringRef

A concept is a named type declaration with no additional data. Sub-concepts reference their parent concept by name.

5.3 SubConcept Encoding

SubConcept:
  name: StringRef
  parent_concept: StringRef
  kind: u8
  data: <depends on kind>

Kind Discriminants:

0x01 = Enum
0x02 = Record

Enum (0x01):

variants: Vec<StringRef>

Record (0x02):

fields: Vec<Field>

Where Field is encoded as:

Field:
  name: StringRef
  value: Value

5.4 ConceptComparison Encoding

ConceptComparison:
  name: StringRef
  concept_ref: StringRef             // The concept being compared
  sub_concept_ref: StringRef         // The sub-concept (enum) being matched
  arms: Vec<ComparisonArm>

ComparisonArm:

ComparisonArm:
  variant: StringRef                 // Enum variant to match
  fields: Vec<Field>                 // Field assignments for this arm
  condition: Option<Expression>      // Optional guard condition

5.5 Binary Example

Source:

concept Cup
sub_concept Cup.Type { Small, Medium, Large }
sub_concept Cup.Material { weight: 100, fragile: true }

concept_comparison CupDefaults for Cup matching Cup.Type {
    Small { capacity: 200 }
    Medium { capacity: 350 }
    Large { capacity: 500 }
}

Binary (conceptual):

ConceptCount: 1
Concepts:
  [0]: name = StringRef("Cup")

SubConceptCount: 2
SubConcepts:
  [0]: SubConcept {
    name: StringRef("Type")
    parent_concept: StringRef("Cup")
    kind: 0x01 (Enum)
    variants: [StringRef("Small"), StringRef("Medium"), StringRef("Large")]
  }
  [1]: SubConcept {
    name: StringRef("Material")
    parent_concept: StringRef("Cup")
    kind: 0x02 (Record)
    fields: [
      Field { name: StringRef("weight"), value: Number(100) },
      Field { name: StringRef("fragile"), value: Boolean(true) }
    ]
  }

ConceptComparisonCount: 1
ConceptComparisons:
  [0]: ConceptComparison {
    name: StringRef("CupDefaults")
    concept_ref: StringRef("Cup")
    sub_concept_ref: StringRef("Type")
    arms: [
      { variant: StringRef("Small"), fields: [("capacity", Number(200))], condition: None },
      { variant: StringRef("Medium"), fields: [("capacity", Number(350))], condition: None },
      { variant: StringRef("Large"), fields: [("capacity", Number(500))], condition: None }
    ]
  }

6. Section 4: Characters

6.1 Structure

Count: u32
Characters: [Character; Count]

6.2 Character Encoding

Character:
  name: StringRef
  species: Option<StringRef>
  fields: Map<StringRef, Value>
  template_refs: Vec<StringRef>          // Templates this character uses
  behavior_links: Vec<BehaviorLink>      // NEW in v0.2.0
  schedule_links: Vec<ScheduleLink>      // NEW in v0.2.0
BehaviorLink:
  behavior_id: u32                       // Index into BEHAVIORS section
  priority: u8                           // 0=Low, 1=Normal, 2=High, 3=Critical
  condition: Option<Expression>          // Optional activation condition
  is_default: bool                       // Default behavior (no condition)

Priority Encoding:

enum Priority {
    Low = 0,
    Normal = 1,
    High = 2,
    Critical = 3,
}

Selection Algorithm:

  1. Filter links where condition evaluates to true (or is None)
  2. Sort by priority (descending)
  3. Return highest priority link
  4. If tie, use declaration order
ScheduleLink:
  schedule_id: u32                       // Index into SCHEDULES section
  condition: Option<Expression>          // Optional activation condition
  is_default: bool                       // Default schedule (fallback)

Selection Algorithm:

  1. Iterate schedule_links in order
  2. Skip default links initially
  3. Return first link where condition is true (or None)
  4. If no match, use default link (if present)

6.5 Value Encoding

Value:
  discriminant: u8
  data: <depends on discriminant>

Discriminants:

0x01 = Number(i64)                               // Renamed from Int in v0.3.0
0x02 = Decimal(f64)                              // Renamed from Float in v0.3.0
0x03 = Text(StringRef)                           // Renamed from String in v0.3.0
0x04 = Boolean(bool)                             // Renamed from Bool in v0.3.0
0x05 = Range(Value, Value)
0x06 = Time(u8 hour, u8 minute, u8 second)
0x07 = Duration(u32 hours, u32 minutes, u32 seconds)
0x08 = Identifier(Vec<StringRef>)  // Qualified path
0x09 = List(Vec<Value>)
0x0A = Object(Vec<Field>)
0x0B = ProseBlock(StringRef tag, String content)
0x0C = Override(...)

Note: The wire format (discriminant bytes 0x01-0x0C) is unchanged from v0.2.0. Only the semantic names have been updated to match the Storybook language's type terminology.

6.6 Expression Encoding

Expression:
  discriminant: u8
  data: <depends on discriminant>

Discriminants:

0x01 = NumberLit(i64)                            // Renamed from IntLit in v0.3.0
0x02 = DecimalLit(f64)                           // Renamed from FloatLit in v0.3.0
0x03 = TextLit(StringRef)                        // Renamed from StringLit in v0.3.0
0x04 = BooleanLit(bool)                          // Renamed from BoolLit in v0.3.0
0x05 = Identifier(Vec<StringRef>)
0x06 = FieldAccess(Box<Expr>, StringRef)
0x07 = Comparison(Box<Expr>, CompOp, Box<Expr>)
0x08 = Logical(Box<Expr>, LogicalOp, Box<Expr>)
0x09 = Unary(UnaryOp, Box<Expr>)
0x0A = Quantifier(QuantifierKind, StringRef var, Box<Expr> collection, Box<Expr> predicate)

CompOp: u8 (0x01=Eq, 0x02=Ne, 0x03=Lt, 0x04=Le, 0x05=Gt, 0x06=Ge)

LogicalOp: u8 (0x01=And, 0x02=Or)

UnaryOp: u8 (0x01=Not, 0x02=Neg)

QuantifierKind: u8 (0x01=ForAll, 0x02=Exists)


7. Section 5: Templates

Count: u32
Templates: [Template; Count]

Template:
  name: StringRef
  species_base: Option<StringRef>    // NEW in v0.3.0 - Species base for field inheritance
  strict: bool
  includes: Vec<StringRef>
  fields: Map<StringRef, Value>

Species Base (NEW in v0.3.0):

When species_base is Some(ref), the template inherits fields from the referenced species as its base layer. The override chain is:

  1. Species fields (base layer)
  2. Included template fields (override species)
  3. Template's own fields (override includes)
  4. Character fields (override template)

Last-one-wins semantics apply at each layer. Type invariance is enforced: a field's type cannot change through the inheritance chain (e.g., a Number field in the species cannot become a Text field in the template).


8. Section 6: Species

Count: u32
Species: [Species; Count]

Species:
  name: StringRef
  includes: Vec<StringRef>
  fields: Map<StringRef, Value>

9. Section 7: Behaviors

9.1 Structure

Count: u32
Behaviors: [Behavior; Count]

9.2 Behavior Encoding

Behavior:
  name: StringRef
  root: BehaviorNode

9.3 BehaviorNode Encoding

BehaviorNode:
  discriminant: u8
  data: <depends on discriminant>

Node Type Discriminants

0x01 = Selector
0x02 = Sequence
0x03 = Condition
0x04 = Action
0x10 = DecoratorRepeat
0x11 = DecoratorRepeatN
0x12 = DecoratorRepeatRange
0x13 = DecoratorInvert
0x14 = DecoratorRetry
0x15 = DecoratorTimeout
0x16 = DecoratorCooldown
0x17 = DecoratorGuard
0x18 = DecoratorSucceedAlways
0x19 = DecoratorFailAlways
0x20 = SubTree

Selector Node (0x01)

label: Option<StringRef>               // NEW in v0.2.0
children: Vec<BehaviorNode>

Keyword Mapping: selector or choose

Sequence Node (0x02)

label: Option<StringRef>               // NEW in v0.2.0
children: Vec<BehaviorNode>

Keyword Mapping: sequence or then

Condition Node (0x03)

expression: Expression

Keyword Mapping: if or when

Action Node (0x04)

name: StringRef
parameters: Vec<Field>

Keyword Mapping: No prefix (just action name)

Decorator Nodes (0x10-0x19)

DecoratorRepeat (0x10):

child: Box<BehaviorNode>

Keyword: repeat { ... }

DecoratorRepeatN (0x11):

count: u32
child: Box<BehaviorNode>

Keyword: repeat(N) { ... }

DecoratorRepeatRange (0x12):

min: u32
max: u32
child: Box<BehaviorNode>

Keyword: repeat(min..max) { ... }

DecoratorInvert (0x13):

child: Box<BehaviorNode>

Keyword: invert { ... }

DecoratorRetry (0x14):

max_attempts: u32
child: Box<BehaviorNode>

Keyword: retry(N) { ... }

DecoratorTimeout (0x15):

milliseconds: u64                      // Duration in milliseconds
child: Box<BehaviorNode>

Keyword: timeout(duration) { ... } Example: timeout(5s), timeout(30m), timeout(2h)

DecoratorCooldown (0x16):

milliseconds: u64                      // Cooldown period in milliseconds
child: Box<BehaviorNode>

Keyword: cooldown(duration) { ... }

DecoratorIf (0x17):

condition: Expression
child: Box<BehaviorNode>

Keyword: if(condition) { ... }

DecoratorSucceedAlways (0x18):

child: Box<BehaviorNode>

Keyword: succeed_always { ... }

DecoratorFailAlways (0x19):

child: Box<BehaviorNode>

Keyword: fail_always { ... }

SubTree Node (0x20)

path: Vec<StringRef>                   // Qualified path to subtree

Keyword: include path::to::subtree


10. Section 8: Schedules

10.1 Structure

Count: u32
Schedules: [Schedule; Count]

10.2 Schedule Encoding (REDESIGNED in v0.2.0)

Schedule:
  name: StringRef
  parent_schedule_id: Option<u32>      // Index into SCHEDULES section (for inheritance)
  blocks: Vec<ScheduleBlock>
  patterns: Vec<SchedulePattern>       // Day-specific, seasonal, recurrence patterns

10.3 ScheduleBlock

ScheduleBlock:
  name: StringRef                      // Required in v0.2.0
  start: u16                           // Minutes since midnight (0-1439)
  end: u16                             // Minutes since midnight (0-1439)
  behavior_ref: Option<Vec<StringRef>> // Reference to behavior (qualified path)
  fields: Map<StringRef, Value>

Changes from v0.1.0:

  • name is now required (was optional)
  • behavior_ref replaces activity: String
  • Time is encoded as minutes since midnight

10.4 SchedulePattern (NEW in v0.2.0)

SchedulePattern:
  kind: u8                             // Pattern discriminant
  specification: Vec<u8>               // Pattern-specific data
  blocks: Vec<ScheduleBlock>           // Blocks to apply when pattern matches

Pattern Kind Discriminants:

0x01 = DayPattern
0x02 = SeasonPattern
0x03 = RecurrencePattern

DayPattern (0x01)

specification:
  day_enum_value: StringRef            // References user-defined DayOfWeek enum

Example: on Fireday → references "Fireday" from user's DayOfWeek enum

SeasonPattern (0x02)

specification:
  season_enum_values: Vec<StringRef>   // Multiple seasons allowed

Example: season (EarlySummer, LateSummer) → references Season enum values

RecurrencePattern (0x03)

specification:
  name: StringRef                      // Recurrence name
  spec: RecurrenceSpec

RecurrenceSpec:

RecurrenceSpec:
  discriminant: u8
  data: <depends on discriminant>

Discriminants:
  0x01 = Every(u32 days)               // every N days
  0x02 = WeeklyOn(Vec<StringRef>)      // weekly on [days]
  0x03 = MonthlyOnDay(u8 day)          // monthly on day N
  0x04 = Annually(u8 month, u8 day)    // annually on month/day

10.5 Runtime Schedule Evaluation

Algorithm:

  1. Resolve character's schedule via ScheduleLink (conditional selection)
  2. Merge inherited schedules (parent → child, depth-first)
  3. Evaluate patterns for current day/season
  4. Overlay patterns on base blocks (later patterns override earlier ones)
  5. Produce final 24-hour schedule
  6. Return ordered list of (time, behavior_ref) pairs

11. Section 9: Institutions

11.1 Structure (EXTENDED in v0.2.0)

Count: u32
Institutions: [Institution; Count]

Institution:
  name: StringRef
  fields: Map<StringRef, Value>
  behavior_links: Vec<BehaviorLink>    // NEW in v0.2.0
  schedule_links: Vec<ScheduleLink>    // NEW in v0.2.0

Note: BehaviorLink and ScheduleLink are identical to Character section (§6.3, §6.4)


12. Section 10: Relationships

Count: u32
Relationships: [Relationship; Count]

Relationship:
  name: StringRef
  participants: Vec<Participant>
  fields: Map<StringRef, Value>

Participant:
  role: Option<StringRef>
  name: Vec<StringRef>                 // Qualified path
  self_block: Option<Vec<Field>>
  other_block: Option<Vec<Field>>

13. Section 11: Locations

Count: u32
Locations: [Location; Count]

Location:
  name: StringRef
  fields: Map<StringRef, Value>

14. Section 12: Life Arcs

Count: u32
LifeArcs: [LifeArc; Count]

LifeArc:
  name: StringRef
  required_fields: Vec<FieldRequirement>   // NEW in v0.3.0
  states: Vec<ArcState>

ArcState:
  name: StringRef
  on_enter: Option<Vec<Field>>
  transitions: Vec<Transition>

Transition:
  to: StringRef
  condition: Expression

14.1 FieldRequirement (NEW in v0.3.0)

FieldRequirement:
  name: StringRef                          // Field name
  type_name: StringRef                     // Expected type (e.g., "Number", "Text")

Purpose: Life arcs can declare required fields that any character using the life arc must have. This enables compile-time validation that characters provide the necessary fields for state transitions and on_enter actions.

Example:

life_arc Career requires { skill_level: Number, title: Text } {
    state Junior { ... }
    state Senior { ... }
}

Binary:

LifeArc:
  name: StringRef("Career")
  required_fields: [
    FieldRequirement { name: StringRef("skill_level"), type_name: StringRef("Number") },
    FieldRequirement { name: StringRef("title"), type_name: StringRef("Text") }
  ]
  states: [...]

15. Section 13: Enums

15.1 Structure

Count: u32
Enums: [EnumDecl; Count]

15.2 EnumDecl Encoding

EnumDecl:
  name: StringRef                      // u32 index into string table
  variants: Vec<StringRef>             // Encoded as below

Variants encoding (Vec):

u32 variant_count                      // Number of enum variants
[StringRef; variant_count]             // Array of variant names (each is u32)

15.3 Binary Example

Source:

enum SkillLevel {
    Novice,
    Beginner,
    Intermediate,
    Advanced,
    Expert,
    Master
}

Binary encoding:

EnumDecl:
  name: StringRef = 42                 // "SkillLevel" (4 bytes)

  u32 variant_count = 6                // 6 variants (4 bytes)
  variants:
    StringRef = 43                     // "Novice" (4 bytes)
    StringRef = 44                     // "Beginner" (4 bytes)
    StringRef = 45                     // "Intermediate" (4 bytes)
    StringRef = 46                     // "Advanced" (4 bytes)
    StringRef = 47                     // "Expert" (4 bytes)
    StringRef = 48                     // "Master" (4 bytes)

Total: 32 bytes (4 + 4 + 6*4)

15.4 Usage

Usage: Enums are used for:

  • Calendar definitions (DayOfWeek, Season, Month)
  • Custom enumerated values
  • Pattern matching in schedules

Note: As of v0.3.0, enum-like types can also be defined via sub_concept with enum kind (Section 5.3). The key distinction is that sub_concept enums are tied to a parent concept and participate in the concept type system (concept comparisons, exhaustiveness checking), while standalone enums in this section are untyped enumerations used primarily for calendar patterns and simple value sets.

Standard Calendar Enums (optional):

  • DayOfWeek: Sunday, Monday, Tuesday, Wednesday, Thursday, Friday, Saturday
  • Season: Spring, Summer, Fall, Winter
  • Month: January, February, ..., December

16. Changelog

v0.3.0 (February 2026)

Major Features:

  • Type system: concepts, sub-concepts (enum and record kinds), concept comparisons
  • Species-based template inheritance with type invariance enforcement
  • Life arc field requirements with type annotations
  • Value/expression type renames aligned with Storybook language terminology

Breaking Changes:

  • TYPES section (Section 3) now populated with concept, sub_concept, and concept_comparison definitions
  • Value discriminant names changed: Int→Number, Float→Decimal, String→Text, Bool→Boolean (wire format unchanged)
  • Expression discriminant names changed: IntLit→NumberLit, FloatLit→DecimalLit, StringLit→TextLit, BoolLit→BooleanLit (wire format unchanged)
  • TEMPLATES section: added species_base: Option<StringRef> field before strict
  • LIFE ARCS section: added required_fields: Vec<FieldRequirement> field after name

Note: SBIR encoder/decoder implementation is deferred until SaberVM design is finalized.

v0.2.0 (February 2026)

Major Features:

  • Resource linking system for behaviors and schedules
  • Year-long schedule patterns (day, season, recurrence)
  • Schedule inheritance and composition
  • Behavior tree keyword support (named nodes)
  • Parameterized decorators (repeat, retry, timeout, cooldown, if)

Breaking Changes:

  • CHARACTERS section: added behavior_links, schedule_links
  • INSTITUTIONS section: added behavior_links, schedule_links
  • SCHEDULES section: complete redesign with patterns and inheritance
  • BEHAVIORS section: added named node support

Deprecations:

  • None (first versioned release)

Bug Fixes:

  • N/A (first formal release)

v0.1.0 (Implicit, Pre-Release)

Initial format (inferred from existing codebase):

  • Basic entity storage (characters, templates, species)
  • Simple schedules (time blocks with activities)
  • Behavior trees (symbolic syntax)
  • Relationships, locations, life arcs
  • Enum support

Appendix A: Binary Encoding Examples

Source:

character Alice: Human {
    age: 7

    uses behavior: CuriousBehavior, when: self.location == Wonderland, priority: High
    uses behavior: DefaultBehavior, default: true

    uses schedule: AdventureSchedule, when: self.in_wonderland
    uses schedule: NormalSchedule, default: true
}

Binary (conceptual):

Character:
  name: StringRef(0)         // "Alice"
  species: Some(StringRef(1)) // "Human"
  fields: [
    ("age", Number(7))
  ]
  template_refs: []
  behavior_links: [
    BehaviorLink {
      behavior_id: 3
      priority: 2              // High
      condition: Some(Comparison(FieldAccess(...), Eq, Identifier(...)))
      is_default: false
    },
    BehaviorLink {
      behavior_id: 5
      priority: 1              // Normal
      condition: None
      is_default: true
    }
  ]
  schedule_links: [
    ScheduleLink {
      schedule_id: 1
      condition: Some(FieldAccess(...))
      is_default: false
    },
    ScheduleLink {
      schedule_id: 2
      condition: None
      is_default: true
    }
  ]

Example 2: Schedule with Patterns

Source:

schedule WorkWeek modifies BaseSchedule {
    block morning { 08:00 - 12:00: WorkTasks }
    block lunch { 12:00 - 13:00: EatLunch }
    block afternoon { 13:00 - 17:00: WorkTasks }

    on Friday {
        override afternoon { 13:00 - 15:00: FinishWeek }
    }

    season (Summer) {
        override morning { 07:00 - 11:00: WorkEarly }
    }
}

Binary (conceptual):

Schedule:
  name: StringRef(10)        // "WorkWeek"
  parent_schedule_id: Some(0) // BaseSchedule index
  blocks: [
    ScheduleBlock {
      name: StringRef(11)    // "morning"
      start: 480             // 08:00 in minutes
      end: 720               // 12:00 in minutes
      behavior_ref: Some(["WorkTasks"])
      fields: []
    },
    ScheduleBlock {
      name: StringRef(12)    // "lunch"
      start: 720
      end: 780
      behavior_ref: Some(["EatLunch"])
      fields: []
    },
    ScheduleBlock {
      name: StringRef(13)    // "afternoon"
      start: 780
      end: 1020
      behavior_ref: Some(["WorkTasks"])
      fields: []
    }
  ]
  patterns: [
    SchedulePattern {
      kind: 0x01             // DayPattern
      specification: StringRef(14)  // "Friday"
      blocks: [
        ScheduleBlock {
          name: StringRef(13) // "afternoon" (override)
          start: 780
          end: 900
          behavior_ref: Some(["FinishWeek"])
          fields: []
        }
      ]
    },
    SchedulePattern {
      kind: 0x02             // SeasonPattern
      specification: [StringRef(15)]  // ["Summer"]
      blocks: [
        ScheduleBlock {
          name: StringRef(11) // "morning" (override)
          start: 420
          end: 660
          behavior_ref: Some(["WorkEarly"])
          fields: []
        }
      ]
    }
  ]

Appendix B: File Size Estimates

Assumptions:

  • Average character: 500 bytes
  • Average behavior tree: 1 KB
  • Average schedule: 800 bytes
  • 1000 characters, 500 behaviors, 300 schedules

Estimated size:

  • Characters: 500 KB
  • Behaviors: 500 KB
  • Schedules: 240 KB
  • Other sections: 100 KB
  • Total: ~1.34 MB

Compression: Typical compression (gzip/zstd) achieves 60-70% reduction → ~400-500 KB


Appendix C: Version History

Version Date Major Changes
0.1.0 (Implicit) Initial format
0.2.0 Feb 2026 Resource linking, year-long schedules, behavior keywords
0.3.0 Feb 2026 Type system, species inheritance, life arc requirements
0.3.1 Feb 2026 Schedule keyword change: extendsmodifies (source-level only)

END OF SPECIFICATION