Procedural Terrain Generation

Pyrite generates infinite, deterministic worlds using highly parallelized OpenSimplex noise. By utilizing MD5 string-hashing, custom string seeds (e.g., “PyriteIsCool”) are converted into numerical integer seeds. The Numba compiler locks this seed via numpy.random.seed(), ensuring the exact same terrain topology is generated every time.

Biome & Topography System

The terrain generation heavily relies on independent, overlapping noise frequencies:

  • Continentalness: Dictates the physical landform (evaluated across 4 layered octaves: f1, f2, f4, f8). Very low values carve deep oceans, mid values create sweeping plains, and high values linearly interpolate the terrain upward into extreme mountains or plateaus.

  • Temperature & Moisture: Evaluated via separate 2D noise maps to dictate the biome’s block palette (e.g., Snow vs. Desert Sand vs. Grass).

Biome Dithering: To prevent unnatural, blocky straight lines at biome borders, a subtle, high-frequency noise “dithering” offset is mathematically added to the temperature and moisture values. This creates a natural scatter effect, allowing blocks from adjacent biomes to organically mix at the edges.

Cave Carving

Underground systems are carved dynamically using volumetric 3D noise (noise3).

First, the engine determines a deterministic crust thickness. Below this safe top layer, 3D noise is evaluated for every single block coordinate. If the 3D noise exceeds a specific density threshold, the block is overridden and forced to become AIR.

Natural Entrances: To create natural cave entrances rather than massive sinkholes, the cave threshold is dynamically tapered near the surface using a 2D entrance_mask noise map. This forces the 3D carving logic to narrow out and tighten before breaking through the grass layer.

Tree & Flora Generation

Trees are placed procedurally during column generation based on the target Biome’s moisture rating. * High moisture yields a 4% probability per column (Dense Forests). * Low moisture yields a 0.01% probability (Sparse Plains).

The tree structure is pre-defined using local relative coordinates. The engine enforces several environment constraints before spawning a tree:

  1. Trees cannot spawn underwater.

  2. Trees cannot spawn on Snow or Sand.

  3. Trees cannot spawn at extreme mountain altitudes (wy > STONE_LVL).

  4. A tree’s bounding box cannot exceed the chunk’s boundaries (preventing neighbor-chunk bleeding crashes).

Noise Foundation (Deterministic Generation)

Seed Conversion:

Seed Conversion (Reference Implementation):

# Note: Actual seed handling in engine uses set_seed() from src/noise.py
# This is a reference implementation showing the concept:
import hashlib

def string_to_seed(seed_string):
    # Convert string seed to integer
    hash_obj = hashlib.md5(seed_string.encode())
    seed_int = int(hash_obj.hexdigest(), 16) % (2**31)
    return seed_int

# Example:
seed = string_to_seed("PyriteIsCool")  # Result: deterministic 32-bit integer

OpenSimplex 2D Noise (Biomes):

OpenSimplex 2D Noise (Biomes - Pseudo-code):

Note

Actual function is get_biome(x, z, perm_array) in src/terrain_gen.py

# Reference implementation of biome noise calculation
from opensimplex import OpenSimplex

def get_biome_noise_ref(x, z, scale=0.05):
    # Initialize noise generator with seed (actual code: perm_array lookup)
    noise_gen = OpenSimplex(seed)

    # Evaluate noise at point (returns temperature/moisture blend)
    value = noise_gen.noise2(x * scale, z * scale)  # Returns -1.0 to 1.0
    return value

OpenSimplex 3D Noise (Caves - Pseudo-code):

Note

Actual cave generation is embedded in Chunk.generate_terrain() in src/world_objects/chunk.py

# Reference implementation of cave carving noise (actual: inlined in chunk generation)
def get_cave_noise_ref(x, y, z, scale=0.09):
    noise_gen = OpenSimplex(seed)
    value = noise_gen.noise3(x * scale, y * scale, z * scale)
    return value  # Returns -1.0 to 1.0; carve if > cave_threshold

Height Generation Algorithm

Multi-Octave Frequency Blending (FBm - Fractional Brownian Motion):

Height at column (x, z) is calculated by summing noise at multiple frequencies. Each octave has half the amplitude and double the frequency of the previous.


Note

Actual function: get_height(x, z, perm_array) in src/terrain_gen.py

