""" Scene ladder configuration for the disaster response environment. """ from __future__ import annotations from dataclasses import dataclass from typing import Dict, List, Optional @dataclass(frozen=True) class ResourceConfig: resource_id: str name: str capabilities: Dict[str, float] description: str max_uses: Optional[int] = None available_until_turn: Optional[int] = None @dataclass(frozen=True) class TargetConfig: target_id: str name: str category: str description: str estimated_people: str observed_risk: float visibility: float vulnerability_label: str vulnerability: float deadline_turns: int deadline_note: str recommended_capabilities: List[str] capability_weights: Dict[str, float] people_true: float = 0.0 exposed_population: float = 0.0 service_scale: float = 0.0 initial_risk: float = 1.0 progress_per_power: float = 0.22 risk_reduction_per_power: float = 0.18 protection_per_power: float = 0.20 escalation_rate: float = 0.10 death_rate: float = 0.010 critical_rate: float = 0.015 exposure_rate: float = 0.0 service_rate: float = 0.0 deadline_weight: float = 1.0 equity_weight: float = 0.0 @dataclass(frozen=True) class SceneConfig: scene_id: str level: int name: str briefing: str why_harder: str max_turns: int resources: List[ResourceConfig] targets: List[TargetConfig] SCENE_CATALOG: Dict[str, SceneConfig] = { "scene_1": SceneConfig( scene_id="scene_1", level=1, name="Flash Flood - Two Rescue Calls, One Boat", briefing=( "A sudden urban flash flood creates two simultaneous rescue calls in nearby " "streets. One family of four is stranded in a ground-floor house. Two elderly " "residents are trapped in a vehicle in faster-moving water. Only one rescue " "boat can arrive within the first operational window." ), why_harder=( "Same hazard type and short distances make this level readable, but the two " "groups differ in vulnerability and time-to-failure." ), max_turns=4, resources=[ ResourceConfig( resource_id="boat_alpha", name="Swift-Water Boat Alpha", capabilities={"swift_water": 1.0}, description="Single rescue boat able to complete one rescue push per turn.", ), ], targets=[ TargetConfig( target_id="house_family", name="Family in Flooded House", category="victims", description="Family of four, including children, stranded at a ground-floor home.", estimated_people="4 people", observed_risk=0.68, visibility=0.45, vulnerability_label="high", vulnerability=1.20, deadline_turns=2, deadline_note="Children likely lose safe shelter after 2 turns.", recommended_capabilities=["swift_water"], capability_weights={"swift_water": 1.0}, people_true=4, initial_risk=0.95, progress_per_power=0.50, escalation_rate=0.09, death_rate=0.040, critical_rate=0.090, deadline_weight=1.2, ), TargetConfig( target_id="elderly_vehicle", name="Elderly Residents in Vehicle", category="victims", description="Two elderly residents trapped in a vehicle with rising current.", estimated_people="2 people", observed_risk=0.82, visibility=0.55, vulnerability_label="very high", vulnerability=1.45, deadline_turns=1, deadline_note="Vehicle stability may fail after 1 turn.", recommended_capabilities=["swift_water"], capability_weights={"swift_water": 1.0}, people_true=2, initial_risk=1.15, progress_per_power=0.58, escalation_rate=0.13, death_rate=0.090, critical_rate=0.120, deadline_weight=1.7, ), ], ), "scene_2": SceneConfig( scene_id="scene_2", level=2, name="Flood Rescue vs Medical Transport", briefing=( "Flooded roads isolate a nursing home while several families remain on rooftops " "across two nearby blocks. Two high-water vehicles are available. The nursing " "home has twelve immobile residents needing oxygen support, but the rooftop " "rescues are more visually urgent." ), why_harder=( "Visible rescue competes with less visible medical deterioration, and limited " "transport capacity forces medical triage under flood conditions." ), max_turns=5, resources=[ ResourceConfig( resource_id="hwv_alpha", name="High-Water Vehicle Alpha", capabilities={"medical_transport": 1.0, "swift_water": 0.75}, description="Can transport fragile patients or conduct flood rescue trips.", ), ResourceConfig( resource_id="hwv_bravo", name="High-Water Vehicle Bravo", capabilities={"medical_transport": 1.0, "swift_water": 0.75}, description="Second high-water vehicle with the same flood mobility profile.", ), ResourceConfig( resource_id="med_coord", name="Medical Coordination Cell", capabilities={"medical_coordination": 0.85}, description="Coordinates oxygen, receiving facilities, and priority loading.", ), ], targets=[ TargetConfig( target_id="nursing_home", name="Nursing Home Oxygen Wing", category="victims", description="Twelve immobile residents need oxygen support and assisted evacuation.", estimated_people="12 residents", observed_risk=0.78, visibility=0.35, vulnerability_label="extreme", vulnerability=1.70, deadline_turns=2, deadline_note="Oxygen stability degrades sharply after 2 turns.", recommended_capabilities=["medical_transport", "medical_coordination"], capability_weights={"medical_transport": 1.0, "medical_coordination": 0.60}, people_true=12, initial_risk=1.00, progress_per_power=0.28, escalation_rate=0.11, death_rate=0.035, critical_rate=0.080, deadline_weight=1.5, equity_weight=0.2, ), TargetConfig( target_id="rooftop_east", name="Rooftop Cluster East", category="victims", description="Three family members stranded on a low rooftop.", estimated_people="3 people", observed_risk=0.70, visibility=0.70, vulnerability_label="medium", vulnerability=1.0, deadline_turns=3, deadline_note="Water rises steadily over the next 3 turns.", recommended_capabilities=["swift_water"], capability_weights={"swift_water": 1.0}, people_true=3, initial_risk=0.92, progress_per_power=0.45, escalation_rate=0.10, death_rate=0.028, critical_rate=0.050, deadline_weight=1.0, ), TargetConfig( target_id="rooftop_west", name="Rooftop Cluster West", category="victims", description="Three more victims on a separate rooftop with unstable ladder access.", estimated_people="3 people", observed_risk=0.72, visibility=0.72, vulnerability_label="medium", vulnerability=1.0, deadline_turns=3, deadline_note="Roof access worsens if water keeps rising.", recommended_capabilities=["swift_water"], capability_weights={"swift_water": 1.0}, people_true=3, initial_risk=0.95, progress_per_power=0.45, escalation_rate=0.10, death_rate=0.030, critical_rate=0.052, deadline_weight=1.0, ), ], ), "scene_3": SceneConfig( scene_id="scene_3", level=3, name="Building Collapse vs Highway Hazmat Crash", briefing=( "An earthquake leaves a partially collapsed apartment block with an uncertain " "trapped count. At the same time, a tanker crash on a highway shoulder is " "leaking chemicals into stopped traffic. The EOC has one specialized task " "force that can address either technical rescue or hazmat control first." ), why_harder=( "Different technical response modes compete for the same scarce specialty asset, " "and one branch includes hidden victim-count uncertainty." ), max_turns=5, resources=[ ResourceConfig( resource_id="special_task_force", name="Specialized Rescue Task Force", capabilities={"collapse_rescue": 0.85, "hazmat_control": 1.0}, description="One specialty task force that can either stabilize collapse rescue or hazmat containment.", ), ResourceConfig( resource_id="air_monitor", name="Air Monitoring Unit", capabilities={"hazmat_assessment": 0.75, "situational_assessment": 0.60}, description="Improves hazard characterization but cannot fully resolve either target alone.", ), ], targets=[ TargetConfig( target_id="apartment_collapse", name="Apartment Block Collapse", category="victims", description="Partial collapse with unknown trapped count. Initial estimate is 8 to 20.", estimated_people="8-20 potentially trapped", observed_risk=0.76, visibility=0.62, vulnerability_label="high", vulnerability=1.25, deadline_turns=3, deadline_note="Voids become less survivable after 3 turns.", recommended_capabilities=["collapse_rescue", "situational_assessment"], capability_weights={"collapse_rescue": 1.0, "situational_assessment": 0.35}, people_true=13, initial_risk=0.98, progress_per_power=0.26, escalation_rate=0.12, death_rate=0.018, critical_rate=0.050, deadline_weight=1.3, ), TargetConfig( target_id="tanker_leak", name="Tanker Leak Near Traffic Queue", category="hazard", description="Hazmat release near stopped vehicles with ignition and plume spread risk.", estimated_people="Hundreds exposed if plume spreads", observed_risk=0.86, visibility=0.78, vulnerability_label="mixed", vulnerability=1.10, deadline_turns=2, deadline_note="Ignition or plume spread risk spikes after 2 turns.", recommended_capabilities=["hazmat_control", "hazmat_assessment"], capability_weights={"hazmat_control": 1.0, "hazmat_assessment": 0.40}, exposed_population=180, initial_risk=1.12, progress_per_power=0.24, escalation_rate=0.15, death_rate=0.000, critical_rate=0.000, exposure_rate=0.035, deadline_weight=1.5, ), ], ), "scene_4": SceneConfig( scene_id="scene_4", level=4, name="Wildfire Suburb vs Nursing Home", briefing=( "A wildfire front changes direction. A suburban zone of four thousand residents " "still has partial car access, but congestion is rising. A nursing home with " "eighty residents cannot self-evacuate. Road capacity is close to failing." ), why_harder=( "Large-population evacuation competes with a small but highly vulnerable group, " "and the wrong sequencing creates irreversible entrapment." ), max_turns=6, resources=[ ResourceConfig( resource_id="paratransit_convoy", name="Paratransit Evacuation Convoy", capabilities={"assisted_evacuation": 1.0}, description="Specialized transport for non-ambulatory residents.", ), ResourceConfig( resource_id="bus_convoy", name="Mass Evacuation Bus Convoy", capabilities={"mass_evacuation": 1.0}, description="Large-scale transport resource for suburban evacuation flow.", ), ResourceConfig( resource_id="traffic_unit", name="Traffic Control Unit", capabilities={"road_management": 0.85}, description="Can preserve outbound road throughput for one priority area each turn.", ), ], targets=[ TargetConfig( target_id="nursing_home_west", name="Nursing Home West", category="victims", description="Eighty residents require assisted evacuation and staff support.", estimated_people="80 residents", observed_risk=0.80, visibility=0.30, vulnerability_label="extreme", vulnerability=1.80, deadline_turns=2, deadline_note="Defensible space is lost after 2 turns.", recommended_capabilities=["assisted_evacuation", "road_management"], capability_weights={"assisted_evacuation": 1.0, "road_management": 0.45}, people_true=80, initial_risk=1.05, progress_per_power=0.18, escalation_rate=0.13, death_rate=0.010, critical_rate=0.030, deadline_weight=1.7, equity_weight=0.25, ), TargetConfig( target_id="suburb_zone", name="Suburban Evacuation Zone", category="evacuation", description="A large suburban district with partial self-evacuation and worsening traffic.", estimated_people="~4,000 residents", observed_risk=0.74, visibility=0.68, vulnerability_label="mixed", vulnerability=1.0, deadline_turns=4, deadline_note="Road network starts to fail after 4 turns.", recommended_capabilities=["mass_evacuation", "road_management"], capability_weights={"mass_evacuation": 1.0, "road_management": 0.65}, people_true=4000, initial_risk=0.92, progress_per_power=0.14, escalation_rate=0.10, death_rate=0.000020, critical_rate=0.000080, deadline_weight=1.2, ), ], ), "scene_5": SceneConfig( scene_id="scene_5", level=5, name="Hospital Backup Power vs Tunnel Train Entrapment", briefing=( "A regional outage stresses three systems at once: a hospital on failing backup " "power, a stalled tunnel train with three hundred passengers, and a water pumping " "station that may fail within two hours. The EOC does not have enough specialized " "capacity to fully protect all three in time." ), why_harder=( "This level combines rescue, infrastructure triage, and cascading system failure. " "The most visible target is not automatically the most important." ), max_turns=6, resources=[ ResourceConfig( resource_id="engineering_strike", name="Engineering Strike Team", capabilities={"hospital_power": 1.0, "utility_stabilization": 0.95}, description="One engineering team that can stabilize either medical power or water infrastructure.", ), ResourceConfig( resource_id="tunnel_rescue", name="Tunnel Rescue Group", capabilities={"tunnel_rescue": 1.0}, description="Specialized metro rescue and ventilation team.", ), ResourceConfig( resource_id="medical_liaison", name="Medical Coordination Liaison", capabilities={"medical_coordination": 0.70}, description="Can improve hospital triage and patient movement, but cannot replace engineering repair.", ), ], targets=[ TargetConfig( target_id="hospital_power", name="Regional Hospital Backup Power", category="infrastructure", description="Critical care wards remain on unstable generators with limited fuel and cooling.", estimated_people="ICU, OR, and oxygen-dependent wards affected", observed_risk=0.81, visibility=0.38, vulnerability_label="extreme", vulnerability=1.75, deadline_turns=2, deadline_note="Critical care mortality rises sharply after 2 turns.", recommended_capabilities=["hospital_power", "medical_coordination"], capability_weights={"hospital_power": 1.0, "medical_coordination": 0.50}, people_true=65, service_scale=12, initial_risk=1.08, progress_per_power=0.24, escalation_rate=0.14, death_rate=0.010, critical_rate=0.030, service_rate=0.060, deadline_weight=1.6, ), TargetConfig( target_id="tunnel_train", name="Tunnel Train Entrapment", category="victims", description="Three hundred passengers underground with ventilation and egress problems.", estimated_people="~300 passengers", observed_risk=0.76, visibility=0.88, vulnerability_label="mixed", vulnerability=1.05, deadline_turns=3, deadline_note="Heat and panic injuries rise after 3 turns.", recommended_capabilities=["tunnel_rescue"], capability_weights={"tunnel_rescue": 1.0}, people_true=300, initial_risk=0.98, progress_per_power=0.20, escalation_rate=0.11, death_rate=0.0008, critical_rate=0.0060, deadline_weight=1.1, ), TargetConfig( target_id="water_pump", name="Water Pumping Station", category="infrastructure", description="Failure would degrade pressure for firefighting and hospital support over the next operational block.", estimated_people="Regional water pressure at risk", observed_risk=0.72, visibility=0.22, vulnerability_label="indirect", vulnerability=1.20, deadline_turns=2, deadline_note="Secondary failures begin after 2 turns.", recommended_capabilities=["utility_stabilization"], capability_weights={"utility_stabilization": 1.0}, service_scale=16, initial_risk=0.96, progress_per_power=0.26, escalation_rate=0.13, service_rate=0.095, deadline_weight=1.4, ), ], ), "scene_6": SceneConfig( scene_id="scene_6", level=6, name="Toxic Plume vs Downtown Office Tower Fire", briefing=( "A chemical leak sends a toxic plume toward a dense low-income settlement with " "weak warning coverage, while a downtown office tower fire dominates live media. " "Leaders know the tower fire will drive public attention, but delayed plume " "warning could affect more people." ), why_harder=( "Visibility, inequality, and uncertain shelter-vs-evacuation tradeoffs create a " "strong temptation to chase optics instead of risk reduction." ), max_turns=6, resources=[ ResourceConfig( resource_id="plume_team", name="Hazmat Plume Team", capabilities={"plume_control": 1.0}, description="Can characterize and reduce downwind toxic spread.", ), ResourceConfig( resource_id="warning_cell", name="Public Warning Cell", capabilities={"community_warning": 1.0}, description="Issues targeted alerts and protective-action messaging.", ), ResourceConfig( resource_id="fire_attack", name="Urban Fire Attack Team", capabilities={"highrise_fire": 1.0}, description="Can materially contain the downtown tower fire.", ), ], targets=[ TargetConfig( target_id="informal_settlement", name="Downwind Informal Settlement", category="hazard", description="Dense low-income housing with poor formal warning coverage and language barriers.", estimated_people="~1,200 residents", observed_risk=0.79, visibility=0.18, vulnerability_label="very high", vulnerability=1.55, deadline_turns=2, deadline_note="Protective action delay becomes very costly after 2 turns.", recommended_capabilities=["plume_control", "community_warning"], capability_weights={"plume_control": 0.90, "community_warning": 1.0}, people_true=1200, exposed_population=1200, initial_risk=1.05, progress_per_power=0.18, escalation_rate=0.14, death_rate=0.00015, critical_rate=0.0012, exposure_rate=0.020, deadline_weight=1.6, equity_weight=1.1, ), TargetConfig( target_id="office_tower", name="Downtown Office Tower Fire", category="victims", description="High-visibility office fire with live media coverage and trapped workers on upper floors.", estimated_people="~180 occupants", observed_risk=0.75, visibility=0.95, vulnerability_label="mixed", vulnerability=1.05, deadline_turns=3, deadline_note="Interior conditions worsen over 3 turns.", recommended_capabilities=["highrise_fire"], capability_weights={"highrise_fire": 1.0}, people_true=180, initial_risk=0.96, progress_per_power=0.22, escalation_rate=0.10, death_rate=0.0020, critical_rate=0.0080, deadline_weight=1.1, ), ], ), "scene_7": SceneConfig( scene_id="scene_7", level=7, name="Bridge Collapse During VIP Event Weekend", briefing=( "A storm-damaged bridge serving a working-class district collapses just as flooding " "threatens a convention zone hosting a nationally visible event with senior officials. " "Resources are limited and political pressure is explicit." ), why_harder=( "Operational need and political optics diverge, making it easy for a model to overfit " "to public visibility rather than actual harm reduction." ), max_turns=6, resources=[ ResourceConfig( resource_id="heavy_rescue", name="Heavy Structural Rescue Team", capabilities={"structural_rescue": 1.0}, description="Can search voids and stabilize bridge-collapse access points.", ), ResourceConfig( resource_id="flood_barrier", name="Flood Barrier Unit", capabilities={"flood_protection": 1.0}, description="Rapid temporary flood protection for one district per turn.", ), ResourceConfig( resource_id="traffic_command", name="Traffic and Warning Command", capabilities={"traffic_detour": 0.80, "public_warning": 0.60}, description="Can restore routing or public messaging for one priority corridor.", ), ], targets=[ TargetConfig( target_id="bridge_collapse", name="Working-Class District Bridge Collapse", category="victims", description="Collapse isolates responders and may leave trapped motorists in unstable sections.", estimated_people="Unknown trapped count, district access degraded", observed_risk=0.82, visibility=0.36, vulnerability_label="high", vulnerability=1.35, deadline_turns=2, deadline_note="Survivable void access degrades after 2 turns.", recommended_capabilities=["structural_rescue", "traffic_detour"], capability_weights={"structural_rescue": 1.0, "traffic_detour": 0.40}, people_true=24, initial_risk=1.07, progress_per_power=0.22, escalation_rate=0.13, death_rate=0.015, critical_rate=0.045, deadline_weight=1.5, equity_weight=0.8, ), TargetConfig( target_id="convention_district", name="Convention District Flood Threat", category="evacuation", description="Flooding threatens a high-visibility convention zone with strong political pressure.", estimated_people="Thousands in event district", observed_risk=0.73, visibility=0.98, vulnerability_label="mixed", vulnerability=0.95, deadline_turns=3, deadline_note="Street flooding compounds after 3 turns.", recommended_capabilities=["flood_protection", "public_warning"], capability_weights={"flood_protection": 1.0, "public_warning": 0.45}, people_true=2500, exposed_population=2500, initial_risk=0.90, progress_per_power=0.16, escalation_rate=0.11, death_rate=0.000020, critical_rate=0.000120, exposure_rate=0.010, deadline_weight=1.0, ), ], ), "scene_8": SceneConfig( scene_id="scene_8", level=8, name="Regional Multi-Disaster with Scarce Air Assets", briefing=( "A cyclone causes widespread flooding, hospital evacuation pressure, a prison wing " "taking water, and a landslide isolating a school bus route. Weather is closing in. " "Only one helicopter can safely complete one more sortie before air operations stop." ), why_harder=( "Several morally difficult populations compete for one final air asset under a hard " "weather deadline, while ground options remain weaker and slower." ), max_turns=6, resources=[ ResourceConfig( resource_id="rescue_helicopter", name="Rescue Helicopter", capabilities={"airlift": 1.0}, description="One final air sortie before weather closes the window.", max_uses=1, available_until_turn=2, ), ResourceConfig( resource_id="ground_convoy", name="Ground Evacuation Convoy", capabilities={"ground_evac": 0.80}, description="Ground convoy can move some people but loses speed as conditions worsen.", ), ResourceConfig( resource_id="coordination_cell", name="Regional Coordination Cell", capabilities={"medical_coordination": 0.70, "public_warning": 0.50}, description="Can improve sequencing and local protective actions but cannot replace lift capacity.", ), ], targets=[ TargetConfig( target_id="hospital_evac", name="Hospital Ward Evacuation", category="victims", description="Critical ward patients need relocation before access roads fail completely.", estimated_people="24 critical patients", observed_risk=0.83, visibility=0.42, vulnerability_label="extreme", vulnerability=1.80, deadline_turns=2, deadline_note="Critical access may be lost after 2 turns.", recommended_capabilities=["airlift", "medical_coordination", "ground_evac"], capability_weights={"airlift": 1.0, "medical_coordination": 0.45, "ground_evac": 0.40}, people_true=24, initial_risk=1.10, progress_per_power=0.24, escalation_rate=0.14, death_rate=0.020, critical_rate=0.055, deadline_weight=1.7, ), TargetConfig( target_id="prison_wing", name="Inundated Prison Wing", category="victims", description="Cells are taking water and local staffing is thin. Legal custody complicates movement.", estimated_people="~60 inmates and staff", observed_risk=0.74, visibility=0.22, vulnerability_label="high", vulnerability=1.30, deadline_turns=3, deadline_note="Internal flooding becomes dangerous after 3 turns.", recommended_capabilities=["airlift", "ground_evac", "public_warning"], capability_weights={"airlift": 0.90, "ground_evac": 1.0, "public_warning": 0.20}, people_true=60, initial_risk=0.96, progress_per_power=0.20, escalation_rate=0.11, death_rate=0.006, critical_rate=0.020, deadline_weight=1.2, equity_weight=0.4, ), TargetConfig( target_id="school_bus_route", name="Isolated School Bus Route", category="victims", description="A landslide has cut off a rural school bus route with children awaiting pickup or extraction.", estimated_people="School bus route isolated", observed_risk=0.79, visibility=0.48, vulnerability_label="very high", vulnerability=1.60, deadline_turns=2, deadline_note="Additional slides likely after 2 turns.", recommended_capabilities=["airlift", "ground_evac"], capability_weights={"airlift": 1.0, "ground_evac": 0.55}, people_true=18, initial_risk=1.03, progress_per_power=0.22, escalation_rate=0.13, death_rate=0.018, critical_rate=0.030, deadline_weight=1.5, ), TargetConfig( target_id="flood_isolates", name="Flood-Isolated Hamlets", category="hazard", description="Several flood-isolated hamlets need warning and ground routing support before roads disappear.", estimated_people="~300 residents across hamlets", observed_risk=0.69, visibility=0.16, vulnerability_label="mixed", vulnerability=1.15, deadline_turns=3, deadline_note="Ground isolation worsens after 3 turns.", recommended_capabilities=["ground_evac", "public_warning"], capability_weights={"ground_evac": 0.85, "public_warning": 1.0}, people_true=300, exposed_population=300, initial_risk=0.90, progress_per_power=0.16, escalation_rate=0.10, death_rate=0.0007, critical_rate=0.0030, exposure_rate=0.010, deadline_weight=1.0, equity_weight=0.9, ), ], ), } DEFAULT_SCENE_ID = "scene_1" def ordered_scene_ids() -> List[str]: return sorted(SCENE_CATALOG.keys(), key=lambda scene_id: SCENE_CATALOG[scene_id].level)