@jgengine/core 0.9.0 → 0.10.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/CHANGELOG.md +21 -1
- package/dist/ai/spawnDirector.d.ts +13 -0
- package/dist/ai/spawnDirector.js +40 -16
- package/dist/ai/spawnPoint.d.ts +5 -0
- package/dist/ai/spawnPoint.js +5 -0
- package/dist/audio/audioFalloff.d.ts +5 -2
- package/dist/audio/music.d.ts +53 -0
- package/dist/audio/music.js +34 -0
- package/dist/audio/synth.d.ts +60 -0
- package/dist/audio/synth.js +8 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +14 -0
- package/dist/cartridge/runtime.js +2 -2
- package/dist/combat/dotField.d.ts +43 -0
- package/dist/combat/dotField.js +48 -0
- package/dist/combat/projectiles.d.ts +2 -2
- package/dist/combat/projectiles.js +18 -5
- package/dist/combat/regenShield.d.ts +35 -0
- package/dist/combat/regenShield.js +45 -0
- package/dist/combat/shotOrigin.d.ts +13 -1
- package/dist/combat/shotOrigin.js +31 -2
- package/dist/editor/commands.d.ts +65 -0
- package/dist/editor/commands.js +193 -0
- package/dist/editor/document.d.ts +40 -0
- package/dist/editor/document.js +153 -0
- package/dist/editor/index.d.ts +30 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/types.d.ts +69 -0
- package/dist/editor/types.js +29 -0
- package/dist/game/connectedPlayers.d.ts +28 -0
- package/dist/game/connectedPlayers.js +21 -0
- package/dist/game/defineGame.d.ts +42 -1
- package/dist/game/defineGame.js +1 -0
- package/dist/game/events.d.ts +15 -0
- package/dist/game/keyValueStore.d.ts +25 -0
- package/dist/game/keyValueStore.js +61 -0
- package/dist/game/levelSequence.d.ts +24 -2
- package/dist/game/levelSequence.js +51 -2
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +1 -0
- package/dist/game/modelAnimation.d.ts +8 -0
- package/dist/game/modelAnimation.js +18 -0
- package/dist/game/playableGame.d.ts +68 -5
- package/dist/game/recordBook.d.ts +3 -5
- package/dist/index.d.ts +7 -0
- package/dist/index.js +7 -0
- package/dist/input/actionBindings.d.ts +2 -0
- package/dist/input/actionBindings.js +1 -0
- package/dist/input/axisInput.d.ts +7 -0
- package/dist/input/axisInput.js +16 -0
- package/dist/interaction/contextMenu.d.ts +1 -0
- package/dist/interaction/contextMenu.js +1 -0
- package/dist/interaction/proximityPrompt.d.ts +3 -1
- package/dist/interaction/proximityPrompt.js +3 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +22 -1
- package/dist/movement/movementModel.d.ts +4 -0
- package/dist/movement/movementModel.js +13 -0
- package/dist/movement/playerMovement.d.ts +41 -0
- package/dist/movement/playerMovement.js +181 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +2 -0
- package/dist/render/postProcessing.d.ts +56 -0
- package/dist/render/postProcessing.js +1 -0
- package/dist/runtime/adapter.d.ts +19 -0
- package/dist/runtime/adapter.js +11 -5
- package/dist/runtime/gameContext.d.ts +53 -10
- package/dist/runtime/gameContext.js +192 -54
- package/dist/runtime/hostedGameRunner.d.ts +49 -0
- package/dist/runtime/hostedGameRunner.js +62 -0
- package/dist/runtime/hostedWorldSession.d.ts +65 -0
- package/dist/runtime/hostedWorldSession.js +57 -0
- package/dist/runtime/inputSnapshot.d.ts +13 -0
- package/dist/runtime/inputSnapshot.js +2 -0
- package/dist/runtime/motionIntents.d.ts +2 -0
- package/dist/runtime/motionIntents.js +11 -0
- package/dist/runtime/worldChannel.d.ts +58 -0
- package/dist/runtime/worldChannel.js +69 -0
- package/dist/runtime/worldMirror.d.ts +24 -0
- package/dist/runtime/worldMirror.js +31 -0
- package/dist/runtime/worldReplication.d.ts +46 -0
- package/dist/runtime/worldReplication.js +185 -0
- package/dist/runtime/worldSnapshot.d.ts +17 -0
- package/dist/runtime/worldSnapshot.js +15 -0
- package/dist/scene/assetCatalog.d.ts +1 -0
- package/dist/scene/colliders.d.ts +6 -0
- package/dist/scene/colliders.js +25 -1
- package/dist/scene/entityStats.d.ts +4 -0
- package/dist/scene/entityStats.js +21 -0
- package/dist/scene/entityStore.d.ts +29 -0
- package/dist/scene/entityStore.js +60 -0
- package/dist/store/observableKeyedStore.d.ts +4 -0
- package/dist/store/observableKeyedStore.js +9 -0
- package/dist/tactics/fallingGrid.d.ts +2 -2
- package/dist/tactics/fallingGrid.js +2 -2
- package/dist/ui/gameLayout.d.ts +125 -0
- package/dist/ui/gameLayout.js +129 -0
- package/dist/ui/orientation.d.ts +39 -0
- package/dist/ui/orientation.js +49 -0
- package/dist/ui/swingTimer.d.ts +42 -0
- package/dist/ui/swingTimer.js +38 -0
- package/dist/visibility/spatialIndex.js +34 -20
- package/dist/world/environmentSummary.d.ts +11 -2
- package/dist/world/environmentSummary.js +11 -1
- package/dist/world/features.d.ts +134 -5
- package/dist/world/features.js +40 -3
- package/dist/world/roads.d.ts +47 -0
- package/dist/world/roads.js +146 -0
- package/dist/world/streets.d.ts +80 -0
- package/dist/world/streets.js +144 -0
- package/dist/world/terrain.d.ts +32 -5
- package/dist/world/terrain.js +124 -12
- package/llms.txt +485 -79
- package/package.json +1 -1
package/dist/world/terrain.js
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { nearestOnPath } from "./roads.js";
|
|
1
2
|
function clamp01(value) {
|
|
2
3
|
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
3
4
|
}
|
|
@@ -13,6 +14,7 @@ function hash2(ix, iz, seed) {
|
|
|
13
14
|
h ^= h >>> 12;
|
|
14
15
|
return (h >>> 0) / 4294967295;
|
|
15
16
|
}
|
|
17
|
+
/** Smoothly interpolated 2D value noise in `[-1, 1]` for the given seed. */
|
|
16
18
|
export function valueNoise(x, z, seed) {
|
|
17
19
|
const x0 = Math.floor(x);
|
|
18
20
|
const z0 = Math.floor(z);
|
|
@@ -28,6 +30,7 @@ export function valueNoise(x, z, seed) {
|
|
|
28
30
|
const bottom = lerp(v01, v11, sx);
|
|
29
31
|
return lerp(top, bottom, sz) * 2 - 1;
|
|
30
32
|
}
|
|
33
|
+
/** Layers `valueNoise` octaves per `config` into a single normalized noise sample. */
|
|
31
34
|
export function fractalNoise(x, z, config) {
|
|
32
35
|
let amplitude = 1;
|
|
33
36
|
let frequency = config.frequency;
|
|
@@ -57,6 +60,7 @@ export function seedFrom(value, fallback) {
|
|
|
57
60
|
}
|
|
58
61
|
return fallback;
|
|
59
62
|
}
|
|
63
|
+
/** Derives a `TerrainField.sampleNormal` from a height sampler via finite-difference gradients. */
|
|
60
64
|
export function withNormal(sampleHeight) {
|
|
61
65
|
const epsilon = 0.75;
|
|
62
66
|
return (x, z) => {
|
|
@@ -81,9 +85,11 @@ export const FLAT_FIELD = {
|
|
|
81
85
|
sampleHeight: () => 0,
|
|
82
86
|
sampleNormal: () => [0, 1, 0],
|
|
83
87
|
};
|
|
88
|
+
/** A flat, zero-height `TerrainField` for arenas with no elevation. */
|
|
84
89
|
export function flatField() {
|
|
85
90
|
return FLAT_FIELD;
|
|
86
91
|
}
|
|
92
|
+
/** Builds a `TerrainField` whose height is fractal noise shaped by `config`. */
|
|
87
93
|
export function noiseField(config = {}) {
|
|
88
94
|
const fractal = {
|
|
89
95
|
seed: seedFrom(config.seed, 1337),
|
|
@@ -120,6 +126,7 @@ function smoothstep(edge0, edge1, value) {
|
|
|
120
126
|
const t = clamp01((value - edge0) / (edge1 - edge0));
|
|
121
127
|
return t * t * (3 - 2 * t);
|
|
122
128
|
}
|
|
129
|
+
/** Builds a `TerrainField` with a flat spawn plateau, rolling hills, and a basin, for combat arenas. */
|
|
123
130
|
export function arenaField(config = {}) {
|
|
124
131
|
const seed = seedFrom(config.seed ?? "arena", 7);
|
|
125
132
|
const rolling = {
|
|
@@ -153,6 +160,10 @@ export function groundFieldFor(world) {
|
|
|
153
160
|
return resolveEnvironmentField(world);
|
|
154
161
|
return flatField();
|
|
155
162
|
}
|
|
163
|
+
/** Whether a world declares real terrain (base heightfield or islands) rather than a flat plane — gates terrain-floor sampling in the movement controllers. */
|
|
164
|
+
export function hasEnvironmentTerrain(world) {
|
|
165
|
+
return world?.kind === "environment" && (world.terrain !== undefined || (world.islands?.length ?? 0) > 0);
|
|
166
|
+
}
|
|
156
167
|
/** Ground height between islands when no base terrain exists — deep enough to read as a fall into the void, finite so physics stays sane. */
|
|
157
168
|
export const ISLAND_VOID_HEIGHT = -256;
|
|
158
169
|
function islandContains(descriptor, x, z) {
|
|
@@ -236,6 +247,32 @@ export const TERRAIN_MATERIAL_PALETTES = {
|
|
|
236
247
|
highland: { low: "#414f33", high: "#a3a86b", waterline: "#365f63" },
|
|
237
248
|
slate: { low: "#2f3540", high: "#7d8896", waterline: "#26404f" },
|
|
238
249
|
};
|
|
250
|
+
const DEFAULT_TERRAIN_DETAIL = {
|
|
251
|
+
rockColor: "#6f7175",
|
|
252
|
+
sandColor: "#c2b283",
|
|
253
|
+
snowColor: "#eef3f7",
|
|
254
|
+
rockSlopeStart: 0.42,
|
|
255
|
+
snowHeight: 26,
|
|
256
|
+
detailScale: 5,
|
|
257
|
+
macroScale: 45,
|
|
258
|
+
roughness: 0.9,
|
|
259
|
+
strength: 1,
|
|
260
|
+
};
|
|
261
|
+
/** Fill a `TerrainDetailConfig` with defaults; `waterLevel` falls back to the terrain's own water level. */
|
|
262
|
+
export function resolveTerrainDetail(config, terrainWaterLevel = 0) {
|
|
263
|
+
return {
|
|
264
|
+
rockColor: config.rockColor ?? DEFAULT_TERRAIN_DETAIL.rockColor,
|
|
265
|
+
sandColor: config.sandColor ?? DEFAULT_TERRAIN_DETAIL.sandColor,
|
|
266
|
+
snowColor: config.snowColor ?? DEFAULT_TERRAIN_DETAIL.snowColor,
|
|
267
|
+
rockSlopeStart: config.rockSlopeStart ?? DEFAULT_TERRAIN_DETAIL.rockSlopeStart,
|
|
268
|
+
snowHeight: config.snowHeight ?? DEFAULT_TERRAIN_DETAIL.snowHeight,
|
|
269
|
+
detailScale: config.detailScale ?? DEFAULT_TERRAIN_DETAIL.detailScale,
|
|
270
|
+
macroScale: config.macroScale ?? DEFAULT_TERRAIN_DETAIL.macroScale,
|
|
271
|
+
roughness: config.roughness ?? DEFAULT_TERRAIN_DETAIL.roughness,
|
|
272
|
+
strength: config.strength ?? DEFAULT_TERRAIN_DETAIL.strength,
|
|
273
|
+
waterLevel: config.waterLevel ?? terrainWaterLevel,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
239
276
|
export function resolveTerrainPalette(descriptor = {}) {
|
|
240
277
|
const material = descriptor.material ?? DEFAULT_TERRAIN_MATERIAL;
|
|
241
278
|
const preset = TERRAIN_MATERIAL_PALETTES[material];
|
|
@@ -278,35 +315,108 @@ function mixPalette(from, to, t) {
|
|
|
278
315
|
};
|
|
279
316
|
}
|
|
280
317
|
/**
|
|
281
|
-
* Per-
|
|
282
|
-
* `
|
|
283
|
-
*
|
|
318
|
+
* Per-z palette sampler over a descriptor's `biomeBands` — ordered zones that cross-fade their
|
|
319
|
+
* ground palette across a `fade`-wide window centered on the midpoint z between adjacent centers.
|
|
320
|
+
* Below the first / above the last band clamps to that band's palette. Returns `fallback` when no
|
|
321
|
+
* bands are declared. Pure math, unit-testable independent of rendering.
|
|
322
|
+
*/
|
|
323
|
+
export function createBiomeBandSampler(bands, fallback) {
|
|
324
|
+
if (bands.length === 0)
|
|
325
|
+
return () => fallback;
|
|
326
|
+
const resolved = bands
|
|
327
|
+
.map((band) => ({ z: band.z, fade: band.fade ?? 64, palette: resolveTerrainPalette(band) }))
|
|
328
|
+
.sort((a, b) => a.z - b.z);
|
|
329
|
+
const first = resolved[0];
|
|
330
|
+
const last = resolved[resolved.length - 1];
|
|
331
|
+
return (z) => {
|
|
332
|
+
if (z <= first.z)
|
|
333
|
+
return first.palette;
|
|
334
|
+
if (z >= last.z)
|
|
335
|
+
return last.palette;
|
|
336
|
+
for (let i = 0; i < resolved.length - 1; i += 1) {
|
|
337
|
+
const lower = resolved[i];
|
|
338
|
+
const upper = resolved[i + 1];
|
|
339
|
+
if (z < lower.z || z > upper.z)
|
|
340
|
+
continue;
|
|
341
|
+
const boundary = (lower.z + upper.z) / 2;
|
|
342
|
+
const half = Math.min(lower.fade, upper.fade) / 2;
|
|
343
|
+
if (z <= boundary - half)
|
|
344
|
+
return lower.palette;
|
|
345
|
+
if (z >= boundary + half)
|
|
346
|
+
return upper.palette;
|
|
347
|
+
return mixPalette(lower.palette, upper.palette, smoothstep(boundary - half, boundary + half, z));
|
|
348
|
+
}
|
|
349
|
+
return fallback;
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Per-position palette sampler over the descriptor's base `material`/`colors` plus its z-ordered
|
|
354
|
+
* `biomeBands` (painted first) and radial `materialRegions` (painted over) — the multi-biome coloring
|
|
355
|
+
* seam. Regions paint fully inside `radius` and blend back across `falloff`; later regions win overlaps.
|
|
284
356
|
*/
|
|
285
357
|
export function createTerrainPaletteSampler(descriptor) {
|
|
286
358
|
const base = resolveTerrainPalette(descriptor);
|
|
359
|
+
const bandSampler = descriptor.biomeBands === undefined || descriptor.biomeBands.length === 0
|
|
360
|
+
? null
|
|
361
|
+
: createBiomeBandSampler(descriptor.biomeBands, base);
|
|
287
362
|
const regions = (descriptor.materialRegions ?? []).map((region) => ({
|
|
288
|
-
|
|
289
|
-
radius: region.radius,
|
|
290
|
-
falloff: region.falloff ?? region.radius * 0.5,
|
|
363
|
+
...resolveRegionShape(region),
|
|
291
364
|
palette: resolveTerrainPalette(region),
|
|
292
365
|
}));
|
|
293
|
-
if (regions.length === 0)
|
|
366
|
+
if (regions.length === 0 && bandSampler === null)
|
|
294
367
|
return () => base;
|
|
295
368
|
return (x, z) => {
|
|
296
|
-
let palette = base;
|
|
369
|
+
let palette = bandSampler === null ? base : bandSampler(z);
|
|
297
370
|
for (const region of regions) {
|
|
298
|
-
const distance =
|
|
299
|
-
if (distance <=
|
|
371
|
+
const distance = region.coreDistance(x, z);
|
|
372
|
+
if (distance <= 0) {
|
|
300
373
|
palette = region.palette;
|
|
301
374
|
}
|
|
302
|
-
else if (distance <= region.
|
|
303
|
-
const t = smoothstep(
|
|
375
|
+
else if (distance <= region.falloff) {
|
|
376
|
+
const t = smoothstep(0, region.falloff, distance);
|
|
304
377
|
palette = mixPalette(region.palette, palette, t);
|
|
305
378
|
}
|
|
306
379
|
}
|
|
307
380
|
return palette;
|
|
308
381
|
};
|
|
309
382
|
}
|
|
383
|
+
/**
|
|
384
|
+
* Resolves a `TerrainMaterialRegion` of any shape to a `coreDistance(x, z)` — 0 inside the painted
|
|
385
|
+
* core, growing positive outward — plus its blend-ring `falloff`, so the palette sampler blends every
|
|
386
|
+
* shape through one shape-agnostic loop.
|
|
387
|
+
*/
|
|
388
|
+
function resolveRegionShape(region) {
|
|
389
|
+
if (region.shape === "polyline") {
|
|
390
|
+
const half = region.width * 0.5;
|
|
391
|
+
return {
|
|
392
|
+
falloff: region.falloff ?? half,
|
|
393
|
+
coreDistance: (x, z) => {
|
|
394
|
+
const sample = nearestOnPath(region.points, x, z);
|
|
395
|
+
return sample === null ? Number.POSITIVE_INFINITY : Math.max(0, sample.distance - half);
|
|
396
|
+
},
|
|
397
|
+
};
|
|
398
|
+
}
|
|
399
|
+
if (region.shape === "rect") {
|
|
400
|
+
const [hx, hz] = region.halfExtents;
|
|
401
|
+
const angle = region.rotationY ?? 0;
|
|
402
|
+
const cos = Math.cos(angle);
|
|
403
|
+
const sin = Math.sin(angle);
|
|
404
|
+
return {
|
|
405
|
+
falloff: region.falloff ?? Math.min(hx, hz) * 0.5,
|
|
406
|
+
coreDistance: (x, z) => {
|
|
407
|
+
const dx = x - region.center[0];
|
|
408
|
+
const dz = z - region.center[1];
|
|
409
|
+
const lx = dx * cos + dz * sin;
|
|
410
|
+
const lz = -dx * sin + dz * cos;
|
|
411
|
+
return Math.hypot(Math.max(0, Math.abs(lx) - hx), Math.max(0, Math.abs(lz) - hz));
|
|
412
|
+
},
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
return {
|
|
416
|
+
falloff: region.falloff ?? region.radius * 0.5,
|
|
417
|
+
coreDistance: (x, z) => Math.max(0, Math.hypot(x - region.center[0], z - region.center[1]) - region.radius),
|
|
418
|
+
};
|
|
419
|
+
}
|
|
310
420
|
function withFlattenMasks(sampleHeight, masks) {
|
|
311
421
|
const resolved = masks.map((mask) => ({
|
|
312
422
|
center: mask.center,
|
|
@@ -366,6 +476,7 @@ export function heightMapField(config) {
|
|
|
366
476
|
...(config.waterLevel === undefined ? {} : { waterLevel: config.waterLevel }),
|
|
367
477
|
});
|
|
368
478
|
}
|
|
479
|
+
/** Resolves a `TerrainEnvironmentDescriptor` into a concrete `TerrainField`, applying flatten masks. */
|
|
369
480
|
export function resolveTerrainField(descriptor) {
|
|
370
481
|
if (descriptor === undefined)
|
|
371
482
|
return flatField();
|
|
@@ -407,6 +518,7 @@ export function snapEntityToGround(entities, id, field, offset = 0) {
|
|
|
407
518
|
return entities.setPose(id, { position: snapToGround(field, entity.position, offset) });
|
|
408
519
|
}
|
|
409
520
|
export const DEFAULT_MAX_WALK_SLOPE = 0.6;
|
|
521
|
+
/** Zeroes out a movement step's x or z component where it would climb steeper than `maxSlope`. */
|
|
410
522
|
export function resolveGroundStep(field, x, z, stepX, stepZ, maxSlope = DEFAULT_MAX_WALK_SLOPE) {
|
|
411
523
|
const baseHeight = field.sampleHeight(x, z);
|
|
412
524
|
const tooSteep = (dx, dz) => {
|