<!-- SOLIDABILITY-WORLD · part 38 -->
 z: -17 },
      hall_f1: { x: 0, y: fh, z: -14 },
      ai_door: { x: -20, y: fh, z: -10 },
      legal_door: { x: 5, y: fh, z: -10 },
    };

    // Edges: adjacency list (bidirectional unless noted)
    const edges = {
      spawn: ['hall_w', 'hall_e'],
      hall_w: ['spawn', 'auth_door'],
      hall_e: ['spawn', 'forum_door', 'stair_base'],
      auth_door: ['hall_w'],
      forum_door: ['hall_e'],
      stair_base: ['hall_e', 'stair_top'],
      stair_top: ['stair_base', 'hall_f1'],
      hall_f1: ['stair_top', 'ai_door', 'legal_door'],
      ai_door: ['hall_f1'],
      legal_door: ['hall_f1'],
    };

    // Room centers (destinations inside rooms, through door gaps)
    layout.rooms.forEach(r => {
      const doorKey = r.key + '_door';
      const centerKey = r.key + '_center';
      nodes[centerKey] = { x: r.position.x, y: r.position.y, z: r.position.z };
      if (!edges[doorKey]) edges[doorKey] = [];
      edges[doorKey].push(centerKey);
      edges[centerKey] = [doorKey];
    });

    return { nodes, edges };
  }

  // BFS shortest path
  function findPath(from, to) {
    const graph = buildGraph();
    if (!graph) return null;
    const { nodes, edges } = graph;
    const layout = SWWorld.getLayout();
    const isFullMap = layout && (layout.fullMap || layout.public || !layout.building);

    // Find nearest node to 'from' position and 'to' room
    const fromNode = nearestNode(from, nodes);
    // Full map: room key is the node directly. Prototype: use _door suffix.
    const toNode = to.roomKey
      ? (isFullMap ? to.roomKey : to.roomKey + '_door')
      : nearestNode(to, nodes);
    if (!fromNode || !toNode || !nodes[toNode]) return null;

    const visited = new Set([fromNode]);
    const queue = [[fromNode]];
    while (queue.length) {
      const path = queue.shift();
      const node = path[path.length - 1];
      if (node === toNode) return path.map(k => nodes[k]);
      for (const next of (edges[node] || [])) {
        if (!visited.has(next)) {
          visited.add(next);
          queue.push([...path, next]);
        }
      }
    }
    return null;
  }

  function nearestNode(pos, nodes) {
    let best = null, bestD = Infinity;
    for (const [key, n] of Object.entries(nodes)) {
      // Only consider nodes on same floor (within 4 units Y)
      if (Math.abs(n.y - pos.y) > 4) continue;
      const d = Math.hypot(n.x - pos.x, n.z - pos.z);
      if (d < bestD) { bestD = d; best = key; }
    }
    return best;
  }

  // NAVIGATE: auto-walk along path
  function navigateTo(room) {
    const player = SWEngine.getPlayer();
    if (!player) return false;
    const path = findPath(
      { x: player.position.x, y: player.position.y, z: player.position.z },
      { roomKey: room.key }
    );
    if (!path || !path.length) return false;

    // Push current position to history before navigating
    pushHistory();

    navPath = path;
    navIndex = 0;
    navigating = true;
    setMode('NAVIGATE');
    showPathTrail(path);
    return true;
  }

  function updateNavigate(dt) {
    if (!navigating || !navPath.length) return;
    const player = SWEngine.getPlayer();
    const target = navPath[navIndex];
    if (!target) { stopNavigate(); return; }

    const dx = target.x - player.position.x;
    const dz = target.z - player.position.z;
    const dy = target.y - player.position.y;
    const dist = Math.hypot(dx, dz);

    if (dist < 0.8) {
      // Reached waypoint — handle Y (stairs) then advance
      if (Math.abs(dy) > 0.5) {
        player.position.y += Math.sign(dy) * Math.min(Math.abs(dy), 3 * dt);
      } else {
        navIndex++;
        if (navIndex >= navPath.length) {
          // Arrived — show glass for the destination room
          stopNavigate();
          const layout = SWWorld.getLayout();
          const destKey = navPath.length > 0 ? null : null;
          // Find room near final position
          const room = layout.rooms.find(r =>
            Math.hypot(r.position.x - player.position.x, r.position.z - player.position.z) < r.size.width
          );
          if (room) SWGlass.show(room);
        }
      }
      return;
    }

    // Move toward waypoint (uses collision via tryMove if available)
    const speed = 5;
    const nx = player.position.x + (dx / dist) * speed * dt;
    const nz = player.position.z + (dz / dist) * speed * dt;
    // Simple collision: use engine's tryMove if exposed, else direct
    player.position.x = nx;
    player.position.z = nz;
    // Y follows path (for stairs)
    if (Math.abs(dy) > 0.1) {
      player.position.y += Math.sign(dy) * Math.min(Math.abs(dy), 4 * dt);
    }
  }

  function stopNavigate() {
    navigating = false;
    navPath = [];
    navIndex = 0;
    clearPathTrail();
    // Don't auto-switch mode — let the caller decide (tour continues, manual stops)
  }

  // FOCUS: select nearest room in view direction
  function focusNearest() {
    const layout = SWWorld.getLayout();
    const pla