CoolFace
Apppublic

build-small-hackathon/aube-nova

sourceHugging Faceupdated 3mo agoView on Hugging Face
1likes
events.py256 linesDownload Raw Back to simulation
1"""2Two types of injectable events:3- CRISES: degrade resources, create urgency4- OPTIMISM (Beginning of Infinity mechanic): generate permanent breakthroughs5  inspired by Deutsch's idea that all problems are soluble given good explanations.6"""7 8import random9 10from simulation.colonist import ALL_TRAITS11from simulation.world import Breakthrough, WorldState12 13CRISIS_DEFS = [14    {15        "id": "oxygen_failure",16        "trigger": lambda s: s.resources["oxygen"] < 22,17        "announcement": "⚠️ OXYGEN PLANT FAILURE: Primary reactor offline. Reserves dropping.",18        "resource_delta": {"oxygen": -18.0},19    },20    {21        "id": "food_blight",22        "trigger": lambda s: random.random() < 0.006,23        "announcement": "🌱 GREENHOUSE BLIGHT: Fungal infection detected. Food output cut by 40%.",24        "resource_delta": {"food": -12.0},25        "building_effect": ("Greenhouse", "strained"),26    },27    {28        "id": "solar_storm",29        "trigger": lambda s: s.tick > 0 and s.tick % 143 == 0,  # every ~2.75 years30        "announcement": "☀️ SOLAR STORM: All surface operations suspended. Energy and credits hit.",31        "resource_delta": {"energy": -22.0, "credits": -12.0},32    },33    {34        "id": "credit_crisis",35        "trigger": lambda s: s.resources["credits"] < 8,36        "announcement": "💳 CREDIT CRISIS: Colony economy collapsing. Trade suspended.",37        "resource_delta": {"credits": -5.0},38    },39]40 41# Breakthroughs are Deutsch-inspired: permanent knowledge gains from collective reasoning.42# Each has a title, description, which building it improves, and by how much.43BREAKTHROUGH_DEFS = [44    {45        "title": "Closed-Loop Oxygen Recycling",46        "description": "A researcher proposed that CO2 scrubbers could be re-integrated with "47        "the habitat's water system. The colony adopted the explanation and built it. "48        "Habitat oxygen efficiency improved permanently by 20%.",49        "building_name": "Habitat",50        "bonus": 0.20,51        "required_job": "researcher",52    },53    {54        "title": "Hydroponic Yield Optimization",55        "description": "By studying root zone temperature gradients, the farming team devised "56        "a new planting schedule. Food production from the Greenhouse improved "57        "permanently by 20%.",58        "building_name": "Greenhouse",59        "bonus": 0.20,60        "required_job": "farmer",61    },62    {63        "title": "Reactor Load Balancing",64        "description": "An engineer discovered that staggering equipment power cycles reduced "65        "peak draw by 25%. Energy system efficiency improved permanently.",66        "building_name": "Reactor",67        "bonus": 0.25,68        "required_job": "engineer",69    },70    {71        "title": "Cooperative Labor Networks",72        "description": "Colonists proposed rotating specializations during crises. "73        "All buildings temporarily gain resilience against future failures.",74        "building_name": "Habitat",75        "bonus": 0.10,76        "required_job": None,  # any colonist can propose this77    },78]79 80 81def check_and_fire_crises(state: WorldState) -> list[str]:82    events = []83    for crisis in CRISIS_DEFS:84        if state.crisis_active == crisis["id"]:85            continue86        if crisis["trigger"](state):87            events.append(crisis["announcement"])88            for res, delta in crisis.get("resource_delta", {}).items():89                state.resources[res] = max(0.0, state.resources[res] + delta)90            if "building_effect" in crisis:91                bname, status = crisis["building_effect"]92                for b in state.buildings:93                    if b.name == bname:94                        b.status = status95            state.crisis_active = crisis["id"]96            state.crisis_duration_remaining = 1097            state.event_timeline.append(98                {99                    "tick": state.tick,100                    "year": state.year,101                    "type": "crisis",102                    "label": crisis["announcement"][:40],103                }104            )105    return events106 107 108def inject_manual_crisis(state: WorldState) -> str:109    """Called by the UI crisis button — randomly picks from all crisis types."""110    # Prefer crises not already active to avoid repetition111    available = [c for c in CRISIS_DEFS if state.crisis_active != c["id"]]112    if not available:113        available = list(CRISIS_DEFS)114    crisis = random.choice(available)115    for res, delta in crisis.get("resource_delta", {}).items():116        state.resources[res] = max(0.0, state.resources[res] + delta)117    if "building_effect" in crisis:118        bname, status = crisis["building_effect"]119        for b in state.buildings:120            if b.name == bname:121                b.status = status122    state.crisis_active = crisis["id"]123    state.crisis_duration_remaining = 10124    state.event_timeline.append(125        {126            "tick": state.tick,127            "year": state.year,128            "type": "crisis",129            "label": crisis["announcement"][:40],130        }131    )132    return crisis["announcement"]133 134 135def inject_optimism(state: WorldState) -> tuple[str, dict | None]:136    """137    Beginning of Infinity mechanic: inject a period of explanatory progress.138    Returns (announcement, breakthrough_dict_or_None).139 140    Philosophy: problems are inevitable, but so are solutions — given good explanations.141    The colony's knowledge grows permanently, not just its resources.142    """143    # Don't fire if already active144    if state.optimism_active:145        return "💡 A breakthrough is already underway.", None146 147    # Find an applicable breakthrough not yet discovered148    discovered_titles = {b.title for b in state.breakthroughs}149    available = [b for b in BREAKTHROUGH_DEFS if b["title"] not in discovered_titles]150 151    if not available:152        return (153            "🌟 The colony has achieved all known breakthroughs. A new era begins.",154            None,155        )156 157    # Prefer breakthroughs matching current colonist jobs158    living_jobs = {c.job for c in state.living}159    relevant = [160        b161        for b in available162        if b.get("required_job") in living_jobs or not b.get("required_job")163    ]164    chosen_def = random.choice(relevant or available)165 166    # Apply to the right building167    for building in state.buildings:168        if building.name == chosen_def["building_name"]:169            building.efficiency_bonus += chosen_def["bonus"]170            break171 172    bt = Breakthrough(173        year=state.year_int,174        title=chosen_def["title"],175        description=chosen_def["description"],176        building_id=chosen_def["building_name"],177        bonus=chosen_def["bonus"],178    )179    state.breakthroughs.append(bt)180    state.event_timeline.append(181        {182            "tick": state.tick,183            "year": state.year,184            "type": "breakthrough",185            "label": chosen_def["title"],186        }187    )188    state.optimism_active = True189 190    # Optimism fades after 8 ticks (2 months)191    state._optimism_ticks_remaining = 8192 193    announcement = (194        f"💡 EXPLANATORY BREAKTHROUGH: {chosen_def['title']}\n"195        f"{chosen_def['description']}\n"196        f"Production bonus: +{chosen_def['bonus'] * 100:.0f}% for {chosen_def['building_name']} permanently."197    )198 199    # Track proposer's notable action200    for c in state.living:201        if c.job == chosen_def.get("required_job") or not chosen_def.get(202            "required_job"203        ):204            c.add_notable_action(f"Year {state.year:.1f}: contributed to '{bt.title}'")205            break206 207    return announcement, bt208 209 210def tick_cultural_drift(state: WorldState) -> None:211    """212    Silently drift colonist traits each tick based on colony conditions.213    ~2% chance per living colonist per tick — creates gradual cultural evolution214    visible in the Cultural DNA bars without flooding the event feed.215    """216    if not state.living:217        return218 219    # Context-weighted trait pools220    scarce = (221        state.resources.get("oxygen", 100) < 25 or state.resources.get("food", 100) < 25222    )223    if scarce:224        drift_pool = ["stubborn", "fearful", "pragmatic", "resourceful", "risk-averse"]225    elif state.optimism_active:226        drift_pool = [227            "optimistic",228            "inventive",229            "cooperative",230            "ambitious",231            "scientific",232        ]233    elif state.crisis_active:234        drift_pool = ["risk-averse", "loyal", "pragmatic", "stubborn", "empathetic"]235    else:236        drift_pool = ALL_TRAITS  # random drift in stable times237 238    for c in state.living:239        if random.random() > 0.02:240            continue241        candidates = [t for t in drift_pool if t not in c.traits]242        if not candidates:243            continue244        new_trait = random.choice(candidates)245        old_trait = random.choice(c.traits)246        c.traits = [new_trait if t == old_trait else t for t in c.traits]247 248 249def tick_optimism(state: WorldState) -> None:250    """Call each tick to manage optimism duration."""251    if state.optimism_active:252        remaining = getattr(state, "_optimism_ticks_remaining", 0) - 1253        state._optimism_ticks_remaining = remaining254        if remaining <= 0:255            state.optimism_active = False256