@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,72 @@
1
+ export function createCommitController(config) {
2
+ const mode = config.mode;
3
+ let expected = config.participants === undefined ? null : [...config.participants];
4
+ let sealed = new Map();
5
+ let revealed = false;
6
+ function orderedSubmissions() {
7
+ const source = expected ?? [...sealed.keys()];
8
+ const result = [];
9
+ for (const participant of source) {
10
+ if (sealed.has(participant))
11
+ result.push({ participant, action: sealed.get(participant) });
12
+ }
13
+ for (const [participant, action] of sealed) {
14
+ if (expected !== null && !expected.includes(participant))
15
+ result.push({ participant, action });
16
+ }
17
+ return result;
18
+ }
19
+ function allReady() {
20
+ if (expected === null)
21
+ return sealed.size > 0;
22
+ return expected.every((participant) => sealed.has(participant));
23
+ }
24
+ function clear() {
25
+ sealed = new Map();
26
+ revealed = false;
27
+ }
28
+ return {
29
+ mode,
30
+ submit(participant, action) {
31
+ if (mode === "immediate") {
32
+ return { status: "committed", committed: [{ participant, action }] };
33
+ }
34
+ if (sealed.has(participant) && !(mode === "rewind")) {
35
+ return { status: "rejected", committed: [], reason: "already-submitted" };
36
+ }
37
+ sealed.set(participant, action);
38
+ if (mode === "simultaneous") {
39
+ return { status: "sealed", committed: [] };
40
+ }
41
+ return { status: "pending", committed: [] };
42
+ },
43
+ expected(participants) {
44
+ expected = [...participants];
45
+ },
46
+ hasSubmitted: (participant) => sealed.has(participant),
47
+ allReady,
48
+ pending: () => orderedSubmissions(),
49
+ reveal() {
50
+ if (mode !== "simultaneous")
51
+ return orderedSubmissions();
52
+ if (!allReady())
53
+ return [];
54
+ revealed = true;
55
+ const out = orderedSubmissions();
56
+ return out;
57
+ },
58
+ commit() {
59
+ if (mode === "simultaneous" && !revealed && !allReady())
60
+ return [];
61
+ const out = orderedSubmissions();
62
+ clear();
63
+ return out;
64
+ },
65
+ rewind() {
66
+ const discarded = orderedSubmissions();
67
+ clear();
68
+ return discarded;
69
+ },
70
+ clear,
71
+ };
72
+ }
@@ -0,0 +1,58 @@
1
+ import { type CommitController, type CommitMode } from "./commit.js";
2
+ export type { CommitMode, CommitController } from "./commit.js";
3
+ export interface PoolConfig {
4
+ id: string;
5
+ max: number;
6
+ start?: number;
7
+ }
8
+ export interface TurnLoopConfig {
9
+ order: readonly string[];
10
+ phases?: readonly string[];
11
+ pools?: readonly PoolConfig[];
12
+ commit?: {
13
+ mode: CommitMode;
14
+ };
15
+ }
16
+ export interface PoolState {
17
+ id: string;
18
+ current: number;
19
+ max: number;
20
+ }
21
+ export interface TurnState {
22
+ round: number;
23
+ order: readonly string[];
24
+ activeIndex: number;
25
+ active: string | null;
26
+ phaseIndex: number;
27
+ phase: string | null;
28
+ }
29
+ export interface TurnLoopSnapshot {
30
+ round: number;
31
+ order: string[];
32
+ activeIndex: number;
33
+ phaseIndex: number;
34
+ pools: Record<string, Record<string, number>>;
35
+ }
36
+ export interface TurnLoop<TAction = unknown> {
37
+ readonly commit: CommitController<TAction>;
38
+ state(): TurnState;
39
+ order(): readonly string[];
40
+ active(): string | null;
41
+ phase(): string | null;
42
+ round(): number;
43
+ setOrder(order: readonly string[], keepActive?: boolean): TurnState;
44
+ addParticipant(id: string, atIndex?: number): TurnState;
45
+ removeParticipant(id: string): TurnState;
46
+ advancePhase(): TurnState;
47
+ advanceTurn(): TurnState;
48
+ advanceRound(): TurnState;
49
+ pools(participantId: string): PoolState[];
50
+ pool(participantId: string, poolId: string): PoolState | null;
51
+ canSpend(participantId: string, poolId: string, amount?: number): boolean;
52
+ spend(participantId: string, poolId: string, amount?: number): boolean;
53
+ gain(participantId: string, poolId: string, amount: number): boolean;
54
+ refill(participantId: string, poolId?: string): void;
55
+ capture(): TurnLoopSnapshot;
56
+ restore(snapshot: TurnLoopSnapshot): void;
57
+ }
58
+ export declare function createTurnLoop<TAction = unknown>(config: TurnLoopConfig): TurnLoop<TAction>;
@@ -0,0 +1,216 @@
1
+ import { createCommitController } from "./commit.js";
2
+ export function createTurnLoop(config) {
3
+ const poolConfigs = config.pools ?? [];
4
+ const phases = config.phases ?? [];
5
+ const commit = createCommitController({
6
+ mode: config.commit?.mode ?? "immediate",
7
+ participants: config.order,
8
+ });
9
+ let order = [...config.order];
10
+ let round = 1;
11
+ let activeIndex = order.length > 0 ? 0 : -1;
12
+ let phaseIndex = 0;
13
+ const pools = new Map();
14
+ function resetPools(participantId) {
15
+ const set = new Map();
16
+ for (const pc of poolConfigs) {
17
+ set.set(pc.id, { id: pc.id, current: pc.start ?? pc.max, max: pc.max });
18
+ }
19
+ pools.set(participantId, set);
20
+ }
21
+ function ensurePools(participantId) {
22
+ const existing = pools.get(participantId);
23
+ if (existing !== undefined)
24
+ return existing;
25
+ resetPools(participantId);
26
+ return pools.get(participantId);
27
+ }
28
+ for (const id of order)
29
+ resetPools(id);
30
+ function active() {
31
+ return activeIndex >= 0 && activeIndex < order.length ? order[activeIndex] : null;
32
+ }
33
+ function phase() {
34
+ return phases.length > 0 ? phases[phaseIndex] ?? null : null;
35
+ }
36
+ function state() {
37
+ return {
38
+ round,
39
+ order: [...order],
40
+ activeIndex,
41
+ active: active(),
42
+ phaseIndex: phases.length > 0 ? phaseIndex : 0,
43
+ phase: phase(),
44
+ };
45
+ }
46
+ function advanceTurn() {
47
+ if (order.length === 0) {
48
+ activeIndex = -1;
49
+ return state();
50
+ }
51
+ phaseIndex = 0;
52
+ if (activeIndex < 0) {
53
+ activeIndex = 0;
54
+ }
55
+ else if (activeIndex >= order.length - 1) {
56
+ activeIndex = 0;
57
+ round += 1;
58
+ }
59
+ else {
60
+ activeIndex += 1;
61
+ }
62
+ const current = active();
63
+ if (current !== null)
64
+ resetPools(current);
65
+ return state();
66
+ }
67
+ function advancePhase() {
68
+ if (phases.length === 0)
69
+ return advanceTurn();
70
+ if (phaseIndex >= phases.length - 1)
71
+ return advanceTurn();
72
+ phaseIndex += 1;
73
+ return state();
74
+ }
75
+ function advanceRound() {
76
+ if (order.length === 0) {
77
+ round += 1;
78
+ return state();
79
+ }
80
+ round += 1;
81
+ activeIndex = 0;
82
+ phaseIndex = 0;
83
+ for (const id of order)
84
+ resetPools(id);
85
+ return state();
86
+ }
87
+ function setOrder(next, keepActive = false) {
88
+ const previousActive = active();
89
+ order = [...next];
90
+ for (const id of order)
91
+ if (!pools.has(id))
92
+ resetPools(id);
93
+ for (const id of [...pools.keys()])
94
+ if (!order.includes(id))
95
+ pools.delete(id);
96
+ if (order.length === 0) {
97
+ activeIndex = -1;
98
+ }
99
+ else if (keepActive && previousActive !== null && order.includes(previousActive)) {
100
+ activeIndex = order.indexOf(previousActive);
101
+ }
102
+ else {
103
+ activeIndex = Math.min(Math.max(activeIndex, 0), order.length - 1);
104
+ }
105
+ return state();
106
+ }
107
+ function addParticipant(id, atIndex) {
108
+ if (order.includes(id))
109
+ return state();
110
+ const index = atIndex === undefined ? order.length : Math.min(Math.max(atIndex, 0), order.length);
111
+ order.splice(index, 0, id);
112
+ resetPools(id);
113
+ if (activeIndex < 0)
114
+ activeIndex = 0;
115
+ else if (index <= activeIndex)
116
+ activeIndex += 1;
117
+ return state();
118
+ }
119
+ function removeParticipant(id) {
120
+ const index = order.indexOf(id);
121
+ if (index < 0)
122
+ return state();
123
+ order.splice(index, 1);
124
+ pools.delete(id);
125
+ if (order.length === 0) {
126
+ activeIndex = -1;
127
+ }
128
+ else if (index < activeIndex) {
129
+ activeIndex -= 1;
130
+ }
131
+ else if (activeIndex >= order.length) {
132
+ activeIndex = 0;
133
+ }
134
+ return state();
135
+ }
136
+ return {
137
+ commit,
138
+ state,
139
+ order: () => [...order],
140
+ active,
141
+ phase,
142
+ round: () => round,
143
+ setOrder,
144
+ addParticipant,
145
+ removeParticipant,
146
+ advancePhase,
147
+ advanceTurn,
148
+ advanceRound,
149
+ pools: (participantId) => [...ensurePools(participantId).values()].map((p) => ({ ...p })),
150
+ pool: (participantId, poolId) => {
151
+ const p = ensurePools(participantId).get(poolId);
152
+ return p === undefined ? null : { ...p };
153
+ },
154
+ canSpend: (participantId, poolId, amount = 1) => {
155
+ const p = ensurePools(participantId).get(poolId);
156
+ return p !== undefined && p.current >= amount;
157
+ },
158
+ spend: (participantId, poolId, amount = 1) => {
159
+ const p = ensurePools(participantId).get(poolId);
160
+ if (p === undefined || p.current < amount)
161
+ return false;
162
+ p.current -= amount;
163
+ return true;
164
+ },
165
+ gain: (participantId, poolId, amount) => {
166
+ const p = ensurePools(participantId).get(poolId);
167
+ if (p === undefined)
168
+ return false;
169
+ p.current = Math.min(p.max, p.current + amount);
170
+ return true;
171
+ },
172
+ refill: (participantId, poolId) => {
173
+ const set = ensurePools(participantId);
174
+ if (poolId === undefined) {
175
+ for (const p of set.values())
176
+ p.current = p.max;
177
+ return;
178
+ }
179
+ const p = set.get(poolId);
180
+ if (p !== undefined)
181
+ p.current = p.max;
182
+ },
183
+ capture: () => {
184
+ const poolSnapshot = {};
185
+ for (const [participantId, set] of pools) {
186
+ const entry = {};
187
+ for (const [poolId, p] of set)
188
+ entry[poolId] = p.current;
189
+ poolSnapshot[participantId] = entry;
190
+ }
191
+ return { round, order: [...order], activeIndex, phaseIndex, pools: poolSnapshot };
192
+ },
193
+ restore: (snapshot) => {
194
+ round = snapshot.round;
195
+ order = [...snapshot.order];
196
+ activeIndex = snapshot.activeIndex;
197
+ phaseIndex = snapshot.phaseIndex;
198
+ pools.clear();
199
+ for (const id of order)
200
+ resetPools(id);
201
+ for (const [participantId, entry] of Object.entries(snapshot.pools)) {
202
+ const set = pools.get(participantId) ?? new Map();
203
+ for (const [poolId, current] of Object.entries(entry)) {
204
+ const existing = set.get(poolId);
205
+ if (existing !== undefined)
206
+ existing.current = current;
207
+ else {
208
+ const pc = poolConfigs.find((c) => c.id === poolId);
209
+ set.set(poolId, { id: poolId, current, max: pc?.max ?? current });
210
+ }
211
+ }
212
+ pools.set(participantId, set);
213
+ }
214
+ },
215
+ };
216
+ }
@@ -0,0 +1,47 @@
1
+ export type BuildRole = "owner" | "editor" | "viewer";
2
+ export interface PlotPermissionConfig {
3
+ plotId: string;
4
+ ownerId: string;
5
+ guildId?: string;
6
+ roles?: Readonly<Record<string, BuildRole>>;
7
+ guildRole?: BuildRole;
8
+ }
9
+ export interface PlotPermissionSnapshot {
10
+ plotId: string;
11
+ ownerId: string;
12
+ guildId?: string;
13
+ roles: Readonly<Record<string, BuildRole>>;
14
+ guildRole?: BuildRole;
15
+ }
16
+ export interface BuildActor {
17
+ userId: string;
18
+ guildId?: string;
19
+ }
20
+ export interface PlotPermissions {
21
+ roleOf(actor: BuildActor): BuildRole | null;
22
+ canEdit(actor: BuildActor): boolean;
23
+ canView(actor: BuildActor): boolean;
24
+ grant(userId: string, role: BuildRole): void;
25
+ revoke(userId: string): void;
26
+ setGuild(guildId: string | null, role?: BuildRole): void;
27
+ transferOwner(userId: string): void;
28
+ snapshot(): PlotPermissionSnapshot;
29
+ }
30
+ export declare function createPlotPermissions(config: PlotPermissionConfig): PlotPermissions;
31
+ export type ContributionGoal = Readonly<Record<string, number>>;
32
+ export interface ContributionResult {
33
+ accepted: number;
34
+ overflow: number;
35
+ complete: boolean;
36
+ }
37
+ export interface ContributionPool {
38
+ contribute(userId: string, resource: string, amount: number): ContributionResult;
39
+ totals(): Readonly<Record<string, number>>;
40
+ remaining(): Readonly<Record<string, number>>;
41
+ byContributor(userId: string): Readonly<Record<string, number>>;
42
+ progress(): number;
43
+ isComplete(): boolean;
44
+ snapshot(): Record<string, Record<string, number>>;
45
+ restore(snapshot: Record<string, Record<string, number>>): void;
46
+ }
47
+ export declare function createContributionPool(goal: ContributionGoal): ContributionPool;
@@ -0,0 +1,134 @@
1
+ const ROLE_RANK = { viewer: 0, editor: 1, owner: 2 };
2
+ export function createPlotPermissions(config) {
3
+ let ownerId = config.ownerId;
4
+ let guildId = config.guildId;
5
+ let guildRole = config.guildRole;
6
+ const roles = new Map(Object.entries(config.roles ?? {}));
7
+ function roleOf(actor) {
8
+ if (actor.userId === ownerId)
9
+ return "owner";
10
+ const explicit = roles.get(actor.userId);
11
+ if (explicit !== undefined)
12
+ return explicit;
13
+ if (guildId !== undefined &&
14
+ guildRole !== undefined &&
15
+ actor.guildId !== undefined &&
16
+ actor.guildId === guildId) {
17
+ return guildRole;
18
+ }
19
+ return null;
20
+ }
21
+ return {
22
+ roleOf,
23
+ canEdit(actor) {
24
+ const role = roleOf(actor);
25
+ return role !== null && ROLE_RANK[role] >= ROLE_RANK.editor;
26
+ },
27
+ canView(actor) {
28
+ return roleOf(actor) !== null;
29
+ },
30
+ grant(userId, role) {
31
+ if (userId === ownerId)
32
+ return;
33
+ roles.set(userId, role);
34
+ },
35
+ revoke(userId) {
36
+ roles.delete(userId);
37
+ },
38
+ setGuild(nextGuildId, role) {
39
+ guildId = nextGuildId ?? undefined;
40
+ guildRole = nextGuildId === null ? undefined : role ?? guildRole ?? "editor";
41
+ },
42
+ transferOwner(userId) {
43
+ roles.delete(userId);
44
+ ownerId = userId;
45
+ },
46
+ snapshot() {
47
+ return {
48
+ plotId: config.plotId,
49
+ ownerId,
50
+ ...(guildId === undefined ? {} : { guildId }),
51
+ roles: Object.fromEntries(roles),
52
+ ...(guildRole === undefined ? {} : { guildRole }),
53
+ };
54
+ },
55
+ };
56
+ }
57
+ export function createContributionPool(goal) {
58
+ const perResource = new Map();
59
+ function totalFor(resource) {
60
+ let sum = 0;
61
+ for (const amount of perResource.get(resource)?.values() ?? [])
62
+ sum += amount;
63
+ return sum;
64
+ }
65
+ function totals() {
66
+ const out = {};
67
+ for (const resource of Object.keys(goal))
68
+ out[resource] = totalFor(resource);
69
+ return out;
70
+ }
71
+ function isComplete() {
72
+ for (const resource of Object.keys(goal)) {
73
+ if (totalFor(resource) < goal[resource])
74
+ return false;
75
+ }
76
+ return true;
77
+ }
78
+ return {
79
+ contribute(userId, resource, amount) {
80
+ if (amount <= 0 || goal[resource] === undefined) {
81
+ return { accepted: 0, overflow: amount > 0 ? amount : 0, complete: isComplete() };
82
+ }
83
+ const need = Math.max(0, goal[resource] - totalFor(resource));
84
+ const accepted = Math.min(need, amount);
85
+ const overflow = amount - accepted;
86
+ if (accepted > 0) {
87
+ const contributors = perResource.get(resource) ?? new Map();
88
+ contributors.set(userId, (contributors.get(userId) ?? 0) + accepted);
89
+ perResource.set(resource, contributors);
90
+ }
91
+ return { accepted, overflow, complete: isComplete() };
92
+ },
93
+ totals,
94
+ remaining() {
95
+ const out = {};
96
+ for (const resource of Object.keys(goal))
97
+ out[resource] = Math.max(0, goal[resource] - totalFor(resource));
98
+ return out;
99
+ },
100
+ byContributor(userId) {
101
+ const out = {};
102
+ for (const [resource, contributors] of perResource) {
103
+ const amount = contributors.get(userId);
104
+ if (amount !== undefined)
105
+ out[resource] = amount;
106
+ }
107
+ return out;
108
+ },
109
+ progress() {
110
+ const resources = Object.keys(goal);
111
+ if (resources.length === 0)
112
+ return 1;
113
+ let sum = 0;
114
+ for (const resource of resources) {
115
+ const target = goal[resource];
116
+ sum += target <= 0 ? 1 : Math.min(1, totalFor(resource) / target);
117
+ }
118
+ return sum / resources.length;
119
+ },
120
+ isComplete,
121
+ snapshot() {
122
+ const out = {};
123
+ for (const [resource, contributors] of perResource)
124
+ out[resource] = Object.fromEntries(contributors);
125
+ return out;
126
+ },
127
+ restore(snapshot) {
128
+ perResource.clear();
129
+ for (const [resource, contributors] of Object.entries(snapshot)) {
130
+ perResource.set(resource, new Map(Object.entries(contributors)));
131
+ }
132
+ },
133
+ };
134
+ }
@@ -0,0 +1,105 @@
1
+ import { type TerrainField } from "./terrain.js";
2
+ import type { WorldBounds } from "./features.js";
3
+ export declare const EMPTY_VOXEL = 0;
4
+ export interface VoxelMaterial {
5
+ /** Cell value stored in the grid; 0 is reserved for empty. */
6
+ id: number;
7
+ name: string;
8
+ /** A carve tool's strength must reach this to remove the material — hard rock resists a weak drill. Default 0 (anything digs it). */
9
+ strength: number;
10
+ /** Debris/harvest hint carried through to game code; the engine only tallies removed cells by id. */
11
+ drop?: string;
12
+ }
13
+ export type VoxelMaterialTable = Readonly<Record<number, VoxelMaterial>>;
14
+ export interface VoxelVolumeConfig {
15
+ /** Grid resolution in cells: [nx, ny, nz]. */
16
+ dims: readonly [number, number, number];
17
+ /** World position of the min corner of cell [0,0,0]. Default [0,0,0]. */
18
+ origin?: readonly [number, number, number];
19
+ /** World-space edge length of one cubic cell. Default 1. */
20
+ scale?: number;
21
+ /** Material id every cell starts as. Default 1 (a solid volume waiting to be dug). */
22
+ fill?: number;
23
+ /** Strength lookup by material id; carve honours it against a tool strength. */
24
+ materials?: VoxelMaterialTable;
25
+ }
26
+ export interface CarveOp {
27
+ /** Sphere centre in world coordinates. */
28
+ center: readonly [number, number, number];
29
+ /** Sphere radius in world units. */
30
+ radius: number;
31
+ /** Only cells whose material `strength` is ≤ this are removed. Default Infinity (dig anything). */
32
+ toolStrength?: number;
33
+ }
34
+ export interface DepositOp {
35
+ center: readonly [number, number, number];
36
+ radius: number;
37
+ /** Material id to fill empty cells with (Engineer platform / Astroneer terrain add). */
38
+ material: number;
39
+ }
40
+ /**
41
+ * A runtime-editable dense voxel grid — the carve/deposit op behind destructible dig worlds (Deep
42
+ * Rock Galactic tunnels, Astroneer terrain). Cells hold a material id (0 = empty); `carve` clears a
43
+ * sphere of solid cells that a tool is strong enough to break and returns how many it removed (feed
44
+ * that to a loot roll), `deposit` fills a sphere with a material. World↔cell mapping is `origin`+`scale`.
45
+ */
46
+ export declare class VoxelVolume {
47
+ readonly nx: number;
48
+ readonly ny: number;
49
+ readonly nz: number;
50
+ readonly scale: number;
51
+ readonly origin: readonly [number, number, number];
52
+ private readonly cells;
53
+ private readonly materials;
54
+ private solidCount;
55
+ constructor(config: VoxelVolumeConfig);
56
+ get solid(): number;
57
+ private index;
58
+ inBounds(ix: number, iy: number, iz: number): boolean;
59
+ get(ix: number, iy: number, iz: number): number;
60
+ set(ix: number, iy: number, iz: number, material: number): void;
61
+ worldToCell(x: number, y: number, z: number): readonly [number, number, number];
62
+ solidAtWorld(x: number, y: number, z: number): boolean;
63
+ private strengthOf;
64
+ private forEachCellInSphere;
65
+ /** Clear every solid cell in the sphere the tool can break; returns the number removed. */
66
+ carve(op: CarveOp): number;
67
+ /** Fill every empty cell in the sphere with `material`; returns the number filled. */
68
+ deposit(op: DepositOp): number;
69
+ }
70
+ export interface CraterOp {
71
+ /** Crater centre (x, z in world units). */
72
+ x: number;
73
+ z: number;
74
+ radius: number;
75
+ /** How deep the bowl cuts at its centre. */
76
+ depth: number;
77
+ }
78
+ export interface MoundOp {
79
+ x: number;
80
+ z: number;
81
+ radius: number;
82
+ /** How high the mound rises at its centre. */
83
+ height: number;
84
+ }
85
+ /**
86
+ * A `TerrainField` you can write craters and mounds into at runtime — the height-field side of
87
+ * destructible terrain (Helldivers 2 explosion craters, engineer-deposited berms). Wraps a base field
88
+ * and layers smooth radial deformations on top, so `sampleHeight` (and therefore ground-snap,
89
+ * collision, and the shell's terrain mesh) all read the deformed surface. `carve` digs a bowl,
90
+ * `deposit` raises a mound.
91
+ */
92
+ export declare class CarvableField implements TerrainField {
93
+ readonly bounds?: WorldBounds;
94
+ readonly waterLevel?: number;
95
+ readonly sampleNormal: TerrainField["sampleNormal"];
96
+ private readonly base;
97
+ private readonly edits;
98
+ constructor(base: TerrainField);
99
+ get editCount(): number;
100
+ sampleHeight(x: number, z: number): number;
101
+ carve(op: CraterOp): void;
102
+ deposit(op: MoundOp): void;
103
+ clear(): void;
104
+ }
105
+ export declare function carvableTerrain(base: TerrainField): CarvableField;