import pytest from engine.entity import Entity, EntityPosition from engine.map import Map from engine.tile import Tile, TileLayerDef, TileLayerInstance from engine.world import World class FakeRegistry: def get_tile_layer_def(self, layer, def_id): if layer == "room" and def_id == "stairs_up": return TileLayerDef(id="stairs_up", layer="room", walkable=True, staircase_direction="up") if layer == "room" and def_id == "stairs_down": return TileLayerDef(id="stairs_down", layer="room", walkable=True, staircase_direction="down") if layer == "room" and def_id == "wood_wall": return TileLayerDef(id="wood_wall", layer="room", walkable=False) return None def make_two_story_map(): tower = Map("tower", 3, 3, 2) for x in range(3): for y in range(3): for z in range(2): tower.set_tile(x, y, z, Tile(flooring=TileLayerInstance("wood_deck"))) tower.get_tile(1, 1, 0).room = TileLayerInstance("stairs_up") tower.get_tile(1, 1, 1).room = TileLayerInstance("stairs_down") world = World(registry=FakeRegistry()) world.add_map(tower) return world, tower def test_tile_staircase_direction_reads_room_layer(): tower = make_two_story_map()[1] registry = FakeRegistry() assert tower.get_tile(1, 1, 0).staircase_direction(registry) == "up" assert tower.get_tile(1, 1, 1).staircase_direction(registry) == "down" assert tower.get_tile(0, 0, 0).staircase_direction(registry) is None def test_walking_onto_stairs_up_carries_entity_to_next_floor(): world, _tower = make_two_story_map() player = Entity(position=EntityPosition("tower", 0, 1, 0)) assert world.try_move(player, 1, 0, 0) is True assert (player.position.x, player.position.y, player.position.z) == (1, 1, 1) def test_walking_onto_stairs_down_carries_entity_to_floor_below(): world, _tower = make_two_story_map() player = Entity(position=EntityPosition("tower", 0, 1, 1)) assert world.try_move(player, 1, 0, 0) is True assert (player.position.x, player.position.y, player.position.z) == (1, 1, 0) def test_landing_on_staircase_does_not_cascade_further(): # standing on the stairs_down tile at z=1 doesn't repeatedly bounce back down on each call world, _tower = make_two_story_map() player = Entity(position=EntityPosition("tower", 1, 1, 1)) # a no-op self-check: player is already there; simulate a step off and back via two moves assert world.try_move(player, 1, 0, 0) is True # off the stairs, to (2,1,1) assert player.position.z == 1 assert world.try_move(player, -1, 0, 0) is True # back onto stairs_down at (1,1,1) assert player.position.z == 0 # single hop down, landed on ground floor def test_staircase_at_top_floor_edge_does_not_crash_or_move_out_of_bounds(): tower = Map("tower", 3, 3, 1) # only one floor - "up" has nowhere to go for x in range(3): for y in range(3): tower.set_tile(x, y, 0, Tile(flooring=TileLayerInstance("wood_deck"))) tower.get_tile(1, 1, 0).room = TileLayerInstance("stairs_up") world = World(registry=FakeRegistry()) world.add_map(tower) player = Entity(position=EntityPosition("tower", 0, 1, 0)) assert world.try_move(player, 1, 0, 0) is True assert (player.position.x, player.position.y, player.position.z) == (1, 1, 0) # stayed put, no crash def test_staircase_blocked_if_target_floor_tile_unwalkable(): world, tower = make_two_story_map() tower.get_tile(1, 1, 1).room = TileLayerInstance("wood_wall") # block the landing spot above player = Entity(position=EntityPosition("tower", 0, 1, 0)) assert world.try_move(player, 1, 0, 0) is True # the horizontal step itself still succeeds assert (player.position.x, player.position.y, player.position.z) == (1, 1, 0) # but no ascent def test_continuous_move_onto_staircase_also_transitions_floor(): world, _tower = make_two_story_map() player = Entity(position=EntityPosition("tower", 0.3, 1.4, 0)) assert world.try_move_continuous(player, 1, 0, 0, distance=0.8) is True assert player.position.z == 1 assert player.position.x == pytest.approx(1.1) assert player.position.y == pytest.approx(1.4)