1707 lines
44 KiB
Markdown
1707 lines
44 KiB
Markdown
|
|
# Storybook Type System
|
||
|
|
|
||
|
|
**Version**: 0.3.0
|
||
|
|
**Status**: Draft
|
||
|
|
|
||
|
|
## Overview
|
||
|
|
|
||
|
|
The Storybook type system is a **declarative, pure functional DSL** for defining narrative simulations. It separates type definitions and logic (language layer) from state management and mutation (runtime layer).
|
||
|
|
|
||
|
|
### Core Principles
|
||
|
|
|
||
|
|
1. **Language is pure** - no mutation, no state changes, only declarations
|
||
|
|
2. **Templates are record types** - universal structural type definitions
|
||
|
|
3. **Entities are typed values** - characters, institutions, locations are instances
|
||
|
|
4. **Behaviors are functional** - control flow and pattern matching
|
||
|
|
5. **Actions are the boundary** - where language meets runtime
|
||
|
|
|
||
|
|
### Core Declarations
|
||
|
|
|
||
|
|
- **`template`**: Record type definitions (structural types)
|
||
|
|
- **`character`/`institution`/`location`**: Typed value instances
|
||
|
|
- **`concept`**: Base type declarations for pattern matching
|
||
|
|
- **`sub_concept`**: Enumerated and typed subtypes (tagged union members)
|
||
|
|
- **`concept_comparison`**: Compile-time pattern matching over subtype combinations
|
||
|
|
- **`action`**: Signature declarations for runtime-implemented operations
|
||
|
|
|
||
|
|
This system enables static validation of entity relationships, behavior conditions, and data structures while maintaining readability for narrative design.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Architecture: Language vs Runtime
|
||
|
|
|
||
|
|
### Language Layer (Storybook DSL)
|
||
|
|
|
||
|
|
**Pure, declarative, no mutation:**
|
||
|
|
- Defines types (templates, concepts)
|
||
|
|
- Defines values (characters, institutions, locations)
|
||
|
|
- Defines logic (behaviors, pattern matching)
|
||
|
|
- Declares action signatures (interface to runtime)
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
template Person {
|
||
|
|
age: Number,
|
||
|
|
profession: Profession
|
||
|
|
}
|
||
|
|
|
||
|
|
character Martha: Person {
|
||
|
|
age: 34,
|
||
|
|
profession: Baker
|
||
|
|
}
|
||
|
|
|
||
|
|
action bake(baker: Baker, item: BakingItem)
|
||
|
|
|
||
|
|
behavior BakingWork {
|
||
|
|
then {
|
||
|
|
check_orders
|
||
|
|
bake
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Runtime Layer (Implementation)
|
||
|
|
|
||
|
|
**Stateful, manages mutation:**
|
||
|
|
- Holds character state
|
||
|
|
- Executes actions (implements mutations)
|
||
|
|
- Runs behavior trees
|
||
|
|
- Updates world state over time
|
||
|
|
|
||
|
|
**Example (Rust):**
|
||
|
|
```rust
|
||
|
|
enum Action {
|
||
|
|
Bake { baker: EntityId, item: ItemId },
|
||
|
|
CheckOrders { baker: EntityId },
|
||
|
|
PrepareIngredients { baker: EntityId, recipe: RecipeId },
|
||
|
|
}
|
||
|
|
|
||
|
|
impl Runtime {
|
||
|
|
fn execute_action(&mut self, action: Action) {
|
||
|
|
match action {
|
||
|
|
Action::Bake { baker, item } => {
|
||
|
|
let character = self.get_character_mut(baker);
|
||
|
|
let baked_item = self.get_item(item).bake();
|
||
|
|
character.inventory.add(baked_item);
|
||
|
|
character.energy -= 10;
|
||
|
|
}
|
||
|
|
Action::CheckOrders { baker } => {
|
||
|
|
let character = self.get_character_mut(baker);
|
||
|
|
let orders = self.query_orders();
|
||
|
|
character.task_queue.extend(orders);
|
||
|
|
}
|
||
|
|
Action::PrepareIngredients { baker, recipe } => {
|
||
|
|
// Implementation
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Actions: The Boundary
|
||
|
|
|
||
|
|
Actions are **declared in Storybook** (signatures) but **implemented in runtime** (behavior).
|
||
|
|
|
||
|
|
**Language sees:**
|
||
|
|
```storybook
|
||
|
|
/// Bakes an item in the oven
|
||
|
|
///
|
||
|
|
/// The baker must have sufficient energy and the item must be prepared.
|
||
|
|
/// Updates baker's inventory and reduces energy.
|
||
|
|
action bake(baker: Baker, item: BakingItem)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Runtime implements:**
|
||
|
|
```rust
|
||
|
|
impl Runtime {
|
||
|
|
fn bake(&mut self, baker: EntityId, item: ItemId) {
|
||
|
|
let character = self.get_character_mut(baker);
|
||
|
|
let baked = self.get_item(item).bake();
|
||
|
|
character.inventory.add(baked);
|
||
|
|
character.energy -= 10;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Benefits:**
|
||
|
|
- ✅ Language stays pure and type-safe
|
||
|
|
- ✅ Runtime has implementation flexibility
|
||
|
|
- ✅ Clear contract between layers
|
||
|
|
- ✅ Can add standard library later without changing language
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Core Declarations
|
||
|
|
|
||
|
|
### `template` - Record Type Definition
|
||
|
|
|
||
|
|
A `template` defines a structural record type. Templates are the universal mechanism for defining structured data in Storybook.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
template TypeName {
|
||
|
|
field1: Type,
|
||
|
|
field2: Type,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
```storybook
|
||
|
|
template Person {
|
||
|
|
age: Number,
|
||
|
|
species_type: Species,
|
||
|
|
profession: Profession
|
||
|
|
}
|
||
|
|
|
||
|
|
template Building {
|
||
|
|
owner: Person, // Reference to Person template
|
||
|
|
capacity: Number,
|
||
|
|
place: Settlement // Reference to Settlement template
|
||
|
|
}
|
||
|
|
|
||
|
|
template Settlement {
|
||
|
|
terrain: Terrain,
|
||
|
|
population: Number
|
||
|
|
}
|
||
|
|
|
||
|
|
template FamilialBond {
|
||
|
|
parent: Person, // Reference to Person template
|
||
|
|
child: Person, // Reference to Person template
|
||
|
|
strength: Number
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Purpose:**
|
||
|
|
- Define reusable record structures
|
||
|
|
- Provide types for characters, institutions, locations, relationships
|
||
|
|
- Enable type checking for fields and references
|
||
|
|
|
||
|
|
**Field Types:**
|
||
|
|
Templates support both value types and reference types:
|
||
|
|
|
||
|
|
**Value Types:**
|
||
|
|
- `Number` - integer values (e.g., `age: 34`)
|
||
|
|
- `Decimal` - floating-point values (e.g., `price: 19.99`)
|
||
|
|
- `Text` - text values (e.g., `name: "Martha"`)
|
||
|
|
- `Boolean` - boolean values (e.g., `active: true`)
|
||
|
|
|
||
|
|
**Range Declarations:**
|
||
|
|
For procedural generation, templates can specify ranges instead of concrete values:
|
||
|
|
```storybook
|
||
|
|
template Person {
|
||
|
|
age: 18..80, // Random age between 18 and 80
|
||
|
|
height: 150..200, // Random height in cm
|
||
|
|
wealth: 100..10000 // Random wealth amount
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
When a character instantiates a template with ranges, it must provide concrete values within those ranges:
|
||
|
|
```storybook
|
||
|
|
character Martha: Person {
|
||
|
|
age: 34, // Must be between 18 and 80
|
||
|
|
height: 165, // Must be between 150 and 200
|
||
|
|
wealth: 5000 // Must be between 100 and 10000
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Reference Types:**
|
||
|
|
Templates can reference concepts and other templates:
|
||
|
|
```storybook
|
||
|
|
template Baker {
|
||
|
|
profession: Profession, // Reference to concept
|
||
|
|
place: Settlement, // Reference to Settlement template
|
||
|
|
tools: BakingTools // Reference to BakingTools template
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Note: Template fields reference **template types** (Person, Settlement, Building), not declaration keywords (character, location, institution).
|
||
|
|
|
||
|
|
**Reserved Keywords:**
|
||
|
|
Field names cannot use reserved keywords. Use suffixes or alternatives instead:
|
||
|
|
```storybook
|
||
|
|
// ✗ Invalid - 'species' is a keyword
|
||
|
|
template Person {
|
||
|
|
age: Number,
|
||
|
|
species: Species, // Error!
|
||
|
|
profession: Profession
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✓ Valid - use alternative names
|
||
|
|
template Person {
|
||
|
|
age: Number,
|
||
|
|
species_type: Species, // OK
|
||
|
|
profession: Profession
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
Common alternatives:
|
||
|
|
- `species` → `species_type`, `creature_type`
|
||
|
|
- `location` → `location_ref`, `place`
|
||
|
|
- `character` → `character_ref`, `person`
|
||
|
|
- `template` → `template_name`, `template_ref`
|
||
|
|
- `behavior` → `behavior_ref`, `action_tree`
|
||
|
|
|
||
|
|
**Template Composition:**
|
||
|
|
Templates can reference other templates and concepts in their fields.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### Entities: Typed Value Instances
|
||
|
|
|
||
|
|
Characters, institutions, locations, and relationships are **typed values** - instances of template types.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
character Name: TemplateName {
|
||
|
|
field1: value,
|
||
|
|
field2: value,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
|
||
|
|
institution Name: TemplateName {
|
||
|
|
field1: value,
|
||
|
|
field2: value,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
|
||
|
|
location Name: TemplateName {
|
||
|
|
field1: value,
|
||
|
|
field2: value,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
|
||
|
|
relationship Name: TemplateName {
|
||
|
|
field1: value,
|
||
|
|
field2: value,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
```storybook
|
||
|
|
character Martha: Person {
|
||
|
|
age: 34,
|
||
|
|
species_type: Human,
|
||
|
|
profession: Baker
|
||
|
|
}
|
||
|
|
|
||
|
|
institution Bakery: Building {
|
||
|
|
owner: Martha,
|
||
|
|
capacity: 20,
|
||
|
|
place: TownSquare
|
||
|
|
}
|
||
|
|
|
||
|
|
location TownSquare: Settlement {
|
||
|
|
terrain: Plains,
|
||
|
|
population: 500
|
||
|
|
}
|
||
|
|
|
||
|
|
relationship ParentChild: FamilialBond {
|
||
|
|
parent: Martha,
|
||
|
|
child: Jane,
|
||
|
|
strength: 10
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Type Checking:**
|
||
|
|
- Values must match their template's field types
|
||
|
|
- Values must fall within range constraints (if specified in template)
|
||
|
|
- All required fields must be provided
|
||
|
|
- References must resolve to declared entities
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
template Person {
|
||
|
|
age: 18..80,
|
||
|
|
profession: Profession
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✓ Valid - within constraints
|
||
|
|
character Martha: Person {
|
||
|
|
age: 34, // 18 <= 34 <= 80
|
||
|
|
profession: Baker
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✗ Error - age out of range
|
||
|
|
character TooYoung: Person {
|
||
|
|
age: 12, // 12 < 18 (violates constraint)
|
||
|
|
profession: Child
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**State Management:**
|
||
|
|
- Entities are values **at declaration time**
|
||
|
|
- State changes are managed by the runtime (outside the language)
|
||
|
|
- Language defines initial state, runtime manages ongoing state
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### `action` - Runtime Operation Signature
|
||
|
|
|
||
|
|
An `action` declares the signature of a runtime-implemented operation. Actions are the interface between the pure language layer and the stateful runtime layer.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
/// Documentation comment (required)
|
||
|
|
///
|
||
|
|
/// Multi-line docstrings explain what the action does,
|
||
|
|
/// its preconditions, and its effects.
|
||
|
|
action name(param1: Type, param2: Type, ...)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
```storybook
|
||
|
|
/// Bakes an item in the oven
|
||
|
|
///
|
||
|
|
/// The baker must have sufficient energy and the item must be prepared.
|
||
|
|
/// Updates baker's inventory and reduces energy.
|
||
|
|
action bake(baker: Baker, item: BakingItem)
|
||
|
|
|
||
|
|
/// Checks pending orders and updates the baker's task list
|
||
|
|
///
|
||
|
|
/// Queries the order queue and populates the baker's work queue.
|
||
|
|
action check_orders(baker: Baker)
|
||
|
|
|
||
|
|
/// Moves a character to a new location
|
||
|
|
///
|
||
|
|
/// Updates the character's location and triggers any location-based events.
|
||
|
|
action move_to(character: Person, destination: Settlement)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Documentation Requirement:**
|
||
|
|
- Actions must have a docstring explaining their behavior
|
||
|
|
- This helps runtime implementers understand intent
|
||
|
|
- Serves as contract between language and runtime
|
||
|
|
|
||
|
|
**Type Checking:**
|
||
|
|
- Action calls in behaviors are validated against signatures
|
||
|
|
- Parameter types must match
|
||
|
|
- Unknown actions are compile errors
|
||
|
|
|
||
|
|
**Implementation:**
|
||
|
|
- Signatures are declared in `.sb` files (typically `actions.sb`)
|
||
|
|
- Implementations are provided by the runtime
|
||
|
|
- Standard library of common actions may come in future versions
|
||
|
|
|
||
|
|
**Current Limitations:**
|
||
|
|
- Actions have no return values (this version)
|
||
|
|
- Actions are statements, not expressions
|
||
|
|
- No support for action composition
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### `concept` - Base Type Declaration
|
||
|
|
|
||
|
|
A `concept` declares a base type with no inherent structure. It serves as a parent for related `sub_concept` declarations.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
concept TypeName
|
||
|
|
```
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
concept Cup
|
||
|
|
concept Customer
|
||
|
|
concept Vendor
|
||
|
|
```
|
||
|
|
|
||
|
|
**Purpose:**
|
||
|
|
- Establish type namespaces for related subtypes
|
||
|
|
- Provide type identity for values in the system
|
||
|
|
- Enable type checking in behaviors and conditions
|
||
|
|
|
||
|
|
**When to use:**
|
||
|
|
- When you need a type that will have multiple variants or aspects
|
||
|
|
- To create type-safe enumerations through `sub_concept`
|
||
|
|
- As a base for compile-time pattern matching via `concept_comparison`
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### `sub_concept` - Subtype Definition
|
||
|
|
|
||
|
|
A `sub_concept` defines members of a tagged union for a parent concept. Sub_concepts are associated with their parent through **dot notation** (explicit parent reference).
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
|
||
|
|
**Enumerated Form** (fixed set of values):
|
||
|
|
```storybook
|
||
|
|
sub_concept Parent.Name {
|
||
|
|
Variant1,
|
||
|
|
Variant2,
|
||
|
|
Variant3
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Typed Form** (structured with fields):
|
||
|
|
```storybook
|
||
|
|
sub_concept Parent.RecordName {
|
||
|
|
field1: TypeOrAny,
|
||
|
|
field2: TypeOrAny
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Parent Declaration:**
|
||
|
|
The parent is explicitly declared using dot notation:
|
||
|
|
- `Cup.Type` → parent: `Cup`, name: `Type`
|
||
|
|
- `Cup.Size` → parent: `Cup`, name: `Size`
|
||
|
|
- `Cupcake.Flavor` → parent: `Cupcake`, name: `Flavor`
|
||
|
|
|
||
|
|
**Why dot notation?**
|
||
|
|
- ✅ Prevents accidental naming collisions (`Cupcake` won't match `Cup`)
|
||
|
|
- ✅ Makes parent-child relationships explicit and clear
|
||
|
|
- ✅ Reads naturally: "Cup's Type", "Cup's Size"
|
||
|
|
- ✅ No ambiguity - parser knows exact parent
|
||
|
|
- ✅ Allows arbitrary sub_concept names without prefix requirements
|
||
|
|
|
||
|
|
**Examples:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Parent concepts
|
||
|
|
concept Cup;
|
||
|
|
concept Cupcake;
|
||
|
|
|
||
|
|
// Cup sub_concepts (tagged union members)
|
||
|
|
sub_concept Cup.Size {
|
||
|
|
Small,
|
||
|
|
Medium,
|
||
|
|
Large
|
||
|
|
}
|
||
|
|
|
||
|
|
sub_concept Cup.Type {
|
||
|
|
Ceramic,
|
||
|
|
Glass,
|
||
|
|
Plastic
|
||
|
|
}
|
||
|
|
|
||
|
|
sub_concept Cup.Color {
|
||
|
|
Red,
|
||
|
|
Blue,
|
||
|
|
Green
|
||
|
|
}
|
||
|
|
|
||
|
|
// Cupcake sub_concepts (no confusion with Cup)
|
||
|
|
sub_concept Cupcake.Flavor {
|
||
|
|
Strawberry,
|
||
|
|
Chocolate,
|
||
|
|
Raspberry
|
||
|
|
}
|
||
|
|
|
||
|
|
// Typed sub_concept (record-like)
|
||
|
|
sub_concept Vendor.Inventory {
|
||
|
|
Bread: any,
|
||
|
|
Pastries: any,
|
||
|
|
Cakes: any,
|
||
|
|
Cup: any
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Type Checking Rules:**
|
||
|
|
- ✅ Field values must be **identifiers** (references to other concepts/sub_concepts or `any`)
|
||
|
|
- ❌ Field values cannot be value types (Text, Number, Decimal, Boolean)
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// ✅ VALID - identifier references
|
||
|
|
sub_concept Inventory {
|
||
|
|
item: Product,
|
||
|
|
container: Cup,
|
||
|
|
quantity: any
|
||
|
|
}
|
||
|
|
|
||
|
|
// ❌ INVALID - value types not allowed
|
||
|
|
sub_concept BadInventory {
|
||
|
|
name: "string", // ✗ string literal
|
||
|
|
count: 42, // ✗ integer literal
|
||
|
|
active: true // ✗ boolean literal
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Purpose:**
|
||
|
|
- **Enumerated**: Define a fixed set of mutually exclusive values
|
||
|
|
- **Typed**: Define structured data with type-safe fields
|
||
|
|
- Both forms create tagged union members of the parent concept
|
||
|
|
|
||
|
|
**Relationship to Parent:**
|
||
|
|
Sub_concepts are **tagged union members**, not inheritance:
|
||
|
|
- `CupSize.Small` is a distinct variant of the `Cup` union
|
||
|
|
- `CupType.Glass` is another distinct variant of the `Cup` union
|
||
|
|
- They coexist as different aspects/facets of the same base type
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
### `concept_comparison` - Compile-time Pattern Matching
|
||
|
|
|
||
|
|
A `concept_comparison` performs compile-time pattern matching over combinations of sub_concept values, mapping them to derived variant names.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
concept_comparison ComparisonName {
|
||
|
|
VariantName1: {
|
||
|
|
SubConceptName1: condition1,
|
||
|
|
SubConceptName2: condition2,
|
||
|
|
...
|
||
|
|
},
|
||
|
|
VariantName2: {
|
||
|
|
SubConceptName1: condition1,
|
||
|
|
SubConceptName2: condition2,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Condition Syntax:**
|
||
|
|
- **`any`**: Matches any value of the specified sub_concept type
|
||
|
|
- **`Type is Value1 or Type is Value2`**: Matches specific enumerated values
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
concept Cup;
|
||
|
|
|
||
|
|
sub_concept Cup.Size {
|
||
|
|
Small, Medium, Large
|
||
|
|
}
|
||
|
|
|
||
|
|
sub_concept Cup.Type {
|
||
|
|
Ceramic, Glass, Plastic
|
||
|
|
}
|
||
|
|
|
||
|
|
sub_concept Cup.Color {
|
||
|
|
Red, Blue, Green
|
||
|
|
}
|
||
|
|
|
||
|
|
concept_comparison CustomerNumbererestInCups {
|
||
|
|
Numbererested: {
|
||
|
|
Cup.Size: any, // Any size
|
||
|
|
Cup.Type: Cup.Type is Glass or Cup.Type is Plastic, // Only Glass or Plastic
|
||
|
|
Cup.Color: Cup.Color is Red or Cup.Color is Blue // Only Red or Blue
|
||
|
|
},
|
||
|
|
NotNumbererested: {
|
||
|
|
Cup.Size: any,
|
||
|
|
Cup.Type: any,
|
||
|
|
Cup.Color: any
|
||
|
|
},
|
||
|
|
Maybe: {
|
||
|
|
Cup.Size: any,
|
||
|
|
Cup.Type: any,
|
||
|
|
Cup.Color: any
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Pattern Matching Semantics:**
|
||
|
|
- The comparison evaluates **at compile-time**
|
||
|
|
- Given concrete sub_concept values, determines which variant they match
|
||
|
|
- Used in behavior conditions to enable type-safe decision making
|
||
|
|
|
||
|
|
**Usage in Behaviors:**
|
||
|
|
```storybook
|
||
|
|
behavior SellAtMarket {
|
||
|
|
repeat {
|
||
|
|
then {
|
||
|
|
greet_customer
|
||
|
|
show_products
|
||
|
|
if(CustomerNumbererestInCups.Numbererested.Cup.Size is Medium or Cup.Size is Large) {
|
||
|
|
make_sale(Cup)
|
||
|
|
}
|
||
|
|
thank_customer
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Purpose:**
|
||
|
|
- Create derived types from combinations of existing sub_concepts
|
||
|
|
- Enable compile-time validation of complex conditions
|
||
|
|
- Provide type-safe pattern matching for behaviors
|
||
|
|
|
||
|
|
**Mixing Enumerated and Typed Sub_concepts:**
|
||
|
|
Concept_comparisons can reference both enumerated and typed sub_concepts in the same pattern (support for this is being implemented).
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Type Compatibility
|
||
|
|
|
||
|
|
### Subtyping Rules
|
||
|
|
|
||
|
|
Sub_concepts are tagged union members of their parent concept:
|
||
|
|
|
||
|
|
```
|
||
|
|
CupSize.Small <: Cup
|
||
|
|
CupType.Glass <: Cup
|
||
|
|
CupColor.Red <: Cup
|
||
|
|
```
|
||
|
|
|
||
|
|
### Substitution
|
||
|
|
|
||
|
|
**Where sub_concept values can be used:**
|
||
|
|
- As arguments to actions that expect the parent concept
|
||
|
|
- In field assignments expecting the parent type
|
||
|
|
- In conditions and comparisons
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
action serve(item: Cup) {
|
||
|
|
// Implementation
|
||
|
|
}
|
||
|
|
|
||
|
|
// Valid calls - sub_concepts substitute for parent
|
||
|
|
serve(CupSize.Small)
|
||
|
|
serve(CupType.Glass)
|
||
|
|
serve(CupColor.Red)
|
||
|
|
```
|
||
|
|
|
||
|
|
### Type Checking
|
||
|
|
|
||
|
|
**Compile-time:**
|
||
|
|
- Sub_concept field types must be identifiers (not value types)
|
||
|
|
- Pattern matching in `concept_comparison` is validated
|
||
|
|
- Type references must resolve to declared concepts
|
||
|
|
|
||
|
|
**Runtime:**
|
||
|
|
- Values are tagged with their specific sub_concept variant
|
||
|
|
- Pattern matching evaluates to variant names
|
||
|
|
- Type information preserved for behavior execution
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## `any` Keyword
|
||
|
|
|
||
|
|
The `any` keyword represents **any value of a specific type**, not a universal type.
|
||
|
|
|
||
|
|
**Semantics:**
|
||
|
|
```storybook
|
||
|
|
sub_concept VendorInventory {
|
||
|
|
Bread: any, // any value of type Bread
|
||
|
|
Pastries: any, // any value of type Pastries
|
||
|
|
Cup: any // any value of type Cup (includes all CupSize, CupType, CupColor)
|
||
|
|
}
|
||
|
|
|
||
|
|
concept_comparison Example {
|
||
|
|
AllCups: {
|
||
|
|
CupSize: any, // matches Small, Medium, Large
|
||
|
|
CupType: any, // matches Ceramic, Glass, Plastic
|
||
|
|
CupColor: any // matches Red, Blue, Green
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Not a universal type:**
|
||
|
|
- `any` is scoped to the field's declared type
|
||
|
|
- Does not mean "any type in the system"
|
||
|
|
- Provides flexibility while maintaining type safety
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Design Guidelines
|
||
|
|
|
||
|
|
### When to use `concept`
|
||
|
|
- You need a base type for multiple related variants
|
||
|
|
- You want type-safe enumerations
|
||
|
|
- You need to group related aspects of an entity
|
||
|
|
|
||
|
|
### When to use enumerated `sub_concept`
|
||
|
|
- You have a fixed set of mutually exclusive values
|
||
|
|
- Values are symbolic/categorical (not data-bearing)
|
||
|
|
- You need type-safe pattern matching over the values
|
||
|
|
|
||
|
|
### When to use typed `sub_concept`
|
||
|
|
- You need structured data with multiple fields
|
||
|
|
- Fields reference other concepts or need flexibility (`any`)
|
||
|
|
- You want record-like types within the concept system
|
||
|
|
|
||
|
|
### When to use `concept_comparison`
|
||
|
|
- You need to map combinations of sub_concepts to outcomes
|
||
|
|
- Complex conditional logic benefits from compile-time validation
|
||
|
|
- Behavior trees need type-safe decision points
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Examples
|
||
|
|
|
||
|
|
### Simple Enumeration
|
||
|
|
```storybook
|
||
|
|
concept Season;
|
||
|
|
|
||
|
|
sub_concept Season.Name {
|
||
|
|
Spring,
|
||
|
|
Summer,
|
||
|
|
Fall,
|
||
|
|
Winter
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Multi-faceted Type
|
||
|
|
```storybook
|
||
|
|
concept Food;
|
||
|
|
|
||
|
|
sub_concept Food.Type {
|
||
|
|
Bread,
|
||
|
|
Pastry,
|
||
|
|
Cake
|
||
|
|
}
|
||
|
|
|
||
|
|
sub_concept Food.Freshness {
|
||
|
|
Fresh,
|
||
|
|
Stale,
|
||
|
|
Spoiled
|
||
|
|
}
|
||
|
|
|
||
|
|
concept_comparison FoodQuality {
|
||
|
|
Excellent: {
|
||
|
|
Food.Type: any,
|
||
|
|
Food.Freshness: Food.Freshness is Fresh
|
||
|
|
},
|
||
|
|
Poor: {
|
||
|
|
Food.Type: any,
|
||
|
|
Food.Freshness: Food.Freshness is Stale or Food.Freshness is Spoiled
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Record Type
|
||
|
|
```storybook
|
||
|
|
concept Inventory;
|
||
|
|
|
||
|
|
sub_concept Inventory.Record {
|
||
|
|
item_type: Product,
|
||
|
|
quantity: any,
|
||
|
|
location: StorageArea
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Implementation Notes
|
||
|
|
|
||
|
|
### Parser
|
||
|
|
- Sub_concept parent inference uses prefix matching on the concept name
|
||
|
|
- Enumerated vs typed sub_concepts are disambiguated by presence of `:` after first identifier
|
||
|
|
- `any` keyword is parsed as `Value::Any` in the AST
|
||
|
|
|
||
|
|
### AST Representation
|
||
|
|
```rust
|
||
|
|
pub struct ConceptDecl {
|
||
|
|
pub name: Text,
|
||
|
|
pub span: Span,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct SubConceptDecl {
|
||
|
|
pub name: Text,
|
||
|
|
pub parent_concept: Text, // Inferred from naming convention
|
||
|
|
pub kind: SubConceptKind,
|
||
|
|
pub span: Span,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub enum SubConceptKind {
|
||
|
|
Enum { variants: Vec<Text> },
|
||
|
|
Record { fields: Vec<Field> },
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct ConceptComparisonDecl {
|
||
|
|
pub name: Text,
|
||
|
|
pub variants: Vec<VariantPattern>,
|
||
|
|
pub span: Span,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub struct VariantPattern {
|
||
|
|
pub name: Text,
|
||
|
|
pub conditions: Vec<FieldCondition>,
|
||
|
|
pub span: Span,
|
||
|
|
}
|
||
|
|
|
||
|
|
pub enum Condition {
|
||
|
|
Any,
|
||
|
|
Is(Vec<Text>), // "is Value1 or Value2"
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Species and Template Inheritance
|
||
|
|
|
||
|
|
### Species as Base Types with Defaults
|
||
|
|
|
||
|
|
Species define base record types with default values that templates can extend and characters can override.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
species SpeciesName {
|
||
|
|
field: Type = default_value,
|
||
|
|
...
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
species Human {
|
||
|
|
bipedal: Boolean = true,
|
||
|
|
has_hair: Boolean = true,
|
||
|
|
base_lifespan: Number = 80,
|
||
|
|
can_use_magic: Boolean = false
|
||
|
|
}
|
||
|
|
|
||
|
|
species Elf {
|
||
|
|
bipedal: Boolean = true,
|
||
|
|
pointed_ears: Boolean = true,
|
||
|
|
base_lifespan: Number = 1000,
|
||
|
|
can_use_magic: Boolean = true
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Template Extension
|
||
|
|
|
||
|
|
Templates extend species using `:` syntax and can override species defaults or add new fields.
|
||
|
|
|
||
|
|
**Syntax:**
|
||
|
|
```storybook
|
||
|
|
template TemplateName: SpeciesName {
|
||
|
|
field: Type = default, // Override species field
|
||
|
|
new_field: Type, // Add new required field
|
||
|
|
new_field2: Type = default // Add new field with default
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Example:**
|
||
|
|
```storybook
|
||
|
|
template Person: Human {
|
||
|
|
// Inherits: bipedal, has_hair, base_lifespan, can_use_magic (with defaults)
|
||
|
|
age: Number, // New required field
|
||
|
|
name: Text // New required field
|
||
|
|
}
|
||
|
|
|
||
|
|
template Cyborg: Human {
|
||
|
|
// Override species defaults
|
||
|
|
base_lifespan: Number = 200, // Cyborgs live longer
|
||
|
|
organic: Boolean = false, // New field
|
||
|
|
|
||
|
|
// Add new fields
|
||
|
|
model: Text, // Required
|
||
|
|
battery_level: Number = 100 // With default
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Override Priority Chain
|
||
|
|
|
||
|
|
**Character > Template > Species**
|
||
|
|
|
||
|
|
When a field is defined at multiple levels, the most specific definition wins:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
species Human {
|
||
|
|
strength: Number = 10,
|
||
|
|
intelligence: Number = 10,
|
||
|
|
speed: Number = 10
|
||
|
|
}
|
||
|
|
|
||
|
|
template Warrior: Human {
|
||
|
|
strength: Number = 15, // Override species default
|
||
|
|
weapon: Text // New required field
|
||
|
|
}
|
||
|
|
|
||
|
|
character Conan: Warrior {
|
||
|
|
strength: 20, // Override template (which overrode species)
|
||
|
|
weapon: "Greatsword", // Required by template
|
||
|
|
intelligence: 5 // Override species default
|
||
|
|
// speed: 10 (from species, no overrides)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Resolution order:**
|
||
|
|
1. Check character fields - use if present
|
||
|
|
2. Check template fields - use if present and not in character
|
||
|
|
3. Check species fields - use if not overridden
|
||
|
|
4. Error if no value found and field is required
|
||
|
|
|
||
|
|
### Character Instantiation with Inheritance
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
character Martha: Person {
|
||
|
|
// Required template fields (no defaults)
|
||
|
|
age: 34,
|
||
|
|
name: "Martha",
|
||
|
|
|
||
|
|
// Optional overrides of species defaults
|
||
|
|
bipedal: false, // She has one leg
|
||
|
|
can_use_magic: true, // Exceptional human
|
||
|
|
|
||
|
|
// Inherited from species (use defaults)
|
||
|
|
// has_hair: true (from Human)
|
||
|
|
// base_lifespan: 80 (from Human)
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Benefits
|
||
|
|
|
||
|
|
✅ **Layered defaults** - species → template → character
|
||
|
|
✅ **No redundancy** - don't repeat common defaults
|
||
|
|
✅ **Composable** - templates customize species for specific roles
|
||
|
|
✅ **Exceptional cases** - characters override when needed
|
||
|
|
✅ **Clear precedence** - explicit override chain
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Life Arcs as Entity State
|
||
|
|
|
||
|
|
### Core Concept
|
||
|
|
|
||
|
|
**Life arcs represent the current state of an entity in various state machines.**
|
||
|
|
|
||
|
|
An entity (character/location/institution) is not just data - it has **state** that changes over time. Life arcs define:
|
||
|
|
- What states exist (Baby, Child, Adult, Elder)
|
||
|
|
- How to transition between states (when age >= 18)
|
||
|
|
- What behaviors are available in each state
|
||
|
|
|
||
|
|
**The entity's life arc assignments ARE its current state in those state machines.**
|
||
|
|
|
||
|
|
### Entity State Model
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
character Martha: Baker {
|
||
|
|
// Data fields
|
||
|
|
age: 34,
|
||
|
|
skill_level: 8,
|
||
|
|
reputation: 85,
|
||
|
|
|
||
|
|
// STATE - current position in life arc state machines
|
||
|
|
life_arcs: {
|
||
|
|
Human: Adult, // In "Adult" state of Human state machine
|
||
|
|
Baker: Master, // In "Master" state of Baker state machine
|
||
|
|
Reputation: Famous // In "Famous" state of Reputation state machine
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Martha's state is:**
|
||
|
|
- Age progression: Adult (can Work, Train, Manage)
|
||
|
|
- Career progression: Master (can BakingWork, TrainApprentice, InnovateTechniques)
|
||
|
|
- Social progression: Famous (can Influence, NetworkGlobally)
|
||
|
|
|
||
|
|
### Life Arcs Define State Machines
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Human requires { age: Number } {
|
||
|
|
Baby {
|
||
|
|
when age < 2
|
||
|
|
can use behaviors: [Cry, Sleep, Eat]
|
||
|
|
-> Child when age >= 2
|
||
|
|
}
|
||
|
|
|
||
|
|
Child {
|
||
|
|
when age >= 2 and age < 12
|
||
|
|
can use behaviors: [Play, Learn, Eat, Sleep]
|
||
|
|
-> Adolescent when age >= 12
|
||
|
|
}
|
||
|
|
|
||
|
|
Adolescent {
|
||
|
|
when age >= 12 and age < 18
|
||
|
|
can use behaviors: [Play, Learn, Socialize, Work]
|
||
|
|
-> Adult when age >= 18
|
||
|
|
}
|
||
|
|
|
||
|
|
Adult {
|
||
|
|
when age >= 18 and age < 65
|
||
|
|
can use behaviors: [Work, Socialize, Train, Manage]
|
||
|
|
-> Elder when age >= 65
|
||
|
|
}
|
||
|
|
|
||
|
|
Elder {
|
||
|
|
when age >= 65
|
||
|
|
can use behaviors: [Rest, Socialize, Mentor]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**This defines:**
|
||
|
|
- **States**: Baby, Child, Adolescent, Adult, Elder
|
||
|
|
- **Transitions**: age thresholds trigger state changes
|
||
|
|
- **Capabilities**: each state grants different behaviors
|
||
|
|
|
||
|
|
### State Determines Capabilities
|
||
|
|
|
||
|
|
**Behavior availability = Life Arc States + Location + Template**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
Can Martha use TrainApprentice at Bakery?
|
||
|
|
|
||
|
|
Check life arc states:
|
||
|
|
✓ Human.Adult grants ability to Train
|
||
|
|
✓ Baker.Master grants TrainApprentice
|
||
|
|
|
||
|
|
Check location:
|
||
|
|
✓ Bakery enables TrainApprentice
|
||
|
|
|
||
|
|
→ YES - all conditions met
|
||
|
|
```
|
||
|
|
|
||
|
|
**If Martha were Baker.Journeyman instead:**
|
||
|
|
```storybook
|
||
|
|
character Martha: Baker {
|
||
|
|
skill_level: 5,
|
||
|
|
life_arcs: {
|
||
|
|
Human: Adult,
|
||
|
|
Baker: Journeyman // Different state!
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
Can Martha use TrainApprentice at Bakery?
|
||
|
|
|
||
|
|
Check life arc states:
|
||
|
|
✓ Human.Adult grants ability to Train
|
||
|
|
✗ Baker.Journeyman does NOT grant TrainApprentice
|
||
|
|
|
||
|
|
→ NO - lacks capability from life arc state
|
||
|
|
```
|
||
|
|
|
||
|
|
### Multiple Concurrent State Machines
|
||
|
|
|
||
|
|
Entities can be in multiple state machines simultaneously:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Human requires { age: Number } {
|
||
|
|
// Age-based progression
|
||
|
|
}
|
||
|
|
|
||
|
|
life_arc Baker requires { skill_level: Number } {
|
||
|
|
// Skill-based progression
|
||
|
|
}
|
||
|
|
|
||
|
|
life_arc Reputation requires { fame: Number } {
|
||
|
|
// Fame-based progression
|
||
|
|
}
|
||
|
|
|
||
|
|
character Martha: Baker {
|
||
|
|
age: 34,
|
||
|
|
skill_level: 8,
|
||
|
|
fame: 85,
|
||
|
|
|
||
|
|
life_arcs: {
|
||
|
|
Human: Adult, // State in age dimension
|
||
|
|
Baker: Master, // State in career dimension
|
||
|
|
Reputation: Famous // State in social dimension
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Each life arc is an independent state machine:**
|
||
|
|
- Human state machine: transitions based on age
|
||
|
|
- Baker state machine: transitions based on skill_level
|
||
|
|
- Reputation state machine: transitions based on fame
|
||
|
|
|
||
|
|
**Available behaviors = union of all state capabilities:**
|
||
|
|
- From Human.Adult: Work, Train, Manage
|
||
|
|
- From Baker.Master: BakingWork, TrainApprentice
|
||
|
|
- From Reputation.Famous: Influence, NetworkGlobally
|
||
|
|
|
||
|
|
### Locations Have State Too
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Settlement requires { population: Number, development: Number } {
|
||
|
|
Village {
|
||
|
|
when population < 500
|
||
|
|
enables behaviors: [Farming, Fishing]
|
||
|
|
-> Town when population >= 500 and development >= 3
|
||
|
|
}
|
||
|
|
|
||
|
|
Town {
|
||
|
|
when population >= 500 and population < 5000
|
||
|
|
enables behaviors: [Trading, Crafting, Farming]
|
||
|
|
-> City when population >= 5000 and development >= 7
|
||
|
|
}
|
||
|
|
|
||
|
|
City {
|
||
|
|
when population >= 5000
|
||
|
|
enables behaviors: [Trading, Manufacturing, Politics, Education]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
location TownSquare: SettlementTemplate {
|
||
|
|
population: 500,
|
||
|
|
development: 4,
|
||
|
|
|
||
|
|
life_arcs: {
|
||
|
|
Settlement: Town // Current state
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**TownSquare's state:**
|
||
|
|
- It's in the "Town" state (not Village, not City)
|
||
|
|
- It enables behaviors: Trading, Crafting, Farming
|
||
|
|
- When population reaches 5000 AND development reaches 7, it transitions to City state
|
||
|
|
|
||
|
|
### Institutions Have State Too
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Business requires { revenue: Number, reputation: Number } {
|
||
|
|
Startup {
|
||
|
|
when revenue < 10000
|
||
|
|
enables behaviors: [Hustle, Experiment]
|
||
|
|
-> Established when revenue >= 10000
|
||
|
|
}
|
||
|
|
|
||
|
|
Established {
|
||
|
|
when revenue >= 10000 and revenue < 100000
|
||
|
|
enables behaviors: [Operate, Expand]
|
||
|
|
-> Corporate when revenue >= 100000
|
||
|
|
}
|
||
|
|
|
||
|
|
Corporate {
|
||
|
|
when revenue >= 100000
|
||
|
|
enables behaviors: [Operate, Expand, Franchise, Lobby]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
institution Bakery: Building {
|
||
|
|
revenue: 15000,
|
||
|
|
reputation: 75,
|
||
|
|
|
||
|
|
life_arcs: {
|
||
|
|
Business: Established // Current state
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
### Bidirectional State Transitions
|
||
|
|
|
||
|
|
**State machines are NOT necessarily forward-only.**
|
||
|
|
|
||
|
|
Characters can progress forward, regress backward, or transition laterally based on field changes:
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Baker requires { skill_level: Number, experience: Number } {
|
||
|
|
Apprentice {
|
||
|
|
when skill_level < 3
|
||
|
|
can use behaviors: [Learn, AssistBaking]
|
||
|
|
-> Journeyman when skill_level >= 3
|
||
|
|
}
|
||
|
|
|
||
|
|
Journeyman {
|
||
|
|
when skill_level >= 3 and skill_level < 7
|
||
|
|
can use behaviors: [BakingWork, ManageInventory]
|
||
|
|
-> Master when skill_level >= 7 // Progress forward
|
||
|
|
-> Apprentice when skill_level < 3 // Regress backward!
|
||
|
|
}
|
||
|
|
|
||
|
|
Master {
|
||
|
|
when skill_level >= 7
|
||
|
|
can use behaviors: [BakingWork, ManageInventory, TrainApprentice, InnovateTechniques]
|
||
|
|
-> Journeyman when skill_level < 7 // Can lose mastery!
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**The `when` clause defines:**
|
||
|
|
- **State validity condition**: What must be true to be in this state
|
||
|
|
- **NOT a one-time check**: Re-evaluated whenever fields change
|
||
|
|
|
||
|
|
**Examples of regression:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
character Martha: Baker {
|
||
|
|
skill_level: 8,
|
||
|
|
life_arcs: { Baker: Master }
|
||
|
|
}
|
||
|
|
|
||
|
|
// Martha is Master Baker, then loses practice
|
||
|
|
action lose_practice(baker: Person) {
|
||
|
|
baker.skill_level -= 5 // Now skill_level = 3
|
||
|
|
}
|
||
|
|
|
||
|
|
lose_practice(Martha)
|
||
|
|
|
||
|
|
// Runtime re-evaluates:
|
||
|
|
// 1. Is Martha still valid for Master? (requires skill_level >= 7)
|
||
|
|
// 3 >= 7? NO - state is invalid!
|
||
|
|
// 2. Check Master's transitions:
|
||
|
|
// -> Journeyman when skill_level < 7
|
||
|
|
// 3 < 7? YES - transition!
|
||
|
|
// 3. Martha is now Journeyman
|
||
|
|
|
||
|
|
// Martha's available behaviors change:
|
||
|
|
// Lost: TrainApprentice, InnovateTechniques
|
||
|
|
// Kept: BakingWork, ManageInventory
|
||
|
|
```
|
||
|
|
|
||
|
|
**Examples of recovery:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
// Later, Martha practices and improves
|
||
|
|
action practice_baking(baker: Person) {
|
||
|
|
baker.skill_level += 1
|
||
|
|
}
|
||
|
|
|
||
|
|
// Practice multiple times
|
||
|
|
practice_baking(Martha) // skill_level = 4
|
||
|
|
practice_baking(Martha) // skill_level = 5
|
||
|
|
practice_baking(Martha) // skill_level = 6
|
||
|
|
practice_baking(Martha) // skill_level = 7
|
||
|
|
|
||
|
|
// After skill_level reaches 7:
|
||
|
|
// Runtime re-evaluates:
|
||
|
|
// 1. Check Journeyman's transitions:
|
||
|
|
// -> Master when skill_level >= 7
|
||
|
|
// 7 >= 7? YES - transition!
|
||
|
|
// 2. Martha is Master again!
|
||
|
|
```
|
||
|
|
|
||
|
|
### State Changes = Mutations
|
||
|
|
|
||
|
|
**Life arcs ARE the mutability mechanism:**
|
||
|
|
|
||
|
|
1. **Actions modify field values:**
|
||
|
|
```storybook
|
||
|
|
action practice_baking(baker: Person) {
|
||
|
|
// Runtime implementation
|
||
|
|
baker.skill_level += 1
|
||
|
|
}
|
||
|
|
|
||
|
|
action lose_practice(baker: Person) {
|
||
|
|
// Runtime implementation
|
||
|
|
baker.skill_level -= 1
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
2. **Modified values trigger state transitions (in ANY direction):**
|
||
|
|
```storybook
|
||
|
|
// Progression
|
||
|
|
// Before: Martha.skill_level = 6, Baker state = Journeyman
|
||
|
|
practice_baking(Martha)
|
||
|
|
// After: Martha.skill_level = 7
|
||
|
|
// Transition: Journeyman -> Master (forward)
|
||
|
|
|
||
|
|
// Regression
|
||
|
|
// Before: Martha.skill_level = 7, Baker state = Master
|
||
|
|
lose_practice(Martha)
|
||
|
|
lose_practice(Martha)
|
||
|
|
lose_practice(Martha)
|
||
|
|
// After: Martha.skill_level = 4
|
||
|
|
// Transition: Master -> Journeyman (backward)
|
||
|
|
```
|
||
|
|
|
||
|
|
3. **New state grants/removes capabilities:**
|
||
|
|
```storybook
|
||
|
|
// After progression to Master:
|
||
|
|
// Gained: TrainApprentice, InnovateTechniques
|
||
|
|
|
||
|
|
// After regression to Journeyman:
|
||
|
|
// Lost: TrainApprentice, InnovateTechniques
|
||
|
|
// Kept: BakingWork, ManageInventory
|
||
|
|
```
|
||
|
|
|
||
|
|
### Runtime State Re-evaluation
|
||
|
|
|
||
|
|
**After ANY action that modifies fields:**
|
||
|
|
|
||
|
|
```rust
|
||
|
|
impl Runtime {
|
||
|
|
fn execute_action(&mut self, action: Action) {
|
||
|
|
// 1. Execute action (mutates entity fields)
|
||
|
|
let entity_id = match &action {
|
||
|
|
Action::PracticeBaking { baker } => {
|
||
|
|
let character = self.get_character_mut(*baker);
|
||
|
|
character.skill_level += 1;
|
||
|
|
*baker
|
||
|
|
}
|
||
|
|
Action::LosePractice { baker } => {
|
||
|
|
let character = self.get_character_mut(*baker);
|
||
|
|
character.skill_level -= 1;
|
||
|
|
*baker
|
||
|
|
}
|
||
|
|
_ => {
|
||
|
|
// Handle other actions
|
||
|
|
return;
|
||
|
|
}
|
||
|
|
};
|
||
|
|
|
||
|
|
// 2. Re-evaluate ALL life arc states for this entity
|
||
|
|
self.update_life_arc_states(entity_id);
|
||
|
|
}
|
||
|
|
|
||
|
|
fn update_life_arc_states(&mut self, entity_id: EntityId) {
|
||
|
|
let entity = self.get_character(entity_id);
|
||
|
|
let life_arcs = entity.life_arcs.clone();
|
||
|
|
|
||
|
|
for (arc_name, current_state) in &life_arcs {
|
||
|
|
let arc = self.get_life_arc(arc_name);
|
||
|
|
let state_def = arc.get_state(current_state);
|
||
|
|
|
||
|
|
// Check if current state condition is still valid
|
||
|
|
if !self.evaluate_condition(entity_id, &state_def.condition) {
|
||
|
|
// State is invalid - find valid transition
|
||
|
|
for transition in &state_def.transitions {
|
||
|
|
if self.evaluate_condition(entity_id, &transition.condition) {
|
||
|
|
// Transition to new state
|
||
|
|
let entity = self.get_character_mut(entity_id);
|
||
|
|
entity.life_arcs.insert(arc_name.clone(), transition.to.clone());
|
||
|
|
|
||
|
|
// Log transition
|
||
|
|
println!(
|
||
|
|
"Entity {} transitioned: {}.{} -> {}.{}",
|
||
|
|
entity.name, arc_name, current_state, arc_name, transition.to
|
||
|
|
);
|
||
|
|
|
||
|
|
break;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Re-evaluation algorithm:**
|
||
|
|
1. After action execution, check ALL life arcs for the affected entity
|
||
|
|
2. For each life arc, evaluate current state's `when` condition
|
||
|
|
3. If condition is FALSE, current state is invalid
|
||
|
|
4. Check state's transitions in order
|
||
|
|
5. Take first transition whose condition is TRUE
|
||
|
|
6. Update entity's life_arcs to new state
|
||
|
|
7. New state's capabilities are now available
|
||
|
|
|
||
|
|
**Design principle:** State follows data. When field values change, states automatically transition to match the new reality.
|
||
|
|
|
||
|
|
### Mental Model
|
||
|
|
|
||
|
|
**Think of entities as:**
|
||
|
|
- **Data**: Fields with values (age, skill_level, reputation)
|
||
|
|
- **State**: Current position in life arc state machines
|
||
|
|
- **Capability**: Behaviors available based on state + location + template
|
||
|
|
|
||
|
|
**Life arcs are NOT metadata - they ARE the state.**
|
||
|
|
|
||
|
|
When you ask "What can Martha do?", you're asking:
|
||
|
|
1. What state is she in? (Human.Adult, Baker.Master)
|
||
|
|
2. Where is she? (Bakery)
|
||
|
|
3. What does her template grant? (Baker template)
|
||
|
|
|
||
|
|
The answer is the intersection of capabilities from all three layers.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Type System Soundness
|
||
|
|
|
||
|
|
### Type Theory Foundations
|
||
|
|
|
||
|
|
Storybook uses a **hybrid type system** combining:
|
||
|
|
- **Nominal typing**: Templates and entities have explicit names and identity
|
||
|
|
- **Structural typing**: Life arc requirements use duck typing (any template with required fields)
|
||
|
|
- **Subtype polymorphism**: Template extension creates subtype relationships
|
||
|
|
- **Algebraic types**: Concepts and sub_concepts form sum types with pattern matching
|
||
|
|
|
||
|
|
### Subtyping Relationship
|
||
|
|
|
||
|
|
**Template extension creates subtyping:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
species Human { strength: Number = 10 }
|
||
|
|
template Person: Human { age: Number }
|
||
|
|
template Baker: Person { skill_level: Number }
|
||
|
|
|
||
|
|
Type hierarchy:
|
||
|
|
Baker <: Person <: Human
|
||
|
|
```
|
||
|
|
|
||
|
|
**Liskov Substitution Principle:**
|
||
|
|
- Baker can be used wherever Person is expected (has all Person fields)
|
||
|
|
- Person can be used wherever Human is expected (has all Human fields)
|
||
|
|
- Subtyping flows from child to parent (upcast is implicit and safe)
|
||
|
|
|
||
|
|
**Action parameter compatibility:**
|
||
|
|
```storybook
|
||
|
|
action greet(person: Person)
|
||
|
|
|
||
|
|
character Martha: Baker
|
||
|
|
greet(Martha) // ✓ Valid - Baker <: Person (implicit upcast)
|
||
|
|
|
||
|
|
action train_baker(baker: Baker)
|
||
|
|
|
||
|
|
character Bob: Person
|
||
|
|
train_baker(Bob) // ✗ ERROR - Person </: Baker (not a subtype)
|
||
|
|
```
|
||
|
|
|
||
|
|
**No downcasting needed:**
|
||
|
|
All types are resolved at compile-time. The compiler knows the exact template type of each entity and validates action calls accordingly.
|
||
|
|
|
||
|
|
### Field Resolution Through Inheritance
|
||
|
|
|
||
|
|
**Override chain priority: Character > Template > Species**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
species Human { strength: Number = 10, speed: Number = 10 }
|
||
|
|
template Warrior: Human { strength: Number = 15 }
|
||
|
|
character Conan: Warrior { strength: 20 }
|
||
|
|
|
||
|
|
Field lookup algorithm:
|
||
|
|
Conan.strength:
|
||
|
|
1. Check character instance → 20 ✓ FOUND
|
||
|
|
2. (not reached)
|
||
|
|
|
||
|
|
Conan.speed:
|
||
|
|
1. Check character instance → not found
|
||
|
|
2. Check Warrior template → not found
|
||
|
|
3. Check Human species → 10 ✓ FOUND
|
||
|
|
```
|
||
|
|
|
||
|
|
**Type invariance through chain:**
|
||
|
|
- Field types cannot change through inheritance
|
||
|
|
- If species defines `strength: Number`, template cannot override to `strength: Text`
|
||
|
|
- Only values/defaults can be overridden, not types
|
||
|
|
|
||
|
|
### Life Arc Type Constraints
|
||
|
|
|
||
|
|
**Life arcs use structural requirements (duck typing):**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Aging requires { age: Number, species: Species }
|
||
|
|
|
||
|
|
// Works with ANY template that has these fields
|
||
|
|
```
|
||
|
|
|
||
|
|
**Compile-time validation:**
|
||
|
|
```storybook
|
||
|
|
template Person: Human {
|
||
|
|
age: Number, // ✓ has required field
|
||
|
|
name: Text,
|
||
|
|
// species inherited from Human ✓ has required field
|
||
|
|
}
|
||
|
|
|
||
|
|
character Martha: Person {
|
||
|
|
life_arcs: {
|
||
|
|
Aging: Adult // ✓ Person has all required fields
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compiler checks:
|
||
|
|
// 1. Does Person have field 'age' of type Number? YES
|
||
|
|
// 2. Does Person have field 'species' of type Species? YES (inherited)
|
||
|
|
// 3. Are types exact match (not subtypes)? YES
|
||
|
|
// → VALID
|
||
|
|
```
|
||
|
|
|
||
|
|
**Unsoundness prevention:**
|
||
|
|
```storybook
|
||
|
|
template Robot {
|
||
|
|
age?: Number // Optional field
|
||
|
|
}
|
||
|
|
|
||
|
|
life_arc Aging requires { age: Number } {
|
||
|
|
// Requires NON-optional age
|
||
|
|
}
|
||
|
|
|
||
|
|
character R2D2: Robot {
|
||
|
|
life_arcs: { Aging: Adult }
|
||
|
|
}
|
||
|
|
|
||
|
|
// ✗ TYPE ERROR: Aging requires age: Number (required)
|
||
|
|
// but Robot has age?: Number (optional)
|
||
|
|
// Required field cannot be optional in template
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rule:** Life arc required fields MUST be non-optional in template.
|
||
|
|
|
||
|
|
### Behavior Type Checking
|
||
|
|
|
||
|
|
**Implicit self with template typing:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
behavior BakingWork {
|
||
|
|
then {
|
||
|
|
check_orders // Desugars to: check_orders(self)
|
||
|
|
bake(Bread) // Desugars to: bake(self, Bread)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
action check_orders(baker: Baker)
|
||
|
|
action bake(baker: Baker, item: Item)
|
||
|
|
```
|
||
|
|
|
||
|
|
**Type of implicit self:**
|
||
|
|
- `self` has the type of the entity's template
|
||
|
|
- Known at compile-time
|
||
|
|
|
||
|
|
**Compatibility checking:**
|
||
|
|
```storybook
|
||
|
|
character Martha: Baker {
|
||
|
|
uses behaviors: [BakingWork]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compiler validates:
|
||
|
|
// When Martha runs BakingWork:
|
||
|
|
// self: Baker (Martha's template)
|
||
|
|
// check_orders(self) → check_orders(Baker) ✓
|
||
|
|
// bake(self, Bread) → bake(Baker, Item) ✓
|
||
|
|
// → VALID
|
||
|
|
|
||
|
|
character Bob: Person {
|
||
|
|
uses behaviors: [BakingWork]
|
||
|
|
}
|
||
|
|
|
||
|
|
// Compiler validates:
|
||
|
|
// When Bob tries to run BakingWork:
|
||
|
|
// self: Person (Bob's template)
|
||
|
|
// check_orders(self) → check_orders(Person)
|
||
|
|
// But check_orders expects Baker
|
||
|
|
// Person </: Baker (not a subtype)
|
||
|
|
// → ERROR: Bob cannot use BakingWork
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rule:** Entity can only use behaviors where all action calls are compatible with entity's template type (via exact match or subtyping).
|
||
|
|
|
||
|
|
### State Validation
|
||
|
|
|
||
|
|
**State conditions are contracts:**
|
||
|
|
|
||
|
|
```storybook
|
||
|
|
life_arc Baker requires { skill_level: Number } {
|
||
|
|
Master {
|
||
|
|
when skill_level >= 7 // Entry condition + runtime assertion
|
||
|
|
can use behaviors: [InnovateTechniques]
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**The `when` clause serves two purposes:**
|
||
|
|
1. **Entry condition**: Must be true to transition INTO this state
|
||
|
|
2. **Runtime assertion**: Should be true while IN this state
|
||
|
|
|
||
|
|
**Potential inconsistency:**
|
||
|
|
```storybook
|
||
|
|
character Martha: Baker {
|
||
|
|
skill_level: 8,
|
||
|
|
life_arcs: { Baker: Master } // State says Master
|
||
|
|
}
|
||
|
|
|
||
|
|
action lower_skill(baker: Baker) {
|
||
|
|
baker.skill_level -= 5 // Now skill_level = 3
|
||
|
|
}
|
||
|
|
|
||
|
|
// After action: Martha is in Master state, but skill_level < 7
|
||
|
|
// State is inconsistent with data!
|
||
|
|
```
|
||
|
|
|
||
|
|
**Resolution - Hybrid approach:**
|
||
|
|
|
||
|
|
**Compile-time (Storybook):**
|
||
|
|
Emit warnings when actions could invalidate state conditions:
|
||
|
|
```
|
||
|
|
Warning: action 'lower_skill' modifies 'skill_level' which is used in
|
||
|
|
state condition for Baker.Master (requires skill_level >= 7).
|
||
|
|
Runtime should validate state before executing state-specific behaviors.
|
||
|
|
```
|
||
|
|
|
||
|
|
**Runtime (Implementation):**
|
||
|
|
Before executing behaviors that depend on state:
|
||
|
|
```rust
|
||
|
|
impl Runtime {
|
||
|
|
fn execute_behavior(&mut self, entity_id: EntityId, behavior: &Behavior) {
|
||
|
|
let character = self.get_character(entity_id);
|
||
|
|
|
||
|
|
// Validate current state conditions
|
||
|
|
for (arc_name, current_state) in &character.life_arcs {
|
||
|
|
let arc = self.get_life_arc(arc_name);
|
||
|
|
let state_def = arc.get_state(current_state);
|
||
|
|
|
||
|
|
if !self.evaluate_condition(entity_id, &state_def.condition) {
|
||
|
|
// State is inconsistent!
|
||
|
|
// Option A: Re-evaluate and transition to correct state
|
||
|
|
// Option B: Log warning and continue
|
||
|
|
// Option C: Throw error and reject behavior
|
||
|
|
// Runtime decides policy
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
// Execute behavior actions...
|
||
|
|
}
|
||
|
|
}
|
||
|
|
```
|
||
|
|
|
||
|
|
**Rule:** State conditions are validated at entry (transition time) and SHOULD be validated at execution time (runtime's responsibility).
|
||
|
|
|
||
|
|
### Type Checking Guarantees
|
||
|
|
|
||
|
|
**Compile-time guarantees:**
|
||
|
|
✅ Template subtyping is safe (Baker <: Person)
|
||
|
|
✅ Field access through inheritance is valid
|
||
|
|
✅ Life arc structural requirements are satisfied
|
||
|
|
✅ Action parameters match expected types
|
||
|
|
✅ Behaviors are compatible with entity templates
|
||
|
|
✅ Initial life arc states satisfy entry conditions
|
||
|
|
|
||
|
|
**Runtime responsibilities:**
|
||
|
|
⚠️ Validate state conditions before executing state-specific behaviors
|
||
|
|
⚠️ Handle state transitions when field values change
|
||
|
|
⚠️ Manage entity state consistency
|
||
|
|
|
||
|
|
**The language guarantees type safety at compile-time. The runtime guarantees state consistency at execution-time.**
|
||
|
|
|
||
|
|
### Formal Type Rules
|
||
|
|
|
||
|
|
**Subtyping (transitive):**
|
||
|
|
```
|
||
|
|
T extends S
|
||
|
|
─────────────── (SUB-EXTEND)
|
||
|
|
T <: S
|
||
|
|
|
||
|
|
T <: S S <: R
|
||
|
|
─────────────── (SUB-TRANS)
|
||
|
|
T <: R
|
||
|
|
```
|
||
|
|
|
||
|
|
**Field access:**
|
||
|
|
```
|
||
|
|
E : T T has field f : τ (through inheritance chain)
|
||
|
|
──────────────────────────────────────────────────── (FIELD-ACCESS)
|
||
|
|
E.f : τ
|
||
|
|
```
|
||
|
|
|
||
|
|
**Life arc compatibility:**
|
||
|
|
```
|
||
|
|
T has fields {f1: τ1, ..., fn: τn} (required, non-optional)
|
||
|
|
L requires {f1: τ1, ..., fn: τn}
|
||
|
|
──────────────────────────────────────────────────── (LIFEARC-COMPAT)
|
||
|
|
T compatible with L
|
||
|
|
```
|
||
|
|
|
||
|
|
**Behavior validity:**
|
||
|
|
```
|
||
|
|
B calls actions {a1(Self, ...), ..., an(Self, ...)}
|
||
|
|
E : T
|
||
|
|
∀i. T <: ParamType(ai, 0) (template is subtype of first param)
|
||
|
|
──────────────────────────────────────────────────── (BEHAVIOR-VALID)
|
||
|
|
E can use behavior B
|
||
|
|
```
|
||
|
|
|
||
|
|
**Action call:**
|
||
|
|
```
|
||
|
|
a : (T1, T2, ..., Tn) → Unit
|
||
|
|
E : T T <: T1
|
||
|
|
args : (T2, ..., Tn)
|
||
|
|
──────────────────────────────────────────────────── (ACTION-CALL)
|
||
|
|
a(E, args) : Unit
|
||
|
|
```
|
||
|
|
|
||
|
|
### Summary
|
||
|
|
|
||
|
|
The type system is **sound** with these properties:
|
||
|
|
- **Progress**: Well-typed programs don't get stuck at compile-time
|
||
|
|
- **Type preservation**: Types are maintained through inheritance and execution
|
||
|
|
- **Memory safety**: No invalid field access or type confusion
|
||
|
|
- **Subtype safety**: Upcasting (child → parent) is safe and implicit
|
||
|
|
|
||
|
|
The runtime must maintain **state consistency** by validating life arc conditions at execution time.
|
||
|
|
|
||
|
|
---
|
||
|
|
|
||
|
|
## Future Considerations
|
||
|
|
|
||
|
|
- **Exhaustiveness checking**: Ensure all variant combinations are covered in concept_comparisons
|
||
|
|
- **Default variants**: Support for catch-all patterns in comparisons
|
||
|
|
- **Type inference**: Automatic parent concept detection beyond prefix matching
|
||
|
|
- **Generic concepts**: Parameterized types for reusable patterns
|
||
|
|
- **Constraint validation**: Runtime validation of type constraints
|