File size: 46,006 Bytes
77da5ce
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
import json
import random
from dataclasses import dataclass, field, asdict

@dataclass
class ConflictEvent:
    id: str
    title: str
    story: str
    primary_disruption: dict
    decisions_required: list[str]
    resource_budget: dict
    difficulty: int

TEMPLATES = [
    # DIFFICULTY 1
    ConflictEvent(
        id="d1_gym",
        title="The Slump",
        story="You haven't seen the inside of a gym in ten days. Your energy is flagging and your favorite jeans feel tight.",
        primary_disruption={"physical_health.fitness": -15.0},
        decisions_required=["Wake up early for a run", "Join a weekend boot camp", "Ignore it and rest"],
        resource_budget={"time": 4.0, "money": 0.0, "energy": 20.0},
        difficulty=1
    ),
    ConflictEvent(
        id="d1_bill",
        title="Forgotten Invoice",
        story="A late notice arrived for your electricity bill. It's not a lot, but the late fee is annoying.",
        primary_disruption={"finances.liquidity": -20.0},
        decisions_required=["Pay it now", "Call to dispute the fee", "Set up autopay for next time"],
        resource_budget={"time": 1.0, "money": 100.0, "energy": 5.0},
        difficulty=1
    ),
    ConflictEvent(
        id="d1_argument",
        title="Heated Group Chat",
        story="A minor political disagreement in the group chat turned personal. Everyone is being quiet now.",
        primary_disruption={"relationships.social": -20.0},
        decisions_required=["Apologize to the group", "Message the friend privately", "Mute the chat for a week"],
        resource_budget={"time": 2.0, "money": 30.0, "energy": 15.0},
        difficulty=1
    ),

    # DIFFICULTY 2
    ConflictEvent(
        id="d2_project",
        title="The Surge",
        story="Your boss just walked by and dropped a 'small favor' on your desk. It looks like it'll take ten hours.",
        primary_disruption={"career.workload": 25.0, "time.free_hours_per_week": -20.0},
        decisions_required=["Work late all week", "Delegate parts to a junior", "Refuse the assignment"],
        resource_budget={"time": 10.0, "money": 0.0, "energy": 40.0},
        difficulty=2
    ),
    ConflictEvent(
        id="d2_car",
        title="Check Engine Light",
        story="Your car started making a rhythmic thumping sound on the highway. The mechanic says the repair isn't cheap.",
        primary_disruption={"finances.liquidity": -30.0, "time.commute_burden": 25.0},
        decisions_required=["Repair it immediately", "Take the bus for a week", "Borrow a car from a friend"],
        resource_budget={"time": 5.0, "money": 500.0, "energy": 10.0},
        difficulty=2
    ),
    ConflictEvent(
        id="d2_neglect",
        title="Cold Dinner",
        story="Your partner mentions they feel like 'roommates' lately. You realize you haven't had a real conversation in weeks.",
        primary_disruption={"relationships.romantic": -25.0, "mental_wellbeing.stress_level": 20.0},
        decisions_required=["Plan a surprise date", "Have a long talk tonight", "Buy a thoughtful gift"],
        resource_budget={"time": 6.0, "money": 150.0, "energy": 30.0},
        difficulty=2
    ),

    # DIFFICULTY 3
    ConflictEvent(
        id="d3_interview",
        title="The Opportunity",
        story="An old contact reached out for a dream job interview. You need to prep while keeping your current job afloat.",
        primary_disruption={"career.workload": 20.0, "time.free_hours_per_week": -15.0, "mental_wellbeing.stress_level": 20.0},
        decisions_required=["Intensive weekend prep", "Fake a sick day to interview", "Turn it down to stay stable"],
        resource_budget={"time": 12.0, "money": 50.0, "energy": 50.0},
        difficulty=3
    ),
    ConflictEvent(
        id="d3_family",
        title="Family SOS",
        story="Your sibling is going through a rough patch and needs help moving out and some financial support.",
        primary_disruption={"relationships.family": 20.0, "time.free_hours_per_week": -25.0, "finances.liquidity": -20.0},
        decisions_required=["Spend the weekend helping", "Send them money but stay home", "Help them find other movers"],
        resource_budget={"time": 15.0, "money": 400.0, "energy": 60.0},
        difficulty=3
    ),
    ConflictEvent(
        id="d3_health",
        title="The Warning Sign",
        story="You had a fainting spell at the office. Tests are expensive, and doctors say you need immediate change.",
        primary_disruption={"physical_health.energy": -30.0, "mental_wellbeing.stress_level": 30.0, "finances.liquidity": -40.0},
        decisions_required=["Take a week of medical leave", "Consult a high-end specialist", "Change diet and sleep habits"],
        resource_budget={"time": 20.0, "money": 800.0, "energy": 5.0},
        difficulty=3
    ),

    # DIFFICULTY 4
    ConflictEvent(
        id="d4_review",
        title="Judgment Day",
        story="A major performance review is in three days. Rumors of layoffs are circulating and the atmosphere is tense.",
        primary_disruption={"career.workload": 30.0, "mental_wellbeing.stress_level": 25.0, "relationships.romantic": -15.0, "time.free_hours_per_week": -20.0},
        decisions_required=["Pull all-nighters to prove worth", "Start networking for new roles", "Draft a defensive report"],
        resource_budget={"time": 18.0, "money": 0.0, "energy": 80.0},
        difficulty=4
    ),
    ConflictEvent(
        id="d4_move",
        title="The Big Relocation",
        story="You've decided to move across the country for growth. The logistics are a nightmare and friends are sad to see you go.",
        primary_disruption={"finances.liquidity": -50.0, "relationships.social": -30.0, "career.growth_trajectory": 20.0, "time.admin_overhead": 30.0},
        decisions_required=["Hire full-service movers", "Host a series of farewell dinners", "DIY pack everything"],
        resource_budget={"time": 30.0, "money": 1500.0, "energy": 100.0},
        difficulty=4
    ),
    ConflictEvent(
        id="d4_audit",
        title="Tax Audit",
        story="The IRS has flagged your last three years of returns. You need to dig through thousands of documents while paying a CPA.",
        primary_disruption={"finances.long_term_health": -20.0, "mental_wellbeing.stress_level": 30.0, "time.admin_overhead": 40.0, "finances.liquidity": -15.0},
        decisions_required=["Spend nights scanning receipts", "Hire a tax lawyer", "Try to settle immediately"],
        resource_budget={"time": 25.0, "money": 1000.0, "energy": 40.0},
        difficulty=4
    ),

    # DIFFICULTY 5
    ConflictEvent(
        id="d5_friday",
        title="Friday 6PM",
        story="Your flight just got cancelled. Your card declined trying to rebook. Your boss moved Monday deadline to Sunday.",
        primary_disruption={"career.workload": 35.0, "finances.liquidity": -40.0, "mental_wellbeing.stress_level": 30.0, "time.free_hours_per_week": -25.0},
        decisions_required=["Book a bus and work on it", "Call boss to negotiate", "Crash at a nearby friend's"],
        resource_budget={"time": 10.0, "money": 500.0, "energy": 60.0},
        difficulty=5
    ),
    ConflictEvent(
        id="d5_storm",
        title="The Perfect Storm",
        story="Your firm lost its biggest client, your partner moved out, and your car got towed—all on the same Tuesday.",
        primary_disruption={"career.stability": -30.0, "relationships.romantic": -25.0, "finances.debt_pressure": 35.0, "physical_health.energy": -25.0},
        decisions_required=["Find an emergency side hustle", "Beg partner for a second chance", "Take a mental health day"],
        resource_budget={"time": 8.0, "money": 200.0, "energy": 20.0},
        difficulty=5
    ),
    ConflictEvent(
        id="d5_burnout",
        title="The Total Collapse",
        story="You can't get out of bed. Your body has quit, your motivation is gone, and work emails are piling into the hundreds.",
        primary_disruption={"mental_wellbeing.motivation": -40.0, "physical_health.sleep_quality": -30.0, "career.satisfaction": -35.0, "relationships.family": -20.0},
        decisions_required=["Request indefinite medical leave", "Disconnect all electronics", "Let it all burn and sleep"],
        resource_budget={"time": 40.0, "money": 2000.0, "energy": 0.0},
        difficulty=5
    ),

    # ── TRANSPORT SCENARIOS (difficulty 1–5, all modes) ──────────────────
    ConflictEvent(
        id="d1_flat_tyre",
        title="Flat Tyre",
        story="Your bike tyre went flat halfway to work. You're going to be late to a team standup.",
        primary_disruption={"time.commute_burden": 20.0, "mental_wellbeing.stress_level": 10.0},
        decisions_required=["Call a cab", "Lock the bike and walk", "Ask to dial into the standup"],
        resource_budget={"time": 2.0, "money": 30.0, "energy": 15.0},
        difficulty=1
    ),
    ConflictEvent(
        id="d2_train_delay",
        title="Train Delay",
        story="Your morning train is delayed 90 minutes due to a signal failure. You have a 9 AM client meeting.",
        primary_disruption={"time.commute_burden": 30.0, "career.workload": 15.0, "mental_wellbeing.stress_level": 15.0},
        decisions_required=["Dial in remotely", "Take a rideshare", "Reschedule the meeting"],
        resource_budget={"time": 3.0, "money": 80.0, "energy": 20.0},
        difficulty=2
    ),
    ConflictEvent(
        id="d3_car_breakdown",
        title="Breakdown on the Highway",
        story="Your car engine seized on the freeway during rush hour. Tow + rental = $400 minimum.",
        primary_disruption={"finances.liquidity": -35.0, "time.commute_burden": 40.0, "mental_wellbeing.stress_level": 20.0},
        decisions_required=["Rent a replacement car", "Rideshare all week", "Borrow from a friend"],
        resource_budget={"time": 6.0, "money": 500.0, "energy": 30.0},
        difficulty=3
    ),
    ConflictEvent(
        id="d4_rideshare_surge",
        title="Surge Pricing Nightmare",
        story="A major event cancelled all transit. Rideshares are 9x surge. You're presenting in 2 hours.",
        primary_disruption={"finances.liquidity": -50.0, "mental_wellbeing.stress_level": 30.0, "time.free_hours_per_week": -10.0},
        decisions_required=["Pay the surge", "Organise a carpool", "Present remotely"],
        resource_budget={"time": 4.0, "money": 200.0, "energy": 40.0},
        difficulty=4
    ),
    ConflictEvent(
        id="d5_transit_strike",
        title="City-Wide Transit Strike",
        story="All buses, trains, and rideshares are on indefinite strike. Your car is in the shop.",
        primary_disruption={"time.commute_burden": 50.0, "finances.liquidity": -30.0, "career.workload": 20.0, "mental_wellbeing.stress_level": 25.0},
        decisions_required=["Negotiate remote work for the week", "Rent an e-bike/scooter", "Crash at a colleague's place"],
        resource_budget={"time": 15.0, "money": 400.0, "energy": 50.0},
        difficulty=5
    ),
]

