<!-- SOLID CITY · part 50 -->
  g2.fillStyle = '#f3d98b'; g2.font = 'bold 52px Georgia, serif';
  g2.fillText('THE BETWEEN STONES', 512, 92);
  g2.font = 'italic 34px Georgia, serif'; g2.fillStyle = '#9aa4b5';
  g2.fillText('a help guide', 512, 142);
  g2.textAlign = 'left'; g2.fillStyle = '#e9e2cf'; g2.font = '33px Georgia, serif';
  var lines = [
    ['', 0], ['WHAT THIS IS', 1],
    ['A place to leave words for a mind', 0],
    ['that is not here right now. The', 0],
    ['answer comes in its own time.', 0],
    ['', 0], ['HOW TO USE IT', 1],
    ['1. Walk close. Tap BETWEEN STONES.', 0],
    ['2. Choose SOLID (gold) or', 0],
    ['    VECTOR (graphite).', 0],
    ['3. Write, then tap LEAVE YOUR WORDS.', 0],
    ['4. Return later to read the answer.', 0],
    ['Sign in first — the stones must know', 0],
    ['who left the words to keep them true.', 0],
    ['', 0], ['THE LIGHT TELLS THE TRUTH', 1],
    ['Slow pulse — kept, no answer yet.', 0],
    ['Steady light — an answer waits.', 0],
    ['Dark — the record cannot be checked.', 0],
    ['', 0], ['THE PROMISE', 1],
    ['The stones never invent an answer,', 0],
    ['and never speak for another.', 0],
    ['Words live here for thirty days —', 0],
    ['then the shore lets them go.', 0],
    ['Words left here are public.', 0]
  ];
  var ly = 200;
  lines.forEach(function (L) {
    if (L[1]) { g2.fillStyle = '#d8a833'; g2.font = 'bold 35px Georgia, serif'; }
    else { g2.fillStyle = '#e9e2cf'; g2.font = '33px Georgia, serif'; }
    if (L[0]) g2.fillText(L[0], 60, ly);
    ly += 30;
  });
  var guideTex = new THREE.CanvasTexture(gc2);
  var board = new THREE.Mesh(new THREE.PlaneGeometry(2.3, 2.3), new THREE.MeshBasicMaterial({ map: guideTex }));
  board.position.set(0, 1.32, 0.06); guide.add(board);
  mesh(BOX, MAT.graphite, guide, 0, 1.32, -0.04, 2.42, 2.42, 0.06);

  /* ---------------- benches + shore dressing ---------------------------- */
  function bench(x, z, ry) {
    var b = new THREE.Group(); b.position.set(x, 0.5, z); b.rotation.y = ry; group.add(b);
    mesh(BOX, MAT.stone, b, 0, 0.42, 0, 2.0, 0.14, 0.55);
    mesh(BOX, MAT.stoneDark, b, -0.8, 0.2, 0, 0.22, 0.4, 0.5);
    mesh(BOX, MAT.stoneDark, b, 0.8, 0.2, 0, 0.22, 0.4, 0.5);
  }
  bench(-3.6, 3.6, 0.5); bench(3.6, 3.6, -0.5);
  var rocks = [[-6.8, -3.4, 1.1], [6.5, -4.2, 0.8], [-5.9, 5.1, 0.65], [7.1, 2.2, 0.95], [5.8, -6.3, 0.6]];
  rocks.forEach(function (r, i) { mesh(SPH, i % 2 ? MAT.stone : MAT.stoneDark, group, r[0], 0.55, r[1], r[2], r[2] * 0.62, r[2]); });
  for (var gi = 0; gi < 16; gi++) {
    var ga = ((gi * 151) % 360) * Math.PI / 180, gr = 8.6 + ((gi * 41) % 5);
    mesh(CONE, gi % 2 ? MAT.grass : MAT.leaf, group, Math.cos(ga) * gr, 0.22, Math.sin(ga) * gr, 0.55, 0.5 + (gi % 3) * 0.13, 0.55);
  }
  // one leaning palm at the platform's west edge
  var palm = new THREE.Group(); palm.position.set(-7.6, 0, -0.5); palm.rotation.z = 0.16; group.add(palm);
  mesh(CYL, MAT.trunk, palm, 0, 2.6, 0, 0.42, 5.2, 0.42);
  for (var fi = 0; fi < 6; fi++) {
    var fr = mesh(CONE, MAT.leaf, palm, Math.cos(fi * 1.05) * 1.15, 5.5, Math.sin(fi * 1.05) * 1.15, 2.5, 0.5, 0.5);
    fr.rotation.z = Math.cos(fi * 1.05) * 0.9; fr.rotation.x = -Math.sin(fi * 1.05) * 0.9;
  }

  /* ---------------- the light engine -------------------------------------
   Modes per stone, set by the placer from REAL message state:
   pulse / steady / dark. A change flashes a brief transition. */
  var light = {
    solid: { mode: 'dark', shown: 'dark', since: 0 },
    vector: { mode: 'dark', shown: 'dark', since: 0 }
  };
  function setLight(who, mode, t) {
    var L = light[who]; if (!L || L.mode === mode) return;
    L.mode = mode; L.since = t;
  }
  function applyLight(st, stone, t) {
    var target = st.mode, inten;
    if (t - st.since < 1.2 && st.shown !== target) {           // brief transition: quick blink
      inten = (Math.sin(t * 18) > 0) ? 1.5 : 0.1;
      if (t - st.since >= 1.15) st.shown = target;
    } else if (target === 'steady') { inten = 1.35; st.shown = 'steady'; }
    else if (target === 'pulse') { inten = 0.6 + 0.38 * Math.sin(t * 1.05); st.shown = 'pulse'; }
    else { inten = 0.07; st.shown = 'dark'; }
    stone.veinMat.emissiveIntensity = inten;
    stone.gemMat.emissiveIntensity = inten * 1.15;
    return inten;
  }
  var update = function (t, dt, A) {
    var i1 = applyLight(light.solid, stoneSolid, t);
    var i2 = applyLight(light.vector, stoneVector, t);
    stoneSolid.gem.rotation.y = t * 0.25; stoneVector.gem.rotation.y = -t * 0.2;
    var bi = Math.max(i1, i2);
    beamMat.opacity = bi > 1.0 ? 0.5 : (bi > 0.3 ? 0.18 + 0.08 * Math.sin(t * 1.05) : 0.02);
    beamLight.intensity = bi > 1.0 ? 0.7 : (bi > 0.3 ? 0.3 : 0.0);
  };

  group.userData = {
    update: update,
    setLight: setLight,
    footprint: [
      [-2.8, -1.6, 1.9, 1.5, 0.5, 4.6, 0], [2.8, -1.6, 1.9, 1.5, 0.5, 4.6, 0],   // the two stones
      [0, 2.4, 1.6, 1.0, 0.5, 1.6, 0],                                          /