@adatechnology/game-vfx-three 0.3.6 → 0.5.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.
@@ -45,6 +45,19 @@ var GameVfxThree = (() => {
45
45
  });
46
46
 
47
47
  // ../game-contracts/dist/index.js
48
+ var DEFAULT_COST_ALLOCATION_BYTES = 1024 * 1024;
49
+ var DEFERRED_QUEUE_ID = {
50
+ DESTRUCTION_EVENTS: "destruction.events",
51
+ BREACH_PAINT: "floorplan.breach-paint",
52
+ AUDIO_VOICES: "audio.voices",
53
+ VEHICLE_CREASE: "vehicles.crease"
54
+ };
55
+ var DEFERRED_PRIORITY = {
56
+ [DEFERRED_QUEUE_ID.DESTRUCTION_EVENTS]: { priority: 300, phase: "present" },
57
+ [DEFERRED_QUEUE_ID.BREACH_PAINT]: { priority: 250, phase: "present" },
58
+ [DEFERRED_QUEUE_ID.AUDIO_VOICES]: { priority: 200, phase: "idle", reserveMs: 1 },
59
+ [DEFERRED_QUEUE_ID.VEHICLE_CREASE]: { priority: 100, phase: "idle" }
60
+ };
48
61
  var KIT_ORIGIN_KEY = "kitOrigin";
