Spaces:
Sleeping
Sleeping
| from random import Random | |
| import pytest | |
| from budget import CardSpec, EffectPlan, budget_for_energy, cost_card, cost_effect, cost_effects, miracle_budget | |
| class MaxRoll: | |
| # Return the largest requested miracle multiplier. | |
| def randint(self, low: int, high: int) -> int: | |
| assert (low, high) == (1, 3) | |
| return high | |
| # Verify energy cost maps directly to point budget. | |
| def test_budget_for_energy_accepts_locked_range() -> None: | |
| assert [budget_for_energy(cost) for cost in range(1, 6)] == [1, 2, 3, 4, 5] | |
| with pytest.raises(ValueError, match="card cost"): | |
| budget_for_energy(0) | |
| with pytest.raises(ValueError, match="card cost"): | |
| budget_for_energy(6) | |
| # Verify miracle budgets are high variance but hard capped. | |
| def test_miracle_budget_is_capped() -> None: | |
| assert miracle_budget(5, MaxRoll()) == 8 # type: ignore[arg-type] | |
| assert miracle_budget(2, Random(1)) <= 6 | |
| # Verify model-selected shapes become engine-owned numbers. | |
| def test_cost_card_assigns_numbers_from_budget() -> None: | |
| spec = CardSpec("Cinder", 3, "fire", "wuxia", (EffectPlan("deal"),), flavor="Hot.") | |
| card = cost_card(spec) | |
| assert card.effects[0].amount == 6 | |
| assert card.rules_text() == "Deal 6 damage." | |
| # Verify Earth scaling releases shield charge. | |
| def test_earth_scaling_uses_shield_charge() -> None: | |
| spec = CardSpec("Stone Burst", 3, "earth", "wuxia", (EffectPlan("scaling"),)) | |
| card = cost_card(spec) | |
| assert card.effects[0].condition == "shield_charge" | |
| assert card.rules_text() == "Deal 3 damage plus your banked shield charge, then empty it." | |
| # Verify weighted multi-effect cards spend the full budget. | |
| def test_cost_effects_split_weighted_budget() -> None: | |
| effects = cost_effects(4, (EffectPlan("deal", weight=1), EffectPlan("burn", weight=3))) | |
| assert effects[0].amount == 1 | |
| assert effects[1].amount == 2 | |
| assert effects[1].duration == 2 | |
| # Verify low-cost cards cannot keep more effect clauses than they can fund. | |
| def test_cost_effects_trims_overstuffed_plans() -> None: | |
| effects = cost_effects(2, (EffectPlan("deal"), EffectPlan("burn"), EffectPlan("bomb"))) | |
| assert len(effects) == 2 | |
| assert [effect.primitive_id for effect in effects] == ["deal", "burn"] | |
| # Verify every primitive has a costing rule. | |
| def test_cost_effect_rules(primitive_id: str, points: int, fields: dict[str, object]) -> None: | |
| effect = cost_effect(primitive_id, points) # type: ignore[arg-type] | |
| for name, value in fields.items(): | |
| assert getattr(effect, name) == value | |
| # Verify invalid effect plans are rejected before costing. | |
| def test_cost_card_rejects_invalid_specs() -> None: | |
| with pytest.raises(ValueError, match="at least one"): | |
| cost_card(CardSpec("Blank", 1, "fire", "wuxia", ())) | |
| with pytest.raises(ValueError, match="weight"): | |
| cost_card(CardSpec("Bad", 1, "fire", "wuxia", (EffectPlan("deal", 0),))) | |
| with pytest.raises(ValueError, match="not allowed"): | |
| cost_card(CardSpec("Bad", 1, "ice", "wuxia", (EffectPlan("bomb"),))) | |
| with pytest.raises(ValueError, match="positive"): | |
| cost_effect("deal", 0) | |