@jgengine/core 0.6.0 → 0.8.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 (330) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/README.md +2 -0
  3. package/dist/ai/crowd.d.ts +36 -0
  4. package/dist/ai/crowd.js +213 -0
  5. package/dist/ai/jobBoard.d.ts +49 -0
  6. package/dist/ai/jobBoard.js +155 -0
  7. package/dist/ai/spawnDirector.d.ts +61 -0
  8. package/dist/ai/spawnDirector.js +162 -0
  9. package/dist/ai/threat.d.ts +25 -0
  10. package/dist/ai/threat.js +67 -0
  11. package/dist/anim/easing.d.ts +14 -0
  12. package/dist/anim/easing.js +55 -0
  13. package/dist/audio/audioFalloff.d.ts +38 -0
  14. package/dist/audio/audioFalloff.js +32 -0
  15. package/dist/behaviour/behaviour.d.ts +136 -0
  16. package/dist/behaviour/behaviour.js +388 -0
  17. package/dist/board/laneBoard.d.ts +63 -0
  18. package/dist/board/laneBoard.js +114 -0
  19. package/dist/board/timelineBoard.d.ts +36 -0
  20. package/dist/board/timelineBoard.js +70 -0
  21. package/dist/cards/cardPile.d.ts +64 -0
  22. package/dist/cards/cardPile.js +175 -0
  23. package/dist/cards/modifierPipeline.d.ts +33 -0
  24. package/dist/cards/modifierPipeline.js +48 -0
  25. package/dist/combat/abilityKit.d.ts +47 -0
  26. package/dist/combat/abilityKit.js +139 -0
  27. package/dist/combat/animationState.d.ts +47 -0
  28. package/dist/combat/animationState.js +101 -0
  29. package/dist/combat/attackTags.d.ts +14 -0
  30. package/dist/combat/attackTags.js +26 -0
  31. package/dist/combat/breakMeters.d.ts +32 -0
  32. package/dist/combat/breakMeters.js +51 -0
  33. package/dist/combat/comboString.d.ts +35 -0
  34. package/dist/combat/comboString.js +59 -0
  35. package/dist/combat/death.d.ts +8 -0
  36. package/dist/combat/death.js +5 -2
  37. package/dist/combat/defensiveWindow.d.ts +34 -0
  38. package/dist/combat/defensiveWindow.js +66 -0
  39. package/dist/combat/downed.d.ts +35 -0
  40. package/dist/combat/downed.js +104 -0
  41. package/dist/combat/effects.d.ts +12 -1
  42. package/dist/combat/effects.js +31 -19
  43. package/dist/combat/hitReaction.d.ts +23 -0
  44. package/dist/combat/hitReaction.js +20 -0
  45. package/dist/combat/projectiles.d.ts +42 -5
  46. package/dist/combat/projectiles.js +98 -12
  47. package/dist/combat/resistance.d.ts +13 -0
  48. package/dist/combat/resistance.js +46 -0
  49. package/dist/combat/telegraph.d.ts +31 -0
  50. package/dist/combat/telegraph.js +51 -0
  51. package/dist/commands/commandRegistry.d.ts +1 -1
  52. package/dist/commands/commandRegistry.js +2 -1
  53. package/dist/crafting/crop.d.ts +59 -0
  54. package/dist/crafting/crop.js +181 -0
  55. package/dist/crafting/production.d.ts +64 -0
  56. package/dist/crafting/production.js +130 -0
  57. package/dist/crafting/recipe.d.ts +62 -0
  58. package/dist/crafting/recipe.js +101 -0
  59. package/dist/data/dataSource.d.ts +26 -0
  60. package/dist/data/dataSource.js +93 -0
  61. package/dist/data/devProxy.d.ts +11 -0
  62. package/dist/data/devProxy.js +38 -0
  63. package/dist/data/fetchJson.d.ts +20 -0
  64. package/dist/data/fetchJson.js +43 -0
  65. package/dist/data/jsonDataSource.d.ts +4 -0
  66. package/dist/data/jsonDataSource.js +6 -0
  67. package/dist/devtools/devtools.d.ts +157 -0
  68. package/dist/devtools/devtools.js +484 -0
  69. package/dist/devtools/transformTunables.d.ts +14 -0
  70. package/dist/devtools/transformTunables.js +38 -0
  71. package/dist/economy/sharedWallet.d.ts +28 -0
  72. package/dist/economy/sharedWallet.js +62 -0
  73. package/dist/economy/techTree.d.ts +40 -0
  74. package/dist/economy/techTree.js +66 -0
  75. package/dist/game/chat.d.ts +66 -0
  76. package/dist/game/chat.js +165 -0
  77. package/dist/game/cosmetics.d.ts +25 -0
  78. package/dist/game/cosmetics.js +51 -0
  79. package/dist/game/defineGame.d.ts +10 -2
  80. package/dist/game/defineGame.js +2 -1
  81. package/dist/game/events.d.ts +87 -0
  82. package/dist/game/levelSequence.d.ts +44 -0
  83. package/dist/game/levelSequence.js +78 -0
  84. package/dist/game/lootFilter.d.ts +33 -0
  85. package/dist/game/lootFilter.js +48 -0
  86. package/dist/game/ping.d.ts +67 -0
  87. package/dist/game/ping.js +86 -0
  88. package/dist/game/playableGame.d.ts +516 -8
  89. package/dist/game/playableGame.js +6 -1
  90. package/dist/game/progression.d.ts +1 -0
  91. package/dist/game/progression.js +23 -2
  92. package/dist/game/race.d.ts +100 -0
  93. package/dist/game/race.js +236 -0
  94. package/dist/game/runDraft.d.ts +34 -0
  95. package/dist/game/runDraft.js +97 -0
  96. package/dist/game/social.d.ts +69 -0
  97. package/dist/game/social.js +138 -0
  98. package/dist/game/spawnPoints.d.ts +19 -0
  99. package/dist/game/spawnPoints.js +23 -0
  100. package/dist/game/worldItem.d.ts +95 -0
  101. package/dist/game/worldItem.js +138 -0
  102. package/dist/input/actionBindings.d.ts +10 -0
  103. package/dist/input/actionBindings.js +14 -0
  104. package/dist/input/axisInput.d.ts +43 -0
  105. package/dist/input/axisInput.js +70 -0
  106. package/dist/input/gestureSurface.d.ts +31 -0
  107. package/dist/input/gestureSurface.js +120 -0
  108. package/dist/input/pointer.d.ts +69 -0
  109. package/dist/input/pointer.js +65 -0
  110. package/dist/input/touchScheme.d.ts +83 -0
  111. package/dist/input/touchScheme.js +115 -0
  112. package/dist/interaction/contextMenu.d.ts +30 -0
  113. package/dist/interaction/contextMenu.js +22 -0
  114. package/dist/interaction/qte.d.ts +20 -0
  115. package/dist/interaction/qte.js +24 -0
  116. package/dist/interaction/skillCheck.d.ts +20 -0
  117. package/dist/interaction/skillCheck.js +26 -0
  118. package/dist/inventory/shapedGrid.d.ts +56 -0
  119. package/dist/inventory/shapedGrid.js +176 -0
  120. package/dist/inventory/storageTier.d.ts +52 -0
  121. package/dist/inventory/storageTier.js +77 -0
  122. package/dist/item/affix.d.ts +54 -0
  123. package/dist/item/affix.js +115 -0
  124. package/dist/item/durability.d.ts +51 -0
  125. package/dist/item/durability.js +78 -0
  126. package/dist/item/modularItem.d.ts +46 -0
  127. package/dist/item/modularItem.js +75 -0
  128. package/dist/meta/changelog.d.ts +1 -1
  129. package/dist/meta/changelog.js +71 -1
  130. package/dist/movement/dash.d.ts +37 -0
  131. package/dist/movement/dash.js +65 -0
  132. package/dist/movement/movementModel.d.ts +36 -3
  133. package/dist/movement/movementModel.js +81 -5
  134. package/dist/movement/voxelController.d.ts +61 -0
  135. package/dist/movement/voxelController.js +229 -0
  136. package/dist/multiplayer/chatContract.d.ts +42 -0
  137. package/dist/multiplayer/chatContract.js +57 -0
  138. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  139. package/dist/multiplayer/combatSnapshot.js +89 -0
  140. package/dist/multiplayer/identity.d.ts +13 -0
  141. package/dist/multiplayer/identity.js +20 -0
  142. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  143. package/dist/multiplayer/lagCompensation.js +154 -0
  144. package/dist/multiplayer/matchmaking.d.ts +33 -0
  145. package/dist/multiplayer/matchmaking.js +60 -0
  146. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  147. package/dist/multiplayer/poseSyncGate.js +13 -1
  148. package/dist/multiplayer/presenceModel.d.ts +3 -0
  149. package/dist/multiplayer/presenceModel.js +15 -1
  150. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  151. package/dist/multiplayer/simultaneousCommit.js +53 -0
  152. package/dist/multiplayer/voiceContract.d.ts +44 -0
  153. package/dist/multiplayer/voiceContract.js +108 -0
  154. package/dist/nav/navConstrain.d.ts +14 -0
  155. package/dist/nav/navConstrain.js +26 -0
  156. package/dist/nav/navFromEnvironment.d.ts +11 -0
  157. package/dist/nav/navFromEnvironment.js +16 -0
  158. package/dist/nav/navGrid.d.ts +56 -0
  159. package/dist/nav/navGrid.js +265 -0
  160. package/dist/nav/pathFollow.d.ts +31 -0
  161. package/dist/nav/pathFollow.js +69 -0
  162. package/dist/physics/ballisticSweep.d.ts +18 -0
  163. package/dist/physics/ballisticSweep.js +49 -0
  164. package/dist/physics/buoyancy.d.ts +49 -0
  165. package/dist/physics/buoyancy.js +112 -0
  166. package/dist/physics/carryable.d.ts +46 -0
  167. package/dist/physics/carryable.js +110 -0
  168. package/dist/physics/damageZones.d.ts +56 -0
  169. package/dist/physics/damageZones.js +107 -0
  170. package/dist/physics/forceVolume.d.ts +48 -0
  171. package/dist/physics/forceVolume.js +119 -0
  172. package/dist/physics/physicsWorld.d.ts +124 -2
  173. package/dist/physics/physicsWorld.js +493 -41
  174. package/dist/physics/ragdoll.d.ts +49 -0
  175. package/dist/physics/ragdoll.js +103 -0
  176. package/dist/physics/spatialGrid.d.ts +41 -0
  177. package/dist/physics/spatialGrid.js +143 -0
  178. package/dist/physics/structure.d.ts +83 -0
  179. package/dist/physics/structure.js +0 -0
  180. package/dist/physics/traversal.d.ts +75 -0
  181. package/dist/physics/traversal.js +138 -0
  182. package/dist/physics/vehicleBody.d.ts +99 -0
  183. package/dist/physics/vehicleBody.js +233 -0
  184. package/dist/puzzle/cellGrid.d.ts +26 -0
  185. package/dist/puzzle/cellGrid.js +110 -0
  186. package/dist/puzzle/fallingPiece.d.ts +24 -0
  187. package/dist/puzzle/fallingPiece.js +51 -0
  188. package/dist/random/nameGen.d.ts +18 -0
  189. package/dist/random/nameGen.js +35 -0
  190. package/dist/random/rng.d.ts +2 -0
  191. package/dist/random/rng.js +23 -0
  192. package/dist/runtime/adapter.d.ts +35 -0
  193. package/dist/runtime/adapter.js +37 -1
  194. package/dist/runtime/cameraDirector.d.ts +10 -0
  195. package/dist/runtime/cameraDirector.js +25 -0
  196. package/dist/runtime/gameContext.d.ts +107 -3
  197. package/dist/runtime/gameContext.js +253 -7
  198. package/dist/runtime/hostPersistence.d.ts +39 -0
  199. package/dist/runtime/hostPersistence.js +54 -0
  200. package/dist/runtime/inputSnapshot.d.ts +7 -0
  201. package/dist/runtime/inputSnapshot.js +12 -0
  202. package/dist/runtime/motionIntents.d.ts +25 -0
  203. package/dist/runtime/motionIntents.js +25 -0
  204. package/dist/runtime/persistenceScope.d.ts +32 -0
  205. package/dist/runtime/persistenceScope.js +64 -0
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/scene/autoTarget.d.ts +15 -0
  208. package/dist/scene/autoTarget.js +54 -0
  209. package/dist/scene/behaviors.d.ts +13 -1
  210. package/dist/scene/behaviors.js +3 -0
  211. package/dist/scene/captureCheck.d.ts +7 -0
  212. package/dist/scene/captureCheck.js +11 -0
  213. package/dist/scene/entityStore.d.ts +9 -1
  214. package/dist/scene/entityStore.js +25 -2
  215. package/dist/scene/form.d.ts +40 -0
  216. package/dist/scene/form.js +56 -0
  217. package/dist/scene/mount.d.ts +62 -0
  218. package/dist/scene/mount.js +107 -0
  219. package/dist/scene/movementSpeed.d.ts +35 -0
  220. package/dist/scene/movementSpeed.js +20 -0
  221. package/dist/scene/objectQuery.d.ts +18 -0
  222. package/dist/scene/objectQuery.js +108 -0
  223. package/dist/scene/objectStore.d.ts +11 -0
  224. package/dist/scene/objectStore.js +109 -4
  225. package/dist/scene/paintLayer.d.ts +15 -0
  226. package/dist/scene/paintLayer.js +53 -0
  227. package/dist/scene/possession.d.ts +30 -0
  228. package/dist/scene/possession.js +51 -0
  229. package/dist/scene/roster.d.ts +25 -0
  230. package/dist/scene/roster.js +57 -0
  231. package/dist/scene/selection.d.ts +30 -0
  232. package/dist/scene/selection.js +38 -0
  233. package/dist/scene/spatial.d.ts +21 -0
  234. package/dist/scene/spatial.js +63 -1
  235. package/dist/scene/stationClaim.d.ts +43 -0
  236. package/dist/scene/stationClaim.js +109 -0
  237. package/dist/sensor/concealment.d.ts +21 -0
  238. package/dist/sensor/concealment.js +63 -0
  239. package/dist/sensor/freezeMonitor.d.ts +17 -0
  240. package/dist/sensor/freezeMonitor.js +35 -0
  241. package/dist/sensor/frustumSensor.d.ts +60 -0
  242. package/dist/sensor/frustumSensor.js +104 -0
  243. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  244. package/dist/sensor/hiddenStateProbe.js +40 -0
  245. package/dist/sensor/recordingBuffer.d.ts +29 -0
  246. package/dist/sensor/recordingBuffer.js +61 -0
  247. package/dist/sensor/revealQuery.d.ts +22 -0
  248. package/dist/sensor/revealQuery.js +36 -0
  249. package/dist/session/contestedChannel.d.ts +36 -0
  250. package/dist/session/contestedChannel.js +100 -0
  251. package/dist/session/extraction.d.ts +54 -0
  252. package/dist/session/extraction.js +108 -0
  253. package/dist/session/ring.d.ts +36 -0
  254. package/dist/session/ring.js +88 -0
  255. package/dist/session/roles.d.ts +6 -0
  256. package/dist/session/roles.js +32 -0
  257. package/dist/session/roundState.d.ts +79 -0
  258. package/dist/session/roundState.js +158 -0
  259. package/dist/stats/accumulatorMeter.d.ts +31 -0
  260. package/dist/stats/accumulatorMeter.js +81 -0
  261. package/dist/stats/eventMeter.d.ts +34 -0
  262. package/dist/stats/eventMeter.js +76 -0
  263. package/dist/stats/rollCheck.d.ts +15 -0
  264. package/dist/stats/rollCheck.js +15 -0
  265. package/dist/survival/decayMeter.d.ts +59 -0
  266. package/dist/survival/decayMeter.js +95 -0
  267. package/dist/survival/moodle.d.ts +48 -0
  268. package/dist/survival/moodle.js +91 -0
  269. package/dist/survival/regionHealth.d.ts +72 -0
  270. package/dist/survival/regionHealth.js +129 -0
  271. package/dist/tactics/fallingGrid.d.ts +62 -0
  272. package/dist/tactics/fallingGrid.js +158 -0
  273. package/dist/tactics/predictiveQuery.d.ts +34 -0
  274. package/dist/tactics/predictiveQuery.js +44 -0
  275. package/dist/tactics/snapshot.d.ts +18 -0
  276. package/dist/tactics/snapshot.js +65 -0
  277. package/dist/tactics/surface.d.ts +52 -0
  278. package/dist/tactics/surface.js +135 -0
  279. package/dist/tactics/tacticalGrid.d.ts +68 -0
  280. package/dist/tactics/tacticalGrid.js +261 -0
  281. package/dist/time/beatClock.d.ts +40 -0
  282. package/dist/time/beatClock.js +73 -0
  283. package/dist/time/simClock.d.ts +25 -1
  284. package/dist/time/simClock.js +28 -3
  285. package/dist/turn/commit.d.ts +27 -0
  286. package/dist/turn/commit.js +72 -0
  287. package/dist/turn/intent.d.ts +14 -0
  288. package/dist/turn/intent.js +29 -0
  289. package/dist/turn/turnLoop.d.ts +60 -0
  290. package/dist/turn/turnLoop.js +223 -0
  291. package/dist/world/buildPermissions.d.ts +47 -0
  292. package/dist/world/buildPermissions.js +134 -0
  293. package/dist/world/carve.d.ts +105 -0
  294. package/dist/world/carve.js +175 -0
  295. package/dist/world/connectors.d.ts +40 -0
  296. package/dist/world/connectors.js +67 -0
  297. package/dist/world/envField.d.ts +71 -0
  298. package/dist/world/envField.js +96 -0
  299. package/dist/world/environmentSummary.d.ts +53 -0
  300. package/dist/world/environmentSummary.js +129 -0
  301. package/dist/world/features.d.ts +87 -6
  302. package/dist/world/features.js +52 -2
  303. package/dist/world/fog.d.ts +40 -0
  304. package/dist/world/fog.js +137 -0
  305. package/dist/world/gridInstances.d.ts +13 -0
  306. package/dist/world/gridInstances.js +22 -0
  307. package/dist/world/markers.d.ts +52 -0
  308. package/dist/world/markers.js +96 -0
  309. package/dist/world/minimap.d.ts +42 -0
  310. package/dist/world/minimap.js +72 -0
  311. package/dist/world/placedStructureStore.d.ts +39 -0
  312. package/dist/world/placedStructureStore.js +95 -0
  313. package/dist/world/placementController.d.ts +69 -0
  314. package/dist/world/placementController.js +153 -0
  315. package/dist/world/realm.d.ts +61 -0
  316. package/dist/world/realm.js +66 -0
  317. package/dist/world/support.d.ts +27 -0
  318. package/dist/world/support.js +75 -0
  319. package/dist/world/terraform.d.ts +54 -0
  320. package/dist/world/terraform.js +186 -0
  321. package/dist/world/terrain.d.ts +23 -1
  322. package/dist/world/terrain.js +64 -1
  323. package/dist/world/voxelField.d.ts +54 -0
  324. package/dist/world/voxelField.js +241 -0
  325. package/dist/world/walls.d.ts +61 -0
  326. package/dist/world/walls.js +221 -0
  327. package/dist/world/weather.d.ts +92 -0
  328. package/dist/world/weather.js +169 -0
  329. package/llms.txt +4102 -0
  330. package/package.json +3 -2
