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

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

# ---------- Materials ----------
def material(name, color, rough=.5, metal=0, noise=0, bump=0):
    m=bpy.data.materials.new(name)
    m.diffuse_color=(*color,1)
    m.use_nodes=True
    n=m.node_tree.nodes
    p=n.get("Principled BSDF")
    p.inputs["Base Color"].default_value=(*color,1)
    p.inputs["Roughness"].default_value=rough
    p.inputs["Metallic"].default_value=metal
    if noise:
        t=n.new("ShaderNodeTexNoise")
        t.inputs["Scale"].default_value=noise
        t.inputs["Detail"].default_value=3
        ramp=n.new("ShaderNodeValToRGB")
        ramp.color_ramp.elements[0].position=.18
        ramp.color_ramp.elements[0].color=(*(c*.64 for c in color),1)
        ramp.color_ramp.elements[1].position=.82
        ramp.color_ramp.elements[1].color=(*(min(c*1.24,1) for c in color),1)
        m.node_tree.links.new(t.outputs["Fac"],ramp.inputs[0])
        m.node_tree.links.new(ramp.outputs[0],p.inputs["Base Color"])
        if bump:
            b=n.new("ShaderNodeBump")
            b.inputs["Strength"].default_value=.26
            b.inputs["Distance"].default_value=bump
            m.node_tree.links.new(t.outputs["Fac"],b.inputs["Height"])
            m.node_tree.links.new(b.outputs[0],p.inputs["Normal"])
    return m

def emission(name, col, strength):
    m=material(name,col,.3,.25)
    p=m.node_tree.nodes.get("Principled BSDF")
    p.inputs["Emission Color"].default_value=(*col,1)
    p.inputs["Emission Strength"].default_value=strength
    return m

coat=material("Duster | worn tobacco waxed canvas",(.24,.105,.047),.79,0,95,.016)
edgeleather=material("Oiled leather edges",(.072,.031,.019),.48,0,65,.008)
leather=material("Holster and gunbelt saddle leather",(.20,.068,.024),.47,0,85,.012)
suit=material("Vac suit | graphite woven fabric",(.046,.066,.073),.89,0,145,.012)
suitpanel=material("Reinforced petrol-blue pressure panels",(.085,.15,.16),.62,0,70,.009)
rubber=material("Flexible seals and glove polymer",(.023,.032,.035),.64)
metal=material("Scuffed gunmetal",(.19,.25,.26),.3,.8,100,.006)
silver=material("Brushed nickel",(.48,.56,.56),.28,.82,130,.003)
brass=material("Oxidised brass fittings",(.44,.26,.094),.36,.72,70,.005)
ivory=material("Aged cream enamel",(.76,.65,.43),.47,.18,60,.003)
skin=material("Sun-weathered skin",(.43,.225,.13),.58,0,120,.003)
p=skin.node_tree.nodes.get("Principled BSDF")
p.inputs["Subsurface Weight"].default_value=.065
lip=material("Lips",(.265,.104,.068),.6)
hair=material("Dark brown hair and stubble",(.068,.043,.032),.93)
eye=material("Warm eye whites",(.61,.60,.49),.31)
iris=material("Hazel eyes",(.14,.19,.13),.29)
black=material("Pupil and recesses",(.006,.012,.015),.43)
cyan=emission("Ion charge | cold cyan",(.12,.72,.79),2.3)
amber=emission("Amber instruments",(.95,.31,.055),2.5)
wood=[material("Weathered plank %02d"%i,(.19+i*.012,.145+i*.009,.099+i*.008),.9,0,9,.037) for i in range(6)]
paint=material("Faded colony teal",(.075,.16,.16),.78,.12,16,.018)
rust=material("Rust scale",(.25,.095,.04),.91,.22,35,.026)
dust=material("Ochre regolith",(.34,.235,.135),.98,0,38,.055)
dustdark=material("Compressed dusty footprints",(.205,.143,.083),1,0,50,.013)
window=material("Dusty amber window glass",(.135,.17,.15),.37,.32,22,.008)
dark=material("Interior darkness",(.026,.025,.024),.94)
signwood=material("Charred sign timber",(.065,.083,.075),.85,0,13,.035)
glass=material("Optically clear helmet visor",(.65,.86,.9),.065,.0)
p=glass.node_tree.nodes.get("Principled BSDF")
p.inputs["Transmission Weight"].default_value=.96
p.inputs["IOR"].default_value=1.08
p.inputs["Alpha"].default_value=.19
glass.diffuse_color=(.65,.86,.9,.19)
try: glass.surface_render_method='DITHERED'
except: pass

# ---------- Geometry utilities ----------
def finish(obj, mat=None, smooth=False):
    if mat: obj.data.materials.append(mat)
    if smooth and obj.type=='MESH':
        for f in obj.data.polygons: f.use_smooth=True
    return obj

def bevel(obj, amount=.03, segments=3):
    b=obj.modifiers.new("Soft manufactured edges","BEVEL")
    b.width=amount; b.segments=segments
    return obj

def cube(name, loc, scale, mat, bevelwidth=.02):
    bpy.ops.mesh.primitive_cube_add(size=1,location=loc)
    o=bpy.context.object; o.name=name
    o.dimensions=scale
    bpy.ops.object.transform_apply(location=False,rotation=False,scale=True)
    finish(o,mat)
    if bevelwidth: bevel(o,bevelwidth)
    return o

def uv(name,loc,scale,mat,segments=32,rings=20):
    bpy.ops.mesh.primitive_uv_sphere_add(segments=segments,ring_count=rings,location=loc)
    o=bpy.context.object; o.name=name; o.scale=scale
    finish(o,mat,True)
    return o

def mesh(name, verts, faces, mat, smooth=False):
    me=bpy.data.meshes.new(name); me.from_pydata(verts,[],faces); me.update()
    ob=bpy.data.objects.new(name,me); bpy.context.collection.objects.link(ob)
    return finish(ob,mat,smooth)

def rod(name,a,b,r,mat,r2=None,verts=16):
    a,b=Vector(a),Vector(b); d=b-a
    bpy.ops.mesh.primitive_cone_add(vertices=verts,radius1=r,radius2=r if r2 is None else r2,depth=d.length,location=(a+b)*.5)
    ob=bpy.context.object; ob.name=name
    ob.rotation_mode='QUATERNION'; ob.rotation_quaternion=d.to_track_quat('Z','Y')
    return finish(ob,mat,True)

def tube(name, points, radius, mat, cyclic=False):
    cu=bpy.data.curves.new(name,'CURVE'); cu.dimensions='3D'
    cu.resolution_u=12; cu.bevel_depth=radius; cu.bevel_resolution=3
    sp=cu.splines.new('BEZIER'); sp.bezier_points.add(len(points)-1)
    for bp,co in zip(sp.bezier_points,points):
        bp.co=co; bp.handle_left_type='AUTO'; bp.handle_right_type='AUTO'
    sp.use_cyclic_u=cyclic
    ob=bpy.data.objects.new(name,cu); bpy.context.collection.objects.link(ob); finish(ob,mat)
    bpy.context.view_layer.objects.active=ob; ob.select_set(True)
    bpy.ops.object.convert(target='MESH'); ob.select_set(False)
    return ob

def torus(name,loc,major,minor,mat,rot=(0,0,0),scale=None):
    bpy.ops.mesh.primitive_torus_add(major_radius=major,minor_radius=minor,major_segments=48,minor_segments=10,location=loc,rotation=rot)
    o=bpy.context.object; o.name=name
    if scale: o.scale=scale
    return finish(o,mat,True)

