@jgengine/core 0.8.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 +36 -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 +13 -0
- package/dist/ai/spawnDirector.js +41 -17
- package/dist/ai/spawnPoint.d.ts +5 -0
- package/dist/ai/spawnPoint.js +5 -0
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -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.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/dotField.d.ts +43 -0
- package/dist/combat/dotField.js +48 -0
- package/dist/combat/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +214 -129
- package/dist/combat/regenShield.d.ts +35 -0
- package/dist/combat/regenShield.js +45 -0
- 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 +52 -0
- package/dist/combat/shotOrigin.js +86 -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/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/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/connectedPlayers.d.ts +28 -0
- package/dist/game/connectedPlayers.js +21 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +44 -1
- package/dist/game/defineGame.js +1 -0
- package/dist/game/events.d.ts +25 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -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 +14 -1
- package/dist/game/lootTable.js +28 -7
- package/dist/game/modelAnimation.d.ts +8 -0
- package/dist/game/modelAnimation.js +18 -0
- package/dist/game/playableGame.d.ts +116 -4
- 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 +27 -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 +8 -0
- package/dist/index.js +8 -0
- package/dist/input/actionBindings.d.ts +2 -0
- package/dist/input/actionBindings.js +1 -0
- package/dist/input/axisInput.d.ts +33 -3
- package/dist/input/axisInput.js +60 -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/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 +44 -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 +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/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/rng.d.ts +2 -0
- package/dist/random/rng.js +2 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -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/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 +72 -8
- package/dist/runtime/gameContext.js +374 -61
- 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/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 +18 -0
- package/dist/runtime/inputSnapshot.js +7 -0
- package/dist/runtime/motionIntents.d.ts +16 -6
- package/dist/runtime/motionIntents.js +31 -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/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 +50 -0
- package/dist/scene/colliders.js +101 -0
- package/dist/scene/entityStats.d.ts +4 -0
- package/dist/scene/entityStats.js +21 -0
- package/dist/scene/entityStore.d.ts +51 -5
- package/dist/scene/entityStore.js +119 -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.d.ts +4 -0
- package/dist/store/observableKeyedStore.js +17 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +2 -2
- package/dist/tactics/fallingGrid.js +2 -2
- 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/gameLayout.d.ts +125 -0
- package/dist/ui/gameLayout.js +129 -0
- 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/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/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 +212 -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 +21 -4
- package/dist/world/environmentSummary.js +22 -3
- package/dist/world/features.d.ts +185 -8
- package/dist/world/features.js +64 -6
- 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/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 +69 -3
- package/dist/world/terrain.js +309 -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 +3013 -1726
- package/package.json +12 -2
|
@@ -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[];
|