@adatechnology/game-destruction 0.4.2 → 0.6.0

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.
package/dist/index.js CHANGED
@@ -419,6 +419,153 @@ var planFragments = (inp) => {
419
419
  return out;
420
420
  };
421
421
 
422
+ // src/breach.ts
423
+ var planeAxes = (thin) => thin === 1 ? [0, 2] : [thin === 0 ? 2 : 0, 1];
424
+ var unitGrid = (dims, thin, units, cap) => {
425
+ const [u, v] = planeAxes(thin);
426
+ let unitU = Math.min(units.size[0], dims[u]), unitV = Math.min(units.size[1], dims[v]);
427
+ const rowsFor = (h) => Math.ceil(dims[v] / h - 1e-9);
428
+ const colsFor = (w) => Math.ceil(dims[u] / w - 1e-9) + (units.bond === "running" ? 1 : 0);
429
+ const merge = Math.ceil(Math.sqrt(rowsFor(unitV) * colsFor(unitU) / cap));
430
+ if (merge > 1) {
431
+ unitU = Math.min(unitU * merge, dims[u]);
432
+ unitV = Math.min(unitV * merge, dims[v]);
433
+ }
434
+ const cells = [];
435
+ for (let row = 0; row < rowsFor(unitV); row++) {
436
+ const v0 = -dims[v] / 2 + row * unitV, v1 = Math.min(v0 + unitV, dims[v] / 2);
437
+ const shift = units.bond === "running" && row % 2 === 1 ? unitU / 2 : 0;
438
+ for (let col = 0; col < colsFor(unitU); col++) {
439
+ const u0 = Math.max(-dims[u] / 2 + col * unitU - shift, -dims[u] / 2), u1 = Math.min(-dims[u] / 2 + (col + 1) * unitU - shift, dims[u] / 2);
440
+ if (u1 - u0 < 0.01) continue;
441
+ const center = [0, 0, 0], size = [0, 0, 0];
442
+ center[u] = (u0 + u1) / 2;
443
+ center[v] = (v0 + v1) / 2;
444
+ size[u] = u1 - u0;
445
+ size[v] = v1 - v0;
446
+ size[thin] = dims[thin];
447
+ cells.push({ center, size });
448
+ }
449
+ }
450
+ return cells;
451
+ };
452
+ var planeDistance = (cell, thin, point) => {
453
+ const [u, v] = planeAxes(thin);
454
+ const du = cell.center[u] - point[u], dv = cell.center[v] - point[v];
455
+ return Math.sqrt(du * du + dv * dv);
456
+ };
457
+ var cellsWithin = (cells, thin, point, radius) => {
458
+ const inside = cells.filter((c) => planeDistance(c, thin, point) <= radius);
459
+ if (inside.length > 0 || cells.length === 0) return inside;
460
+ return [cells.reduce((best, c) => planeDistance(c, thin, point) < planeDistance(best, thin, point) ? c : best)];
461
+ };
462
+ var cellsOutside = (cells, thin, point, radius) => cells.filter((c) => planeDistance(c, thin, point) > radius);
463
+ var MAX_MARKS_PER_FRAGMENT = 12;
464
+ var marksInBox = (center, size, rotation, holes) => {
465
+ const inverse = rotation ? quat.conjugate(rotation) : void 0;
466
+ const marks = [];
467
+ for (const hole of holes) {
468
+ const offset = [hole.point[0] - center[0], hole.point[1] - center[1], hole.point[2] - center[2]];
469
+ const point = inverse ? rotateVec(inverse, offset) : offset;
470
+ const inside = point.every((value, k) => Math.abs(value) <= size[k] / 2 + hole.diameter / 2);
471
+ if (!inside) continue;
472
+ marks.push({ point, normal: inverse ? rotateVec(inverse, hole.normal) : hole.normal, diameter: hole.diameter });
473
+ if (marks.length >= MAX_MARKS_PER_FRAGMENT) break;
474
+ }
475
+ return marks;
476
+ };
477
+
478
+ // src/layers.ts
479
+ var MAX_SPALLS = 64;
480
+ var thinAxisOf = (dims) => dims[0] <= dims[1] && dims[0] <= dims[2] ? 0 : dims[1] <= dims[2] ? 1 : 2;
481
+ var resolveLayer = (def, index, mat) => ({ index, def, mat, energy: mat.penetration * def.thickness * 1e3 });
482
+ var onFace = (layer, face) => layer.def.faces === "both" || layer.def.faces === (face > 0 ? "+" : "-");
483
+ var isSpalled = (spalls, layer, face, point) => {
484
+ for (const s of spalls ?? []) {
485
+ if (s[0] !== layer || s[1] !== face) continue;
486
+ const dx = s[2] - point[0], dy = s[3] - point[1], dz = s[4] - point[2];
487
+ if (dx * dx + dy * dy + dz * dz <= s[5] * s[5]) return true;
488
+ }
489
+ return false;
490
+ };
491
+ var outerLayer = (layers, spalls, face, point) => layers.find((l) => onFace(l, face) && !isSpalled(spalls, l.index, face, point));
492
+ var layersEnergy = (layers, spalls, face, point) => {
493
+ let sum = 0;
494
+ for (const l of layers) if (onFace(l, face) && !isSpalled(spalls, l.index, face, point)) sum += l.energy;
495
+ return sum;
496
+ };
497
+ var layersThickness = (layers, face) => {
498
+ let sum = 0;
499
+ for (const l of layers) if (onFace(l, face)) sum += l.def.thickness;
500
+ return sum;
501
+ };
502
+
503
+ // src/rebar.ts
504
+ var COVER = 0.03;
505
+ var MAX_BARS_PER_DIRECTION = 24;
506
+ var MAX_MEMBER_BARS_PER_SIDE = 6;
507
+ var STEEL_DENSITY = 7850;
508
+ var spread = (length, cover, spacing, cap) => {
509
+ const room = Math.max(0, length - 2 * cover);
510
+ const count = Math.min(cap, Math.max(2, Math.floor(room / spacing) + 1));
511
+ const step = count > 1 && room / (count - 1) < spacing ? room / (count - 1) : spacing;
512
+ return Array.from({ length: count }, (_, i) => (i - (count - 1) / 2) * step);
513
+ };
514
+ var bar = (axis, along, fixed) => {
515
+ const a = [0, 0, 0], b = [0, 0, 0];
516
+ for (const [index, value] of fixed) {
517
+ a[index] = value;
518
+ b[index] = value;
519
+ }
520
+ a[axis] = -along;
521
+ b[axis] = along;
522
+ return [a, b];
523
+ };
524
+ var isMember = (dims) => {
525
+ const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
526
+ return b / a < 3 && c / b >= 3;
527
+ };
528
+ var rebarBars = (dims, thin, spacing) => {
529
+ const out = [];
530
+ if (isMember(dims)) {
531
+ const long = dims.indexOf(Math.max(...dims));
532
+ const [u2 = 0, v2 = 0] = [0, 1, 2].filter((i) => i !== long);
533
+ const us = spread(dims[u2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
534
+ const vs = spread(dims[v2], COVER, spacing, MAX_MEMBER_BARS_PER_SIDE);
535
+ us.forEach((x, i) => vs.forEach((y, j) => {
536
+ if (i === 0 || j === 0 || i === us.length - 1 || j === vs.length - 1) out.push(bar(long, dims[long] / 2 - 0.02, [[u2, x], [v2, y]]));
537
+ }));
538
+ return out;
539
+ }
540
+ const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
541
+ for (const y of spread(dims[v], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(u, dims[u] / 2 - 0.02, [[v, y], [thin, 0]]));
542
+ for (const x of spread(dims[u], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(v, dims[v] / 2 - 0.02, [[u, x], [thin, 0]]));
543
+ return out;
544
+ };
545
+ var clipBarsToDisc = (bars, u, v, center, radius) => {
546
+ const out = [];
547
+ for (const [a, b] of bars) {
548
+ const axis = a[u] !== b[u] ? u : v;
549
+ const other = axis === u ? v : u;
550
+ const off = a[other] - center[other === u ? 0 : 1];
551
+ if (Math.abs(off) >= radius) continue;
552
+ const half = Math.sqrt(radius * radius - off * off);
553
+ const mid = center[axis === u ? 0 : 1];
554
+ const lo = Math.max(Math.min(a[axis], b[axis]), mid - half), hi = Math.min(Math.max(a[axis], b[axis]), mid + half);
555
+ if (hi - lo < 1e-6) continue;
556
+ const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
557
+ p[axis] = lo;
558
+ q[axis] = hi;
559
+ out.push([p, q]);
560
+ }
561
+ return out;
562
+ };
563
+ var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
564
+ let length = 0;
565
+ for (const [a, b] of bars) length += Math.abs(a[0] - b[0]) + Math.abs(a[1] - b[1]) + Math.abs(a[2] - b[2]);
566
+ return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
567
+ };
568
+
422
569
  // src/data/materials.json
423
570
  var materials_default = [
424
571
  {
@@ -1427,9 +1574,19 @@ var fragility = (ductility, thickness) => {
1427
1574
  var DMG_PER_J = 0.02;
1428
1575
  var BLAST_COUPLING = 0.1;
1429
1576
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
1577
+ var BREACH_RADIUS = 0.55;
1578
+ var BREACH_BLAST_FACTOR = 6;
1579
+ var BREACH_AREA = 0.25;
1580
+ var RESIDUAL_CAPACITY = 0.15;
1581
+ var UNIT_FRAGMENT_FACTOR = 4;
1582
+ var BREACH_GRID_CAP = 4096;
1430
1583
  var CUT_SLICE = 1;
1431
1584
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
1432
1585
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
1586
+ var capacityScale = (p) => {
1587
+ if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
1588
+ return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
1589
+ };
1433
1590
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
1434
1591
  var clone = (v) => structuredClone(v);
1435
1592
  var overloadRate = (stress) => Math.min(0.6, 0.12 + 0.8 * (stress - 1));
@@ -1452,6 +1609,8 @@ var createDestruction = (options = {}) => {
1452
1609
  const maxDents = options.maxDents ?? 24;
1453
1610
  const maxHoles = options.maxHoles ?? 32;
1454
1611
  const fireSpread = options.fireSpread ?? 0.6;
1612
+ const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
1613
+ const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
1455
1614
  const rng = createRng(options.seed ?? 1);
1456
1615
  const rnd = () => rng.next();
1457
1616
  let time = 0;
@@ -1460,6 +1619,8 @@ var createDestruction = (options = {}) => {
1460
1619
  const listeners = /* @__PURE__ */ new Map();
1461
1620
  let cur = [];
1462
1621
  const blasts = [];
1622
+ const frontQueue = [];
1623
+ const blastFrontSpeed = options.blastFrontSpeed ?? 0;
1463
1624
  const primed = /* @__PURE__ */ new Map();
1464
1625
  let orderCounter = 0;
1465
1626
  const impls = /* @__PURE__ */ new Map();
@@ -1556,6 +1717,13 @@ var createDestruction = (options = {}) => {
1556
1717
  const maxHp = Math.max(1, ps.hp ?? mass0 * mat.hpPerKg);
1557
1718
  const fuel0 = mat.thermal.ignitionC !== void 0 ? mass0 * (mat.thermal.fuelFraction ?? 0) : 0;
1558
1719
  const c = carry?.parts.get(ps.id);
1720
+ const staged = !!(ps.layers?.length || ps.reinforcement || ps.units || ps.breach);
1721
+ const layers = (ps.layers ?? []).map((l, i) => {
1722
+ const lm = materials.get(l.material);
1723
+ if (!lm) throw new Error(`unknown material "${l.material}" on layer ${i} of part "${ps.id}"`);
1724
+ return resolveLayer(l, i, lm);
1725
+ });
1726
+ if (ps.reinforcement && !materials.has(ps.reinforcement.material)) throw new Error(`unknown material "${ps.reinforcement.material}" on the reinforcement of part "${ps.id}"`);
1559
1727
  const pst = c?.st ?? {
1560
1728
  id: ps.id,
1561
1729
  material: ps.material,
@@ -1571,7 +1739,8 @@ var createDestruction = (options = {}) => {
1571
1739
  dents: [],
1572
1740
  holes: [],
1573
1741
  cracks: [],
1574
- group: 0
1742
+ group: 0,
1743
+ ...staged ? { stage: 0 } : {}
1575
1744
  };
1576
1745
  if (!c && ps.contains) pst.liters = ps.contains.liters;
1577
1746
  const p = {
@@ -1590,7 +1759,10 @@ var createDestruction = (options = {}) => {
1590
1759
  rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
1591
1760
  structural: !!ps.structure && ps.structure.role !== "none",
1592
1761
  anchored: !!ps.anchored || ps.structure?.role === "foundation",
1593
- cap: ps.structure ? partCapacity(ps, mat) : 0
1762
+ cap: ps.structure ? partCapacity(ps, mat) : 0,
1763
+ staged,
1764
+ thin: thinAxisOf(shapeDims(ps.shape)),
1765
+ layers
1594
1766
  };
1595
1767
  if (p.structural) o.hasStructure = true;
1596
1768
  if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
@@ -1730,7 +1902,7 @@ var createDestruction = (options = {}) => {
1730
1902
  o.unsup[i] = o.anyAnchor && !isLost(p);
1731
1903
  continue;
1732
1904
  }
1733
- p.st.stress = p.st.load / p.cap;
1905
+ p.st.stress = p.st.load / (p.cap * capacityScale(p));
1734
1906
  if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
1735
1907
  }
1736
1908
  if (o.maxStress > 0.8) o.active = true;
@@ -1749,6 +1921,7 @@ var createDestruction = (options = {}) => {
1749
1921
  for (const p of ps) {
1750
1922
  o.collapsed.add(p.spec.id);
1751
1923
  p.st.status = "collapsed";
1924
+ setStage(o, p, 3, {}, src);
1752
1925
  }
1753
1926
  emit({ ...base(o, src), type: "collapse", parts: ps.map((p) => p.spec.id), mass: ps.reduce((sum, p) => sum + p.st.mass, 0) });
1754
1927
  }
@@ -1802,6 +1975,7 @@ var createDestruction = (options = {}) => {
1802
1975
  if (isLost(p) || ignore?.has(`${o.id}\0${p.spec.id}`)) continue;
1803
1976
  const r = rayPart(o, p, origin, d);
1804
1977
  if (!r || r.t0 < 0 || r.t0 > maxDist) continue;
1978
+ if (p.st.opening && insideOpening(o, p, vec.madd(origin, d, r.t0))) continue;
1805
1979
  if (!best || r.t0 < best.distance) best = { object: o.id, part: p.spec.id, point: vec.madd(origin, d, r.t0), normal: r.n, distance: r.t0, pathLen: r.t1 - r.t0 };
1806
1980
  }
1807
1981
  }
@@ -1910,6 +2084,7 @@ var createDestruction = (options = {}) => {
1910
2084
  if (p.st.holes.length > maxHoles) p.st.holes.shift();
1911
2085
  markDamaged(p);
1912
2086
  emit({ ...base(o, src), type: "hole", part: p.spec.id, hole, worldPoint: wp, ...exit ? { exitPoint: exit } : {} });
2087
+ setStage(o, p, 1, {}, src);
1913
2088
  return hole;
1914
2089
  };
1915
2090
  const addCrack = (o, p, wp, wn, length, src) => {
@@ -1918,6 +2093,7 @@ var createDestruction = (options = {}) => {
1918
2093
  if (p.st.cracks.length > 16) p.st.cracks.shift();
1919
2094
  markDamaged(p);
1920
2095
  emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
2096
+ setStage(o, p, 1, {}, src);
1921
2097
  };
1922
2098
  const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
1923
2099
  const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
@@ -1966,6 +2142,7 @@ var createDestruction = (options = {}) => {
1966
2142
  if (split && mine.length) amount /= mine.length;
1967
2143
  for (const j of mine) {
1968
2144
  if (j.st.open) continue;
2145
+ if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
1969
2146
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
1970
2147
  j.st.hp -= amount;
1971
2148
  if (j.st.hp <= 0) {
@@ -1979,23 +2156,172 @@ var createDestruction = (options = {}) => {
1979
2156
  }
1980
2157
  }
1981
2158
  };
2159
+ const setStage = (o, p, stage, extra = {}, src) => {
2160
+ if (!p.staged || (p.st.stage ?? 0) >= stage) return;
2161
+ p.st.stage = stage;
2162
+ emit({ ...base(o, src), type: "stage", part: p.spec.id, stage, ...extra });
2163
+ };
2164
+ const faceOf = (o, p, worldNormal) => rotateVec(quat.conjugate(partXf(o, p).q), worldNormal)[p.thin] >= 0 ? 1 : -1;
2165
+ const spallOuter = (o, p, wp, face, normal, radius, src) => {
2166
+ var _a;
2167
+ if (p.layers.length === 0) return;
2168
+ const lp = objLocal(o, wp);
2169
+ const layer = outerLayer(p.layers, p.rt.spalls, face, lp);
2170
+ if (!layer) return;
2171
+ const spalls = (_a = p.rt).spalls ?? (_a.spalls = []);
2172
+ spalls.push([layer.index, face, lp[0], lp[1], lp[2], radius]);
2173
+ if (spalls.length > MAX_SPALLS) spalls.shift();
2174
+ markDamaged(p);
2175
+ emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
2176
+ debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
2177
+ setStage(o, p, 1, {}, src);
2178
+ };
2179
+ const insideOpening = (o, p, objectPoint) => {
2180
+ const op = p.st.opening;
2181
+ if (!op) return false;
2182
+ const [u, v] = planeAxes(p.thin);
2183
+ const lp = toLocal(p.pos, p.q, objectPoint);
2184
+ const lc = toLocal(p.pos, p.q, op.center);
2185
+ return Math.hypot(lp[u] - lc[u], lp[v] - lc[v]) <= op.radius;
2186
+ };
2187
+ const breachAreaOf = (p, damage, blast) => damage / (p.mat.hpPerKg * p.mat.density) / Math.max(p.thickness, 5e-3) * (blast ? breachBlastFactor : 1);
2188
+ const localHolesOf = (p) => p.st.holes.map((h) => ({ point: toLocal(p.pos, p.q, h.point), normal: rotateVec(quat.conjugate(p.q), h.normal), diameter: h.diameter }));
2189
+ const unitsOf = (p) => p.spec.units ?? { size: [p.mat.fragmentSize?.[1] ?? 0.2, p.mat.fragmentSize?.[1] ?? 0.2, p.thickness], bond: "stack" };
2190
+ const FRAGMENT_SHAPE = { shatter: "shard", splinter: "splinter", tear: "scrap" };
2191
+ const cellFragments = (o, p, cells, hitW, dir, speed, mass, tag) => {
2192
+ const { c, q } = partXf(o, p);
2193
+ const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
2194
+ const [u, v] = planeAxes(p.thin);
2195
+ const faceArea = (cell) => cell.size[u] * cell.size[v];
2196
+ const total = cells.reduce((sum, cell) => sum + faceArea(cell), 0) || 1;
2197
+ const shape = p.spec.units ? "chunk" : FRAGMENT_SHAPE[p.mat.fracture] ?? "chunk";
2198
+ return cells.map((cell, i) => {
2199
+ const cw = toWorld(c, q, cell.center);
2200
+ const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
2201
+ const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
2202
+ const k = speed * (0.6 + 0.8 * rnd());
2203
+ const marks = marksInBox(cell.center, cell.size, void 0, holes);
2204
+ return {
2205
+ id: `${p.spec.id}#${tag}${i}`,
2206
+ part: p.spec.id,
2207
+ material: p.spec.material,
2208
+ shape,
2209
+ center: cw,
2210
+ size: [cell.size[0], cell.size[1], cell.size[2]],
2211
+ rotation: q,
2212
+ velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
2213
+ angularVelocity: [(rnd() - 0.5) * 8, (rnd() - 0.5) * 8, (rnd() - 0.5) * 8],
2214
+ mass: mass * faceArea(cell) / total,
2215
+ ...marks.length > 0 ? { marks } : {}
2216
+ };
2217
+ });
2218
+ };
2219
+ const worldBars = (o, p, bars) => {
2220
+ const { c, q } = partXf(o, p);
2221
+ return bars.map(([a, b]) => [toWorld(c, q, a), toWorld(c, q, b)]);
2222
+ };
2223
+ const addBreach = (o, p, wp, area, energy, dir, src) => {
2224
+ var _a;
2225
+ if (!p.staged || area <= 0 || (p.st.stage ?? 0) >= 2 || p.st.stripped || isLost(p)) return;
2226
+ const lp = objLocal(o, wp);
2227
+ const list = (_a = p.rt).breach ?? (_a.breach = []);
2228
+ list.push([lp[0], lp[1], lp[2], area]);
2229
+ if (list.length > MAX_SPALLS) list.shift();
2230
+ let total = 0;
2231
+ const sum = [0, 0, 0];
2232
+ for (const [x, y, z, a] of list) {
2233
+ if (vec.distance([x, y, z], lp) > breachRadius) continue;
2234
+ total += a;
2235
+ sum[0] += x * a;
2236
+ sum[1] += y * a;
2237
+ sum[2] += z * a;
2238
+ }
2239
+ if (total < (p.spec.breach?.area ?? BREACH_AREA)) return;
2240
+ openBreach(o, p, [sum[0] / total, sum[1] / total, sum[2] / total], total, energy, dir, src);
2241
+ };
2242
+ const openBreach = (o, p, center, total, energy, dir, src) => {
2243
+ const [u, v] = planeAxes(p.thin);
2244
+ const radius = clamp2(Math.sqrt(total / Math.PI), Math.sqrt((p.spec.breach?.area ?? BREACH_AREA) / Math.PI), breachRadius);
2245
+ const lc = toLocal(p.pos, p.q, center);
2246
+ lc[p.thin] = 0;
2247
+ const { c, q } = partXf(o, p);
2248
+ const worldCenter = toWorld(c, q, lc);
2249
+ if (2 * radius >= Math.min(p.dims[u], p.dims[v])) {
2250
+ failPart(o, p, worldCenter, dir, energy, 12, src);
2251
+ return;
2252
+ }
2253
+ const cells = cellsWithin(unitGrid(p.dims, p.thin, unitsOf(p), BREACH_GRID_CAP), p.thin, lc, radius);
2254
+ const removed = p.st.mass * cells.reduce((sum, cell) => sum + cell.size[u] * cell.size[v], 0) / (p.dims[u] * p.dims[v]);
2255
+ const speed = clamp2(Math.sqrt(2 * energy * 0.1 / Math.max(removed, 0.05)), 0.5, 8);
2256
+ const fragments = cellFragments(o, p, cells, worldCenter, dir, speed, removed, "b");
2257
+ const holes = localHolesOf(p);
2258
+ p.st.holes = p.st.holes.filter((_, i) => !cells.some((cell) => marksInBox(cell.center, cell.size, void 0, [holes[i]]).length > 0));
2259
+ p.st.mass = Math.max(0, p.st.mass - removed);
2260
+ p.rt.removed = (p.rt.removed ?? 0) + removed;
2261
+ p.st.opening = { center: objLocal(o, worldCenter), radius };
2262
+ markDamaged(p);
2263
+ setStage(o, p, 1, {}, src);
2264
+ setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
2265
+ debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
2266
+ const reinforcement = p.spec.reinforcement;
2267
+ if (reinforcement) {
2268
+ const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
2269
+ if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
2270
+ }
2271
+ if (p.structural) solveStructure(o);
2272
+ };
2273
+ const stripPart = (o, p, reinforcement, src) => {
2274
+ const bars = rebarBars(p.dims, p.thin, reinforcement.spacing);
2275
+ p.st.stripped = true;
2276
+ p.st.hp = p.st.maxHp;
2277
+ p.st.mass = rebarMass(bars, reinforcement.diameter, materials.get(reinforcement.material)?.density);
2278
+ p.st.holes = [];
2279
+ p.st.burning = false;
2280
+ p.rt.jet = false;
2281
+ markDamaged(p);
2282
+ emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
2283
+ solveStructure(o);
2284
+ collapseCheck(o, src);
2285
+ };
2286
+ const unitFragmentPlan = (p, cap) => {
2287
+ const grid = unitGrid(p.dims, p.thin, unitsOf(p), cap);
2288
+ const op = p.st.opening;
2289
+ const cells = op ? cellsOutside(grid, p.thin, toLocal(p.pos, p.q, op.center), op.radius) : grid;
2290
+ const [u, v] = planeAxes(p.thin);
2291
+ return cells.map((cell) => ({ center: cell.center, size: cell.size, q: quat.identity(), shape: "chunk", weight: cell.size[u] * cell.size[v] }));
2292
+ };
2293
+ const loseCage = (o, p, src) => {
2294
+ p.st.status = "broken";
2295
+ p.st.hp = 0;
2296
+ p.st.mass = 0;
2297
+ emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: [] });
2298
+ debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
2299
+ partLost(o, p, src);
2300
+ };
1982
2301
  const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
1983
2302
  if (isLost(p)) return;
2303
+ if (p.st.stripped) {
2304
+ loseCage(o, p, src);
2305
+ return;
2306
+ }
1984
2307
  const ex = p.spec.explodes;
1985
2308
  const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
1986
2309
  const { c, q } = partXf(o, p);
1987
2310
  const hitW = origin ?? c;
1988
2311
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
1989
- const plan = planFragments({
2312
+ const hitLocal = toLocal(c, q, hitW);
2313
+ const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
2314
+ const plan = unitPlan.length > 0 ? unitPlan : planFragments({
1990
2315
  style,
1991
2316
  dims: p.dims,
1992
2317
  thickness: p.thickness,
1993
2318
  grain,
1994
- hit: toLocal(c, q, hitW),
2319
+ hit: hitLocal,
1995
2320
  fragmentSize: p.mat.fragmentSize ?? [0.04, 0.2],
1996
2321
  maxFragments,
1997
2322
  rnd
1998
2323
  });
2324
+ const holes = p.st.holes.length > 0 ? localHolesOf(p) : [];
1999
2325
  const mass = p.st.mass;
2000
2326
  const totW = plan.reduce((s, f) => s + f.weight, 0) || 1;
2001
2327
  const energy = ex ? ex.energy * 0.2 : fragEnergy;
@@ -2005,6 +2331,7 @@ var createDestruction = (options = {}) => {
2005
2331
  const out = vec.normalize(vec.sub(cw, hitW), [0, 1, 0]);
2006
2332
  const dv = dir ? vec.normalize(vec.madd(out, dir, 0.6)) : out;
2007
2333
  const k = speed * (0.6 + 0.8 * rnd());
2334
+ const marks = marksInBox(f.center, f.size, f.q, holes);
2008
2335
  return {
2009
2336
  id: `${p.spec.id}#${i}`,
2010
2337
  part: p.spec.id,
@@ -2015,18 +2342,27 @@ var createDestruction = (options = {}) => {
2015
2342
  rotation: quat.mul(q, f.q),
2016
2343
  velocity: [dv[0] * k, dv[1] * k + 0.5 * rnd(), dv[2] * k],
2017
2344
  angularVelocity: [(rnd() - 0.5) * 12, (rnd() - 0.5) * 12, (rnd() - 0.5) * 12],
2018
- mass: mass * f.weight / totW
2345
+ mass: mass * f.weight / totW,
2346
+ ...marks.length > 0 ? { marks } : {}
2019
2347
  };
2020
2348
  });
2021
- p.st.status = "broken";
2022
- p.st.hp = 0;
2023
- p.st.mass = 0;
2024
- p.st.burning = false;
2025
- p.rt.jet = false;
2349
+ const reinforcement = p.spec.reinforcement;
2350
+ if (!reinforcement) {
2351
+ p.st.status = "broken";
2352
+ p.st.hp = 0;
2353
+ p.st.mass = 0;
2354
+ p.st.burning = false;
2355
+ p.rt.jet = false;
2356
+ }
2026
2357
  emit({ ...base(o, src), type: "fracture", part: p.spec.id, style, fragments, point: hitW });
2027
2358
  debris(o, p, p.mat.breakDebris, c, [0, 1, 0], clamp2(Math.round(Math.cbrt(partVolume(p.spec.shape)) * 30), 3, 20), 2, src);
2359
+ setStage(o, p, 3, {}, src);
2360
+ if (reinforcement) {
2361
+ stripPart(o, p, reinforcement, src);
2362
+ return;
2363
+ }
2028
2364
  if (ex) {
2029
- emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius });
2365
+ emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
2030
2366
  blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
2031
2367
  }
2032
2368
  partLost(o, p, src);
@@ -2058,10 +2394,17 @@ var createDestruction = (options = {}) => {
2058
2394
  };
2059
2395
  const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src) => {
2060
2396
  const mat = p.mat;
2397
+ if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
2398
+ return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
2399
+ }
2061
2400
  const sin = Math.min(1, Math.abs(vec.dot(d, n)));
2062
2401
  const angle = Math.asin(sin) * 180 / Math.PI;
2063
- const effM = p.thickness / Math.max(sin, 0.3);
2064
- const thr = mat.penetration * effM * 1e3;
2402
+ const slant = Math.max(sin, 0.3);
2403
+ const effM = p.thickness / slant;
2404
+ const face = p.layers.length > 0 ? faceOf(o, p, n) : 1;
2405
+ const exitLocal = objLocal(o, vec.madd(entry, d, Math.min(pathLen, effM)));
2406
+ const coats = p.layers.length > 0 ? layersEnergy(p.layers, p.rt.spalls, face, objLocal(o, entry)) + layersEnergy(p.layers, p.rt.spalls, -face, exitLocal) : 0;
2407
+ const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
2065
2408
  const penetrated = E > thr;
2066
2409
  const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
2067
2410
  const id = p.spec.id;
@@ -2072,13 +2415,16 @@ var createDestruction = (options = {}) => {
2072
2415
  const dmg2 = applyDamage(o, p, E * DMG_PER_J * scale * 0.2, "ballistic", entry, d, E * 0.1, 12, src);
2073
2416
  return { penetrated: false, exitEnergy: 0, damage: dmg2 };
2074
2417
  }
2418
+ if (p.layers.length > 0) spallOuter(o, p, entry, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
2075
2419
  const brittle = mat.ductility < 0.3;
2076
2420
  const ratio = E / Math.max(thr, 1e-6);
2077
2421
  const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
2078
2422
  let exitPoint;
2079
2423
  if (penetrated) {
2080
- exitPoint = vec.madd(entry, d, Math.min(pathLen, effM));
2424
+ exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
2081
2425
  const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
2426
+ if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
2427
+ addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
2082
2428
  if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
2083
2429
  if (p.spec.contains && (p.st.liters ?? 0) > 0) {
2084
2430
  emit({
@@ -2123,8 +2469,11 @@ var createDestruction = (options = {}) => {
2123
2469
  }
2124
2470
  if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
2125
2471
  else wearJoints(o, p, E * 0.3, point, 0.06, src);
2472
+ if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
2126
2473
  const scale = mat.damageScale?.[kind] ?? 1;
2127
- return applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
2474
+ const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
2475
+ addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
2476
+ return done;
2128
2477
  };
2129
2478
  const blastObject = (o, point, energy, radius, src) => {
2130
2479
  const start = cur.length;
@@ -2160,7 +2509,15 @@ var createDestruction = (options = {}) => {
2160
2509
  }
2161
2510
  }
2162
2511
  wearJoints(o, p, Ein * 0.5, void 0, 0, src);
2163
- res.damage += applyDamage(o, p, Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1), "explosive", point, null, Ein * 0.3, 40, src);
2512
+ const amount = Ein * DMG_PER_J * (mat.damageScale?.explosive ?? 1);
2513
+ if (blastFrontSpeed > 0 && dist > 0 && mat.tags?.includes("glass")) {
2514
+ frontQueue.push({ o, p, due: time + dist / blastFrontSpeed, amount, fragEnergy: Ein * 0.3, point, ...src !== void 0 ? { src } : {} });
2515
+ continue;
2516
+ }
2517
+ if (Ein > 20 && p.layers.length > 0) spallOuter(o, p, wp, faceOf(o, p, wn), wn, clamp2(0.05 + Math.sqrt(Ein) * 2e-3, 0.05, 0.4), src);
2518
+ const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
2519
+ res.damage += dealt;
2520
+ addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
2164
2521
  }
2165
2522
  res.events = cur.slice(start);
2166
2523
  return res;
@@ -2431,7 +2788,7 @@ var createDestruction = (options = {}) => {
2431
2788
  }
2432
2789
  o.active = hot;
2433
2790
  };
2434
- const spread = (sources, dt) => {
2791
+ const spread2 = (sources, dt) => {
2435
2792
  for (const src of sources) {
2436
2793
  if (!src.p.st.burning && !src.p.rt.jet) continue;
2437
2794
  const sc = toWorld(src.o.st.position, src.o.st.rotation, src.p.pos);
@@ -2483,6 +2840,15 @@ var createDestruction = (options = {}) => {
2483
2840
  }
2484
2841
  time = end;
2485
2842
  };
2843
+ const releaseFront = () => {
2844
+ if (frontQueue.length === 0) return;
2845
+ frontQueue.sort((a, b) => a.due - b.due);
2846
+ while (frontQueue.length > 0 && frontQueue[0].due <= time) {
2847
+ const hit = frontQueue.shift();
2848
+ if (objects.get(hit.o.id) !== hit.o) continue;
2849
+ applyDamage(hit.o, hit.p, hit.amount, "explosive", hit.point, null, hit.fragEnergy, 40, hit.src);
2850
+ }
2851
+ };
2486
2852
  const withCollapses = (r) => {
2487
2853
  const last = r.value[r.value.length - 1];
2488
2854
  if (!last) return r.value;
@@ -2732,8 +3098,9 @@ var createDestruction = (options = {}) => {
2732
3098
  time += dt;
2733
3099
  const sources = [];
2734
3100
  for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
2735
- if (sources.length) spread(sources, dt);
3101
+ if (sources.length) spread2(sources, dt);
2736
3102
  detonatePrimed(time);
3103
+ releaseFront();
2737
3104
  for (const im of [...impls.values()]) {
2738
3105
  if (!im.impl.update) continue;
2739
3106
  for (const e of im.impl.update(dt) ?? []) emit(e.time === void 0 ? { ...e, time } : e);
@@ -2771,7 +3138,7 @@ var createDestruction = (options = {}) => {
2771
3138
  counter,
2772
3139
  objects: [...objects.values()].map((o) => {
2773
3140
  const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
2774
- for (const p of o.parts) runtime.parts[p.spec.id] = { ...p.rt };
3141
+ for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
2775
3142
  for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
2776
3143
  return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
2777
3144
  }),
@@ -2788,7 +3155,7 @@ var createDestruction = (options = {}) => {
2788
3155
  const state = clone(e.state);
2789
3156
  const carry = {
2790
3157
  parts: new Map(
2791
- state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? { ...e.runtime.parts[st.id] } : { ...freshRuntime(), prevTemp: st.temperature } }])
3158
+ state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
2792
3159
  ),
2793
3160
  joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
2794
3161
  collapsed: e.runtime?.collapsed
@@ -2807,6 +3174,109 @@ var createDestruction = (options = {}) => {
2807
3174
  return world;
2808
3175
  };
2809
3176
 
3177
+ // src/data/assemblies.json
3178
+ var assemblies_default = [
3179
+ {
3180
+ id: "brick_wall_plastered",
3181
+ name: "Plastered brick wall",
3182
+ core: { material: "brick", thickness: 0.09 },
3183
+ layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
3184
+ units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
3185
+ breach: { area: 0.25 }
3186
+ },
3187
+ {
3188
+ id: "concrete_block_wall",
3189
+ name: "Concrete block wall",
3190
+ core: { material: "concrete", thickness: 0.14 },
3191
+ fill: 0.55,
3192
+ units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
3193
+ breach: { area: 0.25 }
3194
+ },
3195
+ {
3196
+ id: "reinforced_concrete",
3197
+ name: "Reinforced concrete wall",
3198
+ core: { material: "concrete", thickness: 0.2 },
3199
+ reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
3200
+ units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
3201
+ breach: { area: 0.25 }
3202
+ },
3203
+ {
3204
+ id: "drywall",
3205
+ name: "Drywall partition on steel studs",
3206
+ core: { material: "plaster", thickness: 0.0125 },
3207
+ layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
3208
+ depth: 0.095,
3209
+ breach: { area: 0.25 }
3210
+ },
3211
+ {
3212
+ id: "wood_frame_wall",
3213
+ name: "Wood frame wall",
3214
+ core: { material: "plywood", thickness: 0.012 },
3215
+ layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
3216
+ depth: 0.1135,
3217
+ breach: { area: 0.25 }
3218
+ },
3219
+ {
3220
+ id: "log_wall",
3221
+ name: "Log wall",
3222
+ core: { material: "wood_soft", thickness: 0.2 },
3223
+ fill: 0.785,
3224
+ breach: { area: 0.25 }
3225
+ },
3226
+ {
3227
+ id: "sheet_metal_shack",
3228
+ name: "Corrugated sheet metal wall",
3229
+ core: { material: "steel", thickness: 6e-4 },
3230
+ depth: 0.02,
3231
+ breach: { area: 0.25 }
3232
+ },
3233
+ {
3234
+ id: "glass_pane",
3235
+ name: "Glass pane",
3236
+ core: { material: "glass", thickness: 6e-3 },
3237
+ breach: { area: 0.1 }
3238
+ }
3239
+ ];
3240
+
3241
+ // src/assemblies.ts
3242
+ var ASSEMBLIES = assemblies_default;
3243
+ var registerAssembly = (def) => {
3244
+ const at = ASSEMBLIES.findIndex((a) => a.id === def.id);
3245
+ if (at >= 0) ASSEMBLIES[at] = def;
3246
+ else ASSEMBLIES.push(def);
3247
+ };
3248
+ var getAssembly = (id) => ASSEMBLIES.find((a) => a.id === id);
3249
+ var faceCount = (layer) => layer.faces === "both" ? 2 : 1;
3250
+ var assemblyDepth = (def) => def.depth ?? def.core.thickness + (def.layers ?? []).reduce((sum, l) => sum + l.thickness * faceCount(l), 0);
3251
+ var assemblyPart = (assemblyId, options) => {
3252
+ const def = getAssembly(assemblyId);
3253
+ if (!def) throw new Error(`unknown assembly "${assemblyId}"`);
3254
+ const densityOf = (id) => {
3255
+ const m = options.materials?.get(id) ?? MATERIALS.find((x) => x.id === id);
3256
+ if (!m) throw new Error(`unknown material "${id}" in assembly "${assemblyId}"`);
3257
+ return m.density;
3258
+ };
3259
+ const fill = def.fill ?? 1;
3260
+ const area = options.width * options.height;
3261
+ const coreMass = area * def.core.thickness * fill * densityOf(def.core.material);
3262
+ const layerMass = (def.layers ?? []).reduce((sum, l) => sum + area * l.thickness * faceCount(l) * densityOf(l.material), 0);
3263
+ return {
3264
+ id: options.id,
3265
+ material: def.core.material,
3266
+ shape: { kind: "box", size: [options.width, options.height, assemblyDepth(def)] },
3267
+ position: options.position,
3268
+ ...options.rotation ? { rotation: options.rotation } : {},
3269
+ mass: coreMass + layerMass,
3270
+ thickness: def.core.thickness * fill,
3271
+ ...def.layers ? { layers: def.layers.map((l) => ({ ...l })) } : {},
3272
+ ...def.units ? { units: { ...def.units, size: [...def.units.size] } } : {},
3273
+ ...def.reinforcement ? { reinforcement: { ...def.reinforcement } } : {},
3274
+ ...def.breach ? { breach: { ...def.breach } } : {},
3275
+ ...options.structure ? { structure: options.structure } : {},
3276
+ ...options.extra
3277
+ };
3278
+ };
3279
+
2810
3280
  // src/frame.ts
2811
3281
  var buildFrame = (o = {}) => {
2812
3282
  const spanX = o.spanX ?? 8, spanZ = o.spanZ ?? 8;
@@ -2914,6 +3384,8 @@ var buildFrame = (o = {}) => {
2914
3384
  var sizeStructure = (spec, options = {}) => {
2915
3385
  const safety = options.safety ?? 0.65;
2916
3386
  const redundancy = options.redundancy;
3387
+ const redundancyRoles = options.redundancyRoles ?? ["column"];
3388
+ const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
2917
3389
  const loads = (s) => {
2918
3390
  const w = createDestruction(options.materials ? { materials: options.materials } : {});
2919
3391
  const id = w.add(s);
@@ -2926,11 +3398,11 @@ var sizeStructure = (spec, options = {}) => {
2926
3398
  if (!p.structure || p.structure.role === "none") continue;
2927
3399
  const b = base.get(p.id);
2928
3400
  if (!b || b.stress <= 0) continue;
2929
- need.set(p.id, b.load / safety);
3401
+ need.set(p.id, b.load / safetyOf(p.structure.role));
2930
3402
  }
2931
3403
  if (redundancy !== void 0) {
2932
3404
  for (const col of out.parts) {
2933
- if (col.structure?.role !== "column") continue;
3405
+ if (!col.structure || !redundancyRoles.includes(col.structure.role)) continue;
2934
3406
  const reduced = {
2935
3407
  ...out,
2936
3408
  parts: out.parts.filter((p) => p.id !== col.id),
@@ -3030,6 +3502,10 @@ var locales = {
3030
3502
  primed: "about to explode",
3031
3503
  stress: "overload",
3032
3504
  collapse: "collapsed",
3505
+ spall: "plaster came off",
3506
+ stage: "damage stage",
3507
+ expose: "rebar bare",
3508
+ body: "body",
3033
3509
  destroyed: "destroyed"
3034
3510
  },
3035
3511
  fractureStyles: { shatter: "shatter", crumble: "crumble", splinter: "splinter", tear: "tear", snap: "snap", none: "none" },
@@ -3112,6 +3588,10 @@ var locales = {
3112
3588
  primed: "prestes a explodir",
3113
3589
  stress: "sobrecarga",
3114
3590
  collapse: "desabou",
3591
+ spall: "reboco soltou",
3592
+ stage: "est\xE1gio de dano",
3593
+ expose: "vergalh\xE3o \xE0 mostra",
3594
+ body: "corpo",
3115
3595
  destroyed: "destru\xEDdo"
3116
3596
  },
3117
3597
  fractureStyles: { shatter: "estilha\xE7ar", crumble: "esfarelar", splinter: "lascar", tear: "rasgar", snap: "partir", none: "nenhuma" },
@@ -3128,17 +3608,21 @@ var checkDestructible = (impl, options = {}) => {
3128
3608
  return check(impl, contractOptions);
3129
3609
  };
3130
3610
  export {
3611
+ ASSEMBLIES,
3131
3612
  FIRE_PRIME_C,
3132
3613
  IMPACT_CONTRACT,
3133
3614
  JOINT_BY_MATERIALS,
3134
3615
  JOINT_DEFAULTS,
3135
3616
  MATERIALS,
3617
+ assemblyDepth,
3618
+ assemblyPart,
3136
3619
  buildFrame,
3137
3620
  checkDestructible,
3138
3621
  checkHit,
3139
3622
  createDestruction,
3140
3623
  explosivePart,
3141
3624
  fragility,
3625
+ getAssembly,
3142
3626
  guessMaterial,
3143
3627
  inferJoints,
3144
3628
  locales,
@@ -3148,6 +3632,7 @@ export {
3148
3632
  partVolume,
3149
3633
  quat,
3150
3634
  quatFromEuler,
3635
+ registerAssembly,
3151
3636
  resolveStrings,
3152
3637
  rotateVec,
3153
3638
  sizeStructure,