@jgengine/core 0.6.0 → 0.8.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (330) hide show
  1. package/CHANGELOG.md +214 -2
  2. package/README.md +2 -0
  3. package/dist/ai/crowd.d.ts +36 -0
  4. package/dist/ai/crowd.js +213 -0
  5. package/dist/ai/jobBoard.d.ts +49 -0
  6. package/dist/ai/jobBoard.js +155 -0
  7. package/dist/ai/spawnDirector.d.ts +61 -0
  8. package/dist/ai/spawnDirector.js +162 -0
  9. package/dist/ai/threat.d.ts +25 -0
  10. package/dist/ai/threat.js +67 -0
  11. package/dist/anim/easing.d.ts +14 -0
  12. package/dist/anim/easing.js +55 -0
  13. package/dist/audio/audioFalloff.d.ts +38 -0
  14. package/dist/audio/audioFalloff.js +32 -0
  15. package/dist/behaviour/behaviour.d.ts +136 -0
  16. package/dist/behaviour/behaviour.js +388 -0
  17. package/dist/board/laneBoard.d.ts +63 -0
  18. package/dist/board/laneBoard.js +114 -0
  19. package/dist/board/timelineBoard.d.ts +36 -0
  20. package/dist/board/timelineBoard.js +70 -0
  21. package/dist/cards/cardPile.d.ts +64 -0
  22. package/dist/cards/cardPile.js +175 -0
  23. package/dist/cards/modifierPipeline.d.ts +33 -0
  24. package/dist/cards/modifierPipeline.js +48 -0
  25. package/dist/combat/abilityKit.d.ts +47 -0
  26. package/dist/combat/abilityKit.js +139 -0
  27. package/dist/combat/animationState.d.ts +47 -0
  28. package/dist/combat/animationState.js +101 -0
  29. package/dist/combat/attackTags.d.ts +14 -0
  30. package/dist/combat/attackTags.js +26 -0
  31. package/dist/combat/breakMeters.d.ts +32 -0
  32. package/dist/combat/breakMeters.js +51 -0
  33. package/dist/combat/comboString.d.ts +35 -0
  34. package/dist/combat/comboString.js +59 -0
  35. package/dist/combat/death.d.ts +8 -0
  36. package/dist/combat/death.js +5 -2
  37. package/dist/combat/defensiveWindow.d.ts +34 -0
  38. package/dist/combat/defensiveWindow.js +66 -0
  39. package/dist/combat/downed.d.ts +35 -0
  40. package/dist/combat/downed.js +104 -0
  41. package/dist/combat/effects.d.ts +12 -1
  42. package/dist/combat/effects.js +31 -19
  43. package/dist/combat/hitReaction.d.ts +23 -0
  44. package/dist/combat/hitReaction.js +20 -0
  45. package/dist/combat/projectiles.d.ts +42 -5
  46. package/dist/combat/projectiles.js +98 -12
  47. package/dist/combat/resistance.d.ts +13 -0
  48. package/dist/combat/resistance.js +46 -0
  49. package/dist/combat/telegraph.d.ts +31 -0
  50. package/dist/combat/telegraph.js +51 -0
  51. package/dist/commands/commandRegistry.d.ts +1 -1
  52. package/dist/commands/commandRegistry.js +2 -1
  53. package/dist/crafting/crop.d.ts +59 -0
  54. package/dist/crafting/crop.js +181 -0
  55. package/dist/crafting/production.d.ts +64 -0
  56. package/dist/crafting/production.js +130 -0
  57. package/dist/crafting/recipe.d.ts +62 -0
  58. package/dist/crafting/recipe.js +101 -0
  59. package/dist/data/dataSource.d.ts +26 -0
  60. package/dist/data/dataSource.js +93 -0
  61. package/dist/data/devProxy.d.ts +11 -0
  62. package/dist/data/devProxy.js +38 -0
  63. package/dist/data/fetchJson.d.ts +20 -0
  64. package/dist/data/fetchJson.js +43 -0
  65. package/dist/data/jsonDataSource.d.ts +4 -0
  66. package/dist/data/jsonDataSource.js +6 -0
  67. package/dist/devtools/devtools.d.ts +157 -0
  68. package/dist/devtools/devtools.js +484 -0
  69. package/dist/devtools/transformTunables.d.ts +14 -0
  70. package/dist/devtools/transformTunables.js +38 -0
  71. package/dist/economy/sharedWallet.d.ts +28 -0
  72. package/dist/economy/sharedWallet.js +62 -0
  73. package/dist/economy/techTree.d.ts +40 -0
  74. package/dist/economy/techTree.js +66 -0
  75. package/dist/game/chat.d.ts +66 -0
  76. package/dist/game/chat.js +165 -0
  77. package/dist/game/cosmetics.d.ts +25 -0
  78. package/dist/game/cosmetics.js +51 -0
  79. package/dist/game/defineGame.d.ts +10 -2
  80. package/dist/game/defineGame.js +2 -1
  81. package/dist/game/events.d.ts +87 -0
  82. package/dist/game/levelSequence.d.ts +44 -0
  83. package/dist/game/levelSequence.js +78 -0
  84. package/dist/game/lootFilter.d.ts +33 -0
  85. package/dist/game/lootFilter.js +48 -0
  86. package/dist/game/ping.d.ts +67 -0
  87. package/dist/game/ping.js +86 -0
  88. package/dist/game/playableGame.d.ts +516 -8
  89. package/dist/game/playableGame.js +6 -1
  90. package/dist/game/progression.d.ts +1 -0
  91. package/dist/game/progression.js +23 -2
  92. package/dist/game/race.d.ts +100 -0
  93. package/dist/game/race.js +236 -0
  94. package/dist/game/runDraft.d.ts +34 -0
  95. package/dist/game/runDraft.js +97 -0
  96. package/dist/game/social.d.ts +69 -0
  97. package/dist/game/social.js +138 -0
  98. package/dist/game/spawnPoints.d.ts +19 -0
  99. package/dist/game/spawnPoints.js +23 -0
  100. package/dist/game/worldItem.d.ts +95 -0
  101. package/dist/game/worldItem.js +138 -0
  102. package/dist/input/actionBindings.d.ts +10 -0
  103. package/dist/input/actionBindings.js +14 -0
  104. package/dist/input/axisInput.d.ts +43 -0
  105. package/dist/input/axisInput.js +70 -0
  106. package/dist/input/gestureSurface.d.ts +31 -0
  107. package/dist/input/gestureSurface.js +120 -0
  108. package/dist/input/pointer.d.ts +69 -0
  109. package/dist/input/pointer.js +65 -0
  110. package/dist/input/touchScheme.d.ts +83 -0
  111. package/dist/input/touchScheme.js +115 -0
  112. package/dist/interaction/contextMenu.d.ts +30 -0
  113. package/dist/interaction/contextMenu.js +22 -0
  114. package/dist/interaction/qte.d.ts +20 -0
  115. package/dist/interaction/qte.js +24 -0
  116. package/dist/interaction/skillCheck.d.ts +20 -0
  117. package/dist/interaction/skillCheck.js +26 -0
  118. package/dist/inventory/shapedGrid.d.ts +56 -0
  119. package/dist/inventory/shapedGrid.js +176 -0
  120. package/dist/inventory/storageTier.d.ts +52 -0
  121. package/dist/inventory/storageTier.js +77 -0
  122. package/dist/item/affix.d.ts +54 -0
  123. package/dist/item/affix.js +115 -0
  124. package/dist/item/durability.d.ts +51 -0
  125. package/dist/item/durability.js +78 -0
  126. package/dist/item/modularItem.d.ts +46 -0
  127. package/dist/item/modularItem.js +75 -0
  128. package/dist/meta/changelog.d.ts +1 -1
  129. package/dist/meta/changelog.js +71 -1
  130. package/dist/movement/dash.d.ts +37 -0
  131. package/dist/movement/dash.js +65 -0
  132. package/dist/movement/movementModel.d.ts +36 -3
  133. package/dist/movement/movementModel.js +81 -5
  134. package/dist/movement/voxelController.d.ts +61 -0
  135. package/dist/movement/voxelController.js +229 -0
  136. package/dist/multiplayer/chatContract.d.ts +42 -0
  137. package/dist/multiplayer/chatContract.js +57 -0
  138. package/dist/multiplayer/combatSnapshot.d.ts +45 -0
  139. package/dist/multiplayer/combatSnapshot.js +89 -0
  140. package/dist/multiplayer/identity.d.ts +13 -0
  141. package/dist/multiplayer/identity.js +20 -0
  142. package/dist/multiplayer/lagCompensation.d.ts +43 -0
  143. package/dist/multiplayer/lagCompensation.js +154 -0
  144. package/dist/multiplayer/matchmaking.d.ts +33 -0
  145. package/dist/multiplayer/matchmaking.js +60 -0
  146. package/dist/multiplayer/poseSyncGate.d.ts +1 -0
  147. package/dist/multiplayer/poseSyncGate.js +13 -1
  148. package/dist/multiplayer/presenceModel.d.ts +3 -0
  149. package/dist/multiplayer/presenceModel.js +15 -1
  150. package/dist/multiplayer/simultaneousCommit.d.ts +34 -0
  151. package/dist/multiplayer/simultaneousCommit.js +53 -0
  152. package/dist/multiplayer/voiceContract.d.ts +44 -0
  153. package/dist/multiplayer/voiceContract.js +108 -0
  154. package/dist/nav/navConstrain.d.ts +14 -0
  155. package/dist/nav/navConstrain.js +26 -0
  156. package/dist/nav/navFromEnvironment.d.ts +11 -0
  157. package/dist/nav/navFromEnvironment.js +16 -0
  158. package/dist/nav/navGrid.d.ts +56 -0
  159. package/dist/nav/navGrid.js +265 -0
  160. package/dist/nav/pathFollow.d.ts +31 -0
  161. package/dist/nav/pathFollow.js +69 -0
  162. package/dist/physics/ballisticSweep.d.ts +18 -0
  163. package/dist/physics/ballisticSweep.js +49 -0
  164. package/dist/physics/buoyancy.d.ts +49 -0
  165. package/dist/physics/buoyancy.js +112 -0
  166. package/dist/physics/carryable.d.ts +46 -0
  167. package/dist/physics/carryable.js +110 -0
  168. package/dist/physics/damageZones.d.ts +56 -0
  169. package/dist/physics/damageZones.js +107 -0
  170. package/dist/physics/forceVolume.d.ts +48 -0
  171. package/dist/physics/forceVolume.js +119 -0
  172. package/dist/physics/physicsWorld.d.ts +124 -2
  173. package/dist/physics/physicsWorld.js +493 -41
  174. package/dist/physics/ragdoll.d.ts +49 -0
  175. package/dist/physics/ragdoll.js +103 -0
  176. package/dist/physics/spatialGrid.d.ts +41 -0
  177. package/dist/physics/spatialGrid.js +143 -0
  178. package/dist/physics/structure.d.ts +83 -0
  179. package/dist/physics/structure.js +0 -0
  180. package/dist/physics/traversal.d.ts +75 -0
  181. package/dist/physics/traversal.js +138 -0
  182. package/dist/physics/vehicleBody.d.ts +99 -0
  183. package/dist/physics/vehicleBody.js +233 -0
  184. package/dist/puzzle/cellGrid.d.ts +26 -0
  185. package/dist/puzzle/cellGrid.js +110 -0
  186. package/dist/puzzle/fallingPiece.d.ts +24 -0
  187. package/dist/puzzle/fallingPiece.js +51 -0
  188. package/dist/random/nameGen.d.ts +18 -0
  189. package/dist/random/nameGen.js +35 -0
  190. package/dist/random/rng.d.ts +2 -0
  191. package/dist/random/rng.js +23 -0
  192. package/dist/runtime/adapter.d.ts +35 -0
  193. package/dist/runtime/adapter.js +37 -1
  194. package/dist/runtime/cameraDirector.d.ts +10 -0
  195. package/dist/runtime/cameraDirector.js +25 -0
  196. package/dist/runtime/gameContext.d.ts +107 -3
  197. package/dist/runtime/gameContext.js +253 -7
  198. package/dist/runtime/hostPersistence.d.ts +39 -0
  199. package/dist/runtime/hostPersistence.js +54 -0
  200. package/dist/runtime/inputSnapshot.d.ts +7 -0
  201. package/dist/runtime/inputSnapshot.js +12 -0
  202. package/dist/runtime/motionIntents.d.ts +25 -0
  203. package/dist/runtime/motionIntents.js +25 -0
  204. package/dist/runtime/persistenceScope.d.ts +32 -0
  205. package/dist/runtime/persistenceScope.js +64 -0
  206. package/dist/runtime/transport.d.ts +32 -0
  207. package/dist/scene/autoTarget.d.ts +15 -0
  208. package/dist/scene/autoTarget.js +54 -0
  209. package/dist/scene/behaviors.d.ts +13 -1
  210. package/dist/scene/behaviors.js +3 -0
  211. package/dist/scene/captureCheck.d.ts +7 -0
  212. package/dist/scene/captureCheck.js +11 -0
  213. package/dist/scene/entityStore.d.ts +9 -1
  214. package/dist/scene/entityStore.js +25 -2
  215. package/dist/scene/form.d.ts +40 -0
  216. package/dist/scene/form.js +56 -0
  217. package/dist/scene/mount.d.ts +62 -0
  218. package/dist/scene/mount.js +107 -0
  219. package/dist/scene/movementSpeed.d.ts +35 -0
  220. package/dist/scene/movementSpeed.js +20 -0
  221. package/dist/scene/objectQuery.d.ts +18 -0
  222. package/dist/scene/objectQuery.js +108 -0
  223. package/dist/scene/objectStore.d.ts +11 -0
  224. package/dist/scene/objectStore.js +109 -4
  225. package/dist/scene/paintLayer.d.ts +15 -0
  226. package/dist/scene/paintLayer.js +53 -0
  227. package/dist/scene/possession.d.ts +30 -0
  228. package/dist/scene/possession.js +51 -0
  229. package/dist/scene/roster.d.ts +25 -0
  230. package/dist/scene/roster.js +57 -0
  231. package/dist/scene/selection.d.ts +30 -0
  232. package/dist/scene/selection.js +38 -0
  233. package/dist/scene/spatial.d.ts +21 -0
  234. package/dist/scene/spatial.js +63 -1
  235. package/dist/scene/stationClaim.d.ts +43 -0
  236. package/dist/scene/stationClaim.js +109 -0
  237. package/dist/sensor/concealment.d.ts +21 -0
  238. package/dist/sensor/concealment.js +63 -0
  239. package/dist/sensor/freezeMonitor.d.ts +17 -0
  240. package/dist/sensor/freezeMonitor.js +35 -0
  241. package/dist/sensor/frustumSensor.d.ts +60 -0
  242. package/dist/sensor/frustumSensor.js +104 -0
  243. package/dist/sensor/hiddenStateProbe.d.ts +31 -0
  244. package/dist/sensor/hiddenStateProbe.js +40 -0
  245. package/dist/sensor/recordingBuffer.d.ts +29 -0
  246. package/dist/sensor/recordingBuffer.js +61 -0
  247. package/dist/sensor/revealQuery.d.ts +22 -0
  248. package/dist/sensor/revealQuery.js +36 -0
  249. package/dist/session/contestedChannel.d.ts +36 -0
  250. package/dist/session/contestedChannel.js +100 -0
  251. package/dist/session/extraction.d.ts +54 -0
  252. package/dist/session/extraction.js +108 -0
  253. package/dist/session/ring.d.ts +36 -0
  254. package/dist/session/ring.js +88 -0
  255. package/dist/session/roles.d.ts +6 -0
  256. package/dist/session/roles.js +32 -0
  257. package/dist/session/roundState.d.ts +79 -0
  258. package/dist/session/roundState.js +158 -0
  259. package/dist/stats/accumulatorMeter.d.ts +31 -0
  260. package/dist/stats/accumulatorMeter.js +81 -0
  261. package/dist/stats/eventMeter.d.ts +34 -0
  262. package/dist/stats/eventMeter.js +76 -0
  263. package/dist/stats/rollCheck.d.ts +15 -0
  264. package/dist/stats/rollCheck.js +15 -0
  265. package/dist/survival/decayMeter.d.ts +59 -0
  266. package/dist/survival/decayMeter.js +95 -0
  267. package/dist/survival/moodle.d.ts +48 -0
  268. package/dist/survival/moodle.js +91 -0
  269. package/dist/survival/regionHealth.d.ts +72 -0
  270. package/dist/survival/regionHealth.js +129 -0
  271. package/dist/tactics/fallingGrid.d.ts +62 -0
  272. package/dist/tactics/fallingGrid.js +158 -0
  273. package/dist/tactics/predictiveQuery.d.ts +34 -0
  274. package/dist/tactics/predictiveQuery.js +44 -0
  275. package/dist/tactics/snapshot.d.ts +18 -0
  276. package/dist/tactics/snapshot.js +65 -0
  277. package/dist/tactics/surface.d.ts +52 -0
  278. package/dist/tactics/surface.js +135 -0
  279. package/dist/tactics/tacticalGrid.d.ts +68 -0
  280. package/dist/tactics/tacticalGrid.js +261 -0
  281. package/dist/time/beatClock.d.ts +40 -0
  282. package/dist/time/beatClock.js +73 -0
  283. package/dist/time/simClock.d.ts +25 -1
  284. package/dist/time/simClock.js +28 -3
  285. package/dist/turn/commit.d.ts +27 -0
  286. package/dist/turn/commit.js +72 -0
  287. package/dist/turn/intent.d.ts +14 -0
  288. package/dist/turn/intent.js +29 -0
  289. package/dist/turn/turnLoop.d.ts +60 -0
  290. package/dist/turn/turnLoop.js +223 -0
  291. package/dist/world/buildPermissions.d.ts +47 -0
  292. package/dist/world/buildPermissions.js +134 -0
  293. package/dist/world/carve.d.ts +105 -0
  294. package/dist/world/carve.js +175 -0
  295. package/dist/world/connectors.d.ts +40 -0
  296. package/dist/world/connectors.js +67 -0
  297. package/dist/world/envField.d.ts +71 -0
  298. package/dist/world/envField.js +96 -0
  299. package/dist/world/environmentSummary.d.ts +53 -0
  300. package/dist/world/environmentSummary.js +129 -0
  301. package/dist/world/features.d.ts +87 -6
  302. package/dist/world/features.js +52 -2
  303. package/dist/world/fog.d.ts +40 -0
  304. package/dist/world/fog.js +137 -0
  305. package/dist/world/gridInstances.d.ts +13 -0
  306. package/dist/world/gridInstances.js +22 -0
  307. package/dist/world/markers.d.ts +52 -0
  308. package/dist/world/markers.js +96 -0
  309. package/dist/world/minimap.d.ts +42 -0
  310. package/dist/world/minimap.js +72 -0
  311. package/dist/world/placedStructureStore.d.ts +39 -0
  312. package/dist/world/placedStructureStore.js +95 -0
  313. package/dist/world/placementController.d.ts +69 -0
  314. package/dist/world/placementController.js +153 -0
  315. package/dist/world/realm.d.ts +61 -0
  316. package/dist/world/realm.js +66 -0
  317. package/dist/world/support.d.ts +27 -0
  318. package/dist/world/support.js +75 -0
  319. package/dist/world/terraform.d.ts +54 -0
  320. package/dist/world/terraform.js +186 -0
  321. package/dist/world/terrain.d.ts +23 -1
  322. package/dist/world/terrain.js +64 -1
  323. package/dist/world/voxelField.d.ts +54 -0
  324. package/dist/world/voxelField.js +241 -0
  325. package/dist/world/walls.d.ts +61 -0
  326. package/dist/world/walls.js +221 -0
  327. package/dist/world/weather.d.ts +92 -0
  328. package/dist/world/weather.js +169 -0
  329. package/llms.txt +4102 -0
  330. package/package.json +3 -2
