@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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** The local player's auto-attack state for one frame. `swingTimer` counts DOWN in seconds to 0 (= a swing lands). */
|
|
2
|
+
export interface SwingPlayerInput {
|
|
3
|
+
autoAttack: boolean;
|
|
4
|
+
swingTimer: number;
|
|
5
|
+
weapon: {
|
|
6
|
+
speed: number;
|
|
7
|
+
};
|
|
8
|
+
}
|
|
9
|
+
/** The current target, or the fields the bar needs from it. */
|
|
10
|
+
export interface SwingTargetInput {
|
|
11
|
+
dead: boolean;
|
|
12
|
+
/** Entity kind; an `"object"` target (a node/chest) never shows a swing bar. */
|
|
13
|
+
kind: string;
|
|
14
|
+
}
|
|
15
|
+
/** Whether the bar shows a "ready" label or the seconds-remaining countdown. */
|
|
16
|
+
export type SwingLabelKind = "ready" | "seconds";
|
|
17
|
+
/** One frame of swing-bar state plus the cursor (`nextPeriod`/`nextTimer`) the caller carries back into the next call. */
|
|
18
|
+
export interface SwingTimerState {
|
|
19
|
+
/** Whether the bar should render at all. */
|
|
20
|
+
visible: boolean;
|
|
21
|
+
/** Fill 0..1, growing toward 1 as the next swing nears. */
|
|
22
|
+
frac: number;
|
|
23
|
+
/** True the moment the swing is ready to land. */
|
|
24
|
+
ready: boolean;
|
|
25
|
+
labelKind: SwingLabelKind;
|
|
26
|
+
/** Seconds remaining until the next swing (0 when ready). */
|
|
27
|
+
seconds: number;
|
|
28
|
+
/** Recovered swing period this frame — pass back as `prevPeriod` next call. */
|
|
29
|
+
nextPeriod: number;
|
|
30
|
+
/** This frame's timer — pass back as `prevTimer` next call to detect the reset edge. */
|
|
31
|
+
nextTimer: number;
|
|
32
|
+
}
|
|
33
|
+
/** Threshold above which a timer jump is treated as a fresh swing (period reset). */
|
|
34
|
+
export declare const SWING_EDGE_EPSILON = 0.0001;
|
|
35
|
+
/**
|
|
36
|
+
* Pure swing-timer bar state — no hidden state, no clock, no DOM. The caller
|
|
37
|
+
* threads `prevPeriod`/`prevTimer` back each frame. The period is recovered on
|
|
38
|
+
* the reset edge (when `swingTimer` jumps up = a new swing began) as
|
|
39
|
+
* `max(swingTimer, weapon.speed)`, so the fill is correct even without knowing
|
|
40
|
+
* the weapon's exact cadence. Hidden unless auto-attacking a live, non-object target.
|
|
41
|
+
*/
|
|
42
|
+
export declare function swingTimerState(player: SwingPlayerInput, target: SwingTargetInput | null, prevPeriod: number, prevTimer: number): SwingTimerState;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
/** Threshold above which a timer jump is treated as a fresh swing (period reset). */
|
|
2
|
+
export const SWING_EDGE_EPSILON = 1e-4;
|
|
3
|
+
function clamp01(v) {
|
|
4
|
+
return v < 0 ? 0 : v > 1 ? 1 : v;
|
|
5
|
+
}
|
|
6
|
+
const HIDDEN = {
|
|
7
|
+
visible: false,
|
|
8
|
+
frac: 0,
|
|
9
|
+
ready: false,
|
|
10
|
+
labelKind: "seconds",
|
|
11
|
+
seconds: 0,
|
|
12
|
+
};
|
|
13
|
+
/**
|
|
14
|
+
* Pure swing-timer bar state — no hidden state, no clock, no DOM. The caller
|
|
15
|
+
* threads `prevPeriod`/`prevTimer` back each frame. The period is recovered on
|
|
16
|
+
* the reset edge (when `swingTimer` jumps up = a new swing began) as
|
|
17
|
+
* `max(swingTimer, weapon.speed)`, so the fill is correct even without knowing
|
|
18
|
+
* the weapon's exact cadence. Hidden unless auto-attacking a live, non-object target.
|
|
19
|
+
*/
|
|
20
|
+
export function swingTimerState(player, target, prevPeriod, prevTimer) {
|
|
21
|
+
const swingTimer = player.swingTimer;
|
|
22
|
+
if (!player.autoAttack || target === null || target.dead || target.kind === "object") {
|
|
23
|
+
return { ...HIDDEN, nextPeriod: prevPeriod, nextTimer: swingTimer };
|
|
24
|
+
}
|
|
25
|
+
const isResetEdge = swingTimer > prevTimer + SWING_EDGE_EPSILON || prevPeriod <= 0;
|
|
26
|
+
const period = isResetEdge ? Math.max(swingTimer, player.weapon.speed) : prevPeriod;
|
|
27
|
+
const frac = period <= 0 ? 1 : clamp01(1 - swingTimer / period);
|
|
28
|
+
const ready = swingTimer <= 0;
|
|
29
|
+
return {
|
|
30
|
+
visible: true,
|
|
31
|
+
frac,
|
|
32
|
+
ready,
|
|
33
|
+
labelKind: ready ? "ready" : "seconds",
|
|
34
|
+
seconds: Math.max(0, swingTimer),
|
|
35
|
+
nextPeriod: period,
|
|
36
|
+
nextTimer: swingTimer,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
@@ -33,10 +33,10 @@ export function createSpatialIndex(options = {}) {
|
|
|
33
33
|
const key = packCell(x, y, z);
|
|
34
34
|
let bucket = cells.get(key);
|
|
35
35
|
if (bucket === undefined) {
|
|
36
|
-
bucket = new Set();
|
|
36
|
+
bucket = { x, y, z, ids: new Set() };
|
|
37
37
|
cells.set(key, bucket);
|
|
38
38
|
}
|
|
39
|
-
bucket.add(id);
|
|
39
|
+
bucket.ids.add(id);
|
|
40
40
|
}
|
|
41
41
|
}
|
|
42
42
|
}
|
|
@@ -49,8 +49,8 @@ export function createSpatialIndex(options = {}) {
|
|
|
49
49
|
const bucket = cells.get(key);
|
|
50
50
|
if (bucket === undefined)
|
|
51
51
|
continue;
|
|
52
|
-
bucket.delete(id);
|
|
53
|
-
if (bucket.size === 0)
|
|
52
|
+
bucket.ids.delete(id);
|
|
53
|
+
if (bucket.ids.size === 0)
|
|
54
54
|
cells.delete(key);
|
|
55
55
|
}
|
|
56
56
|
}
|
|
@@ -69,17 +69,20 @@ export function createSpatialIndex(options = {}) {
|
|
|
69
69
|
addToCells(id, range);
|
|
70
70
|
entries.set(id, { range, dynamic });
|
|
71
71
|
}
|
|
72
|
-
function
|
|
73
|
-
const
|
|
74
|
-
if (bucket === undefined)
|
|
75
|
-
return;
|
|
76
|
-
for (const id of bucket) {
|
|
72
|
+
function collectBucket(bucket, out) {
|
|
73
|
+
for (const id of bucket.ids) {
|
|
77
74
|
if (stamps.get(id) === queryStamp)
|
|
78
75
|
continue;
|
|
79
76
|
stamps.set(id, queryStamp);
|
|
80
77
|
out.push(id);
|
|
81
78
|
}
|
|
82
79
|
}
|
|
80
|
+
function collectCell(key, out) {
|
|
81
|
+
const bucket = cells.get(key);
|
|
82
|
+
if (bucket === undefined)
|
|
83
|
+
return;
|
|
84
|
+
collectBucket(bucket, out);
|
|
85
|
+
}
|
|
83
86
|
function collectOversized(out) {
|
|
84
87
|
for (const id of oversized) {
|
|
85
88
|
if (stamps.get(id) === queryStamp)
|
|
@@ -93,12 +96,25 @@ export function createSpatialIndex(options = {}) {
|
|
|
93
96
|
out.length = 0;
|
|
94
97
|
const x0 = cellOf(minX, size), y0 = cellOf(minY, size), z0 = cellOf(minZ, size);
|
|
95
98
|
const x1 = cellOf(maxX, size), y1 = cellOf(maxY, size), z1 = cellOf(maxZ, size);
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
99
|
+
const rangeCellCount = (x1 - x0 + 1) * (y1 - y0 + 1) * (z1 - z0 + 1);
|
|
100
|
+
if (rangeCellCount > cells.size) {
|
|
101
|
+
for (const bucket of cells.values()) {
|
|
102
|
+
if (bucket.x < x0 || bucket.x > x1 || bucket.y < y0 || bucket.y > y1 || bucket.z < z0 || bucket.z > z1) {
|
|
103
|
+
continue;
|
|
104
|
+
}
|
|
105
|
+
if (cellFilter !== undefined && !cellFilter(bucket.x, bucket.y, bucket.z))
|
|
106
|
+
continue;
|
|
107
|
+
collectBucket(bucket, out);
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
for (let x = x0; x <= x1; x += 1) {
|
|
112
|
+
for (let y = y0; y <= y1; y += 1) {
|
|
113
|
+
for (let z = z0; z <= z1; z += 1) {
|
|
114
|
+
if (cellFilter !== undefined && !cellFilter(x, y, z))
|
|
115
|
+
continue;
|
|
116
|
+
collectCell(packCell(x, y, z), out);
|
|
117
|
+
}
|
|
102
118
|
}
|
|
103
119
|
}
|
|
104
120
|
}
|
|
@@ -172,13 +188,11 @@ export function createSpatialIndex(options = {}) {
|
|
|
172
188
|
cells() {
|
|
173
189
|
const result = [];
|
|
174
190
|
for (const [key, bucket] of cells) {
|
|
175
|
-
const parts = key.split(":");
|
|
176
|
-
const x = Number(parts[0]), y = Number(parts[1]), z = Number(parts[2]);
|
|
177
191
|
result.push({
|
|
178
192
|
key,
|
|
179
|
-
count: bucket.size,
|
|
180
|
-
minX: x * size, minY: y * size, minZ: z * size,
|
|
181
|
-
maxX: (x + 1) * size, maxY: (y + 1) * size, maxZ: (z + 1) * size,
|
|
193
|
+
count: bucket.ids.size,
|
|
194
|
+
minX: bucket.x * size, minY: bucket.y * size, minZ: bucket.z * size,
|
|
195
|
+
maxX: (bucket.x + 1) * size, maxY: (bucket.y + 1) * size, maxZ: (bucket.z + 1) * size,
|
|
182
196
|
});
|
|
183
197
|
}
|
|
184
198
|
return result;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { type BuildingPalette, type BuildingStyle, type GeneratedBuilding } from "./buildings.js";
|
|
2
|
-
import type { BuildingEnvironmentDescriptor, EnvironmentWorldFeature, WorldBounds } from "./features.js";
|
|
2
|
+
import type { BuildingEnvironmentDescriptor, EnvironmentArea, EnvironmentWorldFeature, WorldBounds } from "./features.js";
|
|
3
3
|
import type { Aabb } from "./geometry.js";
|
|
4
4
|
import { type TerrainPalette } from "./terrain.js";
|
|
5
5
|
export declare function resolveStructureBuildings(descriptor: BuildingEnvironmentDescriptor): GeneratedBuilding[];
|
|
@@ -24,7 +24,7 @@ export interface StructureSummary {
|
|
|
24
24
|
bounds: Aabb;
|
|
25
25
|
}
|
|
26
26
|
export interface VegetationSummary {
|
|
27
|
-
area:
|
|
27
|
+
area: EnvironmentArea;
|
|
28
28
|
density: number;
|
|
29
29
|
}
|
|
30
30
|
export interface WaterSummary {
|
|
@@ -33,8 +33,15 @@ export interface WaterSummary {
|
|
|
33
33
|
}
|
|
34
34
|
export interface WeatherSummary {
|
|
35
35
|
kind: "rain" | "snow";
|
|
36
|
+
area: EnvironmentArea;
|
|
36
37
|
density: number;
|
|
37
38
|
}
|
|
39
|
+
/** One road descriptor's resolved footprint: vertex count, width, and total centerline length. */
|
|
40
|
+
export interface RoadSummary {
|
|
41
|
+
points: number;
|
|
42
|
+
width: number;
|
|
43
|
+
length: number;
|
|
44
|
+
}
|
|
38
45
|
export interface EnvironmentCounts {
|
|
39
46
|
terrain: number;
|
|
40
47
|
islands: number;
|
|
@@ -44,6 +51,7 @@ export interface EnvironmentCounts {
|
|
|
44
51
|
vegetationFields: number;
|
|
45
52
|
waterBodies: number;
|
|
46
53
|
weatherSystems: number;
|
|
54
|
+
roads: number;
|
|
47
55
|
}
|
|
48
56
|
export interface IslandSummary extends TerrainSummary {
|
|
49
57
|
origin: readonly [number, number];
|
|
@@ -55,6 +63,7 @@ export interface EnvironmentSummary {
|
|
|
55
63
|
vegetation: readonly VegetationSummary[];
|
|
56
64
|
water: readonly WaterSummary[];
|
|
57
65
|
weather: readonly WeatherSummary[];
|
|
66
|
+
roads: readonly RoadSummary[];
|
|
58
67
|
counts: EnvironmentCounts;
|
|
59
68
|
isEmpty: boolean;
|
|
60
69
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { generateBuildingDistrict, resolveBuildingPalette, } from "./buildings.js";
|
|
2
|
+
import { pathLength } from "./roads.js";
|
|
2
3
|
import { resolveTerrainField, resolveTerrainPalette } from "./terrain.js";
|
|
3
4
|
export function resolveStructureBuildings(descriptor) {
|
|
4
5
|
const columns = Math.max(1, Math.ceil(Math.sqrt(descriptor.count)));
|
|
@@ -106,8 +107,14 @@ export function summarizeEnvironment(feature) {
|
|
|
106
107
|
}));
|
|
107
108
|
const weather = (feature.weather ?? []).map((entry) => ({
|
|
108
109
|
kind: entry.kind,
|
|
110
|
+
area: entry.area,
|
|
109
111
|
density: entry.density,
|
|
110
112
|
}));
|
|
113
|
+
const roads = (feature.roads ?? []).map((entry) => ({
|
|
114
|
+
points: entry.path.length,
|
|
115
|
+
width: entry.width,
|
|
116
|
+
length: pathLength(entry.path),
|
|
117
|
+
}));
|
|
111
118
|
const buildings = structures.reduce((sum, entry) => sum + entry.buildings, 0);
|
|
112
119
|
const counts = {
|
|
113
120
|
terrain: terrain === undefined ? 0 : 1,
|
|
@@ -118,13 +125,15 @@ export function summarizeEnvironment(feature) {
|
|
|
118
125
|
vegetationFields: vegetation.length,
|
|
119
126
|
waterBodies: water.length,
|
|
120
127
|
weatherSystems: weather.length,
|
|
128
|
+
roads: roads.length,
|
|
121
129
|
};
|
|
122
130
|
const isEmpty = counts.terrain === 0 &&
|
|
123
131
|
counts.islands === 0 &&
|
|
124
132
|
buildings === 0 &&
|
|
125
133
|
counts.vegetationFields === 0 &&
|
|
126
134
|
counts.waterBodies === 0 &&
|
|
127
|
-
counts.weatherSystems === 0
|
|
135
|
+
counts.weatherSystems === 0 &&
|
|
136
|
+
counts.roads === 0;
|
|
128
137
|
return {
|
|
129
138
|
...(terrain === undefined ? {} : { terrain }),
|
|
130
139
|
islands,
|
|
@@ -132,6 +141,7 @@ export function summarizeEnvironment(feature) {
|
|
|
132
141
|
vegetation,
|
|
133
142
|
water,
|
|
134
143
|
weather,
|
|
144
|
+
roads,
|
|
135
145
|
counts,
|
|
136
146
|
isEmpty,
|
|
137
147
|
};
|
package/dist/world/features.d.ts
CHANGED
|
@@ -15,6 +15,34 @@ export interface TerrainColors {
|
|
|
15
15
|
high?: string;
|
|
16
16
|
waterline?: string;
|
|
17
17
|
}
|
|
18
|
+
/**
|
|
19
|
+
* Procedural detail-surface layer for terrain: a noise-driven shader that keeps the
|
|
20
|
+
* biome-tinted base ground (from `colors`/`biomeBands`) and blends distinct rock,
|
|
21
|
+
* sand, and snow over it by slope, height, and waterline — turning a flat
|
|
22
|
+
* vertex-colour surface into varied, textured-reading ground with no image assets.
|
|
23
|
+
*/
|
|
24
|
+
export interface TerrainDetailConfig {
|
|
25
|
+
/** Steep-slope rock colour. Default "#6f7175". */
|
|
26
|
+
rockColor?: string;
|
|
27
|
+
/** Waterline/beach sand colour. Default "#c2b283". */
|
|
28
|
+
sandColor?: string;
|
|
29
|
+
/** High-altitude snow colour. Default "#eef3f7". */
|
|
30
|
+
snowColor?: string;
|
|
31
|
+
/** Slope (0 flat … 1 vertical) at which rock starts taking over. Default 0.42. */
|
|
32
|
+
rockSlopeStart?: number;
|
|
33
|
+
/** World height above which snow appears. Default 26. */
|
|
34
|
+
snowHeight?: number;
|
|
35
|
+
/** Water level for the sand band; defaults to the terrain's `waterLevel`. */
|
|
36
|
+
waterLevel?: number;
|
|
37
|
+
/** Fine detail noise wavelength in world units. Default 5. */
|
|
38
|
+
detailScale?: number;
|
|
39
|
+
/** Large-scale colour-variation wavelength in world units. Default 45. */
|
|
40
|
+
macroScale?: number;
|
|
41
|
+
/** Surface roughness of the detail material. Default 0.9. */
|
|
42
|
+
roughness?: number;
|
|
43
|
+
/** Overall detail intensity 0..1. Default 1. */
|
|
44
|
+
strength?: number;
|
|
45
|
+
}
|
|
18
46
|
export interface TerrainFlattenMask {
|
|
19
47
|
center: EnvironmentVec2;
|
|
20
48
|
radius: number;
|
|
@@ -23,16 +51,59 @@ export interface TerrainFlattenMask {
|
|
|
23
51
|
/** Extra blend-ring width outside `radius` where the flat height smoothsteps back to the noise value; default `radius * 0.5`. */
|
|
24
52
|
falloff?: number;
|
|
25
53
|
}
|
|
26
|
-
/**
|
|
27
|
-
export interface
|
|
28
|
-
center: EnvironmentVec2;
|
|
29
|
-
radius: number;
|
|
54
|
+
/** Palette and blend fields shared by every `TerrainMaterialRegion` shape. */
|
|
55
|
+
export interface TerrainRegionStyle {
|
|
30
56
|
/** Named palette preset for this region (see `TERRAIN_MATERIAL_PALETTES` in `world/terrain`); overridden field-by-field by `colors`. */
|
|
31
57
|
material?: TerrainMaterial;
|
|
32
58
|
colors?: TerrainColors;
|
|
33
|
-
/** Blend-ring width outside
|
|
59
|
+
/** Blend-ring width outside the region core back to the surrounding palette; default: half the region's characteristic size. */
|
|
34
60
|
falloff?: number;
|
|
35
61
|
}
|
|
62
|
+
/** A circular palette zone painted over the base terrain palette — snow caps, ash wastes, spawn circles. */
|
|
63
|
+
export interface TerrainCircleRegion extends TerrainRegionStyle {
|
|
64
|
+
shape?: "circle";
|
|
65
|
+
center: EnvironmentVec2;
|
|
66
|
+
radius: number;
|
|
67
|
+
}
|
|
68
|
+
/** A ribbon palette zone following a centerline — roads and rivers, instead of chaining overlapping circles. */
|
|
69
|
+
export interface TerrainPolylineRegion extends TerrainRegionStyle {
|
|
70
|
+
shape: "polyline";
|
|
71
|
+
/** Centerline vertices in world XZ; needs at least two points. */
|
|
72
|
+
points: readonly EnvironmentVec2[];
|
|
73
|
+
/** Full ribbon width; the painted core extends `width / 2` either side of the centerline. `falloff` defaults to `width * 0.5`. */
|
|
74
|
+
width: number;
|
|
75
|
+
}
|
|
76
|
+
/** A rectangular palette zone, optionally rotated about the world y axis — plazas, fields, districts. */
|
|
77
|
+
export interface TerrainRectRegion extends TerrainRegionStyle {
|
|
78
|
+
shape: "rect";
|
|
79
|
+
center: EnvironmentVec2;
|
|
80
|
+
/** Half-extents along the region's local x and z before rotation. `falloff` defaults to half the smaller extent. */
|
|
81
|
+
halfExtents: EnvironmentVec2;
|
|
82
|
+
/** Rotation about the world y axis in radians; default 0. */
|
|
83
|
+
rotationY?: number;
|
|
84
|
+
}
|
|
85
|
+
/**
|
|
86
|
+
* A palette zone painted over the base terrain palette. Circle (the default when no `shape` is given),
|
|
87
|
+
* `polyline` ribbons for roads/rivers, and rotatable `rect` districts all paint fully inside their core
|
|
88
|
+
* and blend back across `falloff`; later regions in the list win overlaps.
|
|
89
|
+
*/
|
|
90
|
+
export type TerrainMaterialRegion = TerrainCircleRegion | TerrainPolylineRegion | TerrainRectRegion;
|
|
91
|
+
/**
|
|
92
|
+
* A z-ordered ground palette zone — the linear-boundary counterpart to the radial `materialRegions`.
|
|
93
|
+
* Adjacent bands cross-fade into each other across a `fade`-wide window centered on the midpoint z
|
|
94
|
+
* between their centers, so a multi-biome world (vale → marsh → peaks along z) blends its ground
|
|
95
|
+
* color instead of hard-switching. Order the list by ascending `z`.
|
|
96
|
+
*/
|
|
97
|
+
export interface BiomeBand {
|
|
98
|
+
/** World-space center z of the zone. */
|
|
99
|
+
z: number;
|
|
100
|
+
/** Cross-fade window width straddling the boundary to the next band. Default 64. */
|
|
101
|
+
fade?: number;
|
|
102
|
+
/** Named palette preset for the zone (see `TERRAIN_MATERIAL_PALETTES`); overridden field-by-field by `colors`. */
|
|
103
|
+
material?: TerrainMaterial;
|
|
104
|
+
/** Explicit low/high/waterline hex colors for the zone. */
|
|
105
|
+
colors?: TerrainColors;
|
|
106
|
+
}
|
|
36
107
|
export interface TerrainEnvironmentConfig {
|
|
37
108
|
bounds?: WorldBounds;
|
|
38
109
|
height?: number;
|
|
@@ -49,6 +120,8 @@ export interface TerrainEnvironmentConfig {
|
|
|
49
120
|
colors?: TerrainColors;
|
|
50
121
|
/** Palette zones blended over the base `material`/`colors` for multi-biome readability. */
|
|
51
122
|
materialRegions?: readonly TerrainMaterialRegion[];
|
|
123
|
+
/** Ordered z-banded ground palettes that cross-fade along the world's z axis (vale → marsh → peaks). Painted under `materialRegions`. */
|
|
124
|
+
biomeBands?: readonly BiomeBand[];
|
|
52
125
|
segments?: number;
|
|
53
126
|
seed?: string;
|
|
54
127
|
frequency?: number;
|
|
@@ -58,9 +131,13 @@ export interface TerrainEnvironmentConfig {
|
|
|
58
131
|
waterLevel?: number;
|
|
59
132
|
/** Flat pads carved into the noise field, e.g. for building pads or spawn circles. */
|
|
60
133
|
flatten?: readonly TerrainFlattenMask[];
|
|
134
|
+
/** Procedural detail surface (noise-driven rock/sand/snow over the biome base). Omit for the flat vertex-colour ground. */
|
|
135
|
+
detail?: TerrainDetailConfig;
|
|
61
136
|
}
|
|
62
137
|
export interface RainEnvironmentConfig {
|
|
63
138
|
area?: EnvironmentArea;
|
|
139
|
+
/** World-space center `[x, z]` of the band, matching `building()`/`ocean()` — sugar for `area.position`, letting a biome zone site its own weather away from the origin. */
|
|
140
|
+
position?: EnvironmentVec2;
|
|
64
141
|
density?: number;
|
|
65
142
|
speed?: number;
|
|
66
143
|
dropLength?: number;
|
|
@@ -69,6 +146,8 @@ export interface RainEnvironmentConfig {
|
|
|
69
146
|
}
|
|
70
147
|
export interface SnowEnvironmentConfig {
|
|
71
148
|
area?: EnvironmentArea;
|
|
149
|
+
/** World-space center `[x, z]` of the band, matching `building()`/`ocean()` — sugar for `area.position`, letting a biome zone site its own weather away from the origin. */
|
|
150
|
+
position?: EnvironmentVec2;
|
|
72
151
|
density?: number;
|
|
73
152
|
speed?: number;
|
|
74
153
|
flakeSize?: number;
|
|
@@ -78,6 +157,8 @@ export interface SnowEnvironmentConfig {
|
|
|
78
157
|
}
|
|
79
158
|
export interface GrassEnvironmentConfig {
|
|
80
159
|
area?: EnvironmentArea;
|
|
160
|
+
/** World-space center `[x, z]` of the band, matching `building()`/`ocean()` — sugar for `area.position`, letting a biome zone site its own vegetation away from the origin. */
|
|
161
|
+
position?: EnvironmentVec2;
|
|
81
162
|
density?: number;
|
|
82
163
|
bladeHeight?: readonly [number, number];
|
|
83
164
|
bladeWidth?: number;
|
|
@@ -177,6 +258,36 @@ export type PadEnvironmentDescriptor = {
|
|
|
177
258
|
export type SkyEnvironmentDescriptor = {
|
|
178
259
|
kind: "sky";
|
|
179
260
|
} & Required<Pick<SkyEnvironmentConfig, "preset" | "timeOfDay">> & Omit<SkyEnvironmentConfig, "preset" | "timeOfDay">;
|
|
261
|
+
/** Config for {@link road}: a flat asphalt ribbon draped over the terrain along a centerline. */
|
|
262
|
+
export interface RoadEnvironmentConfig {
|
|
263
|
+
/** Centerline vertices in world XZ; at least two points. */
|
|
264
|
+
path: readonly (readonly [number, number])[];
|
|
265
|
+
/** Ribbon width in world units. Default 8. */
|
|
266
|
+
width?: number;
|
|
267
|
+
/** Asphalt color. Default "#3b3e47". */
|
|
268
|
+
color?: string;
|
|
269
|
+
/** Paint a dashed centerline. Default true. */
|
|
270
|
+
markings?: boolean;
|
|
271
|
+
/** Centerline dash color. Default "#e8c74a". */
|
|
272
|
+
markingColor?: string;
|
|
273
|
+
/** Lift above the terrain to avoid z-fighting; stagger overlapping roads. Default 0.08. */
|
|
274
|
+
elevation?: number;
|
|
275
|
+
/** Sidewalk bands on both edges; `false` for none. Default `{ width: 2.6, color: "#a7adb8" }`. */
|
|
276
|
+
sidewalk?: {
|
|
277
|
+
width?: number;
|
|
278
|
+
color?: string;
|
|
279
|
+
} | false;
|
|
280
|
+
}
|
|
281
|
+
/** Resolved road descriptor produced by {@link road} and rendered by the shell environment scene. */
|
|
282
|
+
export type RoadEnvironmentDescriptor = {
|
|
283
|
+
kind: "road";
|
|
284
|
+
} & Required<Pick<RoadEnvironmentConfig, "path" | "width" | "color" | "markings" | "markingColor" | "elevation">> & {
|
|
285
|
+
/** Resolved sidewalk band, or `false` when the road has none. */
|
|
286
|
+
sidewalk: {
|
|
287
|
+
width: number;
|
|
288
|
+
color: string;
|
|
289
|
+
} | false;
|
|
290
|
+
};
|
|
180
291
|
export type WeatherEnvironmentDescriptor = RainEnvironmentDescriptor | SnowEnvironmentDescriptor;
|
|
181
292
|
export type VegetationEnvironmentDescriptor = GrassEnvironmentDescriptor;
|
|
182
293
|
export type WaterEnvironmentDescriptor = OceanEnvironmentDescriptor;
|
|
@@ -191,6 +302,8 @@ export interface EnvironmentWorldConfig {
|
|
|
191
302
|
vegetation?: EnvironmentDescriptorList<VegetationEnvironmentDescriptor>;
|
|
192
303
|
water?: EnvironmentDescriptorList<WaterEnvironmentDescriptor>;
|
|
193
304
|
structures?: EnvironmentDescriptorList<StructureEnvironmentDescriptor>;
|
|
305
|
+
/** Road ribbons draped over the terrain — see `road()`. */
|
|
306
|
+
roads?: EnvironmentDescriptorList<RoadEnvironmentDescriptor>;
|
|
194
307
|
/** Ground pads, e.g. platforms or paved patches; each implicitly flattens the terrain beneath it. */
|
|
195
308
|
pads?: readonly PadEnvironmentDescriptor[];
|
|
196
309
|
}
|
|
@@ -203,6 +316,7 @@ export interface EnvironmentWorldFeature {
|
|
|
203
316
|
vegetation?: readonly VegetationEnvironmentDescriptor[];
|
|
204
317
|
water?: readonly WaterEnvironmentDescriptor[];
|
|
205
318
|
structures?: readonly StructureEnvironmentDescriptor[];
|
|
319
|
+
roads?: readonly RoadEnvironmentDescriptor[];
|
|
206
320
|
pads?: readonly PadEnvironmentDescriptor[];
|
|
207
321
|
}
|
|
208
322
|
export interface WorldGridCell {
|
|
@@ -242,6 +356,7 @@ export interface PlotsWorldConfig extends WorldGridConfig {
|
|
|
242
356
|
export interface TilemapWorldConfig extends WorldGridConfig {
|
|
243
357
|
map: string;
|
|
244
358
|
}
|
|
359
|
+
/** A declared world shape — biomes, voxel grid, plots, tilemap, environment, or flat — passed to `defineGame`. */
|
|
245
360
|
export type WorldFeature = ({
|
|
246
361
|
kind: "biomes";
|
|
247
362
|
} & BiomesWorldConfig) | ({
|
|
@@ -255,18 +370,32 @@ export type WorldFeature = ({
|
|
|
255
370
|
};
|
|
256
371
|
/** Derives implicit `TerrainFlattenMask`s carving each pad's footprint into the terrain beneath it. Elevated pads (absolute `elevation`) float free and carve nothing. */
|
|
257
372
|
export declare function padFlattenMasks(pads: readonly PadEnvironmentDescriptor[]): readonly TerrainFlattenMask[];
|
|
373
|
+
/** Composes an `environment()` world feature from terrain, sky, weather, vegetation, water, structures, roads, and pads. */
|
|
258
374
|
export declare function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature;
|
|
375
|
+
/** Declares a heightfield terrain patch for `environment()` — bounds, noise, materials, and flatten masks. */
|
|
259
376
|
export declare function terrain(config?: TerrainEnvironmentConfig): TerrainEnvironmentDescriptor;
|
|
260
377
|
export declare function sky(config?: SkyEnvironmentConfig): SkyEnvironmentDescriptor;
|
|
378
|
+
/** Declares a rainfall weather effect for `environment()` — area, density, speed, and wind. */
|
|
261
379
|
export declare function rain(config?: RainEnvironmentConfig): RainEnvironmentDescriptor;
|
|
380
|
+
/** Declares a snowfall weather effect for `environment()` — area, density, drift, and wind. */
|
|
262
381
|
export declare function snow(config?: SnowEnvironmentConfig): SnowEnvironmentDescriptor;
|
|
382
|
+
/** Declares a grass vegetation patch for `environment()` — area, blade sizing, density, and colors. */
|
|
263
383
|
export declare function grass(config?: GrassEnvironmentConfig): GrassEnvironmentDescriptor;
|
|
384
|
+
/** Declares an ocean water body for `environment()` — bounds, level, and wave tuning. */
|
|
264
385
|
export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironmentDescriptor;
|
|
386
|
+
/** Declare a road ribbon for an `environment()` world; the shell drapes and renders it over the terrain. */
|
|
387
|
+
export declare function road(config: RoadEnvironmentConfig): RoadEnvironmentDescriptor;
|
|
265
388
|
export declare function island(config: TerrainIslandConfig): TerrainIslandDescriptor;
|
|
389
|
+
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
|
|
266
390
|
export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
|
|
267
391
|
export declare function pad(config: PadEnvironmentConfig): PadEnvironmentDescriptor;
|
|
392
|
+
/** Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain. */
|
|
268
393
|
export declare function biomes(config: BiomesWorldConfig): WorldFeature;
|
|
394
|
+
/** Declares a voxel-grid world for block-based games. */
|
|
269
395
|
export declare function voxel(config: VoxelWorldConfig): WorldFeature;
|
|
396
|
+
/** Declares a subdivided-plots world — farming, base-building, and other parcel-based layouts. */
|
|
270
397
|
export declare function plots(config?: PlotsWorldConfig): WorldFeature;
|
|
398
|
+
/** Declares a 2D tilemap world from a map string. */
|
|
271
399
|
export declare function tilemap(config: TilemapWorldConfig): WorldFeature;
|
|
400
|
+
/** Declares an empty flat world — the minimal `WorldFeature` for games with no terrain of their own. */
|
|
272
401
|
export declare function flat(): WorldFeature;
|
package/dist/world/features.js
CHANGED
|
@@ -27,11 +27,13 @@ function withPadFlatten(terrainDescriptor, pads) {
|
|
|
27
27
|
flatten: [...(terrainDescriptor.flatten ?? []), ...padFlattenMasks(pads)],
|
|
28
28
|
};
|
|
29
29
|
}
|
|
30
|
+
/** Composes an `environment()` world feature from terrain, sky, weather, vegetation, water, structures, roads, and pads. */
|
|
30
31
|
export function environment(config = {}) {
|
|
31
32
|
const weather = list(config.weather);
|
|
32
33
|
const vegetation = list(config.vegetation);
|
|
33
34
|
const water = list(config.water);
|
|
34
35
|
const structures = list(config.structures);
|
|
36
|
+
const roads = list(config.roads);
|
|
35
37
|
const terrainDescriptor = withPadFlatten(config.terrain, config.pads);
|
|
36
38
|
return {
|
|
37
39
|
kind: "environment",
|
|
@@ -42,9 +44,11 @@ export function environment(config = {}) {
|
|
|
42
44
|
...(vegetation === undefined ? {} : { vegetation }),
|
|
43
45
|
...(water === undefined ? {} : { water }),
|
|
44
46
|
...(structures === undefined ? {} : { structures }),
|
|
47
|
+
...(roads === undefined ? {} : { roads }),
|
|
45
48
|
...(config.pads === undefined ? {} : { pads: config.pads }),
|
|
46
49
|
};
|
|
47
50
|
}
|
|
51
|
+
/** Declares a heightfield terrain patch for `environment()` — bounds, noise, materials, and flatten masks. */
|
|
48
52
|
export function terrain(config = {}) {
|
|
49
53
|
if (config.baseHeight !== undefined &&
|
|
50
54
|
config.height === undefined &&
|
|
@@ -62,6 +66,7 @@ export function terrain(config = {}) {
|
|
|
62
66
|
...(config.material === undefined ? {} : { material: config.material }),
|
|
63
67
|
...(config.colors === undefined ? {} : { colors: config.colors }),
|
|
64
68
|
...(config.materialRegions === undefined ? {} : { materialRegions: config.materialRegions }),
|
|
69
|
+
...(config.biomeBands === undefined ? {} : { biomeBands: config.biomeBands }),
|
|
65
70
|
...(config.segments === undefined ? {} : { segments: config.segments }),
|
|
66
71
|
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
67
72
|
...(config.frequency === undefined ? {} : { frequency: config.frequency }),
|
|
@@ -70,6 +75,7 @@ export function terrain(config = {}) {
|
|
|
70
75
|
...(config.baseHeight === undefined ? {} : { baseHeight: config.baseHeight }),
|
|
71
76
|
...(config.waterLevel === undefined ? {} : { waterLevel: config.waterLevel }),
|
|
72
77
|
...(config.flatten === undefined ? {} : { flatten: config.flatten }),
|
|
78
|
+
...(config.detail === undefined ? {} : { detail: config.detail }),
|
|
73
79
|
});
|
|
74
80
|
}
|
|
75
81
|
export function sky(config = {}) {
|
|
@@ -85,10 +91,14 @@ export function sky(config = {}) {
|
|
|
85
91
|
...(config.fog === undefined ? {} : { fog: config.fog }),
|
|
86
92
|
});
|
|
87
93
|
}
|
|
94
|
+
function withAreaPosition(area, position) {
|
|
95
|
+
return position !== undefined && area.position === undefined ? { ...area, position } : area;
|
|
96
|
+
}
|
|
97
|
+
/** Declares a rainfall weather effect for `environment()` — area, density, speed, and wind. */
|
|
88
98
|
export function rain(config = {}) {
|
|
89
99
|
return {
|
|
90
100
|
kind: "rain",
|
|
91
|
-
area: config.area ?? { w: 256, d: 256, h: 80 },
|
|
101
|
+
area: withAreaPosition(config.area ?? { w: 256, d: 256, h: 80 }, config.position),
|
|
92
102
|
density: config.density ?? 0.65,
|
|
93
103
|
speed: config.speed ?? 18,
|
|
94
104
|
dropLength: config.dropLength ?? 0.8,
|
|
@@ -96,10 +106,11 @@ export function rain(config = {}) {
|
|
|
96
106
|
color: config.color ?? "#9ec8ff",
|
|
97
107
|
};
|
|
98
108
|
}
|
|
109
|
+
/** Declares a snowfall weather effect for `environment()` — area, density, drift, and wind. */
|
|
99
110
|
export function snow(config = {}) {
|
|
100
111
|
return {
|
|
101
112
|
kind: "snow",
|
|
102
|
-
area: config.area ?? { w: 256, d: 256, h: 80 },
|
|
113
|
+
area: withAreaPosition(config.area ?? { w: 256, d: 256, h: 80 }, config.position),
|
|
103
114
|
density: config.density ?? 0.35,
|
|
104
115
|
speed: config.speed ?? 2.4,
|
|
105
116
|
flakeSize: config.flakeSize ?? 0.08,
|
|
@@ -108,10 +119,11 @@ export function snow(config = {}) {
|
|
|
108
119
|
color: config.color ?? "#ffffff",
|
|
109
120
|
};
|
|
110
121
|
}
|
|
122
|
+
/** Declares a grass vegetation patch for `environment()` — area, blade sizing, density, and colors. */
|
|
111
123
|
export function grass(config = {}) {
|
|
112
124
|
return withOptional({
|
|
113
125
|
kind: "grass",
|
|
114
|
-
area: config.area ?? { w: 128, d: 128 },
|
|
126
|
+
area: withAreaPosition(config.area ?? { w: 128, d: 128 }, config.position),
|
|
115
127
|
density: config.density ?? 4,
|
|
116
128
|
bladeHeight: config.bladeHeight ?? [0.25, 0.9],
|
|
117
129
|
bladeWidth: config.bladeWidth ?? 0.035,
|
|
@@ -119,6 +131,7 @@ export function grass(config = {}) {
|
|
|
119
131
|
colors: config.colors ?? ["#3f7d2d", "#66a83f"],
|
|
120
132
|
}, config.seed === undefined ? undefined : { seed: config.seed });
|
|
121
133
|
}
|
|
134
|
+
/** Declares an ocean water body for `environment()` — bounds, level, and wave tuning. */
|
|
122
135
|
export function ocean(config = {}) {
|
|
123
136
|
return {
|
|
124
137
|
kind: "ocean",
|
|
@@ -132,11 +145,30 @@ export function ocean(config = {}) {
|
|
|
132
145
|
...(config.levelAt === undefined ? {} : { levelAt: config.levelAt }),
|
|
133
146
|
};
|
|
134
147
|
}
|
|
148
|
+
/** Declare a road ribbon for an `environment()` world; the shell drapes and renders it over the terrain. */
|
|
149
|
+
export function road(config) {
|
|
150
|
+
if (config.path.length < 2) {
|
|
151
|
+
throw new Error("road: path needs at least two points");
|
|
152
|
+
}
|
|
153
|
+
return {
|
|
154
|
+
kind: "road",
|
|
155
|
+
path: config.path,
|
|
156
|
+
width: config.width ?? 8,
|
|
157
|
+
color: config.color ?? "#3b3e47",
|
|
158
|
+
markings: config.markings ?? true,
|
|
159
|
+
markingColor: config.markingColor ?? "#e8c74a",
|
|
160
|
+
elevation: config.elevation ?? 0.08,
|
|
161
|
+
sidewalk: config.sidewalk === false
|
|
162
|
+
? false
|
|
163
|
+
: { width: config.sidewalk?.width ?? 2.6, color: config.sidewalk?.color ?? "#a7adb8" },
|
|
164
|
+
};
|
|
165
|
+
}
|
|
135
166
|
export function island(config) {
|
|
136
167
|
const { origin, ...terrainConfig } = config;
|
|
137
168
|
const { kind: _kind, ...descriptor } = terrain(terrainConfig);
|
|
138
169
|
return { ...descriptor, kind: "island", origin };
|
|
139
170
|
}
|
|
171
|
+
/** Declares a cluster of procedurally-massed buildings for `environment()` — count, footprint, stories, style. */
|
|
140
172
|
export function building(config = {}) {
|
|
141
173
|
return withOptional({
|
|
142
174
|
kind: "building",
|
|
@@ -164,18 +196,23 @@ export function pad(config) {
|
|
|
164
196
|
...(config.elevation === undefined ? {} : { elevation: config.elevation }),
|
|
165
197
|
});
|
|
166
198
|
}
|
|
199
|
+
/** Declares a biome-painted world — the whole-world alternative to a single `environment()` terrain. */
|
|
167
200
|
export function biomes(config) {
|
|
168
201
|
return { kind: "biomes", ...config };
|
|
169
202
|
}
|
|
203
|
+
/** Declares a voxel-grid world for block-based games. */
|
|
170
204
|
export function voxel(config) {
|
|
171
205
|
return { kind: "voxel", ...config };
|
|
172
206
|
}
|
|
207
|
+
/** Declares a subdivided-plots world — farming, base-building, and other parcel-based layouts. */
|
|
173
208
|
export function plots(config = {}) {
|
|
174
209
|
return { kind: "plots", ...config };
|
|
175
210
|
}
|
|
211
|
+
/** Declares a 2D tilemap world from a map string. */
|
|
176
212
|
export function tilemap(config) {
|
|
177
213
|
return { kind: "tilemap", ...config };
|
|
178
214
|
}
|
|
215
|
+
/** Declares an empty flat world — the minimal `WorldFeature` for games with no terrain of their own. */
|
|
179
216
|
export function flat() {
|
|
180
217
|
return { kind: "flat" };
|
|
181
218
|
}
|