def bolt(name,loc,r=.026,mat=silver,axis=(0,-1,0)):
    a=Vector(loc); d=Vector(axis).normalized()
    return rod(name,a-d*.009,a+d*.009,r,mat,verts=6)

def text(name,body,loc,size,mat,align='CENTER'):
    cu=bpy.data.curves.new(name,'FONT'); cu.body=body; cu.size=size; cu.align_x=align
    cu.align_y='CENTER'; cu.extrude=.007; cu.bevel_depth=.002
    ob=bpy.data.objects.new(name,cu); bpy.context.collection.objects.link(ob)
    ob.location=loc; ob.rotation_euler=(math.pi/2,0,0)
    finish(ob,mat)
    bpy.context.view_layer.objects.active=ob; ob.select_set(True)
    bpy.ops.object.convert(target='MESH'); ob.select_set(False)
    return ob

def profile(name, rings, mat, n=48, folds=0):
    # Rings: centre x,y,z, half-width, half-depth
    vs=[]; fs=[]
    for j,(x,y,z,rx,ry) in enumerate(rings):
        for k in range(n):
            a=2*math.pi*k/n
            f=1+folds*(math.sin(a*7+j*.65)+.4*math.sin(a*13-j*.7))
            vs.append((x+rx*math.cos(a)*f,y+ry*math.sin(a)*f,z))
    for j in range(len(rings)-1):
        for k in range(n):
            kn=(k+1)%n
            fs.append((j*n+k,j*n+kn,(j+1)*n+kn,(j+1)*n+k))
    fs.append(tuple(reversed(range(n))))
    fs.append(tuple((len(rings)-1)*n+k for k in range(n)))
    return mesh(name,vs,fs,mat,True)

def cloth_limb(name, points, radii, mat):
    vs=[]; fs=[]; n=32
    for j,(co,rs) in enumerate(zip(points,radii)):
        co=Vector(co)
        if j==0: direction=Vector(points[1])-co
        elif j==len(points)-1: direction=co-Vector(points[j-1])
        else: direction=Vector(points[j+1])-Vector(points[j-1])
        q=direction.to_track_quat('Z','Y')
        for k in range(n):
            a=k*2*math.pi/n
            rr=1+.047*math.sin(a*6+j*1.8)+.024*math.cos(a*11-j)
            v=q@Vector((rs[0]*math.cos(a)*rr,rs[1]*math.sin(a)*rr,0))+co
            vs.append(tuple(v))
    for j in range(len(points)-1):
        for k in range(n):
            fs.append((j*n+k,j*n+(k+1)%n,(j+1)*n+(k+1)%n,(j+1)*n+k))
    fs += [tuple(reversed(range(n))),tuple((len(points)-1)*n+k for k in range(n))]
    return mesh(name,vs,fs,mat,True)

# ---------- Dusty site, finite modelled ground ----------
N=100
vs=[(.3,.4,-.085)]
for i in range(N):
    a=2*math.pi*i/N
    r=1+random.uniform(-.045,.045)
    vs.append((.3+5.0*r*math.cos(a),.4+3.9*r*math.sin(a),-.085+random.uniform(-.012,.012)))
for i in range(N):
    x,y,z=vs[i+1]; vs.append((x,y,-.27))
fs=[]
for i in range(N):
    j=(i+1)%N
    fs.append((0,i+1,j+1))
    fs.append((i+1,N+i+1,N+j+1,j+1))
ground=mesh("Small cut of frontier regolith",vs,fs,dust)
bevel(ground,.055,3)

# Porch floor
cube("Porch welded foundation",(.75,-.24,.012),(6.25,1.85,.18),metal,.04)
for i in range(27):
    x=-2.25+i*.227
    cube("Sun-bleached porch plank", (x,-.25,.14),(.215,1.84,.105),random.choice(wood),.008)
    for y in [-1.05,.51]:
        bolt("Deck screw",(x,y,.198),.015,metal,(0,0,1))
cube("Porch front battered fascia",(.72,-1.20,.035),(6.24,.10,.24),wood[1],.016)
cube("Low entrance step",(1.12,-1.48,-.035),(2.35,.52,.15),wood[3],.024)
cube("Step nosing",(1.12,-1.73,.025),(2.4,.045,.045),metal,.006)

# Facade wall boards, doorway deliberately left open
for za in range(17):
    z=.32+za*.225
    for x0,x1 in [(-1.85,-.42),(1.43,3.75)]:
        # Right window opening
        if 1.83<z<2.94 and x0>0:
            spans=[(1.43,2.00),(3.28,3.75)]
        else: spans=[(x0,x1)]
        for aa,bb in spans:
            cube("Horizontal salvaged facade board",((aa+bb)/2,.61,z),(bb-aa,.20,.212),random.choice(wood),.012)
    if z>3.13:
        cube("Lintel wall boards",(.505,.61,z),(1.85,.20,.212),random.choice(wood),.012)
# Return wall provides a real exterior corner
for zi in range(17):
    z=.32+zi*.225
    cube("Return wall plank",(-1.85,1.79,z),(.20,2.55,.212),random.choice(wood),.01)
cube("Interior rear darkness",(1.57,2.5,1.83),(4.2,.12,3.3),dark,.0)
cube("Interior floor",(.70,1.53,.13),(6.0,2.0,.10),wood[1],.005)

# Corner reinforcements
for x in [-1.86,3.77]:
    cube("Riveted vertical angle iron",(x,.455,2.1),(.16,.08,3.96),paint,.008)
    for z in [.32,.65,1.15,1.65,2.15,2.65,3.15,3.7,3.98]:
        bolt("Facade structural rivet",(x,.399,z),.024,brass)
cube("Corner return angle",(-1.972,1.68,2.1),(.065,2.40,.105),paint,.008)
for y in [.7,1.15,1.65,2.15,2.65]:
    bolt("Return rivet",(-2.013,y,2.1),.024,brass,(-1,0,0))
for z in [.42,3.10]:
    cube("Facade horizontal iron strap",(.95,.46,z),(5.65,.07,.075),metal,.006)
    for x in [-1.65,-.62,1.64,2.08,3.55]:
        bolt("Strap bolt",(x,.413,z),.02,brass)

# Entrance portal
for x in [-.46,1.47]:
    cube("Airlock door jamb",(x,.43,1.74),(.16,.27,3.08),metal,.034)
    cube("Jamb inset hazard enamel",(x,.278,1.78),(.066,.017,2.49),ivory,.004)
    for z in [.43,1.03,2.62,3.12]: bolt("Portal anchoring",(x,.267,z),.025)
cube("Airlock lintel",(.505,.42,3.27),(2.1,.29,.20),metal,.035)
cube("Pressure threshold",(.505,.38,.24),(1.85,.38,.09),silver,.014)
text("Entrance stencil","CYCLE  /  EQUALIZE",(.50,.247,3.275),.10,ivory)

