```python
import bpy
import mathutils
import math
import random

# Clear scene
bpy.ops.object.select_all(action='SELECT')
bpy.ops.object.delete(use_global=False)

# Set units to metric
bpy.context.scene.unit_settings.system = 'METRIC'
bpy.context.scene.unit_settings.scale_length = 1.0

# Collections
col_main = bpy.data.collections.new('Main')
bpy.context.scene.collection.children.link(col_main)
col_architecture = bpy.data.collections.new('Architecture')
col_main.children.link(col_architecture)
col_vegetation = bpy.data.collections.new('Vegetation')
col_main.children.link(col_vegetation)
col_details = bpy.data.collections.new('Details')
col_main.children.link(col_details)
col_lights = bpy.data.collections.new('Lights')
col_main.children.link(col_lights)

# Set active collection
bpy.context.view_layer.active_layer_collection = bpy.context.view_layer.layer_collection.children[col_main.name]

# -----------------------------------------------------------
# Materials
# -----------------------------------------------------------
def make_material(name, color, roughness=0.6, metallic=0.0, alpha=1.0, use_nodes=True):
    mat = bpy.data.materials.new(name)
    mat.use_nodes = use_nodes
    if use_nodes:
        bsdf = mat.node_tree.nodes.get('Principled BSDF')
        if bsdf:
            bsdf.inputs['Base Color'].default_value = color
            bsdf.inputs['Roughness'].default_value = roughness
            bsdf.inputs['Metallic'].default_value = metallic
            bsdf.inputs['Alpha'].default_value = alpha
    return mat

# Stone material
stone_color = (0.65, 0.55, 0.45, 1.0)
stone_rough = 0.8
stone_mat = make_material('Stone', stone_color, stone_rough)

# Mossy stone (slightly greener)
moss_stone_mat = make_material('MossStone', (0.45, 0.55, 0.35, 1.0), 0.9)

# Dark metal for rails
metal_mat = make_material('Metal', (0.1, 0.1, 0.1, 1.0), 0.3, 0.8)

# Wood for sleepers
wood_mat = make_material('Wood', (0.25, 0.15, 0.05, 1.0), 0.9)

# Green for vegetation (we'll use leaf materials with subsurface later)
leaf_mat = make_material('Leaf', (0.2, 0.7, 0.2, 1.0), 0.4, alpha=0.9)

# Glass (used for intact panes)
glass_mat = make_material('Glass', (0.8, 0.9, 1.0, 0.1), 0.0, alpha=0.3, roughness=0.1)
glass_mat.blend_method = 'BLEND'
glass_mat.shadow_method = 'HASHED'

# Emissive materials
crt_mat = make_material('CRT', (1.0, 0.6, 0.2, 1.0), roughness=0.2, metallic=0.0)
crt_mat.use_nodes = True
crt_bsdf = crt_mat.node_tree.nodes.get('Principled BSDF')
if crt_bsdf:
    crt_bsdf.inputs['Emission Strength'].default_value = 10.0

departures_mat = make_material('Departures', (0.0, 0.8, 0.3, 1.0), roughness=0.2)
departures_mat.use_nodes = True
dep_bsdf = departures_mat.node_tree.nodes.get('Principled BSDF')
if dep_bsdf:
    dep_bsdf.inputs['Emission Strength'].default_value = 8.0

signal_mat = make_material('SignalRed', (1.0, 0.0, 0.0, 1.0), roughness=0.2)
signal_mat.use_nodes = True
sig_bsdf = signal_mat.node_tree.nodes.get('Principled BSDF')
if sig_bsdf:
    sig_bsdf.inputs['Emission Strength'].default_value = 12.0

lamp_mat = make_material('LampGreen', (0.0, 1.0, 0.0, 1.0), roughness=0.2)
lamp_mat.use_nodes = True
lamp_bsdf = lamp_mat.node_tree.nodes.get('Principled BSDF')
if lamp_bsdf:
    lamp_bsdf.inputs['Emission Strength'].default_value = 8.0

# Old rusty metal for signs
rust_mat = make_material('Rust', (0.5, 0.2, 0.1, 1.0), 0.9, 0.1)

# -----------------------------------------------------------
# Terrain and Cutting
# -----------------------------------------------------------
# Ground plane
bpy.ops.mesh.primitive_plane_add(size=200, location=(0, 0, 0))
ground = bpy.context.object
ground.name = 'Ground'
ground.data.materials.append(stone_mat)  # assign a basic ground material
bpy.ops.object.transform_apply(location=True, rotation=True, scale=True)
# Make it slightly textured? We'll use displacement in material later? For simplicity, just flat.

# Create cutting: We'll make two retaining walls and a track bed
# Track bed
track_length = 80
track_width = 9  # width for two lanes
track_depth = -5  # below ground
bpy.ops.mesh.primitive_plane_add(size=1, location=(0, 0, track_depth))
track_bed = bpy.context.object
track_bed.name = 'TrackBed'
track_bed.scale = (track_length/2, track_width/2, 1)
bpy.ops.object.transform_apply(scale=True)
track_bed.data.materials.append(metal_mat)  # dark gravel

# Retaining wall north (where platform will be)
wall_height = 6  # from -5 to 1
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, track_width/2 + 1.5, track_depth + wall_height/2))
wall_north = bpy.context.object
wall_north.name = 'RetainingWallNorth'
wall_north.scale = (track_length/2, 1.5, wall_height/2)
bpy.ops.object.transform_apply(scale=True)
wall_north.data.materials.append(stone_mat)

# Retaining wall south (no platform)
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, -track_width/2 - 1.5, track_depth + wall_height/2))
wall_south = bpy.context.object
wall_south.name = 'RetainingWallSouth'
wall_south.scale = (track_length/2, 1.5, wall_height/2)
bpy.ops.object.transform_apply(scale=True)
wall_south.data.materials.append(stone_mat)

# Platform surface (north side above wall)
platform_y = track_width/2 + 1.5 + 0.1  # edge
platform_z = 1  # top of wall
platform_width = 10
bpy.ops.mesh.primitive_plane_add(size=1, location=(0, platform_y + platform_width/2 - 0.5, platform_z))
platform = bpy.context.object
platform.name = 'Platform'
platform.scale = (track_length/2, platform_width/2, 1)
bpy.ops.object.transform_apply(scale=True)
platform.data.materials.append(stone_mat)

# Platform edge wall (small step)
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, platform_y, platform_z - 0.3))
edge = bpy.context.object
edge.name = 'PlatformEdge'
edge.scale = (track_length/2, 0.3, 0.6)
bpy.ops.object.transform_apply(scale=True)
edge.data.materials.append(stone_mat)

# -----------------------------------------------------------
# Tracks
# -----------------------------------------------------------
# Two parallel tracks, each with two rails
# Track 1 centered at y = -1, track 2 at y = 1
for track_y in [-1, 1]:
    for rail_offset in [-0.6, 0.6]:
        # Create a rail as a cylinder along X
        bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=0.08, depth=track_length, location=(0, track_y + rail_offset, track_depth))
        rail = bpy.context.object
        rail.name = f'Rail_{track_y}_{rail_offset}'
        rail.rotation_euler = (0, math.radians(90), 0)
        bpy.ops.object.transform_apply(rotation=True)
        rail.data.materials.append(metal_mat)

# Sleepers: boxes across tracks every 2 units
for x in range(int(-track_length/2), int(track_length/2), 2):
    for track_y in [-1, 1]:
        bpy.ops.mesh.primitive_cube_add(size=1, location=(x, track_y, track_depth - 0.1))
        sleeper = bpy.context.object
        sleeper.name = f'Sleeper_{x}_{track_y}'
        sleeper.scale = (0.2, 1.2, 0.08)
        bpy.ops.object.transform_apply(scale=True)
        sleeper.data.materials.append(wood_mat)

# -----------------------------------------------------------
# Station Building - Main Booking Hall
# -----------------------------------------------------------
hall_width = 20
hall_depth = 8
hall_height = 10
hall_x = 0  # center
hall_y = platform_y + platform_width - 2  # set back slightly from platform edge
hall_z = platform_z

# Main block
bpy.ops.mesh.primitive_cube_add(size=1, location=(hall_x, hall_y - hall_depth/2, hall_z + hall_height/2))
hall = bpy.context.object
hall.name = 'BookingHall'
hall.scale = (hall_width/2, hall_depth/2, hall_height/2)
bpy.ops.object.transform_apply(scale=True)
hall.data.materials.append(stone_mat)

# Clock Tower (west side, attached to hall)
tower_x = hall_x - hall_width/2 - 2
tower_width = 4
tower_depth = hall_depth
tower_height = 18
bpy.ops.mesh.primitive_cube_add(size=1, location=(tower_x, hall_y - hall_depth/2, hall_z + tower_height/2))
tower = bpy.context.object
tower.name = 'ClockTower'
tower.scale = (tower_width/2, tower_depth/2, tower_height/2)
bpy.ops.object.transform_apply(scale=True)
tower.data.materials.append(stone_mat)

# Clock face (a circle or disc on the tower front)
bpy.ops.mesh.primitive_circle_add(vertices=32, radius=1.0, location=(tower_x - tower_width/2 - 0.1, hall_y - hall_depth/2, hall_z + tower_height*0.7))
clock = bpy.context.object
clock.name = 'ClockFace'
clock.rotation_euler = (0, math.radians(90), 0)
bpy.ops.object.transform_apply(rotation=True)
clock.data.materials.append(make_material('Clock', (0.9, 0.85, 0.7, 1.0), 0.1))  # pale face

# Add clock hands (simple sticks)
bpy.ops.mesh.primitive_cube_add(size=1, location=(tower_x - tower_width/2 - 0.2, hall_y - hall_depth/2, hall_z + tower_height*0.7))
hand_hour = bpy.context.object
hand_hour.name = 'HourHand'
hand_hour.scale = (0.05, 0.5, 0.02)
bpy.ops.object.transform_apply(scale=True)
hand_hour.rotation_euler.z = math.radians(45)
hand_hour.data.materials.append(metal_mat)

bpy.ops.mesh.primitive_cube_add(size=1, location=(tower_x - tower_width/2 - 0.2, hall_y - hall_depth/2, hall_z + tower_height*0.7))
hand_minute = bpy.context.object
hand_minute.name = 'MinuteHand'
hand_minute.scale = (0.03, 0.7, 0.015)
bpy.ops.object.transform_apply(scale=True)
hand_minute.rotation_euler.z = math.radians(10)
hand_minute.data.materials.append(metal_mat)

# -----------------------------------------------------------
# Arched Windows (cut booleans into hall)
# -----------------------------------------------------------
# For simplicity, create arches as separate objects with emissive interior? We'll do booleans.
# But we can also use a window frame shape and assign emissive inside.

# Create a few large arched windows on the south face of the hall
window_base_y = hall_y - hall_depth/2 - 0.1  # just inside
window_base_z = hall_z + 0.5
window_spacing = 5
window_radius = 1.5
window_height = 4

# Use a boolean modifier on the hall object for each window
# We'll create a cutter object (an arch: cylinder + cube)
for i in range(-2, 3):
    x = i * window_spacing
    if abs(x) > 8: continue  # keep within hall width
    # Create a cube with top cylinder for arch shape
    bpy.ops.mesh.primitive_cube_add(size=1, location=(x, window_base_y, window_base_z + window_height/2))
    cutter_cube = bpy.context.object
    cutter_cube.name = f'WindowCutter_{i}'
    cutter_cube.scale = (window_radius, 0.5, window_height/2)
    bpy.ops.object.transform_apply(scale=True)
    # Add cylinder on top
    bpy.ops.mesh.primitive_cylinder_add(vertices=16, radius=window_radius, depth=1, location=(x, window_base_y, window_base_z + window_height))
    cutter_cyl = bpy.context.object
    cutter_cyl.name = f'WindowTop_{i}'
    cutter_cyl.rotation_euler = (math.radians(90), 0, 0)
    bpy.ops.object.transform_apply(rotation=True)
    cutter_cyl.scale = (1, 1, 0.5)  # thin
    bpy.ops.object.transform_apply(scale=True)
    # Join the two into one cutter object
    bpy.context.view_layer.objects.active = cutter_cube
    bpy.ops.object.select_all(action='DESELECT')
    cutter_cube.select_set(True)
    cutter_cyl.select_set(True)
    bpy.ops.object.join()
    cutter = bpy.context.object
    cutter.name = f'ArchCutter_{i}'
    # Apply boolean to hall
    bpy.context.view_layer.objects.active = hall
    mod = hall.modifiers.new(name=f'BoolWindow_{i}', type='BOOLEAN')
    mod.object = cutter
    mod.operation = 'DIFFERENCE'
    bpy.ops.object.modifier_apply(modifier=mod.name)
    # Delete cutter
    bpy.data.objects.remove(cutter, do_unlink=True)

# Now create window frames and glass
for i in range(-2, 3):
    x = i * window_spacing
    if abs(x) > 8: continue
    # Glass plane
    bpy.ops.mesh.primitive_plane_add(size=1, location=(x, window_base_y - 0.05, window_base_z + window_height/2))
    glass_plane = bpy.context.object
    glass_plane.name = f'WindowGlass_{i}'
    glass_plane.scale = (window_radius, 1, window_height/2)
    bpy.ops.object.transform_apply(scale=True)
    glass_plane.data.materials.append(glass_mat)
    # Frame (a thin ring? We'll skip for simplicity)

# -----------------------------------------------------------
# Columns in front of hall
# -----------------------------------------------------------
col_radius = 0.6
col_height = hall_height
for i in range(-3, 4):
    x = i * 3.0
    if abs(x) > 9: continue
    bpy.ops.mesh.primitive_cylinder_add(vertices=12, radius=col_radius, depth=col_height, location=(x, hall_y + 0.5, hall_z + col_height/2))
    column = bpy.context.object
    column.name = f'Column_{i}'
    column.data.materials.append(stone_mat)

# -----------------------------------------------------------
# Iron Roof Trusses (simple structure above hall)
# -----------------------------------------------------------
# A frame along X every 3m
for i in range(-6, 7):
    x = i * 3.0
    # Vertical support
    bpy.ops.mesh.primitive_cube_add(size=1, location=(x, hall_y - hall_depth/2, hall_z + hall_height + 2))
    support = bpy.context.object
    support.name = f'TrussVert_{i}'
    support.scale = (0.08, 0.08, 3)
    bpy.ops.object.transform_apply(scale=True)
    support.data.materials.append(metal_mat)
    # Horizontal beam along Y
    bpy.ops.mesh.primitive_cube_add(size=1, location=(x, hall_y - hall_depth/2, hall_z + hall_height + 3))
    beam_y = bpy.context.object
    beam_y.name = f'TrussHorizY_{i}'
    beam_y.scale = (0.05, hall_depth/2, 0.05)
    bpy.ops.object.transform_apply(scale=True)
    beam_y.data.materials.append(metal_mat)

# Glazed roof: partial panes missing
# Create a grid of planes on top
for ix in range(-8, 9, 2):
    for iy in range(0, int(hall_depth/2), 2):
        # Randomly decide if pane is present (70% missing)
        if random.random() < 0.7:
            continue
        x = ix * 1.0
        y = hall_y - hall_depth/2 + iy
        bpy.ops.mesh.primitive_plane_add(size=1, location=(x, y, hall_z + hall_height + 3.5))
        pane = bpy.context.object
        pane.name = f'RoofPane_{ix}_{iy}'
        pane.scale = (1, 1, 1)  # adjust
        bpy.ops.object.transform_apply(scale=True)
        pane.data.materials.append(glass_mat)

# -----------------------------------------------------------
# Details: Ticket office with CRT, departures board, sign, signal, lamp
# -----------------------------------------------------------
# CRT terminal in ticket office (a small glowing box in a window)
ticket_x = 5
bpy.ops.mesh.primitive_cube_add(size=1, location=(ticket_x, hall_y - hall_depth/2 - 0.2, hall_z + 1.5))
crt = bpy.context.object
crt.name = 'CRT'
crt.scale = (0.5, 0.3, 0.4)
bpy.ops.object.transform_apply(scale=True)
crt.data.materials.append(crt_mat)
# Add a small point light inside
bpy.ops.object.light_add(type='POINT', location=(ticket_x, hall_y - hall_depth/2 - 0.3, hall_z + 1.5))
crt_light = bpy.context.object
crt_light.name = 'CRTLight'
crt_light.data.energy = 50
crt_light.data.color = (1.0, 0.6, 0.2)

# Departures board (green display) on station wall
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, hall_y + 0.2, hall_z + 4.5))
dep_board = bpy.context.object
dep_board.name = 'DeparturesBoard'
dep_board.scale = (2.0, 0.1, 0.8)
bpy.ops.object.transform_apply(scale=True)
dep_board.data.materials.append(departures_mat)

# Phosphor-green lamp beside staff door
bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=0.1, depth=0.3, location=(-4, hall_y - hall_depth/2 - 0.1, hall_z + 0.5))
lamp = bpy.context.object
lamp.name = 'GreenLamp'
lamp.rotation_euler = (math.radians(90), 0, 0)
lamp.data.materials.append(lamp_mat)

# Red signal far down the track
signal_x = 30
signal_y = 0
signal_z = track_depth + 3
bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=0.2, depth=4, location=(signal_x, signal_y, signal_z))
pole = bpy.context.object
pole.name = 'SignalPole'
pole.data.materials.append(metal_mat)
# Light head
bpy.ops.mesh.primitive_cylinder_add(vertices=8, radius=0.3, depth=0.3, location=(signal_x, signal_y, signal_z + 2))
head = bpy.context.object
head.name = 'SignalHead'
head.rotation_euler = (math.radians(90), 0, 0)
head.data.materials.append(signal_mat)

# Heritage sign: "RAVENSTHWAITE & DISTRICT RAILWAY Co."
# Use a text object
bpy.ops.object.text_add(location=(0, hall_y - hall_depth/2 - 0.5, hall_z + 1))
text_obj = bpy.context.object
text_obj.name = 'HeritageSign'
text_obj.data.body = "RAVENSTHWAITE & DISTRICT RAILWAY Co."
text_obj.data.size = 1.0
text_obj.data.extrude = 0.05
text_obj.data.align_x = 'CENTER'
text_obj.location.y = hall_y - hall_depth/2 - 0.2
# Rotate to face camera? We'll orient it to face south (towards tracks)
text_obj.rotation_euler.z = math.radians(0)  # already facing front
# Apply a rusty metal material
text_obj.data.materials.append(rust_mat)

# -----------------------------------------------------------
# Vegetation: Trees, ivy, grass, ferns
# -----------------------------------------------------------
# Tree function: create a simple tree trunk and crown
def make_tree(location, scale=1.0):
    # Trunk
    bpy.ops.mesh.primitive_cylinder_add(vertices=6, radius=0.2*scale, depth=3*scale, location=(location.x, location.y, location.z + 1.5*scale))
    trunk = bpy.context.object
    trunk.name = 'TreeTrunk'
    bpy.ops.object.shade_smooth()
    trunk.data.materials.append(wood_mat)
    # Crown (sphere)
    bpy.ops.mesh.primitive_uv_sphere_add(radius=1.5*scale, location=(location.x, location.y, location.z + 3*scale + 1.5*scale))
    crown = bpy.context.object
    crown.name = 'TreeCrown'
    crown.data.materials.append(leaf_mat)
    # Add some branch sticks (optional)
    return bpy.context.object  # return last created

# Place several trees around the scene
tree_locations = [
    (-25, 15, 0), (-20, 12, 0), (30, -10, 0), (25, -12, 0), (15, 20, 0), (-15, -15, 0)
]
for loc in tree_locations:
    make_tree(mathutils.Vector(loc), scale=random.uniform(0.8, 1.5))

# Ivy on walls: We'll create a few strands of ivy using a curve with leaves along it
# For simplicity, use a particle system on retaining walls with a leaf object
# First create a leaf mesh
bpy.ops.mesh.primitive_plane_add(size=0.2, location=(0,0,0))
leaf = bpy.context.object
leaf.name = 'IvyLeaf'
leaf.data.materials.append(leaf_mat)
# Apply rotation to make leaf flat
leaf.rotation_euler = (0, 0, 0)
bpy.ops.object.transform_apply(rotation=True)
# Hide leaf mesh from render? We'll use it as instance for particles.

# Add particle system to retaining walls
for wall in [wall_north, wall_south, hall, tower]:
    if wall.type != 'MESH': continue
    # Create new particle system
    if 'Ivy' not in wall.particle_systems:
        ps = wall.modifiers.new(name='Ivy', type='PARTICLE_SYSTEM')
        psys = wall.particle_systems[0]
        psys.settings.count = 200
        psys.settings.frame_start = 1
        psys.settings.frame_end = 1
        psys.settings.lifetime = 100
        psys.settings.emit_from = 'FACE'
        psys.settings.distribution = 'RAND'
        psys.settings.normal_factor = 0.5
        psys.settings.instance_object = leaf
        psys.settings.render_type = 'OBJECT'
        psys.settings.particle_size = 1.0
        psys.settings.use_scale_curve = True
        # random rotation
        psys.settings.rotation_mode = 'OBJ'
        psys.settings.random_rotation = 0.5

# Grass on tracks and platform
# Create a simple grass blade plane
bpy.ops.mesh.primitive_plane_add(size=0.1, location=(0,0,0))
grass_blade = bpy.context.object
grass_blade.name = 'GrassBlade'
grass_blade.data.materials.append(leaf_mat)
# Hide from render? We'll use as particle instance.

for obj in [track_bed, platform]:
    if obj.type != 'MESH': continue
    if 'Grass' not in obj.particle_systems:
        ps = obj.modifiers.new(name='Grass', type='PARTICLE_SYSTEM')
        psys = obj.particle_systems[0]
        psys.settings.count = 500
        psys.settings.frame_start = 1
        psys.settings.frame_end = 1
        psys.settings.lifetime = 100
        psys.settings.emit_from = 'FACE'
        psys.settings.normal_factor = 0.0
        psys.settings.instance_object = grass_blade
        psys.settings.render_type = 'OBJECT'
        psys.settings.particle_size = 0.3
        psys.settings.random_rotation = 1.0
        psys.settings.use_scale_curve = True
        # random offset
        psys.settings.size_random = 0.5
        psys.settings.rotation_mode = 'OBJ'
        psys.settings.random_rotation = 0.8

# Hide the source leaf and blade objects from render
leaf.hide_render = True
grass_blade.hide_render = True

# -----------------------------------------------------------
# Lighting
# -----------------------------------------------------------
# Sun lamp for warm summer afternoon
bpy.ops.object.light_add(type='SUN', location=(50, 20, 30))
sun = bpy.context.object
sun.name = 'Sun'
sun.data.energy = 5
sun.data.color = (1.0, 0.85, 0.6)
sun.rotation_euler = (math.radians(30), math.radians(0), math.radians(45))

# Add a few orange point lights for atmosphere
bpy.ops.object.light_add(type='POINT', location=(0, 10, 8))
ambient1 = bpy.context.object
ambient1.data.energy = 30
ambient1.data.color = (1.0, 0.7, 0.4)

bpy.ops.object.light_add(type='POINT', location=(-10, -5, 5))
ambient2 = bpy.context.object
ambient2.data.energy = 20
ambient2.data.color = (0.8, 0.6, 0.4)

# Volumetric mist (large cube with volume scatter)
bpy.ops.mesh.primitive_cube_add(size=1, location=(0, 0, 5))
mist_box = bpy.context.object
mist_box.name = 'MistBox'
mist_box.scale = (50, 30, 10)
bpy.ops.object.transform_apply(scale=True)
# Create volume material
mist_mat = bpy.data.materials.new('Mist')
mist_mat.use_nodes = True
mist_mat.node_tree.nodes.clear()
nodes = mist_mat.node_tree.nodes
links = mist_mat.node_tree.links
# Volume scatter
vol = nodes.new('ShaderNodeVolumeScatter')
vol.inputs['Density'].default_value = 0.01
vol.inputs['Color'].default_value = (1.0, 0.9, 0.7, 1.0)
# output
output = nodes.new('ShaderNodeOutputMaterial')
links.new(vol.outputs['Volume'], output.inputs['Volume'])
mist_box.data.materials.append(mist_mat)
# Ensure object has 'VOLUME' mode? Actually we need to set material to use volume
# The material will be used in Cycles if we enable volume in world? But here we apply volume to a mesh object, not the world.
# For Cycles to render volume from a mesh, it must have a volume shader and the camera must be inside or the object must be with appropriate settings. We'll set the object's 'Volume' attribute in the material settings.
# In practice, we can also add a world volume, but this mesh approach is simpler and visible.

# We'll also add some fog using world settings? Not needed.

# -----------------------------------------------------------
# Camera
# -----------------------------------------------------------
# Set camera to show the station and tracks with a good composition
# From a low angle looking up at the clock tower and building, with tracks in foreground
bpy.ops.object.camera_add(location=(10, -8, -3))
cam = bpy.context.object
cam.name = 'Camera'
# Aim at the clock tower top
target = mathutils.Vector((-5, 0, 8))
# Compute direction
direction = target - cam.location
cam.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()
# Set as active camera
bpy.context.scene.camera = cam

# Adjust camera focal length for better composition
cam.data.lens = 35
cam.data.sensor_width = 36

# -----------------------------------------------------------
# Additional scene details: broken bench, rubble, moss patches
# -----------------------------------------------------------
# Collapsed bench on platform
bpy.ops.mesh.primitive_cube_add(size=1, location=(3, hall_y - hall_depth/2 - 2, hall_z + 0.3))
bench = bpy.context.object
bench.name = 'BrokenBench'
bench.scale = (1.0, 0.2, 0.1)
bpy.ops.object.transform_apply(scale=True)
bench.rotation_euler.z = math.radians(15)
bench.data.materials.append(wood_mat)
# Add a second piece
bpy.ops.mesh.primitive_cube_add(size=1, location=(3.2, hall_y - hall_depth/2 - 2.1, hall_z + 0.2))
bench2 = bpy.context.object
bench2.name = 'BrokenBench2'
bench2.scale = (0.8, 0.1, 0.08)
bpy.ops.object.transform_apply(scale=True)
bench2.rotation_euler.z = math.radians(-10)
bench2.data.materials.append(wood_mat)

# Rubble: small rocks
for _ in range(20):
    loc = (random.uniform(-5,5), random.uniform(5,10), hall_z + 0.1)
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1, radius=0.1, location=loc)
    rock = bpy.context.object
    rock.name = 'Rubble'
    rock.data.materials.append(stone_mat)
    rock.scale = (random.uniform(0.3,1), random.uniform(0.3,1), random.uniform(0.2,0.5))
    bpy.ops.object.transform_apply(scale=True)

# -----------------------------------------------------------
# Final adjustments
# -----------------------------------------------------------
# Set render settings (benchmark overrides, but we set for preview)
bpy.context.scene.render.engine = 'CYCLES'
bpy.context.scene.render.resolution_x = 1536
bpy.context.scene.render.resolution_y = 1536
bpy.context.scene.render.film_transparent = False
# Enable denoising
bpy.context.scene.view_layers[0].cycles.denoising_store_passes = True

# Set world to a sky color (soft blue)
bpy.context.scene.world.use_nodes = True
world_nodes = bpy.context.scene.world.node_tree.nodes
world_links = bpy.context.scene.world.node_tree.links
world_nodes.clear()
# Background node
bg = world_nodes.new('ShaderNodeBackground')
bg.inputs['Color'].default_value = (0.4, 0.6, 0.8, 1.0)  # blue sky
bg.inputs['Strength'].default_value = 1.0
output = world_nodes.new('ShaderNodeOutputWorld')
world_links.new(bg.outputs['Background'], output.inputs['Surface'])

# Add a gradient texture for clouds? Not necessary.

# Finally, tag all objects to be selectable
print("Scene created successfully.")
```