<!-- SOLIDABILITY-WORLD · part 47 -->
 arrival (task completion, not timeout)
    pollForArrival(room, currentCase);
  }

  function pollForArrival(room, case_, attempts = 0) {
    if (!investigating || paused) return;
    if (attempts > 60) { // 30 second max
      presentFindings(room, case_, 'navigation-timeout');
      return;
    }

    const player = SWEngine.getPlayer();
    if (!player) { setTimeout(() => pollForArrival(room, case_, attempts + 1), 500); return; }

    const dist = Math.hypot(
      player.position.x - room.position.x,
      player.position.z - room.position.z
    );

    // Arrived within 15m, or AI Drive finished
    const arrived = dist < 15 || !SWHyper.isAIDriving();
    if (arrived) {
      investigationPhase = 'analyzing';
      analyzeCase(room, case_);
    } else {
      setTimeout(() => pollForArrival(room, case_, attempts + 1), 500);
    }
  }

  // V2: Evidence separation
  function analyzeCase(room, case_) {
    if (!investigating || paused) return;
    investigationPhase = 'analyzing';

    const pattern = case_.pattern;
    const evidence = {
      observed: [],   // directly seen in source
      inferred: [],   // deduced from patterns
      tested: [],     // executed in fixture
      unverified: [], // claimed but not checked
    };

    if (pattern) {
      evidence.observed.push(`Pattern "${pattern.title}" at ${case_.file}:${pattern.line}`);
      evidence.observed.push(`Code: ${pattern.code}`);
      evidence.inferred.push(`Category: ${pattern.category_label} — ${pattern.category_note || ''}`);

      // Callers (observed from analysis)
      const callers = (room.functions || []).reduce((n, f) => n + (f.called_by?.length || 0), 0);
      if (callers > 0) {
        evidence.observed.push(`${callers} intra-file caller relationships verified`);
      }

      evidence.unverified.push('Runtime behavior with production inputs');
      evidence.unverified.push('Whether the pattern causes actual defects in this context');
    }

    case_.evidence = evidence;
    investigationPhase = 'presenting';
    presentFindings(room, case_, null);
  }

  function presentFindings(room, case_, note) {
    if (!investigating) return;

    const pattern = case_.pattern;
    const ev = case_.evidence || {};

    // Plain-language explanation
    let plain = '';
    if (pattern) {
      if (pattern.pattern_id === 'equality-loose' && pattern.code.includes('== null')) {
        plain = `This uses == null, which checks for both null and undefined. ` +
          `This is intentional in most cases — changing to === would break undefined handling. ` +
          `Recommendation: NO CHANGE.`;
        // Record as intentional
        investigationMemory[findingId(case_)] = {
          outcome: 'intentional',
          timestamp: new Date().toISOString(),
          note: '== null is idiomatic null/undefined check',
        };
        saveMemory();
      } else {
        plain = `Found ${pattern.title}. Category: ${pattern.category_label}. ` +
          `Review the code and decide if modernization is warranted.`;
      }
    }

    SWHyper.narrate(
      `At <b>${case_.room}</b>. ${plain} ` +
      `Tap the building for details. Continuing...`
    );

    SWGlass.show(room);

    // Audit with evidence
    if (typeof SWTrust !== 'undefined') {
      SWTrust.serverAudit('ai-investigation', case_.file,
        JSON.stringify({
          pattern: pattern?.title,
          line: pattern?.line,
          observed: ev.observed?.length || 0,
          inferred: ev.inferred?.length || 0,
          outcome: investigationMemory[findingId(case_)]?.outcome || 'needs-review',
        }).slice(0, 500));
    }

    investigationPhase = 'done';

    // Move to next (task completion, not timer)
    setTimeout(() => { if (investigating && !paused) investigateNext(); }, 4000);
  }

  function pause() {
    if (!investigating || paused) return;
    paused = true;
    SWHyper.narrate(`Investigation paused. Tap 🕵️ to resume.`);
    updateButton();
  }

  function resume() {
    if (!investigating || !paused) return;
    paused = false;
    SWHyper.narrate(`Investigation resumed.`);
    updateButton();
    if (investigationPhase === 'done' || investigationPhase === 'idle') {
      investigateNext();
    } else if (currentCase) {
      // Re-present current case
      const layout = SWWorld.getLayout();
      const room = layout.rooms.find(r => r.key === currentCase.roomKey);
      if (room) pollForArrival(room, currentCase);
    }
  }

  function toggle() {
    if (!investigating) { startInvestigation('risks', 5); }
    else if (paused) { resume(); }
    else { pause(); }
  }

  function stop() {
    investigating = false;
    paused = false;
    investigationPhase = 'idle';
    investigationQueue = [];
    currentCase = null;
    SWHyper.narrate(`Investigation stopped. You have control.`);
    updateButton();
  }

  function updateButton() {
    const btn = document.getElementById('hc-investigate');
    if (!btn) r