<!-- SOLIDABILITY-WORLD · part 53 -->
} catch (e) {
      return { ok: false, error: e.message };
    }
  }

  // Audit checkpoints
  async function createCheckpoint() {
    try {
      const resp = await fetch('/api/solidability-world/audit/checkpoint', {
        method: 'POST', credentials: 'same-origin',
      });
      return await resp.json();
    } catch (e) {
      return { ok: false, error: e.message };
    }
  }

  async function verifyCheckpoint() {
    try {
      const resp = await fetch('/api/solidability-world/audit/checkpoint/verify',
        { credentials: 'same-origin' });
      return await resp.json();
    } catch (e) {
      return { ok: false, error: e.message };
    }
  }

  // Show audit trail (server-side)
  async function showAudit() {
    let serverEntries = [];
    let chainStatus = null;
    try {
      const [auditResp, verifyResp] = await Promise.all([
        fetch('/api/solidability-world/audit?limit=50', { credentials: 'same-origin' }).then(r => r.json()),
        fetch('/api/solidability-world/audit/verify', { credentials: 'same-origin' }).then(r => r.json()),
      ]);
      serverEntries = auditResp.entries || [];
      chainStatus = verifyResp;
    } catch (e) { /* offline */ }

    let panel = document.getElementById('audit-panel');
    if (!panel) {
      panel = document.createElement('div');
      panel.id = 'audit-panel';
      document.getElementById('app').appendChild(panel);
    }
    panel.innerHTML = `
      <div class="audit-card">
        <div class="audit-header">
          <h3>📋 Audit Trail</h3>
          <span class="code-close" onclick="document.getElementById('audit-panel').classList.remove('visible')">✕</span>
        </div>
        <div class="role-info">
          Current role: <strong>${currentRole}</strong>
          ${isReadOnly() ? '(suggest only)' : '(can apply with approval)'}
        </div>
        ${chainStatus ? `
        <div class="chain-status ${chainStatus.valid ? 'valid' : 'broken'}">
          ${chainStatus.valid
            ? `✓ Chain valid: ${chainStatus.entries} entries`
            : `✗ CHAIN BROKEN at entry ${chainStatus.breakAt}`}
        </div>` : ''}
        <div class="audit-list">
          ${serverEntries.length ? serverEntries.map(e => `
            <div class="audit-item">
              <div class="audit-type">${escapeHtml(e.action)}</div>
              <div class="audit-summary">${escapeHtml(e.target || '')} ${escapeHtml((e.detail || '').slice(0, 100))}</div>
              <div class="audit-meta">${escapeHtml(e.actor)} · ${new Date(e.timestamp).toLocaleString()}</div>
              <div class="audit-hash">⛓ ${escapeHtml((e.hash || '').slice(0, 12))}…</div>
            </div>
          `).join('') : '<p class="note">No server audit entries yet.</p>'}
        </div>
      </div>
    `;
    panel.classList.add('visible');
  }

  function escapeHtml(s) {
    if (!s) return '';
    return String(s).replace(/&/g, '&amp;').replace(/</g, '&lt;')
                   .replace(/>/g, '&gt;').replace(/"/g, '&quot;');
  }

  return {
    setRole, getRole, canModify, isReadOnly,
    suggestFix, approveFix, logAudit, getAuditLog,
    parseAnnotations, renderRoleBadge, showAudit,
    serverAudit, verifyAuditChain,
    requestApproval, validateApproval,
    createCheckpoint, verifyCheckpoint,
  };
})();


===== FILE: public/solidability-world/js/sw-world.js =====
// Solidability World — World Builder
// Generates 3D geometry from layout: doors, hallways, rooms, alarm lights.
// Simple geometric blocks. No ornamentation.

const SWWorld = (() => {
  let layout = null;
  const colliders = []; // Solid collision boxes (work even when meshes don't render)

  function addCollider(x, z, width, depth) {
    colliders.push({
      minX: x - width / 2, maxX: x + width / 2,
      minZ: z - depth / 2, maxZ: z + depth / 2
    });
  }

  function checkCollision(px, pz, radius) {
    for (const c of colliders) {
      if (px + radius > c.minX && px - radius < c.maxX &&
          pz + radius > c.minZ && pz - radius < c.maxZ) {
        return true;
      }
    }
    return false;
  }
  let interactables = []; // objects the player can approach/select
  let lights = {}; // alarm light meshes by room key
  let raycaster = new THREE.Raycaster();
  let selectedRoom = null;

  function build(worldData) {
    layout = SWLayout.generateLayout(worldData);
    const scene = SWEngine.getScene();

    // Build floors
    for (let f = 0; f < layout.building.floors; f++) {
      buildFloor(scene, f, layout.building);
    }

    // Build rooms (each has its own teal door frame at entrance)
    layout.rooms.forEach(room => buildRoom(scene, room, worldData));

    // NOTE: Old standalone blue door meshes removed (2026-10-08).
    // The teal door frames on each room ARE the doors now. No duplication.

    // Build alarm lights
    layout.lights.forEach(light => buildAlarmLight(scene, light));

    // Build stairs + elevator
    buildVertical(scene, layout);

    // Clic