@adatechnology/game-destruction-three 0.12.0 → 0.13.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.
@@ -55,6 +55,39 @@ var GameDestructionThree = (() => {
55
55
  const length = Math.hypot(direction[0], direction[1], direction[2]);
56
56
  return { speed, direction: [direction[0] / length, direction[1] / length, direction[2] / length], distance, falloff };
57
57
  }
58
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
59
+ var DEFERRED_QUEUE_ID = {
60
+ DESTRUCTION_EVENTS: "destruction.events",
61
+ BREACH_PAINT: "floorplan.breach-paint",
62
+ AUDIO_VOICES: "audio.voices",
63
+ VEHICLE_CREASE: "vehicles.crease"
64
+ };
65
+ var DEFERRED_PRIORITY = {
66
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
67
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
68
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
69
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
70
+ };
71
+ var WARNED_KEY = /* @__PURE__ */ Symbol.for("@adatechnology/game-contracts.warned");
72
+ var warnedKeys = () => {
73
+ const holder = globalThis;
74
+ return holder[WARNED_KEY] ?? (holder[WARNED_KEY] = /* @__PURE__ */ new Set());
75
+ };
76
+ var isProduction = () => {
77
+ try {
78
+ return process.env.NODE_ENV === "production";
79
+ } catch {
80
+ return false;
81
+ }
82
+ };
83
+ var isQuiet = () => globalThis.__GAME_KIT_QUIET__ === true;
84
+ var warnOnce = (key, message) => {
85
+ if (isQuiet() || isProduction()) return;
86
+ const seen = warnedKeys();
87
+ if (seen.has(key)) return;
88
+ seen.add(key);
89
+ console.warn(`[game-kit] ${message}`);
90
+ };
58
91
  var KIT_ORIGIN_KEY = "kitOrigin";
59
92
  function markKitOrigin(object, origin) {
60
93
  const node = object;
@@ -69,6 +102,43 @@ var GameDestructionThree = (() => {
69
102
  }
70
103
  return null;
71
104
  }
105
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
106
+ var GAIT_IDS = ["walk", "run"];
107
+ var LANDING_IDS = ["light", "heavy"];
108
+ var BODY_SOUND_IDS = [
109
+ "jump",
110
+ "cloth_rustle",
111
+ "body_fall",
112
+ "fall_damage",
113
+ "player_hurt",
114
+ "player_breath_run",
115
+ "player_death",
116
+ "bite",
117
+ "grab",
118
+ "punch_swing",
119
+ "punch_hit_body",
120
+ "punch_hit_face",
121
+ "punch_hit_object",
122
+ "kick_swing",
123
+ "kick_hit",
124
+ "kick_hit_object",
125
+ "shove_two_hands",
126
+ "knife_swing",
127
+ "knife_stab",
128
+ "knife_slash",
129
+ "knife_hit_object",
130
+ "blunt_swing",
131
+ "blunt_hit",
132
+ "blunt_hit_object",
133
+ "axe_swing",
134
+ "axe_chop",
135
+ "axe_hit_object"
136
+ ];
137
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
138
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
139
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing2) => `land_${surface}_${landing2}`));
140
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
141
+ var SWING_LEAD_SECONDS = { punch: 0.07, kick: 0.09, shove: 0, blade: 0.05, blunt: 0.1, chop: 0.11 };
72
142
 
73
143
  // ../game-vfx/dist/index.js
74
144
  var __defProp2 = Object.defineProperty;
@@ -1357,11 +1427,231 @@ var GameDestructionThree = (() => {
1357
1427
  glow.outIntensity = glow.i * Math.pow(1 - age, glow.curve) * flicker;
1358
1428
  }
1359
1429
  }
1430
+ var LEHMER_MULTIPLIER = 16807;
1431
+ var LEHMER_MODULUS = 2147483647;
1432
+ var HASH_MIX = 2654435761;
1433
+ function normalizeLehmerSeed(seed) {
1434
+ const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
1435
+ return whole === 0 ? 1 : whole;
1436
+ }
1437
+ function lehmer(seed) {
1438
+ let state = normalizeLehmerSeed(seed);
1439
+ const next = () => {
1440
+ state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
1441
+ return (state - 1) / (LEHMER_MODULUS - 1);
1442
+ };
1443
+ return {
1444
+ next,
1445
+ between: (min2, max2) => min2 + (max2 - min2) * next(),
1446
+ direction(out) {
1447
+ const up = next() * 2 - 1;
1448
+ const angle = next() * Math.PI * 2;
1449
+ const ring2 = Math.sqrt(1 - up * up);
1450
+ out.x = ring2 * Math.cos(angle);
1451
+ out.y = up;
1452
+ out.z = ring2 * Math.sin(angle);
1453
+ return out;
1454
+ },
1455
+ get state() {
1456
+ return state;
1457
+ }
1458
+ };
1459
+ }
1460
+ function mixEventSeed(eventSeed) {
1461
+ let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
1462
+ mixed ^= mixed >>> 13;
1463
+ mixed = Math.imul(mixed, 3266489909);
1464
+ mixed ^= mixed >>> 16;
1465
+ return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
1466
+ }
1467
+ function streamFor(eventSeed) {
1468
+ return lehmer(mixEventSeed(eventSeed));
1469
+ }
1470
+ var SKIN = 5e-3;
1471
+ var MAX_PAD = 0.45;
1472
+ function rotation(q) {
1473
+ const [x, y, z, w] = q ?? [0, 0, 0, 1];
1474
+ const n = Math.hypot(x, y, z, w) || 1;
1475
+ const qx = x / n;
1476
+ const qy = y / n;
1477
+ const qz = z / n;
1478
+ const qw = w / n;
1479
+ return new Float64Array([
1480
+ 1 - 2 * (qy * qy + qz * qz),
1481
+ 2 * (qx * qy - qz * qw),
1482
+ 2 * (qx * qz + qy * qw),
1483
+ 2 * (qx * qy + qz * qw),
1484
+ 1 - 2 * (qx * qx + qz * qz),
1485
+ 2 * (qy * qz - qx * qw),
1486
+ 2 * (qx * qz - qy * qw),
1487
+ 2 * (qy * qz + qx * qw),
1488
+ 1 - 2 * (qx * qx + qy * qy)
1489
+ ]);
1490
+ }
1491
+ var Enclosures = class {
1492
+ constructor(seed = 20973) {
1493
+ __publicField2(this, "spaces", /* @__PURE__ */ new Map());
1494
+ __publicField2(this, "list", []);
1495
+ __publicField2(this, "random");
1496
+ __publicField2(this, "out", { x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 });
1497
+ __publicField2(this, "l0", new Float64Array(3));
1498
+ __publicField2(this, "l1", new Float64Array(3));
1499
+ __publicField2(this, "q", new Float64Array(3));
1500
+ this.random = lehmer(seed);
1501
+ }
1502
+ get empty() {
1503
+ return this.list.length === 0;
1504
+ }
1505
+ get size() {
1506
+ return this.list.length;
1507
+ }
1508
+ /** Declares, moves or (with `null`) ends the space `id`. */
1509
+ set(id, enclosure) {
1510
+ if (!enclosure) {
1511
+ if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
1512
+ return;
1513
+ }
1514
+ const [hx, hy, hz] = enclosure.halfSize;
1515
+ const space = {
1516
+ cx: enclosure.center[0],
1517
+ cy: enclosure.center[1],
1518
+ cz: enclosure.center[2],
1519
+ hx: Math.abs(hx),
1520
+ hy: Math.abs(hy),
1521
+ hz: Math.abs(hz),
1522
+ r: rotation(enclosure.orientation),
1523
+ radius: Math.hypot(hx, hy, hz),
1524
+ openings: enclosure.openings ?? [],
1525
+ leak: Math.max(0, enclosure.leak ?? 0)
1526
+ };
1527
+ const old = this.spaces.get(id);
1528
+ this.spaces.set(id, space);
1529
+ const at3 = old ? this.list.indexOf(old) : -1;
1530
+ if (at3 >= 0) this.list[at3] = space;
1531
+ else this.list.push(space);
1532
+ }
1533
+ get(id) {
1534
+ const s = this.spaces.get(id);
1535
+ if (!s) return void 0;
1536
+ return { center: [s.cx, s.cy, s.cz], halfSize: [s.hx, s.hy, s.hz], openings: s.openings, leak: s.leak };
1537
+ }
1538
+ ids() {
1539
+ return [...this.spaces.keys()];
1540
+ }
1541
+ clear() {
1542
+ this.spaces.clear();
1543
+ this.list = [];
1544
+ }
1545
+ /** Whether a point is inside any space (on the inside of its walls). */
1546
+ inside(x, y, z) {
1547
+ for (const s of this.list) {
1548
+ this.toLocal(s, x, y, z, this.l0);
1549
+ if (Math.abs(this.l0[0]) <= s.hx && Math.abs(this.l0[1]) <= s.hy && Math.abs(this.l0[2]) <= s.hz) return true;
1550
+ }
1551
+ return false;
1552
+ }
1553
+ toLocal(s, x, y, z, out) {
1554
+ const dx = x - s.cx;
1555
+ const dy = y - s.cy;
1556
+ const dz = z - s.cz;
1557
+ const r = s.r;
1558
+ out[0] = r[0] * dx + r[3] * dy + r[6] * dz;
1559
+ out[1] = r[1] * dx + r[4] * dy + r[7] * dz;
1560
+ out[2] = r[2] * dx + r[5] * dy + r[8] * dz;
1561
+ }
1562
+ /** Passes at `rate` per second. */
1563
+ chance(rate, dt) {
1564
+ return rate > 0 && this.random.next() < 1 - Math.exp(-rate * dt);
1565
+ }
1566
+ /** The opening the wall point `q` (local) is in lets it through now. */
1567
+ through(s, q, dt) {
1568
+ for (const o of s.openings) {
1569
+ if (Math.abs(q[0] - o.center[0]) > o.halfSize[0] || Math.abs(q[1] - o.center[1]) > o.halfSize[1] || Math.abs(q[2] - o.center[2]) > o.halfSize[2]) continue;
1570
+ if (o.rate === void 0 || this.chance(o.rate, dt)) return true;
1571
+ }
1572
+ return this.chance(s.leak, dt);
1573
+ }
1574
+ confine(x0, y0, z0, x1, y1, z1, pad, dt) {
1575
+ const list = this.list;
1576
+ if (list.length === 0) return false;
1577
+ const p = Math.min(Math.max(0, pad), MAX_PAD);
1578
+ const l0 = this.l0;
1579
+ const l1 = this.l1;
1580
+ const q = this.q;
1581
+ for (const s of list) {
1582
+ const reach = s.radius + p + 0.05;
1583
+ const dx = x1 - s.cx;
1584
+ const dy = y1 - s.cy;
1585
+ const dz = z1 - s.cz;
1586
+ if (dx * dx + dy * dy + dz * dz > reach * reach) {
1587
+ const ex = x0 - s.cx;
1588
+ const ey = y0 - s.cy;
1589
+ const ez = z0 - s.cz;
1590
+ if (ex * ex + ey * ey + ez * ez > reach * reach) continue;
1591
+ }
1592
+ this.toLocal(s, x0, y0, z0, l0);
1593
+ this.toLocal(s, x1, y1, z1, l1);
1594
+ const h = [s.hx, s.hy, s.hz];
1595
+ const in0 = Math.abs(l0[0]) <= s.hx && Math.abs(l0[1]) <= s.hy && Math.abs(l0[2]) <= s.hz;
1596
+ let axis2 = 0;
1597
+ let sign = 1;
1598
+ if (in0) {
1599
+ if (Math.abs(l1[0]) <= s.hx && Math.abs(l1[1]) <= s.hy && Math.abs(l1[2]) <= s.hz) continue;
1600
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1601
+ if (this.through(s, q, dt)) continue;
1602
+ let worst = -Infinity;
1603
+ for (let a = 0; a < 3; a++) {
1604
+ const over = Math.abs(l1[a]) - h[a];
1605
+ if (over > worst) {
1606
+ worst = over;
1607
+ axis2 = a;
1608
+ }
1609
+ }
1610
+ sign = l1[axis2] < 0 ? -1 : 1;
1611
+ for (let a = 0; a < 3; a++) l1[a] = Math.min(h[a] - SKIN, Math.max(-(h[a] - SKIN), l1[a]));
1612
+ sign = -sign;
1613
+ } else {
1614
+ const padded0 = Math.abs(l0[0]) <= s.hx + p && Math.abs(l0[1]) <= s.hy + p && Math.abs(l0[2]) <= s.hz + p;
1615
+ const bound = padded0 ? 0 : p;
1616
+ if (!(Math.abs(l1[0]) <= s.hx + bound && Math.abs(l1[1]) <= s.hy + bound && Math.abs(l1[2]) <= s.hz + bound)) continue;
1617
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1618
+ if (this.through(s, q, dt)) continue;
1619
+ let worst = -Infinity;
1620
+ for (let a = 0; a < 3; a++) {
1621
+ const over = Math.abs(l0[a]) - (h[a] + bound);
1622
+ if (over > worst) {
1623
+ worst = over;
1624
+ axis2 = a;
1625
+ }
1626
+ }
1627
+ sign = l0[axis2] < 0 ? -1 : 1;
1628
+ l1[axis2] = sign * (h[axis2] + bound + SKIN);
1629
+ }
1630
+ const r = s.r;
1631
+ const out = this.out;
1632
+ out.x = s.cx + r[0] * l1[0] + r[1] * l1[1] + r[2] * l1[2];
1633
+ out.y = s.cy + r[3] * l1[0] + r[4] * l1[1] + r[5] * l1[2];
1634
+ out.z = s.cz + r[6] * l1[0] + r[7] * l1[1] + r[8] * l1[2];
1635
+ out.nx = r[axis2] * sign;
1636
+ out.ny = r[3 + axis2] * sign;
1637
+ out.nz = r[6 + axis2] * sign;
1638
+ return true;
1639
+ }
1640
+ return false;
1641
+ }
1642
+ };
1643
+ function slideAlong(out, vx2, vy2, vz2) {
1644
+ const into = vx2 * out.nx + vy2 * out.ny + vz2 * out.nz;
1645
+ if (into >= 0) return [vx2, vy2, vz2];
1646
+ return [vx2 - into * out.nx, vy2 - into * out.ny, vz2 - into * out.nz];
1647
+ }
1360
1648
  var FLOOR_FRACTION = 0.4;
