@jgengine/core 0.7.0 → 0.9.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 +63 -0
- package/README.md +2 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +5 -0
- package/dist/ai/spawnDirector.js +14 -2
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- package/dist/anim/easing.d.ts +14 -0
- package/dist/anim/easing.js +55 -0
- package/dist/behaviour/behaviour.d.ts +136 -0
- package/dist/behaviour/behaviour.js +388 -0
- package/dist/cards/cardPile.d.ts +1 -0
- package/dist/cards/cardPile.js +12 -3
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +22 -2
- package/dist/combat/abilityKit.js +72 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/effects.d.ts +1 -1
- package/dist/combat/effects.js +9 -6
- package/dist/combat/projectiles.d.ts +64 -6
- package/dist/combat/projectiles.js +232 -74
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +40 -0
- package/dist/combat/shotOrigin.js +57 -0
- package/dist/combat/telegraph.d.ts +30 -1
- package/dist/combat/telegraph.js +61 -0
- package/dist/commands/commandRegistry.d.ts +1 -1
- package/dist/commands/commandRegistry.js +2 -1
- package/dist/crafting/recipe.js +4 -1
- package/dist/data/dataSource.d.ts +26 -0
- package/dist/data/dataSource.js +93 -0
- package/dist/data/devProxy.d.ts +11 -0
- package/dist/data/devProxy.js +38 -0
- package/dist/data/fetchJson.d.ts +20 -0
- package/dist/data/fetchJson.js +43 -0
- package/dist/data/jsonDataSource.d.ts +4 -0
- package/dist/data/jsonDataSource.js +6 -0
- package/dist/devtools/devtools.d.ts +205 -0
- package/dist/devtools/devtools.js +832 -0
- package/dist/devtools/transformTunables.d.ts +14 -0
- package/dist/devtools/transformTunables.js +38 -0
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +68 -0
- package/dist/game/chat.js +173 -0
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +10 -2
- package/dist/game/defineGame.js +2 -1
- package/dist/game/events.d.ts +35 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/levelSequence.d.ts +44 -0
- package/dist/game/levelSequence.js +78 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +27 -7
- package/dist/game/playableGame.d.ts +286 -6
- package/dist/game/playableGame.js +6 -1
- package/dist/game/progression.d.ts +1 -0
- package/dist/game/progression.js +23 -2
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +44 -1
- package/dist/game/race.js +116 -11
- package/dist/game/recordBook.d.ts +29 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/social.d.ts +42 -0
- package/dist/game/social.js +122 -0
- package/dist/game/spawnPoints.d.ts +19 -0
- package/dist/game/spawnPoints.js +23 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/game/worldItem.d.ts +3 -0
- package/dist/game/worldItem.js +12 -6
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/input/actionBindings.d.ts +10 -0
- package/dist/input/actionBindings.js +14 -0
- package/dist/input/axisInput.d.ts +26 -3
- package/dist/input/axisInput.js +44 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -0
- package/dist/input/gestureSurface.d.ts +31 -0
- package/dist/input/gestureSurface.js +120 -0
- package/dist/input/pointer.d.ts +45 -0
- package/dist/input/pointer.js +48 -0
- package/dist/input/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +89 -0
- package/dist/input/touchScheme.js +153 -0
- package/dist/item/affix.d.ts +1 -1
- package/dist/item/affix.js +1 -19
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +59 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +36 -3
- package/dist/movement/movementModel.js +81 -5
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/movement/voxelController.d.ts +61 -0
- package/dist/movement/voxelController.js +229 -0
- package/dist/multiplayer/chatContract.d.ts +42 -0
- package/dist/multiplayer/chatContract.js +57 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/identity.d.ts +13 -0
- package/dist/multiplayer/identity.js +20 -0
- package/dist/multiplayer/poseSyncGate.d.ts +1 -0
- package/dist/multiplayer/poseSyncGate.js +13 -1
- package/dist/multiplayer/presenceModel.d.ts +17 -2
- package/dist/multiplayer/presenceModel.js +30 -4
- package/dist/multiplayer/voiceContract.d.ts +44 -0
- package/dist/multiplayer/voiceContract.js +108 -0
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navConstrain.d.ts +14 -0
- package/dist/nav/navConstrain.js +26 -0
- package/dist/nav/navFromEnvironment.d.ts +11 -0
- package/dist/nav/navFromEnvironment.js +16 -0
- package/dist/nav/navGrid.d.ts +10 -0
- package/dist/nav/navGrid.js +48 -5
- package/dist/nav/pathFollow.d.ts +4 -2
- package/dist/nav/pathFollow.js +5 -3
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/ballisticSweep.d.ts +18 -0
- package/dist/physics/ballisticSweep.js +49 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +43 -5
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +54 -8
- package/dist/physics/physicsWorld.js +400 -79
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +5 -0
- package/dist/physics/vehicleBody.d.ts +6 -0
- package/dist/physics/vehicleBody.js +18 -18
- package/dist/puzzle/cellGrid.d.ts +26 -0
- package/dist/puzzle/cellGrid.js +110 -0
- package/dist/puzzle/fallingPiece.d.ts +24 -0
- package/dist/puzzle/fallingPiece.js +51 -0
- package/dist/random/nameGen.d.ts +18 -0
- package/dist/random/nameGen.js +35 -0
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +23 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/runtime/adapter.d.ts +35 -0
- package/dist/runtime/adapter.js +37 -1
- package/dist/runtime/cameraDirector.d.ts +15 -0
- package/dist/runtime/cameraDirector.js +31 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +65 -2
- package/dist/runtime/gameContext.js +280 -9
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +30 -1
- package/dist/runtime/hostPersistence.js +70 -5
- package/dist/runtime/inputSnapshot.d.ts +12 -0
- package/dist/runtime/inputSnapshot.js +17 -0
- package/dist/runtime/motionIntents.d.ts +33 -0
- package/dist/runtime/motionIntents.js +43 -0
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/transport.d.ts +32 -0
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/scene/colliders.d.ts +44 -0
- package/dist/scene/colliders.js +77 -0
- package/dist/scene/entityStore.d.ts +30 -5
- package/dist/scene/entityStore.js +83 -13
- package/dist/scene/movementSpeed.d.ts +35 -0
- package/dist/scene/movementSpeed.js +20 -0
- package/dist/scene/objectQuery.d.ts +29 -0
- package/dist/scene/objectQuery.js +140 -0
- package/dist/scene/objectStore.d.ts +13 -0
- package/dist/scene/objectStore.js +130 -6
- package/dist/scene/paintLayer.d.ts +15 -0
- package/dist/scene/paintLayer.js +53 -0
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +17 -1
- package/dist/scene/spatial.js +140 -17
- package/dist/sensor/concealment.d.ts +21 -0
- package/dist/sensor/concealment.js +63 -0
- package/dist/sensor/freezeMonitor.d.ts +17 -0
- package/dist/sensor/freezeMonitor.js +35 -0
- package/dist/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- package/dist/session/roles.d.ts +6 -0
- package/dist/session/roles.js +32 -0
- package/dist/session/roundState.d.ts +41 -17
- package/dist/session/roundState.js +67 -38
- package/dist/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.js +8 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +62 -0
- package/dist/tactics/fallingGrid.js +158 -0
- package/dist/tactics/tacticalGrid.d.ts +11 -0
- package/dist/tactics/tacticalGrid.js +17 -0
- package/dist/time/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/simClock.d.ts +25 -1
- package/dist/time/simClock.js +28 -3
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- package/dist/turn/intent.d.ts +14 -0
- package/dist/turn/intent.js +29 -0
- package/dist/turn/turnLoop.d.ts +2 -0
- package/dist/turn/turnLoop.js +8 -1
- package/dist/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +198 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +61 -0
- package/dist/world/environmentSummary.js +138 -0
- package/dist/world/features.d.ts +138 -9
- package/dist/world/features.js +73 -2
- package/dist/world/gridInstances.d.ts +12 -0
- package/dist/world/gridInstances.js +160 -0
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/placementController.d.ts +22 -1
- package/dist/world/placementController.js +56 -0
- package/dist/world/terrain.d.ts +62 -1
- package/dist/world/terrain.js +253 -9
- package/dist/world/voxelField.d.ts +54 -0
- package/dist/world/voxelField.js +241 -0
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +4983 -0
- package/package.json +14 -3
package/dist/world/features.d.ts
CHANGED
|
@@ -1,22 +1,63 @@
|
|
|
1
|
+
import type { BuildingPaletteOverrides, BuildingStyle } from "./buildings.js";
|
|
1
2
|
export interface WorldBounds {
|
|
2
3
|
w: number;
|
|
3
4
|
d: number;
|
|
4
5
|
}
|
|
6
|
+
export type TerrainMaterial = "grass" | "sand" | "snow" | "rock" | "ash" | "highland" | "slate";
|
|
5
7
|
export type EnvironmentVec2 = readonly [number, number];
|
|
6
8
|
export interface EnvironmentArea extends WorldBounds {
|
|
7
9
|
h?: number;
|
|
10
|
+
/** World-space center of the area; default `[0, 0]` (the world origin). Sites a weather/vegetation band away from the origin, like `building()`/`ocean()` `position`. */
|
|
11
|
+
position?: EnvironmentVec2;
|
|
12
|
+
}
|
|
13
|
+
export interface TerrainColors {
|
|
14
|
+
low?: string;
|
|
15
|
+
high?: string;
|
|
16
|
+
waterline?: string;
|
|
17
|
+
}
|
|
18
|
+
export interface TerrainFlattenMask {
|
|
19
|
+
center: EnvironmentVec2;
|
|
20
|
+
radius: number;
|
|
21
|
+
/** Target flat height; defaults to the noise field's height at `center`. */
|
|
22
|
+
height?: number;
|
|
23
|
+
/** Extra blend-ring width outside `radius` where the flat height smoothsteps back to the noise value; default `radius * 0.5`. */
|
|
24
|
+
falloff?: number;
|
|
25
|
+
}
|
|
26
|
+
/** A circular palette zone painted over the base terrain palette — snow caps, ash wastes, sand shores on one heightfield. */
|
|
27
|
+
export interface TerrainMaterialRegion {
|
|
28
|
+
center: EnvironmentVec2;
|
|
29
|
+
radius: number;
|
|
30
|
+
/** Named palette preset for this region (see `TERRAIN_MATERIAL_PALETTES` in `world/terrain`); overridden field-by-field by `colors`. */
|
|
31
|
+
material?: TerrainMaterial;
|
|
32
|
+
colors?: TerrainColors;
|
|
33
|
+
/** Blend-ring width outside `radius` back to the surrounding palette; default `radius * 0.5`. */
|
|
34
|
+
falloff?: number;
|
|
8
35
|
}
|
|
9
36
|
export interface TerrainEnvironmentConfig {
|
|
10
37
|
bounds?: WorldBounds;
|
|
11
38
|
height?: number;
|
|
12
39
|
heightMap?: string;
|
|
13
|
-
|
|
40
|
+
/**
|
|
41
|
+
* Game-supplied height function replacing the built-in noise field — banded biomes, ridge walls,
|
|
42
|
+
* terracing, anything expressible as `(x, z) => y`. `flatten` masks still carve into it, and both
|
|
43
|
+
* the rendered ground and `ctx.world.ground` sample it. A function doesn't serialize; keep it pure.
|
|
44
|
+
*/
|
|
45
|
+
heightField?: (x: number, z: number) => number;
|
|
46
|
+
/** Named palette preset (see `TERRAIN_MATERIAL_PALETTES` in `world/terrain`); default "grass". Overridden field-by-field by `colors`. */
|
|
47
|
+
material?: TerrainMaterial;
|
|
48
|
+
/** Explicit low/high/waterline hex colors; any field left unset falls back to the resolved `material` preset. */
|
|
49
|
+
colors?: TerrainColors;
|
|
50
|
+
/** Palette zones blended over the base `material`/`colors` for multi-biome readability. */
|
|
51
|
+
materialRegions?: readonly TerrainMaterialRegion[];
|
|
52
|
+
segments?: number;
|
|
14
53
|
seed?: string;
|
|
15
54
|
frequency?: number;
|
|
16
55
|
octaves?: number;
|
|
17
56
|
ridged?: boolean;
|
|
18
57
|
baseHeight?: number;
|
|
19
58
|
waterLevel?: number;
|
|
59
|
+
/** Flat pads carved into the noise field, e.g. for building pads or spawn circles. */
|
|
60
|
+
flatten?: readonly TerrainFlattenMask[];
|
|
20
61
|
}
|
|
21
62
|
export interface RainEnvironmentConfig {
|
|
22
63
|
area?: EnvironmentArea;
|
|
@@ -36,7 +77,7 @@ export interface SnowEnvironmentConfig {
|
|
|
36
77
|
color?: string;
|
|
37
78
|
}
|
|
38
79
|
export interface GrassEnvironmentConfig {
|
|
39
|
-
area?:
|
|
80
|
+
area?: EnvironmentArea;
|
|
40
81
|
density?: number;
|
|
41
82
|
bladeHeight?: readonly [number, number];
|
|
42
83
|
bladeWidth?: number;
|
|
@@ -46,24 +87,75 @@ export interface GrassEnvironmentConfig {
|
|
|
46
87
|
}
|
|
47
88
|
export interface OceanEnvironmentConfig {
|
|
48
89
|
bounds?: WorldBounds;
|
|
90
|
+
position?: EnvironmentVec2;
|
|
49
91
|
level?: number;
|
|
92
|
+
/**
|
|
93
|
+
* Scheduled water level as a function of game-time seconds — tides, floods, draining chambers.
|
|
94
|
+
* Wins over `level` (which stays the initial/static value); `waterSurfaceFromDescriptor` bakes it
|
|
95
|
+
* into `WaterSurface.height`, and the shell repositions the rendered ocean from it every frame.
|
|
96
|
+
* A function doesn't serialize; keep it pure and deterministic (compose with `time/stateSchedule`).
|
|
97
|
+
*/
|
|
98
|
+
levelAt?: (time: number) => number;
|
|
50
99
|
waveHeight?: number;
|
|
100
|
+
/** Primary Gerstner wavelength in world units (shared with shell Ocean `waveScale`; default 18). */
|
|
51
101
|
waveScale?: number;
|
|
52
102
|
waveSpeed?: number;
|
|
53
103
|
color?: string;
|
|
54
104
|
}
|
|
105
|
+
export interface SkyEnvironmentConfig {
|
|
106
|
+
/** Fixed look used when `timeOfDay` is off (or no clock is available); default "day". */
|
|
107
|
+
preset?: "day" | "dusk" | "night";
|
|
108
|
+
/** Drive sun/sky from the world clock's `calendar().dayFraction` instead of the fixed `preset`. */
|
|
109
|
+
timeOfDay?: boolean;
|
|
110
|
+
horizonColor?: string;
|
|
111
|
+
zenithColor?: string;
|
|
112
|
+
sunIntensity?: number;
|
|
113
|
+
ambientIntensity?: number;
|
|
114
|
+
fog?: {
|
|
115
|
+
color?: string;
|
|
116
|
+
near?: number;
|
|
117
|
+
far?: number;
|
|
118
|
+
};
|
|
119
|
+
}
|
|
55
120
|
export interface BuildingEnvironmentConfig {
|
|
56
121
|
count?: number;
|
|
122
|
+
position?: EnvironmentVec2;
|
|
57
123
|
footprint?: WorldBounds;
|
|
58
124
|
stories?: readonly [number, number];
|
|
59
125
|
storyHeight?: number;
|
|
60
126
|
spacing?: number;
|
|
61
|
-
|
|
127
|
+
/** Named palette archetype (see `BUILDING_STYLE_PALETTES` in `world/buildings`); default "generic". Overridden part-by-part by `palette`. */
|
|
128
|
+
style?: BuildingStyle;
|
|
129
|
+
/** Explicit per-part-kind hex colors; any part left unset falls back to the resolved `style` palette. */
|
|
130
|
+
palette?: BuildingPaletteOverrides;
|
|
62
131
|
seed?: string;
|
|
63
132
|
}
|
|
133
|
+
export type PadSize = readonly [number, number] | {
|
|
134
|
+
radius: number;
|
|
135
|
+
};
|
|
136
|
+
export interface PadEnvironmentConfig {
|
|
137
|
+
center: EnvironmentVec2;
|
|
138
|
+
size: PadSize;
|
|
139
|
+
/** Surface offset above the terrain height at `center`; default `0.05` — just proud of the ground. Ignored when `elevation` is set. */
|
|
140
|
+
height?: number;
|
|
141
|
+
/** Absolute world-space Y for the pad surface — a landing pad on a rooftop, a floating platform. Skips the implicit terrain flatten (there's no ground to flatten toward). */
|
|
142
|
+
elevation?: number;
|
|
143
|
+
color?: string;
|
|
144
|
+
/** Rotation applied to rectangular pads; ignored for circular pads. */
|
|
145
|
+
rotationY?: number;
|
|
146
|
+
}
|
|
64
147
|
export type TerrainEnvironmentDescriptor = {
|
|
65
148
|
kind: "terrain";
|
|
66
149
|
} & Required<Pick<TerrainEnvironmentConfig, "bounds" | "height">> & Omit<TerrainEnvironmentConfig, "bounds" | "height">;
|
|
150
|
+
/** A bounded terrain patch floating at its own altitude — sky islands, arena platforms, split landmasses with void between. */
|
|
151
|
+
export interface TerrainIslandConfig extends TerrainEnvironmentConfig {
|
|
152
|
+
/** World-space center of the island; its `bounds` rect surrounds this point. */
|
|
153
|
+
origin: EnvironmentVec2;
|
|
154
|
+
}
|
|
155
|
+
export type TerrainIslandDescriptor = Omit<TerrainEnvironmentDescriptor, "kind"> & {
|
|
156
|
+
kind: "island";
|
|
157
|
+
origin: EnvironmentVec2;
|
|
158
|
+
};
|
|
67
159
|
export type RainEnvironmentDescriptor = {
|
|
68
160
|
kind: "rain";
|
|
69
161
|
} & Required<Pick<RainEnvironmentConfig, "area" | "density" | "speed" | "dropLength" | "wind" | "color">>;
|
|
@@ -75,10 +167,16 @@ export type GrassEnvironmentDescriptor = {
|
|
|
75
167
|
} & Required<Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors">> & Pick<GrassEnvironmentConfig, "seed">;
|
|
76
168
|
export type OceanEnvironmentDescriptor = {
|
|
77
169
|
kind: "ocean";
|
|
78
|
-
} & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color"
|
|
170
|
+
} & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">> & Pick<OceanEnvironmentConfig, "position" | "levelAt">;
|
|
79
171
|
export type BuildingEnvironmentDescriptor = {
|
|
80
172
|
kind: "building";
|
|
81
|
-
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed">;
|
|
173
|
+
} & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed" | "position" | "palette">;
|
|
174
|
+
export type PadEnvironmentDescriptor = {
|
|
175
|
+
kind: "pad";
|
|
176
|
+
} & Required<Pick<PadEnvironmentConfig, "center" | "size" | "height" | "color">> & Pick<PadEnvironmentConfig, "rotationY" | "elevation">;
|
|
177
|
+
export type SkyEnvironmentDescriptor = {
|
|
178
|
+
kind: "sky";
|
|
179
|
+
} & Required<Pick<SkyEnvironmentConfig, "preset" | "timeOfDay">> & Omit<SkyEnvironmentConfig, "preset" | "timeOfDay">;
|
|
82
180
|
export type WeatherEnvironmentDescriptor = RainEnvironmentDescriptor | SnowEnvironmentDescriptor;
|
|
83
181
|
export type VegetationEnvironmentDescriptor = GrassEnvironmentDescriptor;
|
|
84
182
|
export type WaterEnvironmentDescriptor = OceanEnvironmentDescriptor;
|
|
@@ -86,36 +184,62 @@ export type StructureEnvironmentDescriptor = BuildingEnvironmentDescriptor;
|
|
|
86
184
|
export type EnvironmentDescriptorList<T> = T | readonly T[];
|
|
87
185
|
export interface EnvironmentWorldConfig {
|
|
88
186
|
terrain?: TerrainEnvironmentDescriptor;
|
|
187
|
+
/** Disconnected landmasses at independent altitudes over the base `terrain` (or over void without one) — see `island()`. */
|
|
188
|
+
islands?: readonly TerrainIslandDescriptor[];
|
|
189
|
+
sky?: SkyEnvironmentDescriptor;
|
|
89
190
|
weather?: EnvironmentDescriptorList<WeatherEnvironmentDescriptor>;
|
|
90
191
|
vegetation?: EnvironmentDescriptorList<VegetationEnvironmentDescriptor>;
|
|
91
192
|
water?: EnvironmentDescriptorList<WaterEnvironmentDescriptor>;
|
|
92
193
|
structures?: EnvironmentDescriptorList<StructureEnvironmentDescriptor>;
|
|
194
|
+
/** Ground pads, e.g. platforms or paved patches; each implicitly flattens the terrain beneath it. */
|
|
195
|
+
pads?: readonly PadEnvironmentDescriptor[];
|
|
93
196
|
}
|
|
94
197
|
export interface EnvironmentWorldFeature {
|
|
95
198
|
kind: "environment";
|
|
96
199
|
terrain?: TerrainEnvironmentDescriptor;
|
|
200
|
+
islands?: readonly TerrainIslandDescriptor[];
|
|
201
|
+
sky?: SkyEnvironmentDescriptor;
|
|
97
202
|
weather?: readonly WeatherEnvironmentDescriptor[];
|
|
98
203
|
vegetation?: readonly VegetationEnvironmentDescriptor[];
|
|
99
204
|
water?: readonly WaterEnvironmentDescriptor[];
|
|
100
205
|
structures?: readonly StructureEnvironmentDescriptor[];
|
|
206
|
+
pads?: readonly PadEnvironmentDescriptor[];
|
|
207
|
+
}
|
|
208
|
+
export interface WorldGridCell {
|
|
209
|
+
x: number;
|
|
210
|
+
z: number;
|
|
211
|
+
/** Extruded box height for this cell; falls back to the config's `baseHeight`, then `1`. */
|
|
212
|
+
height?: number;
|
|
213
|
+
/** Per-cell tint; falls back to the config's `defaultColor`. */
|
|
214
|
+
color?: string;
|
|
215
|
+
}
|
|
216
|
+
/** Shared by `biomes()`/`voxel()`/`plots()`/`tilemap()` so the shell can render their declared content as instanced boxes without a hand-written renderer. */
|
|
217
|
+
export interface WorldGridConfig {
|
|
218
|
+
cells?: readonly WorldGridCell[];
|
|
219
|
+
/** World-unit size of one grid cell. Default 1. */
|
|
220
|
+
cellSize?: number;
|
|
221
|
+
/** Height used for cells that omit their own `height`. Default 1. */
|
|
222
|
+
baseHeight?: number;
|
|
223
|
+
/** Color used for cells that omit their own `color`. */
|
|
224
|
+
defaultColor?: string;
|
|
101
225
|
}
|
|
102
|
-
export interface BiomesWorldConfig {
|
|
226
|
+
export interface BiomesWorldConfig extends WorldGridConfig {
|
|
103
227
|
map: string;
|
|
104
228
|
zones: string;
|
|
105
229
|
bounds?: WorldBounds;
|
|
106
230
|
}
|
|
107
|
-
export interface VoxelWorldConfig {
|
|
231
|
+
export interface VoxelWorldConfig extends WorldGridConfig {
|
|
108
232
|
seed: string;
|
|
109
233
|
generate?: string;
|
|
110
234
|
streaming?: {
|
|
111
235
|
radius: number;
|
|
112
236
|
};
|
|
113
237
|
}
|
|
114
|
-
export interface PlotsWorldConfig {
|
|
238
|
+
export interface PlotsWorldConfig extends WorldGridConfig {
|
|
115
239
|
city?: string;
|
|
116
240
|
interiors?: string;
|
|
117
241
|
}
|
|
118
|
-
export interface TilemapWorldConfig {
|
|
242
|
+
export interface TilemapWorldConfig extends WorldGridConfig {
|
|
119
243
|
map: string;
|
|
120
244
|
}
|
|
121
245
|
export type WorldFeature = ({
|
|
@@ -129,13 +253,18 @@ export type WorldFeature = ({
|
|
|
129
253
|
} & TilemapWorldConfig) | EnvironmentWorldFeature | {
|
|
130
254
|
kind: "flat";
|
|
131
255
|
};
|
|
256
|
+
/** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. Elevated pads (absolute `elevation`) float free and carve nothing. */
|
|
257
|
+
export declare function padFlattenMasks(pads: readonly PadEnvironmentDescriptor[]): readonly TerrainFlattenMask[];
|
|
132
258
|
export declare function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature;
|
|
133
259
|
export declare function terrain(config?: TerrainEnvironmentConfig): TerrainEnvironmentDescriptor;
|
|
260
|
+
export declare function sky(config?: SkyEnvironmentConfig): SkyEnvironmentDescriptor;
|
|
134
261
|
export declare function rain(config?: RainEnvironmentConfig): RainEnvironmentDescriptor;
|
|
135
262
|
export declare function snow(config?: SnowEnvironmentConfig): SnowEnvironmentDescriptor;
|
|
136
263
|
export declare function grass(config?: GrassEnvironmentConfig): GrassEnvironmentDescriptor;
|
|
137
264
|
export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironmentDescriptor;
|
|
265
|
+
export declare function island(config: TerrainIslandConfig): TerrainIslandDescriptor;
|
|
138
266
|
export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
|
|
267
|
+
export declare function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor;
|
|
139
268
|
export declare function biomes(config: BiomesWorldConfig): WorldFeature;
|
|
140
269
|
export declare function voxel(config: VoxelWorldConfig): WorldFeature;
|
|
141
270
|
export declare function plots(config?: PlotsWorldConfig): WorldFeature;
|
package/dist/world/features.js
CHANGED
|
@@ -7,34 +7,82 @@ function list(value) {
|
|
|
7
7
|
function withOptional(base, optional) {
|
|
8
8
|
return optional === undefined ? base : { ...base, ...optional };
|
|
9
9
|
}
|
|
10
|
+
/** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. Elevated pads (absolute `elevation`) float free and carve nothing. */
|
|
11
|
+
export function padFlattenMasks(pads) {
|
|
12
|
+
return pads
|
|
13
|
+
.filter((padDescriptor) => padDescriptor.elevation === undefined)
|
|
14
|
+
.map((padDescriptor) => ({
|
|
15
|
+
center: padDescriptor.center,
|
|
16
|
+
radius: padHalfExtent(padDescriptor.size) * 1.2,
|
|
17
|
+
}));
|
|
18
|
+
}
|
|
19
|
+
function padHalfExtent(size) {
|
|
20
|
+
return "radius" in size ? size.radius : Math.max(size[0], size[1]) / 2;
|
|
21
|
+
}
|
|
22
|
+
function withPadFlatten(terrainDescriptor, pads) {
|
|
23
|
+
if (terrainDescriptor === undefined || pads === undefined || pads.length === 0)
|
|
24
|
+
return terrainDescriptor;
|
|
25
|
+
return {
|
|
26
|
+
...terrainDescriptor,
|
|
27
|
+
flatten: [...(terrainDescriptor.flatten ?? []), ...padFlattenMasks(pads)],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
10
30
|
export function environment(config = {}) {
|
|
11
31
|
const weather = list(config.weather);
|
|
12
32
|
const vegetation = list(config.vegetation);
|
|
13
33
|
const water = list(config.water);
|
|
14
34
|
const structures = list(config.structures);
|
|
35
|
+
const terrainDescriptor = withPadFlatten(config.terrain, config.pads);
|
|
15
36
|
return {
|
|
16
37
|
kind: "environment",
|
|
17
|
-
...(
|
|
38
|
+
...(terrainDescriptor === undefined ? {} : { terrain: terrainDescriptor }),
|
|
39
|
+
...(config.islands === undefined || config.islands.length === 0 ? {} : { islands: config.islands }),
|
|
40
|
+
...(config.sky === undefined ? {} : { sky: config.sky }),
|
|
18
41
|
...(weather === undefined ? {} : { weather }),
|
|
19
42
|
...(vegetation === undefined ? {} : { vegetation }),
|
|
20
43
|
...(water === undefined ? {} : { water }),
|
|
21
44
|
...(structures === undefined ? {} : { structures }),
|
|
45
|
+
...(config.pads === undefined ? {} : { pads: config.pads }),
|
|
22
46
|
};
|
|
23
47
|
}
|
|
24
48
|
export function terrain(config = {}) {
|
|
49
|
+
if (config.baseHeight !== undefined &&
|
|
50
|
+
config.height === undefined &&
|
|
51
|
+
config.heightField === undefined &&
|
|
52
|
+
typeof console !== "undefined") {
|
|
53
|
+
console.warn("[jgengine:terrain] baseHeight is set but height is not — height is the noise amplitude (defaulting to 0 here), so this terrain is a flat plateau at baseHeight. Set height explicitly (height: 0 for an intentional plateau) to silence this.");
|
|
54
|
+
}
|
|
25
55
|
return withOptional({
|
|
26
56
|
kind: "terrain",
|
|
27
57
|
bounds: config.bounds ?? { w: 512, d: 512 },
|
|
28
58
|
height: config.height ?? 0,
|
|
29
59
|
}, {
|
|
30
60
|
...(config.heightMap === undefined ? {} : { heightMap: config.heightMap }),
|
|
61
|
+
...(config.heightField === undefined ? {} : { heightField: config.heightField }),
|
|
31
62
|
...(config.material === undefined ? {} : { material: config.material }),
|
|
63
|
+
...(config.colors === undefined ? {} : { colors: config.colors }),
|
|
64
|
+
...(config.materialRegions === undefined ? {} : { materialRegions: config.materialRegions }),
|
|
65
|
+
...(config.segments === undefined ? {} : { segments: config.segments }),
|
|
32
66
|
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
33
67
|
...(config.frequency === undefined ? {} : { frequency: config.frequency }),
|
|
34
68
|
...(config.octaves === undefined ? {} : { octaves: config.octaves }),
|
|
35
69
|
...(config.ridged === undefined ? {} : { ridged: config.ridged }),
|
|
36
70
|
...(config.baseHeight === undefined ? {} : { baseHeight: config.baseHeight }),
|
|
37
71
|
...(config.waterLevel === undefined ? {} : { waterLevel: config.waterLevel }),
|
|
72
|
+
...(config.flatten === undefined ? {} : { flatten: config.flatten }),
|
|
73
|
+
});
|
|
74
|
+
}
|
|
75
|
+
export function sky(config = {}) {
|
|
76
|
+
return withOptional({
|
|
77
|
+
kind: "sky",
|
|
78
|
+
preset: config.preset ?? "day",
|
|
79
|
+
timeOfDay: config.timeOfDay ?? false,
|
|
80
|
+
}, {
|
|
81
|
+
...(config.horizonColor === undefined ? {} : { horizonColor: config.horizonColor }),
|
|
82
|
+
...(config.zenithColor === undefined ? {} : { zenithColor: config.zenithColor }),
|
|
83
|
+
...(config.sunIntensity === undefined ? {} : { sunIntensity: config.sunIntensity }),
|
|
84
|
+
...(config.ambientIntensity === undefined ? {} : { ambientIntensity: config.ambientIntensity }),
|
|
85
|
+
...(config.fog === undefined ? {} : { fog: config.fog }),
|
|
38
86
|
});
|
|
39
87
|
}
|
|
40
88
|
export function rain(config = {}) {
|
|
@@ -80,8 +128,15 @@ export function ocean(config = {}) {
|
|
|
80
128
|
waveScale: config.waveScale ?? 18,
|
|
81
129
|
waveSpeed: config.waveSpeed ?? 0.55,
|
|
82
130
|
color: config.color ?? "#1d7fa3",
|
|
131
|
+
...(config.position === undefined ? {} : { position: config.position }),
|
|
132
|
+
...(config.levelAt === undefined ? {} : { levelAt: config.levelAt }),
|
|
83
133
|
};
|
|
84
134
|
}
|
|
135
|
+
export function island(config) {
|
|
136
|
+
const { origin, ...terrainConfig } = config;
|
|
137
|
+
const { kind: _kind, ...descriptor } = terrain(terrainConfig);
|
|
138
|
+
return { ...descriptor, kind: "island", origin };
|
|
139
|
+
}
|
|
85
140
|
export function building(config = {}) {
|
|
86
141
|
return withOptional({
|
|
87
142
|
kind: "building",
|
|
@@ -91,7 +146,23 @@ export function building(config = {}) {
|
|
|
91
146
|
storyHeight: config.storyHeight ?? 3,
|
|
92
147
|
spacing: config.spacing ?? 2,
|
|
93
148
|
style: config.style ?? "generic",
|
|
94
|
-
},
|
|
149
|
+
}, {
|
|
150
|
+
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
151
|
+
...(config.position === undefined ? {} : { position: config.position }),
|
|
152
|
+
...(config.palette === undefined ? {} : { palette: config.palette }),
|
|
153
|
+
});
|
|
154
|
+
}
|
|
155
|
+
export function pad(config) {
|
|
156
|
+
return withOptional({
|
|
157
|
+
kind: "pad",
|
|
158
|
+
center: config.center,
|
|
159
|
+
size: config.size,
|
|
160
|
+
height: config.height ?? 0.05,
|
|
161
|
+
color: config.color ?? "#8b8680",
|
|
162
|
+
}, {
|
|
163
|
+
...(config.rotationY === undefined ? {} : { rotationY: config.rotationY }),
|
|
164
|
+
...(config.elevation === undefined ? {} : { elevation: config.elevation }),
|
|
165
|
+
});
|
|
95
166
|
}
|
|
96
167
|
export function biomes(config) {
|
|
97
168
|
return { kind: "biomes", ...config };
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { WorldFeature, WorldGridCell, WorldGridConfig } from "./features.js";
|
|
2
|
+
export interface GridInstanceTransform {
|
|
3
|
+
position: readonly [number, number, number];
|
|
4
|
+
scale: readonly [number, number, number];
|
|
5
|
+
color: string;
|
|
6
|
+
}
|
|
7
|
+
type GridWorldFeature = Extract<WorldFeature, {
|
|
8
|
+
kind: "biomes" | "voxel" | "plots" | "tilemap";
|
|
9
|
+
}>;
|
|
10
|
+
export declare function resolveGridCells(config: WorldGridConfig | GridWorldFeature): readonly WorldGridCell[];
|
|
11
|
+
export declare function resolveGridInstances(config: WorldGridConfig | GridWorldFeature): readonly GridInstanceTransform[];
|
|
12
|
+
export {};
|
|
@@ -0,0 +1,160 @@
|
|
|
1
|
+
import { seedFrom, valueNoise } from "./terrain.js";
|
|
2
|
+
const DEFAULT_CELL_SIZE = 1;
|
|
3
|
+
const DEFAULT_BASE_HEIGHT = 1;
|
|
4
|
+
const DEFAULT_GRID_COLOR = "#8a8f98";
|
|
5
|
+
const DEFAULT_VOXEL_RADIUS = 4;
|
|
6
|
+
function looksLikeAssetPath(value) {
|
|
7
|
+
const trimmed = value.trim();
|
|
8
|
+
if (trimmed.length === 0)
|
|
9
|
+
return false;
|
|
10
|
+
if (/^(?:\.\/|\.\.\/|\/|[A-Za-z]:[\\/])/.test(trimmed))
|
|
11
|
+
return true;
|
|
12
|
+
if (/\.(?:ts|tsx|js|jsx|json|png|jpe?g|webp|gif|bmp)$/i.test(trimmed))
|
|
13
|
+
return true;
|
|
14
|
+
return false;
|
|
15
|
+
}
|
|
16
|
+
function parseZoneTable(zones) {
|
|
17
|
+
const trimmed = zones.trim();
|
|
18
|
+
if (trimmed.startsWith("{")) {
|
|
19
|
+
const parsed = JSON.parse(trimmed);
|
|
20
|
+
if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
21
|
+
throw new Error("biomes zones JSON must be an object keyed by map characters");
|
|
22
|
+
}
|
|
23
|
+
const table = {};
|
|
24
|
+
for (const [key, value] of Object.entries(parsed)) {
|
|
25
|
+
if (typeof value === "string") {
|
|
26
|
+
table[key] = { color: value };
|
|
27
|
+
continue;
|
|
28
|
+
}
|
|
29
|
+
if (value !== null && typeof value === "object" && !Array.isArray(value)) {
|
|
30
|
+
const entry = value;
|
|
31
|
+
table[key] = {
|
|
32
|
+
...(typeof entry.height === "number" ? { height: entry.height } : {}),
|
|
33
|
+
...(typeof entry.color === "string" ? { color: entry.color } : {}),
|
|
34
|
+
};
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
throw new Error(`biomes zones entry "${key}" must be a color string or { height?, color? }`);
|
|
38
|
+
}
|
|
39
|
+
return table;
|
|
40
|
+
}
|
|
41
|
+
throw new Error(`biomes zones must be inline JSON (e.g. {"#":{"height":2,"color":"#4a7"}}); asset path "${zones}" is not auto-loaded`);
|
|
42
|
+
}
|
|
43
|
+
function parseAsciiGrid(map, resolveCell) {
|
|
44
|
+
if (looksLikeAssetPath(map)) {
|
|
45
|
+
throw new Error(`grid-world map "${map}" looks like an asset path and is not auto-loaded — provide inline ASCII or explicit cells`);
|
|
46
|
+
}
|
|
47
|
+
const rows = map
|
|
48
|
+
.replace(/\r\n/g, "\n")
|
|
49
|
+
.split("\n")
|
|
50
|
+
.map((row) => row.trimEnd())
|
|
51
|
+
.filter((row, index, all) => row.length > 0 || (index > 0 && index < all.length - 1));
|
|
52
|
+
const solidRows = rows.filter((row) => row.trim().length > 0);
|
|
53
|
+
if (solidRows.length === 0) {
|
|
54
|
+
throw new Error("grid-world map is empty — provide ASCII rows or explicit cells");
|
|
55
|
+
}
|
|
56
|
+
const width = Math.max(...solidRows.map((row) => row.length));
|
|
57
|
+
const cells = [];
|
|
58
|
+
for (let rowIndex = 0; rowIndex < solidRows.length; rowIndex += 1) {
|
|
59
|
+
const row = solidRows[rowIndex].padEnd(width, " ");
|
|
60
|
+
const z = rowIndex - Math.floor((solidRows.length - 1) / 2);
|
|
61
|
+
for (let colIndex = 0; colIndex < width; colIndex += 1) {
|
|
62
|
+
const glyph = row[colIndex] ?? " ";
|
|
63
|
+
if (glyph === " " || glyph === ".")
|
|
64
|
+
continue;
|
|
65
|
+
const x = colIndex - Math.floor((width - 1) / 2);
|
|
66
|
+
const cell = resolveCell(glyph, x, z);
|
|
67
|
+
if (cell !== null)
|
|
68
|
+
cells.push(cell);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
if (cells.length === 0) {
|
|
72
|
+
throw new Error("grid-world map produced no solid cells — use non-space/non-dot glyphs or explicit cells");
|
|
73
|
+
}
|
|
74
|
+
return cells;
|
|
75
|
+
}
|
|
76
|
+
function parseTilemapCells(config) {
|
|
77
|
+
return parseAsciiGrid(config.map, (glyph, x, z) => {
|
|
78
|
+
if (glyph >= "1" && glyph <= "9")
|
|
79
|
+
return { x, z, height: Number(glyph) };
|
|
80
|
+
return { x, z };
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
function parseBiomeCells(config) {
|
|
84
|
+
if (looksLikeAssetPath(config.zones)) {
|
|
85
|
+
throw new Error(`biomes zones "${config.zones}" looks like an asset path and is not auto-loaded — provide inline JSON zones or explicit cells`);
|
|
86
|
+
}
|
|
87
|
+
const zones = parseZoneTable(config.zones);
|
|
88
|
+
return parseAsciiGrid(config.map, (glyph, x, z) => {
|
|
89
|
+
const zone = zones[glyph];
|
|
90
|
+
if (zone === undefined) {
|
|
91
|
+
throw new Error(`biomes map glyph "${glyph}" is missing from zones`);
|
|
92
|
+
}
|
|
93
|
+
return {
|
|
94
|
+
x,
|
|
95
|
+
z,
|
|
96
|
+
...(zone.height === undefined ? {} : { height: zone.height }),
|
|
97
|
+
...(zone.color === undefined ? {} : { color: zone.color }),
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
function generateVoxelCells(config) {
|
|
102
|
+
const radius = config.streaming?.radius ?? DEFAULT_VOXEL_RADIUS;
|
|
103
|
+
const seed = seedFrom(config.seed, 1337);
|
|
104
|
+
const cells = [];
|
|
105
|
+
for (let z = -radius; z <= radius; z += 1) {
|
|
106
|
+
for (let x = -radius; x <= radius; x += 1) {
|
|
107
|
+
const n = valueNoise(x * 0.37, z * 0.37, seed);
|
|
108
|
+
if (n < -0.15)
|
|
109
|
+
continue;
|
|
110
|
+
const height = Math.max(1, Math.round(1 + (n + 1) * 1.5));
|
|
111
|
+
const tone = Math.round(120 + n * 40);
|
|
112
|
+
cells.push({
|
|
113
|
+
x,
|
|
114
|
+
z,
|
|
115
|
+
height,
|
|
116
|
+
color: `#${tone.toString(16).padStart(2, "0")}${tone.toString(16).padStart(2, "0")}${tone.toString(16).padStart(2, "0")}`,
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
if (cells.length === 0) {
|
|
121
|
+
cells.push({ x: 0, z: 0, height: 1 });
|
|
122
|
+
}
|
|
123
|
+
return cells;
|
|
124
|
+
}
|
|
125
|
+
function resolvePlotsCells(config) {
|
|
126
|
+
if (config.city !== undefined || config.interiors !== undefined) {
|
|
127
|
+
throw new Error(`plots source fields require explicit cells until asset loading is supported (city=${config.city ?? "—"}, interiors=${config.interiors ?? "—"})`);
|
|
128
|
+
}
|
|
129
|
+
return config.cells ?? [];
|
|
130
|
+
}
|
|
131
|
+
export function resolveGridCells(config) {
|
|
132
|
+
if (config.cells !== undefined && config.cells.length > 0)
|
|
133
|
+
return config.cells;
|
|
134
|
+
if ("kind" in config) {
|
|
135
|
+
switch (config.kind) {
|
|
136
|
+
case "tilemap":
|
|
137
|
+
return parseTilemapCells(config);
|
|
138
|
+
case "biomes":
|
|
139
|
+
return parseBiomeCells(config);
|
|
140
|
+
case "voxel":
|
|
141
|
+
return generateVoxelCells(config);
|
|
142
|
+
case "plots":
|
|
143
|
+
return resolvePlotsCells(config);
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
return config.cells ?? [];
|
|
147
|
+
}
|
|
148
|
+
export function resolveGridInstances(config) {
|
|
149
|
+
const cells = resolveGridCells(config);
|
|
150
|
+
const cellSize = config.cellSize ?? DEFAULT_CELL_SIZE;
|
|
151
|
+
const defaultColor = config.defaultColor ?? DEFAULT_GRID_COLOR;
|
|
152
|
+
return cells.map((cell) => {
|
|
153
|
+
const height = cell.height ?? config.baseHeight ?? DEFAULT_BASE_HEIGHT;
|
|
154
|
+
return {
|
|
155
|
+
position: [cell.x * cellSize, height / 2, cell.z * cellSize],
|
|
156
|
+
scale: [cellSize, height, cellSize],
|
|
157
|
+
color: cell.color ?? defaultColor,
|
|
158
|
+
};
|
|
159
|
+
});
|
|
160
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface LodBand {
|
|
2
|
+
maxDistance: number;
|
|
3
|
+
interval: number;
|
|
4
|
+
}
|
|
5
|
+
export interface LodSchedulerConfig {
|
|
6
|
+
bands: readonly LodBand[];
|
|
7
|
+
beyondInterval?: number | null;
|
|
8
|
+
stagger?: boolean;
|
|
9
|
+
}
|
|
10
|
+
export interface LodScheduler {
|
|
11
|
+
bandIndex(distance: number): number;
|
|
12
|
+
step(id: string, distance: number, dtSeconds: number): number;
|
|
13
|
+
remove(id: string): void;
|
|
14
|
+
clear(): void;
|
|
15
|
+
size(): number;
|
|
16
|
+
}
|
|
17
|
+
export declare function createLodScheduler(config: LodSchedulerConfig): LodScheduler;
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
const MAX_ACCUMULATED_SECONDS = 60;
|
|
2
|
+
function hashSeed(text) {
|
|
3
|
+
let h = 2166136261 >>> 0;
|
|
4
|
+
for (let i = 0; i < text.length; i++) {
|
|
5
|
+
h ^= text.charCodeAt(i);
|
|
6
|
+
h = Math.imul(h, 16777619);
|
|
7
|
+
}
|
|
8
|
+
return h >>> 0;
|
|
9
|
+
}
|
|
10
|
+
export function createLodScheduler(config) {
|
|
11
|
+
const bands = [...config.bands].sort((a, b) => a.maxDistance - b.maxDistance);
|
|
12
|
+
const beyondInterval = config.beyondInterval ?? null;
|
|
13
|
+
const stagger = config.stagger ?? true;
|
|
14
|
+
const buckets = new Map();
|
|
15
|
+
function bandIndex(distance) {
|
|
16
|
+
for (let i = 0; i < bands.length; i++) {
|
|
17
|
+
if (distance <= bands[i].maxDistance)
|
|
18
|
+
return i;
|
|
19
|
+
}
|
|
20
|
+
return -1;
|
|
21
|
+
}
|
|
22
|
+
function effectiveInterval(band) {
|
|
23
|
+
return band === -1 ? beyondInterval : bands[band].interval;
|
|
24
|
+
}
|
|
25
|
+
function phaseOffset(id, interval) {
|
|
26
|
+
if (interval === null || interval <= 0)
|
|
27
|
+
return 0;
|
|
28
|
+
return (hashSeed(id) / 4294967296) * interval;
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
bandIndex,
|
|
32
|
+
step(id, distance, dtSeconds) {
|
|
33
|
+
const interval = effectiveInterval(bandIndex(distance));
|
|
34
|
+
let bucket = buckets.get(id);
|
|
35
|
+
if (bucket === undefined) {
|
|
36
|
+
bucket = stagger ? phaseOffset(id, interval) : 0;
|
|
37
|
+
}
|
|
38
|
+
if (dtSeconds > 0) {
|
|
39
|
+
bucket = Math.min(MAX_ACCUMULATED_SECONDS, bucket + dtSeconds);
|
|
40
|
+
}
|
|
41
|
+
if (interval === null) {
|
|
42
|
+
buckets.set(id, bucket);
|
|
43
|
+
return 0;
|
|
44
|
+
}
|
|
45
|
+
if (interval <= 0) {
|
|
46
|
+
buckets.set(id, 0);
|
|
47
|
+
return bucket;
|
|
48
|
+
}
|
|
49
|
+
if (bucket >= interval) {
|
|
50
|
+
buckets.set(id, 0);
|
|
51
|
+
return bucket;
|
|
52
|
+
}
|
|
53
|
+
buckets.set(id, bucket);
|
|
54
|
+
return 0;
|
|
55
|
+
},
|
|
56
|
+
remove(id) {
|
|
57
|
+
buckets.delete(id);
|
|
58
|
+
},
|
|
59
|
+
clear() {
|
|
60
|
+
buckets.clear();
|
|
61
|
+
},
|
|
62
|
+
size() {
|
|
63
|
+
return buckets.size;
|
|
64
|
+
},
|
|
65
|
+
};
|
|
66
|
+
}
|