@adatechnology/game-terrain 0.0.0-stage → 0.1.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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Anderson Fernandes
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,28 @@
1
- # Temporary Holding Version
1
+ # @adatechnology/game-terrain
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Deformable ground with no three.js in it: a height field in chunks where every column has strata, each stratum a material of the destruction catalogue.
4
+
5
+ ```ts
6
+ import { createTerrain, createTerrainImpl } from '@adatechnology/game-terrain'
7
+
8
+ const terrain = createTerrain({ bounds: [-50, -50, 50, 50], layout: { surface: 'lawn', zones: [{ rect: [-50, -4, 50, 4], surface: 'road' }] } })
9
+ world.register(createTerrainImpl(terrain, { id: 'ground' })) // shots, blows, blasts and flames of the destruction world reach it
10
+ terrain.heightAt(x, z) // what things stand on
11
+ ```
12
+
13
+ ## What it does
14
+
15
+ - **Profiles** (`TERRAIN_PROFILES`, replace or add through `profiles`): `lawn` (turf 5 cm, soil 1.5 m, rock), `dirt`, `sand`, `road` (asphalt 5 cm, gravel, soil), `concrete_slab` (12 cm with rebar at 3 cm), `tile_floor` (tile, mortar, slab), `wood_floor`, `stone_paving`.
16
+ - **Digging**: a blow removes `energy / specificEnergy` cubic metres (`TERRAIN_MATERIALS`, per blow type: ballistic, blunt, slash, explosive) as a bowl with the shape of the material. It takes the top stratum and spends what is left on the next. The strata end at the digging limit: **a slab is never pierced**, and a crater has a floor.
17
+ - **Bars**: bare bars bend with each blow and give way at `yieldJoules`. A slab on the ground stays the floor; `suspended_slab` (`TerrainProfile.drop`) opens onto the floor below.
18
+ - **Fine marks**: a bowl under 1.5 cells stays a mark, not a change of the height field. Blows on the same mark grow it until it is dug in.
19
+ - **Budget** (`budget`): marks per area and in total (past it the closest two merge, then the oldest goes), thrown pieces, chunks redrawn per frame, cells checked for sliding, cells on fire.
20
+ - **What comes out**: soft ground heaps a rim and throws clods that land and join the height field, so they can be walked over; rigid material throws chips that stay as rubble. Loose ground slides to its angle of repose.
21
+ - **Fire**: `fire` of each material says how black it gets and whether it burns away and spreads: turf and wood char and carry the fire, concrete only soots.
22
+ - `serialize()` / `load()`: only what changed, as plain JSON.
23
+
24
+ Draw it with [`@adatechnology/game-terrain-three`](../game-terrain-three). The test room's floor is one of these.
25
+
26
+ ## Português
27
+
28
+ Chão deformável: mapa de altura em chunks com estratos por coluna, cada estrato um material do catálogo de destruição. O golpe escava o estrato de cima e só passa ao de baixo quando ele acaba; a laje no chão nunca é atravessada e a cratera tem fundo. Vergalhão exposto entorta a cada golpe e cede; laje sobre vão (`suspended_slab`) abre para o andar de baixo. Marcas finas de tiro com limite por área (fundem, depois reciclam a mais antiga); a terra removida vira borda e torrões que caem e somam na altura; areia escorrega até o ângulo de repouso; grama e madeira carbonizam e o fogo se espalha, concreto só escurece.
@@ -0,0 +1,295 @@
1
+ import { GroundLayout, Hit, DestructibleImpl } from '@adatechnology/game-contracts';
2
+ export { GroundLayout, GroundZone } from '@adatechnology/game-contracts';
3
+
4
+ /** The blows that remove ground. Fire and heat char it instead (`TerrainMaterial.fire`). */
5
+ type TerrainBlow = 'ballistic' | 'blunt' | 'slash' | 'explosive';
6
+ /** What one kind of blow does to a material. */
7
+ interface TerrainBlowResponse {
8
+ /** Joules it takes to remove one cubic metre. */
9
+ readonly specificEnergy: number;
10
+ /** Depth over radius of the bowl it leaves: a soft ground craters deep, a hard one spalls wide and shallow. */
11
+ readonly aspect: number;
12
+ /** A blow under this many joules leaves nothing (a punch on a slab). */
13
+ readonly thresholdJoules?: number;
14
+ /** The bowl is never narrower: a tile comes off as a piece, not as the size of the bullet. */
15
+ readonly minRadius?: number;
16
+ }
17
+ type TerrainDebrisKind = 'clods' | 'chips' | 'shards' | 'splinters' | 'dust';
18
+ /** How a material of the destruction catalogue behaves as ground. `id` is the catalogue's. */
19
+ interface TerrainMaterial {
20
+ readonly id: string;
21
+ readonly hits: Partial<Record<TerrainBlow, TerrainBlowResponse>>;
22
+ readonly debris: TerrainDebrisKind;
23
+ /** Share of what a blow removes that piles up on the rim; the rest is thrown. Rigid materials have none. */
24
+ readonly rimShare?: number;
25
+ /** Steepest slope in degrees it holds; loose ground slides until it is back to it. */
26
+ readonly reposeDegrees?: number;
27
+ readonly fire?: TerrainFireResponse;
28
+ /** Linear rgb, 0..1, of the material where it is exposed. */
29
+ readonly color: readonly [number, number, number];
30
+ /** Surface pattern a renderer draws while the stratum is intact (`TERRAIN_PATTERNS`). */
31
+ readonly pattern?: TerrainPattern;
32
+ }
33
+ interface TerrainFireResponse {
34
+ /** How black it gets, 0..1: concrete only soots, wood and turf char through. */
35
+ readonly maxScorch: number;
36
+ /** Scorch gained per second in a flame. */
37
+ readonly scorchPerSecond: number;
38
+ /** Metres per second the flame eats away (wood char). */
39
+ readonly burnsAway?: number;
40
+ /** Catches and carries the fire to the next cells on its own. */
41
+ readonly flammable?: boolean;
42
+ }
43
+ declare const TERRAIN_PATTERNS: readonly ["plain", "grass", "soil", "sand", "asphalt", "concrete", "tile", "planks", "stone"];
44
+ type TerrainPattern = (typeof TERRAIN_PATTERNS)[number];
45
+ /** One layer of a column, from the top down. */
46
+ interface TerrainStratum {
47
+ /** A `TerrainMaterial.id`. */
48
+ readonly material: string;
49
+ /** Metres. */
50
+ readonly thickness: number;
51
+ /** Bars inside the layer: exposed at `cover`, they make the digging `resistance` times harder. */
52
+ readonly reinforcement?: {
53
+ readonly material: string;
54
+ readonly cover: number;
55
+ readonly spacing: number;
56
+ readonly resistance: number;
57
+ /** Joules on one spot of bare bars before they give way (default `DEFAULT_BAR_YIELD_JOULES`). */
58
+ readonly yieldJoules?: number;
59
+ };
60
+ }
61
+ /** What a column of ground is made of. The strata end at the digging limit: under them nothing is removed. */
62
+ interface TerrainProfile {
63
+ readonly id: string;
64
+ readonly strata: readonly TerrainStratum[];
65
+ /** A slab with nothing under it: where its bars give way the floor opens, and the ground is this many metres further down (the floor below). Without it the ground under the slab holds it up. */
66
+ readonly drop?: number;
67
+ }
68
+ interface TerrainBudget {
69
+ /** Side in metres of the squares that hold a limited number of marks. */
70
+ readonly markAreaSize: number;
71
+ readonly maxMarksPerArea: number;
72
+ readonly maxMarks: number;
73
+ /** Chunks a renderer redraws per frame; the rest wait. */
74
+ readonly maxDirtyChunksPerFrame: number;
75
+ readonly maxDebris: number;
76
+ /** Thrown pieces of one blow. */
77
+ readonly maxDebrisPerBlow: number;
78
+ /** Cells checked for sliding per frame. */
79
+ readonly maxSlideCellsPerFrame: number;
80
+ /** Cells on fire at once. */
81
+ readonly maxBurningCells: number;
82
+ }
83
+ interface TerrainOptions {
84
+ /** World rectangle `[x0, z0, x1, z1]` in metres. */
85
+ readonly bounds: readonly [number, number, number, number];
86
+ /** Metres per cell (default 0.1). */
87
+ readonly cellSize?: number;
88
+ /** Cells per side of a chunk (default 32). */
89
+ readonly chunkCells?: number;
90
+ /** Which profile is where. */
91
+ readonly layout?: GroundLayout;
92
+ /** Extra or overriding profiles, by id. */
93
+ readonly profiles?: readonly TerrainProfile[];
94
+ /** Extra or overriding materials, by id. */
95
+ readonly materials?: readonly TerrainMaterial[];
96
+ readonly budget?: Partial<TerrainBudget>;
97
+ /** Height of the untouched ground (default flat at 0). */
98
+ readonly baseHeight?: (x: number, z: number) => number;
99
+ /** Metres per second squared. */
100
+ readonly gravity?: number;
101
+ readonly seed?: number;
102
+ }
103
+ /** A fine mark: a bullet pock or a chip, smaller than a cell. It grows with the blows on it until it is dug into the height field. */
104
+ interface TerrainMark {
105
+ readonly id: number;
106
+ x: number;
107
+ z: number;
108
+ radius: number;
109
+ depth: number;
110
+ /** The material the mark shows at the bottom. */
111
+ material: string;
112
+ blow: TerrainBlow;
113
+ /** Blows and marks merged into this one. */
114
+ count: number;
115
+ /** Order of creation: the oldest is the one recycled. */
116
+ born: number;
117
+ }
118
+ interface TerrainDebris {
119
+ readonly id: number;
120
+ readonly material: string;
121
+ readonly kind: TerrainDebrisKind;
122
+ readonly radius: number;
123
+ readonly position: [number, number, number];
124
+ readonly velocity: [number, number, number];
125
+ readonly spin: [number, number, number];
126
+ atRest: boolean;
127
+ bounces: number;
128
+ /** Order of creation: the oldest is the one recycled. */
129
+ readonly born: number;
130
+ }
131
+ /** What a blow did. */
132
+ interface TerrainBlowResult {
133
+ readonly point: readonly [number, number, number];
134
+ /** Bowl radius in metres; 0 when the blow left nothing. */
135
+ readonly radius: number;
136
+ /** Metres removed at the point. */
137
+ readonly depth: number;
138
+ /** Metres under the untouched surface the point is now. */
139
+ readonly totalDepth: number;
140
+ /** Materials removed, top first, with the cubic metres of each. */
141
+ readonly removed: readonly {
142
+ readonly material: string;
143
+ readonly volume: number;
144
+ }[];
145
+ /** Material now exposed at the point. */
146
+ readonly exposed: string;
147
+ /** The column has no more to give here: the slab's bottom, the end of the rock. */
148
+ readonly atLimit: boolean;
149
+ /** Reinforcement is showing at the point. */
150
+ readonly rebarExposed: boolean;
151
+ /** The bars gave way here and the slab opened (`TerrainProfile.drop`). */
152
+ readonly collapsed: boolean;
153
+ /** Left a fine mark (`true`) or dug the height field (`false`). */
154
+ readonly fine: boolean;
155
+ }
156
+ interface TerrainRegion {
157
+ readonly x: number;
158
+ readonly z: number;
159
+ readonly radius: number;
160
+ }
161
+ interface TerrainColumn {
162
+ readonly profile: string;
163
+ readonly material: string;
164
+ readonly stratum: number;
165
+ /** Metres under the untouched surface. */
166
+ readonly depth: number;
167
+ /** Metres that can still be dug here. */
168
+ readonly remaining: number;
169
+ readonly scorch: number;
170
+ readonly loose: number;
171
+ }
172
+ interface TerrainStats {
173
+ readonly marks: number;
174
+ readonly debris: number;
175
+ readonly debrisMoving: number;
176
+ readonly dirtyChunks: number;
177
+ readonly burning: number;
178
+ }
179
+ interface TerrainChunk {
180
+ readonly index: number;
181
+ /** First column and row of the chunk's vertices. */
182
+ readonly column: number;
183
+ readonly row: number;
184
+ readonly cells: number;
185
+ /** True while nothing touched it and it has one profile: one flat quad draws it. */
186
+ readonly pristine: boolean;
187
+ }
188
+ interface Terrain {
189
+ readonly bounds: readonly [number, number, number, number];
190
+ readonly cellSize: number;
191
+ readonly columns: number;
192
+ readonly rows: number;
193
+ /** Vertices per row. */
194
+ readonly stride: number;
195
+ readonly chunkCells: number;
196
+ readonly chunksX: number;
197
+ readonly chunksZ: number;
198
+ readonly budget: TerrainBudget;
199
+ /** Per vertex: metres removed under the untouched surface. */
200
+ readonly dug: Float32Array;
201
+ /** Per vertex: metres of loose ground heaped on top. */
202
+ readonly loose: Float32Array;
203
+ /** Per vertex: 0..1, how burnt. */
204
+ readonly scorch: Float32Array;
205
+ /** Per vertex: how far the bare bars have given, 0 straight, 1 or more gone. */
206
+ readonly barBend: Float32Array;
207
+ /** Per vertex: index in `profileList`. */
208
+ readonly profileIndex: Uint8Array;
209
+ readonly profileList: readonly TerrainProfile[];
210
+ material(id: string): TerrainMaterial;
211
+ layout(): GroundLayout;
212
+ setLayout(layout: GroundLayout): void;
213
+ /** Height at (x, z) as things stand on it. */
214
+ heightAt(x: number, z: number): number;
215
+ /** Height of vertex (column, row). */
216
+ vertexHeight(column: number, row: number): number;
217
+ /** Material exposed at a vertex. */
218
+ vertexMaterial(column: number, row: number): TerrainMaterial;
219
+ columnAt(x: number, z: number): TerrainColumn;
220
+ contains(x: number, z: number): boolean;
221
+ /** First point of the ground along a ray, or null. */
222
+ raycast(origin: readonly number[], direction: readonly number[], maxDistance: number): {
223
+ point: [number, number, number];
224
+ distance: number;
225
+ normal: [number, number, number];
226
+ } | null;
227
+ /** A blow at (x, z). `energy` in joules, `direction` where it travels (it throws the pieces back along it). */
228
+ strike(blow: TerrainBlow, spec: {
229
+ x: number;
230
+ z: number;
231
+ energy: number;
232
+ direction?: readonly number[];
233
+ contactRadius?: number;
234
+ }): TerrainBlowResult;
235
+ /** A flame over a disc for `seconds`. */
236
+ burn(spec: {
237
+ x: number;
238
+ z: number;
239
+ radius: number;
240
+ temperature: number;
241
+ seconds: number;
242
+ }): void;
243
+ /** Applies a world hit (`Hit` of the destruction contract); null when it does nothing to the ground. */
244
+ applyHit(hit: Hit): TerrainBlowResult | null;
245
+ /** Follow the ground changing: `region` is the disc it changed in. What rests there has lost its support and must look again. Returns the unsubscribe function. */
246
+ onDeform(listener: (region: TerrainRegion) => void): () => void;
247
+ /** Moves the pieces, the sliding ground and the fire. */
248
+ update(dt: number): void;
249
+ /** Takes up to the budget's chunks that changed since the last call. */
250
+ drainDirtyChunks(max?: number): TerrainChunk[];
251
+ chunk(index: number): TerrainChunk;
252
+ marks(): readonly TerrainMark[];
253
+ debris(): readonly TerrainDebris[];
254
+ /** Cells on fire now, as world points. */
255
+ burningPoints(): readonly (readonly [number, number])[];
256
+ /** Goes up when the marks change. */
257
+ readonly marksVersion: number;
258
+ stats(): TerrainStats;
259
+ /** Back to the untouched ground. */
260
+ reset(): void;
261
+ serialize(): TerrainSnapshot;
262
+ load(snapshot: TerrainSnapshot): void;
263
+ }
264
+ interface TerrainSnapshot {
265
+ readonly layout: GroundLayout;
266
+ readonly dug: readonly (readonly [number, number])[];
267
+ readonly loose: readonly (readonly [number, number])[];
268
+ readonly scorch: readonly (readonly [number, number])[];
269
+ readonly barBend?: readonly (readonly [number, number])[];
270
+ readonly marks: readonly TerrainMark[];
271
+ }
272
+
273
+ declare const DEFAULT_TERRAIN_BUDGET: TerrainBudget;
274
+ declare function createTerrain(options: TerrainOptions): Terrain;
275
+
276
+ interface TerrainImplOptions {
277
+ readonly id: string;
278
+ readonly name?: string;
279
+ /** Every blow that left its mark, with what it did. */
280
+ readonly onBlow?: (result: TerrainBlowResult, hit: Hit) => void;
281
+ }
282
+ /**
283
+ * The ground as an object of the destruction world (`DestructionWorld.register`): shots, blows, blasts and flames reach it through the world's own raycast and blast like anything else.
284
+ * The collision part is a flat box; a ray that enters it goes on to the real surface, so a round fired into a crater lands on its floor.
285
+ */
286
+ declare function createTerrainImpl(terrain: Terrain, options: TerrainImplOptions): DestructibleImpl;
287
+
288
+ declare const TERRAIN_MATERIALS: readonly TerrainMaterial[];
289
+ declare const TERRAIN_PROFILES: readonly TerrainProfile[];
290
+ /** About twelve grenades (200 kJ) on the same spot of bare bars. */
291
+ declare const DEFAULT_BAR_YIELD_JOULES = 2400000;
292
+ /** The profile of a floor with nothing else said: a plain slab. */
293
+ declare const DEFAULT_PROFILE = "concrete_slab";
294
+
295
+ export { DEFAULT_BAR_YIELD_JOULES, DEFAULT_PROFILE, DEFAULT_TERRAIN_BUDGET, TERRAIN_MATERIALS, TERRAIN_PATTERNS, TERRAIN_PROFILES, type Terrain, type TerrainBlow, type TerrainBlowResponse, type TerrainBlowResult, type TerrainBudget, type TerrainChunk, type TerrainColumn, type TerrainDebris, type TerrainDebrisKind, type TerrainFireResponse, type TerrainImplOptions, type TerrainMark, type TerrainMaterial, type TerrainOptions, type TerrainPattern, type TerrainProfile, type TerrainRegion, type TerrainSnapshot, type TerrainStats, type TerrainStratum, createTerrain, createTerrainImpl };