Test suite modernization

This commit is contained in:
John McCardle 2026-02-09 08:15:18 -05:00
commit 52fdfd0347
141 changed files with 9947 additions and 4665 deletions

View file

@ -0,0 +1,311 @@
"""Terrain Generation Demo - Multi-layer Elevation
Demonstrates terrain generation with:
1. Generate base elevation with simplex FBM
2. Normalize to 0-1 range
3. Apply water level (flatten below threshold)
4. Add mountain enhancement (boost peaks)
5. Optional erosion simulation
6. Apply terrain color ranges (biomes)
"""
import mcrfpy
from typing import List
from ..core.demo_base import ProcgenDemoBase, StepDef, LayerDef
from ..core.parameter import Parameter
class TerrainDemo(ProcgenDemoBase):
"""Interactive multi-layer terrain generation demo."""
name = "Terrain"
description = "Multi-layer elevation with noise and biome coloring"
MAP_SIZE = (256, 256)
# Terrain color ranges (elevation -> color gradient)
TERRAIN_COLORS = [
(0.00, 0.15, (30, 50, 120), (50, 80, 150)), # Deep water -> Shallow water
(0.15, 0.22, (50, 80, 150), (180, 170, 130)), # Shallow water -> Beach
(0.22, 0.35, (180, 170, 130), (80, 140, 60)), # Beach -> Grass low
(0.35, 0.55, (80, 140, 60), (50, 110, 40)), # Grass low -> Grass high
(0.55, 0.70, (50, 110, 40), (100, 90, 70)), # Grass high -> Rock low
(0.70, 0.85, (100, 90, 70), (140, 130, 120)), # Rock low -> Rock high
(0.85, 1.00, (140, 130, 120), (220, 220, 225)), # Rock high -> Snow
]
def define_steps(self) -> List[StepDef]:
"""Define the generation steps."""
return [
StepDef("Generate base elevation", self.step_base_elevation,
"Create initial terrain using simplex FBM noise"),
StepDef("Normalize heights", self.step_normalize,
"Normalize elevation values to 0-1 range"),
StepDef("Apply water level", self.step_water_level,
"Flatten terrain below water threshold"),
StepDef("Enhance mountains", self.step_mountains,
"Boost high elevation areas for dramatic peaks"),
StepDef("Apply erosion", self.step_erosion,
"Smooth terrain with erosion simulation"),
StepDef("Color biomes", self.step_biomes,
"Apply biome colors based on elevation"),
]
def define_parameters(self) -> List[Parameter]:
"""Define configurable parameters."""
return [
Parameter(
name="seed",
display="Seed",
type="int",
default=42,
min_val=0,
max_val=99999,
step=1,
affects_step=0,
description="Noise seed"
),
Parameter(
name="octaves",
display="Octaves",
type="int",
default=6,
min_val=1,
max_val=8,
step=1,
affects_step=0,
description="FBM detail octaves"
),
Parameter(
name="world_size",
display="Scale",
type="float",
default=8.0,
min_val=2.0,
max_val=20.0,
step=1.0,
affects_step=0,
description="Noise scale (larger = more zoomed out)"
),
Parameter(
name="water_level",
display="Water Level",
type="float",
default=0.20,
min_val=0.0,
max_val=0.40,
step=0.02,
affects_step=2,
description="Sea level threshold"
),
Parameter(
name="mountain_boost",
display="Mt. Boost",
type="float",
default=0.25,
min_val=0.0,
max_val=0.50,
step=0.05,
affects_step=3,
description="Mountain height enhancement"
),
Parameter(
name="erosion_passes",
display="Erosion",
type="int",
default=2,
min_val=0,
max_val=5,
step=1,
affects_step=4,
description="Erosion smoothing passes"
),
]
def define_layers(self) -> List[LayerDef]:
"""Define visualization layers."""
return [
LayerDef("colored", "Colored Terrain", "color", z_index=-1, visible=True,
description="Final terrain with biome colors"),
LayerDef("elevation", "Elevation", "color", z_index=0, visible=False,
description="Grayscale height values"),
LayerDef("water_mask", "Water Mask", "color", z_index=1, visible=False,
description="Binary water regions"),
]
def __init__(self):
"""Initialize terrain demo."""
super().__init__()
# Create working heightmaps
self.hmap_elevation = self.create_heightmap("elevation", 0.0)
self.hmap_water = self.create_heightmap("water", 0.0)
# Noise source
self.noise = None
def _apply_grayscale(self, layer, hmap, alpha=255):
"""Apply grayscale visualization to layer."""
w, h = self.MAP_SIZE
for y in range(h):
for x in range(w):
val = hmap[x, y]
v = int(max(0, min(255, val * 255)))
layer.set((x, y), mcrfpy.Color(v, v, v, alpha))
def _apply_terrain_colors(self, layer, hmap, alpha=255):
"""Apply terrain biome colors based on elevation."""
w, h = self.MAP_SIZE
for y in range(h):
for x in range(w):
val = hmap[x, y]
color = self._elevation_to_color(val, alpha)
layer.set((x, y), color)
def _elevation_to_color(self, val, alpha=255):
"""Convert elevation value to terrain color."""
for low, high, c1, c2 in self.TERRAIN_COLORS:
if low <= val <= high:
# Interpolate between c1 and c2
t = (val - low) / (high - low) if high > low else 0
r = int(c1[0] + t * (c2[0] - c1[0]))
g = int(c1[1] + t * (c2[1] - c1[1]))
b = int(c1[2] + t * (c2[2] - c1[2]))
return mcrfpy.Color(r, g, b, alpha)
# Default for out of range
return mcrfpy.Color(128, 128, 128)
# === Step Implementations ===
def step_base_elevation(self):
"""Step 1: Generate base elevation with FBM noise."""
seed = self.get_param("seed")
octaves = self.get_param("octaves")
world_size = self.get_param("world_size")
# Create noise source
self.noise = mcrfpy.NoiseSource(
dimensions=2,
algorithm='simplex',
seed=seed
)
# Fill with FBM noise
self.hmap_elevation.fill(0.0)
self.hmap_elevation.add_noise(
self.noise,
world_size=(world_size, world_size),
mode='fbm',
octaves=octaves
)
# Show raw noise (elevation layer alpha=128 for overlay)
elevation_layer = self.get_layer("elevation")
self._apply_grayscale(elevation_layer, self.hmap_elevation, alpha=128)
# Also on colored layer (full opacity for final)
colored_layer = self.get_layer("colored")
self._apply_grayscale(colored_layer, self.hmap_elevation, alpha=255)
def step_normalize(self):
"""Step 2: Normalize elevation to 0-1 range."""
self.hmap_elevation.normalize(0.0, 1.0)
# Update visualization
elevation_layer = self.get_layer("elevation")
self._apply_grayscale(elevation_layer, self.hmap_elevation, alpha=128)
colored_layer = self.get_layer("colored")
self._apply_grayscale(colored_layer, self.hmap_elevation, alpha=255)
def step_water_level(self):
"""Step 3: Flatten terrain below water level."""
water_level = self.get_param("water_level")
w, h = self.MAP_SIZE
# Create water mask
self.hmap_water.fill(0.0)
for y in range(h):
for x in range(w):
val = self.hmap_elevation[x, y]
if val < water_level:
# Flatten to water level
self.hmap_elevation[x, y] = water_level
self.hmap_water[x, y] = 1.0
# Update water mask layer (alpha=128 for overlay)
water_layer = self.get_layer("water_mask")
for y in range(h):
for x in range(w):
if self.hmap_water[x, y] > 0.5:
water_layer.set((x, y), mcrfpy.Color(80, 120, 200, 128))
else:
water_layer.set((x, y), mcrfpy.Color(30, 30, 35, 128))
# Update other layers
elevation_layer = self.get_layer("elevation")
self._apply_grayscale(elevation_layer, self.hmap_elevation, alpha=128)
colored_layer = self.get_layer("colored")
self._apply_grayscale(colored_layer, self.hmap_elevation, alpha=255)
def step_mountains(self):
"""Step 4: Enhance mountain peaks."""
mountain_boost = self.get_param("mountain_boost")
w, h = self.MAP_SIZE
if mountain_boost <= 0:
return # Skip if no boost
for y in range(h):
for x in range(w):
val = self.hmap_elevation[x, y]
# Boost high elevations more than low ones
# Using a power curve
if val > 0.5:
boost = (val - 0.5) * 2 # 0 to 1 for upper half
boost = boost * boost * mountain_boost # Squared for sharper peaks
self.hmap_elevation[x, y] = min(1.0, val + boost)
# Re-normalize to ensure 0-1 range
self.hmap_elevation.normalize(0.0, 1.0)
# Update visualization
elevation_layer = self.get_layer("elevation")
self._apply_grayscale(elevation_layer, self.hmap_elevation, alpha=128)
colored_layer = self.get_layer("colored")
self._apply_grayscale(colored_layer, self.hmap_elevation, alpha=255)
def step_erosion(self):
"""Step 5: Apply erosion/smoothing."""
erosion_passes = self.get_param("erosion_passes")
if erosion_passes <= 0:
return # Skip if no erosion
for _ in range(erosion_passes):
self.hmap_elevation.smooth(iterations=1)
# Update visualization
elevation_layer = self.get_layer("elevation")
self._apply_grayscale(elevation_layer, self.hmap_elevation, alpha=128)
colored_layer = self.get_layer("colored")
self._apply_grayscale(colored_layer, self.hmap_elevation, alpha=255)
def step_biomes(self):
"""Step 6: Apply biome colors based on elevation."""
colored_layer = self.get_layer("colored")
self._apply_terrain_colors(colored_layer, self.hmap_elevation, alpha=255)
def main():
"""Run the terrain demo standalone."""
demo = TerrainDemo()
demo.activate()
if __name__ == "__main__":
main()