@@ -0,0 +1,66 @@
1
+ import { createEnvironmentField } from "./envField.js";
2
+ export const DEFAULT_REALM_ENVIRONMENT = {
3
+ baseTemperature: 20,
4
+ nightDrop: 12,
5
+ altitudeLapse: 0,
6
+ dayLength: 24 * 3600,
7
+ rain: 0,
8
+ ambientFloor: 0.05,
9
+ };
10
+ function applySpawn(table, override) {
11
+ if (override === undefined)
12
+ return;
13
+ if (override.set !== undefined)
14
+ for (const [id, weight] of Object.entries(override.set))
15
+ table[id] = weight;
16
+ if (override.add !== undefined)
17
+ for (const [id, weight] of Object.entries(override.add))
18
+ table[id] = (table[id] ?? 0) + weight;
19
+ if (override.scale !== undefined)
20
+ for (const [id, factor] of Object.entries(override.scale))
21
+ table[id] = (table[id] ?? 0) * factor;
22
+ if (override.remove !== undefined)
23
+ for (const id of override.remove)
24
+ delete table[id];
25
+ }
26
+ /**
27
+ * Assemble a played realm instance at runtime from a deck of modifier cards — the
28
+ * Nightingale "realm card" model. A major card is the biome base; minor cards layer
29
+ * weather, day length, and spawn edits. The result recomposes both the environment
30
+ * (into a sampleable field via `environmentField()`) and the spawn table, and it depends
31
+ * on the weather hooks in this group (#92) to turn its `weather` into gameplay modifiers.
32
+ * Cards apply in array order; sort your deck (majors first) before composing.
33
+ */
34
+ export function composeRealm(base, cards) {
35
+ const params = { ...DEFAULT_REALM_ENVIRONMENT, ...base.environment };
36
+ let weather = base.weather ?? { kind: "clear", intensity: 0 };
37
+ const spawnTable = { ...base.spawn };
38
+ const aesthetics = [];
39
+ for (const card of cards) {
40
+ if (card.environment !== undefined)
41
+ Object.assign(params, card.environment);
42
+ if (card.weather !== undefined)
43
+ weather = { ...weather, ...card.weather };
44
+ applySpawn(spawnTable, card.spawn);
45
+ if (card.aesthetic !== undefined)
46
+ aesthetics.push(card.aesthetic);
47
+ }
48
+ return {
49
+ params,
50
+ weather,
51
+ spawnTable,
52
+ aesthetics,
53
+ cards,
54
+ environmentField(extra) {
55
+ return createEnvironmentField({
56
+ ...extra,
57
+ baseTemperature: params.baseTemperature,
58
+ nightDrop: params.nightDrop,
59
+ altitudeLapse: params.altitudeLapse,
60
+ dayLength: params.dayLength,
61
+ rain: weather.kind === "clear" ? 0 : params.rain,
62
+ ambientFloor: params.ambientFloor,
63
+ });
64
+ },
65
+ };
66
+ }
@@ -0,0 +1,27 @@
1
+ import type { AddBodyOptions } from "../physics/physicsWorld.js";
2
+ import type { ConnectorVec3 } from "./connectors.js";
3
+ export interface SupportPiece {
4
+ id: string;
5
+ grounded?: boolean;
6
+ position?: ConnectorVec3;
7
+ halfExtents?: ConnectorVec3;
8
+ mass?: number;
9
+ }
10
+ export interface SupportLink {
11
+ a: string;
12
+ b: string;
13
+ }
14
+ export interface SupportConfig {
15
+ maxDistance?: number;
16
+ }
17
+ export interface SupportResult {
18
+ supported: readonly string[];
19
+ unsupported: readonly string[];
20
+ distance: Readonly<Record<string, number>>;
21
+ }
22
+ export declare function solveSupport(pieces: readonly SupportPiece[], links: readonly SupportLink[], config?: SupportConfig): SupportResult;
23
+ export interface DebrisOptions {
24
+ scatter?: number;
25
+ seed?: number;
26
+ }
27
+ export declare function toDebrisBodies(pieces: readonly SupportPiece[], collapsedIds: readonly string[], options?: DebrisOptions): AddBodyOptions[];
@@ -0,0 +1,75 @@
1
+ export function solveSupport(pieces, links, config = {}) {
2
+ const maxDistance = config.maxDistance ?? Infinity;
3
+ const ids = new Set(pieces.map((piece) => piece.id));
4
+ const adjacency = new Map();
5
+ for (const id of ids)
6
+ adjacency.set(id, []);
7
+ for (const link of links) {
8
+ if (!ids.has(link.a) || !ids.has(link.b))
9
+ continue;
10
+ adjacency.get(link.a).push(link.b);
11
+ adjacency.get(link.b).push(link.a);
12
+ }
13
+ const distance = {};
14
+ const queue = [];
15
+ for (const piece of pieces) {
16
+ if (piece.grounded === true) {
17
+ distance[piece.id] = 0;
18
+ queue.push(piece.id);
19
+ }
20
+ }
21
+ let head = 0;
22
+ while (head < queue.length) {
23
+ const current = queue[head];
24
+ head += 1;
25
+ const currentDistance = distance[current];
26
+ if (currentDistance >= maxDistance)
27
+ continue;
28
+ for (const neighbor of adjacency.get(current) ?? []) {
29
+ const next = currentDistance + 1;
30
+ if (distance[neighbor] === undefined || next < distance[neighbor]) {
31
+ distance[neighbor] = next;
32
+ queue.push(neighbor);
33
+ }
34
+ }
35
+ }
36
+ const supported = [];
37
+ const unsupported = [];
38
+ for (const piece of pieces) {
39
+ const d = distance[piece.id];
40
+ if (d !== undefined && d <= maxDistance)
41
+ supported.push(piece.id);
42
+ else
43
+ unsupported.push(piece.id);
44
+ }
45
+ return { supported, unsupported, distance };
46
+ }
47
+ function scatterHash(seed, index) {
48
+ let h = (seed ^ Math.imul(index + 1, 0x9e3779b1)) >>> 0;
49
+ h = Math.imul(h ^ (h >>> 15), 0x85ebca6b);
50
+ h ^= h >>> 13;
51
+ return ((h >>> 0) / 4294967295) * 2 - 1;
52
+ }
53
+ export function toDebrisBodies(pieces, collapsedIds, options = {}) {
54
+ const scatter = options.scatter ?? 1.5;
55
+ const seed = options.seed ?? 1;
56
+ const collapse = new Set(collapsedIds);
57
+ const bodies = [];
58
+ let index = 0;
59
+ for (const piece of pieces) {
60
+ if (!collapse.has(piece.id))
61
+ continue;
62
+ const position = piece.position ?? [0, 0, 0];
63
+ const halfExtents = piece.halfExtents ?? [0.5, 0.5, 0.5];
64
+ const vx = scatterHash(seed, index) * scatter;
65
+ const vz = scatterHash(seed, index + 977) * scatter;
66
+ bodies.push({
67
+ position,
68
+ halfExtents,
69
+ velocity: [vx, 0, vz],
70
+ ...(piece.mass === undefined ? {} : { mass: piece.mass }),
71
+ });
72
+ index += 1;
73
+ }
74
+ return bodies;
75
+ }
@@ -0,0 +1,54 @@
1
+ import type { Aabb, Vec2 } from "./geometry.js";
2
+ import { type TerrainField } from "./terrain.js";
3
+ export type TerraformMode = "raise" | "lower" | "flatten" | "paint";
4
+ export type TerraformFalloff = "smooth" | "linear" | "none";
5
+ export interface TerraformEdit {
6
+ mode: TerraformMode;
7
+ center: Vec2;
8
+ radius: number;
9
+ strength?: number;
10
+ target?: number;
11
+ surface?: string;
12
+ falloff?: TerraformFalloff;
13
+ }
14
+ export interface EditableTerrainConfig {
15
+ bounds: Aabb;
16
+ base?: TerrainField;
17
+ cellSize?: number;
18
+ }
19
+ export interface TerraformSnapshot {
20
+ bounds: Aabb;
21
+ cellSize: number;
22
+ cols: number;
23
+ rows: number;
24
+ offsets: readonly number[];
25
+ surfaces: readonly (string | null)[];
26
+ }
27
+ export interface EditableTerrain extends TerrainField {
28
+ apply(edit: TerraformEdit): number;
29
+ heightOffsetAt(x: number, z: number): number;
30
+ surfaceAt(x: number, z: number): string | null;
31
+ reset(): void;
32
+ snapshot(): TerraformSnapshot;
33
+ restore(snapshot: TerraformSnapshot): void;
34
+ }
35
+ export declare function brushWeight(distance: number, radius: number, falloff: TerraformFalloff): number;
36
+ export declare function createEditableTerrain(config: EditableTerrainConfig): EditableTerrain;
37
+ export interface TerraformBrushConfig {
38
+ radius?: number;
39
+ strength?: number;
40
+ falloff?: TerraformFalloff;
41
+ surface?: string;
42
+ }
43
+ export interface TerraformBrush {
44
+ raise(center: Vec2): number;
45
+ lower(center: Vec2): number;
46
+ flatten(center: Vec2, target?: number): number;
47
+ paint(center: Vec2, surface?: string): number;
48
+ setRadius(radius: number): void;
49
+ setStrength(strength: number): void;
50
+ config(): Required<Omit<TerraformBrushConfig, "surface">> & {
51
+ surface: string | undefined;
52
+ };
53
+ }
54
+ export declare function createTerraformBrush(terrain: Pick<EditableTerrain, "apply">, config?: TerraformBrushConfig): TerraformBrush;
@@ -0,0 +1,186 @@
1
+ import { FLAT_FIELD, withNormal } from "./terrain.js";
2
+ export function brushWeight(distance, radius, falloff) {
3
+ if (radius <= 0)
4
+ return distance <= 0 ? 1 : 0;
5
+ const t = distance / radius;
6
+ if (t >= 1)
7
+ return 0;
8
+ if (falloff === "none")
9
+ return 1;
10
+ if (falloff === "linear")
11
+ return 1 - t;
12
+ const s = 1 - t;
13
+ return s * s * (3 - 2 * s);
14
+ }
15
+ function clampIndex(value, max) {
16
+ return value < 0 ? 0 : value > max ? max : value;
17
+ }
18
+ export function createEditableTerrain(config) {
19
+ const base = config.base ?? FLAT_FIELD;
20
+ const bounds = config.bounds;
21
+ const cellSize = config.cellSize && config.cellSize > 0 ? config.cellSize : 1;
22
+ const spanX = Math.max(bounds.maxX - bounds.minX, cellSize);
23
+ const spanZ = Math.max(bounds.maxZ - bounds.minZ, cellSize);
24
+ const cols = Math.max(1, Math.round(spanX / cellSize));
25
+ const rows = Math.max(1, Math.round(spanZ / cellSize));
26
+ const vertsX = cols + 1;
27
+ const vertsZ = rows + 1;
28
+ const offsets = new Float32Array(vertsX * vertsZ);
29
+ const surfaces = new Array(cols * rows).fill(null);
30
+ function gridX(x) {
31
+ return ((x - bounds.minX) / spanX) * cols;
32
+ }
33
+ function gridZ(z) {
34
+ return ((z - bounds.minZ) / spanZ) * rows;
35
+ }
36
+ function heightOffsetAt(x, z) {
37
+ const fx = gridX(x);
38
+ const fz = gridZ(z);
39
+ const x0 = clampIndex(Math.floor(fx), cols);
40
+ const z0 = clampIndex(Math.floor(fz), rows);
41
+ const x1 = clampIndex(x0 + 1, cols);
42
+ const z1 = clampIndex(z0 + 1, rows);
43
+ const tx = fx - x0 < 0 ? 0 : fx - x0 > 1 ? 1 : fx - x0;
44
+ const tz = fz - z0 < 0 ? 0 : fz - z0 > 1 ? 1 : fz - z0;
45
+ const v00 = offsets[z0 * vertsX + x0];
46
+ const v10 = offsets[z0 * vertsX + x1];
47
+ const v01 = offsets[z1 * vertsX + x0];
48
+ const v11 = offsets[z1 * vertsX + x1];
49
+ const top = v00 + (v10 - v00) * tx;
50
+ const bottom = v01 + (v11 - v01) * tx;
51
+ return top + (bottom - top) * tz;
52
+ }
53
+ const sampleHeight = (x, z) => base.sampleHeight(x, z) + heightOffsetAt(x, z);
54
+ const sampleNormal = withNormal(sampleHeight);
55
+ function editHeight(edit) {
56
+ const falloff = edit.falloff ?? "smooth";
57
+ const strength = edit.strength ?? 1;
58
+ const target = edit.mode === "flatten" ? edit.target ?? sampleHeight(edit.center[0], edit.center[1]) : 0;
59
+ const rCells = Math.ceil(edit.radius / cellSize) + 1;
60
+ const cx = gridX(edit.center[0]);
61
+ const cz = gridZ(edit.center[1]);
62
+ let changed = 0;
63
+ for (let iz = -rCells; iz <= rCells; iz += 1) {
64
+ const gz = Math.round(cz) + iz;
65
+ if (gz < 0 || gz >= vertsZ)
66
+ continue;
67
+ for (let ix = -rCells; ix <= rCells; ix += 1) {
68
+ const gx = Math.round(cx) + ix;
69
+ if (gx < 0 || gx >= vertsX)
70
+ continue;
71
+ const wx = bounds.minX + (gx / cols) * spanX;
72
+ const wz = bounds.minZ + (gz / rows) * spanZ;
73
+ const dist = Math.hypot(wx - edit.center[0], wz - edit.center[1]);
74
+ const weight = brushWeight(dist, edit.radius, falloff);
75
+ if (weight <= 0)
76
+ continue;
77
+ const index = gz * vertsX + gx;
78
+ if (edit.mode === "raise")
79
+ offsets[index] += strength * weight;
80
+ else if (edit.mode === "lower")
81
+ offsets[index] -= strength * weight;
82
+ else {
83
+ const current = base.sampleHeight(wx, wz) + offsets[index];
84
+ offsets[index] += (target - current) * weight * strength;
85
+ }
86
+ changed += 1;
87
+ }
88
+ }
89
+ return changed;
90
+ }
91
+ function paintSurface(edit) {
92
+ const surface = edit.surface ?? null;
93
+ const rCells = Math.ceil(edit.radius / cellSize) + 1;
94
+ const cx = gridX(edit.center[0]);
95
+ const cz = gridZ(edit.center[1]);
96
+ let painted = 0;
97
+ for (let iz = -rCells; iz <= rCells; iz += 1) {
98
+ const gz = Math.floor(cz) + iz;
99
+ if (gz < 0 || gz >= rows)
100
+ continue;
101
+ for (let ix = -rCells; ix <= rCells; ix += 1) {
102
+ const gx = Math.floor(cx) + ix;
103
+ if (gx < 0 || gx >= cols)
104
+ continue;
105
+ const wx = bounds.minX + ((gx + 0.5) / cols) * spanX;
106
+ const wz = bounds.minZ + ((gz + 0.5) / rows) * spanZ;
107
+ if (Math.hypot(wx - edit.center[0], wz - edit.center[1]) > edit.radius)
108
+ continue;
109
+ surfaces[gz * cols + gx] = surface;
110
+ painted += 1;
111
+ }
112
+ }
113
+ return painted;
114
+ }
115
+ function surfaceAt(x, z) {
116
+ const gx = Math.floor(gridX(x));
117
+ const gz = Math.floor(gridZ(z));
118
+ if (gx < 0 || gx >= cols || gz < 0 || gz >= rows)
119
+ return null;
120
+ return surfaces[gz * cols + gx] ?? null;
121
+ }
122
+ const field = {
123
+ sampleHeight,
124
+ sampleNormal,
125
+ ...(base.bounds === undefined ? {} : { bounds: base.bounds }),
126
+ ...(base.waterLevel === undefined ? {} : { waterLevel: base.waterLevel }),
127
+ apply(edit) {
128
+ return edit.mode === "paint" ? paintSurface(edit) : editHeight(edit);
129
+ },
130
+ heightOffsetAt,
131
+ surfaceAt,
132
+ reset() {
133
+ offsets.fill(0);
134
+ surfaces.fill(null);
135
+ },
136
+ snapshot() {
137
+ return {
138
+ bounds: { ...bounds },
139
+ cellSize,
140
+ cols,
141
+ rows,
142
+ offsets: Array.from(offsets),
143
+ surfaces: surfaces.slice(),
144
+ };
145
+ },
146
+ restore(snapshot) {
147
+ const count = Math.min(offsets.length, snapshot.offsets.length);
148
+ for (let index = 0; index < count; index += 1)
149
+ offsets[index] = snapshot.offsets[index];
150
+ const surfaceCount = Math.min(surfaces.length, snapshot.surfaces.length);
151
+ for (let index = 0; index < surfaceCount; index += 1)
152
+ surfaces[index] = snapshot.surfaces[index] ?? null;
153
+ },
154
+ };
155
+ return field;
156
+ }
157
+ export function createTerraformBrush(terrain, config = {}) {
158
+ let radius = config.radius ?? 3;
159
+ let strength = config.strength ?? 0.5;
160
+ const falloff = config.falloff ?? "smooth";
161
+ const surface = config.surface;
162
+ return {
163
+ raise(center) {
164
+ return terrain.apply({ mode: "raise", center, radius, strength, falloff });
165
+ },
166
+ lower(center) {
167
+ return terrain.apply({ mode: "lower", center, radius, strength, falloff });
168
+ },
169
+ flatten(center, target) {
170
+ return terrain.apply({ mode: "flatten", center, radius, strength, falloff, ...(target === undefined ? {} : { target }) });
171
+ },
172
+ paint(center, paintSurface) {
173
+ const chosen = paintSurface ?? surface;
174
+ return terrain.apply({ mode: "paint", center, radius, ...(chosen === undefined ? {} : { surface: chosen }) });
175
+ },
176
+ setRadius(next) {
177
+ radius = next;
178
+ },
179
+ setStrength(next) {
180
+ strength = next;
181
+ },
182
+ config() {
183
+ return { radius, strength, falloff, surface };
184
+ },
185
+ };
186
+ }
@@ -1,4 +1,4 @@
1
- import type { TerrainEnvironmentDescriptor, WorldBounds } from "./features.js";
1
+ import type { TerrainEnvironmentConfig, TerrainEnvironmentDescriptor, WorldBounds, WorldFeature } from "./features.js";
2
2
  export type TerrainNormal = readonly [number, number, number];
