<!-- PLAYA 2.0'S WORLD · part 14 -->
keys.has('ArrowLeft')) s -= 1;
  f -= joy.y; s += joy.x; const m = Math.hypot(f, s); if (m > 1) { f /= m; s /= m; }
  const run = keys.has('ShiftLeft') ? 1.7 : 1, sp = 5.5 * run * dt;
  const fx = -Math.sin(P.yaw), fz = -Math.cos(P.yaw), rx = Math.cos(P.yaw), rz = -Math.sin(P.yaw);
  const dx = (fx * f + rx * s) * sp, dz = (fz * f + rz * s) * sp;
  if (m > 0.05) {
    const ox = P.x, oz = P.z;
    if (!blocked(P.x + dx, P.z, P.y)) P.x += dx; if (!blocked(P.x, P.z + dz, P.y)) P.z += dz;
    walked += Math.hypot(P.x - ox, P.z - oz); avatar.rotation.y = Math.atan2(dx, dz) + Math.PI;
  }
  const g = support(P.x, P.z, P.y);
  P.vy -= 18 * dt; P.y += P.vy * dt;
  if (P.y <= g) { P.y = g; P.vy = 0; P.onGround = true; } else if (P.y - g > 0.05) P.onGround = false;
  avatar.position.set(P.x, P.y, P.z); if (P.y > maxY) maxY = P.y;
  const cy = P.y + 1.3, cd = P.dist;
  _head.set(P.x, cy, P.z); _dir.set(Math.sin(P.yaw) * Math.cos(P.pitch), Math.sin(P.pitch), Math.cos(P.yaw) * Math.cos(P.pitch)).normalize();
  const dd = camClear(_head, _dir, cd); camPull = cd - dd;
  camera.position.copy(_head).addScaledVector(_dir, dd); if (camera.position.y < 0.4) camera.position.y = 0.4;
  camera.lookAt(P.x, cy, P.z);
  scene.traverse(o => o.userData.tick && o.userData.tick(t));
}
function loop(now) { const dt = Math.min(0.05, (now - last) / 1000); last = now; step(dt, now / 1000); renderer.render(scene, camera); requestAnimationFrame(loop); }
requestAnimationFrame(loop);
// test/debug hook (read-only state)
window.__playa = { get state() { return { x: P.x, y: P.y, z: P.z, walked, drawCalls: renderer.info.render.calls, triangles: renderer.info.render.triangles, avatarHeight: avatar.userData.height, modules: ok.slice(), colliders: colliders.length, yaw: P.yaw, minDim: Math.min(...groups.map(g => g.userData.minDim)), onGround: P.onGround, maxY, camPull, avatarVisible: avatarVisible() }; },
  test: { setPose(x, z, yaw, pitch = 0.32) { P.x = x; P.z = z; P.y = support(x, z, 50); P.vy = 0; P.yaw = yaw; P.pitch = pitch; maxY = P.y; }, setYaw(y) { P.yaw = y; }, resetMaxY() { maxY = P.y; } } };


