diff --git a/docs/SBIR-v0.3.0-SPEC.md b/docs/SBIR-v0.3.0-SPEC.md new file mode 100644 index 0000000..6f1b267 --- /dev/null +++ b/docs/SBIR-v0.3.0-SPEC.md @@ -0,0 +1,1247 @@ +# Storybook Intermediate Representation (SBIR) v0.3.0 Specification + +**Version:** 0.3.0 +**Status:** Draft +**Date:** February 2026 + +--- + +## Table of Contents + +1. [Introduction](#1-introduction) +2. [File Format Overview](#2-file-format-overview) +3. [Section 1: Header](#3-section-1-header) +4. [Section 2: String Table](#4-section-2-string-table) +5. [Section 3: Types](#5-section-3-types) +6. [Section 4: Characters](#6-section-4-characters) +7. [Section 5: Templates](#7-section-5-templates) +8. [Section 6: Species](#8-section-6-species) +9. [Section 7: Behaviors](#9-section-7-behaviors) +10. [Section 8: Schedules](#10-section-8-schedules) +11. [Section 9: Institutions](#11-section-9-institutions) +12. [Section 10: Relationships](#12-section-10-relationships) +13. [Section 11: Locations](#13-section-11-locations) +14. [Section 12: Life Arcs](#14-section-12-life-arcs) +15. [Section 13: Enums](#15-section-13-enums) +16. [Changelog](#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.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.4 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` when None: +``` +0x00 ← 1 byte total +``` + +`Option` when Some(42): +``` +0x01 ← discriminant (Some) +0x2A 0x00 0x00 0x00 ← StringRef = 42 (u32) + ← 5 bytes total +``` + +`Option>` when None: +``` +0x00 ← 1 byte total +``` + +`Option>` 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: +``` + +**Kind Discriminants:** +``` +0x01 = Enum +0x02 = Record +``` + +**Enum (0x01):** +``` +variants: Vec +``` + +**Record (0x02):** +``` +fields: Vec +``` + +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: + variant: StringRef // Enum variant to match + fields: Vec // Field assignments for this arm + condition: Option // Optional guard condition +``` + +### 5.5 Binary Example + +**Source:** +```storybook +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 + fields: Map + template_refs: Vec // Templates this character uses + behavior_links: Vec // NEW in v0.2.0 + schedule_links: Vec // NEW in v0.2.0 +``` + +### 6.3 BehaviorLink (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 // 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 + +### 6.4 ScheduleLink (NEW in v0.2.0) + +``` +ScheduleLink: + schedule_id: u32 // Index into SCHEDULES section + condition: Option // 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: +``` + +**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) // Qualified path +0x09 = List(Vec) +0x0A = Object(Vec) +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: +``` + +**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) +0x06 = FieldAccess(Box, StringRef) +0x07 = Comparison(Box, CompOp, Box) +0x08 = Logical(Box, LogicalOp, Box) +0x09 = Unary(UnaryOp, Box) +0x0A = Quantifier(QuantifierKind, StringRef var, Box collection, Box 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 // NEW in v0.3.0 - Species base for field inheritance + strict: bool + includes: Vec + fields: Map +``` + +**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 + fields: Map +``` + +--- + +## 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: +``` + +#### 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 // NEW in v0.2.0 +children: Vec +``` + +**Keyword Mapping:** `selector` or `choose` + +#### Sequence Node (0x02) + +``` +label: Option // NEW in v0.2.0 +children: Vec +``` + +**Keyword Mapping:** `sequence` or `then` + +#### Condition Node (0x03) + +``` +expression: Expression +``` + +**Keyword Mapping:** `if` or `when` + +#### Action Node (0x04) + +``` +name: StringRef +parameters: Vec +``` + +**Keyword Mapping:** No prefix (just action name) + +#### Decorator Nodes (0x10-0x19) + +**DecoratorRepeat (0x10):** +``` +child: Box +``` +Keyword: `repeat { ... }` + +**DecoratorRepeatN (0x11):** +``` +count: u32 +child: Box +``` +Keyword: `repeat(N) { ... }` + +**DecoratorRepeatRange (0x12):** +``` +min: u32 +max: u32 +child: Box +``` +Keyword: `repeat(min..max) { ... }` + +**DecoratorInvert (0x13):** +``` +child: Box +``` +Keyword: `invert { ... }` + +**DecoratorRetry (0x14):** +``` +max_attempts: u32 +child: Box +``` +Keyword: `retry(N) { ... }` + +**DecoratorTimeout (0x15):** +``` +milliseconds: u64 // Duration in milliseconds +child: Box +``` +Keyword: `timeout(duration) { ... }` +Example: `timeout(5s)`, `timeout(30m)`, `timeout(2h)` + +**DecoratorCooldown (0x16):** +``` +milliseconds: u64 // Cooldown period in milliseconds +child: Box +``` +Keyword: `cooldown(duration) { ... }` + +**DecoratorIf (0x17):** +``` +condition: Expression +child: Box +``` +Keyword: `if(condition) { ... }` + +**DecoratorSucceedAlways (0x18):** +``` +child: Box +``` +Keyword: `succeed_always { ... }` + +**DecoratorFailAlways (0x19):** +``` +child: Box +``` +Keyword: `fail_always { ... }` + +#### SubTree Node (0x20) + +``` +path: Vec // 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 // Index into SCHEDULES section (for inheritance) + blocks: Vec + patterns: Vec // 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> // Reference to behavior (qualified path) + fields: Map +``` + +**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 // Pattern-specific data + blocks: Vec // 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 // 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: + +Discriminants: + 0x01 = Every(u32 days) // every N days + 0x02 = WeeklyOn(Vec) // 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 + behavior_links: Vec // NEW in v0.2.0 + schedule_links: Vec // 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 + fields: Map + +Participant: + role: Option + name: Vec // Qualified path + self_block: Option> + other_block: Option> +``` + +--- + +## 13. Section 11: Locations + +``` +Count: u32 +Locations: [Location; Count] + +Location: + name: StringRef + fields: Map +``` + +--- + +## 14. Section 12: Life Arcs + +``` +Count: u32 +LifeArcs: [LifeArc; Count] + +LifeArc: + name: StringRef + required_fields: Vec // NEW in v0.3.0 + states: Vec + +ArcState: + name: StringRef + on_enter: Option> + transitions: Vec + +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:** +```storybook +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 // 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:** +```storybook +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` field before `strict` +- LIFE ARCS section: added `required_fields: Vec` 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 + +### Example 1: Character with Resource Links + +**Source:** +```storybook +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:** +```storybook +schedule WorkWeek extends 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 | + +--- + +**END OF SPECIFICATION** diff --git a/src/lsp/semantic_tokens.rs b/src/lsp/semantic_tokens.rs index fd09f07..3c33f9d 100644 --- a/src/lsp/semantic_tokens.rs +++ b/src/lsp/semantic_tokens.rs @@ -16,6 +16,7 @@ use crate::syntax::{ ast::{ Declaration, Field, + SubConceptKind, Value, }, lexer::{ @@ -378,11 +379,109 @@ pub fn get_semantic_tokens(doc: &Document) -> Option { } } }, - | Declaration::Concept(_) | - Declaration::SubConcept(_) | - Declaration::ConceptComparison(_) => { - // TODO: Implement semantic highlighting for type system - // declarations + | Declaration::Concept(concept) => { + // Highlight concept name as TYPE + builder.add_token( + concept.span.start_line, + concept.span.start_col, + concept.name.len(), + token_type_index(SemanticTokenType::TYPE), + 0, + ); + }, + | Declaration::SubConcept(sub_concept) => { + // Highlight parent concept as TYPE + let parent_positions = find_identifiers_in_span( + &doc.text, + sub_concept.span.start, + sub_concept.span.end, + std::slice::from_ref(&sub_concept.parent_concept), + ); + if let Some((offset, parent_name)) = parent_positions.into_iter().next() { + let (line, col) = positions.offset_to_position(offset); + builder.add_token( + line, + col, + parent_name.len(), + token_type_index(SemanticTokenType::TYPE), + 0, + ); + } + + // Highlight sub_concept name as ENUM + let name_positions = find_identifiers_in_span( + &doc.text, + sub_concept.span.start, + sub_concept.span.end, + std::slice::from_ref(&sub_concept.name), + ); + if let Some((offset, name)) = name_positions.into_iter().next() { + let (line, col) = positions.offset_to_position(offset); + builder.add_token( + line, + col, + name.len(), + token_type_index(SemanticTokenType::ENUM), + 0, + ); + } + + // Highlight enum variants as ENUM_MEMBER, or record fields + match &sub_concept.kind { + | SubConceptKind::Enum { variants } => { + let variant_positions = find_identifiers_in_span( + &doc.text, + sub_concept.span.start, + sub_concept.span.end, + variants, + ); + for (offset, variant_name) in variant_positions { + let (line, col) = positions.offset_to_position(offset); + builder.add_token( + line, + col, + variant_name.len(), + token_type_index(SemanticTokenType::ENUM_MEMBER), + 0, + ); + } + }, + | SubConceptKind::Record { fields } => { + for field in fields { + highlight_field(&mut builder, field); + } + }, + } + }, + | Declaration::ConceptComparison(comparison) => { + // Highlight comparison name as TYPE + builder.add_token( + comparison.span.start_line, + comparison.span.start_col, + comparison.name.len(), + token_type_index(SemanticTokenType::TYPE), + 0, + ); + + // Highlight variant names as ENUM_MEMBER + for variant in &comparison.variants { + let variant_positions = find_identifiers_in_span( + &doc.text, + variant.span.start, + variant.span.end, + std::slice::from_ref(&variant.name), + ); + if let Some((offset, name)) = variant_positions.into_iter().next() { + let (line, col) = positions.offset_to_position(offset); + builder.add_token( + line, + col, + name.len(), + token_type_index(SemanticTokenType::ENUM_MEMBER), + 0, + ); + } + } }, } }