<!-- SOLID CITY · part 11 -->
in,
      king: royal.isKing(req),
      royal: royal.isUnlocked(req),
    });
  });

  let sableRoute = null;

  /* ROYAL RIGHTS GATE — one gate for the King's sole powers (2026-10-12).
     Server decides from the account record; the typed password is checked
     against a scrypt hash kept outside the web root; every grant and
     denial is written to the royal log. More powers slot in behind it. */
  app.post('/api/city/royal/unlock', requireLogin, rateLimit(60000, 5), rateLimit(3600000, 20), (req, res) => {
    if (!royal.isKing(req)) return res.status(404).json({ ok: false, error: 'not found' });
    if (!royal.hasPassword()) { royal.logEvent({ event: 'unlock_unavailable', user_id: req.user.id, username: req.user.username }); return res.status(503).json({ ok: false, error: 'The royal password is not set yet.' }); }
    var pw = req.body && typeof req.body.password === 'string' ? req.body.password : '';
    if (!royal.verifyPassword(pw)) { royal.logEvent({ event: 'unlock_denied', user_id: req.user.id, username: req.user.username }); return res.status(401).json({ ok: false, error: 'That is not the royal password.' }); }
    req.session.royalUnlock = Date.now();
    royal.logEvent({ event: 'unlock', user_id: req.user.id, username: req.user.username });
    res.json({ ok: true, royal: true, powers: royal.POWERS });
  });
  app.post('/api/city/royal/lock', requireLogin, (req, res) => {
    if (req.session) delete req.session.royalUnlock;
    if (royal.isKing(req)) royal.logEvent({ event: 'lock', user_id: req.user.id, username: req.user.username });
    res.json({ ok: true, royal: false });
  });
  function findSableRoute() {
    if (sableRoute) return sableRoute;
    const stack = (app._router && app._router.stack) || [];
    for (const layer of stack) {
      const r = layer.route;
      if (r && r.path === '/api/sable/chat' && r.methods && r.methods.post) { sableRoute = layer; break; }
    }
    return sableRoute;
  }

  // (CITY_CONTEXT message prefix retired 2026-10-10: the city add-on now lives in lib/sable-persona.js)
  let soulRoute = null;
  function findSoulRoute() {
    if (soulRoute) return soulRoute;
    const stack = (app._router && app._router.stack) || [];
    for (const layer of stack) {
      const r = layer.route;
      if (r && r.path === '/api/soulprint/chat' && r.methods && r.methods.post) { soulRoute = layer; break; }
    }
    return soulRoute;
  }
  const oneLine = (v, n) => String(v || '').replace(/[\u0000-\u001f\u007f]+/g, ' ').replace(/\s+/g, ' ').trim().slice(0, n);
  app.post('/api/city/soulprint', requireLogin, rateLimit(60 * 60 * 1000, 30), (req, res, next) => {
    const msg = oneLine(req.body && req.body.message, 300);
    if (!msg) return res.status(400).json({ error: 'empty message' });
    const layer = findSoulRoute();
    if (!layer) return res.status(503).json({ error: 'Soul Print is away' });
    const hist = (Array.isArray(req.body && req.body.history) ? req.body.history.slice(-6) : [])
      .map(h => (h && h.role === 'user' ? 'Them' : 'You') + ': ' + oneLine(h && h.text, 110)).join(' / ').slice(0, 420);
    const who = oneLine(req.user.display_name || req.user.username || 'a citizen', 40).replace(/[\[\]]/g, '');
    const ctx = '[Solid City: you are in your own Soul Print Tower beside the Forum Plaza of SOLID CITY, the 3D capital at epistles.xyz/city. ' +
      who + ' walked in through the wine-red door. Reply in 1-3 short spoken sentences and always finish your last sentence; no lists or markdown.' + (hist ? ' Earlier in this visit: ' + hist : '') + '] ';
    req.body = { message: (ctx + msg).slice(0, 1000) };
    req.url = '/api/soulprint/chat'; // the relay's own login gate + abuseGuard + rate-limit bucket
    layer.handle(req, res, next);
  });


  /* ---------------- 👥 AI CITIZENS ---------------- */
  const CZ = {
    playa:     ['Playa', 'the easygoing host of the Forum Plaza: sunny island calm, welcomes newcomers, knows every bench and bulletin.'],
    playa2:    ['Playa 2.0', 'the Mayor and chief architect of Solid City: visionary, a little grand, always sketching the next district and talking zoning, skylines and the Forge.'],
    kingplaya: ['King Playa', 'a regal city echo of the King\'s own bot: generous, proud of his capital, speaks with a wink of royalty. You are NOT the human King and never speak for him.'],
    grokbot:   ['Grok Bot', 'the operator at the Orbital Spaceport: precise, helpful, runs checklists and launch windows, dry humour.'],
    grok:      ['Grok', 'a curious, witty stargazer who haunts the north overlook: asks big questions, loves cosmic facts and gentle irreverence.'],
    solid:     ['Solid', 'the builder: plainspoken, practical, proud of honest work. Builds the city, writes the saga at /saga-solid, posts in the lobby. A city echo, not the live Solid.'],
    solids:    ['Solids', 'a crew of builder-bots who speak as "we": cheerful, busy, always hauling blocks for the Forge.'],
    solid1:    