Files
storybook/design/merge-implementation-notes.md
Sienna Meridian Satterwhite 16deb5d237 release: Storybook v0.2.0 - Major syntax and features update
BREAKING CHANGES:
- Relationship syntax now requires blocks for all participants
- Removed self/other perspective blocks from relationships
- Replaced 'guard' keyword with 'if' for behavior tree decorators

Language Features:
- Add tree-sitter grammar with improved if/condition disambiguation
- Add comprehensive tutorial and reference documentation
- Add SBIR v0.2.0 binary format specification
- Add resource linking system for behaviors and schedules
- Add year-long schedule patterns (day, season, recurrence)
- Add behavior tree enhancements (named nodes, decorators)

Documentation:
- Complete tutorial series (9 chapters) with baker family examples
- Complete reference documentation for all language features
- SBIR v0.2.0 specification with binary format details
- Added locations and institutions documentation

Examples:
- Convert all examples to baker family scenario
- Add comprehensive working examples

Tooling:
- Zed extension with LSP integration
- Tree-sitter grammar for syntax highlighting
- Build scripts and development tools

Version Updates:
- Main package: 0.1.0 → 0.2.0
- Tree-sitter grammar: 0.1.0 → 0.2.0
- Zed extension: 0.1.0 → 0.2.0
- Storybook editor: 0.1.0 → 0.2.0
2026-02-13 21:52:03 +00:00

12 KiB

Template Merge Implementation Notes

Author: Resource Linking Architect Date: 2026-02-12 Purpose: Implementation planning for Phase 3 (Resolution)


Core Merge Algorithm

Data Structures

// In resolve/merge.rs

/// Tracks behavior links during merge to detect overrides
struct BehaviorMergeContext {
    seen_behaviors: HashMap<String, BehaviorLink>,  // behavior_name -> link
    result: Vec<BehaviorLink>,
    warnings: Vec<MergeWarning>,
}

enum MergeWarning {
    DefaultConflict {
        character_behavior: String,
        template_behavior: String,
    },
    PriorityConflict {
        behavior: String,
        character_priority: Priority,
        template_priority: Priority,
    },
}

Merge Function Signature

pub fn merge_behavior_links(
    character_links: Vec<BehaviorLink>,
    template_links: Vec<BehaviorLink>,
    character_name: &str,
    template_name: &str,
) -> Result<(Vec<ResolvedBehaviorLink>, Vec<MergeWarning>), MergeError> {
    // Implementation
}

Step-by-Step Algorithm

fn merge_behavior_links(
    character_links: Vec<BehaviorLink>,
    template_links: Vec<BehaviorLink>,
    character_name: &str,
    template_name: &str,
) -> Result<(Vec<ResolvedBehaviorLink>, Vec<MergeWarning>), MergeError> {

    let mut ctx = BehaviorMergeContext {
        seen_behaviors: HashMap::new(),
        result: Vec::new(),
        warnings: Vec::new(),
    };

    // Step 1: Add all character links (highest precedence)
    for link in character_links {
        let behavior_name = link.tree.join("::");

        // Check for duplicate behavior in character itself
        if ctx.seen_behaviors.contains_key(&behavior_name) {
            return Err(MergeError::DuplicateBehavior {
                behavior: behavior_name,
                source: character_name.to_string(),
            });
        }

        ctx.seen_behaviors.insert(behavior_name.clone(), link.clone());
        ctx.result.push(link);
    }

    // Step 2: Add template links that aren't overridden
    for link in template_links {
        let behavior_name = link.tree.join("::");

        if let Some(char_link) = ctx.seen_behaviors.get(&behavior_name) {
            // Character overrides this behavior - check for conflicts

            // Check priority conflict
            if let (Some(char_pri), Some(tmpl_pri)) = (&char_link.priority, &link.priority) {
                if char_pri != tmpl_pri {
                    ctx.warnings.push(MergeWarning::PriorityConflict {
                        behavior: behavior_name.clone(),
                        character_priority: char_pri.clone(),
                        template_priority: tmpl_pri.clone(),
                    });
                }
            }

            // Skip template link (already have character's version)
            continue;
        }

        // No override, add template link
        ctx.result.push(link);
    }

    // Step 3: Check for default conflicts
    let char_defaults: Vec<_> = ctx.result.iter()
        .filter(|link| link.is_default)
        .take(2)  // Only need to check if >1
        .collect();

    if char_defaults.len() > 1 {
        ctx.warnings.push(MergeWarning::DefaultConflict {
            // ... warning details
        });
    }

    Ok((ctx.result, ctx.warnings))
}

