<!-- SOLID CITY · part 269 -->
CityPodium.open('features'); }
  else if (actTarget.kind === 'campus') { if (window.CityCampus) CityCampus.open(actTarget.id); }
  else if (actTarget.kind === 'portal') openPortal(actTarget.d);
  else if (actTarget.kind === 'board') board();
  else if (actTarget.kind === 'exit') disembark();
  else if (actTarget.kind === 'train') { if (window.CityHyperTrain) CityHyperTrain.board(); }
  else if (actTarget.kind === 'firstparking') { if (window.CityFirstParking) CityFirstParking.act(); }
  else if (actTarget.kind === 'fleet') { if (window.CityFleet) CityFleet.act(); }
  else if (actTarget.kind === 'elevator') { if (window.CityHyperCity) CityHyperCity.boardElevator(); if (window.CityAnnex) CityAnnex.boardElevator(P.x, P.z); if (window.CityQuadrantCrown) CityQuadrantCrown.boardElevator(P.x, P.z); }
}
function openPortal(d) {
  $('p-title').textContent = d.title || d.label; $('p-go').href = d.url; $('p-go').textContent = d.go || 'ENTER';
  var np = window.CityPlayer && d === D.studio ? CityPlayer.state() : null;
  $('p-text').textContent = (np && np.title ? 'Now playing on Studio Radio: \u201c' + np.title + '\u201d. ' : '') + d.text;
  $('sc-portal').hidden = false;
}
var ray = new T.Raycaster(), ndc = new T.Vector2();
function onTap(x, y) {
  if (S.mode !== 'walk') return;
  ndc.set(x / innerWidth * 2 - 1, -(y / innerHeight) * 2 + 1); ray.setFromCamera(ndc, camera);
  if (window.CityForge && CityForge.tap(x, y)) return;
  if (ray.intersectObject(sable, true).length) openTalk();
  else if (window.CityCitizens && CityCitizens.tap(ray)) { /* 👥 citizen talk panel opened */ }
  else if (SOUL.group && Math.hypot(S.p.x - D.soul.x, S.p.z - D.soul.z) < 140 && ray.intersectObject(SOUL.group, true).length) openSoul();
}
function board() { S.mode = 'boarded'; avatar.visible = false; S.ship.x = PAD.x; S.ship.y = PAD.y + 2.4; S.ship.z = PAD.z; S.ship.speed = 0; levelShip(Math.PI); help('Ship boarded. Hit the big red LAUNCH button. Warp Speed mini-game: walk through the spaceport gate.'); }
function disembark() { S.mode = 'walk'; avatar.visible = true; S.p.x = PAD.x; S.p.z = PAD.z - 52; S.p.y = groundAt(S.p.x, S.p.z); S.p.face = Math.PI; setFlightUI(false); help('Clear of the pad — the hangar is behind you, the city ahead.'); }
function levelShip(heading) { S.ship.q.setFromAxisAngle(UP, heading); }
var countT = 0, countEnd = 0, countShown = '';
function startCountdown() {
  if (S.mode !== 'boarded') return; S.mode = 'countdown'; countT = 3.0; countEnd = performance.now() + 3000; countShown = ''; $('sc-launch').hidden = true; $('sc-act').hidden = true; $('sc-count').hidden = false;
}
function setFlightUI(on) { $('sc-thr').hidden = !on; $('sc-fbtns').hidden = !on; }
/* ROYAL FLIGHT (2026-10-12) — the King's sole power. Hyper calls this ONLY
   after the server-side Royal Rights unlock; nothing here self-grants. */
window.CityRoyal = {
  setFly: function (on) {
    if (S.mode !== 'walk') return false;
    if (on && !S.rfly) { S.rfly = true; S.rflyLand = false; S.rflyT = 0; S.rflyV = 0; help('You rise. Climb and sink with the gold arrows (or R / F). Tap WALK to land.', 6); }
    else if (!on && S.rfly) { S.rflyLand = true; }
    return true;
  },
  flying: function () { return !!S.rfly; },
  setRise: function (v) { S.rflyRise = v; },
  onLand: null
};
/* FIRST DRIVES (2026-10-13) — the South Lot rides. The avatar is the real
   human of this world; the rides were built for him to roll in. CityDrive
   owns the drive state and updateWalk integrates it (driveStep), exactly
   the way royal flight is integrated: one sanctioned hook in the physics.
   Any visitor may roll a lot ride, as any visitor may ride the train. */
/* RUNG 2 — THE DOORS ARE SEARCHED (the Apply-Everything Commission, 2026-10-13).
 * VECTOR's exit contract, entered after the Rung 1 survey found the old
 * step-out blind: it placed the avatar 2.6 m to the car's left with no
 * clearance and no standing test. Now both doors are searched — left
 * first, as the city has always stepped out — and each candidate must
 * pass two tests in the city's own terms: no blocker (a solid whose body
 * occupies the landing, judged at the walker's own radius) and stand
 * permission (a surface the walker himself could stand on: terrain,
 * classified support, or the parking system's own surfaces, inside the
 * walker's 1.6 eligibility gate and with no drop greater than 1.2). If
 * neither door is lawful, the exit is REFUSED — NO_SAFE_EXIT — and the
 * driver stays seated. A refusal is a door that would have hurt him,
 * named instead of opened. */
function driveDoorClear(x, z, floorY) {
  for (var k = 0; k < solids.length; k++) {
    var s = solids[k];
    if (s.y1 <= floorY + 0.55) continue; // a floor at the feet, not a blocker
    if (s.y0 >= floorY + 1.7) continue; // above the head — the walker passes under
    if (x > s.x0 - 0.45 && x < s.x1 + 0.45 && z > s.z0 - 0.45 && z < s.z1 + 0.45) return false;
  }
  return true;
}
function driveDoorSt