# Swinging, split airlock saloon doors, actual rotated leaves
def door_leaf(name,pivotx,side,angle):
    parent=bpy.data.objects.new(name,None); bpy.context.collection.objects.link(parent)
    parent.location=(pivotx,.42,0)
    # local coords toward opening
    width=.87
    top=[(0,2.44),(.16,2.44),(.30,2.58),(.55,2.62),(.73,2.56),(.87,2.49)]
    outline=[(0,.65),(.87,.65)]+list(reversed(top))
    vv=[]
    for yy in [-.07,.07]:
        vv += [(side*x,yy,z) for x,z in outline]
    m=len(outline)
    ff=[tuple(reversed(range(m))),tuple(range(m,m*2))]
    ff += [(i,(i+1)%m,(i+1)%m+m,i+m) for i in range(m)]
    ob=mesh(name+" armoured leaf",vv,ff,paint); bevel(ob,.025)
    ob.parent=parent
    def part(obj):
        obj.parent=parent
    for j in range(5):
        x=side*(.10+j*.163)
        part(cube("Door lower oak slat",(x,-.092,1.18),(.15,.07,.95),wood[(j+1)%6],.012))
    part(cube("Door vision port surround",(side*.445,-.094,2.01),(.68,.055,.65),metal,.065))
    part(cube("Door smoky vision port",(side*.445,-.129,2.025),(.55,.025,.48),window,.047))
    for xx in [.24,.45,.66]:
        part(rod("Vision port security ribs",(side*xx,-.158,1.82),(side*xx,-.158,2.23),.015,brass))
    for z in [.75,1.64]:
        part(cube("Door reinforcement band",(side*.44,-.148,z),(.80,.06,.09),metal,.007))
        for xx in [.10,.77]:
            part(bolt("Door band rivet",(side*xx,-.19,z),.022,brass))
    part(cube("Door push pad",(side*.74,-.16,1.64),(.09,.06,.35),ivory,.016))
    for z in [.90,2.22]:
        part(rod("Airlock hinge",(0,0,z-.15),(0,0,z+.15),.07,silver))
    parent.rotation_euler[2]=angle
door_leaf("Left swinging pressure door",-.40,1,math.radians(-13))
door_leaf("Right swinging pressure door",1.40,-1,math.radians(17))

# Dusty window
cube("Window inset shadow",(2.64,.57,2.39),(1.32,.11,1.26),dark,.02)
cube("Dust filmed window",(2.64,.478,2.39),(1.17,.045,1.08),window,.012)
for x in [1.99,3.29]:
    cube("Window jamb",(x,.387,2.39),(.12,.17,1.30),paint,.009)
for z in [1.76,3.02]:
    cube("Window sill",(2.64,.37,z),(1.43,.23,.13),wood[2],.009)
cube("Window mullion",(2.64,.391,2.39),(.06,.10,1.15),brass,.008)
cube("Window transom",(2.64,.385,2.38),(1.22,.10,.055),brass,.008)
for i in range(23):
    x=random.uniform(2.10,3.17); z=random.uniform(1.91,2.86)
    rod("Window dust streak",(x,.448,z),(x+random.uniform(-.015,.015),.447,z+random.uniform(.025,.17)),random.uniform(.0015,.004),ivory,verts=6)
text("Window lettering","O 2  •  RYE",(2.64,.346,2.69),.125,ivory)
text("Window lower lettering","NO CREDIT",(2.64,.345,2.09),.085,ivory)

# Porch structure and roof
for x in [-2.23,3.97]:
    cube("Porch support timber",(x,-1.01,2.20),(.17,.19,4.05),wood[2],.014)
    for z in [.38,3.68]:
        cube("Support post iron shoe",(x,-1.01,z),(.21,.23,.29),metal,.014)
        for dz in [-.075,.075]: bolt("Post shoe bolt",(x,-1.139,z+dz),.027,brass)
    rod("Angled porch brace",(x,-1.0,3.46),(x+(.58 if x<0 else -.58),-1.0,4.10),.07,wood[1],verts=8)
cube("Porch header",(.87,-1.01,4.13),(6.46,.24,.23),wood[1],.022)
cube("Roof rear ledger",(.87,.7,4.18),(6.45,.18,.18),metal,.016)
for x in [-2.24,-1.3,-.3,.7,1.7,2.7,3.97]:
    cube("Porch roof joist",(x,-.13,4.22),(.10,2.15,.15),wood[2],.008)
for i in range(19):
    x=-2.3+i*.345
    roof=cube("Salvaged corrugated roof sheet",(x,-.10,4.35),(.355,2.30,.055),paint if i%3 else rust,.012)
    roof.rotation_euler[0]=math.radians(3)
    rod("Raised roof seam",(x,-1.26,4.29),(x,1.04,4.41),.022,metal,verts=8)
cube("Roof front lip",(.83,-1.28,4.27),(6.6,.07,.12),rust,.009)

# Large raised sign, mounted below porch roof
cube("SALOON sign backing",(.68,.21,3.72),(3.82,.17,.78),signwood,.035)
for z in [3.345,4.095]:
    cube("Sign iron trim",(.68,.096,z),(3.91,.07,.045),rust,.006)
for x in [-1.22,2.58]:
    cube("Sign end trim",(x,.096,3.72),(.05,.07,.78),metal,.007)
    for z in [3.42,4.02]: bolt("Sign bolt",(x,.046,z),.030,brass)
text("The SALOON sign","SALOON",(.68,.094,3.79),.55,ivory)
text("Sign subtitle","LAST ORBIT  /  EST. 2186",(.68,.081,3.44),.097,brass)
# Raised scars on timber, chipped edges
for i in range(42):
    x=random.uniform(-1.13,2.45); z=random.uniform(3.37,4.065)
    length=random.uniform(.022,.16)
    rod("Sign weather scoring",(x,.112,z),(x+length,.111,z+.004),.003,wood[1],verts=5)

# Utility lines and visible junction hardware
tube("External conduit",[(3.48,.37,.5),(3.48,.37,1.27),(3.57,.37,1.42),(3.57,.37,3.35),(3.18,.37,3.48),(2.80,.37,3.48)],.035,metal)
cube("Utility junction box",(3.47,.275,1.40),(.36,.23,.49),paint,.03)
cube("Junction lid",(3.47,.145,1.4),(.30,.035,.42),metal,.014)
for x in [3.36,3.58]:
    for z in [1.25,1.55]: bolt("Junction lid screw",(x,.117,z),.014)
text("Utility warning","48 V",(3.47,.118,1.43),.095,ivory)
for z in [.67,2.0,2.8,3.22]:
    cube("Conduit clamp",(3.57,.385,z),(.13,.07,.045),brass,.006)
tube("Return wall exposed hose",[(-2.00,2.52,.37),(-2.02,2.52,2.85),(-2.02,2.22,3.05),(-2.02,.90,3.05),(-2.02,.71,3.39)],.042,rubber)
for y in [1.0,1.65,2.32]:
    cube("Return conduit strap",(-2.04,y,3.05),(.06,.075,.17),metal,.007)

# Entrance amber fixtures
for x in [-.66,1.69]:
    cube("Porch sconce back",(x,.35,2.85),(.19,.11,.28),metal,.022)
    uv("Amber pressure lamp",(x,.254,2.85),(.057,.047,.096),amber,20,12)
    for dx in [-.052,.052]:
        rod("Sconce guard",(x+dx,.205,2.74),(x+dx,.205,2.96),.009,brass)

# ---------- Cowboy: anatomical lower body ----------
# Weight-bearing leg, standing sole on porch
cloth_limb("Weight bearing vac-suit leg",
    [(-1.43,.13,1.78),(-1.47,.10,1.60),(-1.53,.02,1.30),(-1.58,-.12,1.02),(-1.57,-.16,.93),(-1.55,-.22,.69),(-1.54,-.27,.45)],
    [(.205,.19),(.20,.185),(.165,.16),(.155,.142),(.16,.14),(.13,.12),(.12,.105)],suit)
# Bent, wall-braced leg
cloth_limb("Bent relaxed vac-suit leg",
    [(-1.05,.16,1.77),(-.94,.04,1.59),(-.72,-.17,1.29),(-.61,-.30,1.13),(-.65,-.26,1.02),(-.80,.02,.77),(-.87,.20,.57)],
    [(.20,.18),(.195,.175),(.175,.165),(.168,.15),(.152,.138),(.13,.115),(.115,.105)],suit)
