tower-learns-you / tower_game /evolution.py
vknt's picture
Deploy The Tower Learns You (custom gr.Server frontend, hf_inference + mock fallback)
22a027e verified
Raw
History Blame Contribute Delete
3.14 kB
from __future__ import annotations
from typing import Any
from .registry import EVOLUTION_DIAL_STEPS, EVOLUTION_GAIN_LIMITS
from .schemas import ClassEvolution, GeneratedSkillSpec
EVOLUTION_BUDGETS = {1: 3, 2: 4}
def dial_change_cost(dial: str, delta: float) -> int:
options = EVOLUTION_DIAL_STEPS.get(dial)
if not options:
raise ValueError(f"{dial} is not an approved evolution dial")
for approved_delta, cost in options.items():
if abs(float(delta) - approved_delta) < 1e-6:
return cost
approved = ", ".join(f"{value:g}" for value in options)
raise ValueError(f"{dial} delta must be one of: {approved}")
def validate_evolution_rules(
evolution: ClassEvolution,
state: dict[str, Any],
stage: int,
) -> None:
if stage not in EVOLUTION_BUDGETS:
raise ValueError("evolution stage must be 1 or 2")
budget = sum(
dial_change_cost(change.dial, change.delta)
for change in evolution.dial_changes
)
if budget > EVOLUTION_BUDGETS[stage]:
raise ValueError(
f"evolution {stage} uses {budget} dial points; "
f"the budget is {EVOLUTION_BUDGETS[stage]}"
)
changes = {change.dial: float(change.delta) for change in evolution.dial_changes}
if "global_dmg_mult" in changes and "crit_multiplier_dial" in changes:
raise ValueError("global damage and critical multiplier cannot grow together")
if "max_ap_bonus" in changes and "ap_regen_bonus" in changes:
raise ValueError("maximum AP and AP regeneration cannot grow together")
if "ap_regen_bonus" in changes and stage != 2:
raise ValueError("AP regeneration is only available at Evolution 2")
previous_specials = set(state.get("evolution_special_dials", []))
if "max_ap_bonus" in changes and "max_ap_bonus" in previous_specials:
raise ValueError("maximum AP may increase only once per run")
if "ap_regen_bonus" in changes and "ap_regen_bonus" in previous_specials:
raise ValueError("AP regeneration may increase only once per run")
if (
("max_ap_bonus" in changes and "ap_regen_bonus" in previous_specials)
or ("ap_regen_bonus" in changes and "max_ap_bonus" in previous_specials)
):
raise ValueError("maximum AP and AP regeneration cannot both grow in one run")
current_gains = state.get("evolution_dial_gains", {})
for dial, delta in changes.items():
ceiling = EVOLUTION_GAIN_LIMITS[dial]
if float(current_gains.get(dial, 0.0)) + delta > ceiling + 1e-6:
raise ValueError(f"{dial} exceeds its cumulative evolution ceiling")
signature: GeneratedSkillSpec | None = evolution.signature_skill
if (
signature is not None
and signature.effect_id == "heavy_damage"
and signature.base_power > 17
and ({"global_dmg_mult", "crit_multiplier_dial"} & changes.keys())
):
raise ValueError(
"a heavy signature above 17 cannot also grow global damage "
"or critical multiplier"
)