@jgengine/core 0.7.0 → 0.9.0

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