@adatechnology/game-floorplan-three 0.8.3 → 0.9.1

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-R4RY23RH.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,15 +1529,116 @@ 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
+ // ../game-contracts/dist/index.js
1542
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
1543
+ var DEFAULT_IGNITION_RULES = {
1544
+ fillCurve: [
1545
+ [0, 0.6],
1546
+ [0.1, 1],
1547
+ [0.35, 0.6],
1548
+ [0.65, 0.3],
1549
+ [0.9, 0.15],
1550
+ [1, 0.12]
1551
+ ],
1552
+ flameFillInfluence: 0.5,
1553
+ flameDefaults: { incendiary: 0.7, tracer: 0.2 },
1554
+ sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
1555
+ sparkMetalDefault: 0.5,
1556
+ sparkFluid: { diesel: 0.15 },
1557
+ fireNear: { radius: 1.5, chancePerSecond: 0.5 }
1558
+ };
1559
+ var resolveIgnitionRules = (rules) => ({
1560
+ ...DEFAULT_IGNITION_RULES,
1561
+ ...rules,
1562
+ flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
1563
+ sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
1564
+ sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
1565
+ fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
1566
+ });
1567
+ var fillFactor = (fill2, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
1568
+ if (curve.length === 0) return 1;
1569
+ const f = Math.min(1, Math.max(0, fill2));
1570
+ if (f <= curve[0][0]) return curve[0][1];
1571
+ for (let i = 1; i < curve.length; i++) {
1572
+ const [x1, y1] = curve[i];
1573
+ const [x0, y0] = curve[i - 1];
1574
+ if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
1575
+ }
1576
+ return curve[curve.length - 1][1];
1577
+ };
1578
+ var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
1579
+ var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
1580
+ const k = fillFactor(target.fill, rules.fillCurve);
1581
+ const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
1582
+ const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
1583
+ const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
1584
+ return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
1585
+ };
1586
+ var DEFERRED_QUEUE_ID = {
1587
+ DESTRUCTION_EVENTS: "destruction.events",
1588
+ BREACH_PAINT: "floorplan.breach-paint",
1589
+ AUDIO_VOICES: "audio.voices",
1590
+ VEHICLE_CREASE: "vehicles.crease"
1591
+ };
1592
+ var DEFERRED_PRIORITY = {
1593
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
1594
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
1595
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
1596
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
1597
+ };
1598
+ var KIT_ORIGIN_KEY = "kitOrigin";
1599
+ function markKitOrigin(object2, origin) {
1600
+ const node = object2;
1601
+ node.userData = node.userData ?? {};
1602
+ node.userData[KIT_ORIGIN_KEY] = { ...origin };
1603
+ return object2;
1604
+ }
1605
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
1606
+ var GAIT_IDS = ["walk", "run"];
1607
+ var LANDING_IDS = ["light", "heavy"];
1608
+ var BODY_SOUND_IDS = [
1609
+ "jump",
1610
+ "cloth_rustle",
1611
+ "body_fall",
1612
+ "fall_damage",
1613
+ "player_hurt",
1614
+ "player_breath_run",
1615
+ "player_death",
1616
+ "bite",
1617
+ "grab",
1618
+ "punch_swing",
1619
+ "punch_hit_body",
1620
+ "punch_hit_face",
1621
+ "punch_hit_object",
1622
+ "kick_swing",
1623
+ "kick_hit",
1624
+ "kick_hit_object",
1625
+ "shove_two_hands",
1626
+ "knife_swing",
1627
+ "knife_stab",
1628
+ "knife_slash",
1629
+ "knife_hit_object",
1630
+ "blunt_swing",
1631
+ "blunt_hit",
1632
+ "blunt_hit_object",
1633
+ "axe_swing",
1634
+ "axe_chop",
1635
+ "axe_hit_object"
1636
+ ];
1637
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
1638
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
1639
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
1640
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
1641
+
1541
1642
  // ../game-shapes-three/dist/index.js
1542
1643
  var UV_BEFORE_NORMAL = /* @__PURE__ */ new Set(["LatheGeometry", "ExtrudeGeometry"]);
1543
1644
  var INDEX_FIRST = /* @__PURE__ */ new Set(["BoxGeometry", "CylinderGeometry", "ConeGeometry", "SphereGeometry", "PlaneGeometry", "CircleGeometry", "RingGeometry", "LatheGeometry", "TorusGeometry"]);
@@ -3307,17 +3408,17 @@ var GameFloorplanThree = (() => {
3307
3408
  const index = geometry2.index ? geometry2.index.array : null;
3308
3409
  const count = index ? index.length : position.count;
3309
3410
  for (let item = 0; item < count; item++) {
3310
- const at = index ? index[item] : item;
3311
- vertex.fromBufferAttribute(position, at).applyMatrix4(matrix);
3411
+ const at2 = index ? index[item] : item;
3412
+ vertex.fromBufferAttribute(position, at2).applyMatrix4(matrix);
3312
3413
  positions2.push(vertex.x, vertex.y, vertex.z);
3313
- if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at).applyMatrix3(normalMatrix2).normalize();
3414
+ if (normalAttribute) normal.fromBufferAttribute(normalAttribute, at2).applyMatrix3(normalMatrix2).normalize();
3314
3415
  else normal.set(0, 1, 0);
3315
3416
  normals.push(normal.x, normal.y, normal.z);
3316
3417
  color2.copy(material2.color || color2.set(1, 1, 1)).convertLinearToSRGB();
3317
3418
  if (sample && uv && material2.map) {
3318
3419
  const repeat = material2.map.repeat;
3319
- const u = (uv.getX(at) * repeat.x % 1 + 1) % 1;
3320
- const v = (uv.getY(at) * repeat.y % 1 + 1) % 1;
3420
+ const u = (uv.getX(at2) * repeat.x % 1 + 1) % 1;
3421
+ const v = (uv.getY(at2) * repeat.y % 1 + 1) % 1;
3321
3422
  const pixelX = Math.min(sample.width - 1, u * sample.width | 0);
3322
3423
  const pixelY = Math.min(sample.height - 1, (1 - v) * sample.height | 0);
3323
3424
  const offset = (pixelY * sample.width + pixelX) * 4;
@@ -8997,7 +9098,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
8997
9098
  const em = w.emitters();
8998
9099
  if (em.length && !IV.efx && K.IVK.effects) {
8999
9100
  const g = h.houseGroup;
9000
- IV.efx = ik.gfx.createEffects(g, { maxParticles: 1400, seed: 11, document: DOC });
9101
+ IV.efx = ik.gfx.createEffects(g, { maxParticles: 1400, seed: 11, document: DOC, ...K.IVK.effectSink ? { effects: K.IVK.effectSink } : {} });
9001
9102
  }
9002
9103
  if (IV.efx) {
9003
9104
  const alive = IV.efx.stats().alive;
@@ -9770,8 +9871,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9770
9871
  // was `maquina`
9771
9872
  });
9772
9873
  var OWN_TYPES = Object.freeze({ lamp: true, gas_piped: true });
9874
+ var sourceOf = (result) => result;
9773
9875
  var ADAPTER_VERSION = "0.3.0";
9774
9876
  var r3 = (n) => Math.round(n * 1e3) / 1e3;
9877
+ var at = (it) => ({ x: it.x ?? 0, z: it.z ?? 0, ry: it.ry ?? 0 });
9775
9878
  function mergeRegistry(base, extra) {
9776
9879
  const out = { ...base };
9777
9880
  if (extra) {
@@ -9790,14 +9893,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9790
9893
  });
9791
9894
  function create2(options = {}) {
9792
9895
  const opts = options || {};
9793
- const GI = opts.GameInteractables;
9794
- const HH = opts.GameInteractablesHousehold;
9795
9896
  const locale = opts.locale || "pt-BR";
9796
9897
  const msg = createTranslator2(locale, opts.messages);
9797
- if (!GI || !HH) throw new Error(msg("error.missingKit"));
9898
+ if (!opts.GameInteractables || !opts.GameInteractablesHousehold) throw new Error(msg("error.missingKit"));
9899
+ const GI = opts.GameInteractables;
9900
+ const HH = opts.GameInteractablesHousehold;
9798
9901
  const region = opts.region || "br";
9799
9902
  const hh = HH.createHousehold({ locale, region });
9800
9903
  const householdTypes = new Set(hh.types.map((t) => t.id));
9904
+ const recorderApi = () => HH.createRecorder().api;
9801
9905
  const worlds = /* @__PURE__ */ new WeakMap();
9802
9906
  const typeMap = mergeRegistry(TYPE_MAP, opts.typeMap);
9803
9907
  const contentMap = mergeRegistry(CONTENT_MAP, opts.contentMap);
@@ -9833,7 +9937,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9833
9937
  function colorOf(res, color2) {
9834
9938
  if (color2 == null) return void 0;
9835
9939
  if (typeof color2 === "number") return color2;
9836
- const v = res.colors && res.colors[color2];
9940
+ const v = res.colors && res.colors[String(color2)];
9837
9941
  return typeof v === "number" ? v : void 0;
9838
9942
  }
9839
9943
  function paletteOf(res) {
@@ -9862,17 +9966,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9862
9966
  drawerItem: (s) => drawerItems.has(s.itemId)
9863
9967
  };
9864
9968
  const itemOfContent = (c) => {
9865
- const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || contentByGroup[stackGroup(c)] || contentFallback;
9969
+ const group = stackGroup(c);
9970
+ const id = contentMap[c.itemId] || (hh.items[c.itemId] ? c.itemId : null) || (group != null ? contentByGroup[group] : void 0) || contentFallback;
9866
9971
  return hh.items[id] ? id : contentFallback;
9867
9972
  };
9868
9973
  const volume = (d) => d.size[0] * d.size[1] * d.size[2];
9869
9974
  const candidates = (id, objectType) => {
9870
9975
  const def = hh.items[id];
9871
- 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));
9976
+ 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));
9872
9977
  return [id].concat(siblings.map((i) => i.id), substitutes[objectType] || []);
9873
9978
  };
9874
9979
  function distribute(type, probeItem, stacks) {
9875
- const spec = hh.build(type, probeItem, HH.createRecorder().api);
9980
+ const spec = hh.build(type, probeItem, recorderApi());
9876
9981
  const conts = spec.containers || [];
9877
9982
  const contents = {};
9878
9983
  for (const c of conts) contents[c.id] = c.slots.map(() => null);
@@ -9903,8 +10008,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9903
10008
  }
9904
10009
  return { contents, rejected };
9905
10010
  }
9906
- function plan(res, popts = {}) {
9907
- popts = popts || {};
10011
+ function plan(source, options2 = {}) {
10012
+ const res = sourceOf(source);
10013
+ const popts = options2 || {};
9908
10014
  const o = { ox: popts.originX || 0, oz: popts.originZ || 0, inw: popts.inw || 1, base: popts.base || 0 };
9909
10015
  const K = conv(o);
9910
10016
  const floorSel = popts.floor;
@@ -9968,7 +10074,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9968
10074
  const item = {
9969
10075
  x: K.x(m.x),
9970
10076
  z: K.z(m.z),
9971
- ry: m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0),
10077
+ ry: m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0),
9972
10078
  w: m.w,
9973
10079
  d: m.d,
9974
10080
  h: m.h,
@@ -9981,7 +10087,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9981
10087
  return item;
9982
10088
  }
