```python
import bpy
import math
import random
import mathutils
from math import radians, sin, cos

# ---------------------------------------------------------
# Scene Reset
# ---------------------------------------------------------
def clear_scene():
    bpy.ops.object.select_all(action='SELECT')
    bpy.ops.object.delete(use_global=False)
    for block in [bpy.data.meshes, bpy.data.materials, bpy.data.textures, bpy.data.images, bpy.data.curves, bpy.data.lights]:
        for item in block:
            block.remove(item)

# ---------------------------------------------------------
# Material Generators (Procedural & Cycles Compatible)
# ---------------------------------------------------------
def create_stone_material():
    mat = bpy.data.materials.new(name="Stone_Moss")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    principled = nodes.new('ShaderNodeBsdfPrincipled')

    tex_noise = nodes.new('ShaderNodeTexNoise')
    tex_noise.inputs['Scale'].default_value = 14.0
    tex_noise.inputs['Detail'].default_value = 8.0

    ramp_moss = nodes.new('ShaderNodeValToRGB')
    ramp_moss.color_ramp.elements[0].position = 0.42
    ramp_moss.color_ramp.elements[0].color = (0.24, 0.22, 0.19, 1.0) # Stone grey
    ramp_moss.color_ramp.elements[1].position = 0.65
    ramp_moss.color_ramp.elements[1].color = (0.10, 0.24, 0.06, 1.0) # Mossy green

    bump = nodes.new('ShaderNodeBump')
    bump.inputs['Strength'].default_value = 0.35

    links.new(tex_noise.outputs['Fac'], ramp_moss.inputs['Fac'])
    links.new(ramp_moss.outputs['Color'], principled.inputs['Base Color'])
    links.new(tex_noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], principled.inputs['Normal'])

    principled.inputs['Roughness'].default_value = 0.88
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])
    return mat

def create_rust_metal_material():
    mat = bpy.data.materials.new(name="Rusted_Metal")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    principled = nodes.new('ShaderNodeBsdfPrincipled')

    tex_noise = nodes.new('ShaderNodeTexNoise')
    tex_noise.inputs['Scale'].default_value = 7.0
    tex_noise.inputs['Detail'].default_value = 6.0

    ramp_rust = nodes.new('ShaderNodeValToRGB')
    ramp_rust.color_ramp.elements[0].position = 0.32
    ramp_rust.color_ramp.elements[0].color = (0.38, 0.11, 0.04, 1.0) # Corroded dark rust
    ramp_rust.color_ramp.elements[1].position = 0.68
    ramp_rust.color_ramp.elements[1].color = (0.26, 0.28, 0.30, 1.0) # Weathered steel

    ramp_met = nodes.new('ShaderNodeValToRGB')
    ramp_met.color_ramp.elements[0].position = 0.35
    ramp_met.color_ramp.elements[0].color = (0.0, 0.0, 0.0, 1.0) # Rust non-metal
    ramp_met.color_ramp.elements[1].position = 0.65
    ramp_met.color_ramp.elements[1].color = (0.75, 0.75, 0.75, 1.0) # Metal reflection

    bump = nodes.new('ShaderNodeBump')
    bump.inputs['Strength'].default_value = 0.45

    links.new(tex_noise.outputs['Fac'], ramp_rust.inputs['Fac'])
    links.new(tex_noise.outputs['Fac'], ramp_met.inputs['Fac'])
    links.new(ramp_rust.outputs['Color'], principled.inputs['Base Color'])
    links.new(ramp_met.outputs['Color'], principled.inputs['Metallic'])
    links.new(tex_noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], principled.inputs['Normal'])

    principled.inputs['Roughness'].default_value = 0.78
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])
    return mat

def create_wood_material():
    mat = bpy.data.materials.new(name="Aged_Wood")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    principled = nodes.new('ShaderNodeBsdfPrincipled')

    tex_noise = nodes.new('ShaderNodeTexNoise')
    tex_noise.inputs['Scale'].default_value = 12.0

    ramp = nodes.new('ShaderNodeValToRGB')
    ramp.color_ramp.elements[0].position = 0.2
    ramp.color_ramp.elements[0].color = (0.16, 0.12, 0.09, 1.0) # Dark aged timber
    ramp.color_ramp.elements[1].position = 0.8
    ramp.color_ramp.elements[1].color = (0.35, 0.28, 0.22, 1.0) # Dry sun-bleached wood

    bump = nodes.new('ShaderNodeBump')
    bump.inputs['Strength'].default_value = 0.35

    links.new(tex_noise.outputs['Fac'], ramp.inputs['Fac'])
    links.new(ramp.outputs['Color'], principled.inputs['Base Color'])
    links.new(tex_noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], principled.inputs['Normal'])

    principled.inputs['Roughness'].default_value = 0.85
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])
    return mat

def create_foliage_material():
    mat = bpy.data.materials.new(name="Foliage_Leaf")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    principled = nodes.new('ShaderNodeBsdfPrincipled')

    tex_noise = nodes.new('ShaderNodeTexNoise')
    tex_noise.inputs['Scale'].default_value = 18.0

    ramp = nodes.new('ShaderNodeValToRGB')
    ramp.color_ramp.elements[0].position = 0.25
    ramp.color_ramp.elements[0].color = (0.07, 0.20, 0.03, 1.0) # Forest green
    ramp.color_ramp.elements[1].position = 0.75
    ramp.color_ramp.elements[1].color = (0.24, 0.45, 0.09, 1.0) # Bright leaf tip

    links.new(tex_noise.outputs['Fac'], ramp.inputs['Fac'])
    links.new(ramp.outputs['Color'], principled.inputs['Base Color'])

    principled.inputs['Roughness'].default_value = 0.4
    if 'Subsurface Weight' in principled.inputs:
        principled.inputs['Subsurface Weight'].default_value = 0.25
    elif 'Subsurface' in principled.inputs:
        principled.inputs['Subsurface'].default_value = 0.25

    links.new(principled.outputs['BSDF'], output.inputs['Surface'])
    return mat

def create_ground_material():
    mat = bpy.data.materials.new(name="Ground_Terrain")
    mat.use_nodes = True
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    output = nodes.new('ShaderNodeOutputMaterial')
    principled = nodes.new('ShaderNodeBsdfPrincipled')

    tex_noise = nodes.new('ShaderNodeTexNoise')
    tex_noise.inputs['Scale'].default_value = 5.0
    tex_noise.inputs['Detail'].default_value = 8.0

    ramp = nodes.new('ShaderNodeValToRGB')
    ramp.color_ramp.elements[0].position = 0.25
    ramp.color_ramp.elements[0].color = (0.15, 0.11, 0.07, 1.0) # Mud path
    ramp.color_ramp.elements[1].position = 0.55
    ramp.color_ramp.elements[1].color = (0.09, 0.21, 0.05, 1.0) # Overgrown grass
    ramp.color_ramp.elements.new(0.82)
    ramp.color_ramp.elements[2].color = (0.20, 0.16, 0.11, 1.0) # Pebbles / dirt

    bump = nodes.new('ShaderNodeBump')
    bump.inputs['Strength'].default_value = 0.5

    links.new(tex_noise.outputs['Fac'], ramp.inputs['Fac'])
    links.new(ramp.outputs['Color'], principled.inputs['Base Color'])
    links.new(tex_noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], principled.inputs['Normal'])

    principled.inputs['Roughness'].default_value = 0.82
    links.new(principled.outputs['BSDF'], output.inputs['Surface'])
    return mat

# ---------------------------------------------------------
# Geometry Generators
# ---------------------------------------------------------
def create_corrugated_panel(name, width=1.0, length=2.2, num_waves=10, depth=0.035, mat=None):
    mesh = bpy.data.meshes.new(name + "_mesh")
    obj = bpy.data.objects.new(name, mesh)
    bpy.context.collection.objects.link(obj)

    verts = []
    faces = []
    nx = 28
    ny = 6

    for iy in range(ny + 1):
        y = (iy / ny) * length - length / 2.0
        for ix in range(nx + 1):
            u = ix / nx
            x = u * width - width / 2.0
            z = sin(u * math.pi * 2.0 * num_waves) * depth
            warp = (sin(x * 6.0) + cos(y * 4.0)) * 0.012
            verts.append((x, y, z + warp))

    for iy in range(ny):
        for ix in range(nx):
            p1 = iy * (nx + 1) + ix
            p2 = p1 + 1
            p3 = (iy + 1) * (nx + 1) + ix + 1
            p4 = (iy + 1) * (nx + 1) + ix
            faces.append((p1, p2, p3, p4))

    mesh.from_pydata(verts, [], faces)
    mesh.update()

    mod = obj.modifiers.new("Solidify", 'SOLIDIFY')
    mod.thickness = 0.008

    if mat:
        obj.data.materials.append(mat)
    return obj

def create_beam(name, length, width, height, mat=None):
    bpy.ops.mesh.primitive_cube_add(size=1.0)
    beam = bpy.context.active_object
    beam.name = name
    beam.scale = (width, length, height)
    bpy.ops.object.transform_apply(scale=True)
    if mat:
        beam.data.materials.append(mat)
    return beam

def create_grass_tuft(mat):
    mesh = bpy.data.meshes.new("GrassTuftMesh")
    obj = bpy.data.objects.new("GrassTuft", mesh)
    bpy.context.collection.objects.link(obj)

    verts = []
    faces = []
    num_blades = 8
    v_idx = 0

    for i in range(num_blades):
        angle = (i / num_blades) * math.pi * 2.0 + random.uniform(-0.2, 0.2)
        tilt = random.uniform(0.15, 0.35)
        height = random.uniform(0.35, 0.6)
        width = 0.035

        ca, sa = cos(angle), sin(angle)

        bx1, by1 = -width * sa, width * ca
        bx2, by2 = width * sa, -width * ca

        mx1 = (ca * tilt) - width * 0.6 * sa
        my1 = (sa * tilt) + width * 0.6 * ca
        mz1 = height * 0.5

        mx2 = (ca * tilt) + width * 0.6 * sa
        my2 = (sa * tilt) - width * 0.6 * ca
        mz2 = height * 0.5

        tx = ca * (tilt + 0.18)
        ty = sa * (tilt + 0.18)
        tz = height

        verts.extend([(bx1, by1, 0.0), (bx2, by2, 0.0), (mx1, my1, mz1), (mx2, my2, mz2), (tx, ty, tz)])
        faces.extend([
            (v_idx, v_idx+1, v_idx+3, v_idx+2),
            (v_idx+2, v_idx+3, v_idx+4)
        ])
        v_idx += 5

    mesh.from_pydata(verts, [], faces)
    mesh.update()
    if mat:
        obj.data.materials.append(mat)
    return obj

def create_leaf_plane(mat, size=0.18):
    mesh = bpy.data.meshes.new("LeafMesh")
    obj = bpy.data.objects.new("Leaf", mesh)
    bpy.context.collection.objects.link(obj)

    w = size * 0.5
    h = size
    verts = [
        (0.0, 0.0, 0.0),
        (-w, h*0.4, 0.02),
        (0.0, h, 0.04),
        (w, h*0.4, 0.02)
    ]
    faces = [(0, 3, 2, 1)]
    mesh.from_pydata(verts, [], faces)
    mesh.update()
    if mat:
        obj.data.materials.append(mat)
    return obj

def create_vines_on_wall(start_pos, dir_vector, length=3.0, leaf_obj=None, wood_mat=None):
    curve_data = bpy.data.curves.new('VineCurve', type='CURVE')
    curve_data.dimensions = '3D'
    curve_data.bevel_depth = 0.016
    curve_data.bevel_resolution = 3

    vine_obj = bpy.data.objects.new('VineStem', curve_data)
    bpy.context.collection.objects.link(vine_obj)
    if wood_mat:
        vine_obj.data.materials.append(wood_mat)

    px, py, pz = start_pos
    dx, dy, dz = dir_vector

    spline = curve_data.splines.new('BEZIER')
    num_pts = 6
    spline.bezier_points.add(num_pts - 1)

    curr_x, curr_y, curr_z = px, py, pz
    for i in range(num_pts):
        pt = spline.bezier_points[i]
        pt.co = (curr_x, curr_y, curr_z)
        pt.handle_left_type = 'AUTO'
        pt.handle_right_type = 'AUTO'

        curr_x += dx * (length / num_pts) + random.uniform(-0.12, 0.12)
        curr_y += dy * (length / num_pts) + random.uniform(-0.12, 0.12)
        curr_z += dz * (length / num_pts) + random.uniform(-0.04, 0.18)

        if leaf_obj and i > 0:
            for _ in range(random.randint(2, 4)):
                l_inst = leaf_obj.copy()
                l_inst.data = leaf_obj.data.copy()
                bpy.context.collection.objects.link(l_inst)
                l_inst.location = (pt.co[0] + random.uniform(-0.06, 0.06),
                                   pt.co[1] + random.uniform(-0.06, 0.06),
                                   pt.co[2] + random.uniform(-0.06, 0.06))
                l_inst.rotation_euler = (random.uniform(-1, 1), random.uniform(-1, 1), random.uniform(0, 6.28))
                scale = random.uniform(0.8, 1.4)
                l_inst.scale = (scale, scale, scale)

# ---------------------------------------------------------
# Buildings & Village Structures
# ---------------------------------------------------------
def build_cottage_1(stone_mat, wood_mat, rust_mat, leaf_obj):
    # Main Weathered Stone House with Partial Roof Collapse
    w, l, h = 3.6, 4.2, 2.6
    pos_x, pos_y, pos_z = -2.2, 1.2, 0.0

    # Wall structure
    bpy.ops.mesh.primitive_cube_add(size=1.0, location=(pos_x, pos_y, pos_z + h/2))
    body = bpy.context.active_object
    body.name = "House1_Walls"
    body.scale = (w, l, h)
    bpy.ops.object.transform_apply(scale=True)
    body.data.materials.append(stone_mat)

    # Broken window frames & details
    win_frame = create_beam("WinFrame1", 0.1, 0.9, 1.1, wood_mat)
    win_frame.location = (pos_x - 0.8, pos_y - l/2 - 0.02, pos_z + 1.4)

    sill = create_beam("WinSill1", 0.2, 1.0, 0.08, wood_mat)
    sill.location = (pos_x - 0.8, pos_y - l/2 - 0.05, pos_z + 0.82)
    sill.rotation_euler = (0, 0, 0.08)

    door_frame = create_beam("DoorFrame1", 0.1, 1.1, 2.0, wood_mat)
    door_frame.location = (pos_x + 0.8, pos_y - l/2 - 0.02, pos_z + 1.0)

    # Roof Structure (Timber Rafters)
    num_rafters = 6
    roof_angle = radians(28)
    roof_h = 1.3

    for i in range(num_rafters):
        ry = pos_y - l/2 + 0.2 + i * (l - 0.4) / (num_rafters - 1)
        b1 = create_beam("RafterL", 0.12, 2.2, 0.12, wood_mat)
        b1.location = (pos_x - w/4, ry, pos_z + h + roof_h/2)
        b1.rotation_euler = (0, roof_angle, 0)

        b2 = create_beam("RafterR", 0.12, 2.2, 0.12, wood_mat)
        b2.location = (pos_x + w/4, ry, pos_z + h + roof_h/2)
        b2.rotation_euler = (0, -roof_angle, 0)

    ridge = create_beam("RidgeBeam", l + 0.4, 0.12, 0.15, wood_mat)
    ridge.location = (pos_x, pos_y, pos_z + h + roof_h)

    # Corrugated Rusted Metal Panels (Partial / Damaged)
    for i in range(4):
        if i == 2: # Missing roof section showing interior rafters
            continue
        py = pos_y - l/2 + 0.6 + i * 1.0
        panel = create_corrugated_panel("RoofPanel_L", width=2.1, length=1.1, mat=rust_mat)
        panel.location = (pos_x - w/4 - 0.1, py, pos_z + h + roof_h/2 + 0.08)
        panel.rotation_euler = (0, roof_angle, radians(90))
        if i == 3:
            panel.rotation_euler = (radians(12), roof_angle + 0.12, radians(85))

    for i in range(4):
        if i == 1:
            continue
        py = pos_y - l/2 + 0.6 + i * 1.0
        panel = create_corrugated_panel("RoofPanel_R", width=2.1, length=1.1, mat=rust_mat)
        panel.location = (pos_x + w/4 + 0.1, py, pos_z + h + roof_h/2 + 0.08)
        panel.rotation_euler = (0, -roof_angle, radians(90))

    # Vine networks growing through windows & up walls
    create_vines_on_wall((pos_x - 0.8, pos_y - l/2 - 0.1, pos_z + 0.3), (0, 0, 1), length=2.2, leaf_obj=leaf_obj, wood_mat=wood_mat)
    create_vines_on_wall((pos_x - 1.5, pos_y - l/2 - 0.1, pos_z + 0.5), (0.3, 0, 0.8), length=3.0, leaf_obj=leaf_obj, wood_mat=wood_mat)
    create_vines_on_wall((pos_x - w/2 - 0.05, pos_y, pos_z + 1.0), (0, 0, 1), length=2.8, leaf_obj=leaf_obj, wood_mat=wood_mat)

def build_cottage_2(stone_mat, wood_mat, rust_mat, leaf_obj):
    # Timber & Plaster Cabin on Right
    w, l, h = 3.2, 3.8, 2.4
    pos_x, pos_y, pos_z = 2.5, 0.8, 0.0
    rot_z = radians(-18)

    bpy.ops.mesh.primitive_cube_add(size=1.0, location=(pos_x, pos_y, pos_z + h/2))
    body = bpy.context.active_object
    body.name = "House2_Walls"
    body.scale = (w, l, h)
    body.rotation_euler = (0, 0, rot_z)
    bpy.ops.object.transform_apply(scale=True)
    body.data.materials.append(wood_mat)

    # Stone Chimney
    bpy.ops.mesh.primitive_cube_add(size=1.0, location=(pos_x + 1.2, pos_y + 0.5, pos_z + 2.2))
    chimney = bpy.context.active_object
    chimney.scale = (0.6, 0.6, 2.2)
    chimney.rotation_euler = (0, 0, rot_z)
    chimney.data.materials.append(stone_mat)

    # Sagging Corrugated Roof
    roof_angle = radians(22)

    panel1 = create_corrugated_panel("House2_RoofL", width=2.0, length=4.0, mat=rust_mat)
    panel1.location = (pos_x - 0.7, pos_y, pos_z + h + 0.4)
    panel1.rotation_euler = (0, roof_angle, rot_z + radians(90))

    panel2 = create_corrugated_panel("House2_RoofR", width=2.0, length=4.0, mat=rust_mat)
    panel2.location = (pos_x + 0.7, pos_y, pos_z + h + 0.4)
    panel2.rotation_euler = (0, -roof_angle, rot_z + radians(90))

    # Broken door leaning against entrance
    door = create_beam("BrokenDoor", 1.9, 0.9, 0.06, wood_mat)
    door.location = (pos_x - 0.9, pos_y - l/2 - 0.2, pos_z + 0.8)
    door.rotation_euler = (radians(15), radians(-10), rot_z + radians(25))

    create_vines_on_wall((pos_x + 1.2, pos_y - l/2, pos_z + 0.2), (-0.2, 0.1, 0.9), length=2.5, leaf_obj=leaf_obj, wood_mat=wood_mat)

def build_stone_ruin(stone_mat, wood_mat, leaf_obj):
    pos_x, pos_y, pos_z = 0.8, 5.5, 0.0

    wall1 = bpy.ops.mesh.primitive_cube_add(size=1.0, location=(pos_x - 1.2, pos_y, pos_z + 1.0))
    wall1 = bpy.context.active_object
    wall1.scale = (0.6, 2.2, 2.0)
    wall1.rotation_euler = (radians(-5), radians(8), radians(15))
    wall1.data.materials.append(stone_mat)

    wall2 = bpy.ops.mesh.primitive_cube_add(size=1.0, location=(pos_x + 1.5, pos_y + 0.8, pos_z + 0.8))
    wall2 = bpy.context.active_object
    wall2.scale = (0.6, 1.8, 1.6)
    wall2.rotation_euler = (radians(6), radians(-6), radians(-20))
    wall2.data.materials.append(stone_mat)

    beam = create_beam("ArchBeam", 2.8, 0.4, 0.3, wood_mat)
    beam.location = (pos_x, pos_y + 0.3, pos_z + 2.2)
    beam.rotation_euler = (radians(-8), radians(12), radians(5))

    create_vines_on_wall((pos_x - 1.2, pos_y - 0.5, pos_z + 0.1), (0.1, 0.2, 0.9), length=3.2, leaf_obj=leaf_obj, wood_mat=wood_mat)

# ---------------------------------------------------------
# Environment, Trees, Props
# ---------------------------------------------------------
def create_sapling_tree(x, y, z, scale=1.0, wood_mat=None, leaf_mat=None):
    bpy.ops.mesh.primitive_cylinder_add(radius=0.12 * scale, depth=2.8 * scale, location=(x, y, z + 1.4 * scale))
    trunk = bpy.context.active_object
    trunk.name = "TreeTrunk"
    trunk.rotation_euler = (random.uniform(-0.12, 0.12), random.uniform(-0.12, 0.12), random.uniform(0, 6.28))
    if wood_mat:
        trunk.data.materials.append(wood_mat)

    for i in range(5):
        cx = x + random.uniform(-0.5, 0.5) * scale
        cy = y + random.uniform(-0.5, 0.5) * scale
        cz = z + (2.2 + random.uniform(0, 1.0)) * scale

        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=2, radius=0.65 * scale, location=(cx, cy, cz))
        foliage = bpy.context.active_object
        foliage.name = "TreeCanopy"
        foliage.scale = (random.uniform(0.8, 1.3), random.uniform(0.8, 1.3), random.uniform(0.7, 1.1))
        if leaf_mat:
            foliage.data.materials.append(leaf_mat)

def create_village_terrain(ground_mat):
    mesh = bpy.data.meshes.new("TerrainMesh")
    obj = bpy.data.objects.new("Terrain", mesh)
    bpy.context.collection.objects.link(obj)

    size = 22.0
    res = 40
    verts = []
    faces = []

    for iy in range(res + 1):
        y = (iy / res) * size - size / 2.0
        for ix in range(res + 1):
            x = (ix / res) * size - size / 2.0

            path_x = 0.5 * sin(y * 0.3)
            dist_from_path = abs(x - path_x)

            z = (0.2 * sin(x * 0.8) + 0.15 * cos(y * 0.6))
            if dist_from_path < 2.0:
                z -= 0.25 * (1.0 - (dist_from_path / 2.0)**2)
            else:
                z += 0.2 * (dist_from_path - 2.0)**0.5

            z += (sin(x * 3.0 + y * 2.0) * 0.05)
            verts.append((x, y, z))

    for iy in range(res):
        for ix in range(res):
            p1 = iy * (res + 1) + ix
            p2 = p1 + 1
            p3 = (iy + 1) * (res + 1) + ix + 1
            p4 = (iy + 1) * (res + 1) + ix
            faces.append((p1, p2, p3, p4))

    mesh.from_pydata(verts, [], faces)
    mesh.update()

    if ground_mat:
        obj.data.materials.append(ground_mat)
    return obj

def add_village_props(wood_mat, rust_mat, stone_mat):
    # Scattered Fallen Roof Panels
    p1 = create_corrugated_panel("FallenPanel1", width=1.8, length=1.0, mat=rust_mat)
    p1.location = (-0.8, -1.2, 0.08)
    p1.rotation_euler = (radians(12), radians(-18), radians(45))

    p2 = create_corrugated_panel("FallenPanel2", width=1.5, length=0.9, mat=rust_mat)
    p2.location = (1.1, 1.8, 0.05)
    p2.rotation_euler = (radians(-8), radians(5), radians(-70))

    # Broken Planks
    for i in range(8):
        plank = create_beam(f"Plank_{i}", random.uniform(1.0, 1.8), random.uniform(0.12, 0.2), 0.04, wood_mat)
        px = random.uniform(-2.5, 2.5)
        py = random.uniform(-2.0, 4.0)
        plank.location = (px, py, 0.05)
        plank.rotation_euler = (random.uniform(-0.1, 0.1), random.uniform(-0.1, 0.1), random.uniform(0, 6.28))

    # Broken Wagon Wheel
    bpy.ops.mesh.primitive_torus_add(major_radius=0.5, minor_radius=0.04, location=(-1.1, 0.8, 0.5))
    wheel = bpy.context.active_object
    wheel.name = "WagonWheel"
    wheel.rotation_euler = (radians(80), radians(15), radians(-20))
    wheel.data.materials.append(wood_mat)

    for k in range(6):
        ang = k * math.pi / 3.0
        spoke = create_beam(f"Spoke_{k}", 0.95, 0.03, 0.03, wood_mat)
        spoke.location = (-1.1 + 0.1 * cos(ang), 0.8 + 0.1 * sin(ang), 0.5)
        spoke.rotation_euler = (radians(80), radians(15), ang)

    # Boulders & Loose Stones
    for i in range(12):
        bx = random.uniform(-4.0, 4.0)
        by = random.uniform(-3.0, 5.0)
        if abs(bx) < 0.8:
            bx += 1.2 if bx > 0 else -1.2
        bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=random.uniform(0.15, 0.4), location=(bx, by, 0.1))
        boulder = bpy.context.active_object
        boulder.scale = (random.uniform(0.8, 1.3), random.uniform(0.8, 1.3), random.uniform(0.6, 1.0))
        boulder.data.materials.append(stone_mat)

def scatter_foliage(grass_obj, leaf_mat):
    random.seed(101)

    # Ground grass scattering
    for i in range(120):
        gx = random.uniform(-7.0, 7.0)
        gy = random.uniform(-6.0, 7.0)

        path_x = 0.5 * sin(gy * 0.3)
        if abs(gx - path_x) < 0.7 and random.random() > 0.2:
            continue

        gz = 0.2 * sin(gx * 0.8) + 0.15 * cos(gy * 0.6)

        g_inst = grass_obj.copy()
        g_inst.data = grass_obj.data.copy()
        bpy.context.collection.objects.link(g_inst)
        g_inst.location = (gx, gy, max(0.0, gz))
        g_inst.rotation_euler = (random.uniform(-0.1, 0.1), random.uniform(-0.1, 0.1), random.uniform(0, 6.28))
        sc = random.uniform(0.7, 1.4)
        g_inst.scale = (sc, sc, sc)

    # Rooftop moss/grass patches
    roof_locs = [
        (-2.2, 1.2, 2.7), (-2.8, 1.8, 2.6), (-1.8, 0.6, 2.8),
        (2.3, 0.8, 2.5), (2.8, 1.2, 2.4), (0.8, 5.5, 2.3)
    ]
    for rx, ry, rz in roof_locs:
        for _ in range(5):
            g_inst = grass_obj.copy()
            g_inst.data = grass_obj.data.copy()
            bpy.context.collection.objects.link(g_inst)
            g_inst.location = (rx + random.uniform(-0.6, 0.6), ry + random.uniform(-0.6, 0.6), rz + random.uniform(0, 0.2))
            g_inst.rotation_euler = (random.uniform(-0.15, 0.15), random.uniform(-0.15, 0.15), random.uniform(0, 6.28))
            sc = random.uniform(0.5, 0.9)
            g_inst.scale = (sc, sc, sc)

# ---------------------------------------------------------
# Main Scene Construction
# ---------------------------------------------------------
def main():
    clear_scene()

    # Materials
    mat_stone = create_stone_material()
    mat_rust = create_rust_metal_material()
    mat_wood = create_wood_material()
    mat_leaf = create_foliage_material()
    mat_ground = create_ground_material()

    # Foliage base objects
    leaf_obj = create_leaf_plane(mat_leaf, size=0.18)
    grass_obj = create_grass_tuft(mat_leaf)
    leaf_obj.location = (0, 0, -100)
    grass_obj.location = (0, 0, -100)

    # Environment Building
    create_village_terrain(mat_ground)
    build_cottage_1(mat_stone, mat_wood, mat_rust, leaf_obj)
    build_cottage_2(mat_stone, mat_wood, mat_rust, leaf_obj)
    build_stone_ruin(mat_stone, mat_wood, leaf_obj)

    # Trees growing inside & around village
    create_sapling_tree(-2.0, 1.8, 0.0, scale=1.1, wood_mat=mat_wood, leaf_mat=mat_leaf)
    create_sapling_tree(1.2, 5.8, 0.0, scale=1.4, wood_mat=mat_wood, leaf_mat=mat_leaf)
    create_sapling_tree(-4.8, 6.5, 0.2, scale=1.8, wood_mat=mat_wood, leaf_mat=mat_leaf)
    create_sapling_tree(4.5, 6.0, 0.1, scale=1.7, wood_mat=mat_wood, leaf_mat=mat_leaf)
    create_sapling_tree(0.2, 9.0, 0.5, scale=2.2, wood_mat=mat_wood, leaf_mat=mat_leaf)

    # Details & Foliage
    add_village_props(mat_wood, mat_rust, mat_stone)
    scatter_foliage(grass_obj, mat_leaf)

    # Lighting setup
    sun_data = bpy.data.lights.new(name="GoldenSun", type='SUN')
    sun_data.energy = 4.5
    sun_data.color = (1.0, 0.88, 0.72) # Warm morning/late afternoon golden light
    sun_data.angle = radians(3.0)

    sun_obj = bpy.data.objects.new(name="GoldenSun", object_data=sun_data)
    bpy.context.collection.objects.link(sun_obj)
    sun_obj.rotation_euler = (radians(42), radians(20), radians(-65))

    # Sky Ambient World Lighting
    world = bpy.data.worlds.new("AbandonedVillageWorld")
    bpy.context.scene.world = world
    world.use_nodes = True
    w_nodes = world.node_tree.nodes
    w_links = world.node_tree.links
    w_nodes.clear()

    w_output = w_nodes.new('ShaderNodeOutputWorld')
    w_bg = w_nodes.new('ShaderNodeBackground')
    w_bg.inputs['Color'].default_value = (0.35, 0.48, 0.62, 1.0)
    w_bg.inputs['Strength'].default_value = 1.2
    w_links.new(w_bg.outputs['Background'], w_output.inputs['Surface'])

    # Subtle Atmospheric Fog
    bpy.ops.mesh.primitive_cube_add(size=1.0, location=(0, 2.0, 2.5))
    fog_box = bpy.context.active_object
    fog_box.name = "AtmosphericFog"
    fog_box.scale = (22.0, 22.0, 6.0)

    mat_fog = bpy.data.materials.new("Fog_Material")
    mat_fog.use_nodes = True
    f_nodes = mat_fog.node_tree.nodes
    f_links = mat_fog.node_tree.links
    f_nodes.clear()

    f_out = f_nodes.new('ShaderNodeOutputMaterial')
    f_vol = f_nodes.new('ShaderNodeVolumePrincipled')
    f_vol.inputs['Density'].default_value = 0.015
    f_vol.inputs['Color'].default_value = (0.8, 0.85, 0.9, 1.0)
    f_links.new(f_vol.outputs['Volume'], f_out.inputs['Volume'])
    fog_box.data.materials.append(mat_fog)

    # Camera setup
    cam_data = bpy.data.cameras.new("PresentationCamera")
    cam_data.lens = 38
    cam_data.dof.use_dof = True
    cam_data.dof.focus_distance = 6.8
    cam_data.dof.aperture_fstop = 3.5

    cam_obj = bpy.data.objects.new("PresentationCamera", cam_data)
    bpy.context.collection.objects.link(cam_obj)
    bpy.context.scene.camera = cam_obj

    cam_obj.location = (-3.8, -7.2, 2.1)
    cam_obj.rotation_euler = (radians(78), radians(0), radians(-24))

    # Render Settings
    scene = bpy.context.scene
    scene.render.engine = 'CYCLES'
    scene.cycles.samples = 64
    scene.render.resolution_x = 1536
    scene.render.resolution_y = 1536
    scene.view_settings.view_transform = 'AgX'
    scene.view_settings.look = 'Medium High Contrast'

main()
```