@jgengine/core 0.6.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 (222) hide show
  1. package/CHANGELOG.md +167 -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 +2 -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 +281 -4
  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 +35 -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 +83 -0
  116. package/dist/physics/physicsWorld.js +261 -1
  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 +64 -2
  128. package/dist/runtime/gameContext.js +154 -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/autoTarget.d.ts +15 -0
  134. package/dist/scene/autoTarget.js +54 -0
  135. package/dist/scene/behaviors.d.ts +13 -1
  136. package/dist/scene/behaviors.js +3 -0
  137. package/dist/scene/captureCheck.d.ts +7 -0
  138. package/dist/scene/captureCheck.js +11 -0
  139. package/dist/scene/entityStore.d.ts +1 -1
  140. package/dist/scene/form.d.ts +40 -0
  141. package/dist/scene/form.js +56 -0
  142. package/dist/scene/mount.d.ts +62 -0
  143. package/dist/scene/mount.js +107 -0
  144. package/dist/scene/possession.d.ts +30 -0
  145. package/dist/scene/possession.js +51 -0
  146. package/dist/scene/roster.d.ts +25 -0
  147. package/dist/scene/roster.js +57 -0
  148. package/dist/scene/selection.d.ts +30 -0
  149. package/dist/scene/selection.js +38 -0
  150. package/dist/scene/stationClaim.d.ts +43 -0
  151. package/dist/scene/stationClaim.js +109 -0
  152. package/dist/sensor/frustumSensor.d.ts +60 -0
  153. package/dist/sensor/frustumSensor.js +104 -0
  154. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  155. package/dist/sensor/hiddenStateProbe.js +40 -0
  156. package/dist/sensor/recordingBuffer.d.ts +29 -0
  157. package/dist/sensor/recordingBuffer.js +61 -0
  158. package/dist/sensor/revealQuery.d.ts +22 -0
  159. package/dist/sensor/revealQuery.js +36 -0
  160. package/dist/session/contestedChannel.d.ts +36 -0
  161. package/dist/session/contestedChannel.js +100 -0
  162. package/dist/session/extraction.d.ts +54 -0
  163. package/dist/session/extraction.js +108 -0
  164. package/dist/session/ring.d.ts +36 -0
  165. package/dist/session/ring.js +88 -0
  166. package/dist/session/roundState.d.ts +55 -0
  167. package/dist/session/roundState.js +129 -0
  168. package/dist/stats/accumulatorMeter.d.ts +31 -0
  169. package/dist/stats/accumulatorMeter.js +81 -0
  170. package/dist/stats/eventMeter.d.ts +34 -0
  171. package/dist/stats/eventMeter.js +76 -0
  172. package/dist/stats/rollCheck.d.ts +15 -0
  173. package/dist/stats/rollCheck.js +15 -0
  174. package/dist/survival/decayMeter.d.ts +59 -0
  175. package/dist/survival/decayMeter.js +95 -0
  176. package/dist/survival/moodle.d.ts +48 -0
  177. package/dist/survival/moodle.js +91 -0
  178. package/dist/survival/regionHealth.d.ts +72 -0
  179. package/dist/survival/regionHealth.js +129 -0
  180. package/dist/tactics/predictiveQuery.d.ts +34 -0
  181. package/dist/tactics/predictiveQuery.js +44 -0
  182. package/dist/tactics/snapshot.d.ts +18 -0
  183. package/dist/tactics/snapshot.js +65 -0
  184. package/dist/tactics/surface.d.ts +52 -0
  185. package/dist/tactics/surface.js +135 -0
  186. package/dist/tactics/tacticalGrid.d.ts +57 -0
  187. package/dist/tactics/tacticalGrid.js +244 -0
  188. package/dist/time/beatClock.d.ts +40 -0
  189. package/dist/time/beatClock.js +73 -0
  190. package/dist/turn/commit.d.ts +27 -0
  191. package/dist/turn/commit.js +72 -0
  192. package/dist/turn/turnLoop.d.ts +58 -0
  193. package/dist/turn/turnLoop.js +216 -0
  194. package/dist/world/buildPermissions.d.ts +47 -0
  195. package/dist/world/buildPermissions.js +134 -0
  196. package/dist/world/carve.d.ts +105 -0
  197. package/dist/world/carve.js +175 -0
  198. package/dist/world/connectors.d.ts +40 -0
  199. package/dist/world/connectors.js +67 -0
  200. package/dist/world/envField.d.ts +71 -0
  201. package/dist/world/envField.js +96 -0
  202. package/dist/world/fog.d.ts +40 -0
  203. package/dist/world/fog.js +137 -0
  204. package/dist/world/markers.d.ts +52 -0
  205. package/dist/world/markers.js +96 -0
  206. package/dist/world/minimap.d.ts +42 -0
  207. package/dist/world/minimap.js +72 -0
  208. package/dist/world/placedStructureStore.d.ts +39 -0
  209. package/dist/world/placedStructureStore.js +95 -0
  210. package/dist/world/placementController.d.ts +48 -0
  211. package/dist/world/placementController.js +97 -0
  212. package/dist/world/realm.d.ts +61 -0
  213. package/dist/world/realm.js +66 -0
  214. package/dist/world/support.d.ts +27 -0
  215. package/dist/world/support.js +75 -0
  216. package/dist/world/terraform.d.ts +54 -0
  217. package/dist/world/terraform.js +186 -0
  218. package/dist/world/walls.d.ts +61 -0
  219. package/dist/world/walls.js +221 -0
  220. package/dist/world/weather.d.ts +92 -0
  221. package/dist/world/weather.js +169 -0
  222. 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
