@jgengine/core 0.5.0 → 0.7.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 (244) hide show
  1. package/CHANGELOG.md +191 -2
  2. package/dist/ai/crowd.d.ts +36 -0
  3. package/dist/ai/crowd.js +213 -0
  4. package/dist/ai/jobBoard.d.ts +49 -0
  5. package/dist/ai/jobBoard.js +155 -0
  6. package/dist/ai/spawnDirector.d.ts +56 -0
  7. package/dist/ai/spawnDirector.js +150 -0
  8. package/dist/ai/threat.d.ts +25 -0
  9. package/dist/ai/threat.js +67 -0
  10. package/dist/audio/audioFalloff.d.ts +38 -0
  11. package/dist/audio/audioFalloff.js +32 -0
  12. package/dist/board/laneBoard.d.ts +63 -0
  13. package/dist/board/laneBoard.js +114 -0
  14. package/dist/board/timelineBoard.d.ts +36 -0
  15. package/dist/board/timelineBoard.js +70 -0
  16. package/dist/cards/cardPile.d.ts +63 -0
  17. package/dist/cards/cardPile.js +166 -0
  18. package/dist/cards/modifierPipeline.d.ts +33 -0
  19. package/dist/cards/modifierPipeline.js +48 -0
  20. package/dist/combat/abilityKit.d.ts +42 -0
  21. package/dist/combat/abilityKit.js +129 -0
  22. package/dist/combat/animationState.d.ts +47 -0
  23. package/dist/combat/animationState.js +101 -0
  24. package/dist/combat/attackTags.d.ts +14 -0
  25. package/dist/combat/attackTags.js +26 -0
  26. package/dist/combat/breakMeters.d.ts +32 -0
  27. package/dist/combat/breakMeters.js +51 -0
  28. package/dist/combat/comboString.d.ts +35 -0
  29. package/dist/combat/comboString.js +59 -0
  30. package/dist/combat/death.d.ts +8 -0
  31. package/dist/combat/death.js +5 -2
  32. package/dist/combat/defensiveWindow.d.ts +34 -0
  33. package/dist/combat/defensiveWindow.js +66 -0
  34. package/dist/combat/downed.d.ts +35 -0
  35. package/dist/combat/downed.js +104 -0
  36. package/dist/combat/effects.d.ts +11 -0
  37. package/dist/combat/effects.js +22 -13
  38. package/dist/combat/hitReaction.d.ts +23 -0
  39. package/dist/combat/hitReaction.js +20 -0
  40. package/dist/combat/resistance.d.ts +13 -0
  41. package/dist/combat/resistance.js +46 -0
  42. package/dist/combat/telegraph.d.ts +27 -0
  43. package/dist/combat/telegraph.js +37 -0
  44. package/dist/crafting/crop.d.ts +59 -0
  45. package/dist/crafting/crop.js +181 -0
  46. package/dist/crafting/production.d.ts +64 -0
  47. package/dist/crafting/production.js +130 -0
  48. package/dist/crafting/recipe.d.ts +62 -0
  49. package/dist/crafting/recipe.js +101 -0
  50. package/dist/economy/sharedWallet.d.ts +28 -0
  51. package/dist/economy/sharedWallet.js +62 -0
  52. package/dist/economy/techTree.d.ts +40 -0
  53. package/dist/economy/techTree.js +66 -0
  54. package/dist/game/cosmetics.d.ts +25 -0
  55. package/dist/game/cosmetics.js +51 -0
  56. package/dist/game/defineGame.d.ts +5 -0
  57. package/dist/game/events.d.ts +62 -0
  58. package/dist/game/lootFilter.d.ts +33 -0
  59. package/dist/game/lootFilter.js +48 -0
  60. package/dist/game/ping.d.ts +67 -0
  61. package/dist/game/ping.js +86 -0
  62. package/dist/game/playableGame.d.ts +291 -5
  63. package/dist/game/race.d.ts +95 -0
  64. package/dist/game/race.js +216 -0
  65. package/dist/game/runDraft.d.ts +34 -0
  66. package/dist/game/runDraft.js +97 -0
  67. package/dist/game/social.d.ts +27 -0
  68. package/dist/game/social.js +16 -0
  69. package/dist/game/worldItem.d.ts +92 -0
  70. package/dist/game/worldItem.js +132 -0
  71. package/dist/input/axisInput.d.ts +43 -0
  72. package/dist/input/axisInput.js +70 -0
  73. package/dist/input/pointer.d.ts +24 -0
  74. package/dist/input/pointer.js +17 -0
  75. package/dist/interaction/contextMenu.d.ts +30 -0
  76. package/dist/interaction/contextMenu.js +22 -0
  77. package/dist/interaction/qte.d.ts +20 -0
  78. package/dist/interaction/qte.js +24 -0
  79. package/dist/interaction/skillCheck.d.ts +20 -0
  80. package/dist/interaction/skillCheck.js +26 -0
  81. package/dist/inventory/shapedGrid.d.ts +56 -0
  82. package/dist/inventory/shapedGrid.js +176 -0
  83. package/dist/inventory/storageTier.d.ts +52 -0
  84. package/dist/inventory/storageTier.js +77 -0
  85. package/dist/item/affix.d.ts +54 -0
  86. package/dist/item/affix.js +133 -0
  87. package/dist/item/durability.d.ts +51 -0
  88. package/dist/item/durability.js +78 -0
  89. package/dist/item/modularItem.d.ts +46 -0
  90. package/dist/item/modularItem.js +75 -0
  91. package/dist/meta/changelog.d.ts +1 -1
  92. package/dist/meta/changelog.js +53 -1
  93. package/dist/movement/dash.d.ts +37 -0
  94. package/dist/movement/dash.js +65 -0
  95. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  96. package/dist/multiplayer/combatSnapshot.js +89 -0
  97. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  98. package/dist/multiplayer/lagCompensation.js +154 -0
  99. package/dist/multiplayer/matchmaking.d.ts +33 -0
  100. package/dist/multiplayer/matchmaking.js +60 -0
  101. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  102. package/dist/multiplayer/simultaneousCommit.js +53 -0
  103. package/dist/nav/navGrid.d.ts +46 -0
  104. package/dist/nav/navGrid.js +248 -0
  105. package/dist/nav/pathFollow.d.ts +29 -0
  106. package/dist/nav/pathFollow.js +67 -0
  107. package/dist/physics/buoyancy.d.ts +49 -0
  108. package/dist/physics/buoyancy.js +112 -0
  109. package/dist/physics/carryable.d.ts +46 -0
  110. package/dist/physics/carryable.js +110 -0
  111. package/dist/physics/damageZones.d.ts +56 -0
  112. package/dist/physics/damageZones.js +107 -0
  113. package/dist/physics/forceVolume.d.ts +48 -0
  114. package/dist/physics/forceVolume.js +119 -0
  115. package/dist/physics/physicsWorld.d.ts +219 -0
  116. package/dist/physics/physicsWorld.js +749 -0
  117. package/dist/physics/ragdoll.d.ts +49 -0
  118. package/dist/physics/ragdoll.js +103 -0
  119. package/dist/physics/spatialGrid.d.ts +41 -0
  120. package/dist/physics/spatialGrid.js +138 -0
  121. package/dist/physics/structure.d.ts +83 -0
  122. package/dist/physics/structure.js +0 -0
  123. package/dist/physics/traversal.d.ts +75 -0
  124. package/dist/physics/traversal.js +138 -0
  125. package/dist/physics/vehicleBody.d.ts +93 -0
  126. package/dist/physics/vehicleBody.js +234 -0
  127. package/dist/runtime/gameContext.d.ts +66 -2
  128. package/dist/runtime/gameContext.js +157 -4
  129. package/dist/runtime/hostPersistence.d.ts +18 -0
  130. package/dist/runtime/hostPersistence.js +4 -0
  131. package/dist/runtime/persistenceScope.d.ts +32 -0
  132. package/dist/runtime/persistenceScope.js +64 -0
  133. package/dist/scene/assetCatalog.d.ts +13 -0
  134. package/dist/scene/autoTarget.d.ts +15 -0
  135. package/dist/scene/autoTarget.js +54 -0
  136. package/dist/scene/behaviors.d.ts +13 -1
  137. package/dist/scene/behaviors.js +3 -0
  138. package/dist/scene/captureCheck.d.ts +7 -0
  139. package/dist/scene/captureCheck.js +11 -0
  140. package/dist/scene/entityStore.d.ts +7 -1
  141. package/dist/scene/entityStore.js +15 -1
  142. package/dist/scene/form.d.ts +40 -0
  143. package/dist/scene/form.js +56 -0
  144. package/dist/scene/mount.d.ts +62 -0
  145. package/dist/scene/mount.js +107 -0
  146. package/dist/scene/possession.d.ts +30 -0
  147. package/dist/scene/possession.js +51 -0
  148. package/dist/scene/roster.d.ts +25 -0
  149. package/dist/scene/roster.js +57 -0
  150. package/dist/scene/selection.d.ts +30 -0
  151. package/dist/scene/selection.js +38 -0
  152. package/dist/scene/stationClaim.d.ts +43 -0
  153. package/dist/scene/stationClaim.js +109 -0
  154. package/dist/sensor/frustumSensor.d.ts +60 -0
  155. package/dist/sensor/frustumSensor.js +104 -0
  156. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  157. package/dist/sensor/hiddenStateProbe.js +40 -0
  158. package/dist/sensor/recordingBuffer.d.ts +29 -0
  159. package/dist/sensor/recordingBuffer.js +61 -0
  160. package/dist/sensor/revealQuery.d.ts +22 -0
  161. package/dist/sensor/revealQuery.js +36 -0
  162. package/dist/session/contestedChannel.d.ts +36 -0
  163. package/dist/session/contestedChannel.js +100 -0
  164. package/dist/session/extraction.d.ts +54 -0
  165. package/dist/session/extraction.js +108 -0
  166. package/dist/session/ring.d.ts +36 -0
  167. package/dist/session/ring.js +88 -0
  168. package/dist/session/roundState.d.ts +55 -0
  169. package/dist/session/roundState.js +129 -0
  170. package/dist/stats/accumulatorMeter.d.ts +31 -0
  171. package/dist/stats/accumulatorMeter.js +81 -0
  172. package/dist/stats/eventMeter.d.ts +34 -0
  173. package/dist/stats/eventMeter.js +76 -0
  174. package/dist/stats/rollCheck.d.ts +15 -0
  175. package/dist/stats/rollCheck.js +15 -0
  176. package/dist/survival/decayMeter.d.ts +59 -0
  177. package/dist/survival/decayMeter.js +95 -0
  178. package/dist/survival/moodle.d.ts +48 -0
  179. package/dist/survival/moodle.js +91 -0
  180. package/dist/survival/regionHealth.d.ts +72 -0
  181. package/dist/survival/regionHealth.js +129 -0
  182. package/dist/tactics/predictiveQuery.d.ts +34 -0
  183. package/dist/tactics/predictiveQuery.js +44 -0
  184. package/dist/tactics/snapshot.d.ts +18 -0
  185. package/dist/tactics/snapshot.js +65 -0
  186. package/dist/tactics/surface.d.ts +52 -0
  187. package/dist/tactics/surface.js +135 -0
  188. package/dist/tactics/tacticalGrid.d.ts +57 -0
  189. package/dist/tactics/tacticalGrid.js +244 -0
  190. package/dist/time/beatClock.d.ts +40 -0
  191. package/dist/time/beatClock.js +73 -0
  192. package/dist/time/simClock.d.ts +62 -0
  193. package/dist/time/simClock.js +132 -0
  194. package/dist/turn/commit.d.ts +27 -0
  195. package/dist/turn/commit.js +72 -0
  196. package/dist/turn/turnLoop.d.ts +58 -0
  197. package/dist/turn/turnLoop.js +216 -0
  198. package/dist/world/buildPermissions.d.ts +47 -0
  199. package/dist/world/buildPermissions.js +134 -0
  200. package/dist/world/buildingIndex.d.ts +16 -0
  201. package/dist/world/buildingIndex.js +63 -0
  202. package/dist/world/buildings.d.ts +98 -0
  203. package/dist/world/buildings.js +320 -0
  204. package/dist/world/carve.d.ts +105 -0
  205. package/dist/world/carve.js +175 -0
  206. package/dist/world/connectors.d.ts +40 -0
  207. package/dist/world/connectors.js +67 -0
  208. package/dist/world/envField.d.ts +71 -0
  209. package/dist/world/envField.js +96 -0
  210. package/dist/world/features.d.ts +105 -1
  211. package/dist/world/features.js +95 -0
  212. package/dist/world/fog.d.ts +40 -0
  213. package/dist/world/fog.js +137 -0
  214. package/dist/world/markers.d.ts +52 -0
  215. package/dist/world/markers.js +96 -0
  216. package/dist/world/minimap.d.ts +42 -0
  217. package/dist/world/minimap.js +72 -0
  218. package/dist/world/placedStructureStore.d.ts +39 -0
  219. package/dist/world/placedStructureStore.js +95 -0
  220. package/dist/world/placementController.d.ts +48 -0
  221. package/dist/world/placementController.js +97 -0
  222. package/dist/world/realm.d.ts +61 -0
  223. package/dist/world/realm.js +66 -0
  224. package/dist/world/regions.d.ts +66 -0
  225. package/dist/world/regions.js +158 -0
  226. package/dist/world/scatter.d.ts +24 -0
  227. package/dist/world/scatter.js +126 -0
  228. package/dist/world/scatterItems.d.ts +38 -0
  229. package/dist/world/scatterItems.js +85 -0
  230. package/dist/world/support.d.ts +27 -0
  231. package/dist/world/support.js +75 -0
  232. package/dist/world/terraform.d.ts +54 -0
  233. package/dist/world/terraform.js +186 -0
  234. package/dist/world/terrain.d.ts +54 -0
  235. package/dist/world/terrain.js +181 -0
  236. package/dist/world/walls.d.ts +61 -0
  237. package/dist/world/walls.js +221 -0
  238. package/dist/world/water.d.ts +27 -0
  239. package/dist/world/water.js +125 -0
  240. package/dist/world/weather.d.ts +92 -0
  241. package/dist/world/weather.js +169 -0
  242. package/dist/world/wind.d.ts +17 -0
  243. package/dist/world/wind.js +40 -0
  244. package/package.json +1 -1