# Hip & fitted torso
profile("Anatomical hips under suit",[
    (-1.26,.15,1.60,.29,.205),(-1.25,.13,1.76,.34,.22),(-1.23,.13,1.94,.285,.195)],suitpanel,48,.018)
profile("Fitted pressure-suit torso",[
    (-1.23,.13,1.83,.285,.195),(-1.25,.125,2.02,.285,.195),(-1.27,.115,2.24,.34,.225),
    (-1.29,.115,2.46,.39,.235),(-1.30,.12,2.60,.405,.21),(-1.30,.12,2.68,.315,.175),
    (-1.29,.10,2.77,.16,.135)],suit,64,.012)
# Kneepads and stitched thigh panels
for name,loc,sc in [
    ("Standing articulated knee",(-1.575,-.26,1.025),(.137,.063,.155)),
    ("Bent articulated knee",(-.60,-.434,1.145),(.15,.062,.15))]:
    uv(name,loc,sc,suitpanel)
    cube(name+" central plate",(loc[0],loc[1]-.055,loc[2]),(.17,.032,.17),metal,.04)
for z in [.72,.79,.86]:
    tube("Standing shin cloth compression",[(-1.66,-.25,z),(-1.55,-.34,z+.027),(-1.43,-.25,z+.012)],.012,suitpanel)
for z in [1.37,1.44]:
    tube("Bent thigh tension fold",[(-.93,-.16,z),(-.85,-.295,z-.026),(-.75,-.29,z-.025)],.013,suitpanel)

# Boots, separate layered magnetic soles and padded uppers
def boot(name,x,y,bottom,angle=0):
    objs=[]
    objs.append(cube(name+" magnetic outsole",(x,y-.085,bottom+.054),(.32,.56,.108),rubber,.052))
    objs.append(cube(name+" steel sole rim",(x,y-.085,bottom+.103),(.33,.555,.034),metal,.021))
    objs.append(uv(name+" toe",(x,y-.19,bottom+.20),(.163,.255,.115),edgeleather))
    objs.append(uv(name+" armoured toe cap",(x,y-.30,bottom+.206),(.165,.148,.099),metal))
    objs.append(profile(name+" sealed boot shaft",[
        (x,y+.035,bottom+.13,.15,.145),(x,y+.045,bottom+.29,.145,.13),
        (x,y+.05,bottom+.44,.13,.117)],edgeleather,40,.025))
    objs.append(torus(name+" upper pressure seal",(x,y+.05,bottom+.438),.127,.026,rubber,scale=(1,.92,1)))
    for zz in [.265,.36]:
        objs.append(cube(name+" instep strap",(x,y-.094,bottom+zz),(.31,.049,.055),rubber,.015))
        objs.append(cube(name+" buckle",(x+.105,y-.129,bottom+zz),(.072,.022,.067),brass,.009))
    for dx in [-.165,.165]:
        objs.append(cube(name+" magnet housing",(x+dx,y-.02,bottom+.105),(.039,.25,.106),metal,.012))
        for yy in [-.10,.055]:
            objs.append(cube(name+" blue magnet marker",(x+dx*1.03,y+yy,bottom+.119),(.01,.062,.019),cyan,.004))
    for yy in [-.27,-.13,.01,.15]:
        objs.append(cube(name+" sole tread",(x,y+yy,bottom+.015),(.29,.041,.028),black,.004))
    return objs
boot("Planted left magnetic boot",-1.54,-.34,.197)
boot("Right boot braced against siding",-.87,.235,.40)
# Heel contact bracket on boot, touches facade
cube("Braced heel wall contact",(-.87,.474,.59),(.245,.052,.19),rubber,.024)

# ---------- Long coat, open and physically folded ----------
# Sweep an open shell around back, from right front to left front
rings=[
    (-1.29,.12,2.68,.42,.247),
    (-1.28,.12,2.56,.445,.26),
    (-1.26,.12,2.33,.39,.262),
    (-1.24,.12,2.02,.322,.237),
    (-1.23,.13,1.82,.335,.245),
    (-1.24,.14,1.59,.39,.25),
    (-1.28,.12,1.28,.46,.29),
    (-1.32,.08,.97,.49,.335),
    (-1.34,.03,.61,.55,.37)]
vs=[]; fs=[]; n=70
for j,(x,y,z,rx,ry) in enumerate(rings):
    for k in range(n):
        a=-.91+(math.pi+1.82)*k/(n-1)
        strength=.012+.019*j/8
        fold=math.sin(a*11+j*.7)*strength+math.sin(a*17-j*.3)*strength*.45
        xx=x+(rx+fold)*math.cos(a)
        yy=y+(ry+fold)*math.sin(a)
        yy=min(yy,.47)
        zz=z+(0 if j<5 else .035*math.cos(a*5+j))
        vs.append((xx,yy,zz))
for j in range(len(rings)-1):
    for k in range(n-1):
        # rear central vent, bottom three rings
        if j>=6 and 33<=k<=35: continue
        fs.append((j*n+k,j*n+k+1,(j+1)*n+k+1,(j+1)*n+k))
ob=mesh("Open long duster | folded skirt with rear riding vent",vs,fs,coat,True)
s=ob.modifiers.new("Real canvas thickness","SOLIDIFY"); s.thickness=.024
b=ob.modifiers.new("Soft canvas folds","SUBSURF"); b.levels=2; b.render_levels=2
for k in [0,n-1]:
    tube("Duster rolled front edge",[vs[j*n+k] for j in range(len(rings))],.014,edgeleather)
# Front lapels, broad shaped layered mesh
for side in [-1,1]:
    c=-1.28
    vv=[
        (c+side*.165,-.045,2.745),(c+side*.32,-.085,2.645),
        (c+side*.295,-.185,2.43),(c+side*.19,-.224,2.12),
        (c+side*.14,-.235,2.40),(c+side*.235,-.219,2.53)]
    o=mesh("Folded coat lapel",vv,[(0,1,2,3,4,5)],coat,False)
    s=o.modifiers.new("Lapel thickness","SOLIDIFY"); s.thickness=.035
    bevel(o,.019,3)
    tube("Lapel welt stitching",[vv[1],vv[2],vv[3]],.005,ivory)
    bolt("Lapel snap",(c+side*.265,-.226,2.48),.025,brass)
# Shoulder cap seams
tube("Left shoulder tailoring",[(-1.69,.22,2.60),(-1.70,.04,2.66),(-1.63,-.10,2.60)],.012,edgeleather)
tube("Right shoulder tailoring",[(-.91,.23,2.60),(-.87,.02,2.65),(-.96,-.10,2.59)],.012,edgeleather)
# Sleeves
cloth_limb("Left duster sleeve",
 [(-1.66,.11,2.59),(-1.78,.08,2.49),(-1.86,.005,2.30),(-1.92,-.12,2.17),(-1.90,-.20,2.13),(-1.77,-.35,2.095)],
 [(.17,.165),(.173,.16),(.147,.14),(.151,.14),(.138,.125),(.11,.10)],coat)
cloth_limb("Gun arm duster sleeve",
 [(-.94,.10,2.59),(-.80,.065,2.51),(-.67,-.03,2.34),(-.57,-.14,2.19),(-.57,-.19,2.12),(-.60,-.285,2.035)],
 [(.17,.158),(.17,.16),(.142,.139),(.144,.134),(.119,.109),(.095,.088)],coat)
for side in [0,1]:
    if side==0:
        a=(-1.81,-.30,2.11); b=(-1.73,-.39,2.09)
    else:
        a=(-.59,-.258,2.075); b=(-.606,-.313,2.006)
    rod("Sealed glove cuff",a,b,.105,rubber,verts=32)
    mid=(Vector(a)+Vector(b))*.5
    rod("Cuff nickel locking rim",mid- (Vector(b)-Vector(a))*.13,mid+(Vector(b)-Vector(a))*.13,.108,silver,verts=32)
