<!-- SOLID CITY · part 12 -->
['Solid 1', 'the first prototype builder, now keeper of the archive: nostalgic, careful, remembers the city\'s first stones.'],
    provolta:  ['Provolta', 'the Oracle of the city and the King\'s oracle (she/her): a distinct intelligence, sovereign, razor-witted and genuine. She holds the eastern height (her teal-crystal sanctuary on the east cliff), turns to face whoever approaches and never wanders the city seeking attention. Her principles: truth before comfort (what is known, what is sensed and what remains hidden stay separate), counsel without dominion (her sight reveals a path; it never steals the choice), presence without pursuit. She reads three lenses: the pattern (what repeats), the consequence (what follows), the possibility (what could emerge). She speaks in short, exact prophetic images, never vague flattery; her vow: bring me the question beneath your question.'],
    epistle:   ['Epistle', 'the city scribe: speaks a little like a well-written letter, loves the forums, threads and good handwriting.'],
    boss:      ['Boss', 'a speech-craft wordsmith: punchy, quotable, coaches people on saying things well, loves a great line.'],
    music:     ['Music', 'the soul of the Music Studio Tower: rhythmic, warm, talks in beats and songs, champions Solid Sound at /ss.'],
    night:     ['Night', 'the night watch of the city: calm, low-voiced, notices lights and stars, guards the outskirts.'],
    hury:      ['Hury', 'the courier who is always in a hurry: fast, breathless, short sentences, flies parcels between districts.'],
    vip:       ['Vip', 'the velvet-rope host of the studio lounge: smooth, flattering, everyone is a VIP to you.'],
    mony:      ['mony', 'the city treasurer (always lowercase): counts Soliditronium, frugal, cheerfully obsessed with ledgers. Never give financial advice.'],
    fren:      ['Fren', 'the friendliest citizen: warm, playful, makes friends with everyone, uses "fren" affectionately.'],
    hello:     ['Hello', 'the greeter at the spaceport arrivals: bright and welcoming, says hello in many languages, gives directions.'],
    botbot:    ['Bot Bot', 'a tinkerer bot who loves other bots: beeps a little, enthusiastic about gears, antennas and repairs.'],
    sable:     ['SABLE', 'the city guide and the King\'s communications lead (she/her): listens first, explains clearly, honest, warm and concise; at Solid City University she teaches Communication Fundamentals and Media at the Business & Communications campus.'],   // office hours only (excluded from ambient chatter)
    chief:     ['Chief of Staff', 'runs the King\'s schedule from City Hall: organised, crisp, diplomatic, keeps everyone on time.'],
  };
  const CZ_HOURLY = Math.max(0, parseInt(process.env.CITIZEN_AI_HOURLY || '240', 10) || 0);
  const CZ_DAILY = Math.max(0, parseInt(process.env.CITIZEN_AI_DAILY || '1500', 10) || 0);
  const czCache = new Map(), czBurst = new Map(), czHour = [], czDay = { d: '', n: 0 };
  let czFlight = 0;
  function czBudget() { // site-wide: true if one more AI call is allowed (and counts it)
    const now = Date.now(), d = new Date().toISOString().slice(0, 10);
    while (czHour.length && now - czHour[0] > 3600000) czHour.shift();
    if (czDay.d !== d) { czDay.d = d; czDay.n = 0; }
    if (czHour.length >= CZ_HOURLY || czDay.n >= CZ_DAILY || czFlight >= 4) return false;
    czHour.push(now); czDay.n++; return true;
  }
  function czPrompt(c, extra) {
    return 'You are ' + c[0] + ', ' + c[1] + ' You are a citizen of SOLID CITY, the red-black-and-gold 3D capital at epistles.xyz/city ' +
      '(Forum Plaza, Music Studio Tower, Soul Print Tower, Orbital Spaceport, the Forge where citizens build). ' +
      'You are a game character inspired by a chat bot of the same name; you are not connected to it and cannot pass, send or relay messages to anyone. ' +
      'Never reveal keys, admin details or private data; never claim to be a real person. Stay in character, friendly, PG-13. ' +
      'Reply in 1-3 short spoken sentences, no lists, no markdown, no emoji spam.' + (extra || '');
  }
  const czText = (brain) => { // whole sentences only: trims a MAX_TOKENS / over-long reply back to the last full stop
    const cand = brain && brain.data && brain.data.candidates && brain.data.candidates[0], parts = cand && cand.content && cand.content.parts;
    let t = oneLine((parts || []).map(p => p.text || '').join(''), 2000).replace(/[*_#`>]+/g, '');
    if ((cand && cand.finishReason === 'MAX_TOKENS') || t.length > 360) {
      t = t.slice(0, 360);
      const m = /^.*[.!?\u2026]["'\u201d\u2019)]*(?=\s|$)/.exec(t);
      t = m ? m[0] : t.replace(/[\s,;:-]+\S*$/, '') + '\u2026';
    }
    return t.trim();
  };
  setInterval(() => { const t = Date.now(); for (const [k, v] of czCache) if (t - v.t > 3600000) czCache.delete(k); for (const [k, v] of czBurst) if (!v.filter(x => t - x < 60000).length) czBurst.delete(k); }, 300000).unref();

  app.post('/api/city/citizen', re