@adatechnology/game-destruction 0.6.1 → 0.7.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -181,6 +181,59 @@ panels), `drywall`, `wood_frame_wall`, `log_wall`, `sheet_metal_shack`, `glass_p
181
181
  - **Holes stay on the pieces.** Every `fracture` and breach fragment carries `marks` (`FragmentMark`: point, normal and diameter in the fragment's own
182
182
  axes) for the holes that fell inside it, at most 12 per fragment.
183
183
 
184
+ ## Blasts: shelter, fragment hits and confinement
185
+
186
+ All three are options, **off by default** (a blast then does exactly what it always did, goldens included). Set them for the world with
187
+ `createDestruction({ blast: { shelter: true, ... } })`, or per call: `world.explode(point, energy, radius, source, { shelter: true })`, or on the
188
+ explosive `Hit` (`hit.blast`, `hit.confinement`). A call overrides the world default (`shelter: false` turns it off). Every number below is an
189
+ option; the defaults are exported as `SHELTER_DEFAULTS`, `FRAGMENT_HIT_DEFAULTS` and `CONFINEMENT_DEFAULTS`. This is the kit's own model (no team
190
+ delivery has it), approved by the owner on 2026-10-24.
191
+
192
+ **Shelter.** For each part (or impl) in range, a segment from the centre to the nearest point of the target is cut against every other part of
193
+ every object, skipping parts already lost and the target itself. A part crossed along a path `p` lets through `exp(-r / k)` of the energy, with
194
+ `r = hpPerKg * density * p * damageScale.shelter` (the hit points of the slab it crosses, per m²) and `k = 2800`. The energy that reaches the target
195
+ is `E * product(t_i)`, and it is used for both the damage and the push. Parts are served from the nearest to the farthest, so a wall takes the blast
196
+ (damage, dents, maybe breaking) before what is behind it. A blocker the same blast destroys or breaches lets `brokenTransmission` (0.5) of what it
197
+ held back go through: `t' = t + (1 - t) * 0.5`. An opening (a breach, a bare rebar cage) from before the blast lets everything through. Impls
198
+ (characters, animals) get one hit with the energy-weighted mean of the share that reaches their parts, and `Hit.shelter` carries it. Impls do not
199
+ shelter anything (they have no material).
200
+
201
+ | Blocker | Path | Lets through |
202
+ |---|---|---|
203
+ | glass pane | 6 mm | 83% (`damageScale.shelter: 3` in the material) |
204
+ | wooden door (softwood) | 4 cm | 53% |
205
+ | brick wall | 20 cm | 19% |
206
+ | reinforced concrete column | 30 cm | 3% |
207
+
208
+ **Fragment hits.** The fragments of a part the blast fractures or breaches (the `fracture` and `stage` events) become hits on the first part or impl
209
+ along their flight, in order of energy, at most `max` (24) per blast. Each is a `blunt` hit (or `ballistic` with its mass in grams and a calibre from
210
+ its size) of `energyFactor * m * v² / 2` (0.5), thrown up to `min(range 6 m, speed * flightTime 0.4 s)` within a seeded `spread` (10°) of its
211
+ direction. With `shelter` on, a fragment of a blocker whose line to a target is within `aim` (40°) of its flight goes for that target. The flights
212
+ depend only on the world seed, the blast and the fragment, never on what the world did before. Fragments of what a fragment breaks are not thrown
213
+ again. Events: `fragment_hit` (before the target's own events).
214
+
215
+ **Confinement.** `confinement: true` shoots `rays` (26) seeded rays from the centre as far as `radius * reach` (1.5) against the world's parts (impls
216
+ do not count; open doors, windows and breaches are absent parts). With `c` the share of rays that hit and `d` their mean distance:
217
+ `multiplier = min(max, 1 + gain * c^power * clamp(radius / d, 0, 2))` with `gain` 1.5, `power` 2, `max` 3. The energy of the whole blast is
218
+ multiplied. Open ground gives 1, a closed room of 6 m about 3, the same room with one wall missing about 2. A number (`confinement: 2.5`, or
219
+ `Hit.confinement`) is the multiplier itself, for a host that knows the room volume.
220
+
221
+ **What comes back.** `HitResult.blast = { shelter, parts?, confinement, fragmentHits }` per target (`shelter` is the most exposed part's share),
222
+ and one `blast` event per blast (`energy` after confinement, `shelter` per target, `fragmentHits`). `@adatechnology/game-destruction-three` takes
223
+ the results for the push on pieces: `renderer.explode(point, energy, radius, { report: results })`.
224
+
225
+ ### Bullets and blasts against beams and columns
226
+
227
+ `tests/beams-and-columns.test.ts` documents calibres of `@adatechnology/game-weapons` against a reinforced concrete column and beam, a steel
228
+ I-beam, a hardwood beam and a brick pillar. Two options came out of it, both off or neutral by default:
229
+
230
+ - `Hit.piercing` (6th argument of `shoot`): how well the bullet cuts through for its energy; the energy compared with the penetration of a hard
231
+ material (metal, masonry) is `energy * piercing`. `PIERCING_BY_KIND` (fmj 1, hp 0.6, ap 1.6, buck 0.5, slug 0.25) and `PIERCING_BY_CALIBER`
232
+ (5.56, 5.45 and .22 0.5) give the numbers from `CaliberDef.kind` (`piercingOf(id, kind)`). Without it a 12 ga slug (3000 J) went through 8.5 mm of
233
+ steel.
234
+ - `breach.blastReach` (default `Infinity`, 0.12 recommended): a blast only opens a breach within `blastReach * cbrt(energy / 1000)` metres of the
235
+ part. With 0.12 a hand grenade (200 kJ) at 1 m no longer cuts a reinforced column in two (at 0.2 m it strips it).
236
+
184
237
  ## Contracts: implement your own or use the default world
185
238
 
186
239
  The kit defines contracts; each game module implements them for the objects it owns, and the world in this package is the optional default
@@ -228,9 +281,9 @@ world continues with the same events as the original. The loading world must use
228
281
 
229
282
  `createDestruction(options)` returns a `DestructionWorld`: `add`, `remove`, `has`, `ids`, `setTransform`, `splitGroup`, `hit`, `hitPart`, `explode`,
230
283
  `raycast`, `shoot`, `disassemble`, `workableJoints`, `nearestJoint`, `extinguish`, `setLiters`, `update`, `state`, `part`, `reset`, `on`,
231
- `register`, `serialize`, `load`, `time`. `state()` and `part()` return live views; do not mutate them.
284
+ `register`, `serialize`, `load`, `time`. `explode(point, energy, radius, source?, blastOptions?)`; `shoot(origin, direction, energy, caliber, source?, piercing?)`. `state()` and `part()` return live views; do not mutate them.
232
285
 
233
- Also exported: `buildFrame`, `sizeStructure`, `assemblyPart`, `registerAssembly`, `getAssembly`, `ASSEMBLIES`, `assemblyDepth`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
286
+ Also exported: `SHELTER_DEFAULTS`, `FRAGMENT_HIT_DEFAULTS`, `CONFINEMENT_DEFAULTS`, `PIERCING_BY_KIND`, `PIERCING_BY_CALIBER`, `piercingOf`, `buildFrame`, `sizeStructure`, `assemblyPart`, `registerAssembly`, `getAssembly`, `ASSEMBLIES`, `assemblyDepth`, `partCapacity`, `checkDestructible`, `checkHit`, `IMPACT_CONTRACT`, `MATERIALS`, `JOINT_DEFAULTS`, `JOINT_BY_MATERIALS`, `inferJoints`, `guessMaterial`, `partVolume`, `partMass`, `partThickness`, the
234
287
  helpers `vec`, `quat`, `quatFromEuler`, `rotateVec`, `toLocal`, `toWorld`, the locale (`locales`, `resolveStrings`, `DestructionStrings`) and all types.
235
288
 
236
289
  The locale table has `en` and `pt-BR` strings for material names, part statuses, joint kinds, tools, damage types, event labels and fracture styles:
@@ -244,3 +297,5 @@ plástico derretem e pingam. As juntas cedem com impacto, quando a peça é dest
244
297
  parafuso, chave inglesa para porca e parafuso, maçarico para solda). O mundo devolve tudo como eventos para o renderizador desenhar. É
