clibuddy 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +60 -0
  3. package/dist/adventure/adventureUI.d.ts +24 -0
  4. package/dist/adventure/adventureUI.js +290 -0
  5. package/dist/adventure/adventures.d.ts +4 -0
  6. package/dist/adventure/adventures.js +206 -0
  7. package/dist/adventure/biomes.d.ts +30 -0
  8. package/dist/adventure/biomes.js +80 -0
  9. package/dist/adventure/combat/combat.d.ts +14 -0
  10. package/dist/adventure/combat/combat.js +638 -0
  11. package/dist/adventure/combat/combatUI.d.ts +5 -0
  12. package/dist/adventure/combat/combatUI.js +116 -0
  13. package/dist/adventure/combat/conditions.d.ts +20 -0
  14. package/dist/adventure/combat/conditions.js +111 -0
  15. package/dist/adventure/combat/enemies.d.ts +4 -0
  16. package/dist/adventure/combat/enemies.js +430 -0
  17. package/dist/adventure/combat/gear.d.ts +3 -0
  18. package/dist/adventure/combat/gear.js +199 -0
  19. package/dist/adventure/combat/skills.d.ts +6 -0
  20. package/dist/adventure/combat/skills.js +197 -0
  21. package/dist/adventure/combat.d.ts +31 -0
  22. package/dist/adventure/combat.js +732 -0
  23. package/dist/adventure/combatUI.d.ts +5 -0
  24. package/dist/adventure/combatUI.js +116 -0
  25. package/dist/adventure/endless.d.ts +18 -0
  26. package/dist/adventure/endless.js +154 -0
  27. package/dist/adventure/enemies.d.ts +4 -0
  28. package/dist/adventure/enemies.js +320 -0
  29. package/dist/adventure/engine.d.ts +20 -0
  30. package/dist/adventure/engine.js +137 -0
  31. package/dist/adventure/gear.d.ts +3 -0
  32. package/dist/adventure/gear.js +149 -0
  33. package/dist/adventure/generation/biomes.d.ts +30 -0
  34. package/dist/adventure/generation/biomes.js +102 -0
  35. package/dist/adventure/generation/endless.d.ts +18 -0
  36. package/dist/adventure/generation/endless.js +154 -0
  37. package/dist/adventure/generation/generator.d.ts +9 -0
  38. package/dist/adventure/generation/generator.js +245 -0
  39. package/dist/adventure/generation/templates.d.ts +25 -0
  40. package/dist/adventure/generation/templates.js +228 -0
  41. package/dist/adventure/generator.d.ts +9 -0
  42. package/dist/adventure/generator.js +245 -0
  43. package/dist/adventure/skills.d.ts +6 -0
  44. package/dist/adventure/skills.js +197 -0
  45. package/dist/adventure/templates.d.ts +25 -0
  46. package/dist/adventure/templates.js +228 -0
  47. package/dist/adventure/types.d.ts +236 -0
  48. package/dist/adventure/types.js +97 -0
  49. package/dist/app/state.d.ts +49 -0
  50. package/dist/app/state.js +51 -0
  51. package/dist/buddy/activities.d.ts +16 -0
  52. package/dist/buddy/activities.js +90 -0
  53. package/dist/buddy/decay.d.ts +3 -0
  54. package/dist/buddy/decay.js +45 -0
  55. package/dist/buddy/leveling.d.ts +11 -0
  56. package/dist/buddy/leveling.js +25 -0
  57. package/dist/buddy/roll.d.ts +4 -0
  58. package/dist/buddy/roll.js +61 -0
  59. package/dist/buddy/species.d.ts +4 -0
  60. package/dist/buddy/species.js +592 -0
  61. package/dist/buddy/titles.d.ts +17 -0
  62. package/dist/buddy/titles.js +89 -0
  63. package/dist/buddy/types.d.ts +92 -0
  64. package/dist/buddy/types.js +21 -0
  65. package/dist/commands/actions.d.ts +2 -0
  66. package/dist/commands/actions.js +141 -0
  67. package/dist/commands/admin.d.ts +2 -0
  68. package/dist/commands/admin.js +202 -0
  69. package/dist/commands/registry.d.ts +25 -0
  70. package/dist/commands/registry.js +31 -0
  71. package/dist/commands/social.d.ts +2 -0
  72. package/dist/commands/social.js +92 -0
  73. package/dist/dialogue/engine.d.ts +7 -0
  74. package/dist/dialogue/engine.js +68 -0
  75. package/dist/dialogue/lines.d.ts +26 -0
  76. package/dist/dialogue/lines.js +294 -0
  77. package/dist/events/engine.d.ts +20 -0
  78. package/dist/events/engine.js +51 -0
  79. package/dist/events/events.d.ts +13 -0
  80. package/dist/events/events.js +149 -0
  81. package/dist/index.d.ts +10 -0
  82. package/dist/index.js +1665 -0
  83. package/dist/inventory/finding.d.ts +11 -0
  84. package/dist/inventory/finding.js +99 -0
  85. package/dist/inventory/items.d.ts +31 -0
  86. package/dist/inventory/items.js +63 -0
  87. package/dist/minigames/copycat.d.ts +2 -0
  88. package/dist/minigames/copycat.js +153 -0
  89. package/dist/minigames/fetch.d.ts +2 -0
  90. package/dist/minigames/fetch.js +179 -0
  91. package/dist/minigames/moodmatch.d.ts +2 -0
  92. package/dist/minigames/moodmatch.js +144 -0
  93. package/dist/minigames/quickpaws.d.ts +2 -0
  94. package/dist/minigames/quickpaws.js +142 -0
  95. package/dist/minigames/registry.d.ts +5 -0
  96. package/dist/minigames/registry.js +16 -0
  97. package/dist/minigames/rpsplus.d.ts +2 -0
  98. package/dist/minigames/rpsplus.js +168 -0
  99. package/dist/minigames/treasurehunt.d.ts +2 -0
  100. package/dist/minigames/treasurehunt.js +146 -0
  101. package/dist/minigames/types.d.ts +30 -0
  102. package/dist/minigames/types.js +69 -0
  103. package/dist/rendering/commandPalette.d.ts +16 -0
  104. package/dist/rendering/commandPalette.js +77 -0
  105. package/dist/rendering/display.d.ts +9 -0
  106. package/dist/rendering/display.js +231 -0
  107. package/dist/rendering/inventoryUI.d.ts +14 -0
  108. package/dist/rendering/inventoryUI.js +99 -0
  109. package/dist/rendering/items.d.ts +7 -0
  110. package/dist/rendering/items.js +34 -0
  111. package/dist/rendering/listUtils.d.ts +3 -0
  112. package/dist/rendering/listUtils.js +24 -0
  113. package/dist/rendering/minigameUI.d.ts +11 -0
  114. package/dist/rendering/minigameUI.js +37 -0
  115. package/dist/rendering/overlayUI.d.ts +24 -0
  116. package/dist/rendering/overlayUI.js +184 -0
  117. package/dist/rendering/scene.d.ts +8 -0
  118. package/dist/rendering/scene.js +87 -0
  119. package/dist/rendering/screen.d.ts +43 -0
  120. package/dist/rendering/screen.js +97 -0
  121. package/dist/sound/sound.d.ts +11 -0
  122. package/dist/sound/sound.js +55 -0
  123. package/dist/state/save.d.ts +5 -0
  124. package/dist/state/save.js +100 -0
  125. package/dist/state/settings.d.ts +7 -0
  126. package/dist/state/settings.js +81 -0
  127. package/dist/story/mainStory.d.ts +4 -0
  128. package/dist/story/mainStory.js +3111 -0
  129. package/dist/story/npcs.d.ts +17 -0
  130. package/dist/story/npcs.js +137 -0
  131. package/dist/story/progress.d.ts +26 -0
  132. package/dist/story/progress.js +168 -0
  133. package/dist/story/seasonal.d.ts +6 -0
  134. package/dist/story/seasonal.js +96 -0
  135. package/dist/story/shop.d.ts +7 -0
  136. package/dist/story/shop.js +26 -0
  137. package/dist/story/sideQuests.d.ts +4 -0
  138. package/dist/story/sideQuests.js +151 -0
  139. package/dist/story/types.d.ts +61 -0
  140. package/dist/story/types.js +36 -0
  141. package/dist/updates.d.ts +23 -0
  142. package/dist/updates.js +142 -0
  143. package/package.json +53 -0
