This commit completes the migration started in the previous commit, updating all remaining files: - Lexer: Changed token from Extends to Modifies - Parser: Updated lalrpop grammar rules and AST field names - AST: Renamed Schedule.extends field to modifies - Grammar: Updated tree-sitter grammar.js - Tree-sitter: Regenerated parser.c and node-types.json - Examples: Updated baker-family work schedules - Tests: Updated schedule composition tests and corpus - Docs: Updated all reference documentation and tutorials - Validation: Updated error messages and validation logic - Package: Bumped version to 0.3.1 in all package manifests All 554 tests pass.
7.6 KiB
Best Practices
This chapter compiles best practices for writing clear, maintainable, and effective Storybook code. These guidelines apply across all declaration types and project sizes.
Naming Conventions
Declarations
Use PascalCase for all declaration names:
character MasterBaker { } // Good
species DomesticCat { } // Good
behavior GuardPatrol { } // Good
character master_baker { } // Avoid
behavior guard-patrol { } // Invalid (no hyphens)
Fields
Use snake_case for field names:
character Martha {
skill_level: 0.95 // Good
emotional_state: focused // Good
years_experience: 22 // Good
skillLevel: 0.95 // Avoid (camelCase)
}
Behavior Tree Labels
Use snake_case for node labels, with descriptive names:
choose survival_instinct { // Good
then fight_response { } // Good
then flight_response { } // Good
}
choose s1 { // Avoid (meaningless)
then a { } // Avoid
}
Enum Variants
Use PascalCase or snake_case consistently within an enum:
// PascalCase (good for short names)
enum Size { Tiny, Small, Normal, Large, Huge }
// snake_case (good for compound names)
enum GovernmentStyle {
absolute_tyranny,
constitutional_monarchy,
direct_democracy
}
File Organization
Separate Schema from World
Keep reusable type definitions separate from instance data:
project/
schema/ # Reusable across stories
core_enums.sb # Enum definitions
templates.sb # Template definitions
beings.sb # Species definitions
world/ # Specific to this story
characters/ # Character instances
behaviors/ # Behavior trees
relationships/ # Relationship instances
locations/ # Location instances
One Concern per File
Group related declarations, but avoid putting unrelated things together:
// characters/bakery_staff.sb - Good: related characters together
character Martha { }
character Jane { }
character Elena { }
// everything.sb - Avoid: everything in one file
character Martha { }
behavior BakeRoutine { }
schedule DailyRoutine { }
relationship Partnership { }
Explicit Imports
Prefer explicit imports over wildcards:
// Good: clear what is being used
use schema::core_enums::{SkillLevel, Specialty};
use schema::beings::Human;
// Avoid: unclear dependencies
use schema::core_enums::*;
use schema::beings::*;
Character Design
Use Species for Identity, Templates for Traits
// Species: ontological identity
species Human { lifespan: 70 }
// Templates: compositional traits
template Warrior { strength: 10..20 }
template Scholar { intelligence: 15..20 }
// Character: combines identity and traits
character Aragorn: Human from Warrior {
strength: 18
}
Document with Prose Blocks
character Martha: Human {
age: 34
---backstory
Martha learned to bake from her grandmother, starting at
age twelve. She now runs the most popular bakery in town.
---
---personality
Meticulous and patient, with an unwavering commitment to
quality. Tough but fair with her staff.
---
}
Prefer Flat Inheritance
Avoid deep species hierarchies. Two or three levels is usually enough:
// Good: shallow
species Mammal { warm_blooded: true }
species Human includes Mammal { sapient: true }
// Avoid: too deep
species Being { }
species LivingBeing includes Being { }
species Animal includes LivingBeing { }
species Vertebrate includes Animal { }
species Mammal includes Vertebrate { }
species Human includes Mammal { }
Behavior Tree Design
Name Every Composite Node
// Good: self-documenting
choose daily_priority {
then handle_emergency { }
then do_work { }
then relax { }
}
// Avoid: anonymous nodes
choose {
then { }
then { }
}
Keep Trees Shallow
Extract deep subtrees into separate behaviors:
// Good: flat with includes
behavior Main {
choose mode {
include CombatBehavior
include ExplorationBehavior
include SocialBehavior
}
}
// Avoid: deeply nested
behavior Main {
choose {
then { choose { then { choose { then { Action } } } } }
}
}
Use Decorators for Control Flow
// Good: decorator handles timing
cooldown(30s) { CastSpell }
timeout(10s) { SolvePuzzle }
retry(3) { PickLock }
// Avoid: manual timing in actions
CheckCooldownTimer
IfCooldownReady { CastSpell }
Expression Writing
Use Parentheses for Clarity
// Good: explicit grouping
on (health < 50 or is_poisoned) and has_antidote -> healing
// Risky: relies on precedence knowledge
on health < 50 or is_poisoned and has_antidote -> healing
Break Complex Conditions into Multiple Transitions
// Good: separate transitions, easy to read
state combat {
on health < 20 and not has_potion -> desperate
on surrounded and not has_escape -> desperate
on enemy_count > 10 -> desperate
}
// Avoid: one massive condition
state combat {
on (health < 20 and not has_potion) or (surrounded and not has_escape) or enemy_count > 10 -> desperate
}
Use is for Enum Comparisons
// Good: reads naturally
on status is active -> active_state
on skill_level is master -> teach_others
// Works but less readable
on status == active -> active_state
Schedule Design
Use Named Blocks for Override Support
// Good: named blocks can be overridden
schedule Base {
block work { 09:00 - 17:00, action: standard_work }
}
schedule Variant modifies Base {
block work { 05:00 - 13:00, action: early_work }
}
Group Related Blocks
schedule DailyRoutine {
// Morning
block wake { 06:00 - 07:00, action: morning_routine }
block breakfast { 07:00 - 08:00, action: eat }
// Work
block commute { 08:00 - 09:00, action: travel }
block work { 09:00 - 17:00, action: work }
// Evening
block leisure { 18:00 - 22:00, action: relax }
block sleep { 22:00 - 06:00, action: sleep }
}
Relationship Design
Use Roles for Clarity
// Good: roles clarify the relationship
relationship Marriage {
Martha as spouse
David as spouse
bond: 0.9
}
// Less clear without roles
relationship Marriage {
Martha
David
bond: 0.9
}
Use Perspectives for Asymmetry
// Good: captures different viewpoints
relationship TeacherStudent {
Gandalf as teacher self { patience: 0.8 } other { potential: 0.9 }
Frodo as student self { motivation: 0.7 } other { admiration: 0.95 }
bond: 0.85
}
General Principles
-
Readability over brevity: Storybook code should read like a narrative, not a puzzle.
-
Explicit over implicit: Say what you mean. Use named nodes, explicit imports, and clear field names.
-
Flat over deep: Shallow hierarchies, short behavior trees, and focused files are easier to maintain.
-
Composition over inheritance: Prefer combining templates over building deep species hierarchies.
-
Document with prose: Prose blocks are a feature, not clutter. Use them to explain intent alongside data.
-
One concept per declaration: Each behavior tree, life arc, or schedule should have a single clear purpose.
Cross-References
- Design Patterns - Common structural patterns
- Validation Rules - What the compiler checks
- Language Overview - Language philosophy