<!-- NEW4 — TEST 4 · THE QUILT · part 5 -->
rig.spine.rotation.x = lean;
  rig.spine.rotation.z = -s * 0.03;
  rig.headG.rotation.x = -lean * 0.7;             // eyes stay level
  rig.headG.rotation.y = c * 0.05;
}

/* Seated pedaling pose for the bike riders. */
function posePedal(rig, phase) {
  rig.legL.th.rotation.x = -0.85 + Math.sin(phase) * 0.38;
  rig.legR.th.rotation.x = -0.85 + Math.sin(phase + Math.PI) * 0.38;
  rig.legL.kn.rotation.x = 0.95 + Math.max(0, -Math.sin(phase)) * 0.55;
  rig.legR.kn.rotation.x = 0.95 + Math.max(0, Math.sin(phase)) * 0.55;
  rig.armL.sh.rotation.x = -0.62; rig.armR.sh.rotation.x = -0.62;
  rig.armL.el.rotation.x = -0.25; rig.armR.el.rotation.x = -0.25;
  rig.hips.position.y = rig.LEG + 0.06 + Math.sin(phase * 2) * 0.012;
  rig.spine.rotation.x = 0.38 + Math.sin(phase * 2) * 0.02;
  rig.headG.rotation.x = -0.30;
}

/* Standing idle for citizens at rest: breathe, weight settled, arms
 * loose. These are the footballers' waiting numbers (the game below
 * calls this too) — t is plain seconds; in the game the phase clock
 * ticks 4x and breathes at phase * 0.35, the same 1.4 rad/s as here.
 * amount scales the whole pose, so a director can blend rest in/out. */
function poseIdle(rig, t, amount) {
  const a = amount === undefined ? 1 : amount;
  rig.hips.position.y = rig.LEG + Math.sin(t * 1.4) * 0.008 * a;
  rig.spine.rotation.x = 0.03 * a;
  rig.legL.th.rotation.x = 0.05 * a; rig.legR.th.rotation.x = -0.05 * a;
  rig.legL.kn.rotation.x = 0.08 * a; rig.legR.kn.rotation.x = 0.08 * a;
  rig.armL.sh.rotation.x = 0.04 * a; rig.armR.sh.rotation.x = -0.04 * a;
}

/* The pose kit, exported for the LIFE DIRECTOR: it moves rigs itself
 * (out of doors, down streets, onto bikes) and poses them with these.
 * amount is pose intensity — 1 is the crowd's own full measure (the
 * exact amp/lean/pump poseGait gets in update() below); lower blends
 * toward standing. Pedaling is seated geometry, all-or-nothing, so
 * bike's amount is accepted for a uniform contract, not applied. */
const poses = {
  walk(rig, phase, amount) { poseGait(rig, phase, 0.55 * (amount === undefined ? 1 : amount), 0.06, 0.7); },
  run(rig, phase, amount) { poseGait(rig, phase, 0.95 * (amount === undefined ? 1 : amount), 0.22, 1.25); },
  bike(rig, phase, amount) { posePedal(rig, phase); },
  idle(rig, t, amount) { poseIdle(rig, t, amount); },
};

/* Gait sync table for the director: how far phase advances per unit
 * traveled, and the cruise speed — read straight out of update():
 *  walk — phase += dt * speed * 3.4; citizens cruise 1.25–1.75 u/s.
 *  run  — phase += dt * 9.2, time-driven (the oval's angSpeed is
 *         0.16–0.21 rad/s over radii 27x10, ≈ 3.6 u/s mean ground
 *         speed), so per-unit is that 9.2 over the 3.6 cruise.
 *  bike — phase += dt * (speed / 0.34) * 0.55; cruise 5.2–6.3 u/s. */
const gait = {
  walk: { phasePerUnit: 3.4, speed: 1.5 },
  run: { phasePerUnit: 9.2 / 3.6, speed: 3.6 },
  bike: { phasePerUnit: 0.55 / 0.34, speed: 5.75 },
};

/* ---------------- paths ---------------- */
function rectPath(hw, hd) { // rectangle loop, returns posAt(s) -> {x,z,dx,dz}
  const W = 2 * hw, D = 2 * hd, P = 2 * (W + D);
  return {
    length: P,
    at(s) {
      s = ((s % P) + P) % P;
      if (s < W) return { x: -hw + s, z: -hd, dx: 1, dz: 0 };
      s -= W;
      if (s < D) return { x: hw, z: -hd + s, dx: 0, dz: 1 };
      s -= D;
      if (s < W) return { x: hw - s, z: hd, dx: -1, dz: 0 };
      s -= W;
      return { x: -hw, z: hd - s, dx: 0, dz: -1 };
    },
  };
}
function ellipsePath(cx, cz, rx, rz) {
  return {
    at(s) { // s is an angle-like parameter here (radians of travel)
      const x = cx + Math.cos(s) * rx, z = cz + Math.sin(s) * rz;
      let dx = -Math.sin(s) * rx, dz = Math.cos(s) * rz;
      const m = Math.hypot(dx, dz) || 1;
      return { x, z, dx: dx / m, dz: dz / m };
    },
  };
}
function faceToward(obj, dx, dz) { obj.rotation.y = Math.atan2(dx, dz); }

/* ---------------- the bike ---------------- */
function makeBike(THREE, rng) {
  const g = new THREE.Group();
  const frameM = mat(THREE, pick(rng, SHIRTS));
  const dark = mat(THREE, 0x222226);
  function wheel(z) {
    const w = new THREE.Group();
    w.position.set(0, 0.34, z);
    const tire = new THREE.Mesh(new THREE.TorusGeometry(0.34, 0.03, 8, 22), dark);
    tire.rotation.y = Math.PI / 2;
    w.add(tire);
    const hub = new THREE.Mesh(new THREE.SphereGeometry(0.045, 8, 8), frameM);
    w.add(hub);
    for (let i = 0; i < 3; i++) {
      const sp = new THREE.Mesh(new THREE.BoxGeometry(0.015, 0.62, 0.015), frameM);
      sp.rotation.x = (i / 3) * Math.PI;
      w.add(sp);
    }
    g.add(w);
    return w;
  }
  const wF = wheel(0.56), wB = wheel(-0.56);
  function tube(x1, y1, z1, x2, y2, z2, r) {
    const a = new THREE.Vector3(x1, y1, z1), b = new THREE.Vector3(x2, y2, z2);
    const d = b.clone().sub(a), len = d.length();
    const m = new THREE.Mesh(new THREE.CylinderGeometry(r, r, len, 8), frameM);