9983
10089
  function entry2(id, type, item, origin, floor, extra) {
9984
- return Object.assign({ id, type, item, origin, floor, floorY: floorYOf(res, o, floor), connections: {} }, extra || {});
10090
+ const e = { id, type, item, origin, floor, floorY: floorYOf(res, o, floor), connections: {} };
10091
+ return Object.assign(e, extra || {});
9985
10092
  }
9986
10093
  function store(targets, stacks) {
9987
10094
  let rest2 = stacks || [];
@@ -9994,10 +10101,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
9994
10101
  }
9995
10102
  const hidden = [];
9996
10103
  function distRect(c, x, z) {
9997
- const dx = x - c.item.x;
9998
- const dz = z - c.item.z;
9999
- const s = Math.sin(c.item.ry);
10000
- const co = Math.cos(c.item.ry);
10104
+ const ci = at(c.item);
10105
+ const dx = x - ci.x;
10106
+ const dz = z - ci.z;
10107
+ const s = Math.sin(ci.ry);
10108
+ const co = Math.cos(ci.ry);
10001
10109
  const lx = dx * co - dz * s;
10002
10110
  const lz = dx * s + dz * co;
10003
10111
  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));
@@ -10005,9 +10113,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10005
10113
  function hideCylinders() {
10006
10114
  for (const b of hidden) {
10007
10115
  let target = null;
10116
+ const bi = at(b.item);
10008
10117
  for (const c2 of result.items) {
10009
10118
  if (c2.type !== "kitchen_cabinet_base" || c2.floor !== b.floor) continue;
10010
- const dist = distRect(c2, b.item.x, b.item.z);
10119
+ const dist = distRect(c2, bi.x, bi.z);
10011
10120
  if (!target || dist < target.dist) target = { c: c2, dist };
10012
10121
  }
10013
10122
  if (!target) {
@@ -10015,16 +10124,18 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10015
10124
  continue;
10016
10125
  }
10017
10126
  const c = target.c;
10018
- const spec = hh.build(c.type, c.item, HH.createRecorder().api);
10019
- const sn = Math.sin(c.item.ry);
10020
- const cs = Math.cos(c.item.ry);
10127
+ const spec = hh.build(c.type, c.item, recorderApi());
10128
+ const ci = at(c.item);
10129
+ const sn = Math.sin(ci.ry);
10130
+ const cs = Math.cos(ci.ry);
10021
10131
  let door = null;
10022
10132
  for (const it of spec.interactions || []) {
10023
- const pt = it.action && it.action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === it.action.part) : null;
10024
- if (!pt || pt.motion.kind !== "hinge") continue;
10025
- const wx = c.item.x + it.pos[0] * cs + it.pos[2] * sn;
10026
- const wz = c.item.z - it.pos[0] * sn + it.pos[2] * cs;
10027
- const dist = Math.hypot(wx - b.item.x, wz - b.item.z);
10133
+ const action = it.action;
10134
+ const pt = action.do === "toggle_part" ? (spec.parts || []).find((q) => q.id === action.part) : null;
10135
+ if (!pt || !pt.motion || pt.motion.kind !== "hinge") continue;
10136
+ const wx = ci.x + it.pos[0] * cs + it.pos[2] * sn;
10137
+ const wz = ci.z - it.pos[0] * sn + it.pos[2] * cs;
10138
+ const dist = Math.hypot(wx - bi.x, wz - bi.z);
10028
10139
  if (!door || dist < door.dist) door = { id: pt.id, dist };
10029
10140
  }
10030
10141
  if (!door) {
@@ -10054,9 +10165,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10054
10165
  const campCylinder = firstOfType("gas_canister");
10055
10166
  function planFireplace(m, e, item) {
10056
10167
  const def = hh.types.find((t) => t.id === "fireplace");
10057
- const lack = (def.d - m.d) / 2;
10058
- item.x = r3(item.x + Math.sin(item.ry) * lack);
10059
- item.z = r3(item.z + Math.cos(item.ry) * lack);
10168
+ const lack = ((def ? def.d : m.d) - m.d) / 2;
10169
+ const p = at(item);
10170
+ item.x = r3(p.x + Math.sin(p.ry) * lack);
10171
+ item.z = r3(p.z + Math.cos(p.ry) * lack);
10060
10172
  const room = (res.rooms || []).find((c) => c.id === m.room && c.floor === m.floor);
10061
10173
  const f = (res.floors || []).find((x) => x.index === m.floor);
10062
10174
  item.h = r3(room && room.ceiling || f && f.ceiling || 2.6);
@@ -10084,7 +10196,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10084
10196
  e.connections.gas = m.gas || "cylinder";
10085
10197
  e.connections.cylinder = m.cylinder || null;
10086
10198
  }
10087
- if (map.type === "gas_bottle") result.cylinders.push({ id: e.id, stove: m.link || null, place: m.placement || null });
10199
+ if (map.type === "gas_bottle") result.cylinders.push({ id: e.id, stove: typeof m.link === "string" ? m.link : null, place: m.placement || null });
10088
10200
  if (map.type === "gas_canister") {
10089
10201
  result.canisters.push(e.id);
10090
10202
  if (campCylinder && campCylinder.id === m.id && campStove) result.cylinders.push({ id: e.id, stove: campStove.id, place: null });
@@ -10108,7 +10220,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10108
10220
  push(e);
10109
10221
  }
10110
10222
  function planCompound(m, kind) {
10111
- const ry = m.frontU != null ? K.ry(m.frontU, m.frontV) : r3(m.ry || 0);
10223
+ const ry = m.frontU != null ? K.ry(m.frontU, m.frontV ?? 0) : r3(m.ry || 0);
10112
10224
  const cx = K.x(m.x);
10113
10225
  const cz = K.z(m.z);
10114
10226
  const part = (id, type, w, d, h, y, lx, lz) => {
@@ -10160,11 +10272,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10160
10272
  } else {
10161
10273
  const basin = part(m.id, "sink_bathroom", m.w, m.d, m.h + 0.2, 0, 0, 0);
10162
10274
  list2.push(basin);
10163
- if (contents[m.id]) {
10164
- if (contents[m.id].length) {
10275
+ const own = contents[m.id];
10276
+ if (own) {
10277
+ if (own.length) {
10165
10278
  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);
10166
10279
  list2.push(cabinet);
10167
- store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], contents[m.id]);
10280
+ store([["bathroom_cabinet", cabinet.item], ["sink_bathroom", basin.item]], own);
10168
10281
  } else {
10169
10282
  store([["sink_bathroom", basin.item]], []);
10170
10283
  }
@@ -10204,7 +10317,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10204
10317
  result.chairs = [];
10205
10318
  if (opts.chairs !== false && popts.chairs !== false) {
10206
10319
  const blocked = blockedOf(res, { originX: o.ox, originZ: o.oz, inw: o.inw });
10207
- const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && q.aabb;
10320
+ const floorKind = (q) => !q.decor && !q.flat && (q.y || 0) < 0.9 && !!q.aabb;
10208
10321
  const placed = [];
10209
10322
  const free = (m, cu, cv, half) => {
10210
10323
  const a = [cu - half + 0.03, cv - half + 0.03, cu + half - 0.03, cv + half - 0.03];
@@ -10220,8 +10333,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10220
10333
  for (const m of res.items || []) {
10221
10334
  const mode = chairTables[m.type];
10222
10335
  if (!mode || !onFloor(m.floor, m.outdoor) || m.frontU == null) continue;
10223
- const f = [m.frontU, m.frontV];
10224
- const pp = [-m.frontV, m.frontU];
10336
+ const fv = m.frontV ?? 0;
10337
+ const f = [m.frontU, fv];
10338
+ const pp = [-fv, m.frontU];
10225
10339
  const seats = [];
10226
10340
  if (mode === "dining") {
10227
10341
  const n = Math.max(1, Math.min(3, Math.floor((m.w - 0.1) / 0.55)));
@@ -10290,8 +10404,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10290
10404
  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) };
10291
10405
  item.controls = (s.controls || []).map((id) => id + ":lamp");
10292
10406
  const e = entry2(s.id, "wall_switch", item, s.id, s.floor);
10293
- e.connections.controls = (s.controls || []).slice();
10294
- e.lit = e.connections.controls.some((id) => (result.items.find((x) => x.id === id) || {}).lit === true);
10407
+ const controls = (s.controls || []).slice();
10408
+ e.connections.controls = controls;
10409
+ e.lit = controls.some((id) => result.items.find((x) => x.id === id)?.lit === true);
10295
10410
  if (floorSel == null) {
10296
10411
  for (const id of s.controls || []) if (!lights.has(id)) result.warnings.push(msg("warn.switchUnknownLamp", { id: s.id, lamp: id }));
10297
10412
  }
@@ -10331,7 +10446,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10331
10446
  seed: seedOf2("panel"),
10332
10447
  circuits: circuits.map((c) => ({ id: c.id, label: c.room }))
10333
10448
  };
10334
- if (!item.circuits.length) delete item.circuits;
10449
+ if (!item.circuits?.length) delete item.circuits;
10335
10450
  const e = entry2("panel", "electrical_panel", item, "panel", q.floor);
10336
10451
  push(e);
10337
10452
  result.panel = e.id;
@@ -10353,10 +10468,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10353
10468
  }
10354
10469
  }
10355
10470
  const piped = result.items.filter((e) => e.type === "stove" && e.connections.gas === "piped");
10356
- if (piped.length) {
10471
+ const firstPiped = piped[0];
10472
+ if (firstPiped) {
10357
10473
  const med = utilities.gas && utilities.gas.meter;
10358
- 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 };
10359
- 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);
10474
+ 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 };
10475
+ 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);
10360
10476
  e.floorY = o.base;
10361
10477
  push(e);
10362
10478
  for (const f of piped) f.connections.fuel = "piped_gas:gas";
@@ -10403,9 +10519,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10403
10519
  for (const dv of spec.devices || []) if (L.outlet && dv.socket === "auto") dv.socket = L.outlet + ":socket";
10404
10520
  } else {
10405
10521
  const sh = L.shower || {};
10406
- const heater = (spec.devices || []).find((d) => d.id === "heater");
10522
+ const devices = spec.devices || [];
10523
+ const heater = devices.find((d) => d.id === "heater");
10407
10524
  if (heater && sh.electric === false) {
10408
- spec.devices = spec.devices.filter((d) => d !== heater);
10525
+ spec.devices = devices.filter((d) => d !== heater);
10409
10526
  for (const f of spec.fixtures || []) if (f.heater === "heater") delete f.heater;
10410
10527
  } else if (heater) {
10411
10528
  if (sh.powerW) heater.watts = sh.powerW;
@@ -10416,7 +10533,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10416
10533
  for (const b of spec.burners || []) if (b.fuel === "auto") b.fuel = L.fuel;
10417
10534
  }
10418
10535
  if (entry2.type === "fireplace" && L.chimneyTop) {
10419
- const it = entry2.item;
10536
+ const it = at(entry2.item);
10420
10537
  const dx = L.chimneyTop[0] - it.x;
10421
10538
  const dz = L.chimneyTop[2] - it.z;
10422
10539
  const lx = Math.cos(it.ry) * dx - Math.sin(it.ry) * dz;
@@ -10435,8 +10552,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10435
10552
  }
10436
10553
  return spec;
10437
10554
  }
