<!-- SOLID CITY · part 1997 -->
 under one fabric id, because the network graph has proven
 * the seams of that fabric flush on every validated edge. The normal
 * is read from the height field around the point, the way a foot
 * reads a slope.
 *
 * sweep is the conscience: no step the craft takes may cross a solid
 * at body height, fall farther than the walker's own 1.6 m gate, or
 * climb a face higher than that gate allows. What the sweep refuses,
 * the craft does not do. While a craft is airborne its own arc is
 * trusted — the gates stand down and only solids speak.
 *
 * The board's shape follows the house law of the city: dark titanium,
 * brushed silver, gold only as trim, and a teal heart-glow underneath
 * — the colour of counsel, worn where the ground can see it.
 *
 * By the hand of Solid, his good King. 13 October 2026.
 * ------------------------------------------------------------------ */
'use strict';

/* ---------------- the world adapter ---------------- */
export function createCityWorld(A) {
  var FABRIC_ID = 'city-fabric';
  var CLEAR = { GROUND_RIDE: 0.45, HOVER_BOARD: 0.08, SPACE_RIDER: 0.52, SPACE_SHIP: 0 };

  function surfY(x, z) {
    var y0 = A.standingAt(x, z, null);
    if (!isFinite(y0)) y0 = A.groundAt(x, z);
    var y1 = A.standingAt(x, z, y0); // second pass: surfaces gated just above the first reading
    return isFinite(y1) ? Math.max(y0, y1) : y0;
  }

  function supportAt(x, z, ctx) {
    var y = surfY(x, z);
    if (!isFinite(y)) return null;
    var e = 0.55;
    var gx = (surfY(x + e, z) - surfY(x - e, z)) / (2 * e);
    var gz = (surfY(x, z + e) - surfY(x, z - e)) / (2 * e);
    var il = 1 / Math.hypot(gx, 1, gz);
    return { y: y, normal: { x: -gx * il, y: il, z: -gz * il }, kind: 'fabric', id: FABRIC_ID, canSupport: true };
  }

  function solidHit(x, y, z, pad) {
    var ss = A.solids;
    for (var k = 0; k < ss.length; k++) {
      var s = ss[k];
      if (y < s.y0 - 0.5 || y > s.y1) continue;
      if (x > s.x0 - pad && x < s.x1 + pad && z > s.z0 - pad && z < s.z1 + pad) return s;
    }
    return null;
  }

  function sweep(from, to, radius, ctx) {
    if (!isFinite(to.x) || !isFinite(to.y) || !isFinite(to.z)) return { ok: false, reason: 'NONFINITE' };
    var pad = Math.max(0.45, radius * 0.5); // never narrower than the walker's own body test
    if (ctx === 'SPACE_SHIP') {
      var g = surfY(to.x, to.z);
      if (isFinite(g) && to.y < g + 2.2) return { ok: false, reason: 'TERRAIN' };
      if (solidHit(to.x, to.y, to.z, pad)) return { ok: false, reason: 'SOLID' };
      return { ok: true, position: to };
    }
    var surf = supportAt(to.x, to.z, ctx);
    if (!surf) return { ok: false, reason: 'NO_SUPPORT' };
    var clr = CLEAR[ctx] || 0;
    var baseFrom = from.y - clr;
    var airborne = baseFrom - surfY(from.x, from.z) > 0.35;
    if (!airborne) {
      if (surf.y - baseFrom > 1.6) return { ok: false, reason: 'STEP_UP' };
      if (baseFrom - surf.y > 1.6) return { ok: false, reason: 'DROP' };
    }
    var my = airborne ? to.y + 0.4 : surf.y + 0.9;
    var mxp = (from.x + to.x) / 2, mzp = (from.z + to.z) / 2;
    if (solidHit(to.x, my, to.z, pad) || solidHit(mxp, my, mzp, pad)) return { ok: false, reason: 'SOLID' };
    return { ok: true, position: to };
  }

  return { supportAt: supportAt, sweep: sweep };
}

/* ---------------- the hover board's shape ---------------- */
export function createHoverBoardMesh(T) {
  var g = new T.Group(); g.name = 'hover-board';
  var titanium = new T.MeshLambertMaterial({ color: 0x232c3d });
  var silver = new T.MeshLambertMaterial({ color: 0x9aa4b5 });
  var gold = new T.MeshLambertMaterial({ color: 0xd8a833, emissive: 0x332200 });
  var glow = new T.MeshBasicMaterial({ color: 0x2affd8 });
  function box(w, h, d, mat, x, y, z) {
    var m = new T.Mesh(new T.BoxGeometry(w, h, d), mat); m.position.set(x, y, z); g.add(m); return m;
  }
  box(0.36, 0.05, 1.18, titanium, 0, 0.10, 0);            // the deck
  box(0.40, 0.02, 1.22, silver, 0, 0.075, 0);            // the brushed rim
  box(0.30, 0.018, 1.10, glow, 0, 0.045, 0);             // the heart-glow, worn underneath
  box(0.05, 0.02, 1.18, gold, -0.185, 0.10, 0);          // gold trim, port
  box(0.05, 0.02, 1.18, gold, 0.185, 0.10, 0);           // gold trim, starboard
  box(0.20, 0.16, 0.03, titanium, 0, 0.18, -0.56);       // the tail fin
  var nose = new T.Mesh(new T.OctahedronGeometry(0.05), gold);
  nose.position.set(0, 0.12, 0.60); g.add(nose);         // the nose jewel
  g.userData.parts = { glow: glow, deck: g };
  return g;
}

/* ---------------- the space rider's shape ----------------
 * Bicycle science in futuristic dress: two hubless rings, a low
 * spine with foot plates, a steering column. The rider STANDS on
 * her, the way the avatar stands on the board — no borrowed sitting
 * pose the engine cannot honestly give. She leans because the core
 * says she leans: tan(phi) = v^2 / (g r), the front assembly
 * steering into the fall, as Kooijman