@adatechnology/game-destruction 0.7.2 → 0.8.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;
@@ -2432,22 +3897,25 @@ var createDestruction = (options = {}) => {
2432
3897
  const crackLen = clamp2(0.03 + ratio * 0.2, 0.03, 0.8);
2433
3898
  let exitPoint;
2434
3899
  if (penetrated) {
2435
- exitPoint = vec.madd(entry, d, Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant));
3900
+ const exitDist = p.spec.contains || p.spec.fill !== void 0 && p.spec.fill < 0.5 || p.thickness < pathLen * 0.5 ? pathLen : Math.min(pathLen, effM + layersThickness(p.layers, face) / slant + layersThickness(p.layers, -face) / slant);
3901
+ exitPoint = vec.madd(entry, d, exitDist);
2436
3902
  const hole = addHole(o, p, entry, n, caliber / 1e3 * (mat.ductility >= 0.3 ? 1 : 1.5), true, exitPoint, src);
2437
3903
  if (p.layers.length > 0) spallOuter(o, p, exitPoint, -face, vec.scale(n, -1), Math.max(0.03, caliber / 1e3 * 4), src);
2438
3904
  addBreach(o, p, entry, Math.PI * (hole.diameter / 2) * (hole.diameter / 2), E, d, src);
3905
+ 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
3906
  if (brittle && mat.fracture === "shatter") addCrack(o, p, entry, n, crackLen, src);
2440
- if (p.spec.contains && (p.st.liters ?? 0) > 0) {
3907
+ if (p.spec.contains && (p.st.liters ?? 0) > 1e-4) {
2441
3908
  emit({
2442
3909
  ...base(o, src),
2443
3910
  type: "leak",
2444
3911
  part: id,
2445
3912
  fluid: p.spec.contains.fluid,
2446
3913
  point: entry,
2447
- direction: n,
3914
+ direction: p.spec.contains.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, p.spec.contains.outflow))) : n,
2448
3915
  rate: holeRate(hole, !!p.spec.contains.pressurized),
2449
3916
  pressurized: !!p.spec.contains.pressurized
2450
3917
  });
3918
+ rollIgnition(o, p, round);
2451
3919
  }
2452
3920
  } else if (mat.ductility >= 0.5) {
2453
3921
  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 +3926,7 @@ var createDestruction = (options = {}) => {
2458
3926
  debris(o, p, mat.hitDebris, entry, n, clamp2(Math.round(E / 60) + 2, 2, 14), clamp2(Math.sqrt(E) / 6, 1, 8), src);
2459
3927
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
2460
3928
  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 };
3929
+ return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
2462
3930
  };
2463
3931
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
2464
3932
  const mat = p.mat;
@@ -2478,8 +3946,9 @@ var createDestruction = (options = {}) => {
2478
3946
  }
2479
3947
  debris(o, p, mat.hitDebris, point, n, clamp2(Math.round(E / 25) + 1, 1, 10), clamp2(Math.sqrt(E) / 8, 1, 6), src);
2480
3948
  }
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);
3949
+ if (kind === "blunt") {
3950
+ if (!(p.anchored && p.spec.tags?.includes("terrain"))) wearJoints(o, p, E * 0.3, void 0, 0, src);
3951
+ } else wearJoints(o, p, E * 0.3, point, 0.06, src);
2483
3952
  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
3953
  const scale = mat.damageScale?.[kind] ?? 1;
2485
3954
  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 +3995,8 @@ var createDestruction = (options = {}) => {
2526
3995
  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
3996
  const dealt = applyDamage(o, p, amount, "explosive", point, null, Ein * 0.3, 40, src);
2528
3997
  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);
3998
+ if (p.bend && flexure.enabled) bendByBlast(o, p, point, energy, radius, reach, src);
3999
+ if (dist <= BREACH_BLAST_REACH * Math.cbrt(Math.max(0, energy) / 1e3)) addBreach(o, p, wp, breachAreaOf(p, dealt, true), Ein, null, src);
2530
4000
  return reach;
2531
4001
  };
2532
4002
  const blastObject = (o, point, energy, radius, src) => {
@@ -2589,7 +4059,7 @@ var createDestruction = (options = {}) => {
2589
4059
  };
2590
4060
  const FLAG_LOST = 1;
2591
4061
  const FLAG_OPEN = 2;
2592
- const blastPlain = (point, energy, radius, src) => {
4062
+ const blastPlain = (point, energy, radius, src, lethal) => {
2593
4063
  const out = [];
2594
4064
  for (const o of [...objects.values()]) {
2595
4065
  if (vec.distance(toWorld(o.st.position, o.st.rotation, o.bc), point) - o.br >= radius) continue;
@@ -2597,7 +4067,7 @@ var createDestruction = (options = {}) => {
2597
4067
  if (r.part) out.push(r);
2598
4068
  }
2599
4069
  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 } : {} }));
