@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,175 @@
1
+ import { withNormal } from "./terrain.js";
2
+ export const EMPTY_VOXEL = 0;
3
+ /**
4
+ * A runtime-editable dense voxel grid — the carve/deposit op behind destructible dig worlds (Deep
5
+ * Rock Galactic tunnels, Astroneer terrain). Cells hold a material id (0 = empty); `carve` clears a
6
+ * sphere of solid cells that a tool is strong enough to break and returns how many it removed (feed
7
+ * that to a loot roll), `deposit` fills a sphere with a material. World↔cell mapping is `origin`+`scale`.
8
+ */
9
+ export class VoxelVolume {
10
+ nx;
11
+ ny;
12
+ nz;
13
+ scale;
14
+ origin;
15
+ cells;
16
+ materials;
17
+ solidCount = 0;
18
+ constructor(config) {
19
+ this.nx = Math.max(1, Math.floor(config.dims[0]));
20
+ this.ny = Math.max(1, Math.floor(config.dims[1]));
21
+ this.nz = Math.max(1, Math.floor(config.dims[2]));
22
+ this.scale = config.scale ?? 1;
23
+ this.origin = config.origin ?? [0, 0, 0];
24
+ this.materials = config.materials ?? {};
25
+ this.cells = new Uint16Array(this.nx * this.ny * this.nz);
26
+ const fill = config.fill ?? 1;
27
+ if (fill !== EMPTY_VOXEL) {
28
+ this.cells.fill(fill);
29
+ this.solidCount = this.cells.length;
30
+ }
31
+ }
32
+ get solid() {
33
+ return this.solidCount;
34
+ }
35
+ index(ix, iy, iz) {
36
+ return (iz * this.ny + iy) * this.nx + ix;
37
+ }
38
+ inBounds(ix, iy, iz) {
39
+ return ix >= 0 && ix < this.nx && iy >= 0 && iy < this.ny && iz >= 0 && iz < this.nz;
40
+ }
41
+ get(ix, iy, iz) {
42
+ if (!this.inBounds(ix, iy, iz))
43
+ return EMPTY_VOXEL;
44
+ return this.cells[this.index(ix, iy, iz)];
45
+ }
46
+ set(ix, iy, iz, material) {
47
+ if (!this.inBounds(ix, iy, iz))
48
+ return;
49
+ const at = this.index(ix, iy, iz);
50
+ const prev = this.cells[at];
51
+ if (prev === material)
52
+ return;
53
+ if (prev === EMPTY_VOXEL)
54
+ this.solidCount += 1;
55
+ else if (material === EMPTY_VOXEL)
56
+ this.solidCount -= 1;
57
+ this.cells[at] = material;
58
+ }
59
+ worldToCell(x, y, z) {
60
+ return [
61
+ Math.floor((x - this.origin[0]) / this.scale),
62
+ Math.floor((y - this.origin[1]) / this.scale),
63
+ Math.floor((z - this.origin[2]) / this.scale),
64
+ ];
65
+ }
66
+ solidAtWorld(x, y, z) {
67
+ const [ix, iy, iz] = this.worldToCell(x, y, z);
68
+ return this.get(ix, iy, iz) !== EMPTY_VOXEL;
69
+ }
70
+ strengthOf(material) {
71
+ return this.materials[material]?.strength ?? 0;
72
+ }
73
+ forEachCellInSphere(center, radius, visit) {
74
+ const s = this.scale;
75
+ const [cx, cy, cz] = this.worldToCell(center[0], center[1], center[2]);
76
+ const reach = Math.ceil(radius / s) + 1;
77
+ const r2 = radius * radius;
78
+ for (let iz = cz - reach; iz <= cz + reach; iz += 1) {
79
+ for (let iy = cy - reach; iy <= cy + reach; iy += 1) {
80
+ for (let ix = cx - reach; ix <= cx + reach; ix += 1) {
81
+ if (!this.inBounds(ix, iy, iz))
82
+ continue;
83
+ const wx = this.origin[0] + (ix + 0.5) * s;
84
+ const wy = this.origin[1] + (iy + 0.5) * s;
85
+ const wz = this.origin[2] + (iz + 0.5) * s;
86
+ const dx = wx - center[0];
87
+ const dy = wy - center[1];
88
+ const dz = wz - center[2];
89
+ if (dx * dx + dy * dy + dz * dz <= r2)
90
+ visit(ix, iy, iz);
91
+ }
92
+ }
93
+ }
94
+ }
95
+ /** Clear every solid cell in the sphere the tool can break; returns the number removed. */
96
+ carve(op) {
97
+ const tool = op.toolStrength ?? Number.POSITIVE_INFINITY;
98
+ let removed = 0;
99
+ this.forEachCellInSphere(op.center, op.radius, (ix, iy, iz) => {
100
+ const m = this.get(ix, iy, iz);
101
+ if (m === EMPTY_VOXEL)
102
+ return;
103
+ if (this.strengthOf(m) > tool)
104
+ return;
105
+ this.set(ix, iy, iz, EMPTY_VOXEL);
106
+ removed += 1;
107
+ });
108
+ return removed;
109
+ }
110
+ /** Fill every empty cell in the sphere with `material`; returns the number filled. */
111
+ deposit(op) {
112
+ let filled = 0;
113
+ this.forEachCellInSphere(op.center, op.radius, (ix, iy, iz) => {
114
+ if (this.get(ix, iy, iz) !== EMPTY_VOXEL)
115
+ return;
116
+ this.set(ix, iy, iz, op.material);
117
+ filled += 1;
118
+ });
119
+ return filled;
120
+ }
121
+ }
122
+ function bowl(t) {
123
+ const s = t * t * (3 - 2 * t);
124
+ return s;
125
+ }
126
+ /**
127
+ * A `TerrainField` you can write craters and mounds into at runtime — the height-field side of
128
+ * destructible terrain (Helldivers 2 explosion craters, engineer-deposited berms). Wraps a base field
129
+ * and layers smooth radial deformations on top, so `sampleHeight` (and therefore ground-snap,
130
+ * collision, and the shell's terrain mesh) all read the deformed surface. `carve` digs a bowl,
131
+ * `deposit` raises a mound.
132
+ */
133
+ export class CarvableField {
134
+ bounds;
135
+ waterLevel;
136
+ sampleNormal;
137
+ base;
138
+ edits = [];
139
+ constructor(base) {
140
+ this.base = base;
141
+ if (base.bounds !== undefined)
142
+ this.bounds = base.bounds;
143
+ if (base.waterLevel !== undefined)
144
+ this.waterLevel = base.waterLevel;
145
+ this.sampleNormal = withNormal((x, z) => this.sampleHeight(x, z));
146
+ }
147
+ get editCount() {
148
+ return this.edits.length;
149
+ }
150
+ sampleHeight(x, z) {
151
+ let h = this.base.sampleHeight(x, z);
152
+ for (let i = 0; i < this.edits.length; i += 1) {
153
+ const e = this.edits[i];
154
+ const dx = x - e.x;
155
+ const dz = z - e.z;
156
+ const d = Math.sqrt(dx * dx + dz * dz);
157
+ if (d >= e.radius)
158
+ continue;
159
+ h += e.delta * bowl(1 - d / e.radius);
160
+ }
161
+ return h;
162
+ }
163
+ carve(op) {
164
+ this.edits.push({ x: op.x, z: op.z, radius: op.radius, delta: -op.depth });
165
+ }
166
+ deposit(op) {
167
+ this.edits.push({ x: op.x, z: op.z, radius: op.radius, delta: op.height });
168
+ }
169
+ clear() {
170
+ this.edits.length = 0;
171
+ }
172
+ }
173
+ export function carvableTerrain(base) {
174
+ return new CarvableField(base);
175
+ }
@@ -0,0 +1,40 @@
1
+ export type ConnectorVec3 = readonly [number, number, number];
2
+ export interface ConnectorSocket {
3
+ id: string;
4
+ type: string;
5
+ offset: ConnectorVec3;
6
+ accepts?: readonly string[];
7
+ }
8
+ export interface ConnectorPieceDef {
9
+ kind: string;
10
+ sockets: readonly ConnectorSocket[];
11
+ }
12
+ export interface PlacedPiece {
13
+ id: string;
14
+ kind: string;
15
+ position: ConnectorVec3;
16
+ rotationY: number;
17
+ }
18
+ export interface WorldSocket {
19
+ pieceId: string;
20
+ socket: ConnectorSocket;
21
+ position: ConnectorVec3;
22
+ }
23
+ export interface SnapResult {
24
+ socketId: string;
25
+ targetPieceId: string;
26
+ targetSocketId: string;
27
+ position: ConnectorVec3;
28
+ rotationY: number;
29
+ distance: number;
30
+ }
31
+ export interface SnapOptions {
32
+ snapDistance?: number;
33
+ rotationY?: number;
34
+ }
35
+ export declare function socketsCompatible(a: ConnectorSocket, b: ConnectorSocket): boolean;
36
+ export declare function socketWorldPosition(socket: ConnectorSocket, origin: ConnectorVec3, rotationY: number): ConnectorVec3;
37
+ export declare function worldSockets(def: ConnectorPieceDef, piece: PlacedPiece): WorldSocket[];
38
+ export type ConnectorRegistry = (kind: string) => ConnectorPieceDef | null;
39
+ export declare function collectWorldSockets(registry: ConnectorRegistry, pieces: readonly PlacedPiece[]): WorldSocket[];
40
+ export declare function snapToNearest(registry: ConnectorRegistry, placed: readonly PlacedPiece[], movingDef: ConnectorPieceDef, cursor: ConnectorVec3, options?: SnapOptions): SnapResult | null;
@@ -0,0 +1,67 @@
1
+ export function socketsCompatible(a, b) {
2
+ const aAccepts = a.accepts ?? [a.type];
3
+ const bAccepts = b.accepts ?? [b.type];
4
+ return aAccepts.includes(b.type) && bAccepts.includes(a.type);
5
+ }
6
+ function rotateY(offset, rotationY) {
7
+ if (rotationY === 0)
8
+ return offset;
9
+ const cos = Math.cos(rotationY);
10
+ const sin = Math.sin(rotationY);
11
+ return [offset[0] * cos + offset[2] * sin, offset[1], -offset[0] * sin + offset[2] * cos];
12
+ }
13
+ export function socketWorldPosition(socket, origin, rotationY) {
14
+ const rotated = rotateY(socket.offset, rotationY);
15
+ return [origin[0] + rotated[0], origin[1] + rotated[1], origin[2] + rotated[2]];
16
+ }
17
+ export function worldSockets(def, piece) {
18
+ return def.sockets.map((socket) => ({
19
+ pieceId: piece.id,
20
+ socket,
21
+ position: socketWorldPosition(socket, piece.position, piece.rotationY),
22
+ }));
23
+ }
24
+ export function collectWorldSockets(registry, pieces) {
25
+ const out = [];
26
+ for (const piece of pieces) {
27
+ const def = registry(piece.kind);
28
+ if (def === null)
29
+ continue;
30
+ for (const world of worldSockets(def, piece))
31
+ out.push(world);
32
+ }
33
+ return out;
34
+ }
35
+ export function snapToNearest(registry, placed, movingDef, cursor, options = {}) {
36
+ const snapDistance = options.snapDistance ?? 1.5;
37
+ const rotationY = options.rotationY ?? 0;
38
+ const targets = collectWorldSockets(registry, placed);
39
+ let best = null;
40
+ for (const socket of movingDef.sockets) {
41
+ const socketWorld = socketWorldPosition(socket, cursor, rotationY);
42
+ for (const target of targets) {
43
+ if (!socketsCompatible(socket, target.socket))
44
+ continue;
45
+ const distance = Math.hypot(socketWorld[0] - target.position[0], socketWorld[1] - target.position[1], socketWorld[2] - target.position[2]);
46
+ if (distance > snapDistance)
47
+ continue;
48
+ if (best !== null && distance >= best.distance)
49
+ continue;
50
+ const localRotated = rotateY(socket.offset, rotationY);
51
+ const position = [
52
+ target.position[0] - localRotated[0],
53
+ target.position[1] - localRotated[1],
54
+ target.position[2] - localRotated[2],
55
+ ];
56
+ best = {
57
+ socketId: socket.id,
58
+ targetPieceId: target.pieceId,
59
+ targetSocketId: target.socket.id,
60
+ position,
61
+ rotationY,
62
+ distance,
63
+ };
64
+ }
65
+ }
66
+ return best;
67
+ }
@@ -0,0 +1,71 @@
1
+ import type { TerrainField } from "./terrain.js";
2
+ /** A sky-occluding footprint — a roof, dense canopy, a cave mouth. `shade` 0..1 = fraction of sky blocked. */
3
+ export interface OccluderRect {
4
+ x: number;
5
+ z: number;
6
+ w: number;
7
+ d: number;
8
+ shade?: number;
9
+ }
10
+ /** A localized warmth source — campfire, forge, geothermal vent. */
11
+ export interface HeatSource {
12
+ x: number;
13
+ z: number;
14
+ radius: number;
15
+ /** Degrees added at the center, falling to 0 at `radius`. */
16
+ strength: number;
17
+ }
18
+ export type ShadeProvider = readonly OccluderRect[] | ((x: number, z: number) => number);
19
+ export type ScalarField = number | ((x: number, z: number, time: number) => number);
20
+ export interface EnvironmentFieldConfig {
21
+ /** Game-seconds per day; drives the sun-elevation cycle. Default matches the sim clock day. */
22
+ dayLength?: number;
23
+ /** Temperature at sea level under a noon sun with no weather. Default 20. */
24
+ baseTemperature?: number;
25
+ /** Degrees colder at deep night vs noon. Default 12. */
26
+ nightDrop?: number;
27
+ /** Degrees lost per world-height unit above `seaLevel` (altitude lapse). Default 0. */
28
+ altitudeLapse?: number;
29
+ seaLevel?: number;
30
+ /** Terrain field used to look up ground altitude when a caller omits `y`. */
31
+ terrain?: TerrainField;
32
+ /** Rain/precipitation intensity 0..1 at a position — from a weather state. Default 0. */
33
+ rain?: ScalarField;
34
+ /** Sky occluders (structures, canopy) that block sun and shelter from rain. */
35
+ occluders?: ShadeProvider;
36
+ /** Campfires and other warmth emitters. */
37
+ heatSources?: readonly HeatSource[];
38
+ /** Minimum ambient light at night (moonlight floor), 0..1. Default 0.05. */
39
+ ambientFloor?: number;
40
+ /** Per-position base-temperature offset (biome map). Added to `baseTemperature`. */
41
+ temperatureAt?: (x: number, z: number) => number;
42
+ }
43
+ export interface EnvironmentSample {
44
+ /** Degrees (game units). */
45
+ temperature: number;
46
+ /** 0..1 surface wetness. */
47
+ wetness: number;
48
+ /** 0..1 direct sun/sky exposure — full sun open sky = 1, deep shade/night = 0. */
49
+ lightExposure: number;
50
+ /** 0..1 overall light level for spawn gating — includes ambient/moonlight. */
51
+ ambientLight: number;
52
+ /** Under an occluder (roof/canopy) enough to shelter from rain. */
53
+ sheltered: boolean;
54
+ }
55
+ export interface EnvironmentField {
56
+ sample(x: number, z: number, time: number, y?: number): EnvironmentSample;
57
+ temperature(x: number, z: number, time: number, y?: number): number;
58
+ wetness(x: number, z: number, time: number): number;
59
+ lightExposure(x: number, z: number, time: number): number;
60
+ ambientLight(x: number, z: number, time: number): number;
61
+ /** Sun elevation -1..1 (1 = noon overhead, 0 = horizon, negative = night). */
62
+ sunElevation(time: number): number;
63
+ }
64
+ /**
65
+ * A sampleable environment field: read temperature, wetness, sun/sky exposure, and
66
+ * ambient light at any world position and time. Built on the same renderer-free footing
67
+ * as terrain/wind/water so meters, spawn gating, and damage-in-sunlight read the world
68
+ * the shell renders — no three.js. Instantaneous and pure (no accumulation); stateful
69
+ * build-up belongs to a decay meter reading this field.
70
+ */
71
+ export declare function createEnvironmentField(config?: EnvironmentFieldConfig): EnvironmentField;
@@ -0,0 +1,96 @@
1
+ import { SECONDS_PER_GAME_DAY } from "../time/gameClock.js";
2
+ function clamp01(value) {
3
+ return value < 0 ? 0 : value > 1 ? 1 : value;
4
+ }
5
+ function resolveScalar(field, x, z, time) {
6
+ return typeof field === "function" ? field(x, z, time) : field;
7
+ }
8
+ function pointInRect(rect, x, z) {
9
+ return (x >= rect.x - rect.w / 2 &&
10
+ x <= rect.x + rect.w / 2 &&
11
+ z >= rect.z - rect.d / 2 &&
12
+ z <= rect.z + rect.d / 2);
13
+ }
14
+ function resolveShade(provider, x, z) {
15
+ if (provider === undefined)
16
+ return 0;
17
+ if (typeof provider === "function")
18
+ return clamp01(provider(x, z));
19
+ let shade = 0;
20
+ for (const rect of provider) {
21
+ if (pointInRect(rect, x, z))
22
+ shade = Math.max(shade, rect.shade ?? 1);
23
+ }
24
+ return shade;
25
+ }
26
+ /**
27
+ * A sampleable environment field: read temperature, wetness, sun/sky exposure, and
28
+ * ambient light at any world position and time. Built on the same renderer-free footing
29
+ * as terrain/wind/water so meters, spawn gating, and damage-in-sunlight read the world
30
+ * the shell renders — no three.js. Instantaneous and pure (no accumulation); stateful
31
+ * build-up belongs to a decay meter reading this field.
32
+ */
33
+ export function createEnvironmentField(config = {}) {
34
+ const dayLength = config.dayLength !== undefined && config.dayLength > 0 ? config.dayLength : SECONDS_PER_GAME_DAY;
35
+ const baseTemperature = config.baseTemperature ?? 20;
36
+ const nightDrop = config.nightDrop ?? 12;
37
+ const altitudeLapse = config.altitudeLapse ?? 0;
38
+ const seaLevel = config.seaLevel ?? 0;
39
+ const ambientFloor = clamp01(config.ambientFloor ?? 0.05);
40
+ const heatSources = config.heatSources ?? [];
41
+ const sunElevation = (time) => {
42
+ const dayFraction = ((time % dayLength) + dayLength) % dayLength / dayLength;
43
+ return Math.sin((dayFraction - 0.25) * 2 * Math.PI);
44
+ };
45
+ const heatAt = (x, z) => {
46
+ let heat = 0;
47
+ for (const source of heatSources) {
48
+ const distance = Math.hypot(x - source.x, z - source.z);
49
+ if (distance >= source.radius)
50
+ continue;
51
+ heat += source.strength * (1 - distance / source.radius);
52
+ }
53
+ return heat;
54
+ };
55
+ const rainAt = (x, z, time) => config.rain === undefined ? 0 : clamp01(resolveScalar(config.rain, x, z, time));
56
+ const wetness = (x, z, time) => {
57
+ const shade = resolveShade(config.occluders, x, z);
58
+ return clamp01(rainAt(x, z, time) * (1 - shade));
59
+ };
60
+ const lightExposure = (x, z, time) => {
61
+ const shade = resolveShade(config.occluders, x, z);
62
+ const daylight = Math.max(0, sunElevation(time));
63
+ const cloud = 1 - rainAt(x, z, time) * 0.6;
64
+ return clamp01(daylight * cloud * (1 - shade));
65
+ };
66
+ const ambientLight = (x, z, time) => {
67
+ const shade = resolveShade(config.occluders, x, z);
68
+ const daylight = clamp01(sunElevation(time) * 0.5 + 0.5);
69
+ const sky = ambientFloor + (1 - ambientFloor) * daylight;
70
+ return clamp01(sky * (1 - shade * 0.75));
71
+ };
72
+ const temperature = (x, z, time, y) => {
73
+ const altitude = y ?? config.terrain?.sampleHeight(x, z) ?? seaLevel;
74
+ const daylight = clamp01(sunElevation(time) * 0.5 + 0.5);
75
+ const base = baseTemperature + (config.temperatureAt?.(x, z) ?? 0);
76
+ const lapse = altitudeLapse * Math.max(0, altitude - seaLevel);
77
+ const wetChill = wetness(x, z, time) * 4;
78
+ return base - nightDrop * (1 - daylight) - lapse - wetChill + heatAt(x, z);
79
+ };
80
+ return {
81
+ sample(x, z, time, y) {
82
+ return {
83
+ temperature: temperature(x, z, time, y),
84
+ wetness: wetness(x, z, time),
85
+ lightExposure: lightExposure(x, z, time),
86
+ ambientLight: ambientLight(x, z, time),
87
+ sheltered: resolveShade(config.occluders, x, z) >= 0.5,
88
+ };
89
+ },
90
+ temperature,
91
+ wetness,
92
+ lightExposure,
93
+ ambientLight,
94
+ sunElevation,
95
+ };
96
+ }
@@ -2,6 +2,103 @@ export interface WorldBounds {
2
2
  w: number;
3
3
  d: number;
4
4
  }
