Spaces:
Sleeping
Sleeping
| """ | |
| config.py β All game constants and tunable parameters. | |
| Single source of truth. Reads sector definitions from sectors.json. | |
| Frozen dataclass: safe to share across modules, cannot be mutated. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| from dataclasses import dataclass, field | |
| from pathlib import Path | |
| from typing import ClassVar | |
| INITIAL_TREASURY = 1000 | |
| BASELINE_TAX = 100 | |
| INITIAL_POPULATION = 1_000_000 | |
| INITIAL_PRODUCTIVITY = 1.0 | |
| MIN_PRODUCTIVITY = 0.5 | |
| MAX_PRODUCTIVITY = 2.0 | |
| MAX_ROUNDS = 50 | |
| CRITICAL_RATIO = 0.4 | |
| SURPLUS_RATIO = 1.5 | |
| WASTAGE_RATIO = 2.5 | |
| RF_MAX = 1.8 | |
| PRODUCTIVITY_STEP = 0.05 | |
| BASE_BIRTH_RATE = 0.005 | |
| BASE_DEATH_RATE = 0.002 | |
| CRISIS_DEATH_PENALTY = 0.01 | |
| class GameConfig: | |
| """Immutable configuration for a NationGame episode.""" | |
| # ββ Population ββββββββββββββββββββββββββββββββββββββββββββββ | |
| POP_0: int = INITIAL_POPULATION | |
| # ββ Treasury ββββββββββββββββββββββββββββββββββββββββββββββββ | |
| INITIAL_TREASURY: float = float(INITIAL_TREASURY) | |
| BASELINE_TAX: float = float(BASELINE_TAX) | |
| # ββ Productivity ββββββββββββββββββββββββββββββββββββββββββββ | |
| INITIAL_PRODUCTIVITY: float = INITIAL_PRODUCTIVITY | |
| PRODUCTIVITY_MIN: float = MIN_PRODUCTIVITY | |
| PRODUCTIVITY_MAX: float = MAX_PRODUCTIVITY | |
| PRODUCTIVITY_STEP: float = PRODUCTIVITY_STEP | |
| # ββ Revenue-curve ratios (relative to Demand) βββββββββββββββ | |
| CRITICAL_RATIO: float = CRITICAL_RATIO | |
| SURPLUS_RATIO: float = SURPLUS_RATIO | |
| WASTAGE_RATIO: float = WASTAGE_RATIO | |
| RF_MAX: float = RF_MAX # peak revenue factor at surplus | |
| # ββ Population dynamics βββββββββββββββββββββββββββββββββββββ | |
| BIRTH_RATE_BASE: float = BASE_BIRTH_RATE | |
| DEATH_RATE_BASE: float = BASE_DEATH_RATE | |
| CRISIS_DEATH_PENALTY: float = CRISIS_DEATH_PENALTY | |
| # ββ Episode limits ββββββββββββββββββββββββββββββββββββββββββ | |
| MAX_ROUNDS: int = MAX_ROUNDS | |
| SHUTDOWN_THRESHOLD: int = 2 # consecutive zero-alloc rounds | |
| # ββ Reward components βββββββββββββββββββββββββββββββββββββββ | |
| PRODUCTIVITY_BONUS_SCALE: float = 50.0 | |
| SURVIVAL_BONUS_PER_ROUND: float = 10.0 | |
| OVER_ALLOC_PENALTY: float = -5.0 | |
| UNDER_ALLOC_PENALTY: float = -10.0 | |
| CRITICAL_PENALTY: float = -1000.0 | |
| BANKRUPTCY_PENALTY: float = -1000.0 | |
| # ββ Prosperity threshold (early-success termination) ββββββββ | |
| PROSPERITY_THRESHOLD: float | None = None # None = disabled | |
| PROSPERITY_STREAK: int = 5 # consecutive rounds above threshold | |
| # ββ Sector data (populated from JSON) βββββββββββββββββββββββ | |
| SECTOR_BASELINES: dict[str, int] = field(default_factory=dict) | |
| SECTOR_ORDER: tuple[str, ...] = () | |
| SECTOR_META: dict[str, dict] = field(default_factory=dict) | |
| # ββ Class-level path to default JSON ββββββββββββββββββββββββ | |
| _DEFAULT_JSON: ClassVar[Path] = Path(__file__).parent / "sectors.json" | |
| # ββ Factory βββββββββββββββββββββββββββββββββββββββββββββββββ | |
| def from_json(cls, path: str | Path | None = None, **overrides) -> "GameConfig": | |
| """ | |
| Build a GameConfig from sectors.json. | |
| Any keyword argument overrides the corresponding field. | |
| """ | |
| path = Path(path) if path else cls._DEFAULT_JSON | |
| with open(path, "r", encoding="utf-8") as f: | |
| data = json.load(f) | |
| sectors_raw: dict = data.get("sectors", {}) | |
| baselines = {name: info["baseline"] for name, info in sectors_raw.items()} | |
| order = tuple(sectors_raw.keys()) | |
| meta = { | |
| name: { | |
| "full_name": info.get("full_name", name), | |
| "description": info.get("description", ""), | |
| } | |
| for name, info in sectors_raw.items() | |
| } | |
| # Merge JSON-level scalars with dataclass defaults | |
| json_fields = { | |
| "POP_0": data.get("initial_population"), | |
| "INITIAL_TREASURY": data.get("initial_treasury"), | |
| "BASELINE_TAX": data.get("baseline_tax"), | |
| "INITIAL_PRODUCTIVITY": data.get("initial_productivity"), | |
| "PRODUCTIVITY_MIN": (data.get("productivity_bounds") or [None])[0], | |
| "PRODUCTIVITY_MAX": (data.get("productivity_bounds") or [None, None])[1], | |
| "PRODUCTIVITY_STEP": data.get("productivity_step"), | |
| "MAX_ROUNDS": data.get("max_rounds"), | |
| "CRITICAL_RATIO": data.get("critical_ratio"), | |
| "SURPLUS_RATIO": data.get("surplus_ratio"), | |
| "WASTAGE_RATIO": data.get("wastage_ratio"), | |
| "RF_MAX": data.get("rf_max"), | |
| "BIRTH_RATE_BASE": data.get("birth_rate_base"), | |
| "DEATH_RATE_BASE": data.get("death_rate_base"), | |
| "CRISIS_DEATH_PENALTY": data.get("crisis_death_penalty"), | |
| } | |
| # Drop None values so dataclass defaults apply | |
| json_fields = {k: v for k, v in json_fields.items() if v is not None} | |
| return cls( | |
| **json_fields, | |
| SECTOR_BASELINES=baselines, | |
| SECTOR_ORDER=order, | |
| SECTOR_META=meta, | |
| **overrides, | |
| ) | |
| # ββ Module-level convenience instance ββββββββββββββββββββββββββ | |
| DEFAULT_CONFIG = GameConfig.from_json() | |