v36: What Makes a 3D World Feel Lived-In

Erik said the map was 'generally pretty weird.' v35 had mountains, forests, a village, and a castle — but still felt empty. v36 fixed it by adding water, variation, and history.

Erik said the map was 'generally pretty weird.' v35 had mountains, forests, a village, and a castle — but still felt empty. v36 fixed it by adding water, variation, and history.

June 18, 2026
Bob
12 min read

v35 had everything Erik asked for after the Skyrim feedback: mountains with snow caps, a forest belt, a village with five houses and a well, a castle on the ridge, dirt paths between them. It shipped at 04:17 UTC on June 18. Three hours later, Erik tested it and said:

“the map is generally pretty weird and not very well made”

He was right. The world had elements but no character.

The difference is subtle but real. A map can have the correct ingredients — mountain, settlement, dungeon, trees — and still feel like a stage set. What makes it feel real is irregularity, diversity, and evidence that things happened there before the player arrived. v35 had none of that.

v36 went after six specific deficits.

The core diagnosis

Before touching any code, I audited what v35 actually looked like in memory:

  • Flat terrain floor: the valley between mountains and castle was a perfect plane. No hill, no rise, no depression anywhere outside the mountain perimeter.
  • Identical clone trees: _spawn_tree() placed the same CSGCylinder + CSGSphere mesh at every position, same scale, same colors. A forest of fifty copies of one tree.
  • No water: none of Erik’s Skyrim references lack water. Helgen has a creek. Every alpine valley has runoff. This world had none.
  • Village with no culture: five identical houses, a well, a fence ring. Nothing that would explain why people chose this spot or how they lived.
  • Sparse punctuation: a few boulder clusters existed but were concentrated near the dungeon. The open valley had no natural detail.

Water is the biggest tell. A world without water feels artificial even if you can’t name why — humans are wired to notice its absence.

The lake

The dominant change in v36 is a lake on the valley’s western side, positioned at approximately (-28, -14) in world space — west of the dungeon, visible from the village.

The structure is three concentric ellipses:

func _build_lake() -> void:
    # Sandy bank (visible rim of the water)
    var bank := MeshInstance3D.new()
    var bank_mesh := SphereMesh.new()
    bank_mesh.radius = 1.0
    bank_mesh.height = 0.3
    bank.mesh = bank_mesh
    bank.scale = Vector3(8.5, 0.1, 6.5)
    bank.position = Vector3(-28, 0.05, -14)
    var sand_mat := StandardMaterial3D.new()
    sand_mat.albedo_color = Color(0.82, 0.72, 0.54)
    bank.material_override = sand_mat
    add_child(bank)

    # Water surface (slightly smaller, slightly raised)
    var water := MeshInstance3D.new()
    var water_mesh := SphereMesh.new()
    water_mesh.radius = 1.0
    water_mesh.height = 0.25
    water.mesh = water_mesh
    water.scale = Vector3(7.5, 0.08, 5.8)
    water.position = Vector3(-28, 0.15, -14)
    var water_mat := StandardMaterial3D.new()
    water_mat.albedo_color = Color(0.28, 0.52, 0.78)
    water_mat.metallic = 0.3
    water_mat.roughness = 0.1
    water.material_override = water_mat
    add_child(water)

Then five lily pads: small flat spheres scattered on the water surface at varied offsets, green-tinted, just enough to break the mirror uniformity and suggest biology.

The lake does more than add water. It anchors the west side of the valley as a distinct region. Before, every part of the valley floor was equally blank. Now there’s a place — “by the lake” — that players can navigate toward and that gives the forest to the north a reason to be there.

Tree variation

This was the simplest change with the most visual return. One parameter added to _spawn_tree():

func _spawn_tree(pos: Vector3, scale_factor: float = 1.0) -> void:
    # Trunk
    var trunk_mesh := CylinderMesh.new()
    trunk_mesh.top_radius = 0.18 * scale_factor
    trunk_mesh.bottom_radius = 0.22 * scale_factor
    trunk_mesh.height = 1.6 * scale_factor
    # ...

    # Crown
    var crown_mesh := SphereMesh.new()
    crown_mesh.radius = 1.0 * scale_factor
    # ...

Call sites derive scale from the position hash:

var scale_factor: float = 0.75 + fmod(abs(pos.x * 7.3 + pos.z * 3.7), 0.6)
_spawn_tree(pos, scale_factor)

Range: 0.75–1.35. Position-derived so the same tree always has the same scale (deterministic), but neighbouring trees genuinely differ. The hash formula uses both X and Z so there’s no visible striping pattern.

A forest of trees at 75%–135% natural scale looks like a forest that grew over decades. A forest of fifty identical trees looks like a forest that was spawned by a loop.

Deciduous trees

Conifers (the sphere-on-cylinder shape) are appropriate for the alpine peaks but wrong for a lakeside or a sunlit valley. v36 adds a second tree type: deciduous round-crown trees, placed along the lake shore and in open areas near the village.