4070
+ 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
4071
  }
2602
4072
  return out;
2603
4073
  };
@@ -2606,7 +4076,7 @@ var createDestruction = (options = {}) => {
2606
4076
  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
4077
  });
2608
4078
  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) => {
4079
+ const blastScene = (point, energy, radius, src, cfg, only, lethal) => {
2610
4080
  const mult = confinementOf(cfg.conf, point, radius);
2611
4081
  const E = energy * mult;
2612
4082
  const shelter = cfg.shelter;
@@ -2713,7 +4183,7 @@ var createDestruction = (options = {}) => {
2713
4183
  den += f;
2714
4184
  }
2715
4185
  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 } : {} });
4186
+ 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
4187
  results.set(im, res);
2718
4188
  report(im).shelter = k;
2719
4189
  }
@@ -2811,10 +4281,11 @@ var createDestruction = (options = {}) => {
2811
4281
  if (!rep) reports.set(key, rep = { shelter: 1, confinement: mult, fragmentHits: 0 });
2812
4282
  rep.fragmentHits++;
2813
4283
  };
2814
- const blastWorld = (point, energy, radius, src, call, only) => {
4284
+ const blastWorld = (point, energy, radius, src, call, only, lethal) => {
2815
4285
  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);
4286
+ const res = !cfg.shelter && !cfg.frag && cfg.conf === null ? blastPlain(point, energy, radius, src, lethal) : blastScene(point, energy, radius, src, cfg, only, lethal);
4287
+ lightLeaksNear(point, radius);
4288
+ return res;
2818
4289
  };
2819
4290
  const heatObject = (o, point, T, radius, duration, src) => {
2820
4291
  const start = cur.length;
@@ -2833,6 +4304,7 @@ var createDestruction = (options = {}) => {
2833
4304
  p.st.temperature += (T - p.st.temperature) * (1 - Math.exp(-k * duration));
2834
4305
  }
2835
4306
  o.active = true;
4307
+ if (T >= FLAME_LIGHTS_LEAK_C && !p.rt.jet && !p.rt.lit && leaksFlammable(p)) p.rt.lit = true;
2836
4308
  checkIgnite(o, p, src);
2837
4309
  }
2838
4310
  res.events = cur.slice(start);
@@ -2852,7 +4324,7 @@ var createDestruction = (options = {}) => {
2852
4324
  if (hit.type === "ballistic") {
2853
4325
  const loc = locate(o, point, dir, 0.05, only);
2854
4326
  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);
4327
+ const out = ballistic(o, loc.p, loc.point, loc.normal, loc.dir, E, hit.caliber ?? 9, loc.pathLen, src, hit);
2856
4328
  res = { ...res, part: loc.p.spec.id, point: loc.point, damage: out.damage, penetrated: out.penetrated, exitEnergy: out.exitEnergy };
2857
4329
  }
