```python
import bpy
import math
import random
from mathutils import Vector

# -----------------------------------------------------------------------------
# Scene setup
# -----------------------------------------------------------------------------

random.seed(17)

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

for datablocks in (
    bpy.data.meshes,
    bpy.data.curves,
    bpy.data.materials,
    bpy.data.cameras,
    bpy.data.lights,
):
    for datablock in list(datablocks):
        if datablock.users == 0:
            datablocks.remove(datablock)

scene = bpy.context.scene
scene.render.engine = 'CYCLES'
scene.render.resolution_x = 1536
scene.render.resolution_y = 1536
scene.render.resolution_percentage = 100
scene.render.image_settings.file_format = 'PNG'
scene.render.image_settings.color_mode = 'RGBA'
scene.render.film_transparent = False

try:
    scene.cycles.samples = 64
    scene.cycles.use_denoising = True
    scene.cycles.max_bounces = 10
    scene.cycles.transparent_max_bounces = 10
except Exception:
    pass

try:
    scene.view_settings.view_transform = 'AgX'
    scene.view_settings.look = 'AgX - Medium High Contrast'
    scene.view_settings.exposure = 0.15
except Exception:
    pass

scene.world.use_nodes = True
world_nodes = scene.world.node_tree.nodes
world_links = scene.world.node_tree.links
world_nodes.clear()
world_out = world_nodes.new('ShaderNodeOutputWorld')
world_bg = world_nodes.new('ShaderNodeBackground')
world_bg.inputs['Color'].default_value = (0.002, 0.018, 0.032, 1.0)
world_bg.inputs['Strength'].default_value = 0.32
world_links.new(world_bg.outputs['Background'], world_out.inputs['Surface'])

fish_collection = bpy.data.collections.new("Goldfish")
environment_collection = bpy.data.collections.new("Watery Environment")
bubble_collection = bpy.data.collections.new("Bubbles")
lighting_collection = bpy.data.collections.new("Lighting and Camera")
scene.collection.children.link(fish_collection)
scene.collection.children.link(environment_collection)
scene.collection.children.link(bubble_collection)
scene.collection.children.link(lighting_collection)

def move_to_collection(obj, collection):
    for old_collection in list(obj.users_collection):
        old_collection.objects.unlink(obj)
    collection.objects.link(obj)

def set_socket(node, names, value):
    if isinstance(names, str):
        names = [names]
    for name in names:
        socket = node.inputs.get(name)
        if socket is not None:
            socket.default_value = value
            return socket
    return None

def make_principled_material(
        name,
        color,
        roughness=0.35,
        metallic=0.0,
        alpha=1.0,
        transmission=0.0,
        coat=0.0,
        emission_strength=0.0,
        emission_color=None,
        ior=1.45):
    mat = bpy.data.materials.new(name)
    mat.use_nodes = True
    mat.diffuse_color = (color[0], color[1], color[2], alpha)
    mat.use_backface_culling = False

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    out = nodes.new('ShaderNodeOutputMaterial')
    bsdf = nodes.new('ShaderNodeBsdfPrincipled')
    bsdf.location = (-100, 0)

    set_socket(bsdf, "Base Color", (color[0], color[1], color[2], 1.0))
    set_socket(bsdf, "Roughness", roughness)
    set_socket(bsdf, "Metallic", metallic)
    set_socket(bsdf, ["IOR"], ior)
    set_socket(bsdf, ["Alpha"], alpha)
    set_socket(bsdf, ["Transmission Weight", "Transmission"], transmission)
    set_socket(bsdf, ["Coat Weight", "Clearcoat"], coat)
    set_socket(bsdf, ["Coat Roughness", "Clearcoat Roughness"], 0.18)
    set_socket(bsdf, ["Specular IOR Level", "Specular"], 0.5)

    if emission_color is None:
        emission_color = color
    set_socket(
        bsdf,
        ["Emission Color", "Emission"],
        (emission_color[0], emission_color[1], emission_color[2], 1.0)
    )
    set_socket(bsdf, "Emission Strength", emission_strength)

    links.new(bsdf.outputs['BSDF'], out.inputs['Surface'])

    if alpha < 0.999:
        try:
            mat.surface_render_method = 'DITHERED'
        except Exception:
            try:
                mat.blend_method = 'BLEND'
            except Exception:
                pass
        try:
            mat.use_transparency_overlap = False
        except Exception:
            pass

    return mat, bsdf

def make_body_material():
    mat, bsdf = make_principled_material(
        "Living Goldfish Orange",
        (1.0, 0.19, 0.008),
        roughness=0.27,
        metallic=0.0,
        coat=0.28,
        ior=1.43
    )
    nodes = mat.node_tree.nodes
    links = mat.node_tree.links

    tex = nodes.new('ShaderNodeTexCoord')
    tex.location = (-720, -180)

    vor = nodes.new('ShaderNodeTexVoronoi')
    vor.location = (-520, -180)
    vor.voronoi_dimensions = '3D'
    vor.feature = 'DISTANCE_TO_EDGE'
    vor.inputs['Scale'].default_value = 13.0

    ramp = nodes.new('ShaderNodeValToRGB')
    ramp.location = (-330, -180)
    ramp.color_ramp.interpolation = 'EASE'
    ramp.color_ramp.elements[0].position = 0.008
    ramp.color_ramp.elements[0].color = (0.05, 0.05, 0.05, 1.0)
    ramp.color_ramp.elements[1].position = 0.070
    ramp.color_ramp.elements[1].color = (0.88, 0.88, 0.88, 1.0)

    bump = nodes.new('ShaderNodeBump')
    bump.location = (-120, -150)
    bump.inputs['Strength'].default_value = 0.11
    bump.inputs['Distance'].default_value = 0.055

    links.new(tex.outputs['Generated'], vor.inputs['Vector'])
    links.new(vor.outputs['Distance'], ramp.inputs['Fac'])
    links.new(ramp.outputs['Color'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], bsdf.inputs['Normal'])

    return mat

def make_backdrop_material():
    mat = bpy.data.materials.new("Calm Teal Water Backdrop")
    mat.use_nodes = True
    mat.diffuse_color = (0.008, 0.12, 0.16, 1.0)
    mat.use_backface_culling = True

    nodes = mat.node_tree.nodes
    links = mat.node_tree.links
    nodes.clear()

    out = nodes.new('ShaderNodeOutputMaterial')
    mix_shader = nodes.new('ShaderNodeMixShader')
    transparent = nodes.new('ShaderNodeBsdfTransparent')
    geometry = nodes.new('ShaderNodeNewGeometry')
    bsdf = nodes.new('ShaderNodeBsdfPrincipled')

    texcoord = nodes.new('ShaderNodeTexCoord')
    separate = nodes.new('ShaderNodeSeparateXYZ')
    gradient = nodes.new('ShaderNodeValToRGB')
    noise = nodes.new('ShaderNodeTexNoise')
    noise_ramp = nodes.new('ShaderNodeValToRGB')
    color_mix = nodes.new('ShaderNodeMixRGB')
    bump = nodes.new('ShaderNodeBump')

    texcoord.location = (-900, 120)
    separate.location = (-700, 180)
    gradient.location = (-510, 220)
    noise.location = (-690, -80)
    noise_ramp.location = (-500, -70)
    color_mix.location = (-285, 150)
    bump.location = (-80, -130)
    bsdf.location = (-40, 180)
    geometry.location = (10, -250)
    transparent.location = (180, -150)
    mix_shader.location = (350, 120)
    out.location = (550, 120)

    gradient.color_ramp.interpolation = 'EASE'
    gradient.color_ramp.elements[0].position = 0.0
    gradient.color_ramp.elements[0].color = (0.002, 0.032, 0.052, 1.0)
    gradient.color_ramp.elements[1].position = 1.0
    gradient.color_ramp.elements[1].color = (0.014, 0.22, 0.25, 1.0)
    middle = gradient.color_ramp.elements.new(0.43)
    middle.color = (0.004, 0.105, 0.145, 1.0)

    noise.inputs['Scale'].default_value = 3.8
    noise.inputs['Detail'].default_value = 2.4
    noise.inputs['Roughness'].default_value = 0.55
    noise.inputs['Distortion'].default_value = 0.18

    noise_ramp.color_ramp.elements[0].color = (0.28, 0.42, 0.45, 1.0)
    noise_ramp.color_ramp.elements[1].color = (0.75, 0.91, 0.92, 1.0)

    color_mix.blend_type = 'SOFT_LIGHT'
    color_mix.inputs[0].default_value = 0.23

    set_socket(bsdf, "Base Color", (0.005, 0.11, 0.15, 1.0))
    set_socket(bsdf, "Roughness", 0.82)
    set_socket(bsdf, ["Emission Color", "Emission"], (0.003, 0.09, 0.13, 1.0))
    set_socket(bsdf, "Emission Strength", 0.065)

    bump.inputs['Strength'].default_value = 0.06
    bump.inputs['Distance'].default_value = 0.3

    links.new(texcoord.outputs['Generated'], separate.inputs['Vector'])
    links.new(separate.outputs['Z'], gradient.inputs['Fac'])
    links.new(texcoord.outputs['Generated'], noise.inputs['Vector'])
    links.new(noise.outputs['Fac'], noise_ramp.inputs['Fac'])
    links.new(gradient.outputs['Color'], color_mix.inputs[1])
    links.new(noise_ramp.outputs['Color'], color_mix.inputs[2])
    links.new(color_mix.outputs['Color'], bsdf.inputs['Base Color'])
    links.new(color_mix.outputs['Color'], bsdf.inputs.get('Emission Color', bsdf.inputs.get('Emission')))
    links.new(noise.outputs['Fac'], bump.inputs['Height'])
    links.new(bump.outputs['Normal'], bsdf.inputs['Normal'])

    links.new(geometry.outputs['Backfacing'], mix_shader.inputs[0])
    links.new(bsdf.outputs['BSDF'], mix_shader.inputs[1])
    links.new(transparent.outputs['BSDF'], mix_shader.inputs[2])
    links.new(mix_shader.outputs['Shader'], out.inputs['Surface'])

    return mat

body_mat = make_body_material()
orange_plain_mat, _ = make_principled_material(
    "Caudal Orange", (1.0, 0.18, 0.006), roughness=0.29, coat=0.2
)
belly_mat, _ = make_principled_material(
    "Warm Golden Belly", (1.0, 0.29, 0.018), roughness=0.32, coat=0.16
)
scale_mat, _ = make_principled_material(
    "Raised Golden Scale Arcs", (1.0, 0.37, 0.025),
    roughness=0.32, metallic=0.12, coat=0.18
)
gill_mat, _ = make_principled_material(
    "Burnished Gill Line", (0.55, 0.045, 0.002),
    roughness=0.38, coat=0.12
)

fin_core_mat, _ = make_principled_material(
    "Fin Membrane Core", (1.0, 0.17, 0.008),
    roughness=0.28, alpha=0.80, transmission=0.08,
    coat=0.14, emission_strength=0.018
)
fin_mid_mat, _ = make_principled_material(
    "Fin Membrane Mid", (1.0, 0.27, 0.020),
    roughness=0.25, alpha=0.58, transmission=0.20,
    coat=0.12, emission_strength=0.028
)
fin_edge_mat, _ = make_principled_material(
    "Translucent Fin Edge", (1.0, 0.49, 0.075),
    roughness=0.22, alpha=0.34, transmission=0.40,
    coat=0.1, emission_strength=0.055
)
fin_ray_mat, _ = make_principled_material(
    "Fine Orange Fin Rays", (0.78, 0.075, 0.003),
    roughness=0.3, alpha=0.75, transmission=0.05,
    coat=0.1, emission_strength=0.012
)
fin_outline_mat, _ = make_principled_material(
    "Luminous Fin Rims", (1.0, 0.38, 0.025),
    roughness=0.22, alpha=0.67, transmission=0.18,
    emission_strength=0.06
)

eye_base_mat, _ = make_principled_material(
    "Deep Glossy Eye", (0.018, 0.005, 0.001),
    roughness=0.12, coat=0.62
)
iris_mat, _ = make_principled_material(
    "Amber Iris", (1.0, 0.34, 0.015),
    roughness=0.16, metallic=0.16, coat=0.48
)
pupil_mat, _ = make_principled_material(
    "Black Pupil", (0.001, 0.001, 0.001),
    roughness=0.10, coat=0.65
)
eye_highlight_mat, _ = make_principled_material(
    "Eye Catchlight", (1.0, 0.94, 0.72),
    roughness=0.08, emission_strength=2.2
)
eye_socket_mat, _ = make_principled_material(
    "Eye Socket Orange", (0.72, 0.065, 0.002),
    roughness=0.32, coat=0.2
)
lip_mat, _ = make_principled_material(
    "Puckered Coral Lips", (0.95, 0.045, 0.003),
    roughness=0.33, coat=0.22
)
mouth_dark_mat, _ = make_principled_material(
    "Mouth Interior", (0.012, 0.0015, 0.001),
    roughness=0.5
)

bubble_mat, _ = make_principled_material(
    "Clear Blue Bubbles", (0.32, 0.82, 1.0),
    roughness=0.07, alpha=0.20, transmission=0.78,
    coat=0.28, ior=1.333
)
bubble_highlight_mat, _ = make_principled_material(
    "Bubble Sparkles", (0.50, 0.95, 1.0),
    roughness=0.08, alpha=0.82, emission_strength=1.0
)

plant_mat, _ = make_principled_material(
    "Distant Teal Grass", (0.008, 0.19, 0.15),
    roughness=0.72, alpha=0.82
)
stone_mat_a, _ = make_principled_material(
    "Blue Green Pebbles", (0.018, 0.11, 0.13), roughness=0.78
)
stone_mat_b, _ = make_principled_material(
    "Cool Slate Pebbles", (0.032, 0.075, 0.105), roughness=0.82
)
backdrop_mat = make_backdrop_material()

goldfish_root = bpy.data.objects.new("Goldfish Hero Root", None)
fish_collection.objects.link(goldfish_root)
goldfish_root["description"] = (
    "A rounded orange goldfish in a gentle S-curve, with translucent flowing fins, "
    "raised scales, two complete eyes, and a forked veil tail."
)

def make_mesh_object(name, verts, faces, collection, materials=None, face_materials=None, parent=None):
    mesh = bpy.data.meshes.new(name + " Mesh")
    mesh.from_pydata(verts, [], faces)
    mesh.validate(verbose=False)
    mesh.update()

    obj = bpy.data.objects.new(name, mesh)
    collection.objects.link(obj)

    if parent is not None:
        obj.parent = parent

    if materials:
        for mat in materials:
            mesh.materials.append(mat)

    if face_materials:
        for poly, material_index in zip(mesh.polygons, face_materials):
            poly.material_index = material_index

    for poly in mesh.polygons:
        poly.use_smooth = True

    return obj

def add_solidify(obj, thickness):
    modifier = obj.modifiers.new("Delicate Membrane Thickness", 'SOLIDIFY')
    modifier.thickness = thickness
    modifier.offset = 0.0
    modifier.use_even_offset = True
    return modifier

def curve_tube(name, points, radius, material, collection=fish_collection, parent=goldfish_root):
    if len(points) < 2:
        return None

    curve_data = bpy.data.curves.new(name + " Curve", 'CURVE')
    curve_data.dimensions = '3D'
    curve_data.resolution_u = 1
    curve_data.bevel_depth = radius
    curve_data.bevel_resolution = 2
    curve_data.resolution_u = 1

    spline = curve_data.splines.new('POLY')
    spline.points.add(len(points) - 1)
    for point, co in zip(spline.points, points):
        point.co = (co[0], co[1], co[2], 1.0)

    obj = bpy.data.objects.new(name, curve_data)
    collection.objects.link(obj)
    obj.data.materials.append(material)
    if parent is not None:
        obj.parent = parent

    bpy.ops.object.select_all(action='DESELECT')
    obj.select_set(True)
    bpy.context.view_layer.objects.active = obj
    bpy.ops.object.convert(target='MESH')
    obj = bpy.context.object
    obj.name = name

    for poly in obj.data.polygons:
        poly.use_smooth = True

    return obj

def add_uv_sphere(name, location, scale, material, collection, parent=None, segments=32, rings=20):
    bpy.ops.mesh.primitive_uv_sphere_add(
        segments=segments,
        ring_count=rings,
        radius=1.0,
        location=location
    )
    obj = bpy.context.object
    obj.name = name
    obj.scale = scale
    move_to_collection(obj, collection)
    if parent is not None:
        obj.parent = parent
    obj.data.materials.append(material)
    for poly in obj.data.polygons:
        poly.use_smooth = True
    return obj

# -----------------------------------------------------------------------------
# Parametric rounded body with an actual lateral S-curve
# -----------------------------------------------------------------------------

body_x_min = -2.60
body_x_max = 1.15

profile_controls = [
    (0.00, 0.035),
    (0.035, 0.48),
    (0.09, 0.92),
    (0.18, 1.28),
    (0.32, 1.48),
    (0.46, 1.52),
    (0.60, 1.38),
    (0.72, 1.15),
    (0.84, 0.82),
    (0.94, 0.56),
    (1.00, 0.43),
]

def smoothstep(value):
    return value * value * (3.0 - 2.0 * value)

def body_parameters_from_t(t):
    t = max(0.0, min(1.0, t))

    rz = profile_controls[-1][1]
    for index in range(len(profile_controls) - 1):
        t0, r0 = profile_controls[index]
        t1, r1 = profile_controls[index + 1]
        if t0 <= t <= t1:
            f = smoothstep((t - t0) / max(t1 - t0, 1.0e-8))
            rz = r0 * (1.0 - f) + r1 * f
            break

    ry = rz * (0.63 + 0.045 * math.sin(math.pi * t))
    center_y = 0.16 * math.sin(2.0 * math.pi * (t - 0.10)) + 0.14 * (t - 0.5)
    center_z = 0.10 * math.sin(2.0 * math.pi * (t + 0.05))
    return center_y, center_z, ry, rz

def body_parameters_from_x(x):
    t = (x - body_x_min) / (body_x_max - body_x_min)
    cy, cz, ry, rz = body_parameters_from_t(t)
    return t, cy, cz, ry, rz

def body_surface_y(x, z, side_sign):
    if x < body_x_min or x > body_x_max:
        return None
    _, cy, cz, ry, rz = body_parameters_from_x(x)
    local_z = (z - cz) / max(rz, 1.0e-6)
    if abs(local_z) >= 0.985:
        return None
    radial = math.sqrt(max(0.0, 1.0 - local_z * local_z))
    return cy + side_sign * ry * radial

body_verts = []
body_faces = []
body_face_mats = []
body_rings = 44
body_sides = 52

for ring in range(body_rings):
    t = ring / (body_rings - 1)
    x = body_x_min + (body_x_max - body_x_min) * t
    cy, cz, ry, rz = body_parameters_from_t(t)
    for side in range(body_sides):
        angle = 2.0 * math.pi * side / body_sides
        body_verts.append((
            x,
            cy + ry * math.cos(angle),
            cz + rz * math.sin(angle)
        ))

for ring in range(body_rings - 1):
    for side in range(body_sides):
        next_side = (side + 1) % body_sides
        a = ring * body_sides + side
        b = ring * body_sides + next_side
        c = (ring + 1) * body_sides + next_side
        d = (ring + 1) * body_sides + side
        body_faces.append((a, b, c, d))

        angle_mid = 2.0 * math.pi * (side + 0.5) / body_sides
        body_face_mats.append(1 if math.sin(angle_mid) < -0.78 else 0)

nose_cy, nose_cz, _, _ = body_parameters_from_t(0.0)
tail_cy, tail_cz, _, _ = body_parameters_from_t(1.0)
nose_center_index = len(body_verts)
body_verts.append((body_x_min - 0.02, nose_cy, nose_cz))
tail_center_index = len(body_verts)
body_verts.append((body_x_max + 0.02, tail_cy, tail_cz))

for side in range(body_sides):
    next_side = (side + 1) % body_sides
    body_faces.append((nose_center_index, next_side, side))
    body_face_mats.append(0)

    a = (body_rings - 1) * body_sides + side
    b = (body_rings - 1) * body_sides + next_side
    body_faces.append((tail_center_index, a, b))
    body_face_mats.append(0)

body_obj = make_mesh_object(
    "Rounded S-Curved Goldfish Body",
    body_verts,
    body_faces,
    fish_collection,
    [body_mat, belly_mat],
    body_face_mats,
    goldfish_root
)

# Curving caudal peduncle continues the S through the tail root.
ped_verts = []
ped_faces = []
ped_rings = 28
ped_sides = 32

start_x = 0.74
end_x = 1.78
_, ped_start_y, ped_start_z, _, _ = body_parameters_from_x(start_x)

for ring in range(ped_rings):
    u = ring / (ped_rings - 1)
    eased = smoothstep(u)
    x = start_x + (end_x - start_x) * u
    cy = ped_start_y * (1.0 - eased) + 0.34 * eased + 0.065 * math.sin(math.pi * u)
    cz = ped_start_z * (1.0 - eased) - 0.055 * eased + 0.035 * math.sin(math.pi * u)
    rz = (
        0.64 * (1.0 - smoothstep(min(u / 0.58, 1.0))) +
        0.36 * smoothstep(min(u / 0.58, 1.0))
    )
    if u > 0.58:
        f = smoothstep((u - 0.58) / 0.42)
        rz = 0.36 * (1.0 - f) + 0.30 * f
    ry = rz * 0.70

    for side in range(ped_sides):
        angle = 2.0 * math.pi * side / ped_sides
        ped_verts.append((
            x,
            cy + ry * math.cos(angle),
            cz + rz * math.sin(angle)
        ))

for ring in range(ped_rings - 1):
    for side in range(ped_sides):
        ns = (side + 1) % ped_sides
        a = ring * ped_sides + side
        b = ring * ped_sides + ns
        c = (ring + 1) * ped_sides + ns
        d = (ring + 1) * ped_sides + side
        ped_faces.append((a, b, c, d))

peduncle_obj = make_mesh_object(
    "Curved Caudal Peduncle",
    ped_verts,
    ped_faces,
    fish_collection,
    [orange_plain_mat],
    None,
    goldfish_root
)

# -----------------------------------------------------------------------------
# Large forked, rippling veil tail
# -----------------------------------------------------------------------------

tail_root = Vector((1.55, 0.34, -0.055))

def tail_point(u, v):
    half_height = 0.20 * (1.0 - u) + 2.08 * (u ** 0.82)
    x = tail_root.x + u * (2.00 + 0.85 * (abs(v) ** 1.55))
    y = (
        tail_root.y
        + 0.35 * math.sin(math.pi * u) * math.sin(math.pi * v)
        - 0.26 * u * v
        + 0.09 * (u ** 1.6) * math.sin(5.0 * math.pi * v)
    )
    z = (
        tail_root.z
        + v * half_height
        + 0.12 * (u ** 1.55) * math.sin(3.0 * math.pi * v)
    )
    return Vector((x, y, z))

tail_verts = []
tail_faces = []
tail_mats = []
tail_u_count = 24
tail_v_count = 43

for iu in range(tail_u_count):
    u = iu / (tail_u_count - 1)
    for iv in range(tail_v_count):
        v = -1.0 + 2.0 * iv / (tail_v_count - 1)
        tail_verts.append(tuple(tail_point(u, v)))

for iu in range(tail_u_count - 1):
    umid = (iu + 0.5) / (tail_u_count - 1)
    if umid < 0.53:
        mat_index = 0
    elif umid < 0.82:
        mat_index = 1
    else:
        mat_index = 2

    for iv in range(tail_v_count - 1):
        a = iu * tail_v_count + iv
        b = (iu + 1) * tail_v_count + iv
        c = (iu + 1) * tail_v_count + iv + 1
        d = iu * tail_v_count + iv + 1
        tail_faces.append((a, b, c, d))
        tail_mats.append(mat_index)

tail_obj = make_mesh_object(
    "Big Forked Flowing Tail Fin",
    tail_verts,
    tail_faces,
    fish_collection,
    [fin_core_mat, fin_mid_mat, fin_edge_mat],
    tail_mats,
    goldfish_root
)
add_solidify(tail_obj, 0.022)

curve_tube(
    "Tail Trailing Translucent Rim",
    [tail_point(1.0, -1.0 + 2.0 * i / 50.0) for i in range(51)],
    0.026,
    fin_outline_mat
)
curve_tube(
    "Tail Upper Flowing Edge",
    [tail_point(i / 32.0, 1.0) for i in range(33)],
    0.022,
    fin_outline_mat
)
curve_tube(
    "Tail Lower Flowing Edge",
    [tail_point(i / 32.0, -1.0) for i in range(33)],
    0.022,
    fin_outline_mat
)

for index, v in enumerate((-0.86, -0.66, -0.46, -0.24, 0.0, 0.24, 0.46, 0.66, 0.86)):
    curve_tube(
        "Tail Fin Ray %02d" % index,
        [tail_point(0.04 + 0.96 * i / 28.0, v) for i in range(29)],
        0.014 if abs(v) > 0.01 else 0.017,
        fin_ray_mat
    )

# -----------------------------------------------------------------------------
# Dorsal and anal fins
# -----------------------------------------------------------------------------

def make_midline_fin(name, x0, x1, direction=1.0, maximum_height=1.0, ray_count=6):
    def point(s, v):
        x_base = x0 + (x1 - x0) * s
        _, cy, cz, _, rz = body_parameters_from_x(x_base)
        base_z = cz + direction * rz
        envelope = (
            0.055 +
            maximum_height *
            (math.sin(math.pi * s) ** 0.58) *
            (1.0 - 0.28 * s if direction > 0.0 else 1.0 - 0.12 * s)
        )
        x = x_base + 0.16 * v * s
        y = cy + 0.105 * v * math.sin(2.0 * math.pi * s) + 0.025 * v
        z = base_z + direction * v * envelope
        return Vector((x, y, z))

    verts = []
    faces = []
    face_mats = []
    s_count = 22
    v_count = 7

    for si in range(s_count):
        s = si / (s_count - 1)
        for vi in range(v_count):
            v = vi / (v_count - 1)
            verts.append(tuple(point(s, v)))

    for si in range(s_count - 1):
        for vi in range(v_count - 1):
            a = si * v_count + vi
            b = (si + 1) * v_count + vi
            c = (si + 1) * v_count + vi + 1
            d = si * v_count + vi + 1
            faces.append((a, b, c, d))
            vmid = (vi + 0.5) / (v_count - 1)
            face_mats.append(0 if vmid < 0.52 else (1 if vmid < 0.82 else 2))

    obj = make_mesh_object(
        name,
        verts,
        faces,
        fish_collection,
        [fin_core_mat, fin_mid_mat, fin_edge_mat],
        face_mats,
        goldfish_root
    )
    add_solidify(obj, 0.018)

    curve_tube(
        name + " Translucent Rim",
        [point(i / 36.0, 1.0) for i in range(37)],
        0.020,
        fin_outline_mat
    )

    for index in range(ray_count):
        s = 0.10 + 0.78 * index / max(ray_count - 1, 1)
        curve_tube(
            name + " Ray %02d" % index,
            [point(s, 0.06 + 0.94 * j / 16.0) for j in range(17)],
            0.0115,
            fin_ray_mat
        )

    return obj

make_midline_fin(
    "Tall Flowing Dorsal Fin",
    -1.02,
    0.88,
    direction=1.0,
    maximum_height=1.05,
    ray_count=7
)
make_midline_fin(
    "Small Lower Anal Fin",
    0.12,
    1.04,
    direction=-1.0,
    maximum_height=0.62,
    ray_count=4
)

# -----------------------------------------------------------------------------
# Pectoral side fins, complete on both sides and slightly asymmetric in motion
# -----------------------------------------------------------------------------

def make_pectoral_fin(side_sign):
    side_name = "Near" if side_sign < 0 else "Far"
    root_x = -1.16
    root_z = -0.08
    root_y = body_surface_y(root_x, root_z, side_sign)
    far_lift = 0.16 if side_sign > 0 else 0.0
    far_sweep = 0.10 if side_sign > 0 else 0.0

    def point(u, v):
        root = Vector((
            root_x + 0.045 * v,
            root_y,
            root_z + 0.065 * v
        ))
        end = Vector((
            -0.08 + 0.36 * v + far_sweep,
            root_y + side_sign * (0.74 + 0.08 * math.cos(math.pi * v)),
            -0.82 + 0.58 * v + far_lift
        ))
        eased = u ** 0.92
        pos = root.lerp(end, eased)
        pos.y += side_sign * 0.11 * math.sin(math.pi * u) * math.sin(math.pi * v)
        pos.z += 0.055 * math.sin(math.pi * u) * math.cos(math.pi * v)
        return pos

    u_count = 14
    v_count = 17
    verts = []
    faces = []
    face_mats = []

    for ui in range(u_count):
        u = ui / (u_count - 1)
        for vi in range(v_count):
            v = -1.0 + 2.0 * vi / (v_count - 1)
            verts.append(tuple(point(u, v)))

    for ui in range(u_count - 1):
        umid = (ui + 0.5) / (u_count - 1)
        mat_index = 0 if umid < 0.50 else (1 if umid < 0.80 else 2)
        for vi in range(v_count - 1):
            a = ui * v_count + vi
            b = (ui + 1) * v_count + vi
            c = (ui + 1) * v_count + vi + 1
            d = ui * v_count + vi + 1
            faces.append((a, b, c, d))
            face_mats.append(mat_index)

    obj = make_mesh_object(
        side_name + " Pectoral Side Fin",
        verts,
        faces,
        fish_collection,
        [fin_core_mat, fin_mid_mat, fin_edge_mat],
        face_mats,
        goldfish_root
    )
    add_solidify(obj, 0.016)

    curve_tube(
        side_name + " Pectoral Trailing Rim",
        [point(1.0, -1.0 + 2.0 * i / 28.0) for i in range(29)],
        0.019,
        fin_outline_mat
    )

    for index, v in enumerate((-0.72, -0.35, 0.0, 0.35, 0.72)):
        curve_tube(
            side_name + " Pectoral Ray %02d" % index,
            [point(0.05 + 0.95 * i / 18.0, v) for i in range(19)],
            0.0105,
            fin_ray_mat
        )

    return obj

make_pectoral_fin(-1.0)
make_pectoral_fin(1.0)

# -----------------------------------------------------------------------------
# Raised scale arcs and curved gill covers on both flanks
# -----------------------------------------------------------------------------

def make_surface_ribbons(name, paths, side_sign, width, material, outward_offset=0.018):
    verts = []
    faces = []

    for path in paths:
        if len(path) < 2:
            continue

        valid = True
        for x, z in path:
            if body_surface_y(x, z, side_sign) is None:
                valid = False
                break
        if not valid:
            continue

        base_index = len(verts)

        for index, (x, z) in enumerate(path):
            if index == 0:
                tx = path[1][0] - path[0][0]
                tz = path[1][1] - path[0][1]
            elif index == len(path) - 1:
                tx = path[-1][0] - path[-2][0]
                tz = path[-1][1] - path[-2][1]
            else:
                tx = path[index + 1][0] - path[index - 1][0]
                tz = path[index + 1][1] - path[index - 1][1]

            length = math.sqrt(tx * tx + tz * tz)
            if length < 1.0e-6:
                length = 1.0
            px = -tz / length
            pz = tx / length

            x0 = x - px * width * 0.5
            z0 = z - pz * width * 0.5
            x1 = x + px * width * 0.5
            z1 = z + pz * width * 0.5
            y0 = body_surface_y(x0, z0, side_sign)
            y1 = body_surface_y(x1, z1, side_sign)

            if y0 is None:
                y0 = body_surface_y(x, z, side_sign)
            if y1 is None:
                y1 = body_surface_y(x, z, side_sign)

            verts.append((x0, y0 + side_sign * outward_offset, z0))
            verts.append((x1, y1 + side_sign * outward_offset, z1))

        for index in range(len(path) - 1):
            a = base_index + index * 2
            b = base_index + (index + 1) * 2
            faces.append((a, b, b + 1, a + 1))

    if not faces:
        return None

    return make_mesh_object(
        name,
        verts,
        faces,
        fish_collection,
        [material],
        None,
        goldfish_root
    )

scale_paths = []
scale_rows = (-0.72, -0.39, -0.06, 0.27, 0.60, 0.90)

for row_index, z_center in enumerate(scale_rows):
    stagger = 0.17 if row_index % 2 else 0.0
    for column in range(7):
        x_center = -1.13 + column * 0.34 + stagger
        radius = 0.17
        path = []
        for sample in range(14):
            angle = -1.28 + 2.56 * sample / 13.0
            x = x_center + radius * math.cos(angle)
            z = z_center + radius * math.sin(angle)
            path.append((x, z))
        scale_paths.append(path)

make_surface_ribbons(
    "Near Flank Raised Scale Pattern",
    scale_paths,
    -1.0,
    0.022,
    scale_mat
)
make_surface_ribbons(
    "Far Flank Raised Scale Pattern",
    scale_paths,
    1.0,
    0.022,
    scale_mat
)

gill_path = []
for sample in range(28):
    angle = -1.18 + 2.36 * sample / 27.0
    gill_path.append((
        -1.53 + 0.39 * math.cos(angle),
        0.04 + 0.65 * math.sin(angle)
    ))

make_surface_ribbons(
    "Near Curved Gill Cover",
    [gill_path],
    -1.0,
    0.052,
    gill_mat,
    outward_offset=0.025
)
make_surface_ribbons(
    "Far Curved Gill Cover",
    [gill_path],
    1.0,
    0.052,
    gill_mat,
    outward_offset=0.025
)

# -----------------------------------------------------------------------------
# Two complete expressive eyes
# -----------------------------------------------------------------------------

eye_x = -1.86
eye_z = 0.47

for side_sign, side_name in ((-1.0, "Near"), (1.0, "Far")):
    surface_y = body_surface_y(eye_x, eye_z, side_sign)

    bpy.ops.mesh.primitive_torus_add(
        major_segments=40,
        minor_segments=10,
        major_radius=0.35,
        minor_radius=0.065,
        location=(eye_x, surface_y + side_sign * 0.020, eye_z),
        rotation=(math.pi / 2.0, 0.0, 0.0)
    )
    socket = bpy.context.object
    socket.name = side_name + " Raised Eye Socket"
    move_to_collection(socket, fish_collection)
    socket.parent = goldfish_root
    socket.data.materials.append(eye_socket_mat)
    for poly in socket.data.polygons:
        poly.use_smooth = True

    add_uv_sphere(
        side_name + " Glossy Eyeball",
        (eye_x, surface_y + side_sign * 0.115, eye_z),
        (0.325, 0.215, 0.345),
        eye_base_mat,
        fish_collection,
        goldfish_root
    )
    add_uv_sphere(
        side_name + " Amber Iris",
        (eye_x, surface_y + side_sign * 0.305, eye_z),
        (0.230, 0.055, 0.238),
        iris_mat,
        fish_collection,
        goldfish_root
    )
    add_uv_sphere(
        side_name + " Black Pupil",
        (eye_x - 0.010, surface_y + side_sign * 0.350, eye_z - 0.005),
        (0.132, 0.040, 0.153),
        pupil_mat,
        fish_collection,
        goldfish_root,
        segments=28,
        rings=16
    )
    add_uv_sphere(
        side_name + " Eye Catchlight",
        (
            eye_x - 0.060,
            surface_y + side_sign * 0.392,
            eye_z + 0.080
        ),
        (0.046, 0.018, 0.046),
        eye_highlight_mat,
        fish_collection,
        goldfish_root,
        segments=20,
        rings=12
    )

# Puckered circular goldfish mouth.
mouth_location = (body_x_min - 0.045, nose_cy, nose_cz - 0.13)

bpy.ops.mesh.primitive_cylinder_add(
    vertices=32,
    radius=0.105,
    depth=0.032,
    location=(mouth_location[0] - 0.012, mouth_location[1], mouth_location[2]),
    rotation=(0.0, math.pi / 2.0, 0.0)
)
mouth_inner = bpy.context.object
mouth_inner.name = "Small Dark Mouth Opening"
move_to_collection(mouth_inner, fish_collection)
mouth_inner.parent = goldfish_root
mouth_inner.data.materials.append(mouth_dark_mat)

bpy.ops.mesh.primitive_torus_add(
    major_segments=40,
    minor_segments=10,
    major_radius=0.175,
    minor_radius=0.060,
    location=mouth_location,
    rotation=(0.0, math.pi / 2.0, 0.0)
)
mouth_lips = bpy.context.object
mouth_lips.name = "Puckered Goldfish Lips"
move_to_collection(mouth_lips, fish_collection)
mouth_lips.parent = goldfish_root
mouth_lips.scale.z = 0.82
mouth_lips.data.materials.append(lip_mat)
for poly in mouth_lips.data.polygons:
    poly.use_smooth = True

# -----------------------------------------------------------------------------
# Calm underwater cyclorama, pebbles, and distant water grass
# -----------------------------------------------------------------------------

profile = [
    (-5.5, -3.0),
    (-2.0, -3.0),
    (0.0, -3.0),
    (2.0, -3.0),
]

for index in range(1, 13):
    theta = -math.pi / 2.0 + (math.pi / 2.0) * index / 12.0
    y = 2.0 + 2.5 * math.cos(theta)
    z = -0.5 + 2.5 * math.sin(theta)
    profile.append((y, z))

profile.extend([
    (4.5, 2.0),
    (4.5, 5.7),
])

backdrop_verts = []
backdrop_faces = []
backdrop_half_width = 6.2

for y, z in profile:
    backdrop_verts.append((-backdrop_half_width, y, z))
    backdrop_verts.append((backdrop_half_width, y, z))

for index in range(len(profile) - 1):
    a = index * 2
    b = a + 1
    c = (index + 1) * 2 + 1
    d = (index + 1) * 2
    backdrop_faces.append((a, b, c, d))

backdrop = make_mesh_object(
    "One Sided Calm Water Cyclorama",
    backdrop_verts,
    backdrop_faces,
    environment_collection,
    [backdrop_mat]
)
backdrop["note"] = "Front-facing in presentation, backface-culled for unobstructed rear inspection."

# Pebbles sit low enough not to compete with the hero.
pebble_data = [
    (-3.85, 1.10, -2.86, 0.48, 0.33, 0.16, 0.20),
    (-3.15, 1.75, -2.88, 0.30, 0.24, 0.13, -0.25),
    (-1.65, 2.00, -2.90, 0.36, 0.26, 0.11, 0.35),
    (2.75, 1.75, -2.87, 0.43, 0.30, 0.15, -0.20),
    (3.65, 1.10, -2.89, 0.34, 0.25, 0.12, 0.38),
    (4.25, 1.90, -2.88, 0.50, 0.31, 0.14, 0.12),
]

for index, (x, y, z, sx, sy, sz, rot) in enumerate(pebble_data):
    bpy.ops.mesh.primitive_ico_sphere_add(
        subdivisions=2,
        radius=1.0,
        location=(x, y, z)
    )
    pebble = bpy.context.object
    pebble.name = "Smooth Underwater Pebble %02d" % index
    pebble.scale = (sx, sy, sz)
    pebble.rotation_euler.z = rot
    move_to_collection(pebble, environment_collection)
    pebble.data.materials.append(stone_mat_a if index % 2 == 0 else stone_mat_b)
    for poly in pebble.data.polygons:
        poly.use_smooth = True

def make_grass_blade(name, base_x, base_y, height, bend, phase):
    steps = 11
    verts = []
    faces = []

    for index in range(steps):
        t = index / (steps - 1)
        center_x = (
            base_x
            + bend * (t ** 1.4)
            + 0.07 * math.sin(2.0 * math.pi * t + phase) * t
        )
        center_y = base_y + 0.08 * math.sin(math.pi * t + phase) * t
        center_z = -2.96 + height * t
        width = 0.095 * ((1.0 - t) ** 0.62) + 0.008

        verts.append((center_x - width, center_y, center_z))
        verts.append((center_x + width, center_y, center_z))

    for index in range(steps - 1):
        a = index * 2
        b = (index + 1) * 2
        faces.append((a, b, b + 1, a + 1))

    blade = make_mesh_object(
        name,
        verts,
        faces,
        environment_collection,
        [plant_mat]
    )
    add_solidify(blade, 0.018)
    return blade

for cluster_index, cluster_x in enumerate((-4.35, 4.55)):
    for blade_index in range(6):
        make_grass_blade(
            "Distant Water Grass %d %d" % (cluster_index, blade_index),
            cluster_x + (blade_index - 2.5) * 0.13,
            1.65 + 0.05 * blade_index,
            0.72 + 0.16 * ((blade_index * 3 + cluster_index) % 4),
            (-0.28 + 0.11 * blade_index) * (-1.0 if cluster_index else 1.0),
            blade_index * 0.65
        )

# -----------------------------------------------------------------------------
# Bubbles: a small breath trail plus quiet ambient bubbles
# -----------------------------------------------------------------------------

bubble_specs = [
    (-2.92, -0.12, 0.20, 0.060),
    (-3.18, 0.02, 0.58, 0.085),
    (-3.38, 0.17, 1.04, 0.125),
    (-3.02, 0.75, 2.15, 0.085),
    (-1.80, 1.30, 2.86, 0.125),
    (0.75, 2.10, 3.10, 0.070),
    (2.75, 1.10, 2.68, 0.155),
    (3.45, 0.15, 1.52, 0.085),
    (3.82, 1.00, -0.35, 0.110),
    (2.10, -0.85, -2.28, 0.070),
    (-3.78, 1.80, -1.28, 0.115),
    (0.05, 2.65, 2.35, 0.055),
]

for index, (x, y, z, radius) in enumerate(bubble_specs):
    bubble = add_uv_sphere(
        "Water Bubble %02d" % index,
        (x, y, z),
        (radius, radius, radius),
        bubble_mat,
        bubble_collection,
        None,
        segments=24,
        rings=14
    )

    if radius >= 0.08:
        add_uv_sphere(
            "Bubble Glint %02d" % index,
            (
                x - radius * 0.30,
                y - radius * 0.60,
                z + radius * 0.35
            ),
            (radius * 0.17, radius * 0.07, radius * 0.17),
            bubble_highlight_mat,
            bubble_collection,
            None,
            segments=16,
            rings=10
        )

# -----------------------------------------------------------------------------
# Cinematic lighting
# -----------------------------------------------------------------------------

def aim_object(obj, target):
    direction = Vector(target) - obj.location
    obj.rotation_euler = direction.to_track_quat('-Z', 'Y').to_euler()

def add_area_light(name, location, target, energy, size, color, shape='DISK'):
    light_data = bpy.data.lights.new(name + " Data", 'AREA')
    light_data.energy = energy
    light_data.shape = shape
    light_data.size = size
    light_data.color = color

    obj = bpy.data.objects.new(name, light_data)
    lighting_collection.objects.link(obj)
    obj.location = location
    aim_object(obj, target)
    return obj

add_area_light(
    "Warm Upper Key",
    (-4.4, -5.6, 7.1),
    (-0.25, 0.0, 0.05),
    1050.0,
    4.2,
    (1.0, 0.78, 0.58)
)
add_area_light(
    "Cool Underwater Fill",
    (4.6, -4.4, 1.1),
    (0.4, 0.0, -0.15),
    620.0,
    3.8,
    (0.26, 0.67, 0.82)
)
add_area_light(
    "Cyan Translucent Fin Rim",
    (2.7, 3.0, 5.4),
    (0.8, 0.15, 0.0),
    1350.0,
    3.4,
    (0.34, 0.88, 1.0)
)
add_area_light(
    "Soft Overhead Water Light",
    (-0.2, 0.5, 8.0),
    (0.3, 0.0, -0.2),
    720.0,
    4.8,
    (0.68, 0.91, 1.0)
)

# -----------------------------------------------------------------------------
# Presentation camera
# -----------------------------------------------------------------------------

focus = bpy.data.objects.new("Goldfish Camera Focus", None)
lighting_collection.objects.link(focus)
focus.location = (0.45, 0.0, -0.05)

camera_data = bpy.data.cameras.new("Goldfish Hero Camera Data")
camera = bpy.data.objects.new("Goldfish Hero Camera", camera_data)
lighting_collection.objects.link(camera)

camera.location = (-5.8, -14.5, 3.8)
camera_data.lens = 70.0
camera_data.sensor_width = 36.0
camera_data.clip_start = 0.05
camera_data.clip_end = 200.0
camera_data.dof.use_dof = True
camera_data.dof.focus_object = focus
camera_data.dof.aperture_fstop = 5.6
camera_data.dof.aperture_blades = 8
aim_object(camera, focus.location)

scene.camera = camera

# Slightly lift the whole living subject off the exact mechanical axes.
goldfish_root.rotation_euler = (
    math.radians(1.4),
    math.radians(0.8),
    math.radians(0.5)
)
goldfish_root.location.z = 0.03

# Tidy final selection and preserve the complete scene.
bpy.ops.object.select_all(action='DESELECT')
camera.select_set(True)
bpy.context.view_layer.objects.active = camera

scene.render.filepath = "/tmp/goldfish_hero.png"

try:
    bpy.ops.wm.save_as_mainfile(filepath="/tmp/goldfish_hero.blend")
except Exception:
    pass
```