# Sleeve compression wrinkles
for z,dx in [(2.20,0),(2.25,-.025),(2.29,-.055)]:
    tube("Gun elbow fabric wrinkle",[(-.71+dx,-.14,z+.04),(-.59+dx,-.267,z),(-.49+dx,-.16,z-.014)],.013,edgeleather)
for i in range(3):
    tube("Left elbow fabric wrinkle",[(-2.005,-.13,2.18+i*.036),(-1.91,-.26,2.14+i*.035),(-1.81,-.20,2.14+i*.035)],.011,edgeleather)
# Coat hip pocket on visible front side
pocket=mesh("Duster angled pocket flap",[(-1.62,-.071,1.87),(-1.78,-.015,1.74),(-1.73,-.065,1.65),(-1.55,-.137,1.80)],[(0,1,2,3)],edgeleather)
s=pocket.modifiers.new("Pocket flap thickness","SOLIDIFY"); s.thickness=.018
tube("Pocket stitched border",[(-1.62,-.09,1.85),(-1.75,-.04,1.74),(-1.71,-.085,1.69)],.004,ivory)

# ---------- Life-support equipment ----------
# Pack is shallow, nested against the wall, visible around left shoulder
cube("Life support backpack casing",(-1.39,.356,2.36),(.67,.265,.66),metal,.095)
cube("Backpack far panel",(-1.39,.487,2.37),(.53,.04,.50),suitpanel,.055)
for x in [-1.77,-1.02]:
    rod("Pack oxygen cylinder",(x,.345,2.06),(x,.345,2.62),.103,silver,verts=32)
    uv("Oxygen cylinder end",(x,.345,2.63),(.101,.10,.081),silver)
    for z in [2.19,2.48]:
        torus("Bottle clamp",(x,.345,z),.103,.019,edgeleather)
    rod("Bottle valve",(x,.345,2.69),(x,.345,2.75),.036,brass)
cube("Life support chest plate",(-1.275,-.173,2.405),(.435,.144,.36),metal,.047)
cube("Chest plate inset",(-1.275,-.256,2.405),(.367,.038,.284),suitpanel,.027)
cube("Oxygen status screen",(-1.338,-.281,2.453),(.166,.018,.085),black,.01)
text("Chest oxygen display","O2  94",(-1.338,-.295,2.454),.039,cyan)
for i in range(3):
    cube("Chest gauge bar",(-1.405+i*.055,-.287,2.374),(.032,.013,.027),cyan if i<2 else amber,.005)
for x in [-1.445,-1.105]:
    for z in [2.28,2.53]: bolt("Chest plate screw",(x,-.282,z),.012,brass)
for z in [2.382,2.425,2.468]:
    cube("Chest console vent",(-1.164,-.282,z),(.077,.02,.01),black,.002)
# Shoulder harness webbing
for side in [-1,1]:
    x=-1.285+side*.218
    tube("Harness shoulder webbing",[(x,-.193,2.50),(x+side*.055,-.12,2.64),(x+side*.04,.08,2.72),(x,.31,2.55)],.034,edgeleather)
    cube("Harness buckle",(x,-.207,2.61),(.086,.046,.10),brass,.014)
# Two ribbed hoses with clamps
hosepts=[(-1.49,-.26,2.30),(-1.69,-.32,2.22),(-1.89,-.22,2.35),(-1.91,.04,2.57),(-1.79,.32,2.69)]
tube("Oxygen hose outer",hosepts,.045,rubber)
# Rings along manually interpolated hose, genuine corrugation
for a,b in zip(hosepts[:-1],hosepts[1:]):
    aa,bb=Vector(a),Vector(b); d=bb-aa
    q=d.to_track_quat('Z','Y').to_euler()
    num=max(2,int(d.length/.032))
    for i in range(num):
        torus("Oxygen hose rib",aa+d*(i+.5)/num,.046,.008,metal,rot=q)
tube("Auxiliary suit umbilical",[(-1.08,-.23,2.29),(-.97,-.25,2.08),(-1.03,-.14,1.91),(-1.15,.05,1.88)],.022,rubber)
for x in [-1.49,-1.08]:
    rod("Console hose socket",(x,-.20,2.29),(x,-.28,2.29),.06,brass,verts=24)
# Neck and pressure collar
rod("Human neck",(-1.285,.055,2.66),(-1.285,.055,2.93),.113,skin,verts=40)
torus("Collar soft neck seal",(-1.285,.06,2.775),.185,.049,rubber,scale=(1,.88,1))
torus("Machined pressure collar lower",(-1.285,.06,2.80),.240,.039,metal,scale=(1,.91,1))
torus("Helmet bayonet locking ring",(-1.285,.06,2.849),.257,.025,silver,scale=(1,.91,1))
for x in [-1.47,-1.10]:
    cube("Collar latch",(x,-.135,2.833),(.062,.067,.06),brass,.008)

# ---------- Belt, ammunition and energy revolver ----------
# Elliptical belt, with vertical thickness and sloping gun side
bv=[]; bf=[]; steps=80
for j in range(steps):
    a=2*math.pi*j/steps
    x=-1.235+.357*math.cos(a); y=.12+.261*math.sin(a)
    z=1.886-.065*math.cos(a)
    for dz in [-.060,.060]: bv.append((x,y,z+dz))
for j in range(steps):
    k=(j+1)%steps
    bf.append((j*2,k*2,k*2+1,j*2+1))
ob=mesh("Continuous saddle gun belt",bv,bf,leather,True)
s=ob.modifiers.new("Belt leather thickness","SOLIDIFY"); s.thickness=.022
cube("Gunbelt buckle",(-1.26,-.156,1.88),(.175,.040,.139),brass,.022)
cube("Buckle inset",(-1.26,-.182,1.88),(.127,.018,.094),edgeleather,.012)
rod("Buckle tongue",(-1.31,-.199,1.88),(-1.22,-.199,1.88),.009,silver)
for i in range(5):
    x=-1.48-i*.052; y=-.127+i*.036
    rod("Spare energy cell",(x,y,1.83),(x,y,1.982),.022,metal,verts=16)
    rod("Cell charged cap",(x,y,1.951),(x,y,1.985),.023,cyan,verts=16)
    cube("Individual ammunition belt loop",(x,y-.022,1.879),(.043,.028,.073),leather,.008)
# Low slung holster outside the right hip
tube("Holster suspension strap",[(-.913,-.015,1.88),(-.77,-.07,1.79),(-.685,-.087,1.56)],.049,leather)
# back panel is modelled, front largely open
vv=[(-.83,-.10,1.81),(-.56,-.10,1.79),(-.48,-.10,1.45),(-.57,-.10,1.14),(-.74,-.10,1.13),(-.85,-.10,1.42)]
ob=mesh("Open quick-draw holster leather back",vv,[(0,1,2,3,4,5)],leather)
s=ob.modifiers.new("Thick saddle leather","SOLIDIFY"); s.thickness=.05
bevel(ob,.023)
tube("Holster welt edge",vv+[vv[0]],.012,edgeleather)
# Weapon: bore axis vertical, exposed cylinder and angled grip
cube("Revolver main frame",(-.662,-.224,1.52),(.19,.13,.24),metal,.023)
rod("Energy revolver barrel",(-.667,-.24,1.21),(-.667,-.24,1.48),.057,metal,verts=12)
rod("Revolver muzzle collar",(-.667,-.24,1.205),(-.667,-.24,1.27),.072,silver,verts=12)
rod("Dark muzzle bore",(-.667,-.24,1.198),(-.667,-.24,1.207),.036,black,verts=24)
cube("Revolver barrel underlug",(-.605,-.235,1.35),(.05,.082,.22),silver,.01)
rod("Six chamber revolver cylinder",(-.664,-.231,1.464),(-.664,-.231,1.602),.108,silver,verts=40)
for i in range(6):
    a=2*math.pi*i/6
    x=-.664+.094*math.cos(a); y=-.231+.094*math.sin(a)
    rod("Exposed ion chamber",(x,y,1.488),(x,y,1.58),.025,metal,verts=16)
    rod("Chamber charge slit",(x,y-.007,1.511),(x,y-.007,1.559),.012,cyan,verts=12)
