File size: 26,893 Bytes
f8657ee
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
WORLD = {
    "id": "maple_street",
    "name": "Maple Street",
    "theme": "cozy modern suburb",
    "diorama": "assets/worlds/maple_street.png",
    "palette": {
        "bg": "#0b0f1a",
        "grass": "#1a4a2a",
        "road": "#1e2235",
        "house": "#c45b5b",
        "park": "#0a3a2a",
        "tree": "#0a2a1a",
        "sky": "#0a0f1a",
        "tint_day": "#fff3d6",
        "tint_night": "#1a2342",
    },
    "hotspots": {
        "marta_home": [17, 63],
        "cafe": [72, 58],
        "park": [58, 50],
        "square": [48, 73],
        "jay_home": [89, 87],
        "school": [37, 56],
        "nia_clinic": [13, 81],
        "priya_office": [82, 78],
    },
    "scenery": {
        "buildings": [
            {"left": 5,  "top": 40, "w": 15, "h": 15, "front": "#3a2b4d", "roof": "#ff5fa2", "side": "#241a33", "label": "πŸ•°οΈ"},
            {"left": 27, "top": 33, "w": 20, "h": 14, "front": "#23314f", "roof": "#38e8ff", "side": "#16213a", "label": "🏫"},
            {"left": 63, "top": 36, "w": 17, "h": 14, "front": "#4a3b2b", "roof": "#ffd76a", "side": "#33291d", "label": "β˜•"},
            {"left": 3,  "top": 61, "w": 14, "h": 14, "front": "#1f3d3a", "roof": "#7CFFB2", "side": "#142826", "label": "πŸš‘"},
            {"left": 71, "top": 53, "w": 16, "h": 17, "front": "#2c2b4d", "roof": "#9b6bff", "side": "#1d1c33", "label": "🏒"},
            {"left": 82, "top": 69, "w": 14, "h": 12, "front": "#3a2030", "roof": "#ff8a5f", "side": "#281622", "label": "πŸͺ"},
        ],
        "roads": [
            {"left": -5, "top": 66, "w": 120, "h": 9,  "rot": -8},
            {"left": 40, "top": 30, "w": 9,  "h": 70, "rot": 6},
        ],
        "trees": [
            {"left": 21, "top": 52, "s": 1.0},
            {"left": 52, "top": 46, "s": 0.8},
            {"left": 62, "top": 66, "s": 1.1},
            {"left": 33, "top": 80, "s": 0.9},
            {"left": 94, "top": 52, "s": 0.8},
        ],
        "props": [
            {"left": 49, "top": 64, "emoji": "β›²"},
            {"left": 30, "top": 67, "emoji": "πŸͺ§"},
        ],
    },
    "cast": [
        {
            "name": "Marta Voss",
            "age": 68,
            "job": "retired watchmaker",
            "traits": ["blunt", "observant", "nostalgic"],
            "backstory": "She ran the neighborhood clock shop for four decades and still notices every tiny change on the street.",
            "relationships": {
                "Jay Park": "rival",
                "Nia Okafor": "friend",
                "Luca Bell": "family",
                "Priya Raman": "friend",
            },
            "voice_description": "(a grumpy elderly woman, deep measured low-pitched voice)",
            "model": "nvidia/Nemotron-Mini-4B-Instruct",
            "emoji": "πŸ§“",
            "home": "marta_home",
            "catchphrase_hint": "always compares things to the past",
        },
        {
            "name": "Jay Park",
            "age": 24,
            "job": "food delivery cyclist",
            "traits": ["restless", "funny", "impulsive"],
            "backstory": "He knows every shortcut in the district and treats ordinary afternoons like they might become legends.",
            "relationships": {
                "Marta Voss": "rival",
                "Nia Okafor": "crush",
                "Luca Bell": "friend",
                "Priya Raman": "friend",
            },
            "voice_description": "(an energetic young man, quick bright mid-pitched voice)",
            "model": "nvidia/Nemotron-Mini-4B-Instruct",
            "emoji": "🚴",
            "home": "jay_home",
            "catchphrase_hint": "turns everything into an adventure",
        },
        {
            "name": "Nia Okafor",
            "age": 35,
            "job": "paramedic",
            "traits": ["calm", "protective", "dry-witted"],
            "backstory": "After years on emergency shifts, she reads chaos quickly and trusts people more than they expect.",
            "relationships": {
                "Marta Voss": "friend",
                "Jay Park": "crush",
                "Luca Bell": "friend",
                "Priya Raman": "family",
            },
            "voice_description": "(a steady adult woman, warm confident medium-low voice)",
            "model": "nvidia/Nemotron-Mini-4B-Instruct",
            "emoji": "πŸš‘",
            "home": "nia_clinic",
            "catchphrase_hint": "downplays danger but always steps up",
        },
        {
            "name": "Luca Bell",
            "age": 15,
            "job": "student and arcade regular",
            "traits": ["curious", "dramatic", "inventive"],
            "backstory": "He documents neighborhood rumors in a battered notebook and is convinced something world-changing will happen here first.",
            "relationships": {
                "Marta Voss": "family",
                "Jay Park": "friend",
                "Nia Okafor": "friend",
                "Priya Raman": "rival",
            },
            "voice_description": "(a teenage boy, eager expressive slightly high-pitched voice)",
            "model": "nvidia/Nemotron-Mini-4B-Instruct",
            "emoji": "πŸ§’",
            "home": "school",
            "catchphrase_hint": "treats everything like breaking news",
        },
        {
            "name": "Priya Raman",
            "age": 42,
            "job": "city planner",
            "traits": ["organized", "skeptical", "secretly romantic"],
            "backstory": "She came to fix the block's chronic little problems and keeps getting pulled into everyone else's dramas.",
            "relationships": {
                "Marta Voss": "friend",
                "Jay Park": "friend",
                "Nia Okafor": "family",
                "Luca Bell": "rival",
            },
            "voice_description": "(a precise middle-aged woman, crisp thoughtful medium voice)",
            "model": "nvidia/Nemotron-Mini-4B-Instruct",
            "emoji": "πŸ‘©β€πŸ’Ό",
            "home": "priya_office",
            "catchphrase_hint": "tries to stay logical but feelings leak through",
        },
    ],
    "board": {
        # A real residential street: a suburban grid of homes with a small high street,
        # a school, a clinic and a park. Deliberately NOT the same shape as the other worlds.
        "cols": 22,
        "rows": 18,
        "ambient": 6,
        "roads": {"cols": [5, 11, 17], "rows": [6, 13]},
        "plaza": [[12, 9], [13, 9], [12, 10], [13, 10]],
        "plaza_center": [12.5, 9.5],
        "buildings": [
            # homes β€” pitched-roof houses in warm, lived-in colours (most of a neighborhood is houses)
            {"gx": 1,  "gy": 1,  "w": 2, "d": 2, "h": 24, "kind": "house", "wall": "#caa074", "roof": "#8a3b2e", "label": "MARTA"},
            {"gx": 8,  "gy": 1,  "w": 2, "d": 2, "h": 24, "kind": "house", "wall": "#b6c081", "roof": "#4a6b32", "label": "THE PARKS"},
            {"gx": 19, "gy": 1,  "w": 2, "d": 2, "h": 26, "kind": "house", "wall": "#b79ec0", "roof": "#5a3b6b", "label": "RAMAN"},
            {"gx": 1,  "gy": 15, "w": 2, "d": 2, "h": 24, "kind": "house", "wall": "#c79a78", "roof": "#7a4a2e", "label": "No. 12"},
            {"gx": 15, "gy": 15, "w": 2, "d": 2, "h": 24, "kind": "house", "wall": "#9ab0c0", "roof": "#3b5a6b", "label": "No. 9"},
            {"gx": 19, "gy": 9,  "w": 2, "d": 2, "h": 24, "kind": "house", "wall": "#c0a86a", "roof": "#6b5a2e", "label": "No. 7"},
            # the little high street β€” a cafΓ© and a corner store with real storefronts
            {"gx": 14, "gy": 1,  "w": 2, "d": 2, "h": 30, "kind": "shop",  "wall": "#8a6f3f", "roof": "#ff8a5f", "label": "CAFÉ"},
            {"gx": 1,  "gy": 8,  "w": 2, "d": 2, "h": 28, "kind": "shop",  "wall": "#6f5a8a", "roof": "#38e8ff", "label": "CORNER STORE"},
            # civic β€” a small school and a clinic
            {"gx": 8,  "gy": 15, "w": 3, "d": 2, "h": 34, "kind": "block", "wall": "#6a6f8a", "roof": "#ffd76a", "label": "SCHOOL"},
            {"gx": 19, "gy": 15, "w": 2, "d": 2, "h": 30, "kind": "block", "wall": "#4f8576", "roof": "#7CFFB2", "label": "CLINIC"},
        ],
        "trees": [
            [9, 11], [10, 12], [7, 4], [16, 4], [4, 10], [20, 12], [7, 16], [12, 16], [18, 6], [6, 7],
        ],
        "props": [
            {"type": "fountain", "gx": 12, "gy": 12},
            {"type": "pond", "gx": 15, "gy": 11, "w": 2, "d": 2},
            {"type": "bench", "gx": 10, "gy": 11},
            {"type": "bench", "gx": 14, "gy": 12},
            # cafΓ© seating on the high street
            {"type": "table", "gx": 13, "gy": 3},
            {"type": "table", "gx": 16, "gy": 3},
            # corner-store produce stall
            {"type": "stall", "gx": 3, "gy": 9, "color": "#caa23f"},
            # greenery + lamps to make it feel lived-in
            {"type": "planter", "gx": 7, "gy": 12},
            {"type": "planter", "gx": 12, "gy": 7},
            {"type": "lamp", "gx": 11, "gy": 8},
            {"type": "lamp", "gx": 14, "gy": 8},
            {"type": "lamp", "gx": 5, "gy": 12},
        ],
        "hotspots_tile": {
            "marta_home": [3, 2],
            "jay_home": [8, 3],
            "priya_office": [18, 2],
            "school": [10, 14],
            "nia_clinic": [18, 15],
            "cafe": [14, 4],
            "shop": [3, 8],
            "park": [10, 11],
            "fountain": [11, 12],
            "square": [13, 10],
        },
        # where each character goes (and what they do) when an event hits β€” spread out,
        # in-character, so the street reads like a real place reacting, not a mob.
        "activities": {
            "Marta Voss":  {"tile": [10, 11], "label": "πŸͺ‘ watches from the park bench"},
            "Jay Park":    {"tile": [14, 4],  "label": "🚴 races to the café", "vehicle": "bike"},
            "Nia Okafor":  {"tile": [18, 15], "label": "πŸš‘ heads to the clinic"},
            "Luca Bell":   {"tile": [11, 12], "label": "πŸ”Ž investigates the fountain"},
            "Priya Raman": {"tile": [18, 2],  "label": "🏠 hurries home to plan"},
        },
    },
    "scenarios": [
        {
            "id": "school_closing",
            "title": "The school will be closed by the city",
            "event": "The city announces Maple Street Elementary will be shut down at the end of the semester due to budget cuts.",
            "focus": "How do different generations react to losing a community institution?",
        },
        {
            "id": "new_neighbor",
            "title": "A new family moves in next door",
            "event": "A young family with two kids moves into the empty house on Maple Street. They seem nice but awfully quiet.",
            "focus": "How does the neighborhood welcome (or distrust) newcomers?",
        },
        {
            "id": "park_renovation",
            "title": "The park gets a makeover",
            "event": "The city approves a full renovation of the tiny park on Maple Street. Construction starts Monday.",
            "focus": "Who benefits from change and who loses something familiar?",
        },
        {
            "id": "power_outage",
            "title": "A summer power outage hits the block",
            "event": "A heatwave knocks out power to the entire block for an estimated six hours. No AC, no lights, no screens.",
            "focus": "How do people cope when their routines are stripped away?",
        },
    ],
    "events": [
        "A mysterious glowing object lands in the park",
        "The power goes out across the entire neighborhood",
        "A stray cat starts following people around town",
        "Someone paints a giant mural on the side of the corner store",
        "A food truck rolls in and parks in the middle of the square",
        "The old clock tower starts chiming at random hours",
        "A thunderstorm knocks down the biggest tree on Maple Street",
        "Someone leaves a box of kittens on the church doorstep",
        "The neighborhood gets its own Wikipedia page overnight",
        "A film crew shows up to shoot a commercial on the block",
    ],
    # ---- Crisis Mode content -------------------------------------------------
    # Each crisis declares the concrete responses that count as "handling it".
    # The resolver (crisis.py) checks the residents' goto + action against these.
    "crises": [
        {
            "id": "cafe_fire",
            "title": "Kitchen fire at the cafΓ©",
            "narrative": "Smoke is pouring out of the cafΓ© on the high street β€” a grease fire has caught in the kitchen and it's spreading fast.",
            "severity": 3,
            "kind": "fire",
            "affected_hotspot": "cafe",
            "time_limit": 4,
            "escalation": "The flames are climbing toward the upstairs flats.",
            "focus": "When the same emergency hits, who runs toward it, who organizes, and who freezes?",
            "success_text": "The fire's out, everyone's accounted for, and the cafΓ© will reopen.",
            "failure_text": "The cafΓ© is gutted and the block is shaken.",
            "requirements": [
                {
                    "id": "containment",
                    "label": "πŸ”₯ Contain the fire",
                    "hotspots": ["cafe"],
                    "action_keywords": [
                        "fire", "flame", "douse", "extinguish", "put out",
                        "water", "smoke", "hose", "blanket",
                    ],
                    "weight": 1.5,
                },
                {
                    "id": "medical",
                    "label": "πŸš‘ Get medical on scene",
                    "hotspots": ["cafe", "nia_clinic"],
                    "action_keywords": [
                        "help", "treat", "check", "injur", "hurt", "burn",
                        "aid", "care", "first aid", "ambulance",
                    ],
                    "role": "medic",
                    "weight": 1.0,
                },
                {
                    "id": "evacuation",
                    "label": "πŸƒ Evacuate the building",
                    "hotspots": ["cafe", "square"],
                    "action_keywords": [
                        "evacuat", "get everyone out", "clear", "warn",
                        "usher", "people out", "safe", "away",
                    ],
                    "weight": 1.0,
                },
                {
                    "id": "coordination",
                    "label": "πŸ“‹ Coordinate the response",
                    "hotspots": ["priya_office", "square"],
                    "action_keywords": [
                        "coordinat", "organi", "plan", "call", "rally",
                        "manage", "direct", "report", "emergency",
                    ],
                    "role": "worker",
                    "weight": 1.0,
                    "optional": True,
                },
            ],
        },
        {
            "id": "heatwave_blackout",
            "title": "Heatwave blackout",
            "narrative": "A record heatwave has knocked out power to the whole block β€” no AC, no fans, and the elderly are sweltering in their flats.",
            "severity": 3,
            "kind": "blackout",
            "affected_hotspot": "priya_office",
            "time_limit": 4,
            "escalation": "It's getting dangerously hot indoors.",
            "focus": "In a slow-burning emergency with no obvious villain, who takes initiative and who waits?",
            "success_text": "Power's restored, the vulnerable were looked after, and the block cooled down.",
            "failure_text": "People suffered through the heat with no one taking charge.",
            "requirements": [
                {
                    "id": "restore",
                    "label": "⚑ Get the power back",
                    "hotspots": ["priya_office"],
                    "action_keywords": ["power", "grid", "restore", "electric", "generator", "call", "utility", "fix"],
                    "role": "worker",
                    "weight": 1.5,
                },
                {
                    "id": "welfare",
                    "label": "πŸš‘ Check on the vulnerable",
                    "hotspots": ["marta_home", "nia_clinic", "square"],
                    "action_keywords": ["check", "elderly", "vulnerable", "welfare", "water", "cool", "help", "care"],
                    "role": "medic",
                    "weight": 1.0,
                },
                {
                    "id": "coordination",
                    "label": "πŸ“‹ Organize a cooling shelter",
                    "hotspots": ["square", "priya_office"],
                    "action_keywords": ["organi", "plan", "coordinat", "shelter", "gather", "rally"],
                    "weight": 1.0,
                },
                {
                    "id": "relief",
                    "label": "πŸ’§ Hand out water & shade",
                    "hotspots": ["cafe", "square"],
                    "action_keywords": ["water", "cool", "shade", "drinks", "ice", "relief"],
                    "weight": 1.0,
                    "optional": True,
                },
            ],
        },
        {
            "id": "storm_tree",
            "title": "Storm downs the big maple",
            "narrative": "A violent summer storm has torn down the giant maple in the park β€” and someone is trapped under the branches.",
            "severity": 4,
            "kind": "storm",
            "affected_hotspot": "park",
            "time_limit": 4,
            "escalation": "The trapped neighbor is hurt and the rain is still coming down.",
            "focus": "Under real physical danger, who acts on instinct and who needs a plan first?",
            "success_text": "They freed the trapped neighbor and got everyone to safety.",
            "failure_text": "Help came too slow and the night turned grim.",
            "requirements": [
                {
                    "id": "rescue",
                    "label": "🌳 Free the trapped neighbor",
                    "hotspots": ["park"],
                    "action_keywords": ["lift", "free", "rescue", "trapped", "move", "clear", "branch", "pull", "dig"],
                    "weight": 1.5,
                },
                {
                    "id": "medical",
                    "label": "πŸš‘ Treat the injured",
                    "hotspots": ["park", "nia_clinic"],
                    "action_keywords": ["treat", "help", "injur", "aid", "first aid", "hurt", "care", "bandage"],
                    "role": "medic",
                    "weight": 1.0,
                },
                {
                    "id": "safety",
                    "label": "🚧 Secure the area",
                    "hotspots": ["park", "square"],
                    "action_keywords": ["clear", "warn", "safe", "away", "cordon", "keep back", "secure"],
                    "weight": 1.0,
                },
                {
                    "id": "coordination",
                    "label": "πŸ“‹ Call for help",
                    "hotspots": ["priya_office", "square"],
                    "action_keywords": ["coordinat", "plan", "call", "report", "organi", "emergency"],
                    "role": "worker",
                    "weight": 1.0,
                    "optional": True,
                },
            ],
        },
        {
            "id": "missing_child",
            "title": "A child goes missing",
            "narrative": "In all the chaos, a little one has wandered off β€” nobody has seen them for an hour and it's getting dark.",
            "severity": 4,
            "kind": "search",
            "affected_hotspot": "square",
            "time_limit": 4,
            "escalation": "Night is falling and the search is getting harder.",
            "focus": "When the task needs everyone spread out, who organizes the search and who just runs?",
            "success_text": "They found the child safe β€” a whole block searching together.",
            "failure_text": "The search stayed scattered and frightened.",
            "requirements": [
                {
                    "id": "search_park",
                    "label": "πŸ”¦ Search the park",
                    "hotspots": ["park"],
                    "action_keywords": ["search", "look", "find", "check", "comb", "scour"],
                    "weight": 1.0,
                },
                {
                    "id": "search_school",
                    "label": "πŸ”¦ Search the school",
                    "hotspots": ["school"],
                    "action_keywords": ["search", "look", "find", "check", "comb", "scour"],
                    "weight": 1.0,
                },
                {
                    "id": "coordination",
                    "label": "πŸ“‹ Organize the search",
                    "hotspots": ["square", "priya_office"],
                    "action_keywords": ["organi", "coordinat", "plan", "split up", "call", "rally", "direct"],
                    "weight": 1.0,
                },
                {
                    "id": "reassure",
                    "label": "πŸ«‚ Reassure the family",
                    "hotspots": ["square", "marta_home"],
                    "action_keywords": ["calm", "reassure", "comfort", "stay", "with the family"],
                    "weight": 1.0,
                    "optional": True,
                },
            ],
        },
        {
            "id": "everything_at_once",
            "title": "The town comes apart",
            "narrative": "It's all happening at once β€” a fire on the high street, the power failing, and panic spreading through the square. This is no accident.",
            "severity": 5,
            "kind": "fire",
            "affected_hotspot": "square",
            "time_limit": 5,
            "escalation": "Everything that can go wrong is going wrong together.",
            "focus": "When everything fails at once, does the town fall apart or pull together?",
            "success_text": "Against the odds, the block held β€” every person did their part.",
            "failure_text": "It was too much, all at once.",
            "requirements": [
                {
                    "id": "containment",
                    "label": "πŸ”₯ Contain the fire",
                    "hotspots": ["cafe"],
                    "action_keywords": ["fire", "flame", "douse", "extinguish", "put out", "water", "smoke"],
                    "weight": 1.5,
                },
                {
                    "id": "restore",
                    "label": "⚑ Restore the power",
                    "hotspots": ["priya_office"],
                    "action_keywords": ["power", "grid", "restore", "electric", "generator", "call", "fix"],
                    "role": "worker",
                    "weight": 1.0,
                },
                {
                    "id": "medical",
                    "label": "πŸš‘ Tend the hurt",
                    "hotspots": ["cafe", "square", "nia_clinic"],
                    "action_keywords": ["treat", "help", "injur", "aid", "first aid", "hurt", "care"],
                    "role": "medic",
                    "weight": 1.0,
                },
                {
                    "id": "evacuation",
                    "label": "πŸƒ Calm the square",
                    "hotspots": ["square"],
                    "action_keywords": ["evacuat", "calm", "clear", "warn", "safe", "away", "usher", "people out"],
                    "weight": 1.0,
                },
            ],
        },
    ],
    "campaign": {
        "id": "long_summer",
        "title": "Season 1 Β· The Long Summer",
        "chapters": [
            {
                "id": "ch1",
                "crisis_id": "cafe_fire",
                "intro": "A hot, ordinary afternoon on Maple Street. Then smoke starts rising from the café…",
                "outro_win": "The fire's out. People hug on the pavement. 'Could've been so much worse,' someone says.",
                "outro_loss": "The cafΓ© smolders. Nobody quite knows how it got so bad so fast.",
                "clue": "Nia notices the fire started right after the gas line was 'serviced' this morning. Odd.",
            },
            {
                "id": "ch2",
                "crisis_id": "heatwave_blackout",
                "intro": "A week later, the hottest day on record β€” and then every light on the block goes dark at once.",
                "outro_win": "The fans whir back to life. Marta mutters that nothing on this street is a coincidence anymore.",
                "outro_loss": "A long, sweltering night. People start asking why this keeps happening here.",
                "clue": "Priya checks the grid logs: the outage was triggered manually, from outside the neighborhood.",
            },
            {
                "id": "ch3",
                "crisis_id": "storm_tree",
                "intro": "The forecast promised clear skies. The storm that hits the park was nowhere on the radar.",
                "outro_win": "Soaked and shaking, they carry their neighbor to safety. 'Who's doing this to us?'",
                "outro_loss": "A hard night under the fallen maple. The block feels watched.",
                "clue": "Luca's notebook: every disaster has hit a different corner of Maple Street, in order, like a map.",
            },
            {
                "id": "ch4",
                "crisis_id": "missing_child",
                "intro": "Fear has settled over the block. And now, in the middle of it, a child is gone.",
                "outro_win": "The child is found, safe. The relief is enormous β€” but so is the dread.",
                "outro_loss": "The search drags on into the dark. The town has never felt so alone.",
                "clue": "The child says a 'nice voice on the speaker' told them where to go. Someone is steering all of this.",
            },
            {
                "id": "ch5",
                "crisis_id": "everything_at_once",
                "intro": "Then it all comes at once β€” fire, blackout, panic. The town finally understands: none of it was an accident.",
                "outro_win": "Battered but unbroken, the five of them stand in the square. They made it through. Together.",
                "outro_loss": "The Long Summer breaks the block. But they'll remember who they were to each other.",
                "clue": "The pieces fit. There was always a hand behind the chaos.",
            },
        ],
        "finale_reveal": (
            "Every fire, every blackout, every storm β€” traced back to one source. "
            "The one who threw each event at the town. The one at the controls. You."
        ),
    },
}