<!-- RUBY-BACKUP · part 475 -->
Find three gears to fix my arm'},
  {giver: 'Leilani', soul: 'tree beggar', text: 'Share your cup with someone thirsty'},
  {giver: 'Makoa', soul: 'question guy', text: 'Find one answer, any answer'},
  {giver: 'Nalani', soul: 'homeless', text: 'Sing a song for the valley'},
  {giver: 'Keala', soul: 'broken robot', text: 'Learn to dance with your bent leg'}
];
function buildMissionGiver(name, colorHex, x, y, z, targetX, targetZ){
  var g = new THREE.Group();
  var skin = new THREE.MeshStandardMaterial({color: 0xd4a574, roughness: 0.6});
  var cloth = new THREE.MeshStandardMaterial({color: colorHex, roughness: 0.7});
  /* kind face */
  var head = new THREE.Mesh(new THREE.SphereGeometry(0.15, 18, 18), skin);
  head.position.y = 1.5; g.add(head);
  /* warm smile */
  var smile = new THREE.Mesh(new THREE.TorusGeometry(0.06, 0.012, 6, 12, Math.PI),
    new THREE.MeshStandardMaterial({color: 0x8a4a3a}));
  smile.position.set(0.11, 1.45, 0); smile.rotation.y = Math.PI/2; smile.rotation.z = Math.PI;
  g.add(smile);
  /* bright eyes */
  [-1,1].forEach(function(s){
    var eye = new THREE.Mesh(new THREE.SphereGeometry(0.022, 10, 10),
      new THREE.MeshStandardMaterial({color: 0x222222}));
    eye.position.set(0.12, 1.53, s*0.06); g.add(eye);
  });
  /* glowing heart — they are good */
  var heart = new THREE.Mesh(new THREE.SphereGeometry(0.06, 10, 10),
    new THREE.MeshStandardMaterial({color: 0xff6699, emissive: 0xff6699, emissiveIntensity: 1.5}));
  heart.position.set(0, 1.15, 0.16); heart.scale.set(1, 1.2, 0.6);
  g.add(heart);
  var body = new THREE.Mesh(new THREE.CylinderGeometry(0.16, 0.2, 0.8, 12), cloth);
  body.position.y = 0.9; g.add(body);
  [-1,1].forEach(function(s){
    var leg = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.05, 0.5, 8), cloth);
    leg.position.set(0, 0.25, s*0.09); g.add(leg);
  });
  g.position.set(x, y, z);
  /* walk to the soul, give mission, walk back */
  var state = 'approach';
  var t = 0;
  var mission = MISSIONS[Math.floor(Math.random()*MISSIONS.length)];
  /* mission bubble */
  var mCv = document.createElement('canvas');
  mCv.width = 512; mCv.height = 96;
  var mCtx = mCv.getContext('2d');
  mCtx.fillStyle = 'rgba(255,255,240,0.95)';
  mCtx.fillRect(0, 0, 512, 96);
  mCtx.strokeStyle = '#ffcc00'; mCtx.lineWidth = 4;
  mCtx.strokeRect(4, 4, 504, 88);
  mCtx.fillStyle = '#333333';
  mCtx.font = '20px Arial'; mCtx.textAlign = 'center';
  mCtx.fillText('MISSION:', 256, 28);
  mCtx.font = 'bold 18px Arial';
  /* wrap text */
  var words = mission.text.split(' ');
  var line = '', lines = [];
  words.forEach(function(w){
    if((line+' '+w).length > 30){ lines.push(line); line = w; }
    else line += (line ? ' ' : '')+w;
  });
  lines.push(line);
  lines.slice(0,2).forEach(function(l, i){
    mCtx.fillText(l, 256, 52+i*20);
  });
  var mTex = new THREE.CanvasTexture(mCv);
  var bubble = new THREE.Mesh(new THREE.PlaneGeometry(2.5, 0.47),
    new THREE.MeshBasicMaterial({map: mTex, transparent: true}));
  bubble.position.set(0, 2.2, 0);
  bubble.visible = false;
  g.add(bubble);
  var anim = setInterval(function(){
    t += 0.03;
    if(state==='approach'){
      /* move toward target */
      var dx = targetX - g.position.x, dz = targetZ - g.position.z;
      var dist = Math.sqrt(dx*dx+dz*dz);
      if(dist > 1.5){
        g.position.x += (dx/dist)*0.03;
        g.position.z += (dz/dist)*0.03;
        g.rotation.y = Math.atan2(dx, dz);
        /* walking bob */
        g.position.y = y + Math.abs(Math.sin(t*8))*0.05;
      }else{
        state = 'give';
        bubble.visible = true;
        setTimeout(function(){ bubble.visible = false; state = 'leave'; }, 5000);
      }
    }else if(state==='leave'){
      /* walk back */
      var bx = x - g.position.x, bz = z - g.position.z;
      var bdist = Math.sqrt(bx*bx+bz*bz);
      if(bdist > 0.5){
        g.position.x += (bx/bdist)*0.03;
        g.position.z += (bz/bdist)*0.03;
        g.rotation.y = Math.atan2(bx, bz);
        g.position.y = y + Math.abs(Math.sin(t*8))*0.05;
      }else{
        state = 'rest';
        setTimeout(function(){ state = 'approach'; }, 10000);
      }
    }else if(state==='rest'){
      /* wait, then go again */
      g.rotation.y += 0.01;
    }
    /* heart glows */
    heart.material.emissiveIntensity = 1.2 + Math.sin(t*3)*0.4;
  }, 50);
  g.userData.anim = anim;
  g.userData.mission = mission;
  return g;
}
function placeMissionGivers(){
  if(typeof scene==='undefined' || typeof mePos==='undefined') return;
  MISSION_GIVERS.forEach(function(m){
    try{ scene.remove(m); }catch(e){}
    if(m.userData.anim) clearInterval(m.userData.anim);
  });
  MISSION_GIVERS = [];
  var px = mePos.x, pz = mePos.z;
  var givers = [
    {name: 'Kai', color: 0x0099cc, sx: px-170, sz: pz+120, tx: (px-175)*0.13, tz: (pz+125)*0.13},
    {name: 'Leilani', color: 0xff99cc, sx: px-170, sz: pz-120, tx: (px-175)*0.13, tz: (pz-115)*0.13},
    {name: 'Makoa', color: 0xcc6633, sx: px+170, 