## Agility Courses Agility courses are defined in `data/courses/.yaml`. Each course defines a sequence of obstacle rooms, each with a specific verb the player must type to advance. Obstacle rooms themselves are standard room YAML files (typically in `data/rooms/agility/`). **The filename is the ID** (`vent_shaft.yaml` → `vent_shaft`); the loader derives it from the filename. Do not put an `id:` field in the file — it is ignored. ### Course YAML ```yaml name: "Ventilation Shaft Course" required_level: 1 start_room: 200 completion_xp: 40 obstacles: - room_id: 201 verb: scramble ticks_per_phase: 2 xp: 8 fail_damage: [1, 2] messages: - "You approach the corroded ventilation wall..." - "You find footholds in the rusted panels and begin to climb..." - "You scramble up the wall and haul yourself onto the ledge!" - room_id: 202 verb: balance ticks_per_phase: 2 xp: 8 fail_damage: [1, 2] messages: - "You step onto the narrow coolant pipe..." - "Arms outstretched, you carefully place one foot in front of the other..." - "You reach the other side of the pipe and step onto solid ground!" ``` ### CourseConfig Fields | Field | Type | Description | | ---------------- | ------------- | ---------------------------------------------------- | | `id` | string | Unique course identifier | | `name` | string | Display name shown to players | | `required_level` | int | Minimum Agility level to attempt the course | | `start_room` | int | Hub room — teleported here on fail or lap completion | | `completion_xp` | int | Bonus XP awarded when a full lap is completed | | `obstacles` | []ObstacleDef | Ordered list of obstacles | ### ObstacleDef Fields | Field | Type | Description | | ----------------- | -------- | --------------------------------------------------------------------------- | | `room_id` | int | Room ID for this obstacle | | `verb` | string | Command the player types to attempt it (e.g. `scramble`, `jump`, `climb`) | | `ticks_per_phase` | float64 | Ticks between each phase message (supports fractional via `engine.ToTicks`) | | `xp` | int | XP awarded for successfully completing this single obstacle | | `fail_damage` | [2]int | `[min, max]` damage on failure (HP clamped to minimum 1 — cannot kill) | | `messages` | []string | Exactly 3 strings for the 3-phase advancement system | ### 3-Phase Message System Each obstacle advances through 3 phases, printing one message per phase: ``` Phase 0 (start): messages[0] printed immediately Phase 1 (middle): messages[1] printed — FAILURE CHECK happens here Phase 2 (end): messages[2] printed — XP awarded, teleport to next room ``` Failure only occurs at phase 1. Success chance is based on Agility level: - At required level: 70% success (30% fail) - Each level above reduces fail chance by 1% (down to minimum 5%) - Fail chance capped at 60% On failure, the player takes random damage in `[fail_damage[0], fail_damage[1]]`, is teleported back to `start_room`, and must restart the course. ### Lap Counting Completing all obstacles in a course increments a lap counter stored as a player flag (`agility_laps_`). The `completion_xp` bonus is awarded on the final obstacle only. ### Obstacle Room YAML Each obstacle room should have an `on_enter` message telling the player which verb to use, and a `down` exit back to the course hub: ```yaml name: "Ventilation Shaft - Corroded Wall" description: "A towering wall of corroded ventilation panels..." on_enter: - message: "Type 'scramble' to climb the wall." exits: down: room: 200 blocked_message: "" ``` The hub room (e.g. room 200) links to the first obstacle of each course: ```yaml name: "Agility Training Grounds" exits: south: 17 north: 201 # Vent Shaft (level 1) east: 210 # Rooftop (level 20) west: 220 # Reactor (level 50) ``` ### Supported Obstacle Verbs | Verb | Gerund (display) | | ---------- | ---------------- | | `scramble` | scrambling | | `jump` | jumping | | `swing` | swinging | | `balance` | balancing | | `climb` | climbing | | `crawl` | crawling | | `vault` | vaulting | | `leap` | leaping | | `slide` | sliding | Obstacle verbs are auto-registered at startup from all course YAML files. No changes to `cmd_registry.go` needed when adding new courses — any verb in a `data/courses/` file will work.