mud/tests/test_terrain.py
Jared Miller 8934397b1e
Make world wrap seamlessly in both axes
Tileable Perlin noise: each octave wraps its integer grid coordinates
with modulo at the octave's frequency, so gradients at opposite edges
match and the noise field is continuous across the boundary.

Coarse elevation grid interpolation wraps instead of padding boundary
cells. Rivers can flow across world edges. All coordinate access
(get_tile, is_passable, get_viewport) wraps via modulo. Movement,
spawn search, nearby-player detection, and viewport relative positions
all handle the toroidal topology.
2026-02-07 13:50:06 -05:00

206 lines
6.6 KiB
Python

from mudlib.world.terrain import World
def test_world_deterministic_from_seed():
"""Same seed produces identical terrain."""
world1 = World(seed=42, width=100, height=100)
world2 = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
assert world1.get_tile(x, y) == world2.get_tile(x, y)
def test_world_different_seeds_produce_different_terrain():
"""Different seeds produce different terrain."""
world1 = World(seed=42, width=100, height=100)
world2 = World(seed=99, width=100, height=100)
different_tiles = 0
for y in range(100):
for x in range(100):
if world1.get_tile(x, y) != world2.get_tile(x, y):
different_tiles += 1
# at least 10% should be different
assert different_tiles > 1000
def test_world_dimensions():
"""World has correct dimensions."""
world = World(seed=42, width=100, height=50)
# should not raise for valid coordinates
world.get_tile(0, 0)
world.get_tile(99, 49)
# coordinates wrap instead of raising
assert world.get_tile(100, 0) == world.get_tile(0, 0)
assert world.get_tile(0, 50) == world.get_tile(0, 0)
assert world.get_tile(-1, 0) == world.get_tile(99, 0)
assert world.get_tile(0, -1) == world.get_tile(0, 49)
def test_get_tile_returns_valid_terrain():
"""get_tile returns valid terrain characters."""
world = World(seed=42, width=100, height=100)
valid_terrain = {".", "^", "~", "T", ":"}
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
assert tile in valid_terrain
def test_is_passable_mountains_impassable():
"""Mountains are impassable."""
world = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
if tile == "^":
assert not world.is_passable(x, y)
def test_is_passable_water_impassable():
"""Water is impassable."""
world = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
if tile == "~":
assert not world.is_passable(x, y)
def test_is_passable_grass_passable():
"""Grass is passable."""
world = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
if tile == ".":
assert world.is_passable(x, y)
def test_is_passable_forest_passable():
"""Forest is passable."""
world = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
if tile == "T":
assert world.is_passable(x, y)
def test_is_passable_sand_passable():
"""Sand is passable."""
world = World(seed=42, width=100, height=100)
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
if tile == ":":
assert world.is_passable(x, y)
def test_get_viewport_dimensions():
"""get_viewport returns correct dimensions."""
world = World(seed=42, width=100, height=100)
viewport = world.get_viewport(50, 50, width=10, height=8)
assert len(viewport) == 8
assert len(viewport[0]) == 10
def test_get_viewport_centers_correctly():
"""get_viewport centers on given coordinates."""
world = World(seed=42, width=100, height=100)
viewport = world.get_viewport(50, 50, width=5, height=5)
# center tile should be at position (2, 2) in viewport
center_tile = viewport[2][2]
assert center_tile == world.get_tile(50, 50)
def test_get_viewport_wraps_at_edges():
"""Viewport wraps around world boundaries."""
world = World(seed=42, width=100, height=100)
# near top-left corner - viewport should wrap to bottom/right
viewport = world.get_viewport(0, 0, width=5, height=5)
assert len(viewport) == 5
assert len(viewport[0]) == 5
# top-left of viewport wraps to bottom-right of world
assert viewport[0][0] == world.get_tile(98, 98)
# near bottom-right corner - viewport should wrap to top/left
viewport = world.get_viewport(99, 99, width=5, height=5)
assert len(viewport) == 5
assert len(viewport[0]) == 5
# bottom-right of viewport wraps to top-left of world
assert viewport[4][4] == world.get_tile(1, 1)
def test_terrain_distribution_reasonable():
"""Terrain has reasonable distribution (not all one type)."""
world = World(seed=42, width=100, height=100)
terrain_counts = {".": 0, "^": 0, "~": 0, "T": 0, ":": 0}
for y in range(100):
for x in range(100):
tile = world.get_tile(x, y)
terrain_counts[tile] += 1
# should have at least 3 different terrain types
types_present = sum(1 for count in terrain_counts.values() if count > 0)
assert types_present >= 3
# no single type should dominate completely (> 95%)
total = sum(terrain_counts.values())
for terrain_type, count in terrain_counts.items():
assert count < 0.95 * total, f"{terrain_type} dominates with {count}/{total}"
def test_rivers_exist():
"""Terrain has some water tiles (rivers/lakes)."""
world = World(seed=42, width=100, height=100)
water_count = 0
for y in range(100):
for x in range(100):
if world.get_tile(x, y) == "~":
water_count += 1
# should have at least some water
assert water_count > 0
def test_terrain_tiles_seamlessly():
"""Terrain at opposite edges matches for seamless wrapping."""
world = World(seed=42, width=100, height=100)
# check that a strip along the right edge is continuous with the left edge
# by verifying the elevation-derived terrain doesn't have a hard seam.
# the noise is tileable, so adjacent tiles across the boundary should be
# from the same smooth noise field (not identical, but not a cliff).
# we verify the wrap helper itself works correctly:
for y in range(100):
assert world.get_tile(-1, y) == world.get_tile(99, y)
assert world.get_tile(100, y) == world.get_tile(0, y)
for x in range(100):
assert world.get_tile(x, -1) == world.get_tile(x, 99)
assert world.get_tile(x, 100) == world.get_tile(x, 0)
def test_large_world_generation():
"""Can generate a 1000x1000 world without errors."""
world = World(seed=42, width=1000, height=1000)
# spot check some tiles
assert world.get_tile(0, 0) in {".", "^", "~", "T", ":"}
assert world.get_tile(500, 500) in {".", "^", "~", "T", ":"}
assert world.get_tile(999, 999) in {".", "^", "~", "T", ":"}