<!-- AVATAR-FORGE · part 9 -->
und.
  landing: {
    rootY: -0.35,
    rightHip:      { x: -0.85 },
    rightKnee:     { x: 1.30 },
    rightAnkle:    { x: -0.40 },
    leftHip:       { x: 0.55 },
    leftKnee:      { x: 0.90 },
    leftAnkle:     { x: -0.30 },
    spine:         { x: 0.35 },
    rightShoulder: { x: -1.90 },
    rightElbow:    { x: -0.25 },
    leftShoulder:  { x: 0.50 },
    leftElbow:     { x: -0.40 }
  },
  // The team fist bump: right arm extended forward, nearly straight;
  // the rest of the body stays easy. Upper body only — two walkers
  // can bump without breaking stride.
  fistBump: {
    rightShoulder: { x: -1.35 },
    rightElbow:    { x: -0.08 },
    leftShoulder:  { x: -0.10, z: 0.12 },
    leftElbow:     { x: -0.20 }
  }
};

// 'ready' is the milestone's power stance; both names stand.
HERO_POSES.powerStance = HERO_POSES.ready;

const GESTURES = {
  powerStance: { duration: 1.6, priority: 1 },
  ready:       { duration: 1.6, priority: 1 },
  powerCharge: { duration: 1.8, priority: 2 },
  shieldBlock: { duration: 1.2, priority: 2 },
  fistBump:    { duration: 1.4, priority: 2 },
  victory:     { duration: 1.8, priority: 2 },
  landing:     { duration: 1.1, priority: 3 }
};

const clamp01 = v => Math.min(1, Math.max(0, v));
const smooth = t => { t = clamp01(t); return t * t * (3 - 2 * t); };
// Frame-rate independent damping (the exponential form of lerp).
const damp = (a, b, lambda, dt) => a + (b - a) * (1 - Math.exp(-lambda * dt));

// The envelope: ease in over the first quarter, hold, ease out over
// the last third. At weight 0 the gesture owns nothing.
function envelope(t, duration) {
  const p = t / duration;
  if (p <= 0 || p >= 1) return 0;
  if (p < 0.25) return smooth(p / 0.25);
  if (p > 0.65) return smooth((1 - p) / 0.35);
  return 1;
}

/* createHeroMotion(parts, {lambda})
 * parts: the rig's named parts ({leftShoulder, rightElbow, ...}),
 * exactly as the Forge factory registers them.
 * update(dt, base): base is null (neutral), a pose map, or a
 * function(jointName) returning {x,y,z} or undefined — the base
 * locomotion layer's word for this frame. Returns {rootY}, the
 * active full-body gesture's suggested root offset (0 if none);
 * the caller composes it with its own bob and grounding. */
function createHeroMotion(parts, opts = {}) {
  const lambda = opts.lambda ?? 12;
  const onEvent = opts.onEvent || (() => {});
  let active = null; // {name, pose, t, duration, priority}
  let cancelling = false;

  // Intent arrives here; execution is the mixer's alone. play()
  // decides admissibility (unknown gesture, or a stronger action
  // already owns the rig) and reports the gesture's life by event:
  // started, then completed or cancelled — the intent/execution
  // separation the Architect ordered, so any caller (a human, an
  // AI citizen, a game event) requests and listens the same way.
  function play(name, o = {}) {
    const pose = HERO_POSES[name];
    const meta = GESTURES[name];
    if (!pose || !meta) { onEvent('refused', name); return false; }
    const priority = o.priority ?? meta.priority;
    if (active && priority < active.priority) { onEvent('refused', name); return false; }
    active = { name, pose, t: 0, duration: o.duration ?? meta.duration, priority };
    cancelling = false;
    onEvent('started', name);
    return true;
  }

  // Cancellation enters the envelope's ease-out at once (the
  // remaining release stretch plays smoothly), it never snaps.
  function cancel() {
    if (!active) return;
    cancelling = true;
    const p = active.t / active.duration;
    if (p < 0.65) active.duration = active.t / 0.65;
  }
  function current() { return active ? active.name : null; }
  function isPlaying() { return !!active; }

  function update(dt, base) {
    let w = 0, pose = null;
    if (active) { active.t += dt; w = envelope(active.t, active.duration); pose = active.pose; }
    const baseOf = joint => {
      if (!base) return null;
      return typeof base === 'function' ? base(joint) : base[joint];
    };
    let rootY = 0;
    for (const [joint, bone] of Object.entries(parts)) {
      if (!bone || !bone.rotation) continue;
      const b = baseOf(joint) || {};
      const g = pose && w > 0 ? pose[joint] : null;
      for (const axis of ['x', 'y', 'z']) {
        let target = b[axis] ?? 0;
        if (g && g[axis] !== undefined) target = target + (g[axis] - target) * w;
        bone.rotation[axis] = damp(bone.rotation[axis], target, lambda, dt);
      }
      if (g && pose.rootY !== undefined) rootY = pose.rootY * w;
    }
    if (active && active.t >= active.duration) {
      onEvent(cancelling ? 'cancelled' : 'completed', active.name);
      active = null; cancelling = false;
    }
    return { rootY };
  }

  return { play, cancel, current, isPlaying, update, poses: HERO_POSES };
}

/* collectParts(model): the Forge builder names every rig marker
 * 'rig_<part>' and parents it inside its part group — so a detached
 * model, which