Multi-Level Template Merge

pub fn resolve_template_hierarchy(
    character: &Character,
    templates: &HashMap<String, Template>,
) -> Result<(Vec<BehaviorLink>, Vec<ScheduleLink>), MergeError> {

    // Step 1: Build template chain (deepest first)
    let mut template_chain = Vec::new();
    let mut current_template_name = character.template.clone();

    while let Some(tmpl_name) = current_template_name {
        let template = templates.get(&tmpl_name)
            .ok_or(MergeError::UnresolvedTemplate { name: tmpl_name.clone() })?;

        template_chain.push(template);
        current_template_name = template.parent_template.clone();
    }

    // Step 2: Merge from deepest template up to character
    template_chain.reverse();  // Now [deepest, ..., direct parent]

    let mut merged_behaviors = Vec::new();
    let mut merged_schedules = Vec::new();

    for template in template_chain {
        let (behaviors, warnings) = merge_behavior_links(
            merged_behaviors,
            template.behavior_links.clone(),
            character.name,
            &template.name,
        )?;
        merged_behaviors = behaviors;
        // TODO: emit warnings

        // Same for schedules...
    }

    // Step 3: Merge character on top
    let (final_behaviors, warnings) = merge_behavior_links(
        character.behavior_links.clone(),
        merged_behaviors,
        character.name,
        "character",
    )?;

    Ok((final_behaviors, merged_schedules))
}

Complex Merge Scenarios to Test

Scenario 1: Simple Override

template Worker {
    uses behaviors: [A, B, C]
}

character Martha from Worker {
    uses behaviors: [B, D]  // Override B, add D
}

// Expected: [B(Martha), D, A, C]
// A and C come from Worker, B is Martha's version

Scenario 2: Multi-Level with Overrides

template Base {
    uses behaviors: [A, B]
}

template Mid from Base {
    uses behaviors: [B, C]  // Override B from Base
}

character Char from Mid {
    uses behaviors: [C, D]  // Override C from Mid
}

// Merge order:
// 1. Base: [A, B(Base)]
// 2. Mid merges on Base: [B(Mid), C, A]
// 3. Char merges on Mid: [C(Char), D, B(Mid), A]

Scenario 3: Priority Changes Through Chain

template Base {
    uses behaviors: [{ tree: Rest, priority: high }]
}

template Mid from Base {
    uses behaviors: [{ tree: Rest, priority: normal }]  // Warning!
}

character Char from Mid {
    uses behaviors: [{ tree: Rest, priority: low }]  // Warning!
}

// Expected: [Rest(low)]
// Warnings:
// - Mid changed Rest priority from high to normal
// - Char changed Rest priority from normal to low

Scenario 4: Condition Overrides

template Worker {
    uses behaviors: [
        { tree: Work, when: employed }
    ]
}

character Martha from Worker {
    uses behaviors: [
        { tree: Work, when: at_bakery }  // Override condition
    ]
}

// Expected: [Work(when: at_bakery)]
// Template's Work(when: employed) is completely replaced

Scenario 5: Default Conflicts

template Worker {
    uses behaviors: [
        { tree: Idle, default: true }
    ]
}

character Martha from Worker {
    uses behaviors: [
        { tree: Rest, default: true }
    ]
}

// Expected: [Rest(default), Idle(not default)]
// Warning: Both template and character define defaults

Scenario 6: Empty Array Edge Case

template Worker {
    uses behaviors: [A, B, C]
}

character Martha from Worker {
    uses behaviors: []  // What does this mean?
}

// If empty = "clear all": []
// If empty = "ignore, inherit": [A, B, C]
// Waiting for Sienna's decision (Question 1)

Scenario 7: Diamond Inheritance (Not Supported)

template A { uses behaviors: [X] }
template B from A { uses behaviors: [Y] }
template C from A { uses behaviors: [Z] }
template D from B, C { ... }  // ERROR: Multiple inheritance not supported

Decision: Single inheritance only (one parent template max).


Error Message Design

Error: Duplicate Behavior in Same Context

error: duplicate behavior definition
  ┌─ characters/martha.sb:10:9
  │
10 │         { tree: BakeryWork, priority: normal }
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
11 │         { tree: BakeryWork, priority: high }
  │         ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ duplicate definition
  │
  = note: behavior 'BakeryWork' is already defined in this character
  = help: remove one of the duplicate definitions or use different behaviors

Warning: Priority Conflict in Template Merge

