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
This commit is contained in:
2026-02-13 21:52:03 +00:00
parent 80332971b8
commit 16deb5d237
290 changed files with 90316 additions and 5827 deletions

View File

@@ -133,14 +133,9 @@ pub fn validate_relationship_bonds(relationships: &[Relationship], collector: &m
}
}
// Validate self/other blocks if present
// Validate participant fields
for participant in &rel.participants {
if let Some(ref self_fields) = participant.self_block {
validate_trait_ranges(self_fields, collector);
}
if let Some(ref other_fields) = participant.other_block {
validate_trait_ranges(other_fields, collector);
}
validate_trait_ranges(&participant.fields, collector);
}
}
}
@@ -165,7 +160,7 @@ pub fn validate_schedule_overlaps(schedule: &Schedule, collector: &mut ErrorColl
collector.add(ResolveError::ScheduleOverlap {
block1: format!(
"{} ({}:{:02}-{}:{:02})",
block1.activity,
block1.name.as_ref().unwrap_or(&block1.activity),
block1.start.hour,
block1.start.minute,
block1.end.hour,
@@ -173,7 +168,7 @@ pub fn validate_schedule_overlaps(schedule: &Schedule, collector: &mut ErrorColl
),
block2: format!(
"{} ({}:{:02}-{}:{:02})",
block2.activity,
block2.name.as_ref().unwrap_or(&block2.activity),
block2.start.hour,
block2.start.minute,
block2.end.hour,
@@ -242,7 +237,7 @@ fn validate_tree_node_actions(
collector: &mut ErrorCollector,
) {
match node {
| BehaviorNode::Sequence(children) | BehaviorNode::Selector(children) => {
| BehaviorNode::Sequence { children, .. } | BehaviorNode::Selector { children, .. } => {
for child in children {
validate_tree_node_actions(child, action_registry, tree_name, collector);
}
@@ -258,7 +253,7 @@ fn validate_tree_node_actions(
| BehaviorNode::Condition(_) => {
// Conditions are validated separately via expression validation
},
| BehaviorNode::Decorator(_name, child) => {
| BehaviorNode::Decorator { child, .. } => {
validate_tree_node_actions(child, action_registry, tree_name, collector);
},
| BehaviorNode::SubTree(_path) => {
@@ -267,6 +262,154 @@ fn validate_tree_node_actions(
}
}
/// Validate character resource linking
///
/// Checks:
/// 1. No duplicate behavior tree references
/// 2. Priority values are valid (handled by type system)
pub fn validate_character_resource_links(character: &Character, collector: &mut ErrorCollector) {
// Check for duplicate behavior tree references
if let Some(ref behavior_links) = character.uses_behaviors {
let mut seen_trees: HashSet<String> = HashSet::new();
for link in behavior_links {
let tree_name = link.tree.join("::");
if seen_trees.contains(&tree_name) {
collector.add(ResolveError::ValidationError {
message: format!(
"Character '{}' has duplicate behavior tree reference: {}",
character.name,
tree_name
),
help: Some(format!(
"The behavior tree '{}' is referenced multiple times in the uses behaviors list. Each behavior tree should only be referenced once. If you want different conditions or priorities, combine them into a single entry.",
tree_name
)),
});
}
seen_trees.insert(tree_name);
}
}
// Check for duplicate schedule references
if let Some(ref schedules) = character.uses_schedule {
let mut seen_schedules: HashSet<String> = HashSet::new();
for schedule in schedules {
if seen_schedules.contains(schedule) {
collector.add(ResolveError::ValidationError {
message: format!(
"Character '{}' has duplicate schedule reference: {}",
character.name,
schedule
),
help: Some(format!(
"The schedule '{}' is referenced multiple times. Each schedule should only be referenced once.",
schedule
)),
});
}
seen_schedules.insert(schedule.clone());
}
}
}
/// Validate institution resource linking
pub fn validate_institution_resource_links(
institution: &Institution,
collector: &mut ErrorCollector,
) {
// Check for duplicate behavior tree references
if let Some(ref behavior_links) = institution.uses_behaviors {
let mut seen_trees: HashSet<String> = HashSet::new();
for link in behavior_links {
let tree_name = link.tree.join("::");
if seen_trees.contains(&tree_name) {
collector.add(ResolveError::ValidationError {
message: format!(
"Institution '{}' has duplicate behavior tree reference: {}",
institution.name,
tree_name
),
help: Some(format!(
"The behavior tree '{}' is referenced multiple times. Each behavior tree should only be referenced once.",
tree_name
)),
});
}
seen_trees.insert(tree_name);
}
}
// Check for duplicate schedule references
if let Some(ref schedules) = institution.uses_schedule {
let mut seen_schedules: HashSet<String> = HashSet::new();
for schedule in schedules {
if seen_schedules.contains(schedule) {
collector.add(ResolveError::ValidationError {
message: format!(
"Institution '{}' has duplicate schedule reference: {}",
institution.name, schedule
),
help: Some(format!(
"The schedule '{}' is referenced multiple times.",
schedule
)),
});
}
seen_schedules.insert(schedule.clone());
}
}
}
/// Validate schedule composition requirements
///
/// Checks:
/// 1. All blocks in extended schedules have names
/// 2. Override blocks reference valid block names (requires name resolution)
pub fn validate_schedule_composition(schedule: &Schedule, collector: &mut ErrorCollector) {
// If schedule extends another, all blocks must have names for override system
if schedule.extends.is_some() {
for block in &schedule.blocks {
if block.name.is_none() && !block.activity.is_empty() {
collector.add(ResolveError::ValidationError {
message: format!(
"Schedule '{}' extends another schedule but has unnamed blocks",
schedule.name
),
help: Some(
"When a schedule extends another, all blocks must have names to support the override system. Use 'block name { ... }' syntax instead of 'time -> time : activity { ... }'.".to_string()
),
});
}
}
}
// Validate that new-style blocks have action references
for block in &schedule.blocks {
if block.name.is_some() && block.action.is_none() && block.activity.is_empty() {
collector.add(ResolveError::ValidationError {
message: format!(
"Schedule '{}' block '{}' missing action reference",
schedule.name,
block.name.as_ref().unwrap()
),
help: Some(
"Named blocks should specify a behavior using 'action: BehaviorName'. Example: 'block work { 9:00 -> 17:00, action: WorkBehavior }'".to_string()
),
});
}
}
}
/// Validate an entire file
///
/// Collects all validation errors and returns them together instead of failing
@@ -278,12 +421,17 @@ pub fn validate_file(file: &File, action_registry: &HashSet<String>) -> Result<(
match decl {
| Declaration::Character(c) => {
validate_trait_ranges(&c.fields, &mut collector);
validate_character_resource_links(c, &mut collector);
},
| Declaration::Institution(i) => {
validate_institution_resource_links(i, &mut collector);
},
| Declaration::Relationship(r) => {
validate_relationship_bonds(std::slice::from_ref(r), &mut collector);
},
| Declaration::Schedule(s) => {
validate_schedule_overlaps(s, &mut collector);
validate_schedule_composition(s, &mut collector);
},
| Declaration::LifeArc(la) => {
validate_life_arc_transitions(la, &mut collector);
@@ -472,10 +620,13 @@ mod tests {
let tree = Behavior {
name: "Test".to_string(),
root: BehaviorNode::Sequence(vec![
BehaviorNode::Action("walk".to_string(), vec![]),
BehaviorNode::Action("eat".to_string(), vec![]),
]),
root: BehaviorNode::Sequence {
label: None,
children: vec![
BehaviorNode::Action("walk".to_string(), vec![]),
BehaviorNode::Action("eat".to_string(), vec![]),
],
},
span: Span::new(0, 100),
};