@@ -0,0 +1,16 @@
1
+ import { type Aabb, type Vec2 } from "./geometry.js";
2
+ import type { GeneratedBuilding } from "./buildings.js";
3
+ export interface BuildingHit {
4
+ building: GeneratedBuilding;
5
+ distance: number;
6
+ }
7
+ export interface BuildingIndex {
8
+ readonly buildings: readonly GeneratedBuilding[];
9
+ at(point: Vec2): GeneratedBuilding | undefined;
10
+ within(area: Aabb): GeneratedBuilding[];
11
+ nearest(point: Vec2): BuildingHit | undefined;
12
+ isInside(point: Vec2): boolean;
13
+ blockers(margin?: number): Aabb[];
14
+ readonly bounds: Aabb | undefined;
15
+ }
16
+ export declare function buildingIndex(buildings: readonly GeneratedBuilding[]): BuildingIndex;
@@ -0,0 +1,63 @@
1
+ import { aabbOverlap, expandAabb, pointInAabb } from "./geometry.js";
2
+ function centerDistance(point, center) {
3
+ const dx = point[0] - center[0];
4
+ const dz = point[1] - center[1];
5
+ return Math.sqrt(dx * dx + dz * dz);
6
+ }
7
+ function unionBounds(buildings) {
8
+ if (buildings.length === 0)
9
+ return undefined;
10
+ let { minX, minZ, maxX, maxZ } = buildings[0].bounds;
11
+ for (let index = 1; index < buildings.length; index += 1) {
12
+ const b = buildings[index].bounds;
13
+ if (b.minX < minX)
14
+ minX = b.minX;
15
+ if (b.minZ < minZ)
16
+ minZ = b.minZ;
17
+ if (b.maxX > maxX)
18
+ maxX = b.maxX;
19
+ if (b.maxZ > maxZ)
20
+ maxZ = b.maxZ;
21
+ }
22
+ return { minX, minZ, maxX, maxZ };
23
+ }
24
+ export function buildingIndex(buildings) {
25
+ const bounds = unionBounds(buildings);
26
+ function at(point) {
27
+ let best;
28
+ let bestDistance = Infinity;
29
+ for (const building of buildings) {
30
+ if (!pointInAabb(point, building.bounds))
31
+ continue;
32
+ const distance = centerDistance(point, building.center);
33
+ if (distance < bestDistance) {
34
+ bestDistance = distance;
35
+ best = building;
36
+ }
37
+ }
38
+ return best;
39
+ }
40
+ return {
41
+ buildings,
42
+ bounds,
43
+ at,
44
+ within(area) {
45
+ return buildings.filter((building) => aabbOverlap(building.bounds, area));
46
+ },
47
+ nearest(point) {
48
+ let best;
49
+ for (const building of buildings) {
50
+ const distance = centerDistance(point, building.center);
51
+ if (best === undefined || distance < best.distance)
52
+ best = { building, distance };
53
+ }
54
+ return best;
55
+ },
56
+ isInside(point) {
57
+ return at(point) !== undefined;
58
+ },
59
+ blockers(margin = 0) {
60
+ return buildings.map((building) => margin === 0 ? building.bounds : expandAabb(building.bounds, margin));
61
+ },
62
+ };
63
+ }
@@ -0,0 +1,98 @@
1
+ import { type Aabb, type Footprint, type Vec2 } from "./geometry.js";
2
+ export type BuildingSeed = number | string;
3
+ export type Vec3 = readonly [number, number, number];
4
+ export type BuildingFacade = "front" | "back" | "left" | "right" | "roof";
5
+ export type BuildingPartKind = "wall" | "window" | "awning" | "airConditioner" | "clothesline" | "storefront" | "shutter" | "storeSign" | "roof" | "roofProp" | "guardrail" | "corner";
6
+ export type BuildingPartTag = "ground" | "upper" | "open" | "closed" | "decor" | "structure";
7
+ export interface BuildingProbabilities {
8
+ window: number;
9
+ openStore: number;
10
+ awning: number;
11
+ airConditioner: number;
12
+ clothesline: number;
13
+ storeSign: number;
14
+ roofProp: number;
15
+ }
16
+ export interface BuildingVariantCounts {
17
+ wall: number;
18
+ window: number;
19
+ awning: number;
20
+ airConditioner: number;
21
+ clothesline: number;
22
+ storefront: number;
23
+ shutter: number;
24
+ storeSign: number;
25
+ roofProp: number;
26
+ }
27
+ export interface BuildingConfig {
28
+ id: string;
29
+ seed: BuildingSeed;
30
+ center: Vec2;
31
+ floors: number;
32
+ baysWide: number;
33
+ baysDeep: number;
34
+ bayWidth: number;
35
+ floorHeight: number;
36
+ facadeDepth: number;
37
+ roofOverhang: number;
38
+ probabilities: BuildingProbabilities;
39
+ variants: BuildingVariantCounts;
40
+ }
41
+ export type BuildingConfigInput = Partial<Omit<BuildingConfig, "probabilities" | "variants">> & {
42
+ probabilities?: Partial<BuildingProbabilities>;
43
+ variants?: Partial<BuildingVariantCounts>;
44
+ };
45
+ export interface BuildingKitSlot {
46
+ key: string;
47
+ variant: number;
48
+ }
49
+ export interface BuildingCellRef {
50
+ facade: BuildingFacade;
51
+ level: number;
52
+ bay: number;
53
+ }
54
+ export interface BuildingPartPlacement {
55
+ id: string;
56
+ buildingId: string;
57
+ kind: BuildingPartKind;
58
+ facade: BuildingFacade;
59
+ position: Vec3;
60
+ rotationY: number;
61
+ scale: Vec3;
62
+ kit: BuildingKitSlot;
63
+ cell?: BuildingCellRef;
64
+ tags?: readonly BuildingPartTag[];
65
+ }
66
+ export interface GeneratedBuilding {
67
+ id: string;
68
+ seed: BuildingSeed;
69
+ center: Vec2;
70
+ footprint: Footprint;
71
+ floors: number;
72
+ floorHeight: number;
73
+ bounds: Aabb;
74
+ parts: readonly BuildingPartPlacement[];
75
+ }
76
+ export interface BuildingLot {
77
+ id: string;
78
+ center: Vec2;
79
+ footprint: Footprint;
80
+ quarterTurns: number;
81
+ config: BuildingConfig;
82
+ }
83
+ export interface BuildingGridConfig {
84
+ rows: number;
85
+ columns: number;
86
+ origin?: Vec2;
87
+ lotSize?: Footprint;
88
+ streetWidth?: number;
89
+ seed?: BuildingSeed;
90
+ idPrefix?: string;
91
+ base?: BuildingConfigInput;
92
+ floorRange?: readonly [number, number];
93
+ }
94
+ export declare const DEFAULT_BUILDING_CONFIG: BuildingConfig;
95
+ export declare function createBuildingConfig(input?: BuildingConfigInput): BuildingConfig;
96
+ export declare function generateBuilding(input?: BuildingConfigInput): GeneratedBuilding;
97
+ export declare function createBuildingGrid(config: BuildingGridConfig): BuildingLot[];
98
+ export declare function generateBuildingDistrict(config: BuildingGridConfig): GeneratedBuilding[];
@@ -0,0 +1,320 @@
1
+ import { footprintAabb } from "./geometry.js";
2
+ const DEFAULT_PROBABILITIES = {
3
+ window: 0.88,
4
+ openStore: 0.58,
5
+ awning: 0.45,
6
+ airConditioner: 0.42,
7
+ clothesline: 0.28,
8
+ storeSign: 0.62,
9
+ roofProp: 0.34,
10
+ };
11
+ const DEFAULT_VARIANTS = {
12
+ wall: 6,
13
+ window: 4,
14
+ awning: 3,
15
+ airConditioner: 3,
16
+ clothesline: 3,
17
+ storefront: 4,
18
+ shutter: 3,
19
+ storeSign: 5,
20
+ roofProp: 5,
21
+ };
22
+ export const DEFAULT_BUILDING_CONFIG = {
23
+ id: "building",
24
+ seed: 0,
25
+ center: [0, 0],
26
+ floors: 6,
27
+ baysWide: 6,
28
+ baysDeep: 3,
29
+ bayWidth: 2,
30
+ floorHeight: 2.8,
31
+ facadeDepth: 0.18,
32
+ roofOverhang: 0.35,
33
+ probabilities: DEFAULT_PROBABILITIES,
34
+ variants: DEFAULT_VARIANTS,
35
+ };
36
+ function hashU32(input) {
37
+ let hash = 2166136261;
38
+ for (let index = 0; index < input.length; index += 1) {
39
+ hash ^= input.charCodeAt(index);
40
+ hash = Math.imul(hash, 16777619);
41
+ }
42
+ hash ^= hash >>> 16;
43
+ hash = Math.imul(hash, 2246822507);
44
+ hash ^= hash >>> 13;
45
+ hash = Math.imul(hash, 3266489909);
46
+ hash ^= hash >>> 16;
47
+ return hash >>> 0;
48
+ }
49
+ function hash01(seed, key) {
50
+ return hashU32(`${seed}:${key}`) / 4294967296;
51
+ }
52
+ function probability(value) {
53
+ if (!Number.isFinite(value))
54
+ return 0;
55
+ return Math.min(Math.max(value, 0), 1);
56
+ }
57
+ function positiveInt(value, fallback) {
58
+ if (!Number.isFinite(value))
59
+ return fallback;
60
+ return Math.max(1, Math.floor(value));
61
+ }
62
+ function positiveNumber(value, fallback) {
63
+ return Number.isFinite(value) && value > 0 ? value : fallback;
64
+ }
65
+ function variantCount(value) {
66
+ return positiveInt(value, 1);
67
+ }
68
+ function variant(seed, key, count) {
69
+ return Math.floor(hash01(seed, key) * variantCount(count));
70
+ }
71
+ function chance(seed, key, p) {
72
+ return hash01(seed, key) < probability(p);
73
+ }
74
+ function rangeInt(seed, key, min, max) {
75
+ const lo = Math.min(min, max);
76
+ const hi = Math.max(min, max);
77
+ return lo + Math.floor(hash01(seed, key) * (hi - lo + 1));
78
+ }
79
+ export function createBuildingConfig(input = {}) {
80
+ return {
81
+ id: input.id ?? DEFAULT_BUILDING_CONFIG.id,
82
+ seed: input.seed ?? DEFAULT_BUILDING_CONFIG.seed,
83
+ center: input.center ?? DEFAULT_BUILDING_CONFIG.center,
84
+ floors: positiveInt(input.floors ?? DEFAULT_BUILDING_CONFIG.floors, DEFAULT_BUILDING_CONFIG.floors),
85
+ baysWide: positiveInt(input.baysWide ?? DEFAULT_BUILDING_CONFIG.baysWide, DEFAULT_BUILDING_CONFIG.baysWide),
86
+ baysDeep: positiveInt(input.baysDeep ?? DEFAULT_BUILDING_CONFIG.baysDeep, DEFAULT_BUILDING_CONFIG.baysDeep),
87
+ bayWidth: positiveNumber(input.bayWidth ?? DEFAULT_BUILDING_CONFIG.bayWidth, DEFAULT_BUILDING_CONFIG.bayWidth),
88
+ floorHeight: positiveNumber(input.floorHeight ?? DEFAULT_BUILDING_CONFIG.floorHeight, DEFAULT_BUILDING_CONFIG.floorHeight),
89
+ facadeDepth: positiveNumber(input.facadeDepth ?? DEFAULT_BUILDING_CONFIG.facadeDepth, DEFAULT_BUILDING_CONFIG.facadeDepth),
90
+ roofOverhang: Math.max(0, input.roofOverhang ?? DEFAULT_BUILDING_CONFIG.roofOverhang),
91
+ probabilities: {
92
+ window: probability(input.probabilities?.window ?? DEFAULT_PROBABILITIES.window),
93
+ openStore: probability(input.probabilities?.openStore ?? DEFAULT_PROBABILITIES.openStore),
94
+ awning: probability(input.probabilities?.awning ?? DEFAULT_PROBABILITIES.awning),
95
+ airConditioner: probability(input.probabilities?.airConditioner ?? DEFAULT_PROBABILITIES.airConditioner),
96
+ clothesline: probability(input.probabilities?.clothesline ?? DEFAULT_PROBABILITIES.clothesline),
97
+ storeSign: probability(input.probabilities?.storeSign ?? DEFAULT_PROBABILITIES.storeSign),
98
+ roofProp: probability(input.probabilities?.roofProp ?? DEFAULT_PROBABILITIES.roofProp),
99
+ },
100
+ variants: {
101
+ wall: variantCount(input.variants?.wall ?? DEFAULT_VARIANTS.wall),
102
+ window: variantCount(input.variants?.window ?? DEFAULT_VARIANTS.window),
103
+ awning: variantCount(input.variants?.awning ?? DEFAULT_VARIANTS.awning),
104
+ airConditioner: variantCount(input.variants?.airConditioner ?? DEFAULT_VARIANTS.airConditioner),
105
+ clothesline: variantCount(input.variants?.clothesline ?? DEFAULT_VARIANTS.clothesline),
106
+ storefront: variantCount(input.variants?.storefront ?? DEFAULT_VARIANTS.storefront),
107
+ shutter: variantCount(input.variants?.shutter ?? DEFAULT_VARIANTS.shutter),
108
+ storeSign: variantCount(input.variants?.storeSign ?? DEFAULT_VARIANTS.storeSign),
109
+ roofProp: variantCount(input.variants?.roofProp ?? DEFAULT_VARIANTS.roofProp),
110
+ },
111
+ };
112
+ }
113
+ function faceRotation(facade) {
114
+ if (facade === "front")
115
+ return Math.PI;
116
+ if (facade === "left")
117
+ return Math.PI / 2;
118
+ if (facade === "right")
119
+ return -Math.PI / 2;
120
+ return 0;
121
+ }
122
+ function faceDepth(facade, width, depth, config) {
123
+ if (facade === "front")
124
+ return config.center[1] + depth / 2 + config.facadeDepth / 2;
125
+ if (facade === "back")
126
+ return config.center[1] - depth / 2 - config.facadeDepth / 2;
127
+ if (facade === "left")
128
+ return config.center[0] - width / 2 - config.facadeDepth / 2;
129
+ return config.center[0] + width / 2 + config.facadeDepth / 2;
130
+ }
131
+ function panelPosition(config, facade, bay, level, width, depth) {
132
+ const y = level * config.floorHeight + config.floorHeight / 2;
133
+ if (facade === "front" || facade === "back") {
134
+ const x = config.center[0] - width / 2 + config.bayWidth * (bay + 0.5);
135
+ return [x, y, faceDepth(facade, width, depth, config)];
136
+ }
137
+ const z = config.center[1] - depth / 2 + config.bayWidth * (bay + 0.5);
138
+ return [faceDepth(facade, width, depth, config), y, z];
139
+ }
140
+ function panelScale(config, _facade, heightRatio, widthRatio = 0.92) {
141
+ const along = config.bayWidth * widthRatio;
142
+ const high = config.floorHeight * heightRatio;
143
+ return [along, high, config.facadeDepth];
144
+ }
145
+ function part(config, kind, facade, position, scale, key, cell, tags) {
146
+ const selectedVariant = kind === "airConditioner"
147
+ ? variant(config.seed, `${key}:variant`, config.variants.airConditioner)
148
+ : kind === "roofProp"
149
+ ? variant(config.seed, `${key}:variant`, config.variants.roofProp)
150
+ : kind === "storefront"
151
+ ? variant(config.seed, `${key}:variant`, config.variants.storefront)
152
+ : kind === "shutter"
153
+ ? variant(config.seed, `${key}:variant`, config.variants.shutter)
154
+ : kind === "storeSign"
155
+ ? variant(config.seed, `${key}:variant`, config.variants.storeSign)
156
+ : kind === "awning"
157
+ ? variant(config.seed, `${key}:variant`, config.variants.awning)
158
+ : kind === "clothesline"
159
+ ? variant(config.seed, `${key}:variant`, config.variants.clothesline)
160
+ : kind === "window"
161
+ ? variant(config.seed, `${key}:variant`, config.variants.window)
162
+ : kind === "wall"
163
+ ? variant(config.seed, `${key}:variant`, config.variants.wall)
164
+ : 0;
165
+ return {
166
+ id: `${config.id}:${key}:${kind}`,
167
+ buildingId: config.id,
168
+ kind,
169
+ facade,
170
+ position,
171
+ rotationY: faceRotation(facade),
172
+ scale,
173
+ kit: { key: `${facade}.${kind}`, variant: selectedVariant },
174
+ ...(cell === undefined ? {} : { cell }),
175
+ ...(tags === undefined ? {} : { tags }),
176
+ };
177
+ }
178
+ function pushUpperFloorParts(parts, config, facade, bay, level, width, depth) {
179
+ const cell = { facade, level, bay };
180
+ const key = `${facade}:${level}:${bay}`;
181
+ const position = panelPosition(config, facade, bay, level, width, depth);
182
+ parts.push(part(config, "wall", facade, position, panelScale(config, facade, 0.92), `${key}:wall`, cell, ["structure", "upper"]));
183
+ if (!chance(config.seed, `${key}:window`, config.probabilities.window))
184
+ return;
185
+ parts.push(part(config, "window", facade, position, panelScale(config, facade, 0.46, 0.58), `${key}:window`, cell, ["upper"]));
186
+ if (chance(config.seed, `${key}:awning`, config.probabilities.awning)) {
187
+ const awningPosition = [position[0], position[1] + config.floorHeight * 0.29, position[2]];
188
+ parts.push(part(config, "awning", facade, awningPosition, panelScale(config, facade, 0.1, 0.72), `${key}:awning`, cell, ["decor", "upper"]));
189
+ }
190
+ if (chance(config.seed, `${key}:ac`, config.probabilities.airConditioner)) {
191
+ const acPosition = [position[0], position[1] - config.floorHeight * 0.25, position[2]];
192
+ parts.push(part(config, "airConditioner", facade, acPosition, panelScale(config, facade, 0.16, 0.24), `${key}:ac`, cell, ["decor", "upper"]));
193
+ }
194
+ if (chance(config.seed, `${key}:clothesline`, config.probabilities.clothesline)) {
195
+ const linePosition = [position[0], position[1] - config.floorHeight * 0.36, position[2]];
196
+ parts.push(part(config, "clothesline", facade, linePosition, panelScale(config, facade, 0.04, 0.72), `${key}:line`, cell, ["decor", "upper"]));
197
+ }
198
+ }
199
+ function pushGroundFloorParts(parts, config, facade, bay, width, depth) {
200
+ const cell = { facade, level: 0, bay };
201
+ const key = `${facade}:0:${bay}`;
202
+ const position = panelPosition(config, facade, bay, 0, width, depth);
203
+ if (facade === "front" && chance(config.seed, `${key}:open`, config.probabilities.openStore)) {
204
+ parts.push(part(config, "storefront", facade, position, panelScale(config, facade, 0.86), `${key}:store`, cell, ["ground", "open"]));
205
+ if (chance(config.seed, `${key}:sign`, config.probabilities.storeSign)) {
206
+ const signPosition = [position[0], position[1] + config.floorHeight * 0.34, position[2]];
207
+ parts.push(part(config, "storeSign", facade, signPosition, panelScale(config, facade, 0.14, 0.76), `${key}:sign`, cell, ["decor", "ground"]));
208
+ }
209
+ return;
210
+ }
211
+ const kind = facade === "front" ? "shutter" : "wall";
212
+ parts.push(part(config, kind, facade, position, panelScale(config, facade, 0.86), `${key}:${kind}`, cell, ["ground", "closed"]));
213
+ }
214
+ function pushFacade(parts, config, facade, width, depth) {
215
+ const bays = facade === "front" || facade === "back" ? config.baysWide : config.baysDeep;
216
+ for (let bay = 0; bay < bays; bay += 1) {
217
+ pushGroundFloorParts(parts, config, facade, bay, width, depth);
218
+ for (let level = 1; level < config.floors; level += 1) {
219
+ pushUpperFloorParts(parts, config, facade, bay, level, width, depth);
220
+ }
221
+ }
222
+ }
223
+ function pushRoof(parts, config, width, depth) {
224
+ const height = config.floors * config.floorHeight;
225
+ parts.push(part(config, "roof", "roof", [config.center[0], height + config.facadeDepth / 2, config.center[1]], [width + config.roofOverhang * 2, config.facadeDepth, depth + config.roofOverhang * 2], "roof:slab", { facade: "roof", level: config.floors, bay: 0 }, ["structure"]));
226
+ const edges = ["front", "back", "left", "right"];
227
+ for (const facade of edges) {
228
+ const position = facade === "front" || facade === "back"
229
+ ? [config.center[0], height + config.floorHeight * 0.18, faceDepth(facade, width, depth, config)]
230
+ : [faceDepth(facade, width, depth, config), height + config.floorHeight * 0.18, config.center[1]];
231
+ const scale = facade === "front" || facade === "back"
232
+ ? [width + config.roofOverhang * 2, config.floorHeight * 0.28, config.facadeDepth]
233
+ : [depth + config.roofOverhang * 2, config.floorHeight * 0.28, config.facadeDepth];
234
+ parts.push(part(config, "guardrail", facade, position, scale, `roof:${facade}:rail`, undefined, ["structure"]));
235
+ }
236
+ const roofCells = config.baysWide * config.baysDeep;
237
+ for (let index = 0; index < roofCells; index += 1) {
238
+ if (!chance(config.seed, `roof:prop:${index}`, config.probabilities.roofProp))
239
+ continue;
240
+ const x = config.center[0] - width / 2 + config.bayWidth * ((index % config.baysWide) + 0.5);
241
+ const z = config.center[1] - depth / 2 + config.bayWidth * (Math.floor(index / config.baysWide) + 0.5);
242
+ parts.push(part(config, "roofProp", "roof", [x, height + config.facadeDepth + config.floorHeight * 0.16, z], [config.bayWidth * 0.34, config.floorHeight * 0.32, config.bayWidth * 0.34], `roof:prop:${index}`, { facade: "roof", level: config.floors, bay: index }, ["decor"]));
243
+ }
244
+ }
245
+ function pushCorners(parts, config, width, depth) {
246
+ const height = config.floors * config.floorHeight;
247
+ const positions = [
248
+ [config.center[0] - width / 2, height / 2, config.center[1] - depth / 2],
249
+ [config.center[0] + width / 2, height / 2, config.center[1] - depth / 2],
250
+ [config.center[0] - width / 2, height / 2, config.center[1] + depth / 2],
251
+ [config.center[0] + width / 2, height / 2, config.center[1] + depth / 2],
252
+ ];
253
+ positions.forEach((position, index) => {
254
+ parts.push(part(config, "corner", index < 2 ? "back" : "front", position, [config.facadeDepth * 1.35, height, config.facadeDepth * 1.35], `corner:${index}`, undefined, ["structure"]));
255
+ });
256
+ }
257
+ export function generateBuilding(input = {}) {
258
+ const config = createBuildingConfig(input);
259
+ const width = config.baysWide * config.bayWidth;
260
+ const depth = config.baysDeep * config.bayWidth;
261
+ const footprint = { w: width + config.roofOverhang * 2, d: depth + config.roofOverhang * 2 };
262
+ const parts = [];
263
+ pushFacade(parts, config, "front", width, depth);
264
+ pushFacade(parts, config, "back", width, depth);
265
+ pushFacade(parts, config, "left", width, depth);
266
+ pushFacade(parts, config, "right", width, depth);
267
+ pushCorners(parts, config, width, depth);
268
+ pushRoof(parts, config, width, depth);
269
+ return {
270
+ id: config.id,
271
+ seed: config.seed,
272
+ center: config.center,
273
+ footprint,
274
+ floors: config.floors,
275
+ floorHeight: config.floorHeight,
276
+ bounds: footprintAabb(config.center, footprint),
277
+ parts,
278
+ };
279
+ }
280
+ export function createBuildingGrid(config) {
281
+ const rows = positiveInt(config.rows, 1);
282
+ const columns = positiveInt(config.columns, 1);
283
+ const lotSize = config.lotSize ?? { w: 14, d: 10 };
284
+ const streetWidth = Math.max(0, config.streetWidth ?? 4);
285
+ const origin = config.origin ?? [0, 0];
286
+ const seed = config.seed ?? 0;
287
+ const idPrefix = config.idPrefix ?? "building";
288
+ const lots = [];
289
+ for (let row = 0; row < rows; row += 1) {
290
+ for (let column = 0; column < columns; column += 1) {
291
+ const index = row * columns + column;
292
+ const id = `${idPrefix}-${row}-${column}`;
293
+ const center = [
294
+ origin[0] + column * (lotSize.w + streetWidth),
295
+ origin[1] + row * (lotSize.d + streetWidth),
296
+ ];
297
+ const floorRange = config.floorRange ?? [3, 9];
298
+ const floors = rangeInt(seed, `${id}:floors`, floorRange[0], floorRange[1]);
299
+ const base = config.base ?? {};
300
+ const bayWidth = positiveNumber(base.bayWidth ?? DEFAULT_BUILDING_CONFIG.bayWidth, DEFAULT_BUILDING_CONFIG.bayWidth);
301
+ const baysWide = Math.max(1, Math.floor(lotSize.w / bayWidth));
302
+ const baysDeep = Math.max(1, Math.floor(lotSize.d / bayWidth));
303
+ const buildingConfig = createBuildingConfig({
304
+ ...base,
305
+ id,
306
+ seed: `${seed}:${index}`,
307
+ center,
308
+ floors,
309
+ baysWide: base.baysWide ?? baysWide,
310
+ baysDeep: base.baysDeep ?? baysDeep,
311
+ bayWidth,
312
+ });
313
+ lots.push({ id, center, footprint: lotSize, quarterTurns: 0, config: buildingConfig });
314
+ }
315
+ }
316
+ return lots;
317
+ }
318
+ export function generateBuildingDistrict(config) {
319
+ return createBuildingGrid(config).map((lot) => generateBuilding(lot.config));
320
+ }
@@ -0,0 +1,105 @@
1
+ import { type TerrainField } from "./terrain.js";
2
+ import type { WorldBounds } from "./features.js";
3
+ export declare const EMPTY_VOXEL = 0;
4
+ export interface VoxelMaterial {
5
+ /** Cell value stored in the grid; 0 is reserved for empty. */
6
+ id: number;
7
+ name: string;
8
+ /** A carve tool's strength must reach this to remove the material — hard rock resists a weak drill. Default 0 (anything digs it). */
9
+ strength: number;
10
+ /** Debris/harvest hint carried through to game code; the engine only tallies removed cells by id. */
11
+ drop?: string;
12
+ }
13
+ export type VoxelMaterialTable = Readonly<Record<number, VoxelMaterial>>;
14
+ export interface VoxelVolumeConfig {
15
+ /** Grid resolution in cells: [nx, ny, nz]. */
16
+ dims: readonly [number, number, number];
17
+ /** World position of the min corner of cell [0,0,0]. Default [0,0,0]. */
18
+ origin?: readonly [number, number, number];
19
+ /** World-space edge length of one cubic cell. Default 1. */
20
+ scale?: number;
21
+ /** Material id every cell starts as. Default 1 (a solid volume waiting to be dug). */
22
+ fill?: number;
23
+ /** Strength lookup by material id; carve honours it against a tool strength. */
24
+ materials?: VoxelMaterialTable;
25
+ }
26
+ export interface CarveOp {
27
+ /** Sphere centre in world coordinates. */
28
+ center: readonly [number, number, number];
29
+ /** Sphere radius in world units. */
30
+ radius: number;
31
+ /** Only cells whose material `strength` is ≤ this are removed. Default Infinity (dig anything). */
32
+ toolStrength?: number;
33
+ }
34
+ export interface DepositOp {
35
+ center: readonly [number, number, number];
36
+ radius: number;
37
+ /** Material id to fill empty cells with (Engineer platform / Astroneer terrain add). */
38
+ material: number;
39
+ }
40
+ /**
41
+ * A runtime-editable dense voxel grid — the carve/deposit op behind destructible dig worlds (Deep
42
+ * Rock Galactic tunnels, Astroneer terrain). Cells hold a material id (0 = empty); `carve` clears a
43
+ * sphere of solid cells that a tool is strong enough to break and returns how many it removed (feed
44
+ * that to a loot roll), `deposit` fills a sphere with a material. World↔cell mapping is `origin`+`scale`.
45
+ */
46
+ export declare class VoxelVolume {
47
+ readonly nx: number;
48
+ readonly ny: number;
49
+ readonly nz: number;
50
+ readonly scale: number;
51
+ readonly origin: readonly [number, number, number];
52
+ private readonly cells;
53
+ private readonly materials;
54
+ private solidCount;
55
+ constructor(config: VoxelVolumeConfig);
56
+ get solid(): number;
57
+ private index;
58
+ inBounds(ix: number, iy: number, iz: number): boolean;
59
+ get(ix: number, iy: number, iz: number): number;
60
+ set(ix: number, iy: number, iz: number, material: number): void;
61
+ worldToCell(x: number, y: number, z: number): readonly [number, number, number];
62
+ solidAtWorld(x: number, y: number, z: number): boolean;
63
+ private strengthOf;
64
+ private forEachCellInSphere;
65
+ /** Clear every solid cell in the sphere the tool can break; returns the number removed. */
66
+ carve(op: CarveOp): number;
67
+ /** Fill every empty cell in the sphere with `material`; returns the number filled. */
68
+ deposit(op: DepositOp): number;
69
+ }
70
+ export interface CraterOp {
71
+ /** Crater centre (x, z in world units). */
72
+ x: number;
73
+ z: number;
74
+ radius: number;
75
+ /** How deep the bowl cuts at its centre. */
76
+ depth: number;
77
+ }
78
+ export interface MoundOp {
79
+ x: number;
80
+ z: number;
81
+ radius: number;
82
+ /** How high the mound rises at its centre. */
83
+ height: number;
84
+ }
85
+ /**
86
+ * A `TerrainField` you can write craters and mounds into at runtime — the height-field side of
87
+ * destructible terrain (Helldivers 2 explosion craters, engineer-deposited berms). Wraps a base field
88
+ * and layers smooth radial deformations on top, so `sampleHeight` (and therefore ground-snap,
89
+ * collision, and the shell's terrain mesh) all read the deformed surface. `carve` digs a bowl,
90
+ * `deposit` raises a mound.
91
+ */
92
+ export declare class CarvableField implements TerrainField {
93
+ readonly bounds?: WorldBounds;
94
+ readonly waterLevel?: number;
95
+ readonly sampleNormal: TerrainField["sampleNormal"];
96
+ private readonly base;
97
+ private readonly edits;
98
+ constructor(base: TerrainField);
99
+ get editCount(): number;
100
+ sampleHeight(x: number, z: number): number;
101
+ carve(op: CraterOp): void;
102
+ deposit(op: MoundOp): void;
103
+ clear(): void;
104
+ }
105
+ export declare function carvableTerrain(base: TerrainField): CarvableField;