from pathlib import Path import pytest from engine.character import Ailment, BodyPart, Character from engine.defs import DefRegistry from resources.logic.combat import ( FRACTURE_ROLL_THRESHOLD, IMPACT_TRAUMA_ROLL_THRESHOLD, apply_combat_ailment, combat_ailment_for_roll, perform_attack, ) DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) def make_human(registry, **kwargs) -> Character: return Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"), **kwargs) class FixedRng: def __init__(self, roll: int): self._roll = roll def randint(self, _lo, _hi): return self._roll def choice(self, seq): return seq[0] # --- combat_ailment_for_roll: pure roll -> ailment tiers ------------------------------------- def test_low_roll_causes_no_ailment(): assert combat_ailment_for_roll(IMPACT_TRAUMA_ROLL_THRESHOLD - 1, "torso") is None def test_solid_roll_causes_impact_trauma(): assert combat_ailment_for_roll(IMPACT_TRAUMA_ROLL_THRESHOLD, "torso") == "impact_trauma" assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD - 1, "left_arm") == "impact_trauma" def test_near_perfect_roll_causes_a_fracture_on_a_non_head_slot(): assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD, "left_arm") == "fracture" assert combat_ailment_for_roll(20, "torso") == "fracture" def test_near_perfect_roll_on_the_head_causes_brain_trauma_instead(): assert combat_ailment_for_roll(FRACTURE_ROLL_THRESHOLD, "head") == "brain_trauma" assert combat_ailment_for_roll(20, "head") == "brain_trauma" # --- apply_combat_ailment: add vs. refresh ----------------------------------------------------- def test_apply_combat_ailment_adds_a_new_ailment(): part = BodyPart("torso", "human_torso") ailment = apply_combat_ailment(part, "fracture") assert part.ailments == [ailment] assert ailment.id == "fracture" assert ailment.severity == pytest.approx(0.8) def test_apply_combat_ailment_does_not_duplicate_the_same_ailment(): part = BodyPart("torso", "human_torso") apply_combat_ailment(part, "impact_trauma") apply_combat_ailment(part, "impact_trauma") assert len(part.ailments) == 1 def test_apply_combat_ailment_upgrades_severity_but_never_downgrades(): part = BodyPart("torso", "human_torso") part.ailments.append(Ailment(id="fracture", name="Fracture", severity=0.2)) # e.g. healing apply_combat_ailment(part, "fracture") assert part.ailments[0].severity == pytest.approx(0.8) # took the worse (combat) severity # --- integration: perform_attack actually inflicts ailments on a hit --------------------------- def test_low_precision_hit_damages_but_inflicts_no_ailment(registry): attacker = make_human(registry) attacker.bio["athletics"] = 10 defender = make_human(registry) result = perform_attack(attacker, defender, registry, target_slot="torso", rng=FixedRng(roll=12)) assert result.hit is True torso = defender.get_body_part("torso") assert torso.integrity < 100.0 assert torso.ailments == [] def test_solid_hit_inflicts_impact_trauma(registry): attacker = make_human(registry) attacker.bio["athletics"] = 10 defender = make_human(registry) result = perform_attack(attacker, defender, registry, target_slot="torso", rng=FixedRng(roll=IMPACT_TRAUMA_ROLL_THRESHOLD)) assert result.hit is True torso = defender.get_body_part("torso") assert [a.id for a in torso.ailments] == ["impact_trauma"] def test_near_perfect_hit_inflicts_a_fracture(registry): attacker = make_human(registry) attacker.bio["athletics"] = 10 defender = make_human(registry) result = perform_attack(attacker, defender, registry, target_slot="left_arm", rng=FixedRng(roll=FRACTURE_ROLL_THRESHOLD)) assert result.hit is True arm = defender.get_body_part("left_arm") assert [a.id for a in arm.ailments] == ["fracture"] def test_near_perfect_hit_on_the_head_inflicts_brain_trauma(registry): attacker = make_human(registry) attacker.bio["athletics"] = 10 defender = make_human(registry) result = perform_attack(attacker, defender, registry, target_slot="head", rng=FixedRng(roll=20)) assert result.hit is True head = defender.get_body_part("head") assert [a.id for a in head.ailments] == ["brain_trauma"] def test_a_miss_inflicts_no_ailment(registry): attacker = make_human(registry) # athletics 0 defender = make_human(registry) result = perform_attack(attacker, defender, registry, target_slot="head", rng=FixedRng(roll=1)) assert result.hit is False assert defender.get_body_part("head").ailments == [] # --- ailment_penalty / total_check_penalty ------------------------------------------------------ def test_ailment_penalty_zero_when_no_ailments(registry): character = make_human(registry) assert character.ailment_penalty("strength", registry) == 0.0 def test_ailment_penalty_zero_for_an_ailment_with_no_matching_def(registry): character = make_human(registry) torso = character.get_or_create_body_part_override("torso") torso.ailments.append(Ailment(id="frostbite", name="Frostbite", severity=1.0)) # no AilmentDef exists for this assert character.ailment_penalty("strength", registry) == 0.0 def test_ailment_penalty_rises_with_a_fracture(registry): character = make_human(registry) arm = character.get_or_create_body_part_override("left_arm") apply_combat_ailment(arm, "fracture") penalty = character.ailment_penalty("strength", registry) assert penalty > 0.0 def test_ailment_penalty_zero_for_an_unrelated_check(registry): character = make_human(registry) torso = character.get_or_create_body_part_override("torso") apply_combat_ailment(torso, "fracture") # fracture doesn't weight "insight" assert character.ailment_penalty("insight", registry) == 0.0 def test_brain_trauma_hits_insight_and_perception_hard(registry): character = make_human(registry) head = character.get_or_create_body_part_override("head") apply_combat_ailment(head, "brain_trauma") assert character.ailment_penalty("insight", registry) == pytest.approx(0.9) def test_total_check_penalty_combines_skill_organ_and_ailment_damage(registry): character = make_human(registry) character.body_parts = [BodyPart("left_arm", "human_left_arm", integrity=60.0)] arm = character.get_or_create_body_part_override("left_arm") apply_combat_ailment(arm, "impact_trauma") combined = character.total_check_penalty("athletics", registry) body_only = character.skill_penalty("athletics", registry) ailment_only = character.ailment_penalty("athletics", registry) assert combined > body_only assert combined > ailment_only assert combined <= 1.0 def test_total_check_penalty_unaffected_when_no_ailments_present(registry): character = make_human(registry) character.body_parts = [BodyPart("left_leg", "human_left_leg", integrity=20.0)] assert character.total_check_penalty("acrobatics", registry) == pytest.approx( character.skill_penalty("acrobatics", registry) ) # --- registry loading ----------------------------------------------------------------------- def test_ailment_defs_load_with_check_weights(registry): fracture = registry.get_ailment_def("fracture") assert fracture is not None assert fracture.check_weights["strength"] == pytest.approx(0.5) def test_get_ailment_def_returns_none_for_unknown_id(registry): assert registry.get_ailment_def("not_a_real_ailment") is None