1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
|
## Agility Courses
Agility courses are defined in `data/courses/<id>.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/`).
### Course YAML
```yaml
id: "vent_shaft"
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_<course_id>`). 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
id: 201
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
id: 200
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.
|