2858
4330
  } else if (hit.type === "blunt" || hit.type === "slash") {
@@ -3006,7 +4478,8 @@ var createDestruction = (options = {}) => {
3006
4478
  if (rate > 0) {
3007
4479
  s.liters = Math.max(0, (s.liters ?? 0) - rate * dt);
3008
4480
  hot = true;
3009
- if (FLAMMABLE_FLUIDS.includes(cont.fluid) && s.temperature >= 350 && !p.rt.jet) {
4481
+ if (FLAMMABLE_FLUIDS.includes(cont.fluid) && (s.temperature >= 350 || p.rt.lit) && !p.rt.jet) {
4482
+ p.rt.lit = false;
3010
4483
  if (ex && chainDelayOf(p.spec, "fire") !== void 0) {
3011
4484
  prime(o, p, "fire");
3012
4485
  continue;
@@ -3021,6 +4494,7 @@ var createDestruction = (options = {}) => {
3021
4494
  }
3022
4495
  }
3023
4496
  }
4497
+ if ((s.liters ?? 0) <= 0) p.rt.lit = false;
3024
4498
  if (p.rt.jet && (s.liters ?? 0) <= 0) {
3025
4499
  p.rt.jet = false;
3026
4500
  s.burning = false;
@@ -3070,6 +4544,25 @@ var createDestruction = (options = {}) => {
3070
4544
  }
3071
4545
  o.active = hot;
3072
4546
  };
4547
+ const igniteLeaksNearFire = (sources, dt) => {
4548
+ const { radius, chancePerSecond } = ignitionRules.fireNear;
4549
+ if (chancePerSecond <= 0) return;
4550
+ const chance = 1 - Math.exp(-chancePerSecond * dt);
4551
+ for (const src of sources) {
4552
+ if (!src.p.st.burning && !src.p.rt.jet) continue;
4553
+ const sc = partXf(src.o, src.p).c;
4554
+ for (const o of objects.values()) {
4555
+ for (const q of o.parts) {
4556
+ if (q === src.p || isLost(q) || q.rt.lit || q.rt.jet || !leaksFlammable(q)) continue;
4557
+ if (vec.distance(partXf(o, q).c, sc) - q.bound - src.p.bound > radius) continue;
4558
+ if (rnd() < chance) {
4559
+ q.rt.lit = true;
4560
+ o.active = true;
4561
+ }
4562
+ }
4563
+ }
4564
+ }
4565
+ };
3073
4566
  const spread2 = (sources, dt) => {
3074
4567
  for (const src of sources) {
3075
4568
  if (!src.p.st.burning && !src.p.rt.jet) continue;
@@ -3195,8 +4688,8 @@ var createDestruction = (options = {}) => {
3195
4688
  return;
3196
4689
  }
3197
4690
  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]];
4691
+ o.base = { pos: [position[0], position[1], position[2]], rot: [rotation[0], rotation[1], rotation[2], rotation[3]] };
4692
+ applySoilPose(o);
3200
4693
  },
3201
4694
  splitGroup(id, group, newId) {
3202
4695
  if (impls.has(id)) throw new Error(`object "${id}" is a registered DestructibleImpl and cannot be split; its module owns its pieces`);
@@ -3219,13 +4712,17 @@ var createDestruction = (options = {}) => {
3219
4712
  const s = { parts: o.spec.parts.filter((p) => keep(p.id)), joints: (o.spec.joints ?? []).filter((j) => keep(j.a) && keep(j.b)) };
3220
4713
  if (o.spec.name !== void 0) s.name = o.spec.name;
3221
4714
  if (o.spec.tags) s.tags = [...o.spec.tags];
4715
+ if (o.spec.soil !== void 0) s.soil = o.spec.soil;
3222
4716
  return clone(s);
3223
4717
  };
3224
4718
  const inNew = (pid) => moved.has(pid);
3225
4719
  const inOld = (pid) => !moved.has(pid);
3226
4720
  const pos = o.st.position, rot = o.st.rotation;
3227
4721
  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))));
4722
+ const restCarry = carryFor(inOld, (a, b) => !(inNew(a) && inNew(b)));
4723
+ if (o.st.soil) restCarry.soil = o.st.soil;
4724
+ if (o.soil?.sets.size) restCarry.soilSets = Object.fromEntries(o.soil.sets);
4725
+ const rest = build(sub(inOld), id, o.soil ? o.base.pos : pos, o.soil ? o.base.rot : rot, restCarry);
3229
4726
  objects.set(id, rest);
3230
4727
  objects.set(nid, fresh);
3231
4728
  return nid;
@@ -3250,7 +4747,8 @@ var createDestruction = (options = {}) => {
3250
4747
  const r = raycastInternal(origin, direction, maxDistance);
3251
4748
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
3252
4749
  },