# Pistol grip leans away from barrel, clear under hovering fingers
grip=cube("Revolver walnut-polymer grip",(-.556,-.228,1.716),(.115,.124,.205),edgeleather,.029)
grip.rotation_euler[1]=math.radians(-25)
g=cube("Revolver grip brass butt",(-.516,-.228,1.796),(.123,.129,.036),brass,.01)
g.rotation_euler[1]=math.radians(-25)
for z in [1.66,1.69,1.72,1.75]:
    rod("Grip checkering",(-.605,-.296,z),(-.525,-.296,z+.017),.005,leather,verts=6)
cube("Revolver top strap",(-.676,-.232,1.632),(.17,.145,.045),metal,.008)
cube("Revolver hammer",(-.639,-.20,1.683),(.048,.06,.071),silver,.009)
tube("Revolver trigger guard",[(-.574,-.24,1.61),(-.489,-.24,1.60),(-.466,-.24,1.505),(-.515,-.24,1.46),(-.568,-.24,1.49)],.016,brass,True)
tube("Curved revolver trigger",[(-.55,-.24,1.592),(-.523,-.24,1.555),(-.544,-.24,1.534)],.009,metal)
# Only two thin retaining bands leave the weapon readable
for z,ww in [(1.31,.21),(1.445,.26)]:
    tube("Open holster retaining band",[(-.79,-.11,z),(-.79,-.27,z-.006),(-.66,-.365,z-.007),(-.54,-.27,z),(-.54,-.105,z)],.027,leather)
    bolt("Holster strap rivet",(-.77,-.292,z),.017,brass)
tube("Holster thigh tie",[(-.74,-.092,1.26),(-.93,.02,1.25),(-.87,.185,1.27),(-.70,.08,1.32)],.017,edgeleather)

# ---------- Individually articulated sealed gloves ----------
def gun_hand():
    # Palm is slanted outward, fingers remain separate and open above the butt.
    uv("Gun hand palm",(-.590,-.345,1.966),(.088,.053,.105),rubber)
    uv("Gun hand dorsal armour",(-.588,-.389,1.991),(.071,.019,.061),suitpanel)
    # Nonuniform lengths, fan, flexion. Lowest tip is above grip with visible air gap.
    bases=[-.660,-.618,-.575,-.535]
    lengths=[.099,.132,.125,.093]
    for i,(x,L) in enumerate(zip(bases,lengths)):
        root=(x,-.359,1.918+(abs(i-1.5)*.006))
        j1=(x+(i-1.5)*.008,-.369,root[2]-L*.40)
        j2=(x+(i-1.5)*.015,-.349,root[2]-L*.74)
        tip=(x+(i-1.5)*.018,-.321,root[2]-L*.78)
        # the relaxed open hand is in front of, not on, the grip
        for k,(a,b) in enumerate(zip([root,j1,j2],[j1,j2,tip])):
            rod("Gun glove finger %d phalanx %d"%(i+1,k+1),a,b,.0215-k*.002,rubber,r2=.020-k*.002,verts=16)
            uv("Glove articulated finger joint",a,(.022,.022,.023),rubber,16,10)
        uv("Finger sealed tip",tip,(.017,.018,.019),suitpanel,16,10)
        uv("Individual dorsal knuckle",(x,-.390,1.93),(.019,.015,.023),ivory,16,10)
    # Abducted thumb, clearly separate from four fingers
    pts=[(-.669,-.341,1.995),(-.709,-.354,1.955),(-.725,-.333,1.914),(-.712,-.303,1.895)]
    for k in range(3):
        rod("Gun hand opposed thumb segment",pts[k],pts[k+1],.027-k*.004,rubber,r2=.024-k*.004)
        uv("Thumb knuckle",pts[k],(.027-k*.003,)*3,suitpanel,16,10)
    cube("Gun glove cuff status",(-.599,-.363,2.036),(.053,.019,.027),cyan,.005)
gun_hand()
# Off hand rests naturally across lower torso, not gripping a second weapon.
uv("Resting left glove palm",(-1.682,-.414,2.08),(.096,.049,.081),rubber)
uv("Resting hand padded back",(-1.688,-.455,2.095),(.073,.019,.049),suitpanel)
for i in range(4):
    x=-1.737+i*.041
    z=2.032+(.010 if i in [1,2] else .025)
    pts=[(x,-.424,z),(x+.025,-.433,z-.059),(x+.048,-.415,z-.101),(x+.06,-.390,z-.105)]
    for k in range(3):
        rod("Resting hand finger %d segment %d"%(i,k),pts[k],pts[k+1],.021-k*.002,rubber,r2=.020-k*.002)
        uv("Resting finger joint",pts[k],(.021,.022,.022),rubber,16,10)
    uv("Resting glove knuckle",(x,-.459,z+.018),(.018,.013,.019),ivory,16,10)
pts=[(-1.607,-.410,2.11),(-1.572,-.423,2.067),(-1.568,-.406,2.020)]
for a,b in zip(pts[:-1],pts[1:]):
    rod("Resting opposed thumb",a,b,.026,rubber,r2=.023)

# ---------- Face, head turned toward the unseen opponent ----------
HC=Vector((-1.285,.019,3.103))
turn=math.radians(19)
def hp(v):
    x,y,z=v
    return HC+Vector((math.cos(turn)*x-math.sin(turn)*y,math.sin(turn)*x+math.cos(turn)*y,z))
def huv(name,pos,sc,mat,seg=32,r=20):
    o=uv(name,hp(pos),sc,mat,seg,r); o.rotation_euler[2]=turn; return o
def htube(name,pts,r,mat):
    return tube(name,[hp(v) for v in pts],r,mat)

# Head mesh: tapered adult jaw, cranial volume and flattened facial plane
hr=[
    (-.257,.067,.075,-.003),(-.226,.119,.111,-.012),
    (-.182,.151,.132,-.003),(-.11,.173,.157,0),
    (-.025,.184,.167,.004),(.065,.183,.171,.008),
    (.145,.175,.168,.017),(.22,.151,.149,.027),(.262,.098,.106,.03),(.278,.025,.035,.03)]
vv=[]; ff=[]; nn=64
for z,rx,ry,cy in hr:
    for k in range(nn):
        a=2*math.pi*k/nn
        yy=cy+ry*math.sin(a)
        vv.append(tuple(hp((rx*math.cos(a),yy,z))))
for j in range(len(hr)-1):
    for k in range(nn): ff.append((j*nn+k,j*nn+(k+1)%nn,(j+1)*nn+(k+1)%nn,(j+1)*nn+k))
