@adatechnology/game-floorplan-structure 1.2.3 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -32,6 +32,7 @@ var GameFloorplanStructure = (() => {
32
32
  FLAT_ROOF_DEFAULTS: () => FLAT_ROOF_DEFAULTS,
33
33
  GROUND: () => GROUND,
34
34
  MECHANICAL_DEFAULTS: () => MECHANICAL_DEFAULTS,
35
+ MEMBER_CAGE: () => MEMBER_CAGE,
35
36
  PILE_AREA: () => PILE_AREA,
36
37
  PILE_DEFAULTS: () => PILE_DEFAULTS,
37
38
  PLATE_DEFAULTS: () => PLATE_DEFAULTS,
@@ -2791,7 +2792,9 @@ var GameFloorplanStructure = (() => {
2791
2792
  const band = { c: [root.c[0], root.c[1], root.c[2]], s: [dims[0], dims[1], dims[2]] };
2792
2793
  band.c[L] = (lo + hi) / 2;
2793
2794
  band.s[L] = Math.max(hi - lo, 0.01);
2794
- for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
2795
+ if (inp.style === "snap") {
2796
+ for (const b of bsp(band, Math.max(3, Math.min(6, inp.maxFragments - out.length)), [0, 1, 2], inp.rnd)) out.push({ center: b.c, size: b.s, q: quat.identity(), shape: "chunk", weight: b.s[0] * b.s[1] * b.s[2] });
2797
+ } else for (const p of splinterBox(inp, band, Math.max(3, inp.maxFragments - out.length))) out.push(p);
2795
2798
  return out;
2796
2799
  };
2797
2800
  var tearStack = (inp, root, t, u, v, n, sheet) => {
@@ -2822,6 +2825,7 @@ var GameFloorplanStructure = (() => {
2822
2825
  const t = sorted[0];
2823
2826
  const [u, v] = [sorted[1], sorted[2]].sort((a, b) => a - b);
2824
2827
  if (style === "snap") {
2828
+ if (inp.cut) return severAt(inp, root, inp.cut.at, inp.cut.gap);
2825
2829
  const n2 = Math.min(maxFragments, 2 + Math.floor(rnd() * 3));
2826
2830
  for (const b of bsp(root, n2, [0, 1, 2], rnd)) out.push({ center: b.c, size: b.s, q: idq, shape: "slab", weight: b.s[0] * b.s[1] * b.s[2] });
2827
2831
  return out;
@@ -2995,6 +2999,27 @@ var GameFloorplanStructure = (() => {
2995
2999
  for (const x of spread(dims[u], COVER, spacing, MAX_BARS_PER_DIRECTION)) out.push(bar(v, dims[v] / 2 - 0.02, [[u, x], [thin, 0]]));
2996
3000
  return out;
2997
3001
  };
3002
+ var clipBarsToBox = (bars, u, v, center, size) => {
3003
+ const out = [];
3004
+ for (const [a, b] of bars) {
3005
+ const axis = a[u] !== b[u] ? u : v;
3006
+ const other = axis === u ? v : u;
3007
+ if (Math.abs(a[other] - center[other]) > size[other] / 2) continue;
3008
+ const lo = Math.max(Math.min(a[axis], b[axis]), center[axis] - size[axis] / 2), hi = Math.min(Math.max(a[axis], b[axis]), center[axis] + size[axis] / 2);
3009
+ if (hi - lo < 1e-6) continue;
3010
+ const p = [a[0], a[1], a[2]], q = [b[0], b[1], b[2]];
3011
+ p[axis] = lo;
3012
+ q[axis] = hi;
3013
+ out.push([p, q]);
3014
+ }
3015
+ return out;
3016
+ };
3017
+ var bendBar = ([a, b], thin, bow, i) => {
3018
+ const length = Math.hypot(b[0] - a[0], b[1] - a[1], b[2] - a[2]);
3019
+ const m = [(a[0] + b[0]) / 2, (a[1] + b[1]) / 2, (a[2] + b[2]) / 2];
3020
+ m[thin] += (i % 2 === 0 ? 1 : -1) * bow * length;
3021
+ return [[a, m], [m, b]];
3022
+ };
2998
3023
  var clipBarsToDisc = (bars, u, v, center, radius) => {
2999
3024
  const out = [];
3000
3025
  for (const [a, b] of bars) {
@@ -3915,14 +3940,21 @@ var GameFloorplanStructure = (() => {
3915
3940
  smoke: "grey"
3916
3941
  },
3917
3942
  visual: {
3918
- charColor: 1709072
3943
+ charColor: 1709072,
3944
+ innerColor: 14730131
3919
3945
  },
3920
3946
  tags: [
3921
3947
  "wood",
3922
3948
  "organic",
3923
3949
  "flammable"
3924
3950
  ],
3925
- compressive: 1e5
3951
+ compressive: 1e5,
3952
+ hits: {
3953
+ blunt: {
3954
+ tear: "splinters",
3955
+ tearJoules: 60
3956
+ }
3957
+ }
3926
3958
  },
3927
3959
  {
3928
3960
  id: "wood_hard",
@@ -3952,14 +3984,21 @@ var GameFloorplanStructure = (() => {
3952
3984
  smoke: "grey"
3953
3985
  },
3954
3986
  visual: {
3955
- charColor: 1709072
3987
+ charColor: 1709072,
3988
+ innerColor: 13213802
3956
3989
  },
3957
3990
  tags: [
3958
3991
  "wood",
3959
3992
  "organic",
3960
3993
  "flammable"
3961
3994
  ],
3962
- compressive: 15e4
3995
+ compressive: 15e4,
3996
+ hits: {
3997
+ blunt: {
3998
+ tear: "splinters",
3999
+ tearJoules: 150
4000
+ }
4001
+ }
3963
4002
  },
3964
4003
  {
3965
4004
  id: "plywood",
@@ -3989,14 +4028,24 @@ var GameFloorplanStructure = (() => {
3989
4028
  smoke: "grey"
3990
4029
  },
3991
4030
  visual: {
3992
- charColor: 1709072
4031
+ charColor: 1709072,
4032
+ innerColor: 14862234
3993
4033
  },
3994
4034
  tags: [
3995
4035
  "wood",
3996
4036
  "organic",
3997
4037
  "flammable"
3998
4038
  ],
3999
- compressive: 6e4
4039
+ compressive: 6e4,
4040
+ hits: {
4041
+ ballistic: {
4042
+ tear: "splinters"
4043
+ },
4044
+ blunt: {
4045
+ tear: "splinters",
4046
+ tearJoules: 80
4047
+ }
4048
+ }
4000
4049
  },
4001
4050
  {
4002
4051
  id: "mdf",
@@ -4023,14 +4072,25 @@ var GameFloorplanStructure = (() => {
4023
4072
  smoke: "grey"
4024
4073
  },
4025
4074
  visual: {
4026
- charColor: 1709072
4075
+ charColor: 1709072,
4076
+ innerColor: 12096362
4027
4077
  },
4028
4078
  tags: [
4029
4079
  "wood",
4030
4080
  "organic",
4031
4081
  "flammable"
4032
4082
  ],
4033
- compressive: 3e4
4083
+ compressive: 3e4,
4084
+ hits: {
4085
+ ballistic: {
4086
+ tear: "lumps",
4087
+ tearJoules: 120
4088
+ },
4089
+ blunt: {
4090
+ tear: "lumps",
4091
+ tearJoules: 20
4092
+ }
4093
+ }
4034
4094
  },
4035
4095
  {
4036
4096
  id: "glass",
@@ -4144,6 +4204,17 @@ var GameFloorplanStructure = (() => {
4144
4204
  damageScale: {
4145
4205
  explosive: 1.2
4146
4206
  },
4207
+ hits: {
4208
+ ballistic: {
4209
+ unitJoules: 500,
4210
+ floor: 900,
4211
+ crater: true
4212
+ },
4213
+ blunt: {
4214
+ unitJoules: 1500,
4215
+ floor: 60
4216
+ }
4217
+ },
4147
4218
  hitDebris: "chips",
4148
4219
  breakDebris: "dust",
4149
4220
  thermal: {
@@ -4512,7 +4583,8 @@ var GameFloorplanStructure = (() => {
4512
4583
  smoke: "white"
4513
4584
  },
4514
4585
  visual: {
4515
- charColor: 1709072
4586
+ charColor: 1709072,
4587
+ innerColor: 14207126
4516
4588
  },
4517
4589
  tags: [
4518
4590
  "plant",
@@ -4520,7 +4592,12 @@ var GameFloorplanStructure = (() => {
4520
4592
  "organic",
4521
4593
  "flammable"
4522
4594
  ],
4523
- compressive: 6e4
4595
+ compressive: 6e4,
4596
+ hits: {
4597
+ blunt: {
4598
+ tear: "none"
4599
+ }
4600
+ }
4524
4601
  },
4525
4602
  {
4526
4603
  id: "cactus",
@@ -4635,6 +4712,130 @@ var GameFloorplanStructure = (() => {
4635
4712
  "masonry"
4636
4713
  ],
4637
4714
  compressive: 2e4
4715
+ },
4716
+ {
4717
+ id: "asphalt",
4718
+ density: 2350,
4719
+ hpPerKg: 10,
4720
+ penetration: 14,
4721
+ penetrationClass: "concrete",
4722
+ penetrationScale: 0.8,
4723
+ ductility: 0.1,
4724
+ fracture: "crumble",
4725
+ fragmentSize: [
4726
+ 0.03,
4727
+ 0.15
4728
+ ],
4729
+ hitDebris: "chips",
4730
+ breakDebris: "dust",
4731
+ thermal: {
4732
+ conductivity: 0.1,
4733
+ heatCapacity: 2,
4734
+ ignitionC: 400,
4735
+ burnRate: 2e-3,
4736
+ smoke: "black"
4737
+ },
4738
+ tags: [
4739
+ "masonry",
4740
+ "brittle"
4741
+ ],
4742
+ compressive: 3e4
4743
+ },
4744
+ {
4745
+ id: "sand",
4746
+ density: 1600,
4747
+ hpPerKg: 2,
4748
+ penetration: 6,
4749
+ ductility: 0.4,
4750
+ fracture: "crumble",
4751
+ fragmentSize: [
4752
+ 0.01,
4753
+ 0.04
4754
+ ],
4755
+ hitDebris: "dust",
4756
+ breakDebris: "dust",
4757
+ thermal: {
4758
+ conductivity: 0.1,
4759
+ heatCapacity: 2
4760
+ },
4761
+ tags: [
4762
+ "soil"
4763
+ ],
4764
+ compressive: 1e4
4765
+ },
4766
+ {
4767
+ id: "gravel",
4768
+ density: 1800,
4769
+ hpPerKg: 3,
4770
+ penetration: 8,
4771
+ ductility: 0.3,
4772
+ fracture: "crumble",
4773
+ fragmentSize: [
4774
+ 0.02,
4775
+ 0.06
4776
+ ],
4777
+ hitDebris: "chips",
4778
+ breakDebris: "dust",
4779
+ thermal: {
4780
+ conductivity: 0.1,
4781
+ heatCapacity: 2
4782
+ },
4783
+ tags: [
4784
+ "soil",
4785
+ "masonry"
4786
+ ],
4787
+ compressive: 2e4
4788
+ },
4789
+ {
4790
+ id: "turf",
4791
+ density: 900,
4792
+ hpPerKg: 3,
4793
+ penetration: 4,
4794
+ ductility: 0.5,
4795
+ fracture: "crumble",
4796
+ fragmentSize: [
4797
+ 0.03,
4798
+ 0.1
4799
+ ],
4800
+ hitDebris: "dust",
4801
+ breakDebris: "dust",
4802
+ thermal: {
4803
+ conductivity: 0.1,
4804
+ heatCapacity: 1.5,
4805
+ ignitionC: 250,
4806
+ burnRate: 0.03,
4807
+ smoke: "white"
4808
+ },
4809
+ tags: [
4810
+ "soil",
4811
+ "organic"
4812
+ ],
4813
+ compressive: 15e3
4814
+ },
4815
+ {
4816
+ id: "mortar",
4817
+ density: 1900,
4818
+ hpPerKg: 10,
4819
+ penetration: 12,
4820
+ penetrationClass: "concrete",
4821
+ penetrationScale: 1.2,
4822
+ ductility: 0.03,
4823
+ fracture: "crumble",
4824
+ fragmentSize: [
4825
+ 0.02,
4826
+ 0.08
4827
+ ],
4828
+ hitDebris: "chips",
4829
+ breakDebris: "dust",
4830
+ thermal: {
4831
+ conductivity: 0.15,
4832
+ heatCapacity: 2.5
4833
+ },
4834
+ tags: [
4835
+ "masonry",
4836
+ "brittle"
4837
+ ],
4838
+ compressive: 1e5
4638
4839
  }
4639
4840
  ];
4640
4841
  var material_hints_default = {
@@ -4808,6 +5009,32 @@ var GameFloorplanStructure = (() => {
4808
5009
  return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
4809
5010
  };
4810
5011
  var THIN_SHEET_DISHING = 5e-3;
5012
+ var SWEEP_BACK = 0.01;
5013
+ function sweepOrigins(box2, d, spacing) {
5014
+ const origins = [];
5015
+ const line = (from, to) => {
5016
+ const n = Math.max(1, Math.ceil((to - from) / spacing));
5017
+ return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
5018
+ };
5019
+ for (const axis of [0, 1, 2]) {
5020
+ if (Math.abs(d[axis]) < 1e-3) continue;
5021
+ const lead = d[axis] > 0 ? box2.max[axis] - SWEEP_BACK : box2.min[axis] + SWEEP_BACK;
5022
+ const u = (axis + 1) % 3;
5023
+ const v = (axis + 2) % 3;
5024
+ for (const a of line(box2.min[u], box2.max[u])) {
5025
+ for (const b of line(box2.min[v], box2.max[v])) {
5026
+ const point = [0, 0, 0];
5027
+ point[axis] = lead;
5028
+ point[u] = a;
5029
+ point[v] = b;
5030
+ origins.push(point);
5031
+ }
5032
+ }
5033
+ }
5034
+ return origins;
5035
+ }
5036
+ var THIN_SHEET_CRUSH = 5e-4;
5037
+ var THIN_SHEET_CONTACT = 0.025;
4811
5038
  var DMG_PER_J = 0.02;
4812
5039
  var BLAST_COUPLING = 0.1;
4813
5040
  var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
@@ -4824,11 +5051,14 @@ var GameFloorplanStructure = (() => {
4824
5051
  var SEVER_BAND = 0.1;
4825
5052
  var SEVER_ASPECT = 3;
4826
5053
  var SEVER_BALLISTIC = 2;
5054
+ var TEAR_JOULES = 150;
5055
+ var tears = 0;
4827
5056
  var SUPPORT_TOLERANCE = 0.05;
4828
5057
  var UNIT_FRAGMENT_FACTOR = 4;
4829
5058
  var BREACH_GRID_CAP = 4096;
4830
5059
  var CHIP_ENERGY = 200;
4831
5060
  var CHIP_SPLASH = 0.5;
5061
+ var BAR_BOW = 0.08;
4832
5062
  var CUT_SLICE = 1;
4833
5063
  var cutFactorOf = (shape) => clamp22(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
4834
5064
  var clamp22 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -4838,8 +5068,24 @@ var GameFloorplanStructure = (() => {
4838
5068
  return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
4839
5069
  };
4840
5070
  var capacityScale = (p) => {
4841
- if (p.st.stripped) return p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
4842
- return p.rt.removed ? Math.max(0.05, 1 - p.rt.removed / p.mass0) : 1;
5071
+ const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
5072
+ if (p.st.stripped) return residual;
5073
+ const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
5074
+ return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
5075
+ };
5076
+ var sectionLoss = (p) => {
5077
+ const cells = p.st.chipped;
5078
+ if (!cells?.length) return 0;
5079
+ const [u, v] = planeAxes(p.thin);
5080
+ const along = p.dims[u] >= p.dims[v] ? u : v;
5081
+ const across = along === u ? v : u;
5082
+ let worst = 0;
5083
+ for (const c of cells) {
5084
+ let gone = 0;
5085
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
5086
+ worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
5087
+ }
5088
+ return Math.min(1, worst);
4843
5089
  };
4844
5090
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
4845
5091
  var clone = (v) => structuredClone(v);
@@ -6020,6 +6266,12 @@ var GameFloorplanStructure = (() => {
6020
6266
  debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
6021
6267
  setStage(o, p, 1, {}, src);
6022
6268
  };
6269
+ const crater = (o, p, wp, normal, radius, src) => {
6270
+ markDamaged(p);
6271
+ emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
6272
+ debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
6273
+ setStage(o, p, 1, {}, src);
6274
+ };
6023
6275
  const insideOpening = (o, p, objectPoint) => {
6024
6276
  const op = p.st.opening;
6025
6277
  if (!op) return false;
@@ -6096,9 +6348,13 @@ var GameFloorplanStructure = (() => {
6096
6348
  const lp = toLocal(p.pos, p.q, objectPoint);
6097
6349
  return cells.some((c) => Math.abs(lp[u] - c.center[u]) <= c.size[u] / 2 && Math.abs(lp[v] - c.center[v]) <= c.size[v] / 2);
6098
6350
  };
6099
- const chipUnits = (o, p, wp, energy, dir, radius, src) => {
6351
+ const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
6100
6352
  var _a, _b;
6101
- if (!p.spec.units || p.spec.reinforcement || !p.staged || chipEnergy <= 0 || energy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6353
+ if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6354
+ const rule = p.mat.hits?.[kind];
6355
+ const perKg = rule?.unitJoules ?? chipEnergy;
6356
+ const energy = hitEnergy - (rule?.floor ?? 0);
6357
+ if (perKg <= 0 || energy <= 0) return;
6102
6358
  const [u, v] = planeAxes(p.thin);
6103
6359
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6104
6360
  const grid = gridOf(p);
@@ -6112,7 +6368,7 @@ var GameFloorplanStructure = (() => {
6112
6368
  const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
6113
6369
  const total = (worn[i] ?? 0) + energy * share;
6114
6370
  const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
6115
- if (total < chipEnergy * mass) {
6371
+ if (total < perKg * mass) {
6116
6372
  worn[i] = total;
6117
6373
  continue;
6118
6374
  }
@@ -6137,7 +6393,32 @@ var GameFloorplanStructure = (() => {
6137
6393
  emit({ ...base(o, src), type: "chip", part: p.spec.id, center, size: [cell.size[0], cell.size[1], cell.size[2]], rotation: q, fragments });
6138
6394
  debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
6139
6395
  }
6396
+ if (p.spec.reinforcement) exposeRebar(o, p, src);
6140
6397
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
6398
+ if (p.structural && !isLost(p)) {
6399
+ solveStructure(o);
6400
+ collapseCheck(o, src);
6401
+ }
6402
+ };
6403
+ const exposeRebar = (o, p, src) => {
6404
+ const r = p.spec.reinforcement;
6405
+ if (!r) return;
6406
+ const [u, v] = planeAxes(p.thin);
6407
+ const all = rebarBars(p.dims, p.thin, r.spacing);
6408
+ let bars;
6409
+ if (p.st.stripped) bars = all;
6410
+ else {
6411
+ bars = [];
6412
+ for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
6413
+ const op = p.st.opening;
6414
+ if (op) {
6415
+ const lc = toLocal(p.pos, p.q, op.center);
6416
+ bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
6417
+ }
6418
+ }
6419
+ const bow = clamp22(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
6420
+ if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
6421
+ emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
6141
6422
  };
6142
6423
  const openBreach = (o, p, center, total, energy, dir, src) => {
6143
6424
  const [u, v] = planeAxes(p.thin);
@@ -6165,10 +6446,7 @@ var GameFloorplanStructure = (() => {
6165
6446
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
6166
6447
  debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
6167
6448
  const reinforcement = p.spec.reinforcement;
6168
- if (reinforcement) {
6169
- const bars = worldBars(o, p, clipBarsToDisc(rebarBars(p.dims, p.thin, reinforcement.spacing), u, v, [lc[u], lc[v]], radius + 0.05));
6170
- if (bars.length > 0) emit({ ...base(o, src), type: "expose", part: p.spec.id, bars });
6171
- }
6449
+ if (reinforcement) exposeRebar(o, p, src);
6172
6450
  if (p.structural) solveStructure(o);
6173
6451
  };
6174
6452
  const stripPart = (o, p, reinforcement, src) => {
@@ -6180,8 +6458,8 @@ var GameFloorplanStructure = (() => {
6180
6458
  p.st.burning = false;
6181
6459
  p.rt.jet = false;
6182
6460
  markDamaged(p);
6183
- emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
6184
6461
  solveStructure(o);
6462
+ exposeRebar(o, p, src);
6185
6463
  collapseCheck(o, src);
6186
6464
  };
6187
6465
  const unitFragmentPlan = (p, cap) => {
@@ -6235,7 +6513,7 @@ var GameFloorplanStructure = (() => {
6235
6513
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
6236
6514
  const hitLocal = toLocal(c, q, hitW);
6237
6515
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
6238
- const axis = style === "splinter" ? severAxis(p) : -1;
6516
+ const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
6239
6517
  const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
6240
6518
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
6241
6519
  style,
@@ -6307,7 +6585,7 @@ var GameFloorplanStructure = (() => {
6307
6585
  };
6308
6586
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
6309
6587
  const severAxis = (p) => {
6310
- if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
6588
+ if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
6311
6589
  const d = p.dims;
6312
6590
  const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
6313
6591
  return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
@@ -6368,6 +6646,49 @@ var GameFloorplanStructure = (() => {
6368
6646
  }
6369
6647
  }
6370
6648
  };
6649
+ const tearOf = (mat, kind) => {
6650
+ const h = mat.hits?.[kind];
6651
+ const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
6652
+ return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
6653
+ };
6654
+ const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
6655
+ const { tear, joules } = tearOf(p.mat, kind);
6656
+ if (tear === "none") return;
6657
+ const shot = kind === "ballistic";
6658
+ const n = shot ? exit ? clamp22(Math.round(E / joules), 1, 7) : 1 : clamp22(Math.floor(E / joules), 0, 5);
6659
+ if (n === 0) return;
6660
+ const fibre = tear === "lumps";
6661
+ const { q } = partXf(o, p);
6662
+ const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
6663
+ const fragments = [];
6664
+ for (let i = 0; i < n; i++) {
6665
+ const back = !exit || i % 3 === 2;
6666
+ const len = scale * (fibre ? 1.2 + 1.2 * rnd() : 3 + 4 * rnd()), w = scale * (0.8 + 0.8 * rnd()), t = scale * (fibre ? 0.7 + 0.6 * rnd() : 0.4 + 0.4 * rnd());
6667
+ const size = [w, w, w];
6668
+ size[along] = len;
6669
+ size[(along + 1) % 3] = w;
6670
+ size[(along + 2) % 3] = t;
6671
+ const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
6672
+ const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
6673
+ const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
6674
+ const mass = size[0] * size[1] * size[2] * p.mat.density;
6675
+ fragments.push({
6676
+ id: `${p.spec.id}#t${tears++}-${i}`,
6677
+ part: p.spec.id,
6678
+ material: p.spec.material,
6679
+ shape: fibre ? "chunk" : "splinter",
6680
+ center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
6681
+ size,
6682
+ rotation: q,
6683
+ velocity: v,
6684
+ inner: true,
6685
+ angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
6686
+ mass
6687
+ });
6688
+ }
6689
+ const patch = [scale * 4, scale * 4, scale * 4];
6690
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
6691
+ };
6371
6692
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6372
6693
  const mat = p.mat;
6373
6694
  const piercing = round3.piercing ?? 1;
@@ -6396,6 +6717,7 @@ var GameFloorplanStructure = (() => {
6396
6717
  return { penetrated: false, exitEnergy: 0, damage: dmg2 };
6397
6718
  }
6398
6719
  if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
6720
+ else if (mat.hits?.ballistic?.crater) crater(o, p, entry2, n, clamp22(0.02 + Math.sqrt(E) * 15e-4, 0.02, 0.15), src);
6399
6721
  const brittle = mat.ductility < 0.3;
6400
6722
  const ratio = Ep / Math.max(thr, 1e-6);
6401
6723
  const crackLen = clamp22(0.03 + ratio * 0.2, 0.03, 0.8);
@@ -6429,8 +6751,10 @@ var GameFloorplanStructure = (() => {
6429
6751
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6430
6752
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6431
6753
  wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true);
6432
- const dmg = applyDamage(o, p, E * DMG_PER_J * scale, "ballistic", entry2, d, E * 0.1, 12, src);
6433
- chipUnits(o, p, entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
6754
+ const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
6755
+ const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
6756
+ if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
6757
+ chipUnits(o, p, "ballistic", entry2, penetrated ? Math.min(E, Math.max(thr, E * 0.5)) : E, d, Math.max(0.05, caliber / 1e3 * 8), src);
6434
6758
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
6435
6759
  };
6436
6760
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -6438,14 +6762,20 @@ var GameFloorplanStructure = (() => {
6438
6762
  const thrMm = mat.penetration * p.thickness * 1e3;
6439
6763
  const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
6440
6764
  const Ec = E * fragile;
6765
+ const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
6766
+ const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
6767
+ const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
6441
6768
  if (E > 0) {
6442
6769
  if (kind === "blunt") {
6443
- if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(contactR, 0.015), clamp22(E * 1e-4, 1e-3, 0.06), src);
6770
+ if (mat.ductility >= 0.5) addDent(o, p, point, n, Math.max(patch, 0.015), clamp22(E * 1e-4 * fold, 1e-3, sheet ? Math.min(0.06, Math.min(...p.dims) * 0.45) : 0.06), src);
6444
6771
  else if (mat.ductility < 0.3) {
6445
6772
  if (Ec >= 5) addCrack(o, p, point, n, clamp22(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
6446
6773
  } else if (E >= thrMm * 0.2) addDent(o, p, point, n, Math.max(contactR, 0.015), clamp22(E * 6e-5, 1e-3, 0.04), src);
6447
6774
  } else if (mat.fracture === "tear") {
6448
6775
  addHole(o, p, point, n, clamp22(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
6776
+ } else if (mat.ductility >= 0.3 && E >= thrMm) {
6777
+ addHole(o, p, point, n, clamp22(contactR, 0.01, 0.08), true, void 0, src);
6778
+ if (sheet) addDent(o, p, point, n, Math.max(patch * 0.6, 0.01), clamp22(E * 4e-5 * fold, 5e-4, 0.02), src);
6449
6779
  } else if (mat.ductility >= 0.5) {
6450
6780
  addDent(o, p, point, n, Math.max(contactR, 0.01), clamp22(E * 4e-5, 5e-4, 0.02), src);
6451
6781
  }
@@ -6458,7 +6788,8 @@ var GameFloorplanStructure = (() => {
6458
6788
  const scale = mat.damageScale?.[kind] ?? 1;
6459
6789
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
6460
6790
  addBreach(o, p, point, breachAreaOf(p, done, false), E, d, src);
6461
- if (kind === "blunt") chipUnits(o, p, point, E, d, Math.max(contactR, 0.03), src);
6791
+ if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
6792
+ if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp22(contactR * 0.2, 6e-3, 0.02), src);
6462
6793
  return done;
6463
6794
  };
6464
6795
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -7037,6 +7368,7 @@ var GameFloorplanStructure = (() => {
7037
7368
  if (p.rt.stressT <= 0) {
7038
7369
  p.rt.stressT = 0.5;
7039
7370
  emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
7371
+ if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
7040
7372
  }
7041
7373
  applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
7042
7374
  } else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
@@ -7253,6 +7585,18 @@ var GameFloorplanStructure = (() => {
7253
7585
  const r = raycastInternal(origin, direction, maxDistance);
7254
7586
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
7255
7587
  },
7588
+ sweepBox(box2, direction, distance2, options2 = {}) {
7589
+ const d = vec.normalize(direction);
7590
+ const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
7591
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
7592
+ let best = null;
7593
+ for (const origin of sweepOrigins(box2, d, options2.spacing ?? 0.2)) {
7594
+ const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
7595
+ if (!r || best && r.distance >= best.distance) continue;
7596
+ best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
7597
+ }
7598
+ return best;
7599
+ },
7256
7600
  shoot(origin, direction, energy, caliber, source, round3) {
7257
7601
  const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
7258
7602
  return withCollapses(run(() => {
@@ -8677,7 +9021,24 @@ var GameFloorplanStructure = (() => {
8677
9021
  }
8678
9022
  return spec;
8679
9023
  };
8680
- var partSpecOf = (part, capacity, detailed) => detailed && part.assembly ? assemblySpec(part, capacity) : boxSpec(part, capacity);
9024
+ var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
9025
+ var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
9026
+ var caged = (spec, part, rebar) => {
9027
+ if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
9028
+ const size = spec.shape.kind === "box" ? spec.shape.size : null;
9029
+ if (!size) return spec;
9030
+ const thin = Math.min(...size);
9031
+ return {
9032
+ ...spec,
9033
+ reinforcement: { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
9034
+ units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
9035
+ };
9036
+ };
9037
+ var partSpecOf = (part, capacity, detailed, rebar) => {
9038
+ if (detailed && part.assembly) return assemblySpec(part, capacity);
9039
+ const spec = boxSpec(part, capacity);
9040
+ return detailed ? caged(spec, part, rebar) : spec;
9041
+ };
8681
9042
  function toDestructibleSpec(structure, options = {}) {
8682
9043
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
8683
9044
  const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
@@ -8694,7 +9055,7 @@ var GameFloorplanStructure = (() => {
8694
9055
  const sizing = { safety, redundancy, safetyOthers };
8695
9056
  const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
8696
9057
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
8697
- const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
9058
+ const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
8698
9059
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
8699
9060
  const x0 = Math.min(...parts.map((part) => part.aabb[0]));
8700
9061
  const z0 = Math.min(...parts.map((part) => part.aabb[1]));