245
298
  determinístico (semente fixa) e serializável, então um host de LAN pode rodar o mundo e enviar só as entradas. Os nomes em português
246
299
  (materiais, ferramentas, juntas, eventos) estão em `resolveStrings('pt-BR')`.
300
+
301
+ Explosões: `blast.shelter` (paredes e portas entre a explosão e o alvo ficam com a sua parte: tijolo de 20 cm deixa passar ~19%, porta de madeira ~53%, vidro ~83%; a parede que quebra na mesma explosão deixa passar metade do que segurou), `blast.fragmentHits` (os pedaços do que quebra viram golpes de baixa energia no que está atrás) e `blast.confinement` (pressão num ambiente fechado, de 1 a 3 vezes). Tudo desligado por padrão, por opção do mundo ou da chamada, com os números em `SHELTER_DEFAULTS`, `FRAGMENT_HIT_DEFAULTS` e `CONFINEMENT_DEFAULTS`.
package/dist/index.d.ts CHANGED
@@ -1,5 +1,5 @@
1
- import { Vec3, Quat, MaterialId, FractureStyle, DamageType, DebrisKind, DestructionEvent, DestructibleSpec, ObjectState, DestructibleImpl, Hit, HitResult, RayHit, ToolKind, JointState, PartState, DestructionEventType, StructureRole, ExplosionChain, PartSpec, JointKind, JointSpec, PartShape, SurfaceLayer, PartUnits, Reinforcement, PartBreach, PartStatus } from '@adatechnology/game-contracts';
2
- export { ChainDelay, Crack, DamageStage, DamageType, DebrisKind, Dent, DestructibleImpl, DestructibleSpec, DestructionEvent, DestructionEventType, ExplosionChain, FluidKind, FractureStyle, Fragment, FragmentMark, Hit, HitResult, Hole, IMPACT_CONTRACT, ImpactContract, JointKind, JointSpec, JointState, MaterialId, ObjectState, PartBreach, PartShape, PartSpec, PartState, PartStatus, PartUnits, Quat, RayHit, Reinforcement, StructureRole, SurfaceLayer, ToolKind, Vec3, checkHit } from '@adatechnology/game-contracts';
1
+ import { Vec3, Quat, MaterialId, FractureStyle, DamageType, DebrisKind, DestructionEvent, BlastOptions, DestructibleSpec, ObjectState, DestructibleImpl, Hit, HitResult, RayHit, ToolKind, JointState, PartState, DestructionEventType, StructureRole, ConfinementOptions, FragmentHitOptions, ShelterOptions, ExplosionChain, PartSpec, JointKind, JointSpec, PartShape, SurfaceLayer, PartUnits, Reinforcement, PartBreach, PartStatus } from '@adatechnology/game-contracts';
2
+ export { BlastOptions, BlastReport, ChainDelay, ConfinementOptions, Crack, DamageStage, DamageType, DebrisKind, Dent, DestructibleImpl, DestructibleSpec, DestructionEvent, DestructionEventType, ExplosionChain, FluidKind, FractureStyle, Fragment, FragmentHitOptions, FragmentMark, Hit, HitResult, Hole, IMPACT_CONTRACT, ImpactContract, JointKind, JointSpec, JointState, MaterialId, ObjectState, PartBreach, PartShape, PartSpec, PartState, PartStatus, PartUnits, Quat, RayHit, Reinforcement, ShelterOptions, StructureRole, SurfaceLayer, ToolKind, Vec3, checkHit } from '@adatechnology/game-contracts';
3
3
 
