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.
777 lines
16 KiB
Markdown
777 lines
16 KiB
Markdown
# Schedules
|
|
|
|
Schedules define time-based routines for characters and institutions. They specify what activities occur during specific time ranges, support seasonal variations, recurring events, and template composition. Schedules enable rich temporal behavior from simple daily routines to complex year-long patterns.
|
|
|
|
## What is a Schedule?
|
|
|
|
A schedule is a collection of time blocks that define activities throughout a day, week, season, or year:
|
|
|
|
- **Blocks**: Time ranges (e.g., `06:00 - 14:00`) with associated activities/behaviors
|
|
- **Temporal constraints**: When blocks apply (season, day of week, month)
|
|
- **Recurrence patterns**: Repeating events (e.g., "Market Day every Earthday")
|
|
- **Composition**: Schedules can extend and override other schedules
|
|
|
|
```
|
|
Schedule: BakerySchedule
|
|
├─ Block: 06:00 - 08:00 → prepare_dough
|
|
├─ Block: 08:00 - 14:00 → serve_customers
|
|
├─ Recurrence: Market Day (on Earthday) → special_market_hours
|
|
└─ Modifies: BaseBusiness (inherits closing hours)
|
|
```
|
|
|
|
## Syntax
|
|
|
|
```bnf
|
|
<schedule-decl> ::= "schedule" <identifier> <modifies-clause>? <body>
|
|
|
|
<modifies-clause> ::= "modifies" <identifier>
|
|
|
|
<body> ::= "{" <schedule-item>* "}"
|
|
|
|
<schedule-item> ::= <schedule-block>
|
|
| <recurrence-pattern>
|
|
|
|
<schedule-block> ::= "block" <block-name>? "{" <block-content>+ "}"
|
|
|
|
<block-name> ::= <identifier>
|
|
|
|
<block-content> ::= <time-range>
|
|
| "action" ":" <qualified-path>
|
|
| <temporal-constraint>
|
|
| <field>
|
|
|
|
<time-range> ::= <time> "-" <time>
|
|
|
|
<temporal-constraint> ::= "on" <temporal-spec>
|
|
|
|
<temporal-spec> ::= "season" <identifier>
|
|
| "day" <identifier>
|
|
| "month" <identifier>
|
|
| "dates" <string> ".." <string>
|
|
|
|
<recurrence-pattern> ::= "recurs" <identifier> <temporal-constraint> "{" <schedule-block>+ "}"
|
|
```
|
|
|
|
## Schedule Blocks
|
|
|
|
A schedule block defines an activity during a specific time range.
|
|
|
|
### Basic Block
|
|
|
|
```storybook
|
|
schedule SimpleBaker {
|
|
block {
|
|
06:00 - 14:00
|
|
action: baking::prepare_and_sell
|
|
}
|
|
}
|
|
```
|
|
|
|
### Named Block
|
|
|
|
Named blocks support the override system:
|
|
|
|
```storybook
|
|
schedule BakeryBase {
|
|
block work {
|
|
09:00 - 17:00
|
|
action: baking::standard_work
|
|
}
|
|
}
|
|
|
|
schedule EarlyBaker modifies BakeryBase {
|
|
block work { // Override by name
|
|
05:00 - 13:00
|
|
action: baking::early_shift
|
|
}
|
|
}
|
|
```
|
|
|
|
### Block Content
|
|
|
|
Blocks can contain:
|
|
|
|
1. **Time range** (required): `HH:MM - HH:MM`
|
|
2. **Action** (optional): Behavior tree reference
|
|
3. **Temporal constraint** (optional): When the block applies
|
|
4. **Fields** (optional): Additional metadata
|
|
|
|
```storybook
|
|
schedule CompleteBlock {
|
|
block morning_work {
|
|
06:00 - 12:00
|
|
action: work::morning_routine
|
|
on season summer
|
|
location: "Outdoor Market"
|
|
}
|
|
}
|
|
```
|
|
|
|
## Time Ranges
|
|
|
|
Time ranges use 24-hour clock format.
|
|
|
|
### Syntax
|
|
|
|
```storybook
|
|
HH:MM - HH:MM
|
|
```
|
|
|
|
- **HH**: Hour (00-23)
|
|
- **MM**: Minute (00-59)
|
|
- Optional seconds: `HH:MM:SS - HH:MM:SS`
|
|
|
|
### Examples
|
|
|
|
```storybook
|
|
schedule Examples {
|
|
block early { 05:00 - 08:00, action: prep }
|
|
block midday { 12:00 - 13:00, action: lunch }
|
|
block late { 20:00 - 23:59, action: closing }
|
|
}
|
|
```
|
|
|
|
### Overnight Ranges
|
|
|
|
Blocks can span midnight:
|
|
|
|
```storybook
|
|
schedule NightShift {
|
|
block night_work {
|
|
22:00 - 06:00 // 10 PM to 6 AM next day
|
|
action: security::patrol
|
|
}
|
|
}
|
|
```
|
|
|
|
The system interprets this as 22:00-24:00 (day 1) + 00:00-06:00 (day 2).
|
|
|
|
## Actions
|
|
|
|
The `action` field links a schedule block to a behavior tree.
|
|
|
|
### Syntax
|
|
|
|
```storybook
|
|
action: <qualified-path>
|
|
```
|
|
|
|
### Examples
|
|
|
|
```storybook
|
|
schedule BakerSchedule {
|
|
block morning {
|
|
05:00 - 08:00
|
|
action: baking::prepare_dough
|
|
}
|
|
|
|
block sales {
|
|
08:00 - 14:00
|
|
action: baking::serve_customers
|
|
}
|
|
|
|
block cleanup {
|
|
14:00 - 15:00
|
|
action: baking::close_shop
|
|
}
|
|
}
|
|
```
|
|
|
|
### Qualified Paths
|
|
|
|
Actions can reference behaviors from other modules:
|
|
|
|
```storybook
|
|
schedule GuardSchedule {
|
|
block patrol {
|
|
08:00 - 16:00
|
|
action: behaviors::guards::patrol_route
|
|
}
|
|
|
|
block training {
|
|
16:00 - 18:00
|
|
action: combat::training::drills
|
|
}
|
|
}
|
|
```
|
|
|
|
## Temporal Constraints
|
|
|
|
Temporal constraints specify when a block applies (season, day of week, month, date range).
|
|
|
|
### Season Constraint
|
|
|
|
```storybook
|
|
schedule SeasonalHours {
|
|
block summer_hours {
|
|
06:00 - 20:00
|
|
action: work::long_day
|
|
on season summer
|
|
}
|
|
|
|
block winter_hours {
|
|
07:00 - 18:00
|
|
action: work::short_day
|
|
on season winter
|
|
}
|
|
}
|
|
```
|
|
|
|
**Season values** are defined by enums in your storybook:
|
|
```storybook
|
|
enum Season {
|
|
spring
|
|
summer
|
|
fall
|
|
winter
|
|
}
|
|
```
|
|
|
|
### Day of Week Constraint
|
|
|
|
```storybook
|
|
schedule WeeklyPattern {
|
|
block weekday_work {
|
|
09:00 - 17:00
|
|
action: work::standard
|
|
on day monday // Also: tuesday, wednesday, thursday, friday, saturday, sunday
|
|
}
|
|
|
|
block weekend_rest {
|
|
10:00 - 16:00
|
|
action: leisure::relax
|
|
on day saturday
|
|
}
|
|
}
|
|
```
|
|
|
|
**Day values** are typically defined by a `DayOfWeek` enum:
|
|
```storybook
|
|
enum DayOfWeek {
|
|
monday
|
|
tuesday
|
|
wednesday
|
|
thursday
|
|
friday
|
|
saturday
|
|
sunday
|
|
}
|
|
```
|
|
|
|
### Month Constraint
|
|
|
|
```storybook
|
|
schedule AnnualPattern {
|
|
block harvest {
|
|
06:00 - 20:00
|
|
action: farming::harvest_crops
|
|
on month october
|
|
}
|
|
|
|
block spring_planting {
|
|
07:00 - 19:00
|
|
action: farming::plant_seeds
|
|
on month april
|
|
}
|
|
}
|
|
```
|
|
|
|
### Date Range Constraint
|
|
|
|
```storybook
|
|
schedule TouristSeason {
|
|
block high_season {
|
|
08:00 - 22:00
|
|
action: tourism::busy_service
|
|
on dates "Jun 1" .. "Sep 1"
|
|
}
|
|
}
|
|
```
|
|
|
|
**Note**: Date format is implementation-specific. Check your runtime for supported formats.
|
|
|
|
## Recurrence Patterns
|
|
|
|
Recurrence patterns define recurring events that modify the schedule.
|
|
|
|
### Syntax
|
|
|
|
```storybook
|
|
recurs EventName on <temporal-constraint> {
|
|
<schedule-block>+
|
|
}
|
|
```
|
|
|
|
### Examples
|
|
|
|
#### Weekly Recurring Event
|
|
|
|
```storybook
|
|
schedule MarketSchedule {
|
|
// Regular daily hours
|
|
block work {
|
|
08:00 - 17:00
|
|
action: shop::regular_sales
|
|
}
|
|
|
|
// Market day every saturday
|
|
recurs MarketDay on day saturday {
|
|
block setup {
|
|
06:00 - 08:00
|
|
action: market::setup_stall
|
|
}
|
|
|
|
block busy_market {
|
|
08:00 - 18:00
|
|
action: market::busy_sales
|
|
}
|
|
|
|
block teardown {
|
|
18:00 - 20:00
|
|
action: market::pack_up
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Monthly Recurring Event
|
|
|
|
```storybook
|
|
schedule TownSchedule {
|
|
recurs CouncilMeeting on month first_monday {
|
|
block meeting {
|
|
18:00 - 21:00
|
|
action: governance::council_session
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
#### Seasonal Recurring Event
|
|
|
|
```storybook
|
|
schedule FarmSchedule {
|
|
recurs SpringPlanting on season spring {
|
|
block planting {
|
|
06:00 - 18:00
|
|
action: farming::plant_all_fields
|
|
}
|
|
}
|
|
|
|
recurs FallHarvest on season fall {
|
|
block harvest {
|
|
06:00 - 20:00
|
|
action: farming::harvest_all_crops
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Schedule Composition
|
|
|
|
Schedules can extend other schedules using `modifies`, inheriting and overriding blocks.
|
|
|
|
### Modifies Clause
|
|
|
|
```storybook
|
|
schedule Base {
|
|
block work {
|
|
09:00 - 17:00
|
|
action: work::standard
|
|
}
|
|
}
|
|
|
|
schedule Extended modifies Base {
|
|
block work { // Override inherited block
|
|
05:00 - 13:00
|
|
action: work::early_shift
|
|
}
|
|
}
|
|
```
|
|
|
|
### Override Semantics
|
|
|
|
When extending a schedule:
|
|
|
|
1. **Named blocks** with matching names override base blocks
|
|
2. **Unnamed blocks** are appended
|
|
3. **Time range** can change
|
|
4. **Action** can change
|
|
5. **Constraints** can be added/changed
|
|
|
|
### Multiple Levels
|
|
|
|
```storybook
|
|
schedule BaseWork {
|
|
block work {
|
|
09:00 - 17:00
|
|
action: work::standard
|
|
}
|
|
}
|
|
|
|
schedule BakerWork modifies BaseWork {
|
|
block work {
|
|
05:00 - 13:00 // Earlier hours
|
|
action: baking::work
|
|
}
|
|
}
|
|
|
|
schedule MasterBaker modifies BakerWork {
|
|
block work {
|
|
03:00 - 11:00 // Even earlier!
|
|
action: baking::master_work
|
|
}
|
|
|
|
block teaching {
|
|
14:00 - 16:00
|
|
action: baking::teach_apprentice
|
|
}
|
|
}
|
|
```
|
|
|
|
**Resolution:**
|
|
- `work` block: 03:00-11:00 with `baking::master_work` (MasterBaker overrides BakerWork overrides BaseWork)
|
|
- `teaching` block: 14:00-16:00 with `baking::teach_apprentice` (from MasterBaker)
|
|
|
|
## Complete Examples
|
|
|
|
### Simple Daily Schedule
|
|
|
|
```storybook
|
|
schedule SimpleFarmer {
|
|
block sleep {
|
|
22:00 - 05:00
|
|
action: rest::sleep
|
|
}
|
|
|
|
block morning_chores {
|
|
05:00 - 08:00
|
|
action: farming::feed_animals
|
|
}
|
|
|
|
block fieldwork {
|
|
08:00 - 17:00
|
|
action: farming::tend_crops
|
|
}
|
|
|
|
block evening_chores {
|
|
17:00 - 19:00
|
|
action: farming::evening_tasks
|
|
}
|
|
|
|
block leisure {
|
|
19:00 - 22:00
|
|
action: social::family_time
|
|
}
|
|
}
|
|
```
|
|
|
|
### Seasonal Variation
|
|
|
|
```storybook
|
|
schedule SeasonalBaker {
|
|
block summer_work {
|
|
06:00 - 20:00
|
|
action: baking::long_shift
|
|
on season summer
|
|
}
|
|
|
|
block winter_work {
|
|
07:00 - 18:00
|
|
action: baking::short_shift
|
|
on season winter
|
|
}
|
|
|
|
block spring_work {
|
|
06:30 - 19:00
|
|
action: baking::medium_shift
|
|
on season spring
|
|
}
|
|
|
|
block fall_work {
|
|
06:30 - 19:00
|
|
action: baking::medium_shift
|
|
on season fall
|
|
}
|
|
}
|
|
```
|
|
|
|
### Weekly Pattern with Recurrence
|
|
|
|
```storybook
|
|
schedule InnkeeperSchedule {
|
|
// Weekday routine
|
|
block weekday_service {
|
|
08:00 - 22:00
|
|
action: inn::regular_service
|
|
on day monday // Repeat for each weekday
|
|
}
|
|
|
|
block weekday_service {
|
|
08:00 - 22:00
|
|
action: inn::regular_service
|
|
on day tuesday
|
|
}
|
|
|
|
// ... (wednesday, thursday, friday)
|
|
|
|
// Weekend hours
|
|
block weekend_service {
|
|
10:00 - 24:00
|
|
action: inn::busy_service
|
|
on day saturday
|
|
}
|
|
|
|
block weekend_service {
|
|
10:00 - 24:00
|
|
action: inn::busy_service
|
|
on day sunday
|
|
}
|
|
|
|
// Weekly market day
|
|
recurs MarketDay on day wednesday {
|
|
block market_prep {
|
|
06:00 - 08:00
|
|
action: inn::prepare_market_goods
|
|
}
|
|
|
|
block market_rush {
|
|
08:00 - 16:00
|
|
action: inn::market_day_chaos
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
### Extended Schedule
|
|
|
|
```storybook
|
|
schedule BaseShopkeeper {
|
|
block open {
|
|
09:00 - 17:00
|
|
action: shop::standard_hours
|
|
}
|
|
}
|
|
|
|
schedule BlacksmithSchedule modifies BaseShopkeeper {
|
|
block open { // Override
|
|
06:00 - 18:00 // Longer hours
|
|
action: smithing::work
|
|
}
|
|
|
|
block forge_heat { // New block
|
|
05:00 - 06:00
|
|
action: smithing::heat_forge
|
|
}
|
|
}
|
|
```
|
|
|
|
### Complex Year-Round Schedule
|
|
|
|
```storybook
|
|
schedule MasterBakerYear {
|
|
// Daily base pattern
|
|
block prep {
|
|
04:00 - 06:00
|
|
action: baking::prepare
|
|
}
|
|
|
|
block baking {
|
|
06:00 - 10:00
|
|
action: baking::bake
|
|
}
|
|
|
|
block sales {
|
|
10:00 - 16:00
|
|
action: baking::serve
|
|
}
|
|
|
|
block cleanup {
|
|
16:00 - 17:00
|
|
action: baking::clean
|
|
}
|
|
|
|
// Seasonal variations
|
|
block summer_hours {
|
|
10:00 - 20:00 // Extended sales
|
|
action: baking::busy_summer
|
|
on season summer
|
|
}
|
|
|
|
// Weekly market
|
|
recurs MarketDay on day saturday {
|
|
block market_prep {
|
|
02:00 - 04:00
|
|
action: baking::market_prep
|
|
}
|
|
|
|
block market_sales {
|
|
08:00 - 18:00
|
|
action: baking::market_rush
|
|
}
|
|
}
|
|
|
|
// Annual events
|
|
recurs HarvestFestival on dates "Sep 20" .. "Sep 25" {
|
|
block festival {
|
|
06:00 - 23:00
|
|
action: baking::festival_mode
|
|
}
|
|
}
|
|
}
|
|
```
|
|
|
|
## Integration with Characters
|
|
|
|
Characters link to schedules using the `uses schedule` clause:
|
|
|
|
```storybook
|
|
character Martha {
|
|
uses schedule: BakerySchedule
|
|
}
|
|
```
|
|
|
|
**Multiple schedules:**
|
|
```storybook
|
|
character Innkeeper {
|
|
uses schedules: [WeekdaySchedule, WeekendSchedule]
|
|
}
|
|
```
|
|
|
|
See [Characters](./10-characters.md#schedule-integration) for complete integration syntax.
|
|
|
|
## Execution Semantics
|
|
|
|
### Time-Based Selection
|
|
|
|
At any given time, the runtime:
|
|
|
|
1. **Evaluates temporal constraints**: Which blocks apply right now?
|
|
2. **Selects matching block**: First block whose time range and constraint match
|
|
3. **Executes action**: Runs the associated behavior tree
|
|
4. **Repeats next tick**
|
|
|
|
### Priority Order
|
|
|
|
When multiple blocks could apply:
|
|
|
|
1. **Recurrences** take priority over regular blocks
|
|
2. **Constraints** filter blocks (season, day, month)
|
|
3. **Time ranges** must overlap current time
|
|
4. **First match** wins (declaration order)
|
|
|
|
### Block Overlap
|
|
|
|
Blocks can overlap in time. The runtime selects the first matching block.
|
|
|
|
**Example:**
|
|
```storybook
|
|
schedule Overlapping {
|
|
block general {
|
|
08:00 - 17:00
|
|
action: work::general
|
|
}
|
|
|
|
block specialized {
|
|
10:00 - 12:00 // Overlaps with general
|
|
action: work::specialized
|
|
on day wednesday
|
|
}
|
|
}
|
|
```
|
|
|
|
On Wednesday at 11:00, `specialized` block is selected (more specific constraint).
|
|
|
|
## Validation Rules
|
|
|
|
1. **Time format**: Times must be valid HH:MM or HH:MM:SS (24-hour)
|
|
2. **Time order**: Start time must be before end time (unless spanning midnight)
|
|
3. **Modifies exists**: If using `modifies`, base schedule must be defined
|
|
4. **No circular modifies**: Cannot form circular dependency chains
|
|
5. **Named blocks unique**: Block names must be unique within a schedule
|
|
6. **Action exists**: Action references must resolve to defined behaviors
|
|
7. **Constraint values**: Temporal constraint values must reference defined enums
|
|
8. **Recurrence names unique**: Recurrence names must be unique within a schedule
|
|
|
|
## Best Practices
|
|
|
|
### 1. Use Named Blocks for Overrides
|
|
|
|
**Avoid:**
|
|
```storybook
|
|
schedule Extended modifies Base {
|
|
block { 05:00 - 13:00, action: work } // Appends instead of overriding
|
|
}
|
|
```
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
schedule Extended modifies Base {
|
|
block work { 05:00 - 13:00, action: early_work } // Overrides by name
|
|
}
|
|
```
|
|
|
|
### 2. Group Related Blocks
|
|
|
|
```storybook
|
|
schedule DailyRoutine {
|
|
// Sleep blocks
|
|
block night_sleep { 22:00 - 05:00, action: sleep }
|
|
|
|
// Morning blocks
|
|
block wake_up { 05:00 - 06:00, action: morning_routine }
|
|
block breakfast { 06:00 - 07:00, action: eat_breakfast }
|
|
|
|
// Work blocks
|
|
block commute { 07:00 - 08:00, action: travel_to_work }
|
|
block work { 08:00 - 17:00, action: work }
|
|
}
|
|
```
|
|
|
|
### 3. Use Recurrences for Special Events
|
|
|
|
**Avoid:** Duplicating blocks manually
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
recurs MarketDay on day saturday {
|
|
block market { 08:00 - 16:00, action: market_work }
|
|
}
|
|
```
|
|
|
|
### 4. Extend for Variations
|
|
|
|
**Avoid:** Duplicating entire schedules
|
|
|
|
**Prefer:**
|
|
```storybook
|
|
schedule WorkerBase { ... }
|
|
schedule EarlyWorker modifies WorkerBase { ... }
|
|
schedule NightWorker modifies WorkerBase { ... }
|
|
```
|
|
|
|
### 5. Use Temporal Constraints for Clarity
|
|
|
|
```storybook
|
|
block summer_hours {
|
|
06:00 - 20:00
|
|
action: long_shift
|
|
on season summer // Explicit and readable
|
|
}
|
|
```
|
|
|
|
## Cross-References
|
|
|
|
- [Characters](./10-characters.md) - Linking schedules to characters
|
|
- [Behavior Trees](./11-behavior-trees.md) - Actions reference behavior trees
|
|
- [Value Types](./18-value-types.md) - Time and duration literals
|
|
- [Enums](../reference/16-other-declarations.md#enums) - Defining seasons, days, months
|
|
|
|
## Related Concepts
|
|
|
|
- **Time-based AI**: Schedules drive time-dependent behavior
|
|
- **Template inheritance**: `modifies` enables schedule reuse
|
|
- **Temporal constraints**: Filter blocks by season, day, month
|
|
- **Recurrence patterns**: Define repeating events
|
|
- **Composition**: Build complex schedules from simple parts
|