1361
1649
  var FLOOR_BOUNCE = -0.2;
1362
1650
  var ARRIVAL_FRAME = 30;
1363
1651
  var ARRIVAL_BASE = 0.3;
1364
1652
  var GROWTH_CAP_SECONDS = 3.5;
1653
+ var CARRIED_KINDS = [true, true, false, true, false];
1654
+ var PAD_OF_SIZE = 0.35;
1365
1655
  function createPuffPool(capacity) {
1366
1656
  const placeholder = { next: () => 0, between: (min2) => min2, direction: (out) => out, state: 0 };
1367
1657
  return new OrderedPool(capacity, () => ({
@@ -1456,13 +1746,24 @@ var GameDestructionThree = (() => {
1456
1746
  if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
1457
1747
  }
1458
1748
  }
1459
- function updatePuffs(pool, dt, wind) {
1749
+ function updatePuffs(pool, dt, wind, confiner) {
1460
1750
  pool.compact(ageOrKeep2, dt);
1751
+ const confining = confiner !== void 0 && !confiner.empty;
1461
1752
  for (const puff3 of pool.items) {
1462
1753
  const age = puff3.t / puff3.life;
1463
1754
  const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
1755
+ const x0 = puff3.x;
1756
+ const y0 = puff3.y;
1757
+ const z0 = puff3.z;
1464
1758
  if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
1465
1759
  else moveFree(puff3, dt, age, heat, wind);
1760
+ if (confining && CARRIED_KINDS[puff3.kind] && confiner.confine(x0, y0, z0, puff3.x, puff3.y, puff3.z, puff3.size * PAD_OF_SIZE, dt)) {
1761
+ const out = confiner.out;
1762
+ puff3.x = out.x;
1763
+ puff3.y = out.y;
1764
+ puff3.z = out.z;
1765
+ [puff3.vx, puff3.vy, puff3.vz] = slideAlong(out, puff3.vx, puff3.vy, puff3.vz);
1766
+ }
1466
1767
  const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
1467
1768
  puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1468
1769
  puff3.age = age;
@@ -1988,10 +2289,10 @@ var GameDestructionThree = (() => {
1988
2289
  ])
1989
2290
  ]
1990
2291
  };
1991
- function localToWorld(translation, rotation, local2) {
2292
+ function localToWorld(translation, rotation2, local2) {
1992
2293
  const [x, y, z] = local2;
1993
- const [a, c, e] = [Math.cos(rotation[0]), Math.cos(rotation[1]), Math.cos(rotation[2])];
1994
- const [b, d, f] = [Math.sin(rotation[0]), Math.sin(rotation[1]), Math.sin(rotation[2])];
2294
+ const [a, c, e] = [Math.cos(rotation2[0]), Math.cos(rotation2[1]), Math.cos(rotation2[2])];
2295
+ const [b, d, f] = [Math.sin(rotation2[0]), Math.sin(rotation2[1]), Math.sin(rotation2[2])];
1995
2296
  const [ae, af, be, bf] = [a * e, a * f, b * e, b * f];
1996
2297
  return [
1997
2298
  c * e * x + -c * f * y + d * z + translation[0],
@@ -2333,22 +2634,22 @@ var GameDestructionThree = (() => {
2333
2634
  )
2334
2635
  ]
2335
2636
  };
2336
- function normalized(vector) {
2337
- const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]) || 1;
2637
+ function normalized(vector2) {
2638
+ const length = Math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1] + vector2[2] * vector2[2]) || 1;
2338
2639
  const inverse = 1 / length;
2339
- return [vector[0] * inverse, vector[1] * inverse, vector[2] * inverse];
2640
+ return [vector2[0] * inverse, vector2[1] * inverse, vector2[2] * inverse];
2340
2641
  }
2341
- function plus(vector, x, y, z) {
2342
- return [vector[0] + x, vector[1] + y, vector[2] + z];
2642
+ function plus(vector2, x, y, z) {
2643
+ return [vector2[0] + x, vector2[1] + y, vector2[2] + z];
2343
2644
  }
2344
- function scaledBy(vector, factor) {
2345
- return [vector[0] * factor, vector[1] * factor, vector[2] * factor];
2645
+ function scaledBy(vector2, factor) {
2646
+ return [vector2[0] * factor, vector2[1] * factor, vector2[2] * factor];
2346
2647
  }
2347
- function plusScaled(vector, direction, factor) {
2348
- return [vector[0] + direction[0] * factor, vector[1] + direction[1] * factor, vector[2] + direction[2] * factor];
2648
+ function plusScaled(vector2, direction, factor) {
2649
+ return [vector2[0] + direction[0] * factor, vector2[1] + direction[1] * factor, vector2[2] + direction[2] * factor];
2349
2650
  }
2350
- function negated(vector) {
2351
- return [-vector[0], -vector[1], -vector[2]];
2651
+ function negated(vector2) {
2652
+ return [-vector2[0], -vector2[1], -vector2[2]];
2352
2653
  }
2353
2654
  var FRAME_DUST = normalized([0, -0.3, 1]);
2354
2655
  var SPLINTER_DIRECTION = normalized([0, 0.3, 1]);
@@ -3651,49 +3952,32 @@ var GameDestructionThree = (() => {
3651
3952
  /** The leak caught fire (an incendiary or tracer round, a spark, a fire near): the page's whole sequence, the fire running along the pool to the tank and the blast. */
3652
3953
  aliasOf(FUEL_LEAK, "vehicle_leak_ignited", {})
3653
3954
  ];
3955
+ var vector = [variable("vx"), variable("vy"), variable("vz")];
3956
+ var range = (name) => [variable(name), variable(name)];
3957
+ var singleParticle = (id, system) => ({
3958
+ id,
3959
+ pageId: `particle:${id}`,
3960
+ team: "world",
3961
+ budget: { particles: 1, lights: 0 },
3962
+ loop: 1e-3,
3963
+ timeline: [
3964
+ spawn(system, {
3965
+ p: [0, 0, 0],
3966
+ v: vector,
3967
+ life: range("life"),
3968
+ size: range("size"),
3969
+ grow: variable("grow"),
3970
+ c0: variable("color"),
3971
+ a: variable("alpha"),
3972
+ g: variable("gravity"),
3973
+ drag: variable("drag")
3974
+ })
3975
+ ]
3976
+ });
3977
+ var SINGLE_PARTICLE_EFFECTS = [singleParticle("particle_puff", "normal"), singleParticle("particle_glow", "add"), singleParticle("particle_streak", "streak")];
3654
3978
  var ALL_EFFECTS = [BLOOD_HIT, HEADSHOT, DISMEMBER, BLOATER_CLOUD, SCREAMER_SHRIEK, CORPSE_DECAY, REANIMATION, EYES_IN_FOG, MUZZLE_FLASH, IMPACT_BY_MATERIAL, PENETRATION, CASINGS_AND_MAGAZINE, MOLOTOV, GRENADE, EXPLOSIVE_BARREL, SHOPFRONT_GLASS, DOOR_STAGES, BUILDING_COLLAPSE, BLOCK_WALL, METAL_DOOR_EFFECT, IRON_RAILING, WOODEN_FENCE, HIT_POINT_BREAKAGE, PRESSURE_CYLINDERS, BUILDING_FIRE, RAIN_AND_PUDDLES, FOG_AND_ASH, SCRAPING_SPARKS, ENGINE_SMOKE, FUEL_LEAK, CAR_EXPLOSION, WINDSHIELD, HEADLIGHTS_IN_RAIN, DAMAGE_AND_BITE, INFECTION_PHASES, PANIC, BLOATER_BLINDNESS, CAMPFIRE, COUGH_AND_BREATH];
3655
3979
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
3656
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3657
- var LEHMER_MULTIPLIER = 16807;
3658
- var LEHMER_MODULUS = 2147483647;
3659
- var HASH_MIX = 2654435761;
3660
- function normalizeLehmerSeed(seed) {
3661
- const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
3662
- return whole === 0 ? 1 : whole;
3663
- }
3664
- function lehmer(seed) {
3665
- let state = normalizeLehmerSeed(seed);
3666
- const next = () => {
3667
- state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
3668
- return (state - 1) / (LEHMER_MODULUS - 1);
3669
- };
3670
- return {
3671
- next,
3672
- between: (min2, max2) => min2 + (max2 - min2) * next(),
3673
- direction(out) {
3674
- const up = next() * 2 - 1;
3675
- const angle = next() * Math.PI * 2;
3676
- const ring2 = Math.sqrt(1 - up * up);
3677
- out.x = ring2 * Math.cos(angle);
3678
- out.y = up;
3679
- out.z = ring2 * Math.sin(angle);
3680
- return out;
3681
- },
3682
- get state() {
3683
- return state;
3684
- }
3685
- };
3686
- }
3687
- function mixEventSeed(eventSeed) {
3688
- let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
3689
- mixed ^= mixed >>> 13;
3690
- mixed = Math.imul(mixed, 3266489909);
3691
- mixed ^= mixed >>> 16;
3692
- return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
3693
- }
3694
- function streamFor(eventSeed) {
3695
- return lehmer(mixEventSeed(eventSeed));
3696
- }
3980
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS, ...SINGLE_PARTICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3697
3981
  var TABLE = {
3698
3982
  concrete: { dust: 9078138, chip: 7236195, spark: 2 },
3699
3983
  metal: { dust: 0, chip: 0, spark: 18 },
@@ -4363,8 +4647,8 @@ var GameDestructionThree = (() => {
4363
4647
  var UP12 = [0, 1, 0];
4364
4648
  var ZERO = [0, 0, 0];
4365
4649
  var FULL_TURN_DRAWN = 6.283;
4366
- function scaledSizes(range, factor) {
4367
- return [range[0] * Math.sqrt(factor), range[1] * Math.sqrt(factor)];
4650
+ function scaledSizes(range2, factor) {
4651
+ return [range2[0] * Math.sqrt(factor), range2[1] * Math.sqrt(factor)];
4368
4652
  }
4369
4653
  function fireEmit(engine, key, dt, centre, radius, factor, options = {}) {
4370
4654
  const wind = options.wind ?? ZERO;
@@ -4997,8 +5281,8 @@ var GameDestructionThree = (() => {
4997
5281
  function addScorch(store, random, x, z, radius) {
4998
5282
  const variant = Math.floor(random.next() * SCORCH_VARIANTS);
4999
5283
  const lift = 0.03 + nextLift(store) * 1e-3;
5000
- const rotation = random.next() * 6;
5001
- store.add({ kind: "scorch", x, z, radius, variant, lift, rotation });
5284
+ const rotation2 = random.next() * 6;
5285
+ store.add({ kind: "scorch", x, z, radius, variant, lift, rotation: rotation2 });
5002
5286
  }
5003
5287
  function addRimRocks(store, ground, random, x, z, radius) {
5004
5288
  const count = Math.round(6 + radius * 7);
@@ -5027,8 +5311,8 @@ var GameDestructionThree = (() => {
5027
5311
  function addCrater(store, ground, random, spec) {
5028
5312
  ground?.dent(random, spec.x, spec.z, spec.radius, spec.depth);
5029
5313
  if (!spec.marks) return;
5030
- const rotation = random.next() * 6;
5031
- store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation });
5314
+ const rotation2 = random.next() * 6;
5315
+ store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation: rotation2 });
5032
5316
  addRimRocks(store, ground, random, spec.x, spec.z, spec.radius);
5033
5317
  }
5034
5318
  function addBloodSplat(store, ground, random, spec) {
@@ -5584,6 +5868,7 @@ var GameDestructionThree = (() => {
5584
5868
  state.gas = Math.max(0, state.gas - dt / DECAY.gasSeconds);
5585
5869
  state.beat *= Math.exp(-DECAY.beat * dt);
5586
5870
  }
5871
+ var PAD_OF_SIZE2 = 0.35;
5587
5872
  var clamp4 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
5588
5873
  var lerp4 = (a, b, t) => a + (b - a) * t;
5589
5874
  function rgb(hex) {
@@ -5743,9 +6028,20 @@ var GameDestructionThree = (() => {
5743
6028
  vy2 *= k;
5744
6029
  vz2 *= k;
5745
6030
  }
5746
- const x = this.position[i3] + vx2 * dt;
6031
+ let x = this.position[i3] + vx2 * dt;
5747
6032
  let y = this.position[i3 + 1] + vy2 * dt;
5748
- const z = this.position[i3 + 2] + vz2 * dt;
6033
+ let z = this.position[i3 + 2] + vz2 * dt;
6034
+ const confiner = host.confiner;
6035
+ if (def.carried && confiner && !confiner.empty) {
6036
+ const pad = this.size0[i] * lerp4(1, def.grow, t) * PAD_OF_SIZE2;
6037
+ if (confiner.confine(this.position[i3], this.position[i3 + 1], this.position[i3 + 2], x, y, z, pad, dt)) {
6038
+ const out = confiner.out;
6039
+ x = out.x;
6040
+ y = out.y;
6041
+ z = out.z;
6042
+ [vx2, vy2, vz2] = slideAlong(out, vx2, vy2, vz2);
6043
+ }
6044
+ }
5749
6045
  if (def.ground !== "none") {
5750
6046
  const own = this.floor[i];
5751
6047
  const gy = Number.isNaN(own) ? host.groundAt(x, z) + 5e-3 : own;
@@ -5831,7 +6127,8 @@ var GameDestructionThree = (() => {
5831
6127
  alpha: 0.35,
5832
6128
  fadeIn: 0.15,
5833
6129
  cone: 0.6,
5834
- ground: "none"
6130
+ ground: "none",
6131
+ carried: true
5835
6132
  },
5836
6133
  sparks: {
5837
6134
  id: "sparks",
@@ -5875,7 +6172,8 @@ var GameDestructionThree = (() => {
5875
6172
  alpha: 0.4,
5876
6173
  fadeIn: 0.1,
5877
6174
  cone: 0.4,
5878
- ground: "none"
6175
+ ground: "none",
6176
+ carried: true
5879
6177
  },
5880
6178
  shards: {
5881
6179
  id: "shards",
@@ -6033,7 +6331,8 @@ var GameDestructionThree = (() => {
6033
6331
  fadeIn: 0.1,
6034
6332
  cone: 0.5,
6035
6333
  ground: "stop",
6036
- flutter: 0.6
6334
+ flutter: 0.6,
6335
+ carried: true
6037
6336
  },
6038
6337
  steam: {
6039
6338
  id: "steam",
@@ -6055,7 +6354,8 @@ var GameDestructionThree = (() => {
6055
6354
  alpha: 0.35,
6056
6355
  fadeIn: 0.15,
6057
6356
  cone: 0.6,
6058
- ground: "none"
6357
+ ground: "none",
6358
+ carried: true
6059
6359
  },
6060
6360
  flash: {
6061
6361
  id: "flash",
@@ -6188,7 +6488,8 @@ var GameDestructionThree = (() => {
6188
6488
  const host = {
6189
6489
  groundAt: this.options.groundAt,
6190
6490
  gravity: this.options.gravity,
6191
- land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag)
6491
+ land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag),
6492
+ ...this.options.confiner ? { confiner: this.options.confiner } : {}
6192
6493
  };
6193
6494
  if (this.additive.live > 0 || this.additive.drawCount > 0) this.additive.update(dt, host);
6194
6495
  if (this.normal.live > 0 || this.normal.drawCount > 0) this.normal.update(dt, host);
@@ -6201,9 +6502,9 @@ var GameDestructionThree = (() => {
6201
6502
  var TWO_PI_ROUNDED = 6.2832;
6202
6503
  var WIDE_CONE = 3.1;
6203
6504
  var UP13 = [0, 1, 0];
6204
- function drawRange(random, range) {
6205
- if (Array.isArray(range)) return random.between(range[0], range[1]);
6206
- return range || 0;
6505
+ function drawRange(random, range2) {
6506
+ if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
6507
+ return range2 || 0;
6207
6508
  }
6208
6509
  function randomUnit(random, out) {
6209
6510
  const up = random.next() * 2 - 1;
@@ -6394,8 +6695,8 @@ var GameDestructionThree = (() => {
6394
6695
  * Takes away the marks inside a box: world `center`, `half` extents along the axes of the world `rotation` (a quaternion, x y z w; none means the
6395
6696
  * world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
6396
6697
  */
6397
- clearInside(center, half, rotation = [0, 0, 0, 1]) {
6398
- const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
6698
+ clearInside(center, half, rotation2 = [0, 0, 0, 1]) {
6699
+ const qx = -rotation2[0], qy = -rotation2[1], qz = -rotation2[2], qw = rotation2[3];
6399
6700
  let gone = 0;
6400
6701
  for (const decal2 of this.decals) {
6401
6702
  if (!decal2.on) continue;
@@ -6516,10 +6817,14 @@ var GameDestructionThree = (() => {
6516
6817
  var DEFAULT_FADE_IN = 0.1;
6517
6818
  var FLOATS_PER_VECTOR = 3;
6518
6819
  var BYTE_MAX2 = 255;
6820
+ var CARRIED_GRAVITY = 3;
6821
+ var PAD_OF_SIZE3 = 0.35;
6519
6822
  var SpritePool = class {
6520
- constructor(capacity, context) {
6823
+ /** `carried`: the normal-blend pool (smoke, dust, steam), whose light sprites go round the closed spaces; the additive one (flames, sparks) does not. */
6824
+ constructor(capacity, context, carried = false) {
6521
6825
  __publicField2(this, "capacity", capacity);
6522
6826
  __publicField2(this, "context", context);
6827
+ __publicField2(this, "carried", carried);
6523
6828
  __publicField2(this, "count", 0);
6524
6829
  __publicField2(this, "position");
6525
6830
  __publicField2(this, "velocity");
@@ -6619,6 +6924,7 @@ var GameDestructionThree = (() => {
6619
6924
  }
6620
6925
  update(dt, leaveDecal) {
6621
6926
  const { position, velocity } = this;
6927
+ const confiner = this.context.confiner;
6622
6928
  for (let index = this.count - 1; index >= 0; index--) {
6623
6929
  this.life[index] -= dt;
6624
6930
  if (this.life[index] <= 0) {
@@ -6632,9 +6938,25 @@ var GameDestructionThree = (() => {
6632
6938
  velocity[offset] *= factor;
6633
6939
  velocity[offset + 1] *= factor;
6634
6940
  velocity[offset + 2] *= factor;
6941
+ const x0 = position[offset];
6942
+ const y0 = position[offset + 1];
6943
+ const z0 = position[offset + 2];
6635
6944
  position[offset] += velocity[offset] * dt;
6636
6945
  position[offset + 1] += velocity[offset + 1] * dt;
6637
6946
  position[offset + 2] += velocity[offset + 2] * dt;
6947
+ if (this.carried && confiner && !confiner.empty && Math.abs(this.gravity[index]) < CARRIED_GRAVITY) {
6948
+ const size = this.startSize[index] * (1 + (this.growth[index] - 1) * age);
6949
+ if (confiner.confine(x0, y0, z0, position[offset], position[offset + 1], position[offset + 2], size * PAD_OF_SIZE3, dt)) {
6950
+ const out = confiner.out;
6951
+ position[offset] = out.x;
6952
+ position[offset + 1] = out.y;
6953
+ position[offset + 2] = out.z;
6954
+ const [vx2, vy2, vz2] = slideAlong(out, velocity[offset], velocity[offset + 1], velocity[offset + 2]);
6955
+ velocity[offset] = vx2;
6956
+ velocity[offset + 1] = vy2;
6957
+ velocity[offset + 2] = vz2;
6958
+ }
6959
+ }
6638
6960
  const mode = this.groundMode[index];
6639
6961
  if (mode) {
6640
6962
  const groundHeight = this.context.groundAt(position[offset], position[offset + 2]);
@@ -6911,9 +7233,9 @@ var GameDestructionThree = (() => {
6911
7233
  __publicField2(this, "placement");
6912
7234
  __publicField2(this, "accumulators", /* @__PURE__ */ new Map());
6913
7235
  const profile = options.profile ?? PROFILES.high;
6914
- this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace };
7236
+ this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace, ...options.confiner ? { confiner: options.confiner } : {} };
6915
7237
  this.add = new SpritePool(profile.sprites.add, this.context);
6916
- this.normal = new SpritePool(profile.sprites.normal, this.context);
7238
+ this.normal = new SpritePool(profile.sprites.normal, this.context, true);
6917
7239
  this.streaks = new StreakPool(profile.streaks, this.context);
6918
7240
  this.debris = new DebrisPool(this.context);
6919
7241
  this.decals = new DecalPool(this.context);
@@ -6949,20 +7271,20 @@ var GameDestructionThree = (() => {
6949
7271
  return [origin[0] + cos2 * x + sin2 * z, origin[1] + point[1] - anchor[1], origin[2] - sin2 * x + cos2 * z];
6950
7272
  }
6951
7273
  /** A direction or velocity of the recipe's space in the world. */
6952
- turn(vector) {
7274
+ turn(vector2) {
6953
7275
  const placement = this.placement;
6954
- if (!placement) return vector;
6955
- if (placement.orientation) return rotate(placement.orientation, vector);
7276
+ if (!placement) return vector2;
7277
+ if (placement.orientation) return rotate(placement.orientation, vector2);
6956
7278
  const cos2 = Math.cos(placement.yaw);
6957
7279
  const sin2 = Math.sin(placement.yaw);
6958
- return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
7280
+ return [cos2 * vector2[0] + sin2 * vector2[2], vector2[1], -sin2 * vector2[0] + cos2 * vector2[2]];
6959
7281
  }
6960
7282
  placed(options) {
6961
7283
  if (!this.placement) return options;
6962
7284
  return { ...options, p: this.place(options.p), ...options.dir ? { dir: this.turn(options.dir) } : {}, ...options.v ? { v: this.turn(options.v) } : {} };
6963
7285
  }
6964
- spawn(system, recipe) {
6965
- const options = this.placed(recipe);
7286
+ spawn(system, recipe2) {
7287
+ const options = this.placed(recipe2);
6966
7288
  this.context.trace?.(system, [options]);
6967
7289
  if (system === "add") this.add.spawn(options);
6968
7290
  else if (system === "normal") this.normal.spawn(options);
@@ -6982,43 +7304,43 @@ var GameDestructionThree = (() => {
6982
7304
  this.accumulators.set(key, carried);
6983
7305
  }
6984
7306
  /** A solid particle (a cone, a sphere): the group sits at `p` turned by the placement, `v` is turned with it and `wv` is not. */
6985
- mesh(recipe) {
7307
+ mesh(recipe2) {
6986
7308
  const placement = this.placement;
6987
- const options = placement ? { ...recipe, p: this.place(recipe.p), ...recipe.v ? { v: this.turn(recipe.v) } : {} } : recipe;
7309
+ const options = placement ? { ...recipe2, p: this.place(recipe2.p), ...recipe2.v ? { v: this.turn(recipe2.v) } : {} } : recipe2;
6988
7310
  this.context.trace?.("mesh", [options]);
6989
- const grouped = recipe.at !== void 0 || recipe.roll !== void 0;
7311
+ const grouped = recipe2.at !== void 0 || recipe2.roll !== void 0;
6990
7312
  return this.meshes.spawn(options, placement && grouped ? placement.orientation ?? [0, Math.sin(placement.yaw / 2), 0, Math.cos(placement.yaw / 2)] : void 0);
6991
7313
  }
6992
- debrisPiece(recipe) {
6993
- const options = this.placed(recipe);
7314
+ debrisPiece(recipe2) {
7315
+ const options = this.placed(recipe2);
6994
7316
  this.context.trace?.("debris", [options]);
6995
7317
  this.debris.spawn(options);
6996
7318
  }
6997
- decal(recipe) {
6998
- let args = recipe;
7319
+ decal(recipe2) {
7320
+ let args = recipe2;
6999
7321
  if (this.placement) {
7000
- const [x, y, z] = this.place([recipe[0], recipe[1], recipe[2]]);
7001
- const options = recipe[5];
7002
- args = [x, y, z, recipe[3], recipe[4], options?.n ? { ...options, n: this.turn(options.n) } : options];
7322
+ const [x, y, z] = this.place([recipe2[0], recipe2[1], recipe2[2]]);
7323
+ const options = recipe2[5];
7324
+ args = [x, y, z, recipe2[3], recipe2[4], options?.n ? { ...options, n: this.turn(options.n) } : options];
7003
7325
  }
7004
7326
  this.context.trace?.("decal", args);
7005
7327
  this.decals.add(args[0], args[1], args[2], args[3], args[4], args[5]);
7006
7328
  }
7007
- ring(recipe) {
7008
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5] && this.turn(recipe[5]), recipe[6]] : recipe;
7329
+ ring(recipe2) {
7330
+ const args = this.placement ? [this.place(recipe2[0]), recipe2[1], recipe2[2], recipe2[3], recipe2[4], recipe2[5] && this.turn(recipe2[5]), recipe2[6]] : recipe2;
7009
7331
  this.context.trace?.("ring", args);
7010
7332
  this.rings.add(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
7011
7333
  }
7012
7334
  /** `steady` (a seventh number): the light holds its intensity for its duration and goes out, where the Workshop's fades with the square of its age. */
7013
- flash(recipe) {
7014
- const args = this.placement ? [this.place(recipe[0]), recipe[1], recipe[2], recipe[3], recipe[4], recipe[5], recipe[6]] : recipe;
7335
+ flash(recipe2) {
7336
+ const args = this.placement ? [this.place(recipe2[0]), recipe2[1], recipe2[2], recipe2[3], recipe2[4], recipe2[5], recipe2[6]] : recipe2;
7015
7337
  this.context.trace?.("flash", args.slice(0, 6));
7016
7338
  this.lights.flash(args[0], args[1], args[2], args[3], args[4], args[5], args[6]);
7017
7339
  }
7018
7340
  /** `fireLight` (W400): sways with the clock and flickers with one draw. */
7019
- fireLight(slot, recipe, intensity, time) {
7341
+ fireLight(slot, recipe2, intensity, time) {
7020
7342
  const light = this.lights.fire[slot];
7021
- const point = this.place(recipe);
7343
+ const point = this.place(recipe2);
7022
7344
  light.x = point[0];
7023
7345
  light.y = point[1];
7024
7346
  light.z = point[2];
@@ -7101,12 +7423,14 @@ var GameDestructionThree = (() => {
7101
7423
  const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
7102
7424
  const handles = { next: 0, events: 0 };
7103
7425
  const low = options.profile === "low";
7426
+ const enclosures = new Enclosures(20973 + (options.seed ?? 0));
7104
7427
  const spriteSystem = new SpriteKindSystem({
7105
7428
  capacity: options.sprites?.capacity ?? (low ? 1250 : 2500),
7106
7429
  density: options.sprites?.density ?? (low ? 0.6 : 1),
7107
7430
  seed: 2654435761 + (options.seed ?? 0),
7108
7431
  groundAt,
7109
7432
  gravity: options.gravity ?? DEFAULT_GRAVITY,
7433
+ confiner: enclosures,
7110
7434
  ...options.sprites?.kinds ? { kinds: options.sprites.kinds } : {}
7111
7435
  });
7112
7436
  const core = {
@@ -7156,7 +7480,7 @@ var GameDestructionThree = (() => {
7156
7480
  return [camera2.x, camera2.y, camera2.z];
7157
7481
  }, forward: (local2) => [local2[0] ?? 0, local2[1] ?? 0, local2[2] ?? 0] };
7158
7482
  function workshop() {
7159
- return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt }));
7483
+ return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt, confiner: enclosures }));
7160
7484
  }
7161
7485
  function effectPlayer() {
7162
7486
  return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
@@ -7215,6 +7539,10 @@ var GameDestructionThree = (() => {
7215
7539
  puff3.vz += dz * strength;
7216
7540
  }
7217
7541
  },
7542
+ enclose(id, enclosure) {
7543
+ enclosures.set(id, enclosure);
7544
+ },
7545
+ enclosures: () => enclosures,
7218
7546
  stop(handle) {
7219
7547
  player?.stop(handle);
7220
7548
  const fires = core.fires;
@@ -7242,7 +7570,7 @@ var GameDestructionThree = (() => {
7242
7570
  core.clock.time += dt;
7243
7571
  core.schedule.update(core.clock.time);
7244
7572
  updateRings(core.rings, dt);
7245
- updatePuffs(core.puffs, dt, wind);
7573
+ updatePuffs(core.puffs, dt, wind, enclosures);
7246
7574
  updateSparks(core.sparks, { dt, groundAt });
7247
7575
  updateGlows(core.glows, dt);
7248
7576
  updateShocks(core.shocks, core.puffs.items, dt);
@@ -7326,7 +7654,22 @@ var GameDestructionThree = (() => {
7326
7654
  return key === void 0 ? void 0 : entry[key];
7327
7655
  }
7328
7656
 
7329
- // ../game-vfx-three/dist/index.js
7657
+ // ../game-render-three/dist/index.js
7658
+ var DEFAULT_ANGLE = Math.PI / 3;
7659
+ var LIGHT_PRIORITY = {
7660
+ blast: 900,
7661
+ rocket: 800,
7662
+ muzzle: 750,
7663
+ fire: 600,
7664
+ flare: 500,
7665
+ headlight: 400,
7666
+ weaponLight: 400,
7667
+ lamp: 200,
7668
+ street: 150
7669
+ };
7670
+ var EFFECT_STEP_SECONDS = 1 / 50;
7671
+
7672
+ // ../game-vfx-three/dist/chunk-ALDCQ5RH.js
7330
7673
  var GLOW_VERTEX = `attribute vec4 aP;attribute vec4 aC;varying vec2 vU;varying vec4 vC;
7331
7674
  void main(){vec3 R=vec3(viewMatrix[0][0],viewMatrix[1][0],viewMatrix[2][0]);vec3 Up=vec3(viewMatrix[0][1],viewMatrix[1][1],viewMatrix[2][1]);
7332
7675
  vec3 wp=aP.xyz+(R*position.x+Up*position.y)*aP.w;vU=position.xy;vC=aC;gl_Position=projectionMatrix*viewMatrix*vec4(wp,1.);}`;
@@ -7605,8 +7948,26 @@ void main(){
7605
7948
  }
7606
7949
  };
7607
7950
  }
7608
- function createLightLayer(THREE, pool, scale) {
7951
+ var LIGHT_ON_THRESHOLD = 0.05;
7952
+ var FIRE_COLOR = 16742954;
7953
+ var FIRE_DECAY = 2;
7954
+ var BLAST_DECAY = 2;
7955
+ function followEntry(budget, handles, index, entry, request) {
7956
+ const handle = handles[index] ?? null;
7957
+ if (entry.intensity <= LIGHT_ON_THRESHOLD) {
7958
+ handle?.release({ fadeMs: 0 });
7959
+ handles[index] = null;
7960
+ return;
7961
+ }
7962
+ if (handle?.alive) {
7963
+ handle.update({ color: request.color, intensity: request.intensity, distance: request.distance, position: request.position });
7964
+ return;
7965
+ }
7966
+ handles[index] = budget.acquire(request);
7967
+ }
7968
+ function createLightLayer(THREE, pool, scale, budget) {
7609
7969
  const group = new THREE.Group();
7970
+ if (budget) return createBudgetedLightLayer(group, pool, scale, budget);
7610
7971
  const blast = pool.blast.map(() => {
7611
7972
  const light = new THREE.PointLight(16752720, 0, 30, 2);
7612
7973
  light.position.set(0, -50, 0);
@@ -7645,6 +8006,30 @@ void main(){
7645
8006
  }
7646
8007
  };
7647
8008
  }
8009
+ function createBudgetedLightLayer(group, pool, scale, budget) {
8010
+ const blast = pool.blast.map(() => null);
8011
+ const fire = pool.fire.map(() => null);
8012
+ return {
8013
+ group,
8014
+ sync() {
8015
+ let lit = 0;
8016
+ pool.blast.forEach((entry, index) => {
8017
+ const request = { owner: "vfx.blast", priority: LIGHT_PRIORITY.blast, urgent: true, color: entry.color, intensity: entry.intensity * scale, distance: entry.distance, decay: BLAST_DECAY, position: [entry.x, entry.y, entry.z] };
8018
+ followEntry(budget, blast, index, entry, request);
8019
+ if (entry.intensity > LIGHT_ON_THRESHOLD) lit++;
8020
+ });
8021
+ pool.fire.forEach((entry, index) => {
8022
+ const request = { owner: "vfx.fire", priority: LIGHT_PRIORITY.fire, color: FIRE_COLOR, intensity: entry.intensity * scale, distance: entry.distance, decay: FIRE_DECAY, position: [entry.x, entry.y, entry.z] };
8023
+ followEntry(budget, fire, index, entry, request);
8024
+ if (entry.intensity > LIGHT_ON_THRESHOLD) lit++;
8025
+ });
8026
+ return lit;
8027
+ },
8028
+ dispose() {
8029
+ for (const handle of [...blast, ...fire]) handle?.release({ fadeMs: 0 });
8030
+ }
8031
+ };
8032
+ }
7648
8033
  var SCORCH_SEED = 5;
7649
8034
  var TEXTURE_SIZE = 256;
7650
8035
  var FULL_TURN = Math.PI * 2;
@@ -7778,6 +8163,7 @@ void main(){
7778
8163
  group.add(mesh2);
7779
8164
  return mesh2;
7780
8165
  }
8166
+ let probeGeometry;
7781
8167
  const groundMarks = [];
7782
8168
  const spareGeometries = [];
7783
8169
  const blood = [];
@@ -7883,6 +8269,15 @@ void main(){
7883
8269
  rockMesh();
7884
8270
  plateMesh();
7885
8271
  },
8272
+ probes() {
8273
+ probeGeometry ?? (probeGeometry = new THREE.PlaneGeometry(1e-3, 1e-3).rotateX(-Math.PI / 2));
8274
+ return [...scorchMaterials, dirtMaterial].map((material) => {
8275
+ const mesh2 = new THREE.Mesh(probeGeometry, material);
8276
+ mesh2.receiveShadow = true;
8277
+ mesh2.frustumCulled = false;
8278
+ return mesh2;
8279
+ });
8280
+ },
7886
8281
  update(dt) {
7887
8282
  for (let index = 0; index < blood.length; index++) {
7888
8283
  const drawn = blood[index];
@@ -7895,6 +8290,7 @@ void main(){
7895
8290
  dispose() {
7896
8291
  clearMarks();
7897
8292
  for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
8293
+ probeGeometry?.dispose();
7898
8294
  for (const item of owned) item.dispose();
7899
8295
  }
7900
8296
  };
@@ -8470,7 +8866,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8470
8866
  }
8471
8867
  };
8472
8868
  }
8473
- function createWorkshopLayers(THREE, engine, pointScale, lightScale) {
8869
+ function createWorkshopLayers(THREE, engine, pointScale, lightScale, budget) {
8474
8870
  const group = new THREE.Group();
8475
8871
  group.name = "workshop";
8476
8872
  const layers = [
@@ -8485,15 +8881,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8485
8881
  for (const layer2 of layers) group.add(layer2.object);
8486
8882
  const meshes = createMeshLayer(THREE, engine.meshes);
8487
8883
  group.add(meshes.group);
8488
- const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
8489
- const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
8490
- for (const light of [...flashLights, ...fireLights]) group.add(light);
8884
+ const lights = budget ? budgetedLights(engine, lightScale, budget) : legacyLights(THREE, engine, lightScale, group);
8491
8885
  return {
8492
8886
  group,
8493
8887
  sync() {
8494
8888
  let drawCalls = 0;
8495
8889
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
8496
8890
  drawCalls += meshes.sync();
8891
+ lights.sync();
8892
+ return drawCalls;
8893
+ },
8894
+ dispose() {
8895
+ for (const layer2 of layers) layer2.dispose();
8896
+ meshes.dispose();
8897
+ lights.dispose();
8898
+ group.clear();
8899
+ }
8900
+ };
8901
+ }
8902
+ function legacyLights(THREE, engine, lightScale, group) {
8903
+ const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
8904
+ const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
8905
+ for (const light of [...flashLights, ...fireLights]) group.add(light);
8906
+ return {
8907
+ sync() {
8497
8908
  engine.lights.lights.forEach((source, index) => {
8498
8909
  const light = flashLights[index];
8499
8910
  light.position.set(source.x, source.y, source.z);
@@ -8506,12 +8917,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8506
8917
  light.position.set(source.x, source.y, source.z);
8507
8918
  light.intensity = source.intensity * lightScale;
8508
8919
  });
8509
- return drawCalls;
8510
8920
  },
8511
8921
  dispose() {
8512
- for (const layer2 of layers) layer2.dispose();
8513
- meshes.dispose();
8514
- group.clear();
8922
+ }
8923
+ };
8924
+ }
8925
+ function budgetedLights(engine, lightScale, budget) {
8926
+ const flash2 = engine.lights.lights.map(() => null);
8927
+ const fire = engine.lights.fire.map(() => null);
8928
+ return {
8929
+ sync() {
8930
+ engine.lights.lights.forEach((source, index) => {
8931
+ const request = { owner: "vfx.workshop", priority: LIGHT_PRIORITY.blast, urgent: true, color: source.color, intensity: source.intensity * lightScale, distance: source.distance, position: [source.x, source.y, source.z] };
8932
+ followEntry(budget, flash2, index, source, request);
8933
+ });
8934
+ engine.lights.fire.forEach((source, index) => {
8935
+ const request = { owner: "vfx.workshop", priority: LIGHT_PRIORITY.fire, color: FIRE_LIGHT_COLOR, intensity: source.intensity * lightScale, distance: FIRE_LIGHT_DISTANCE, position: [source.x, source.y, source.z] };
8936
+ followEntry(budget, fire, index, source, request);
8937
+ });
8938
+ },
8939
+ dispose() {
8940
+ for (const handle of [...flash2, ...fire]) handle?.release({ fadeMs: 0 });
8515
8941
  }
8516
8942
  };
8517
8943
  }
@@ -8569,7 +8995,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8569
8995
  const chunks = createChunkLayer(THREE, pools.chunks, { rock: rockGeometry, plate: plateGeometry }, colorOfChunk);
8570
8996
  const shocks = createShockLayer(THREE, pools.shocks);
8571
8997
  const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
8572
- const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
8998
+ const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI, options.lights);
8573
8999
  const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
8574
9000
  root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
8575
9001
  const stopMarks = world.onMarks((event) => marks.handle(event));
@@ -8602,7 +9028,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8602
9028
  workshopLayers?.dispose();
8603
9029
  if (workshopLayers) root.remove(workshopLayers.group);
8604
9030
  workshopEngine = engine;
8605
- workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
9031
+ workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1, options.lights);
8606
9032
  root.add(workshopLayers.group);
8607
9033
  applyLayers();
8608
9034
  }
@@ -8642,6 +9068,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8642
9068
  });
8643
9069
  },
8644
9070
  stop: (handle) => world.stop(handle),
9071
+ // a module's closed space (a shut car's cabin): the world's smoke, dust and steam go round it
9072
+ enclose: (id, enclosure) => world.enclose(id, enclosure),
8645
9073
  configure(settings) {
8646
9074
  const band = settings.lod_band;
8647
9075
  if (typeof band === "string" && band in LOD_BANDS) world.forceLod(LOD_BANDS[band] ?? null);
@@ -8673,7 +9101,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8673
9101
  const meshes = createMeshLayerProbes(THREE);
8674
9102
  for (const mesh2 of meshes) mesh2.frustumCulled = false;
8675
9103
  const group = new THREE.Group();
8676
- group.add(...meshes);
9104
+ group.add(...meshes, ...marks.probes());
8677
9105
  anchors = { meshes, group };
8678
9106
  return { group, dispose: () => {
8679
9107
  } };
@@ -8791,6 +9219,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8791
9219
  var BIG_LENGTH = 0.2;
8792
9220
  var BIG_WIDTH = 0.03;
8793
9221
  var FRAGMENT_SEED = 1374496523;
9222
+ var SHARD_SHAPES = 6;
8794
9223
  var isBig = (size) => {
8795
9224
  const [, middle, longest] = [...size].sort((a, b) => a - b);
8796
9225
  return longest >= BIG_LENGTH && middle >= BIG_WIDTH;
@@ -8840,7 +9269,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8840
9269
  return m;
8841
9270
  }
8842
9271
  shardGeometry(seed) {
8843
- const idx = seed % 6;
9272
+ const idx = seed % SHARD_SHAPES;
8844
9273
  let g = this.shardGeos[idx];
8845
9274
  if (!g) {
8846
9275
  const r = createRng(1e3 + idx);
@@ -8861,17 +9290,32 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
8861
9290
  }
8862
9291
  return g;
8863
9292
  }
9293
+ markParts() {
9294
+ this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
9295
+ this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
9296
+ return { geometry: this.markGeometry, material: this.markMaterial };
9297
+ }
9298
+ /**
9299
+ * Meshes, never put in a scene by this class, that draw every shared piece of a fragment once: the box, the six shards, the bullet-hole disc, and the opaque and
9300
+ * glass materials. A host draws them at load so the first blast does not upload these buffers in the middle of a frame.
9301
+ */
9302
+ warmMeshes() {
9303
+ const opaque = this.materialFor("warm", 8421504, false);
9304
+ const glass = this.materialFor("warm", 8421504, true);
9305
+ const geometries = [this.box, ...Array.from({ length: SHARD_SHAPES }, (_, index) => this.shardGeometry(index))];
9306
+ const { geometry, material } = this.markParts();
9307
+ return [...geometries.flatMap((shared) => [new this.THREE.Mesh(shared, opaque), new this.THREE.Mesh(shared, glass)]), new this.THREE.Mesh(geometry, material)];
9308
+ }
8864
9309
  /**
8865
9310
  * Bullet holes that stay on a fragment (`Fragment.marks`): dark discs that are children of its mesh, so they fall, spin and settle with it. The mesh is
8866
9311
  * a unit box scaled to the fragment, so each disc is scaled back; exact for a mark on a face, which is where a bullet hole is.
8867
9312
  */
8868
9313
  addMarks(mesh2, f) {
8869
9314
  if (!f.marks?.length) return;
8870
- this.markGeometry ?? (this.markGeometry = new this.THREE.CircleGeometry(1, 10));
8871
- this.markMaterial ?? (this.markMaterial = new this.THREE.MeshBasicMaterial({ color: 723465, depthWrite: false, polygonOffset: true, polygonOffsetFactor: -4, polygonOffsetUnits: -4 }));
9315
+ const { geometry, material } = this.markParts();
8872
9316
  const sc = mesh2.scale;
8873
9317
  for (const mark of f.marks) {
8874
- const disc = new this.THREE.Mesh(this.markGeometry, this.markMaterial);
9318
+ const disc = new this.THREE.Mesh(geometry, material);
8875
9319
  const radius = Math.max(4e-3, mark.diameter) * 0.6;
8876
9320
  const n = this.v.set(mark.normal[0], mark.normal[1], mark.normal[2]).normalize();
8877
9321
  disc.position.set(mark.point[0] / sc.x + n.x * 2e-3 / sc.x, mark.point[1] / sc.y + n.y * 2e-3 / sc.y, mark.point[2] / sc.z + n.z * 2e-3 / sc.z);
@@ -9454,7 +9898,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9454
9898
  const invM = 1 / b.mass;
9455
9899
  const steps = clamp6(Math.ceil(dt / SUB_DT), 1, 8);
9456
9900
  const h = dt / steps;
9457
- let landing = 0;
9901
+ let landing2 = 0;
9458
9902
  let landPoint = [pos[0], pos[1], pos[2]];
9459
9903
  let contactedLast = false;
9460
9904
  let touched = false;
@@ -9560,8 +10004,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9560
10004
  let impact2 = 0;
9561
10005
  for (const r of rs) impact2 = Math.max(impact2, -(v[1] + w[2] * r[0] - w[0] * r[2]));
9562
10006
  const restitution = impact2 > 2 ? 0.25 : 0;
9563
- if (impact2 > landing) {
9564
- landing = impact2;
10007
+ if (impact2 > landing2) {
10008
+ landing2 = impact2;
9565
10009
  let sx = 0;
9566
10010
  let sz = 0;
9567
10011
  for (const r of rs) {
@@ -9697,9 +10141,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9697
10141
  b.root.position.set(pos[0] - off.x, pos[1] - off.y, pos[2] - off.z);
9698
10142
  b.root.updateMatrixWorld(true);
9699
10143
  b.onGround = contactedLast;
9700
- if (landing > HARD_LANDING && this.time - b.lastHit > 0.15) {
10144
+ if (landing2 > HARD_LANDING && this.time - b.lastHit > 0.15) {
9701
10145
  b.lastHit = this.time;
9702
- this.o.onHardLanding(b, landPoint, landing);
10146
+ this.o.onHardLanding(b, landPoint, landing2);
9703
10147
  }
9704
10148
  const still = touched && !unstable && Math.hypot(v[0], v[1], v[2]) < 0.12 && Math.hypot(w[0], w[1], w[2]) < 0.45;
9705
10149
  if (still) b.still += dt;
@@ -11179,6 +11623,69 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11179
11623
  return out;
11180
11624
  };
11181
11625
  var RESTING_HOLD_COS = Math.cos(Math.PI / 6);
11626
+ var assemblies_default = [
11627
+ {
11628
+ id: "brick_wall_plastered",
11629
+ name: "Plastered brick wall",
11630
+ core: { material: "brick", thickness: 0.09 },
11631
+ layers: [{ material: "plaster", thickness: 0.02, faces: "both" }],
11632
+ units: { size: [0.23, 0.075, 0.11], bond: "running", mortar: "plaster" },
11633
+ breach: { area: 0.25 }
11634
+ },
11635
+ {
11636
+ id: "concrete_block_wall",
11637
+ name: "Concrete block wall",
11638
+ core: { material: "concrete", thickness: 0.14 },
11639
+ fill: 0.55,
11640
+ units: { size: [0.39, 0.19, 0.14], bond: "running", mortar: "plaster" },
11641
+ breach: { area: 0.25 }
11642
+ },
11643
+ {
11644
+ id: "reinforced_concrete",
11645
+ name: "Reinforced concrete wall",
11646
+ core: { material: "concrete", thickness: 0.2 },
11647
+ reinforcement: { material: "steel", spacing: 0.18, diameter: 8e-3, residualCapacity: 0.15 },
11648
+ units: { size: [0.6, 0.42, 0.2], bond: "stack", mortar: "concrete" },
11649
+ breach: { area: 0.25 }
11650
+ },
11651
+ {
11652
+ id: "drywall",
11653
+ name: "Drywall partition on steel studs",
11654
+ core: { material: "plaster", thickness: 0.0125 },
11655
+ layers: [{ material: "plaster", thickness: 0.0125, faces: "+" }],
11656
+ depth: 0.095,
11657
+ breach: { area: 0.25 }
11658
+ },
11659
+ {
11660
+ id: "wood_frame_wall",
11661
+ name: "Wood frame wall",
11662
+ core: { material: "plywood", thickness: 0.012 },
11663
+ layers: [{ material: "plaster", thickness: 0.0125, faces: "-" }],
11664
+ depth: 0.1135,
11665
+ breach: { area: 0.25 }
11666
+ },
11667
+ {
11668
+ id: "log_wall",
11669
+ name: "Log wall",
11670
+ core: { material: "wood_soft", thickness: 0.2 },
11671
+ fill: 0.785,
11672
+ breach: { area: 0.25 }
11673
+ },
11674
+ {
11675
+ id: "sheet_metal_shack",
11676
+ name: "Corrugated sheet metal wall",
11677
+ core: { material: "steel", thickness: 6e-4 },
11678
+ depth: 0.02,
11679
+ breach: { area: 0.25 }
11680
+ },
11681
+ {
11682
+ id: "glass_pane",
11683
+ name: "Glass pane",
11684
+ core: { material: "glass", thickness: 6e-3 },
11685
+ breach: { area: 0.1 }
11686
+ }
11687
+ ];
11688
+ var SHIPPED = structuredClone(assemblies_default);
11182
11689
 
11183
11690
  // src/spec.ts
11184
11691
  function containsOf(value) {
@@ -11544,6 +12051,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11544
12051
  this.b = tmpV(THREE);
11545
12052
  this.m4 = new THREE.Matrix4();
11546
12053
  this.ray = new THREE.Raycaster();
12054
+ this.ray.layers.enableAll();
11547
12055
  this.box = new THREE.Box3();
11548
12056
  this.t = tmpV(THREE);
11549
12057
  this.lbox = new THREE.Box3();
@@ -11551,9 +12059,9 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11551
12059
  /**
11552
12060
  * @param range vertex range `[v0, v1)` of a merged mesh this part is drawn by (see `MeshRange`); `hosts` caches the shared state per mesh for one `bind`.
11553
12061
  */
11554
- makeMeshVis(mesh2, home, range, hosts) {
12062
+ makeMeshVis(mesh2, home, range2, hosts) {
11555
12063
  let host = null;
11556
- if (range) {
12064
+ if (range2) {
11557
12065
  host = hosts?.get(mesh2) ?? null;
11558
12066
  if (!host) {
11559
12067
  host = { mesh: mesh2, origGeometry: mesh2.geometry, origMaterial: mesh2.material, ownGeo: false, work: null, ownMat: false, sharers: [] };
@@ -11579,8 +12087,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11579
12087
  bottomY: 0,
11580
12088
  moved: false,
11581
12089
  host,
11582
- range: range ? [range[0], range[1]] : null,
11583
- merged: host && range ? { host, range: [range[0], range[1]] } : null,
12090
+ range: range2 ? [range2[0], range2[1]] : null,
12091
+ merged: host && range2 ? { host, range: [range2[0], range2[1]] } : null,
11584
12092
  loose: false,
11585
12093
  collapsed: false,
11586
12094
  hidden: false,
@@ -11922,8 +12430,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
11922
12430
  * Takes away the decals of a mesh that sit inside a box (world `center`, `half` extents along the axes of the world `rotation`): the surface they
11923
12431
  * marked is gone (a unit came out of the wall with its bullet holes and spalls).
11924
12432
  */
11925
- clearDecals(mv, center, half, rotation) {
11926
- const inv = rotation.clone().invert();
12433
+ clearDecals(mv, center, half, rotation2) {
12434
+ const inv = rotation2.clone().invert();
11927
12435
  const p = new this.THREE.Vector3();
11928
12436
  for (let i = mv.decals.length - 1; i >= 0; i--) {
11929
12437
  const d = mv.decals[i];
@@ -12149,24 +12657,24 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12149
12657
  geo.boundingBox = null;
12150
12658
  geo.boundingSphere = null;
12151
12659
  }
12152
- collapseRange(arr, range) {
12153
- const x = arr[range[0] * 3];
12154
- const y = arr[range[0] * 3 + 1];
12155
- const z = arr[range[0] * 3 + 2];
12156
- for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
12660
+ collapseRange(arr, range2) {
12661
+ const x = arr[range2[0] * 3];
12662
+ const y = arr[range2[0] * 3 + 1];
12663
+ const z = arr[range2[0] * 3 + 2];
12664
+ for (let i = range2[0] * 3; i < range2[1] * 3; i += 3) {
12157
12665
  arr[i] = x;
12158
12666
  arr[i + 1] = y;
12159
12667
  arr[i + 2] = z;
12160
12668
  }
12161
12669
  }
12162
12670
  /** Melt of one range: shorter in Y (down to 25 %) with the bottom fixed, a bit wider around its centre, with the same noise as a whole mesh. */
12163
- meltRange(arr, work, range, melt) {
12671
+ meltRange(arr, work, range2, melt) {
12164
12672
  let minY = Infinity;
12165
12673
  let maxY = -Infinity;
12166
12674
  let cx = 0;
12167
12675
  let cz = 0;
12168
- const n = Math.max(1, range[1] - range[0]);
12169
- for (let i = range[0] * 3; i < range[1] * 3; i += 3) {
12676
+ const n = Math.max(1, range2[1] - range2[0]);
12677
+ for (let i = range2[0] * 3; i < range2[1] * 3; i += 3) {
12170
12678
  minY = Math.min(minY, work[i + 1]);
12171
12679
  maxY = Math.max(maxY, work[i + 1]);
12172
12680
  cx += work[i];
@@ -12177,7 +12685,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12177
12685
  const h = Math.max(1e-4, maxY - minY);
12178
12686
  const sy = lerp5(1, 0.25, melt);
12179
12687
  const sxz = 1 + 0.18 * melt;
12180
- for (let i = range[0] * 3, v = 0; i < range[1] * 3; i += 3, v++) {
12688
+ for (let i = range2[0] * 3, v = 0; i < range2[1] * 3; i += 3, v++) {
12181
12689
  const noise = (Math.imul(v + 1, 2654435761) >>> 0) % 1e3 / 1e3;
12182
12690
  arr[i] = cx + (work[i] - cx) * sxz;
12183
12691
  arr[i + 2] = cz + (work[i + 2] - cz) * sxz;
@@ -12534,8 +13042,26 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12534
13042
  __publicField(this, "queue", []);
12535
13043
  /** Presentation work an event left for later frames (fragments still to spawn, loose groups still to split), run in order within the frame budget. */
12536
13044
  __publicField(this, "slices", []);
12537
- /** When the work of this frame must stop; `Infinity` outside `drainQueue`, where a call finishes what it started. */
13045
+ /** When the work of this frame must stop; `Infinity` outside a drain, where a call finishes what it started. */
12538
13046
  __publicField(this, "sliceDeadline", Infinity);
13047
+ /** The clock the drain reads; a budget lends its own while it runs the queue. */
13048
+ __publicField(this, "drainClock", () => performance.now());
13049
+ /** true once a frame budget owns the draining: `update` no longer drains by itself. */
13050
+ __publicField(this, "attached", false);
13051
+ __publicField(this, "eventQueue", {
13052
+ id: DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS,
13053
+ pending: () => this.queue.length + this.slices.length,
13054
+ run: (deadline, clock) => this.runBudgeted(deadline, clock),
13055
+ flush: () => {
13056
+ for (let guard = 0; guard < 16 && this.queue.length + this.slices.length > 0; guard++) this.runBudgeted(Infinity, () => 0);
13057
+ },
13058
+ attach: () => {
13059
+ this.attached = true;
13060
+ },
13061
+ detach: () => {
13062
+ this.attached = false;
13063
+ }
13064
+ });
12539
13065
  __publicField(this, "unsubscribe");
12540
13066
  __publicField(this, "visuals");
12541
13067
  __publicField(this, "fragments");
@@ -12581,6 +13107,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12581
13107
  const maxParticles = Math.max(8, Math.floor((o.maxParticles ?? 2500) * (this.low ? 0.5 : 1)));
12582
13108
  this.visuals = new Visuals(THREE, this.low, 600);
12583
13109
  this.ground = new GroundSpots(THREE, o.scene, this.groundAt, 48);
13110
+ if (!o.vfx) warnOnce("game-destruction-three.own-effects-world", "game-destruction-three made an effects world of its own because the host lent none (options.vfx); pass the host effects so the effects share one budget");
12584
13111
  this.vfx = o.vfx ?? createVfx({ profile: this.quality, gravity: this.gravity, groundAt: this.groundAt, sprites: { capacity: maxParticles, density: this.low ? 0.6 : 1 } });
12585
13112
  this.ownVfx = o.vfx ? void 0 : createVfxRenderer(THREE, { scene: o.scene, camera: o.camera ?? new THREE.PerspectiveCamera(), vfx: this.vfx, pointScale: o.pointScale ?? 700 });
12586
13113
  this.stopLanding = this.vfx.onLand(this.owner, (x, _y, z, color, tag) => {
@@ -12818,7 +13345,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12818
13345
  if (this.disposed) return;
12819
13346
  dt = clamp6(dt, 0, 0.1);
12820
13347
  this.time += dt;
12821
- this.drainQueue();
13348
+ if (!this.attached) this.drainQueue();
12822
13349
  for (const b of this.bounds.values()) b.onFrame?.(dt);
12823
13350
  for (const b of this.bounds.values()) if (b.soilNode && this.world.state(b.id)?.soil?.status === "yielding") this.onSettle(b.id);
12824
13351
  this.updateActive(dt);
@@ -12829,36 +13356,50 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
12829
13356
  this.ownVfx?.update(dt);
12830
13357
  this.syncTransforms();
12831
13358
  }
13359
+ deferredQueue() {
13360
+ return this.eventQueue;
13361
+ }
12832
13362
  drainQueue() {
13363
+ this.runBudgeted(performance.now() + this.eventBudgetMs, () => performance.now());
13364
+ }
13365
+ /** Applies the waiting events in order until the deadline, after the slices earlier events left; always at least one unit when something waits. Returns the units run. */
13366
+ runBudgeted(deadline, clock) {
12833
13367
  const batch = this.queue.splice(0, this.queue.length);
12834
- const deadline = performance.now() + this.eventBudgetMs;
13368
+ const outerClock = this.drainClock;
13369
+ this.drainClock = clock;
12835
13370
  this.sliceDeadline = deadline;
13371
+ let ran = 0;
12836
13372
  try {
12837
- this.runSlices(deadline);
13373
+ ran = this.runSlices(deadline);
12838
13374
  for (let i = 0; i < batch.length; i++) {
12839
- if (i > 0 && performance.now() >= deadline) {
13375
+ if ((i > 0 || ran > 0 && this.attached) && clock() >= deadline) {
12840
13376
  this.queue.unshift(...batch.slice(i));
12841
- return;
13377
+ return ran;
12842
13378
  }
12843
13379
  try {
12844
13380
  this.apply(batch[i]);
12845
13381
  } catch (err) {
12846
13382
  if (typeof console !== "undefined") console.error("game-destruction-three: failed to apply", batch[i].type, err);
12847
13383
  }
13384
+ ran++;
12848
13385
  }
12849
13386
  } finally {
12850
13387
  this.sliceDeadline = Infinity;
13388
+ this.drainClock = outerClock;
12851
13389
  }
13390
+ return ran;
12852
13391
  }
12853
13392
  /** Runs the work left by earlier frames until the deadline; at least one piece per frame, so it always finishes. */
12854
13393
  runSlices(deadline) {
12855
- for (let ran = 0; this.slices.length > 0 && (ran === 0 || performance.now() < deadline); ran++) {
13394
+ let ran = 0;
13395
+ for (; this.slices.length > 0 && (ran === 0 || this.drainClock() < deadline); ran++) {
12856
13396
  try {
12857
13397
  this.slices.shift()();
12858
13398
  } catch (err) {
12859
13399
  if (typeof console !== "undefined") console.error("game-destruction-three: failed to run deferred work", err);
12860
13400
  }
12861
13401
  }
13402
+ return ran;
12862
13403
  }
12863
13404
  /**
12864
13405
  * The ground under an object settled or tilted: its parts are drawn under a node with the soil's offset (`ObjectState.soil.offset`), so the host's
@@ -13364,8 +13905,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13364
13905
  const T = this.THREE;
13365
13906
  const center = new T.Vector3(ev.center[0], ev.center[1], ev.center[2]);
13366
13907
  const half = new T.Vector3(ev.size[0] / 2 + CHIP_DECAL_EDGE, ev.size[1] / 2 + CHIP_DECAL_EDGE, ev.size[2] / 2 + CHIP_DECAL_EDGE);
13367
- const rotation = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
13368
- for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation);
13908
+ const rotation2 = new T.Quaternion(ev.rotation[0], ev.rotation[1], ev.rotation[2], ev.rotation[3]);
13909
+ for (const part of this.bounds.get(ev.object)?.parts.values() ?? []) for (const mv of part.meshes) this.visuals.clearDecals(mv, center, half, rotation2);
13369
13910
  this.vfx.peekWorkshop()?.decals.clearInside(ev.center, [half.x, half.y, half.z], ev.rotation);
13370
13911
  this.vfx.burst("dust", ev.center, [0, 1, 0], 8, 0.8, { color });
13371
13912
  this.sound(impactKey(def), ev.center, 0.7);
@@ -13416,7 +13957,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13416
13957
  const maxAlive = this.low ? Math.floor(this.maxFragments / 2) : this.maxFragments;
13417
13958
  const step = this.low ? 2 : 1;
13418
13959
  for (let i = from; i < ev.fragments.length; i += step) {
13419
- if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && performance.now() >= this.sliceDeadline) {
13960
+ if (i - from >= MIN_FRAGMENTS_PER_SLICE * step && this.drainClock() >= this.sliceDeadline) {
13420
13961
  this.slices.push(() => this.spawnFragments(ev, i, look));
13421
13962
  return;
13422
13963
  }
@@ -13440,7 +13981,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13440
13981
  if (!st || st.groups.length < 2) return;
13441
13982
  const loose = st.groups.length - 1;
13442
13983
  for (let i = 0; i < loose; i++) {
13443
- if (i > 0 && performance.now() >= this.sliceDeadline) {
13984
+ if (i > 0 && this.drainClock() >= this.sliceDeadline) {
13444
13985
  this.slices.push(() => {
13445
13986
  this.staticDirty.add(objectId);
13446
13987
  this.splitLoose(objectId);
@@ -13819,6 +14360,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13819
14360
  const materials = [kit.discMaterial().clone(), kit.patchMaterial(8421504).clone(), kit.lineMaterial(0).clone()];
13820
14361
  const group = new T.Group();
13821
14362
  group.add(new T.Mesh(kit.discGeometry(), materials[0]), new T.Mesh(kit.discGeometry(), materials[1]), new T.LineSegments(geometry, materials[2]));
14363
+ group.add(...this.fragments.warmMeshes());
13822
14364
  for (const probe of group.children) probe.frustumCulled = false;
13823
14365
  this.warmProbes = { group, geometry, materials };
13824
14366
  return { group, dispose: () => {
@@ -13849,7 +14391,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13849
14391
  }
13850
14392
  };
13851
14393
 
13852
- // ../game-audio/dist/index.js
14394
+ // ../game-audio/dist/chunk-DZITVFMJ.js
13853
14395
  var DISTANCE_CURVES = {
13854
14396
  /** The game's `sfx`: `vol / (1 + d² × 0.004)`, cutoff `/ (1 + d × 0.01)`, silent below 0.01 (jogo L62091–62092, L62101). */
13855
14397
  game_sfx: { model: "inverse_square", k: 4e-3, cutoffK: 0.01, minGain: 0.01 },
@@ -13864,31 +14406,6 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13864
14406
  /** The game's animal calls: `max(0, 1 − d / 70) × 0.25`, silent at or below 0.005 (jogo L66627–66628). */
13865
14407
  animals: { model: "linear", range: 70, scale: 0.25, minGain: 5e-3 }
13866
14408
  };
13867
- var mix_profiles_default = [
13868
- {
13869
- id: "game",
13870
- master: 0.5,
13871
- noise: { seconds: 1.5, create: "lazy" },
13872
- sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
13873
- },
13874
- {
13875
- id: "art_scene",
13876
- master: 0.85,
13877
- compressor: { threshold: -12, ratio: 5 },
13878
- reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
13879
- noise: { seconds: 2, create: "eager" },
13880
- sources: [
13881
- "cena:L1308 compressor -12 dB, ratio 5",
13882
- "cena:L1309 master 0.85",
13883
- "cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
13884
- "cena:L1311 wet 0.4",
13885
- "cena:L1312 noise 2 s made with the chain"
13886
- ]
13887
- }
13888
- ];
13889
- var MIX_PROFILES = Object.fromEntries(
13890
- mix_profiles_default.map((profile) => [profile.id, profile])
13891
- );
13892
14409
  var game_sfx_default = {
13893
14410
  sources: ["jogo:L62089-62107"],
13894
14411
  note: "The game's sfx table P: [lowpass cutoff Hz, peak gain, seconds]. Played as a noise burst with the game's distance curve (falloff game_sfx) and a 0.05 s tail. Team names in `team`.",
@@ -13933,6 +14450,78 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13933
14450
  };
13934
14451
  }
13935
14452
  var GAME_SFX_BANK = { id: "game_sfx", sounds: [...GAME_SFX_CLASSES, GAME_SFX_FALLBACK].map((row) => noiseClassRecipe(row)) };
14453
+ var mix_profiles_default = [
14454
+ {
14455
+ id: "game",
14456
+ master: 0.5,
14457
+ noise: { seconds: 1.5, create: "lazy" },
14458
+ sources: ["jogo:L62077-62081 master 0.5 to the output", "jogo:L62082-62088 noise 1.5 s made by the first sound that needs it"]
14459
+ },
14460
+ {
14461
+ id: "art_scene",
14462
+ master: 0.85,
14463
+ compressor: { threshold: -12, ratio: 5 },
14464
+ reverb: { seconds: 2.2, decay: 3.4, wet: 0.4 },
14465
+ noise: { seconds: 2, create: "eager" },
14466
+ sources: [
14467
+ "cena:L1308 compressor -12 dB, ratio 5",
14468
+ "cena:L1309 master 0.85",
14469
+ "cena:L1310 convolver, impulse 2.2 s of (1 - i/len)^3.4",
14470
+ "cena:L1311 wet 0.4",
14471
+ "cena:L1312 noise 2 s made with the chain"
14472
+ ]
14473
+ }
14474
+ ];
14475
+ var MIX_PROFILES = Object.fromEntries(
14476
+ mix_profiles_default.map((profile) => [profile.id, profile])
14477
+ );
14478
+ var BENCH_BANK = {
14479
+ id: "audio_bench",
14480
+ sounds: [
14481
+ {
14482
+ id: "bench_ping",
14483
+ bus: "ui",
14484
+ pan: true,
14485
+ duration: 0.25,
14486
+ layers: [
14487
+ {
14488
+ gain: 0.5,
14489
+ wet: 0.3,
14490
+ source: { kind: "osc", wave: "sine", frequency: { from: 880 } },
14491
+ envelope: [
14492
+ [0, 1e-4, "set", "fixed"],
14493
+ [2e-3, 1, "exp"],
14494
+ ["1d", 1e-4, "exp", "fixed"]
14495
+ ]
14496
+ }
14497
+ ],
14498
+ falloff: DISTANCE_CURVES.art_scene,
14499
+ sources: ["sdk: bench probe, a 880 Hz ping through the art scene output stage"]
14500
+ },
14501
+ {
14502
+ id: "bench_hiss",
14503
+ bus: "ui",
14504
+ pan: true,
14505
+ duration: [0.3, 0.9],
14506
+ layers: [
14507
+ {
14508
+ gain: 0.4,
14509
+ source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
14510
+ filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3e3 }, q: 0.7 }] },
14511
+ envelope: [
14512
+ [0, 1e-4, "set", "fixed"],
14513
+ [2e-3, 1, "exp"],
14514
+ ["1d", 1e-4, "exp", "fixed"]
14515
+ ]
14516
+ }
14517
+ ],
14518
+ falloff: DISTANCE_CURVES.art_scene,
14519
+ sources: ["sdk: bench probe, a highpass hiss of random length"]
14520
+ }
14521
+ ]
14522
+ };
14523
+
14524
+ // ../game-audio/dist/index.js
13936
14525
  var ATTACK = 2e-3;
13937
14526
  function lengthOf(d, attack) {
13938
14527
  if (typeof d === "number") return attack + d;
@@ -13970,6 +14559,31 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13970
14559
  envelope: envelope(ATTACK)
13971
14560
  };
13972
14561
  }
14562
+ function sceneVoice(o) {
14563
+ return {
14564
+ duration: lengthOf(o.d, 0),
14565
+ gain: o.vol,
14566
+ wet: 0.45,
14567
+ source: {
14568
+ kind: "formant_voice",
14569
+ wave: o.wave ?? "sawtooth",
14570
+ frequency: { from: o.f0, to: o.f1, curve: "linear", over: "duration" },
14571
+ vibrato: { rate: [4, 10], depth: 0.08 },
14572
+ formants: [
14573
+ { frequency: o.fA, q: 4 },
14574
+ { frequency: o.fB, q: 6 }
14575
+ ],
14576
+ breath: { frequency: o.fB, q: 1.1, gain: o.noise },
14577
+ mix: 3.2
14578
+ },
14579
+ envelope: [
14580
+ [0, 1e-4, "set", "fixed"],
14581
+ [0.1, 1, "exp"],
14582
+ ["0.6d", 1, "set"],
14583
+ ["1d", 1e-4, "exp", "fixed"]
14584
+ ]
14585
+ };
14586
+ }
13973
14587
  var sceneMany = (count, make) => Array.from({ length: count }, make);
13974
14588
  function sceneRecipe(id, layers, line, options = {}) {
13975
14589
  return {
@@ -13983,51 +14597,172 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
13983
14597
  sources: [`cena:${line}`]
13984
14598
  };
13985
14599
  }
13986
- var BENCH_BANK = {
13987
- id: "audio_bench",
13988
- sounds: [
13989
- {
13990
- id: "bench_ping",
13991
- bus: "ui",
13992
- pan: true,
13993
- duration: 0.25,
13994
- layers: [
13995
- {
13996
- gain: 0.5,
13997
- wet: 0.3,
13998
- source: { kind: "osc", wave: "sine", frequency: { from: 880 } },
13999
- envelope: [
14000
- [0, 1e-4, "set", "fixed"],
14001
- [2e-3, 1, "exp"],
14002
- ["1d", 1e-4, "exp", "fixed"]
14003
- ]
14004
- }
14005
- ],
14006
- falloff: DISTANCE_CURVES.art_scene,
14007
- sources: ["sdk: bench probe, a 880 Hz ping through the art scene output stage"]
14008
- },
14009
- {
14010
- id: "bench_hiss",
14011
- bus: "ui",
14012
- pan: true,
14013
- duration: [0.3, 0.9],
14014
- layers: [
14015
- {
14016
- gain: 0.4,
14017
- source: { kind: "noise", offset: "random", offsetScale: 1.5, loop: true },
14018
- filters: { mode: "serial", bank: [{ type: "highpass", frequency: { set: 3e3 }, q: 0.7 }] },
14019
- envelope: [
14020
- [0, 1e-4, "set", "fixed"],
14021
- [2e-3, 1, "exp"],
14022
- ["1d", 1e-4, "exp", "fixed"]
14023
- ]
14024
- }
14025
- ],
14026
- falloff: DISTANCE_CURVES.art_scene,
14027
- sources: ["sdk: bench probe, a highpass hiss of random length"]
14028
- }
14029
- ]
14600
+ var TAKES = [
14601
+ { f: 1, t: 1, v: 1 },
14602
+ { f: 1.08, t: 0.9, v: 0.92 },
14603
+ { f: 0.93, t: 1.12, v: 1.05 },
14604
+ { f: 1.03, t: 1.05, v: 0.88 }
14605
+ ];
14606
+ var FOOTSTEP_FALLOFF = { model: "inverse_square", k: 0.03, cutoffK: 0.03, minGain: 4e-3 };
14607
+ var BODY_FALLOFF = { model: "inverse_square", k: 0.012, cutoffK: 0.015, minGain: 4e-3 };
14608
+ var SURFACES = {
14609
+ concrete: { heel: { f: 95, d: 0.05, vol: 0.55 }, roll: { type: "bandpass", f: 2300, q: 1.1, d: 0.05, vol: 0.38 }, grains: { count: 2, f: 4200, q: 2, spread: 0.04, vol: 0.08 } },
14610
+ asphalt: { heel: { f: 90, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 1700, q: 0.9, d: 0.06, vol: 0.36 }, grains: { count: 4, f: 3600, q: 2.5, spread: 0.06, vol: 0.1 } },
14611
+ wood: { heel: { f: 130, d: 0.08, vol: 0.55 }, roll: { type: "lowpass", f: 1300, q: 0.8, d: 0.06, vol: 0.3 }, ring: [{ f: 210, d: 0.1, vol: 0.18 }, { f: 470, d: 0.06, vol: 0.06 }] },
14612
+ tile: { heel: { f: 110, d: 0.04, vol: 0.45 }, roll: { type: "bandpass", f: 3600, q: 2, d: 0.03, vol: 0.42 }, ring: [{ f: 1900, d: 0.03, vol: 0.05 }] },
14613
+ dirt: { heel: { f: 75, d: 0.06, vol: 0.45 }, roll: { type: "lowpass", f: 750, q: 0.7, d: 0.09, vol: 0.4 }, grains: { count: 3, f: 1800, q: 1.5, spread: 0.07, vol: 0.06 } },
14614
+ grass: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "highpass", f: 2600, q: 0.7, d: 0.13, vol: 0.28 }, grains: { count: 4, f: 5200, q: 1.2, spread: 0.1, vol: 0.07 } },
14615
+ gravel: { heel: { f: 80, d: 0.05, vol: 0.4 }, roll: { type: "lowpass", f: 950, q: 0.8, d: 0.1, vol: 0.3 }, grains: { count: 8, f: 2600, q: 3, spread: 0.13, vol: 0.2 } },
14616
+ sand: { heel: { f: 65, d: 0.06, vol: 0.3 }, roll: { type: "bandpass", f: 1500, q: 0.6, d: 0.16, vol: 0.3 }, grains: { count: 5, f: 4800, q: 1.5, spread: 0.12, vol: 0.05 } },
14617
+ metal: { heel: { f: 120, d: 0.05, vol: 0.5 }, roll: { type: "bandpass", f: 2800, q: 1.5, d: 0.03, vol: 0.25 }, ring: [{ f: 420, d: 0.25, vol: 0.16 }, { f: 1130, d: 0.18, vol: 0.09 }, { f: 2470, d: 0.1, vol: 0.04 }] },
14618
+ water: { heel: { f: 70, d: 0.04, vol: 0.25 }, roll: { type: "bandpass", f: 3e3, q: 1.2, d: 0.1, vol: 0.18 }, splash: { f: 1100, f2: 380, d: 0.22, vol: 0.55 }, grains: { count: 4, f: 2400, q: 6, spread: 0.18, vol: 0.12 } },
14619
+ glass: { heel: { f: 90, d: 0.04, vol: 0.4 }, roll: { type: "highpass", f: 4500, q: 0.8, d: 0.06, vol: 0.2 }, grains: { count: 9, f: 6200, q: 5, spread: 0.12, vol: 0.22 }, ring: [{ f: 3900, d: 0.04, vol: 0.04 }, { f: 5300, d: 0.03, vol: 0.03 }] },
14620
+ carpet: { heel: { f: 70, d: 0.05, vol: 0.32 }, roll: { type: "lowpass", f: 420, q: 0.7, d: 0.07, vol: 0.3 } }
14621
+ };
14622
+ var GAITS = {
14623
+ walk: { vol: 1, bright: 1, toe: 0.075, short: 1 },
14624
+ run: { vol: 1.35, bright: 1.25, toe: 0.035, short: 0.75 }
14625
+ };
14626
+ var grainLayers = (g, take, start, scale) => Array.from(
14627
+ { length: g.count },
14628
+ (_, i) => sceneNoise({ type: "bandpass", f: [g.f * take.f * 0.85, g.f * take.f * 1.15], q: g.q, d: [8e-3, 0.02], vol: g.vol * scale * take.v, delay: start + (i + 0.5) / g.count * g.spread * take.t })
14629
+ );
14630
+ var ringLayers = (rings, take, scale, at3 = 0) => rings.map((r) => sceneTone({ f: r.f * take.f, d: r.d * take.t, vol: r.vol * scale * take.v, delay: at3 }));
14631
+ function footLayers(surface, take, gait, weight = 1) {
14632
+ const s = SURFACES[surface];
14633
+ const toe = gait.toe * take.t;
14634
+ return [
14635
+ sceneTone({ f: s.heel.f * take.f * weight ** -0.3, f2: s.heel.f * take.f * 0.6, d: s.heel.d * gait.short * take.t * weight ** 0.5, vol: s.heel.vol * gait.vol * take.v * weight ** 0.5 }),
14636
+ sceneNoise({ type: "lowpass", f: 500 * gait.bright, d: 0.02 * take.t, vol: 0.18 * gait.vol * take.v * weight ** 0.5 }),
14637
+ sceneNoise({ type: s.roll.type, f: [s.roll.f * take.f * gait.bright * 0.92, s.roll.f * take.f * gait.bright * 1.08], q: s.roll.q, d: s.roll.d * gait.short * take.t * weight ** 0.3, vol: s.roll.vol * gait.vol * take.v, delay: toe }),
14638
+ ...s.grains ? grainLayers(s.grains, take, toe * 0.5, gait.vol * weight ** 0.5) : [],
14639
+ ...s.ring ? ringLayers(s.ring, take, gait.vol * weight ** 0.5) : [],
14640
+ ...s.splash ? [sceneNoise({ type: "bandpass", f: s.splash.f * take.f, f2: s.splash.f2 * take.f, q: 1.4, d: s.splash.d * take.t * weight ** 0.4, vol: s.splash.vol * gait.vol * take.v * weight ** 0.5 })] : []
14641
+ ];
14642
+ }
14643
+ var quieter = (level, layers) => layers.map((layer2) => {
14644
+ const gain = layer2.gain ?? 1;
14645
+ if (typeof gain === "number") return { ...layer2, gain: gain * level };
14646
+ if (Array.isArray(gain)) return { ...layer2, gain: [gain[0] * level, gain[1] * level] };
14647
+ return layer2;
14648
+ });
14649
+ var recipe = (id, layers, falloff, bus = "world") => ({ id, bus, pan: true, layers, falloff, sources: ["sdk"] });
14650
+ var takeId = (id, index) => `${id}_${String(index + 1).padStart(2, "0")}`;
14651
+ function takes(id, layersOf, falloff, rate = [0.96, 1.04], bus) {
14652
+ const sounds = TAKES.map((take, index) => recipe(takeId(id, index), layersOf(take, index), falloff, bus));
14653
+ return { sounds, set: { id, variants: sounds.map((sound) => sound.id), rate } };
14654
+ }
14655
+ var footstep = (surface, gait) => takes(footstepSoundId(surface, gait), (take) => footLayers(surface, take, GAITS[gait]), FOOTSTEP_FALLOFF);
14656
+ var LANDINGS = { light: { weight: 1.4, cloth: 0.12 }, heavy: { weight: 2.4, cloth: 0.22 } };
14657
+ var LANDING_LEVEL = 0.65;
14658
+ var landing = (surface, landing2) => takes(
14659
+ `land_${surface}_${landing2}`,
14660
+ (take) => quieter(LANDING_LEVEL, [
14661
+ // both feet, a hair apart, then the knees taking the weight (a low body thump) and the clothes settling
14662
+ ...footLayers(surface, take, GAITS.run, LANDINGS[landing2].weight),
14663
+ ...footLayers(surface, { ...take, v: take.v * 0.6 }, GAITS.run, LANDINGS[landing2].weight * 0.6).map((layer2) => ({ ...layer2, at: (typeof layer2.at === "number" ? layer2.at : 0) + 0.018 * take.t })),
14664
+ sceneTone({ f: 60 * take.f, f2: 38, d: 0.14 * LANDINGS[landing2].weight * take.t, vol: 0.22 * LANDINGS[landing2].weight ** 0.7 * take.v, delay: 0.02 }),
14665
+ sceneNoise({ type: "bandpass", f: [1800, 2600], q: 0.8, d: 0.18, vol: LANDINGS[landing2].cloth * take.v, delay: 0.04 })
14666
+ ]),
14667
+ FOOTSTEP_FALLOFF
14668
+ );
14669
+ var whoosh = (blow3, from, to, d, vol, q = 1.6) => (take) => [
14670
+ // the air: a band sweeping up as the limb speeds up; it peaks `SWING_LEAD_SECONDS` in, the moment before the contact, then dies away
14671
+ sceneNoise({ type: "bandpass", f: from * take.f, f2: to * take.f, q, a: SWING_LEAD_SECONDS[blow3], d: d * take.t, vol: vol * take.v }),
14672
+ sceneNoise({ type: "lowpass", f: 600 * take.f, a: SWING_LEAD_SECONDS[blow3], d: d * take.t * 0.8, vol: vol * 0.35 * take.v })
14673
+ ];
14674
+ var fleshHit = (o) => (take) => [
14675
+ sceneNoise({ type: "bandpass", f: o.slapF * take.f, q: 1.2, d: 0.03 * take.t, vol: o.slap * take.v }),
14676
+ sceneNoise({ type: "highpass", f: 3500 * take.f, d: 0.012, vol: o.slap * 0.4 * take.v }),
14677
+ sceneTone({ f: o.thudF * take.f, f2: o.thudF * 0.5, d: o.thudD * take.t, vol: o.thud * take.v }),
14678
+ sceneNoise({ type: "lowpass", f: 600 * take.f, d: o.thudD * 1.2 * take.t, vol: o.thud * 0.5 * take.v }),
14679
+ ...o.crack ? [sceneNoise({ type: "highpass", f: 2500, d: 0.015, vol: o.crack * take.v, delay: 6e-3 }), sceneTone({ wave: "square", f: 900 * take.f, f2: 300, d: 0.02, vol: o.crack * 0.15 * take.v, delay: 6e-3 })] : [],
14680
+ ...o.wet ? [sceneNoise({ type: "bandpass", f: 1400 * take.f, f2: 500, q: 2.5, d: 0.12 * take.t, vol: o.wet * take.v, delay: 0.01 })] : []
14681
+ ];
14682
+ var knock = (o) => (take) => [
14683
+ sceneNoise({ type: "bandpass", f: o.clickF * take.f, q: 1.5, d: 0.018, vol: o.click * take.v }),
14684
+ ...o.tones.map(([f, d, vol]) => sceneTone({ f: f * take.f, d: d * take.t, vol: vol * take.v }))
14685
+ ];
14686
+ var voice = (o) => (take) => [
14687
+ sceneVoice({ f0: o.f0 * take.f, f1: o.f1 * take.f, d: o.d * take.t, fA: o.fA * take.f, fB: o.fB * take.f, noise: o.noise, vol: o.vol * take.v })
14688
+ ];
14689
+ var BODY = {
14690
+ jump: (take) => [
14691
+ sceneNoise({ type: "bandpass", f: 900 * take.f, f2: 1700 * take.f, q: 1, a: 0.03, d: 0.12, vol: 0.18 * take.v }),
14692
+ sceneNoise({ type: "bandpass", f: 2200 * take.f, q: 0.8, d: 0.04, vol: 0.12 * take.v }),
14693
+ sceneTone({ f: 85 * take.f, f2: 60, d: 0.04, vol: 0.2 * take.v })
14694
+ ],
14695
+ cloth_rustle: (take) => [
14696
+ { ...sceneNoise({ type: "bandpass", f: 2400 * take.f, q: 0.8, a: 0.06, d: 0.22 * take.t, vol: 0.12 * take.v }), modulators: [{ target: "gain", wave: "triangle", rate: [9, 14], depth: 0.5 }] }
14697
+ ],
14698
+ body_fall: (take) => [
14699
+ sceneTone({ f: 58 * take.f, f2: 35, d: 0.28 * take.t, vol: 0.9 * take.v }),
14700
+ sceneNoise({ type: "lowpass", f: 480 * take.f, d: 0.3 * take.t, vol: 0.55 * take.v }),
14701
+ sceneNoise({ type: "bandpass", f: 1300 * take.f, q: 1, d: 0.05, vol: 0.3 * take.v }),
14702
+ // the limbs a moment later, and the clothes
14703
+ sceneTone({ f: 75 * take.f, f2: 45, d: 0.12, vol: 0.4 * take.v, delay: 0.13 * take.t }),
14704
+ sceneNoise({ type: "bandpass", f: 2200, q: 0.8, d: 0.2, vol: 0.12 * take.v, delay: 0.05 })
14705
+ ],
14706
+ fall_damage: (take) => [
14707
+ ...BODY.body_fall(take),
14708
+ sceneNoise({ type: "highpass", f: 2800, d: 0.02, vol: 0.35 * take.v, delay: 0.01 }),
14709
+ ...voice({ f0: 170, f1: 110, d: 0.32, fA: 650, fB: 1150, noise: 0.25, vol: 0.32 })(take).map((layer2) => ({ ...layer2, at: 0.08 }))
14710
+ ],
14711
+ player_hurt: voice({ f0: 150, f1: 105, d: 0.26, fA: 620, fB: 1100, noise: 0.3, vol: 0.35 }),
14712
+ player_breath_run: (take) => [
14713
+ sceneNoise({ type: "bandpass", f: 1200 * take.f, q: 1, a: 0.08, d: 0.22 * take.t, vol: 0.25 * take.v }),
14714
+ sceneNoise({ type: "bandpass", f: 900 * take.f, q: 1, a: 0.06, d: 0.26 * take.t, vol: 0.3 * take.v, delay: 0.3 * take.t })
14715
+ ],
14716
+ player_death: voice({ f0: 135, f1: 68, d: 1, fA: 560, fB: 980, noise: 0.35, vol: 0.4 }),
14717
+ bite: (take) => [
14718
+ sceneNoise({ type: "highpass", f: 2600, d: 0.015, vol: 0.4 * take.v }),
14719
+ sceneNoise({ type: "bandpass", f: 1600 * take.f, f2: 450, q: 2.2, d: 0.16 * take.t, vol: 0.55 * take.v, delay: 0.01 }),
14720
+ sceneNoise({ type: "bandpass", f: 3200 * take.f, f2: 1800, q: 1.2, d: 0.22 * take.t, vol: 0.25 * take.v, delay: 0.09 }),
14721
+ sceneTone({ f: 120 * take.f, f2: 70, d: 0.06, vol: 0.3 * take.v })
14722
+ ],
14723
+ grab: (take) => [
14724
+ sceneNoise({ type: "bandpass", f: 1900 * take.f, q: 0.9, a: 4e-3, d: 0.13 * take.t, vol: 0.32 * take.v }),
14725
+ sceneTone({ f: 95 * take.f, f2: 60, d: 0.07, vol: 0.3 * take.v }),
14726
+ sceneNoise({ type: "lowpass", f: 700, d: 0.08, vol: 0.2 * take.v })
14727
+ ],
14728
+ punch_swing: whoosh("punch", 450, 1500, 0.16, 0.9),
14729
+ punch_hit_body: fleshHit({ slap: 0.55, slapF: 1500, thud: 0.6, thudF: 115, thudD: 0.09 }),
14730
+ punch_hit_face: fleshHit({ slap: 0.75, slapF: 2300, thud: 0.45, thudF: 150, thudD: 0.07, crack: 0.25 }),
14731
+ punch_hit_object: knock({ clickF: 1800, click: 0.3, tones: [[210, 0.04, 0.25]] }),
14732
+ kick_swing: whoosh("kick", 280, 950, 0.22, 1, 1.3),
14733
+ kick_hit: fleshHit({ slap: 0.45, slapF: 1100, thud: 0.85, thudF: 82, thudD: 0.14 }),
14734
+ kick_hit_object: knock({ clickF: 1e3, click: 0.3, tones: [[92, 0.08, 0.5]] }),
14735
+ shove_two_hands: (take) => [
14736
+ sceneNoise({ type: "bandpass", f: 950 * take.f, q: 0.9, a: 0.01, d: 0.13 * take.t, vol: 0.38 * take.v }),
14737
+ sceneTone({ f: 100 * take.f, f2: 60, d: 0.1 * take.t, vol: 0.45 * take.v }),
14738
+ sceneNoise({ type: "bandpass", f: 2300, q: 0.8, d: 0.16, vol: 0.12 * take.v, delay: 0.03 })
14739
+ ],
14740
+ knife_swing: whoosh("blade", 1900, 4600, 0.12, 1.1, 2.2),
14741
+ knife_stab: fleshHit({ slap: 0.2, slapF: 1200, thud: 0.4, thudF: 160, thudD: 0.06, wet: 0.5 }),
14742
+ knife_slash: (take) => [
14743
+ sceneNoise({ type: "highpass", f: 3200 * take.f, f2: 1800, d: 0.13 * take.t, vol: 0.35 * take.v }),
14744
+ sceneNoise({ type: "bandpass", f: 1300 * take.f, f2: 600, q: 2.5, d: 0.12 * take.t, vol: 0.35 * take.v, delay: 0.02 })
14745
+ ],
14746
+ knife_hit_object: knock({ clickF: 3200, click: 0.25, tones: [[2800, 0.08, 0.07], [4150, 0.06, 0.05]] }),
14747
+ blunt_swing: whoosh("blunt", 220, 760, 0.28, 1.1, 1.5),
14748
+ blunt_hit: fleshHit({ slap: 0.35, slapF: 900, thud: 1, thudF: 70, thudD: 0.18, crack: 0.3 }),
14749
+ blunt_hit_object: knock({ clickF: 1400, click: 0.35, tones: [[300, 0.12, 0.25], [760, 0.08, 0.12]] }),
14750
+ axe_swing: whoosh("chop", 190, 620, 0.3, 1.2, 1.4),
14751
+ axe_chop: fleshHit({ slap: 0.3, slapF: 1400, thud: 0.75, thudF: 140, thudD: 0.1, crack: 0.35, wet: 0.45 }),
14752
+ axe_hit_object: knock({ clickF: 2200, click: 0.4, tones: [[1500, 0.15, 0.1], [2300, 0.1, 0.06], [140, 0.1, 0.35]] })
14753
+ };
14754
+ var BODY_BUS = { player_hurt: "voices", player_death: "voices", player_breath_run: "voices", fall_damage: "voices" };
14755
+ var built = [
14756
+ ...SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstep(surface, gait))),
14757
+ ...SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((l) => landing(surface, l))),
14758
+ ...BODY_SOUND_IDS.map((id) => takes(id, BODY[id], BODY_FALLOFF, id.endsWith("_swing") ? [0.94, 1.06] : [0.96, 1.04], BODY_BUS[id]))
14759
+ ];
14760
+ var BODY_FOLEY_BANK = {
14761
+ id: "body_foley",
14762
+ sounds: built.flatMap((entry) => entry.sounds),
14763
+ sets: built.map((entry) => entry.set)
14030
14764
  };
14765
+ var BODY_FOLEY_SET_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
14031
14766
 
14032
14767
  // src/audio/recipes.ts
14033
14768
  var scene = (id, layers, line, vars) => sceneRecipe(id, layers, line, vars ? { vars } : {});