4
4
  /**
5
5
  * Public types of @adatechnology/game-destruction.
@@ -52,8 +52,8 @@ interface MaterialDef {
52
52
  fracture: FractureStyle;
53
53
  /** Fragment size range in metres (default `[0.04, 0.2]`). */
54
54
  fragmentSize?: [number, number];
55
- /** Damage multipliers per damage type (default 1). */
56
- damageScale?: Partial<Record<DamageType, number>>;
55
+ /** Damage multipliers per damage type (default 1); `shelter` multiplies the resistance the material adds between a blast and a target (`blast.shelter`). */
56
+ damageScale?: Partial<Record<DamageType | 'shelter', number>>;
57
57
  /** Debris emitted on a hit that does not break the part. */
58
58
  hitDebris: DebrisKind;
59
59
  /** Debris emitted when the part fractures. */
@@ -101,12 +101,23 @@ interface DestructionOptions {
101
101
  radius?: number;
102
102
  /** Multiplier of the breach area a blast makes over a blow of the same damage: a blast loads the whole crater (default 6). */
103
103
  blastFactor?: number;
104
+ /**
105
+ * Metres of reach per cube root of kilojoule: a blast only opens a breach in a part within `blastReach * cbrt(energy / 1000)` of the centre
106
+ * (a hand grenade of 200 kJ: 0.7 m; 2 MJ: 1.5 m). Farther away it still damages, dents and cracks. Without it a falloff that is gentle over a
107
+ * large radius let a grenade at 1 m cut a reinforced column in two. Default `Infinity` (off, so the goldens stay as they were); 0.12 is the value that matches real charges.
108
+ */
109
+ blastReach?: number;
104
110
  };