+ }
@@ -0,0 +1,40 @@
1
+ export interface FogBounds {
2
+ minX: number;
3
+ minZ: number;
4
+ maxX: number;
5
+ maxZ: number;
6
+ }
7
+ export interface FogConfig {
8
+ bounds: FogBounds;
9
+ cellSize: number;
10
+ /** Cells revealed on construction (start-of-run known area). */
11
+ revealed?: readonly number[];
12
+ }
13
+ export interface FogCells {
14
+ cols: number;
15
+ rows: number;
16
+ minX: number;
17
+ minZ: number;
18
+ cellSize: number;
19
+ /** Row-major flags; index = row * cols + col. */
20
+ revealed: readonly boolean[];
21
+ revealedCount: number;
22
+ }
23
+ /**
24
+ * Reveal-on-event fog of war over a fixed grid. Walking (`revealAlong`) and
25
+ * digging/acting (`reveal`) clear cells; once revealed a cell stays revealed.
26
+ * Pure and renderer-free — the shell/react map draws `cells()`.
27
+ */
28
+ export interface FogField {
29
+ reveal(x: number, z: number, radius?: number): number;
30
+ revealAlong(from: readonly [number, number], to: readonly [number, number], radius?: number): number;
31
+ isRevealed(x: number, z: number): boolean;
32
+ isRevealedCell(col: number, row: number): boolean;
33
+ revealedCount(): number;
34
+ cellCount(): number;
35
+ fraction(): number;
36
+ cells(): FogCells;
37
+ reset(): void;
38
+ subscribe(listener: () => void): () => void;
39
+ }
40
+ export declare function createFogField(config: FogConfig): FogField;
@@ -0,0 +1,137 @@
1
+ function clampInt(value, min, max) {
2
+ const rounded = Math.floor(value);
3
+ return rounded < min ? min : rounded > max ? max : rounded;
4
+ }
5
+ export function createFogField(config) {
6
+ const { minX, minZ, maxX, maxZ } = config.bounds;
7
+ const cellSize = config.cellSize;
8
+ if (cellSize <= 0)
9
+ throw new Error("createFogField: cellSize must be positive");
10
+ const cols = Math.max(1, Math.ceil((maxX - minX) / cellSize));
11
+ const rows = Math.max(1, Math.ceil((maxZ - minZ) / cellSize));
12
+ const total = cols * rows;
13
+ const revealed = new Array(total).fill(false);
14
+ let revealedCount = 0;
15
+ const listeners = new Set();
16
+ let snapshotCache = null;
17
+ function colOf(x) {
18
+ return clampInt((x - minX) / cellSize, 0, cols - 1);
19
+ }
20
+ function rowOf(z) {
21
+ return clampInt((z - minZ) / cellSize, 0, rows - 1);
22
+ }
23
+ function markCell(col, row) {
24
+ if (col < 0 || col >= cols || row < 0 || row >= rows)
25
+ return false;
26
+ const index = row * cols + col;
27
+ if (revealed[index])
28
+ return false;
29
+ revealed[index] = true;
30
+ revealedCount += 1;
31
+ return true;
32
+ }
33
+ function notify(changed) {
34
+ if (changed === 0)
35
+ return;
36
+ snapshotCache = null;
37
+ for (const listener of listeners)
38
+ listener();
39
+ }
40
+ function revealAt(x, z, radius) {
41
+ const centerCol = colOf(x);
42
+ const centerRow = rowOf(z);
43
+ let changed = markCell(centerCol, centerRow) ? 1 : 0;
44
+ if (radius <= 0)
45
+ return changed;
46
+ const span = Math.ceil(radius / cellSize);
47
+ const radiusSq = radius * radius;
48
+ for (let dr = -span; dr <= span; dr += 1) {
49
+ for (let dc = -span; dc <= span; dc += 1) {
50
+ if (dc === 0 && dr === 0)
51
+ continue;
52
+ const cellX = minX + (centerCol + dc + 0.5) * cellSize;
53
+ const cellZ = minZ + (centerRow + dr + 0.5) * cellSize;
54
+ const dx = cellX - x;
55
+ const dz = cellZ - z;
56
+ if (dx * dx + dz * dz > radiusSq)
57
+ continue;
58
+ if (markCell(centerCol + dc, centerRow + dr))
59
+ changed += 1;
60
+ }
61
+ }
62
+ return changed;
63
+ }
64
+ if (config.revealed !== undefined) {
65
+ for (const index of config.revealed) {
66
+ if (index >= 0 && index < total && !revealed[index]) {
67
+ revealed[index] = true;
68
+ revealedCount += 1;
69
+ }
70
+ }
71
+ snapshotCache = null;
72
+ }
73
+ return {
74
+ reveal(x, z, radius = 0) {
75
+ const changed = revealAt(x, z, radius);
76
+ notify(changed);
77
+ return changed;
78
+ },
79
+ revealAlong(from, to, radius = cellSize) {
80
+ const dx = to[0] - from[0];
81
+ const dz = to[1] - from[1];
82
+ const distance = Math.hypot(dx, dz);
83
+ const steps = Math.max(1, Math.ceil(distance / cellSize));
84
+ let changed = 0;
85
+ for (let step = 0; step <= steps; step += 1) {
86
+ const t = step / steps;
87
+ changed += revealAt(from[0] + dx * t, from[1] + dz * t, radius);
88
+ }
89
+ notify(changed);
90
+ return changed;
91
+ },
92
+ isRevealed(x, z) {
93
+ return revealed[rowOf(z) * cols + colOf(x)] === true;
94
+ },
95
+ isRevealedCell(col, row) {
96
+ if (col < 0 || col >= cols || row < 0 || row >= rows)
97
+ return false;
98
+ return revealed[row * cols + col] === true;
99
+ },
100
+ revealedCount() {
101
+ return revealedCount;
102
+ },
103
+ cellCount() {
104
+ return total;
105
+ },
106
+ fraction() {
107
+ return total === 0 ? 0 : revealedCount / total;
108
+ },
109
+ cells() {
110
+ if (snapshotCache === null) {
111
+ snapshotCache = {
112
+ cols,
113
+ rows,
114
+ minX,
115
+ minZ,
116
+ cellSize,
117
+ revealed: revealed.slice(),
118
+ revealedCount,
119
+ };
120
+ }
121
+ return snapshotCache;
122
+ },
123
+ reset() {
124
+ if (revealedCount === 0)
125
+ return;
126
+ revealed.fill(false);
127
+ revealedCount = 0;
128
+ notify(1);
129
+ },
130
+ subscribe(listener) {
131
+ listeners.add(listener);
132
+ return () => {
133
+ listeners.delete(listener);
134
+ };
135
+ },
136
+ };
137
+ }
@@ -0,0 +1,52 @@
1
+ export type MarkerPosition = readonly [number, number, number];
2
+ export interface MapMarker<TMeta = unknown> {
3
+ id: string;
4
+ kind: string;
5
+ position: MarkerPosition;
6
+ label?: string;
7
+ owner?: string;
8
+ createdAt: number;
9
+ expiresAt?: number;
10
+ meta?: TMeta;
11
+ }
12
+ export interface MarkerInput<TMeta = unknown> {
13
+ id?: string;
14
+ kind: string;
15
+ position: MarkerPosition;
16
+ label?: string;
17
+ owner?: string;
18
+ createdAt?: number;
19
+ expiresAt?: number;
20
+ meta?: TMeta;
21
+ }
22
+ export interface MarkerQuery {
23
+ kind?: string;
24
+ owner?: string;
25
+ near?: MarkerPosition;
26
+ radius?: number;
27
+ }
28
+ /**
29
+ * Visual descriptor for a marker kind. Games supply their own palette; the
30
+ * engine ships `DEFAULT_MARKER_KINDS` as a content-agnostic starting set that
31
+ * the react minimap/compass read for colors and glyphs.
32
+ */
33
+ export interface MarkerKindStyle {
34
+ id: string;
35
+ color: string;
36
+ glyph: string;
37
+ priority: number;
38
+ }
39
+ export declare const DEFAULT_MARKER_KINDS: Record<string, MarkerKindStyle>;
40
+ export declare function markerKindStyle(kind: string, styles?: Record<string, MarkerKindStyle>): MarkerKindStyle;
41
+ export interface MarkerSet<TMeta = unknown> {
42
+ add(marker: MarkerInput<TMeta>): string;
43
+ remove(id: string): boolean;
44
+ get(id: string): MapMarker<TMeta> | null;
45
+ list(): readonly MapMarker<TMeta>[];
46
+ query(query: MarkerQuery): MapMarker<TMeta>[];
47
+ prune(now: number): number;
48
+ clear(): void;
49
+ subscribe(listener: () => void): () => void;
50
+ snapshot(): readonly MapMarker<TMeta>[];
51
+ }
52
+ export declare function createMarkerSet<TMeta = unknown>(now?: () => number): MarkerSet<TMeta>;