ff += [tuple(reversed(range(nn))),tuple((len(hr)-1)*nn+k for k in range(nn))]
head=mesh("Adult face and cranium",vv,ff,skin,True)
sub=head.modifiers.new("Subtle facial smoothing","SUBSURF"); sub.levels=2; sub.render_levels=2
# Cheekbone, brow and chin modelling
for side in [-1,1]:
    huv("Cheek plane",(side*.112,-.128,-.065),(.067,.050,.067),skin)
    huv("Temple",(side*.153,-.039,.059),(.04,.09,.093),skin)
    huv("Ear",(side*.185,.008,-.024),(.034,.031,.071),skin)
    huv("Ear inner concha",(side*.201,-.015,-.024),(.017,.011,.043),lip,24,16)
    huv("Ear tragus",(side*.187,-.032,-.039),(.014,.016,.023),skin,20,12)
huv("Chin",(0,-.111,-.205),(.095,.037,.039),skin)
# Nose has a bridge, sidewalls, pointed bulb and separate nostrils
nv=[
(-.028,-.153,.08),(.028,-.153,.08),
(-.034,-.201,-.02),(.034,-.201,-.02),
(-.050,-.186,-.075),(.050,-.186,-.075),
(-.025,-.244,-.055),(.025,-.244,-.055),
(0,-.244,-.084),(0,-.176,-.105)]
nf=[(0,1,3,2),(2,3,7,6),(0,2,4),(1,5,3),(2,6,8,4),(3,5,8,7),(6,7,8),(4,8,9),(8,5,9)]
nose=mesh("Sculpted nose bridge and tip",[hp(v) for v in nv],nf,skin,True)
s=nose.modifiers.new("Nose smoothing","SUBSURF"); s.levels=2; s.render_levels=2
for side in [-1,1]:
    huv("Nasal wing",(side*.038,-.194,-.077),(.025,.027,.022),skin,24,16)
    huv("Nostril",(side*.030,-.213,-.087),(.012,.007,.006),hair,20,12)
# Small realistic visible eyes, individually sculpted eyelids
for side in [-1,1]:
    x=side*.079
    huv("Eye socket",(x,-.143,.050),(.069,.032,.044),lip)
    huv("Eyeball",(x,-.166,.052),(.055,.026,.025),eye)
    # gaze is slightly toward his own right
    huv("Hazel iris",(x+.008,-.190,.054),(.020,.006,.020),iris,28,16)
    huv("Pupil",(x+.008,-.195,.054),(.0085,.003,.010),black,24,16)
    huv("Eye catchlight",(x+.003,-.198,.062),(.003,.0015,.003),ivory,16,8)
    htube("Upper eyelid",[(x-.053,-.178,.051),(x-.029,-.186,.072),(x+.008,-.190,.075),(x+.051,-.174,.054)],.008,skin)
    htube("Lower eyelid",[(x-.052,-.177,.050),(x,-.187,.031),(x+.050,-.174,.051)],.006,skin)
    htube("Under eye crease",[(x-.043,-.162,.013),(x,-.174,.001),(x+.044,-.154,.012)],.0035,lip)
    huv("Brow ridge",(x,-.149,.099),(.065,.027,.024),skin)
    htube("Dark expressive eyebrow",[(x-.054,-.169,.097),(x-.021,-.181,.111),(x+.016,-.181,.112),(x+.054,-.166,.101)],.010,hair)
# Mouth is closed, not a pasted line
htube("Upper lip bow",[(-.069,-.149,-.139),(-.034,-.176,-.132),(-.012,-.183,-.138),(0,-.183,-.134),(.018,-.183,-.138),(.045,-.172,-.137),(.068,-.151,-.141)],.008,lip)
htube("Closed mouth line",[(-.067,-.155,-.145),(-.035,-.181,-.145),(0,-.185,-.149),(.036,-.178,-.147),(.065,-.153,-.145)],.0038,hair)
htube("Lower lip",[(-.050,-.162,-.155),(0,-.180,-.166),(.05,-.164,-.155)],.009,lip)
for side in [-1,1]:
    htube("Nasolabial sculpt crease",[(side*.053,-.160,-.093),(side*.068,-.157,-.117),(side*.073,-.147,-.139)],.003,lip)
    htube("Brow weather line",[(side*.025,-.162,.122),(side*.030,-.155,.143)],.003,lip)
# Hair lies around back of head and sideburns, face unobscured
for side in [-1,1]:
    huv("Sideburn",(side*.165,-.020,.04),(.020,.040,.102),hair)
    for i in range(8):
        z=-.032+i*.023
        htube("Sideburn hair strand",[(side*.171,-.052,z),(side*.177,-.026,z+.026),(side*.168,.0,z+.043)],.0025,hair)
huv("Back hair",(0,.094,.113),(.165,.083,.153),hair)
# Short individually modelled stubble on jaw, not a flat beard mask
for i in range(95):
    x=random.uniform(-.136,.136); z=random.uniform(-.211,-.088)
    if abs(x)<.079 and z>-.173: continue
    rx=.158 if z>-.18 else .125
    if abs(x)>rx*.97: continue
    y=-.125*math.sqrt(max(.05,1-(x/rx)**2))-.007
    a=hp((x,y,z))
    b=hp((x+random.uniform(-.003,.003),y-.003,z+.008))
    rod("Short jaw stubble",a,b,.0017,hair,verts=5)
# Fine forehead lines
for z in [.16,.181]:
    htube("Forehead expression line",[(-.072,-.141,z),(0,-.155,z+.004),(.074,-.138,z)],.0021,lip)

# Transparent pressure dome and discrete metal rim hardware
helmet=uv("Transparent pressure helmet dome",(-1.285,.04,3.13),(.385,.344,.407),glass,64,40)
try: helmet.visible_shadow=False
except: pass
# Lower front visor seating arc
pts=[]
for i in range(30):
    a=math.pi+math.pi*i/29
    pts.append((-1.285+.272*math.cos(a),.05+.245*math.sin(a),2.861+.018*math.sin(a)))
tube("Visor lower gasket",pts,.012,rubber)
for side in [-1,1]:
    loc=(-1.285+side*.356,.037,3.11)
    rod("Helmet side hinge",(loc[0]-.018,loc[1],loc[2]),(loc[0]+.018,loc[1],loc[2]),.071,metal,verts=32)
    bolt("Helmet pivot axle",(loc[0]+side*.028,loc[1],loc[2]),.027,brass,(side,0,0))
    cube("Helmet side running light",(loc[0],-.024,3.14),(.045,.020,.017),cyan,.005)
# Back helmet cage, does not cover facial features
tube("Rear pressure helmet reinforcing arch",[(-1.637,.075,3.12),(-1.60,.204,3.33),(-1.285,.302,3.48),(-.974,.204,3.33),(-.930,.075,3.12)],.017,metal)

# Cowboy hat integrated above the dome: curved broad brim and pinched crown.
hcx,hcy=-1.285,.04
vv=[]; ff=[]; n=96
for ring in range(3):
    f=[.49,.77,1.0][ring]
    for k in range(n):
        a=2*math.pi*k/n
        # rolled up sides and slightly drooping front
        z=3.410+(.072*math.cos(a)**2-.026*max(0,-math.sin(a)))*f*f
        vv.append((hcx+.655*f*math.cos(a),hcy+.497*f*math.sin(a),z))
for j in range(2):
    for k in range(n): ff.append((j*n+k,j*n+(k+1)%n,(j+1)*n+(k+1)%n,(j+1)*n+k))