@@ -0,0 +1,96 @@
1
+ import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
2
+ export const DEFAULT_MARKER_KINDS = {
3
+ player: { id: "player", color: "#4ade80", glyph: "◆", priority: 90 },
4
+ ally: { id: "ally", color: "#38bdf8", glyph: "▲", priority: 70 },
5
+ objective: { id: "objective", color: "#facc15", glyph: "★", priority: 100 },
6
+ location: { id: "location", color: "#e2e8f0", glyph: "⬡", priority: 40 },
7
+ loot: { id: "loot", color: "#c084fc", glyph: "◈", priority: 60 },
8
+ enemy: { id: "enemy", color: "#f87171", glyph: "✖", priority: 80 },
9
+ danger: { id: "danger", color: "#fb923c", glyph: "⚠", priority: 85 },
10
+ ping: { id: "ping", color: "#22d3ee", glyph: "◎", priority: 65 },
11
+ };
12
+ export function markerKindStyle(kind, styles = DEFAULT_MARKER_KINDS) {
13
+ return styles[kind] ?? { id: kind, color: "#e2e8f0", glyph: "•", priority: 30 };
14
+ }
15
+ function distanceXZ(a, b) {
16
+ return Math.hypot(a[0] - b[0], a[2] - b[2]);
17
+ }
18
+ export function createMarkerSet(now = Date.now) {
19
+ const store = createObservableKeyedStore();
20
+ let counter = 0;
21
+ function generateId() {
22
+ let id = `marker-${counter}`;
23
+ while (store.has(id)) {
24
+ counter += 1;
25
+ id = `marker-${counter}`;
26
+ }
27
+ counter += 1;
28
+ return id;
29
+ }
30
+ return {
31
+ add(input) {
32
+ const id = input.id ?? generateId();
33
+ const marker = {
34
+ id,
35
+ kind: input.kind,
36
+ position: input.position,
37
+ createdAt: input.createdAt ?? now(),
38
+ };
39
+ if (input.label !== undefined)
40
+ marker.label = input.label;
41
+ if (input.owner !== undefined)
42
+ marker.owner = input.owner;
43
+ if (input.expiresAt !== undefined)
44
+ marker.expiresAt = input.expiresAt;
45
+ if (input.meta !== undefined)
46
+ marker.meta = input.meta;
47
+ store.set(id, marker);
48
+ return id;
49
+ },
50
+ remove(id) {
51
+ const existed = store.has(id);
52
+ store.delete(id);
53
+ return existed;
54
+ },
55
+ get(id) {
56
+ return store.get(id) ?? null;
57
+ },
58
+ list() {
59
+ return store.arraySnapshot();
60
+ },
61
+ query(query) {
62
+ return store.arraySnapshot().filter((marker) => {
63
+ if (query.kind !== undefined && marker.kind !== query.kind)
64
+ return false;
65
+ if (query.owner !== undefined && marker.owner !== query.owner)
66
+ return false;
67
+ if (query.near !== undefined) {
68
+ const radius = query.radius ?? Number.POSITIVE_INFINITY;
69
+ if (distanceXZ(marker.position, query.near) > radius)
70
+ return false;
71
+ }
72
+ return true;
73
+ });
74
+ },
75
+ prune(at) {
76
+ let removed = 0;
77
+ for (const marker of store.arraySnapshot()) {
78
+ if (marker.expiresAt !== undefined && marker.expiresAt <= at) {
79
+ store.delete(marker.id);
80
+ removed += 1;
81
+ }
82
+ }
83
+ return removed;
84
+ },
85
+ clear() {
86
+ for (const marker of store.arraySnapshot())
87
+ store.delete(marker.id);
88
+ },
89
+ subscribe(listener) {
90
+ return store.subscribe(listener);
91
+ },
92
+ snapshot() {
93
+ return store.arraySnapshot();
94
+ },
95
+ };
96
+ }
@@ -0,0 +1,42 @@
1
+ export type WorldXZ = readonly [number, number];
2
+ /**
3
+ * Compass bearing (radians, 0 = map north = −Z, increasing clockwise toward +X
4
+ * = east) from one world XZ point to another. Feeds both the minimap direction
5
+ * and the compass strip.
6
+ */
7
+ export declare function compassBearing(from: WorldXZ, to: WorldXZ): number;
8
+ /** Bearing of an entity facing direction given its `rotationY` (yaw) in radians. */
9
+ export declare function headingToBearing(yaw: number): number;
10
+ export type Cardinal = "N" | "NE" | "E" | "SE" | "S" | "SW" | "W" | "NW";
11
+ export declare function bearingToCardinal(bearing: number): Cardinal;
12
+ /** Signed offset of `bearing` from `reference`, wrapped into (−π, π]. */
13
+ export declare function relativeBearing(bearing: number, reference: number): number;
14
+ export interface MinimapView {
15
+ /** World XZ at the minimap center (usually the player position). */
16
+ center: WorldXZ;
17
+ /** World-unit distance from center to the minimap edge. */
18
+ worldRadius: number;
19
+ /** Pixel diameter of the minimap. */
20
+ size: number;
21
+ /** Rotate the projection so this facing bearing points up (rotating minimap). Omit for north-up. */
22
+ rotate?: number;
23
+ }
24
+ export interface MinimapPoint {
25
+ x: number;
26
+ y: number;
27
+ /** Radial distance in world units. */
28
+ distance: number;
29
+ /** True when within `worldRadius` of center. */
30
+ inside: boolean;
31
+ }
32
+ /**
33
+ * Project a world XZ (or XYZ) point into minimap pixel space. Origin is the
34
+ * top-left of the `size×size` box; north (−Z) maps to −Y (up). Pass
35
+ * `view.rotate` to spin the map under a fixed north-up player arrow.
36
+ */
37
+ export declare function projectToMinimap(world: WorldXZ | readonly [number, number, number], view: MinimapView): MinimapPoint;
38
+ /** Clamp a projected point to the minimap edge, preserving direction (edge markers). */
39
+ export declare function clampToMinimapEdge(point: MinimapPoint, size: number): {
40
+ x: number;
41
+ y: number;
42
+ };
@@ -0,0 +1,72 @@
1
+ const TWO_PI = Math.PI * 2;
2
+ function normalizeAngle(angle) {
3
+ let value = angle % TWO_PI;
4
+ if (value < 0)
5
+ value += TWO_PI;
6
+ return value;
7
+ }
8
+ /**
9
+ * Compass bearing (radians, 0 = map north = −Z, increasing clockwise toward +X
10
+ * = east) from one world XZ point to another. Feeds both the minimap direction
11
+ * and the compass strip.
12
+ */
13
+ export function compassBearing(from, to) {
14
+ return normalizeAngle(Math.atan2(to[0] - from[0], -(to[1] - from[1])));
15
+ }
16
+ /** Bearing of an entity facing direction given its `rotationY` (yaw) in radians. */
17
+ export function headingToBearing(yaw) {
18
+ return normalizeAngle(Math.atan2(Math.sin(yaw), -Math.cos(yaw)));
19
+ }
20
+ const CARDINALS = ["N", "NE", "E", "SE", "S", "SW", "W", "NW"];
21
+ export function bearingToCardinal(bearing) {
22
+ const index = Math.round(normalizeAngle(bearing) / (Math.PI / 4)) % 8;
23
+ return CARDINALS[index];
24
+ }
25
+ /** Signed offset of `bearing` from `reference`, wrapped into (−π, π]. */
26
+ export function relativeBearing(bearing, reference) {
27
+ let delta = normalizeAngle(bearing) - normalizeAngle(reference);
28
+ if (delta > Math.PI)
29
+ delta -= TWO_PI;
30
+ if (delta <= -Math.PI)
31
+ delta += TWO_PI;
32
+ return delta;
33
+ }
34
+ /**
35
+ * Project a world XZ (or XYZ) point into minimap pixel space. Origin is the
36
+ * top-left of the `size×size` box; north (−Z) maps to −Y (up). Pass
37
+ * `view.rotate` to spin the map under a fixed north-up player arrow.
38
+ */
39
+ export function projectToMinimap(world, view) {
40
+ const worldX = world[0];
41
+ const worldZ = world.length === 3 ? world[2] : world[1];
42
+ let dx = worldX - view.center[0];
43
+ let dz = worldZ - view.center[1];
44
+ if (view.rotate !== undefined && view.rotate !== 0) {
45
+ const cos = Math.cos(view.rotate);
46
+ const sin = Math.sin(view.rotate);
47
+ const rx = dx * cos - dz * sin;
48
+ const rz = dx * sin + dz * cos;
49
+ dx = rx;
50
+ dz = rz;
51
+ }
52
+ const distance = Math.hypot(dx, dz);
53
+ const half = view.size / 2;
54
+ const scale = view.worldRadius === 0 ? 0 : half / view.worldRadius;
55
+ return {
56
+ x: half + dx * scale,
57
+ y: half + dz * scale,
58
+ distance,
59
+ inside: distance <= view.worldRadius,
60
+ };
61
+ }
62
+ /** Clamp a projected point to the minimap edge, preserving direction (edge markers). */
63
+ export function clampToMinimapEdge(point, size) {
64
+ const half = size / 2;
65
+ const dx = point.x - half;
66
+ const dy = point.y - half;
67
+ const length = Math.hypot(dx, dy);
68
+ if (length <= half || length === 0)
69
+ return { x: point.x, y: point.y };
70
+ const scale = half / length;
71
+ return { x: half + dx * scale, y: half + dy * scale };
72
+ }
@@ -0,0 +1,39 @@
1
+ export type StructureVec3 = readonly [number, number, number];
2
+ export interface PlacedStructure {
3
+ id: string;
4
+ catalogId: string;
5
+ position: StructureVec3;
6
+ rotationY: number;
7
+ plotId?: string;
8
+ data?: Readonly<Record<string, unknown>>;
9
+ }
10
+ export interface AddStructureInput {
11
+ id?: string;
12
+ catalogId: string;
13
+ position: StructureVec3;
14
+ rotationY?: number;
15
+ plotId?: string;
16
+ data?: Readonly<Record<string, unknown>>;
17
+ }
18
+ export interface StructureFilter {
19
+ plotId?: string;
20
+ catalogId?: string;
21
+ }
22
+ export interface PlacedStructureSnapshot {
23
+ structures: readonly PlacedStructure[];
24
+ }
25
+ export interface PlacedStructureStore {
26
+ add(input: AddStructureInput): PlacedStructure;
27
+ move(id: string, position: StructureVec3): boolean;
28
+ rotate(id: string, rotationY: number): boolean;
29
+ remove(id: string): boolean;
30
+ get(id: string): PlacedStructure | null;
31
+ list(filter?: StructureFilter): readonly PlacedStructure[];
32
+ clear(): void;
33
+ select(id: string | null): boolean;
34
+ selected(): PlacedStructure | null;
35
+ snapshot(): PlacedStructureSnapshot;
36
+ load(snapshot: PlacedStructureSnapshot): void;
37
+ subscribe(listener: () => void): () => void;
38
+ }
39
+ export declare function createPlacedStructureStore(): PlacedStructureStore;
@@ -0,0 +1,95 @@
1
+ import { createObservableKeyedStore } from "../store/observableKeyedStore.js";
2
+ export function createPlacedStructureStore() {
3
+ const store = createObservableKeyedStore();
4
+ let counter = 1;
5
+ let selectedId = null;
6
+ function generateId() {
7
+ let id = `structure-${counter}`;
8
+ while (store.has(id)) {
9
+ counter += 1;
10
+ id = `structure-${counter}`;
11
+ }
12
+ counter += 1;
13
+ return id;
14
+ }
15
+ return {
16
+ add(input) {
17
+ if (input.id !== undefined && store.has(input.id)) {
18
+ throw new Error(`Structure id "${input.id}" already exists.`);
19
+ }
20
+ const id = input.id ?? generateId();
21
+ const structure = {
22
+ id,
23
+ catalogId: input.catalogId,
24
+ position: input.position,
25
+ rotationY: input.rotationY ?? 0,
26
+ ...(input.plotId === undefined ? {} : { plotId: input.plotId }),
27
+ ...(input.data === undefined ? {} : { data: input.data }),
28
+ };
29
+ store.set(id, structure);
30
+ return structure;
31
+ },
32
+ move(id, position) {
33
+ const current = store.get(id);
34
+ if (current === undefined)
35
+ return false;
36
+ store.set(id, { ...current, position });
37
+ return true;
38
+ },
39
+ rotate(id, rotationY) {
40
+ const current = store.get(id);
41
+ if (current === undefined)
42
+ return false;
43
+ store.set(id, { ...current, rotationY });
44
+ return true;
45
+ },
46
+ remove(id) {
47
+ const existed = store.has(id);
48
+ store.delete(id);
49
+ if (selectedId === id)
50
+ selectedId = null;
51
+ return existed;
52
+ },
53
+ get(id) {
54
+ return store.get(id) ?? null;
55
+ },
56
+ list(filter) {
57
+ const all = store.arraySnapshot();
58
+ if (filter === undefined)
59
+ return all;
60
+ return all.filter((structure) => (filter.plotId === undefined || structure.plotId === filter.plotId) &&
61
+ (filter.catalogId === undefined || structure.catalogId === filter.catalogId));
62
+ },
63
+ clear() {
64
+ for (const structure of store.arraySnapshot())
65
+ store.delete(structure.id);
66
+ selectedId = null;
67
+ },
68
+ select(id) {
69
+ if (id === null) {
70
+ selectedId = null;
71
+ return true;
72
+ }
73
+ if (!store.has(id))
74
+ return false;
75
+ selectedId = id;
76
+ return true;
77
+ },
78
+ selected() {
79
+ return selectedId === null ? null : store.get(selectedId) ?? null;
80
+ },
81
+ snapshot() {
82
+ return { structures: store.arraySnapshot() };
83
+ },
84
+ load(snapshot) {
85
+ for (const structure of store.arraySnapshot())
86
+ store.delete(structure.id);
87
+ selectedId = null;
88
+ for (const structure of snapshot.structures)
89
+ store.set(structure.id, structure);
90
+ },
91
+ subscribe(listener) {
92
+ return store.subscribe(listener);
93
+ },
94
+ };
95
+ }
@@ -0,0 +1,69 @@
1
+ import { type Aabb, type Footprint, type Vec2 } from "./geometry.js";
2
+ import { type PlacementRules } from "./placement.js";
3
+ export type SnapMode = "grid" | "free" | "surface";
4
+ export type PlacementVec3 = readonly [number, number, number];
5
+ export interface PlacementHit {
6
+ point: PlacementVec3;
7
+ normal: PlacementVec3;
8
+ }
9
+ /** A fixed buildable slot the placement controller snaps to instead of grid/free positioning. */
10
+ export interface PlacementSlot {
11
+ id: string;
12
+ center: PlacementVec3;
13
+ /** Capture radius around `center`. Defaults to `config.slotRadius`, then `1.5`. */
14
+ radius?: number;
15
+ /** `false` hides the slot from hover resolution without releasing it. Default `true`. */
16
+ enabled?: boolean;
17
+ }
18
+ export interface PlacementControllerConfig {
19
+ footprint: Footprint;
20
+ rules?: PlacementRules;
21
+ snapMode?: SnapMode;
22
+ grid?: number;
23
+ quarterTurns?: number;
24
+ slots?: readonly PlacementSlot[];
25
+ /** Fallback capture radius for slots that don't set their own. Default `1.5`. */
26
+ slotRadius?: number;
27
+ }
28
+ export interface PlacementPreview {
29
+ center: Vec2;
30
+ y: number;
31
+ quarterTurns: number;
32
+ footprint: Footprint;
33
+ aabb: Aabb;
34
+ valid: boolean;
35
+ reason?: "out-of-bounds" | "overlap" | "no-slot";
36
+ snapMode: SnapMode;
37
+ normal: PlacementVec3;
38
+ /** Id of the resolved slot, when the controller is in slot mode and a slot matched. */
39
+ slotId?: string;
40
+ }
41
+ export interface PlacementCommit {
42
+ center: Vec2;
43
+ y: number;
44
+ rotationY: number;
45
+ quarterTurns: number;
46
+ footprint: Footprint;
47
+ aabb: Aabb;
48
+ /** Id of the slot this commit occupied, when the controller is in slot mode. */
49
+ slotId?: string;
50
+ }
51
+ export interface PlacementController {
52
+ hover(hit: PlacementHit): PlacementPreview;
53
+ current(): PlacementPreview | null;
54
+ rotate(steps?: number): PlacementPreview | null;
55
+ setSnapMode(mode: SnapMode): PlacementPreview | null;
56
+ cycleSnapMode(): SnapMode;
57
+ setFootprint(footprint: Footprint): void;
58
+ setRules(rules: PlacementRules): void;
59
+ setGrid(grid: number): void;
60
+ setSlots(slots: readonly PlacementSlot[]): void;
61
+ /** Id of the currently hovered slot, or `null` when idle, out of slot mode, or unmatched. */
62
+ hoveredSlotId(): string | null;
63
+ /** Frees a previously committed slot so it can match again. */
64
+ releaseSlot(id: string): void;
65
+ commit(): PlacementCommit | null;
66
+ reset(): void;
67
+ }
68
+ export declare function quarterTurnsToRotationY(quarterTurns: number): number;
69
+ export declare function createPlacementController(config: PlacementControllerConfig): PlacementController;
@@ -0,0 +1,153 @@
1
+ import { footprintAabb, snapToGrid } from "./geometry.js";
2
+ import { validatePlacement } from "./placement.js";
3
+ const SNAP_ORDER = ["grid", "free", "surface"];
4
+ const DEFAULT_SLOT_RADIUS = 1.5;
5
+ function distanceVec3(a, b) {
6
+ return Math.hypot(a[0] - b[0], a[1] - b[1], a[2] - b[2]);
7
+ }
8
+ export function quarterTurnsToRotationY(quarterTurns) {
9
+ const turns = ((quarterTurns % 4) + 4) % 4;
10
+ return turns === 0 ? 0 : -turns * (Math.PI / 2);
11
+ }
12
+ export function createPlacementController(config) {
13
+ let footprint = config.footprint;
14
+ let rules = config.rules ?? {};
15
+ let snapMode = config.snapMode ?? "grid";
16
+ let grid = config.grid ?? rules.snap ?? 1;
17
+ let quarterTurns = ((config.quarterTurns ?? 0) % 4 + 4) % 4;
18
+ let slots = config.slots;
19
+ const slotRadius = config.slotRadius ?? DEFAULT_SLOT_RADIUS;
20
+ const occupiedSlots = new Set();
21
+ let lastHit = null;
22
+ let preview = null;
23
+ function snapCenter(raw) {
24
+ if (snapMode === "grid")
25
+ return snapToGrid(raw, grid);
26
+ return raw;
27
+ }
28
+ function nearestSlot(hit) {
29
+ let best = null;
30
+ let bestDistance = Number.POSITIVE_INFINITY;
31
+ for (const slot of slots ?? []) {
32
+ if (slot.enabled === false || occupiedSlots.has(slot.id))
33
+ continue;
34
+ const distance = distanceVec3(hit.point, slot.center);
35
+ if (distance <= (slot.radius ?? slotRadius) && distance < bestDistance) {
36
+ best = slot;
37
+ bestDistance = distance;
38
+ }
39
+ }
40
+ return best;
41
+ }
42
+ function computeSlotPreview(hit) {
43
+ const slot = nearestSlot(hit);
44
+ const center = slot === null ? [hit.point[0], hit.point[2]] : [slot.center[0], slot.center[2]];
45
+ const next = {
46
+ center,
47
+ y: slot === null ? hit.point[1] : slot.center[1],
48
+ quarterTurns,
49
+ footprint,
50
+ aabb: footprintAabb(center, footprint, quarterTurns),
51
+ valid: slot !== null,
52
+ snapMode,
53
+ normal: hit.normal,
54
+ ...(slot === null ? { reason: "no-slot" } : { slotId: slot.id }),
55
+ };
56
+ preview = next;
57
+ return next;
58
+ }
59
+ function compute(hit) {
60
+ if (slots !== undefined)
61
+ return computeSlotPreview(hit);
62
+ const raw = [hit.point[0], hit.point[2]];
63
+ const center = snapCenter(raw);
64
+ const rulesWithoutSnap = snapMode === "grid" ? { ...rules, snap: undefined } : rules;
65
+ const result = validatePlacement({ center, footprint, quarterTurns }, rulesWithoutSnap);
66
+ const aabb = result.status === "ok" ? result.aabb : footprintAabb(center, footprint, quarterTurns);
67
+ const next = {
68
+ center: result.status === "ok" ? result.center : center,
69
+ y: hit.point[1],
70
+ quarterTurns,
71
+ footprint,
72
+ aabb,
73
+ valid: result.status === "ok",
74
+ snapMode,
75
+ normal: hit.normal,
76
+ ...(result.status === "rejected" ? { reason: result.reason } : {}),
77
+ };
78
+ preview = next;
79
+ return next;
80
+ }
81
+ return {
82
+ hover(hit) {
83
+ lastHit = hit;
84
+ return compute(hit);
85
+ },
86
+ current() {
87
+ return preview;
88
+ },
89
+ rotate(steps = 1) {
90
+ quarterTurns = ((quarterTurns + steps) % 4 + 4) % 4;
91
+ return lastHit === null ? null : compute(lastHit);
92
+ },
93
+ setSnapMode(mode) {
94
+ snapMode = mode;
95
+ return lastHit === null ? null : compute(lastHit);
96
+ },
97
+ cycleSnapMode() {
98
+ const index = SNAP_ORDER.indexOf(snapMode);
99
+ snapMode = SNAP_ORDER[(index + 1) % SNAP_ORDER.length];
100
+ if (lastHit !== null)
101
+ compute(lastHit);
102
+ return snapMode;
103
+ },
104
+ setFootprint(next) {
105
+ footprint = next;
106
+ if (lastHit !== null)
107
+ compute(lastHit);
108
+ },
109
+ setRules(next) {
110
+ rules = next;
111
+ if (lastHit !== null)
112
+ compute(lastHit);
113
+ },
114
+ setGrid(next) {
115
+ grid = next;
116
+ if (lastHit !== null)
117
+ compute(lastHit);
118
+ },
119
+ setSlots(next) {
120
+ slots = next;
121
+ if (lastHit !== null)
122
+ compute(lastHit);
123
+ },
124
+ hoveredSlotId() {
125
+ return preview?.slotId ?? null;
126
+ },
127
+ releaseSlot(id) {
128
+ occupiedSlots.delete(id);
129
+ if (lastHit !== null)
130
+ compute(lastHit);
131
+ },
132
+ commit() {
133
+ if (preview === null || !preview.valid)
134
+ return null;
135
+ if (preview.slotId !== undefined)
136
+ occupiedSlots.add(preview.slotId);
137
+ return {
138
+ center: preview.center,
139
+ y: preview.y,
140
+ rotationY: quarterTurnsToRotationY(quarterTurns),
141
+ quarterTurns,
142
+ footprint: preview.footprint,
143
+ aabb: preview.aabb,
144
+ ...(preview.slotId !== undefined ? { slotId: preview.slotId } : {}),
145
+ };
146
+ },
147
+ reset() {
148
+ lastHit = null;
149
+ preview = null;
150
+ quarterTurns = ((config.quarterTurns ?? 0) % 4 + 4) % 4;
151
+ },
152
+ };
153
+ }
@@ -0,0 +1,61 @@
1
+ import { type EnvironmentField, type EnvironmentFieldConfig } from "./envField.js";
2
+ import type { WeatherState } from "./weather.js";
3
+ /** The recomposable environment parameters a realm card can override. */
4
+ export interface RealmEnvironmentParams {
5
+ baseTemperature: number;
6
+ nightDrop: number;
7
+ altitudeLapse: number;
8
+ /** Game-seconds per day (a "long night" minor card shortens the day cycle). */
9
+ dayLength: number;
10
+ /** Ambient rain intensity 0..1. */
11
+ rain: number;
12
+ /** Ambient light floor 0..1 (moonlight). */
13
+ ambientFloor: number;
14
+ }
15
+ export declare const DEFAULT_REALM_ENVIRONMENT: RealmEnvironmentParams;
16
+ /** Spawn-table edits a card layers on: replace weights, add/scale, or remove ids. */
17
+ export interface SpawnTableOverride {
18
+ set?: Record<string, number>;
19
+ add?: Record<string, number>;
20
+ scale?: Record<string, number>;
21
+ remove?: readonly string[];
22
+ }
23
+ export interface RealmCard {
24
+ id: string;
25
+ /** A major card sets the biome base; minors layer weather/day-length/aesthetic on top. */
26
+ kind: "major" | "minor";
27
+ /** Environment param overrides (last write wins across the ordered deck). */
28
+ environment?: Partial<RealmEnvironmentParams>;
29
+ /** Weather state overrides (kind/intensity/wind). */
30
+ weather?: Partial<WeatherState>;
31
+ /** Spawn-table edits applied in deck order. */
32
+ spawn?: SpawnTableOverride;
33
+ /** Opaque aesthetic tag (skybox, palette) the renderer maps — engine never inspects it. */
34
+ aesthetic?: string;
35
+ }
36
+ export interface RealmBase {
37
+ environment?: Partial<RealmEnvironmentParams>;
38
+ weather?: WeatherState;
39
+ spawn?: Record<string, number>;
40
+ }
41
+ export interface ComposedRealm {
42
+ params: RealmEnvironmentParams;
43
+ weather: WeatherState;
44
+ spawnTable: Record<string, number>;
45
+ aesthetics: readonly string[];
46
+ cards: readonly RealmCard[];
47
+ /**
48
+ * Build an environment field (#91) from the composed params, wired to the composed
49
+ * weather's rain intensity. Extra config (terrain, occluders, heat sources) merges on top.
50
+ */
51
+ environmentField(extra?: Omit<EnvironmentFieldConfig, "baseTemperature" | "nightDrop" | "altitudeLapse" | "dayLength" | "rain" | "ambientFloor">): EnvironmentField;
52
+ }
53
+ /**
54
+ * Assemble a played realm instance at runtime from a deck of modifier cards — the
55
+ * Nightingale "realm card" model. A major card is the biome base; minor cards layer
56
+ * weather, day length, and spawn edits. The result recomposes both the environment
57
+ * (into a sampleable field via `environmentField()`) and the spawn table, and it depends
58
+ * on the weather hooks in this group (#92) to turn its `weather` into gameplay modifiers.
59
+ * Cards apply in array order; sort your deck (majors first) before composing.
60
+ */
61
+ export declare function composeRealm(base: RealmBase, cards: readonly RealmCard[]): ComposedRealm;