<!-- SOLID CITY · part 2040 -->

}
function applyFinish(d, f) {
  d.mats.forEach(function (m) {
    if (f === 'matte') { m.e = 0; m.ei = 0; m.o = 1; }
    else if (f === 'glow') { m.e = m.c; m.ei = 0.9; m.o = 1; }
    else if (f === 'glass') { m.o = 0.4; m.e = 0; m.ei = 0; }
  });
}

/* ---------------- state ---------------- */
var root = new T.Group(); root.name = 'forge'; A.world.add(root);
var F = { on: false, scope: 'piece', grid: 0.5, shape: 'cube', color: null, finish: 'matte', ghostRel: null, ghostName: '', ghostVer: 0, gOff: { x: 0, z: 0 }, lift: 0,
  sel: null, cur: null, bench: [], world: [], undo: [], me: null, lastKey: '', ghostOk: false, ghostWhy: '', ghostAbs: null, ready: false, loadErr: '' };
var ghostGroup = null;
var ghostBox = new T.Box3(new T.Vector3(), new T.Vector3(1, 1, 1)), ghostHelp = new T.Box3Helper(ghostBox, 0xffd27a); ghostHelp.visible = false; root.add(ghostHelp);
var selBox = new T.Box3(new T.Vector3(), new T.Vector3(1, 1, 1)), selHelp = new T.Box3Helper(selBox, 0xffd700); selHelp.visible = false; root.add(selHelp);

/* ---------------- items (one piece in the world = mesh + solid AABB) ---------------- */
function addItem(cr, d) {
  var it = { d: d, cr: cr };
  it.mesh = makeMesh(d); it.mesh.userData.forgeItem = it; root.add(it.mesh);
  it.box = C.pieceAABB(d); it.box.forge = it; A.solids.push(it.box);
  cr.items.push(it); return it;
}
function removeItem(it) {
  root.remove(it.mesh); var k = A.solids.indexOf(it.box); if (k > -1) A.solids.splice(k, 1);
  var j = it.cr.items.indexOf(it); if (j > -1) it.cr.items.splice(j, 1);
  if (F.sel === it) F.sel = null;
}
function refreshItem(it) {
  root.remove(it.mesh); it.mesh = makeMesh(it.d); it.mesh.userData.forgeItem = it; root.add(it.mesh);
  var b = C.pieceAABB(it.d); for (var k in b) it.box[k] = b[k];
}
function dropCreation(cr) { cr.items.slice().forEach(removeItem); var i = F.bench.indexOf(cr); if (i > -1) F.bench.splice(i, 1); i = F.world.indexOf(cr); if (i > -1) F.world.splice(i, 1); if (F.cur === cr) F.cur = null; }
function newCreation(name) {
  var cr = { lid: 'c' + Date.now().toString(36) + Math.floor(Math.random() * 1e4), name: name || ('Creation ' + (F.bench.length + 1)), local: true, items: [] };
  F.bench.push(cr); F.cur = cr; return cr;
}
var saveT = null;
function saveBench() {
  clearTimeout(saveT);
  saveT = setTimeout(function () {
    try { localStorage.setItem(LS_BENCH, JSON.stringify(F.bench.filter(function (c) { return c.items.length; }).map(function (c) { return { lid: c.lid, name: c.name, pieces: c.items.map(function (it) { return it.d; }) }; }))); }
    catch (e) { say('Could not autosave on this device (storage full?).', true); }
  }, 300);
}
function changed() {
  if (window.CityHyper && CityHyper.regrid) CityHyper.regrid();
  saveBench(); F.lastKey = ''; updateSel(); render();
}

/* ---------------- validity: solids, zones, height, land ---------------- */
function boxesOf(datas) { return datas.map(C.pieceAABB); }
function check(datas, cr, skip) {
  var boxes = boxesOf(datas), u = C.unionAABB(boxes), cx = (u.x0 + u.x1) / 2, cz = (u.z0 + u.z1) / 2;
  if (A.groundAt(cx, cz) < 0.3) return { ok: false, why: 'Build on land — the sea won\u2019t hold it.' };
  for (var i = 0; i < boxes.length; i++) {
    var b = boxes[i], dx = b.x1 - b.x0, dy = b.y1 - b.y0, dz = b.z1 - b.z0;
    if (Math.min(dx, dy, dz) < L.MIN_DIM - 1e-6) return { ok: false, why: 'Too thin — THE LAW: minimum 0.13.' };
    if (Math.max(dx, dy, dz) > L.MAX_DIM) return { ok: false, why: 'Too big — one piece stays under ' + L.MAX_DIM + ' m.' };
    var z = C.zoneHit(b); if (z) return { ok: false, why: 'No building at ' + z.n + '.' };
    var g = Math.max(gnd(b.x0, b.z0), gnd(b.x1, b.z1), gnd(b.x0, b.z1), gnd(b.x1, b.z0), gnd((b.x0 + b.x1) / 2, (b.z0 + b.z1) / 2));
    if (b.y0 < g - 0.05) return { ok: false, why: 'That would sink into the ground.' };
    if (b.y1 - g > L.MAX_HEIGHT) return { ok: false, why: 'Too high — builds stop at ' + L.MAX_HEIGHT + ' m so the flight lanes stay clear.' };
  }
  var all = boxes;
  if (cr) { var mine = cr.items.filter(function (it) { return !skip || skip.indexOf(it) < 0; }); if (mine.length + datas.length > L.MAX_PIECES) return { ok: false, why: 'A creation holds ' + L.MAX_PIECES + ' pieces — tap ☰ → NEW CREATION.' }; all = boxes.concat(mine.map(function (it) { return it.box; })); }
  var w = C.unionAABB(all);
  if (w.x1 - w.x0 > L.MAX_SPAN || w.z1 - w.z0 > L.MAX_SPAN) return { ok: false, why: 'A creation stays within ' + L.MAX_SPAN + ' m — start a NEW CREATION for more.' };
  var sol = A.solids;
  for (var k = 0; k < sol.length; k++) {
    var s = sol[k]; if (s.forge && (s.forge.cr === cr || (skip && skip.indexOf(s.forge) > -1))) continue;
    if (!C.overlap(u, s)) continue;
    for (i = 0; i < boxes.length; i++) if (C.overlap(boxes[i], s)) return { ok: false, why: s.forge ? 'Overlaps another creation (no overlap).' : 'Overlaps a building — everything here is solid.' };
  }
  return { ok: true };
}

/* -----