<!-- SOLID CITY · part 1122 -->
ew T.PlaneGeometry(7.2, 10.6), doorM)); door.position.set(ex, Y + 5.4, ez); door.rotation.y = Math.atan2(dx, dz);
  animated.push(function (t) { doorM.opacity = 0.42 + 0.16 * Math.sin(t * 2.4); });
  var sg = sign('SOUL PRINT', 'ENTER · TALK WITH HER', ex + dx * 0.4, Y + 15, ez + dz * 0.4, 12, Math.atan2(dx, dz)); world.remove(sg); add(sg);
}

/* ---------------- avatar (Solidivism 60-30-10) ---------------- */
function makeAvatar(visor) {
  var g = new T.Group();
  var torso = new T.Mesh(new T.BoxGeometry(0.62, 0.8, 0.34), M.black); torso.position.y = 1.15; g.add(torso);
  var head = new T.Mesh(new T.BoxGeometry(0.34, 0.36, 0.34), M.black); head.position.y = 1.78; g.add(head);
  var vis = new T.Mesh(new T.BoxGeometry(0.3, 0.08, 0.02), new T.MeshBasicMaterial({ color: visor || 0xff2a40 })); vis.position.set(0, 1.8, 0.18); g.add(vis);
  [-1, 1].forEach(function (s) {
    var sh = new T.Mesh(new T.BoxGeometry(0.24, 0.16, 0.38), M.gun); sh.position.set(0.4 * s, 1.5, 0); g.add(sh);
    var arm = new T.Mesh(new T.BoxGeometry(0.14, 0.7, 0.14), M.black); arm.position.set(0.4 * s, 1.08, 0); g.add(arm); arm.name = 'arm' + s;
    var leg = new T.Mesh(new T.BoxGeometry(0.2, 0.76, 0.2), M.black); leg.position.set(0.15 * s, 0.38, 0); g.add(leg); leg.name = 'leg' + s;
  });
  var belt = new T.Mesh(new T.BoxGeometry(0.64, 0.06, 0.36), M.gold); belt.position.y = 0.78; g.add(belt);
  return g;
}

/* ---------------- spaceship (red/black/gold) ---------------- */
function makeShip() {
  var g = new T.Group();
  var hullM = new T.MeshLambertMaterial({ color: 0x1a1418 }), redM = new T.MeshLambertMaterial({ color: 0xb0101e, emissive: 0x30030a }), goldM = new T.MeshLambertMaterial({ color: 0xd8aa48, emissive: 0x2a1a04 });
  var body = new T.Mesh(new T.ConeGeometry(1.6, 11, 8), hullM); body.rotation.x = Math.PI / 2; body.position.z = 1.5; g.add(body);
  var aft = new T.Mesh(new T.CylinderGeometry(1.6, 1.9, 3, 8), hullM); aft.rotation.x = Math.PI / 2; aft.position.z = -5.4; g.add(aft);
  var stripe = new T.Mesh(new T.BoxGeometry(0.5, 0.2, 8), goldM); stripe.position.set(0, 1.25, 0); g.add(stripe);
  var cockpit = new T.Mesh(new T.SphereGeometry(1.0, 12, 8, 0, Math.PI * 2, 0, Math.PI / 2), new T.MeshLambertMaterial({ color: 0x220a10, emissive: 0x5a0a14 })); cockpit.scale.set(0.8, 0.7, 1.6); cockpit.position.set(0, 0.9, 1.4); g.add(cockpit);
  var wing = new T.Mesh(new T.BoxGeometry(12, 0.25, 3.6), redM); wing.position.set(0, -0.2, -2.6); g.add(wing);
  [-1, 1].forEach(function (s) {
    var tipw = new T.Mesh(new T.BoxGeometry(0.3, 2.4, 2.2), goldM); tipw.position.set(6 * s, 0.8, -3.2); g.add(tipw);
    var eng = new T.Mesh(new T.CylinderGeometry(0.7, 0.8, 3.6, 10), hullM); eng.rotation.x = Math.PI / 2; eng.position.set(2.4 * s, -0.3, -5.2); g.add(eng);
    var fl = new T.Mesh(new T.ConeGeometry(0.62, 4, 10), new T.MeshBasicMaterial({ color: 0xff4a1a, transparent: true, opacity: 0.75, blending: T.AdditiveBlending, depthWrite: false })); fl.rotation.x = -Math.PI / 2; fl.position.set(2.4 * s, -0.3, -9); g.add(fl); fl.name = 'flame';
  });
  var fin = new T.Mesh(new T.BoxGeometry(0.25, 2.6, 3), redM); fin.position.set(0, 1.6, -5); g.add(fin);
  return g;
}

/* ---------------- SABLE NPC ---------------- */
function makeSable() {
  var g = makeAvatar(0xffd27a);
  g.scale.setScalar(1.35);
  var halo = new T.Mesh(new T.TorusGeometry(0.55, 0.05, 8, 32), new T.MeshBasicMaterial({ color: 0xff2a40 })); halo.rotation.x = Math.PI / 2; halo.position.y = 2.2; g.add(halo);
  var cloak = new T.Mesh(new T.ConeGeometry(0.62, 1.3, 8, 1, true), new T.MeshLambertMaterial({ color: 0x5a0410, side: T.DoubleSide })); cloak.position.y = 0.95; g.add(cloak);
  var tag = new T.Sprite(new T.SpriteMaterial({ map: labelTex('SABLE', 'TAP OR WALK UP TO TALK'), depthTest: true })); tag.scale.set(3.6, 0.9, 1); tag.position.y = 3.1; g.add(tag);
  g.userData.halo = halo;
  return g;
}

/* ================= game state ================= */
var Y0 = CITY_Y, PAD = { x: D.port.x, y: CITY_Y + 2, z: D.port.z };
var S = {
  mode: 'walk',   // walk | boarded | countdown | launch | fly | auto
  p: { x: 6, y: Y0, z: 62, face: Math.PI, vy: 0 },
  camYaw: 0, camPitch: 0.28, camDist: 7.5,
  ship: { x: PAD.x, y: PAD.y + 2.4, z: PAD.z, q: new T.Quaternion(), speed: 0, throttle: 0.5, boost: false },
  autoLevel: true, auto: null, t: 0, launchT: 0, warnT: 0, portalCool: 0, lastPortal: null
};
var avatar = makeAvatar(); scene.add(avatar);
var ship = makeShip(); scene.add(ship);
var sable = makeSable(); scene.add(sable);
var SABLE_POS = { x: -16, y: Y0, z: -24 };
var shipFlames = []; ship.traverse(function (o) { if (o.name === 'flame') shipFlames.push(o); });
ship.position.set(0, 0, 0);

var input = { sx: 0, sy: 0, keys: {}, dragX: 0, dragY: 0, wheel: 0 };
var V3 = T.Vector3, tv = new V3(), tv2 = new V3(), tq = new T.Quaternion(), tm = new T.Matrix4(), UP = new V3(0, 1, 0);
var AX = new V3(1, 0, 0), AZ = new V3(0, 0, 1);

/* ---------------- input: joystick