<!-- SAGA · part 68 -->
,24), goldMat);
  entry.rotation.x=-Math.PI/2; entry.position.set(CX,0.3,CZ+60); scene.add(entry);
  var green=new THREE.Mesh(new THREE.CircleGeometry(30,24),
    new THREE.MeshStandardMaterial({color:0x2d6a2d,roughness:0.9}));
  green.rotation.x=-Math.PI/2; green.position.set(CX,0.3,CZ); scene.add(green);
  var pad=new THREE.Mesh(new THREE.CircleGeometry(25,24), goldMat);
  pad.rotation.x=-Math.PI/2; pad.position.set(CX,0.3,CZ-62); scene.add(pad);
  var r1=new THREE.Mesh(new THREE.BoxGeometry(6,0.5,40), roadMat);
  r1.position.set(CX,0.5,CZ+30); scene.add(r1);
  var r2=new THREE.Mesh(new THREE.BoxGeometry(6,0.5,37), roadMat);
  r2.position.set(CX,0.5,CZ-31); scene.add(r2);
  var tg=new THREE.MeshStandardMaterial({color:0xffd700,emissive:0xffd700,emissiveIntensity:0.7,roughness:0.3,metalness:0.6});
  var pw=16,ph=26,pyy=0;
  for(var i=0;i<4;i++){
    var t=new THREE.Mesh(new THREE.BoxGeometry(pw,ph,pw), tg);
    t.position.set(CX,pyy+ph/2,CZ-62); window.applyMinimum(t); scene.add(t);
    pyy+=ph; pw/=PHI; ph/=PHI;
  }
  for(var p=0;p<6;p++){
    var a=(p/6)*Math.PI*2;
    var post=new THREE.Mesh(new THREE.CylinderGeometry(0.8,1,12,8), goldMat);
    post.position.set(CX+Math.cos(a)*10,6,CZ+Math.sin(a)*10);
    window.applyMinimum(post); scene.add(post);
  }
  var roof=new THREE.Mesh(new THREE.ConeGeometry(14,8,6), goldMat);
  roof.position.set(CX,16,CZ); window.applyMinimum(roof); scene.add(roof);
});
dOrder.push('practice');

/* ===== LOOP ===== */

/* 1. AUTO-SWIM — water zones, auto-enter/exit like Genshin */
var waterBodies = [
  {x: 100, z: 100, r: 30, surfaceY: 0.5},
  {x: -80, z: 60, r: 25, surfaceY: 0.5}
];
function waterAt(x, z){
  for(var i=0; i<waterBodies.length; i++){
    var w = waterBodies[i];
    var dx = x - w.x, dz = z - w.z;
    if(dx*dx + dz*dz < w.r*w.r) return w;
  }
  return null;
}

function animate(){
  requestAnimationFrame(animate);
  /* The Epistolary always stands — self-healing startup spawn (bounded).
     v85: counter lives on window so the 120 bound actually holds across frames. */
  if(typeof window._bodyTries === 'undefined') window._bodyTries = 0;
  if(typeof playerBody !== 'undefined' && !playerBody && typeof spawnNewBody === 'function'){
    if(window._bodyTries < 120){
      window._bodyTries++;
      try{ spawnNewBody(); }catch(e){}
    }
  }
  /* v85 — IDLE DETAIL STREAMING. Heavy intricacy passes wait in _idleQueue and
     drain only after the King stands still ~4s (or 20s after boot no matter
     what), a little per check, so detail never interrupts play. */
  if(!window._idle) window._idle = {frames:0, lastX:1e9, lastZ:1e9, still:0, boot:Date.now()};
  (function(){
    var I = window._idle;
    I.frames++;
    if(I.frames % 60 !== 0 || !window._idleQueue || !window._idleQueue.length) return;
    if(typeof kingPos !== 'undefined'){
      var moved = Math.abs(kingPos.x - I.lastX) > 0.5 || Math.abs(kingPos.z - I.lastZ) > 0.5;
      I.lastX = kingPos.x; I.lastZ = kingPos.z;
      I.still = moved ? 0 : I.still + 1;
    }
    if(I.still >= 4 || Date.now() - I.boot > 20000){
      var t0 = Date.now();
      while(window._idleQueue.length && Date.now() - t0 < 30){
        var fn = window._idleQueue.shift();
        try{ fn(); }catch(e){}
      }
      I.still = 0;
    }
  })();
  /* body movement */
  if(typeof moveMode !== 'undefined' && typeof joyActive !== 'undefined' && joyActive){
    if(moveMode === 'walk'){
      var sp = 0.3;
      kingPos.x += (Math.cos(camYaw) * -joyDY + Math.sin(camYaw) * joyDX) * sp;
      kingPos.z += (Math.sin(camYaw) * -joyDY - Math.cos(camYaw) * joyDX) * sp;
    } else if(moveMode === 'fly'){
      var fs = 0.5;
      kingPos.x += (Math.cos(camYaw) * -joyDY + Math.sin(camYaw) * joyDX) * fs;
      kingPos.z += (Math.sin(camYaw) * -joyDY - Math.cos(camYaw) * joyDX) * fs;
      kingPos.y = Math.max(0, kingPos.y - joyDY * fs);
    }
  }
  /* Body follows kingPos — STATE-GATED animations with BLEND */
  if(typeof playerBody !== 'undefined' && playerBody){
    playerBody.position.set(kingPos.x, kingPos.y, kingPos.z);
    /* 2. FACE TRAVEL DIRECTION */
    var mdx = lastDX, mdz = lastDZ;
    if(Math.abs(mdx) > 0.01 || Math.abs(mdz) > 0.01){
      var targetYaw = Math.atan2(mdx, mdz);
      var dyaw = targetYaw - playerBody.rotation.y;
      dyaw = Math.atan2(Math.sin(dyaw), Math.cos(dyaw));
      playerBody.rotation.y += dyaw * 0.15;
    }
    /* 6. SEPARATE clocks, 7. BLEND amplitudes, 4. STATE-GATED */
    if(typeof animPhase === 'undefined'){ animPhase = {walk:0, swim:0}; animAmp = {walk:0, swim:0}; }
    var moving = Math.abs(lastDX) > 0.1 || Math.abs(lastDZ) > 0.1;
    var tWalk = (moveMode==='walk' && moving) ? 0.5 : 0;
    var tSwim = (moveMode==='swim') ? 0.8 : 0;
    animAmp.walk += (tWalk - animAmp.walk) * 0.15;
    animAmp.swim += (tSwim - animAmp.swim) * 0.15;
    if(moveMode==='walk' && moving){
      animPhase.walk += 0.15;
      if(playerBody.userData.legL) playerBody.userData.legL.rotation.x = Math.sin(