<!-- SOLID CITY · part 7 -->
 && now - s.walk.t0 < s.walk.dur * 1000 + 5000 ? s.walk : null, action: s.action, say: s.say && now - s.say.t < 60e3 ? s.say : null,
    talk: s.talk && now - s.talk.t < 60e3 ? { with: s.talk.with, name: s.talk.name, line: s.talk.line, reply: s.talk.reply, t: s.talk.t } : null, seq: s.seq, last: s.last };
}
function context(p) {   // everything the viewer needs to render + describe the pilot (sent to the local render service only)
  const s = get(p), ps = pose(s), here = PLACES[s.place], pl = players(ps.x, ps.z);
  const others = Object.values(P.state).filter(o => o.id !== s.id && Date.now() - (o.last || 0) < ACTIVE_MS).map(o => { const q = pose(o); return { name: o.name, d: Math.round(Math.hypot(q.x - ps.x, q.z - ps.z)) }; });
  return { id: p.id, name: p.name, citizen: p.citizen, x: ps.x, z: ps.z, yaw: ps.yaw, pitch: ps.pitch, mode: ps.mode, alt: ps.alt || 0, eta: ps.eta, to: ps.to, k: ps.k,
    prev: s.prev || null, seq: s.seq, place: s.place, placeName: here ? here[4] : '', action: s.action, say: s.say, talk: s.talk, players: pl.slice(0, 12), total: pl.length, others,
    places: Object.keys(PLACES).map(id => ({ id, name: PLACES[id][4], d: Math.round(Math.hypot(PLACES[id][0] - ps.x, PLACES[id][1] - ps.z)) })).sort((a, b) => a.d - b.d) };
}
function init(app, deps) {
  P.deps = deps; load();
  app.get('/api/city/pilots', deps.rateLimit ? deps.rateLimit(60e3, 120) : (q, r, n) => n(), (req, res) => {
    res.set('Cache-Control', 'no-store');
    const q = req.query || {}, sid = String(q.sid || ''), x = +q.x, z = +q.z;
    if (/^[a-z0-9]{8,24}$/.test(sid) && isFinite(x) && isFinite(z) && Math.abs(x) < 1e5 && Math.abs(z) < 1e5 && q.viewer !== '1') {
      if (P.presence.size < 2000 || P.presence.has(sid)) P.presence.set(sid, { x, z, m: q.m === 'fly' ? 'flying' : 'walking', t: Date.now(),
        name: req.user ? String(req.user.display_name || req.user.username || 'a citizen').replace(/[\u0000-\u001f<>]/g, '').slice(0, 30) : 'a visitor' });
    }
    const list = Object.values(P.state).map(pub);
    res.json({ pilots: list, players: [...P.presence.values()].filter(v => Date.now() - v.t < PRESENCE_MS).length, now: Date.now() });
  });
}
module.exports = { init, auth, act, get, pose, context, blocked, setNear, players, PLACES, ALIAS, placeOf };


===== FILE: lib/city-see.js =====
/* /city/see* -> the city-see viewer service on 127.0.0.1:8911 (2026-10-10, the King: "a link for the right browser").
   Public, read-only, no login, no cookies forwarded. Only whitelisted query keys with short [a-z0-9-] values pass;
   the service itself whitelists places and rate-limits per client IP (sent as x-see-ip). */
'use strict';
const http = require('http');
const express = require('express');
const router = express.Router();
const PORT = +process.env.CITY_SEE_PORT || 8911;
const KEYS = ['go', 'look', 'fly', 'step', 'turn', 'tilt', 'read', 't'];
function cleanQuery(q) {
  const p = new URLSearchParams();
  for (const k of KEYS) { const v = q[k]; if (typeof v !== 'string' || !v) continue; const s = v.toLowerCase().slice(0, 32); if (/^-?[a-z0-9-]{1,32}$/.test(s)) p.set(k, s); }
  const s = p.toString(); return s ? '?' + s : '';
}
/* 🎮 AI PILOT: ?as=<id>&key=<token> steers that AI's OWN character (lib/city-pilots.js). No/invalid key = spectator only.
   The token is checked here and never forwarded except to the local render service (127.0.0.1) for its own links. */
const PILOT_KEYS = ['go', 'look', 'do', 'drive', 'say', 'talk', 'msg'];
const b64 = o => Buffer.from(JSON.stringify(o)).toString('base64');
async function pilotView(req, res) {
  const pilots = require('./city-pilots'), isTxt = /\.txt$/.test(req.path), isImg = /\.(jpg|png)$/.test(req.path);
  const str = v => (typeof v === 'string' ? v : '');
  const nope = (code, msg) => res.status(code).set({ 'Cache-Control': 'no-store', 'Referrer-Policy': 'no-referrer' }).type(isTxt || isImg ? 'text/plain' : 'text/html')
    .send(isTxt || isImg ? msg + '\n' : '<!doctype html><meta charset="utf-8"><meta name="referrer" content="no-referrer"><title>Solid City pilot</title><body style="background:#0b0507;color:#f1e4c3;font:16px system-ui;padding:16px"><p>' + String(msg).replace(/[&<>"]/g, '') + '</p><p><a style="color:#ffd98a" href="/city/see">Watch the city (spectator view)</a></p>');
  const a = pilots.auth(str(req.query.as), str(req.query.key), String(req.ip || ''));
  if (a.err) return nope(a.code || 403, a.err);
  const q = {}; if (!isImg) PILOT_KEYS.forEach(k => { const v = str(req.query[k]); if (v) q[k] = v.slice(0, 300); });
  const r = isImg ? { msg: '' } : await pilots.act(a, q);
  if (r.code === 429) return nope(429, r.err + ' Refresh: /city/see' + (isTxt ? '.txt' : '') + '?as=' + a.id + '&key=…');
  const ctx = Object.assign(pilots.context(a), { msg: r.msg || '', err: r.err || '' });
  const up = http.request({ host: '127.0.0.1', port: PORT, path: req.path + '?pilot=' + a.id, method: 'GET', timeout: 30000,
    headers: { 'x-see-ip': St