3
3
  export interface TerrainField {
4
4
  sampleHeight(x: number, z: number): number;
@@ -46,7 +46,29 @@ export interface ArenaFieldConfig {
46
46
  }
47
47
  export declare const ARENA_WATER_LEVEL = -0.9;
48
48
  export declare function arenaField(config?: ArenaFieldConfig): TerrainField;
49
+ export declare function groundFieldFor(world?: WorldFeature): TerrainField;
50
+ export interface TerrainPalette {
51
+ low: string;
52
+ high: string;
53
+ waterline: string;
54
+ }
55
+ export declare const DEFAULT_TERRAIN_MATERIAL = "grass";
56
+ export declare const TERRAIN_MATERIAL_PALETTES: Record<string, TerrainPalette>;
57
+ export declare function resolveTerrainPalette(descriptor?: Pick<TerrainEnvironmentConfig, "material" | "colors">): TerrainPalette;
49
58
  export declare function resolveTerrainField(descriptor?: TerrainEnvironmentDescriptor): TerrainField;
59
+ /** Returns `position` with `y` replaced by the field's ground height (plus `offset`) at its `x`/`z`. */
60
+ export declare function snapToGround(field: TerrainField, position: readonly [number, number, number], offset?: number): [number, number, number];
61
+ export interface GroundSnapTarget {
62
+ position: readonly [number, number, number];
63
+ }
64
+ export interface GroundSnapEntityStore {
65
+ get(id: string): GroundSnapTarget | null;
66
+ setPose(id: string, pose: {
67
+ position?: readonly [number, number, number];
68
+ }): boolean;
69
+ }
70
+ /** Ground-snaps an already-spawned entity in place; returns false when `id` is unknown. */
71
+ export declare function snapEntityToGround(entities: GroundSnapEntityStore, id: string, field: TerrainField, offset?: number): boolean;
50
72
  export declare const DEFAULT_MAX_WALK_SLOPE = 0.6;
51
73
  export declare function resolveGroundStep(field: TerrainField, x: number, z: number, stepX: number, stepZ: number, maxSlope?: number): {
52
74
  stepX: number;
@@ -148,10 +148,55 @@ export function arenaField(config = {}) {
148
148
  };
149
149
  return fieldFromHeight(sampleHeight, { bounds: config.bounds, waterLevel: ARENA_WATER_LEVEL });
150
150
  }
151
+ export function groundFieldFor(world) {
152
+ if (world !== undefined && world.kind === "environment")
153
+ return resolveTerrainField(world.terrain);
154
+ return flatField();
155
+ }
156
+ export const DEFAULT_TERRAIN_MATERIAL = "grass";
157
+ export const TERRAIN_MATERIAL_PALETTES = {
158
+ grass: { low: "#30402c", high: "#7f8b50", waterline: "#1d4c6e" },
159
+ sand: { low: "#9c8354", high: "#e0c98a", waterline: "#2f6f8f" },
160
+ snow: { low: "#c7d3dc", high: "#ffffff", waterline: "#3a6ea5" },
161
+ rock: { low: "#3a3a3d", high: "#8a8a8d", waterline: "#1d4c6e" },
162
+ ash: { low: "#2b2622", high: "#5c534a", waterline: "#3a3630" },
163
+ };
164
+ export function resolveTerrainPalette(descriptor = {}) {
165
+ const preset = TERRAIN_MATERIAL_PALETTES[descriptor.material ?? DEFAULT_TERRAIN_MATERIAL] ?? TERRAIN_MATERIAL_PALETTES[DEFAULT_TERRAIN_MATERIAL];
166
+ const colors = descriptor.colors ?? {};
167
+ return {
168
+ low: colors.low ?? preset.low,
169
+ high: colors.high ?? preset.high,
170
+ waterline: colors.waterline ?? preset.waterline,
171
+ };
172
+ }
173
+ function withFlattenMasks(sampleHeight, masks) {
174
+ const resolved = masks.map((mask) => ({
175
+ center: mask.center,
176
+ radius: mask.radius,
177
+ target: mask.height ?? sampleHeight(mask.center[0], mask.center[1]),
178
+ falloff: mask.falloff ?? mask.radius * 0.5,
179
+ }));
180
+ return (x, z) => {
181
+ const base = sampleHeight(x, z);
182
+ let height = base;
183
+ for (const mask of resolved) {
184
+ const distance = Math.hypot(x - mask.center[0], z - mask.center[1]);
185
+ if (distance <= mask.radius) {
186
+ height = mask.target;
187
+ }
188
+ else if (distance <= mask.radius + mask.falloff) {
189
+ const t = smoothstep(mask.radius, mask.radius + mask.falloff, distance);
190
+ height = lerp(mask.target, base, t);
191
+ }
192
+ }
193
+ return height;
194
+ };
195
+ }
151
196
  export function resolveTerrainField(descriptor) {
152
197
  if (descriptor === undefined)
153
198
  return flatField();
154
- return noiseField({
199
+ const noise = noiseField({
155
200
  seed: descriptor.seed,
156
201
  amplitude: descriptor.height,
157
202
  frequency: descriptor.frequency,
@@ -161,6 +206,24 @@ export function resolveTerrainField(descriptor) {
161
206
  waterLevel: descriptor.waterLevel,
162
207
  bounds: descriptor.bounds,
163
208
  });
209
+ if (descriptor.flatten === undefined || descriptor.flatten.length === 0)
210
+ return noise;
211
+ return fieldFromHeight(withFlattenMasks(noise.sampleHeight, descriptor.flatten), {
212
+ bounds: noise.bounds,
213
+ waterLevel: noise.waterLevel,
214
+ });
215
+ }
216
+ /** Returns `position` with `y` replaced by the field's ground height (plus `offset`) at its `x`/`z`. */
217
+ export function snapToGround(field, position, offset = 0) {
218
+ const [x, , z] = position;
219
+ return [x, field.sampleHeight(x, z) + offset, z];
220
+ }
221
+ /** Ground-snaps an already-spawned entity in place; returns false when `id` is unknown. */
222
+ export function snapEntityToGround(entities, id, field, offset = 0) {
223
+ const entity = entities.get(id);
224
+ if (entity === null)
225
+ return false;
226
+ return entities.setPose(id, { position: snapToGround(field, entity.position, offset) });
164
227
  }
165
228
  export const DEFAULT_MAX_WALK_SLOPE = 0.6;
166
229
  export function resolveGroundStep(field, x, z, stepX, stepZ, maxSlope = DEFAULT_MAX_WALK_SLOPE) {
@@ -0,0 +1,54 @@
1
+ export type VoxelCoord = readonly [number, number, number];
2
+ export type VoxelFace = "px" | "nx" | "py" | "ny" | "pz" | "nz";
3
+ export interface VoxelFieldConfig {
4
+ chunkSize?: number;
5
+ }
6
+ export interface VoxelCell<T extends string = string> {
7
+ x: number;
8
+ y: number;
9
+ z: number;
10
+ type: T;
11
+ }
12
+ export interface VoxelHit<T extends string = string> {
13
+ x: number;
14
+ y: number;
15
+ z: number;
16
+ type: T;
17
+ face: VoxelFace;
18
+ adjacent: VoxelCoord;
19
+ distance: number;
20
+ }
21
+ export interface VoxelBounds {
22
+ min: VoxelCoord;
23
+ max: VoxelCoord;
24
+ }
25
+ export interface VoxelFieldSummary<T extends string = string> {
26
+ blocks: number;
27
+ types: Record<T, number>;
28
+ bounds: VoxelBounds | null;
29
+ }
30
+ export interface VoxelField<T extends string = string> {
31
+ set(x: number, y: number, z: number, type: T): boolean;
32
+ remove(x: number, y: number, z: number): boolean;
33
+ get(x: number, y: number, z: number): T | null;
34
+ has(x: number, y: number, z: number): boolean;
35
+ fill(min: VoxelCoord, max: VoxelCoord, type: T): number;
36
+ clear(): void;
37
+ count(): number;
38
+ cells(): IterableIterator<VoxelCell<T>>;
39
+ bounds(): VoxelBounds | null;
40
+ neighbors(x: number, y: number, z: number): VoxelCell<T>[];
41
+ exposedFaces(x: number, y: number, z: number): readonly VoxelFace[];
42
+ raycast(origin: VoxelCoord, direction: VoxelCoord, maxDistance: number): VoxelHit<T> | null;
43
+ chunkOf(x: number, y: number, z: number): {
44
+ cx: number;
45
+ cy: number;
46
+ cz: number;
47
+ };
48
+ chunkVersion(cx: number, cy: number, cz: number): number;
49
+ subscribe(listener: () => void): () => void;
50
+ summary(): VoxelFieldSummary<T>;
51
+ }
52
+ export declare const VOXEL_FACES: readonly VoxelFace[];
53
+ export declare const VOXEL_FACE_NORMALS: Record<VoxelFace, VoxelCoord>;
54
+ export declare function createVoxelField<T extends string = string>(config?: VoxelFieldConfig): VoxelField<T>;