# Terrain height generation at a column (x, z in world coordinates)
def get_height(x, z, perm_array):
    # Column generation (for all 48 Y values)

    # Step 1: Evaluate continentalness (overall landform)
    continentalness = 0
    amplitude = 1.0
    frequency = 0.005  # Scale for first octave

    for octave in [1, 2, 4, 8]:  # 4 octaves
        freq = frequency * octave
        noise_value = opensimplex_noise(x * freq, z * freq)
        continentalness += noise_value * amplitude
        amplitude *= 0.5  # Halve amplitude each octave

    # Normalize continentalness to -1.0 to 1.0
    continentalness = clamp(continentalness / 1.875, -1.0, 1.0)  # 1.875 = sum of amplitudes

    # Step 2: Evaluate jaggedness (mountain sharpness)
    jaggedness = opensimplex_noise(x * 0.04, z * 0.04)  # Higher frequency

    # Step 3: Determine base height from continentalness
    if continentalness < -0.2:
        # Deep ocean
        base_height = 5.6 + continentalness * 3  # Range: ~2 to 5.6 blocks
    elif -0.2 <= continentalness <= 0.1:
        # Beaches, plains
        base_height = 5.6 + continentalness * 8  # Range: 5.6 to ~6 blocks (mostly flat)
    elif 0.1 < continentalness <= 0.3:
        # Plateaus
        base_height = 6.0 + continentalness * 30  # Range: 6 to ~15 blocks, cliffier
        jaggedness *= 2  # Emphasize jaggedness on plateaus
    else:
        # Mountains
        base_height = 15.0 + continentalness * 50  # Range: 15 to 35+ blocks
        jaggedness *= 3  # Very jagged

    # Apply jaggedness
    height = base_height + jaggedness * 4

    # Clamp to world bounds (0 to 256)
    height = clamp(height, 0, 256)

    return int(height)

Height Constants:

STONE_LVL = 8          # Below this Y, all blocks are stone (bedrock layer)
WATER_LVL = 5.6        # Sea level
SNOW_LVL = 25          # Above this, snow biomes appear
GRASS_LVL = 15         # Below this, grass; above, snow/rock

Biome Selection Algorithm

Two-Stage Process:

  1. Evaluate temperature and moisture maps (independent 2D noise)

  2. Use lookup table or conditions to select block palette


Note

Actual function: get_biome(x, z, perm_array) in src/terrain_gen.py

# Determine biome type based on temperature and moisture at column
def get_biome(x, z, perm_array):
    # Sample temperature and moisture
    temperature = opensimplex_noise(x * 0.01, z * 0.01)  # -1.0 to 1.0
    moisture = opensimplex_noise(x * 0.015, z * 0.015)

    # Apply dithering (high-frequency noise for variety)
    dither = opensimplex_noise(x * 0.1, z * 0.1) * 0.05  # ±0.05 offset
    temperature += dither
    moisture += dither

    # Biome lookup table
    if temperature > 0.3 and moisture < -0.2:
        return 'DESERT'
    elif temperature < -0.2:
        return 'SNOW'
    elif moisture > 0.2:
        return 'FOREST'
    elif moisture < -0.1:
        return 'PLAINS'
    else:
        return 'GRASSLAND'

Biome Block Palettes:

DESERT:
    Surface:    SAND
    Under:      SAND (3 blocks)
    Base:       STONE

SNOW:
    Surface:    SNOW
    Under:      DIRT (2 blocks)
    Base:       STONE

FOREST/GRASSLAND:
    Surface:    GRASS
    Under:      DIRT (2-4 blocks)
    Base:       STONE

Cave Carving Algorithm (3D)

Volumetric Carving:

def generate_and_carve_chunk(chunk_voxels, chunk_x, chunk_y, chunk_z):
    # First, generate terrain normally (above stone level)

    # Then, carve caves below a certain Y threshold
    CAVE_THRESHOLD = 40  # Only cave below this
    CAVE_SPAWN_Y = 15

    for local_x in range(48):
        for local_y in range(48):
            for local_z in range(48):
                world_x = chunk_x * 48 + local_x
                world_y = chunk_y * 48 + local_y
                world_z = chunk_z * 48 + local_z

                # Only carve solidblocks below threshold
                if world_y < CAVE_SPAWN_Y or world_y > CAVE_THRESHOLD:
                    continue

                # Skip air/water
                voxel_id = chunk_voxels[local_x + local_z * 48 + local_y * 48**2]
                if is_transparent(voxel_id):
                    continue

                # Evaluate 3D cave noise
                cave_noise = opensimplex_noise_3d(world_x * 0.09, world_y * 0.09, world_z * 0.09)

                # Apply entrance tapering (near surface, make caves narrower)
                surface_distance = world_y - CAVE_SPAWN_Y
                taper_factor = 1.0 - (surface_distance / 14.0) ** 2  # Tapers over 14 blocks

                entrance_mask = opensimplex_noise(world_x * 0.02, world_z * 0.02)
                cave_threshold = 0.3 + taper_factor * 0.2

                # Carve if noise exceeds threshold
                if cave_noise > cave_threshold and entrance_mask < 0.5:
                    chunk_voxels[...] = AIR  # Carve out voxel

