<!-- NEW6 — TEST 6 · THE FIVE SHORES · part 440 -->
-------------------------- */
/* Build memory (the King's law: no map ships without it)              */
/* ------------------------------------------------------------------ */

var MEMORY_KEY = 'solid-memory';
var memoryMirror = [];

function memoryRead() {
  try {
    var raw = window.localStorage.getItem(MEMORY_KEY);
    if (raw) {
      var list = JSON.parse(raw);
      if (Array.isArray(list)) memoryMirror = list;
    }
    /* raw === null: storage is empty or unreachable — the mirror
     * already holds the truth; never let an empty read erase it. */
  } catch (e) { /* storage unavailable — the mirror still holds the log */ }
  return memoryMirror;
}

function memoryLog(site, what) {
  memoryRead();
  memoryMirror.push({ t: Date.now(), site: site, what: what });
  try {
    window.localStorage.setItem(MEMORY_KEY, JSON.stringify(memoryMirror));
  } catch (e) { /* mirror-only logging still counts */ }
}

if (typeof window !== 'undefined' && !window.logBuild) {
  window.logBuild = function (what) { memoryLog('new2', what); };
  window.readMemory = function () { return memoryRead(); };
}

/* ------------------------------------------------------------------ */
/* Small deterministic helper for floor tinting                        */
/* ------------------------------------------------------------------ */

function mulberry32(seed) {
  var a = seed >>> 0;
  return function () {
    a |= 0; a = (a + 0x6D2B79F5) | 0;
    var t = Math.imul(a ^ (a >>> 15), 1 | a);
    t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
    return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
  };
}

function clamp(v, lo, hi) { return Math.min(hi, Math.max(lo, v)); }

/* ------------------------------------------------------------------ */
/* The floor's face: a canvas-drawn apron.                             */
/* Test 2's floor carries no plan of its own — the district's streets  */
/* and blocks are built on top of it — so the weave is quiet: deep     */
/* neutral asphalt, gently mottled, a whisper of speckle. Dark enough  */
/* to make a skyline read, plain enough to sit under any district.     */
/* Procedural — no image files, nothing to load.                       */
/* ------------------------------------------------------------------ */

function makeFloorTexture(THREE, size) {
  var px = 512;
  var canvas = document.createElement('canvas');
  canvas.width = px; canvas.height = px;
  var g = canvas.getContext('2d');
  var rand = mulberry32(20261015);

  g.fillStyle = '#131517';                 /* the apron: near-black asphalt */
  g.fillRect(0, 0, px, px);

  /* broad soft mottling: big faint patches, lighter and darker,
   * so the ground has weather without having a pattern */
  for (var p = 0; p < 260; p++) {
    var x = rand() * px, y = rand() * px;
    var r = 18 + rand() * 60;
    var lift = Math.floor(rand() * 13) - 6;
    var grad = g.createRadialGradient(x, y, 0, x, y, r);
    var base = 'rgba(' + (26 + lift) + ',' + (29 + lift) + ',' +
               (31 + lift) + ',';
    grad.addColorStop(0, base + '0.16)');
    grad.addColorStop(1, base + '0)');
    g.fillStyle = grad;
    g.fillRect(x - r, y - r, r * 2, r * 2);
  }

  /* fine speckle: the grain of asphalt, barely there */
  for (var s = 0; s < 2600; s++) {
    var sx = rand() * px, sy = rand() * px;
    var tone = 24 + Math.floor(rand() * 22);
    g.fillStyle = 'rgb(' + tone + ',' + (tone + 2) + ',' + (tone + 4) + ')';
    g.fillRect(sx, sy, 1.4, 1.4);
  }

  /* one faint seam cross at the tile heart — a surveyor's mark the
   * district can align to, invisible unless you go looking */
  g.fillStyle = 'rgba(214,178,84,0.05)';
  g.fillRect(px / 2 - 1, 0, 2, px);
  g.fillRect(0, px / 2 - 1, px, 2);

  var tex = new THREE.CanvasTexture(canvas);
  tex.wrapS = THREE.RepeatWrapping;
  tex.wrapT = THREE.RepeatWrapping;
  var tiles = Math.max(4, Math.round(size / 300));
  tex.repeat.set(tiles, tiles);
  if ('colorSpace' in tex && THREE.SRGBColorSpace) {
    tex.colorSpace = THREE.SRGBColorSpace;
  }
  tex.anisotropy = 4;
  return tex;
}

/* ------------------------------------------------------------------ */
/* makeStage                                                           */
/* ------------------------------------------------------------------ */

function makeStage(THREE, renderer, opts) {
  opts = opts || {};
  var size = opts.size || 2600;             /* floor + apron, edge to edge */
  var site = opts.site || 'new2';
  var STAND_Y = 0.02;                       /* the contract height */

  var scene = new THREE.Scene();
  var SKY = new THREE.Color(0x0b0e12);
  scene.background = SKY;
  /* Fog matched to the sky and pushed out with the stage: from the
   * sky view the district's far edge must stay readable, so the fog
   * only takes hold well past the district — the world edge melts
   * into dark before the camera could ever frame raw void. */
  scene.fog = new THREE.Fog(SKY.getHex(), size * 1.3, size * 3.4