5
+ export type EnvironmentVec2 = readonly [number, number];
6
+ export interface EnvironmentArea extends WorldBounds {
7
+ h?: number;
8
+ }
9
+ export interface TerrainEnvironmentConfig {
10
+ bounds?: WorldBounds;
11
+ height?: number;
12
+ heightMap?: string;
13
+ material?: string;
14
+ seed?: string;
15
+ frequency?: number;
16
+ octaves?: number;
17
+ ridged?: boolean;
18
+ baseHeight?: number;
19
+ waterLevel?: number;
20
+ }
21
+ export interface RainEnvironmentConfig {
22
+ area?: EnvironmentArea;
23
+ density?: number;
24
+ speed?: number;
25
+ dropLength?: number;
26
+ wind?: EnvironmentVec2;
27
+ color?: string;
28
+ }
29
+ export interface SnowEnvironmentConfig {
30
+ area?: EnvironmentArea;
31
+ density?: number;
32
+ speed?: number;
33
+ flakeSize?: number;
34
+ drift?: number;
35
+ wind?: EnvironmentVec2;
36
+ color?: string;
37
+ }
38
+ export interface GrassEnvironmentConfig {
39
+ area?: WorldBounds;
40
+ density?: number;
41
+ bladeHeight?: readonly [number, number];
42
+ bladeWidth?: number;
43
+ windStrength?: number;
44
+ colors?: readonly string[];
45
+ seed?: string;
46
+ }
47
+ export interface OceanEnvironmentConfig {
48
+ bounds?: WorldBounds;
49
+ level?: number;
50
+ waveHeight?: number;
51
+ waveScale?: number;
52
+ waveSpeed?: number;
53
+ color?: string;
54
+ }
55
+ export interface BuildingEnvironmentConfig {
56
+ count?: number;
57
+ footprint?: WorldBounds;
58
+ stories?: readonly [number, number];
59
+ storyHeight?: number;
60
+ spacing?: number;
61
+ style?: string;
62
+ seed?: string;
63
+ }
64
+ export type TerrainEnvironmentDescriptor = {
65
+ kind: "terrain";
66
+ } & Required<Pick<TerrainEnvironmentConfig, "bounds" | "height">> & Omit<TerrainEnvironmentConfig, "bounds" | "height">;
67
+ export type RainEnvironmentDescriptor = {
68
+ kind: "rain";
69
+ } & Required<Pick<RainEnvironmentConfig, "area" | "density" | "speed" | "dropLength" | "wind" | "color">>;
70
+ export type SnowEnvironmentDescriptor = {
71
+ kind: "snow";
72
+ } & Required<Pick<SnowEnvironmentConfig, "area" | "density" | "speed" | "flakeSize" | "drift" | "wind" | "color">>;
73
+ export type GrassEnvironmentDescriptor = {
74
+ kind: "grass";
75
+ } & Required<Pick<GrassEnvironmentConfig, "area" | "density" | "bladeHeight" | "bladeWidth" | "windStrength" | "colors">> & Pick<GrassEnvironmentConfig, "seed">;
76
+ export type OceanEnvironmentDescriptor = {
77
+ kind: "ocean";
78
+ } & Required<Pick<OceanEnvironmentConfig, "bounds" | "level" | "waveHeight" | "waveScale" | "waveSpeed" | "color">>;
79
+ export type BuildingEnvironmentDescriptor = {
80
+ kind: "building";
81
+ } & Required<Pick<BuildingEnvironmentConfig, "count" | "footprint" | "stories" | "storyHeight" | "spacing" | "style">> & Pick<BuildingEnvironmentConfig, "seed">;
82
+ export type WeatherEnvironmentDescriptor = RainEnvironmentDescriptor | SnowEnvironmentDescriptor;
83
+ export type VegetationEnvironmentDescriptor = GrassEnvironmentDescriptor;
84
+ export type WaterEnvironmentDescriptor = OceanEnvironmentDescriptor;
85
+ export type StructureEnvironmentDescriptor = BuildingEnvironmentDescriptor;
86
+ export type EnvironmentDescriptorList<T> = T | readonly T[];
87
+ export interface EnvironmentWorldConfig {
88
+ terrain?: TerrainEnvironmentDescriptor;
89
+ weather?: EnvironmentDescriptorList<WeatherEnvironmentDescriptor>;
90
+ vegetation?: EnvironmentDescriptorList<VegetationEnvironmentDescriptor>;
91
+ water?: EnvironmentDescriptorList<WaterEnvironmentDescriptor>;
92
+ structures?: EnvironmentDescriptorList<StructureEnvironmentDescriptor>;
93
+ }
94
+ export interface EnvironmentWorldFeature {
95
+ kind: "environment";
96
+ terrain?: TerrainEnvironmentDescriptor;
97
+ weather?: readonly WeatherEnvironmentDescriptor[];
98
+ vegetation?: readonly VegetationEnvironmentDescriptor[];
99
+ water?: readonly WaterEnvironmentDescriptor[];
100
+ structures?: readonly StructureEnvironmentDescriptor[];
101
+ }
5
102
  export interface BiomesWorldConfig {
6
103
  map: string;
7
104
  zones: string;
@@ -29,9 +126,16 @@ export type WorldFeature = ({
29
126
  kind: "plots";
30
127
  } & PlotsWorldConfig) | ({
31
128
  kind: "tilemap";
32
- } & TilemapWorldConfig) | {
129
+ } & TilemapWorldConfig) | EnvironmentWorldFeature | {
33
130
  kind: "flat";
34
131
  };