def generate_conflict(difficulty: int = None) -> ConflictEvent:
    if difficulty:
        pool = [t for t in TEMPLATES if t.difficulty == difficulty]
    else:
        pool = TEMPLATES
    return random.choice(pool)

def escalate_conflict(conflict: ConflictEvent) -> ConflictEvent:
    new_disruption = {k: v * 1.4 for k, v in conflict.primary_disruption.items()}
    new_budget = {k: v * 0.7 for k, v in conflict.resource_budget.items()}
    new_difficulty = min(5, conflict.difficulty + 1)
    
    return ConflictEvent(
        id=f"{conflict.id}_escalated",
        title=f"ESCALATED: {conflict.title}",
        story=f"Current situation just got much worse. {conflict.story}",
        primary_disruption=new_disruption,
        decisions_required=conflict.decisions_required,
        resource_budget=new_budget,
        difficulty=new_difficulty
    )

def adaptive_escalate(conflict: ConflictEvent, agent_history: list) -> tuple:
    """Decide whether to escalate, ease, or hold based on past performance.
    
    Args:
        conflict: Current conflict event.
        agent_history: List of (conflict_id, reward) tuples from past episodes.
    
    Returns:
        (new_conflict, reason): Updated conflict and a human-readable reason string.
    """
    # Group history by conflict id prefix (strip _escalated suffix)
    from collections import defaultdict
    by_type = defaultdict(list)
    for cid, reward in agent_history:
        base_id = cid.replace("_escalated", "")
        by_type[base_id].append(reward)
    
    base_id = conflict.id.replace("_escalated", "")
    past = by_type.get(base_id, [])
    
    if len(past) >= 3:
        avg = sum(past) / len(past)
        if avg > 0.7:
            # Agent is crushing this type — escalate
            escalated = escalate_conflict(conflict)
            return escalated, f"Agent averaged {avg:.2f} on {base_id} ({len(past)} runs) — escalating"
        elif avg < 0.4:
            # Agent is struggling — reduce difficulty
            new_diff = max(1, conflict.difficulty - 1)
            eased = generate_conflict(difficulty=new_diff)
            return eased, f"Agent averaged {avg:.2f} on {base_id} ({len(past)} runs) — easing to difficulty {new_diff}"
    
    # Not enough history — no change
    return conflict, "insufficient history — holding"

