@jgengine/core 0.8.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 +16 -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.js +1 -1
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- 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 +17 -2
- package/dist/combat/abilityKit.js +62 -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/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +201 -129
- 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 +25 -0
- package/dist/combat/telegraph.js +47 -0
- package/dist/crafting/recipe.js +4 -1
- package/dist/devtools/devtools.d.ts +72 -24
- package/dist/devtools/devtools.js +449 -101
- package/dist/devtools/transformTunables.js +3 -3
- 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 +2 -0
- package/dist/game/chat.js +9 -1
- 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 +2 -0
- package/dist/game/events.d.ts +10 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +27 -7
- package/dist/game/playableGame.d.ts +50 -1
- package/dist/game/playableGame.js +1 -1
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +39 -1
- package/dist/game/race.js +96 -11
- package/dist/game/recordBook.d.ts +29 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -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/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +6 -0
- package/dist/input/touchScheme.js +45 -7
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +23 -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/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/presenceModel.d.ts +14 -2
- package/dist/multiplayer/presenceModel.js +15 -3
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navGrid.js +30 -4
- 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/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 +38 -0
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +13 -6
- package/dist/physics/physicsWorld.js +169 -40
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/vehicleBody.js +2 -1
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/runtime/cameraDirector.d.ts +6 -1
- package/dist/runtime/cameraDirector.js +6 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +21 -0
- package/dist/runtime/gameContext.js +184 -9
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +9 -1
- package/dist/runtime/hostPersistence.js +23 -8
- package/dist/runtime/inputSnapshot.d.ts +5 -0
- package/dist/runtime/inputSnapshot.js +5 -0
- package/dist/runtime/motionIntents.d.ts +14 -6
- package/dist/runtime/motionIntents.js +20 -2
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- 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 +22 -5
- package/dist/scene/entityStore.js +59 -12
- package/dist/scene/objectQuery.d.ts +13 -2
- package/dist/scene/objectQuery.js +34 -2
- package/dist/scene/objectStore.d.ts +2 -0
- package/dist/scene/objectStore.js +31 -12
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +11 -16
- package/dist/scene/spatial.js +99 -38
- 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/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/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/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/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 +10 -2
- package/dist/world/environmentSummary.js +11 -2
- package/dist/world/features.d.ts +56 -8
- package/dist/world/features.js +24 -3
- package/dist/world/gridInstances.d.ts +7 -8
- package/dist/world/gridInstances.js +145 -7
- 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/terrain.d.ts +42 -3
- package/dist/world/terrain.js +197 -16
- 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 +2585 -1704
- package/package.json +12 -2
package/dist/world/features.js
CHANGED
|
@@ -7,9 +7,11 @@ 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. */
|
|
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
11
|
export function padFlattenMasks(pads) {
|
|
12
|
-
return pads
|
|
12
|
+
return pads
|
|
13
|
+
.filter((padDescriptor) => padDescriptor.elevation === undefined)
|
|
14
|
+
.map((padDescriptor) => ({
|
|
13
15
|
center: padDescriptor.center,
|
|
14
16
|
radius: padHalfExtent(padDescriptor.size) * 1.2,
|
|
15
17
|
}));
|
|
@@ -34,6 +36,7 @@ export function environment(config = {}) {
|
|
|
34
36
|
return {
|
|
35
37
|
kind: "environment",
|
|
36
38
|
...(terrainDescriptor === undefined ? {} : { terrain: terrainDescriptor }),
|
|
39
|
+
...(config.islands === undefined || config.islands.length === 0 ? {} : { islands: config.islands }),
|
|
37
40
|
...(config.sky === undefined ? {} : { sky: config.sky }),
|
|
38
41
|
...(weather === undefined ? {} : { weather }),
|
|
39
42
|
...(vegetation === undefined ? {} : { vegetation }),
|
|
@@ -43,14 +46,22 @@ export function environment(config = {}) {
|
|
|
43
46
|
};
|
|
44
47
|
}
|
|
45
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
|
+
}
|
|
46
55
|
return withOptional({
|
|
47
56
|
kind: "terrain",
|
|
48
57
|
bounds: config.bounds ?? { w: 512, d: 512 },
|
|
49
58
|
height: config.height ?? 0,
|
|
50
59
|
}, {
|
|
51
60
|
...(config.heightMap === undefined ? {} : { heightMap: config.heightMap }),
|
|
61
|
+
...(config.heightField === undefined ? {} : { heightField: config.heightField }),
|
|
52
62
|
...(config.material === undefined ? {} : { material: config.material }),
|
|
53
63
|
...(config.colors === undefined ? {} : { colors: config.colors }),
|
|
64
|
+
...(config.materialRegions === undefined ? {} : { materialRegions: config.materialRegions }),
|
|
54
65
|
...(config.segments === undefined ? {} : { segments: config.segments }),
|
|
55
66
|
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
56
67
|
...(config.frequency === undefined ? {} : { frequency: config.frequency }),
|
|
@@ -118,8 +129,14 @@ export function ocean(config = {}) {
|
|
|
118
129
|
waveSpeed: config.waveSpeed ?? 0.55,
|
|
119
130
|
color: config.color ?? "#1d7fa3",
|
|
120
131
|
...(config.position === undefined ? {} : { position: config.position }),
|
|
132
|
+
...(config.levelAt === undefined ? {} : { levelAt: config.levelAt }),
|
|
121
133
|
};
|
|
122
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
|
+
}
|
|
123
140
|
export function building(config = {}) {
|
|
124
141
|
return withOptional({
|
|
125
142
|
kind: "building",
|
|
@@ -132,6 +149,7 @@ export function building(config = {}) {
|
|
|
132
149
|
}, {
|
|
133
150
|
...(config.seed === undefined ? {} : { seed: config.seed }),
|
|
134
151
|
...(config.position === undefined ? {} : { position: config.position }),
|
|
152
|
+
...(config.palette === undefined ? {} : { palette: config.palette }),
|
|
135
153
|
});
|
|
136
154
|
}
|
|
137
155
|
export function pad(config) {
|
|
@@ -141,7 +159,10 @@ export function pad(config) {
|
|
|
141
159
|
size: config.size,
|
|
142
160
|
height: config.height ?? 0.05,
|
|
143
161
|
color: config.color ?? "#8b8680",
|
|
144
|
-
},
|
|
162
|
+
}, {
|
|
163
|
+
...(config.rotationY === undefined ? {} : { rotationY: config.rotationY }),
|
|
164
|
+
...(config.elevation === undefined ? {} : { elevation: config.elevation }),
|
|
165
|
+
});
|
|
145
166
|
}
|
|
146
167
|
export function biomes(config) {
|
|
147
168
|
return { kind: "biomes", ...config };
|
|
@@ -1,13 +1,12 @@
|
|
|
1
|
-
import type { WorldGridConfig } from "./features.js";
|
|
1
|
+
import type { WorldFeature, WorldGridCell, WorldGridConfig } from "./features.js";
|
|
2
2
|
export interface GridInstanceTransform {
|
|
3
3
|
position: readonly [number, number, number];
|
|
4
4
|
scale: readonly [number, number, number];
|
|
5
5
|
color: string;
|
|
6
6
|
}
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
export declare function resolveGridInstances(config: WorldGridConfig): readonly GridInstanceTransform[];
|
|
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 {};
|
|
@@ -1,14 +1,152 @@
|
|
|
1
|
+
import { seedFrom, valueNoise } from "./terrain.js";
|
|
1
2
|
const DEFAULT_CELL_SIZE = 1;
|
|
2
3
|
const DEFAULT_BASE_HEIGHT = 1;
|
|
3
4
|
const DEFAULT_GRID_COLOR = "#8a8f98";
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
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
|
+
}
|
|
10
148
|
export function resolveGridInstances(config) {
|
|
11
|
-
const cells = config
|
|
149
|
+
const cells = resolveGridCells(config);
|
|
12
150
|
const cellSize = config.cellSize ?? DEFAULT_CELL_SIZE;
|
|
13
151
|
const defaultColor = config.defaultColor ?? DEFAULT_GRID_COLOR;
|
|
14
152
|
return cells.map((cell) => {
|
|
@@ -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
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
export type MapXZ = readonly [number, number];
|
|
2
|
+
export type MapLayerTone = "danger" | "warning" | "info" | "safe" | "neutral";
|
|
3
|
+
export interface MapRoute {
|
|
4
|
+
id: string;
|
|
5
|
+
/** World-XZ polyline. */
|
|
6
|
+
points: readonly MapXZ[];
|
|
7
|
+
tone?: MapLayerTone;
|
|
8
|
+
/** Dashed rendering — a planned/forecast route rather than a live one. */
|
|
9
|
+
forecast?: boolean;
|
|
10
|
+
/** Line width in map pixels; default `2`. */
|
|
11
|
+
width?: number;
|
|
12
|
+
/** Close the polyline back to its first point (patrol loops). */
|
|
13
|
+
closed?: boolean;
|
|
14
|
+
}
|
|
15
|
+
export type MapZoneShape = {
|
|
16
|
+
kind: "circle";
|
|
17
|
+
center: MapXZ;
|
|
18
|
+
radius: number;
|
|
19
|
+
} | {
|
|
20
|
+
kind: "rect";
|
|
21
|
+
center: MapXZ;
|
|
22
|
+
w: number;
|
|
23
|
+
d: number;
|
|
24
|
+
rotate?: number;
|
|
25
|
+
} | {
|
|
26
|
+
kind: "polygon";
|
|
27
|
+
points: readonly MapXZ[];
|
|
28
|
+
};
|
|
29
|
+
export interface MapZone {
|
|
30
|
+
id: string;
|
|
31
|
+
shape: MapZoneShape;
|
|
32
|
+
tone?: MapLayerTone;
|
|
33
|
+
/** Fill opacity in `[0, 1]`; default `0.25`. */
|
|
34
|
+
opacity?: number;
|
|
35
|
+
/** Dashed outline + no fill — the previewed future state (storm's next ring, incoming hazard band). */
|
|
36
|
+
forecast?: boolean;
|
|
37
|
+
label?: string;
|
|
38
|
+
}
|
|
39
|
+
export interface MapCellStates {
|
|
40
|
+
id: string;
|
|
41
|
+
/** World-space center of cell (0, 0). */
|
|
42
|
+
origin: MapXZ;
|
|
43
|
+
cellSize: number;
|
|
44
|
+
/** Sparse cell statuses — pair with `world/cellStates`' `cellsIn`/`counts` output. */
|
|
45
|
+
cells: readonly {
|
|
46
|
+
col: number;
|
|
47
|
+
row: number;
|
|
48
|
+
tone: MapLayerTone;
|
|
49
|
+
opacity?: number;
|
|
50
|
+
}[];
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Renderer-free overlay data for map surfaces (#285.1-2): zones with live + forecast states,
|
|
54
|
+
* route polylines, and per-cell status heatmaps. `@jgengine/react/map`'s `Minimap`/`WorldMap`
|
|
55
|
+
* take these straight in; `pointInMapZone` answers gameplay queries against the same data,
|
|
56
|
+
* so the HUD and the rules can never disagree about where the storm is.
|
|
57
|
+
*/
|
|
58
|
+
export declare function pointInMapZone(zone: MapZoneShape, x: number, z: number): boolean;
|
|
59
|
+
export declare const MAP_LAYER_TONE_COLORS: Record<MapLayerTone, string>;
|
|
60
|
+
export declare function mapLayerColor(tone: MapLayerTone | undefined): string;
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Renderer-free overlay data for map surfaces (#285.1-2): zones with live + forecast states,
|
|
3
|
+
* route polylines, and per-cell status heatmaps. `@jgengine/react/map`'s `Minimap`/`WorldMap`
|
|
4
|
+
* take these straight in; `pointInMapZone` answers gameplay queries against the same data,
|
|
5
|
+
* so the HUD and the rules can never disagree about where the storm is.
|
|
6
|
+
*/
|
|
7
|
+
export function pointInMapZone(zone, x, z) {
|
|
8
|
+
if (zone.kind === "circle") {
|
|
9
|
+
return Math.hypot(x - zone.center[0], z - zone.center[1]) <= zone.radius;
|
|
10
|
+
}
|
|
11
|
+
if (zone.kind === "rect") {
|
|
12
|
+
let dx = x - zone.center[0];
|
|
13
|
+
let dz = z - zone.center[1];
|
|
14
|
+
if (zone.rotate !== undefined && zone.rotate !== 0) {
|
|
15
|
+
const cos = Math.cos(-zone.rotate);
|
|
16
|
+
const sin = Math.sin(-zone.rotate);
|
|
17
|
+
const rx = dx * cos - dz * sin;
|
|
18
|
+
const rz = dx * sin + dz * cos;
|
|
19
|
+
dx = rx;
|
|
20
|
+
dz = rz;
|
|
21
|
+
}
|
|
22
|
+
return Math.abs(dx) <= zone.w / 2 && Math.abs(dz) <= zone.d / 2;
|
|
23
|
+
}
|
|
24
|
+
const points = zone.points;
|
|
25
|
+
let inside = false;
|
|
26
|
+
for (let i = 0, j = points.length - 1; i < points.length; j = i, i += 1) {
|
|
27
|
+
const [xi, zi] = points[i];
|
|
28
|
+
const [xj, zj] = points[j];
|
|
29
|
+
const crosses = zi > z !== zj > z && x < ((xj - xi) * (z - zi)) / (zj - zi) + xi;
|
|
30
|
+
if (crosses)
|
|
31
|
+
inside = !inside;
|
|
32
|
+
}
|
|
33
|
+
return inside;
|
|
34
|
+
}
|
|
35
|
+
export const MAP_LAYER_TONE_COLORS = {
|
|
36
|
+
danger: "#ef4444",
|
|
37
|
+
warning: "#f59e0b",
|
|
38
|
+
info: "#38bdf8",
|
|
39
|
+
safe: "#4ade80",
|
|
40
|
+
neutral: "#a1a1aa",
|
|
41
|
+
};
|
|
42
|
+
export function mapLayerColor(tone) {
|
|
43
|
+
return MAP_LAYER_TONE_COLORS[tone ?? "neutral"];
|
|
44
|
+
}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export type MassingComposition = "bar" | "split" | "cluster" | "stack" | "court" | "bridge" | "ring" | "capsule" | "megastructure";
|
|
2
|
+
export declare const MASSING_COMPOSITIONS: readonly MassingComposition[];
|
|
3
|
+
export type MassingProfile = "straight" | "stepped" | "tapered" | "top-heavy" | "offset" | "twisted";
|
|
4
|
+
export declare const MASSING_PROFILES: readonly MassingProfile[];
|
|
5
|
+
export type MassingSpec = {
|
|
6
|
+
seed: string;
|
|
7
|
+
width: number;
|
|
8
|
+
height: number;
|
|
9
|
+
depth: number;
|
|
10
|
+
floorHeight: number;
|
|
11
|
+
baySpacing: number;
|
|
12
|
+
pilotis: number;
|
|
13
|
+
podiumHeight: number;
|
|
14
|
+
cores: number;
|
|
15
|
+
terraces: number;
|
|
16
|
+
cantilever: number;
|
|
17
|
+
voids: number;
|
|
18
|
+
taper: number;
|
|
19
|
+
articulation: number;
|
|
20
|
+
crown: number;
|
|
21
|
+
moduleDensity: number;
|
|
22
|
+
branches: number;
|
|
23
|
+
composition: MassingComposition;
|
|
24
|
+
profile: MassingProfile;
|
|
25
|
+
};
|
|
26
|
+
export type MassingBodyRole = "mass" | "core" | "transfer";
|
|
27
|
+
export type MassingBody = {
|
|
28
|
+
x: number;
|
|
29
|
+
y: number;
|
|
30
|
+
z: number;
|
|
31
|
+
w: number;
|
|
32
|
+
h: number;
|
|
33
|
+
d: number;
|
|
34
|
+
ry?: number;
|
|
35
|
+
facade?: boolean;
|
|
36
|
+
kind?: "box" | "capsule";
|
|
37
|
+
role?: MassingBodyRole;
|
|
38
|
+
branch?: boolean;
|
|
39
|
+
crown?: boolean;
|
|
40
|
+
};
|
|
41
|
+
export declare const hashMassingSeed: (text: string) => number;
|
|
42
|
+
export declare const massingBase: (spec: Pick<MassingSpec, "pilotis" | "height">, cap?: number) => number;
|
|
43
|
+
export declare const isVillageMassing: (spec: Pick<MassingSpec, "composition" | "height" | "articulation" | "moduleDensity">) => boolean;
|
|
44
|
+
export declare function massingFloorCount(spec: Pick<MassingSpec, "pilotis" | "height" | "floorHeight">): number;
|
|
45
|
+
export declare function composeMassing(b: MassingSpec): MassingBody[];
|