@adatechnology/game-floorplan-structure 1.2.3 → 1.3.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.
@@ -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,8 +4204,20 @@ 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",
4220
+ visual: { innerColor: 10197652 },
4149
4221
  thermal: {
4150
4222
  conductivity: 0.15,
4151
4223
  heatCapacity: 2.5
@@ -4176,6 +4248,7 @@ var GameFloorplanStructure = (() => {
4176
4248
  ],
4177
4249
  hitDebris: "chips",
4178
4250
  breakDebris: "dust",
4251
+ visual: { innerColor: 12085567 },
4179
4252
  thermal: {
4180
4253
  conductivity: 0.15,
4181
4254
  heatCapacity: 2.5
@@ -4206,6 +4279,7 @@ var GameFloorplanStructure = (() => {
4206
4279
  ],
4207
4280
  hitDebris: "dust",
4208
4281
  breakDebris: "dust",
4282
+ visual: { innerColor: 14999507 },
4209
4283
  thermal: {
4210
4284
  conductivity: 0.1,
4211
4285
  heatCapacity: 1.5
@@ -4231,6 +4305,7 @@ var GameFloorplanStructure = (() => {
4231
4305
  ],
4232
4306
  hitDebris: "chips",
4233
4307
  breakDebris: "dust",
4308
+ visual: { innerColor: 9407364 },
4234
4309
  thermal: {
4235
4310
  conductivity: 0.2,
4236
4311
  heatCapacity: 2.5
@@ -4512,7 +4587,8 @@ var GameFloorplanStructure = (() => {
4512
4587
  smoke: "white"
4513
4588
  },
4514
4589
  visual: {
4515
- charColor: 1709072
4590
+ charColor: 1709072,
4591
+ innerColor: 14207126
4516
4592
  },
4517
4593
  tags: [
4518
4594
  "plant",
@@ -4520,7 +4596,12 @@ var GameFloorplanStructure = (() => {
4520
4596
  "organic",
4521
4597
  "flammable"
4522
4598
  ],
4523
- compressive: 6e4
4599
+ compressive: 6e4,
4600
+ hits: {
4601
+ blunt: {
4602
+ tear: "none"
4603
+ }
4604
+ }
4524
4605
  },
4525
4606
  {
4526
4607
  id: "cactus",
@@ -4635,6 +4716,130 @@ var GameFloorplanStructure = (() => {
4635
4716
  "masonry"
4636
4717
  ],
4637
4718
  compressive: 2e4
4719
+ },
4720
+ {
4721
+ id: "asphalt",
4722
+ density: 2350,
4723
+ hpPerKg: 10,
4724
+ penetration: 14,
4725
+ penetrationClass: "concrete",
4726
+ penetrationScale: 0.8,
4727
+ ductility: 0.1,
4728
+ fracture: "crumble",
4729
+ fragmentSize: [
4730
+ 0.03,
4731
+ 0.15
4732
+ ],
4733
+ hitDebris: "chips",
4734
+ breakDebris: "dust",
4735
+ thermal: {
4736
+ conductivity: 0.1,
4737
+ heatCapacity: 2,
4738
+ ignitionC: 400,
4739
+ burnRate: 2e-3,
4740
+ smoke: "black"
4741
+ },
4742
+ tags: [
4743
+ "masonry",
4744
+ "brittle"
4745
+ ],
4746
+ compressive: 3e4
4747
+ },
4748
+ {
4749
+ id: "sand",
4750
+ density: 1600,
4751
+ hpPerKg: 2,
4752
+ penetration: 6,
4753
+ ductility: 0.4,
4754
+ fracture: "crumble",
4755
+ fragmentSize: [
4756
+ 0.01,
4757
+ 0.04
4758
+ ],
4759
+ hitDebris: "dust",
4760
+ breakDebris: "dust",
4761
+ thermal: {
4762
+ conductivity: 0.1,
4763
+ heatCapacity: 2
4764
+ },
4765
+ tags: [
4766
+ "soil"
4767
+ ],
4768
+ compressive: 1e4
4769
+ },
4770
+ {
4771
+ id: "gravel",
4772
+ density: 1800,
4773
+ hpPerKg: 3,
4774
+ penetration: 8,
4775
+ ductility: 0.3,
4776
+ fracture: "crumble",
4777
+ fragmentSize: [
4778
+ 0.02,
4779
+ 0.06
4780
+ ],
4781
+ hitDebris: "chips",
4782
+ breakDebris: "dust",
4783
+ thermal: {
4784
+ conductivity: 0.1,
4785
+ heatCapacity: 2
4786
+ },
4787
+ tags: [
4788
+ "soil",
4789
+ "masonry"
4790
+ ],
4791
+ compressive: 2e4
4792
+ },
4793
+ {
4794
+ id: "turf",
4795
+ density: 900,
4796
+ hpPerKg: 3,
4797
+ penetration: 4,
4798
+ ductility: 0.5,
4799
+ fracture: "crumble",
4800
+ fragmentSize: [
4801
+ 0.03,
4802
+ 0.1
4803
+ ],
4804
+ hitDebris: "dust",
4805
+ breakDebris: "dust",
4806
+ thermal: {
4807
+ conductivity: 0.1,
4808
+ heatCapacity: 1.5,
4809
+ ignitionC: 250,
4810
+ burnRate: 0.03,
4811
+ smoke: "white"
4812
+ },
4813
+ tags: [
4814
+ "soil",
4815
+ "organic"
4816
+ ],
4817
+ compressive: 15e3
4818
+ },
4819
+ {
4820
+ id: "mortar",
4821
+ density: 1900,
4822
+ hpPerKg: 10,
4823
+ penetration: 12,
4824
+ penetrationClass: "concrete",
4825
+ penetrationScale: 1.2,
4826
+ ductility: 0.03,
4827
+ fracture: "crumble",
4828
+ fragmentSize: [
4829
+ 0.02,
4830
+ 0.08
4831
+ ],
4832
+ hitDebris: "chips",
4833
+ breakDebris: "dust",
4834
+ thermal: {
4835
+ conductivity: 0.15,
4836
+ heatCapacity: 2.5
4837
+ },
4838
+ tags: [
4839
+ "masonry",
4840
+ "brittle"
4841
+ ],
4842
+ compressive: 1e5
4638
4843
  }
4639
4844
  ];
4640
4845
  var material_hints_default = {
@@ -4808,6 +5013,32 @@ var GameFloorplanStructure = (() => {
4808
5013
  return (1 + 4 * (0.3 - ductility) / 0.3) * Math.min(25, Math.max(1, (0.02 / Math.max(thickness, 1e-4)) ** 2));
4809
5014
  };
4810
5015
  var THIN_SHEET_DISHING = 5e-3;
5016
+ var SWEEP_BACK = 0.01;
5017
+ function sweepOrigins(box2, d, spacing) {
5018
+ const origins = [];
5019
+ const line = (from, to) => {
5020
+ const n = Math.max(1, Math.ceil((to - from) / spacing));
5021
+ return Array.from({ length: n + 1 }, (_, i) => from + (to - from) * i / n);
5022
+ };
5023
+ for (const axis of [0, 1, 2]) {
5024
+ if (Math.abs(d[axis]) < 1e-3) continue;
5025
+ const lead = d[axis] > 0 ? box2.max[axis] - SWEEP_BACK : box2.min[axis] + SWEEP_BACK;
5026
+ const u = (axis + 1) % 3;
5027
+ const v = (axis + 2) % 3;
5028
+ for (const a of line(box2.min[u], box2.max[u])) {
5029
+ for (const b of line(box2.min[v], box2.max[v])) {
5030
+ const point = [0, 0, 0];
5031
+ point[axis] = lead;
5032
+ point[u] = a;
5033
+ point[v] = b;
5034
+ origins.push(point);
5035
+ }
5036
+ }
5037
+ }
5038
+ return origins;
5039
+ }
5040
+ var THIN_SHEET_CRUSH = 5e-4;
5041
+ var THIN_SHEET_CONTACT = 0.025;
4811
5042
  var DMG_PER_J = 0.02;
4812
5043
  var BLAST_COUPLING = 0.1;
4813
5044
  var SHELTER_DEFAULTS = { k: 2800, brokenTransmission: 0.5, minPath: 5e-3 };
@@ -4824,11 +5055,15 @@ var GameFloorplanStructure = (() => {
4824
5055
  var SEVER_BAND = 0.1;
4825
5056
  var SEVER_ASPECT = 3;
4826
5057
  var SEVER_BALLISTIC = 2;
5058
+ var TEAR_JOULES = 150;
5059
+ var tears = 0;
4827
5060
  var SUPPORT_TOLERANCE = 0.05;
4828
5061
  var UNIT_FRAGMENT_FACTOR = 4;
4829
5062
  var BREACH_GRID_CAP = 4096;
4830
5063
  var CHIP_ENERGY = 200;
4831
5064
  var CHIP_SPLASH = 0.5;
5065
+ var BAR_BOW = 0.08;
5066
+ var BULLET_JOINT_SPAN = 0.3;
4832
5067
  var CUT_SLICE = 1;
4833
5068
  var cutFactorOf = (shape) => clamp22(Math.max(...shapeDims(shape)) / CUT_SLICE, 1, 50);
4834
5069
  var clamp22 = (v, a, b) => Math.max(a, Math.min(b, v));
@@ -4838,8 +5073,24 @@ var GameFloorplanStructure = (() => {
4838
5073
  return v === void 0 ? void 0 : v * (mat.penetrationScale ?? 1);
4839
5074
  };
4840
5075
  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;
5076
+ const residual = p.spec.reinforcement?.residualCapacity ?? RESIDUAL_CAPACITY;
5077
+ if (p.st.stripped) return residual;
5078
+ const kept = Math.min(p.rt.removed ? 1 - p.rt.removed / p.mass0 : 1, 1 - sectionLoss(p));
5079
+ return Math.max(p.spec.reinforcement ? residual : 0.05, kept);
5080
+ };
5081
+ var sectionLoss = (p) => {
5082
+ const cells = p.st.chipped;
5083
+ if (!cells?.length) return 0;
5084
+ const [u, v] = planeAxes(p.thin);
5085
+ const along = p.dims[u] >= p.dims[v] ? u : v;
5086
+ const across = along === u ? v : u;
5087
+ let worst = 0;
5088
+ for (const c of cells) {
5089
+ let gone = 0;
5090
+ for (const d of cells) if (Math.abs(d.center[along] - c.center[along]) < d.size[along] / 2 + 1e-6) gone += d.size[across];
5091
+ worst = Math.max(worst, gone / Math.max(p.dims[across], 1e-6));
5092
+ }
5093
+ return Math.min(1, worst);
4843
5094
  };
4844
5095
  var isLost = (p) => p.st.status === "broken" || p.st.status === "melted" || p.st.status === "ashes";
4845
5096
  var clone = (v) => structuredClone(v);
@@ -5939,7 +6190,7 @@ var GameFloorplanStructure = (() => {
5939
6190
  emit({ ...base(o, src), type: "crack", part: p.spec.id, crack, worldPoint: wp });
5940
6191
  setStage(o, p, 1, {}, src);
5941
6192
  };
5942
- const debris = (o, p, kind, point, normal, count, speed, src) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed });
6193
+ const debris = (o, p, kind, point, normal, count, speed, src, material) => emit({ ...base(o, src), type: "debris", part: p.spec.id, kind, point, normal, count, speed, ...material && material !== p.spec.material ? { material } : {} });
5943
6194
  const holeRate = (hole, pressurized) => (pressurized ? 0.5 : 0.1) * (hole.diameter / 9e-3);
5944
6195
  const detachEvents = (o, opened, by, src) => {
5945
6196
  for (const j of opened) emit({ ...base(o, src), type: "detach", joint: j.spec.id, parts: [j.spec.a, j.spec.b], by, groups: o.st.groups.map((g) => [...g]) });
@@ -5980,7 +6231,13 @@ var GameFloorplanStructure = (() => {
5980
6231
  const small = pa.dims[0] * pa.dims[1] * pa.dims[2] <= pb.dims[0] * pb.dims[1] * pb.dims[2] ? pa : pb;
5981
6232
  return Math.max(0.06, 0.5 * Math.min(small.dims[0], small.dims[1], small.dims[2]));
5982
6233
  };
5983
- const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false) => {
6234
+ const jointLength = (o, j) => {
6235
+ const A = partWorldAabb(o, o.parts[j.ia]), B = partWorldAabb(o, o.parts[j.ib]);
6236
+ let len = 0;
6237
+ for (let k = 0; k < 3; k++) len = Math.max(len, Math.min(A.max[k], B.max[k]) - Math.max(A.min[k], B.min[k]));
6238
+ return len;
6239
+ };
6240
+ const wearJoints = (o, p, amount, near, radius = 0.06, src, split = false, span) => {
5984
6241
  if (amount <= 0) return;
5985
6242
  const mine = (o.byPart.get(p.spec.id) ?? []).filter((j) => !j.st.open);
5986
6243
  if (split && mine.length) amount /= mine.length;
@@ -5988,7 +6245,7 @@ var GameFloorplanStructure = (() => {
5988
6245
  if (j.st.open) continue;
5989
6246
  if (j.spec.kind === "rebar" && (isReinforced(o.parts[j.ia].spec) || isReinforced(o.parts[j.ib].spec))) continue;
5990
6247
  if (near && jointReach(o, j, near) > Math.max(radius, jointReachLimit(o, j))) continue;
5991
- j.st.hp -= amount;
6248
+ j.st.hp -= span === void 0 ? amount : amount * Math.min(1, span / Math.max(jointLength(o, j), 1e-6));
5992
6249
  if (j.st.hp <= 0) {
5993
6250
  j.st.hp = 0;
5994
6251
  j.st.open = true;
@@ -6017,7 +6274,13 @@ var GameFloorplanStructure = (() => {
6017
6274
  if (spalls.length > MAX_SPALLS) spalls.shift();
6018
6275
  markDamaged(p);
6019
6276
  emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: layer.index, point: wp, area: Math.PI * radius * radius });
6020
- debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src);
6277
+ debris(o, p, layer.mat.hitDebris, wp, normal, 5, 2, src, layer.mat.id);
6278
+ setStage(o, p, 1, {}, src);
6279
+ };
6280
+ const crater = (o, p, wp, normal, radius, src) => {
6281
+ markDamaged(p);
6282
+ emit({ ...base(o, src), type: "spall", part: p.spec.id, layer: -1, point: wp, area: Math.PI * radius * radius });
6283
+ debris(o, p, p.mat.breakDebris, wp, normal, 6, 1.5, src);
6021
6284
  setStage(o, p, 1, {}, src);
6022
6285
  };
6023
6286
  const insideOpening = (o, p, objectPoint) => {
@@ -6096,9 +6359,13 @@ var GameFloorplanStructure = (() => {
6096
6359
  const lp = toLocal(p.pos, p.q, objectPoint);
6097
6360
  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
6361
  };
6099
- const chipUnits = (o, p, wp, energy, dir, radius, src) => {
6362
+ const chipUnits = (o, p, kind, wp, hitEnergy, dir, radius, src) => {
6100
6363
  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;
6364
+ if (!p.spec.units || !p.staged || chipEnergy <= 0 || p.st.stripped || isLost(p) || (p.st.stage ?? 0) >= 2) return;
6365
+ const rule = p.mat.hits?.[kind];
6366
+ const perKg = rule?.unitJoules ?? chipEnergy;
6367
+ const energy = hitEnergy - (rule?.floor ?? 0);
6368
+ if (perKg <= 0 || energy <= 0) return;
6102
6369
  const [u, v] = planeAxes(p.thin);
6103
6370
  const lp = toLocal(p.pos, p.q, objLocal(o, wp));
6104
6371
  const grid = gridOf(p);
@@ -6112,7 +6379,7 @@ var GameFloorplanStructure = (() => {
6112
6379
  const share = gap === 0 ? 1 : CHIP_SPLASH * (1 - gap / radius);
6113
6380
  const total = (worn[i] ?? 0) + energy * share;
6114
6381
  const mass = p.mat.density * cell.size[0] * cell.size[1] * cell.size[2];
6115
- if (total < chipEnergy * mass) {
6382
+ if (total < perKg * mass) {
6116
6383
  worn[i] = total;
6117
6384
  continue;
6118
6385
  }
@@ -6137,7 +6404,32 @@ var GameFloorplanStructure = (() => {
6137
6404
  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
6405
  debris(o, p, p.mat.breakDebris, center, vec.scale(dir ?? [0, 1, 0], -1), 6, 2, src);
6139
6406
  }
6407
+ if (p.spec.reinforcement) exposeRebar(o, p, src);
6140
6408
  for (const cell of out) addBreach(o, p, toWorld(c, q, cell.center), cell.size[u] * cell.size[v], energy, dir, src);
6409
+ if (p.structural && !isLost(p)) {
6410
+ solveStructure(o);
6411
+ collapseCheck(o, src);
6412
+ }
6413
+ };
6414
+ const exposeRebar = (o, p, src) => {
6415
+ const r = p.spec.reinforcement;
6416
+ if (!r) return;
6417
+ const [u, v] = planeAxes(p.thin);
6418
+ const all = rebarBars(p.dims, p.thin, r.spacing);
6419
+ let bars;
6420
+ if (p.st.stripped) bars = all;
6421
+ else {
6422
+ bars = [];
6423
+ for (const cell of p.st.chipped ?? []) bars.push(...clipBarsToBox(all, u, v, cell.center, cell.size));
6424
+ const op = p.st.opening;
6425
+ if (op) {
6426
+ const lc = toLocal(p.pos, p.q, op.center);
6427
+ bars.push(...clipBarsToDisc(all, u, v, [lc[u], lc[v]], op.radius + 0.05));
6428
+ }
6429
+ }
6430
+ const bow = clamp22(((p.st.stress ?? 0) - 1) / 0.5, 0, 1);
6431
+ if (bow > 0) bars = bars.flatMap((bar2, i) => bendBar(bar2, p.thin, bow * BAR_BOW, i));
6432
+ emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
6141
6433
  };
6142
6434
  const openBreach = (o, p, center, total, energy, dir, src) => {
6143
6435
  const [u, v] = planeAxes(p.thin);
@@ -6165,10 +6457,7 @@ var GameFloorplanStructure = (() => {
6165
6457
  setStage(o, p, 2, { opening: { center: worldCenter, radius }, fragments }, src);
6166
6458
  debris(o, p, p.mat.breakDebris, worldCenter, vec.scale(dir ?? [0, 1, 0], -1), 14, 3, src);
6167
6459
  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
- }
6460
+ if (reinforcement) exposeRebar(o, p, src);
6172
6461
  if (p.structural) solveStructure(o);
6173
6462
  };
6174
6463
  const stripPart = (o, p, reinforcement, src) => {
@@ -6180,8 +6469,8 @@ var GameFloorplanStructure = (() => {
6180
6469
  p.st.burning = false;
6181
6470
  p.rt.jet = false;
6182
6471
  markDamaged(p);
6183
- emit({ ...base(o, src), type: "expose", part: p.spec.id, bars: worldBars(o, p, bars) });
6184
6472
  solveStructure(o);
6473
+ exposeRebar(o, p, src);
6185
6474
  collapseCheck(o, src);
6186
6475
  };
6187
6476
  const unitFragmentPlan = (p, cap) => {
@@ -6235,7 +6524,7 @@ var GameFloorplanStructure = (() => {
6235
6524
  const grain = p.spec.grain ? AXIS[p.spec.grain] : void 0;
6236
6525
  const hitLocal = toLocal(c, q, hitW);
6237
6526
  const unitPlan = p.spec.units ? unitFragmentPlan(p, maxFragments * UNIT_FRAGMENT_FACTOR) : [];
6238
- const axis = style === "splinter" ? severAxis(p) : -1;
6527
+ const axis = style === "splinter" || style === "snap" ? severAxis(p) : -1;
6239
6528
  const cutAt2 = axis < 0 ? void 0 : p.rt.cutAt ?? (origin ? hitLocal[axis] : void 0);
6240
6529
  const plan = unitPlan.length > 0 ? unitPlan : planFragments({
6241
6530
  style,
@@ -6307,7 +6596,7 @@ var GameFloorplanStructure = (() => {
6307
6596
  };
6308
6597
  const CHAIN_TRIGGER_OF = { ballistic: "shot", explosive: "explosion" };
6309
6598
  const severAxis = (p) => {
6310
- if (p.mat.fracture !== "splinter" || p.spec.shape.kind !== "box") return -1;
6599
+ if (p.mat.fracture !== "splinter" && p.mat.fracture !== "snap" || p.spec.shape.kind !== "box") return -1;
6311
6600
  const d = p.dims;
6312
6601
  const order = [0, 1, 2].sort((a, b) => d[b] - d[a]);
6313
6602
  return d[order[0]] >= SEVER_ASPECT * d[order[1]] && d[order[0]] >= 3 * SEVER_BAND ? order[0] : -1;
@@ -6368,6 +6657,49 @@ var GameFloorplanStructure = (() => {
6368
6657
  }
6369
6658
  }
6370
6659
  };
6660
+ const tearOf = (mat, kind) => {
6661
+ const h = mat.hits?.[kind];
6662
+ const tear = h?.tear ?? (kind === "ballistic" && mat.penetrationClass === "wood" ? "splinters" : "none");
6663
+ return { tear, joules: h?.tearJoules ?? TEAR_JOULES };
6664
+ };
6665
+ const tearAt = (o, p, kind, entry2, exit, d, E, scale, src) => {
6666
+ const { tear, joules } = tearOf(p.mat, kind);
6667
+ if (tear === "none") return;
6668
+ const shot = kind === "ballistic";
6669
+ const n = shot ? exit ? clamp22(Math.round(E / joules), 1, 7) : 1 : clamp22(Math.floor(E / joules), 0, 5);
6670
+ if (n === 0) return;
6671
+ const fibre = tear === "lumps";
6672
+ const { q } = partXf(o, p);
6673
+ const along = (p.spec.grain ? AXIS[p.spec.grain] : void 0) ?? [0, 1, 2].sort((a, b) => p.dims[b] - p.dims[a])[0];
6674
+ const fragments = [];
6675
+ for (let i = 0; i < n; i++) {
6676
+ const back = !exit || i % 3 === 2;
6677
+ 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());
6678
+ const size = [w, w, w];
6679
+ size[along] = len;
6680
+ size[(along + 1) % 3] = w;
6681
+ size[(along + 2) % 3] = t;
6682
+ const spread3 = [(rnd() - 0.5) * 0.8, (rnd() - 0.2) * 0.8, (rnd() - 0.5) * 0.8];
6683
+ const speed = shot ? back ? 1.5 + 2 * rnd() : 2 + 4 * rnd() : 0.8 + 1.2 * rnd();
6684
+ const v = vec.scale(vec.normalize(vec.add(back ? vec.scale(d, -1) : d, spread3)), speed);
6685
+ const mass = size[0] * size[1] * size[2] * p.mat.density;
6686
+ fragments.push({
6687
+ id: `${p.spec.id}#t${tears++}-${i}`,
6688
+ part: p.spec.id,
6689
+ material: p.spec.material,
6690
+ shape: fibre ? "chunk" : "splinter",
6691
+ center: back || !exit ? vec.madd(entry2, d, -0.01) : vec.madd(exit, d, 0.01),
6692
+ size,
6693
+ rotation: q,
6694
+ velocity: v,
6695
+ inner: true,
6696
+ angularVelocity: [(rnd() - 0.5) * 30, (rnd() - 0.5) * 30, (rnd() - 0.5) * 30],
6697
+ mass
6698
+ });
6699
+ }
6700
+ const patch = [scale * 4, scale * 4, scale * 4];
6701
+ emit({ ...base(o, src), type: "chip", part: p.spec.id, center: exit ?? entry2, size: patch, rotation: q, fragments });
6702
+ };
6371
6703
  const ballistic = (o, p, entry2, n, d, E, caliber, pathLen, src, round3 = {}) => {
6372
6704
  const mat = p.mat;
6373
6705
  const piercing = round3.piercing ?? 1;
@@ -6396,6 +6728,7 @@ var GameFloorplanStructure = (() => {
6396
6728
  return { penetrated: false, exitEnergy: 0, damage: dmg2 };
6397
6729
  }
6398
6730
  if (p.layers.length > 0) spallOuter(o, p, entry2, face, n, Math.max(0.03, caliber / 1e3 * 4), src);
6731
+ 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
6732
  const brittle = mat.ductility < 0.3;
6400
6733
  const ratio = Ep / Math.max(thr, 1e-6);
6401
6734
  const crackLen = clamp22(0.03 + ratio * 0.2, 0.03, 0.8);
@@ -6428,9 +6761,11 @@ var GameFloorplanStructure = (() => {
6428
6761
  if (mat.fracture !== "shatter") addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6429
6762
  } else addHole(o, p, entry2, n, caliber / 1e3, false, void 0, src);
6430
6763
  debris(o, p, mat.hitDebris, entry2, n, clamp22(Math.round(E / 60) + 2, 2, 14), clamp22(Math.sqrt(E) / 6, 1, 8), src);
6431
- 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);
6764
+ wearJoints(o, p, (penetrated ? thr : E) * 0.02, void 0, 0, src, true, BULLET_JOINT_SPAN);
6765
+ const absorbed = penetrated && mat.ductility >= 0.5 && mat.fracture !== "tear" ? Math.min(E, thr) : E;
6766
+ const dmg = applyDamage(o, p, absorbed * DMG_PER_J * scale, "ballistic", entry2, d, absorbed * 0.1, 12, src);
6767
+ if (!isLost(p)) tearAt(o, p, "ballistic", entry2, penetrated ? exitPoint : void 0, d, E, caliber / 1e3, src);
6768
+ 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
6769
  return { penetrated, exitEnergy: penetrated ? penMm !== void 0 ? E * (1 - thr / Ep) : E - thr / (hard ? piercing : 1) : 0, exitPoint, damage: dmg };
6435
6770
  };
6436
6771
  const melee = (o, p, kind, E, point, n, d, contactR, src) => {
@@ -6438,14 +6773,20 @@ var GameFloorplanStructure = (() => {
6438
6773
  const thrMm = mat.penetration * p.thickness * 1e3;
6439
6774
  const fragile = kind === "blunt" ? fragility(mat.ductility, p.thickness) : 1;
6440
6775
  const Ec = E * fragile;
6776
+ const sheet = mat.ductility >= 0.5 && p.thickness <= THIN_SHEET_CRUSH;
6777
+ const fold = sheet ? Math.min(4, Math.sqrt(1e-3 / p.thickness)) : 1;
6778
+ const patch = sheet ? Math.min(contactR, THIN_SHEET_CONTACT) : contactR;
6441
6779
  if (E > 0) {
6442
6780
  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);
6781
+ 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
6782
  else if (mat.ductility < 0.3) {
6445
6783
  if (Ec >= 5) addCrack(o, p, point, n, clamp22(0.04 + Math.min(Ec, E * 6) * 8e-4, 0.04, 0.6), src);
6446
6784
  } 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
6785
  } else if (mat.fracture === "tear") {
6448
6786
  addHole(o, p, point, n, clamp22(0.02 + E * 5e-4, 0.02, 0.3), true, void 0, src);
6787
+ } else if (mat.ductility >= 0.3 && E >= thrMm) {
6788
+ addHole(o, p, point, n, clamp22(contactR, 0.01, 0.08), true, void 0, src);
6789
+ 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
6790
  } else if (mat.ductility >= 0.5) {
6450
6791
  addDent(o, p, point, n, Math.max(contactR, 0.01), clamp22(E * 4e-5, 5e-4, 0.02), src);
6451
6792
  }
@@ -6458,7 +6799,8 @@ var GameFloorplanStructure = (() => {
6458
6799
  const scale = mat.damageScale?.[kind] ?? 1;
6459
6800
  const done = applyDamage(o, p, Ec * DMG_PER_J * scale * (kind === "slash" ? p.cutFactor : 1), kind, point, d, E * 0.1, 12, src);
6460
6801
  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);
6802
+ if (kind === "blunt") chipUnits(o, p, "blunt", point, E, d, Math.max(contactR, 0.03), src);
6803
+ if (E > 0 && !isLost(p)) tearAt(o, p, kind, point, void 0, d, E, clamp22(contactR * 0.2, 6e-3, 0.02), src);
6462
6804
  return done;
6463
6805
  };
6464
6806
  const blastPart = (o, p, point, energy, radius, src, res, reachAt) => {
@@ -7037,6 +7379,7 @@ var GameFloorplanStructure = (() => {
7037
7379
  if (p.rt.stressT <= 0) {
7038
7380
  p.rt.stressT = 0.5;
7039
7381
  emit({ ...base(o), type: "stress", part: p.spec.id, stress, load: p.st.load ?? 0 });
7382
+ if (p.spec.reinforcement && (p.st.stripped || p.st.chipped?.length || p.st.opening)) exposeRebar(o, p);
7040
7383
  }
7041
7384
  applyDamage(o, p, overloadRate(stress) * p.st.maxHp * dt, "blunt", null, null, 5, 3);
7042
7385
  } else if (stress > 0.8 && p.mat.ductility < 0.3 && p.rt.stressT <= 0 && p.st.cracks.length < 6) {
@@ -7253,6 +7596,18 @@ var GameFloorplanStructure = (() => {
7253
7596
  const r = raycastInternal(origin, direction, maxDistance);
7254
7597
  return r ? { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: r.distance } : null;
7255
7598
  },
7599
+ sweepBox(box2, direction, distance2, options2 = {}) {
7600
+ const d = vec.normalize(direction);
7601
+ const ignored = options2.ignore === void 0 ? void 0 : objects.get(options2.ignore);
7602
+ const ignore = ignored ? new Set(ignored.parts.map((p) => `${ignored.id}\0${p.spec.id}`)) : void 0;
7603
+ let best = null;
7604
+ for (const origin of sweepOrigins(box2, d, options2.spacing ?? 0.2)) {
7605
+ const r = raycastInternal(origin, d, distance2 + SWEEP_BACK, ignore);
7606
+ if (!r || best && r.distance >= best.distance) continue;
7607
+ best = { object: r.object, part: r.part, point: r.point, normal: r.normal, distance: Math.max(0, r.distance - SWEEP_BACK) };
7608
+ }
7609
+ return best;
7610
+ },
7256
7611
  shoot(origin, direction, energy, caliber, source, round3) {
7257
7612
  const extras = typeof round3 === "number" ? { piercing: round3 } : round3 ?? {};
7258
7613
  return withCollapses(run(() => {
@@ -8677,7 +9032,24 @@ var GameFloorplanStructure = (() => {
8677
9032
  }
8678
9033
  return spec;
8679
9034
  };
8680
- var partSpecOf = (part, capacity, detailed) => detailed && part.assembly ? assemblySpec(part, capacity) : boxSpec(part, capacity);
9035
+ var MEMBER_CAGE = { spacing: 0.1, diameter: 8e-3, unit: 0.1 };
9036
+ var CAGED_ROLES = /* @__PURE__ */ new Set(["beam", "column"]);
9037
+ var caged = (spec, part, rebar) => {
9038
+ if (!rebar || part.material !== "concrete" || !CAGED_ROLES.has(part.role) || spec.reinforcement) return spec;
9039
+ const size = spec.shape.kind === "box" ? spec.shape.size : null;
9040
+ if (!size) return spec;
9041
+ const thin = Math.min(...size);
9042
+ return {
9043
+ ...spec,
9044
+ reinforcement: { material: rebar, spacing: MEMBER_CAGE.spacing, diameter: MEMBER_CAGE.diameter },
9045
+ units: { size: [MEMBER_CAGE.unit, MEMBER_CAGE.unit, thin], bond: "stack", mortar: "concrete" }
9046
+ };
9047
+ };
9048
+ var partSpecOf = (part, capacity, detailed, rebar) => {
9049
+ if (detailed && part.assembly) return assemblySpec(part, capacity);
9050
+ const spec = boxSpec(part, capacity);
9051
+ return detailed ? caged(spec, part, rebar) : spec;
9052
+ };
8681
9053
  function toDestructibleSpec(structure, options = {}) {
8682
9054
  const { safety = 0.65, redundancy = 0.97, safetyOthers = 0.45 } = options;
8683
9055
  const kept = structure.parts.filter((part) => !options.filter || options.filter(part));
@@ -8694,7 +9066,7 @@ var GameFloorplanStructure = (() => {
8694
9066
  const sizing = { safety, redundancy, safetyOthers };
8695
9067
  const sized = { ...panels, capacities: options.sized === false ? /* @__PURE__ */ new Map() : sizeCapacities(panels.parts, panels.supports, sizing) };
8696
9068
  const { parts, supports, capacities } = detail === "units" ? applyUnits(sized, structure) : sized;
8697
- const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse"));
9069
+ const specs = parts.map((part) => partSpecOf(part, capacities.get(part.id), detail !== "coarse", structure.construction.frame.rebar));
8698
9070
  const margin = options.terrainMargin ?? TERRAIN_MARGIN;
8699
9071
  const x0 = Math.min(...parts.map((part) => part.aabb[0]));
8700
9072
  const z0 = Math.min(...parts.map((part) => part.aabb[1]));