3253
- shoot(origin, direction, energy, caliber, source, piercing) {
4750
+ shoot(origin, direction, energy, caliber, source, round) {
4751
+ const extras = typeof round === "number" ? { piercing: round } : round ?? {};
3254
4752
  return withCollapses(run(() => {
3255
4753
  const results = [];
3256
4754
  const d = vec.normalize(direction);
@@ -3262,7 +4760,7 @@ var createDestruction = (options = {}) => {
3262
4760
  if (!rh) break;
3263
4761
  const im = impls.get(rh.object);
3264
4762
  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 } : {} });
4763
+ const r = implHit(im, { type: "ballistic", point: rh.point, direction: d, energy: E, caliber, ...extras, ...source !== void 0 ? { source } : {} });
3266
4764
  r.part = rh.part;
3267
4765
  r.point = rh.point;
3268
4766
  r.exitEnergy = r.penetrated ? E * 0.5 : 0;
@@ -3276,7 +4774,7 @@ var createDestruction = (options = {}) => {
3276
4774
  const o = getObj(rh.object);
3277
4775
  const p = getPart(o, rh.part);
3278
4776
  const start = cur.length;
3279
- const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, piercing ?? 1);
4777
+ const out = ballistic(o, p, rh.point, rh.normal, d, E, caliber, rh.pathLen, source, extras);
3280
4778
  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
4779
  ignore.add(`${o.id}\0${p.spec.id}`);
3282
4780
  if (!out.penetrated || !out.exitPoint) break;
@@ -3375,6 +4873,52 @@ var createDestruction = (options = {}) => {
3375
4873
  o.active = true;
3376
4874
  });
3377
4875
  },
4876
+ setWind(next) {
4877
+ windOpts = { ...windOpts, ...Object.fromEntries(Object.entries(next).filter(([, v]) => v !== void 0)) };
4878
+ windCfg = resolveWind(windOpts);
4879
+ for (const o of objects.values()) {
4880
+ if (!o.hasStructure || o.spec.wind === false) continue;
4881
+ o.wind ?? (o.wind = newWind(o));
4882
+ o.dirty = true;
4883
+ o.active = true;
4884
+ }
4885
+ },
4886
+ vent(objectId, partId, opening = {}) {
4887
+ if (impls.has(objectId)) return false;
4888
+ const o = getObj(objectId);
4889
+ const p = getPart(o, partId);
4890
+ const c = p.spec.contains;
4891
+ if (!c || isLost(p) || (p.st.liters ?? 0) <= 0) return false;
4892
+ if (p.st.holes.some((h) => h.vent)) return true;
4893
+ return run(() => {
4894
+ const wp = opening.point ?? partXf(o, p).c;
4895
+ const wn = vec.normalize(opening.normal ?? [0, 1, 0]);
4896
+ const hole = { point: objLocal(o, wp), normal: objLocalDir(o, wn), diameter: opening.diameter ?? VENT_DIAMETER, through: true, vent: true };
4897
+ p.st.holes.push(hole);
4898
+ if (p.st.holes.length > maxHoles) p.st.holes.shift();
4899
+ o.active = true;
4900
+ emit({
4901
+ ...base(o),
4902
+ type: "leak",
4903
+ part: partId,
4904
+ fluid: c.fluid,
4905
+ point: wp,
4906
+ direction: c.outflow ? vec.normalize(rotateVec(o.st.rotation, rotateVec(p.q, c.outflow))) : wn,
4907
+ rate: holeRate(hole, !!c.pressurized),
4908
+ pressurized: !!c.pressurized
4909
+ });
4910
+ return true;
4911
+ }).value;
4912
+ },
4913
+ seal(objectId, partId) {
4914
+ if (impls.has(objectId)) return 0;
4915
+ const o = getObj(objectId);
4916
+ const p = getPart(o, partId);
4917
+ const before = p.st.holes.length;
4918
+ p.st.holes = p.st.holes.filter((h) => !h.vent);
4919
+ o.active = true;
4920
+ return before - p.st.holes.length;
4921
+ },
3378
4922
  setLiters(objectId, partId, liters) {
3379
4923
  if (impls.has(objectId)) return;
3380
4924
  const p = getPart(getObj(objectId), partId);
@@ -3388,7 +4932,12 @@ var createDestruction = (options = {}) => {
3388
4932
  time += dt;
3389
4933
  const sources = [];
3390
4934
  for (const o of [...objects.values()]) if (o.active) stepObject(o, dt, sources);
3391
- if (sources.length) spread2(sources, dt);
4935
+ for (const o of [...objects.values()]) if (o.soil && objects.get(o.id) === o) stepSoil(o, dt);
4936
+ for (const o of [...objects.values()]) if (o.wind && objects.get(o.id) === o) stepWind(o, dt);
4937
+ if (sources.length) {
4938
+ spread2(sources, dt);
4939
+ igniteLeaksNearFire(sources, dt);
4940
+ }
3392
4941
  detonatePrimed(time);
3393
4942
  releaseFront();
3394
4943
  for (const im of [...impls.values()]) {
@@ -3410,7 +4959,7 @@ var createDestruction = (options = {}) => {
3410
4959
  return;
3411
4960
  }
3412
4961
  const o = getObj(id);
3413
- objects.set(id, build(o.spec, id, o.st.position, o.st.rotation));
4962
+ objects.set(id, build(o.spec, id, o.base.pos, o.base.rot));
3414
4963
  },
3415
4964
  on(type, fn) {
3416
4965
  let set = listeners.get(type);
@@ -3427,7 +4976,7 @@ var createDestruction = (options = {}) => {
3427
4976
  rng: rng.state(),
3428
4977
  counter,
3429
4978
  objects: [...objects.values()].map((o) => {
3430
- const runtime = { parts: {}, joints: {}, collapsed: [...o.collapsed] };
4979
+ 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
4980
  for (const p of o.parts) runtime.parts[p.spec.id] = clone(p.rt);
3432
4981
  for (const j of o.joints) runtime.joints[j.spec.id] = j.step;
3433
4982
  return { id: o.id, spec: clone(o.spec), state: clone(o.st), runtime };
@@ -3448,9 +4997,17 @@ var createDestruction = (options = {}) => {
3448
4997
  state.parts.map((st) => [st.id, { st, rt: e.runtime?.parts[st.id] ? clone(e.runtime.parts[st.id]) : { ...freshRuntime(), prevTemp: st.temperature } }])
3449
4998
  ),
3450
4999
  joints: new Map(state.joints.map((st) => [st.id, { st, step: e.runtime?.joints[st.id] ?? 0 }])),
3451
- collapsed: e.runtime?.collapsed
5000
+ collapsed: e.runtime?.collapsed,
5001
+ ...e.runtime?.wind ? { wind: e.runtime.wind } : {}
3452
5002
  };
3453
- objects.set(e.id, build(clone(e.spec), e.id, state.position, state.rotation, carry));
5003
+ let pos = state.position, rot = state.rotation;
5004
+ if (state.soil) {
5005
+ carry.soil = state.soil;
5006
+ if (e.runtime?.soil) carry.soilSets = e.runtime.soil;
5007
+ rot = quat.normalize(quat.mul(state.rotation, quat.conjugate(state.soil.offset.rotation)));
5008
+ pos = vec.sub(state.position, rotateVec(rot, state.soil.offset.position));
5009
+ }
5010
+ objects.set(e.id, build(clone(e.spec), e.id, pos, rot, carry));
3454
5011
  }
3455
5012
  for (const e of snap.impls ?? []) {
3456
5013
  const im = impls.get(e.id);
@@ -3464,21 +5021,6 @@ var createDestruction = (options = {}) => {
3464
5021
  return world;
3465
5022
  };
3466
5023
 
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
5024
  // src/data/assemblies.json
3483
5025
  var assemblies_default = [
3484
5026
  {
@@ -3591,21 +5133,35 @@ var buildFrame = (o = {}) => {
3591
5133
  const dw = o.doorWidth ?? 1, dh = o.doorHeight ?? 2.1;
3592
5134
  const mat = o.material ?? "concrete", wmat = o.wallMaterial ?? "brick";
3593
5135
  const roof = o.roof ?? true;
5136
+ const rebarOf = (kind) => o.rebar === false ? {} : { rebar: { ratio: o.rebar?.[kind] ?? (kind === "beam" ? 0.01 : 5e-3) } };
3594
5137
  const xs = Array.from({ length: nx }, (_, i) => -spanX / 2 + i * spanX / (nx - 1));
3595
5138
  const zs = Array.from({ length: nz }, (_, j) => -spanZ / 2 + j * spanZ / (nz - 1));
3596
5139
  const parts = [];
3597
5140
  const joints = [];
3598
5141
  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
- });
5142
+ const pad = o.footing ?? cs * 2.2;
5143
+ const pile = o.piles ? { pile: { ...o.piles } } : {};
5144
+ if (o.raft) {
5145
+ parts.push({
5146
+ id: "raft",
5147
+ material: mat,
5148
+ shape: { kind: "box", size: [spanX + pad, 0.4, spanZ + pad] },
5149
+ position: [0, -0.2, 0],
5150
+ structure: { role: "foundation", ...pile },
5151
+ anchored: true
5152
+ });
5153
+ } else {
5154
+ for (let i = 0; i < nx; i++) {
5155
+ for (let j = 0; j < nz; j++) {
5156
+ parts.push({
5157
+ id: `foundation_${i}_${j}`,
5158
+ material: mat,
5159
+ shape: { kind: "box", size: [pad, 0.3, pad] },
5160
+ position: [xs[i], -0.15, zs[j]],
5161
+ structure: { role: "foundation", ...pile },
5162
+ anchored: true
5163
+ });
5164
+ }
3609
5165
  }
3610
5166
  }
3611
5167
  for (let f = 0; f < floors; f++) {
@@ -3621,14 +5177,15 @@ var buildFrame = (o = {}) => {
3621
5177
  material: mat,
3622
5178
  shape: { kind: "box", size: [cs, H, cs] },
3623
5179
  position: [xs[i], yb + H / 2, zs[j]],
3624
- structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity }
5180
+ structure: o.columnCapacity === void 0 ? { role: "column" } : { role: "column", capacity: o.columnCapacity },
5181
+ ...o.columnCage ? { reinforcement: { ...o.columnCage } } : {}
3625
5182
  });
3626
- on(id, f === 0 ? `foundation_${i}_${j}` : `slab_${f - 1}`, "rebar");
5183
+ on(id, f === 0 ? o.raft ? "raft" : `foundation_${i}_${j}` : o.continuousColumns ? `column_${f - 1}_${i}_${j}` : `slab_${f - 1}`, "rebar");
3627
5184
  }
3628
5185
  }
3629
5186
  const beams = [];
3630
5187
  const beam = (id, size, pos, c1, c2) => {
3631
- parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam" } });
5188
+ parts.push({ id, material: mat, shape: { kind: "box", size }, position: pos, structure: { role: "beam", ...rebarOf("beam") } });
3632
5189
  on(id, c1, "rebar");
3633
5190
  on(id, c2, "rebar");
3634
5191
  beams.push(id);
@@ -3643,13 +5200,13 @@ var buildFrame = (o = {}) => {
3643
5200
  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
5201
  }
3645
5202
  }
3646
- const thick = last && roof ? T * 0.75 : T;
5203
+ const thick = last && roof ? o.roofThickness ?? T * 0.75 : T;
3647
5204
  parts.push({
3648
5205
  id: slabId,
3649
5206
  material: mat,
3650
5207
  shape: { kind: "box", size: [spanX + cs + 0.2, thick, spanZ + cs + 0.2] },
3651
5208
  position: [0, top + thick / 2, 0],
3652
- structure: { role: last && roof ? "roof" : "slab" }
5209
+ structure: { role: last && roof ? "roof" : "slab", ...rebarOf("slab") }
3653
5210
  });
3654
5211
  for (const b of beams) on(slabId, b, last && roof ? "resting" : "rebar");
3655
5212
  if (o.walls) {
@@ -3682,28 +5239,91 @@ var buildFrame = (o = {}) => {
3682
5239
  }
3683
5240
  }
3684
5241
  }
3685
- return { name: "frame", parts, joints, tags: ["structure"] };
5242
+ return { name: "frame", parts, joints, tags: ["structure"], ...o.soil !== void 0 ? { soil: o.soil } : {} };
3686
5243
  };
3687
5244
 
3688
5245
  // src/size.ts
5246
+ var MAX_REBAR_RATIO = 0.04;
5247
+ var onRock = (spec) => {
5248
+ if (spec.soil === void 0) return spec;
5249
+ const { soil: _soil, ...rest } = spec;
5250
+ return rest;
5251
+ };
5252
+ var without = (spec, id) => {
5253
+ const reduced = { ...spec, parts: spec.parts.filter((p) => p.id !== id), joints: (spec.joints ?? []).filter((j) => j.a !== id && j.b !== id) };
5254
+ if (!spec.joints) delete reduced.joints;
5255
+ return reduced;
5256
+ };
5257
+ var sizeRebar = (spec, options = {}) => {
5258
+ const flexure = resolveFlexure(options.flexure);
5259
+ const materials = new Map(MATERIALS.map((m) => [m.id, m]));
5260
+ for (const m of options.materials ?? []) materials.set(m.id, m);
5261
+ const out = structuredClone(spec);
5262
+ const targets = out.parts.filter((p) => {
5263
+ const m = materials.get(p.material);
5264
+ return p.shape.kind === "box" && !!m && isReinforced(p) && isFlexural(p, m, flexure);
5265
+ });
5266
+ if (targets.length === 0) return out;
5267
+ const safety = options.safety ?? 0.65;
5268
+ const safetyOf = (role) => options.safetyByRole?.[role] ?? safety;
5269
+ const bending = (s) => {
5270
+ const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, structure: { flexure: options.flexure } });
5271
+ const id = w.add(onRock(s));
5272
+ return new Map(w.state(id).parts.map((p) => [p.id, p.flexure ?? 0]));
5273
+ };
5274
+ const base = bending(out);
5275
+ const factor = new Map(targets.map((p) => [p.id, (base.get(p.id) ?? 0) / safetyOf(p.structure.role)]));
5276
+ if (options.redundancy !== void 0) {
5277
+ const roles = options.redundancyRoles ?? ["column"];
5278
+ for (const lost of out.parts) {
5279
+ if (!lost.structure || !roles.includes(lost.structure.role)) continue;
5280
+ const l = bending(without(out, lost.id));
5281
+ 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));
5282
+ }
5283
+ }
5284
+ for (const p of targets) {
5285
+ const f = factor.get(p.id);
5286
+ const g = bendGeometry(p);
5287
+ if (f <= 1 || g.gravity < 0) continue;
5288
+ const section = { b: sectionWidth(g, g.gravity), h: g.dims[g.gravity] };
5289
+ const steel = tensionSteel(p, g.dims, section, flexure);
5290
+ const m = materials.get(p.material);
5291
+ const need = f * sectionMoments(m, section, steel, flexure).ultimate;
5292
+ const momentOf = (area) => steelMoment(area, steel.yieldMPa, steel.d, section.b, m.strength.compressiveMPa, flexure);
5293
+ const cap = MAX_REBAR_RATIO * section.b * steel.d;
5294
+ if (steel.area >= cap) continue;
5295
+ let lo = steel.area, hi = cap;
5296
+ if (momentOf(hi) > need) {
5297
+ for (let k = 0; k < 60; k++) {
5298
+ const mid = (lo + hi) / 2;
5299
+ if (momentOf(mid) < need) lo = mid;
5300
+ else hi = mid;
5301
+ }
5302
+ }
5303
+ const ratio = hi / (section.b * steel.d);
5304
+ p.structure = { ...p.structure, rebar: { ...p.structure.rebar, ratio } };
5305
+ }
5306
+ return out;
5307
+ };
3689
5308
  var sizeStructure = (spec, options = {}) => {
3690
5309
  const safety = options.safety ?? 0.65;
3691
5310
  const redundancy = options.redundancy;
3692
5311
  const redundancyRoles = options.redundancyRoles ?? ["column"];
3693
5312
  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 }]));