49
62
  function markKitOrigin(object, origin) {
50
63
  const node = object;
@@ -52,6 +65,42 @@ var GameVfxThree = (() => {
52
65
  node.userData[KIT_ORIGIN_KEY] = { ...origin };
53
66
  return object;
54
67
  }
68
+ var SURFACE_IDS = ["concrete", "asphalt", "wood", "tile", "dirt", "grass", "gravel", "sand", "metal", "water", "glass", "carpet"];
69
+ var GAIT_IDS = ["walk", "run"];
70
+ var LANDING_IDS = ["light", "heavy"];
71
+ var BODY_SOUND_IDS = [
72
+ "jump",
73
+ "cloth_rustle",
74
+ "body_fall",
75
+ "fall_damage",
76
+ "player_hurt",
77
+ "player_breath_run",
78
+ "player_death",
79
+ "bite",
80
+ "grab",
81
+ "punch_swing",
82
+ "punch_hit_body",
83
+ "punch_hit_face",
84
+ "punch_hit_object",
85
+ "kick_swing",
86
+ "kick_hit",
87
+ "kick_hit_object",
88
+ "shove_two_hands",
89
+ "knife_swing",
90
+ "knife_stab",
91
+ "knife_slash",
92
+ "knife_hit_object",
93
+ "blunt_swing",
94
+ "blunt_hit",
95
+ "blunt_hit_object",
96
+ "axe_swing",
97
+ "axe_chop",
98
+ "axe_hit_object"
99
+ ];
100
+ var footstepSoundId = (surface, gait) => `footstep_${surface}_${gait}`;
101
+ var FOOTSTEP_SOUND_IDS = SURFACE_IDS.flatMap((surface) => GAIT_IDS.map((gait) => footstepSoundId(surface, gait)));
102
+ var LAND_SOUND_IDS = SURFACE_IDS.flatMap((surface) => LANDING_IDS.map((landing) => `land_${surface}_${landing}`));
103
+ var BODY_FOLEY_IDS = [...FOOTSTEP_SOUND_IDS, ...LAND_SOUND_IDS, ...BODY_SOUND_IDS];
55
104
 
56
105
  // ../game-vfx/dist/index.js
57
106
  var __defProp2 = Object.defineProperty;
@@ -1344,11 +1393,231 @@ var GameVfxThree = (() => {
1344
1393
  glow.outIntensity = glow.i * Math.pow(1 - age, glow.curve) * flicker;
1345
1394
  }
1346
1395
  }
1396
+ var LEHMER_MULTIPLIER = 16807;
1397
+ var LEHMER_MODULUS = 2147483647;
1398
+ var HASH_MIX = 2654435761;
1399
+ function normalizeLehmerSeed(seed) {
1400
+ const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
1401
+ return whole === 0 ? 1 : whole;
1402
+ }
1403
+ function lehmer(seed) {
1404
+ let state = normalizeLehmerSeed(seed);
1405
+ const next = () => {
1406
+ state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
1407
+ return (state - 1) / (LEHMER_MODULUS - 1);
1408
+ };
1409
+ return {
1410
+ next,
1411
+ between: (min2, max2) => min2 + (max2 - min2) * next(),
1412
+ direction(out) {
1413
+ const up = next() * 2 - 1;
1414
+ const angle = next() * Math.PI * 2;
1415
+ const ring2 = Math.sqrt(1 - up * up);
1416
+ out.x = ring2 * Math.cos(angle);
1417
+ out.y = up;
1418
+ out.z = ring2 * Math.sin(angle);
1419
+ return out;
1420
+ },
1421
+ get state() {
1422
+ return state;
1423
+ }
1424
+ };
1425
+ }
1426
+ function mixEventSeed(eventSeed) {
1427
+ let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
1428
+ mixed ^= mixed >>> 13;
1429
+ mixed = Math.imul(mixed, 3266489909);
1430
+ mixed ^= mixed >>> 16;
1431
+ return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
1432
+ }
1433
+ function streamFor(eventSeed) {
1434
+ return lehmer(mixEventSeed(eventSeed));
1435
+ }
1436
+ var SKIN = 5e-3;
1437
+ var MAX_PAD = 0.45;
1438
+ function rotation(q) {
1439
+ const [x, y, z, w] = q ?? [0, 0, 0, 1];
1440
+ const n = Math.hypot(x, y, z, w) || 1;
1441
+ const qx = x / n;
1442
+ const qy = y / n;
1443
+ const qz = z / n;
1444
+ const qw = w / n;
1445
+ return new Float64Array([
1446
+ 1 - 2 * (qy * qy + qz * qz),
1447
+ 2 * (qx * qy - qz * qw),
1448
+ 2 * (qx * qz + qy * qw),
1449
+ 2 * (qx * qy + qz * qw),
1450
+ 1 - 2 * (qx * qx + qz * qz),
1451
+ 2 * (qy * qz - qx * qw),
1452
+ 2 * (qx * qz - qy * qw),
1453
+ 2 * (qy * qz + qx * qw),
1454
+ 1 - 2 * (qx * qx + qy * qy)
1455
+ ]);
1456
+ }
1457
+ var Enclosures = class {
1458
+ constructor(seed = 20973) {
1459
+ __publicField(this, "spaces", /* @__PURE__ */ new Map());
1460
+ __publicField(this, "list", []);
1461
+ __publicField(this, "random");
1462
+ __publicField(this, "out", { x: 0, y: 0, z: 0, nx: 0, ny: 0, nz: 0 });
1463
+ __publicField(this, "l0", new Float64Array(3));
1464
+ __publicField(this, "l1", new Float64Array(3));
1465
+ __publicField(this, "q", new Float64Array(3));
1466
+ this.random = lehmer(seed);
1467
+ }
1468
+ get empty() {
1469
+ return this.list.length === 0;
1470
+ }
1471
+ get size() {
1472
+ return this.list.length;
1473
+ }
1474
+ /** Declares, moves or (with `null`) ends the space `id`. */
1475
+ set(id, enclosure) {
1476
+ if (!enclosure) {
1477
+ if (this.spaces.delete(id)) this.list = [...this.spaces.values()];
1478
+ return;
1479
+ }
1480
+ const [hx, hy, hz] = enclosure.halfSize;
1481
+ const space = {
1482
+ cx: enclosure.center[0],
1483
+ cy: enclosure.center[1],
1484
+ cz: enclosure.center[2],
1485
+ hx: Math.abs(hx),
1486
+ hy: Math.abs(hy),
1487
+ hz: Math.abs(hz),
1488
+ r: rotation(enclosure.orientation),
1489
+ radius: Math.hypot(hx, hy, hz),
1490
+ openings: enclosure.openings ?? [],
1491
+ leak: Math.max(0, enclosure.leak ?? 0)
1492
+ };
1493
+ const old = this.spaces.get(id);
1494
+ this.spaces.set(id, space);
1495
+ const at3 = old ? this.list.indexOf(old) : -1;
1496
+ if (at3 >= 0) this.list[at3] = space;
1497
+ else this.list.push(space);
1498
+ }
1499
+ get(id) {
1500
+ const s = this.spaces.get(id);
1501
+ if (!s) return void 0;
1502
+ return { center: [s.cx, s.cy, s.cz], halfSize: [s.hx, s.hy, s.hz], openings: s.openings, leak: s.leak };
1503
+ }
1504
+ ids() {
1505
+ return [...this.spaces.keys()];
1506
+ }
1507
+ clear() {
1508
+ this.spaces.clear();
1509
+ this.list = [];
1510
+ }
1511
+ /** Whether a point is inside any space (on the inside of its walls). */
1512
+ inside(x, y, z) {
1513
+ for (const s of this.list) {
1514
+ this.toLocal(s, x, y, z, this.l0);
1515
+ if (Math.abs(this.l0[0]) <= s.hx && Math.abs(this.l0[1]) <= s.hy && Math.abs(this.l0[2]) <= s.hz) return true;
1516
+ }
1517
+ return false;
1518
+ }
1519
+ toLocal(s, x, y, z, out) {
1520
+ const dx = x - s.cx;
1521
+ const dy = y - s.cy;
1522
+ const dz = z - s.cz;
1523
+ const r = s.r;
1524
+ out[0] = r[0] * dx + r[3] * dy + r[6] * dz;
1525
+ out[1] = r[1] * dx + r[4] * dy + r[7] * dz;
1526
+ out[2] = r[2] * dx + r[5] * dy + r[8] * dz;
1527
+ }
1528
+ /** Passes at `rate` per second. */
1529
+ chance(rate, dt) {
1530
+ return rate > 0 && this.random.next() < 1 - Math.exp(-rate * dt);
1531
+ }
1532
+ /** The opening the wall point `q` (local) is in lets it through now. */
1533
+ through(s, q, dt) {
1534
+ for (const o of s.openings) {
1535
+ 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;
1536
+ if (o.rate === void 0 || this.chance(o.rate, dt)) return true;
1537
+ }
1538
+ return this.chance(s.leak, dt);
1539
+ }
1540
+ confine(x0, y0, z0, x1, y1, z1, pad, dt) {
1541
+ const list = this.list;
1542
+ if (list.length === 0) return false;
1543
+ const p = Math.min(Math.max(0, pad), MAX_PAD);
1544
+ const l0 = this.l0;
1545
+ const l1 = this.l1;
1546
+ const q = this.q;
1547
+ for (const s of list) {
1548
+ const reach = s.radius + p + 0.05;
1549
+ const dx = x1 - s.cx;
1550
+ const dy = y1 - s.cy;
1551
+ const dz = z1 - s.cz;
1552
+ if (dx * dx + dy * dy + dz * dz > reach * reach) {
1553
+ const ex = x0 - s.cx;
1554
+ const ey = y0 - s.cy;
1555
+ const ez = z0 - s.cz;
1556
+ if (ex * ex + ey * ey + ez * ez > reach * reach) continue;
1557
+ }
1558
+ this.toLocal(s, x0, y0, z0, l0);
1559
+ this.toLocal(s, x1, y1, z1, l1);
1560
+ const h = [s.hx, s.hy, s.hz];
1561
+ const in0 = Math.abs(l0[0]) <= s.hx && Math.abs(l0[1]) <= s.hy && Math.abs(l0[2]) <= s.hz;
1562
+ let axis2 = 0;
1563
+ let sign = 1;
1564
+ if (in0) {
1565
+ if (Math.abs(l1[0]) <= s.hx && Math.abs(l1[1]) <= s.hy && Math.abs(l1[2]) <= s.hz) continue;
1566
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1567
+ if (this.through(s, q, dt)) continue;
1568
+ let worst = -Infinity;
1569
+ for (let a = 0; a < 3; a++) {
1570
+ const over = Math.abs(l1[a]) - h[a];
1571
+ if (over > worst) {
1572
+ worst = over;
1573
+ axis2 = a;
1574
+ }
1575
+ }
1576
+ sign = l1[axis2] < 0 ? -1 : 1;
1577
+ for (let a = 0; a < 3; a++) l1[a] = Math.min(h[a] - SKIN, Math.max(-(h[a] - SKIN), l1[a]));
1578
+ sign = -sign;
1579
+ } else {
1580
+ const padded0 = Math.abs(l0[0]) <= s.hx + p && Math.abs(l0[1]) <= s.hy + p && Math.abs(l0[2]) <= s.hz + p;
1581
+ const bound = padded0 ? 0 : p;
1582
+ if (!(Math.abs(l1[0]) <= s.hx + bound && Math.abs(l1[1]) <= s.hy + bound && Math.abs(l1[2]) <= s.hz + bound)) continue;
1583
+ for (let a = 0; a < 3; a++) q[a] = Math.min(h[a], Math.max(-h[a], l1[a]));
1584
+ if (this.through(s, q, dt)) continue;
1585
+ let worst = -Infinity;
1586
+ for (let a = 0; a < 3; a++) {
1587
+ const over = Math.abs(l0[a]) - (h[a] + bound);
1588
+ if (over > worst) {
1589
+ worst = over;
1590
+ axis2 = a;
1591
+ }
1592
+ }
1593
+ sign = l0[axis2] < 0 ? -1 : 1;
1594
+ l1[axis2] = sign * (h[axis2] + bound + SKIN);
1595
+ }
1596
+ const r = s.r;
1597
+ const out = this.out;
1598
+ out.x = s.cx + r[0] * l1[0] + r[1] * l1[1] + r[2] * l1[2];
1599
+ out.y = s.cy + r[3] * l1[0] + r[4] * l1[1] + r[5] * l1[2];
1600
+ out.z = s.cz + r[6] * l1[0] + r[7] * l1[1] + r[8] * l1[2];
1601
+ out.nx = r[axis2] * sign;
1602
+ out.ny = r[3 + axis2] * sign;
1603
+ out.nz = r[6 + axis2] * sign;
1604
+ return true;
1605
+ }
1606
+ return false;
1607
+ }
1608
+ };
1609
+ function slideAlong(out, vx2, vy2, vz2) {
1610
+ const into = vx2 * out.nx + vy2 * out.ny + vz2 * out.nz;
1611
+ if (into >= 0) return [vx2, vy2, vz2];
1612
+ return [vx2 - into * out.nx, vy2 - into * out.ny, vz2 - into * out.nz];
1613
+ }
1347
1614
  var FLOOR_FRACTION = 0.4;
1348
1615
  var FLOOR_BOUNCE = -0.2;
1349
1616
  var ARRIVAL_FRAME = 30;
1350
1617
  var ARRIVAL_BASE = 0.3;
1351
1618
  var GROWTH_CAP_SECONDS = 3.5;
1619
+ var CARRIED_KINDS = [true, true, false, true, false];
1620
+ var PAD_OF_SIZE = 0.35;
1352
1621
  function createPuffPool(capacity) {
1353
1622
  const placeholder = { next: () => 0, between: (min2) => min2, direction: (out) => out, state: 0 };
1354
1623
  return new OrderedPool(capacity, () => ({
@@ -1443,13 +1712,24 @@ var GameVfxThree = (() => {
1443
1712
  if (puff3.vy < 0) puff3.vy *= FLOOR_BOUNCE;
1444
1713
  }
1445
1714
  }
1446
- function updatePuffs(pool, dt, wind) {
1715
+ function updatePuffs(pool, dt, wind, confiner) {
1447
1716
  pool.compact(ageOrKeep2, dt);
1717
+ const confining = confiner !== void 0 && !confiner.empty;
1448
1718
  for (const puff3 of pool.items) {
1449
1719
  const age = puff3.t / puff3.life;
1450
1720
  const heat = puff3.h0 > 0 ? puff3.h0 * Math.pow(Math.max(0, 1 - puff3.t / puff3.hl), 1.25) : 0;
1721
+ const x0 = puff3.x;
1722
+ const y0 = puff3.y;
1723
+ const z0 = puff3.z;
1451
1724
  if (puff3.ring) moveOnRing(puff3, puff3.ring, wind);
1452
1725
  else moveFree(puff3, dt, age, heat, wind);
1726
+ if (confining && CARRIED_KINDS[puff3.kind] && confiner.confine(x0, y0, z0, puff3.x, puff3.y, puff3.z, puff3.size * PAD_OF_SIZE, dt)) {
1727
+ const out = confiner.out;
1728
+ puff3.x = out.x;
1729
+ puff3.y = out.y;
1730
+ puff3.z = out.z;
1731
+ [puff3.vx, puff3.vy, puff3.vz] = slideAlong(out, puff3.vx, puff3.vy, puff3.vz);
1732
+ }
1453
1733
  const size = puff3.s0 + (puff3.s1 - puff3.s0) * (1 - Math.exp(-puff3.t * puff3.gk)) + puff3.sg * Math.min(puff3.t, GROWTH_CAP_SECONDS);
1454
1734
  puff3.size = size * Math.min(1, puff3.t * ARRIVAL_FRAME + ARRIVAL_BASE);
1455
1735
  puff3.age = age;
@@ -1975,10 +2255,10 @@ var GameVfxThree = (() => {
1975
2255
  ])
1976
2256
  ]
1977
2257
  };
1978
- function localToWorld(translation, rotation, local2) {
2258
+ function localToWorld(translation, rotation2, local2) {
1979
2259
  const [x, y, z] = local2;
1980
- const [a, c, e] = [Math.cos(rotation[0]), Math.cos(rotation[1]), Math.cos(rotation[2])];
1981
- const [b, d, f] = [Math.sin(rotation[0]), Math.sin(rotation[1]), Math.sin(rotation[2])];
2260
+ const [a, c, e] = [Math.cos(rotation2[0]), Math.cos(rotation2[1]), Math.cos(rotation2[2])];
2261
+ const [b, d, f] = [Math.sin(rotation2[0]), Math.sin(rotation2[1]), Math.sin(rotation2[2])];
1982
2262
  const [ae, af, be, bf] = [a * e, a * f, b * e, b * f];
1983
2263
  return [
1984
2264
  c * e * x + -c * f * y + d * z + translation[0],
@@ -2320,22 +2600,22 @@ var GameVfxThree = (() => {
2320
2600
  )
2321
2601
  ]
2322
2602
  };
2323
- function normalized(vector) {
2324
- const length = Math.sqrt(vector[0] * vector[0] + vector[1] * vector[1] + vector[2] * vector[2]) || 1;
2603
+ function normalized(vector2) {
2604
+ const length = Math.sqrt(vector2[0] * vector2[0] + vector2[1] * vector2[1] + vector2[2] * vector2[2]) || 1;
2325
2605
  const inverse = 1 / length;
2326
- return [vector[0] * inverse, vector[1] * inverse, vector[2] * inverse];
2606
+ return [vector2[0] * inverse, vector2[1] * inverse, vector2[2] * inverse];
2327
2607
  }
2328
- function plus(vector, x, y, z) {
2329
- return [vector[0] + x, vector[1] + y, vector[2] + z];
2608
+ function plus(vector2, x, y, z) {
2609
+ return [vector2[0] + x, vector2[1] + y, vector2[2] + z];
2330
2610
  }
2331
- function scaledBy(vector, factor) {
2332
- return [vector[0] * factor, vector[1] * factor, vector[2] * factor];
2611
+ function scaledBy(vector2, factor) {
2612
+ return [vector2[0] * factor, vector2[1] * factor, vector2[2] * factor];
2333
2613
  }
2334
- function plusScaled(vector, direction, factor) {
2335
- return [vector[0] + direction[0] * factor, vector[1] + direction[1] * factor, vector[2] + direction[2] * factor];
2614
+ function plusScaled(vector2, direction, factor) {
2615
+ return [vector2[0] + direction[0] * factor, vector2[1] + direction[1] * factor, vector2[2] + direction[2] * factor];
2336
2616
  }
2337
- function negated(vector) {
2338
- return [-vector[0], -vector[1], -vector[2]];
2617
+ function negated(vector2) {
2618
+ return [-vector2[0], -vector2[1], -vector2[2]];
2339
2619
  }
2340
2620
  var FRAME_DUST = normalized([0, -0.3, 1]);
2341
2621
  var SPLINTER_DIRECTION = normalized([0, 0.3, 1]);
@@ -3638,49 +3918,32 @@ var GameVfxThree = (() => {
3638
3918
  /** 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. */
3639
3919
  aliasOf(FUEL_LEAK, "vehicle_leak_ignited", {})
3640
3920
  ];
3921
+ var vector = [variable("vx"), variable("vy"), variable("vz")];
3922
+ var range = (name) => [variable(name), variable(name)];
3923
+ var singleParticle = (id, system) => ({
3924
+ id,
3925
+ pageId: `particle:${id}`,
3926
+ team: "world",
3927
+ budget: { particles: 1, lights: 0 },
3928
+ loop: 1e-3,
3929
+ timeline: [
3930
+ spawn(system, {
3931
+ p: [0, 0, 0],
3932
+ v: vector,
3933
+ life: range("life"),
3934
+ size: range("size"),
3935
+ grow: variable("grow"),
3936
+ c0: variable("color"),
3937
+ a: variable("alpha"),
3938
+ g: variable("gravity"),
3939
+ drag: variable("drag")
3940
+ })
3941
+ ]
3942
+ });
3943
+ var SINGLE_PARTICLE_EFFECTS = [singleParticle("particle_puff", "normal"), singleParticle("particle_glow", "add"), singleParticle("particle_streak", "streak")];
3641
3944
  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];
3642
3945
  var WORKSHOP_EFFECTS = Object.fromEntries(ALL_EFFECTS.map((effect) => [effect.id, effect]));
3643
- var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3644
- var LEHMER_MULTIPLIER = 16807;
3645
- var LEHMER_MODULUS = 2147483647;
3646
- var HASH_MIX = 2654435761;
3647
- function normalizeLehmerSeed(seed) {
3648
- const whole = Math.floor(Math.abs(seed)) % (LEHMER_MODULUS - 1);
3649
- return whole === 0 ? 1 : whole;
3650
- }
3651
- function lehmer(seed) {
3652
- let state = normalizeLehmerSeed(seed);
3653
- const next = () => {
3654
- state = state * LEHMER_MULTIPLIER % LEHMER_MODULUS;
3655
- return (state - 1) / (LEHMER_MODULUS - 1);
3656
- };
3657
- return {
3658
- next,
3659
- between: (min2, max2) => min2 + (max2 - min2) * next(),
3660
- direction(out) {
3661
- const up = next() * 2 - 1;
3662
- const angle = next() * Math.PI * 2;
3663
- const ring2 = Math.sqrt(1 - up * up);
3664
- out.x = ring2 * Math.cos(angle);
3665
- out.y = up;
3666
- out.z = ring2 * Math.sin(angle);
3667
- return out;
3668
- },
3669
- get state() {
3670
- return state;
3671
- }
3672
- };
3673
- }
3674
- function mixEventSeed(eventSeed) {
3675
- let mixed = Math.imul((eventSeed | 0) ^ HASH_MIX, 2246822507);
3676
- mixed ^= mixed >>> 13;
3677
- mixed = Math.imul(mixed, 3266489909);
3678
- mixed ^= mixed >>> 16;
3679
- return (mixed >>> 0) % (LEHMER_MODULUS - 1) + 1;
3680
- }
3681
- function streamFor(eventSeed) {
3682
- return lehmer(mixEventSeed(eventSeed));
3683
- }
3946
+ var GAME_EFFECTS = { ...WORKSHOP_EFFECTS, ...Object.fromEntries([...EVENT_EFFECTS, ...VIEWMODEL_EFFECTS, ...VEHICLE_EFFECTS, ...SINGLE_PARTICLE_EFFECTS].map((effect) => [effect.id, effect])) };
3684
3947
  var TABLE = {
3685
3948
  concrete: { dust: 9078138, chip: 7236195, spark: 2 },
3686
3949
  metal: { dust: 0, chip: 0, spark: 18 },
@@ -4350,8 +4613,8 @@ var GameVfxThree = (() => {
4350
4613
  var UP12 = [0, 1, 0];
4351
4614
  var ZERO = [0, 0, 0];
4352
4615
  var FULL_TURN_DRAWN = 6.283;
4353
- function scaledSizes(range, factor) {
4354
- return [range[0] * Math.sqrt(factor), range[1] * Math.sqrt(factor)];
4616
+ function scaledSizes(range2, factor) {
4617
+ return [range2[0] * Math.sqrt(factor), range2[1] * Math.sqrt(factor)];
4355
4618
  }
4356
4619
  function fireEmit(engine, key, dt, centre, radius, factor, options = {}) {
4357
4620
  const wind = options.wind ?? ZERO;
@@ -4984,8 +5247,8 @@ var GameVfxThree = (() => {
4984
5247
  function addScorch(store, random, x, z, radius) {
4985
5248
  const variant = Math.floor(random.next() * SCORCH_VARIANTS);
4986
5249
  const lift = 0.03 + nextLift(store) * 1e-3;
4987
- const rotation = random.next() * 6;
4988
- store.add({ kind: "scorch", x, z, radius, variant, lift, rotation });
5250
+ const rotation2 = random.next() * 6;
5251
+ store.add({ kind: "scorch", x, z, radius, variant, lift, rotation: rotation2 });
4989
5252
  }
4990
5253
  function addRimRocks(store, ground, random, x, z, radius) {
4991
5254
  const count = Math.round(6 + radius * 7);
@@ -5014,8 +5277,8 @@ var GameVfxThree = (() => {
5014
5277
  function addCrater(store, ground, random, spec) {
5015
5278
  ground?.dent(random, spec.x, spec.z, spec.radius, spec.depth);
5016
5279
  if (!spec.marks) return;
5017
- const rotation = random.next() * 6;
5018
- store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation });
5280
+ const rotation2 = random.next() * 6;
5281
+ store.add({ kind: "dirt", x: spec.x, z: spec.z, radius: spec.radius * 1.02, lift: 0.045, rotation: rotation2 });
5019
5282
  addRimRocks(store, ground, random, spec.x, spec.z, spec.radius);
5020
5283
  }
5021
5284
  function addBloodSplat(store, ground, random, spec) {
@@ -5571,6 +5834,7 @@ var GameVfxThree = (() => {
5571
5834
  state.gas = Math.max(0, state.gas - dt / DECAY.gasSeconds);
5572
5835
  state.beat *= Math.exp(-DECAY.beat * dt);
5573
5836
  }
5837
+ var PAD_OF_SIZE2 = 0.35;
5574
5838
  var clamp4 = (v, lo, hi) => v < lo ? lo : v > hi ? hi : v;
5575
5839
  var lerp4 = (a, b, t) => a + (b - a) * t;
5576
5840
  function rgb(hex) {
@@ -5730,9 +5994,20 @@ var GameVfxThree = (() => {
5730
5994
  vy2 *= k;
5731
5995
  vz2 *= k;
5732
5996
  }
5733
- const x = this.position[i3] + vx2 * dt;
5997
+ let x = this.position[i3] + vx2 * dt;
5734
5998
  let y = this.position[i3 + 1] + vy2 * dt;
5735
- const z = this.position[i3 + 2] + vz2 * dt;
5999
+ let z = this.position[i3 + 2] + vz2 * dt;
6000
+ const confiner = host.confiner;
6001
+ if (def.carried && confiner && !confiner.empty) {
6002
+ const pad = this.size0[i] * lerp4(1, def.grow, t) * PAD_OF_SIZE2;
6003
+ if (confiner.confine(this.position[i3], this.position[i3 + 1], this.position[i3 + 2], x, y, z, pad, dt)) {
6004
+ const out = confiner.out;
6005
+ x = out.x;
6006
+ y = out.y;
6007
+ z = out.z;
6008
+ [vx2, vy2, vz2] = slideAlong(out, vx2, vy2, vz2);
6009
+ }
6010
+ }
5736
6011
  if (def.ground !== "none") {
5737
6012
  const own = this.floor[i];
5738
6013
  const gy = Number.isNaN(own) ? host.groundAt(x, z) + 5e-3 : own;
@@ -5818,7 +6093,8 @@ var GameVfxThree = (() => {
5818
6093
  alpha: 0.35,
5819
6094
  fadeIn: 0.15,
5820
6095
  cone: 0.6,
5821
- ground: "none"
6096
+ ground: "none",
6097
+ carried: true
5822
6098
  },
5823
6099
  sparks: {
5824
6100
  id: "sparks",
@@ -5862,7 +6138,8 @@ var GameVfxThree = (() => {
5862
6138
  alpha: 0.4,
5863
6139
  fadeIn: 0.1,
5864
6140
  cone: 0.4,
5865
- ground: "none"
6141
+ ground: "none",
6142
+ carried: true
5866
6143
  },
5867
6144
  shards: {
5868
6145
  id: "shards",
@@ -6020,7 +6297,8 @@ var GameVfxThree = (() => {
6020
6297
  fadeIn: 0.1,
6021
6298
  cone: 0.5,
6022
6299
  ground: "stop",
6023
- flutter: 0.6
6300
+ flutter: 0.6,
6301
+ carried: true
6024
6302
  },
6025
6303
  steam: {
6026
6304
  id: "steam",
@@ -6042,7 +6320,8 @@ var GameVfxThree = (() => {
6042
6320
  alpha: 0.35,
6043
6321
  fadeIn: 0.15,
6044
6322
  cone: 0.6,
6045
- ground: "none"
6323
+ ground: "none",
6324
+ carried: true
6046
6325
  },
6047
6326
  flash: {
6048
6327
  id: "flash",
@@ -6175,7 +6454,8 @@ var GameVfxThree = (() => {
6175
6454
  const host = {
6176
6455
  groundAt: this.options.groundAt,
6177
6456
  gravity: this.options.gravity,
6178
- land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag)
6457
+ land: (owner, x, y, z, color, tag) => this.listeners.get(owner)?.(x, y, z, color, tag),
6458
+ ...this.options.confiner ? { confiner: this.options.confiner } : {}
6179
6459
  };
6180
6460
  if (this.additive.live > 0 || this.additive.drawCount > 0) this.additive.update(dt, host);
6181
6461
  if (this.normal.live > 0 || this.normal.drawCount > 0) this.normal.update(dt, host);
@@ -6188,9 +6468,9 @@ var GameVfxThree = (() => {
6188
6468
  var TWO_PI_ROUNDED = 6.2832;
6189
6469
  var WIDE_CONE = 3.1;
6190
6470
  var UP13 = [0, 1, 0];
6191
- function drawRange(random, range) {
6192
- if (Array.isArray(range)) return random.between(range[0], range[1]);
6193
- return range || 0;
6471
+ function drawRange(random, range2) {
6472
+ if (Array.isArray(range2)) return random.between(range2[0], range2[1]);
6473
+ return range2 || 0;
6194
6474
  }
6195
6475
  function randomUnit(random, out) {
6196
6476
  const up = random.next() * 2 - 1;
@@ -6381,8 +6661,8 @@ var GameVfxThree = (() => {
6381
6661
  * 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
6382
6662
  * world axes). The surface they were on is gone (a brick came out of a wall with its bullet holes). Returns how many went.
6383
6663
  */
6384
- clearInside(center, half, rotation = [0, 0, 0, 1]) {
6385
- const qx = -rotation[0], qy = -rotation[1], qz = -rotation[2], qw = rotation[3];
6664
+ clearInside(center, half, rotation2 = [0, 0, 0, 1]) {
6665
+ const qx = -rotation2[0], qy = -rotation2[1], qz = -rotation2[2], qw = rotation2[3];
6386
6666
  let gone = 0;
6387
6667
  for (const decal2 of this.decals) {
6388
6668
  if (!decal2.on) continue;
@@ -6503,10 +6783,14 @@ var GameVfxThree = (() => {
6503
6783
  var DEFAULT_FADE_IN = 0.1;
6504
6784
  var FLOATS_PER_VECTOR = 3;
6505
6785
  var BYTE_MAX2 = 255;
6786
+ var CARRIED_GRAVITY = 3;
6787
+ var PAD_OF_SIZE3 = 0.35;
6506
6788
  var SpritePool = class {
6507
- constructor(capacity, context) {
6789
+ /** `carried`: the normal-blend pool (smoke, dust, steam), whose light sprites go round the closed spaces; the additive one (flames, sparks) does not. */
6790
+ constructor(capacity, context, carried = false) {
6508
6791
  __publicField(this, "capacity", capacity);
6509
6792
  __publicField(this, "context", context);
6793
+ __publicField(this, "carried", carried);
6510
6794
  __publicField(this, "count", 0);
6511
6795
  __publicField(this, "position");
6512
6796
  __publicField(this, "velocity");
@@ -6606,6 +6890,7 @@ var GameVfxThree = (() => {
6606
6890
  }
6607
6891
  update(dt, leaveDecal) {
6608
6892
  const { position, velocity } = this;
6893
+ const confiner = this.context.confiner;
6609
6894
  for (let index = this.count - 1; index >= 0; index--) {
6610
6895
  this.life[index] -= dt;
6611
6896
  if (this.life[index] <= 0) {
@@ -6619,9 +6904,25 @@ var GameVfxThree = (() => {
6619
6904
  velocity[offset] *= factor;
6620
6905
  velocity[offset + 1] *= factor;
6621
6906
  velocity[offset + 2] *= factor;
6907
+ const x0 = position[offset];
6908
+ const y0 = position[offset + 1];
6909
+ const z0 = position[offset + 2];
6622
6910
  position[offset] += velocity[offset] * dt;
6623
6911
  position[offset + 1] += velocity[offset + 1] * dt;
6624
6912
  position[offset + 2] += velocity[offset + 2] * dt;
6913
+ if (this.carried && confiner && !confiner.empty && Math.abs(this.gravity[index]) < CARRIED_GRAVITY) {
6914
+ const size = this.startSize[index] * (1 + (this.growth[index] - 1) * age);
6915
+ if (confiner.confine(x0, y0, z0, position[offset], position[offset + 1], position[offset + 2], size * PAD_OF_SIZE3, dt)) {
6916
+ const out = confiner.out;
6917
+ position[offset] = out.x;
6918
+ position[offset + 1] = out.y;
6919
+ position[offset + 2] = out.z;
6920
+ const [vx2, vy2, vz2] = slideAlong(out, velocity[offset], velocity[offset + 1], velocity[offset + 2]);
6921
+ velocity[offset] = vx2;
6922
+ velocity[offset + 1] = vy2;
6923
+ velocity[offset + 2] = vz2;
6924
+ }
6925
+ }
6625
6926
  const mode = this.groundMode[index];
6626
6927
  if (mode) {
6627
6928
  const groundHeight = this.context.groundAt(position[offset], position[offset + 2]);
@@ -6898,9 +7199,9 @@ var GameVfxThree = (() => {
6898
7199
  __publicField(this, "placement");
6899
7200
  __publicField(this, "accumulators", /* @__PURE__ */ new Map());
6900
7201
  const profile = options.profile ?? PROFILES.high;
6901
- this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace };
7202
+ this.context = { random: options.random, groundAt: options.groundAt ?? workshopGround, quality: { level: 0, mult: 1 }, trace: options.trace, ...options.confiner ? { confiner: options.confiner } : {} };
6902
7203
  this.add = new SpritePool(profile.sprites.add, this.context);
6903
- this.normal = new SpritePool(profile.sprites.normal, this.context);
7204
+ this.normal = new SpritePool(profile.sprites.normal, this.context, true);
6904
7205
  this.streaks = new StreakPool(profile.streaks, this.context);
6905
7206
  this.debris = new DebrisPool(this.context);
6906
7207
  this.decals = new DecalPool(this.context);
@@ -6936,13 +7237,13 @@ var GameVfxThree = (() => {
6936
7237
  return [origin[0] + cos2 * x + sin2 * z, origin[1] + point[1] - anchor[1], origin[2] - sin2 * x + cos2 * z];
6937
7238
  }
6938
7239
  /** A direction or velocity of the recipe's space in the world. */
6939
- turn(vector) {
7240
+ turn(vector2) {
6940
7241
  const placement = this.placement;
6941
- if (!placement) return vector;
6942
- if (placement.orientation) return rotate(placement.orientation, vector);
7242
+ if (!placement) return vector2;
7243
+ if (placement.orientation) return rotate(placement.orientation, vector2);
6943
7244
  const cos2 = Math.cos(placement.yaw);
6944
7245
  const sin2 = Math.sin(placement.yaw);
6945
- return [cos2 * vector[0] + sin2 * vector[2], vector[1], -sin2 * vector[0] + cos2 * vector[2]];
7246
+ return [cos2 * vector2[0] + sin2 * vector2[2], vector2[1], -sin2 * vector2[0] + cos2 * vector2[2]];
6946
7247
  }
6947
7248
  placed(options) {
6948
7249
  if (!this.placement) return options;
@@ -7088,12 +7389,14 @@ var GameVfxThree = (() => {
7088
7389
  const groundAt = options.groundAt ?? (ground ? (x, z) => ground.heightAt(x, z) : () => 0);
7089
7390
  const handles = { next: 0, events: 0 };
7090
7391
  const low = options.profile === "low";
7392
+ const enclosures = new Enclosures(20973 + (options.seed ?? 0));
7091
7393
  const spriteSystem = new SpriteKindSystem({
7092
7394
  capacity: options.sprites?.capacity ?? (low ? 1250 : 2500),
7093
7395
  density: options.sprites?.density ?? (low ? 0.6 : 1),
7094
7396
  seed: 2654435761 + (options.seed ?? 0),
7095
7397
  groundAt,
7096
7398
  gravity: options.gravity ?? DEFAULT_GRAVITY,
7399
+ confiner: enclosures,
7097
7400
  ...options.sprites?.kinds ? { kinds: options.sprites.kinds } : {}
7098
7401
  });
7099
7402
  const core = {
@@ -7143,7 +7446,7 @@ var GameVfxThree = (() => {
7143
7446
  return [camera2.x, camera2.y, camera2.z];
7144
7447
  }, forward: (local2) => [local2[0] ?? 0, local2[1] ?? 0, local2[2] ?? 0] };
7145
7448
  function workshop() {
7146
- return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt }));
7449
+ return engine ?? (engine = new WorkshopEngine({ random: sharedRandom, profile, groundAt, confiner: enclosures }));
7147
7450
  }
7148
7451
  function effectPlayer() {
7149
7452
  return player ?? (player = createEffectPlayer({ engine: workshop(), effects, view }));
@@ -7202,6 +7505,10 @@ var GameVfxThree = (() => {
7202
7505
  puff3.vz += dz * strength;
7203
7506
  }
7204
7507
  },
7508
+ enclose(id, enclosure) {
7509
+ enclosures.set(id, enclosure);
7510
+ },
7511
+ enclosures: () => enclosures,
7205
7512
  stop(handle) {
7206
7513
  player?.stop(handle);
7207
7514
  const fires = core.fires;
@@ -7229,7 +7536,7 @@ var GameVfxThree = (() => {
7229
7536
  core.clock.time += dt;
7230
7537
  core.schedule.update(core.clock.time);
7231
7538
  updateRings(core.rings, dt);
7232
- updatePuffs(core.puffs, dt, wind);
7539
+ updatePuffs(core.puffs, dt, wind, enclosures);
7233
7540
  updateSparks(core.sparks, { dt, groundAt });
7234
7541
  updateGlows(core.glows, dt);
7235
7542
  updateShocks(core.shocks, core.puffs.items, dt);
@@ -8396,9 +8703,42 @@ void main(){
8396
8703
  };
8397
8704
  }
8398
8705
 
8706
+ // ../game-render-three/dist/index.js
8707
+ var DEFAULT_ANGLE = Math.PI / 3;
8708
+ var LIGHT_PRIORITY = {
8709
+ blast: 900,
8710
+ rocket: 800,
8711
+ muzzle: 750,
8712
+ fire: 600,
8713
+ flare: 500,
8714
+ headlight: 400,
8715
+ weaponLight: 400,
8716
+ lamp: 200,
8717
+ street: 150
8718
+ };
8719
+ var EFFECT_STEP_SECONDS = 1 / 50;
8720
+
8399
8721
  // src/lights.ts
8400
- function createLightLayer(THREE, pool, scale) {
8722
+ var LIGHT_ON_THRESHOLD = 0.05;
8723
+ var FIRE_COLOR = 16742954;
8724
+ var FIRE_DECAY = 2;
8725
+ var BLAST_DECAY = 2;
8726
+ function followEntry(budget, handles, index, entry, request) {
8727
+ const handle = handles[index] ?? null;
8728
+ if (entry.intensity <= LIGHT_ON_THRESHOLD) {
8729
+ handle?.release({ fadeMs: 0 });
8730
+ handles[index] = null;
8731
+ return;
8732
+ }
8733
+ if (handle?.alive) {
8734
+ handle.update({ color: request.color, intensity: request.intensity, distance: request.distance, position: request.position });
8735
+ return;
8736
+ }
8737
+ handles[index] = budget.acquire(request);
8738
+ }
8739
+ function createLightLayer(THREE, pool, scale, budget) {
8401
8740
  const group = new THREE.Group();
8741
+ if (budget) return createBudgetedLightLayer(group, pool, scale, budget);
8402
8742
  const blast = pool.blast.map(() => {
8403
8743
  const light = new THREE.PointLight(16752720, 0, 30, 2);
8404
8744
  light.position.set(0, -50, 0);
@@ -8437,6 +8777,30 @@ void main(){
8437
8777
  }
8438
8778
  };
8439
8779
  }
8780
+ function createBudgetedLightLayer(group, pool, scale, budget) {
8781
+ const blast = pool.blast.map(() => null);
8782
+ const fire = pool.fire.map(() => null);
8783
+ return {
8784
+ group,
8785
+ sync() {
8786
+ let lit = 0;
8787
+ pool.blast.forEach((entry, index) => {
8788
+ 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] };
8789
+ followEntry(budget, blast, index, entry, request);
8790
+ if (entry.intensity > LIGHT_ON_THRESHOLD) lit++;
8791
+ });
8792
+ pool.fire.forEach((entry, index) => {
8793
+ 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] };
8794
+ followEntry(budget, fire, index, entry, request);
8795
+ if (entry.intensity > LIGHT_ON_THRESHOLD) lit++;
8796
+ });
8797
+ return lit;
8798
+ },
8799
+ dispose() {
8800
+ for (const handle of [...blast, ...fire]) handle?.release({ fadeMs: 0 });
8801
+ }
8802
+ };
8803
+ }
8440
8804
 
8441
8805
  // src/canvas.ts
8442
8806
  var SCORCH_SEED = 5;
@@ -8574,6 +8938,7 @@ void main(){
8574
8938
  group.add(mesh2);
8575
8939
  return mesh2;
8576
8940
  }
8941
+ let probeGeometry;
8577
8942
  const groundMarks = [];
8578
8943
  const spareGeometries = [];
8579
8944
  const blood = [];
@@ -8679,6 +9044,15 @@ void main(){
8679
9044
  rockMesh();
8680
9045
  plateMesh();
8681
9046
  },
9047
+ probes() {
9048
+ probeGeometry ?? (probeGeometry = new THREE.PlaneGeometry(1e-3, 1e-3).rotateX(-Math.PI / 2));
9049
+ return [...scorchMaterials, dirtMaterial].map((material) => {
9050
+ const mesh2 = new THREE.Mesh(probeGeometry, material);
9051
+ mesh2.receiveShadow = true;
9052
+ mesh2.frustumCulled = false;
9053
+ return mesh2;
9054
+ });
9055
+ },
8682
9056
  update(dt) {
8683
9057
  for (let index = 0; index < blood.length; index++) {
8684
9058
  const drawn = blood[index];
@@ -8691,6 +9065,7 @@ void main(){
8691
9065
  dispose() {
8692
9066
  clearMarks();
8693
9067
  for (const mesh2 of [instanced.blood, instanced.rock, instanced.plate]) mesh2?.dispose();
9068
+ probeGeometry?.dispose();
8694
9069
  for (const item of owned) item.dispose();
8695
9070
  }
8696
9071
  };
@@ -9282,7 +9657,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9282
9657
  }
9283
9658
  };
9284
9659
  }
9285
- function createWorkshopLayers(THREE, engine, pointScale, lightScale) {
9660
+ function createWorkshopLayers(THREE, engine, pointScale, lightScale, budget) {
9286
9661
  const group = new THREE.Group();
9287
9662
  group.name = "workshop";
9288
9663
  const layers = [
@@ -9297,15 +9672,30 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9297
9672
  for (const layer2 of layers) group.add(layer2.object);
9298
9673
  const meshes = createMeshLayer(THREE, engine.meshes);
9299
9674
  group.add(meshes.group);
9300
- const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
9301
- const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
9302
- for (const light of [...flashLights, ...fireLights]) group.add(light);
9675
+ const lights = budget ? budgetedLights(engine, lightScale, budget) : legacyLights(THREE, engine, lightScale, group);
9303
9676
  return {
9304
9677
  group,
9305
9678
  sync() {
9306
9679
  let drawCalls = 0;
9307
9680
  for (const layer2 of layers) if (layer2.sync()) drawCalls++;
9308
9681
  drawCalls += meshes.sync();
9682
+ lights.sync();
9683
+ return drawCalls;
9684
+ },
9685
+ dispose() {
9686
+ for (const layer2 of layers) layer2.dispose();
9687
+ meshes.dispose();
9688
+ lights.dispose();
9689
+ group.clear();
9690
+ }
9691
+ };
9692
+ }
9693
+ function legacyLights(THREE, engine, lightScale, group) {
9694
+ const flashLights = engine.lights.lights.map(() => new THREE.PointLight(16760944, 0, 14));
9695
+ const fireLights = engine.lights.fire.map(() => new THREE.PointLight(FIRE_LIGHT_COLOR, 0, FIRE_LIGHT_DISTANCE));
9696
+ for (const light of [...flashLights, ...fireLights]) group.add(light);
9697
+ return {
9698
+ sync() {
9309
9699
  engine.lights.lights.forEach((source, index) => {
9310
9700
  const light = flashLights[index];
9311
9701
  light.position.set(source.x, source.y, source.z);
@@ -9318,12 +9708,27 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9318
9708
  light.position.set(source.x, source.y, source.z);
9319
9709
  light.intensity = source.intensity * lightScale;
9320
9710
  });
9321
- return drawCalls;
9322
9711
  },
9323
9712
  dispose() {
9324
- for (const layer2 of layers) layer2.dispose();
9325
- meshes.dispose();
9326
- group.clear();
9713
+ }
9714
+ };
9715
+ }
9716
+ function budgetedLights(engine, lightScale, budget) {
9717
+ const flash2 = engine.lights.lights.map(() => null);
9718
+ const fire = engine.lights.fire.map(() => null);
9719
+ return {
9720
+ sync() {
9721
+ engine.lights.lights.forEach((source, index) => {
9722
+ 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] };
9723
+ followEntry(budget, flash2, index, source, request);
9724
+ });
9725
+ engine.lights.fire.forEach((source, index) => {
9726
+ 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] };
9727
+ followEntry(budget, fire, index, source, request);
9728
+ });
9729
+ },
9730
+ dispose() {
9731
+ for (const handle of [...flash2, ...fire]) handle?.release({ fadeMs: 0 });
9327
9732
  }
9328
9733
  };
9329
9734
  }
@@ -9384,7 +9789,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9384
9789
  const chunks = createChunkLayer(THREE, pools.chunks, { rock: rockGeometry, plate: plateGeometry }, colorOfChunk);
9385
9790
  const shocks = createShockLayer(THREE, pools.shocks);
9386
9791
  const sprites = createSpriteLayer(THREE, world.sprites(), options.pointScale ?? 700);
9387
- const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI);
9792
+ const lights = createLightLayer(THREE, pools.lights, options.lightScale ?? Math.PI, options.lights);
9388
9793
  const marks = createMarkLayer(THREE, { ground: pools.ground, createCanvas: options.createCanvas, rockGeometry, plateGeometry, ...options.markLimits });
9389
9794
  root.add(puffs.mesh, sparks.mesh, glows.mesh, chunks.rocks, chunks.plates, shocks.group, lights.group, sprites.additive, sprites.normal, marks.group);
9390
9795
  const stopMarks = world.onMarks((event) => marks.handle(event));
@@ -9417,7 +9822,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9417
9822
  workshopLayers?.dispose();
9418
9823
  if (workshopLayers) root.remove(workshopLayers.group);
9419
9824
  workshopEngine = engine;
9420
- workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1);
9825
+ workshopLayers = createWorkshopLayers(THREE, engine, options.pointScale ?? 700, 1, options.lights);
9421
9826
  root.add(workshopLayers.group);
9422
9827
  applyLayers();
9423
9828
  }
@@ -9457,6 +9862,8 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9457
9862
  });
9458
9863
  },
9459
9864
  stop: (handle) => world.stop(handle),
9865
+ // a module's closed space (a shut car's cabin): the world's smoke, dust and steam go round it
9866
+ enclose: (id, enclosure) => world.enclose(id, enclosure),
9460
9867
  configure(settings) {
9461
9868
  const band = settings.lod_band;
9462
9869
  if (typeof band === "string" && band in LOD_BANDS) world.forceLod(LOD_BANDS[band] ?? null);
@@ -9488,7 +9895,7 @@ void main(){vec2 d=gl_PointCoord-.5;float r=length(d)*2.;if(r>1.)discard;float a
9488
9895
  const meshes = createMeshLayerProbes(THREE);
9489
9896
  for (const mesh2 of meshes) mesh2.frustumCulled = false;
9490
9897
  const group = new THREE.Group();
9491
- group.add(...meshes);
9898
+ group.add(...meshes, ...marks.probes());
9492
9899
  anchors = { meshes, group };
9493
9900
  return { group, dispose: () => {
9494
9901
  } };