10438
- function createWorld(res, copts = {}) {
10439
- copts = copts || {};
10555
+ function createWorld(source, options2 = {}) {
10556
+ const res = sourceOf(source);
10557
+ const copts = options2 || {};
10440
10558
  const info = { points: /* @__PURE__ */ new Map(), water: copts.water || null, lightsOn: copts.lightsOn };
10441
10559
  const electrical = res.utilities && res.utilities.electrical;
10442
10560
  const world = GI.createWorld({
@@ -10464,8 +10582,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10464
10582
  const info = worlds.get(world);
10465
10583
  return info ? info.points.get(fixtureId) : void 0;
10466
10584
  }
10467
- function blockedOf(res, bopts = {}) {
10468
- bopts = bopts || {};
10585
+ function blockedOf(source, options2 = {}) {
10586
+ const res = sourceOf(source);
10587
+ const bopts = options2 || {};
10469
10588
  const ox = bopts.originX || 0;
10470
10589
  const oz = bopts.originZ || 0;
10471
10590
  const inw = bopts.inw || 1;
@@ -10500,33 +10619,33 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10500
10619
  return false;
10501
10620
  };
10502
10621
  }
10503
- function add(world, planned, aopts = {}) {
10504
- aopts = aopts || {};
10622
+ function add(world, planned, options2 = {}) {
10623
+ const aopts = options2 || {};
10505
10624
  const info = worlds.get(world);
10506
- const build = aopts.build || ((type, item) => hh.build(type, item, HH.createRecorder().api));
10625
+ const build = aopts.build || ((type, item) => hh.build(type, item, recorderApi()));
10507
10626
  const isBlocked = aopts.isBlocked || null;
10508
10627
  const out = { added: 0, parts: [], hinges: {}, moved: {}, warnings: [] };
10509
10628
  const override = aopts.lightsOn !== void 0 ? aopts.lightsOn : info && info.lightsOn;
10510
10629
  if (override !== void 0) {
10511
10630
  for (const e of planned.items) if (e.type === "lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
10512
10631
  for (const e of planned.items) if (e.type === "lantern_lamp") e.lit = typeof override === "function" ? !!override(e.id, e) : !!override;
10513
- 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);
10632
+ 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);
10514
10633
  }
10515
10634
  for (const e of planned.items) if (e.type === "lantern_lamp") e.item.on = !!e.lit;
10516
10635
  let scratch = null;
10517
- function probe(e, hinge, setback) {
10636
+ function probe(e, hinge, setback, blockedAt) {
10518
10637
  if (!scratch) scratch = GI.createWorld({ locale, strings: hh.strings, items: hh.items, loot: hh.loot, grid: false });
10519
- const it = e.item;
10638
+ const it = at(e.item);
10520
10639
  const x = it.x + Math.sin(it.ry) * setback;
10521
10640
  const z = it.z + Math.cos(it.ry) * setback;
10522
- const spec = hh.build(e.type, Object.assign({}, it, { hinge }), HH.createRecorder().api);
10641
+ const spec = hh.build(e.type, Object.assign({}, e.item, { hinge }), recorderApi());
10523
10642
  scratch.add("__probe", spec, { x, y: e.floorY, z, ry: it.ry });
10524
- const blocks = (px, pz) => isBlocked(px, pz, e.floor);
10643
+ const blocks = (px, pz) => blockedAt(px, pz, e.floor);
10525
10644
  let sum = 0;
10526
10645
  let min = 1;
10527
10646
  let n = 0;
10528
10647
  for (const p of spec.parts || []) {
10529
- if (p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
10648
+ if (!p.motion || p.motion.kind !== "hinge" || !(p.bounds || p.sweep)) continue;
10530
10649
  const t = scratch.fitSweep("__probe", p.id, blocks);
10531
10650
  sum += t;
10532
10651
  min = Math.min(min, t);
@@ -10538,12 +10657,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10538
10657
  const better = (a, b) => a.sum > b.sum + 1e-9 || Math.abs(a.sum - b.sum) <= 1e-9 && a.min > b.min + 1e-9;
10539
10658
  function fitDoors(e) {
10540
10659
  if (!isBlocked || !householdTypes.has(e.type)) return;
10541
- const standard = probe(e, void 0, 0);
10660
+ const standard = probe(e, void 0, 0, isBlocked);
10542
10661
  if (!standard.n || standard.min >= 1) return;
10543
10662
  let bestSide;
10544
10663
  let m = standard;
10545
10664
  for (const side of ["left", "right"]) {
10546
- const r = probe(e, side, 0);
10665
+ const r = probe(e, side, 0, isBlocked);
10547
10666
  if (better(r, m)) {
10548
10667
  m = r;
10549
10668
  bestSide = side;
@@ -10556,13 +10675,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10556
10675
  if (m.min >= 1) return;
10557
10676
  let target = { setback: 0, r: m };
10558
10677
  for (const setback of [0.01, 0.02, 0.03, 0.04]) {
10559
- const r = probe(e, bestSide, setback);
10678
+ const r = probe(e, bestSide, setback, isBlocked);
10560
10679
  if (better(r, target.r)) target = { setback, r };
10561
10680
  if (r.min >= 1) break;
10562
10681
  }
10563
10682
  if (target.setback > 0 && target.r.sum > m.sum + 0.2) {
10564
- e.item.x = r3(e.item.x + Math.sin(e.item.ry) * target.setback);
10565
- e.item.z = r3(e.item.z + Math.cos(e.item.ry) * target.setback);
10683
+ const p = at(e.item);
10684
+ e.item.x = r3(p.x + Math.sin(p.ry) * target.setback);
10685
+ e.item.z = r3(p.z + Math.cos(p.ry) * target.setback);
10566
10686
  e.moved = target.setback;
10567
10687
  out.moved[e.id] = target.setback;
10568
10688
  }
@@ -10585,7 +10705,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10585
10705
  }
10586
10706
  }
10587
10707
  connect(e, spec);
10588
- world.add(e.id, spec, { x: e.item.x, y: e.floorY, z: e.item.z, ry: e.item.ry });
10708
+ const pose = at(e.item);
10709
+ world.add(e.id, spec, { x: pose.x, y: e.floorY, z: pose.z, ry: pose.ry });
10589
10710
  out.added++;
10590
10711
  if (!isBlocked) continue;
10591
10712
  for (const p of spec.parts || []) {
@@ -10614,7 +10735,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10614
10735
  return null;
10615
10736
  }
10616
10737
  const A = AD.plan ? AD : AD.create({ ...o.adapterOptions || {}, GameInteractables: GI, GameInteractablesHousehold: HH, locale: o.locale || cfg.locale, region: o.region, ...cfg.seedKey ? { seedKey: cfg.seedKey } : {} });
10617
- return { GI, HH, GT, A, gfx: GT.createThreeAdapter(THREE, { document: DOC, Reflector }), items: o.items !== false, effects: o.effects !== false, camera: o.camera || null, onEvent: o.onEvent || null, onSeat: o.onSeat || null };
10738
+ return { GI, HH, GT, A, gfx: GT.createThreeAdapter(THREE, { document: DOC, Reflector }), items: o.items !== false, effects: o.effects !== false, effectSink: o.effectSink || null, camera: o.camera || null, onEvent: o.onEvent || null, onSeat: o.onSeat || null };
10618
10739
  })();
10619
10740
  const VEG = (() => {
10620
10741
  const l = cfg.vegetation || null, G = l && (l.GameVegetation || (l.buildLot ? l : null)), S = l && (l.GameVegetationSpecies || (l.toSpec ? l : null));
@@ -10631,7 +10752,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10631
10752
 
10632
10753
  // src/lights/room-light.ts
10633
10754
  function installLightsRoomLight(K, shared) {
10634
- const { THREE, rectsOf: rectsOf2 } = K;
10755
+ const { THREE, rectsOf: rectsOf2, cfg } = K;
10635
10756
  const MAX_LIGHTS = 12, EMPTY = [1e9, 1e9, -1e9, -1e9];
10636
10757
  const LZU = { uRB0: { value: new Float32Array(MAX_LIGHTS * 4) }, uRB1: { value: new Float32Array(MAX_LIGHTS * 4) }, uRB2: { value: new Float32Array(MAX_LIGHTS * 4) }, uRBY: { value: new Float32Array(MAX_LIGHTS * 2) }, uREx: { value: new Float32Array(MAX_LIGHTS * 4) }, uIndK: { value: 0.06 } };
10637
10758
  for (let i = 0; i < MAX_LIGHTS; i++) for (const k of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[k].value.set(EMPTY, i * 4);
@@ -10670,8 +10791,15 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10670
10791
  o = o || {};
10671
10792
  const scene = o.scene, N = Math.max(1, Math.min(MAX_LIGHTS, o.n || 6)), NS = Math.max(0, Math.min(N, o.shadows != null ? o.shadows : 2)), size = o.shadowSize || 512, MAX_EXTERNAL = o.external != null ? o.external : 2;
10672
10793
  for (const k of Object.keys(shared.mats)) roomLightMask(shared.mats[k]);
10794
+ const budget = o.budget, lease = budget ? budget.lease({ owner: "floorplan.rooms", point: N, shadows: NS }) : null;
10795
+ if (budget && !lease) throw new Error("lighting: the LightBudget has no " + N + " free point slots (" + NS + " with shadow) for the rooms");
10796
+ if (budget) for (const [key, width] of [["uRB0", 4], ["uRB1", 4], ["uRB2", 4], ["uRBY", 2], ["uREx", 4]]) {
10797
+ const next = new Float32Array(budget.sizes.point * width);
10798
+ if (width === 4) for (let i = 0; i < budget.sizes.point; i++) next.set(EMPTY, i * 4);
10799
+ LZU[key].value = next;
10800
+ }
10673
10801
  const PL = [], sig = new Array(N).fill(""), seg = { n: 0 };
10674
- for (let i = 0; i < N; i++) {
10802
+ if (!lease) for (let i = 0; i < N; i++) {
10675
10803
  const l = new THREE.PointLight(16763008, 0, 8, 1.6);
10676
10804
  l.visible = true;
10677
10805
  l.castShadow = i < NS;
@@ -10687,12 +10815,30 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10687
10815
  if (scene) scene.add(l);
10688
10816
  PL.push(l);
10689
10817
  }
10690
- const A = { lights: PL, crates: new Array(N).fill(null), n: N, shadows: NS, stats: { rooms: 0, external: 0, shadows: 0, shadowChanged: false }, indirect: (v) => {
10818
+ const slotOf = (i) => lease ? lease.handles[i].slot : i;
10819
+ const put = (i, v) => {
10820
+ if (lease) {
10821
+ lease.handles[i].update(v.color === void 0 || typeof v.color === "number" ? v : { ...v, color: new THREE.Color(v.color).getHex() });
10822
+ return;
10823
+ }
10824
+ const pl = PL[i];
10825
+ if (v.position) pl.position.set(v.position[0], v.position[1], v.position[2]);
10826
+ if (v.color !== void 0) pl.color.set(v.color);
10827
+ if (v.intensity !== void 0) pl.intensity = v.intensity;
10828
+ if (v.distance !== void 0) pl.distance = v.distance;
10829
+ if (v.decay !== void 0) pl.decay = v.decay;
10830
+ };
10831
+ const effects = lease ? effectLights({ budget, ours: lease.handles.map((h) => h.slot) }) : null;
10832
+ const A = { lights: lease ? lease.handles : PL, crates: new Array(N).fill(null), n: N, shadows: NS, stats: { rooms: 0, external: 0, shadows: 0, shadowChanged: false }, indirect: (v) => {
10691
10833
  LZU.uIndK.value = v;
10692
10834
  } };
10693
10835
  const _p = new THREE.Vector3(), _f = new THREE.Vector3();
10694
10836
  const lim = (v, a, b) => Math.max(a, Math.min(b, v));
10695
10837
  A.invalidate = () => {
10838
+ if (lease) {
10839
+ lease.invalidateShadow();
10840
+ return;
10841
+ }
10696
10842
  for (let i = 0; i < NS; i++) PL[i].shadow.needsUpdate = true;
10697
10843
  };
10698
10844
  A.update = function(houses2, focus, op) {
@@ -10800,47 +10946,44 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10800
10946
  A.stats.external = Ne;
10801
10947
  let shadows = 0;
10802
10948
  for (let i = 0; i < N; i++) {
10803
- const pl = PL[i], g = asg[i], b0 = LZU.uRB0.value, b1 = LZU.uRB1.value, b2 = LZU.uRB2.value, by = LZU.uRBY.value, ex = LZU.uREx.value;
10949
+ 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;
10804
10950
  A.crates[i] = g ? { k: g.k, rects: g.rects, y: g.y, exterior: !!g.exterior, exclusion: g.exclusion || null, d: g.d } : null;
10805
10951
  if (!g) {
10806
- pl.intensity = 0;
10807
- for (const a of [b0, b1, b2, ex]) a.set(EMPTY, i * 4);
10808
- by.set([0, 0], i * 2);
10952
+ put(i, { intensity: 0 });
10953
+ for (const a of [b0, b1, b2, ex]) a.set(EMPTY, at2 * 4);
10954
+ by.set([0, 0], at2 * 2);
10809
10955
  continue;
10810
10956
  }
10811
- pl.position.set(g.pos[0], g.pos[1], g.pos[2]);
10812
- pl.color.set(g.color);
10813
- if (g.exterior) {
10814
- pl.intensity = g.lumens;
10815
- pl.distance = g.dist;
10816
- pl.decay = g.decay;
10817
- } else {
10818
- pl.intensity = (7 + g.range * 0.8) * lim(1 + 0.4 * (Math.sqrt(g.n) - 1), 1, 2.2);
10819
- pl.distance = Math.max(3, g.dm * 1.3 + 0.5);
10820
- pl.decay = 1.6;
10821
- }
10957
+ 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 };
10958
+ put(i, { position: g.pos, color: g.color, ...lamp });
10822
10959
  const rs = g.rects;
10823
- b0.set(rs[0] || EMPTY, i * 4);
10824
- b1.set(rs[1] || EMPTY, i * 4);
10825
- b2.set(rs[2] || EMPTY, i * 4);
10826
- by.set(g.y, i * 2);
10827
- ex.set(g.exclusion || EMPTY, i * 4);
10960
+ b0.set(rs[0] || EMPTY, at2 * 4);
10961
+ b1.set(rs[1] || EMPTY, at2 * 4);
10962
+ b2.set(rs[2] || EMPTY, at2 * 4);
10963
+ by.set(g.y, at2 * 2);
10964
+ ex.set(g.exclusion || EMPTY, at2 * 4);
10828
10965
  if (i < NS) {
10829
10966
  shadows++;
10830
10967
  const s2 = g.house.group.uuid + "|" + g.k + "|" + g.pos.map((v) => v.toFixed(2)).join(",");
10831
10968
  if (sig[i] !== s2) {
10832
10969
  sig[i] = s2;
10833
- pl.shadow.needsUpdate = true;
10970
+ if (lease) lease.invalidateShadow(i);
10971
+ else PL[i].shadow.needsUpdate = true;
10834
10972
  seg.n++;
10835
10973
  A.stats.shadowChanged = true;
10836
10974
  }
10837
- pl.shadow.camera.far = Math.max(4, pl.distance);
10975
+ if (!lease) PL[i].shadow.camera.far = Math.max(4, lamp.distance);
10838
10976
  }
10839
10977
  }
10978
+ if (effects) effects.update(houses2);
10840
10979
  A.stats.shadows = shadows;
10841
10980
  return A.stats;
10842
10981
  };
10843
10982
  A.dispose = () => {
10983
+ if (lease) {
10984
+ lease.release();
10985
+ return;
10986
+ }
10844
10987
  for (const l of PL) {
10845
10988
  if (l.shadow && l.shadow.map) l.shadow.map.dispose();
10846
10989
  l.removeFromParent();
@@ -10848,59 +10991,116 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
10848
10991
  };
10849
10992
  return A;
10850
10993
  }
10851
- Object.assign(K, { MAX_LIGHTS, EMPTY, LZU, roomLightMask, lighting });
10852
- }
10853
-
10854
- // ../game-contracts/dist/index.js
10855
- var DEFAULT_IGNITION_RULES = {
10856
- fillCurve: [
10857
- [0, 0.6],
10858
- [0.1, 1],
10859
- [0.35, 0.6],
10860
- [0.65, 0.3],
10861
- [0.9, 0.15],
10862
- [1, 0.12]
10863
- ],
10864
- flameFillInfluence: 0.5,
10865
- flameDefaults: { incendiary: 0.7, tracer: 0.2 },
10866
- sparkTarget: { steel: 1, iron: 1, aluminium: 0.25 },
10867
- sparkMetalDefault: 0.5,
10868
- sparkFluid: { diesel: 0.15 },
10869
- fireNear: { radius: 1.5, chancePerSecond: 0.5 }
10870
- };
10871
- var resolveIgnitionRules = (rules) => ({
10872
- ...DEFAULT_IGNITION_RULES,
10873
- ...rules,
10874
- flameDefaults: { ...DEFAULT_IGNITION_RULES.flameDefaults, ...rules?.flameDefaults },
10875
- sparkTarget: { ...DEFAULT_IGNITION_RULES.sparkTarget, ...rules?.sparkTarget },
10876
- sparkFluid: { ...DEFAULT_IGNITION_RULES.sparkFluid, ...rules?.sparkFluid },
10877
- fireNear: { ...DEFAULT_IGNITION_RULES.fireNear, ...rules?.fireNear }
10878
- });
10879
- var fillFactor = (fill2, curve = DEFAULT_IGNITION_RULES.fillCurve) => {
10880
- if (curve.length === 0) return 1;
10881
- const f = Math.min(1, Math.max(0, fill2));
10882
- if (f <= curve[0][0]) return curve[0][1];
10883
- for (let i = 1; i < curve.length; i++) {
10884
- const [x1, y1] = curve[i];
10885
- const [x0, y0] = curve[i - 1];
10886
- if (f <= x1) return x1 === x0 ? y1 : y0 + (y1 - y0) * (f - x0) / (x1 - x0);
10994
+ const effectsByBudget = /* @__PURE__ */ new WeakMap();
10995
+ const OPENING_REACH = 2.5, OPENING_SIDE = 0.6;
10996
+ function effectLights(o) {
10997
+ o = o || {};
10998
+ const budget = o.budget;
10999
+ if (!budget) throw new Error("effectLights: a LightBudget is required");
11000
+ const known = effectsByBudget.get(budget);
11001
+ if (known && !o.ours) return known;
11002
+ const ours = new Set(o.ours || []), size = budget.sizes.point, _p = new THREE.Vector3(), P0 = new THREE.Vector3();
11003
+ for (const [key, width] of [["uRB0", 4], ["uRB1", 4], ["uRB2", 4], ["uRBY", 2], ["uREx", 4]]) {
11004
+ const had = LZU[key].value;
11005
+ if (had.length >= size * width) continue;
11006
+ const next = new Float32Array(size * width);
11007
+ if (width === 4) for (let i = 0; i < size; i++) next.set(EMPTY, i * 4);
11008
+ next.set(had);
11009
+ LZU[key].value = next;
11010
+ }
11011
+ const roomBoxes = (house, M) => {
11012
+ const list2 = [];
11013
+ for (const c of house.r.rooms) {
11014
+ if (c.type === "void") continue;
11015
+ const rs = [];
11016
+ for (const [a, b, c2, d] of rectsOf2(c)) {
11017
+ let x0 = 1e9, z0 = 1e9, x1 = -1e9, z1 = -1e9;
11018
+ for (const [u, v] of [[a, b], [c2, b], [a, d], [c2, d]]) {
11019
+ _p.set(u, 0, v).applyMatrix4(M);
11020
+ x0 = Math.min(x0, _p.x);
11021
+ x1 = Math.max(x1, _p.x);
11022
+ z0 = Math.min(z0, _p.z);
11023
+ z1 = Math.max(z1, _p.z);
11024
+ }
11025
+ rs.push([x0 - 0.08, z0 - 0.08, x1 + 0.08, z1 + 0.08]);
11026
+ }
11027
+ _p.set(0, house.y0(c.floor), 0).applyMatrix4(M);
11028
+ list2.push({ c, rs, y: [_p.y - 0.06, _p.y + (c.ceiling || 2.8) + 0.3] });
11029
+ }
11030
+ return list2;
11031
+ };
11032
+ const boxAt = (boxes, x, y, z) => boxes.find((q) => y >= q.y[0] && y <= q.y[1] && q.rs.some((r) => x >= r[0] && x <= r[2] && z >= r[1] && z <= r[3]));
11033
+ const openingSides = (house, M, boxes, into) => {
11034
+ const open = house.internal && house.internal.doorState || /* @__PURE__ */ new Map();
11035
+ const reach = (list2, isDoor) => {
11036
+ for (const p of list2 || []) {
11037
+ if (isDoor && !(open.has(p.id) ? open.get(p.id) : !!p.open)) continue;
11038
+ 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);
11039
+ const at2 = (side) => _p.set(alongU ? p.at : p.pos + side, y, alongU ? p.pos + side : p.at).applyMatrix4(M).clone();
11040
+ if (at2(0).distanceTo(P0) > OPENING_REACH) continue;
11041
+ for (const side of [-OPENING_SIDE, OPENING_SIDE]) {
11042
+ const w = at2(side), hit = boxAt(boxes, w.x, w.y, w.z);
11043
+ if (hit) into.set(hit.c, hit);
11044
+ }
11045
+ }
11046
+ };
11047
+ reach(house.r.doors, true);
11048
+ reach(house.r.windows, false);
11049
+ };
11050
+ const clear = (at2) => {
11051
+ for (const key of ["uRB0", "uRB1", "uRB2", "uREx"]) LZU[key].value.set(EMPTY, at2 * 4);
11052
+ LZU.uRBY.value.set([0, 0], at2 * 2);
11053
+ };
11054
+ const write = (at2, into) => {
11055
+ const rs = [];
11056
+ let lo = 1e9, hi = -1e9;
11057
+ for (const q of into.values()) {
11058
+ rs.push(...q.rs);
11059
+ lo = Math.min(lo, q.y[0]);
11060
+ hi = Math.max(hi, q.y[1]);
11061
+ }
11062
+ while (rs.length > 3) {
11063
+ const r23 = rs.pop(), r1 = rs[rs.length - 1];
11064
+ 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])];
11065
+ }
11066
+ LZU.uRB0.value.set(rs[0] || EMPTY, at2 * 4);
11067
+ LZU.uRB1.value.set(rs[1] || EMPTY, at2 * 4);
11068
+ LZU.uRB2.value.set(rs[2] || EMPTY, at2 * 4);
11069
+ LZU.uRBY.value.set([lo, hi], at2 * 2);
11070
+ LZU.uREx.value.set(EMPTY, at2 * 4);
11071
+ };
11072
+ const A = {
11073
+ /** 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. */
11074
+ update(houses2) {
11075
+ const indoors = cfg.effectLightsIndoors !== false, list2 = (Array.isArray(houses2) ? houses2 : [houses2]).filter(Boolean), cache2 = /* @__PURE__ */ new Map();
11076
+ for (let at2 = 0; at2 < size; at2++) {
11077
+ if (ours.has(at2)) continue;
11078
+ const light = budget.group.children[at2];
11079
+ if (!indoors || !light || !light.isPointLight || !(light.intensity > 0) || !light.visible) {
11080
+ clear(at2);
11081
+ continue;
11082
+ }
11083
+ const into = /* @__PURE__ */ new Map();
11084
+ P0.set(light.position.x, light.position.y, light.position.z);
11085
+ for (const house of list2) {
11086
+ if (house.group && !house.group.visible) continue;
11087
+ house.house.updateMatrixWorld(true);
11088
+ const M = house.house.matrixWorld;
11089
+ let boxes = cache2.get(house);
11090
+ if (!boxes) cache2.set(house, boxes = roomBoxes(house, M));
11091
+ const here = boxAt(boxes, P0.x, P0.y, P0.z);
11092
+ if (here) into.set(here.c, here);
11093
+ openingSides(house, M, boxes, into);
11094
+ }
11095
+ if (into.size) write(at2, into);
11096
+ else clear(at2);
11097
+ }
11098
+ }
11099
+ };
11100
+ if (!o.ours) effectsByBudget.set(budget, A);
11101
+ return A;
10887
11102
  }
10888
- return curve[curve.length - 1][1];
10889
- };
10890
- var sparkTargetFactor = (materialId, metal, rules = DEFAULT_IGNITION_RULES) => !metal ? 0 : rules.sparkTarget[materialId] ?? rules.sparkMetalDefault;
10891
- var ignitionChance = (hit, target, rules = DEFAULT_IGNITION_RULES) => {
10892
- const k = fillFactor(target.fill, rules.fillCurve);
10893
- const spark = (hit.ignition?.spark ?? 0) * target.sparkTarget * (target.fluid === void 0 ? 1 : rules.sparkFluid[target.fluid] ?? 1) * k;
10894
- const flameBase = hit.ignition?.flame ?? Math.max(hit.incendiary ? rules.flameDefaults.incendiary : 0, hit.tracer ? rules.flameDefaults.tracer : 0);
10895
- const flame = flameBase * (1 - rules.flameFillInfluence * (1 - k));
10896
- return 1 - (1 - Math.min(1, Math.max(0, spark))) * (1 - Math.min(1, Math.max(0, flame)));
10897
- };
10898
- var KIT_ORIGIN_KEY = "kitOrigin";
10899
- function markKitOrigin(object2, origin) {
10900
- const node = object2;
10901
- node.userData = node.userData ?? {};
10902
- node.userData[KIT_ORIGIN_KEY] = { ...origin };
10903
- return object2;
11103
+ Object.assign(K, { MAX_LIGHTS, EMPTY, LZU, roomLightMask, lighting, effectLights });
10904
11104
  }
10905
11105
 
10906
11106
  // ../game-destruction/dist/index.js
@@ -11250,11 +11450,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
11250
11450
  }
11251
11451
  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] }));
11252
11452
  };
11253
- var severAt = (inp, root, at, gap) => {
11453
+ var severAt = (inp, root, at2, gap) => {
11254
11454
  const dims = root.s;
11255
11455
  const L = [0, 1, 2].sort((x, y) => dims[y] - dims[x])[0];
11256
11456
  const half = dims[L] / 2;
11257
- const lo = clamp(at - gap / 2, -half, half), hi = clamp(at + gap / 2, -half, half);
11457
+ const lo = clamp(at2 - gap / 2, -half, half), hi = clamp(at2 + gap / 2, -half, half);
11258
11458
  const out = [];
11259
11459
  const stub = (from, to) => {
11260
11460
  if (to - from < 0.02) return;
@@ -13613,6 +13813,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13613
13813
  const ignitionRules = resolveIgnitionRules(options.ignition);
13614
13814
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
13615
13815
  const chipEnergy = options.breach?.chipEnergy ?? CHIP_ENERGY;
13816
+ const chainLinkCap = options.chainLinksPerUpdate !== void 0 && options.chainLinksPerUpdate >= 1 ? Math.floor(options.chainLinksPerUpdate) : Infinity;
13616
13817
  const rng2 = createRng(options.seed ?? 1);
13617
13818
  const rnd = () => rng2.next();
13618
13819
  let time = 0;
@@ -13635,7 +13836,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13635
13836
  const typed = listeners.get(e.type);
13636
13837
  if (typed) for (const fn of [...typed]) fn(e);
13637
13838
  };
13638
- const base = (o, source) => source === void 0 ? { object: o.id, time } : { object: o.id, time, source };
13839
+ let blow;
13840
+ const base = (o, source) => ({
13841
+ object: o.id,
13842
+ time,
13843
+ ...source === void 0 ? {} : { source },
13844
+ ...blow === void 0 ? {} : { blow }
13845
+ });
13639
13846
  const pendingCollapse = /* @__PURE__ */ new Map();
13640
13847
  let runDepth = 0;
13641
13848
  const drainBlasts = () => {
@@ -13905,9 +14112,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
13905
14112
  const [lo, hi] = contactBox(p, above);
13906
14113
  return toWorld(p.pos, p.q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
13907
14114
  };
13908
- const overSupportAt = (o, p, at, lower) => {
14115
+ const overSupportAt = (o, p, at2, lower) => {
13909
14116
  const g = p.bend;
13910
- const local = toLocal(p.pos, p.q, at);
14117
+ const local = toLocal(p.pos, p.q, at2);
13911
14118
  const depth = g.gravity >= 0 ? g.dims[g.gravity] : Math.min(...g.depthAxes.map((a) => g.dims[a]));
13912
14119
  let best = -1;
13913
14120
  let bestGap = depth;
@@ -14061,10 +14268,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14061
14268
  const incoming = new Array(n).fill(0);
14062
14269
  const bending = new Array(n).fill(0);
14063
14270
  const through = parts.map(() => []);
14064
- const pass = (k, at, mass) => {
14065
- const over = overSupportAt(o, parts[k], at, lowerOf.get(k) ?? []);
14271
+ const pass = (k, at2, mass) => {
14272
+ const over = overSupportAt(o, parts[k], at2, lowerOf.get(k) ?? []);
14066
14273
  if (over < 0) bending[k] += mass;
14067
- else through[k].push({ at, mass, over });
14274
+ else through[k].push({ at: at2, mass, over });
14068
14275
  };
14069
14276
  for (let h = queue.length - 1; h >= 0; h--) {
14070
14277
  const i = queue[h];
@@ -14131,7 +14338,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14131
14338
  const terrain = cfg.terrains.get(cfg.terrain);
14132
14339
  if (!terrain) throw new Error(`unknown wind terrain "${cfg.terrain}"`);
14133
14340
  const inputs = [];
14134
- const at = /* @__PURE__ */ new Map();
14341
+ const at2 = /* @__PURE__ */ new Map();
14135
14342
  const keep = [];
14136
14343
  let ground = Infinity, foundationTop = -Infinity;
14137
14344
  o.parts.forEach((p, i) => {
@@ -14140,7 +14347,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14140
14347
  const role = p.spec.structure?.role;
14141
14348
  const e = p.mat.strength?.elasticGPa;
14142
14349
  const kind = role === "wall" ? "wall" : role === "column" ? "column" : role === "beam" ? "beam" : role === "foundation" ? "foundation" : "other";
14143
- at.set(i, inputs.length);
14350
+ at2.set(i, inputs.length);
14144
14351
  keep.push(i);
14145
14352
  ground = Math.min(ground, box3.min[1]);
14146
14353
  if (kind === "foundation") foundationTop = Math.max(foundationTop, box3.max[1]);
@@ -14151,7 +14358,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14151
14358
  const beams = [];
14152
14359
  for (const j of o.byPart.get(o.parts[i].spec.id)) {
14153
14360
  if (j.st.open) continue;
14154
- const b = at.get(j.ia === i ? j.ib : j.ia);
14361
+ const b = at2.get(j.ia === i ? j.ib : j.ia);
14155
14362
  if (b !== void 0 && inputs[b].kind === "beam") beams.push(b);
14156
14363
  }
14157
14364
  inputs[k].beams = beams;
@@ -14560,7 +14767,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14560
14767
  if (disc < 0) return true;
14561
14768
  return -b - Math.sqrt(disc) > maxDist;
14562
14769
  };
14563
- const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false) => {
14770
+ const raycastInternal = (origin, dir, maxDist, ignore, worldOnly = false, precise = false) => {
14564
14771
  const d = vec.normalize(dir);
14565
14772
  let best = null;
14566
14773
  for (const o of objects.values()) {
@@ -14575,13 +14782,25 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14575
14782
  }
14576
14783
  }
14577
14784
  for (const im of worldOnly ? [] : impls.values()) {
14785
+ let boxHit = null;
14578
14786
  for (const ps of im.impl.describe().parts) {
14579
14787
  if (ignore?.has(`${im.impl.id}\0${ps.id}`)) continue;
14580
14788
  const ov = im.poses.get(ps.id);
14581
14789
  const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), origin, d, ov?.pos);
14582
- if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
14583
- if (!best || r.t0 < best.distance) best = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
14790
+ if (!r || r.t1 < 0 || r.t0 > maxDist) continue;
14791
+ if (r.t0 < 0) {
14792
+ if (precise && im.impl.surfaceRay) boxHit = boxHit ?? { object: im.impl.id, part: ps.id, point: origin, normal: r.n, distance: 0, pathLen: r.t1 };
14793
+ continue;
14794
+ }
14795
+ if (!boxHit || r.t0 < boxHit.distance) boxHit = { object: im.impl.id, part: ps.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
14584
14796
  }
14797
+ if (!boxHit) continue;
14798
+ if (precise && im.impl.surfaceRay) {
14799
+ const far = best ? Math.min(maxDist, best.distance) : maxDist;
14800
+ const implId = im.impl.id;
14801
+ const s = im.impl.surfaceRay({ origin, direction: d, maxDistance: far, ...ignore ? { ignored: (part) => ignore.has(`${implId}\0${part}`) } : {} });
14802
+ if (s && (!best || s.distance < best.distance)) best = { object: implId, part: s.part, point: s.point, normal: s.normal, distance: s.distance, pathLen: s.pathLen };
14803
+ } else if (!best || boxHit.distance < best.distance) best = boxHit;
14585
14804
  }
14586
14805
  return best;
14587
14806
  };
@@ -14955,7 +15174,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14955
15174
  }
14956
15175
  };
14957
15176
  const tornToEdge = (o) => {
14958
- const { p, cell, a, b, at, crater: crater2, reach, gone } = o;
15177
+ const { p, cell, a, b, at: at2, crater: crater2, reach, gone } = o;
14959
15178
  const half = p.dims[a] / 2;
14960
15179
  const lo = cell.center[a] - cell.size[a] / 2, hi = cell.center[a] + cell.size[a] / 2;
14961
15180
  const free = (side, sign2) => {
@@ -14963,8 +15182,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
14963
15182
  const probe = side + sign2 * 1e-3;
14964
15183
  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);
14965
15184
  };
14966
- if (at - lo <= reach && free(lo, -1)) return [lo, Math.max(crater2[1], lo)];
14967
- if (hi - at <= reach && free(hi, 1)) return [Math.min(crater2[0], hi), hi];
15185
+ if (at2 - lo <= reach && free(lo, -1)) return [lo, Math.max(crater2[1], lo)];
15186
+ if (hi - at2 <= reach && free(hi, 1)) return [Math.min(crater2[0], hi), hi];
14968
15187
  return crater2;
14969
15188
  };
14970
15189
  const craterAcross = (energy) => clamp2(0.03 + 12e-4 * Math.sqrt(Math.max(0, energy)), 0.03, 0.14);
@@ -15768,7 +15987,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
15768
15987
  res.events = cur.slice(start);
15769
15988
  return res;
15770
15989
  };
15771
- const doHit = (o, hit, only) => {
15990
+ const withBlow = (type, fn) => () => {
15991
+ const outer = blow;
15992
+ blow = type;
15993
+ try {
15994
+ return fn();
15995
+ } finally {
15996
+ blow = outer;
15997
+ }
15998
+ };
15999
+ const doHit = (o, hit, only) => withBlow(hit.type, () => resolveHit(o, hit, only))();
16000
+ const resolveHit = (o, hit, only) => {
15772
16001
  let point = hit.point;
15773
16002
  let dir = hit.direction;
15774
16003
  if (hit.space === "local") {
@@ -16065,7 +16294,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16065
16294
  };
16066
16295
  const detonatePrimed = (until) => {
16067
16296
  const end = time;
16068
- for (let next = nextPrimed(until); next; next = nextPrimed(until)) {
16297
+ let fired = 0;
16298
+ for (let next = nextPrimed(until); next && fired < chainLinkCap; next = nextPrimed(until)) {
16299
+ fired++;
16069
16300
  primed.delete(next.p);
16070
16301
  time = Math.max(0, Math.min(end, next.p.rt.chainDue));
16071
16302
  next.p.rt.chainDue = void 0;
@@ -16135,6 +16366,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16135
16366
  updateBound(o);
16136
16367
  },
16137
16368
  remove(id) {
16369
+ impls.get(id)?.impl.dispose?.();
16138
16370
  objects.delete(id);
16139
16371
  impls.delete(id);
16140
16372
  },
@@ -16204,13 +16436,51 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16204
16436
  blastProbe() {
16205
16437
  return { ...blastProbe };
16206
16438
  },
16439
+ blastReach(point, target, options2 = {}) {
16440
+ const L = vec.distance(point, target);
16441
+ if (L < 1e-4) return 1;
16442
+ const shelter = { ...SHELTER_DEFAULTS, ...options2.shelter };
16443
+ const ignore = new Set(options2.ignore ?? []);
16444
+ const d = vec.scale(vec.sub(target, point), 1 / L);
16445
+ let t = 1;
16446
+ for (const o of objects.values()) {
16447
+ if (ignore.has(o.id) || raySphereMiss(point, d, toWorld(o.st.position, o.st.rotation, o.bc), o.br, L)) continue;
16448
+ for (const p of o.parts) {
16449
+ if (isLost(p) || p.st.stripped) continue;
16450
+ const r = rayPart(o, p, point, d);
16451
+ if (!r) continue;
16452
+ const t0 = Math.max(r.t0, 0);
16453
+ const path = Math.min(r.t1, L) - t0;
16454
+ if (path < shelter.minPath) continue;
16455
+ if (p.st.opening && insideOpening(o, p, toLocal(o.st.position, o.st.rotation, vec.madd(point, d, t0 + path / 2)))) continue;
16456
+ t *= Math.exp(-(p.mat.hpPerKg * p.mat.density * path * (p.mat.damageScale?.shelter ?? 1)) / shelter.k);
16457
+ if (t < 1e-4) return 0;
16458
+ }
16459
+ }
16460
+ for (const im of impls.values()) {
16461
+ if (ignore.has(im.impl.id)) continue;
16462
+ for (const ps of im.impl.describe().parts) {
16463
+ const mat = materials.get(ps.material);
16464
+ if (!mat) continue;
16465
+ const ov = im.poses.get(ps.id);
16466
+ const r = rayPartRaw(im.position, im.rotation, ps, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0]), point, d, ov?.pos);
16467
+ if (!r || r.t1 < 0 || r.t0 > L) continue;
16468
+ let path = Math.min(r.t1, L) - Math.max(r.t0, 0);
16469
+ if (ps.thickness !== void 0 && ps.thickness > 0) path = Math.min(path, 2 * ps.thickness);
16470
+ else if (path < shelter.minPath) continue;
16471
+ t *= Math.exp(-(mat.hpPerKg * mat.density * path * (mat.damageScale?.shelter ?? 1)) / shelter.k);
16472
+ if (t < 1e-4) return 0;
16473
+ }
16474
+ }
16475
+ return t;
16476
+ },
16207
16477
  explode(point, energy, radius, source, blast) {
16208
16478
  blastProbe.partsTested = 0;
16209
16479
  blastProbe.partsNear = 0;
16210
16480
  return withCollapses(run(() => blastWorld(point, energy, radius, source, blast)));
16211
16481
  },
16212
16482
  raycast(origin, direction, maxDistance = Infinity) {
16213
- const r = raycastInternal(origin, direction, maxDistance);
16483
+ const r = raycastInternal(origin, direction, maxDistance, void 0, false, true);
16214
16484
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
16215
16485
  },
16216
16486
  sweepBox(box3, direction, distance2, options2 = {}) {
@@ -16228,21 +16498,23 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16228
16498
  },
16229
16499
  shoot(origin, direction, energy, caliber, source, round3) {
16230
16500
  const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
16231
- return withCollapses(run(() => {
16501
+ return withCollapses(run(withBlow("ballistic", () => {
16232
16502
  const results = [];
16233
16503
  const d = vec.normalize(direction);
16234
16504
  const ignore = /* @__PURE__ */ new Set();
16235
16505
  let org = origin;
16236
16506
  let E = energy;
16237
16507
  for (let i = 0; i < 4 && E > 0; i++) {
16238
- const rh = raycastInternal(org, d, Infinity, ignore);
16508
+ const rh = raycastInternal(org, d, Infinity, ignore, false, true);
16239
16509
  if (!rh) break;
16240
16510
  const im = impls.get(rh.object);
16241
16511
  if (im) {
16242
- const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
16512
+ const scaled = extras.penetration && E < energy ? { ...extras, penetration: Object.fromEntries(Object.entries(extras.penetration).map(([k, mm]) => [k, mm * (E / energy)])) } : extras;
16513
+ 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 } : {} });
16243
16514
  r.part = rh.part;
16244
16515
  r.point = rh.point;
16245
- r.exitEnergy = r.penetrated ? E * 0.5 : 0;
16516
+ const left = r.events.find((e) => e.type === "hole" && e.energyLeft !== void 0);
16517
+ r.exitEnergy = r.penetrated ? left?.energyLeft ?? E * 0.5 : 0;
16246
16518
  results.push(r);
16247
16519
  ignore.add(`${im.impl.id}\0${rh.part}`);
16248
16520
  if (!r.penetrated) break;
@@ -16261,7 +16533,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16261
16533
  E = out.exitEnergy;
16262
16534
  }
16263
16535
  return results;
16264
- }));
16536
+ })));
16265
16537
  },
16266
16538
  disassemble(objectId, jointId, tool, dt) {
16267
16539
  const im = impls.get(objectId);
@@ -16561,12 +16833,27 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16561
16833
  breach: { area: 0.1 }
16562
16834
  }
16563
16835
  ];
16836
+ var SHIPPED = structuredClone(assemblies_default);
16564
16837
  var ASSEMBLIES = assemblies_default;
16565
- var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
16838
+ var assembliesOver = (defs) => {
16839
+ const get = (id) => defs.find((a) => a.id === id);
16840
+ return {
16841
+ list: () => defs,
16842
+ get,
16843
+ register(def) {
16844
+ const at2 = defs.findIndex((a) => a.id === def.id);
16845
+ if (at2 >= 0) defs[at2] = def;
16846
+ else defs.push(def);
16847
+ },
16848
+ part: (assemblyId, options) => buildAssemblyPart(get(assemblyId), assemblyId, options)
16849
+ };
16850
+ };
16851
+ var defaultSet;
16852
+ var defaultAssemblies = () => defaultSet ?? (defaultSet = assembliesOver(ASSEMBLIES));
16853
+ var getAssembly = (id) => defaultAssemblies().get(id);
16566
16854
  var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
16567
16855
  var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
16568
- var assemblyPart = (assemblyId, options) => {
16569
- const def = getAssembly(assemblyId);
16856
+ var buildAssemblyPart = (def, assemblyId, options) => {
16570
16857
  if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
16571
16858
  const densityOf2 = (id) => {
16572
16859
  const m = options.materials?.get(id) ?? MATERIALS2.find((x) => x.id === id);
@@ -16593,6 +16880,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
16593
16880
  ...options.extra
16594
16881
  };
16595
16882
  };
16883
+ var assemblyPart = (assemblyId, options) => defaultAssemblies().part(assemblyId, options);
16596
16884
  var buildFrame = (o = {}) => {
16597
16885
  const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
16598
16886
  const nx = Math.max(2, o.columnsX ?? 3), nz = Math.max(2, o.columnsZ ?? 3);
@@ -17625,9 +17913,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
17625
17913
  }
17626
17914
  if (context.isTimber && construction.roof.shape !== "slab") addCeilingJoists(context);
17627
17915
  }
17916
+ var isStair = (item) => item.category === "stairs" && typeof item.link === "object" && item.link !== null;
17628
17917
  function flightsOf(items) {
17629
17918
  const flights = /* @__PURE__ */ new Map();
17630
- for (const item of items.filter((it) => it.category === "stairs" && it.link)) {
17919
+ for (const item of items.filter(isStair)) {
17631
17920
  const key = `${item.link.from}_${item.link.to}`;
17632
17921
  if (!flights.has(key) || item.floor === item.link.from) flights.set(key, item);
17633
17922
  }
@@ -19774,6 +20063,34 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19774
20063
  }
19775
20064
  function installDestructionBreach(K, _shared) {
19776
20065
  const { THREE, fragmentMaterial, invalidate, invalidateShadow } = K;
20066
+ const paintState = { attached: false };
20067
+ const paintQueues = /* @__PURE__ */ new Set();
20068
+ const paintQueue = {
20069
+ id: DEFERRED_QUEUE_ID.BREACH_PAINT,
20070
+ pending: () => {
20071
+ let n = 0;
20072
+ for (const q of paintQueues) n += q.pending();
20073
+ return n;
20074
+ },
20075
+ run(deadline, clock) {
20076
+ let ran = 0;
20077
+ for (const q of [...paintQueues]) {
20078
+ if (q.pending() === 0) continue;
20079
+ if (ran > 0 && clock() >= deadline) break;
20080
+ ran += q.run(deadline, clock);
20081
+ }
20082
+ return ran;
20083
+ },
20084
+ flush() {
20085
+ for (const q of [...paintQueues]) q.flush();
20086
+ },
20087
+ attach() {
20088
+ paintState.attached = true;
20089
+ },
20090
+ detach() {
20091
+ paintState.attached = false;
20092
+ }
20093
+ };
19777
20094
  function createBreachView(opts) {
19778
20095
  const { house, structure } = opts;
19779
20096
  const views = /* @__PURE__ */ new Map();
@@ -19979,17 +20296,17 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
19979
20296
  for (const { c, part, units: units2, thick, mid } of shown) {
19980
20297
  if (!near(c)) continue;
19981
20298
  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;
19982
- const at = (s, dy, d, colour) => {
20299
+ const at2 = (s, dy, d, colour) => {
19983
20300
  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);
19984
20301
  if (unit) list2.push(unit);
19985
20302
  };
19986
20303
  const base = fragmentMaterial(part.material).color.getHex();
19987
20304
  const n = noise2(centre * 3.1, y * 2.7, 0.07);
19988
20305
  if (units2.mortar === "concrete") {
19989
- at(len, h, thick - 2 * COVER2, tone(base, 0.9 + 0.04 * n));
20306
+ at2(len, h, thick - 2 * COVER2, tone(base, 0.9 + 0.04 * n));
19990
20307
  } else {
19991
- at(len, h, thick - 2 * COVER2 - 0.012, 10327948);
19992
- at(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
20308
+ at2(len, h, thick - 2 * COVER2 - 0.012, 10327948);
20309
+ at2(len - 0.012, h - 0.012, thick - 2 * COVER2, tone(base, 0.8 + 0.35 * n));
19993
20310
  }
19994
20311
  }
19995
20312
  if (view.units) {
@@ -20088,8 +20405,10 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20088
20405
  else s.data = collapseRange(s.it.mesh, s.v0, s.it.v1);
20089
20406
  }
20090
20407
  }
20091
- function flush(budgetMs = paintBudgetMs) {
20408
+ function flush(budgetMs) {
20092
20409
  if (dirty.size === 0) return 0;
20410
+ if (budgetMs === void 0 && paintState.attached) return dirty.size;
20411
+ budgetMs ?? (budgetMs = paintBudgetMs);
20093
20412
  const deadline = performance.now() + budgetMs;
20094
20413
  for (const view of dirty) {
20095
20414
  dirty.delete(view);
@@ -20100,6 +20419,34 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20100
20419
  invalidateShadow();
20101
20420
  return dirty.size;
20102
20421
  }
20422
+ const queue = {
20423
+ id: DEFERRED_QUEUE_ID.BREACH_PAINT,
20424
+ pending: () => dirty.size,
20425
+ run(deadline, clock) {
20426
+ let ran = 0;
20427
+ for (const view of dirty) {
20428
+ dirty.delete(view);
20429
+ paint(view);
20430
+ ran++;
20431
+ if (clock() >= deadline) break;
20432
+ }
20433
+ if (ran > 0) {
20434
+ invalidate();
20435
+ invalidateShadow();
20436
+ }
20437
+ return ran;
20438
+ },
20439
+ flush() {
20440
+ flush(Infinity);
20441
+ },
20442
+ attach() {
20443
+ paintState.attached = true;
20444
+ },
20445
+ detach() {
20446
+ paintState.attached = false;
20447
+ }
20448
+ };
20449
+ paintQueues.add(queue);
20103
20450
  function dispose(view) {
20104
20451
  dirty.delete(view);
20105
20452
  if (view.units) {
@@ -20134,6 +20481,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20134
20481
  */
20135
20482
  claim() {
20136
20483
  var _a;
20484
+ paintQueues.add(queue);
20137
20485
  for (const q of opts.spec()?.parts ?? []) {
20138
20486
  if (!q.units) continue;
20139
20487
  const host = hostOf(q.id);
@@ -20186,6 +20534,12 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20186
20534
  dropped.length = 0;
20187
20535
  },
20188
20536
  flush,
20537
+ /** Takes this house's painting out of the renderer's queue (the house goes away); `claim` puts it back. */
20538
+ release() {
20539
+ paintQueues.delete(queue);
20540
+ },
20541
+ /** The painting of this house alone. */
20542
+ deferredQueue: () => queue,
20189
20543
  /** The wall has damage drawn. */
20190
20544
  has(partId) {
20191
20545
  return views.has(partId);
@@ -20200,7 +20554,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20200
20554
  })
20201
20555
  };
20202
20556
  }
20203
- Object.assign(K, { createBreachView });
20557
+ Object.assign(K, { createBreachView, breachPaintQueue: paintQueue });
20204
20558
  }
20205
20559
 
20206
20560
  // src/destruction/world.ts
@@ -20268,11 +20622,11 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20268
20622
  };
20269
20623
  const extract = (it) => {
20270
20624
  const g0 = it.mesh.geometry, src = it.mesh.userData._p0 || g0.attributes.position.array, g = new THREE.BufferGeometry();
20271
- const at = (name, k, arr) => g.setAttribute(name, new THREE.BufferAttribute((arr || g0.attributes[name].array).slice(it.v0 * k, it.v1 * k), k));
20272
- at("position", 3, src);
20273
- at("normal", 3);
20274
- at("color", 3);
20275
- at("uv", 2);
20625
+ const at2 = (name, k, arr) => g.setAttribute(name, new THREE.BufferAttribute((arr || g0.attributes[name].array).slice(it.v0 * k, it.v1 * k), k));
20626
+ at2("position", 3, src);
20627
+ at2("normal", 3);
20628
+ at2("color", 3);
20629
+ at2("uv", 2);
20276
20630
  const ix = g0.index.array.slice(it.i0, it.i1);
20277
20631
  for (let k = 0; k < ix.length; k++) ix[k] -= it.v0;
20278
20632
  g.setIndex(new THREE.BufferAttribute(ix, 1));
@@ -20455,6 +20809,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20455
20809
  if (R && D.id && D.active) R.unbind(D.id);
20456
20810
  if (D.active && D.world.has(D.id)) D.world.remove(D.id);
20457
20811
  D.active = false;
20812
+ breach.release();
20458
20813
  };
20459
20814
  D.follow = (world, id, spec) => {
20460
20815
  D.spec = spec;
@@ -20489,6 +20844,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20489
20844
  return { restore: () => breach.restore(), dispose: () => {
20490
20845
  offFollow();
20491
20846
  breach.restore();
20847
+ breach.release();
20492
20848
  }, update: () => {
20493
20849
  breach.flush();
20494
20850
  } };
@@ -20985,9 +21341,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
20985
21341
  shared.vegMaterials.setActors(focus ? [{ x: focus.x, y: focus.y, z: focus.z, radius: 0.9 }] : []);
20986
21342
  }
20987
21343
  if (focus && cfg.lod !== false) {
20988
- const L = Object.assign({ interior: 45, hide: 220 }, cfg.lod), d = distance2(focus), at = h.lodCurrent;
21344
+ const L = Object.assign({ interior: 45, hide: 220 }, cfg.lod), d = distance2(focus), at2 = h.lodCurrent;
20989
21345
  let n = d > L.hide ? 2 : d > L.interior ? 1 : 0;
20990
- if (n < at) n = d < (at === 2 ? L.hide : L.interior) * 0.92 ? n : at;
21346
+ if (n < at2) n = d < (at2 === 2 ? L.hide : L.interior) * 0.92 ? n : at2;
20991
21347
  applyLod(Math.max(n, h.lodMin));
20992
21348
  }
20993
21349
  },
@@ -21101,7 +21457,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21101
21457
 
21102
21458
  // src/build/api.ts
21103
21459
  function buildApi(K, shared) {
21104
- const { cfg, VERSION: VERSION2, ATLAS, TEX, applyPart, carGroup, carModel, props, TYPES, roomLightMask, lighting, newState, build } = K;
21460
+ const { cfg, VERSION: VERSION2, ATLAS, TEX, applyPart, carGroup, carModel, props, TYPES, roomLightMask, lighting, effectLights, newState, build } = K;
21105
21461
  return {
21106
21462
  version: VERSION2,
21107
21463
  build,
@@ -21116,10 +21472,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21116
21472
  },
21117
21473
  roomLightMask,
21118
21474
  lighting,
21475
+ effectLights,
21119
21476
  setNight(n) {
21120
21477
  shared.mats.glass.emissive.set(n ? 9067040 : 0);
21121
21478
  shared.mats.glass.opacity = n ? 0.4 : 0.24;
21122
21479
  },
21480
+ /** The chipped-plaster painting of every house of this renderer as a `DeferredQueue` for a frame budget (id `floorplan.breach-paint`); asking for it changes nothing. */
21481
+ deferredQueue: () => K.breachPaintQueue,
21123
21482
  cars: { model: carModel, group: carGroup, info: (k, seed) => {
21124
21483
  const m = carModel(k, seed);
21125
21484
  return m && { parts: m.parts.length, tris: m.tris, L: m.L, W: m.W, H: m.H };
@@ -21586,6 +21945,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21586
21945
  return { off, reset: () => navigator.undoChanges(houseId) };
21587
21946
  }
21588
21947
 
21948
+ // ../game-render-three/dist/index.js
21949
+ var DEFAULT_ANGLE = Math.PI / 3;
21950
+ function isLightBudget(value) {
21951
+ const candidate = value;
21952
+ return typeof candidate === "object" && candidate !== null && typeof candidate.acquire === "function" && typeof candidate.addSource === "function" && typeof candidate.lease === "function" && typeof candidate.present === "function";
21953
+ }
21954
+ var EFFECT_STEP_SECONDS = 1 / 50;
21955
+
21589
21956
  // src/test-area/text.ts
21590
21957
  var TEXT = {
21591
21958
  en: {
@@ -21608,7 +21975,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21608
21975
  hit: { burst_9mm: "9 mm burst", burst_762: "7.62 burst", sledge: "Sledgehammer", c4: "C4", rpg: "RPG warhead" },
21609
21976
  state: { normal: "Normal", fortified: "Fortified", abandoned: "Abandoned" }
21610
21977
  },
21611
- layers: { roof: "Roof", lot: "Lot (ground, wall, gate)", night: "Night (lit windows and lamps)" },
21978
+ layers: { roof: "Roof", lot: "Lot (ground, wall, gate)", night: "Night (lit windows and lamps)", room_lights: "Room lights (one lamp per lit room, stopped by the walls)" },
21612
21979
  metrics: { houses: "Houses", parts: "Parts", standing: "Standing", broken: "Broken", holes: "Holes", draw_calls: "Draw calls", triangles: "Triangles", lod: "Level of detail (nearest)", floor: "Your floor", doors_open: "Doors open", target: "You aim at", planned: "Planned draw calls" },
21613
21980
  sheet: { kicker: "House", title: "House of the bench", template: "Typology", seed: "Seed", state: "State", floors: "Floors", size: "Size", parts: "Structure parts", look: "You aim at", none: "nothing to use", no_engine: "The page gave no floorplan engine" },
21614
21981
  specimens: {
@@ -21674,7 +22041,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21674
22041
  hit: { burst_9mm: "Rajada de 9 mm", burst_762: "Rajada de 7,62", sledge: "Marreta", c4: "C4", rpg: "Ogiva de RPG" },
21675
22042
  state: { normal: "Normal", fortified: "Fortificada", abandoned: "Abandonada" }
21676
22043
  },
21677
- layers: { roof: "Telhado", lot: "Lote (ch\xE3o, muro, port\xE3o)", night: "Noite (janelas e l\xE2mpadas acesas)" },
22044
+ layers: { roof: "Telhado", lot: "Lote (ch\xE3o, muro, port\xE3o)", night: "Noite (janelas e l\xE2mpadas acesas)", room_lights: "Luz por c\xF4modo (uma l\xE2mpada por c\xF4modo aceso, barrada pelas paredes)" },
21678
22045
  metrics: { houses: "Casas", parts: "Pe\xE7as", standing: "De p\xE9", broken: "Quebradas", holes: "Furos", draw_calls: "Draw calls", triangles: "Tri\xE2ngulos", lod: "N\xEDvel de detalhe (a mais perto)", floor: "Seu andar", doors_open: "Portas abertas", target: "Voc\xEA mira em", planned: "Draw calls planejados" },
21679
22046
  sheet: { kicker: "Casa", title: "Casa da bancada", template: "Tipologia", seed: "Semente", state: "Estado", floors: "Andares", size: "Tamanho", parts: "Pe\xE7as da estrutura", look: "Voc\xEA mira em", none: "nada para usar", no_engine: "A p\xE1gina n\xE3o deu o motor de plantas" },
21680
22047
  specimens: {
@@ -21727,14 +22094,14 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21727
22094
 
21728
22095
  // src/test-area/scenarios.ts
21729
22096
  var SPECIMEN_AT = [-5, 0, -11];
21730
- function createHouseScenarios(text, at = SPECIMEN_AT) {
22097
+ function createHouseScenarios(text, at2 = SPECIMEN_AT) {
21731
22098
  const T2 = text.steps;
21732
22099
  const spawn = (room, item) => {
21733
- const so = room.spawn(item, [at[0], at[1], at[2]], 0);
22100
+ const so = room.spawn(item, [at2[0], at2[1], at2[2]], 0);
21734
22101
  const parts = so.root.userData.floorplanSpec.parts;
21735
22102
  const centre = (id) => {
21736
22103
  const p = parts.find((x) => x.id === id);
21737
- return [p.position[0] + at[0], p.position[1] + at[1], p.position[2] + at[2]];
22104
+ return [p.position[0] + at2[0], p.position[1] + at2[1], p.position[2] + at2[2]];
21738
22105
  };
21739
22106
  return { oid: so.objectId, parts, centre };
21740
22107
  };
@@ -21971,6 +22338,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21971
22338
  let THREE = null;
21972
22339
  let houseRenderer = null;
21973
22340
  let houseRendererKey = "";
22341
+ let stopPaintQueue;
22342
+ let roomLights = null;
21974
22343
  const placed = [];
21975
22344
  let time = 0;
21976
22345
  let dirty = false;
@@ -21998,20 +22367,29 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
21998
22367
  const rendererFor = () => {
21999
22368
  const key = `${settings.quality}|${locale}`;
22000
22369
  if (!houseRenderer || houseRendererKey !== key) {
22370
+ stopPaintQueue?.();
22371
+ dropRoomLights();
22001
22372
  houseRenderer?.dispose?.();
22002
22373
  houseRenderer = create3(THREE, {
22003
22374
  locale,
22004
22375
  lod: { interior: 10, hide: 40 },
22005
22376
  breachPaintBudgetMs: 4,
22006
22377
  ...options.house,
22378
+ ...options.house?.interactives ? { interactives: { effectSink: () => host?.effects, ...options.house.interactives } } : {},
22007
22379
  quality: settings.quality,
22008
22380
  ...options.world ? { destruction: options.world } : {},
22009
22381
  ...options.renderer ? { destructionRenderer: options.renderer } : {}
22010
22382
  });
22011
22383
  houseRendererKey = key;
22384
+ const paintQueue = houseRenderer.deferredQueue?.();
22385
+ stopPaintQueue = paintQueue ? host?.defer?.(paintQueue, DEFERRED_PRIORITY[DEFERRED_QUEUE_ID.BREACH_PAINT]) : void 0;
22012
22386
  }
22013
22387
  return houseRenderer;
22014
22388
  };
22389
+ const dropRoomLights = () => {
22390
+ roomLights?.dispose();
22391
+ roomLights = null;
22392
+ };
22015
22393
  const clearHouses = () => {
22016
22394
  for (const p of placed) p.house.dispose();
22017
22395
  placed.length = 0;
@@ -22098,9 +22476,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22098
22476
  };
22099
22477
  const eyeOf = (p) => {
22100
22478
  const camera = host?.camera;
22101
- const at = toLocal2(p, camera.position.x, camera.position.y, camera.position.z);
22479
+ const at2 = toLocal2(p, camera.position.x, camera.position.y, camera.position.z);
22102
22480
  const forward = new THREE.Vector3(0, 0, -1).applyQuaternion(camera.quaternion).transformDirection(new THREE.Matrix4().copy(p.house.group.matrixWorld).invert());
22103
- 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))) };
22481
+ 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))) };
22104
22482
  };
22105
22483
  const blocked = (x, z, radius) => {
22106
22484
  for (const p of placed) {
@@ -22168,7 +22546,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22168
22546
  const doorReach = (p, fo) => {
22169
22547
  const g = fo.gs[0];
22170
22548
  const pivot = fo.handles[0].pivot;
22171
- const at = (angle) => {
22549
+ const at2 = (angle) => {
22172
22550
  const c = Math.cos(angle);
22173
22551
  const s = Math.sin(angle);
22174
22552
  const v = new THREE.Vector3(pivot[0] * c + pivot[2] * s, pivot[1], -pivot[0] * s + pivot[2] * c).add(g.position);
@@ -22177,8 +22555,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22177
22555
  const camera = host?.camera;
22178
22556
  const eye = new THREE.Vector3(camera.position.x, 0, camera.position.z);
22179
22557
  const flat = (v) => Math.hypot(v.x - eye.x, v.z - eye.z);
22180
- const closed = at(0);
22181
- const open = at(fo.rot);
22558
+ const closed = at2(0);
22559
+ const open = at2(fo.rot);
22182
22560
  const opening = !fo.open;
22183
22561
  return {
22184
22562
  handle: () => {
@@ -22249,6 +22627,7 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22249
22627
  }
22250
22628
  if (change.kind === "layer") {
22251
22629
  if (change.id === "roof") showAll();
22630
+ else if (change.id === "room_lights") dropRoomLights();
22252
22631
  else if (placed.length > 0) {
22253
22632
  dirty = true;
22254
22633
  dirtyAt = time;
@@ -22415,7 +22794,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22415
22794
  layers: () => [
22416
22795
  { id: "roof", name: text.layers.roof, default: true },
22417
22796
  { id: "lot", name: text.layers.lot, default: false },
22418
- { id: "night", name: text.layers.night, default: false }
22797
+ { id: "night", name: text.layers.night, default: false },
22798
+ { id: "room_lights", name: text.layers.room_lights, default: false }
22419
22799
  ],
22420
22800
  metrics: () => [
22421
22801
  { id: "houses_built", name: text.metrics.houses, read: () => placed.length },
@@ -22461,6 +22841,21 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22461
22841
  updateSheet();
22462
22842
  }
22463
22843
  });
22844
+ const stopEffectLights = lent.onFrame(
22845
+ () => {
22846
+ const budget = lent.lights;
22847
+ if (!isLightBudget(budget) || placed.length === 0 || !houseRenderer) return;
22848
+ const houses2 = placed.map((p) => p.house);
22849
+ if (lent.bench?.layer("room_lights") ?? false) {
22850
+ roomLights ?? (roomLights = houseRenderer.lighting({ scene: lent.scene, n: 3, shadows: 0, budget }));
22851
+ roomLights.update(houses2, cameraWorld(), {});
22852
+ return;
22853
+ }
22854
+ dropRoomLights();
22855
+ houseRenderer.effectLights?.({ budget })?.update(houses2);
22856
+ },
22857
+ { phase: "present", order: 20 }
22858
+ );
22464
22859
  const onKey = (event) => {
22465
22860
  if (event.code === "KeyE" && !event.repeat && placed.length > 0) useNearest();
22466
22861
  };
@@ -22468,9 +22863,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22468
22863
  return () => {
22469
22864
  stopChange?.();
22470
22865
  stopFrame();
22866
+ stopEffectLights();
22867
+ dropRoomLights();
22471
22868
  globalThis.removeEventListener?.("keydown", onKey);
22472
22869
  for (const remove of removers.splice(0)) remove();
22473
22870
  clearHouses();
22871
+ stopPaintQueue?.();
22872
+ stopPaintQueue = void 0;
22474
22873
  houseRenderer?.dispose?.();
22475
22874
  houseRenderer = null;
22476
22875
  host = null;
@@ -22865,8 +23264,9 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
22865
23264
  facadeExtra: (f, rng2, w, d, op) => {
22866
23265
  f.roof = "slab";
22867
23266
  f.finish = rng2() < 0.55 ? "exposed_block" : "partial_plaster";
22868
- 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 };
22869
- if (floorsOf(op) > 1) f.slab.exposedRebar = rng2() < 0.6;
23267
+ 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 };
23268
+ f.slab = slab;
23269
+ if (floorsOf(op) > 1) slab.exposedRebar = rng2() < 0.6;
22870
23270
  }
22871
23271
  };
22872
23272
  }
@@ -23578,13 +23978,13 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23578
23978
  function apartment(strip, floor, k, from, to, plate, ceiling, win) {
23579
23979
  const wide = to - from, depth = strip.depth, facade = r22(depth * 0.56);
23580
23980
  const id = (name) => `apt_${floor}_${strip.id}_${k}_${name}`;
23581
- const at = (f0, f1, b0, b1) => boxOf2(strip, r22(from + wide * f0), r22(from + wide * f1), b0, b1);
23981
+ const at2 = (f0, f1, b0, b1) => boxOf2(strip, r22(from + wide * f0), r22(from + wide * f1), b0, b1);
23582
23982
  const rooms = [
23583
- { id: id("living"), type: "living", label: "living", library: "living_tower", rect: at(0, 0.58, 0, facade) },
23584
- { id: id("bedroom"), type: "bedroom", label: "bedroom", rect: at(0.58, 1, 0, facade) },
23585
- { id: id("kitchen"), type: "kitchen", label: "kitchen", library: "kitchen_integrated", rect: at(0, 0.4, facade, depth) },
23586
- { id: id("hall"), type: "hall", label: "hall", rect: at(0.4, 0.78, facade, depth) },
23587
- { id: id("bath"), type: "bathroom", label: "bathroom", library: "bathroom_wc", rect: at(0.78, 1, facade, depth) }
23983
+ { id: id("living"), type: "living", label: "living", library: "living_tower", rect: at2(0, 0.58, 0, facade) },
23984
+ { id: id("bedroom"), type: "bedroom", label: "bedroom", rect: at2(0.58, 1, 0, facade) },
23985
+ { id: id("kitchen"), type: "kitchen", label: "kitchen", library: "kitchen_integrated", rect: at2(0, 0.4, facade, depth) },
23986
+ { id: id("hall"), type: "hall", label: "hall", rect: at2(0.4, 0.78, facade, depth) },
23987
+ { id: id("bath"), type: "bathroom", label: "bathroom", library: "bathroom_wc", rect: at2(0.78, 1, facade, depth) }
23588
23988
  ];
23589
23989
  const doors = [
23590
23990
  stripDoor(id("hall"), strip, floor, 0.95),
@@ -23617,8 +24017,8 @@ uniform mat4 uNodes[${nodes.length}]; attribute float aNodeIndex; attribute vec3
23617
24017
  if (strip.id !== "south") return apartments(plan)(strip, context);
23618
24018
  const { floor, plate, ceiling } = context;
23619
24019
  const lobby = `lobby_${floor}_entrance`, concierge = `concierge_${floor}`, bikes = `bike_room_${floor}`;
23620
- const at = (f0, f1) => boxOf2(strip, r22(strip.length * f0), r22(strip.length * f1), 0, strip.depth);
23621
- const [left, middle, right] = [at(0, 0.3), at(0.3, 0.7), at(0.7, 1)];
24020
+ const at2 = (f0, f1) => boxOf2(strip, r22(strip.length * f0), r22(strip.length * f1), 0, strip.depth);
24021
+ const [left, middle, right] = [at2(0, 0.3), at2(0.3, 0.7), at2(0.7, 1)];
23622
24022
  const rooms = [
23623
24023
  { id: concierge, type: "office", label: "concierge", rect: left },
23624
24024
  { id: lobby, type: "hall", label: "lobby_entrance", rect: middle },