5313
+ const loads = (s, wind) => {
5314
+ const w = createDestruction({ ...options.materials ? { materials: options.materials } : {}, ...wind ? { wind } : {} });
5315
+ const id = w.add(onRock(s));
5316
+ return new Map(w.state(id).parts.map((p) => [p.id, { load: p.load ?? 0, stress: p.stress ?? 0, wind: p.wind ?? 0 }]));
3698
5317
  };
3699
5318
  const out = structuredClone(spec);
3700
- const base = loads(out);
5319
+ const base = loads(out, options.wind);
5320
+ const gamma = options.wind ? resolveWind(options.wind).loadFactor : 0;
3701
5321
  const need = /* @__PURE__ */ new Map();
3702
5322
  for (const p of out.parts) {
3703
5323
  if (!p.structure || p.structure.role === "none") continue;
3704
5324
  const b = base.get(p.id);
3705
5325
  if (!b || b.stress <= 0) continue;
3706
- need.set(p.id, b.load / safetyOf(p.structure.role));
5326
+ need.set(p.id, (b.load + (b.wind > 0 ? gamma * b.wind / GRAVITY : 0)) / safetyOf(p.structure.role));
3707
5327
  }
3708
5328
  if (redundancy !== void 0) {
3709
5329
  for (const col of out.parts) {
@@ -3725,7 +5345,20 @@ var sizeStructure = (spec, options = {}) => {
3725
5345
  const n = need.get(p.id);
3726
5346
  if (n !== void 0) p.structure = { ...p.structure, capacity: n };
3727
5347
  }
3728
- return out;
5348
+ if (options.wind) {
5349
+ const yieldMPa = resolveFlexure(options.flexure).rebarYieldMPa;
5350
+ for (const p of out.parts) {
5351
+ const b = base.get(p.id);
5352
+ if (!b || b.wind >= 0 || !p.reinforcement || p.shape.kind !== "box") continue;
5353
+ const pull = -(b.load + gamma * b.wind / GRAVITY) * GRAVITY;
5354
+ if (pull <= 0) continue;
5355
+ const dims = shapeDims(p.shape);
5356
+ const have = tensionCapacity(dims, { reinforcement: p.reinforcement, yieldMPa });
5357
+ const want = pull / safetyOf(p.structure?.role ?? "wall");
5358
+ if (have > 0 && want > have) p.reinforcement = { ...p.reinforcement, diameter: Math.min(0.04, p.reinforcement.diameter * Math.sqrt(want / have)) };
5359
+ }
5360
+ }
5361
+ return sizeRebar(out, options);
3729
5362
  };
3730
5363
 
3731
5364
  // src/locale.ts
@@ -3807,6 +5440,8 @@ var locales = {
3807
5440
  primed: "about to explode",
3808
5441
  stress: "overload",
3809
5442
  collapse: "collapsed",
5443
+ settle: "ground gave way",
5444
+ sway: "sway",
3810
5445
  spall: "plaster came off",
3811
5446
  stage: "damage stage",
3812
5447
  expose: "rebar bare",
@@ -3895,6 +5530,8 @@ var locales = {
3895
5530
  primed: "prestes a explodir",
3896
5531
  stress: "sobrecarga",
3897
5532
  collapse: "desabou",
5533
+ settle: "recalque do solo",
5534
+ sway: "balan\xE7o",
3898
5535
  spall: "reboco soltou",
3899
5536
  stage: "est\xE1gio de dano",
3900
5537
  expose: "vergalh\xE3o \xE0 mostra",
@@ -3920,39 +5557,87 @@ export {
3920
5557
  ASSEMBLIES,
3921
5558
  CONFINEMENT_DEFAULTS,
3922
5559
  FIRE_PRIME_C,
5560
+ FLEXURE_DEFAULTS,
3923
5561
  FRAGMENT_HIT_DEFAULTS,
5562
+ GRAVITY,
3924
5563
  IMPACT_CONTRACT,
3925
5564
  JOINT_BY_MATERIALS,
3926
5565
  JOINT_DEFAULTS,
5566
+ MASONRY_DEFAULTS,
3927
5567
  MATERIALS,
3928
- PIERCING_BY_CALIBER,
3929
- PIERCING_BY_KIND,
3930
5568
  SHELTER_DEFAULTS,
5569
+ SOILS,
5570
+ SOIL_DEFAULTS,
5571
+ WIND_DEFAULTS,
5572
+ WIND_TERRAIN,
5573
+ analyzeWind,
3931
5574
  assemblyDepth,
3932
5575
  assemblyPart,
5576
+ bearingWallThickness,
5577
+ bendGeometry,
5578
+ blastImpulse,
3933
5579
  buildFrame,
3934
5580
  checkDestructible,
3935
5581
  checkHit,
5582
+ crackingMoment,
3936
5583
  createDestruction,
5584
+ damperAmplification,
5585
+ damperTuning,
5586
+ elasticEnergy,
3937
5587
  explosivePart,
5588
+ exposureOf,
5589
+ footingArea,
5590
+ footingSoil,
3938
5591
  fragility,
3939
5592
  getAssembly,
3940
5593
  guessMaterial,
5594
+ gustAt,
5595
+ heightFactor,
5596
+ impulseEnergy,
3941
5597
  inferJoints,
5598
+ layersBetween,
3942
5599
  locales,
5600
+ makeGust,
5601
+ meanHeightFactor,
5602
+ momentNodes,
5603
+ momentOfEnergy,
3943
5604
  partCapacity,
3944
5605
  partMass,
3945
5606
  partThickness,
3946
5607
  partVolume,
3947
- piercingOf,
5608
+ pileCapacity,
5609
+ pileLength,
5610
+ pileStiffness,
5611
+ profileOf,
3948
5612
  quat,
3949
5613
  quatFromEuler,
3950
5614
  registerAssembly,
3951
5615
  resolveStrings,
5616
+ resolveWind,
3952
5617
  rotateVec,
5618
+ sectionMoments,
5619
+ shapeAt,
5620
+ shearNodes,
5621
+ sizeRebar,
3953
5622
  sizeStructure,
5623
+ slendernessFactor,
5624
+ soilAt,
5625
+ solveFoundation,
5626
+ solveWallFrame,
5627
+ spanMoment,
5628
+ spanOf,
5629
+ steelMoment,
5630
+ stepSway,
5631
+ stiffnessCentre,
5632
+ subgradeModulus,
5633
+ tBeamInertia,
5634
+ tensionCapacity,
5635
+ tensionSteel,
3954
5636
  toLocal,
3955
5637
  toWorld,
3956
- vec
5638
+ turbulenceIntensity,
5639
+ vec,
5640
+ windClass,
5641
+ windPressure
3957
5642
  };
3958
5643
  //# sourceMappingURL=index.js.map