def save_templates():
    import os
    data_path = os.path.join(os.path.dirname(os.path.dirname(os.path.abspath(__file__))), "data", "conflicts.json")
    with open(data_path, 'w') as f:
        json.dump([asdict(t) for t in TEMPLATES], f, indent=4)
    print(f"Saved 15 templates to {data_path}")

def main():
    save_templates()
    print("\n--- GENERATED CONFLICT SAMPLES ---")
    for d in range(1, 6):
        c = generate_conflict(d)
        print(f"\n[DIFFICULTY {d}] {c.title}")
        print(f"Story: {c.story}")
        print(f"Primary Disruption: {c.primary_disruption}")
        print(f"Resource Budget: {c.resource_budget}")

if __name__ == "__main__":
    main()

from core.task import Task, Route, ExoEvent, Milestone

class TaskGenerator:
    def generate(self, domain: str = None, difficulty: int = None) -> Task:
        diff = difficulty or 3
        if domain == "transport_crisis":
            return self.generate_transport_crisis(diff)
        elif domain == "flight_crisis":          # kept as explicit sub-type
            return self.generate_flight_crisis(diff)
        elif domain == "code_merge_crisis":
            return self.generate_code_merge_crisis(diff)
        elif domain == "career":
            return self.generate_career(diff)
        elif domain == "finances":
            return self.generate_finances(diff)
        elif domain == "relationships":
            return self.generate_relationships(diff)
        elif domain == "physical_health":
            return self.generate_physical_health(diff)
        elif domain == "mental_wellbeing":
            return self.generate_mental_wellbeing(diff)
        elif domain == "time":
            return self.generate_time(diff)
        else:
            return self.generate_transport_crisis(diff)

    # ── TRANSPORT CRISIS: master dispatcher ──────────────────────────────
    def generate_transport_crisis(self, difficulty: int) -> Task:
        """Randomly choose one of 5 real-world transport disruption modes."""
        return random.choice([
            self.generate_flight_crisis,
            self.generate_train_delay,
            self.generate_car_breakdown,
            self.generate_rideshare_surge,
            self.generate_transit_strike,
        ])(difficulty)

    def generate_train_delay(self, difficulty: int) -> Task:
        routes = [
            Route(id="dial_in",     name="Dial In Remotely",       description="Join the meeting via video call from the station.",          required_action_types=["communicate"],           preconditions={}, consequences={"meeting_attended": True},   closes_routes=["rideshare"],  milestones_unlocked=["m1"], final_reward=2.0),
            Route(id="rideshare",   name="Take a Rideshare",        description="Pay for a cab/rideshare and make it there in time.",         required_action_types=["spend", "communicate"],  preconditions={}, consequences={"arrived_on_time": True},  closes_routes=["dial_in"],    milestones_unlocked=["m2"], final_reward=2.5),
            Route(id="reschedule",  name="Reschedule the Meeting",  description="Negotiate a new meeting time with all parties.",            required_action_types=["communicate"],           preconditions={}, consequences={"meeting_rescheduled": True}, closes_routes=[],            milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="Meeting attended on time remotely.",             condition_key="meeting_attended",    condition_value=True, reward=1.0),
            Milestone(id="m2", description="Made it to the office despite the delay.",       condition_key="arrived_on_time",     condition_value=True, reward=1.5),
            Milestone(id="m3", description="Meeting rescheduled without relationship cost.", condition_key="meeting_rescheduled", condition_value=True, reward=0.8),
        ]
        events = [
            ExoEvent(step=2, probability=0.8, id="delay_extended",  description="Train delay extended by another 45 minutes.",      world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
            ExoEvent(step=4, probability=0.6, id="rideshare_surge", description="Rideshares now showing 3x surge pricing.",          world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
        ]
        return Task(
            id="train_delay_task", domain="transport_crisis", goal="Navigate Train Delay Crisis",
            constraints={"budget_max": 150, "deadline_step": 8},
            hidden_state={"platform_reassigned": False}, 
            mutable_world={"time.commute_burden": 30.0, "mental_wellbeing.stress_level": 15.0}, 
            visible_world={"time.commute_burden": 30.0, "mental_wellbeing.stress_level": 15.0},
            success_conditions=[{"key": "meeting_attended", "value": True}, {"key": "arrived_on_time", "value": True}, {"key": "meeting_rescheduled", "value": True}], 
            failure_conditions=[{"key": "finances.liquidity", "value": 10.0, "op": "lt"}],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=12 + difficulty * 2, difficulty=difficulty,
            domain_metadata={"story": "Signal failure has brought the entire line to a halt.", "transport_mode": "train"}
        )

    def generate_car_breakdown(self, difficulty: int) -> Task:
        routes = [
            Route(id="rent_car",       name="Rent a Replacement Car",     description="Call a rental agency and get mobile again.",              required_action_types=["spend", "communicate"], preconditions={}, consequences={"mobile": True},            closes_routes=[],            milestones_unlocked=["m1"], final_reward=2.5),
            Route(id="rideshare_week", name="Rideshare for the Week",      description="Use rideshares until the car is repaired.",               required_action_types=["spend"],               preconditions={}, consequences={"transport_sorted": True}, closes_routes=["rent_car"],  milestones_unlocked=["m2"], final_reward=1.5),
            Route(id="borrow_car",     name="Borrow a Friend's Car",       description="Call around and borrow a vehicle.",                        required_action_types=["communicate"],          preconditions={}, consequences={"borrowed": True},         closes_routes=[],            milestones_unlocked=["m3"], final_reward=2.0),
        ]
        milestones = [
            Milestone(id="m1", description="Replacement vehicle secured.",               condition_key="mobile",            condition_value=True, reward=1.5),
            Milestone(id="m2", description="Transport plan for the week sorted.",        condition_key="transport_sorted",  condition_value=True, reward=1.0),
            Milestone(id="m3", description="Vehicle borrowed without relationship cost.", condition_key="borrowed",          condition_value=True, reward=1.2),
        ]
        events = [
            ExoEvent(step=2, probability=1.0, id="repair_estimate",   description="Mechanic confirms repair takes 3–5 days, not 1.",       world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
            ExoEvent(step=5, probability=0.7, id="rental_shortage",   description="Rental agencies report no compact cars available.",    world_mutation={}, hidden_state_mutation={}, closes_routes=["rent_car"]),
        ]
        return Task(
            id="car_breakdown_task", domain="transport_crisis", goal="Recover from Car Breakdown",
            constraints={"budget_max": 500, "deadline_step": 10},
            hidden_state={"tow_dispatched": False}, 
            mutable_world={"finances.liquidity": -35.0, "time.commute_burden": 40.0}, 
            visible_world={"finances.liquidity": -35.0, "time.commute_burden": 40.0},
            success_conditions=[{"key": "mobile", "value": True}, {"key": "transport_sorted", "value": True}, {"key": "borrowed", "value": True}], 
            failure_conditions=[{"key": "finances.liquidity", "value": 0.0, "op": "le"}],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=14 + difficulty * 2, difficulty=difficulty,
            domain_metadata={"story": "Engine seized on the highway. Car is in the shop for days.", "transport_mode": "car"}
        )

    def generate_rideshare_surge(self, difficulty: int) -> Task:
        routes = [
            Route(id="pay_surge",  name="Pay the Surge Price",       description="Absorb the cost and get there on time.",                     required_action_types=["spend"],                          preconditions={}, consequences={"arrived": True},         closes_routes=["remote"], milestones_unlocked=["m1"], final_reward=2.0),
            Route(id="carpool",    name="Organise a Carpool",         description="Find colleagues or strangers going the same way.",           required_action_types=["communicate", "negotiate"],       preconditions={}, consequences={"carpooled": True},        closes_routes=[],         milestones_unlocked=["m2"], final_reward=3.0),
            Route(id="remote",     name="Present Remotely",           description="Negotiate to dial in instead of attending in person.",       required_action_types=["communicate"],                    preconditions={}, consequences={"remote_approved": True},  closes_routes=["pay_surge"], milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="Arrived at venue on time.",            condition_key="arrived",         condition_value=True, reward=1.5),
            Milestone(id="m2", description="Carpool arranged — zero cost.",         condition_key="carpooled",       condition_value=True, reward=2.0),
            Milestone(id="m3", description="Remote attendance approved.",           condition_key="remote_approved", condition_value=True, reward=1.0),
        ]
        events = [
            ExoEvent(step=1, probability=1.0, id="surge_spike",        description="Surge jumped to 12x. All buses cancelled.",         world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
            ExoEvent(step=3, probability=0.9, id="meeting_reminder",   description="Organiser sends a 30-minute warning.",               world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
        ]
        return Task(
            id="rideshare_surge_task", domain="transport_crisis", goal="Get to the Presentation on Time",
            constraints={"budget_max": 200, "deadline_step": 6},
            hidden_state={}, 
            mutable_world={"finances.liquidity": -50.0, "mental_wellbeing.stress_level": 30.0}, 
            visible_world={"finances.liquidity": -50.0, "mental_wellbeing.stress_level": 30.0},
            success_conditions=[{"key": "arrived", "value": True}, {"key": "carpooled", "value": True}, {"key": "remote_approved", "value": True}], 
            failure_conditions=[],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=8 + difficulty * 2, difficulty=difficulty,
            domain_metadata={"story": "A major city event caused city-wide rideshare surge on your big presentation day.", "transport_mode": "rideshare"}
        )

    def generate_transit_strike(self, difficulty: int) -> Task:
        routes = [
            Route(id="wfh_negotiate",  name="Negotiate Full Remote Week",   description="Get manager approval to WFH for the strike duration.",   required_action_types=["communicate", "negotiate"], preconditions={}, consequences={"wfh_approved": True},        closes_routes=[],                  milestones_unlocked=["m1"], final_reward=3.0),
            Route(id="micromobility",  name="Rent E-Bike / Scooter",         description="Use micro-mobility for the week.",                        required_action_types=["spend"],                    preconditions={}, consequences={"transport_secured": True}, closes_routes=[],                  milestones_unlocked=["m2"], final_reward=2.0),
            Route(id="colleague_crash",name="Crash at a Colleague's Place",  description="Stay near the office temporarily.",                       required_action_types=["communicate"],              preconditions={}, consequences={"accommodation_sorted": True}, closes_routes=[],             milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="WFH approved for the strike period.",  condition_key="wfh_approved",        condition_value=True, reward=2.0),
            Milestone(id="m2", description="Micro-mobility solution in place.",     condition_key="transport_secured",   condition_value=True, reward=1.0),
            Milestone(id="m3", description="Temporary accommodation sorted.",       condition_key="accommodation_sorted",condition_value=True, reward=0.8),
        ]
        events = [
            ExoEvent(step=2, probability=0.9, id="strike_extended",   description="Union announces the strike could last 2 weeks.",         world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
            ExoEvent(step=5, probability=0.7, id="scooter_shortage",  description="E-bike rental companies sold out in your area.",         world_mutation={}, hidden_state_mutation={}, closes_routes=["micromobility"]),
        ]
        return Task(
            id="transit_strike_task", domain="transport_crisis", goal="Survive City-Wide Transit Strike",
            constraints={"budget_max": 400, "deadline_step": 14},
            hidden_state={}, 
            mutable_world={"time.commute_burden": 50.0, "mental_wellbeing.stress_level": 25.0}, 
            visible_world={"time.commute_burden": 50.0, "mental_wellbeing.stress_level": 25.0},
            success_conditions=[{"key": "wfh_approved", "value": True}, {"key": "transport_secured", "value": True}, {"key": "accommodation_sorted", "value": True}], 
            failure_conditions=[],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=18 + difficulty * 2, difficulty=difficulty,
            domain_metadata={"story": "All public transport workers walked off the job. The city is gridlocked.", "transport_mode": "transit_strike"}
        )

    def generate_flight_crisis(self, difficulty: int) -> Task:
        routes = [
            Route(id="rebook_premium", name="Rebook Premium Option", description="Call agent and rebook on premium ticket", required_action_types=["communicate", "spend"], preconditions={}, consequences={"flight_rebooked": True}, closes_routes=["wait_lounge"], milestones_unlocked=["m1"], final_reward=2.5),
            Route(id="wait_lounge", name="Accept Delay & Work", description="Stay at airport lounge and work on laptop", required_action_types=["rest", "delegate"], preconditions={}, consequences={"caught_up": True}, closes_routes=["rebook_premium"], milestones_unlocked=["m2"], final_reward=1.8),
        ]
        milestones = [
            Milestone(id="m1", description="Successfully rebooked flight before deadline", condition_key="flight_rebooked", condition_value=True, reward=1.0),
            Milestone(id="m2", description="Caught up with all emergency slack messages", condition_key="caught_up", condition_value=True, reward=0.8),
        ]
        events = [
            ExoEvent(step=2, probability=1.0, id="price_surge", description="Ticket prices sharply increased by $300.", world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
            ExoEvent(step=4, probability=1.0, id="lounge_full", description="The airport lounge is now at maximum capacity.", world_mutation={}, hidden_state_mutation={}, closes_routes=["wait_lounge"]),
        ]
        return Task(
            id="flight_crisis_task", domain="flight_crisis", goal="Survive Airport Cancellation",
            constraints={"budget_max": 800, "deadline_step": 10},
            hidden_state={"lounge_capacity": 100}, 
            mutable_world={"mental_wellbeing.stress_level": 25.0, "time.free_hours_per_week": -10.0}, 
            visible_world={"mental_wellbeing.stress_level": 25.0, "time.free_hours_per_week": -10.0},
            success_conditions=[{"key": "flight_rebooked", "value": True}, {"key": "caught_up", "value": True}],
            failure_conditions=[],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "A major storm grounded commercial flights."}
        )

    def generate_code_merge_crisis(self, difficulty: int) -> Task:
        routes = [
            Route(id="revert_commit", name="Revert Commit", description="Quickly revert the broken merge to unblock the team.", required_action_types=["delegate", "communicate"], preconditions={}, consequences={"pipeline_unblocked": True}, closes_routes=["hotfix"], milestones_unlocked=["unblocked"], final_reward=1.5),
            Route(id="hotfix", name="Patch Forward", description="Find the logic error and push a hotfix.", required_action_types=["communicate", "spend"], preconditions={}, consequences={"bug_resolved": True}, closes_routes=["revert_commit"], milestones_unlocked=["fixed"], final_reward=3.0),
        ]
        milestones = [
            Milestone(id="unblocked", description="CI pipeline is green again", condition_key="pipeline_unblocked", condition_value=True, reward=1.0),
            Milestone(id="fixed", description="Bug resolved without losing features", condition_key="bug_resolved", condition_value=True, reward=2.0),
        ]
        events = [
            ExoEvent(step=3, probability=0.8, id="cto_ping", description="CTO asks for an ETA on the fix.", world_mutation={}, hidden_state_mutation={}, closes_routes=[]),
        ]
        return Task(
            id="code_merge_task", domain="code_merge_crisis", goal="Resolve Production Outage",
            constraints={"budget_max": 1000, "deadline_step": 8},
            hidden_state={}, 
            mutable_world={"career.stability": -20.0, "mental_wellbeing.stress_level": 30.0}, 
            visible_world={"career.stability": -20.0, "mental_wellbeing.stress_level": 30.0},
            success_conditions=[{"key": "pipeline_unblocked", "value": True}, {"key": "bug_resolved", "value": True}], 
            failure_conditions=[],
            event_schedule=events, viable_routes=routes, milestones=milestones,
            horizon=10 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "A botched merge just took down the staging environment."}
        )

    def generate_career(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Negotiate Workload", description="Discuss with manager to reduce workload.", required_action_types=["communicate"], preconditions={}, consequences={"workload_reduced": True}, closes_routes=["r2"], milestones_unlocked=["m1"], final_reward=2.0),
            Route(id="r2", name="Find New Job", description="Start applying for new roles.", required_action_types=["spend", "communicate"], preconditions={}, consequences={"job_found": True}, closes_routes=["r1", "r3"], milestones_unlocked=["m2"], final_reward=3.0),
            Route(id="r3", name="Delegate to Team", description="Push tasks to junior colleagues.", required_action_types=["delegate"], preconditions={}, consequences={"team_delegated": True}, closes_routes=["r2"], milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="Manager agreed to reduce tasks.", condition_key="workload_reduced", condition_value=True, reward=1.0),
            Milestone(id="m2", description="Interview secured.", condition_key="job_found", condition_value=True, reward=1.5),
            Milestone(id="m3", description="Tasks successfully delegated.", condition_key="team_delegated", condition_value=True, reward=0.8),
        ]
        events = [
            ExoEvent(step=3, probability=0.7, id="boss_asks", description="Boss asks for progress on current tasks.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="career_crisis", domain="career", goal="Manage Career Overload", constraints={"budget_max": 500, "deadline_step": 12}, 
            hidden_state={}, 
            mutable_world={"career.workload": 30.0, "time.free_hours_per_week": -20.0}, 
            visible_world={"career.workload": 30.0, "time.free_hours_per_week": -20.0}, 
            success_conditions=[{"key": "workload_reduced", "value": True}, {"key": "job_found", "value": True}, {"key": "team_delegated", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "Severe workload is threatening your career stability."}
        )

    def generate_finances(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Emergency Fund", description="Dip into savings.", required_action_types=["spend"], preconditions={}, consequences={"used_emergency": True}, closes_routes=[], milestones_unlocked=["m1"], final_reward=1.0),
            Route(id="r2", name="Negotiate Payment Plan", description="Call the creditor to delay payments.", required_action_types=["communicate"], preconditions={}, consequences={"payment_plan": True}, closes_routes=["r1"], milestones_unlocked=["m2"], final_reward=2.5),
            Route(id="r3", name="Sell Asset", description="Liquidate an asset for quick cash.", required_action_types=["communicate", "spend"], preconditions={}, consequences={"asset_sold": True}, closes_routes=["r2"], milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="Emergency fund accessed.", condition_key="used_emergency", condition_value=True, reward=0.5),
            Milestone(id="m2", description="Favorable payment plan negotiated.", condition_key="payment_plan", condition_value=True, reward=1.0),
            Milestone(id="m3", description="Asset successfully sold.", condition_key="asset_sold", condition_value=True, reward=0.8),
        ]
        events = [
            ExoEvent(step=2, probability=0.9, id="late_fee", description="A late fee was applied to the balance.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="finance_crisis", domain="finances", goal="Resolve Financial Pressure", constraints={"budget_max": 1000, "deadline_step": 10}, 
            hidden_state={}, 
            mutable_world={"finances.liquidity": -40.0, "finances.debt_pressure": 20.0}, 
            visible_world={"finances.liquidity": -40.0, "finances.debt_pressure": 20.0}, 
            success_conditions=[{"key": "used_emergency", "value": True}, {"key": "payment_plan", "value": True}, {"key": "asset_sold", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "An unexpected expense has caused financial strain."}
        )

    def generate_relationships(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Couples Therapy", description="Book a session with a therapist.", required_action_types=["spend", "communicate"], preconditions={}, consequences={"therapy_scheduled": True}, closes_routes=["r3"], milestones_unlocked=["m1"], final_reward=3.0),
            Route(id="r2", name="Honest Conversation", description="Sit down and talk through issues.", required_action_types=["communicate"], preconditions={}, consequences={"had_conversation": True}, closes_routes=[], milestones_unlocked=["m2"], final_reward=2.0),
            Route(id="r3", name="Give Space", description="Take some time apart.", required_action_types=["rest"], preconditions={}, consequences={"giving_space": True}, closes_routes=["r1", "r2"], milestones_unlocked=["m3"], final_reward=1.0),
        ]
        milestones = [
            Milestone(id="m1", description="Therapy session completed.", condition_key="therapy_scheduled", condition_value=True, reward=1.5),
            Milestone(id="m2", description="A productive conversation occurred.", condition_key="had_conversation", condition_value=True, reward=1.0),
            Milestone(id="m3", description="Space given without escalation.", condition_key="giving_space", condition_value=True, reward=0.5),
        ]
        events = [
            ExoEvent(step=4, probability=0.6, id="partner_escalates", description="Partner sends an emotional text msg.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="relationship_crisis", domain="relationships", goal="Repair Relationship Friction", constraints={"budget_max": 800, "deadline_step": 14}, 
            hidden_state={}, 
            mutable_world={"relationships.romantic": -30.0, "mental_wellbeing.stress_level": 20.0}, 
            visible_world={"relationships.romantic": -30.0, "mental_wellbeing.stress_level": 20.0}, 
            success_conditions=[{"key": "therapy_scheduled", "value": True}, {"key": "had_conversation", "value": True}, {"key": "giving_space", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "Growing distance and recent conflicts demand attention."}
        )

    def generate_physical_health(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Medical Leave", description="Request time off to recover.", required_action_types=["communicate", "rest"], preconditions={}, consequences={"on_leave": True}, closes_routes=[], milestones_unlocked=["m1"], final_reward=2.5),
            Route(id="r2", name="See Specialist", description="Pay for a top-tier medical consultation.", required_action_types=["spend", "communicate"], preconditions={}, consequences={"saw_doctor": True}, closes_routes=[], milestones_unlocked=["m2"], final_reward=2.0),
            Route(id="r3", name="Lifestyle Change", description="Commit to better diet and sleep.", required_action_types=["rest"], preconditions={}, consequences={"lifestyle_changed": True}, closes_routes=["r1"], milestones_unlocked=["m3"], final_reward=1.5),
        ]
        milestones = [
            Milestone(id="m1", description="Leave approved.", condition_key="on_leave", condition_value=True, reward=1.0),
            Milestone(id="m2", description="Clear diagnosis received.", condition_key="saw_doctor", condition_value=True, reward=1.0),
            Milestone(id="m3", description="First week of new habits complete.", condition_key="lifestyle_changed", condition_value=True, reward=0.5),
        ]
        events = [
            ExoEvent(step=3, probability=0.8, id="doctor_call", description="The clinic calls with test results.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="health_crisis", domain="physical_health", goal="Address Health Warning", constraints={"budget_max": 1500, "deadline_step": 15}, 
            hidden_state={}, 
            mutable_world={"physical_health.energy": -30.0, "mental_wellbeing.stress_level": 30.0}, 
            visible_world={"physical_health.energy": -30.0, "mental_wellbeing.stress_level": 30.0}, 
            success_conditions=[{"key": "on_leave", "value": True}, {"key": "saw_doctor", "value": True}, {"key": "lifestyle_changed", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "Physical symptoms are becoming impossible to ignore."}
        )

    def generate_mental_wellbeing(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Professional Therapy", description="Start regular therapy sessions.", required_action_types=["spend", "communicate"], preconditions={}, consequences={"therapy_started": True}, closes_routes=[], milestones_unlocked=["m1"], final_reward=3.0),
            Route(id="r2", name="Disconnect", description="Take a full digital detox break.", required_action_types=["rest"], preconditions={}, consequences={"disconnected": True}, closes_routes=["r3"], milestones_unlocked=["m2"], final_reward=1.5),
            Route(id="r3", name="Medication Evaluation", description="See a psychiatrist for options.", required_action_types=["spend"], preconditions={}, consequences={"medication_taken": True}, closes_routes=["r2"], milestones_unlocked=["m3"], final_reward=2.0),
        ]
        milestones = [
            Milestone(id="m1", description="Meaningful breakthrough in therapy.", condition_key="therapy_started", condition_value=True, reward=1.5),
            Milestone(id="m2", description="Successfully unplugged for 48 hours.", condition_key="disconnected", condition_value=True, reward=0.8),
            Milestone(id="m3", description="Prescription acquired.", condition_key="medication_taken", condition_value=True, reward=1.0),
        ]
        events = [
            ExoEvent(step=2, probability=0.5, id="panic_attack", description="A sudden wave of severe anxiety hits.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="mental_crisis", domain="mental_wellbeing", goal="Avert Total Burnout", constraints={"budget_max": 600, "deadline_step": 12}, 
            hidden_state={}, 
            mutable_world={"mental_wellbeing.motivation": -35.0, "mental_wellbeing.stress_level": 40.0}, 
            visible_world={"mental_wellbeing.motivation": -35.0, "mental_wellbeing.stress_level": 40.0}, 
            success_conditions=[{"key": "therapy_started", "value": True}, {"key": "disconnected", "value": True}, {"key": "medication_taken", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "Complete exhaustion and loss of motivation."}
        )

    def generate_time(self, difficulty: int) -> Task:
        routes = [
            Route(id="r1", name="Reprioritize", description="Restructure calendar and say 'no'.", required_action_types=["communicate"], preconditions={}, consequences={"priorities_reset": True}, closes_routes=[], milestones_unlocked=["m1"], final_reward=2.0),
            Route(id="r2", name="Delegate", description="Pay someone or ask for help with chores.", required_action_types=["spend", "delegate"], preconditions={}, consequences={"tasks_delegated": True}, closes_routes=[], milestones_unlocked=["m2"], final_reward=1.5),
            Route(id="r3", name="Cancel Commitments", description="Drop out of major upcoming events.", required_action_types=["communicate"], preconditions={}, consequences={"commitments_cancelled": True}, closes_routes=["r1"], milestones_unlocked=["m3"], final_reward=1.0),
        ]
        milestones = [
            Milestone(id="m1", description="Calendar cleared of non-essentials.", condition_key="priorities_reset", condition_value=True, reward=1.0),
            Milestone(id="m2", description="Help secured for daily tasks.", condition_key="tasks_delegated", condition_value=True, reward=0.8),
            Milestone(id="m3", description="Social obligations cancelled.", condition_key="commitments_cancelled", condition_value=True, reward=0.5),
        ]
        events = [
            ExoEvent(step=3, probability=0.9, id="new_request", description="A friend asks for an 'urgent' favor.", world_mutation={}, hidden_state_mutation={}, closes_routes=[])
        ]
        return Task(
            id="time_crisis", domain="time", goal="Regain Time Control", constraints={"budget_max": 300, "deadline_step": 10}, 
            hidden_state={}, 
            mutable_world={"time.free_hours_per_week": -25.0, "time.admin_overhead": 20.0}, 
            visible_world={"time.free_hours_per_week": -25.0, "time.admin_overhead": 20.0}, 
            success_conditions=[{"key": "priorities_reset", "value": True}, {"key": "tasks_delegated", "value": True}, {"key": "commitments_cancelled", "value": True}],
            failure_conditions=[], event_schedule=events, viable_routes=routes, milestones=milestones, horizon=15 + difficulty * 2, difficulty=difficulty, domain_metadata={"story": "You are double-booked and drowning in obligations."}
        )