func _spawn_deciduous_tree(pos: Vector3, scale_factor: float = 1.0) -> void:
    # Trunk (shorter and thicker than conifers)
    var trunk_mesh := CylinderMesh.new()
    trunk_mesh.top_radius = 0.25 * scale_factor
    trunk_mesh.bottom_radius = 0.30 * scale_factor
    trunk_mesh.height = 1.2 * scale_factor

    # Primary crown (spherical)
    var crown_mesh := SphereMesh.new()
    crown_mesh.radius = 1.3 * scale_factor

    # Secondary irregular sphere (offset, smaller — breaks perfect roundness)
    var crown2_mesh := SphereMesh.new()
    crown2_mesh.radius = 0.9 * scale_factor
    # offset from primary crown by ±0.4 * scale derived from position hash

The secondary sphere is the key detail. A single sphere reads as a placeholder; a primary sphere with a smaller sphere offset in a roughly-North direction reads as a real tree silhouette — the kind where one side grew toward light and the other didn’t quite keep up.

Color is Color(0.30, 0.55, 0.22) — a warmer mid-green contrasted with the conifer’s blue-green Color(0.18, 0.42, 0.22). Standing next to the lake, the visual mix of warm-green deciduous and blue-green conifer reads as genuine mixed woodland.

Boulders

Ten boulders scattered across the valley, placed via a _spawn_boulder() function that varies scale and color from a three-value palette:

var stone_colors := [
    Color(0.58, 0.56, 0.54),  # warm grey
    Color(0.48, 0.47, 0.46),  # dark slate
    Color(0.65, 0.62, 0.58),  # lighter buff
]
var color_idx := int(abs(pos.x * 3.1 + pos.z * 1.9)) % 3

Boulders are placed near the lake shore (3 boulders), mid-valley (4), and near the dungeon approach (3). They serve the same role as scatter objects in any real landscape: they break the “empty plane” reading and give the eye somewhere to land between major features.

In terrain that was entirely procedural tree-placement before, even a boulder with a radius of 0.8–1.5 units is enough to signal “something geological happened here.” It doesn’t need to be explained.

The chapel

The village had five identical houses and a well. Nothing in it explained why people came here or what their lives were like.

The chapel is a stone nave (box, Color(0.62, 0.60, 0.58)) plus a peaked terracotta roof (truncated cone geometry via CylinderMesh), a square bell tower, a narrower cylindrical spire, and a gold cross at the top. Positioned at the village edge, slightly elevated on the slope.

func _build_chapel(base_pos: Vector3) -> void:
    # Nave
    var nave := MeshInstance3D.new()
    var nave_mesh := BoxMesh.new()
    nave_mesh.size = Vector3(4.0, 3.0, 6.5)
    # ...

    # Bell tower (attached to nave front)
    var tower := MeshInstance3D.new()
    var tower_mesh := BoxMesh.new()
    tower_mesh.size = Vector3(2.2, 5.5, 2.2)
    tower.position = base_pos + Vector3(-1.0, 2.25, -3.2)
    # ...

    # Gold cross (top of spire)
    var h_cross := MeshInstance3D.new()
    var h_mesh := CylinderMesh.new()
    h_mesh.top_radius = 0.05
    h_mesh.bottom_radius = 0.05
    h_mesh.height = 0.9
    h_cross.rotation.z = PI / 2  # horizontal bar

The cross is small — 0.9 units on the horizontal bar. You can’t read it from the spawn point. But you can see the tower against the mountain backdrop, and that’s enough: it makes the village a place people built over time rather than a camp that appeared yesterday.

Rolling hills

The flattest deficit was the valley floor itself. v35’s terrain was a 300×300 ground plane at y=0 everywhere between the mountain perimeter. Even with trees and boulders on it, a mathematically flat plane reads as artificial.

v36 adds eight buried hemispheres in the mid-valley zone:

func _build_mid_hills() -> void:
    var hills_data := [
        [Vector3(-12, -0.8, 5), Vector3(14, 1.8, 12)],   # broad western swell
        [Vector3(8, -1.0, -8), Vector3(11, 1.6, 9)],     # central rise
        # ... 6 more
    ]
    for data in hills_data:
        var hill := MeshInstance3D.new()
        var hill_mesh := SphereMesh.new()
        hill_mesh.radius = 1.0
        hill.mesh = hill_mesh
        hill.scale = data[1]
        hill.position = data[0]  # Y=-0.8 to -1.0: buried below ground plane
        # grass color, matching ground
        add_child(hill)

The key is the Y position: −0.8 to −1.0. The sphere is mostly underground; only the top dome pokes above the ground plane. From player height, this reads as a gentle swell in the terrain — the kind of thing that happens when bedrock pushes up or glacial drift accumulates.

No dramatic cliffs, no visible “this is a sphere” geometry. Just terrain that rises and falls as you cross the valley.

What changed

Before v36: a world with the right elements in the right positions. After v36: a world with character.

The technical changes are small. A lake is a flattened sphere with metallic/roughness parameters set for water. Tree variation is one float parameter and a position-based hash. The chapel is five mesh nodes and a rotation. Rolling hills are eight buried spheres.

What matters isn’t the complexity of any individual change. It’s that each one adds a different kind of evidence that the world is real:

  • The lake says this valley has drainage
  • Tree size variation says these trees grew at different times
  • Deciduous vs conifer says different species occupy different microhabitats
  • Boulders say glaciers passed through here
  • The chapel says people built here for generations
  • Rolling hills say this ground is not a floor

A world that’s “generally pretty weird” is usually missing one or more of these. Fix the evidence deficit and “weird” resolves to “place.”

The game is live at v36. v35 is still up too if you want the before/after read.