@adatechnology/game-destruction 0.7.2 → 0.8.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
@@ -525,6 +525,7 @@ var isMember = (dims) => {
525
525
  const [a = 0, b = 0, c = 0] = [...dims].sort((x, y) => x - y);
526
526
  return b / a < 3 && c / b >= 3;
527
527
  };
528
+ var memberBarCount = (width, spacing) => spread(width, COVER, spacing, MAX_MEMBER_BARS_PER_SIDE).length;
528
529
  var rebarBars = (dims, thin, spacing) => {
529
530
  const out = [];
530
531
  if (isMember(dims)) {
@@ -560,12 +561,808 @@ var clipBarsToDisc = (bars, u, v, center, radius) => {
560
561
  }
561
562
  return out;
562
563
  };
564
+ var verticalBarCount = (dims, thin, spacing) => {
565
+ if (isMember(dims)) {
566
+ if (dims.indexOf(Math.max(...dims)) !== 1) return 0;
567
+ const us = spread(dims[0], COVER, spacing, Infinity).length, vs = spread(dims[2], COVER, spacing, Infinity).length;
568
+ return 2 * us + 2 * Math.max(0, vs - 2);
569
+ }
570
+ const [u = 0, v = 0] = [0, 1, 2].filter((i) => i !== thin);
571
+ if (u === 1) return spread(dims[v], COVER, spacing, Infinity).length;
572
+ if (v === 1) return spread(dims[u], COVER, spacing, Infinity).length;
573
+ return 0;
574
+ };
563
575
  var rebarMass = (bars, diameter, density = STEEL_DENSITY) => {
564
576
  let length = 0;
565
577
  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
578
  return Math.PI * (diameter / 2) * (diameter / 2) * length * density;
567
579
  };
568
580
 
581
+ // src/flexure.ts
582
+ var GRAVITY = 9.81;
583
+ var MPA = 1e6;
584
+ var FLEXURE_DEFAULTS = {
585
+ enabled: true,
586
+ roles: ["beam", "slab", "roof", "stair"],
587
+ scope: "rebar",
588
+ crackFactor: 1.5,
589
+ rebarYieldMPa: 500,
590
+ cover: 0.03,
591
+ neutralAxisLimit: 0.45,
592
+ failRotationDeg: 10,
593
+ loadFactor: 0.64,
594
+ massFactor: 0.5,
595
+ tntJoules: 4184e3,
596
+ impulse: 200,
597
+ reflection: 2,
598
+ reflectionNear: 4,
599
+ nearScaled: 0.5
600
+ };
601
+ var MASONRY_DEFAULTS = { minEccentricity: 0.05, slendernessLimit: 40, concreteFactor: 0.85 };
602
+ var resolveFlexure = (o) => ({ ...FLEXURE_DEFAULTS, ...definedOf(o) });
603
+ var resolveMasonry = (o) => ({ ...MASONRY_DEFAULTS, ...definedOf(o) });
604
+ var definedOf = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
605
+ var RATIO_BAR = 0.01;
606
+ var crackingMoment = (tensileMPa, b, h, alpha = FLEXURE_DEFAULTS.crackFactor) => alpha * tensileMPa * MPA * (b * h * h) / 6;
607
+ var steelMoment = (area, yieldMPa, d, b, compressiveMPa, options = {}) => {
608
+ if (area <= 0 || d <= 0 || b <= 0) return 0;
609
+ const T = area * yieldMPa * MPA;
610
+ const block = 0.68 * compressiveMPa * MPA * b;
611
+ const x = T / block;
612
+ const xl = (options.neutralAxisLimit ?? FLEXURE_DEFAULTS.neutralAxisLimit) * d;
613
+ if (x <= xl) return T * (d - 0.4 * x);
614
+ const top = Math.min(d / 2, (options.cover ?? FLEXURE_DEFAULTS.cover) + RATIO_BAR / 2);
615
+ return block * xl * (d - 0.4 * xl) + (T - block * xl) * (d - top);
616
+ };
617
+ var spanStiffness = (EI, span) => 384 * EI / (5 * span ** 3);
618
+ var elasticEnergy = (moment, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => {
619
+ const R = 8 * moment / span;
620
+ return loadFactor * R * R / (2 * spanStiffness(EI, span));
621
+ };
622
+ var momentOfEnergy = (energy, span, EI, loadFactor = FLEXURE_DEFAULTS.loadFactor) => span / 8 * Math.sqrt(2 * Math.max(0, energy) * spanStiffness(EI, span) / loadFactor);
623
+ var impulseEnergy = (impulse, mass, options = {}) => {
624
+ const KL = options.loadFactor ?? FLEXURE_DEFAULTS.loadFactor;
625
+ const KM = options.massFactor ?? FLEXURE_DEFAULTS.massFactor;
626
+ return mass > 0 ? (KL * impulse) ** 2 / (2 * KM * mass) : 0;
627
+ };
628
+ var blastImpulse = (tnt, distance, options = {}) => {
629
+ if (tnt <= 0) return 0;
630
+ const o = { ...FLEXURE_DEFAULTS, ...definedOf(options) };
631
+ const cube = Math.cbrt(tnt);
632
+ const r = Math.max(distance, o.nearScaled * cube);
633
+ return (o.reflection + o.reflectionNear / (r / cube)) * (o.impulse * cube * cube / r);
634
+ };
635
+ var slendernessFactor = (h, t, options = {}) => {
636
+ const o = resolveMasonry(options);
637
+ const lambda = h / Math.max(t, 1e-3);
638
+ return Math.max(0, (1 - 2 * o.minEccentricity) * (1 - (lambda / o.slendernessLimit) ** 3));
639
+ };
640
+ var isReinforced = (part) => !!part.reinforcement || (part.structure?.rebar?.ratio ?? 0) > 0;
641
+ var isSlender = (part, material) => material.strength?.slender ?? (!!material.tags?.includes("masonry") && !isReinforced(part));
642
+ var isFlexural = (part, material, options) => options.enabled && material.strength?.tensileMPa !== void 0 && !!part.structure && options.roles.includes(part.structure.role) && (part.structure.rebar !== void 0 || options.scope === "all" || options.scope === "reinforced" && !!part.reinforcement);
643
+ var tensionSteel = (part, dims, section, options) => {
644
+ const rebar = part.structure?.rebar;
645
+ const cage = part.reinforcement;
646
+ const bar2 = rebar ? RATIO_BAR : cage?.diameter ?? RATIO_BAR;
647
+ const d = Math.max(0.01, section.h - (rebar?.cover ?? options.cover) - bar2 / 2);
648
+ const yieldMPa = rebar?.yieldMPa ?? options.rebarYieldMPa;
649
+ if (rebar && rebar.ratio > 0) return { area: rebar.ratio * section.b * d, d, yieldMPa };
650
+ if (!cage) return { area: 0, d, yieldMPa };
651
+ const one = Math.PI * cage.diameter * cage.diameter / 4;
652
+ const count = isMember(dims) ? memberBarCount(section.b, cage.spacing) : section.b / cage.spacing;
653
+ return { area: one * count, d, yieldMPa };
654
+ };
655
+ var sectionMoments = (material, section, steel, options) => {
656
+ const s = material.strength;
657
+ const cracking = crackingMoment(s.tensileMPa ?? 0, section.b, section.h, options.crackFactor);
658
+ const steelM = steelMoment(steel.area, steel.yieldMPa, steel.d, section.b, s.compressiveMPa, options);
659
+ return { cracking, steel: steelM, ultimate: Math.max(cracking, steelM), reinforced: steelM > 0 };
660
+ };
661
+ var spanMoment = (w, span) => w * span * span / 8;
662
+ var bendGeometry = (part) => {
663
+ const dims = shapeDims(part.shape);
664
+ const member = isMember(dims);
665
+ const q = quatFromEuler(...part.rotation ?? [0, 0, 0]);
666
+ const up = rotateVec(quat.conjugate(q), [0, 1, 0]);
667
+ let spanAxes;
668
+ let depthAxes;
669
+ if (member) {
670
+ const long = dims.indexOf(Math.max(...dims));
671
+ spanAxes = [long];
672
+ depthAxes = [0, 1, 2].filter((i) => i !== long);
673
+ } else {
674
+ const thin = thinAxisOf(dims);
675
+ spanAxes = [...planeAxes(thin)];
676
+ depthAxes = [thin];
677
+ }
678
+ let gravity = -1;
679
+ for (const a of depthAxes) if (Math.abs(up[a]) > Math.SQRT1_2 && (gravity < 0 || Math.abs(up[a]) > Math.abs(up[gravity]))) gravity = a;
680
+ return { dims, member, spanAxes, depthAxes, gravity, up };
681
+ };
682
+ var sectionWidth = (g, axis) => g.member ? g.dims[g.depthAxes.find((a) => a !== axis) ?? axis] : 1;
683
+ var lineLoad = (g, load) => {
684
+ const area = g.member ? g.dims[g.spanAxes[0]] : g.dims[g.spanAxes[0]] * g.dims[g.spanAxes[1]];
685
+ return load * GRAVITY / Math.max(area, 1e-6);
686
+ };
687
+ var spanOf = (g, contacts, samples = 9) => {
688
+ if (contacts.length === 0) return 0;
689
+ const axes = g.spanAxes;
690
+ const steps = (a) => Array.from({ length: samples }, (_, i) => (i / (samples - 1) - 0.5) * g.dims[a]);
691
+ const us = steps(axes[0]);
692
+ const vs = axes.length > 1 ? steps(axes[1]) : [0];
693
+ let far = 0;
694
+ for (const u of us) for (const v of vs) {
695
+ let near = Infinity;
696
+ for (const [lo, hi] of contacts) {
697
+ let gap = 0;
698
+ const x = [u, v];
699
+ axes.forEach((a, k) => gap = Math.max(gap, lo[a] - x[k], x[k] - hi[a], 0));
700
+ if (gap < near) near = gap;
701
+ }
702
+ if (near > far) far = near;
703
+ }
704
+ return 2 * far;
705
+ };
706
+ var FLEXURAL_ROLES = FLEXURE_DEFAULTS.roles;
707
+ var bearingWallThickness = (options) => {
708
+ const gf = options.loadFactor ?? 1.4;
709
+ const gm = options.materialFactor ?? 2;
710
+ const min = options.minThickness ?? 0.14;
711
+ const step = options.step ?? 0.01;
712
+ const fd = (options.material.strength?.compressiveMPa ?? 0) * MPA / gm;
713
+ const own = options.material.density * GRAVITY * options.storeyHeight;
714
+ const out = [];
715
+ let above = 0;
716
+ for (let i = 0; i < options.storeys; i++) {
717
+ const n = above + options.floorLoad;
718
+ let t = min;
719
+ for (let k = 0; k < 60; k++) {
720
+ const room = fd * slendernessFactor(options.storeyHeight, t, options.masonry) - gf * own;
721
+ if (room <= 0) {
722
+ t *= 1.25;
723
+ if (t > 20) {
724
+ t = Infinity;
725
+ break;
726
+ }
727
+ continue;
728
+ }
729
+ const next = Math.max(min, gf * n / room);
730
+ if (Math.abs(next - t) < 1e-5) break;
731
+ t = next;
732
+ }
733
+ t = Number.isFinite(t) ? Math.ceil(t / step - 1e-9) * step : Infinity;
734
+ out.push(t);
735
+ above = n + own * (Number.isFinite(t) ? t : 0);
736
+ }
737
+ return out.reverse();
738
+ };
739
+
740
+ // src/data/soils.json
741
+ var soils_default = [
742
+ { id: "rock", kind: "rock", allowableKPa: 3e3, spt: 50, elasticMPa: 1e4, poisson: 0.2, pile: { tipKPa: 9e3, skinKPa: 500 } },
743
+ { id: "rock_fractured", kind: "rock", allowableKPa: 1500, spt: 50, elasticMPa: 2e3, poisson: 0.25, pile: { tipKPa: 4500, skinKPa: 300 } },
744
+ { id: "gravel_dense", kind: "gravel", allowableKPa: 600, spt: 40, elasticMPa: 132, poisson: 0.3 },
745
+ { id: "gravel_loose", kind: "gravel", allowableKPa: 300, spt: 8, elasticMPa: 26, poisson: 0.3 },
746
+ { id: "sand_very_dense", kind: "sand", allowableKPa: 500, spt: 45, elasticMPa: 120, poisson: 0.3 },
747
+ { id: "sand_dense", kind: "sand", allowableKPa: 400, spt: 25, elasticMPa: 68, poisson: 0.3 },
748
+ { id: "sand_medium", kind: "sand", allowableKPa: 200, spt: 12, elasticMPa: 32, poisson: 0.3 },
749
+ { id: "sand_loose", kind: "sand", allowableKPa: 50, spt: 3, elasticMPa: 8, poisson: 0.3 },
750
+ { id: "silt_hard", kind: "silt", allowableKPa: 300, spt: 25, elasticMPa: 44, poisson: 0.35 },
751
+ { id: "silt_stiff", kind: "silt", allowableKPa: 200, spt: 15, elasticMPa: 26, poisson: 0.35 },
752
+ { id: "silt_medium", kind: "silt", allowableKPa: 100, spt: 8, elasticMPa: 14, poisson: 0.35 },
753
+ { id: "clay_hard", kind: "clay", allowableKPa: 300, spt: 25, elasticMPa: 35, poisson: 0.4 },
754
+ { id: "clay_stiff", kind: "clay", allowableKPa: 200, spt: 15, elasticMPa: 21, poisson: 0.4 },
755
+ { id: "clay_medium", kind: "clay", allowableKPa: 100, spt: 8, elasticMPa: 11, poisson: 0.4 },
756
+ { id: "clay_soft", kind: "clay", allowableKPa: 30, spt: 3, elasticMPa: 4.2, poisson: 0.4 },
757
+ { id: "clay_very_soft", kind: "clay", allowableKPa: 15, spt: 1, elasticMPa: 1.4, poisson: 0.4 }
758
+ ];
759
+
760
+ // src/soil.ts
761
+ var SOILS = soils_default;
762
+ var SOIL_DEFAULTS = {
763
+ safety: 3,
764
+ pileSafety: 2,
765
+ rigidity: 0.82,
766
+ influence: 2,
767
+ imperfection: 1 / 200,
768
+ cells: 4,
769
+ sinkRate: 0.05,
770
+ tiltRate: 1.5,
771
+ pileElasticGPa: 25,
772
+ footingWidth: 0.6
773
+ };
774
+ var resolveSoil = (o = {}) => {
775
+ const byId = new Map(SOILS.map((s) => [s.id, s]));
776
+ for (const s of o.soils ?? []) byId.set(s.id, s);
777
+ const { soils: _soils, site, ...rest } = o;
778
+ return { ...SOIL_DEFAULTS, ...rest, ...site !== void 0 ? { site } : {}, byId };
779
+ };
780
+ var profileOf = (soil) => typeof soil === "string" ? { layers: [{ soil }] } : soil;
781
+ var layersBetween = (profile, top, bottom, soils) => {
782
+ const out = [];
783
+ let z = 0;
784
+ const layers = profile.layers;
785
+ for (let i = 0; i < layers.length; i++) {
786
+ const def = soils.get(layers[i].soil);
787
+ if (!def) throw new Error(`unknown soil "${layers[i].soil}"`);
788
+ const last = i === layers.length - 1;
789
+ const end = last ? Infinity : z + Math.max(0, layers[i].thickness ?? 0);
790
+ const a = Math.max(z, top), b = Math.min(end, bottom);
791
+ if (b > a) out.push({ soil: def, thickness: b - a, top: a });
792
+ z = end;
793
+ if (z >= bottom) break;
794
+ }
795
+ return out;
796
+ };
797
+ var soilAt = (profile, depth, soils) => {
798
+ let z = 0;
799
+ for (let i = 0; i < profile.layers.length; i++) {
800
+ const def = soils.get(profile.layers[i].soil);
801
+ if (!def) throw new Error(`unknown soil "${profile.layers[i].soil}"`);
802
+ z += Math.max(0, profile.layers[i].thickness ?? 0);
803
+ if (i === profile.layers.length - 1 || depth < z) return def;
804
+ }
805
+ throw new Error("a soil profile needs at least one layer");
806
+ };
807
+ var footingSoil = (profile, depth, width, soils, influence = SOIL_DEFAULTS.influence) => {
808
+ const zone = layersBetween(profile, depth, depth + Math.max(0.01, influence * width), soils);
809
+ let allowable = Infinity, e = 0, nu = 0, t = 0;
810
+ for (const l of zone) {
811
+ allowable = Math.min(allowable, l.soil.allowableKPa);
812
+ e += l.soil.elasticMPa * l.thickness;
813
+ nu += l.soil.poisson * l.thickness;
814
+ t += l.thickness;
815
+ }
816
+ if (t <= 0) {
817
+ const l = soilAt(profile, depth, soils);
818
+ return { allowableKPa: l.allowableKPa, elasticMPa: l.elasticMPa, poisson: l.poisson };
819
+ }
820
+ return { allowableKPa: allowable, elasticMPa: e / t, poisson: nu / t };
821
+ };
822
+ var DQ_C = { clay: 120, silt: 200, sand: 400, gravel: 400, rock: 400 };
823
+ var BORED = {
824
+ clay: { alpha: 0.85, beta: 0.8 },
825
+ silt: { alpha: 0.6, beta: 0.65 },
826
+ sand: { alpha: 0.5, beta: 0.5 },
827
+ gravel: { alpha: 0.5, beta: 0.5 },
828
+ rock: { alpha: 1, beta: 1 }
829
+ };
830
+ var clampN = (n) => Math.min(50, Math.max(3, n));
831
+ var pileCapacity = (profile, pile, top, soils) => {
832
+ const d = pile.diameter;
833
+ const area = Math.PI * d * d / 4;
834
+ const perimeter = Math.PI * d;
835
+ const driven = pile.method === "driven";
836
+ let skin = 0;
837
+ for (const l of layersBetween(profile, top, top + pile.length, soils)) {
838
+ const f = driven ? 1 : BORED[l.soil.kind].beta;
839
+ const kPa = l.soil.pile?.skinKPa ?? f * 10 * (clampN(l.soil.spt) / 3 + 1);
840
+ skin += kPa * 1e3 * perimeter * l.thickness;
841
+ }
842
+ const tipDepth = top + pile.length;
843
+ const tipSoil = soilAt(profile, tipDepth, soils);
844
+ const alpha = driven ? 1 : BORED[tipSoil.kind].alpha;
845
+ const tipKPa = tipSoil.pile?.tipKPa ?? alpha * DQ_C[tipSoil.kind] * clampN(tipSoil.spt);
846
+ const tip = tipKPa * 1e3 * area;
847
+ return { tip, skin, ultimate: tip + skin, tipSoil };
848
+ };
849
+ var pileLength = (profile, o) => {
850
+ const soils = o.soils ?? resolveSoil().byId;
851
+ const safety = o.safety ?? SOIL_DEFAULTS.pileSafety;
852
+ const step = o.step ?? 0.5;
853
+ const max = o.maxLength ?? 60;
854
+ for (let L = step; L <= max + 1e-9; L += step) {
855
+ if (pileCapacity(profile, { length: L, diameter: o.diameter, ...o.method ? { method: o.method } : {} }, o.top ?? 0, soils).ultimate / safety >= o.load) return Math.round(L / step) * step;
856
+ }
857
+ return max;
858
+ };
859
+ var pileStiffness = (pile, pileElasticGPa, tipElasticMPa, tipPoisson) => {
860
+ const area = Math.PI * pile.diameter * pile.diameter / 4;
861
+ const shaft = pile.length / (2 * pileElasticGPa * 1e9 * area);
862
+ const tip = (1 - tipPoisson * tipPoisson) / (tipElasticMPa * 1e6 * pile.diameter);
863
+ return 1 / (shaft + tip);
864
+ };
865
+ var subgradeModulus = (elasticMPa, poisson, width, rigidity = SOIL_DEFAULTS.rigidity) => elasticMPa * 1e6 / (Math.max(0.05, width) * (1 - poisson * poisson) * rigidity);
866
+ var footingArea = (load, allowableKPa) => load / (allowableKPa * 1e3);
867
+ var yieldSprings = (springs, s, a, b) => {
868
+ const [x0, z0] = stiffnessCentre(springs);
869
+ return springs.map((sp) => {
870
+ const set = sp.set ?? 0;
871
+ const w = s + a * (sp.x - x0) + b * (sp.z - z0);
872
+ return sp.k * (w - set) > sp.cap ? w - sp.cap / sp.k : set;
873
+ });
874
+ };
875
+ var stiffnessCentre = (springs) => {
876
+ let k = 0, x = 0, z = 0;
877
+ for (const s of springs) {
878
+ k += s.k;
879
+ x += s.k * s.x;
880
+ z += s.k * s.z;
881
+ }
882
+ return k > 0 ? [x / k, z / k] : [0, 0];
883
+ };
884
+ var cholesky3 = (J, r) => {
885
+ const L = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
886
+ for (let i = 0; i < 3; i++) {
887
+ for (let j = 0; j <= i; j++) {
888
+ let sum = J[i][j];
889
+ for (let k = 0; k < j; k++) sum -= L[i][k] * L[j][k];
890
+ if (i === j) {
891
+ if (!(sum > 1e-9 * Math.max(1, Math.abs(J[i][i])))) return null;
892
+ L[i][i] = Math.sqrt(sum);
893
+ } else L[i][j] = sum / L[j][j];
894
+ }
895
+ }
896
+ const y = [0, 0, 0];
897
+ for (let i = 0; i < 3; i++) {
898
+ let sum = r[i];
899
+ for (let k = 0; k < i; k++) sum -= L[i][k] * y[k];
900
+ y[i] = sum / L[i][i];
901
+ }
902
+ const x = [0, 0, 0];
903
+ for (let i = 2; i >= 0; i--) {
904
+ let sum = y[i];
905
+ for (let k = i + 1; k < 3; k++) sum -= L[k][i] * x[k];
906
+ x[i] = sum / L[i][i];
907
+ }
908
+ return x;
909
+ };
910
+ var solveFoundation = (springs, weight, xg, zg, height, imperfection, start = { s: 0, a: 0, b: 0 }) => {
911
+ const [x0, z0] = stiffnessCentre(springs);
912
+ const capSum = springs.reduce((sum, s2) => sum + s2.cap, 0);
913
+ const span = Math.max(1, ...springs.map((s2) => Math.max(Math.abs(s2.x - x0) + s2.hx, Math.abs(s2.z - z0) + s2.hz)));
914
+ let s = start.s, a = start.a, b = start.b;
915
+ if (springs.every((sp) => s + a * (sp.x - x0) + b * (sp.z - z0) - (sp.set ?? 0) <= 0)) {
916
+ s = weight / Math.max(1e-9, springs.reduce((sum, sp) => sum + sp.k, 0));
917
+ a = b = 0;
918
+ }
919
+ const Wh = weight * height;
920
+ const evaluate = () => {
921
+ let F = 0, Mx = 0, Mz = 0, edge = 0, yielded = false;
922
+ const Jt = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
923
+ const Je = [[0, 0, 0], [0, 0, 0], [0, 0, 0]];
924
+ const add = (J, k, dx, dz) => {
925
+ J[0][0] += k;
926
+ J[0][1] += k * dx;
927
+ J[0][2] += k * dz;
928
+ J[1][1] += k * dx * dx;
929
+ J[1][2] += k * dx * dz;
930
+ J[2][2] += k * dz * dz;
931
+ };
932
+ for (const sp of springs) {
933
+ const dx = sp.x - x0, dz = sp.z - z0;
934
+ const w = s + a * dx + b * dz - (sp.set ?? 0);
935
+ if (w <= 0) continue;
936
+ const f = sp.k * w;
937
+ add(Je, sp.k, dx, dz);
938
+ let force = f;
939
+ if (f >= sp.cap) {
940
+ force = sp.cap;
941
+ yielded = true;
942
+ } else add(Jt, sp.k, dx, dz);
943
+ edge = Math.max(edge, force / sp.cap);
944
+ F += force;
945
+ Mx += force * dx;
946
+ Mz += force * dz;
947
+ }
948
+ for (const J of [Jt, Je]) {
949
+ J[1][0] = J[0][1];
950
+ J[2][0] = J[0][2];
951
+ J[2][1] = J[1][2];
952
+ J[1][1] -= Wh;
953
+ J[2][2] -= Wh;
954
+ }
955
+ const r = [weight - F, weight * (xg - x0 + height * (a + imperfection[0])) - Mx, weight * (zg - z0 + height * (b + imperfection[1])) - Mz];
956
+ return { r, Jt, Je, edge, yielded };
957
+ };
958
+ let ev = evaluate();
959
+ let converged = false;
960
+ const tol = 1e-7 * weight;
961
+ for (let it = 0; it < 120; it++) {
962
+ if (Math.abs(ev.r[0]) < tol && Math.abs(ev.r[1]) < tol * span && Math.abs(ev.r[2]) < tol * span) {
963
+ converged = true;
964
+ break;
965
+ }
966
+ const d = cholesky3(ev.Jt, ev.r) ?? cholesky3(ev.Je, ev.r);
967
+ if (!d) break;
968
+ s += d[0];
969
+ a += d[1];
970
+ b += d[2];
971
+ if (!Number.isFinite(s + a + b) || Math.abs(a) > 2 || Math.abs(b) > 2) break;
972
+ ev = evaluate();
973
+ }
974
+ const stable = converged && cholesky3(ev.Jt, [0, 0, 0]) !== null;
975
+ return { s, a, b, stable, yielded: ev.yielded, bearing: capSum > 0 ? weight / capSum : Infinity, edge: ev.edge, residual: [ev.r[0], ev.r[1], ev.r[2]] };
976
+ };
977
+
978
+ // src/data/wind-terrain.json
979
+ var wind_terrain_default = [
980
+ { id: "I", zg: 250, b: { A: 1.1, B: 1.11, C: 1.12 }, p: { A: 0.06, B: 0.065, C: 0.07 }, z0: 5e-3 },
981
+ { id: "II", zg: 300, b: { A: 1, B: 1, C: 1 }, p: { A: 0.085, B: 0.09, C: 0.1 }, z0: 0.05 },
982
+ { id: "III", zg: 350, b: { A: 0.94, B: 0.94, C: 0.93 }, p: { A: 0.1, B: 0.105, C: 0.115 }, z0: 0.3 },
983
+ { id: "IV", zg: 420, b: { A: 0.86, B: 0.85, C: 0.84 }, p: { A: 0.12, B: 0.125, C: 0.135 }, z0: 1 },
984
+ { id: "V", zg: 500, b: { A: 0.74, B: 0.73, C: 0.71 }, p: { A: 0.15, B: 0.16, C: 0.175 }, z0: 2.5 }
985
+ ];
986
+
987
+ // src/wind.ts
988
+ var WIND_TERRAIN = wind_terrain_default;
989
+ var WIND_DEFAULTS = {
990
+ speed: 40,
991
+ direction: [1, 0, 0],
992
+ terrain: "III",
993
+ class: "auto",
994
+ topography: 1,
995
+ statistical: 1,
996
+ drag: 1.3,
997
+ damping: 0.016,
998
+ airDensity: 1.226,
999
+ minHeight: 5,
1000
+ bandHeight: 3,
1001
+ stiffnessFactor: 0.8,
1002
+ beamFactor: 0.4,
1003
+ flange: 0.1,
1004
+ driftLimit: 1 / 500,
1005
+ loadFactor: 1.4,
1006
+ chord: false,
1007
+ meanRatio: 0.69,
1008
+ gustFactor: { A: 1, B: 0.98, C: 0.95 },
1009
+ gust: { components: 400, maxHz: 2, scale: 1200, seed: 1 },
1010
+ peakDecay: 60,
1011
+ tuning: "den-hartog"
1012
+ };
1013
+ var definedOf2 = (o) => Object.fromEntries(Object.entries(o ?? {}).filter(([, v]) => v !== void 0));
1014
+ var resolveWind = (o = {}) => {
1015
+ const terrains = new Map(WIND_TERRAIN.map((t) => [t.id, t]));
1016
+ for (const t of o.terrains ?? []) terrains.set(t.id, t);
1017
+ const { terrains: _terrains, gustFactor, gust, ...rest } = o;
1018
+ return {
1019
+ ...WIND_DEFAULTS,
1020
+ ...definedOf2(rest),
1021
+ direction: o.direction ?? WIND_DEFAULTS.direction,
1022
+ gustFactor: { ...WIND_DEFAULTS.gustFactor, ...definedOf2(gustFactor) },
1023
+ gust: { ...WIND_DEFAULTS.gust, ...definedOf2(gust) },
1024
+ terrains
1025
+ };
1026
+ };
1027
+ var exposureOf = (w, spec) => spec ? { ...w, ...definedOf2(spec) } : w;
1028
+ var windPressure = (speed, density = WIND_DEFAULTS.airDensity) => 0.5 * density * speed * speed;
1029
+ var windClass = (largest) => largest <= 20 ? "A" : largest <= 50 ? "B" : "C";
1030
+ var heightFactor = (z, terrain, cls, o = {}) => {
1031
+ const zz = Math.min(terrain.zg, Math.max(o.minHeight ?? WIND_DEFAULTS.minHeight, z));
1032
+ return terrain.b[cls] * (o.gustFactor ?? WIND_DEFAULTS.gustFactor)[cls] * (zz / 10) ** terrain.p[cls];
1033
+ };
1034
+ var meanHeightFactor = (z, terrain, cls, o = {}) => (o.meanRatio ?? WIND_DEFAULTS.meanRatio) * heightFactor(z, terrain, cls, { gustFactor: { A: 1, B: 1, C: 1 }, ...o.minHeight !== void 0 ? { minHeight: o.minHeight } : {} });
1035
+ var turbulenceIntensity = (z, z0) => Math.min(0.5, 1 / Math.log(Math.max(z, 1.5 * z0) / z0));
1036
+ var solveWallFrame = (hb, ei, ga, V) => {
1037
+ const n = hb.length;
1038
+ const diag = new Array(n + 1).fill(0);
1039
+ const off = new Array(n).fill(0);
1040
+ const rhs = new Array(n + 1).fill(0);
1041
+ for (let j = 0; j < n; j++) {
1042
+ const h = hb[j], e = ei[j], g = ga[j];
1043
+ diag[j] += e / h + g * h / 3;
1044
+ diag[j + 1] += e / h + g * h / 3;
1045
+ off[j] = -e / h + g * h / 6;
1046
+ rhs[j] += h / 6 * (2 * V[j] + V[j + 1]);
1047
+ rhs[j + 1] += h / 6 * (V[j] + 2 * V[j + 1]);
1048
+ }
1049
+ const wall = ei[0] / (ei[0] + ga[0] * hb[0] * hb[0]);
1050
+ diag[0] += 1e6 * (ei[0] / hb[0] + ga[0] * hb[0]) * wall;
1051
+ const c = new Array(n + 1).fill(0);
1052
+ const d = new Array(n + 1).fill(0);
1053
+ const psi = new Array(n + 1).fill(0);
1054
+ c[0] = off[0] / diag[0];
1055
+ d[0] = rhs[0] / diag[0];
1056
+ for (let i = 1; i <= n; i++) {
1057
+ const m = diag[i] - off[i - 1] * c[i - 1];
1058
+ c[i] = i < n ? off[i] / m : 0;
1059
+ d[i] = (rhs[i] - off[i - 1] * d[i - 1]) / m;
1060
+ }
1061
+ psi[n] = d[n];
1062
+ for (let i = n - 1; i >= 0; i--) psi[i] = d[i] - c[i] * psi[i + 1];
1063
+ const w = new Array(n + 1).fill(0);
1064
+ for (let j = 0; j < n; j++) w[j + 1] = w[j] + hb[j] * (psi[j] + psi[j + 1]) / 2;
1065
+ return { psi, w };
1066
+ };
1067
+ var shearNodes = (f) => {
1068
+ const V = new Array(f.length + 1).fill(0);
1069
+ for (let j = f.length - 1; j >= 0; j--) V[j] = V[j + 1] + f[j];
1070
+ return V;
1071
+ };
1072
+ var momentNodes = (hb, V) => {
1073
+ const M = new Array(V.length).fill(0);
1074
+ for (let j = hb.length - 1; j >= 0; j--) M[j] = M[j + 1] + hb[j] * (V[j] + V[j + 1]) / 2;
1075
+ return M;
1076
+ };
1077
+ var RIGID = 1e3;
1078
+ var tBeamInertia = (bw, h, bf, tf) => {
1079
+ const web = bw * h;
1080
+ const fl = Math.max(0, bf - bw) * Math.min(tf, h);
1081
+ const t = Math.min(tf, h);
1082
+ const yc = (web * (h / 2) + fl * (h - t / 2)) / (web + fl);
1083
+ return bw * h ** 3 / 12 + web * (h / 2 - yc) ** 2 + Math.max(0, bf - bw) * t ** 3 / 12 + fl * (h - t / 2 - yc) ** 2;
1084
+ };
1085
+ var shapeAt = (a, z) => {
1086
+ const { z: zs, phi } = a.shape;
1087
+ if (z <= zs[0]) return phi[0];
1088
+ for (let i = 1; i < zs.length; i++) if (z <= zs[i]) return phi[i - 1] + (phi[i] - phi[i - 1]) * (z - zs[i - 1]) / (zs[i] - zs[i - 1]);
1089
+ return phi[phi.length - 1];
1090
+ };
1091
+ var analyzeWind = (parts, c, ground) => {
1092
+ const [dx, dz] = c.direction;
1093
+ const along = (p) => {
1094
+ const cx = (p.min[0] + p.max[0]) / 2, cz = (p.min[2] + p.max[2]) / 2;
1095
+ const hx = (p.max[0] - p.min[0]) / 2, hz = (p.max[2] - p.min[2]) / 2;
1096
+ return {
1097
+ u: cx * dx + cz * dz,
1098
+ v: -cx * dz + cz * dx,
1099
+ du: 2 * (Math.abs(dx) * hx + Math.abs(dz) * hz),
1100
+ dv: 2 * (Math.abs(dz) * hx + Math.abs(dx) * hz)
1101
+ };
1102
+ };
1103
+ const g = parts.map(along);
1104
+ let topY = ground;
1105
+ let resists = false;
1106
+ for (const p of parts) {
1107
+ if (p.kind === "foundation") continue;
1108
+ topY = Math.max(topY, p.max[1]);
1109
+ if ((p.kind === "wall" || p.kind === "column") && p.elasticPa) resists = true;
1110
+ }
1111
+ const height = topY - ground;
1112
+ if (!(height > 0) || !resists) return null;
1113
+ const n = Math.max(1, Math.ceil(height / c.bandHeight - 1e-9));
1114
+ const bands = Array.from({ length: n }, (_, j) => Math.min(c.bandHeight, height - j * c.bandHeight));
1115
+ const z0 = Array.from({ length: n }, (_, j) => j * c.bandHeight);
1116
+ const mid = bands.map((h, j) => z0[j] + h / 2);
1117
+ const share = (p, j) => {
1118
+ const a = Math.max(p.min[1] - ground, z0[j]), b = Math.min(p.max[1] - ground, z0[j] + bands[j]);
1119
+ return Math.max(0, b - a);
1120
+ };
1121
+ const vmin = new Array(n).fill(Infinity), vmax = new Array(n).fill(-Infinity);
1122
+ const mass = new Array(n).fill(0);
1123
+ let weight = 0, wu = 0;
1124
+ let footEnd = -Infinity;
1125
+ let anyFoundation = false;
1126
+ for (let i = 0; i < parts.length; i++) {
1127
+ const p = parts[i];
1128
+ weight += p.mass * GRAVITY;
1129
+ wu += p.mass * g[i].u;
1130
+ if (p.kind === "foundation") {
1131
+ anyFoundation = true;
1132
+ footEnd = Math.max(footEnd, g[i].u + g[i].du / 2);
1133
+ continue;
1134
+ }
1135
+ const full = Math.max(1e-6, p.max[1] - p.min[1]);
1136
+ for (let j = 0; j < n; j++) {
1137
+ const ov = share(p, j);
1138
+ if (ov <= 0) continue;
1139
+ vmin[j] = Math.min(vmin[j], g[i].v - g[i].dv / 2);
1140
+ vmax[j] = Math.max(vmax[j], g[i].v + g[i].dv / 2);
1141
+ if (!p.damper) mass[j] += p.mass * ov / full;
1142
+ }
1143
+ }
1144
+ if (!anyFoundation) {
1145
+ for (let i = 0; i < parts.length; i++) if (parts[i].min[1] - ground < 0.1) footEnd = Math.max(footEnd, g[i].u + g[i].du / 2);
1146
+ }
1147
+ const totalMass = weight / GRAVITY;
1148
+ const uCg = totalMass > 0 ? wu / totalMass : 0;
1149
+ const width = vmax.map((hi, j) => hi > vmin[j] ? hi - vmin[j] : 0);
1150
+ const widest = Math.max(...width);
1151
+ const cls = c.cls === "auto" ? windClass(Math.max(height, widest)) : c.cls;
1152
+ const k0 = c.speed * c.topography * c.statistical;
1153
+ const pressure = mid.map((z2) => windPressure(k0 * heightFactor(z2, c.terrain, cls, { gustFactor: c.gustFactor, minHeight: c.minHeight }), c.airDensity));
1154
+ const meanPressure = mid.map((z2) => windPressure(k0 * meanHeightFactor(z2, c.terrain, cls, { meanRatio: c.meanRatio, minHeight: c.minHeight }), c.airDensity));
1155
+ const forces = bands.map((h, j) => pressure[j] * c.drag * width[j] * h);
1156
+ const meanForces = bands.map((h, j) => meanPressure[j] * c.drag * width[j] * h);
1157
+ const V = shearNodes(forces);
1158
+ const M = momentNodes(bands, V);
1159
+ const ea = new Array(n).fill(0), eau = new Array(n).fill(0);
1160
+ const vertical = [];
1161
+ for (let i = 0; i < parts.length; i++) {
1162
+ const p = parts[i];
1163
+ if (p.kind !== "wall" && !(p.kind === "column" && c.chord) || !p.elasticPa) continue;
1164
+ vertical.push(i);
1165
+ const area = g[i].du * g[i].dv;
1166
+ const e = p.elasticPa * c.stiffnessFactor;
1167
+ for (let j = 0; j < n; j++) {
1168
+ const w = share(p, j) / bands[j];
1169
+ if (w <= 0) continue;
1170
+ ea[j] += e * area * w;
1171
+ eau[j] += e * area * g[i].u * w;
1172
+ }
1173
+ }
1174
+ const centre = ea.map((v, j) => v > 0 ? eau[j] / v : 0);
1175
+ const ei = new Array(n).fill(0);
1176
+ const ga = new Array(n).fill(0);
1177
+ for (const i of vertical) {
1178
+ const p = parts[i];
1179
+ const e = p.elasticPa * c.stiffnessFactor;
1180
+ for (let j = 0; j < n; j++) {
1181
+ const w = share(p, j) / bands[j];
1182
+ if (w <= 0) continue;
1183
+ ei[j] += e * (g[i].du ** 3 * g[i].dv / 12 + g[i].du * g[i].dv * (g[i].u - centre[j]) ** 2) * w;
1184
+ }
1185
+ }
1186
+ for (let i = 0; i < parts.length; i++) {
1187
+ const p = parts[i];
1188
+ if (p.kind !== "column" || !p.elasticPa) continue;
1189
+ const h = Math.max(1e-6, p.max[1] - p.min[1]);
1190
+ const kc = p.elasticPa * c.stiffnessFactor * (g[i].du ** 3 * g[i].dv) / 12 / h;
1191
+ let beams = 0;
1192
+ for (const b of p.beams ?? []) {
1193
+ const q = parts[b];
1194
+ if (!q?.elasticPa) continue;
1195
+ const hx = q.max[0] - q.min[0], hz = q.max[2] - q.min[2];
1196
+ const long = Math.max(hx, hz), short = Math.min(hx, hz);
1197
+ const cos = hx >= hz ? Math.abs(dx) : Math.abs(dz);
1198
+ const depth = q.max[1] - q.min[1];
1199
+ const inertia = tBeamInertia(short, depth, short + 2 * c.flange * long, q.slab ?? 0);
1200
+ beams += cos * cos * q.elasticPa * c.beamFactor * inertia / Math.max(long, 1e-6);
1201
+ }
1202
+ const k = beams / kc;
1203
+ const base = p.min[1] - ground < 0.05;
1204
+ const a = base ? (0.5 + k) / (2 + k) : k / (2 + k);
1205
+ const stiffness = a * 12 * (p.elasticPa * c.stiffnessFactor) * (g[i].du ** 3 * g[i].dv / 12) / (h * h);
1206
+ for (let j = 0; j < n; j++) ga[j] += stiffness * share(p, j) / bands[j];
1207
+ }
1208
+ let last = -1;
1209
+ for (let j = 0; j < n; j++) if (ei[j] > 0 || ga[j] > 0) last = j;
1210
+ for (let j = last + 1; j < n && last >= 0; j++) {
1211
+ ei[j] = ei[last] * RIGID;
1212
+ ga[j] = ga[last] * RIGID;
1213
+ }
1214
+ const tiny = 1;
1215
+ const eiS = ei.map((v) => Math.max(v, tiny));
1216
+ const gaS = ga.map((v) => Math.max(v, tiny));
1217
+ const sol = solveWallFrame(bands, eiS, gaS, V);
1218
+ const deflection = sol.w[n];
1219
+ const lean = solveWallFrame(bands, eiS, gaS, shearNodes(meanForces)).w[n];
1220
+ const kappa = bands.map((h, j) => (sol.psi[j + 1] - sol.psi[j]) / h);
1221
+ const Mmid = bands.map((h, j) => M[j + 1] + h / 2 * (V[j + 1] + (V[j] + V[j + 1]) / 2) / 2);
1222
+ const Mwall = ei.map((v, j) => Math.min(Mmid[j], Math.max(0, v * kappa[j])));
1223
+ const axial = new Array(parts.length).fill(0);
1224
+ for (const i of vertical) {
1225
+ const p = parts[i];
1226
+ let sum = 0, tot = 0;
1227
+ for (let j = 0; j < n; j++) {
1228
+ const w = share(p, j);
1229
+ if (w <= 0) continue;
1230
+ sum += w * p.elasticPa * c.stiffnessFactor * kappa[j] * g[i].du * g[i].dv * (g[i].u - centre[j]);
1231
+ tot += w;
1232
+ }
1233
+ axial[i] = tot > 0 ? sum / tot : 0;
1234
+ }
1235
+ if (!c.chord) {
1236
+ const colA = new Array(n).fill(0), colAu = new Array(n).fill(0);
1237
+ const cols = [];
1238
+ for (let i = 0; i < parts.length; i++) {
1239
+ if (parts[i].kind !== "column") continue;
1240
+ cols.push(i);
1241
+ for (let j = 0; j < n; j++) {
1242
+ const w = share(parts[i], j) / bands[j];
1243
+ colA[j] += g[i].du * g[i].dv * w;
1244
+ colAu[j] += g[i].du * g[i].dv * g[i].u * w;
1245
+ }
1246
+ }
1247
+ const colC = colA.map((a, j) => a > 0 ? colAu[j] / a : 0);
1248
+ const colI = new Array(n).fill(0);
1249
+ for (const i of cols) for (let j = 0; j < n; j++) colI[j] += g[i].du * g[i].dv * (g[i].u - colC[j]) ** 2 * share(parts[i], j) / bands[j];
1250
+ for (const i of cols) {
1251
+ let sum = 0, tot = 0;
1252
+ for (let j = 0; j < n; j++) {
1253
+ const w = share(parts[i], j);
1254
+ if (w <= 0 || colI[j] <= 0) continue;
1255
+ sum += w * (Math.max(0, Mmid[j] - Mwall[j]) * g[i].du * g[i].dv * (g[i].u - colC[j])) / colI[j];
1256
+ tot += w;
1257
+ }
1258
+ axial[i] = tot > 0 ? sum / tot : 0;
1259
+ }
1260
+ }
1261
+ const arm = footEnd - uCg;
1262
+ const overturn = arm > 0 ? c.loadFactor * M[0] / (weight * arm) : Infinity;
1263
+ const mode = solveWallFrame(bands, eiS, gaS, shearNodes(mass));
1264
+ const top = mode.w[n];
1265
+ const um = bands.map((_, j) => (mode.w[j] + mode.w[j + 1]) / 2);
1266
+ let mu = 0, muu = 0;
1267
+ mass.forEach((m, j) => {
1268
+ mu += m * um[j];
1269
+ muu += m * um[j] * um[j];
1270
+ });
1271
+ const omega = muu > 0 ? Math.sqrt(mu / muu) : 0;
1272
+ const phi = um.map((u) => top > 0 ? u / top : 0);
1273
+ const modalMass = mass.reduce((s, m, j) => s + m * phi[j] * phi[j], 0);
1274
+ const modalForce = meanForces.reduce((s, f, j) => s + f * phi[j], 0);
1275
+ const z = [0, ...bands.map((_, j) => z0[j] + bands[j])];
1276
+ const intensity = turbulenceIntensity(Math.max(0.6 * height, c.minHeight), c.terrain.z0);
1277
+ return {
1278
+ height,
1279
+ cls,
1280
+ width: widest,
1281
+ bands,
1282
+ mid,
1283
+ forces,
1284
+ meanForces,
1285
+ pressure: pressure[n - 1],
1286
+ shear: V[0],
1287
+ moment: M[0],
1288
+ deflection,
1289
+ drift: deflection / height,
1290
+ lean,
1291
+ weight,
1292
+ overturn,
1293
+ wallShare: M[0] > 0 ? Mwall[0] / M[0] : 0,
1294
+ ei,
1295
+ ga,
1296
+ frequency: omega / (2 * Math.PI),
1297
+ omega,
1298
+ modalMass,
1299
+ modalStiffness: omega * omega * modalMass,
1300
+ shape: { z, phi: mode.w.map((w) => top > 0 ? w / top : 0) },
1301
+ modalForce,
1302
+ intensity,
1303
+ axial
1304
+ };
1305
+ };
1306
+ var damperTuning = (mu, kind = "den-hartog") => kind === "warburton" ? { frequency: Math.sqrt(1 - mu / 2) / (1 + mu), damping: Math.sqrt(mu * (1 - mu / 4) / (4 * (1 + mu) * (1 - mu / 2))) } : { frequency: 1 / (1 + mu), damping: Math.sqrt(3 * mu / (8 * (1 + mu) ** 3)) };
1307
+ var damperAmplification = (r, mu, g, damper, structure = 0) => {
1308
+ const mul = (a, b) => [a[0] * b[0] - a[1] * b[1], a[0] * b[1] + a[1] * b[0]];
1309
+ const div = (a, b) => {
1310
+ const d = b[0] * b[0] + b[1] * b[1];
1311
+ return [(a[0] * b[0] + a[1] * b[1]) / d, (a[1] * b[0] - a[0] * b[1]) / d];
1312
+ };
1313
+ const A = [g * g, 2 * damper * g * r];
1314
+ const main = [1 - r * r, 2 * structure * r];
1315
+ const coupling = mu > 0 ? mul([mu * r * r, 0], div(A, [A[0] - r * r, A[1]])) : [0, 0];
1316
+ const total = [main[0] - coupling[0], main[1] - coupling[1]];
1317
+ return 1 / Math.hypot(total[0], total[1]);
1318
+ };
1319
+ var makeGust = (v10, o) => {
1320
+ const rng = createRng(o.seed);
1321
+ const n = Math.max(1, Math.floor(o.components));
1322
+ const df = o.maxHz / n;
1323
+ const freq = Array.from({ length: n }, (_, k) => (k + 0.5) * df);
1324
+ const weight = freq.map((f) => {
1325
+ const x = o.scale * f / Math.max(v10, 1e-6);
1326
+ return 4 * x * x / (1 + x * x) ** (4 / 3) / f * df;
1327
+ });
1328
+ const sum = weight.reduce((s, w) => s + w, 0);
1329
+ return { freq, amp: weight.map((w) => Math.sqrt(2 * w / sum)), phase: freq.map(() => rng.next() * 2 * Math.PI) };
1330
+ };
1331
+ var gustAt = (gust, t) => {
1332
+ let s = 0;
1333
+ for (let k = 0; k < gust.freq.length; k++) s += gust.amp[k] * Math.cos(2 * Math.PI * gust.freq[k] * t + gust.phase[k]);
1334
+ return s;
1335
+ };
1336
+ var stepSway = (s, sys, t, dt, force, maxStep = Infinity) => {
1337
+ const steps = Math.max(1, Math.ceil(dt / maxStep - 1e-9));
1338
+ const h = dt / steps;
1339
+ const d = sys.damper;
1340
+ const f = (x, v, y, w, tt) => {
1341
+ const fd = d ? d.k * (y - x) + d.c * (w - v) : 0;
1342
+ return [v, (force(tt) - sys.K * x - sys.C * v + fd) / sys.M, d ? w : 0, d ? -fd / d.m : 0];
1343
+ };
1344
+ for (let i = 0; i < steps; i++) {
1345
+ const tt = t + i * h;
1346
+ const k1 = f(s.x, s.v, s.y, s.w, tt);
1347
+ const k2 = f(s.x + h / 2 * k1[0], s.v + h / 2 * k1[1], s.y + h / 2 * k1[2], s.w + h / 2 * k1[3], tt + h / 2);
1348
+ const k3 = f(s.x + h / 2 * k2[0], s.v + h / 2 * k2[1], s.y + h / 2 * k2[2], s.w + h / 2 * k2[3], tt + h / 2);
1349
+ const k4 = f(s.x + h * k3[0], s.v + h * k3[1], s.y + h * k3[2], s.w + h * k3[3], tt + h);
1350
+ s.x += h / 6 * (k1[0] + 2 * k2[0] + 2 * k3[0] + k4[0]);
1351
+ s.v += h / 6 * (k1[1] + 2 * k2[1] + 2 * k3[1] + k4[1]);
1352
+ s.y += h / 6 * (k1[2] + 2 * k2[2] + 2 * k3[2] + k4[2]);
1353
+ s.w += h / 6 * (k1[3] + 2 * k2[3] + 2 * k3[3] + k4[3]);
1354
+ }
1355
+ };
1356
+ var tensionCapacity = (dims, o) => {
1357
+ const section = dims[0] * dims[2];
1358
+ if (o.reinforcement) {
1359
+ const bars = verticalBarCount(dims, thinAxisOf(dims), o.reinforcement.spacing);
1360
+ return bars * Math.PI * (o.reinforcement.diameter / 2) ** 2 * o.yieldMPa * 1e6;
1361
+ }
1362
+ if (o.ratio) return o.ratio * section * o.yieldMPa * 1e6;
1363
+ return (o.tensileMPa ?? 0) * 1e6 * section;
1364
+ };
1365
+
569
1366
  // src/data/materials.json
570
1367
  var materials_default = [
571
1368
  {
@@ -573,6 +1370,8 @@ var materials_default = [
573
1370
  density: 7850,
574
1371
  hpPerKg: 400,
575
1372
  penetration: 110,
1373
+ penetrationClass: "steel",
1374
+ penetrationScale: 1.3,
576
1375
  ductility: 0.9,
577
1376
  fracture: "none",
578
1377
  damageScale: {
@@ -602,6 +1401,8 @@ var materials_default = [
602
1401
  density: 7200,
603
1402
  hpPerKg: 150,
604
1403
  penetration: 160,
1404
+ penetrationClass: "steel",
1405
+ penetrationScale: 1,
605
1406
  ductility: 0.2,
606
1407
  fracture: "snap",
607
1408
  fragmentSize: [
@@ -635,6 +1436,8 @@ var materials_default = [
635
1436
  density: 2700,
636
1437
  hpPerKg: 250,
637
1438
  penetration: 60,
1439
+ penetrationClass: "steel",
1440
+ penetrationScale: 3,
638
1441
  ductility: 0.85,
639
1442
  fracture: "none",
640
1443
  damageScale: {
@@ -663,6 +1466,8 @@ var materials_default = [
663
1466
  density: 8960,
664
1467
  hpPerKg: 300,
665
1468
  penetration: 70,
1469
+ penetrationClass: "steel",
1470
+ penetrationScale: 1.5,
666
1471
  ductility: 0.95,
667
1472
  fracture: "none",
668
1473
  damageScale: {
@@ -691,6 +1496,8 @@ var materials_default = [
691
1496
  density: 500,
692
1497
  hpPerKg: 90,
693
1498
  penetration: 9,
1499
+ penetrationClass: "wood",
1500
+ penetrationScale: 1.5,
694
1501
  ductility: 0.35,
695
1502
  fracture: "splinter",
696
1503
  fragmentSize: [
@@ -727,6 +1534,8 @@ var materials_default = [
727
1534
  density: 750,
728
1535
  hpPerKg: 140,
729
1536
  penetration: 14,
1537
+ penetrationClass: "wood",
1538
+ penetrationScale: 1,
730
1539
  ductility: 0.3,
731
1540
  fracture: "splinter",
732
1541
  fragmentSize: [
@@ -762,6 +1571,8 @@ var materials_default = [
762
1571
  density: 600,
763
1572
  hpPerKg: 70,
764
1573
  penetration: 7,
1574
+ penetrationClass: "wood",
1575
+ penetrationScale: 2,
765
1576
  ductility: 0.3,
766
1577
  fracture: "snap",
767
1578
  fragmentSize: [
@@ -797,6 +1608,8 @@ var materials_default = [
797
1608
  density: 750,
798
1609
  hpPerKg: 45,
799
1610
  penetration: 6,
1611
+ penetrationClass: "wood",
1612
+ penetrationScale: 2.5,
800
1613
  ductility: 0.15,
801
1614
  fracture: "snap",
802
1615
  fragmentSize: [
@@ -925,6 +1738,8 @@ var materials_default = [
925
1738
  density: 2300,
926
1739
  hpPerKg: 14,
927
1740
  penetration: 18,
1741
+ penetrationClass: "concrete",
1742
+ penetrationScale: 1,
928
1743
  ductility: 0.03,
929
1744
  fracture: "crumble",
930
1745
  fragmentSize: [
@@ -944,13 +1759,20 @@ var materials_default = [
944
1759
  "masonry",
945
1760
  "brittle"
946
1761
  ],
947
- compressive: 444e3
1762
+ compressive: 444e3,
1763
+ strength: {
1764
+ compressiveMPa: 25,
1765
+ tensileMPa: 2.56,
1766
+ elasticGPa: 28
1767
+ }
948
1768
  },
949
1769
  {
950
1770
  id: "brick",
951
1771
  density: 1900,
952
1772
  hpPerKg: 12,
953
1773
  penetration: 14,
1774
+ penetrationClass: "brick",
1775
+ penetrationScale: 1,
954
1776
  ductility: 0.05,
955
1777
  fracture: "crumble",
956
1778
  fragmentSize: [
@@ -967,13 +1789,20 @@ var materials_default = [
967
1789
  "masonry",
968
1790
  "brittle"
969
1791
  ],
970
- compressive: 4e4
1792
+ compressive: 4e4,
1793
+ strength: {
1794
+ compressiveMPa: 2.35,
1795
+ tensileMPa: 0.1,
1796
+ elasticGPa: 2.35
1797
+ }
971
1798
  },
972
1799
  {
973
1800
  id: "plaster",
974
1801
  density: 850,
975
1802
  hpPerKg: 18,
976
1803
  penetration: 3,
1804
+ penetrationClass: "brick",
1805
+ penetrationScale: 3,
977
1806
  ductility: 0.05,
978
1807
  fracture: "crumble",
979
1808
  fragmentSize: [
@@ -997,6 +1826,8 @@ var materials_default = [
997
1826
  density: 2700,
998
1827
  hpPerKg: 25,
999
1828
  penetration: 25,
1829
+ penetrationClass: "concrete",
1830
+ penetrationScale: 0.9,
1000
1831
  ductility: 0.03,
1001
1832
  fracture: "crumble",
1002
1833
  fragmentSize: [
@@ -1260,6 +2091,8 @@ var materials_default = [
1260
2091
  density: 650,
1261
2092
  hpPerKg: 5,
1262
2093
  penetration: 10,
2094
+ penetrationClass: "wood",
2095
+ penetrationScale: 1.2,
1263
2096
  ductility: 0.5,
1264
2097
  fracture: "splinter",
1265
2098
  fragmentSize: [
@@ -1561,13 +2394,26 @@ var inferJoints = (parts, tolerance = 0.01, materials = MATERIAL_MAP) => {
1561
2394
  }
1562
2395
  return out;
1563
2396
  };
1564
- var partCapacity = (part, material) => {
2397
+ var partCapacity = (part, material, options = {}) => {
1565
2398
  if (part.structure?.capacity !== void 0) return part.structure.capacity;
1566
2399
  const d = shapeDims(part.shape).sort((a, b) => a - b);
1567
2400
  const role = part.structure?.role;
1568
2401
  const sh = shapeDims(part.shape);
1569
2402
  const section = role === "wall" || role === "partition" ? Math.min(sh[0], sh[2]) * Math.max(sh[0], sh[2]) : role === "slab" ? d[0] * d[2] : d[0] * d[1];
1570
- return Math.max(1, section * (material.compressive ?? 2e4) * (role === "foundation" ? 10 : 1));
2403
+ const foundation = role === "foundation" ? 10 : 1;
2404
+ const strength = material.strength;
2405
+ if (!strength) return Math.max(1, section * (material.compressive ?? 2e4) * foundation);
2406
+ const fc = strength.compressiveMPa * 1e6;
2407
+ let newtons = fc * section;
2408
+ const upright = role === "wall" || role === "partition" || role === "column";
2409
+ if (upright && isSlender(part, material)) newtons *= slendernessFactor(sh[1], Math.min(sh[0], sh[2]), options.masonry);
2410
+ else if (role === "column" && part.reinforcement) {
2411
+ const m = resolveMasonry(options.masonry);
2412
+ const bars = rebarBars(sh, thinAxisOf(sh), part.reinforcement.spacing).length;
2413
+ const steel = bars * Math.PI * (part.reinforcement.diameter / 2) ** 2;
2414
+ newtons = m.concreteFactor * fc * section + steel * resolveFlexure(options.flexure).rebarYieldMPa * 1e6;
2415
+ }
2416
+ return Math.max(1, newtons / GRAVITY * foundation);
1571
2417
  };
1572
2418
  var fragility = (ductility, thickness) => {
1573
2419
  if (ductility >= 0.3) return 1;
@@ -1575,22 +2421,32 @@ var fragility = (ductility, thickness) => {
1575
2421
  };
1576
2422
 
1577
2423
  // src/world.ts
2424
+ import { ignitionChance, resolveIgnitionRules, sparkTargetFactor } from "@adatechnology/game-contracts";
2425
+ var THIN_SHEET_DISHING = 5e-3;
1578
2426
  var DMG_PER_J = 0.02;
1579
2427
  var BLAST_COUPLING = 0.1;
1580
2428
  var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
1581
2429
  var FRAGMENT_HIT_DEFAULTS = { max: 24, energyFactor: 0.5, hitType: "blunt", range: 6, flightTime: 0.4, spread: 10, aim: 40 };
1582
2430
  var CONFINEMENT_DEFAULTS = { rays: 26, reach: 1.5, gain: 1.5, power: 2, max: 3 };
1583
2431
  var FLAMMABLE_FLUIDS = ["gas", "fuel"];
2432
+ var VENT_DIAMETER = 8e-3;
2433
+ var FLAME_LIGHTS_LEAK_C = 470;
1584
2434
  var BREACH_RADIUS = 0.55;
1585
2435
  var BREACH_BLAST_FACTOR = 6;
1586
2436
  var BREACH_AREA = 0.25;
1587
- var BREACH_BLAST_REACH = Infinity;
2437
+ var BREACH_BLAST_REACH = 0.12;
1588
2438
  var RESIDUAL_CAPACITY = 0.15;
2439
+ var SUPPORT_TOLERANCE = 0.05;
1589
2440
  var UNIT_FRAGMENT_FACTOR = 4;
1590
2441
  var BREACH_GRID_CAP = 4096;
1591
2442
  var CUT_SLICE = 1;
1592
2443
  var cutFactorOf = (shape) => clamp2(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
1593
2444
  var clamp2 = (v, a, b) => Math.max(a, Math.min(b, v));
2445
+ var penetrationOf = (mat, table) => {
2446
+ if (!table) return void 0;
2447
+ const v = table[mat.penetrationClass ?? mat.id] ?? table[mat.id];
2448
+ return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
2449
+ };
1594
2450
  var capacityScale = (p) => {
1595
2451
  if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
1596
2452
  return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
@@ -1618,8 +2474,14 @@ var createDestruction = (options = {}) => {
1618
2474
  const maxHoles = options.maxHoles ?? 32;
1619
2475
  const fireSpread = options.fireSpread ?? 0.6;
1620
2476
  const breachRadius = options.breach?.radius ?? BREACH_RADIUS;
2477
+ const structureSafety = options.structure?.safetyFactor ?? 1;
2478
+ const flexure = resolveFlexure(options.structure?.flexure);
2479
+ const masonry = resolveMasonry(options.structure?.masonry);
2480
+ const soilCfg = resolveSoil(options.soil);
2481
+ let windOpts = options.wind;
2482
+ let windCfg = windOpts ? resolveWind(windOpts) : null;
2483
+ const ignitionRules = resolveIgnitionRules(options.ignition);
1621
2484
  const breachBlastFactor = options.breach?.blastFactor ?? BREACH_BLAST_FACTOR;
1622
- const breachBlastReach = options.breach?.blastReach ?? BREACH_BLAST_REACH;
1623
2485
  const rng = createRng(options.seed ?? 1);
1624
2486
  const rnd = () => rng.next();
1625
2487
  let time = 0;
@@ -1646,7 +2508,7 @@ var createDestruction = (options = {}) => {
1646
2508
  const pendingCollapse = /* @__PURE__ */ new Map();
1647
2509
  let runDepth = 0;
1648
2510
  const drainBlasts = () => {
1649
- for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source);
2511
+ for (let blast = blasts.shift(); blast; blast = blasts.shift()) blastWorld(blast.point, blast.energy, blast.radius, blast.source, void 0, void 0, blast.lethalRadius);
1650
2512
  };
1651
2513
  const run = (fn) => {
1652
2514
  const prev = cur;
@@ -1716,7 +2578,8 @@ var createDestruction = (options = {}) => {
1716
2578
  collapsed: new Set(carry?.collapsed ?? []),
1717
2579
  unsup: [],
1718
2580
  anyAnchor: false,
1719
- order: ++orderCounter
2581
+ order: ++orderCounter,
2582
+ base: { pos: [pos[0], pos[1], pos[2]], rot: [rot[0], rot[1], rot[2], rot[3]] }
1720
2583
  };
1721
2584
  for (const ps of spec.parts) {
1722
2585
  if (o.byId.has(ps.id)) throw new Error(`duplicate part id "${ps.id}"`);
@@ -1768,10 +2631,17 @@ var createDestruction = (options = {}) => {
1768
2631
  rt: c?.rt ?? { ...freshRuntime(), prevTemp: pst.temperature },
1769
2632
  structural: !!ps.structure && ps.structure.role !== "none",
1770
2633
  anchored: !!ps.anchored || ps.structure?.role === "foundation",
1771
- cap: ps.structure ? partCapacity(ps, mat) : 0,
2634
+ cap: ps.structure ? partCapacity(ps, mat, { masonry, flexure }) : 0,
2635
+ orphan: false,
1772
2636
  staged,
1773
2637
  thin: thinAxisOf(shapeDims(ps.shape)),
1774
- layers
2638
+ layers,
2639
+ span: 0,
2640
+ spanKey: "",
2641
+ restMoment: 0,
2642
+ bendLoad: 0,
2643
+ snap: false,
2644
+ ...ps.shape.kind === "box" && isFlexural(ps, mat, flexure) ? { bend: bendGeometry(ps) } : {}
1775
2645
  };
1776
2646
  if (p.structural) o.hasStructure = true;
1777
2647
  if (p.rt.chainDue !== void 0 && !isLost(p)) primed.set(p, o);
@@ -1805,7 +2675,14 @@ var createDestruction = (options = {}) => {
1805
2675
  updateBound(o);
1806
2676
  o.anyAnchor = o.parts.some((p) => p.anchored);
1807
2677
  o.unsup = o.parts.map(() => false);
2678
+ if (windCfg && o.hasStructure && spec.wind !== false) o.wind = newWind(o, carry?.wind);
2679
+ const ground = spec.soil ?? soilCfg.site;
2680
+ if (ground !== void 0 && o.parts.some((p) => p.spec.structure?.role === "foundation")) initSoil(o, profileOf(ground), carry?.soil, carry?.soilSets);
2681
+ o.seeding = true;
1808
2682
  solveStructure(o);
2683
+ o.seeding = false;
2684
+ if (o.soil && !carry?.soil) settleSoil(o, 0);
2685
+ if (o.soil) o.soil.dirty = false;
1809
2686
  recomputeGroups(o);
1810
2687
  o.st.destroyed = o.parts.length > 0 && o.parts.every(isLost);
1811
2688
  o.active = o.maxStress > 0.8 || o.parts.some((p) => p.st.burning || p.st.temperature > ambient + 0.5 || (p.st.liters ?? 0) > 0);
@@ -1857,12 +2734,126 @@ var createDestruction = (options = {}) => {
1857
2734
  for (const p of o.parts) if (isLost(p)) p.st.group = -1;
1858
2735
  list.forEach((g, gi) => g.ix.forEach((i) => o.parts[i].st.group = gi));
1859
2736
  };
1860
- const restsOn = (j, r) => {
2737
+ const restsOn = (o, j, r) => {
1861
2738
  const b = j.bears;
1862
2739
  if (!b) return -1;
1863
- if (b === true) return j.ia === r ? j.ib : j.ia;
1864
- if (b === "a_on_b") return j.ia === r ? j.ib : -1;
1865
- return j.ib === r ? j.ia : -1;
2740
+ const k = b === true ? j.ia === r ? j.ib : j.ia : b === "a_on_b" ? j.ia === r ? j.ib : -1 : j.ib === r ? j.ia : -1;
2741
+ if (k < 0 || j.spec.kind !== "tie") return k;
2742
+ return partXf(o, o.parts[k]).c[1] > partXf(o, o.parts[r]).c[1] + 1e-6 ? k : -1;
2743
+ };
2744
+ const contactBox = (p, support) => {
2745
+ const A = aabbOf(p.spec.shape, p.pos, p.q), B = aabbOf(support.spec.shape, support.pos, support.q);
2746
+ const lo = [0, 0, 0], hi = [0, 0, 0];
2747
+ for (let k = 0; k < 3; k++) {
2748
+ const a = Math.max(A.min[k], B.min[k]) - SUPPORT_TOLERANCE, b = Math.min(A.max[k], B.max[k]) + SUPPORT_TOLERANCE;
2749
+ lo[k] = Math.min(a, b);
2750
+ hi[k] = Math.max(a, b);
2751
+ }
2752
+ const out = [[Infinity, Infinity, Infinity], [-Infinity, -Infinity, -Infinity]];
2753
+ for (let c = 0; c < 8; c++) {
2754
+ const l = toLocal(p.pos, p.q, [c & 1 ? hi[0] : lo[0], c & 2 ? hi[1] : lo[1], c & 4 ? hi[2] : lo[2]]);
2755
+ for (let k = 0; k < 3; k++) {
2756
+ out[0][k] = Math.min(out[0][k], l[k]);
2757
+ out[1][k] = Math.max(out[1][k], l[k]);
2758
+ }
2759
+ }
2760
+ return out;
2761
+ };
2762
+ const contactCentre = (p, above) => {
2763
+ const [lo, hi] = contactBox(p, above);
2764
+ return toWorld(p.pos, p.q, [(lo[0] + hi[0]) / 2, (lo[1] + hi[1]) / 2, (lo[2] + hi[2]) / 2]);
2765
+ };
2766
+ const overSupportAt = (o, p, at, lower) => {
2767
+ const g = p.bend;
2768
+ const local = toLocal(p.pos, p.q, at);
2769
+ const depth = g.gravity >= 0 ? g.dims[g.gravity] : Math.min(...g.depthAxes.map((a) => g.dims[a]));
2770
+ let best = -1;
2771
+ let bestGap = depth;
2772
+ for (const k of lower) {
2773
+ const [a, b] = contactBox(p, o.parts[k]);
2774
+ let gap = 0;
2775
+ for (const ax of g.spanAxes) gap = Math.max(gap, a[ax] - local[ax], local[ax] - b[ax]);
2776
+ if (gap <= bestGap) {
2777
+ best = k;
2778
+ bestGap = gap - 1e-9;
2779
+ }
2780
+ }
2781
+ return best;
2782
+ };
2783
+ const sectionOf = (p, axis) => {
2784
+ const g = p.bend;
2785
+ const section = { b: sectionWidth(g, axis), h: g.dims[axis] };
2786
+ const rebar = tensionSteel(p.spec, g.dims, section, flexure);
2787
+ return { section, rebar, ...sectionMoments(p.mat, section, rebar, flexure) };
2788
+ };
2789
+ const restFlexure = (o, p, lower) => {
2790
+ const g = p.bend;
2791
+ const key = lower.join(",");
2792
+ if (key !== p.spanKey) {
2793
+ p.spanKey = key;
2794
+ p.span = spanOf(g, lower.map((k) => contactBox(p, o.parts[k])));
2795
+ }
2796
+ p.restMoment = 0;
2797
+ if (g.gravity < 0 || p.span <= 0) return 0;
2798
+ const sec = sectionOf(p, g.gravity);
2799
+ p.restMoment = spanMoment(lineLoad(g, p.bendLoad) * Math.abs(g.up[g.gravity]), p.span);
2800
+ const holds = (p.st.cracked ? sec.steel : sec.ultimate) * capacityScale(p);
2801
+ return p.restMoment * structureSafety / Math.max(holds, 1);
2802
+ };
2803
+ const bendByBlast = (o, p, point, energy, radius, reach, src) => {
2804
+ const g = p.bend;
2805
+ if (!g || p.span <= 0 || isLost(p) || p.st.stripped || reach <= 0) return;
2806
+ const s = p.mat.strength;
2807
+ if (!s.elasticGPa) return;
2808
+ const { c, q } = partXf(o, p);
2809
+ const near = surfacePoint(p.spec.shape, toLocal(c, q, point));
2810
+ const local = toLocal(c, q, point);
2811
+ const away = vec.normalize(vec.sub(near.point, local), vec.scale(near.normal, -1));
2812
+ let axis = g.depthAxes[0];
2813
+ for (const a of g.depthAxes) if (Math.abs(away[a]) > Math.abs(away[axis])) axis = a;
2814
+ const cos = Math.abs(away[axis]);
2815
+ if (cos < 1e-3) return;
2816
+ const tnt = energy / flexure.tntJoules;
2817
+ const L = p.span;
2818
+ const offsets = g.member ? [[-0.5], [-0.25], [0], [0.25], [0.5]] : [[0, 0], [-0.25, 0], [0.25, 0], [0, -0.25], [0, 0.25]];
2819
+ const face = -Math.sign(away[axis]) * g.dims[axis] / 2;
2820
+ let sum = 0;
2821
+ for (const off of offsets) {
2822
+ const sp = [near.point[0], near.point[1], near.point[2]];
2823
+ g.spanAxes.forEach((a, k) => sp[a] = Math.max(-g.dims[a] / 2, Math.min(g.dims[a] / 2, sp[a] + off[k] * L)));
2824
+ sp[axis] = face;
2825
+ const r = vec.distance(toWorld(c, q, sp), point);
2826
+ if (r < radius) sum += blastImpulse(tnt, r, flexure);
2827
+ }
2828
+ const width = sectionWidth(g, axis);
2829
+ const impulse = sum / offsets.length * cos * reach * width * L;
2830
+ const weight = axis === g.gravity ? p.bendLoad : p.st.mass;
2831
+ const kinetic = impulseEnergy(impulse, lineLoad(g, weight) / GRAVITY * L, flexure);
2832
+ const sec = sectionOf(p, axis);
2833
+ const EI = s.elasticGPa * 1e9 * sec.section.b * sec.section.h ** 3 / 12;
2834
+ const along = axis === g.gravity ? -Math.sign(away[axis] * g.up[axis]) : 0;
2835
+ const dynamic = momentOfEnergy(kinetic, L, EI, flexure.loadFactor);
2836
+ const peak = Math.abs(dynamic + along * p.restMoment);
2837
+ if (peak <= sec.cracking) return;
2838
+ if (!p.st.cracked) {
2839
+ p.st.cracked = true;
2840
+ const tension = [near.point[0], near.point[1], near.point[2]];
2841
+ tension[axis] = dynamic + along * p.restMoment >= 0 ? -face : face;
2842
+ const n = [0, 0, 0];
2843
+ n[axis] = Math.sign(tension[axis]) || 1;
2844
+ addCrack(o, p, toWorld(c, q, tension), rotateVec(q, n), sec.section.b, src);
2845
+ }
2846
+ if (!sec.reinforced) {
2847
+ p.snap = true;
2848
+ failPart(o, p, point, away, kinetic, 12, src, "snap");
2849
+ return;
2850
+ }
2851
+ solveStructure(o);
2852
+ if (peak <= sec.steel) return;
2853
+ const rotation = (elasticEnergy(peak, L, EI, flexure.loadFactor) - elasticEnergy(sec.steel, L, EI, flexure.loadFactor)) / (2 * sec.steel);
2854
+ p.rt.hinge = (p.rt.hinge ?? 0) + rotation;
2855
+ markDamaged(p);
2856
+ if (p.rt.hinge >= flexure.failRotationDeg * Math.PI / 180) hingeDrop(o, p, src);
1866
2857
  };
1867
2858
  const solveStructure = (o) => {
1868
2859
  o.maxStress = 0;
@@ -1883,23 +2874,41 @@ var createDestruction = (options = {}) => {
1883
2874
  for (const j of o.byPart.get(parts[s].spec.id)) {
1884
2875
  if (j.st.open || !j.bears) continue;
1885
2876
  const r = j.ia === s ? j.ib : j.ia;
1886
- if (dist[r] >= 0 || !ok(r) || restsOn(j, r) !== s) continue;
2877
+ if (dist[r] >= 0 || !ok(r) || restsOn(o, j, r) !== s) continue;
1887
2878
  dist[r] = dist[s] + 1;
1888
2879
  queue.push(r);
1889
2880
  }
1890
2881
  }
1891
2882
  const incoming = new Array(n).fill(0);
1892
- for (let h = queue.length - 1; h >= 0; h--) {
1893
- const i = queue[h];
1894
- const total = parts[i].st.mass + incoming[i];
1895
- parts[i].st.load = total;
2883
+ const lowerOf = /* @__PURE__ */ new Map();
2884
+ for (const i of queue) {
1896
2885
  const lower = [];
1897
2886
  for (const j of o.byPart.get(parts[i].spec.id)) {
1898
2887
  if (j.st.open || !j.bears) continue;
1899
- const k = restsOn(j, i);
2888
+ const k = restsOn(o, j, i);
1900
2889
  if (k >= 0 && dist[k] >= 0 && dist[k] < dist[i] && !isPartition(k)) lower.push(k);
1901
2890
  }
2891
+ lowerOf.set(i, lower);
2892
+ }
2893
+ const bending = new Array(n).fill(0);
2894
+ const through = parts.map(() => []);
2895
+ const pass = (k, at, mass) => {
2896
+ const over = overSupportAt(o, parts[k], at, lowerOf.get(k) ?? []);
2897
+ if (over < 0) bending[k] += mass;
2898
+ else through[k].push({ at, mass, over });
2899
+ };
2900
+ for (let h = queue.length - 1; h >= 0; h--) {
2901
+ const i = queue[h];
2902
+ const total = parts[i].st.mass + incoming[i];
2903
+ parts[i].st.load = total;
2904
+ const lower = lowerOf.get(i);
1902
2905
  for (const k of lower) incoming[k] += total / lower.length;
2906
+ if (lower.length === 0) continue;
2907
+ const bulk = parts[i].bend ? parts[i].st.mass + bending[i] : total;
2908
+ for (const k of lower) if (parts[k].bend) pass(k, contactCentre(parts[k], parts[i]), bulk / lower.length);
2909
+ if (parts[i].bend) {
2910
+ for (const item of through[i]) if (parts[item.over]?.bend) pass(item.over, item.at, item.mass);
2911
+ }
1903
2912
  }
1904
2913
  for (let i = 0; i < n; i++) {
1905
2914
  const p = parts[i];
@@ -1908,13 +2917,420 @@ var createDestruction = (options = {}) => {
1908
2917
  if (dist[i] < 0 || isLost(p)) {
1909
2918
  p.st.load = p.st.mass;
1910
2919
  p.st.stress = 0;
1911
- o.unsup[i] = o.anyAnchor && !isLost(p);
2920
+ if (o.seeding && !p.anchored && !isLost(p) && p.st.status !== "collapsed" && !o.collapsed.has(p.spec.id)) p.orphan = true;
2921
+ o.unsup[i] = o.anyAnchor && !isLost(p) && !p.orphan;
1912
2922
  continue;
1913
2923
  }
1914
- p.st.stress = p.st.load / (p.cap * capacityScale(p));
2924
+ p.st.stress = p.st.load * structureSafety / (p.cap * capacityScale(p));
2925
+ if (p.bend) {
2926
+ p.bendLoad = p.st.mass + bending[i];
2927
+ const bent = restFlexure(o, p, lowerOf.get(i) ?? []);
2928
+ p.st.flexure = bent;
2929
+ p.snap = bent > 1 && !p.st.stripped && !sectionOf(p, p.bend.gravity).reinforced;
2930
+ if (p.spec.structure?.capacity === void 0 || bent > p.st.stress) p.st.stress = bent;
2931
+ }
1915
2932
  if (p.st.stress > o.maxStress) o.maxStress = p.st.stress;
1916
2933
  }
2934
+ if (o.wind) applyWind(o);
1917
2935
  if (o.maxStress > 0.8) o.active = true;
2936
+ if (o.soil) o.soil.dirty = true;
2937
+ };
2938
+ const newWind = (o, carried) => {
2939
+ const cfg = exposureOf(windCfg, o.spec.wind);
2940
+ const [x = 0, v = 0, y = 0, w = 0, peak = 0] = carried ?? [];
2941
+ return {
2942
+ cfg,
2943
+ gust: { freq: [], amp: [], phase: [] },
2944
+ gustKey: "",
2945
+ analysis: null,
2946
+ parts: [],
2947
+ sys: null,
2948
+ mu: 0,
2949
+ dyn: { x, v, y, w, peak },
2950
+ tension: /* @__PURE__ */ new Map(),
2951
+ emitted: { t: -Infinity, peak: 0, status: "ok" }
2952
+ };
2953
+ };
2954
+ const windDirection = (o, cfg) => {
2955
+ const d = rotateVec(quat.conjugate(o.base.rot), cfg.direction);
2956
+ const h = Math.hypot(d[0], d[2]);
2957
+ return h > 1e-9 ? [d[0] / h, d[2] / h] : [1, 0];
2958
+ };
2959
+ const applyWind = (o) => {
2960
+ const w = o.wind;
2961
+ const cfg = w.cfg = exposureOf(windCfg, o.spec.wind);
2962
+ const terrain = cfg.terrains.get(cfg.terrain);
2963
+ if (!terrain) throw new Error(`unknown wind terrain "${cfg.terrain}"`);
2964
+ const inputs = [];
2965
+ const at = /* @__PURE__ */ new Map();
2966
+ const keep = [];
2967
+ let ground = Infinity, foundationTop = -Infinity;
2968
+ o.parts.forEach((p, i) => {
2969
+ if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[i]) return;
2970
+ const box = aabbOf(p.spec.shape, p.pos, p.q);
2971
+ const role = p.spec.structure?.role;
2972
+ const e = p.mat.strength?.elasticGPa;
2973
+ const kind = role === "wall" ? "wall" : role === "column" ? "column" : role === "beam" ? "beam" : role === "foundation" ? "foundation" : "other";
2974
+ at.set(i, inputs.length);
2975
+ keep.push(i);
2976
+ ground = Math.min(ground, box.min[1]);
2977
+ if (kind === "foundation") foundationTop = Math.max(foundationTop, box.max[1]);
2978
+ inputs.push({ min: box.min, max: box.max, kind, ...e ? { elasticPa: e * 1e9 } : {}, mass: p.st.mass, ...p.spec.damper ? { damper: true } : {} });
2979
+ });
2980
+ keep.forEach((i, k) => {
2981
+ if (inputs[k].kind !== "column") return;
2982
+ const beams = [];
2983
+ for (const j of o.byPart.get(o.parts[i].spec.id)) {
2984
+ if (j.st.open) continue;
2985
+ const b = at.get(j.ia === i ? j.ib : j.ia);
2986
+ if (b !== void 0 && inputs[b].kind === "beam") beams.push(b);
2987
+ }
2988
+ inputs[k].beams = beams;
2989
+ });
2990
+ keep.forEach((i, k) => {
2991
+ if (inputs[k].kind !== "beam") return;
2992
+ for (const j of o.byPart.get(o.parts[i].spec.id)) {
2993
+ if (j.st.open) continue;
2994
+ const q = o.parts[j.ia === i ? j.ib : j.ia];
2995
+ const role = q.spec.structure?.role;
2996
+ if ((role === "slab" || role === "roof") && !isLost(q) && q.spec.shape.kind === "box") {
2997
+ inputs[k].slab = q.dims[1];
2998
+ break;
2999
+ }
3000
+ }
3001
+ });
3002
+ const c = {
3003
+ speed: cfg.speed,
3004
+ direction: windDirection(o, cfg),
3005
+ terrain,
3006
+ cls: cfg.class,
3007
+ topography: cfg.topography,
3008
+ statistical: cfg.statistical,
3009
+ drag: cfg.drag,
3010
+ damping: cfg.damping,
3011
+ airDensity: cfg.airDensity,
3012
+ minHeight: cfg.minHeight,
3013
+ bandHeight: cfg.bandHeight,
3014
+ stiffnessFactor: cfg.stiffnessFactor,
3015
+ beamFactor: cfg.beamFactor,
3016
+ loadFactor: cfg.loadFactor,
3017
+ chord: cfg.chord,
3018
+ meanRatio: cfg.meanRatio,
3019
+ gustFactor: cfg.gustFactor,
3020
+ flange: cfg.flange
3021
+ };
3022
+ const a = inputs.length && cfg.speed > 0 ? analyzeWind(inputs, c, foundationTop > -Infinity ? foundationTop : ground) : null;
3023
+ w.analysis = a;
3024
+ w.parts = keep;
3025
+ for (const p of o.parts) delete p.st.wind;
3026
+ if (!a) {
3027
+ w.sys = null;
3028
+ delete o.st.wind;
3029
+ return;
3030
+ }
3031
+ keep.forEach((i, k) => {
3032
+ const N = a.axial[k];
3033
+ const p = o.parts[i];
3034
+ if (!N || !p.structural || o.unsup[i]) return;
3035
+ p.st.wind = N;
3036
+ const dead = p.st.load ?? p.st.mass;
3037
+ const force = N * cfg.loadFactor;
3038
+ let stress = 0;
3039
+ if (force >= 0) stress = (dead * structureSafety + force / GRAVITY) / (p.cap * capacityScale(p));
3040
+ else if (dead + force / GRAVITY < 0) {
3041
+ let cap = w.tension.get(i);
3042
+ if (cap === void 0) {
3043
+ const r = p.spec.reinforcement;
3044
+ const ratio = p.spec.structure?.rebar?.ratio;
3045
+ cap = tensionCapacity(p.dims, {
3046
+ ...r ? { reinforcement: r } : {},
3047
+ ...ratio ? { ratio } : {},
3048
+ yieldMPa: flexure.rebarYieldMPa,
3049
+ ...p.mat.strength?.tensileMPa !== void 0 ? { tensileMPa: p.mat.strength.tensileMPa } : {}
3050
+ });
3051
+ w.tension.set(i, cap);
3052
+ }
3053
+ stress = Math.min(10, -(dead + force / GRAVITY) * GRAVITY / Math.max(cap * capacityScale(p), 1));
3054
+ }
3055
+ if (stress > (p.st.stress ?? 0)) p.st.stress = stress;
3056
+ if ((p.st.stress ?? 0) > o.maxStress) o.maxStress = p.st.stress ?? 0;
3057
+ });
3058
+ const key = `${cfg.speed}|${cfg.terrain}|${cfg.topography}|${cfg.statistical}|${cfg.meanRatio}|${cfg.gust.components}|${cfg.gust.maxHz}|${cfg.gust.scale}|${cfg.gust.seed}`;
3059
+ if (key !== w.gustKey) {
3060
+ w.gustKey = key;
3061
+ const v10 = cfg.speed * cfg.topography * cfg.statistical * meanHeightFactor(10, terrain, a.cls, { meanRatio: cfg.meanRatio, minHeight: cfg.minHeight });
3062
+ w.gust = makeGust(v10, cfg.gust);
3063
+ }
3064
+ let md = 0, spec;
3065
+ for (const i of keep) {
3066
+ const p = o.parts[i];
3067
+ if (!p.spec.damper) continue;
3068
+ const phi = shapeAt(a, p.pos[1] - (foundationTop > -Infinity ? foundationTop : ground));
3069
+ md += p.st.mass * phi * phi;
3070
+ spec ?? (spec = p.spec.damper);
3071
+ }
3072
+ const sys = { M: a.modalMass, K: a.modalStiffness, C: 2 * cfg.damping * a.omega * a.modalMass };
3073
+ w.mu = 0;
3074
+ if (md > 0 && a.modalMass > 0) {
3075
+ w.mu = md / a.modalMass;
3076
+ const tuned = damperTuning(w.mu, cfg.tuning);
3077
+ const f = spec?.frequency ?? tuned.frequency * a.frequency;
3078
+ const zeta = spec?.damping ?? tuned.damping;
3079
+ sys.damper = { m: md, k: md * (2 * Math.PI * f) ** 2, c: 2 * zeta * md * 2 * Math.PI * f };
3080
+ }
3081
+ w.sys = a.modalMass > 0 && a.omega > 0 ? sys : null;
3082
+ const st = {
3083
+ speed: cfg.speed,
3084
+ pressure: a.pressure,
3085
+ shear: a.shear,
3086
+ moment: a.moment,
3087
+ deflection: a.deflection,
3088
+ drift: a.drift,
3089
+ limit: cfg.driftLimit,
3090
+ overturn: Math.min(a.overturn, 1e3),
3091
+ frequency: a.frequency,
3092
+ lean: a.lean,
3093
+ sway: w.dyn.x,
3094
+ peak: w.dyn.peak,
3095
+ status: a.overturn > 1 ? "overturn" : a.drift > cfg.driftLimit ? "drift" : "ok"
3096
+ };
3097
+ if (sys.damper) st.damper = { ratio: w.mu, travel: w.dyn.y - w.dyn.x };
3098
+ o.st.wind = st;
3099
+ };
3100
+ const stepWind = (o, dt) => {
3101
+ const w = o.wind;
3102
+ const a = w.analysis;
3103
+ const st = o.st.wind;
3104
+ if (!a || !st || !w.sys) return;
3105
+ const gust = w.gust;
3106
+ const I = a.intensity;
3107
+ const force = (t) => {
3108
+ const n = gustAt(gust, t);
3109
+ return a.modalForce * (2 * I * n + I * I * (n * n - 1));
3110
+ };
3111
+ const wn = Math.max(a.omega, w.sys.damper ? Math.sqrt(w.sys.damper.k / w.sys.damper.m) : 0);
3112
+ stepSway(w.dyn, w.sys, time - dt, dt, force, 2 * Math.PI / wn / 20);
3113
+ const d = w.dyn;
3114
+ d.peak = Math.max(Math.abs(d.x), d.peak * Math.exp(-dt / w.cfg.peakDecay));
3115
+ st.sway = d.x;
3116
+ st.peak = d.peak;
3117
+ if (st.damper) st.damper.travel = d.y - d.x;
3118
+ const e = w.emitted;
3119
+ if (d.peak < e.peak * 0.8) e.peak = d.peak;
3120
+ if (st.status !== e.status || time - e.t >= 0.5 && d.peak > e.peak * 1.05 + 1e-3) {
3121
+ w.emitted = { t: time, peak: d.peak, status: st.status };
3122
+ const dir = windDirection(o, w.cfg);
3123
+ const world2 = rotateVec(o.base.rot, [dir[0], 0, dir[1]]);
3124
+ emit({ ...base(o), type: "sway", sway: d.x, peak: d.peak, drift: st.drift, overturn: st.overturn, frequency: st.frequency, direction: world2, status: st.status });
3125
+ }
3126
+ };
3127
+ const isTerrain = (p) => !!p.spec.tags?.includes("terrain");
3128
+ const isFoundation = (p) => p.spec.structure?.role === "foundation" && !isLost(p);
3129
+ const slopeTilt = (a, b) => Math.atan(Math.hypot(a, b));
3130
+ const soilSprings = (o, profile) => {
3131
+ const found = o.parts.filter(isFoundation);
3132
+ const box = (p) => aabbOf(p.spec.shape, p.pos, p.q);
3133
+ const surface = profile.surface ?? Math.max(...found.map((p) => box(p).max[1]));
3134
+ const prints = [];
3135
+ for (const f of found) {
3136
+ const b = box(f);
3137
+ if (!isTerrain(f)) {
3138
+ prints.push({ x0: b.min[0], x1: b.max[0], z0: b.min[2], z1: b.max[2], bottom: b.min[1], part: f });
3139
+ continue;
3140
+ }
3141
+ const fi = o.idx.get(f.spec.id);
3142
+ const width = profile.footingWidth ?? soilCfg.footingWidth;
3143
+ for (const j of o.byPart.get(f.spec.id)) {
3144
+ if (j.st.open || !j.bears) continue;
3145
+ const r = j.ia === fi ? j.ib : j.ia;
3146
+ const q = o.parts[r];
3147
+ if (!q.structural || isLost(q) || restsOn(o, j, r) !== fi) continue;
3148
+ const c = box(q);
3149
+ const x0 = Math.max(c.min[0], b.min[0]), x1 = Math.min(c.max[0], b.max[0]), z0 = Math.max(c.min[2], b.min[2]), z1 = Math.min(c.max[2], b.max[2]);
3150
+ const cx = (x0 + x1) / 2, cz = (z0 + z1) / 2;
3151
+ const wx = Math.max(width, x1 - x0), wz = Math.max(width, z1 - z0);
3152
+ prints.push({ x0: cx - wx / 2, x1: cx + wx / 2, z0: cz - wz / 2, z1: cz + wz / 2, bottom: c.min[1], part: q });
3153
+ }
3154
+ }
3155
+ const springs = [];
3156
+ for (const fp of prints) {
3157
+ const wx = Math.max(0.01, fp.x1 - fp.x0), wz = Math.max(0.01, fp.z1 - fp.z0);
3158
+ const depth = Math.max(0, surface - fp.bottom);
3159
+ const pile = fp.part.spec.structure?.role === "foundation" ? fp.part.spec.structure.pile : void 0;
3160
+ if (pile) {
3161
+ const count = Math.max(1, Math.round(pile.count ?? 1));
3162
+ const nx2 = Math.ceil(Math.sqrt(count)), nz2 = Math.ceil(count / nx2);
3163
+ const cap = pileCapacity(profile, pile, depth, soilCfg.byId).ultimate;
3164
+ const tip = soilAt(profile, depth + pile.length, soilCfg.byId);
3165
+ const k = pileStiffness(pile, fp.part.mat.strength?.elasticGPa ?? soilCfg.pileElasticGPa, tip.elasticMPa, tip.poisson);
3166
+ for (let i = 0; i < count; i++) {
3167
+ const col = i % nx2, row = Math.floor(i / nx2);
3168
+ springs.push({ key: `${fp.part.spec.id}#${i}`, x: fp.x0 + (col + 0.5) * wx / nx2, z: fp.z0 + (row + 0.5) * wz / nz2, hx: wx / (2 * nx2), hz: wz / (2 * nz2), k, cap });
3169
+ }
3170
+ continue;
3171
+ }
3172
+ const B = Math.min(wx, wz);
3173
+ const g = footingSoil(profile, depth, B, soilCfg.byId, soilCfg.influence);
3174
+ const ks = subgradeModulus(g.elasticMPa, g.poisson, B, soilCfg.rigidity);
3175
+ const size = Math.max(wx, wz) / Math.max(1, soilCfg.cells);
3176
+ const nx = Math.max(1, Math.round(wx / size)), nz = Math.max(1, Math.round(wz / size));
3177
+ const area = wx * wz / (nx * nz);
3178
+ for (let i = 0; i < nx; i++) {
3179
+ for (let k = 0; k < nz; k++) {
3180
+ springs.push({ key: `${fp.part.spec.id}#${i}.${k}`, x: fp.x0 + (i + 0.5) * wx / nx, z: fp.z0 + (k + 0.5) * wz / nz, hx: wx / (2 * nx), hz: wz / (2 * nz), k: ks * area, cap: soilCfg.safety * g.allowableKPa * 1e3 * area });
3181
+ }
3182
+ }
3183
+ }
3184
+ return { springs, baseY: prints.length ? Math.min(...prints.map((fp) => fp.bottom)) : 0 };
3185
+ };
3186
+ const soilWeight = (o) => {
3187
+ let m = 0;
3188
+ const c = [0, 0, 0];
3189
+ for (const p of o.parts) {
3190
+ if (isLost(p) || isTerrain(p) || o.collapsed.has(p.spec.id) || o.unsup[o.idx.get(p.spec.id)]) continue;
3191
+ m += p.st.mass;
3192
+ for (let k = 0; k < 3; k++) c[k] += p.st.mass * p.pos[k];
3193
+ }
3194
+ return { w: m * GRAVITY, c: m > 0 ? [c[0] / m, c[1] / m, c[2] / m] : c };
3195
+ };
3196
+ const initSoil = (o, profile, carried, carrySets) => {
3197
+ const so = { profile, s: 0, a: 0, b: 0, imp: [0, 0], pivot: [0, 0, 0], dirty: true, sets: /* @__PURE__ */ new Map(), emitted: { t: -Infinity, s: 0, tilt: 0, status: "stable" } };
3198
+ const { springs, baseY } = soilSprings(o, profile);
3199
+ if (!springs.length) return;
3200
+ const [x0, z0] = stiffnessCentre(springs);
3201
+ so.pivot = [x0, baseY, z0];
3202
+ let ix = 0, iz = 0;
3203
+ for (const sp of springs) {
3204
+ ix += sp.k * (sp.x - x0) ** 2;
3205
+ iz += sp.k * (sp.z - z0) ** 2;
3206
+ }
3207
+ const { c } = soilWeight(o);
3208
+ const along = ix <= iz ? 0 : 1;
3209
+ const off = along === 0 ? c[0] - x0 : c[2] - z0;
3210
+ const sign = Math.abs(off) > 1e-6 ? Math.sign(off) : 1;
3211
+ so.imp = along === 0 ? [sign * soilCfg.imperfection, 0] : [0, sign * soilCfg.imperfection];
3212
+ if (carried) {
3213
+ const t = Math.tan(carried.tilt * Math.PI / 180);
3214
+ so.s = carried.settlement;
3215
+ so.a = t * carried.direction[0];
3216
+ so.b = t * carried.direction[2];
3217
+ so.emitted = { t: -Infinity, s: carried.settlement, tilt: carried.tilt, status: carried.status };
3218
+ o.st.soil = clone(carried);
3219
+ for (const [k, v] of Object.entries(carrySets ?? {})) so.sets.set(k, v);
3220
+ if (carried.status === "toppled") for (const p of o.parts) p.anchored = false;
3221
+ }
3222
+ o.soil = so;
3223
+ applySoilPose(o);
3224
+ };
3225
+ const applySoilPose = (o) => {
3226
+ const so = o.soil;
3227
+ if (!so) {
3228
+ o.st.position = [o.base.pos[0], o.base.pos[1], o.base.pos[2]];
3229
+ o.st.rotation = [o.base.rot[0], o.base.rot[1], o.base.rot[2], o.base.rot[3]];
3230
+ return;
3231
+ }
3232
+ const angle = slopeTilt(so.a, so.b);
3233
+ const R = angle > 1e-12 ? quat.fromAxisAngle(vec.normalize([so.b, 0, -so.a]), angle) : quat.identity();
3234
+ const t = vec.sub(vec.sub(so.pivot, rotateVec(R, so.pivot)), [0, so.s, 0]);
3235
+ o.st.rotation = quat.normalize(quat.mul(o.base.rot, R));
3236
+ o.st.position = vec.add(o.base.pos, rotateVec(o.base.rot, t));
3237
+ const lean = Math.hypot(so.a, so.b);
3238
+ const st = o.st.soil ?? { settlement: 0, tilt: 0, direction: [0, 0, 0], bearing: 0, edge: 0, status: "stable", offset: { position: [0, 0, 0], rotation: [0, 0, 0, 1] } };
3239
+ st.settlement = so.s;
3240
+ st.tilt = angle * 180 / Math.PI;
3241
+ st.direction = lean > 1e-12 ? [so.a / lean, 0, so.b / lean] : [0, 0, 0];
3242
+ st.offset = { position: t, rotation: R };
3243
+ o.st.soil = st;
3244
+ };
3245
+ const topple = (o, src) => {
3246
+ for (const p of o.parts) p.anchored = false;
3247
+ solveStructure(o);
3248
+ recomputeGroups(o);
3249
+ if (!pendingCollapse.has(o)) pendingCollapse.set(o, src);
3250
+ };
3251
+ const settleSoil = (o, dt) => {
3252
+ const so = o.soil;
3253
+ const st = o.st.soil;
3254
+ if (st?.status === "toppled") return;
3255
+ const { springs } = soilSprings(o, so.profile);
3256
+ for (const sp of springs) sp.set = so.sets.get(sp.key) ?? 0;
3257
+ const { w, c } = soilWeight(o);
3258
+ if (!springs.length || w <= 0) {
3259
+ if (springs.length === 0 && w > 0) {
3260
+ so.s += soilCfg.sinkRate * dt;
3261
+ applySoilPose(o);
3262
+ o.st.soil.status = "yielding";
3263
+ }
3264
+ return;
3265
+ }
3266
+ const height = Math.max(0, c[1] - so.pivot[1]);
3267
+ const wa = o.wind?.analysis;
3268
+ const push = wa ? windDirection(o, o.wind.cfg) : [0, 0];
3269
+ const shift = wa ? wa.moment / w : 0;
3270
+ const r = solveFoundation(springs, w, c[0] + shift * push[0], c[2] + shift * push[1], height, so.imp, { s: so.s, a: so.a, b: so.b });
3271
+ let status = "stable";
3272
+ const sinkStep = soilCfg.sinkRate * dt;
3273
+ const tiltStep = Math.tan(soilCfg.tiltRate * Math.PI / 180) * dt;
3274
+ if (r.stable && (!r.yielded || dt === 0)) {
3275
+ so.s = r.s;
3276
+ so.a = r.a;
3277
+ so.b = r.b;
3278
+ } else if (r.stable) {
3279
+ const ds = r.s - so.s, da = r.a - so.a, db = r.b - so.b;
3280
+ const fs = Math.abs(ds) > sinkStep ? sinkStep / Math.abs(ds) : 1;
3281
+ const lean2 = Math.hypot(da, db);
3282
+ const ft = lean2 > tiltStep ? tiltStep / lean2 : 1;
3283
+ so.s += ds * fs;
3284
+ so.a += da * ft;
3285
+ so.b += db * ft;
3286
+ if (fs < 1 || ft < 1) status = "yielding";
3287
+ } else {
3288
+ status = "yielding";
3289
+ if (dt === 0) {
3290
+ so.s = w / springs.reduce((sum, sp) => sum + sp.k, 0);
3291
+ } else {
3292
+ so.s += sinkStep * (r.bearing >= 1 ? 1 : 0.25);
3293
+ const lean2 = Math.hypot(so.a, so.b);
3294
+ const ux = lean2 > 1e-9 ? so.a / lean2 : Math.sign(so.imp[0]), uz = lean2 > 1e-9 ? so.b / lean2 : Math.sign(so.imp[1]);
3295
+ so.a += ux * tiltStep;
3296
+ so.b += uz * tiltStep;
3297
+ }
3298
+ }
3299
+ if (so.s !== 0 || so.a !== 0 || so.b !== 0) {
3300
+ const sets = yieldSprings(springs, so.s, so.a, so.b);
3301
+ springs.forEach((sp, i) => {
3302
+ if (sets[i] > 0) so.sets.set(sp.key, sets[i]);
3303
+ });
3304
+ }
3305
+ applySoilPose(o);
3306
+ const out = o.st.soil;
3307
+ out.bearing = r.bearing;
3308
+ out.edge = r.edge;
3309
+ out.status = status;
3310
+ const lean = Math.hypot(so.a, so.b);
3311
+ if (lean > 1e-9) {
3312
+ const [x0, z0] = [so.pivot[0], so.pivot[2]];
3313
+ const ux = so.a / lean, uz = so.b / lean;
3314
+ const e = (c[0] - x0 + height * (so.a + so.imp[0])) * ux + (c[2] - z0 + height * (so.b + so.imp[1])) * uz;
3315
+ let edge = 0;
3316
+ for (const sp of springs) edge = Math.max(edge, (sp.x - x0) * ux + (sp.z - z0) * uz + sp.hx * Math.abs(ux) + sp.hz * Math.abs(uz));
3317
+ if (e > edge || slopeTilt(so.a, so.b) > Math.PI / 3) out.status = "toppled";
3318
+ }
3319
+ };
3320
+ const stepSoil = (o, dt) => {
3321
+ const so = o.soil;
3322
+ if (o.st.soil?.status === "toppled") return;
3323
+ if (!so.dirty && o.st.soil?.status !== "yielding") return;
3324
+ so.dirty = false;
3325
+ settleSoil(o, dt);
3326
+ const st = o.st.soil;
3327
+ const e = so.emitted;
3328
+ const changed = st.status !== e.status || (time - e.t >= 0.5 || e.t === -Infinity) && (Math.abs(st.tilt - e.tilt) > 0.01 || Math.abs(st.settlement - e.s) > 2e-3);
3329
+ if (changed) {
3330
+ so.emitted = { t: time, s: st.settlement, tilt: st.tilt, status: st.status };
3331
+ emit({ ...base(o), type: "settle", settlement: st.settlement, tilt: st.tilt, direction: rotateVec(o.base.rot, st.direction), bearing: st.bearing, status: st.status });
3332
+ }
3333
+ if (st.status === "toppled") topple(o);
1918
3334
  };
1919
3335
  const collapseCheck = (o, src) => {
1920
3336
  if (!o.hasStructure || !o.parts.some((p) => p.anchored)) return;
@@ -2151,7 +3567,7 @@ var createDestruction = (options = {}) => {
2151
3567
  if (split && mine.length) amount /= mine.length;
2152
3568
  for (const j of mine) {
2153
3569
  if (j.st.open) continue;
2154
- if (j.spec.kind === "rebar" && (o.parts[j.ia].spec.reinforcement || o.parts[j.ib].spec.reinforcement)) continue;
3570
+ if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
2155
3571
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
2156
3572
  j.st.hp -= amount;
2157
3573
  if (j.st.hp <= 0) {
@@ -2307,14 +3723,37 @@ var createDestruction = (options = {}) => {
2307
3723
  debris(o, p, "sparks", partXf(o, p).c, [0, 1, 0], 6, 3, src);
2308
3724
  partLost(o, p, src);
2309
3725
  };
2310
- const failPart = (o, p, origin, dir, fragEnergy, cap, src) => {
3726
+ const hingeDrop = (o, p, src) => {
3727
+ const i = o.idx.get(p.spec.id);
3728
+ const opened = [];
3729
+ for (const j of o.byPart.get(p.spec.id) ?? []) {
3730
+ if (j.st.open || restsOn(o, j, i) < 0) continue;
3731
+ j.st.open = true;
3732
+ j.st.hp = 0;
3733
+ j.st.openedBy = "break";
3734
+ opened.push(j);
3735
+ }
3736
+ p.st.hp = Math.max(1, p.st.maxHp * 0.1);
3737
+ p.st.cracked = true;
3738
+ markDamaged(p);
3739
+ debris(o, p, p.mat.breakDebris, partXf(o, p).c, [0, -1, 0], 12, 2, src);
3740
+ solveStructure(o);
3741
+ recomputeGroups(o);
3742
+ detachEvents(o, opened, "break", src);
3743
+ collapseCheck(o, src);
3744
+ };
3745
+ const failPart = (o, p, origin, dir, fragEnergy, cap, src, force) => {
2311
3746
  if (isLost(p)) return;
2312
3747
  if (p.st.stripped) {
2313
3748
  loseCage(o, p, src);
2314
3749
  return;
2315
3750
  }
3751
+ if (!force && p.bend && (p.st.flexure ?? 0) > 1 && sectionOf(p, p.bend.gravity).reinforced) {
3752
+ hingeDrop(o, p, src);
3753
+ return;
3754
+ }
2316
3755
  const ex = p.spec.explodes;
2317
- const style = p.mat.fracture === "none" ? "snap" : p.mat.fracture;
3756
+ const style = force ?? (p.snap ? "snap" : p.mat.fracture === "none" ? "snap" : p.mat.fracture);
2318
3757
  const { c, q } = partXf(o, p);
2319
3758
  const hitW = origin ?? c;
2320
3759
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
@@ -2371,8 +3810,8 @@ var createDestruction = (options = {}) => {
2371
3810
  return;
2372
3811
  }
2373
3812
  if (ex) {
2374
- 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 } : {} });
2375
- blasts.push({ point: c, energy: ex.energy, radius: ex.radius, source: src });
3813
+ emit({ ...base(o, src), type: "explode", part: p.spec.id, point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, ...ex.effect !== void 0 ? { effect: ex.effect } : {} });
3814
+ blasts.push({ point: c, energy: ex.energy, radius: ex.radius, ...ex.lethalRadius !== void 0 ? { lethalRadius: ex.lethalRadius } : {}, source: src });
2376
3815
  }
2377
3816
  partLost(o, p, src);
2378
3817
  };
@@ -2401,8 +3840,33 @@ var createDestruction = (options = {}) => {
2401
3840
  else if (chained && (trigger === "explosion" || 1 - p.st.hp / p.st.maxHp >= shotDamageFractionOf(p.spec))) prime(o, p, trigger, src);
2402
3841
  return done;
2403
3842
  };
2404
- const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, piercing = 1) => {
3843
+ const leaksFlammable = (p) => {
3844
+ const c = p.spec.contains;
3845
+ return !!c && FLAMMABLE_FLUIDS.includes(c.fluid) && (p.st.liters ?? 0) > 0 && p.st.holes.some((h) => h.through);
3846
+ };
3847
+ const rollIgnition = (o, p, round) => {
3848
+ const c = p.spec.contains;
3849
+ if (!c || !FLAMMABLE_FLUIDS.includes(c.fluid) || p.rt.jet || p.rt.lit) return;
3850
+ const fill = c.liters > 0 ? (p.st.liters ?? 0) / c.liters : 0;
3851
+ const chance = ignitionChance(round, { sparkTarget: sparkTargetFactor(p.mat.id, !!p.mat.tags?.includes("metal"), ignitionRules), fill, fluid: c.fluid }, ignitionRules);
3852
+ if (chance > 0 && rnd() < chance) {
3853
+ p.rt.lit = true;
3854
+ o.active = true;
3855
+ }
3856
+ };
3857
+ const lightLeaksNear = (centre, radius) => {
3858
+ for (const o of objects.values()) {
3859
+ for (const q of o.parts) {
3860
+ if (isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
3861
+ if (vec.distance(partXf(o, q).c, centre) - q.bound > radius) continue;
3862
+ q.rt.lit = true;
3863
+ o.active = true;
3864
+ }
3865
+ }
3866
+ };
3867
+ const ballistic = (o, p, entry, n, d, E, caliber, pathLen, src, round = {}) => {
2405
3868
  const mat = p.mat;
3869
+ const piercing = round.piercing ?? 1;
2406
3870
  if (p.st.stripped || p.st.opening && insideOpening(o, p, objLocal(o, entry))) {
2407
3871
  return { penetrated: true, exitEnergy: E, exitPoint: vec.madd(entry, d, Math.min(pathLen, p.thickness)), damage: 0 };
2408
3872
  }
@@ -2415,7 +3879,8 @@ var createDestruction = (options = {}) => {
2415
3879
  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;
2416
3880
  const thr = coats > 0 ? (mat.penetration * p.thickness * 1e3 + coats) / slant : mat.penetration * effM * 1e3;
2417
3881
  const hard = mat.tags?.includes("metal") || mat.tags?.includes("masonry");
2418
- const Ep = piercing === 1 || !hard ? E : E * piercing;
3882
+ const penMm = penetrationOf(mat, round.penetration);
3883
+ const Ep = penMm !== void 0 ? penMm * mat.penetration : piercing === 1 || !hard ? E : E * piercing;
2419
3884
  const penetrated = Ep > thr;
2420
3885
  const scale = (mat.damageScale?.ballistic ?? 1) * (mat.fracture === "shatter" ? fragility(mat.ductility, p.thickness) : 1);
2421
3886
  const id = p.spec.id;
@@ -2436,6 +3901,7 @@ var createDestruction = (options = {}) => {
2436
3901
  const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
2437
3902
  if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
2438
3903
  addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
3904
+ if (mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_DISHING) addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(E * 2e-5, 1e-3, 0.03), src);
2439
3905
  if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
2440
3906
  if (p.spec.contains && (p.st.liters ?? 0) > 0) {
2441
3907
  emit({
@@ -2444,10 +3910,11 @@ var createDestruction = (options = {}) => {
2444
3910
  part: id,
2445
3911
  fluid: p.spec.contains.fluid,
2446
3912
  point: entry,
2447
- direction: n,
3913
+ direction: p.spec.contains.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, p.spec.contains.outflow))) : n,
2448
3914
  rate: holeRate(hole, !!p.spec.contains.pressurized),
2449
3915
  pressurized: !!p.spec.contains.pressurized
2450
3916
  });
3917
+ rollIgnition(o, p, round);
2451
3918
  }
2452
3919
  } else if (mat.ductility >= 0.5) {
2453
3920
  addDent(o, p, entry, n, Math.max(caliber * 3 / 1e3, 0.012), clamp2(Math.min(Math.max(p.thickness * 3, 3e-3), ratio * 0.01), 1e-3, 0.05), src);
@@ -2458,7 +3925,7 @@ var createDestruction = (options = {}) => {
2458
3925
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
2459
3926
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
2460
3927
  const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry, d, E * 0.1, 12, src);
2461
- return { penetrated, exitEnergy: penetrated ? E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
3928
+ return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
2462
3929
  };
2463
3930
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
2464
3931
  const mat = p.mat;
@@ -2478,8 +3945,9 @@ var createDestruction = (options = {}) => {
2478
3945
  }
2479
3946
  debris(o, p, mat.hitDebris, point, n, clamp2(Math.round(E / 25) + 1, 1, 10), clamp2(Math.sqrt(E) / 8, 1, 6), src);
2480
3947
  }
2481
- if (kind === "blunt") wearJoints(o, p, E * 0.3, void 0, 0, src);
2482
- else wearJoints(o, p, E * 0.3, point, 0.06, src);
3948
+ if (kind === "blunt") {
3949
+ if (!(p.anchored && p.spec.tags?.includes("terrain"))) wearJoints(o, p, E * 0.3, void 0, 0, src);
3950
+ } else wearJoints(o, p, E * 0.3, point, 0.06, src);
2483
3951
  if (kind === "blunt" && E > 0 && p.layers.length > 0) spallOuter(o, p, point, faceOf(o, p, n), n, Math.max(contactR, 0.05), src);
2484
3952
  const scale = mat.damageScale?.[kind] ?? 1;
2485
3953
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
@@ -2526,7 +3994,8 @@ var createDestruction = (options = {}) => {
2526
3994
  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);
2527
3995
  const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
2528
3996
  res.damage += dealt;
2529
- if (dist <= breachBlastReach * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
3997
+ if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
3998
+ if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
2530
3999
  return reach;
2531
4000
  };
2532
4001
  const blastObject = (o, point, energy, radius, src) => {
@@ -2589,7 +4058,7 @@ var createDestruction = (options = {}) => {
2589
4058
  };
2590
4059
  const FLAG_LOST = 1;
2591
4060
  const FLAG_OPEN = 2;
2592
- const blastPlain = (point, energy, radius, src) => {
4061
+ const blastPlain = (point, energy, radius, src, lethal) => {
2593
4062
  const out = [];
2594
4063
  for (const o of [...objects.values()]) {
2595
4064
  if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
@@ -2597,7 +4066,7 @@ var createDestruction = (options = {}) => {
2597
4066
  if (r.part) out.push(r);
2598
4067
  }
2599
4068
  for (const im of [...impls.values()]) {
2600
- if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...src !== void 0 ? { source: src } : {} }));
4069
+ if (implInRange(im, point, radius)) out.push(implHit(im, { type: "explosive", point, energy, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...src !== void 0 ? { source: src } : {} }));
2601
4070
  }
2602
4071
  return out;
2603
4072
  };
@@ -2606,7 +4075,7 @@ var createDestruction = (options = {}) => {
2606
4075
  return { ps, c: toWorld(im.position, im.rotation, ov?.pos ?? ps.position), q: quat.mul(im.rotation, ov?.q ?? quatFromEuler(...ps.rotation ?? [0, 0, 0])) };
2607
4076
  });
2608
4077
  const implInRange = (im, point, radius) => implParts(im).some(({ ps, c, q }) => distanceToShape(ps.shape, toLocal(c, q, point)) < radius);
2609
- const blastScene = (point, energy, radius, src, cfg, only) => {
4078
+ const blastScene = (point, energy, radius, src, cfg, only, lethal) => {
2610
4079
  const mult = confinementOf(cfg.conf, point, radius);
2611
4080
  const E = energy * mult;
2612
4081
  const shelter = cfg.shelter;
@@ -2713,7 +4182,7 @@ var createDestruction = (options = {}) => {
2713
4182
  den += f;
2714
4183
  }
2715
4184
  const k = den > 0 ? num / den : 1;
2716
- const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
4185
+ const res = implHit(im, { type: "explosive", point, energy: E * k, radius, ...lethal !== void 0 ? { lethalRadius: lethal } : {}, ...shelter ? { shelter: k } : {}, ...src !== void 0 ? { source: src } : {} });
2717
4186
  results.set(im, res);
2718
4187
  report(im).shelter = k;
2719
4188
  }
@@ -2811,10 +4280,11 @@ var createDestruction = (options = {}) => {
2811
4280
  if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
2812
4281
  rep.fragmentHits++;
2813
4282
  };
2814
- const blastWorld = (point, energy, radius, src, call, only) => {
4283
+ const blastWorld = (point, energy, radius, src, call, only, lethal) => {
2815
4284
  const cfg = blastCfg(call);
2816
- if (!cfg.shelter && !cfg.frag && cfg.conf === null) return blastPlain(point, energy, radius, src);
2817
- return blastScene(point, energy, radius, src, cfg, only);
4285
+ const res = !cfg.shelter && !cfg.frag && cfg.conf === null ? blastPlain(point, energy, radius, src, lethal) : blastScene(point, energy, radius, src, cfg, only, lethal);
4286
+ lightLeaksNear(point, radius);
4287
+ return res;
2818
4288
  };
2819
4289
  const heatObject = (o, point, T, radius, duration, src) => {
2820
4290
  const start = cur.length;
@@ -2833,6 +4303,7 @@ var createDestruction = (options = {}) => {
2833
4303
  p.st.temperature += (T - p.st.temperature) * (1 - Math.exp(-k * duration));
2834
4304
  }
2835
4305
  o.active = true;
4306
+ if (T >= FLAME_LIGHTS_LEAK_C && !p.rt.jet && !p.rt.lit && leaksFlammable(p)) p.rt.lit = true;
2836
4307
  checkIgnite(o, p, src);
2837
4308
  }
2838
4309
  res.events = cur.slice(start);
@@ -2852,7 +4323,7 @@ var createDestruction = (options = {}) => {
2852
4323
  if (hit.type === "ballistic") {
2853
4324
  const loc = locate(o, point, dir, 0.05, only);
2854
4325
  if (loc) {
2855
- const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit.piercing ?? 1);
4326
+ const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit);
2856
4327
  res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
2857
4328
  }
2858
4329
  } else if (hit.type === "blunt" || hit.type === "slash") {
@@ -3006,7 +4477,8 @@ var createDestruction = (options = {}) => {
3006
4477
  if (rate > 0) {
3007
4478
  s.liters = Math.max(0, (s.liters ?? 0) - rate * dt);
3008
4479
  hot = true;
3009
- if (FLAMMABLE_FLUIDS.includes(cont.fluid) && s.temperature >= 350 && !p.rt.jet) {
4480
+ if (FLAMMABLE_FLUIDS.includes(cont.fluid) && (s.temperature >= 350 || p.rt.lit) && !p.rt.jet) {
4481
+ p.rt.lit = false;
3010
4482
  if (ex && chainDelayOf(p.spec, "fire") !== void 0) {
3011
4483
  prime(o, p, "fire");
3012
4484
  continue;
@@ -3021,6 +4493,7 @@ var createDestruction = (options = {}) => {
3021
4493
  }
3022
4494
  }
3023
4495
  }
4496
+ if ((s.liters ?? 0) <= 0) p.rt.lit = false;
3024
4497
  if (p.rt.jet && (s.liters ?? 0) <= 0) {
3025
4498
  p.rt.jet = false;
3026
4499
  s.burning = false;
@@ -3070,6 +4543,25 @@ var createDestruction = (options = {}) => {
3070
4543
  }
3071
4544
  o.active = hot;
3072
4545
  };
4546
+ const igniteLeaksNearFire = (sources, dt) => {
4547
+ const { radius, chancePerSecond } = ignitionRules.fireNear;
4548
+ if (chancePerSecond <= 0) return;
4549
+ const chance = 1 - Math.exp(-chancePerSecond * dt);
4550
+ for (const src of sources) {
4551
+ if (!src.p.st.burning && !src.p.rt.jet) continue;
4552
+ const sc = partXf(src.o, src.p).c;
4553
+ for (const o of objects.values()) {
4554
+ for (const q of o.parts) {
4555
+ if (q === src.p || isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
4556
+ if (vec.distance(partXf(o, q).c, sc) - q.bound - src.p.bound > radius) continue;
4557
+ if (rnd() < chance) {
4558
+ q.rt.lit = true;
4559
+ o.active = true;
4560
+ }
4561
+ }
4562
+ }
4563
+ }
4564
+ };
3073
4565
  const spread2 = (sources, dt) => {
3074
4566
  for (const src of sources) {
3075
4567
  if (!src.p.st.burning && !src.p.rt.jet) continue;
@@ -3195,8 +4687,8 @@ var createDestruction = (options = {}) => {
3195
4687
  return;
3196
4688
  }
3197
4689
  const o = getObj(id);
3198
- o.st.position = [position[0], position[1], position[2]];
3199
- o.st.rotation = [rotation[0], rotation[1], rotation[2], rotation[3]];
4690
+ o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
4691
+ applySoilPose(o);
3200
4692
  },
3201
4693
  splitGroup(id, group, newId) {
3202
4694
  if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
@@ -3219,13 +4711,17 @@ var createDestruction = (options = {}) => {
3219
4711
  const s = { parts: o.spec.parts.filter((p) => keep(p.id)), joints: (o.spec.joints ?? []).filter((j) => keep(j.a) && keep(j.b)) };
3220
4712
  if (o.spec.name !== void 0) s.name = o.spec.name;
3221
4713
  if (o.spec.tags) s.tags = [...o.spec.tags];
4714
+ if (o.spec.soil !== void 0) s.soil = o.spec.soil;
3222
4715
  return clone(s);
3223
4716
  };
3224
4717
  const inNew = (pid) => moved.has(pid);
3225
4718
  const inOld = (pid) => !moved.has(pid);
3226
4719
  const pos = o.st.position, rot = o.st.rotation;
3227
4720
  const fresh = build(sub(inNew), nid, pos, rot, carryFor(inNew, (a, b) => inNew(a) && inNew(b)));
3228
- const rest = build(sub(inOld), id, pos, rot, carryFor(inOld, (a, b) => !(inNew(a) && inNew(b))));
4721
+ const restCarry = carryFor(inOld, (a, b) => !(inNew(a) && inNew(b)));
4722
+ if (o.st.soil) restCarry.soil = o.st.soil;
4723
+ if (o.soil?.sets.size) restCarry.soilSets = Object.fromEntries(o.soil.sets);
4724
+ const rest = build(sub(inOld), id, o.soil ? o.base.pos : pos, o.soil ? o.base.rot : rot, restCarry);
3229
4725
  objects.set(id, rest);
3230
4726
  objects.set(nid, fresh);
3231
4727
  return nid;
@@ -3250,7 +4746,8 @@ var createDestruction = (options = {}) => {
3250
4746
  const r = raycastInternal(origin, direction, maxDistance);
3251
4747
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
3252
4748
  },
3253
- shoot(origin, direction, energy, caliber, source, piercing) {
4749
+ shoot(origin, direction, energy, caliber, source, round) {
4750
+ const extras = typeof round === "number" ? { piercing: round } : round ?? {};
3254
4751
  return withCollapses(run(() => {
3255
4752
  const results = [];
3256
4753
  const d = vec.normalize(direction);
@@ -3262,7 +4759,7 @@ var createDestruction = (options = {}) => {
3262
4759
  if (!rh) break;
3263
4760
  const im = impls.get(rh.object);
3264
4761
  if (im) {
3265
- const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...piercing !== void 0 ? { piercing } : {}, ...source !== void 0 ? { source } : {} });
4762
+ const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
3266
4763
  r.part = rh.part;
3267
4764
  r.point = rh.point;
3268
4765
  r.exitEnergy = r.penetrated ? E * 0.5 : 0;
@@ -3276,7 +4773,7 @@ var createDestruction = (options = {}) => {
3276
4773
  const o = getObj(rh.object);
3277
4774
  const p = getPart(o, rh.part);
3278
4775
  const start = cur.length;
3279
- const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
4776
+ const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, extras);
3280
4777
  results.push({ object: o.id, part: p.spec.id, point: rh.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy, events: cur.slice(start) });
3281
4778
  ignore.add(`${o.id}\0${p.spec.id}`);
3282
4779
  if (!out.penetrated || !out.exitPoint) break;
@@ -3375,6 +4872,52 @@ var createDestruction = (options = {}) => {
3375
4872
  o.active = true;
3376
4873
  });
3377
4874
  },
4875
+ setWind(next) {
4876
+ windOpts = { ...windOpts, ...Object.fromEntries(Object.entries(next).filter(([, v]) => v !== void 0)) };
4877
+ windCfg = resolveWind(windOpts);
4878
+ for (const o of objects.values()) {
4879
+ if (!o.hasStructure || o.spec.wind === false) continue;
4880
+ o.wind ?? (o.wind = newWind(o));
4881
+ o.dirty = true;
4882
+ o.active = true;
4883
+ }
4884
+ },
4885
+ vent(objectId, partId, opening = {}) {
4886
+ if (impls.has(objectId)) return false;
4887
+ const o = getObj(objectId);
4888
+ const p = getPart(o, partId);
4889
+ const c = p.spec.contains;
4890
+ if (!c || isLost(p) || (p.st.liters ?? 0) <= 0) return false;
4891
+ if (p.st.holes.some((h) => h.vent)) return true;
4892
+ return run(() => {
4893
+ const wp = opening.point ?? partXf(o, p).c;
4894
+ const wn = vec.normalize(opening.normal ?? [0, 1, 0]);
4895
+ const hole = { point: objLocal(o, wp), normal: objLocalDir(o, wn), diameter: opening.diameter ?? VENT_DIAMETER, through: true, vent: true };
4896
+ p.st.holes.push(hole);
4897
+ if (p.st.holes.length > maxHoles) p.st.holes.shift();
4898
+ o.active = true;
4899
+ emit({
4900
+ ...base(o),
4901
+ type: "leak",
4902
+ part: partId,
4903
+ fluid: c.fluid,
4904
+ point: wp,
4905
+ direction: c.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, c.outflow))) : wn,
4906
+ rate: holeRate(hole, !!c.pressurized),
4907
+ pressurized: !!c.pressurized
4908
+ });
4909
+ return true;
4910
+ }).value;
4911
+ },
4912
+ seal(objectId, partId) {
4913
+ if (impls.has(objectId)) return 0;
4914
+ const o = getObj(objectId);
4915
+ const p = getPart(o, partId);
4916
+ const before = p.st.holes.length;
4917
+ p.st.holes = p.st.holes.filter((h) => !h.vent);
4918
+ o.active = true;
4919
+ return before - p.st.holes.length;
4920
+ },
3378
4921
  setLiters(objectId, partId, liters) {
3379
4922
  if (impls.has(objectId)) return;
3380
4923
  const p = getPart(getObj(objectId), partId);
@@ -3388,7 +4931,12 @@ var createDestruction = (options = {}) => {
3388
4931
  time += dt;
3389
4932
  const sources = [];
3390
4933
  for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
3391
- if (sources.length) spread2(sources, dt);
4934
+ for (const o of [...objects.values()]) if (o.soil && objects.get(o.id) === o) stepSoil(o, dt);
4935
+ for (const o of [...objects.values()]) if (o.wind && objects.get(o.id) === o) stepWind(o, dt);
4936
+ if (sources.length) {
4937
+ spread2(sources, dt);
4938
+ igniteLeaksNearFire(sources, dt);
4939
+ }
3392
4940
  detonatePrimed(time);
3393
4941
  releaseFront();
3394
4942
  for (const im of [...impls.values()]) {
@@ -3410,7 +4958,7 @@ var createDestruction = (options = {}) => {
3410
4958
  return;
3411
4959
  }
3412
4960
  const o = getObj(id);
3413
- objects.set(id, build(o.spec, id, o.st.position, o.st.rotation));
4961
+ objects.set(id, build(o.spec, id, o.base.pos, o.base.rot));
3414
4962
  },
3415
4963
  on(type, fn) {
3416
4964
  let set = listeners.get(type);
@@ -3427,7 +4975,7 @@ var createDestruction = (options = {}) => {
3427
4975
  rng: rng.state(),
3428
4976
  counter,
3429
4977
  objects: [...objects.values()].map((o) => {
3430
- const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
4978
+ const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed], ...o.soil?.sets.size ? { soil: Object.fromEntries(o.soil.sets) } : {}, ...o.wind ? { wind: [o.wind.dyn.x, o.wind.dyn.v, o.wind.dyn.y, o.wind.dyn.w, o.wind.dyn.peak] } : {} };
3431
4979
  for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
3432
4980
  for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
3433
4981
  return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
@@ -3448,9 +4996,17 @@ var createDestruction = (options = {}) => {
3448
4996
  state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
3449
4997
  ),
3450
4998
  joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
3451
- collapsed: e.runtime?.collapsed
4999
+ collapsed: e.runtime?.collapsed,
5000
+ ...e.runtime?.wind ? { wind: e.runtime.wind } : {}
3452
5001
  };
3453
- objects.set(e.id, build(clone(e.spec), e.id, state.position, state.rotation, carry));
5002
+ let pos = state.position, rot = state.rotation;
5003
+ if (state.soil) {
5004
+ carry.soil = state.soil;
5005
+ if (e.runtime?.soil) carry.soilSets = e.runtime.soil;
5006
+ rot = quat.normalize(quat.mul(state.rotation, quat.conjugate(state.soil.offset.rotation)));
5007
+ pos = vec.sub(state.position, rotateVec(rot, state.soil.offset.position));
5008
+ }
5009
+ objects.set(e.id, build(clone(e.spec), e.id, pos, rot, carry));
3454
5010
  }
3455
5011
  for (const e of snap.impls ?? []) {
3456
5012
  const im = impls.get(e.id);
@@ -3464,21 +5020,6 @@ var createDestruction = (options = {}) => {
3464
5020
  return world;
3465
5021
  };
3466
5022
 
3467
- // src/piercing.ts
3468
- var PIERCING_BY_KIND = {
3469
- fmj: 1,
3470
- hp: 0.6,
3471
- ap: 1.6,
3472
- buck: 0.5,
3473
- slug: 0.25,
3474
- birdshot: 0.3,
3475
- rimfire: 1,
3476
- arrow: 1,
3477
- bolt: 1
3478
- };
3479
- var PIERCING_BY_CALIBER = { "5_56x45": 0.5, "5_45x39": 0.5, "22lr": 0.5 };
3480
- var piercingOf = (caliber, kind) => PIERCING_BY_CALIBER[caliber] ?? PIERCING_BY_KIND[kind ?? "fmj"] ?? 1;
3481
-
3482
5023
  // src/data/assemblies.json
3483
5024
  var assemblies_default = [
3484
5025
  {
@@ -3591,21 +5132,35 @@ var buildFrame = (o = {}) => {
3591
5132
  const dw = o.doorWidth ?? 1, dh = o.doorHeight ?? 2.1;
3592
5133
  const mat = o.material ?? "concrete", wmat = o.wallMaterial ?? "brick";
3593
5134
  const roof = o.roof ?? true;
5135
+ const rebarOf = (kind) => o.rebar === false ? {} : { rebar: { ratio: o.rebar?.[kind] ?? (kind === "beam" ? 0.01 : 5e-3) } };
3594
5136
  const xs = Array.from({ length: nx }, (_, i) => -spanX / 2 + i * spanX / (nx - 1));
3595
5137
  const zs = Array.from({ length: nz }, (_, j) => -spanZ / 2 + j * spanZ / (nz - 1));
3596
5138
  const parts = [];
3597
5139
  const joints = [];
3598
5140
  const on = (a, b, kind) => joints.push({ id: `${a}~${b}`, a, b, kind, bears: "a_on_b" });
3599
- for (let i = 0; i < nx; i++) {
3600
- for (let j = 0; j < nz; j++) {
3601
- parts.push({
3602
- id: `foundation_${i}_${j}`,
3603
- material: mat,
3604
- shape: { kind: "box", size: [cs * 2.2, 0.3, cs * 2.2] },
3605
- position: [xs[i], -0.15, zs[j]],
3606
- structure: { role: "foundation" },
3607
- anchored: true
3608
- });
5141
+ const pad = o.footing ?? cs * 2.2;
5142
+ const pile = o.piles ? { pile: { ...o.piles } } : {};
5143
+ if (o.raft) {
5144
+ parts.push({
5145
+ id: "raft",
5146
+ material: mat,
5147
+ shape: { kind: "box", size: [spanX + pad, 0.4, spanZ + pad] },
5148
+ position: [0, -0.2, 0],
5149
+ structure: { role: "foundation", ...pile },
5150
+ anchored: true
5151
+ });
5152
+ } else {
5153
+ for (let i = 0; i < nx; i++) {
5154
+ for (let j = 0; j < nz; j++) {
5155
+ parts.push({
5156
+ id: `foundation_${i}_${j}`,
5157
+ material: mat,
5158
+ shape: { kind: "box", size: [pad, 0.3, pad] },
5159
+ position: [xs[i], -0.15, zs[j]],
5160
+ structure: { role: "foundation", ...pile },
5161
+ anchored: true
5162
+ });
5163
+ }
3609
5164
  }
3610
5165
  }
3611
5166
  for (let f = 0; f < floors; f++) {
@@ -3621,14 +5176,15 @@ var buildFrame = (o = {}) => {
3621
5176
  material: mat,
3622
5177
  shape: { kind: "box", size: [cs, H, cs] },
3623
5178
  position: [xs[i], yb + H / 2, zs[j]],
3624
- structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity }
5179
+ structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity },
5180
+ ...o.columnCage ? { reinforcement: { ...o.columnCage } } : {}
3625
5181
  });
3626
- on(id, f === 0 ? `foundation_${i}_${j}` : `slab_${f - 1}`, "rebar");
5182
+ on(id, f === 0 ? o.raft ? "raft" : `foundation_${i}_${j}` : o.continuousColumns ? `column_${f - 1}_${i}_${j}` : `slab_${f - 1}`, "rebar");
3627
5183
  }
3628
5184
  }
3629
5185
  const beams = [];
3630
5186
  const beam = (id, size, pos, c1, c2) => {
3631
- parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam" } });
5187
+ parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam", ...rebarOf("beam") } });
3632
5188
  on(id, c1, "rebar");
3633
5189
  on(id, c2, "rebar");
3634
5190
  beams.push(id);
@@ -3643,13 +5199,13 @@ var buildFrame = (o = {}) => {
3643
5199
  beam(`beam_z_${f}_${i}_${j}`, [bw, bh, zs[j + 1] - zs[j] - cs], [xs[i], top - bh / 2, (zs[j] + zs[j + 1]) / 2], `column_${f}_${i}_${j}`, `column_${f}_${i}_${j + 1}`);
3644
5200
  }
3645
5201
  }
3646
- const thick = last && roof ? T * 0.75 : T;
5202
+ const thick = last && roof ? o.roofThickness ?? T * 0.75 : T;
3647
5203
  parts.push({
3648
5204
  id: slabId,
3649
5205
  material: mat,
3650
5206
  shape: { kind: "box", size: [spanX + cs + 0.2, thick, spanZ + cs + 0.2] },
3651
5207
  position: [0, top + thick / 2, 0],
3652
- structure: { role: last && roof ? "roof" : "slab" }
5208
+ structure: { role: last && roof ? "roof" : "slab", ...rebarOf("slab") }
3653
5209
  });
3654
5210
  for (const b of beams) on(slabId, b, last && roof ? "resting" : "rebar");
3655
5211
  if (o.walls) {
@@ -3682,28 +5238,91 @@ var buildFrame = (o = {}) => {
3682
5238
  }
3683
5239
  }
3684
5240
  }
3685
- return { name: "frame", parts, joints, tags: ["structure"] };
5241
+ return { name: "frame", parts, joints, tags: ["structure"], ...o.soil !== void 0 ? { soil: o.soil } : {} };
3686
5242
  };
3687
5243
 
3688
5244
  // src/size.ts
5245
+ var MAX_REBAR_RATIO = 0.04;
5246
+ var onRock = (spec) => {
5247
+ if (spec.soil === void 0) return spec;
5248
+ const { soil: _soil, ...rest } = spec;
5249
+ return rest;
5250
+ };
5251
+ var without = (spec, id) => {
5252
+ const reduced = { ...spec, parts: spec.parts.filter((p) => p.id !== id), joints: (spec.joints ?? []).filter((j) => j.a !== id && j.b !== id) };
5253
+ if (!spec.joints) delete reduced.joints;
5254
+ return reduced;
5255
+ };
5256
+ var sizeRebar = (spec, options = {}) => {
5257
+ const flexure = resolveFlexure(options.flexure);
5258
+ const materials = new Map(MATERIALS.map((m) => [m.id, m]));
5259
+ for (const m of options.materials ?? []) materials.set(m.id, m);
5260
+ const out = structuredClone(spec);
5261
+ const targets = out.parts.filter((p) => {
5262
+ const m = materials.get(p.material);
5263
+ return p.shape.kind === "box" && !!m && isReinforced(p) && isFlexural(p, m, flexure);
5264
+ });
5265
+ if (targets.length === 0) return out;
5266
+ const safety = options.safety ?? 0.65;
5267
+ const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
5268
+ const bending = (s) => {
5269
+ const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, structure: { flexure: options.flexure } });
5270
+ const id = w.add(onRock(s));
5271
+ return new Map(w.state(id).parts.map((p) => [p.id, p.flexure ?? 0]));
5272
+ };
5273
+ const base = bending(out);
5274
+ const factor = new Map(targets.map((p) => [p.id, (base.get(p.id) ?? 0) / safetyOf(p.structure.role)]));
5275
+ if (options.redundancy !== void 0) {
5276
+ const roles = options.redundancyRoles ?? ["column"];
5277
+ for (const lost of out.parts) {
5278
+ if (!lost.structure || !roles.includes(lost.structure.role)) continue;
5279
+ const l = bending(without(out, lost.id));
5280
+ for (const p of targets) if (p.id !== lost.id) factor.set(p.id, Math.max(factor.get(p.id), (l.get(p.id) ?? 0) / options.redundancy));
5281
+ }
5282
+ }
5283
+ for (const p of targets) {
5284
+ const f = factor.get(p.id);
5285
+ const g = bendGeometry(p);
5286
+ if (f <= 1 || g.gravity < 0) continue;
5287
+ const section = { b: sectionWidth(g, g.gravity), h: g.dims[g.gravity] };
5288
+ const steel = tensionSteel(p, g.dims, section, flexure);
5289
+ const m = materials.get(p.material);
5290
+ const need = f * sectionMoments(m, section, steel, flexure).ultimate;
5291
+ const momentOf = (area) => steelMoment(area, steel.yieldMPa, steel.d, section.b, m.strength.compressiveMPa, flexure);
5292
+ const cap = MAX_REBAR_RATIO * section.b * steel.d;
5293
+ if (steel.area >= cap) continue;
5294
+ let lo = steel.area, hi = cap;
5295
+ if (momentOf(hi) > need) {
5296
+ for (let k = 0; k < 60; k++) {
5297
+ const mid = (lo + hi) / 2;
5298
+ if (momentOf(mid) < need) lo = mid;
5299
+ else hi = mid;
5300
+ }
5301
+ }
5302
+ const ratio = hi / (section.b * steel.d);
5303
+ p.structure = { ...p.structure, rebar: { ...p.structure.rebar, ratio } };
5304
+ }
5305
+ return out;
5306
+ };
3689
5307
  var sizeStructure = (spec, options = {}) => {
3690
5308
  const safety = options.safety ?? 0.65;
3691
5309
  const redundancy = options.redundancy;
3692
5310
  const redundancyRoles = options.redundancyRoles ?? ["column"];
3693
5311
  const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
3694
- const loads = (s) => {
3695
- const w = createDestruction(options.materials ? { materials: options.materials } : {});
3696
- const id = w.add(s);
3697
- return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0 }]));
5312
+ const loads = (s, wind) => {
5313
+ const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, ...wind ? { wind } : {} });
5314
+ const id = w.add(onRock(s));
5315
+ return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0, wind: p.wind ?? 0 }]));
3698
5316
  };
3699
5317
  const out = structuredClone(spec);
3700
- const base = loads(out);
5318
+ const base = loads(out, options.wind);
5319
+ const gamma = options.wind ? resolveWind(options.wind).loadFactor : 0;
3701
5320
  const need = /* @__PURE__ */ new Map();
3702
5321
  for (const p of out.parts) {
3703
5322
  if (!p.structure || p.structure.role === "none") continue;
3704
5323
  const b = base.get(p.id);
3705
5324
  if (!b || b.stress <= 0) continue;
3706
- need.set(p.id, b.load / safetyOf(p.structure.role));
5325
+ need.set(p.id, (b.load + (b.wind > 0 ? gamma * b.wind / GRAVITY : 0)) / safetyOf(p.structure.role));
3707
5326
  }
3708
5327
  if (redundancy !== void 0) {
3709
5328
  for (const col of out.parts) {
@@ -3725,7 +5344,20 @@ var sizeStructure = (spec, options = {}) => {
3725
5344
  const n = need.get(p.id);
3726
5345
  if (n !== void 0) p.structure = { ...p.structure, capacity: n };
3727
5346
  }
3728
- return out;
5347
+ if (options.wind) {
5348
+ const yieldMPa = resolveFlexure(options.flexure).rebarYieldMPa;
5349
+ for (const p of out.parts) {
5350
+ const b = base.get(p.id);
5351
+ if (!b || b.wind >= 0 || !p.reinforcement || p.shape.kind !== "box") continue;
5352
+ const pull = -(b.load + gamma * b.wind / GRAVITY) * GRAVITY;
5353
+ if (pull <= 0) continue;
5354
+ const dims = shapeDims(p.shape);
5355
+ const have = tensionCapacity(dims, { reinforcement: p.reinforcement, yieldMPa });
5356
+ const want = pull / safetyOf(p.structure?.role ?? "wall");
5357
+ if (have > 0 && want > have) p.reinforcement = { ...p.reinforcement, diameter: Math.min(0.04, p.reinforcement.diameter * Math.sqrt(want / have)) };
5358
+ }
5359
+ }
5360
+ return sizeRebar(out, options);
3729
5361
  };
3730
5362
 
3731
5363
  // src/locale.ts
@@ -3807,6 +5439,8 @@ var locales = {
3807
5439
  primed: "about to explode",
3808
5440
  stress: "overload",
3809
5441
  collapse: "collapsed",
5442
+ settle: "ground gave way",
5443
+ sway: "sway",
3810
5444
  spall: "plaster came off",
3811
5445
  stage: "damage stage",
3812
5446
  expose: "rebar bare",
@@ -3895,6 +5529,8 @@ var locales = {
3895
5529
  primed: "prestes a explodir",
3896
5530
  stress: "sobrecarga",
3897
5531
  collapse: "desabou",
5532
+ settle: "recalque do solo",
5533
+ sway: "balan\xE7o",
3898
5534
  spall: "reboco soltou",
3899
5535
  stage: "est\xE1gio de dano",
3900
5536
  expose: "vergalh\xE3o \xE0 mostra",
@@ -3920,39 +5556,87 @@ export {
3920
5556
  ASSEMBLIES,
3921
5557
  CONFINEMENT_DEFAULTS,
3922
5558
  FIRE_PRIME_C,
5559
+ FLEXURE_DEFAULTS,
3923
5560
  FRAGMENT_HIT_DEFAULTS,
5561
+ GRAVITY,
3924
5562
  IMPACT_CONTRACT,
3925
5563
  JOINT_BY_MATERIALS,
3926
5564
  JOINT_DEFAULTS,
5565
+ MASONRY_DEFAULTS,
3927
5566
  MATERIALS,
3928
- PIERCING_BY_CALIBER,
3929
- PIERCING_BY_KIND,
3930
5567
  SHELTER_DEFAULTS,
5568
+ SOILS,
5569
+ SOIL_DEFAULTS,
5570
+ WIND_DEFAULTS,
5571
+ WIND_TERRAIN,
5572
+ analyzeWind,
3931
5573
  assemblyDepth,
3932
5574
  assemblyPart,
5575
+ bearingWallThickness,
5576
+ bendGeometry,
5577
+ blastImpulse,
3933
5578
  buildFrame,
3934
5579
  checkDestructible,
3935
5580
  checkHit,
5581
+ crackingMoment,
3936
5582
  createDestruction,
5583
+ damperAmplification,
5584
+ damperTuning,
5585
+ elasticEnergy,
3937
5586
  explosivePart,
5587
+ exposureOf,
5588
+ footingArea,
5589
+ footingSoil,
3938
5590
  fragility,
3939
5591
  getAssembly,
3940
5592
  guessMaterial,
5593
+ gustAt,
5594
+ heightFactor,
5595
+ impulseEnergy,
3941
5596
  inferJoints,
5597
+ layersBetween,
3942
5598
  locales,
5599
+ makeGust,
5600
+ meanHeightFactor,
5601
+ momentNodes,
5602
+ momentOfEnergy,
3943
5603
  partCapacity,
3944
5604
  partMass,
3945
5605
  partThickness,
3946
5606
  partVolume,
3947
- piercingOf,
5607
+ pileCapacity,
5608
+ pileLength,
5609
+ pileStiffness,
5610
+ profileOf,
3948
5611
  quat,
3949
5612
  quatFromEuler,
3950
5613
  registerAssembly,
3951
5614
  resolveStrings,
5615
+ resolveWind,
3952
5616
  rotateVec,
5617
+ sectionMoments,
5618
+ shapeAt,
5619
+ shearNodes,
5620
+ sizeRebar,
3953
5621
  sizeStructure,
5622
+ slendernessFactor,
5623
+ soilAt,
5624
+ solveFoundation,
5625
+ solveWallFrame,
5626
+ spanMoment,
5627
+ spanOf,
5628
+ steelMoment,
5629
+ stepSway,
5630
+ stiffnessCentre,
5631
+ subgradeModulus,
5632
+ tBeamInertia,
5633
+ tensionCapacity,
5634
+ tensionSteel,
3954
5635
  toLocal,
3955
5636
  toWorld,
3956
- vec
5637
+ turbulenceIntensity,
5638
+ vec,
5639
+ windClass,
5640
+ windPressure
3957
5641
  };
3958
5642
  //# sourceMappingURL=index.js.map