from pathlib import Path import pytest from engine.character import Character from engine.defs import DefRegistry from engine.map import Map from engine.tile import Tile, TileLayerInstance from engine.world import World from resources.logic.hacking import find_hacking_implant, roll_tech_check, try_hack_tile_layer DEFS_DIR = Path(__file__).resolve().parent.parent / "resources" / "defs" @pytest.fixture() def registry(): return DefRegistry.load(DEFS_DIR) class FixedRng: """Deterministic stand-in for random.Random: fixed die roll.""" def __init__(self, roll: int): self._roll = roll def randint(self, _lo, _hi): return self._roll def choice(self, seq): return seq[0] def uniform(self, a, b): return a def make_human(registry, **kwargs) -> Character: return Character(species_id="human", default_body_parts=registry.new_default_body_parts("human"), **kwargs) def make_world_with_locked_door(registry) -> World: field = Map("field", 5, 5, 1) for x in range(5): for y in range(5): field.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("grass"))) field.set_tile(2, 2, 0, Tile(flooring=TileLayerInstance("grass"), room=TileLayerInstance("door_locked"))) world = World(registry=registry) world.add_map(field) return world # --- base_tech_skill / roll_tech_check ------------------------------------------------------- def test_base_tech_skill_averages_intelligence_and_perception(registry): human = make_human(registry) human.bio["intelligence"] = 4 human.bio["perception"] = 2 assert human.base_tech_skill() == 3.0 def test_base_tech_skill_is_zero_by_default(registry): human = make_human(registry) assert human.base_tech_skill() == 0.0 def test_tech_is_not_a_directly_buyable_skill(registry): human = make_human(registry) assert "tech" not in human.bio def test_roll_tech_check_matches_the_derived_base_skill_with_no_penalties(registry): human = make_human(registry) human.bio["intelligence"] = 4 human.bio["perception"] = 2 roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=11)) assert roll == 11 assert effective_skill == 3.0 # --- try_hack_tile_layer ----------------------------------------------------------------------- def test_hack_succeeds_and_swaps_the_layer_to_its_unlocked_def(registry): world = make_world_with_locked_door(registry) human = make_human(registry) human.bio["intelligence"] = 10 human.bio["perception"] = 10 # base_tech_skill = 10, easily clears the door's DC 12 assert try_hack_tile_layer(human, world, registry, "field", 2, 2, 0, "room", rng=FixedRng(roll=10)) is True tile = world.maps["field"].get_tile(2, 2, 0) assert tile.room.def_id == "door_open" assert tile.is_walkable(registry) is True def test_hack_fails_a_roll_too_low_and_leaves_the_door_locked(registry): world = make_world_with_locked_door(registry) human = make_human(registry) # base_tech_skill = 0 assert try_hack_tile_layer(human, world, registry, "field", 2, 2, 0, "room", rng=FixedRng(roll=1)) is False tile = world.maps["field"].get_tile(2, 2, 0) assert tile.room.def_id == "door_locked" assert tile.is_walkable(registry) is False def test_hack_is_a_no_op_on_a_non_hackable_layer(registry): world = make_world_with_locked_door(registry) world.maps["field"].get_tile(2, 2, 0).room = TileLayerInstance("wood_wall") human = make_human(registry) human.bio["intelligence"] = 10 human.bio["perception"] = 10 assert try_hack_tile_layer(human, world, registry, "field", 2, 2, 0, "room", rng=FixedRng(roll=20)) is False tile = world.maps["field"].get_tile(2, 2, 0) assert tile.room.def_id == "wood_wall" def test_hack_is_a_no_op_on_an_empty_layer(registry): world = make_world_with_locked_door(registry) human = make_human(registry) assert try_hack_tile_layer(human, world, registry, "field", 0, 0, 0, "room", rng=FixedRng(roll=20)) is False # --- hacking implants (cyberdecks) -------------------------------------------------------------- def test_find_hacking_implant_returns_none_without_one(registry): human = make_human(registry) assert find_hacking_implant(human, registry) is None def test_find_hacking_implant_returns_the_installed_cyberdeck(registry): human = make_human(registry) human.install_implant("cyberdeck_biomechanics_mk1", registry) implant = find_hacking_implant(human, registry) assert implant is not None assert implant.id == "cyberdeck_biomechanics_mk1" def test_biomechanics_mk1_adds_a_plus_one_bonus_with_no_roll_floor(registry): human = make_human(registry) human.install_implant("cyberdeck_biomechanics_mk1", registry) roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert roll == 1 # no floor - a bad roll stays bad assert effective_skill == 1.0 # 0 base + 1 bonus def test_biomechanics_mk2_adds_a_plus_two_bonus(registry): human = make_human(registry) human.install_implant("cyberdeck_biomechanics_mk2", registry) _roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert effective_skill == 2.0 def test_infoinc_mk1_applies_penalty_but_floors_the_roll_at_45_percent(registry): human = make_human(registry) human.install_implant("cyberdeck_infoinc_mk1", registry) roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert roll == 9 # round(0.45 * 20) assert effective_skill == -3.0 def test_infoinc_mk1_does_not_lower_an_already_better_roll(registry): human = make_human(registry) human.install_implant("cyberdeck_infoinc_mk1", registry) roll, _effective_skill = roll_tech_check(human, registry, FixedRng(roll=15)) assert roll == 15 # already above the 9-roll floor - left alone def test_infoinc_mk2_has_no_bonus_and_no_roll_floor(registry): human = make_human(registry) human.install_implant("cyberdeck_infoinc_mk2", registry) roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert roll == 1 assert effective_skill == 0.0 def test_infoinc_mk3_adds_a_plus_two_bonus_and_floors_the_roll_at_60_percent(registry): human = make_human(registry) human.install_implant("cyberdeck_infoinc_mk3", registry) roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert roll == 12 # round(0.6 * 20) assert effective_skill == 2.0 def test_the_miro_secret_cyberdeck_adds_a_plus_five_bonus_and_floors_the_roll_at_75_percent(registry): human = make_human(registry) human.install_implant("cyberdeck_the_miro_model_3a", registry) roll, effective_skill = roll_tech_check(human, registry, FixedRng(roll=1)) assert roll == 15 # round(0.75 * 20) assert effective_skill == 5.0 def test_the_miro_secret_cyberdeck_is_marked_secret(registry): assert registry.get_item_def("cyberdeck_the_miro_model_3a").secret is True def test_other_cyberdecks_are_not_marked_secret(registry): for def_id in ( "cyberdeck_biomechanics_mk1", "cyberdeck_biomechanics_mk2", "cyberdeck_infoinc_mk1", "cyberdeck_infoinc_mk2", "cyberdeck_infoinc_mk3", ): assert registry.get_item_def(def_id).secret is False def test_hack_succeeds_via_implant_bonus_alone_when_base_skill_would_otherwise_fail(registry): world = make_world_with_locked_door(registry) human = make_human(registry) # base_tech_skill = 0, would never clear DC 12 unaided human.install_implant("cyberdeck_the_miro_model_3a", registry) assert try_hack_tile_layer(human, world, registry, "field", 2, 2, 0, "room", rng=FixedRng(roll=1)) is True tile = world.maps["field"].get_tile(2, 2, 0) assert tile.room.def_id == "door_open"