Tree Placement and Structure

Probabilistic Placement:

def try_place_tree_in_column(x, z, biome, height):
    # Determine tree probability from biome
    if biome == 'FOREST':
        tree_probability = 0.04  # 4% per column
    elif biome == 'GRASSLAND':
        tree_probability = 0.005  # 0.5% per column
    elif biome == 'PLAINS':
        tree_probability = 0.0001  # 0.01% per column
    else:
        return None  # No trees in desert/snow

    # Seeded random check
    rng_value = pseudo_random(x, z, seed)  # 0.0 to 1.0

    if rng_value > tree_probability:
        return None  # Don't place tree

    # Check placement constraints

    # 1. Not underwater
    if height < WATER_LVL:
        return None

    # 2. Not on sand or snow
    if biome in ['DESERT', 'SNOW']:
        return None

    # 3. Not at extreme altitudes
    if height > SNOW_LVL:
        return None

    # 4. Bounding box doesn't exceed chunk
    tree_height = 8
    tree_crown_radius = 4

    local_x = x % 48
    local_z = z % 48

    if local_x - tree_crown_radius < 0 or local_x + tree_crown_radius >= 48:
        return None
    if local_z - tree_crown_radius < 0 or local_z + tree_crown_radius >= 48:
        return None

    # All checks pass - place tree
    return place_tree_structure(x, height, z)

Tree Structure (Predefined):

def place_tree_structure(x, base_y, z):
    # Tree structure (relative coordinates from base)
    trunk_height = 8
    crown_start_y = 5

    # Trunk (straight column of wood)
    for dy in range(trunk_height):
        world_x, world_y, world_z = x, base_y + dy, z
        set_voxel(world_x, world_y, world_z, WOOD)

    # Crown (spherical leaves)
    crown_y = base_y + crown_start_y
    crown_radius = 4

    for dx in range(-crown_radius, crown_radius + 1):
        for dy in range(-crown_radius, crown_radius + 1):
            for dz in range(-crown_radius, crown_radius + 1):
                distance = sqrt(dx**2 + dy**2 + dz**2)
                if distance <= crown_radius:
                    # Checker pattern (sparse leaves for light penetration)
                    if (abs(dx) + abs(dz)) % 4 != 0:
                        world_x = x + dx
                        world_y = crown_y + dy
                        world_z = z + dz
                        set_voxel(world_x, world_y, world_z, LEAVES)

Ore Generation

Pyrite does not currently implement dedicated ore spawning. Terrain generation focuses on biome placement, caves, trees, and surface details rather than mineral deposits.

Column Generation Sequence

Per-Column Processing (called for each X,Z coordinate):

def generate_column(x, z, perm_array):
    height = get_height(x, z, perm_array)
    biome = get_biome(x, z, perm_array)

    column_voxels = []  # 0 to 256 (Y=0 is bedrock)

    # 1. Fill with stone below height
    for y in range(STONE_LVL, int(height)):
        if y <= STONE_LVL + 1:
            column_voxels.append(BEDROCK)
        else:
            column_voxels.append(STONE)

    # 2. Add surface layer
    surface = get_biome_surface_block(biome, int(height))
    if int(height) < 256:
        column_voxels[int(height)] = surface

    # 3. Add dirt layer (2-4 blocks)
    dirt_depth = 3
    for i in range(dirt_depth):
        if int(height) - i - 1 >= STONE_LVL:
            column_voxels[int(height) - i - 1] = DIRT

    # 4. Fill above height with air (or water if below sea level)
    for y in range(int(height) + 1, 256):
        if y < WATER_LVL:
            column_voxels.append(WATER)
        else:
            column_voxels.append(AIR)

    # 5. Return for insertion into chunk
    return column_voxels

Replication Checklist

To reimplement terrain generation:

  1. ✓ Seed conversion (string → int via MD5 or similar)

  2. ✓ OpenSimplex initialization with deterministic seed

  3. ✓ Multi-octave FBm height calculation

  4. ✓ Biome lookup (temperature + moisture → palette)

  5. ✓ 3D cave carving with entrance tapering

  6. ✓ 2D tree placement with probabilistic seeding

  7. ✓ Tree structure generation (trunk + crown)

  8. ✓ Column generation in correct order

  9. ✓ Integration with chunk meshing pipeline