@adatechnology/game-floorplan-three 0.9.0 → 0.9.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -70,7 +70,7 @@ var GameFloorplanThree = (() => {
70
70
  shade: () => shade
71
71
  });
72
72
 
73
- // ../game-shapes/dist/chunk-IFXQIDSA.js
73
+ // ../game-shapes/dist/chunk-M5N2SHNL.js
74
74
  function canonicalJson(value) {
75
75
  if (value === null) return "null";
76
76
  if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
@@ -1227,13 +1227,13 @@ var GameFloorplanThree = (() => {
1227
1227
  uvArray.push(x);
1228
1228
  uvArray.push(y);
1229
1229
  };
1230
- const at = (index, axis) => verticesArray[index * 3 + axis];
1230
+ const at2 = (index, axis) => verticesArray[index * 3 + axis];
1231
1231
  const f3 = (a, b, c) => {
1232
1232
  addVertex(a);
1233
1233
  addVertex(b);
1234
1234
  addVertex(c);
1235
1235
  const nextIndex = verticesArray.length / 3;
1236
- for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1));
1236
+ for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at2(index, 0), at2(index, 1));
1237
1237
  };
1238
1238
  const f4 = (a, b, c, d) => {
1239
1239
  addVertex(a);
@@ -1247,8 +1247,8 @@ var GameFloorplanThree = (() => {
1247
1247
  const iB = nextIndex - 3;
1248
1248
  const iC = nextIndex - 2;
1249
1249
  const iD = nextIndex - 1;
1250
- const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0));
1251
- const uv = (index) => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)];
1250
+ const byX = Math.abs(at2(iA, 1) - at2(iB, 1)) < Math.abs(at2(iA, 0) - at2(iB, 0));
1251
+ const uv = (index) => [byX ? at2(index, 0) : at2(index, 1), 1 - at2(index, 2)];
1252
1252
  const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)];
1253
1253
  for (const k of [0, 1, 3, 1, 2, 3]) addUV(...uvs[k]);
1254
1254
  };
@@ -1353,8 +1353,8 @@ var GameFloorplanThree = (() => {
1353
1353
  groups: []
1354
1354
  };
1355
1355
  }