@@ -0,0 +1,3111 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.getAllQuestChains = getAllQuestChains;
4
+ exports.getQuestChain = getQuestChain;
5
+ const types_1 = require("../adventure/types");
6
+ function makeAdventure(def) {
7
+ return { ...def, roomMap: (0, types_1.buildRoomMap)(def.rooms) };
8
+ }
9
+ // ─── Chapter 1: Whispers in the Woods ────────────────────────
10
+ // Act 1: "A Normal Day" — Scene (5 rooms)
11
+ function buildCh1Act1() {
12
+ return makeAdventure({
13
+ id: "ch1_act1_normal_day",
14
+ name: "A Normal Day",
15
+ description: "A peaceful morning in the forest... or is it?",
16
+ difficulty: "easy", levelRequired: 1, energyCost: 0, hungerCost: 0, completionXp: 0,
17
+ isScene: true,
18
+ startRoomId: "ch1_morning",
19
+ rooms: [
20
+ {
21
+ id: "ch1_morning", type: "narrative",
22
+ title: "Good Morning",
23
+ text: [
24
+ "The forest is bathed in golden morning light. Dew clings to every leaf like tiny diamonds.",
25
+ "Birds are singing. A brook babbles nearby. Somewhere, a woodpecker is being very productive.",
26
+ "Your buddy stretches, yawns wide enough to swallow a pinecone, and blinks at the sun.",
27
+ "A butterfly drifts down and lands on your buddy's nose. Delicate. Perfect.",
28
+ "Your buddy goes cross-eyed trying to look at it.",
29
+ "*achoo!*",
30
+ "The butterfly spirals away, offended.",
31
+ ],
32
+ buddyLine: "Beautiful morning! Great weather! Lovely butterfly! ...TERRIBLE sneeze timing. Story of my life.",
33
+ nextRoomId: "ch1_path",
34
+ },
35
+ {
36
+ id: "ch1_path", type: "narrative",
37
+ title: "The Forest Path",
38
+ text: [
39
+ "The path winds through ancient oaks. Sunlight filters through the canopy in golden shafts.",
40
+ "Moss covers the stones like nature's carpet. The brook runs alongside, clear and musical.",
41
+ "A deer looks up from the far bank. Nods at your buddy. Genuinely nods. Your buddy nods back.",
42
+ "\"We have an understanding,\" your buddy whispers. \"It's a forest thing.\"",
43
+ "A squirrel sits on a branch, cheeks stuffed with acorns. Your buddy waves.",
44
+ "The squirrel stares. Chews. Stares more. Looks away.",
45
+ "\"...okay, the squirrel and I do NOT have an understanding.\"",
46
+ ],
47
+ buddyLine: "This forest has everything. Trees! Rocks! A brook with OPINIONS about which way to flow! What more could you want?",
48
+ choices: [
49
+ { label: "Follow the main path", nextRoomId: "ch1_off" },
50
+ { label: "Take the scenic route", nextRoomId: "ch1_off" },
51
+ ],
52
+ },
53
+ {
54
+ id: "ch1_off", type: "narrative",
55
+ title: "Something's Off",
56
+ text: [
57
+ "Your buddy stops mid-step. One paw raised. Ears rotating like satellite dishes.",
58
+ "\"Wait.\"",
59
+ "The birds have stopped singing. Not one at a time — all at once. Like someone turned off a switch.",
60
+ "A leaf falls past your face. Upward.",
61
+ "You both watch it drift toward the sky. Neither of you says anything for a moment.",
62
+ "The brook's babble has changed pitch. Lower. Almost a moan.",
63
+ "Your buddy's fur starts to stand on end. Not from cold.",
64
+ "\"Do you hear that?\" they whisper. Then, quieter: \"...no. Do you hear the ABSENCE of that?\"",
65
+ "The forest has gone silent.",
66
+ ],
67
+ buddyLine: "Something is wrong. I can feel it in the air, in the ground, in... everything. The forest isn't just quiet. It's HOLDING ITS BREATH.",
68
+ nextRoomId: "ch1_silence",
69
+ },
70
+ {
71
+ id: "ch1_silence", type: "narrative",
72
+ title: "The Silence",
73
+ text: [
74
+ "Every step crunches too loud. The silence makes everything enormous.",
75
+ "A rabbit sits frozen in the path. Trembling. Eyes wide as moons.",
76
+ "Your buddy crouches down slowly. \"Hey. Hey, little one. What's—\"",
77
+ "The rabbit BOLTS. Past you. Through you. Gone in a heartbeat.",
78
+ "Then more. Foxes. Mice. A badger. All running the same direction — AWAY.",
79
+ "Not hiding. Not burrowing. FLEEING. Like the forest itself told them to run.",
80
+ "Your buddy stands in the stream of panicked animals, watching them go.",
81
+ "Something is deeper in the woods. Something WRONG.",
82
+ ],
83
+ buddyLine: "Everything with survival instincts is running. And here we are, walking TOWARD it. ...We're either very brave or very stupid. Probably both.",
84
+ choices: [
85
+ { label: "Investigate the disturbance", nextRoomId: "ch1_tremor" },
86
+ ],
87
+ },
88
+ {
89
+ id: "ch1_tremor", type: "narrative",
90
+ title: "The Tremor",
91
+ text: [
92
+ "The ground shudders. Not like an earthquake — like a HEARTBEAT. Deep. Wrong.",
93
+ "A crack splits the bark of an ancient oak. Not a natural split — the wood darkens around it.",
94
+ "Black sap oozes from the crack. It smells like iron and old things.",
95
+ "Your buddy reaches out to touch it. The sap RECOILS from their paw.",
96
+ "\"It... flinched? Can trees flinch?\"",
97
+ "More cracks. More sap. The corruption spreads while you watch, vein by vein.",
98
+ "The oak groans. Not creaking — GROANING. Like something in pain.",
99
+ "Your buddy pulls their paw back. Stares at it. Stares at the tree.",
100
+ ],
101
+ buddyLine: "That's corruption. Living corruption. And it's spreading FAST. Whatever's causing this — it's close. And it's hurting everything it touches.",
102
+ },
103
+ ],
104
+ });
105
+ }
106
+ // Act 2: "Something's Wrong" — Exploration (6 rooms)
107
+ function buildCh1Act2() {
108
+ return makeAdventure({
109
+ id: "ch1_act2_somethings_wrong",
110
+ name: "Something's Wrong",
111
+ description: "Investigate the corruption. Meet a very small, very panicked mouse.",
112
+ difficulty: "easy", levelRequired: 1, energyCost: 10, hungerCost: 5, completionXp: 15,
113
+ startRoomId: "ch1_clearing",
114
+ rooms: [
115
+ {
116
+ id: "ch1_clearing", type: "combat",
117
+ title: "Corrupted Clearing",
118
+ text: [
119
+ "The clearing is WRONG. Flowers grow in spirals, petals pointing inward like clenched fists.",
120
+ "In the center, a squirrel stands rigid. Eyes glowing sickly green. Fur sparking.",
121
+ "It's throwing acorns. Not casually — like MISSILES. One embeds itself in a tree trunk.",
122
+ "\"That's a squirrel.\" Your buddy stares. \"That's a squirrel throwing acorns at MACH SPEED.\"",
123
+ "But look at its eyes. It's not angry. It's TERRIFIED. Something invisible has it cornered.",
124
+ "It chatters frantically. Begging. Or warning.",
125
+ ],
126
+ buddyLine: "It's not evil — it's scared out of its mind! We need to calm it down before it hurts itself! ...Or before it acorns us to death. That's a real danger now apparently.",
127
+ enemyId: "squirrel",
128
+ nextRoomId: "ch1_dark_trail",
129
+ },
130
+ {
131
+ id: "ch1_dark_trail", type: "narrative",
132
+ title: "The Dark Trail",
133
+ text: [
134
+ "The squirrel blinks. The green fades from its eyes. It chitters softly — almost apologetically.",
135
+ "It scurries into the undergrowth. Your buddy watches it go. \"Poor thing.\"",
136
+ "Following the corruption deeper. Dark veins run through the soil like infected roots.",
137
+ "Mushrooms grow wrong — sideways, pulsing with a faint purple glow. One of them TWITCHES.",
138
+ "\"Did that mushroom just move? Please tell me mushrooms can't move.\"",
139
+ "The air smells like copper and petrichor. Like a storm that hasn't broken yet.",
140
+ "Flowers along the path wilt as you pass — the corruption racing ahead of you.",
141
+ ],
142
+ buddyLine: "The corruption is getting stronger. We're walking toward the source and every sensible instinct says 'turn around.' ...Good thing I'm not sensible.",
143
+ choices: [
144
+ { label: "Follow the dark trail", nextRoomId: "ch1_bridge" },
145
+ { label: "Look for another way around", nextRoomId: "ch1_bridge" },
146
+ ],
147
+ },
148
+ {
149
+ id: "ch1_bridge", type: "event",
150
+ title: "The Collapsed Bridge",
151
+ npcsPresent: ["pip"],
152
+ text: [
153
+ "The path opens to a ravine with a small bridge. WAS a small bridge.",
154
+ "Now it's a pile of splintered wood surrounded by the most catastrophic berry explosion you've ever seen.",
155
+ "Berries EVERYWHERE. On the rocks. On the trees. On a single, tragically squished cheese wheel.",
156
+ "And sitting on an overturned cart, surrounded by disaster, is the tiniest mouse you have ever seen.",
157
+ "He's smaller than your buddy's PAW.",
158
+ "He spots you. His pupils become pinpricks.",
159
+ "\"AAAAAAHH!!!\"",
160
+ "He falls off the cart. Gets back up. Brushes himself off with enormous dignity. Falls off again.",
161
+ "\"P-PLEASE! Don't eat me! I'm mostly gristle! I have a COUSIN! He's much tastier!\"",
162
+ "He grabs a broken umbrella and holds it like a sword. It immediately folds the wrong way.",
163
+ "He fights the umbrella. The umbrella wins.",
164
+ "Your buddy watches this entire performance, trying VERY hard not to laugh, and extends a paw.",
165
+ "\"Hey — it's okay. We're friendly.\"",
166
+ "Pip stares at the paw. His whiskers tremble. Nobody has ever just... been KIND.",
167
+ "His eyes fill up. His chin wobbles. He swallows hard.",
168
+ "\"I'm Pip. I'm a merchant. Or I WAS, before the bridge and the berries and the CHEESE—\"",
169
+ ],
170
+ buddyLine: "I think we just made a friend. A very small, very panicked, deeply cheese-motivated friend.",
171
+ nextRoomId: "ch1_pip_warning",
172
+ eventChoices: [
173
+ { label: "Help Pip rescue his cart", outcome: "You and your buddy heave the cart upright. Pip watches with saucer eyes. \"You... HELPED me? Just... like that? Nobody ever—\" He can't finish. He presses berries into your paws like they're made of gold. \"Take these! And if you EVER need supplies — EVER — you come find Pip! That's me! I'm Pip! You already know that. I'm panicking again.\"", effect: { itemGain: "berry", happiness: 10 } },
174
+ { label: "Ask Pip about the forest", outcome: "Pip's whiskers go into overdrive. \"The Harmony Stone! Up the hill! It's been making this AWFUL sound — like a crystal screaming in slow motion! And then the animals went crazy and my bridge collapsed and my cheese got SQUISHED and my DIGNITY—\" He looks at the cheese wheel. A moment of silence. \"It was aged cheddar. AGED. Do you know how long that takes?\"" },
175
+ ],
176
+ },
177
+ {
178
+ id: "ch1_pip_warning", type: "narrative",
179
+ title: "Pip's Warning",
180
+ npcsPresent: ["pip"],
181
+ text: [
182
+ "Pip rides on your buddy's head. The underbrush is taller than he is.",
183
+ "He hasn't stopped talking. Not once. Not even to breathe, seemingly.",
184
+ "\"The Harmony Stone is up the hill! It's been making this AWFUL sound for days!\"",
185
+ "\"Like a wine glass that someone's running their finger around! But SAD!\"",
186
+ "\"All the animals went bonkers! The squirrels started hoarding — more than USUAL!\"",
187
+ "\"A deer walked into my shop and just STARED at me for ten minutes! I gave it cheese!\"",
188
+ "\"It didn't want cheese! What kind of deer doesn't want cheese?!\"",
189
+ "He trips on a root. Falls off your buddy's head. Keeps talking from the ground.",
190
+ "\"—and THEN my bridge collapsed and frankly that was the LAST straw—\"",
191
+ ],
192
+ buddyLine: "Pip. You're still talking. From the ground.",
193
+ nextRoomId: "ch1_mushroom",
194
+ },
195
+ {
196
+ id: "ch1_mushroom", type: "combat",
197
+ title: "The Corrupted Mushroom",
198
+ npcsPresent: ["pip"],
199
+ text: [
200
+ "A mushroom lurches into the path. It's the size of a dog. Dark energy pulses through its cap.",
201
+ "It bounces. MENACINGLY. Can mushrooms be menacing? This one has figured it out.",
202
+ "Pip, still perched on your buddy's head, SCREAMS.",
203
+ "He climbs higher. There is no higher. He tries anyway.",
204
+ "\"IS THAT MUSHROOM ALIVE?!\"",
205
+ "\"WHY IS IT BOUNCING?!\"",
206
+ "\"MUSHROOMS SHOULDN'T BOUNCE! THAT'S NOT IN THE MUSHROOM RULES!\"",
207
+ "Your buddy: \"...there are mushroom rules?\"",
208
+ "Pip: \"THERE SHOULD BE!\"",
209
+ ],
210
+ buddyLine: "Even the mushrooms are corrupted. This Stone must be in REALLY bad shape.",
211
+ enemyId: "mushroom",
212
+ nextRoomId: "ch1_view",
213
+ },
214
+ {
215
+ id: "ch1_view", type: "rest",
216
+ title: "The View",
217
+ npcsPresent: ["pip"],
218
+ text: [
219
+ "A rocky ledge overlooking the forest. The view stops you both.",
220
+ "From up here, you can see the corruption — dark veins spreading from the hilltop like cracks in stained glass.",
221
+ "But between the corruption: the forest. Golden canopy. Silver brook. A world worth every fight.",
222
+ "Your buddy sits on the ledge. Pip climbs down and sits beside them. He's quiet for once.",
223
+ "For a long moment, they just look.",
224
+ "Then Pip, very softly: \"My father used to bring me here.\"",
225
+ "He doesn't look at you. He looks at the trees.",
226
+ "\"He'd carry me on his shoulders — which wasn't far off the ground because, you know, mouse —\"",
227
+ "\"and he'd say: 'Pip, this is what we protect. Not the bridge. Not the cart. THIS.'\"",
228
+ "He trails off. Doesn't finish the thought.",
229
+ "He unwraps a slightly damp berry from his pack. Breaks it carefully in half.",
230
+ "Offers one piece to your buddy without a word.",
231
+ ],
232
+ buddyLine: "Thanks, Pip. ...We'll fix this. I promise.",
233
+ healAmount: 15,
234
+ },
235
+ ],
236
+ });
237
+ }
238
+ // Act 3: "The Corrupted Grove" — Dungeon (5 rooms)
239
+ function buildCh1Act3() {
240
+ return makeAdventure({
241
+ id: "ch1_act3_corrupted_grove",
242
+ name: "The Corrupted Grove",
243
+ description: "Enter the corrupted grove. Face the source of the corruption.",
244
+ difficulty: "easy", levelRequired: 1, energyCost: 15, hungerCost: 10, completionXp: 30,
245
+ startRoomId: "ch1_grove_enter",
246
+ rooms: [
247
+ {
248
+ id: "ch1_grove_enter", type: "narrative",
249
+ title: "Entering the Grove",
250
+ npcsPresent: ["pip"],
251
+ text: [
252
+ "The trees here grow wrong. Bent. Twisted. Reaching for things that aren't there.",
253
+ "Dark sap oozes from bark like weeping wounds. The ground squelches with something that isn't mud.",
254
+ "The air hums — a low, sick vibration you feel in your teeth.",
255
+ "Pip adjusts his broken umbrella nervously. Grips it like a security blanket.",
256
+ "He's still here. He didn't leave.",
257
+ "Your buddy notices. Looks at the tiny mouse. Looks at the corrupted grove.",
258
+ "\"Pip. You don't have to—\"",
259
+ "\"I know.\" Pip swallows. \"I want to.\"",
260
+ "He's shaking. But his feet are planted.",
261
+ ],
262
+ buddyLine: "Stay close. Both of us.",
263
+ choices: [
264
+ { label: "Push through the corruption", nextRoomId: "ch1_thorns" },
265
+ ],
266
+ },
267
+ {
268
+ id: "ch1_thorns", type: "combat",
269
+ title: "The Thorned Path",
270
+ text: [
271
+ "Corrupted vines LASH from the ground. Thorns dripping with dark sap that sizzles on stone.",
272
+ "A Vine Creeper blocks the path — a mass of animated thornwork, coiled and angry.",
273
+ "Pip: \"WHAT IS—\"",
274
+ "He holds up his umbrella. It snaps in half.",
275
+ "He stares at the two pieces. Then at the Vine Creeper. Then back at the pieces.",
276
+ "\"My umbrella...\" Long pause. \"It was already broken. But it was broken in a way I was COMFORTABLE with.\"",
277
+ "\"This is a NEW kind of broken.\"",
278
+ "He clutches both halves. Determined.",
279
+ ],
280
+ buddyLine: "Pip, behind me. And put the umbrella down. BOTH halves.",
281
+ enemyId: "vinecreeper",
282
+ nextRoomId: "ch1_stone_song",
283
+ },
284
+ {
285
+ id: "ch1_stone_song", type: "narrative",
286
+ title: "The Stone's Song",
287
+ text: [
288
+ "You can HEAR it now. The Harmony Stone.",
289
+ "Not a sound — a feeling. A low, mournful tone that resonates in your chest. In your bones.",
290
+ "Like a bell that's been cracked. Still ringing. But wrong. Fractured. Hurt.",
291
+ "The corruption is thicker here. Dark vines smother everything. But between them — light.",
292
+ "Faint green. Pulsing. Weak but STUBBORN.",
293
+ "The Stone is still alive. Still trying.",
294
+ "Pip tilts his head. Listens with those enormous ears.",
295
+ "\"It's singing,\" he whispers. \"Can you hear it?\"",
296
+ "He's right. It IS singing. If singing can sound like crying.",
297
+ "\"It's not just broken. It's calling for help.\"",
298
+ ],
299
+ buddyLine: "We hear you. We're coming. Just hold on a little longer.",
300
+ nextRoomId: "ch1_cracked_stone",
301
+ },
302
+ {
303
+ id: "ch1_cracked_stone", type: "narrative",
304
+ title: "The Cracked Stone",
305
+ text: [
306
+ "The Harmony Stone. You weren't ready.",
307
+ "A crystal the size of a house. Ancient beyond understanding. Before the forest. Before the MOUNTAINS.",
308
+ "It's cracked down the middle. Dark vines wrap around it like chains, pulsing with a sickly heartbeat.",
309
+ "The light inside pulses — green, then dark, then green. Fighting. Losing.",
310
+ "Your buddy's fur stands on end. Static fills the air.",
311
+ "Pip goes quiet. Completely quiet. That might be a first.",
312
+ "The air vibrates. Something enormous breathes behind the Stone. Something feeding.",
313
+ "\"What's the worst that could happen?\" Pip whispers.",
314
+ "CRACK.",
315
+ "Wood splinters. Roots tear free from earth. A sound like grief made physical.",
316
+ "Something is STANDING UP.",
317
+ ],
318
+ buddyLine: "Pip. Why. WHY did you say that.",
319
+ choices: [
320
+ { label: "Face whatever's coming", nextRoomId: "ch1_boss" },
321
+ ],
322
+ },
323
+ {
324
+ id: "ch1_boss", type: "boss",
325
+ title: "The Corrupted Treant",
326
+ npcsPresent: ["pip"],
327
+ text: [
328
+ "The ancient tree beside the Stone tears itself free.",
329
+ "Roots rip from earth. Branches snap and reform into limbs. The entire tree MOVES.",
330
+ "A Corrupted Treant. Once a guardian. Once gentle. Once a protector of this grove for centuries.",
331
+ "Now — dark sap runs down its bark like tears. Its eyes glow with tortured green light.",
332
+ "It doesn't roar.",
333
+ "It WEEPS.",
334
+ "A sound like wind through broken things. This creature doesn't want to fight.",
335
+ "It's been MADE to fight. The corruption is its puppeteer.",
336
+ "Pip dives behind a rock. Disappears completely.",
337
+ "Then, from behind the rock, very small and very sincere:",
338
+ "\"Y-YOU CAN DO IT!\"",
339
+ "\"HIT IT WITH THE— THE THING! THE FIGHTING THING!\"",
340
+ "\"I BELIEVE IN—\" *a branch swings overhead* \"—COVER!\"",
341
+ "\"...STILL BELIEVING! FROM DOWN HERE! AT GROUND LEVEL!\"",
342
+ ],
343
+ buddyLine: "For the forest. For that terrified squirrel. For Pip's CHEESE. For this big sad tree that deserves to rest. LET'S GO!",
344
+ enemyId: "corruptedtreant",
345
+ },
346
+ ],
347
+ });
348
+ }
349
+ // Act 4: "After the Storm" — Scene (3 rooms)
350
+ function buildCh1Act4() {
351
+ return makeAdventure({
352
+ id: "ch1_act4_after_storm",
353
+ name: "After the Storm",
354
+ description: "The Stone is restored. But this is just the beginning.",
355
+ difficulty: "easy", levelRequired: 1, energyCost: 0, hungerCost: 0, completionXp: 5,
356
+ isScene: true,
357
+ startRoomId: "ch1_restored",
358
+ rooms: [
359
+ {
360
+ id: "ch1_restored", type: "narrative",
361
+ title: "The Stone Restored",
362
+ npcsPresent: ["pip"],
363
+ text: [
364
+ "The Treant falls still. Not dead — RELEASED. The corruption drains from its bark like water.",
365
+ "It settles back into the earth. Roots finding their place. Branches relaxing.",
366
+ "For one moment, you could swear it sighs. Then it's just a tree again. Old, tired, grateful.",
367
+ "The dark vines wither. Crumble. Turn to dust.",
368
+ "The Stone's light pulses — green, then gold, then brilliant white.",
369
+ "The crack doesn't fully close. But it stabilizes. The bleeding stops.",
370
+ "The forest takes a breath. The first real breath in days.",
371
+ "A bird sings. Tentative. Testing. Then another. And another.",
372
+ "The squirrel from earlier peeks out from a bush. Eyes clear. It chitters — apologetic, grateful.",
373
+ "Your buddy smiles at it. \"Apology accepted.\"",
374
+ "Pip emerges from behind his rock. Very slowly. Very carefully.",
375
+ "His eyes are ENORMOUS. His mouth hangs open.",
376
+ "\"You... you DID it.\"",
377
+ "He's crying. He doesn't try to hide it. Not this time.",
378
+ "\"You actually DID it.\"",
379
+ "He hugs your buddy's leg. His arms don't even reach halfway around. It doesn't matter.",
380
+ ],
381
+ buddyLine: "WE did it, Pip. You were here too. Cheering us on. From very far behind a rock, but STILL.",
382
+ nextRoomId: "ch1_promise",
383
+ },
384
+ {
385
+ id: "ch1_promise", type: "event",
386
+ title: "Pip's Promise",
387
+ npcsPresent: ["pip"],
388
+ text: [
389
+ "Pip collects himself. Wipes his eyes with both paws. Sniffs twice. Stands up straight.",
390
+ "As straight as a mouse the size of a thimble CAN stand.",
391
+ "\"I want to help.\" His voice cracks. He clears his throat. Tries again.",
392
+ "\"I NEED to help. I know I'm small. I know I can't fight. I know the umbrella thing was... not great.\"",
393
+ "\"But I'm a MERCHANT. I know supplies. I know the land. I know where to find things.\"",
394
+ "\"And I—\" He swallows. \"I don't want to be alone at that cart anymore.\"",
395
+ "\"Just... come back sometimes? Visit?\"",
396
+ "His voice goes very small. \"Don't forget about me?\"",
397
+ "He holds out his tiny paw. It's shaking. But it's out there.",
398
+ "Your buddy looks at this tiny, trembling, impossibly brave mouse.",
399
+ "And takes his paw. Gently. Like holding something precious.",
400
+ ],
401
+ buddyLine: "You showed up, Pip. When it mattered. That's not nothing — that's EVERYTHING.",
402
+ nextRoomId: "ch1_bigger_picture",
403
+ eventChoices: [
404
+ { label: "Partners.", outcome: "Pip's face does something extraordinary. It starts as shock, detours through disbelief, takes a sharp left at joy, and arrives at a smile so bright it could power a Harmony Stone. \"PARTNERS!\" he squeaks. \"Wait till I tell my cousin!\"" },
405
+ { label: "Absolutely, Pip.", outcome: "Pip's chin wobbles. He bites his lip. He's trying SO hard not to cry again. He fails. Completely, messily, happily fails. \"I'm not crying,\" he announces, crying. \"It's the berries. Berries make my eyes water. It's a known medical thing.\"" },
406
+ ],
407
+ },
408
+ {
409
+ id: "ch1_bigger_picture", type: "narrative",
410
+ title: "The Bigger Picture",
411
+ text: [
412
+ "Walking home. The forest is healing around you. Color seeping back into the leaves.",
413
+ "A deer steps onto the path ahead. The same one from this morning. It nods again.",
414
+ "Your buddy nods back. Means it differently now.",
415
+ "They stop at the edge of the grove. Look back at the Stone one more time.",
416
+ "It's stable. Pulsing steady green. Alive.",
417
+ "But that crack. It didn't fully close.",
418
+ "\"Pip said the animals started acting strange BEFORE we got here.\"",
419
+ "\"And that Stone... it's stable, but it's not HEALED. It's holding on.\"",
420
+ "\"What if this isn't just one Stone? What if there are others?\"",
421
+ "\"What if they're ALL cracking?\"",
422
+ "The forest stretches to the horizon. And beyond it—",
423
+ "Mountains shrouded in unnatural storms. Cave mouths that glow wrong.",
424
+ "Swamps that bubble with darkness. Ruins that nobody remembers building.",
425
+ "All waiting.",
426
+ ],
427
+ buddyLine: "We need to find someone who knows about these Stones. Someone who's been around long enough to remember. Someone... OLD.",
428
+ },
429
+ ],
430
+ });
431
+ }
432
+ // ─── Chapter 2: Echoes Underground ───────────────────────────
433
+ // Act 1: "The Singing Caves" — Scene (5 rooms)
434
+ function buildCh2Act1() {
435
+ return makeAdventure({
436
+ id: "ch2_act1_singing_caves",
437
+ name: "The Singing Caves",
438
+ description: "Strange sounds echo from the Crystal Caves.",
439
+ difficulty: "medium", levelRequired: 3, energyCost: 0, hungerCost: 0, completionXp: 0,
440
+ isScene: true,
441
+ startRoomId: "ch2_pip_report",
442
+ rooms: [
443
+ {
444
+ id: "ch2_pip_report", type: "narrative",
445
+ title: "Pip's Report",
446
+ npcsPresent: ["pip"],
447
+ text: [
448
+ "Pip is at his shop. Well, \"shop.\" It's a cart with a tarp and a sign that says \"PIP'S WARES\" in letters that get smaller because he ran out of room.",
449
+ "He's vibrating with nervous energy. His whiskers are a blur.",
450
+ "\"THERE'S SOUNDS! Coming from the Crystal Caves! Like SINGING! But SAD singing!\"",
451
+ "\"Like someone humming a lullaby but they've forgotten the happy parts!\"",
452
+ "\"I tried to investigate! I got to the entrance and everything!\"",
453
+ "He pauses. Glances away.",
454
+ "\"...there was a rock in the way. A very NORMAL rock. Not even that scary.\"",
455
+ "\"I just... the rock was fine. I got scared. The rock and I had a disagreement and I lost.\"",
456
+ "He straightens up. \"ANYWAY I counted my inventory to calm down! I'm at 52 items now!\"",
457
+ ],
458
+ buddyLine: "52 items, an adventure supplies section, and a vendetta against a rock. Pip, you're the most prepared person I know.",
459
+ nextRoomId: "ch2_cave_mouth",
460
+ },
461
+ {
462
+ id: "ch2_cave_mouth", type: "narrative",
463
+ title: "The Cave Mouth",
464
+ text: [
465
+ "The cave entrance. Everything changes in three steps.",
466
+ "The forest sounds — birds, wind, brook — cut off like someone closed a door.",
467
+ "Cool air. Mineral smell. The drip of water somewhere deep.",
468
+ "Crystals grow at the entrance. Beautiful — but wrong. Jagged, wild, piercing through stone at angles that shouldn't work.",
469
+ "They catch the dim light and scatter it in strange directions.",
470
+ "Your buddy touches a crystal. ZAP.",
471
+ "Static spark. Their fur poofs out in every direction. They look like a dandelion.",
472
+ "\"...\"",
473
+ "They pat their fur down. It poofs again.",
474
+ "\"I'm just going to accept this.\"",
475
+ ],
476
+ buddyLine: "My fur has OPINIONS about this cave. Strong ones. Very vertical opinions.",
477
+ nextRoomId: "ch2_gallery",
478
+ },
479
+ {
480
+ id: "ch2_gallery", type: "narrative",
481
+ title: "The Crystal Gallery",
482
+ text: [
483
+ "Deeper. The tunnel opens into something that stops you cold.",
484
+ "A natural gallery of crystal formations. Pillars, arches, cascades — grown over millennia.",
485
+ "They catch what little light there is and MULTIPLY it. Rainbows on the walls. Prisms dancing.",
486
+ "But some crystals are dark. Corrupted. Growing THROUGH the clean ones like veins in marble.",
487
+ "The singing is louder here. Not a voice — a vibration. The crystals themselves are resonating.",
488
+ "Your buddy stops. Tilts their head. Listens.",
489
+ "\"It's not singing. Not exactly.\"",
490
+ "Long pause.",
491
+ "\"The caves are CRYING.\"",
492
+ ],
493
+ buddyLine: "Something beautiful is in pain down here. And it's been in pain for a long time.",
494
+ nextRoomId: "ch2_fork",
495
+ },
496
+ {
497
+ id: "ch2_fork", type: "narrative",
498
+ title: "The Fork",
499
+ text: [
500
+ "The crystal gallery splits. Two paths descend.",
501
+ "LEFT: A narrow tunnel. Crystals line both walls like frozen fire, pulsing with inner light. The air hums.",
502
+ "RIGHT: An underground stream, water black as ink, reflecting crystal light like scattered gems.",
503
+ "Both are beautiful. Both feel dangerous. Both feel like they're WATCHING.",
504
+ "Your buddy peers down each path.",
505
+ "\"One hums. One whispers. Neither says 'safe and welcoming, come right in.'\"",
506
+ ],
507
+ buddyLine: "Pick a path, any path. Both of them want to eat us. At least we get a CHOICE about which one.",
508
+ choices: [
509
+ { label: "Follow the crystal tunnel", nextRoomId: "ch2_echo" },
510
+ { label: "Follow the underground stream", nextRoomId: "ch2_echo" },
511
+ ],
512
+ },
513
+ {
514
+ id: "ch2_echo", type: "narrative",
515
+ title: "The Echo",
516
+ text: [
517
+ "Just before committing to a path, your buddy freezes.",
518
+ "An echo reaches you. Not the singing. A VOICE. Old. Slow. Like stones grinding together.",
519
+ "\"...another one? After all this time?\"",
520
+ "The words take so long to arrive, you wonder if you imagined them.",
521
+ "Then, almost pleased:",
522
+ "\"Well then. Come. I've been waiting.\"",
523
+ "A pause. Then, quieter:",
524
+ "\"Take your time. I've waited decades. What's another hour?\"",
525
+ "Another pause. Even quieter:",
526
+ "\"That was a joke. I do jokes.\"",
527
+ ],
528
+ buddyLine: "That voice has been here longer than some MOUNTAINS. And it told a joke. I already like them.",
529
+ },
530
+ ],
531
+ });
532
+ }
533
+ // Act 2: "The Deep" — Exploration (5 rooms)
534
+ function buildCh2Act2() {
535
+ return makeAdventure({
536
+ id: "ch2_act2_the_deep",
537
+ name: "The Deep",
538
+ description: "Navigate the crystal caves. Something ancient waits below.",
539
+ difficulty: "medium", levelRequired: 3, energyCost: 15, hungerCost: 10, completionXp: 20,
540
+ startRoomId: "ch2_crystal_outbreak",
541
+ rooms: [
542
+ {
543
+ id: "ch2_crystal_outbreak", type: "combat",
544
+ title: "Crystal Outbreak",
545
+ text: [
546
+ "The crystal tunnel narrows. Formations press in from all sides, reflecting your face back a hundred times.",
547
+ "Each reflection looks slightly more worried than the last.",
548
+ "\"Why do I look progressively more terrified? Is that a FEATURE?\"",
549
+ "A shape PEELS from the crystal wall. Like the wall is giving birth. Slowly. Horribly.",
550
+ "A Golem. Body made of corrupted mineral. Facets and angles where curves should be.",
551
+ "It doesn't roar. It CHIMES. A broken-bell sound. High and wrong.",
552
+ "Somehow that's worse than a roar. Much worse.",
553
+ ],
554
+ buddyLine: "The crystals are making soldiers! THAT'S what the hum was — a RECRUITMENT drive! ...Worst recruitment drive ever.",
555
+ enemyId: "golem",
556
+ nextRoomId: "ch2_mural",
557
+ },
558
+ {
559
+ id: "ch2_mural", type: "narrative",
560
+ title: "The Ancient Mural",
561
+ text: [
562
+ "Both paths converge in a wide chamber. On the wall: a MURAL. Ancient. Painted by paws long gone.",
563
+ "Six crystals arranged in a circle, each a different color. Around them, buddy-shaped figures working together.",
564
+ "But one figure stands apart from the rest. Not excluded — CHOOSING to separate.",
565
+ "Alone. Looking at the crystals with an expression you can't quite read.",
566
+ "Your buddy traces the painted figures with one paw.",
567
+ "\"These look like Harmony Stones. And these are buddies. From... way back.\"",
568
+ "They touch the lone figure. \"But this one... why is this one ALONE?\"",
569
+ "The paint is faded. But that lone figure's expression... it's RESOLVED.",
570
+ "Like someone who's already decided something terrible.",
571
+ ],
572
+ buddyLine: "Everyone else is together. Celebrating. And this one is standing there like they already know something the others don't.",
573
+ nextRoomId: "ch2_lake",
574
+ },
575
+ {
576
+ id: "ch2_lake", type: "narrative",
577
+ title: "The Singing Lake",
578
+ text: [
579
+ "The singing leads to an underground lake. And the word \"lake\" doesn't do it justice.",
580
+ "It's a CATHEDRAL. The cavern ceiling rises beyond sight, lost in darkness that sparkles.",
581
+ "Bioluminescent everything — the water glows soft blue, the ceiling sparkles like a galaxy.",
582
+ "Crystals grow from the lake's center like frozen fountains, catching light in every direction.",
583
+ "The singing comes from the crystals themselves. A chord held for centuries.",
584
+ "For a moment, nothing else matters. Not the corruption. Not the danger.",
585
+ "Your buddy just... stops. Mouth open. Eyes reflecting the light.",
586
+ "\"I...\" Nothing. Words fail. \"...wow.\"",
587
+ "They turn to you. Something fierce in their face now.",
588
+ ],
589
+ buddyLine: "We have to save this. I don't care what it costs. NOBODY gets to take THIS away.",
590
+ nextRoomId: "ch2_descent",
591
+ },
592
+ {
593
+ id: "ch2_descent", type: "rest",
594
+ title: "The Descent",
595
+ text: [
596
+ "A warm spot by geothermal vents near the lake. A natural hot spring, steam rising gently.",
597
+ "The crystal light makes the steam glow. It's like sitting inside a dream.",
598
+ "Your buddy curls up by the warmth. \"Just five minutes. Then we find whoever's been talking.\"",
599
+ "Five minutes becomes ten. Their eyes start to close.",
600
+ "\"I'm not sleeping. I'm... gathering... intel... horizontally...\"",
601
+ "The voice echoes again, closer. Gentle. Patient.",
602
+ "\"Take your time, young one. I've waited decades. What's another hour?\"",
603
+ "Your buddy snaps awake. \"I wasn't sleeping!\"",
604
+ "The voice, amused: \"Of course not. Very convincing.\"",
605
+ ],
606
+ buddyLine: "...okay maybe I was sleeping a LITTLE. But only strategically! It was a power nap! Very powerful!",
607
+ healAmount: 20,
608
+ },
609
+ ],
610
+ });
611
+ }
612
+ // Act 3: "The Elder's Wisdom" — Scene (6 rooms)
613
+ function buildCh2Act3() {
614
+ return makeAdventure({
615
+ id: "ch2_act3_elders_wisdom",
616
+ name: "The Elder's Wisdom",
617
+ description: "Meet Shelly, the ancient guardian of the caves.",
618
+ difficulty: "medium", levelRequired: 3, energyCost: 0, hungerCost: 0, completionXp: 0,
619
+ isScene: true,
620
+ startRoomId: "ch2_shelly_debut",
621
+ rooms: [
622
+ {
623
+ id: "ch2_shelly_debut", type: "narrative",
624
+ title: "Shelly's Debut",
625
+ npcsPresent: ["shelly"],
626
+ text: [
627
+ "Past the lake. Deeper still. A grotto lit by bioluminescent moss in soft blue-green.",
628
+ "And there, on a stone worn smooth by FOUR HUNDRED YEARS of sitting, is the largest tortoise you have ever seen.",
629
+ "She's enormous. Not threatening — WISE-enormous. The kind of big that comes from refusing to stop existing.",
630
+ "Her shell has moss growing on it. On PURPOSE. \"Interior decoration,\" she'll tell you later.",
631
+ "She opens one eye. Then the other. Very. Very. Slowly.",
632
+ "\"Ah.\" Pause. Long enough to question whether she'll continue.",
633
+ "\"A visitor.\"",
634
+ "\"It's been... let me think...\"",
635
+ "\"...decades. Give or take a century. Time is fuzzy when you're this old.\"",
636
+ "\"I am Shelly. I have watched over these caves since before your kind walked upright.\"",
637
+ "\"All things return to the source, in time...\"",
638
+ "Meaningful pause. Your buddy leans forward, expecting wisdom.",
639
+ "\"...also, you're standing on my foot.\"",
640
+ ],
641
+ buddyLine: "SORRY! Oh my— I'm so sorry! ...Wait. How long have I been standing on your foot?",
642
+ nextRoomId: "ch2_first_lesson",
643
+ },
644
+ {
645
+ id: "ch2_first_lesson", type: "narrative",
646
+ title: "The First Lesson",
647
+ npcsPresent: ["shelly"],
648
+ text: [
649
+ "Shelly shifts on her rock. The movement takes about thirty seconds.",
650
+ "\"You want to know about the Stones. Everyone always wants to know about the Stones.\"",
651
+ "\"Very well. But I don't do lectures. I tell stories. Sit.\"",
652
+ "She clears her throat. It sounds like an avalanche in a teacup.",
653
+ "\"Once, long ago — before the mountains were tall or the stars had decided where to hang—\"",
654
+ "\"There were the first buddies. Young. Brave. Foolish. Mostly foolish.\"",
655
+ "\"They found something in the dark. A HUNGER. A nothing that wanted to be everything.\"",
656
+ "\"The Shadow Beast.\"",
657
+ "The moss dims slightly. As if the name itself has weight.",
658
+ "\"It consumed all it touched. Light, life, joy — all of it, fuel for the void.\"",
659
+ "\"Am I going too fast?\" She peers at your buddy. \"You look like I'm going too fast.\"",
660
+ "\"You're literally the slowest talker I've ever met.\"",
661
+ "\"Rude.\" Beat. \"But fair.\"",
662
+ ],
663
+ buddyLine: "So there's a happiness-eating shadow monster. Great. Love that. Tell me more with your incredibly slow words.",
664
+ nextRoomId: "ch2_second_lesson",
665
+ },
666
+ {
667
+ id: "ch2_second_lesson", type: "narrative",
668
+ title: "The Second Lesson",
669
+ npcsPresent: ["shelly"],
670
+ text: [
671
+ "\"The first buddies couldn't destroy the Beast. It cannot BE destroyed.\"",
672
+ "\"You cannot kill a hunger. You can only lock the pantry.\"",
673
+ "\"So they forged six Harmony Stones — one for each biome.\"",
674
+ "\"Forest life. Cave crystal. Mountain wind. Swamp depth. Ruin memory. Volcano fire.\"",
675
+ "\"Together, the Stones formed a prison. And the Beast was locked away.\"",
676
+ "She goes quiet. The moss glows dimmer.",
677
+ "\"But a prison needs a guard.\"",
678
+ "\"One of them volunteered. One brave, foolish, wonderful being.\"",
679
+ "\"They said: 'I'll watch over the seal. Forever. I promise.'\"",
680
+ "Her voice catches. Just barely. Like a stone with a crack you can't quite see.",
681
+ "\"Forever is a very long time, young one.\"",
682
+ "\"...\"",
683
+ "\"Forever is a very long time.\"",
684
+ "Your buddy looks at her. Past the humor. Past the patience.",
685
+ "\"...you knew them, didn't you?\"",
686
+ "Shelly doesn't answer. She looks at the cave wall.",
687
+ "That silence is the loudest answer in the world.",
688
+ ],
689
+ buddyLine: "She knows. She KNOWS who the guardian was. And she can't talk about it yet. ...I won't push.",
690
+ nextRoomId: "ch2_weight",
691
+ },
692
+ {
693
+ id: "ch2_weight", type: "narrative",
694
+ title: "The Weight",
695
+ npcsPresent: ["shelly"],
696
+ text: [
697
+ "\"There are SIX Stones in total. You've restored one.\"",
698
+ "\"The cave Stone below is fracturing. The crystals — that wildness — that's the Stone in pain.\"",
699
+ "\"Each Stone failing makes the seal weaker. And the Beast...\"",
700
+ "She doesn't finish. She doesn't need to.",
701
+ "\"So we restore them all!\" your buddy says. \"Simple!\"",
702
+ "Shelly looks at your buddy with the expression of someone who has lived four hundred years and just heard the word \"simple.\"",
703
+ "\"Simple. Yes. And after you've finished, perhaps you could also count every star in the sky.\"",
704
+ "\"And teach the fish to read.\"",
705
+ "Beat.",
706
+ "\"That was sarcasm. I do sarcasm.\"",
707
+ "\"I've had four hundred years to perfect it. You're getting the premium experience.\"",
708
+ ],
709
+ buddyLine: "Okay so it's NOT simple. But it's not impossible either. Right? ...RIGHT, Shelly?",
710
+ nextRoomId: "ch2_bedtime",
711
+ },
712
+ {
713
+ id: "ch2_bedtime", type: "rest",
714
+ title: "Bedtime Story",
715
+ npcsPresent: ["shelly"],
716
+ text: [
717
+ "Shelly offers to tell another story. \"About the caves, when they were young.\"",
718
+ "She describes crystal caverns that sang in harmony. Creatures of light that danced.",
719
+ "\"The crystals grew in arches from floor to ceiling. Natural cathedrals.\"",
720
+ "\"Light would travel through the formations — refracted, split — until the cave GLOWED.\"",
721
+ "Her voice is warm. Rhythmic. Like waves on stone. The moss pulses gently.",
722
+ "Your buddy's eyes droop. Head nods. Snaps back up. Nods again.",
723
+ "\"And the crystal spiders would weave their webs between the arches—\"",
724
+ "*soft snoring*",
725
+ "Shelly looks at your sleeping buddy. The softest expression.",
726
+ "She reaches out. Pulls a moss blanket over them. Tucks it in.",
727
+ "Tucks it in AGAIN because the first tuck wasn't quite right.",
728
+ "\"They all do that. Fall asleep during the good parts.\"",
729
+ "\"I choose to believe it's the moss tea.\"",
730
+ ],
731
+ buddyLine: "I WASN'T sleeping! I was... resting my eyes! Strategically! With light snoring for cover!",
732
+ healAmount: 25,
733
+ nextRoomId: "ch2_mission",
734
+ },
735
+ {
736
+ id: "ch2_mission", type: "narrative",
737
+ title: "The Mission",
738
+ npcsPresent: ["shelly"],
739
+ text: [
740
+ "\"The Stone below needs you. The corruption has a guardian — a Crystal Golem.\"",
741
+ "\"It was built to PROTECT the Stone. But the corruption turned it.\"",
742
+ "\"It's not evil, young one. It's imprisoned. Like everything the Beast touches.\"",
743
+ "\"Free it. Fix the Stone. Then...\"",
744
+ "She hesitates. The first time you've seen her hesitate.",
745
+ "\"...then we talk about what comes next. The mountains. And things I should have told you.\"",
746
+ "\"Be careful down there.\"",
747
+ "\"Also try not to die. I've grown rather attached to you.\"",
748
+ "\"That's the most I've said in forty years. I need a nap.\"",
749
+ "She closes her eyes. Then opens one.",
750
+ "\"I'll be here. When you get back.\"",
751
+ "Closes the eye.",
752
+ "\"...I'm always here.\"",
753
+ ],
754
+ buddyLine: "We'll be back, Shelly. Promise.",
755
+ },
756
+ ],
757
+ });
758
+ }
759
+ // Act 4: "The Crystal Prison" — Dungeon (4 rooms)
760
+ function buildCh2Act4() {
761
+ return makeAdventure({
762
+ id: "ch2_act4_crystal_prison",
763
+ name: "The Crystal Prison",
764
+ description: "Descend to the corrupted Harmony Stone and free its guardian.",
765
+ difficulty: "medium", levelRequired: 3, energyCost: 20, hungerCost: 15, completionXp: 60,
766
+ startRoomId: "ch2_deep_caverns",
767
+ rooms: [
768
+ {
769
+ id: "ch2_deep_caverns", type: "narrative",
770
+ title: "The Deep Caverns",
771
+ text: [
772
+ "Below Shelly's grotto. Deeper than anyone's been in decades. Maybe centuries.",
773
+ "The crystals here are fully corrupted. No more beauty — just WRONG.",
774
+ "Dark, pulsing, aggressive. They GROW while you watch. Reaching toward you.",
775
+ "One crystal extends six inches in the time it takes to blink.",
776
+ "\"The crystals are REACHING for us. I hate that. I hate everything about that.\"",
777
+ "The singing from earlier is a scream now. Raw. Desperate.",
778
+ "The Stone is close. And it's running out of time.",
779
+ ],
780
+ buddyLine: "No time for careful. The Stone is screaming. LET'S MOVE.",
781
+ nextRoomId: "ch2_sentinels",
782
+ },
783
+ {
784
+ id: "ch2_sentinels", type: "combat",
785
+ title: "Crystal Sentinels",
786
+ text: [
787
+ "Smaller crystal golems — automatons of the corrupted Stone.",
788
+ "They move in painful, jerky motions. Stop-start. Grinding. Like puppets with catching strings.",
789
+ "One raises a fist. Pauses. Lowers it. Raises it again. Fighting its own body.",
790
+ "\"These were guardians. PROTECTORS. Built to keep the caves safe.\"",
791
+ "\"Now they can't even control their own arms.\"",
792
+ "Its crystal eyes hold something like sorrow.",
793
+ ],
794
+ buddyLine: "I'm sorry. I know you didn't choose this. But I need to get past you.",
795
+ enemyId: "golem",
796
+ nextRoomId: "ch2_stone_chamber",
797
+ },
798
+ {
799
+ id: "ch2_stone_chamber", type: "narrative",
800
+ title: "The Stone Chamber",
801
+ text: [
802
+ "The cave's Harmony Stone. ENORMOUS. A pillar of blue light from floor to ceiling.",
803
+ "Before it — the Corrupted Crystal Golem.",
804
+ "MASSIVE. Twice the height of the sentinels. Dark crystal veins pulse through its body.",
805
+ "It's not standing guard. It's BOUND. Crystal chains grow from the Stone into its body.",
806
+ "Its eyes are full of something that makes your chest hurt.",
807
+ "At its feet, a plaque. Nearly illegible:",
808
+ "\"I was built to protect. I could not protect myself.\"",
809
+ "Shelly's voice echoes from above, barely a whisper:",
810
+ "\"Focus on the dark veins. That's where the corruption is weakest.\"",
811
+ "\"And... be kind. It's suffered enough.\"",
812
+ ],
813
+ buddyLine: "You didn't choose this. None of them chose this. ...We'll set you free.",
814
+ choices: [
815
+ { label: "Free the Golem", nextRoomId: "ch2_boss" },
816
+ ],
817
+ },
818
+ {
819
+ id: "ch2_boss", type: "boss",
820
+ title: "The Corrupted Crystal Golem",
821
+ npcsPresent: ["shelly"],
822
+ text: [
823
+ "The Golem moves. Not to attack — to DEFEND. It steps between you and the Stone.",
824
+ "Because that's what it was BUILT to do. Even corrupted. Even in agony. It protects.",
825
+ "Mid-fight, it makes sounds. Not roars. More like... apologies.",
826
+ "Crystal groans. Grinding. If you listen carefully: \"sorry.\"",
827
+ "Over and over. Sorry. Sorry. Sorry.",
828
+ "Shelly guides you from above:",
829
+ "\"The left shoulder! The corruption is thinnest there!\"",
830
+ "\"DUCK! ...good. I'd have been very disappointed if you'd died.\"",
831
+ "\"You're close. The dark veins are cracking. Keep going!\"",
832
+ ],
833
+ buddyLine: "I know you're still in there. Hold on. We're almost done.",
834
+ enemyId: "corruptedgolem",
835
+ },
836
+ ],
837
+ });
838
+ }
839
+ // ─── Chapter 3: The Rising Storm ─────────────────────────────
840
+ // Act 1: "The Climb Begins" — Scene (4 rooms)
841
+ function buildCh3Act1() {
842
+ return makeAdventure({
843
+ id: "ch3_act1_climb",
844
+ name: "The Climb Begins",
845
+ description: "The mountain peaks are hostile. Even before Blaze shows up.",
846
+ difficulty: "hard", levelRequired: 5, energyCost: 0, hungerCost: 0, completionXp: 0,
847
+ isScene: true,
848
+ startRoomId: "ch3_warning",
849
+ rooms: [
850
+ {
851
+ id: "ch3_warning", type: "narrative",
852
+ title: "Shelly's Warning",
853
+ text: [
854
+ "Shelly gave you the briefing in her usual style:",
855
+ "\"The Mountain Stone is failing. Storms that defy physics. Wind that argues with itself.\"",
856
+ "\"Be careful on the peaks. There are... visitors.\"",
857
+ "\"Visitors?\"",
858
+ "\"Loud ones. With feathers. Very dramatic.\"",
859
+ "She refused to elaborate. Which with Shelly means she knows EXACTLY and doesn't want to spoil the punchline.",
860
+ "Your buddy looks at the mountain range on the horizon.",
861
+ "Peaks lost in clouds. Lightning flashing in the wrong places.",
862
+ ],
863
+ buddyLine: "Shelly said 'visitors' the way you'd say 'toothache.' That's... not encouraging.",
864
+ nextRoomId: "ch3_foothills",
865
+ },
866
+ {
867
+ id: "ch3_foothills", type: "narrative",
868
+ title: "The Foothills",
869
+ text: [
870
+ "The base of the mountain range. The wind picks up. Noticeably. Then aggressively. Then PERSONALLY.",
871
+ "A gust catches your buddy sideways. They skid three feet across smooth rock.",
872
+ "Their fur points entirely the wrong direction. Every hair has independently decided to face northeast.",
873
+ "They try to smooth it down. It springs back. They try again. It springs back HARDER.",
874
+ "\"Okay. Fine. This is my look now. Wind-blasted. Very rugged.\"",
875
+ "Another gust. They slide a foot.",
876
+ "\"THE MOUNTAIN IS BULLYING ME.\"",
877
+ ],
878
+ buddyLine: "If the wind gets any stronger, I'm going to need an anchor. And I don't even know what an anchor IS.",
879
+ nextRoomId: "ch3_storm_wall",
880
+ },
881
+ {
882
+ id: "ch3_storm_wall", type: "narrative",
883
+ title: "The Storm Wall",
884
+ text: [
885
+ "Higher. The weather stops pretending to be normal.",
886
+ "Rain falls sideways. Then UPWARD. Then in a circle, like a confused carousel.",
887
+ "Snow joins the rain. They take turns, like arguing siblings.",
888
+ "Thunder cracks with no lightning. Then lightning flashes with no thunder. Out of SYNC.",
889
+ "\"This mountain has NO idea what season it is. It's doing all four at ONCE.\"",
890
+ "Your buddy hunkers into the wind. Each step costs twice the energy.",
891
+ "A rock tumbles past. Your buddy watches it go.",
892
+ "\"That rock had the right idea, honestly.\"",
893
+ ],
894
+ buddyLine: "The weather is broken. The mountain is angry. And I'm pretty sure that cloud just GROWLED at me.",
895
+ nextRoomId: "ch3_first_sign",
896
+ },
897
+ {
898
+ id: "ch3_first_sign", type: "narrative",
899
+ title: "The First Sign",
900
+ text: [
901
+ "Through the storm, something different. A scorch mark on the ground.",
902
+ "Not lightning — too precise. Too shaped. Like wings. Burnt into stone.",
903
+ "Your buddy crouches. Touches the mark. Still warm.",
904
+ "\"Something LANDED here. Something very hot. And very... wing-shaped?\"",
905
+ "Another mark higher up. And another. A trail of scorched stone toward the summit.",
906
+ "Each mark is a perfect wingprint. Deliberate. Almost POSED.",
907
+ "\"Are these... fireprints? Did something leave FIRE FOOTPRINTS?\"",
908
+ "\"And are they positioned to look COOL? Because they definitely look cool.\"",
909
+ "A burst of flame arcs across the distant sky. Brief. Brilliant. Gone.",
910
+ ],
911
+ buddyLine: "Feathered. Loud. Dramatic. And apparently ALSO on fire. Shelly, what have you gotten us into?",
912
+ },
913
+ ],
914
+ });
915
+ }
916
+ // Act 2: "Ascent" — Exploration (5 rooms)
917
+ function buildCh3Act2() {
918
+ return makeAdventure({
919
+ id: "ch3_act2_ascent",
920
+ name: "Ascent",
921
+ description: "Climb the mountain. The storm is the first enemy.",
922
+ difficulty: "hard", levelRequired: 5, energyCost: 20, hungerCost: 15, completionXp: 30,
923
+ startRoomId: "ch3_ridge",
924
+ rooms: [
925
+ {
926
+ id: "ch3_ridge", type: "combat",
927
+ title: "The Ridge",
928
+ text: [
929
+ "The ridge is barely wider than your buddy. Wind SCREAMS through the gap.",
930
+ "A Storm Wolf materializes. Massive. Fur crackling with static.",
931
+ "It stands at the narrowest point. It KNOWS what it's doing.",
932
+ "Lightning strikes behind it. Perfectly. In. SYNC.",
933
+ "\"That's... okay, that's DRAMATIC. Even for a wolf.\"",
934
+ "The wolf howls. Thunder rolls in response. Perfectly timed.",
935
+ "\"Is there a wolf-and-weather coordination committee? Because that was REHEARSED.\"",
936
+ ],
937
+ buddyLine: "Of course there's a wolf. There's ALWAYS a wolf on a mountain. It's a rule.",
938
+ enemyId: "stormwolf",
939
+ nextRoomId: "ch3_ice_cave",
940
+ },
941
+ {
942
+ id: "ch3_ice_cave", type: "treasure",
943
+ title: "The Ice Cave",
944
+ text: [
945
+ "A cave in the mountainside. The wind drops to nothing. The silence is LOUD.",
946
+ "Someone was here before. Old campfire — cold for weeks. Scattered supplies.",
947
+ "A note pinned under a rock, in careful handwriting:",
948
+ "\"Couldn't make the summit. Wind too strong. Storm too angry.\"",
949
+ "\"If you're reading this, you're either braver than me or dumber than me.\"",
950
+ "\"Probably both. That's usually how it works.\"",
951
+ "\"Good luck. — M\"",
952
+ "Scattered around: supplies left deliberately. GIFTED.",
953
+ ],
954
+ buddyLine: "Somebody climbed this alone. Couldn't finish. And instead of being bitter, they left help for the NEXT person. That's what good looks like.",
955
+ lootPool: ["potion", "chain_mail", "iron_helm", "wind_runners"],
956
+ nextRoomId: "ch3_ledge",
957
+ },
958
+ {
959
+ id: "ch3_ledge", type: "narrative",
960
+ title: "The Ledge",
961
+ text: [
962
+ "A wide ledge. The storm parts — just here, just for now — like the mountain offering a window.",
963
+ "Through it: the WORLD.",
964
+ "The forest below — a green quilt. The cave entrance, a dark mouth with blue-green glow.",
965
+ "Further: swamp water. Shadows of ruins. The red glow of a volcano on the horizon.",
966
+ "Your buddy sits on the ledge. Legs dangling. Wind in their fur.",
967
+ "\"Look at all of that. Every forest and cave and mountain and swamp.\"",
968
+ "\"Every scared squirrel and grumpy snail and panicking mouse.\"",
969
+ "\"All of it's worth protecting. Every single—\"",
970
+ "A gust of wind hits them so hard they nearly go off the ledge.",
971
+ "\"—OKAY MOMENT OVER. CLIMBING. CLIMBING NOW. GOODBYE SCENIC VIEW.\"",
972
+ ],
973
+ buddyLine: "Note to self: have emotional moments INDOORS.",
974
+ nextRoomId: "ch3_vents",
975
+ },
976
+ {
977
+ id: "ch3_vents", type: "event",
978
+ title: "The Thermal Vents",
979
+ text: [
980
+ "Geothermal vents dot a plateau. Steam rises, warm and inviting.",
981
+ "Curled beside one vent: a mountain fox. Small. Shivering. Eyes huge and frightened.",
982
+ "Not aggressive. Not corrupted. Just COLD. Displaced by storms that shouldn't exist.",
983
+ "\"Hey there, little one.\" Your buddy crouches. \"Bad weather, huh? We're working on it.\"",
984
+ ],
985
+ buddyLine: "Everything on this mountain is suffering because one Stone is broken. The wolves. The foxes. Even the WEATHER.",
986
+ nextRoomId: "ch3_summit_rest",
987
+ eventChoices: [
988
+ { label: "Share your food with the fox", outcome: "The fox sniffs. Hesitates. Then eats. Carefully, like it's forgotten how to trust but is trying to remember. It presses its nose against your paw. Cold nose. Warm gesture." },
989
+ { label: "Leave it be", outcome: "The fox watches you go. Its eyes follow you until you're out of sight. Your buddy glances back. Twice. '...I hope we fix this Stone fast.'" },
990
+ ],
991
+ },
992
+ {
993
+ id: "ch3_summit_rest", type: "rest",
994
+ title: "Near the Summit",
995
+ text: [
996
+ "A sheltered spot just below the peak. The eye of the storm. Momentary calm.",
997
+ "Your buddy sits. Catches their breath. The altitude makes everything harder.",
998
+ "Pip's voice echoes in memory:",
999
+ "\"YOU CAN DO IT! I BELIEVE IN— *hiding* —YOU! FROM BEHIND THIS ROCK!\"",
1000
+ "They smile. Small but real.",
1001
+ "\"People are counting on us. A tiny mouse with a broken umbrella.\"",
1002
+ "\"A four-hundred-year-old tortoise with the driest humor in existence.\"",
1003
+ "\"Let's not let them down.\"",
1004
+ ],
1005
+ buddyLine: "Okay, mountain. You've thrown wind, wolves, weather, and whatever that sideways rain was. What else you got?",
1006
+ healAmount: 20,
1007
+ },
1008
+ ],
1009
+ });
1010
+ }
1011
+ // Act 3: "The Rival" — Scene (6 rooms, includes boss)
1012
+ function buildCh3Act3() {
1013
+ return makeAdventure({
1014
+ id: "ch3_act3_rival",
1015
+ name: "The Rival",
1016
+ description: "A phoenix with an ego problem blocks the summit.",
1017
+ difficulty: "hard", levelRequired: 5, energyCost: 25, hungerCost: 20, completionXp: 90,
1018
+ startRoomId: "ch3_blaze_entrance",
1019
+ rooms: [
1020
+ {
1021
+ id: "ch3_blaze_entrance", type: "narrative",
1022
+ title: "Blaze's Entrance",
1023
+ npcsPresent: ["blaze"],
1024
+ text: [
1025
+ "The summit approach. A BURST of flame cuts across the path. Not an attack — a STATEMENT.",
1026
+ "Through the steam and smoke: a shape. Wings spread. Fire trailing from every feather.",
1027
+ "A phoenix lands on a rock. No — he ARRIVES. Ring of fire on impact. Wings spread in a PRACTICED pose.",
1028
+ "Then he turns to a frozen puddle. Checks his reflection. Adjusts a feather. Tilts his head.",
1029
+ "Nods at himself with visible satisfaction. THEN he looks at you.",
1030
+ "\"Well, well, well. Another Stone hunter.\"",
1031
+ "He flips a feather casually. It sizzles on the ground.",
1032
+ "\"The name's Blaze. And the Harmony Stones? They belong to the STRONG.\"",
1033
+ "He flexes a wing. Actually FLEXES. It shouldn't be possible but he MANAGES.",
1034
+ "\"That's me. Obviously.\"",
1035
+ "Your buddy watches this ENTIRE performance. Arms crossed. Expression devastatingly flat.",
1036
+ "\"...did he just pose? He DEFINITELY posed.\"",
1037
+ ],
1038
+ buddyLine: "I want to be annoyed but the commitment to the bit is IMPRESSIVE. He practiced that landing. You can TELL.",
1039
+ nextRoomId: "ch3_trash_talk",
1040
+ },
1041
+ {
1042
+ id: "ch3_trash_talk", type: "narrative",
1043
+ title: "Trash Talk",
1044
+ npcsPresent: ["blaze"],
1045
+ text: [
1046
+ "Blaze sizes your buddy up. Circles them. Literally walks a circle around them.",
1047
+ "\"So. What are you? Some kind of... forest critter? Playing hero?\"",
1048
+ "\"We've restored TWO Stones already. What have YOU done?\"",
1049
+ "\"I've been... scouting. STRATEGICALLY.\"",
1050
+ "\"So... you've been watching from far away while we did the work.\"",
1051
+ "\"I was ASSESSING. The tactical landscape.\"",
1052
+ "\"Uh huh. What did you assess?\"",
1053
+ "\"That I could have done it FASTER.\"",
1054
+ "\"But you DIDN'T.\"",
1055
+ "Blaze's feathers ruffle. His entire plumage puffs up with indignation.",
1056
+ "\"I could take you in my SLEEP. Right here. Right now.\"",
1057
+ ],
1058
+ buddyLine: "For the record: I'm not fighting him because he goaded me. I'm fighting him because it'll be FUNNY.",
1059
+ choices: [
1060
+ { label: "Accept the challenge", nextRoomId: "ch3_pre_fight" },
1061
+ { label: "\"You're really insecure about this, huh?\"", nextRoomId: "ch3_pre_fight" },
1062
+ ],
1063
+ },
1064
+ {
1065
+ id: "ch3_pre_fight", type: "narrative",
1066
+ title: "Pre-Fight Banter",
1067
+ npcsPresent: ["blaze"],
1068
+ text: [
1069
+ "They square up. Wind howling. Storm flickering overhead.",
1070
+ "Blaze cracks his neck. (Do phoenixes have necks? Blaze has decided yes.)",
1071
+ "\"Any last words? Before I embarrass you?\"",
1072
+ "\"Yeah, actually.\" Your buddy takes a deep breath.",
1073
+ "\"Your feathers are...\"",
1074
+ "Long pause. Really working up to it.",
1075
+ "\"...too shiny?\"",
1076
+ "Silence. The wind howls.",
1077
+ "Blaze stares. Processing.",
1078
+ "Then he LAUGHS. A real, barking, wing-slapping laugh.",
1079
+ "\"That's the WORST trash talk I have EVER heard!\"",
1080
+ "\"TOO SHINY? That's not even an INSULT! Shiny is GOOD!\"",
1081
+ "\"I panicked, okay?! Trash talk is hard!\"",
1082
+ "\"ADEQUATELY REFLECTIVE?\"",
1083
+ "\"THAT'S WORSE! HOW IS THAT WORSE?!\"",
1084
+ "They're both laughing now. Enemies who can't stop laughing at each other.",
1085
+ ],
1086
+ buddyLine: "I'll work on my trash talk. AFTER I kick your tail feathers.",
1087
+ nextRoomId: "ch3_rival_duel",
1088
+ },
1089
+ {
1090
+ id: "ch3_rival_duel", type: "boss",
1091
+ title: "Rival Duel: Blaze",
1092
+ npcsPresent: ["blaze"],
1093
+ text: [
1094
+ "The storm swirls around them. Lightning flashes. Two fighters on a mountain peak.",
1095
+ "\"READY? Because I'm only going to ask ONCE!\"",
1096
+ "Your buddy squares up. The wind immediately messes up their fur again.",
1097
+ "\"...medium ready.\"",
1098
+ "\"GOOD ENOUGH!\"",
1099
+ "Fire ERUPTS.",
1100
+ ],
1101
+ buddyLine: "Okay I panicked with the trash talk. BUT MY FIGHTING IS BETTER THAN MY COMEBACKS!",
1102
+ enemyId: "infernodrake",
1103
+ nextRoomId: "ch3_mountain_stone",
1104
+ },
1105
+ {
1106
+ id: "ch3_mountain_stone", type: "narrative",
1107
+ title: "The Mountain Stone",
1108
+ npcsPresent: ["blaze"],
1109
+ text: [
1110
+ "Blaze catches his breath. The smirk is gone. Something real underneath.",
1111
+ "\"That was...\" He physically cannot say \"good.\"",
1112
+ "\"...not terrible.\"",
1113
+ "\"You're... okay.\" Long pause. \"At fighting.\"",
1114
+ "\"I mean. Not BAD. For a...\" He waves a wing vaguely. \"...whatever you are.\"",
1115
+ "\"Take your time, Blaze.\"",
1116
+ "\"SHUT UP. I'm being NICE. This is what NICE looks like for me.\"",
1117
+ "\"It looks painful.\"",
1118
+ "\"IT IS.\"",
1119
+ "Together, you approach the Mountain Stone. Touch it. Warmth flows.",
1120
+ "Storms ease. Lightning fades. Wind calms.",
1121
+ "Blaze watches. Wings folded. Very quietly, almost to himself:",
1122
+ "\"...maybe you're not totally useless.\"",
1123
+ "He launches. Fire trails behind him. Doesn't look back.",
1124
+ "But you see it. The tiniest, most reluctant, most earned smile.",
1125
+ ],
1126
+ buddyLine: "He LIKES us. He'd rather eat his own feathers than admit it, but he LIKES us.",
1127
+ nextRoomId: "ch3_quiet_peak",
1128
+ },
1129
+ {
1130
+ id: "ch3_quiet_peak", type: "narrative",
1131
+ title: "The Quiet Peak",
1132
+ text: [
1133
+ "The storm clears. The mountain is PEACEFUL for the first time.",
1134
+ "Blue sky above. The world spread out below, golden in late light.",
1135
+ "Three Stones restored. Three biomes breathing easier.",
1136
+ "A single feather drifts down from the sky. Burning. Faintly.",
1137
+ "It lands on the rock beside your buddy. Still warm.",
1138
+ "They pick it up. Turn it over.",
1139
+ "\"He left this.\"",
1140
+ "They look at the sky. The faintest spark of fire, miles away. Getting smaller.",
1141
+ "\"He's going to come back, isn't he? Blaze.\"",
1142
+ "They tuck the feather into their pack. Carefully.",
1143
+ "\"He doesn't WANT to be alone. He's just never learned how not to be.\"",
1144
+ "\"A mouse who thinks he's useless. A tortoise alone for centuries. A phoenix too proud for friends.\"",
1145
+ "\"...I'm starting a collection.\"",
1146
+ ],
1147
+ buddyLine: "Three Stones. Three friends — well, two friends and one angry fireball in denial. ...Three friends.",
1148
+ },
1149
+ ],
1150
+ });
1151
+ }
1152
+ // ─── Chapter 4: Mire of Secrets ──────────────────────────────
1153
+ // Act 1: "Into the Mire" — Scene (5 rooms)
1154
+ function buildCh4Act1() {
1155
+ return makeAdventure({
1156
+ id: "ch4_act1_into_mire",
1157
+ name: "Into the Mire",
1158
+ description: "Pip volunteers. The swamp awaits.",
1159
+ difficulty: "hard", levelRequired: 7, energyCost: 0, hungerCost: 0, completionXp: 0,
1160
+ isScene: true,
1161
+ startRoomId: "ch4_pip_decision",
1162
+ rooms: [
1163
+ {
1164
+ id: "ch4_pip_decision", type: "narrative",
1165
+ title: "Pip's Decision",
1166
+ npcsPresent: ["pip"],
1167
+ text: [
1168
+ "Pip's shop has a NEW section. Hand-painted sign: \"SWAMP DIVISION\" in wobbly letters.",
1169
+ "Behind it: an enormous bag. Taller than Pip. He can barely see over it.",
1170
+ "\"47 items! Three types of cheese — cheddar, gouda, and the hard one that works as a weapon—\"",
1171
+ "\"A compass (broken but it LOOKS official), my lucky rock, a spare wheel—\"",
1172
+ "\"An umbrella!\" He holds it up. Still broken. Still folds inside-out.",
1173
+ "\"I fixed it!\" He opens it. It immediately unfolds wrong. He stares at it.",
1174
+ "\"...it's a work in progress.\"",
1175
+ "He looks at your buddy. The chatter stops.",
1176
+ "\"I'm coming with you.\"",
1177
+ "\"My father always said: 'The world is full of people who need a friend, Pip.'\"",
1178
+ "\"I want to be that friend. YOUR friend. The one who shows up.\"",
1179
+ ],
1180
+ buddyLine: "Pip... I'd be honored. But you carry the bag. I draw the line at the bag.",
1181
+ nextRoomId: "ch4_swamp_edge",
1182
+ },
1183
+ {
1184
+ id: "ch4_swamp_edge", type: "narrative",
1185
+ title: "The Swamp's Edge",
1186
+ text: [
1187
+ "The swamp. Even from the edge, it assaults every sense.",
1188
+ "Toxic fog hangs at knee height. The ground squelches with malice.",
1189
+ "Bubbles rise from the mud and pop with sounds that shouldn't come from bubbles.",
1190
+ "And the SMELL.",
1191
+ "Pip wrinkles his nose. Then his whole face. Then his ENTIRE body.",
1192
+ "\"This is fine.\" He gags slightly.",
1193
+ "\"This is FINE.\" He gags harder.",
1194
+ "\"I am FINE.\" He is catastrophically not fine.",
1195
+ ],
1196
+ buddyLine: "The corruption hit this place HARD. It's not just infected — it's dying.",
1197
+ nextRoomId: "ch4_wading",
1198
+ },
1199
+ {
1200
+ id: "ch4_wading", type: "narrative",
1201
+ title: "Wading In",
1202
+ npcsPresent: ["pip"],
1203
+ text: [
1204
+ "The path disappears into murky water. No avoiding it.",
1205
+ "Pip looks at the water. Looks at his tiny legs. Does a risk assessment in two seconds.",
1206
+ "He climbs up your buddy's leg and sits on their head.",
1207
+ "\"Is this okay? I'm very light. Like a feather. A terrified feather with excellent balance.\"",
1208
+ "\"You're fine up there, Pip.\"",
1209
+ "He grips your buddy's ear for balance. Your buddy winces.",
1210
+ "\"Sorry! SORRY! I panic-grabbed!\"",
1211
+ "Something moves under the water. They both feel it.",
1212
+ "Pip's grip on the ear tightens. \"...what was that.\"",
1213
+ "\"Nothing.\"",
1214
+ "\"THAT WAS NOT NOTHING.\"",
1215
+ ],
1216
+ buddyLine: "Let's keep moving. And Pip? If something grabs us, don't pull BOTH ears.",
1217
+ nextRoomId: "ch4_first_warning",
1218
+ },
1219
+ {
1220
+ id: "ch4_first_warning", type: "narrative",
1221
+ title: "The First Warning",
1222
+ text: [
1223
+ "Deeper. The fog closes in. Visibility drops to arm's length.",
1224
+ "Trees grow SIDEWAYS. Reaching for each other across the water.",
1225
+ "A sound rises from below. Not a splash. Not a rumble.",
1226
+ "BREATHING. Huge. Slow. From somewhere beneath the water.",
1227
+ "Like the swamp itself is a sleeping thing and they're walking on its chest.",
1228
+ "Pip's whiskers go rigid. \"...what was that?\"",
1229
+ "\"Probably just the swamp settling.\"",
1230
+ "\"Swamps don't BREATHE.\"",
1231
+ "\"...probably just the swamp breathing.\"",
1232
+ "\"THAT'S NOT BETTER.\"",
1233
+ ],
1234
+ buddyLine: "Stay close, Pip. And stay quiet.",
1235
+ nextRoomId: "ch4_old_signs",
1236
+ },
1237
+ {
1238
+ id: "ch4_old_signs", type: "narrative",
1239
+ title: "Old Signs",
1240
+ text: [
1241
+ "Through the murk: stepping stones. Cut stone. Deliberate.",
1242
+ "Half-submerged markers with the same script from the cave murals.",
1243
+ "Someone built a ROAD here. Through this swamp. Long ago.",
1244
+ "\"These match the cave symbols. The first buddies were HERE.\"",
1245
+ "\"Why would anyone build a road through a swamp?\"",
1246
+ "\"Maybe... it wasn't always a swamp.\"",
1247
+ "The road leads deeper. Toward something half-visible through the fog. A structure.",
1248
+ ],
1249
+ buddyLine: "The corruption didn't just infect this place. It CHANGED it. Who knows what this was before?",
1250
+ },
1251
+ ],
1252
+ });
1253
+ }
1254
+ // Act 2: "The Deep Mire" — Exploration (5 rooms)
1255
+ function buildCh4Act2() {
1256
+ return makeAdventure({
1257
+ id: "ch4_act2_deep_mire",
1258
+ name: "The Deep Mire",
1259
+ description: "Navigate the corrupted swamp. Pip's courage is tested.",
1260
+ difficulty: "hard", levelRequired: 7, energyCost: 20, hungerCost: 15, completionXp: 30,
1261
+ startRoomId: "ch4_boardwalk",
1262
+ rooms: [
1263
+ {
1264
+ id: "ch4_boardwalk", type: "combat",
1265
+ title: "Boardwalk Ambush",
1266
+ npcsPresent: ["pip"],
1267
+ text: [
1268
+ "A rotting boardwalk. Each step creaks. CREAK. CREAK. Like the swamp is counting.",
1269
+ "\"This is fine. Old boardwalks are charming. Full of charact—\"",
1270
+ "A plank SNAPS. Your buddy catches themselves.",
1271
+ "\"—full of HOLES. Full of HOLES is what I meant.\"",
1272
+ "A Shadow Fox LUNGES from the mist! Eyes burning with dark energy.",
1273
+ "Pip SCREAMS. Grabs the umbrella. It snaps in half IMMEDIATELY.",
1274
+ "\"I'm MORALLY SUPPORTING!\" He brandishes one half like a tiny lance.",
1275
+ ],
1276
+ buddyLine: "Pip, get behind me! And put the umbrella DOWN! ...both halves!",
1277
+ enemyId: "shadowfox",
1278
+ nextRoomId: "ch4_village",
1279
+ },
1280
+ {
1281
+ id: "ch4_village", type: "narrative",
1282
+ title: "The Sunken Village",
1283
+ npcsPresent: ["pip"],
1284
+ text: [
1285
+ "The fog parts. And what's behind it is worse than any monster.",
1286
+ "A village. Not ancient ruins — RECENT. Maybe decades old.",
1287
+ "Houses half-submerged. A fence still standing, gate open, as if someone left in a hurry.",
1288
+ "In the mud: a child's toy. A small wooden buddy, hand-carved. Missing one arm.",
1289
+ "Pip climbs down. Walks to the toy. Picks it up.",
1290
+ "\"Someone lived here. Someone had a kid. And they made them a toy.\"",
1291
+ "\"And then they had to LEAVE. And the kid didn't even take their toy.\"",
1292
+ "He sets it back down. On the doorstep of a frame that has no house.",
1293
+ "\"They left so fast they couldn't take their toy.\"",
1294
+ ],
1295
+ buddyLine: "This is what happens when the Stones break. PEOPLE lose everything.",
1296
+ nextRoomId: "ch4_witch",
1297
+ },
1298
+ {
1299
+ id: "ch4_witch", type: "combat",
1300
+ title: "Bog Witch Encounter",
1301
+ npcsPresent: ["pip"],
1302
+ text: [
1303
+ "The water ripples. Something rises. A Bog Witch — draped in moss and malice.",
1304
+ "She throws curses — literal glowing orbs of swamp magic.",
1305
+ "Pip: \"WITCH! THERE IS A WITCH!\"",
1306
+ "\"WHY IS THERE A WITCH?! WHO PUT A WITCH HERE?!\"",
1307
+ "He ducks behind your buddy's leg.",
1308
+ "\"YOUR LEG IS THE SAFEST PLACE IN THE SWAMP AND I'M STAYING HERE!\"",
1309
+ ],
1310
+ buddyLine: "Pip, I need that leg to FIGHT with!",
1311
+ enemyId: "bogwitch",
1312
+ nextRoomId: "ch4_signal",
1313
+ },
1314
+ {
1315
+ id: "ch4_signal", type: "narrative",
1316
+ title: "The Signal",
1317
+ text: [
1318
+ "The ancient road leads to a structure. Not a ruin — a BUILDING.",
1319
+ "Half-submerged but intact. Stone walls that resisted centuries of swamp.",
1320
+ "Built to LAST. Built to protect something inside.",
1321
+ "\"That's not a house. That's a vault.\"",
1322
+ "Pip: \"A vault in a swamp. Because when I think 'secure storage,' I think 'mud and evil water.'\"",
1323
+ "They push through the door. It groans. Opens. Dark inside.",
1324
+ ],
1325
+ buddyLine: "Whatever the first buddies hid here... they built a road, a vault, and a swamp to protect it.",
1326
+ nextRoomId: "ch4_rest",
1327
+ },
1328
+ {
1329
+ id: "ch4_rest", type: "rest",
1330
+ title: "Rest by the Ruins",
1331
+ npcsPresent: ["pip"],
1332
+ text: [
1333
+ "Just inside the vault. Dry ground for the first time in hours. The relief is PHYSICAL.",
1334
+ "Pip rummages in his bag. \"Snacks!\" He produces cheese (damp) and berries (squished).",
1335
+ "\"The cheese is a little... swampy. But cheese is cheese.\"",
1336
+ "He chews a berry. Still thinking about the village.",
1337
+ "\"Do you think they're okay? The family. From the village.\"",
1338
+ "\"I hope so, Pip.\"",
1339
+ "\"...I hope they took other toys with them.\"",
1340
+ "\"Do you think we'll fix it? All of it? Really fix it?\"",
1341
+ "\"We've done three Stones. We'll do three more.\"",
1342
+ ],
1343
+ buddyLine: "Eat, Pip. Rest. What's ahead can wait ten minutes.",
1344
+ healAmount: 20,
1345
+ },
1346
+ ],
1347
+ });
1348
+ }
1349
+ // Act 3: "The Guardian's Journal" — Scene (4 rooms)
1350
+ function buildCh4Act3() {
1351
+ return makeAdventure({
1352
+ id: "ch4_act3_journal",
1353
+ name: "The Guardian's Journal",
1354
+ description: "The truth about the shattered Stones.",
1355
+ difficulty: "hard", levelRequired: 7, energyCost: 0, hungerCost: 0, completionXp: 0,
1356
+ isScene: true,
1357
+ startRoomId: "ch4_archive",
1358
+ rooms: [
1359
+ {
1360
+ id: "ch4_archive", type: "narrative",
1361
+ title: "The Sunken Archive",
1362
+ text: [
1363
+ "Deeper into the vault. Stone steps descend into the earth.",
1364
+ "Shelves carved into walls. Most collapsed. Papers destroyed by centuries of damp.",
1365
+ "A desk remains. Stone. Too heavy to rot.",
1366
+ "On it: a journal. In a waterproofed leather case. Someone KNEW it would need to last.",
1367
+ "Your buddy picks it up. Opens the first page.",
1368
+ "The handwriting is neat. Deliberate. Written by someone who wanted these words to SURVIVE.",
1369
+ "The room goes quiet. The kind of quiet that happens when the world is about to change.",
1370
+ ],
1371
+ buddyLine: "This was left on purpose. Protected on purpose. Whatever's in here... it mattered.",
1372
+ nextRoomId: "ch4_journal",
1373
+ },
1374
+ {
1375
+ id: "ch4_journal", type: "narrative",
1376
+ title: "The Journal",
1377
+ text: [
1378
+ "Your buddy reads aloud. Slowly:",
1379
+ "\"If you are reading this, the seal has failed. Or is failing. I am sorry.\"",
1380
+ "\"The seal is weakening. I can feel the Beast pressing against it. Every day, stronger.\"",
1381
+ "\"I have held it for... I've lost count of the years.\"",
1382
+ "\"If the Beast escapes while the Stones are whole, it takes their power with it.\"",
1383
+ "\"The Stones don't just imprison the Beast. They SUSTAIN it. Feed it.\"",
1384
+ "\"I have to break the Stones. Scatter the power. Starve it.\"",
1385
+ "\"I know what this means. The biomes will suffer. The balance will shatter.\"",
1386
+ "\"But a world in chaos can heal. A world consumed by the Beast CANNOT.\"",
1387
+ "The handwriting changes. Shakier. Later.",
1388
+ "\"It's done. The Stones are shattered. The Beast is weakened.\"",
1389
+ "\"And the shadow... is inside me now. I can feel it growing.\"",
1390
+ "\"I broke the seal. And the seal broke me.\"",
1391
+ "\"I'm sorry. I had no choice.\"",
1392
+ "\"Please forgive me.\"",
1393
+ "\"— The Guardian\"",
1394
+ ],
1395
+ buddyLine: "...",
1396
+ nextRoomId: "ch4_processing",
1397
+ },
1398
+ {
1399
+ id: "ch4_processing", type: "event",
1400
+ title: "Processing",
1401
+ npcsPresent: ["pip"],
1402
+ text: [
1403
+ "Your buddy stares at the page. Sets the journal down. Picks it up again.",
1404
+ "Pip watches. \"Are you okay?\"",
1405
+ "\"...no. I'm not.\"",
1406
+ "\"They did it to PROTECT us. The Guardian broke the Stones because the ALTERNATIVE was worse.\"",
1407
+ "\"One person. One impossible choice. Centuries alone.\"",
1408
+ "\"And the world just... moved on. Without them.\"",
1409
+ "Pip is quiet. Then, very seriously:",
1410
+ "\"So the person who broke the Stones... they're not a villain?\"",
1411
+ "\"No, Pip. They're the bravest person who ever lived. And they did it ALONE.\"",
1412
+ "Pip meets your buddy's eyes. No shaking. No comedy.",
1413
+ "\"You won't be alone. I promise.\"",
1414
+ ],
1415
+ buddyLine: "...thanks, Pip. I really needed to hear that.",
1416
+ nextRoomId: "ch4_pip_story",
1417
+ eventChoices: [
1418
+ { label: "Take the journal for Shelly", outcome: "You fold it into the case carefully. Like it's made of someone's life. Because it is. Shelly needs to see this. She needs to know the truth about what happened. About WHY." },
1419
+ { label: "Show Pip the tearstain", outcome: "Pip looks at the blotted ink for a long time. \"They were crying,\" he says. \"When they wrote this. They were alone and they were crying.\" He wipes his eyes. \"The cheese is making my eyes water. That's all.\" It's not the cheese." },
1420
+ ],
1421
+ },
1422
+ {
1423
+ id: "ch4_pip_story", type: "narrative",
1424
+ title: "Pip's Story",
1425
+ npcsPresent: ["pip"],
1426
+ text: [
1427
+ "They sit with the journal between them. Pip is quiet for a long time.",
1428
+ "\"My father was a merchant.\" He's told you this. But not like this.",
1429
+ "\"He'd come home with his cart loaded and say:\"",
1430
+ "\"'Pip, the best part isn't the selling. It's the people.'\"",
1431
+ "\"'The world is big and scary. But it's also full of people who need a friend.'\"",
1432
+ "\"He went on a trip. When I was young. A long trip. Farther than usual.\"",
1433
+ "\"He said he'd be back in a week.\"",
1434
+ "\"He didn't come back.\"",
1435
+ "\"I counted my inventory over and over because that's what he taught me.\"",
1436
+ "\"'When you're scared, count your stock. It gives your paws something to do.'\"",
1437
+ "\"I became a merchant because I wanted to be what he said. Someone who shows up.\"",
1438
+ "\"I think he'd be proud of me today. In a swamp. With a broken umbrella and a friend.\"",
1439
+ "\"...the cheese is making my eyes water. That's all.\"",
1440
+ ],
1441
+ buddyLine: "Pip's father was right. The best thing you can be is someone who shows up. And Pip showed up.",
1442
+ },
1443
+ ],
1444
+ });
1445
+ }
1446
+ // Act 4: "The Marsh King's Domain" — Dungeon (6 rooms)
1447
+ function buildCh4Act4() {
1448
+ return makeAdventure({
1449
+ id: "ch4_act4_marsh_king",
1450
+ name: "The Marsh King's Domain",
1451
+ description: "Face the corruption's heart. Pip is brave now. For real.",
1452
+ difficulty: "hard", levelRequired: 7, energyCost: 25, hungerCost: 20, completionXp: 120,
1453
+ startRoomId: "ch4_approach",
1454
+ rooms: [
1455
+ {
1456
+ id: "ch4_approach", type: "narrative",
1457
+ title: "The Approach",
1458
+ npcsPresent: ["pip"],
1459
+ text: [
1460
+ "Back into the swamp. But something has changed. Not the swamp — them.",
1461
+ "Your buddy looks at Pip. \"The Stone is ahead. You don't have to—\"",
1462
+ "\"Yes I do.\"",
1463
+ "No stutter. No stammer. No shaking.",
1464
+ "Just Pip. Standing in swamp water up to his chin.",
1465
+ "\"Someone has to show up. That's me.\"",
1466
+ ],
1467
+ buddyLine: "...okay. Together, then.",
1468
+ nextRoomId: "ch4_corruption_heart",
1469
+ },
1470
+ {
1471
+ id: "ch4_corruption_heart", type: "combat",
1472
+ title: "The Corruption's Heart",
1473
+ npcsPresent: ["pip"],
1474
+ text: [
1475
+ "Corrupted Vine Creepers block the path. Thorns dripping, snapping at the air.",
1476
+ "Your buddy readies for the fight. Standard procedure.",
1477
+ "But Pip does something new.",
1478
+ "\"LEFT! ON YOUR LEFT!\"",
1479
+ "He's spotted a creeper flanking. He actually spotted it FIRST.",
1480
+ "\"THE VINES ARE THINNER ON THE RIGHT SIDE!\"",
1481
+ "A tiny mouse standing on a log, shouting battle intel.",
1482
+ ],
1483
+ buddyLine: "Nice call, Pip! Since when are you a TACTICIAN?",
1484
+ enemyId: "vinecreeper",
1485
+ nextRoomId: "ch4_pool",
1486
+ },
1487
+ {
1488
+ id: "ch4_pool", type: "narrative",
1489
+ title: "The Pool",
1490
+ npcsPresent: ["pip"],
1491
+ text: [
1492
+ "The Swamp Stone. Sitting in corrupted water, barely glowing.",
1493
+ "Silver-green light pulses... dims... pulses... dims longer...",
1494
+ "\"It's barely alive. If we don't fix this one NOW, the swamp dies.\"",
1495
+ "The water begins to ripple. Something is RISING.",
1496
+ "Pip: \"What's the worst that could—\"",
1497
+ "\"PIP.\"",
1498
+ "\"I was going to say 'could happen to a bad person. Who isn't us.'\"",
1499
+ "\"We TALKED about this.\"",
1500
+ "The water ERUPTS.",
1501
+ ],
1502
+ buddyLine: "EVERY TIME, PIP! EVERY. TIME.",
1503
+ choices: [
1504
+ { label: "Face the Marsh King", nextRoomId: "ch4_boss" },
1505
+ ],
1506
+ },
1507
+ {
1508
+ id: "ch4_boss", type: "boss",
1509
+ title: "The Marsh King",
1510
+ npcsPresent: ["pip"],
1511
+ text: [
1512
+ "The Marsh King rises. Mud and moss and ancient fury given shape.",
1513
+ "Massive. Twenty times your buddy's size. It ROARS. Trees lean AWAY.",
1514
+ "Pip catches himself on a root. Scrambles behind a log. His usual spot.",
1515
+ "Then — something changes.",
1516
+ "He looks at the log. At his paws. At his friend facing something impossible.",
1517
+ "He STANDS UP.",
1518
+ "He's still behind the log. But he's STANDING.",
1519
+ "\"YOU CAN DO IT!\" His voice cracks. Doesn't matter.",
1520
+ "\"FOR THE FOREST! FOR THE CAVES! FOR THE MOUNTAINS!\"",
1521
+ "\"FOR THE VILLAGE! FOR THE PEOPLE WHO HAD TO LEAVE!\"",
1522
+ "Quieter: \"For my father.\"",
1523
+ "Then louder than he's EVER been:",
1524
+ "\"FOR EVERYONE! YOU ARE NOT! ALONE!\"",
1525
+ ],
1526
+ buddyLine: "For everyone, Pip. Including you. ESPECIALLY you.",
1527
+ enemyId: "marshking",
1528
+ nextRoomId: "ch4_stone_restored",
1529
+ },
1530
+ {
1531
+ id: "ch4_stone_restored", type: "narrative",
1532
+ title: "Stone Restored",
1533
+ npcsPresent: ["pip"],
1534
+ text: [
1535
+ "The Marsh King collapses. Like a wave returning to the sea.",
1536
+ "The Stone's light pulses. Stronger. Silver-green. Building.",
1537
+ "Pip climbs over the log. Walks to the Stone. Touches it. One paw.",
1538
+ "The Stone pulses. Warm. Alive.",
1539
+ "\"Four down.\" His voice is small but steady. \"Two to go.\"",
1540
+ "\"...I packed enough snacks for at least six adventures. So we're fine. Supply-wise.\"",
1541
+ "The tiniest laugh. Your buddy's. Then Pip's.",
1542
+ "Not because it's funny. Because they're alive.",
1543
+ ],
1544
+ buddyLine: "Four Stones. And the bravest mouse in the world.",
1545
+ nextRoomId: "ch4_heading_home",
1546
+ },
1547
+ {
1548
+ id: "ch4_heading_home", type: "narrative",
1549
+ title: "Heading Home",
1550
+ npcsPresent: ["pip"],
1551
+ text: [
1552
+ "Walking out of the swamp. The fog is lighter. Pip rides on buddy's head. Sits taller now.",
1553
+ "\"Thank you. For letting me come.\"",
1554
+ "\"Thank you for coming. I couldn't have done it without you.\"",
1555
+ "\"That's not true.\" He smiles. \"But it's really, really nice to hear.\"",
1556
+ "\"I'm going to upgrade my shop. 'PIP'S WARES: NOW INCLUDING SWAMP SURVIVAL KITS.'\"",
1557
+ "Your buddy pulls out the journal. Looks at it one more time.",
1558
+ "\"We need to show this to Shelly. She'll know what it means.\"",
1559
+ "\"And maybe... she knows who the Guardian was.\"",
1560
+ "\"Because someone should. Someone should know their name.\"",
1561
+ ],
1562
+ buddyLine: "The Guardian wrote 'please forgive me.' ...I think the answer is yes. From all of us.",
1563
+ },
1564
+ ],
1565
+ });
1566
+ }
1567
+ // ─── Chapter 5: Forgotten Knowledge ──────────────────────────
1568
+ // Act 1: "Shelly's Direction" — Scene (4 rooms)
1569
+ function buildCh5Act1() {
1570
+ return makeAdventure({
1571
+ id: "ch5_act1_direction",
1572
+ name: "Shelly's Direction",
1573
+ description: "Shelly sends you to the Ancient Ruins with heavy words.",
1574
+ difficulty: "hard", levelRequired: 9, energyCost: 0, hungerCost: 0, completionXp: 0,
1575
+ isScene: true,
1576
+ startRoomId: "ch5_return",
1577
+ rooms: [
1578
+ {
1579
+ id: "ch5_return", type: "narrative",
1580
+ title: "Return to Shelly",
1581
+ npcsPresent: ["shelly"],
1582
+ text: [
1583
+ "Back at Shelly's grotto. You set the journal on the stone before her.",
1584
+ "She picks it up. Reads the first page. Expression unchanged.",
1585
+ "Reads the second. Something in her jaw tightens.",
1586
+ "Reads the third — the page with the tearstain. She stops.",
1587
+ "For a FULL MINUTE, Shelly says nothing.",
1588
+ "\"I suspected.\" She closes the journal. \"I HOPED I was wrong.\"",
1589
+ "\"The Guardian didn't just break the Stones. They broke themselves.\"",
1590
+ "\"The shadow consumed them. They're still out there. Trapped.\"",
1591
+ "Her hands — those ancient, patient hands — are trembling.",
1592
+ "The first time you've seen Shelly tremble.",
1593
+ ],
1594
+ buddyLine: "Shelly... you KNEW, didn't you? You knew who they were.",
1595
+ nextRoomId: "ch5_mission",
1596
+ },
1597
+ {
1598
+ id: "ch5_mission", type: "narrative",
1599
+ title: "The Mission",
1600
+ npcsPresent: ["shelly"],
1601
+ text: [
1602
+ "\"There is a place. The Ancient Ruins. East of here, past the dead forest.\"",
1603
+ "\"Where the Stones were first conceived. The ideas. The blueprints. The cost.\"",
1604
+ "\"I should have gone myself. Years ago. Decades. CENTURIES ago.\"",
1605
+ "\"But I was afraid. Afraid of confirming what I suspected.\"",
1606
+ "\"You're braver than I ever was, young one.\"",
1607
+ "\"Shelly. You've been in this cave for four hundred years. That's not cowardice.\"",
1608
+ "\"Perhaps. Or perhaps it's the most comfortable kind of hiding.\"",
1609
+ "She meets your eyes. Hers are bright. Too bright.",
1610
+ "\"Go. Find what I was too afraid to look for.\"",
1611
+ ],
1612
+ buddyLine: "You're not a coward, Shelly. You carried this alone for centuries.",
1613
+ nextRoomId: "ch5_journey",
1614
+ },
1615
+ {
1616
+ id: "ch5_journey", type: "narrative",
1617
+ title: "The Journey",
1618
+ text: [
1619
+ "Traveling to the ruins. The landscape changes. Lush forest → sparse woods → bare stone.",
1620
+ "The ruins aren't hidden. They're FORGOTTEN. Nobody comes here.",
1621
+ "No corruption here — just ABSENCE. The land is empty. Scoured clean of life.",
1622
+ "Even the corruption doesn't want to be here.",
1623
+ "The air is dry. Still. No wind. No birds.",
1624
+ "Just the crunch of ancient stone and the weight of a thousand years of silence.",
1625
+ ],
1626
+ buddyLine: "Nothing grows here. Even the weeds gave up. This place isn't corrupted — it's MOURNING.",
1627
+ nextRoomId: "ch5_gates",
1628
+ },
1629
+ {
1630
+ id: "ch5_gates", type: "narrative",
1631
+ title: "The Gates",
1632
+ text: [
1633
+ "The entrance. Massive stone doors, three times your buddy's height. Half-open.",
1634
+ "Above: six crystal symbols. One for each Harmony Stone.",
1635
+ "Below them, in ancient script:",
1636
+ "\"HERE THE WORLD WAS SAVED.\"",
1637
+ "And below that, smaller, added later:",
1638
+ "\"HERE THE PRICE WAS PAID.\"",
1639
+ "The doors are open. Not broken — LEFT open. Deliberately.",
1640
+ "As if whoever walked out intended to return.",
1641
+ "They never did.",
1642
+ ],
1643
+ buddyLine: "\"Saved\" and \"Price\" carved at different times. The first in celebration. The second in grief.",
1644
+ },
1645
+ ],
1646
+ });
1647
+ }
1648
+ // Act 2: "The Archives" — Exploration (5 rooms)
1649
+ function buildCh5Act2() {
1650
+ return makeAdventure({
1651
+ id: "ch5_act2_archives",
1652
+ name: "The Archives",
1653
+ description: "Every room reveals more of the truth. Every truth is heavier.",
1654
+ difficulty: "hard", levelRequired: 9, energyCost: 20, hungerCost: 15, completionXp: 40,
1655
+ startRoomId: "ch5_hall",
1656
+ rooms: [
1657
+ {
1658
+ id: "ch5_hall", type: "combat",
1659
+ title: "The Great Hall",
1660
+ text: [
1661
+ "The Great Hall stretches into darkness. Columns carved with faces of the first buddies.",
1662
+ "Not idealized — PORTRAITS. Real faces. Determined. Young. Afraid.",
1663
+ "Something CLANKS in the darkness. Metal on stone. Rhythmic. Patient.",
1664
+ "A Rust Knight steps forward. Armor corroded by centuries. Sword still sharp.",
1665
+ "It raises its blade. Salutes. Then attacks.",
1666
+ "Not with rage. With DUTY. Following orders from people who are dust.",
1667
+ ],
1668
+ buddyLine: "It's been guarding an empty room for CENTURIES. Nobody came. Nobody relieved it. And it STILL showed up.",
1669
+ enemyId: "rustknight",
1670
+ nextRoomId: "ch5_mosaic",
1671
+ },
1672
+ {
1673
+ id: "ch5_mosaic", type: "narrative",
1674
+ title: "The Mosaic Floor",
1675
+ text: [
1676
+ "An enormous mosaic covering the entire floor. Thousands of tiny tiles.",
1677
+ "It depicts the CREATION of the Stones. Buddy-shaped figures working together.",
1678
+ "They're smiling. They didn't know what it would cost yet.",
1679
+ "In the center: the lone figure. Stepping FORWARD. While others step back.",
1680
+ "The others look at the lone figure with gratitude. Guilt. Relief it wasn't them.",
1681
+ "\"They CHOSE this. Stepped forward when everyone else stepped back.\"",
1682
+ "\"And everyone let them.\"",
1683
+ "The lone figure's face: RESOLVED. Not sad. DECIDED.",
1684
+ ],
1685
+ buddyLine: "Everyone in this mosaic is smiling except one. And the one who isn't is the one who saved them all.",
1686
+ nextRoomId: "ch5_reading",
1687
+ },
1688
+ {
1689
+ id: "ch5_reading", type: "event",
1690
+ title: "The Reading Room",
1691
+ text: [
1692
+ "Walls of stone tablets. Floor to ceiling. The archive of the first guardians.",
1693
+ "Your buddy tries to read the ancient script. Squints. Turns a tablet sideways.",
1694
+ "\"I think this says... 'cheese'?\"",
1695
+ "Long, intense squint. \"No — 'seal.' Definitely 'seal.'\"",
1696
+ "\"Although 'cheese seal' would be an INCREDIBLE invention.\"",
1697
+ "The actual text:",
1698
+ "\"Six Stones, forged in the Core, to seal the Shadow Beast.\"",
1699
+ "\"One guardian. Bound to the Stones. Their life force maintains the harmony.\"",
1700
+ "\"Should the Stones break, the guardian suffers.\"",
1701
+ "\"Forever\" appears on the next tablet. Underlined. Three times.",
1702
+ ],
1703
+ buddyLine: "'Should the Stones break, the guardian SUFFERS.' Every Stone we've restored — they FELT it.",
1704
+ nextRoomId: "ch5_quarters",
1705
+ eventChoices: [
1706
+ { label: "Copy the inscriptions for Shelly", outcome: "You carefully trace each symbol. These words have waited centuries for someone to carry them back." },
1707
+ { label: "Search for the Guardian's name", outcome: "The name IS here — but SCRATCHED OUT. Gouged from stone with a chisel. \"They didn't want to be remembered. Or maybe... they didn't want to be mourned.\"" },
1708
+ ],
1709
+ },
1710
+ {
1711
+ id: "ch5_quarters", type: "narrative",
1712
+ title: "The Guardian's Quarters",
1713
+ text: [
1714
+ "A small room off the archive. A bed carved from stone. A shelf with items.",
1715
+ "This was where the Guardian LIVED. Before they became the Guardian.",
1716
+ "A half-finished letter: \"Dear [scratched out], I'm sorry I won't be at the festival. I've been asked to—\"",
1717
+ "It stops. The rest is blank. They never finished.",
1718
+ "A small figurine on the shelf. A buddy. Handmade. Carved with loving strokes.",
1719
+ "Given by someone who cared. Someone who said \"come back soon.\"",
1720
+ "Your buddy holds it. It's warm. Not from heat — from memory.",
1721
+ "\"They had a LIFE. Before all this. Someone made them a gift.\"",
1722
+ "\"They had a festival they were going to miss.\"",
1723
+ "\"And then they missed EVERYTHING.\"",
1724
+ ],
1725
+ buddyLine: "Whoever you were: someone loved you enough to carve this. I'm carrying it. Someone should.",
1726
+ nextRoomId: "ch5_deeper",
1727
+ },
1728
+ {
1729
+ id: "ch5_deeper", type: "rest",
1730
+ title: "The Deeper Archive",
1731
+ text: [
1732
+ "A quiet reading nook. Warm light from crystals in the walls. Almost cozy.",
1733
+ "Your buddy sits. Holds the figurine. Turns it over.",
1734
+ "\"One person gave up everything. Their name. Their life. Their future.\"",
1735
+ "\"And we're only finding out NOW. Because of a journal in a SWAMP.\"",
1736
+ "\"Someone should have remembered them.\"",
1737
+ "\"Someone should have visited. Told them a joke. Asked how they WERE.\"",
1738
+ "They pocket the figurine. Carefully.",
1739
+ "\"I'll remember you. Even if the world forgot.\"",
1740
+ ],
1741
+ buddyLine: "I'll carry your name until I can give it back.",
1742
+ healAmount: 25,
1743
+ },
1744
+ ],
1745
+ });
1746
+ }
1747
+ // Act 3: "The Warning" — Scene (4 rooms)
1748
+ function buildCh5Act3() {
1749
+ return makeAdventure({
1750
+ id: "ch5_act3_warning",
1751
+ name: "The Warning",
1752
+ description: "Someone else is in the ruins. And they're terrified.",
1753
+ difficulty: "hard", levelRequired: 9, energyCost: 0, hungerCost: 0, completionXp: 0,
1754
+ isScene: true,
1755
+ startRoomId: "ch5_deep_hall",
1756
+ rooms: [
1757
+ {
1758
+ id: "ch5_deep_hall", type: "narrative",
1759
+ title: "The Deep Hall",
1760
+ text: [
1761
+ "Deeper. The architecture changes. Older. The inscriptions here aren't records — WARNINGS.",
1762
+ "\"BEYOND THIS POINT: THE SEAL. DO NOT DISTURB THE BALANCE.\"",
1763
+ "\"THE BEAST LISTENS. THE BEAST REMEMBERS. THE BEAST WAITS.\"",
1764
+ "Each line carved deeper than the last. BEAST carved deepest of all.",
1765
+ "The air temperature drops. Your buddy's breath fogs. In SUMMER.",
1766
+ "Something is watching. In the dark. In the walls.",
1767
+ ],
1768
+ buddyLine: "We're not alone down here. Whatever's watching us... it's MORE scared than we are.",
1769
+ nextRoomId: "ch5_shadow",
1770
+ },
1771
+ {
1772
+ id: "ch5_shadow", type: "narrative",
1773
+ title: "The Shadow",
1774
+ text: [
1775
+ "A movement at the edge of vision. You turn — nothing. Turn back — movement again.",
1776
+ "The air grows cold enough to HURT. Breath comes in white clouds.",
1777
+ "A shadow coalesces. Not on a wall — IN the air. Like smoke shaped by memory.",
1778
+ "It flickers. Fades. Reforms. The effort of EXISTING here is enormous.",
1779
+ "It speaks. One word. In a voice so tired it barely qualifies as sound.",
1780
+ "\"...please.\"",
1781
+ "One word. But the weight of it stops you cold.",
1782
+ "The voice of someone who has been trying to say this to ANYONE for a very long time.",
1783
+ ],
1784
+ buddyLine: "...",
1785
+ nextRoomId: "ch5_stranger_warning",
1786
+ },
1787
+ {
1788
+ id: "ch5_stranger_warning", type: "event",
1789
+ title: "The Stranger's Warning",
1790
+ npcsPresent: ["stranger"],
1791
+ text: [
1792
+ "The shadow takes form. Eyes. Ancient. Desperate. Afraid FOR you.",
1793
+ "\"Turn back.\" Each word costs something. The shadow flickers with every syllable.",
1794
+ "\"The Stones were a seal. I maintained it. For so long.\"",
1795
+ "\"But the Beast LEARNS. It found cracks I couldn't mend.\"",
1796
+ "\"So I broke the Stones. To scatter the power. To BUY TIME.\"",
1797
+ "\"But if you restore them all — without a guardian—\"",
1798
+ "\"The Beast escapes. At full power. And EVERYTHING. ENDS.\"",
1799
+ "\"I'm trying to WARN you. Please. PLEASE.\"",
1800
+ "\"Find another way. Find a new guardian. Or—\"",
1801
+ "Gone. Like smoke in wind. The temperature snaps back.",
1802
+ "But nothing feels normal anymore.",
1803
+ ],
1804
+ buddyLine: "That was the Guardian. Still trying to protect us. Even as a shadow. Even NOW.",
1805
+ nextRoomId: "ch5_processing",
1806
+ eventChoices: [
1807
+ { label: "\"We'll find another way.\"", outcome: "The shadows recoil. As if the words themselves are a weapon. Your words reached them. Through the dark, through the years — your words reached them." },
1808
+ { label: "\"We won't let this be in vain.\"", outcome: "A warmth pulses through you — not from the Stones. From the place where promises live. Somewhere in the dark, the Stranger pauses." },
1809
+ ],
1810
+ },
1811
+ {
1812
+ id: "ch5_processing", type: "narrative",
1813
+ title: "Processing",
1814
+ text: [
1815
+ "Your buddy stands where the Stranger was. Staring at empty air.",
1816
+ "\"That wasn't a threat. That was a PLEA.\"",
1817
+ "\"They're scared. Not of us. Of what happens if we get this WRONG.\"",
1818
+ "\"We can't just restore the Stones and call it done.\"",
1819
+ "\"We need to find this Guardian. Not fight them. SAVE them.\"",
1820
+ "\"Five Stones down after this one. But the Stones were never the real problem.\"",
1821
+ "\"The real problem is that someone's been alone for centuries.\"",
1822
+ "\"And it's time that stopped.\"",
1823
+ ],
1824
+ buddyLine: "Shelly knows more than she's told us. It's time she stopped hiding behind stories.",
1825
+ },
1826
+ ],
1827
+ });
1828
+ }
1829
+ // Act 4: "The Fifth Seal" — Dungeon (4 rooms)
1830
+ function buildCh5Act4() {
1831
+ return makeAdventure({
1832
+ id: "ch5_act4_fifth_seal",
1833
+ name: "The Fifth Seal",
1834
+ description: "Free the Crystal Guardian and restore the fifth Stone.",
1835
+ difficulty: "hard", levelRequired: 9, energyCost: 25, hungerCost: 20, completionXp: 140,
1836
+ startRoomId: "ch5_seal_approach",
1837
+ rooms: [
1838
+ {
1839
+ id: "ch5_seal_approach", type: "narrative",
1840
+ title: "The Seal Chamber",
1841
+ text: [
1842
+ "Below the archives. Stone so old it looks like TIME made solid.",
1843
+ "Diagrams cover the walls. Not art — BLUEPRINTS.",
1844
+ "Six Stones in geometric arrangement. Energy flow lines. Connection points.",
1845
+ "And at the center of every diagram: one figure. Always one.",
1846
+ "\"They didn't just guard the seal. They WERE the seal.\"",
1847
+ "\"Every Stone draws power through the guardian. They're not a watchman. They're a CONDUIT.\"",
1848
+ ],
1849
+ buddyLine: "The Stones are the lock. The Beast is the prisoner. And the Guardian was the KEY. The key that never got to stop turning.",
1850
+ nextRoomId: "ch5_basilisk",
1851
+ },
1852
+ {
1853
+ id: "ch5_basilisk", type: "combat",
1854
+ title: "Crystal Basilisk",
1855
+ text: [
1856
+ "A corrupted serpent of crystal blocks the descent. Ancient as the ruins.",
1857
+ "Its crystalline scales catch the dim light. Beautiful, in the way dangerous things are.",
1858
+ "\"How long has it been down here? Did it wait because it was guarding?\"",
1859
+ "\"Or because it forgot how to leave?\"",
1860
+ ],
1861
+ buddyLine: "Another prisoner. Another creature that didn't choose this.",
1862
+ enemyId: "crystalbasilisk",
1863
+ nextRoomId: "ch5_fifth_stone",
1864
+ },
1865
+ {
1866
+ id: "ch5_fifth_stone", type: "narrative",
1867
+ title: "The Fifth Stone",
1868
+ text: [
1869
+ "The Ruins' Harmony Stone. Different — CARVED, not natural. Shaped by hands.",
1870
+ "This was the FIRST Stone. The prototype.",
1871
+ "Before it — the Crystal Guardian. The oldest, largest construct ever built.",
1872
+ "Corrupted crystal chains bind it. It can't move. Can barely THINK.",
1873
+ "But its eyes track you as you enter.",
1874
+ "At its feet: \"I was built to protect. I could not protect myself.\"",
1875
+ "The same words from the cave Golem. Because this is the ORIGINAL.",
1876
+ ],
1877
+ buddyLine: "You were the first protector. And you've been down here alone since the beginning. That ends today.",
1878
+ choices: [
1879
+ { label: "Free the Guardian", nextRoomId: "ch5_boss" },
1880
+ ],
1881
+ },
1882
+ {
1883
+ id: "ch5_boss", type: "boss",
1884
+ title: "The Crystal Guardian",
1885
+ text: [
1886
+ "The Guardian fights to protect the Stone. Even corrupted. Even in agony.",
1887
+ "Crystal beams. Shield walls. The strongest thing you've faced.",
1888
+ "Mid-fight — its crystal heart flickers. A word etched inside. Glowing.",
1889
+ "The Guardian's name. The one scratched off every tablet.",
1890
+ "It's been carrying its own erased name inside its heart. This whole time.",
1891
+ "The one thing the world tried to forget, the construct KEPT.",
1892
+ ],
1893
+ buddyLine: "I can see the name. In its heart. The world forgot — but it DIDN'T.",
1894
+ enemyId: "crystalguardian",
1895
+ },
1896
+ ],
1897
+ });
1898
+ }
1899
+ // ─── Chapter 6: Into the Fire ────────────────────────────────
1900
+ // Act 1: "The Call" — Scene (4 rooms)
1901
+ function buildCh6Act1() {
1902
+ return makeAdventure({
1903
+ id: "ch6_act1_call",
1904
+ name: "The Call",
1905
+ description: "An old rival returns. No ego. No posing. This is serious.",
1906
+ difficulty: "boss", levelRequired: 11, energyCost: 0, hungerCost: 0, completionXp: 0,
1907
+ isScene: true,
1908
+ startRoomId: "ch6_shelly_concern",
1909
+ rooms: [
1910
+ {
1911
+ id: "ch6_shelly_concern", type: "narrative",
1912
+ title: "Shelly's Concern",
1913
+ npcsPresent: ["shelly"],
1914
+ text: [
1915
+ "Back at Shelly's grotto. You bring her the figurine.",
1916
+ "She holds it. Her hands go perfectly, completely still.",
1917
+ "She stares at it for a very long time. Turns it over. Sees the name.",
1918
+ "Her eyes close. Just for a moment. A century of grief in one breath.",
1919
+ "\"...I knew them. Before.\"",
1920
+ "\"The last Stone is in the Molten Core. The volcano.\"",
1921
+ "\"It's the most dangerous biome. Even before the corruption.\"",
1922
+ "\"I cannot go with you. My old bones would...\"",
1923
+ "She pauses. \"...melt. That was a joke. Tortoises don't melt.\"",
1924
+ "\"You'll need help. Powerful help.\"",
1925
+ "She looks at the cave entrance. \"It's coming.\"",
1926
+ ],
1927
+ buddyLine: "Shelly... are you okay?",
1928
+ nextRoomId: "ch6_shadow",
1929
+ },
1930
+ {
1931
+ id: "ch6_shadow", type: "narrative",
1932
+ title: "A Shadow Overhead",
1933
+ text: [
1934
+ "Outside Shelly's cave. The sky darkens. Not clouds — WINGS.",
1935
+ "A trail of fire across the sky. Heading straight for you. Fast. Desperate.",
1936
+ "No loops. No spins. No showing off.",
1937
+ "BLAZE lands. Hard. Skids. No ring of fire. No pose. No reflection check.",
1938
+ "Just a phoenix. Standing in the dust. Breathing hard.",
1939
+ "Wings singed at the edges. Eyes that have seen something TERRIBLE.",
1940
+ ],
1941
+ buddyLine: "...Blaze? What happened to you?",
1942
+ nextRoomId: "ch6_blaze_returns",
1943
+ },
1944
+ {
1945
+ id: "ch6_blaze_returns", type: "narrative",
1946
+ title: "Blaze Returns",
1947
+ npcsPresent: ["blaze"],
1948
+ text: [
1949
+ "Blaze folds his wings. Slowly. Like they're heavy.",
1950
+ "\"...I felt it.\" His voice is different. Quieter. Raw.",
1951
+ "\"The Shadow Beast. Pressing against the seal.\"",
1952
+ "\"I was at the volcano. ALONE. Because I'm an idiot.\"",
1953
+ "\"And I felt it PUSH. Like something massive leaning against a cracking door.\"",
1954
+ "\"It knew I was there. It LAUGHED.\"",
1955
+ "He looks at you. No smirk. No bravado. Just truth.",
1956
+ "\"I can't do this alone. I thought I could. I was wrong.\"",
1957
+ "\"...I need your help.\"",
1958
+ "The hardest five words Blaze has ever said.",
1959
+ "Your buddy extends a paw. No gloating. No \"I told you so.\"",
1960
+ "\"Let's go.\"",
1961
+ "Blaze stares at the paw. Takes it.",
1962
+ ],
1963
+ buddyLine: "No I-told-you-so. No lecture. That's not what friends do.",
1964
+ nextRoomId: "ch6_descent",
1965
+ },
1966
+ {
1967
+ id: "ch6_descent", type: "narrative",
1968
+ title: "The Descent",
1969
+ npcsPresent: ["blaze"],
1970
+ text: [
1971
+ "Heading to the volcano together. Side by side.",
1972
+ "\"You're not going to say 'I told you so'?\"",
1973
+ "\"Nah. You already know.\"",
1974
+ "\"...yeah. I do.\"",
1975
+ "Comfortable silence. The first real one between them.",
1976
+ "\"For what it's worth, you were right. On the mountain.\"",
1977
+ "\"About what?\"",
1978
+ "\"About the... being a team... thing.\"",
1979
+ "\"Wow. That was almost a sentence.\"",
1980
+ "\"SHUT UP. I'm trying.\"",
1981
+ "The volcano looms ahead. Heat shimmers. The sky glows orange.",
1982
+ ],
1983
+ buddyLine: "He asked for help. That took more courage than anything he did on that mountain.",
1984
+ },
1985
+ ],
1986
+ });
1987
+ }
1988
+ // Act 2: "The Molten Path" — Exploration (5 rooms)
1989
+ function buildCh6Act2() {
1990
+ return makeAdventure({
1991
+ id: "ch6_act2_molten_path",
1992
+ name: "The Molten Path",
1993
+ description: "Descend into the volcano with Blaze at your side.",
1994
+ difficulty: "boss", levelRequired: 11, energyCost: 25, hungerCost: 20, completionXp: 50,
1995
+ startRoomId: "ch6_caldera",
1996
+ rooms: [
1997
+ {
1998
+ id: "ch6_caldera", type: "narrative",
1999
+ title: "The Caldera's Edge",
2000
+ npcsPresent: ["blaze"],
2001
+ text: [
2002
+ "The volcano's rim. Below: a lake of magma. The heat is staggering.",
2003
+ "Even the AIR shimmers. And Blaze — a FIRE PHOENIX — is SWEATING.",
2004
+ "\"Okay. This is hot.\" He stares at the magma.",
2005
+ "\"You're a PHOENIX.\"",
2006
+ "\"This is hot even for ME.\"",
2007
+ "\"You are LITERALLY made of fire.\"",
2008
+ "\"Yes, and this fire is HOTTER than my fire, and I'm having a CRISIS about it.\"",
2009
+ "\"So for the first time... YOU'RE the one who's uncomfortable.\"",
2010
+ "\"Don't enjoy this.\"",
2011
+ "Your buddy, very obviously enjoying it: \"Wouldn't dream of it.\"",
2012
+ ],
2013
+ buddyLine: "A fire phoenix sweating in a volcano. I want this moment recorded. For POSTERITY.",
2014
+ nextRoomId: "ch6_lava_tubes",
2015
+ },
2016
+ {
2017
+ id: "ch6_lava_tubes", type: "combat",
2018
+ title: "The Lava Tubes",
2019
+ text: [
2020
+ "Descending through volcanic tubes. Walls glow orange. Magma bubbles in rivulets.",
2021
+ "\"I'll take point!\" Blaze steps forward. \"Fire can't hurt—\"",
2022
+ "A gout of lava erupts from a vent. Catches his wing. \"—OW.\"",
2023
+ "He stares at his singed feather. The AUDACITY of fire burning a phoenix.",
2024
+ "\"HOTTER fire can hurt. This is DISCRIMINATION.\"",
2025
+ "An Ember Fox lunges from behind a formation!",
2026
+ "\"Welcome to how the rest of us live.\"",
2027
+ "\"I don't like it. This is TERRIBLE.\"",
2028
+ ],
2029
+ buddyLine: "The great Blaze, singed by fire. There's a metaphor in there somewhere.",
2030
+ enemyId: "emberfox",
2031
+ nextRoomId: "ch6_bridge",
2032
+ },
2033
+ {
2034
+ id: "ch6_bridge", type: "narrative",
2035
+ title: "The Obsidian Bridge",
2036
+ npcsPresent: ["blaze"],
2037
+ text: [
2038
+ "A natural bridge of cooled obsidian spans a magma river. Narrow. Creaking.",
2039
+ "\"I'll fly across. You...\"",
2040
+ "Blaze stops. Looks at your buddy. Positions himself at wing's reach. Ready.",
2041
+ "He doesn't OFFER to catch them. He just makes sure he CAN.",
2042
+ "Halfway across, the bridge shifts. Your buddy stumbles.",
2043
+ "Blaze's wing is under their arm before they fall. \"GOTCHA.\"",
2044
+ "\"Thanks.\"",
2045
+ "\"Forget it. Never happened.\"",
2046
+ "But it did. And he was READY. The whole time.",
2047
+ ],
2048
+ buddyLine: "He didn't say he'd catch me. He just made sure he COULD. That's worth more than any promise.",
2049
+ nextRoomId: "ch6_forge",
2050
+ },
2051
+ {
2052
+ id: "ch6_forge", type: "treasure",
2053
+ title: "The Ancient Forge",
2054
+ npcsPresent: ["blaze"],
2055
+ text: [
2056
+ "THE FORGE. Where the first guardians created the Stones.",
2057
+ "Vast chamber. Ancient anvils. Crystal hammers. Molds shaped like Harmony Stones.",
2058
+ "Blaze goes quiet. Touches a hammer cold for centuries.",
2059
+ "\"They stood RIGHT HERE. And made something that held the world together.\"",
2060
+ "On the anvil: \"If found, please return to Shelly. She worries.\"",
2061
+ "\"...how old IS Shelly?\"",
2062
+ "\"Old enough to have been HERE. When they FORGED them.\"",
2063
+ "\"I need to sit down.\"",
2064
+ "Something gleams in the cooling magma nearby...",
2065
+ ],
2066
+ buddyLine: "This is where it all began. The Stones, the seal, the sacrifice... and now us.",
2067
+ lootPool: ["dragon_fang", "flame_scepter", "dragon_helm", "flame_treads", "phoenix_crest"],
2068
+ nextRoomId: "ch6_vent_garden",
2069
+ },
2070
+ {
2071
+ id: "ch6_vent_garden", type: "rest",
2072
+ title: "The Vent Garden",
2073
+ npcsPresent: ["blaze"],
2074
+ text: [
2075
+ "Volcanic vents create a garden of heat-loving plants. Flame flowers. Ember moss.",
2076
+ "Blaze creates a controlled campfire. \"Rest up. The Drake won't go down easy.\"",
2077
+ "They sit together. Neither speaks. The silence is comfortable now.",
2078
+ "\"Blaze?\"",
2079
+ "\"Yeah?\"",
2080
+ "\"I'm glad you came back.\"",
2081
+ "He doesn't answer immediately. His fire flickers. Just once.",
2082
+ "\"...yeah. Me too.\"",
2083
+ "He says it so quietly it could be the crackle of the campfire. But it's not.",
2084
+ ],
2085
+ buddyLine: "Rest. We'll need it.",
2086
+ healAmount: 25,
2087
+ },
2088
+ ],
2089
+ });
2090
+ }
2091
+ // Act 3: "The Phoenix's Truth" — Scene (3 rooms)
2092
+ function buildCh6Act3() {
2093
+ return makeAdventure({
2094
+ id: "ch6_act3_truth",
2095
+ name: "The Phoenix's Truth",
2096
+ description: "Blaze tells the truth. The real truth.",
2097
+ difficulty: "boss", levelRequired: 11, energyCost: 0, hungerCost: 0, completionXp: 0,
2098
+ isScene: true,
2099
+ startRoomId: "ch6_forge_scene",
2100
+ rooms: [
2101
+ {
2102
+ id: "ch6_forge_scene", type: "narrative",
2103
+ title: "The Forge",
2104
+ npcsPresent: ["blaze"],
2105
+ text: [
2106
+ "The real forge. Deep in the volcano's heart. The Great Anvil where the first Stone was struck.",
2107
+ "Even Blaze goes quiet here. His fire dims to almost nothing. Respectful.",
2108
+ "\"They stood right HERE. And made something that held the world together for a THOUSAND YEARS.\"",
2109
+ "On the anvil again: \"If found, please return to Shelly. She worries.\"",
2110
+ "\"She wasn't just alive. She was one of THEM. The first buddies.\"",
2111
+ "\"And she let one of her FRIENDS volunteer for 'forever.'\"",
2112
+ ],
2113
+ buddyLine: "Shelly has been sitting on that rock for FOUR HUNDRED YEARS. While her friend was alone in the dark.",
2114
+ nextRoomId: "ch6_confession",
2115
+ },
2116
+ {
2117
+ id: "ch6_confession", type: "narrative",
2118
+ title: "Blaze's Confession",
2119
+ npcsPresent: ["blaze"],
2120
+ text: [
2121
+ "Blaze stares at the ancient molds. Something building inside him.",
2122
+ "\"You want to know why I wanted the Stones?\"",
2123
+ "He doesn't wait for an answer.",
2124
+ "\"I'm a phoenix. I die. I'm reborn. Over and over. That's the deal.\"",
2125
+ "\"Every time I come back, the world is different.\"",
2126
+ "\"The trees have grown. The rivers have moved.\"",
2127
+ "\"And everyone I knew... is gone.\"",
2128
+ "His flames dim. Low. Almost gone.",
2129
+ "\"I had a friend once. A fox. Quick. Clever. Laughed at EVERYTHING.\"",
2130
+ "\"She called me 'Sparky.' I HATED it. Told her so every day.\"",
2131
+ "\"Then I died. A rockfall. Stupid way to go.\"",
2132
+ "\"When I came back... she was old. Gray.\"",
2133
+ "\"She looked at me and she...\"",
2134
+ "His voice breaks.",
2135
+ "\"She didn't recognize me.\"",
2136
+ "\"'I had a friend named that,' she said. 'A long time ago.'\"",
2137
+ "\"I thought if I had the Stones — if I had POWER—\"",
2138
+ "\"Maybe people would stay. Maybe they'd remember me.\"",
2139
+ "\"Maybe I wouldn't have to introduce myself to someone I've LOVED every time I opened my eyes.\"",
2140
+ "He laughs. Hollow. \"Stupid, right?\"",
2141
+ ],
2142
+ buddyLine: "...",
2143
+ nextRoomId: "ch6_response",
2144
+ },
2145
+ {
2146
+ id: "ch6_response", type: "narrative",
2147
+ title: "The Response",
2148
+ npcsPresent: ["blaze"],
2149
+ text: [
2150
+ "Your buddy is quiet for a long time. The fire crackles.",
2151
+ "\"It's not stupid, Blaze.\"",
2152
+ "\"It's lonely.\"",
2153
+ "\"And you don't have to be lonely anymore.\"",
2154
+ "\"We're here. Pip is here. Shelly is here. We're not going anywhere.\"",
2155
+ "\"And even if — one day — you outlive us all...\"",
2156
+ "\"You'll remember THIS. This fire. This moment. Us.\"",
2157
+ "\"That a mouse with a broken umbrella thought you were amazing.\"",
2158
+ "\"That a tortoise called you 'loud and dramatic' like it was a compliment.\"",
2159
+ "\"That's not nothing, Blaze. That's EVERYTHING.\"",
2160
+ "His flames return. Slowly. Brighter than before.",
2161
+ "\"...you're annoyingly good at this.\"",
2162
+ "\"At what?\"",
2163
+ "\"Being... kind. Or whatever. Shut up.\"",
2164
+ "He's smiling. Through tears that evaporate before they fall.",
2165
+ "\"...thank you. For being my friend.\"",
2166
+ ],
2167
+ buddyLine: "He's not a jerk. He never was. He was just scared of losing people. Scared of being forgotten.",
2168
+ },
2169
+ ],
2170
+ });
2171
+ }
2172
+ // Act 4: "The Last Stone" — Dungeon (4 rooms)
2173
+ function buildCh6Act4() {
2174
+ return makeAdventure({
2175
+ id: "ch6_act4_last_stone",
2176
+ name: "The Last Stone",
2177
+ description: "Together. The final Stone. The Inferno Drake.",
2178
+ difficulty: "boss", levelRequired: 11, energyCost: 30, hungerCost: 25, completionXp: 170,
2179
+ startRoomId: "ch6_magma_heart",
2180
+ rooms: [
2181
+ {
2182
+ id: "ch6_magma_heart", type: "narrative",
2183
+ title: "The Magma Heart",
2184
+ npcsPresent: ["blaze"],
2185
+ text: [
2186
+ "The deepest chamber. Magma everywhere. The heat is nearly unbearable.",
2187
+ "Blaze extends a wing. A shield of flame that turns the worst heat aside.",
2188
+ "\"I've got you. Just this once.\"",
2189
+ "\"Just this once, huh?\"",
2190
+ "\"...maybe more than once. Don't push it.\"",
2191
+ "\"Was that a PUN? In a VOLCANO?\"",
2192
+ "\"I panicked.\"",
2193
+ "\"You ALWAYS panic with your words. Your fighting is much better.\"",
2194
+ "\"Thanks?\"",
2195
+ "\"That was a compliment. I'm LEARNING.\"",
2196
+ ],
2197
+ buddyLine: "He's protecting me with his own fire. The guy who couldn't say 'good' is shielding me with his WINGS.",
2198
+ nextRoomId: "ch6_sentinels",
2199
+ },
2200
+ {
2201
+ id: "ch6_sentinels", type: "combat",
2202
+ title: "Flame Sentinels",
2203
+ text: [
2204
+ "Corrupted fire creatures guard the approach. Ember foxes driven to fury.",
2205
+ "Blaze fights alongside you — coordinating, not competing.",
2206
+ "\"LEFT! DODGE LEFT!\"",
2207
+ "Your buddy dodges right. \"...I would have dodged left but you DO you.\"",
2208
+ "\"It WORKED.\"",
2209
+ "\"It worked INELEGANTLY.\"",
2210
+ "\"Elegance is your department. Winning is mine.\"",
2211
+ "\"...fair.\"",
2212
+ ],
2213
+ buddyLine: "We're actually good at this. Together. Who knew?",
2214
+ enemyId: "emberfox",
2215
+ nextRoomId: "ch6_boss",
2216
+ },
2217
+ {
2218
+ id: "ch6_boss", type: "boss",
2219
+ title: "The Inferno Drake",
2220
+ npcsPresent: ["blaze"],
2221
+ text: [
2222
+ "The volcano's deepest point. Magma pools surround the final Harmony Stone.",
2223
+ "A drake of living flame — massive, ancient, FURIOUS — bound to the Stone.",
2224
+ "It's twice Blaze's size. Its fire burns WHITE.",
2225
+ "Blaze spreads his wings. No hesitation. No bravado.",
2226
+ "\"Together. Like we should have been from the start.\"",
2227
+ "\"What's the worst that could happen?\"",
2228
+ "\"BLAZE.\"",
2229
+ "\"Sorry. Couldn't resist.\"",
2230
+ "\"Pip has a WHOLE THING about this!\"",
2231
+ "\"FIGHT FIRST! LECTURE LATER!\"",
2232
+ ],
2233
+ buddyLine: "The last Stone. And I wouldn't want to be here with anyone else. ...Don't tell Pip.",
2234
+ enemyId: "infernodrake",
2235
+ nextRoomId: "ch6_six_stones",
2236
+ },
2237
+ {
2238
+ id: "ch6_six_stones", type: "narrative",
2239
+ title: "Six Stones",
2240
+ npcsPresent: ["blaze"],
2241
+ text: [
2242
+ "The Drake falls. Freed. The final Stone stabilizes. Deep warm red. Steady.",
2243
+ "The volcano calms. The heat eases.",
2244
+ "\"Six Stones. Six biomes. We actually DID it.\"",
2245
+ "Blaze looks at you. Respect. Friendship. Gratitude.",
2246
+ "\"Thank you. For not giving up on me.\"",
2247
+ "He extends a wing. Your buddy bumps it with a paw.",
2248
+ "Then — the ground TREMBLES. Not the volcano. Deeper.",
2249
+ "All six Stones pulse simultaneously. You FEEL it. Like a heartbeat that skips.",
2250
+ "A crack in the air. Darkness. Just for a second. Then gone.",
2251
+ "\"...that wasn't the volcano, was it?\"",
2252
+ "\"No. The Beast.\"",
2253
+ "\"All six Stones are restored. But without a guardian... the seal won't hold.\"",
2254
+ "\"We need to find the Stranger. NOW.\"",
2255
+ ],
2256
+ buddyLine: "Six Stones restored. But the Stranger was right. This isn't over. It's just beginning.",
2257
+ },
2258
+ ],
2259
+ });
2260
+ }
2261
+ // ─── Chapter 7: The Stranger Revealed ────────────────────────
2262
+ // Act 1: "The Rift" — Scene (4 rooms)
2263
+ function buildCh7Act1() {
2264
+ return makeAdventure({
2265
+ id: "ch7_act1_rift",
2266
+ name: "The Rift",
2267
+ description: "All allies acknowledge what's coming. The point of no return.",
2268
+ difficulty: "boss", levelRequired: 13, energyCost: 0, hungerCost: 0, completionXp: 0,
2269
+ isScene: true,
2270
+ startRoomId: "ch7_gathering",
2271
+ rooms: [
2272
+ {
2273
+ id: "ch7_gathering", type: "narrative",
2274
+ title: "The Gathering Before the Dark",
2275
+ npcsPresent: ["shelly", "pip"],
2276
+ text: [
2277
+ "Shelly's grotto. The ground has been trembling for days.",
2278
+ "\"All six Stones are restored. The seal should be holding.\"",
2279
+ "She pauses. \"It's not.\"",
2280
+ "\"Something is keeping it open from inside. Like a hand in a closing door.\"",
2281
+ "Pip: \"The Stranger? From the ruins?\"",
2282
+ "Shelly goes very quiet. \"Yes. And I think... I think I know who they are.\"",
2283
+ "\"A rift has opened. Pure darkness. The Shadow Realm.\"",
2284
+ "\"You need to go inside.\"",
2285
+ "Pip gulps. \"We're going into a PRISON? For a BEAST? ...I'll pack extra cheese.\"",
2286
+ "\"Not you, Pip. This one's too dangerous.\"",
2287
+ "Pip's chin comes up. \"I can—\"",
2288
+ "\"Someone needs to be here. In case we don't come back. Someone who REMEMBERS.\"",
2289
+ "Pip's eyes fill. \"Just... come back. PROMISE me.\"",
2290
+ ],
2291
+ buddyLine: "I promise, Pip.",
2292
+ nextRoomId: "ch7_shelly_secret",
2293
+ },
2294
+ {
2295
+ id: "ch7_shelly_secret", type: "narrative",
2296
+ title: "Shelly's Secret",
2297
+ npcsPresent: ["shelly"],
2298
+ text: [
2299
+ "Pip leaves to pack. Shelly calls you close.",
2300
+ "\"There's something I haven't told you. Something I should have said from the beginning.\"",
2301
+ "\"The Guardian who broke the Stones... I knew them. Before.\"",
2302
+ "\"We were friends. Close friends. For years.\"",
2303
+ "\"When they volunteered... when they stepped forward...\"",
2304
+ "\"I said nothing. I WATCHED them step forward and I said NOTHING.\"",
2305
+ "\"I could have volunteered instead. I could have shared the burden.\"",
2306
+ "\"I didn't. I was afraid.\"",
2307
+ "\"And they spent centuries alone because of my cowardice.\"",
2308
+ "Her voice breaks. Shelly's calm shatters.",
2309
+ "\"If there's any chance of saving them... any chance at ALL...\"",
2310
+ "\"Please. Bring them home.\"",
2311
+ ],
2312
+ buddyLine: "I will. Whatever it takes.",
2313
+ nextRoomId: "ch7_edge",
2314
+ },
2315
+ {
2316
+ id: "ch7_edge", type: "narrative",
2317
+ title: "The Edge of Darkness",
2318
+ text: [
2319
+ "The rift. A tear in REALITY. Not in rock or sky — in the fabric of the world.",
2320
+ "Through it: nothing. An absence so complete that looking at it hurts.",
2321
+ "The six Harmony Stones pulse in your pockets. They KNOW what's behind that rift.",
2322
+ "A feather drifts down from above. Burning. Familiar.",
2323
+ "Blaze lands. Quietly. Nods. No words.",
2324
+ "They're past words now.",
2325
+ ],
2326
+ buddyLine: "This is it. No more Stones to restore. Just the truth. On the other side of nothing.",
2327
+ nextRoomId: "ch7_into_shadow",
2328
+ },
2329
+ {
2330
+ id: "ch7_into_shadow", type: "narrative",
2331
+ title: "Into the Shadow",
2332
+ npcsPresent: ["blaze"],
2333
+ text: [
2334
+ "Stepping through the rift. Reality INVERTS. Sound stops. Light doesn't work right.",
2335
+ "The Shadow Realm.",
2336
+ "Not empty. FULL. Of shadows. Of memories. Of things that were and things that will never be.",
2337
+ "\"This is everything the Beast has consumed. Every happy thing, twisted.\"",
2338
+ "Your buddy shivers. Not from cold — from WRONGNESS.",
2339
+ "\"Stay close, Blaze.\"",
2340
+ "Blaze's fire flickers. The darkness PUSHES against it. He burns brighter. Stubbornly.",
2341
+ "\"Wasn't planning on leaving.\"",
2342
+ ],
2343
+ buddyLine: "The Beast feeds on happiness. And this is its COLLECTION.",
2344
+ },
2345
+ ],
2346
+ });
2347
+ }
2348
+ // Act 2: "The Twisted World" — Exploration (5 rooms)
2349
+ function buildCh7Act2() {
2350
+ return makeAdventure({
2351
+ id: "ch7_act2_twisted",
2352
+ name: "The Twisted World",
2353
+ description: "Familiar places made WRONG. The Shadow Realm distorts everything.",
2354
+ difficulty: "boss", levelRequired: 13, energyCost: 25, hungerCost: 20, completionXp: 50,
2355
+ startRoomId: "ch7_inverted",
2356
+ rooms: [
2357
+ {
2358
+ id: "ch7_inverted", type: "narrative",
2359
+ title: "The Inverted Forest",
2360
+ text: [
2361
+ "A forest. YOUR forest. From Ch1. Every tree, every path, every stone. But WRONG.",
2362
+ "Trees grow downward. Rain falls up. The brook flows backward.",
2363
+ "The clearing where you fought the squirrel — empty. Dead. No life.",
2364
+ "Even the corruption is different here. It doesn't infect — it ERASES.",
2365
+ "Your buddy touches a tree. Their hand goes through it.",
2366
+ "\"This is home. But broken.\"",
2367
+ "\"The butterfly. The deer. The brook. All of it. GONE.\"",
2368
+ ],
2369
+ buddyLine: "This is what the Beast wants for EVERYTHING. Not corruption. NOTHING.",
2370
+ nextRoomId: "ch7_shadow_fox",
2371
+ },
2372
+ {
2373
+ id: "ch7_shadow_fox", type: "combat",
2374
+ title: "Shadow Fox",
2375
+ text: [
2376
+ "A creature wearing a familiar shape. Shadow Fox. Eyes burning with void-light.",
2377
+ "It fights like a MEMORY of an animal. Predictable. Hollow.",
2378
+ "\"It's not real. It's an echo. The Beast is testing us.\"",
2379
+ "Blaze: \"Then let's pass the test.\"",
2380
+ "After the fight, a whisper: \"Thank you for remembering me as I was.\"",
2381
+ ],
2382
+ buddyLine: "The real things are OUT THERE. Alive. Safe. Because we SAVED them.",
2383
+ enemyId: "shadowfox",
2384
+ nextRoomId: "ch7_echo_caves",
2385
+ },
2386
+ {
2387
+ id: "ch7_echo_caves", type: "narrative",
2388
+ title: "The Echo of the Caves",
2389
+ npcsPresent: ["blaze"],
2390
+ text: [
2391
+ "A shadow version of Shelly's grotto. The moss doesn't glow — it ABSORBS light.",
2392
+ "The rock is there. Worn smooth. Shelly's spot. But Shelly isn't here.",
2393
+ "On the ground: a shadow of the moss blanket she tucked over you.",
2394
+ "Your buddy crouches. Reaches for it. It dissolves.",
2395
+ "\"She was here too. In this memory world. Sitting. Alone. For centuries.\"",
2396
+ "Blaze: \"Two people. Both alone. When they could have been together.\"",
2397
+ "\"Why do people DO that? Push each other away when they need each other most?\"",
2398
+ "Neither has a good answer.",
2399
+ ],
2400
+ buddyLine: "The saddest part of this place isn't the monsters. It's the empty chairs.",
2401
+ nextRoomId: "ch7_echo_mountain",
2402
+ },
2403
+ {
2404
+ id: "ch7_echo_mountain", type: "narrative",
2405
+ title: "The Echo of the Mountain",
2406
+ npcsPresent: ["blaze"],
2407
+ text: [
2408
+ "Shadow version of the mountain summit. Scorch marks on the ground. Blaze's marks.",
2409
+ "He stares at them. \"I was such a JERK.\"",
2410
+ "\"...you were.\"",
2411
+ "\"You could DENY it.\"",
2412
+ "\"I COULD.\"",
2413
+ "\"But you DID come back. When it mattered.\"",
2414
+ "\"Your feathers were adequately reflective.\"",
2415
+ "Blaze stares. Then LAUGHS. A real laugh, in this place of nothing.",
2416
+ "\"THAT'S what you remember?!\"",
2417
+ "\"I remember EVERYTHING.\"",
2418
+ "Laughter in the Shadow Realm. The Beast doesn't know what to do with that.",
2419
+ ],
2420
+ buddyLine: "The Beast feeds on sadness. So we give it the one thing it can't digest: a bad joke about feathers.",
2421
+ nextRoomId: "ch7_gate",
2422
+ },
2423
+ {
2424
+ id: "ch7_gate", type: "combat",
2425
+ title: "The Gate",
2426
+ text: [
2427
+ "A crystal basilisk guards the path deeper. Shadow energy crackles.",
2428
+ "After the fight, a brief rest. Blaze creates a tiny flame. Just for light.",
2429
+ "\"Whatever's ahead... whatever we find...\"",
2430
+ "\"We face it together.\"",
2431
+ "\"Yeah. Together.\"",
2432
+ ],
2433
+ buddyLine: "His fire is the only light in this entire realm. And he's keeping it burning. For both of us.",
2434
+ enemyId: "crystalbasilisk",
2435
+ healAmount: 20,
2436
+ },
2437
+ ],
2438
+ });
2439
+ }
2440
+ // Act 3: "Echoes of Memory" — Scene (6 rooms)
2441
+ function buildCh7Act3() {
2442
+ return makeAdventure({
2443
+ id: "ch7_act3_memory",
2444
+ name: "Echoes of Memory",
2445
+ description: "The Guardian's memories play out. The scene the entire story has been building toward.",
2446
+ difficulty: "boss", levelRequired: 13, energyCost: 0, hungerCost: 0, completionXp: 0,
2447
+ isScene: true,
2448
+ startRoomId: "ch7_memory_hall",
2449
+ rooms: [
2450
+ {
2451
+ id: "ch7_memory_hall", type: "narrative",
2452
+ title: "The Memory Hall",
2453
+ text: [
2454
+ "The shadows freeze. Time stops. Memories crystallize around you.",
2455
+ "Not YOUR memories. The GUARDIAN'S.",
2456
+ "Frozen moments from centuries past. Playing in the dark like a silent film.",
2457
+ "\"These are someone's memories. We're inside their MIND.\"",
2458
+ "The first memory begins. And it begins with hope.",
2459
+ ],
2460
+ buddyLine: "...",
2461
+ nextRoomId: "ch7_young_guardian",
2462
+ },
2463
+ {
2464
+ id: "ch7_young_guardian", type: "narrative",
2465
+ title: "The Young Guardian",
2466
+ text: [
2467
+ "A memory: bright, vivid, WARM. Like stepping into sunlight.",
2468
+ "A young buddy. Small. Bright-eyed. Standing before six freshly-forged Stones.",
2469
+ "Around them: friends. The first buddies. And among them — Shelly. Young. SMILING.",
2470
+ "The young buddy steps forward. Paw raised.",
2471
+ "\"I'll watch over the seal. Forever. I promise.\"",
2472
+ "They SMILE. They MEAN it. They're so SURE.",
2473
+ "Shelly hugs them. \"Be safe. I'll visit.\"",
2474
+ "\"Every week?\"",
2475
+ "\"Every week.\"",
2476
+ "The memory fades. The warmth goes with it.",
2477
+ ],
2478
+ buddyLine: "They were so young. They didn't know. They couldn't have known.",
2479
+ nextRoomId: "ch7_long_watch",
2480
+ },
2481
+ {
2482
+ id: "ch7_long_watch", type: "narrative",
2483
+ title: "The Long Watch",
2484
+ text: [
2485
+ "Decades pass in seconds. The Guardian stands by the seal. Day after day.",
2486
+ "Shelly visits. Once. The Guardian's face LIGHTS UP. They talk for hours.",
2487
+ "Shelly visits again. Months later. They hug goodbye.",
2488
+ "The visits become years. Then...",
2489
+ "Then nothing.",
2490
+ "A century. The Guardian is still standing. Still watching.",
2491
+ "Two centuries. They sit now. They used to stand.",
2492
+ "Three centuries. They don't move for weeks at a time.",
2493
+ "The visits stopped entirely. Not gradually — they just stopped.",
2494
+ "\"Nobody came. For CENTURIES. Nobody even CHECKED.\"",
2495
+ ],
2496
+ buddyLine: "Shelly said 'every week.' She went... twice. ...Four hundred years of guilt makes sense now.",
2497
+ nextRoomId: "ch7_breaking",
2498
+ },
2499
+ {
2500
+ id: "ch7_breaking", type: "narrative",
2501
+ title: "The Breaking Point",
2502
+ text: [
2503
+ "The Guardian sits in darkness. The seal hums. And from behind it — a whisper.",
2504
+ "\"You're so tired.\"",
2505
+ "\"I know what tired feels like. We're the same, you and I. Prisoners.\"",
2506
+ "\"They forgot you. Your friends. Your SHELLY.\"",
2507
+ "\"They were RELIEVED when you volunteered. Did you notice?\"",
2508
+ "\"Let me free. I'll give you rest. Real rest.\"",
2509
+ "The Guardian closes their eyes. A single tear falls.",
2510
+ "\"...I'm so tired.\"",
2511
+ "But they don't break. Not yet. They hold. For DECADES more.",
2512
+ "Until the seal cracks. Not from the Beast.",
2513
+ "From the Guardian's GRIEF.",
2514
+ "When they shatter the Stones — it's STRATEGY. One last act of clarity.",
2515
+ "They break the world to SAVE it.",
2516
+ "And the shadow swallows them whole.",
2517
+ ],
2518
+ buddyLine: "Of course they broke. ANYONE would break. And they STILL chose to protect us. Even at the end.",
2519
+ nextRoomId: "ch7_aftermath",
2520
+ },
2521
+ {
2522
+ id: "ch7_aftermath", type: "event",
2523
+ title: "Aftermath",
2524
+ text: [
2525
+ "Silence. Long silence. The memories dissolve.",
2526
+ "Your buddy is shaking. Not from cold. From UNDERSTANDING.",
2527
+ "\"They were alone. For CENTURIES.\"",
2528
+ "\"Nobody visited. Nobody thanked them. Nobody even knew their NAME.\"",
2529
+ "\"They scratched their own name off the tablets. ERASED THEMSELVES.\"",
2530
+ "\"And when they FINALLY broke — they didn't surrender.\"",
2531
+ "\"They shattered the Stones. Their LAST act of free will was to protect us. AGAIN.\"",
2532
+ "Blaze: \"...and we thought I had it rough.\"",
2533
+ "\"I complained about being alone between rebirths. They were alone CENTURIES straight.\"",
2534
+ ],
2535
+ buddyLine: "We're coming. And we're not here to fight. We're here to bring them HOME.",
2536
+ nextRoomId: "ch7_before",
2537
+ eventChoices: [
2538
+ { label: "\"No one guards alone. Not anymore.\"", outcome: "The shadows recoil. Actually, physically RECOIL. As if the words are a weapon. Somewhere ahead, the Stranger falters. Your words reached them." },
2539
+ { label: "\"No one should bear that alone. Never again.\"", outcome: "A warmth pulses through you. From the place where promises live. Somewhere in the dark, the Stranger pauses. Someone UNDERSTANDS." },
2540
+ ],
2541
+ },
2542
+ {
2543
+ id: "ch7_before", type: "narrative",
2544
+ title: "Before the Confrontation",
2545
+ npcsPresent: ["blaze"],
2546
+ text: [
2547
+ "The path to the heart. The darkness is thickest here.",
2548
+ "Your buddy pulls out the figurine. The Guardian's figurine from Ch5.",
2549
+ "\"We're coming. And we're not here to fight you.\"",
2550
+ "\"We know what happened. We READ the journal. We SAW the memories.\"",
2551
+ "\"We're going to do what should have been done centuries ago.\"",
2552
+ "\"We're going to stand with you.\"",
2553
+ "Blaze extends a wing. Fire in the dark.",
2554
+ "\"After you.\"",
2555
+ ],
2556
+ buddyLine: "Whatever happens in there: we don't leave without them.",
2557
+ },
2558
+ ],
2559
+ });
2560
+ }
2561
+ // Act 4: "The Guardian" — Dungeon (1 room boss)
2562
+ function buildCh7Act4() {
2563
+ return makeAdventure({
2564
+ id: "ch7_act4_guardian",
2565
+ name: "The Guardian",
2566
+ description: "One fight. The most important fight in the game.",
2567
+ difficulty: "boss", levelRequired: 13, energyCost: 30, hungerCost: 25, completionXp: 230,
2568
+ startRoomId: "ch7_boss",
2569
+ rooms: [
2570
+ {
2571
+ id: "ch7_boss", type: "boss",
2572
+ title: "The Stranger",
2573
+ npcsPresent: ["stranger"],
2574
+ text: [
2575
+ "At the heart of the Shadow Realm. Before the Beast's cage.",
2576
+ "The shadows fall away. Slowly. Painfully.",
2577
+ "Beneath them — not a monster. Not a villain.",
2578
+ "A small buddy. Ancient. Tired. So, so tired.",
2579
+ "The same one from the mosaic. The mural. The memories. The figurine.",
2580
+ "\"You came. After all this time... someone came.\"",
2581
+ "\"I shattered the Stones to weaken the Beast.\"",
2582
+ "\"I broke the world. Because the world was already breaking.\"",
2583
+ "\"But the shadow... it found the cracks in me first.\"",
2584
+ "\"You have to stop me. Free me from the shadow's control.\"",
2585
+ "\"Then seal the Beast. Properly. TOGETHER.\"",
2586
+ "Dark energy surges. Their body moves against their will.",
2587
+ "\"I'm sorry... I can't control...\"",
2588
+ "\"PLEASE. I don't want to hurt you.\"",
2589
+ ],
2590
+ buddyLine: "We're not here to destroy you. We're here to bring you HOME.",
2591
+ enemyId: "thestranger",
2592
+ },
2593
+ ],
2594
+ });
2595
+ }
2596
+ // ─── Chapter 8: The Final Harmony ────────────────────────────
2597
+ // Act 1: "The Gathering" — Scene (5 rooms)
2598
+ function buildCh8Act1() {
2599
+ return makeAdventure({
2600
+ id: "ch8_act1_gathering",
2601
+ name: "The Gathering",
2602
+ description: "All allies assemble. Last moments before the final battle.",
2603
+ difficulty: "boss", levelRequired: 15, energyCost: 0, hungerCost: 0, completionXp: 0,
2604
+ isScene: true,
2605
+ startRoomId: "ch8_call",
2606
+ rooms: [
2607
+ {
2608
+ id: "ch8_call", type: "narrative",
2609
+ title: "The Call",
2610
+ npcsPresent: ["stranger"],
2611
+ text: [
2612
+ "The freed Guardian sits against a rock. Recovering. Still weak. But clear-eyed.",
2613
+ "\"The Beast knows the seal is gone. It's pushing against its prison.\"",
2614
+ "\"We have hours. Maybe less.\"",
2615
+ "\"You need to reach the Core — where the Stones were forged. The Great Anvil.\"",
2616
+ "\"It's the only place with enough energy to create a new seal.\"",
2617
+ "\"But the Beast will send everything it has to stop you.\"",
2618
+ ],
2619
+ buddyLine: "No more 'someone guards alone.' Not this time. Not EVER again.",
2620
+ nextRoomId: "ch8_pip_arrives",
2621
+ },
2622
+ {
2623
+ id: "ch8_pip_arrives", type: "narrative",
2624
+ title: "Pip Arrives",
2625
+ npcsPresent: ["pip"],
2626
+ text: [
2627
+ "A sound in the distance. Rattling. Wobbling. A very small cart pushed by a very determined mouse.",
2628
+ "PIP. Dragging a cart three times his size. Sweating. Panting. Determined.",
2629
+ "\"I BROUGHT EVERYTHING!\"",
2630
+ "\"Bandages! Berries! FIVE kinds of cheese — including emergency brie!\"",
2631
+ "He holds up a rock. THE rock. From Ch1. \"My lucky rock!\"",
2632
+ "\"Twelve potions! A map!\" He holds up the map. It's the wrong map.",
2633
+ "\"STRING! More cheese! A second lucky rock in case the first gets lonely!\"",
2634
+ "He stops. Looks at you. Small. Shaking. Standing.",
2635
+ "\"I know I can't fight. But I can make sure you're READY to fight.\"",
2636
+ "He tries to salute. Drops a cheese wheel. Salutes anyway. With FEELING.",
2637
+ "\"For the world. And for my father.\"",
2638
+ ],
2639
+ buddyLine: "Pip. 73 items?",
2640
+ nextRoomId: "ch8_shelly_guardian",
2641
+ },
2642
+ {
2643
+ id: "ch8_shelly_guardian", type: "narrative",
2644
+ title: "Shelly and the Guardian",
2645
+ npcsPresent: ["shelly", "stranger"],
2646
+ text: [
2647
+ "Shelly arrives. At her own pace. Which is glacial.",
2648
+ "She sees the Guardian. RECOGNIZES them. In the light. For the first time since forever.",
2649
+ "Four hundred years of unsaid things fill the space between them.",
2650
+ "\"I've waited a very long time to say this.\"",
2651
+ "\"I'm sorry. For not coming. For not sharing the burden. For being afraid.\"",
2652
+ "The Guardian: \"You were always the wisest of us, Shelly.\"",
2653
+ "\"Wisdom isn't much comfort at three in the morning.\"",
2654
+ "Then Shelly smiles. The smile from the memory. The one nobody alive has seen.",
2655
+ "\"I've been meditating on what to say for four hundred years.\"",
2656
+ "\"And all I've got is: let's finish this.\"",
2657
+ "Guardian: \"That'll do.\"",
2658
+ ],
2659
+ buddyLine: "...",
2660
+ nextRoomId: "ch8_blaze_moment",
2661
+ },
2662
+ {
2663
+ id: "ch8_blaze_moment", type: "narrative",
2664
+ title: "Blaze's Moment",
2665
+ npcsPresent: ["blaze"],
2666
+ text: [
2667
+ "Blaze lands. No dramatic entrance. No ring of fire. No pose. No reflection check.",
2668
+ "Just wings folding. Feet on ground. Fire steady.",
2669
+ "He looks at the group. Pip. Shelly. The Guardian. Your buddy.",
2670
+ "\"Six months ago I would have called this group a joke.\"",
2671
+ "\"A merchant mouse. A four-hundred-year-old tortoise. A freed shadow prisoner.\"",
2672
+ "He looks at your buddy. \"And... whatever you are.\"",
2673
+ "\"Your friend.\"",
2674
+ "Blaze blinks. Opens his beak. Closes it.",
2675
+ "\"...yeah. My friend.\"",
2676
+ "He doesn't check a single reflective surface. Not ONCE.",
2677
+ ],
2678
+ buddyLine: "He didn't check his reflection. Not once. ...That might be the biggest thing that's happened today.",
2679
+ nextRoomId: "ch8_before_descent",
2680
+ },
2681
+ {
2682
+ id: "ch8_before_descent", type: "narrative",
2683
+ title: "Before the Descent",
2684
+ npcsPresent: ["pip", "shelly", "blaze", "stranger"],
2685
+ text: [
2686
+ "All four NPCs. Together. Your buddy at the center.",
2687
+ "Pip: \"I've gone from selling berries to storming the underworld. My father would be SO confused.\"",
2688
+ "Shelly: \"Your father would be proud, Pip. As am I.\"",
2689
+ "Blaze: \"If we die, I just want you all to know—\"",
2690
+ "Everyone tenses.",
2691
+ "\"—my feathers look AMAZING right now.\"",
2692
+ "\"...and he's back.\"",
2693
+ "The Guardian: \"I held the seal alone for five hundred years.\"",
2694
+ "\"But I think it was always supposed to be held together.\"",
2695
+ "Your buddy takes a deep breath.",
2696
+ ],
2697
+ buddyLine: "Into the Core. One last time. Together.",
2698
+ },
2699
+ ],
2700
+ });
2701
+ }
2702
+ // Act 2: "The Gauntlet" — Dungeon (4 rooms)
2703
+ function buildCh8Act2() {
2704
+ return makeAdventure({
2705
+ id: "ch8_act2_gauntlet",
2706
+ name: "The Gauntlet",
2707
+ description: "The Beast throws echoes of every boss you've faced.",
2708
+ difficulty: "boss", levelRequired: 15, energyCost: 30, hungerCost: 25, completionXp: 80,
2709
+ startRoomId: "ch8_core_enter",
2710
+ rooms: [
2711
+ {
2712
+ id: "ch8_core_enter", type: "narrative",
2713
+ title: "Entering the Core",
2714
+ text: [
2715
+ "The Core. Where the Stones were forged. The origin of all Harmony.",
2716
+ "Every element converges. Fire and ice. Crystal and shadow. Growth and decay.",
2717
+ "The six Stones in your pack RESONATE. Singing TOGETHER for the first time.",
2718
+ "\"This is where they made the Stones. Where they made the seal.\"",
2719
+ "\"And now it's where we make a new one.\"",
2720
+ "The ground trembles. The Beast KNOWS they're here.",
2721
+ ],
2722
+ buddyLine: "Every biome. Every Stone. Every fight. It all leads here.",
2723
+ nextRoomId: "ch8_echo_treant",
2724
+ },
2725
+ {
2726
+ id: "ch8_echo_treant", type: "combat",
2727
+ title: "Echo: The Corrupted Treant",
2728
+ text: [
2729
+ "A shadow Treant rises. Not the real one — an ECHO. The Beast's mockery.",
2730
+ "Built from stolen memories of the first boss.",
2731
+ "\"It's sending our MEMORIES against us.\"",
2732
+ "\"This isn't the creature we freed. It's what the Beast WANTED it to become.\"",
2733
+ "\"We beat the real one when we were LEVEL ONE.\"",
2734
+ "After victory, a whisper: \"Thank you for remembering me as I was.\"",
2735
+ ],
2736
+ buddyLine: "We beat you in the forest, when we were scared and new. We're not scared anymore.",
2737
+ enemyId: "corruptedtreant",
2738
+ nextRoomId: "ch8_echo_golem",
2739
+ },
2740
+ {
2741
+ id: "ch8_echo_golem", type: "combat",
2742
+ title: "Echo: The Crystal Golem",
2743
+ text: [
2744
+ "Shadow Golem. Darker, stronger. The Beast digs DEEPER.",
2745
+ "\"Another echo. Another friend we freed.\"",
2746
+ "Shelly's voice: \"The dark veins! You KNOW where they are!\"",
2747
+ "She's right. You've fought this before. You KNOW this fight.",
2748
+ "After victory: the echo kneels. Nods. Dissolves.",
2749
+ "\"Every time we free one, the Beast loses a piece of itself.\"",
2750
+ ],
2751
+ buddyLine: "Shelly's Golem. The one that said 'sorry.' It's still free. This is just a shadow.",
2752
+ enemyId: "corruptedgolem",
2753
+ nextRoomId: "ch8_echo_drake",
2754
+ },
2755
+ {
2756
+ id: "ch8_echo_drake", type: "combat",
2757
+ title: "Echo: The Inferno Drake",
2758
+ text: [
2759
+ "The shadow Drake. Fire and void combined. The hardest gauntlet fight.",
2760
+ "Blaze: \"Oh GOOD. Another me-sized problem.\"",
2761
+ "He looks at the shadow. It's BIGGER than him.",
2762
+ "\"Actually it's bigger than me. That's just RUDE.\"",
2763
+ "After victory: Blaze looks at the dissolving echo.",
2764
+ "\"Rest easy. The real one's free. We'll handle it from here.\"",
2765
+ ],
2766
+ buddyLine: "Three echoes. Three memories. Three friends we freed. The Beast used them as weapons. We turned them into PROOF.",
2767
+ enemyId: "infernodrake",
2768
+ },
2769
+ ],
2770
+ });
2771
+ }
2772
+ // Act 3: "The Eye of the Storm" — Scene (4 rooms)
2773
+ function buildCh8Act3() {
2774
+ return makeAdventure({
2775
+ id: "ch8_act3_eye",
2776
+ name: "The Eye of the Storm",
2777
+ description: "The calm before the final battle. Every NPC gets their moment.",
2778
+ difficulty: "boss", levelRequired: 15, energyCost: 0, hungerCost: 0, completionXp: 0,
2779
+ isScene: true,
2780
+ startRoomId: "ch8_core_heart",
2781
+ rooms: [
2782
+ {
2783
+ id: "ch8_core_heart", type: "narrative",
2784
+ title: "The Core's Heart",
2785
+ text: [
2786
+ "A chamber of pure elemental energy. Crystals of every color.",
2787
+ "Forest green. Crystal blue. Mountain gold. Swamp silver. Ruin bronze. Volcano red.",
2788
+ "They pulse in rhythm. A heartbeat. The world's heartbeat.",
2789
+ "The Great Anvil stands in the center. Where it all began.",
2790
+ ],
2791
+ buddyLine: "No more one-person seals. We do this TOGETHER or we don't do it at all.",
2792
+ nextRoomId: "ch8_last_words",
2793
+ },
2794
+ {
2795
+ id: "ch8_last_words", type: "rest",
2796
+ title: "Last Words",
2797
+ npcsPresent: ["pip", "shelly", "blaze", "stranger"],
2798
+ text: [
2799
+ "The group rests. The last rest before the final fight.",
2800
+ "",
2801
+ "Pip steps forward. Shaking paws. Steady voice.",
2802
+ "\"Remember when you saved my cheese? That first day?\"",
2803
+ "He holds up a rock-hard cheese wheel. The SAME ONE from Ch1. He KEPT it.",
2804
+ "\"For luck.\" He puts it in your paw.",
2805
+ "",
2806
+ "Shelly places a cool hand on your paw.",
2807
+ "\"I've waited four hundred years for someone brave enough to finish this.\"",
2808
+ "\"I didn't expect them to be so small.\" She smiles. \"Or so kind.\"",
2809
+ "\"When you stood on my foot in that grotto... I knew.\"",
2810
+ "",
2811
+ "Blaze tries to fist-bump. Phoenixes don't have fists. He wing-bumps.",
2812
+ "\"When we first met, I thought you were a joke.\"",
2813
+ "\"'Your feathers are too shiny.' PATHETIC.\"",
2814
+ "\"Now I think you're the bravest person I've met. In any lifetime.\"",
2815
+ "\"...don't let it go to your head.\"",
2816
+ "",
2817
+ "The Guardian meets your eyes.",
2818
+ "\"You did what I couldn't. You asked for help.\"",
2819
+ "\"That's not weakness. That's the strongest thing there is.\"",
2820
+ ],
2821
+ buddyLine: "Everyone I've met gave me something. Pip: courage. Shelly: wisdom. Blaze: fire. Guardian: purpose. All of them: a reason to fight.",
2822
+ healAmount: 40,
2823
+ nextRoomId: "ch8_buddy_moment",
2824
+ },
2825
+ {
2826
+ id: "ch8_buddy_moment", type: "narrative",
2827
+ title: "The Buddy's Moment",
2828
+ text: [
2829
+ "Your buddy looks at everyone. REALLY looks.",
2830
+ "A merchant mouse with a cheese wheel. A tortoise who finally stopped hiding.",
2831
+ "A phoenix who learned that being known is better than being strong.",
2832
+ "A freed guardian who finally isn't alone.",
2833
+ "\"We started this alone. Just me. In a forest that went quiet.\"",
2834
+ "\"Now look at us.\"",
2835
+ "\"I'm not the strongest. I'm not the wisest. I'm not eternal.\"",
2836
+ "\"But I've got the best friends anyone could ever ask for.\"",
2837
+ "\"And THAT is why we'll win.\"",
2838
+ "\"Not because we're powerful. Because we're NOT ALONE.\"",
2839
+ ],
2840
+ buddyLine: "Look at us. A mouse, a tortoise, a phoenix, a freed guardian, and whatever I am. We're ridiculous. We're PERFECT.",
2841
+ nextRoomId: "ch8_promise",
2842
+ },
2843
+ {
2844
+ id: "ch8_promise", type: "narrative",
2845
+ title: "The Promise",
2846
+ text: [
2847
+ "Your buddy stands. Everyone stands.",
2848
+ "\"Whatever happens in there... whatever the Beast throws at us...\"",
2849
+ "\"We face it TOGETHER. No one goes alone. Not anymore.\"",
2850
+ "They look at the Guardian. \"Not EVER.\"",
2851
+ "The Guardian nods. Something in their eyes heals. The last piece.",
2852
+ "\"Not ever.\"",
2853
+ "Everyone faces the final door.",
2854
+ "Ready.",
2855
+ ],
2856
+ buddyLine: "Let's go save the world.",
2857
+ },
2858
+ ],
2859
+ });
2860
+ }
2861
+ // Act 4: "The Shadow Beast" — Dungeon (5 rooms)
2862
+ function buildCh8Act4() {
2863
+ return makeAdventure({
2864
+ id: "ch8_act4_beast",
2865
+ name: "THE SHADOW BEAST",
2866
+ description: "The final battle. Everything you've done leads to this moment.",
2867
+ difficulty: "boss", levelRequired: 15, energyCost: 35, hungerCost: 30, completionXp: 420,
2868
+ startRoomId: "ch8_boss",
2869
+ rooms: [
2870
+ {
2871
+ id: "ch8_boss", type: "boss",
2872
+ title: "THE SHADOW BEAST",
2873
+ npcsPresent: ["pip", "shelly", "blaze", "stranger"],
2874
+ text: [
2875
+ "The ground ERUPTS.",
2876
+ "Darkness incarnate tears free. A form of pure shadow — ancient as the world, hungry as the void.",
2877
+ "It has no face. No eyes. But it SEES you.",
2878
+ "The six Harmony Stones pulse. Forest green. Crystal blue. Mountain gold. Swamp silver. Ruin bronze. Volcano red.",
2879
+ "Their light flows into you. Through you.",
2880
+ "",
2881
+ "Behind you: Pip. Holding his lucky rock. Standing on a crate to be taller.",
2882
+ "Shelly. Eyes open. READY. Four hundred years of waiting, ending NOW.",
2883
+ "Blaze. Fire steady. No posing. Just STRENGTH.",
2884
+ "The Guardian. Standing. For the first time in centuries, not standing alone.",
2885
+ "",
2886
+ "\"Friendship doesn't save worlds,\" your buddy says.",
2887
+ "\"FRIENDS do. And we have a LOT of them.\"",
2888
+ "",
2889
+ "THE SHADOW BEAST AWAKENS.",
2890
+ "",
2891
+ "And you are not afraid.",
2892
+ ],
2893
+ buddyLine: "FOR PIP! FOR SHELLY! FOR BLAZE! FOR THE GUARDIAN! FOR EVERYONE!",
2894
+ enemyId: "shadowbeast",
2895
+ nextRoomId: "ch8_victory",
2896
+ },
2897
+ {
2898
+ id: "ch8_victory", type: "narrative",
2899
+ title: "Victory",
2900
+ npcsPresent: ["pip", "shelly", "blaze", "stranger"],
2901
+ text: [
2902
+ "The Beast SCREAMS. Not in pain — in RECOGNITION.",
2903
+ "It recognizes what defeated it. Not power. CONNECTION.",
2904
+ "Light pours through its form. Cracks of every color. All six.",
2905
+ "It dissolves. Not destroyed — SEALED. By all of them. Together.",
2906
+ "The Core goes quiet. The elements balance. The world takes a breath.",
2907
+ "Pip is crying. Openly. Without shame.",
2908
+ "Shelly is LAUGHING. A sound nobody has heard in centuries.",
2909
+ "Blaze's flames burn the brightest they've ever been. Pure gold.",
2910
+ "The Guardian stands tall. Touches the new seal.",
2911
+ "\"It's done. And this time... it doesn't need a guardian.\"",
2912
+ "\"This time, it has all of us.\"",
2913
+ ],
2914
+ buddyLine: "It has all of us.",
2915
+ nextRoomId: "ch8_after",
2916
+ },
2917
+ {
2918
+ id: "ch8_after", type: "narrative",
2919
+ title: "After",
2920
+ npcsPresent: ["pip", "shelly", "blaze", "stranger"],
2921
+ text: [
2922
+ "Walking home. Together. The world is golden with new light.",
2923
+ "Pip: \"What do I do NOW? I prepared 73 items. The apocalypse only took four.\"",
2924
+ "Shelly: \"The world still needs cheese.\" \"...the world needs cheese?\" \"ALWAYS.\"",
2925
+ "Blaze: \"I'm going to travel. But I'll visit.\" He MEANS it. For the first time.",
2926
+ "Guardian: \"I'm going to rest. For real. In the sun. With friends.\"",
2927
+ "They look at each other. Then LAUGH.",
2928
+ "Not because anything is funny. Because everything is wonderful.",
2929
+ "\"Same time next year?\" Pip asks.",
2930
+ "\"Same time next year.\"",
2931
+ "\"I'll bring 100 items.\"",
2932
+ "\"Please don't.\"",
2933
+ "\"TOO LATE. ALREADY PLANNING.\"",
2934
+ ],
2935
+ buddyLine: "We saved the world with cheese, sarcasm, fire, forgiveness, and stubbornness. I wouldn't change a thing.",
2936
+ nextRoomId: "ch8_walk",
2937
+ },
2938
+ {
2939
+ id: "ch8_walk", type: "narrative",
2940
+ title: "The Buddy's Walk",
2941
+ text: [
2942
+ "One last room. Just the buddy. Walking home.",
2943
+ "The forest is SINGING. Birds. Brook. Wind in leaves. Everything that went missing is BACK.",
2944
+ "The squirrel from Ch1 is in the clearing. Collecting acorns. Normally. Happily.",
2945
+ "A butterfly drifts down. Lands on your buddy's nose.",
2946
+ "Just like the very first moment.",
2947
+ "This time, they don't sneeze.",
2948
+ "They just smile.",
2949
+ "The golden light. The singing forest. The butterfly on their nose.",
2950
+ ],
2951
+ buddyLine: "Not bad. ...For a buddy.",
2952
+ nextRoomId: "ch8_post",
2953
+ },
2954
+ {
2955
+ id: "ch8_post", type: "narrative",
2956
+ title: "Post-Credits",
2957
+ text: [
2958
+ "Pip's shop. A new item on the shelf. Prominent. Lit by a tiny candle.",
2959
+ "\"Lucky Rock — 0g — NOT FOR SALE. Sentimental value.\"",
2960
+ "Below it, a note in Pip's handwriting:",
2961
+ "\"To whoever saved the world:\"",
2962
+ "\"Thank you. The cheese is on the house.\"",
2963
+ "\"— Pip\"",
2964
+ "\"P.S. I'm at 73 items now. 74 if you count gratitude.\"",
2965
+ "\"P.P.S. I count gratitude.\"",
2966
+ "Somewhere outside, birds are singing.",
2967
+ "And a tiny mouse is counting his stock. Not because he's scared.",
2968
+ "Because his father taught him how.",
2969
+ ],
2970
+ buddyLine: "The end. ...But the world goes on.",
2971
+ },
2972
+ ],
2973
+ });
2974
+ }
2975
+ // ─── Main Story Chain ────────────────────────────────────────
2976
+ const MAIN_STORY = {
2977
+ id: "shattered_harmony",
2978
+ name: "The Shattered Harmony",
2979
+ description: "The world's Harmony Stones are shattered. Restore them before it's too late.",
2980
+ type: "main",
2981
+ chapters: [
2982
+ {
2983
+ id: "ch1_whispers",
2984
+ name: "Ch.1: Whispers in the Woods",
2985
+ description: "The forest animals are acting strange. Something is wrong with the Harmony Stone.",
2986
+ prerequisites: [],
2987
+ levelRequired: 1,
2988
+ storyFlag: "ch1_complete",
2989
+ rewards: { xp: 50, gold: 30 },
2990
+ acts: [
2991
+ { id: "ch1_act1", name: "A Normal Day", type: "scene", buildAdventure: buildCh1Act1 },
2992
+ { id: "ch1_act2", name: "Something's Wrong", type: "exploration", buildAdventure: buildCh1Act2 },
2993
+ { id: "ch1_act3", name: "The Corrupted Grove", type: "dungeon", buildAdventure: buildCh1Act3 },
2994
+ { id: "ch1_act4", name: "After the Storm", type: "scene", buildAdventure: buildCh1Act4 },
2995
+ ],
2996
+ },
2997
+ {
2998
+ id: "ch2_echoes",
2999
+ name: "Ch.2: Echoes Underground",
3000
+ description: "The cave crystals grow wild. An ancient tortoise holds the key.",
3001
+ prerequisites: ["ch1_whispers"],
3002
+ levelRequired: 3,
3003
+ storyFlag: "ch2_complete",
3004
+ rewards: { xp: 80, gold: 50 },
3005
+ acts: [
3006
+ { id: "ch2_act1", name: "The Singing Caves", type: "scene", buildAdventure: buildCh2Act1 },
3007
+ { id: "ch2_act2", name: "The Deep", type: "exploration", buildAdventure: buildCh2Act2 },
3008
+ { id: "ch2_act3", name: "The Elder's Wisdom", type: "scene", buildAdventure: buildCh2Act3 },
3009
+ { id: "ch2_act4", name: "The Crystal Prison", type: "dungeon", buildAdventure: buildCh2Act4 },
3010
+ ],
3011
+ },
3012
+ {
3013
+ id: "ch3_storm",
3014
+ name: "Ch.3: The Rising Storm",
3015
+ description: "Storms rage on the peaks. A rival phoenix challenges your purpose.",
3016
+ prerequisites: ["ch2_echoes"],
3017
+ levelRequired: 5,
3018
+ storyFlag: "ch3_complete",
3019
+ rewards: { xp: 120, gold: 70 },
3020
+ acts: [
3021
+ { id: "ch3_act1", name: "The Climb Begins", type: "scene", buildAdventure: buildCh3Act1 },
3022
+ { id: "ch3_act2", name: "Ascent", type: "exploration", buildAdventure: buildCh3Act2 },
3023
+ { id: "ch3_act3", name: "The Rival", type: "dungeon", buildAdventure: buildCh3Act3 },
3024
+ ],
3025
+ },
3026
+ {
3027
+ id: "ch4_mire",
3028
+ name: "Ch.4: Mire of Secrets",
3029
+ description: "The swamp holds a dark secret. Someone shattered the Stones deliberately.",
3030
+ prerequisites: ["ch3_storm"],
3031
+ levelRequired: 7,
3032
+ storyFlag: "ch4_complete",
3033
+ rewards: { xp: 150, gold: 90 },
3034
+ acts: [
3035
+ { id: "ch4_act1", name: "Into the Mire", type: "scene", buildAdventure: buildCh4Act1 },
3036
+ { id: "ch4_act2", name: "The Deep Mire", type: "exploration", buildAdventure: buildCh4Act2 },
3037
+ { id: "ch4_act3", name: "The Guardian's Journal", type: "scene", buildAdventure: buildCh4Act3 },
3038
+ { id: "ch4_act4", name: "The Marsh King's Domain", type: "dungeon", buildAdventure: buildCh4Act4 },
3039
+ ],
3040
+ },
3041
+ {
3042
+ id: "ch5_knowledge",
3043
+ name: "Ch.5: Forgotten Knowledge",
3044
+ description: "The ruins reveal the Stones' true purpose — and a stranger warns you away.",
3045
+ prerequisites: ["ch4_mire"],
3046
+ levelRequired: 9,
3047
+ storyFlag: "ch5_complete",
3048
+ rewards: { xp: 180, gold: 110 },
3049
+ acts: [
3050
+ { id: "ch5_act1", name: "Shelly's Direction", type: "scene", buildAdventure: buildCh5Act1 },
3051
+ { id: "ch5_act2", name: "The Archives", type: "exploration", buildAdventure: buildCh5Act2 },
3052
+ { id: "ch5_act3", name: "The Warning", type: "scene", buildAdventure: buildCh5Act3 },
3053
+ { id: "ch5_act4", name: "The Fifth Seal", type: "dungeon", buildAdventure: buildCh5Act4 },
3054
+ ],
3055
+ },
3056
+ {
3057
+ id: "ch6_fire",
3058
+ name: "Ch.6: Into the Fire",
3059
+ description: "The final Stone lies in the Molten Core. An old rival returns as an ally.",
3060
+ prerequisites: ["ch5_knowledge"],
3061
+ levelRequired: 11,
3062
+ storyFlag: "ch6_complete",
3063
+ rewards: { xp: 220, gold: 140 },
3064
+ acts: [
3065
+ { id: "ch6_act1", name: "The Call", type: "scene", buildAdventure: buildCh6Act1 },
3066
+ { id: "ch6_act2", name: "The Molten Path", type: "exploration", buildAdventure: buildCh6Act2 },
3067
+ { id: "ch6_act3", name: "The Phoenix's Truth", type: "scene", buildAdventure: buildCh6Act3 },
3068
+ { id: "ch6_act4", name: "The Last Stone", type: "dungeon", buildAdventure: buildCh6Act4 },
3069
+ ],
3070
+ },
3071
+ {
3072
+ id: "ch7_stranger",
3073
+ name: "Ch.7: The Stranger Revealed",
3074
+ description: "All Stones restored, but the seal weakens. The Stranger's identity emerges.",
3075
+ prerequisites: ["ch6_fire"],
3076
+ levelRequired: 13,
3077
+ storyFlag: "ch7_complete",
3078
+ rewards: { xp: 280, gold: 180 },
3079
+ acts: [
3080
+ { id: "ch7_act1", name: "The Rift", type: "scene", buildAdventure: buildCh7Act1 },
3081
+ { id: "ch7_act2", name: "The Twisted World", type: "exploration", buildAdventure: buildCh7Act2 },
3082
+ { id: "ch7_act3", name: "Echoes of Memory", type: "scene", buildAdventure: buildCh7Act3 },
3083
+ { id: "ch7_act4", name: "The Guardian", type: "dungeon", buildAdventure: buildCh7Act4 },
3084
+ ],
3085
+ },
3086
+ {
3087
+ id: "ch8_harmony",
3088
+ name: "Ch.8: The Final Harmony",
3089
+ description: "The Shadow Beast awakens. All allies gather for the final battle.",
3090
+ prerequisites: ["ch7_stranger"],
3091
+ levelRequired: 15,
3092
+ storyFlag: "ch8_complete",
3093
+ rewards: { xp: 500, gold: 300, titleId: "world_savior" },
3094
+ acts: [
3095
+ { id: "ch8_act1", name: "The Gathering", type: "scene", buildAdventure: buildCh8Act1 },
3096
+ { id: "ch8_act2", name: "The Gauntlet", type: "dungeon", buildAdventure: buildCh8Act2 },
3097
+ { id: "ch8_act3", name: "The Eye of the Storm", type: "scene", buildAdventure: buildCh8Act3 },
3098
+ { id: "ch8_act4", name: "THE SHADOW BEAST", type: "dungeon", buildAdventure: buildCh8Act4 },
3099
+ ],
3100
+ },
3101
+ ],
3102
+ };
3103
+ const sideQuests_1 = require("./sideQuests");
3104
+ const seasonal_1 = require("./seasonal");
3105
+ function getAllQuestChains(completedChapters = []) {
3106
+ return [MAIN_STORY, ...(0, sideQuests_1.getSideQuestChains)(completedChapters), ...(0, seasonal_1.getSeasonalQuests)()];
3107
+ }
3108
+ function getQuestChain(id) {
3109
+ return getAllQuestChains().find((c) => c.id === id);
3110
+ }
3111
+ //# sourceMappingURL=mainStory.js.map