105
111
  /**
106
112
  * Speed of the blast front in m/s. A glass part at distance `d` from an explosion breaks `d / blastFrontSpeed` seconds later, in `update`
107
113
  * (the speed of sound is 340). Default off: every part takes the blast at once.
108
114
  */
109
115
  blastFrontSpeed?: number;
116
+ /**
117
+ * Defaults of every blast (`explode`, an explosive `hit`, a part that explodes); each call can override them. All off by default, and a world
118
+ * with all of them off gives the results it always gave. See `ShelterOptions`, `FragmentHitOptions` and `ConfinementOptions`.
119
+ */
120
+ blast?: BlastOptions;
110
121
  }
111
122
  interface SizeStructureOptions {
112
123
  /** Stress every structural part has at rest after sizing (`load / capacity`), 0..1 (default 0.65). */
@@ -161,12 +172,16 @@ interface DestructionWorld {
161
172
  hit(objectId: string, hit: Hit): HitResult;
162
173
  /** Apply a hit to a known part (e.g. after the host's own raycast). */
163
174
  hitPart(objectId: string, partId: string, hit: Hit): HitResult;
164
- /** A blast in world space against every object in range. */
165
- explode(point: Vec3, energy: number, radius: number, source?: string): HitResult[];
175
+ /**
176
+ * A blast in world space against every object in range. With `options` (else `DestructionOptions.blast`) the blast can be sheltered by what
177
+ * stands in the way, throw the fragments of what it breaks and gain pressure in an enclosed volume; each `HitResult.blast` says what it did to
178
+ * that target, and a `blast` event sums it up.
179
+ */
180
+ explode(point: Vec3, energy: number, radius: number, source?: string, options?: BlastOptions): HitResult[];
166
181
  /** Raycast against every part shape (world space). */
167
182
  raycast(origin: Vec3, direction: Vec3, maxDistance?: number): RayHit | null;
168
183
  /** Ballistic shot: raycast, hit, and continue through with the exit energy (up to 4 objects/parts). */
169
- shoot(origin: Vec3, direction: Vec3, energy: number, caliber: number, source?: string): HitResult[];
184
+ shoot(origin: Vec3, direction: Vec3, energy: number, caliber: number, source?: string, piercing?: number): HitResult[];
170
185
  /**
171
186
  * Work on a joint with a tool for `dt` seconds (call every frame while the player holds the button). Returns the progress 0..1, or -1 when the
172
187
  * tool cannot undo this joint. At 1 the joint opens (`detach` with `by: 'tool'`).
@@ -257,6 +272,15 @@ interface DestructionSnapshot {
257
272
  }>;
258
273
  }
259
274
 
275
+ /**
276
+ * Blast options (all off until asked for). Shelter: a part the line from the blast to the target crosses lets through `exp(-r / k)`, `r` being the
277
+ * hit points of the slab it crosses per square metre (`hpPerKg * density * path * damageScale.shelter`). Fragment hits: up to 24 fragments, half their
278
+ * kinetic energy lands as a blunt hit, thrown at most 6 m (and `speed * 0.4 s`) within 10 degrees of their aim. Confinement: 26 rays reaching
279
+ * `1.5 * radius`, multiplier `1 + 1.5 * closedShare^2 * clamp(radius / meanDistance, 0, 2)` up to 3.
280
+ */
281
+ declare const SHELTER_DEFAULTS: Required<ShelterOptions>;
282
+ declare const FRAGMENT_HIT_DEFAULTS: Required<FragmentHitOptions>;
283
+ declare const CONFINEMENT_DEFAULTS: Required<ConfinementOptions>;
260
284
  declare const createDestruction: (options?: DestructionOptions) => DestructionWorld;
261
285
 
262
286
  /** What can set an explosive part off from outside, in the order a hit can carry it. */
@@ -280,6 +304,18 @@ interface ExplosivePartOptions {
280
304
  */
281
305
  declare const explosivePart: (explosive: WorldExplosive, part: Omit<PartSpec, "explodes">, options?: ExplosivePartOptions) => PartSpec;
282
306
 
307
+ /**
308
+ * How well a bullet cuts through for its energy (`Hit.piercing`, the 6th argument of `shoot`). The material's `penetration` is joules per
309
+ * millimetre for a hard bullet; soft lead deforms on steel and concrete and a core of hardened steel does better. A host that knows the kind
310
+ * of the bullet (`CaliberDef.kind` of `@adatechnology/game-weapons`) takes the number from here, or replaces the tables with its own.
311
+ *
312
+ * Reference: a 12 ga slug (3000 J) does not go through 8 mm of mild steel, a .50 BMG (18000 J) goes through 20 mm and more.
313
+ */
314
+ declare const PIERCING_BY_KIND: Readonly<Record<string, number>>;
315
+ /** Caliber ids whose bullet is lighter on the target than its kind says (a soft-core 5.56 ball does not go through a steel flange). */
316
+ declare const PIERCING_BY_CALIBER: Readonly<Record<string, number>>;
317
+ declare const piercingOf: (caliber: string, kind?: string) => number;
318
+
283
319
  /** Built-in materials (data in `src/data/materials.json`). Override or extend with the `materials` option of `createDestruction`. */
284
320
  declare const MATERIALS: MaterialDef[];
285
321
  /** Default joint strength (joint hit points), tools that undo it and seconds of tool work. */
@@ -481,4 +517,4 @@ declare const checkDestructible: (impl: DestructibleImpl, options?: CheckOptions
481
517
  problems: string[];
482
518
  };
483
519
 
484
- export { ASSEMBLIES, type AddOptions, type AssemblyDef, type AssemblyPartOptions, type ChainTrigger, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type ExplosivePartOptions, FIRE_PRIME_C, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, type PartRuntime, type SizeStructureOptions, type WorldExplosive, assemblyDepth, assemblyPart, buildFrame, checkDestructible, createDestruction, explosivePart, fragility, getAssembly, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, quat, quatFromEuler, registerAssembly, resolveStrings, rotateVec, sizeStructure, toLocal, toWorld, vec };
520
+ export { ASSEMBLIES, type AddOptions, type AssemblyDef, type AssemblyPartOptions, CONFINEMENT_DEFAULTS, type ChainTrigger, type CheckOptions, type DestructionLocale, type DestructionOptions, type DestructionSnapshot, type DestructionStrings, type DestructionWorld, type ExplosivePartOptions, FIRE_PRIME_C, FRAGMENT_HIT_DEFAULTS, type FrameOptions, JOINT_BY_MATERIALS, JOINT_DEFAULTS, MATERIALS, type MaterialDef, type MaterialThermal, type ObjectRuntime, PIERCING_BY_CALIBER, PIERCING_BY_KIND, type PartRuntime, SHELTER_DEFAULTS, type SizeStructureOptions, type WorldExplosive, assemblyDepth, assemblyPart, buildFrame, checkDestructible, createDestruction, explosivePart, fragility, getAssembly, guessMaterial, inferJoints, locales, partCapacity, partMass, partThickness, partVolume, piercingOf, quat, quatFromEuler, registerAssembly, resolveStrings, rotateVec, sizeStructure, toLocal, toWorld, vec };