1356
- function make(kind, p) {
1357
- switch (kind) {
1356
+ function make(p) {
1357
+ switch (p.kind) {
1358
1358
  case "box": {
1359
1359
  const size = p.size ?? [1, 1, 1];
1360
1360
  const segments = p.segments ?? [1, 1, 1];
@@ -1383,7 +1383,7 @@ var GameFloorplanThree = (() => {
1383
1383
  case "mesh":
1384
1384
  return mesh(p);
1385
1385
  default:
1386
- throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
1386
+ throw new Error(`game-shapes: unknown primitive kind "${String(p.kind)}"`);
1387
1387
  }
1388
1388
  }
1389
1389
  var templates = /* @__PURE__ */ new Map();
@@ -1391,7 +1391,7 @@ var GameFloorplanThree = (() => {
1391
1391
  var stats = { hits: 0, misses: 0 };
1392
1392
  function buildPrimitive(node, options = {}) {
1393
1393
  const parameters = primitiveParameters(node, options);
1394
- if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
1394
+ if (node.kind === "mesh" || templateLimit === 0) return make({ ...node, ...parameters });
1395
1395
  const key = `${node.kind}:${canonicalJson(parameters)}`;
1396
1396
  const cached = templates.get(key);
1397
1397
  if (cached) {
@@ -1401,7 +1401,7 @@ var GameFloorplanThree = (() => {
1401
1401
  return cached;
1402
1402
  }
1403
1403
  stats.misses++;
1404
- const built = make(node.kind, parameters);
1404
+ const built = make({ ...node, ...parameters });
1405
1405
  templates.set(key, built);
1406
1406
  if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
1407
1407
  return built;
@@ -1499,7 +1499,7 @@ var GameFloorplanThree = (() => {
1499
1499
  const positions2 = new Float32Array(total * 3);
1500
1500
  const normals = new Float32Array(total * 3);
1501
1501
  const uvs = new Float32Array(total * 2);
1502
- let at = 0;
1502
+ let at2 = 0;
1503
1503
  for (const piece2 of pieces) {
1504
1504
  const e = piece2.matrix;
1505
1505
  const nm = normalMatrix(e);
@@ -1513,15 +1513,15 @@ var GameFloorplanThree = (() => {
1513
1513
  if (piece2.normals) n[k * 3 + c] = piece2.normals[i * 3 + c];
1514
1514
  }
1515
1515
  if (piece2.uvs) {
1516
- uvs[(at + k) * 2] = piece2.uvs[i * 2];
1517
- uvs[(at + k) * 2 + 1] = piece2.uvs[i * 2 + 1];
1516
+ uvs[(at2 + k) * 2] = piece2.uvs[i * 2];
1517
+ uvs[(at2 + k) * 2 + 1] = piece2.uvs[i * 2 + 1];
1518
1518
  }
1519
1519
  }
1520
1520
  for (let k = 0; k < count; k++) {
1521
1521
  const q = transformPoint(e, p[k * 3], p[k * 3 + 1], p[k * 3 + 2]);
1522
- positions2[(at + k) * 3] = q[0];
1523
- positions2[(at + k) * 3 + 1] = q[1];
1524
- positions2[(at + k) * 3 + 2] = q[2];
1522
+ positions2[(at2 + k) * 3] = q[0];
1523
+ positions2[(at2 + k) * 3 + 1] = q[1];
1524
+ positions2[(at2 + k) * 3 + 2] = q[2];
1525
1525
  const x = n[k * 3];
1526
1526
  const y = n[k * 3 + 1];
1527
1527
  const z = n[k * 3 + 2];
@@ -1529,16 +1529,17 @@ var GameFloorplanThree = (() => {
1529
1529
  const vy = nm[1] * x + nm[4] * y + nm[7] * z;
1530
1530
  const vz = nm[2] * x + nm[5] * y + nm[8] * z;
1531
1531
  const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1);
1532
- normals[(at + k) * 3] = vx * s;
1533
- normals[(at + k) * 3 + 1] = vy * s;
1534
- normals[(at + k) * 3 + 2] = vz * s;
1532
+ normals[(at2 + k) * 3] = vx * s;
1533
+ normals[(at2 + k) * 3 + 1] = vy * s;
1534
+ normals[(at2 + k) * 3 + 2] = vz * s;
1535
1535
  }
1536
- at += count;
1536
+ at2 += count;
1537
1537
  }
1538
1538
  return { positions: positions2, normals, uvs };
1539
1539
  }
1540
1540
 
1541
1541
  // ../game-contracts/dist/index.js
1542
+ var DEFAULT_BLAST_IMPULSE = { bodyArea: 0.7, lift: 0.15, minSpeed: 0.3, tntPerJoule: 0.24 / 2e5 };
1542
1543
  var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
1543
1544
  var DEFAULT_IGNITION_RULES = {
1544
1545
  fillCurve: [
@@ -3408,17 +3409,17 @@ var GameFloorplanThree = (() => {
3408
3409
  const index = geometry2.index ? geometry2.index.array : null;
3409
3410
  const count = index ? index.length : position.count;
3410
3411
  for (let item = 0; item < count; item++) {
3411
- const at = index ? index[item] : item;
3412
- vertex.fromBufferAttribute(position, at).applyMatrix4(matrix);
3412
+ const at2 = index ? index[item] : item;
3413
+ vertex.fromBufferAttribute(position, at2).applyMatrix4(matrix);
3413
3414
  positions2.push(vertex.x, vertex.y, vertex.z);
3414
- if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix2).normalize();
3415
+ if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at2).applyMatrix3(normalMatrix2).normalize();
3415
3416
  else normal.set(0, 1, 0);
3416
3417
  normals.push(normal.x, normal.y, normal.z);
3417
3418
  color2.copy(material2.color || color2.set(1, 1, 1)).convertLinearToSRGB();
3418
3419
  if (sample && uv && material2.map) {
3419
3420
  const repeat = material2.map.repeat;
3420
- const u = (uv.getX(at) * repeat.x % 1 + 1) % 1;
3421
- const v = (uv.getY(at) * repeat.y % 1 + 1) % 1;
3421
+ const u = (uv.getX(at2) * repeat.x % 1 + 1) % 1;
3422
+ const v = (uv.getY(at2) * repeat.y % 1 + 1) % 1;
3422
3423
  const pixelX = Math.min(sample.width - 1, u * sample.width | 0);
3423
3424
  const pixelY = Math.min(sample.height - 1, (1 - v) * sample.height | 0);
3424
3425
  const offset = (pixelY * sample.width + pixelX) * 4;
@@ -9871,8 +9872,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9871
9872
  // was `maquina`
9872
9873
  });
9873
9874
  var OWN_TYPES = Object.freeze({ lamp: true, gas_piped: true });
9875
+ var sourceOf = (result) => result;
9874
9876
  var ADAPTER_VERSION = "0.3.0";
9875
9877
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
9878
+ var at = (it) => ({ x: it.x ?? 0, z: it.z ?? 0, ry: it.ry ?? 0 });
9876
9879
  function mergeRegistry(base, extra) {
9877
9880
  const out = { ...base };
9878
9881
  if (extra) {
@@ -9891,14 +9894,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9891
9894
  });
9892
9895
  function create2(options = {}) {
9893
9896
  const opts = options || {};
9894
- const GI = opts.GameInteractables;
9895
- const HH = opts.GameInteractablesHousehold;
9896
9897
  const locale = opts.locale || "pt-BR";
9897
9898
  const msg = createTranslator2(locale, opts.messages);
9898
- if (!GI || !HH) throw new Error(msg("error.missingKit"));
9899
+ if (!opts.GameInteractables || !opts.GameInteractablesHousehold) throw new Error(msg("error.missingKit"));
9900
+ const GI = opts.GameInteractables;
9901
+ const HH = opts.GameInteractablesHousehold;
9899
9902
  const region = opts.region || "br";
9900
9903
  const hh = HH.createHousehold({ locale, region });
9901
9904
  const householdTypes = new Set(hh.types.map((t) => t.id));
9905
+ const recorderApi = () => HH.createRecorder().api;
9902
9906
  const worlds = /* @__PURE__ */ new WeakMap();
9903
9907
  const typeMap = mergeRegistry(TYPE_MAP, opts.typeMap);
9904
9908
  const contentMap = mergeRegistry(CONTENT_MAP, opts.contentMap);
@@ -9934,7 +9938,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9934
9938
  function colorOf(res, color2) {
9935
9939
  if (color2 == null) return void 0;
9936
9940
  if (typeof color2 === "number") return color2;
9937
- const v = res.colors && res.colors[color2];
9941
+ const v = res.colors && res.colors[String(color2)];
9938
9942
  return typeof v === "number" ? v : void 0;
9939
9943
  }
9940
9944
  function paletteOf(res) {
@@ -9963,17 +9967,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9963
9967
  drawerItem: (s) => drawerItems.has(s.itemId)
9964
9968
  };
9965
9969
  const itemOfContent = (c) => {
9966
- const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || contentByGroup[stackGroup(c)] || contentFallback;
9970
+ const group = stackGroup(c);
9971
+ const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || (group != null ? contentByGroup[group] : void 0) || contentFallback;
9967
9972
  return hh.items[id] ? id : contentFallback;
9968
9973
  };
9969
9974
  const volume = (d) => d.size[0] * d.size[1] * d.size[2];
9970
9975
  const candidates = (id, objectType) => {
9971
9976
  const def = hh.items[id];
9972
- const siblings = Object.values(hh.items).filter((i) => i.category === def.category && i.id !== id).sort((a, b) => volume(a) - volume(b) || (a.id < b.id ? -1 : 1));
9977
+ const siblings = Object.values(hh.items).filter((i) => i.category === def?.category && i.id !== id).sort((a, b) => volume(a) - volume(b) || (a.id < b.id ? -1 : 1));
9973
9978
  return [id].concat(siblings.map((i) => i.id), substitutes[objectType] || []);
9974
9979
  };
9975
9980
  function distribute(type, probeItem, stacks) {
9976
- const spec = hh.build(type, probeItem, HH.createRecorder().api);
9981
+ const spec = hh.build(type, probeItem, recorderApi());
9977
9982
  const conts = spec.containers || [];
9978
9983
  const contents = {};
9979
9984
  for (const c of conts) contents[c.id] = c.slots.map(() => null);
@@ -10004,8 +10009,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10004
10009
  }
10005
10010
  return { contents, rejected };
10006
10011
  }
10007
- function plan(res, popts = {}) {
10008
- popts = popts || {};
10012
+ function plan(source, options2 = {}) {
10013
+ const res = sourceOf(source);
10014
+ const popts = options2 || {};
10009
10015
  const o = { ox: popts.originX || 0, oz: popts.originZ || 0, inw: popts.inw || 1, base: popts.base || 0 };
10010
10016
  const K = conv(o);
10011
10017
  const floorSel = popts.floor;
@@ -10069,7 +10075,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10069
10075
  const item = {
10070
10076
  x: K.x(m.x),
10071
10077
  z: K.z(m.z),
10072
- ry: m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0),
10078
+ ry: m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0),
10073
10079
  w: m.w,
10074
10080
  d: m.d,
10075
10081
  h: m.h,
@@ -10082,7 +10088,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10082
10088
  return item;
10083
10089
  }
10084
10090
  function entry2(id, type, item, origin, floor, extra) {
10085
- return Object.assign({ id, type, item, origin, floor, floorY: floorYOf(res, o, floor), connections: {} }, extra || {});
10091
+ const e = { id, type, item, origin, floor, floorY: floorYOf(res, o, floor), connections: {} };
10092
+ return Object.assign(e, extra || {});
10086
10093
  }
10087
10094
  function store(targets, stacks) {
10088
10095
  let rest2 = stacks || [];
@@ -10095,10 +10102,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10095
10102
  }
10096
10103
  const hidden = [];
10097
10104
  function distRect(c, x, z) {
10098
- const dx = x - c.item.x;
10099
- const dz = z - c.item.z;
10100
- const s = Math.sin(c.item.ry);
10101
- const co = Math.cos(c.item.ry);
10105
+ const ci = at(c.item);
10106
+ const dx = x - ci.x;
10107
+ const dz = z - ci.z;
10108
+ const s = Math.sin(ci.ry);
10109
+ const co = Math.cos(ci.ry);
10102
10110
  const lx = dx * co - dz * s;
10103
10111
  const lz = dx * s + dz * co;
10104
10112
  return Math.hypot(Math.max(Math.abs(lx) - (c.item.w || 0) / 2, 0), Math.max(Math.abs(lz) - (c.item.d || 0) / 2, 0));
@@ -10106,9 +10114,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10106
10114
  function hideCylinders() {
10107
10115
  for (const b of hidden) {
10108
10116
  let target = null;
10117
+ const bi = at(b.item);
10109
10118
  for (const c2 of result.items) {
10110
10119
  if (c2.type !== "kitchen_cabinet_base" || c2.floor !== b.floor) continue;
10111
- const dist = distRect(c2, b.item.x, b.item.z);
10120
+ const dist = distRect(c2, bi.x, bi.z);
10112
10121
  if (!target || dist < target.dist) target = { c: c2, dist };
10113
10122
  }
10114
10123
  if (!target) {
@@ -10116,16 +10125,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10116
10125
  continue;
10117
10126
  }
10118
10127
  const c = target.c;
10119
- const spec = hh.build(c.type, c.item, HH.createRecorder().api);
10120
- const sn = Math.sin(c.item.ry);
10121
- const cs = Math.cos(c.item.ry);
10128
+ const spec = hh.build(c.type, c.item, recorderApi());
10129
+ const ci = at(c.item);
10130
+ const sn = Math.sin(ci.ry);
10131
+ const cs = Math.cos(ci.ry);
10122
10132
  let door = null;
10123
10133
  for (const it of spec.interactions || []) {
10124
- const pt = it.action && it.action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === it.action.part) : null;
10125
- if (!pt || pt.motion.kind !== "hinge") continue;
10126
- const wx = c.item.x + it.pos[0] * cs + it.pos[2] * sn;
10127
- const wz = c.item.z - it.pos[0] * sn + it.pos[2] * cs;
10128
- const dist = Math.hypot(wx - b.item.x, wz - b.item.z);
10134
+ const action = it.action;
10135
+ const pt = action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === action.part) : null;
10136
+ if (!pt || !pt.motion || pt.motion.kind !== "hinge") continue;
10137
+ const wx = ci.x + it.pos[0] * cs + it.pos[2] * sn;
10138
+ const wz = ci.z - it.pos[0] * sn + it.pos[2] * cs;
10139
+ const dist = Math.hypot(wx - bi.x, wz - bi.z);
10129
10140
  if (!door || dist < door.dist) door = { id: pt.id, dist };
10130
10141
  }
10131
10142
  if (!door) {
@@ -10155,9 +10166,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10155
10166
  const campCylinder = firstOfType("gas_canister");
10156
10167
  function planFireplace(m, e, item) {
10157
10168
  const def = hh.types.find((t) => t.id === "fireplace");
10158
- const lack = (def.d - m.d) / 2;
10159
- item.x = r3(item.x + Math.sin(item.ry) * lack);
10160
- item.z = r3(item.z + Math.cos(item.ry) * lack);
10169
+ const lack = ((def ? def.d : m.d) - m.d) / 2;
10170
+ const p = at(item);
10171
+ item.x = r3(p.x + Math.sin(p.ry) * lack);
10172
+ item.z = r3(p.z + Math.cos(p.ry) * lack);
10161
10173
  const room = (res.rooms || []).find((c) => c.id === m.room && c.floor === m.floor);
10162
10174
  const f = (res.floors || []).find((x) => x.index === m.floor);
10163
10175
  item.h = r3(room && room.ceiling || f && f.ceiling || 2.6);
@@ -10185,7 +10197,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10185
10197
  e.connections.gas = m.gas || "cylinder";
10186
10198
  e.connections.cylinder = m.cylinder || null;
10187
10199
  }
10188
- if (map.type === "gas_bottle") result.cylinders.push({ id: e.id, stove: m.link || null, place: m.placement || null });
10200
+ if (map.type === "gas_bottle") result.cylinders.push({ id: e.id, stove: typeof m.link === "string" ? m.link : null, place: m.placement || null });
10189
10201
  if (map.type === "gas_canister") {
10190
10202
  result.canisters.push(e.id);
10191
10203
  if (campCylinder && campCylinder.id === m.id && campStove) result.cylinders.push({ id: e.id, stove: campStove.id, place: null });
@@ -10209,7 +10221,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10209
10221
  push(e);
10210
10222
  }
10211
10223
  function planCompound(m, kind) {
10212
- const ry = m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0);
10224
+ const ry = m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0);
10213
10225
  const cx = K.x(m.x);
10214
10226
  const cz = K.z(m.z);
10215
10227
  const part = (id, type, w, d, h, y, lx, lz) => {
@@ -10261,11 +10273,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10261
10273
  } else {
10262
10274
  const basin = part(m.id, "sink_bathroom", m.w, m.d, m.h + 0.2, 0, 0, 0);
10263
10275
  list2.push(basin);
10264
- if (contents[m.id]) {
10265
- if (contents[m.id].length) {
10276
+ const own = contents[m.id];
10277
+ if (own) {
10278
+ if (own.length) {
10266
10279
  const cabinet = part(m.id + "#cabinet", "bathroom_cabinet", Math.max(0.45, Math.min(0.6, m.w)), 0.15, 0.75, 1.3, 0, -(m.d / 2) + 0.075);
10267
10280
  list2.push(cabinet);
10268
- store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], contents[m.id]);
10281
+ store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], own);
10269
10282
  } else {
10270
10283
  store([["sink_bathroom", basin.item]], []);
10271
10284
  }
@@ -10305,7 +10318,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10305
10318
  result.chairs = [];
10306
10319
  if (opts.chairs !== false && popts.chairs !== false) {
10307
10320
  const blocked = blockedOf(res, { originX: o.ox, originZ: o.oz, inw: o.inw });
10308
- const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && q.aabb;
10321
+ const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && !!q.aabb;
10309
10322
  const placed = [];
10310
10323
  const free = (m, cu, cv, half) => {
10311
10324
  const a = [cu - half + 0.03, cv - half + 0.03, cu + half - 0.03, cv + half - 0.03];
@@ -10321,8 +10334,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10321
10334
  for (const m of res.items || []) {
10322
10335
  const mode = chairTables[m.type];
10323
10336
  if (!mode || !onFloor(m.floor, m.outdoor) || m.frontU == null) continue;
10324
- const f = [m.frontU, m.frontV];
10325
- const pp = [-m.frontV, m.frontU];
10337
+ const fv = m.frontV ?? 0;
10338
+ const f = [m.frontU, fv];
10339
+ const pp = [-fv, m.frontU];
10326
10340
  const seats = [];
10327
10341
  if (mode === "dining") {
10328
10342
  const n = Math.max(1, Math.min(3, Math.floor((m.w - 0.1) / 0.55)));
@@ -10391,8 +10405,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10391
10405
  const item = { x: K.x(s.u), z: K.z(s.v), ry: K.ry(s.frontU, s.frontV), y: s.y, seed: seedOf2(s.id) };
10392
10406
  item.controls = (s.controls || []).map((id) => id + ":lamp");
10393
10407
  const e = entry2(s.id, "wall_switch", item, s.id, s.floor);
10394
- e.connections.controls = (s.controls || []).slice();
10395
- e.lit = e.connections.controls.some((id) => (result.items.find((x) => x.id === id) || {}).lit === true);
10408
+ const controls = (s.controls || []).slice();
10409
+ e.connections.controls = controls;
10410
+ e.lit = controls.some((id) => result.items.find((x) => x.id === id)?.lit === true);
10396
10411
  if (floorSel == null) {
10397
10412
  for (const id of s.controls || []) if (!lights.has(id)) result.warnings.push(msg("warn.switchUnknownLamp", { id: s.id, lamp: id }));
10398
10413
  }
@@ -10432,7 +10447,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10432
10447
  seed: seedOf2("panel"),
10433
10448
  circuits: circuits.map((c) => ({ id: c.id, label: c.room }))
10434
10449
  };
10435
- if (!item.circuits.length) delete item.circuits;
10450
+ if (!item.circuits?.length) delete item.circuits;
10436
10451
  const e = entry2("panel", "electrical_panel", item, "panel", q.floor);
10437
10452
  push(e);
10438
10453
  result.panel = e.id;
@@ -10454,10 +10469,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10454
10469
  }
10455
10470
  }
10456
10471
  const piped = result.items.filter((e) => e.type === "stove" && e.connections.gas === "piped");
10457
- if (piped.length) {
10472
+ const firstPiped = piped[0];
10473
+ if (firstPiped) {
10458
10474
  const med = utilities.gas && utilities.gas.meter;
10459
- const ref = med ? { x: K.x(med.u), z: K.z(med.v), y: med.y || 1 } : { x: piped[0].item.x, z: piped[0].item.z, y: 0.3 };
10460
- const e = entry2("piped_gas", "gas_piped", { x: ref.x, z: ref.z, ry: 0, y: ref.y, seed: seedOf2("piped_gas") }, "piped_gas", piped[0].floor);
10475
+ const ref = med ? { x: K.x(med.u), z: K.z(med.v), y: med.y || 1 } : { x: firstPiped.item.x, z: firstPiped.item.z, y: 0.3 };
10476
+ const e = entry2("piped_gas", "gas_piped", { x: ref.x, z: ref.z, ry: 0, y: ref.y, seed: seedOf2("piped_gas") }, "piped_gas", firstPiped.floor);
10461
10477
  e.floorY = o.base;
10462
10478
  push(e);
10463
10479
  for (const f of piped) f.connections.fuel = "piped_gas:gas";
@@ -10504,9 +10520,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10504
10520
  for (const dv of spec.devices || []) if (L.outlet && dv.socket === "auto") dv.socket = L.outlet + ":socket";
10505
10521
  } else {
10506
10522
  const sh = L.shower || {};
10507
- const heater = (spec.devices || []).find((d) => d.id === "heater");
10523
+ const devices = spec.devices || [];
10524
+ const heater = devices.find((d) => d.id === "heater");
10508
10525
  if (heater && sh.electric === false) {
10509
- spec.devices = spec.devices.filter((d) => d !== heater);
10526
+ spec.devices = devices.filter((d) => d !== heater);
10510
10527
  for (const f of spec.fixtures || []) if (f.heater === "heater") delete f.heater;
10511
10528
  } else if (heater) {
10512
10529
  if (sh.powerW) heater.watts = sh.powerW;
@@ -10517,7 +10534,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10517
10534
  for (const b of spec.burners || []) if (b.fuel === "auto") b.fuel = L.fuel;
10518
10535
  }
10519
10536
  if (entry2.type === "fireplace" && L.chimneyTop) {
10520
- const it = entry2.item;
10537
+ const it = at(entry2.item);
10521
10538
  const dx = L.chimneyTop[0] - it.x;
10522
10539
  const dz = L.chimneyTop[2] - it.z;
10523
10540
  const lx = Math.cos(it.ry) * dx - Math.sin(it.ry) * dz;
@@ -10536,8 +10553,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10536
10553
  }
10537
10554
  return spec;
10538
10555
  }
10539
- function createWorld(res, copts = {}) {
10540
- copts = copts || {};
10556
+ function createWorld(source, options2 = {}) {
10557
+ const res = sourceOf(source);
10558
+ const copts = options2 || {};
10541
10559
  const info = { points: /* @__PURE__ */ new Map(), water: copts.water || null, lightsOn: copts.lightsOn };
10542
10560
  const electrical = res.utilities && res.utilities.electrical;
10543
10561
  const world = GI.createWorld({
@@ -10565,8 +10583,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10565
10583
  const info = worlds.get(world);
10566
10584
  return info ? info.points.get(fixtureId) : void 0;
10567
10585
  }
10568
- function blockedOf(res, bopts = {}) {
10569
- bopts = bopts || {};
10586
+ function blockedOf(source, options2 = {}) {
10587
+ const res = sourceOf(source);
10588
+ const bopts = options2 || {};
10570
10589
  const ox = bopts.originX || 0;
10571
10590
  const oz = bopts.originZ || 0;
10572
10591
  const inw = bopts.inw || 1;
@@ -10601,33 +10620,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10601
10620
  return false;
10602
10621
  };
10603
10622
  }
10604
- function add(world, planned, aopts = {}) {
10605
- aopts = aopts || {};
10623
+ function add(world, planned, options2 = {}) {
10624
+ const aopts = options2 || {};
10606
10625
  const info = worlds.get(world);
10607
- const build = aopts.build || ((type, item) => hh.build(type, item, HH.createRecorder().api));
10626
+ const build = aopts.build || ((type, item) => hh.build(type, item, recorderApi()));
10608
10627
  const isBlocked = aopts.isBlocked || null;
10609
10628
  const out = { added: 0, parts: [], hinges: {}, moved: {}, warnings: [] };
10610
10629
  const override = aopts.lightsOn !== void 0 ? aopts.lightsOn : info && info.lightsOn;
10611
10630
  if (override !== void 0) {
10612
10631
  for (const e of planned.items) if (e.type === "lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
10613
10632
  for (const e of planned.items) if (e.type === "lantern_lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
10614
- for (const e of planned.items) if (e.type === "wall_switch") e.lit = (e.connections.controls || []).some((id) => (planned.items.find((x) => x.id === id) || {}).lit === true);
10633
+ for (const e of planned.items) if (e.type === "wall_switch") e.lit = (e.connections.controls || []).some((id) => planned.items.find((x) => x.id === id)?.lit === true);
10615
10634
  }
10616
10635
  for (const e of planned.items) if (e.type === "lantern_lamp") e.item.on = !!e.lit;
10617
10636
  let scratch = null;
10618
- function probe(e, hinge, setback) {
10637
+ function probe(e, hinge, setback, blockedAt) {
10619
10638
  if (!scratch) scratch = GI.createWorld({ locale, strings: hh.strings, items: hh.items, loot: hh.loot, grid: false });
10620
- const it = e.item;
10639
+ const it = at(e.item);
10621
10640
  const x = it.x + Math.sin(it.ry) * setback;
10622
10641
  const z = it.z + Math.cos(it.ry) * setback;
10623
- const spec = hh.build(e.type, Object.assign({}, it, { hinge }), HH.createRecorder().api);
10642
+ const spec = hh.build(e.type, Object.assign({}, e.item, { hinge }), recorderApi());
10624
10643
  scratch.add("__probe", spec, { x, y: e.floorY, z, ry: it.ry });
10625
- const blocks = (px, pz) => isBlocked(px, pz, e.floor);
10644
+ const blocks = (px, pz) => blockedAt(px, pz, e.floor);
10626
10645
  let sum = 0;
10627
10646
  let min = 1;
10628
10647
  let n = 0;
10629
10648
  for (const p of spec.parts || []) {
10630
- if (p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
10649
+ if (!p.motion || p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
10631
10650
  const t = scratch.fitSweep("__probe", p.id, blocks);
10632
10651
  sum += t;
10633
10652
  min = Math.min(min, t);
@@ -10639,12 +10658,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10639
10658
  const better = (a, b) => a.sum > b.sum + 1e-9 || Math.abs(a.sum - b.sum) <= 1e-9 && a.min > b.min + 1e-9;
10640
10659
  function fitDoors(e) {
10641
10660
  if (!isBlocked || !householdTypes.has(e.type)) return;
10642
- const standard = probe(e, void 0, 0);
10661
+ const standard = probe(e, void 0, 0, isBlocked);
10643
10662
  if (!standard.n || standard.min >= 1) return;
10644
10663
  let bestSide;
10645
10664
  let m = standard;
10646
10665
  for (const side of ["left", "right"]) {
10647
- const r = probe(e, side, 0);
10666
+ const r = probe(e, side, 0, isBlocked);
10648
10667
  if (better(r, m)) {
10649
10668
  m = r;
10650
10669
  bestSide = side;
@@ -10657,13 +10676,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10657
10676
  if (m.min >= 1) return;
10658
10677
  let target = { setback: 0, r: m };
10659
10678
  for (const setback of [0.01, 0.02, 0.03, 0.04]) {
10660
- const r = probe(e, bestSide, setback);
10679
+ const r = probe(e, bestSide, setback, isBlocked);
10661
10680
  if (better(r, target.r)) target = { setback, r };
10662
10681
  if (r.min >= 1) break;
10663
10682
  }
10664
10683
  if (target.setback > 0 && target.r.sum > m.sum + 0.2) {
10665
- e.item.x = r3(e.item.x + Math.sin(e.item.ry) * target.setback);
10666
- e.item.z = r3(e.item.z + Math.cos(e.item.ry) * target.setback);
10684
+ const p = at(e.item);
10685
+ e.item.x = r3(p.x + Math.sin(p.ry) * target.setback);
10686
+ e.item.z = r3(p.z + Math.cos(p.ry) * target.setback);
10667
10687
  e.moved = target.setback;
10668
10688
  out.moved[e.id] = target.setback;
10669
10689
  }
@@ -10686,7 +10706,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10686
10706
  }
10687
10707
  }
10688
10708
  connect(e, spec);
10689
- world.add(e.id, spec, { x: e.item.x, y: e.floorY, z: e.item.z, ry: e.item.ry });
10709
+ const pose = at(e.item);
10710
+ world.add(e.id, spec, { x: pose.x, y: e.floorY, z: pose.z, ry: pose.ry });
10690
10711
  out.added++;
10691
10712
  if (!isBlocked) continue;
10692
10713
  for (const p of spec.parts || []) {
@@ -10926,22 +10947,22 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10926
10947
  A.stats.external = Ne;
10927
10948
  let shadows = 0;
10928
10949
  for (let i = 0; i < N; i++) {
10929
- const g = asg[i], at = slotOf(i), b0 = LZU.uRB0.value, b1 = LZU.uRB1.value, b2 = LZU.uRB2.value, by = LZU.uRBY.value, ex = LZU.uREx.value;
10950
+ const g = asg[i], at2 = slotOf(i), b0 = LZU.uRB0.value, b1 = LZU.uRB1.value, b2 = LZU.uRB2.value, by = LZU.uRBY.value, ex = LZU.uREx.value;
10930
10951
  A.crates[i] = g ? { k: g.k, rects: g.rects, y: g.y, exterior: !!g.exterior, exclusion: g.exclusion || null, d: g.d } : null;
10931
10952
  if (!g) {
10932
10953
  put(i, { intensity: 0 });
10933
- for (const a of [b0, b1, b2, ex]) a.set(EMPTY, at * 4);
10934
- by.set([0, 0], at * 2);
10954
+ for (const a of [b0, b1, b2, ex]) a.set(EMPTY, at2 * 4);
10955
+ by.set([0, 0], at2 * 2);
10935
10956
  continue;
10936
10957
  }
10937
10958
  const lamp = g.exterior ? { intensity: g.lumens, distance: g.dist, decay: g.decay } : { intensity: (7 + g.range * 0.8) * lim(1 + 0.4 * (Math.sqrt(g.n) - 1), 1, 2.2), distance: Math.max(3, g.dm * 1.3 + 0.5), decay: 1.6 };
10938
10959
  put(i, { position: g.pos, color: g.color, ...lamp });
10939
10960
  const rs = g.rects;
10940
- b0.set(rs[0] || EMPTY, at * 4);
10941
- b1.set(rs[1] || EMPTY, at * 4);
10942
- b2.set(rs[2] || EMPTY, at * 4);
10943
- by.set(g.y, at * 2);
10944
- ex.set(g.exclusion || EMPTY, at * 4);
10961
+ b0.set(rs[0] || EMPTY, at2 * 4);
10962
+ b1.set(rs[1] || EMPTY, at2 * 4);
10963
+ b2.set(rs[2] || EMPTY, at2 * 4);
10964
+ by.set(g.y, at2 * 2);
10965
+ ex.set(g.exclusion || EMPTY, at2 * 4);
10945
10966
  if (i < NS) {
10946
10967
  shadows++;
10947
10968
  const s2 = g.house.group.uuid + "|" + g.k + "|" + g.pos.map((v) => v.toFixed(2)).join(",");
@@ -11016,10 +11037,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11016
11037
  for (const p of list2 || []) {
11017
11038
  if (isDoor && !(open.has(p.id) ? open.get(p.id) : !!p.open)) continue;
11018
11039
  const alongU = p.axis === "u", y = house.y0(p.floor) + (isDoor ? 1 : ((p.y0 != null ? p.y0 : 0.9) + (p.y1 != null ? p.y1 : 2)) / 2);
11019
- const at = (side) => _p.set(alongU ? p.at : p.pos + side, y, alongU ? p.pos + side : p.at).applyMatrix4(M).clone();
11020
- if (at(0).distanceTo(P0) > OPENING_REACH) continue;
11040
+ const at2 = (side) => _p.set(alongU ? p.at : p.pos + side, y, alongU ? p.pos + side : p.at).applyMatrix4(M).clone();
11041
+ if (at2(0).distanceTo(P0) > OPENING_REACH) continue;
11021
11042
  for (const side of [-OPENING_SIDE, OPENING_SIDE]) {
11022
- const w = at(side), hit = boxAt(boxes, w.x, w.y, w.z);
11043
+ const w = at2(side), hit = boxAt(boxes, w.x, w.y, w.z);
11023
11044
  if (hit) into.set(hit.c, hit);
11024
11045
  }
11025
11046
  }
@@ -11027,11 +11048,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11027
11048
  reach(house.r.doors, true);
11028
11049
  reach(house.r.windows, false);
11029
11050
  };
11030
- const clear = (at) => {
11031
- for (const key of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[key].value.set(EMPTY, at * 4);
11032
- LZU.uRBY.value.set([0, 0], at * 2);
11051
+ const clear = (at2) => {
11052
+ for (const key of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[key].value.set(EMPTY, at2 * 4);
11053
+ LZU.uRBY.value.set([0, 0], at2 * 2);
11033
11054
  };
11034
- const write = (at, into) => {
11055
+ const write = (at2, into) => {
11035
11056
  const rs = [];
11036
11057
  let lo = 1e9, hi = -1e9;
11037
11058
  for (const q of into.values()) {
@@ -11043,21 +11064,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11043
11064
  const r23 = rs.pop(), r1 = rs[rs.length - 1];
11044
11065
  rs[rs.length - 1] = [Math.min(r1[0], r23[0]), Math.min(r1[1], r23[1]), Math.max(r1[2], r23[2]), Math.max(r1[3], r23[3])];
11045
11066
  }
11046
- LZU.uRB0.value.set(rs[0] || EMPTY, at * 4);
11047
- LZU.uRB1.value.set(rs[1] || EMPTY, at * 4);
11048
- LZU.uRB2.value.set(rs[2] || EMPTY, at * 4);
11049
- LZU.uRBY.value.set([lo, hi], at * 2);
11050
- LZU.uREx.value.set(EMPTY, at * 4);
11067
+ LZU.uRB0.value.set(rs[0] || EMPTY, at2 * 4);
11068
+ LZU.uRB1.value.set(rs[1] || EMPTY, at2 * 4);
11069
+ LZU.uRB2.value.set(rs[2] || EMPTY, at2 * 4);
11070
+ LZU.uRBY.value.set([lo, hi], at2 * 2);
11071
+ LZU.uREx.value.set(EMPTY, at2 * 4);
11051
11072
  };
11052
11073
  const A = {
11053
11074
  /** Once per frame, after the budget's `present`: points the mask of every lit slot that is not the house's at the rooms its light reaches. */
11054
11075
  update(houses2) {
11055
11076
  const indoors = cfg.effectLightsIndoors !== false, list2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean), cache2 = /* @__PURE__ */ new Map();
11056
- for (let at = 0; at < size; at++) {
11057
- if (ours.has(at)) continue;
11058
- const light = budget.group.children[at];
11077
+ for (let at2 = 0; at2 < size; at2++) {
11078
+ if (ours.has(at2)) continue;
11079
+ const light = budget.group.children[at2];
11059
11080
  if (!indoors || !light || !light.isPointLight || !(light.intensity > 0) || !light.visible) {
11060
- clear(at);
11081
+ clear(at2);
11061
11082
  continue;
11062
11083
  }
11063
11084
  const into = /* @__PURE__ */ new Map();
@@ -11072,8 +11093,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11072
11093
  if (here) into.set(here.c, here);
11073
11094
  openingSides(house, M, boxes, into);
11074
11095
  }
11075
- if (into.size) write(at, into);
11076
- else clear(at);
11096
+ if (into.size) write(at2, into);
11097
+ else clear(at2);
11077
11098
  }
11078
11099
  }
11079
11100
  };
@@ -11430,11 +11451,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11430
11451
  }
11431
11452
  return pieces.slice(0, limit).map((p) => ({ center: p.c, size: [p.s[g], p.s[a], p.s[b]], q: frame(g, a, b), shape: "splinter", weight: p.s[0] * p.s[1] * p.s[2] }));
11432
11453
  };
11433
- var severAt = (inp, root, at, gap) => {
11454
+ var severAt = (inp, root, at2, gap) => {
11434
11455
  const dims = root.s;
11435
11456
  const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
11436
11457
  const half = dims[L] / 2;
11437
- const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
11458
+ const lo = clamp(at2 - gap / 2, -half, half), hi = clamp(at2 + gap / 2, -half, half);
11438
11459
  const out = [];
11439
11460
  const stub = (from, to) => {
11440
11461
  if (to - from < 0.02) return;
@@ -13816,7 +13837,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13816
13837
  const typed = listeners.get(e.type);
13817
13838
  if (typed) for (const fn of [...typed]) fn(e);
13818
13839
  };
13819
- const base = (o, source) => source === void 0 ? { object: o.id, time } : { object: o.id, time, source };
13840
+ let blow;
13841
+ const base = (o, source) => ({
13842
+ object: o.id,
13843
+ time,
13844
+ ...source === void 0 ? {} : { source },
13845
+ ...blow === void 0 ? {} : { blow }
13846
+ });
13820
13847
  const pendingCollapse = /* @__PURE__ */ new Map();
13821
13848
  let runDepth = 0;
13822
13849
  const drainBlasts = () => {
@@ -14086,9 +14113,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14086
14113
  const [lo, hi] = contactBox(p, above);
14087
14114
  return toWorld(p.pos, p.q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
14088
14115
  };
14089
- const overSupportAt = (o, p, at, lower) => {
14116
+ const overSupportAt = (o, p, at2, lower) => {
14090
14117
  const g = p.bend;
14091
- const local = toLocal(p.pos, p.q, at);
14118
+ const local = toLocal(p.pos, p.q, at2);
14092
14119
  const depth = g.gravity >= 0 ? g.dims[g.gravity] : Math.min(...g.depthAxes.map((a) => g.dims[a]));
14093
14120
  let best = -1;
14094
14121
  let bestGap = depth;
@@ -14242,10 +14269,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14242
14269
  const incoming = new Array(n).fill(0);
14243
14270
  const bending = new Array(n).fill(0);
14244
14271
  const through = parts.map(() => []);
14245
- const pass = (k, at, mass) => {
14246
- const over = overSupportAt(o, parts[k], at, lowerOf.get(k) ?? []);
14272
+ const pass = (k, at2, mass) => {
14273
+ const over = overSupportAt(o, parts[k], at2, lowerOf.get(k) ?? []);
14247
14274
  if (over < 0) bending[k] += mass;
14248
- else through[k].push({ at, mass, over });
14275
+ else through[k].push({ at: at2, mass, over });
14249
14276
  };
14250
14277
  for (let h = queue.length - 1; h >= 0; h--) {
14251
14278
  const i = queue[h];
@@ -14312,7 +14339,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14312
14339
  const terrain = cfg.terrains.get(cfg.terrain);
14313
14340
  if (!terrain) throw new Error(`unknown wind terrain "${cfg.terrain}"`);
14314
14341
  const inputs = [];
14315
- const at = /* @__PURE__ */ new Map();
14342
+ const at2 = /* @__PURE__ */ new Map();
14316
14343
  const keep = [];
14317
14344
  let ground = Infinity, foundationTop = -Infinity;
14318
14345
  o.parts.forEach((p, i) => {
@@ -14321,7 +14348,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14321
14348
  const role = p.spec.structure?.role;
14322
14349
  const e = p.mat.strength?.elasticGPa;
14323
14350
  const kind = role === "wall" ? "wall" : role === "column" ? "column" : role === "beam" ? "beam" : role === "foundation" ? "foundation" : "other";
14324
- at.set(i, inputs.length);
14351
+ at2.set(i, inputs.length);
14325
14352
  keep.push(i);
14326
14353
  ground = Math.min(ground, box3.min[1]);
14327
14354
  if (kind === "foundation") foundationTop = Math.max(foundationTop, box3.max[1]);
@@ -14332,7 +14359,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14332
14359
  const beams = [];
14333
14360
  for (const j of o.byPart.get(o.parts[i].spec.id)) {
14334
14361
  if (j.st.open) continue;
14335
- const b = at.get(j.ia === i ? j.ib : j.ia);
14362
+ const b = at2.get(j.ia === i ? j.ib : j.ia);
14336
14363
  if (b !== void 0 && inputs[b].kind === "beam") beams.push(b);
14337
14364
  }
14338
14365
  inputs[k].beams = beams;
@@ -15148,7 +15175,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15148
15175
  }
15149
15176
  };
15150
15177
  const tornToEdge = (o) => {
15151
- const { p, cell, a, b, at, crater: crater2, reach, gone } = o;
15178
+ const { p, cell, a, b, at: at2, crater: crater2, reach, gone } = o;
15152
15179
  const half = p.dims[a] / 2;
15153
15180
  const lo = cell.center[a] - cell.size[a] / 2, hi = cell.center[a] + cell.size[a] / 2;
15154
15181
  const free = (side, sign2) => {
@@ -15156,8 +15183,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15156
15183
  const probe = side + sign2 * 1e-3;
15157
15184
  return gridOf(p).some((c, k) => gone(k) && Math.abs(probe - c.center[a]) <= c.size[a] / 2 && Math.abs(cell.center[b] - c.center[b]) <= c.size[b] / 2);
15158
15185
  };
15159
- if (at - lo <= reach && free(lo, -1)) return [lo, Math.max(crater2[1], lo)];
15160
- if (hi - at <= reach && free(hi, 1)) return [Math.min(crater2[0], hi), hi];
15186
+ if (at2 - lo <= reach && free(lo, -1)) return [lo, Math.max(crater2[1], lo)];
15187
+ if (hi - at2 <= reach && free(hi, 1)) return [Math.min(crater2[0], hi), hi];
15161
15188
  return crater2;
15162
15189
  };
15163
15190
  const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
@@ -15367,7 +15394,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15367
15394
  return;
15368
15395
  }
15369
15396
  if (ex) {
15370
- emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
15397
+ emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.tnt !== void 0 ? { tnt: ex.tnt } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
15371
15398
  blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
15372
15399
  }
15373
15400
  partLost(o, p, src);
@@ -15961,7 +15988,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15961
15988
  res.events = cur.slice(start);
15962
15989
  return res;
15963
15990
  };
15964
- const doHit = (o, hit, only) => {
15991
+ const withBlow = (type, fn) => () => {
15992
+ const outer = blow;
15993
+ blow = type;
15994
+ try {
15995
+ return fn();
15996
+ } finally {
15997
+ blow = outer;
15998
+ }
15999
+ };
16000
+ const doHit = (o, hit, only) => withBlow(hit.type, () => resolveHit(o, hit, only))();
16001
+ const resolveHit = (o, hit, only) => {
15965
16002
  let point = hit.point;
15966
16003
  let dir = hit.direction;
15967
16004
  if (hit.space === "local") {
@@ -16400,6 +16437,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16400
16437
  blastProbe() {
16401
16438
  return { ...blastProbe };
16402
16439
  },
16440
+ blastReach(point, target, options2 = {}) {
16441
+ const L = vec.distance(point, target);
16442
+ if (L < 1e-4) return 1;
16443
+ const shelter = { ...SHELTER_DEFAULTS, ...options2.shelter };
16444
+ const ignore = new Set(options2.ignore ?? []);
16445
+ const d = vec.scale(vec.sub(target, point), 1 / L);
16446
+ let t = 1;
16447
+ for (const o of objects.values()) {
16448
+ if (ignore.has(o.id) || raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
16449
+ for (const p of o.parts) {
16450
+ if (isLost(p) || p.st.stripped) continue;
16451
+ const r = rayPart(o, p, point, d);
16452
+ if (!r) continue;
16453
+ const t0 = Math.max(r.t0, 0);
16454
+ const path = Math.min(r.t1, L) - t0;
16455
+ if (path < shelter.minPath) continue;
16456
+ if (p.st.opening && insideOpening(o, p, toLocal(o.st.position, o.st.rotation, vec.madd(point, d, t0 + path / 2)))) continue;
16457
+ t *= Math.exp(-(p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1)) / shelter.k);
16458
+ if (t < 1e-4) return 0;
16459
+ }
16460
+ }
16461
+ for (const im of impls.values()) {
16462
+ if (ignore.has(im.impl.id)) continue;
16463
+ for (const ps of im.impl.describe().parts) {
16464
+ const mat = materials.get(ps.material);
16465
+ if (!mat) continue;
16466
+ const ov = im.poses.get(ps.id);
16467
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), point, d, ov?.pos);
16468
+ if (!r || r.t1 < 0 || r.t0 > L) continue;
16469
+ let path = Math.min(r.t1, L) - Math.max(r.t0, 0);
16470
+ if (ps.thickness !== void 0 && ps.thickness > 0) path = Math.min(path, 2 * ps.thickness);
16471
+ else if (path < shelter.minPath) continue;
16472
+ t *= Math.exp(-(mat.hpPerKg * mat.density * path * (mat.damageScale?.shelter ?? 1)) / shelter.k);
16473
+ if (t < 1e-4) return 0;
16474
+ }
16475
+ }
16476
+ return t;
16477
+ },
16403
16478
  explode(point, energy, radius, source, blast) {
16404
16479
  blastProbe.partsTested = 0;
16405
16480
  blastProbe.partsNear = 0;
@@ -16424,7 +16499,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16424
16499
  },
16425
16500
  shoot(origin, direction, energy, caliber, source, round3) {
16426
16501
  const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
16427
- return withCollapses(run(() => {
16502
+ return withCollapses(run(withBlow("ballistic", () => {
16428
16503
  const results = [];
16429
16504
  const d = vec.normalize(direction);
16430
16505
  const ignore = /* @__PURE__ */ new Set();
@@ -16435,10 +16510,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16435
16510
  if (!rh) break;
16436
16511
  const im = impls.get(rh.object);
16437
16512
  if (im) {
16438
- const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
16513
+ const scaled = extras.penetration && E < energy ? { ...extras, penetration: Object.fromEntries(Object.entries(extras.penetration).map(([k, mm]) => [k, mm * (E / energy)])) } : extras;
16514
+ const r = implHit(im, { type: "ballistic", point: rh.point, part: rh.part, normal: rh.normal, direction: d, energy: E, caliber, ...scaled, ...source !== void 0 ? { source } : {} });
16439
16515
  r.part = rh.part;
16440
16516
  r.point = rh.point;
16441
- r.exitEnergy = r.penetrated ? E * 0.5 : 0;
16517
+ const left = r.events.find((e) => e.type === "hole" && e.energyLeft !== void 0);
16518
+ r.exitEnergy = r.penetrated ? left?.energyLeft ?? E * 0.5 : 0;
16442
16519
  results.push(r);
16443
16520
  ignore.add(`${im.impl.id}\0${rh.part}`);
16444
16521
  if (!r.penetrated) break;
@@ -16457,7 +16534,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16457
16534
  E = out.exitEnergy;
16458
16535
  }
16459
16536
  return results;
16460
- }));
16537
+ })));
16461
16538
  },
16462
16539
  disassemble(objectId, jointId, tool, dt) {
16463
16540
  const im = impls.get(objectId);
@@ -16765,8 +16842,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16765
16842
  list: () => defs,
16766
16843
  get,
16767
16844
  register(def) {
16768
- const at = defs.findIndex((a) => a.id === def.id);
16769
- if (at >= 0) defs[at] = def;
16845
+ const at2 = defs.findIndex((a) => a.id === def.id);
16846
+ if (at2 >= 0) defs[at2] = def;
16770
16847
  else defs.push(def);
16771
16848
  },
16772
16849
  part: (assemblyId, options) => buildAssemblyPart(get(assemblyId), assemblyId, options)
@@ -17837,9 +17914,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17837
17914
  }
17838
17915
  if (context.isTimber && construction.roof.shape !== "slab") addCeilingJoists(context);
17839
17916
  }
17917
+ var isStair = (item) => item.category === "stairs" && typeof item.link === "object" && item.link !== null;
17840
17918
  function flightsOf(items) {
17841
17919
  const flights = /* @__PURE__ */ new Map();
17842
- for (const item of items.filter((it) => it.category === "stairs" && it.link)) {
17920
+ for (const item of items.filter(isStair)) {
17843
17921
  const key = `${item.link.from}_${item.link.to}`;
17844
17922
  if (!flights.has(key) || item.floor === item.link.from) flights.set(key, item);
17845
17923
  }
@@ -20219,17 +20297,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20219
20297
  for (const { c, part, units: units2, thick, mid } of shown) {
20220
20298
  if (!near(c)) continue;
20221
20299
  const centre = mask.start + (c.a + c.b) / 2, y = (c.y0 + c.y1) / 2, len = c.b - c.a, h = c.y1 - c.y0;
20222
- const at = (s, dy, d, colour) => {
20300
+ const at2 = (s, dy, d, colour) => {
20223
20301
  const unit = clipToBox({ x: mask.alongU ? centre : mid, y, z: mask.alongU ? mid : centre, sx: mask.alongU ? s : d, sy: dy, sz: mask.alongU ? d : s, colour }, view.bounds);
20224
20302
  if (unit) list2.push(unit);
20225
20303
  };
20226
20304
  const base = fragmentMaterial(part.material).color.getHex();
20227
20305
  const n = noise2(centre * 3.1, y * 2.7, 0.07);
20228
20306
  if (units2.mortar === "concrete") {
20229
- at(len, h, thick - 2 * COVER2, tone(base, 0.9 + 0.04 * n));
20307
+ at2(len, h, thick - 2 * COVER2, tone(base, 0.9 + 0.04 * n));
20230
20308
  } else {
20231
- at(len, h, thick - 2 * COVER2 - 0.012, 10327948);
20232
- at(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
20309
+ at2(len, h, thick - 2 * COVER2 - 0.012, 10327948);
20310
+ at2(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
20233
20311
  }
20234
20312
  }
20235
20313
  if (view.units) {
@@ -20545,11 +20623,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20545
20623
  };
20546
20624
  const extract = (it) => {
20547
20625
  const g0 = it.mesh.geometry, src = it.mesh.userData._p0 || g0.attributes.position.array, g = new THREE.BufferGeometry();
20548
- const at = (name, k, arr) => g.setAttribute(name, new THREE.BufferAttribute((arr || g0.attributes[name].array).slice(it.v0 * k, it.v1 * k), k));
20549
- at("position", 3, src);
20550
- at("normal", 3);
20551
- at("color", 3);
20552
- at("uv", 2);
20626
+ const at2 = (name, k, arr) => g.setAttribute(name, new THREE.BufferAttribute((arr || g0.attributes[name].array).slice(it.v0 * k, it.v1 * k), k));
20627
+ at2("position", 3, src);
20628
+ at2("normal", 3);
20629
+ at2("color", 3);
20630
+ at2("uv", 2);
20553
20631
  const ix = g0.index.array.slice(it.i0, it.i1);
20554
20632
  for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
20555
20633
  g.setIndex(new THREE.BufferAttribute(ix, 1));
@@ -21264,9 +21342,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21264
21342
  shared.vegMaterials.setActors(focus ? [{ x: focus.x, y: focus.y, z: focus.z, radius: 0.9 }] : []);
21265
21343
  }
21266
21344
  if (focus && cfg.lod !== false) {
21267
- const L = Object.assign({ interior: 45, hide: 220 }, cfg.lod), d = distance2(focus), at = h.lodCurrent;
21345
+ const L = Object.assign({ interior: 45, hide: 220 }, cfg.lod), d = distance2(focus), at2 = h.lodCurrent;
21268
21346
  let n = d > L.hide ? 2 : d > L.interior ? 1 : 0;
21269
- if (n < at) n = d < (at === 2 ? L.hide : L.interior) * 0.92 ? n : at;
21347
+ if (n < at2) n = d < (at2 === 2 ? L.hide : L.interior) * 0.92 ? n : at2;
21270
21348
  applyLod(Math.max(n, h.lodMin));
21271
21349
  }
21272
21350
  },
@@ -22017,14 +22095,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22017
22095
 
22018
22096
  // src/test-area/scenarios.ts
22019
22097
  var SPECIMEN_AT = [-5, 0, -11];
22020
- function createHouseScenarios(text, at = SPECIMEN_AT) {
22098
+ function createHouseScenarios(text, at2 = SPECIMEN_AT) {
22021
22099
  const T2 = text.steps;
22022
22100
  const spawn = (room, item) => {
22023
- const so = room.spawn(item, [at[0], at[1], at[2]], 0);
22101
+ const so = room.spawn(item, [at2[0], at2[1], at2[2]], 0);
22024
22102
  const parts = so.root.userData.floorplanSpec.parts;
22025
22103
  const centre = (id) => {
22026
22104
  const p = parts.find((x) => x.id === id);
22027
- return [p.position[0] + at[0], p.position[1] + at[1], p.position[2] + at[2]];
22105
+ return [p.position[0] + at2[0], p.position[1] + at2[1], p.position[2] + at2[2]];
22028
22106
  };
22029
22107
  return { oid: so.objectId, parts, centre };
22030
22108
  };
@@ -22399,9 +22477,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22399
22477
  };
22400
22478
  const eyeOf = (p) => {
22401
22479
  const camera = host?.camera;
22402
- const at = toLocal2(p, camera.position.x, camera.position.y, camera.position.z);
22480
+ const at2 = toLocal2(p, camera.position.x, camera.position.y, camera.position.z);
22403
22481
  const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion).transformDirection(new THREE.Matrix4().copy(p.house.group.matrixWorld).invert());
22404
- return { x: at.x, y: at.y, z: at.z, floor: floorOf(p, camera.position.y), yaw: Math.atan2(forward.x, forward.z), pitch: Math.asin(Math.max(-1, Math.min(1, forward.y))) };
22482
+ return { x: at2.x, y: at2.y, z: at2.z, floor: floorOf(p, camera.position.y), yaw: Math.atan2(forward.x, forward.z), pitch: Math.asin(Math.max(-1, Math.min(1, forward.y))) };
22405
22483
  };
22406
22484
  const blocked = (x, z, radius) => {
22407
22485
  for (const p of placed) {
@@ -22469,7 +22547,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22469
22547
  const doorReach = (p, fo) => {
22470
22548
  const g = fo.gs[0];
22471
22549
  const pivot = fo.handles[0].pivot;
22472
- const at = (angle) => {
22550
+ const at2 = (angle) => {
22473
22551
  const c = Math.cos(angle);
22474
22552
  const s = Math.sin(angle);
22475
22553
  const v = new THREE.Vector3(pivot[0] * c + pivot[2] * s, pivot[1], -pivot[0] * s + pivot[2] * c).add(g.position);
@@ -22478,8 +22556,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22478
22556
  const camera = host?.camera;
22479
22557
  const eye = new THREE.Vector3(camera.position.x, 0, camera.position.z);
22480
22558
  const flat = (v) => Math.hypot(v.x - eye.x, v.z - eye.z);
22481
- const closed = at(0);
22482
- const open = at(fo.rot);
22559
+ const closed = at2(0);
22560
+ const open = at2(fo.rot);
22483
22561
  const opening = !fo.open;
22484
22562
  return {
22485
22563
  handle: () => {
@@ -23187,8 +23265,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23187
23265
  facadeExtra: (f, rng2, w, d, op) => {
23188
23266
  f.roof = "slab";
23189
23267
  f.finish = rng2() < 0.55 ? "exposed_block" : "partial_plaster";
23190
- f.slab = { exposedRebar: true, waterTank: { u: r22(0.5 + rng2() * (w - 1.5)), v: r22(1 + rng2() * (d - 2)) }, annex: rng2() < 0.55 ? { rect: [0, 0, r22(w * (0.5 + rng2() * 0.3)), r22(d * 0.4)], state: "exposed_brick" } : null };
23191
- if (floorsOf(op) > 1) f.slab.exposedRebar = rng2() < 0.6;
23268
+ const slab = { exposedRebar: true, waterTank: { u: r22(0.5 + rng2() * (w - 1.5)), v: r22(1 + rng2() * (d - 2)) }, annex: rng2() < 0.55 ? { rect: [0, 0, r22(w * (0.5 + rng2() * 0.3)), r22(d * 0.4)], state: "exposed_brick" } : null };
23269
+ f.slab = slab;
23270
+ if (floorsOf(op) > 1) slab.exposedRebar = rng2() < 0.6;
23192
23271
  }
23193
23272
  };
23194
23273
  }
@@ -23900,13 +23979,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23900
23979
  function apartment(strip, floor, k, from, to, plate, ceiling, win) {
23901
23980
  const wide = to - from, depth = strip.depth, facade = r22(depth * 0.56);
23902
23981
  const id = (name) => `apt_${floor}_${strip.id}_${k}_${name}`;
23903
- const at = (f0, f1, b0, b1) => boxOf2(strip, r22(from + wide * f0), r22(from + wide * f1), b0, b1);
23982
+ const at2 = (f0, f1, b0, b1) => boxOf2(strip, r22(from + wide * f0), r22(from + wide * f1), b0, b1);
23904
23983
  const rooms = [
23905
- { id: id("living"), type: "living", label: "living", library: "living_tower", rect: at(0, 0.58, 0, facade) },
23906
- { id: id("bedroom"), type: "bedroom", label: "bedroom", rect: at(0.58, 1, 0, facade) },
23907
- { id: id("kitchen"), type: "kitchen", label: "kitchen", library: "kitchen_integrated", rect: at(0, 0.4, facade, depth) },
23908
- { id: id("hall"), type: "hall", label: "hall", rect: at(0.4, 0.78, facade, depth) },
23909
- { id: id("bath"), type: "bathroom", label: "bathroom", library: "bathroom_wc", rect: at(0.78, 1, facade, depth) }
23984
+ { id: id("living"), type: "living", label: "living", library: "living_tower", rect: at2(0, 0.58, 0, facade) },
23985
+ { id: id("bedroom"), type: "bedroom", label: "bedroom", rect: at2(0.58, 1, 0, facade) },
23986
+ { id: id("kitchen"), type: "kitchen", label: "kitchen", library: "kitchen_integrated", rect: at2(0, 0.4, facade, depth) },
23987
+ { id: id("hall"), type: "hall", label: "hall", rect: at2(0.4, 0.78, facade, depth) },
23988
+ { id: id("bath"), type: "bathroom", label: "bathroom", library: "bathroom_wc", rect: at2(0.78, 1, facade, depth) }
23910
23989
  ];
23911
23990
  const doors = [
23912
23991
  stripDoor(id("hall"), strip, floor, 0.95),
@@ -23939,8 +24018,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23939
24018
  if (strip.id !== "south") return apartments(plan)(strip, context);
23940
24019
  const { floor, plate, ceiling } = context;
23941
24020
  const lobby = `lobby_${floor}_entrance`, concierge = `concierge_${floor}`, bikes = `bike_room_${floor}`;
23942
- const at = (f0, f1) => boxOf2(strip, r22(strip.length * f0), r22(strip.length * f1), 0, strip.depth);
23943
- const [left, middle, right] = [at(0, 0.3), at(0.3, 0.7), at(0.7, 1)];
24021
+ const at2 = (f0, f1) => boxOf2(strip, r22(strip.length * f0), r22(strip.length * f1), 0, strip.depth);
24022
+ const [left, middle, right] = [at2(0, 0.3), at2(0.3, 0.7), at2(0.7, 1)];
23944
24023
  const rooms = [
23945
24024
  { id: concierge, type: "office", label: "concierge", rect: left },
23946
24025
  { id: lobby, type: "hall", label: "lobby_entrance", rect: middle },