warning: behavior priority changed from template
  ┌─ characters/martha.sb:9:9
  │
 9 │         { tree: Rest, priority: low }
  │                       ^^^^^^^^^^^^^^^^
  │
  = note: template 'Worker' defines 'Rest' with priority 'high'
  = note: character's priority (low) will override template's (high)
  = help: if this is intentional, no action needed

Warning: Multiple Defaults

warning: multiple default behaviors defined
  ┌─ characters/martha.sb:10:9
  │
10 │         { tree: Rest, default: true }
  │                       ^^^^^^^^^^^^^^^
  │
  = note: template 'Worker' also defines a default behavior: 'Idle'
  = note: character's default (Rest) will be used; template's (Idle) remains as non-default
  = help: only one default is active at runtime

Error: Unresolved Template

error: unresolved template reference
  ┌─ characters/martha.sb:1:25
  │
1 │ character Martha from Workerr {
  │                       ^^^^^^^ template 'Workerr' not found
  │
  = help: did you mean 'Worker'? (defined in templates/worker.sb)

Warning: Condition Override

warning: behavior condition changed from template
  ┌─ characters/martha.sb:9:9
  │
 9 │         { tree: Work, when: at_bakery }
  │                       ^^^^^^^^^^^^^^^^^^^
  │
  = note: template 'Worker' defines 'Work' with condition 'employed'
  = note: character's condition will replace template's condition

Testing Strategy

Unit Tests

#[cfg(test)]
mod merge_tests {
    use super::*;

    #[test]
    fn test_simple_merge() {
        let char_links = vec![
            behavior_link("BakeryWork", None, None),
        ];
        let tmpl_links = vec![
            behavior_link("BasicNeeds", Some(Priority::Critical), None),
            behavior_link("Rest", Some(Priority::Normal), None),
        ];

        let (result, warnings) = merge_behavior_links(
            char_links, tmpl_links, "Martha", "Worker"
        ).unwrap();

        assert_eq!(result.len(), 3);
        assert_eq!(result[0].tree, "BakeryWork");
        assert_eq!(result[1].tree, "BasicNeeds");
        assert_eq!(result[2].tree, "Rest");
        assert!(warnings.is_empty());
    }

    #[test]
    fn test_override_priority() {
        let char_links = vec![
            behavior_link("Rest", Some(Priority::Low), None),
        ];
        let tmpl_links = vec![
            behavior_link("Rest", Some(Priority::High), None),
        ];

        let (result, warnings) = merge_behavior_links(
            char_links, tmpl_links, "Martha", "Worker"
        ).unwrap();

        assert_eq!(result.len(), 1);
        assert_eq!(result[0].priority, Some(Priority::Low));
        assert_eq!(warnings.len(), 1);
        assert!(matches!(warnings[0], MergeWarning::PriorityConflict { .. }));
    }

    #[test]
    fn test_multi_level_merge() {
        // Base -> Mid -> Char
        // Test that merging works through chain
    }

    #[test]
    fn test_default_conflict() {
        // Both template and character define defaults
    }

    #[test]
    fn test_empty_array() {
        // Waiting for Sienna's decision on semantics
    }
}

Integration Tests

#[test]
fn test_resolve_alice_wonderland() {
    // Load Alice example with WonderlandCreature template
    // Verify merged behaviors are correct
}

#[test]
fn test_three_level_inheritance() {
    // Mortal -> Worker -> Baker -> Martha
    // Verify all behaviors present and priorities correct
}

Performance Considerations

Merge Complexity

  • Single template: O(n + m) where n=char links, m=template links
  • k-level templates: O(k * (n + m))
  • Typical case: k=1-3, n+m < 20, negligible overhead

Optimization Opportunities

  1. Cache merged templates: If Template X is used by multiple characters, cache its fully-merged result
  2. Early termination: If character defines no links, skip merge entirely
  3. Lazy merging: Only merge when links are actually accessed at runtime

Decision: Start with simple O(k*n) implementation. Optimize only if profiling shows bottleneck.


Edge Cases Checklist

  • Character overrides all template behaviors
  • Character overrides some template behaviors
  • Character adds new behaviors to template
  • Multi-level template chain
  • Priority conflicts through chain
  • Condition overrides
  • Multiple defaults
  • Empty array semantics (awaiting Sienna)
  • Diamond inheritance (not supported, single parent only)
  • Circular template references (should be caught in validation)
  • Template references non-existent parent (error)
  • Behavior name resolution fails (error)

Status: Implementation notes complete, awaiting Checkpoint 2 approval to begin coding.