132
+ export declare function environment(config?: EnvironmentWorldConfig): EnvironmentWorldFeature;
133
+ export declare function terrain(config?: TerrainEnvironmentConfig): TerrainEnvironmentDescriptor;
134
+ export declare function rain(config?: RainEnvironmentConfig): RainEnvironmentDescriptor;
135
+ export declare function snow(config?: SnowEnvironmentConfig): SnowEnvironmentDescriptor;
136
+ export declare function grass(config?: GrassEnvironmentConfig): GrassEnvironmentDescriptor;
137
+ export declare function ocean(config?: OceanEnvironmentConfig): OceanEnvironmentDescriptor;
138
+ export declare function building(config?: BuildingEnvironmentConfig): BuildingEnvironmentDescriptor;
35
139
  export declare function biomes(config: BiomesWorldConfig): WorldFeature;
36
140
  export declare function voxel(config: VoxelWorldConfig): WorldFeature;
37
141
  export declare function plots(config?: PlotsWorldConfig): WorldFeature;
@@ -1,3 +1,98 @@
1
+ function list(value) {
2
+ if (value === undefined) {
3
+ return undefined;
4
+ }
5
+ return Array.isArray(value) ? value : [value];
6
+ }
7
+ function withOptional(base, optional) {
8
+ return optional === undefined ? base : { ...base, ...optional };
9
+ }
10
+ export function environment(config = {}) {
11
+ const weather = list(config.weather);
12
+ const vegetation = list(config.vegetation);
13
+ const water = list(config.water);
14
+ const structures = list(config.structures);
15
+ return {
16
+ kind: "environment",
17
+ ...(config.terrain === undefined ? {} : { terrain: config.terrain }),
18
+ ...(weather === undefined ? {} : { weather }),
19
+ ...(vegetation === undefined ? {} : { vegetation }),
20
+ ...(water === undefined ? {} : { water }),
21
+ ...(structures === undefined ? {} : { structures }),
22
+ };
23
+ }
24
+ export function terrain(config = {}) {
25
+ return withOptional({
26
+ kind: "terrain",
27
+ bounds: config.bounds ?? { w: 512, d: 512 },
28
+ height: config.height ?? 0,
29
+ }, {
30
+ ...(config.heightMap === undefined ? {} : { heightMap: config.heightMap }),
31
+ ...(config.material === undefined ? {} : { material: config.material }),
32
+ ...(config.seed === undefined ? {} : { seed: config.seed }),
33
+ ...(config.frequency === undefined ? {} : { frequency: config.frequency }),
34
+ ...(config.octaves === undefined ? {} : { octaves: config.octaves }),
35
+ ...(config.ridged === undefined ? {} : { ridged: config.ridged }),
36
+ ...(config.baseHeight === undefined ? {} : { baseHeight: config.baseHeight }),
37
+ ...(config.waterLevel === undefined ? {} : { waterLevel: config.waterLevel }),
38
+ });
39
+ }
40
+ export function rain(config = {}) {
41
+ return {
42
+ kind: "rain",
43
+ area: config.area ?? { w: 256, d: 256, h: 80 },
44
+ density: config.density ?? 0.65,
45
+ speed: config.speed ?? 18,
46
+ dropLength: config.dropLength ?? 0.8,
47
+ wind: config.wind ?? [0, 0],
48
+ color: config.color ?? "#9ec8ff",
49
+ };
50
+ }
51
+ export function snow(config = {}) {
52
+ return {
53
+ kind: "snow",
54
+ area: config.area ?? { w: 256, d: 256, h: 80 },
55
+ density: config.density ?? 0.35,
56
+ speed: config.speed ?? 2.4,
57
+ flakeSize: config.flakeSize ?? 0.08,
58
+ drift: config.drift ?? 0.4,
59
+ wind: config.wind ?? [0, 0],
60
+ color: config.color ?? "#ffffff",
61
+ };
62
+ }
63
+ export function grass(config = {}) {
64
+ return withOptional({
65
+ kind: "grass",
66
+ area: config.area ?? { w: 128, d: 128 },
67
+ density: config.density ?? 4,
68
+ bladeHeight: config.bladeHeight ?? [0.25, 0.9],
69
+ bladeWidth: config.bladeWidth ?? 0.035,
70
+ windStrength: config.windStrength ?? 0.35,
71
+ colors: config.colors ?? ["#3f7d2d", "#66a83f"],
72
+ }, config.seed === undefined ? undefined : { seed: config.seed });
73
+ }
74
+ export function ocean(config = {}) {
75
+ return {
76
+ kind: "ocean",
77
+ bounds: config.bounds ?? { w: 1024, d: 1024 },
78
+ level: config.level ?? 0,
79
+ waveHeight: config.waveHeight ?? 1.2,
80
+ waveScale: config.waveScale ?? 18,
81
+ waveSpeed: config.waveSpeed ?? 0.55,
82
+ color: config.color ?? "#1d7fa3",
83
+ };
84
+ }
85
+ export function building(config = {}) {
86
+ return withOptional({
87
+ kind: "building",
88
+ count: config.count ?? 1,
89
+ footprint: config.footprint ?? { w: 8, d: 8 },
90
+ stories: config.stories ?? [1, 4],
91
+ storyHeight: config.storyHeight ?? 3,
92
+ spacing: config.spacing ?? 2,
93
+ style: config.style ?? "generic",
94
+ }, config.seed === undefined ? undefined : { seed: config.seed });
95
+ }
1
96
  export function biomes(config) {
2
97
  return { kind: "biomes", ...config };
3
98
  }