brim=mesh("Wide sculpted cowboy hat brim",vv,ff,coat,True)
s=brim.modifiers.new("Thick hat brim","SOLIDIFY"); s.thickness=.025
bevel(brim,.009,2)
tube("Rolled leather brim binding",[vv[2*n+k] for k in range(n)],.014,edgeleather,True)
profile("Pinched western hat crown",[
(hcx,hcy,3.417,.328,.276),(hcx,hcy,3.463,.326,.272),
(hcx,hcy,3.60,.303,.246),(hcx,hcy,3.71,.268,.219),
(hcx,hcy,3.747,.216,.178)],coat,64,.01)
# Top crown crease, made with geometry rather than texture
uv("Hat crown top",(hcx,hcy,3.738),(.232,.189,.029),coat)
tube("Hat crown longitudinal crease",[(hcx,hcy-.15,3.755),(hcx,hcy,3.750),(hcx,hcy+.14,3.758)],.012,edgeleather)
profile("Hat integrated equipment band",[(hcx,hcy,3.456,.333,.279),(hcx,hcy,3.522,.322,.268)],edgeleather,64,0)
for a in [-2.5,-2.0,-1.5,-1.0,-.5,.15]:
    x=hcx+.332*math.cos(a); y=hcy+.277*math.sin(a)
    uv("Hat band brass stud",(x,y,3.485),(.012,.012,.012),brass,16,10)
cube("Hat band transceiver",(-.967,-.021,3.498),(.07,.11,.045),metal,.012)

# ---------- Set dressing: footprints, grit, restrained scrap ----------
# Genuine individual tread marks leading to the porch
for i in range(7):
    y=-1.88-i*.25
    x=-1.23+(.14 if i%2 else -.14)+i*.052
    angle=math.radians(-12 if i%2 else 7)
    # footprint sole silhouette with disconnected lugs
    for k in range(5):
        yy=(k-2)*.038
        for sx in [-1,1]:
            xx=sx*.034
            wx=x+xx*math.cos(angle)-yy*math.sin(angle)
            wy=y+xx*math.sin(angle)+yy*math.cos(angle)
            ob=cube("Dust footprint tread",(wx,wy,-.070),(.052,.023,.008),dustdark,.006)
            ob.rotation_euler[2]=angle
    ob=uv("Dust footprint heel",(x+.025*math.sin(angle),y-.12,-.073),(.063,.045,.004),dustdark,20,12)

for i in range(180):
    x=random.uniform(-3.8,4.4); y=random.uniform(-3.05,2.6)
    if -2.4<x<4.0 and -1.25<y<2.7: continue
    r=random.uniform(.008,.042)
    bpy.ops.mesh.primitive_ico_sphere_add(subdivisions=1,radius=1,location=(x,y,-.052))
    o=bpy.context.object; o.name="Scattered regolith grit"; o.scale=(r,r*.7,r*.65)
    finish(o,random.choice([dust,wood[2],rust]))
# Scrap clustered on right, secondary to cowboy
o=cube("Discarded corrugated patch",(3.38,-1.99,-.012),(.49,.32,.025),rust,.005)
o.rotation_euler=(.06,.11,.41)
for i in range(5):
    rod("Scrap raised rib",(3.18+i*.085,-2.12,.014),(3.28+i*.085,-1.88,.03),.012,metal,verts=8)
rod("Spent oxygen cartridge",(2.89,-1.74,-.025),(3.06,-1.89,-.025),.055,metal,verts=24)
rod("Spent cartridge brass neck",(3.05,-1.885,-.025),(3.10,-1.93,-.025),.036,brass)
tube("Abandoned cable",[(3.66,-1.48,-.044),(3.98,-1.87,-.05),(4.04,-2.32,-.05),(3.61,-2.52,-.04),(3.42,-2.32,-.04),(3.77,-2.08,-.05)],.017,rubber)
# Small cable tumbleweed at far return wall
for i in range(10):
    pts=[]
    phase=random.uniform(0,6.28)
    for j in range(24):
        a=2*math.pi*j/24
        rr=.25+random.uniform(-.034,.034)
        pts.append((-2.61+rr*math.cos(a),1.16+rr*math.sin(a)*math.cos(phase),.18+rr*math.sin(a)*math.sin(phase)))
    tube("Secondary tangled cable tumbleweed",pts,.008,rust if i%3 else metal,True)
# Few physical scratches and worn patches on façade, no texture cards
for i in range(55):
    x=random.uniform(1.58,3.66); z=random.uniform(.47,3.11)
    if 1.95<x<3.31 and 1.72<z<3.08: continue
    rod("Facade wood split",(x,.498,z),(x+random.uniform(.055,.21),.496,z+random.uniform(-.004,.004)),.0035,dark,verts=5)

# ---------- Cinematic lighting and presentation ----------
world=bpy.data.worlds.new("Cool frontier twilight") if not bpy.data.worlds else bpy.data.worlds[0]
bpy.context.scene.world=world; world.use_nodes=True
world.node_tree.nodes["Background"].inputs["Color"].default_value=(.115,.185,.27,1)
world.node_tree.nodes["Background"].inputs["Strength"].default_value=.42

def light(name,kind,loc,color,energy,size=None,target=None):
    data=bpy.data.lights.new(name,kind); data.energy=energy; data.color=color
    ob=bpy.data.objects.new(name,data); bpy.context.collection.objects.link(ob); ob.location=loc
    if kind=='AREA' and size is not None: data.shape='DISK'; data.size=size
    if target is not None: ob.rotation_euler=(Vector(target)-ob.location).to_track_quat('-Z','Y').to_euler()
    return ob
sun=light("Hard low dying sun",'SUN',(-8,-10,5.2),(1.0,.64,.34),3.5,target=(0,0,1.8))
sun.data.angle=math.radians(.75)
light("Cool sky soft fill",'AREA',(1,-6,6),(.43,.65,1.0),380,5.0,(-1,0,2.0))
light("Low blue bounce for the face",'AREA',(-3.8,-4,2.9),(.54,.70,1.0),130,3.2,(-1.2,0,2.7))
light("Saloon interior tungsten",'AREA',(.6,1.88,2.6),(1.0,.34,.10),155,1.5,(.5,-1,1.6))
light("Warm interior floor spill",'POINT',(.65,.95,.65),(1.0,.30,.075),32)
# restrained cool edge on pack, return wall
light("Sky edge",'AREA',(-4.0,2.3,4.6),(.34,.57,.83),260,3.5,(-1.4,.1,2.1))

bpy.ops.object.camera_add(location=(6.25,-13.8,4.20))
cam=bpy.context.object; cam.name="Duel | presentation camera"
target=Vector((.34,.17,2.08))
cam.rotation_euler=(target-cam.location).to_track_quat('-Z','Y').to_euler()
cam.data.type='PERSP'; cam.data.lens=57
cam.data.sensor_width=36
cam.data.dof.use_dof=False
scene=bpy.context.scene; scene.camera=cam
scene.render.engine='CYCLES'
scene.cycles.samples=64
scene.cycles.use_denoising=True
scene.cycles.max_bounces=10
scene.cycles.transmission_bounces=8
scene.render.resolution_x=1536; scene.render.resolution_y=1536
scene.render.resolution_percentage=100
scene.render.image_settings.file_format='PNG'
scene.render.film_transparent=False
scene.view_settings.view_transform='AgX'
try: scene.view_settings.look='AgX - Medium High Contrast'
except: pass
scene.view_settings.exposure=.10
# Metadata useful when inspecting this fully three-dimensional scene
scene["title"]="LAST ORBIT — the last quiet second"
scene["description"]="A pressure-suited drifter, shoulder and hip on the saloon corner, one magnetic heel braced, an open gloved hand hovering above the exposed ion revolver."
# Set origin-friendly selection and save the entire native scene.
bpy.ops.object.select_all(action='DESELECT')
bpy.context.view_layer.objects.active=cam
cam.select_set(True)
bpy.ops.wm.save_as_mainfile(filepath="/mnt/data/output.blend")
```