@jgengine/core 0.19.0 → 0.22.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.
- package/CHANGELOG.md +364 -0
- package/dist/ai/decisionGraph.d.ts +2 -0
- package/dist/ai/decisionGraph.js +6 -0
- package/dist/ai/flock.d.ts +1 -1
- package/dist/ai/flock.js +7 -4
- package/dist/anim/animGraph.d.ts +33 -2
- package/dist/anim/animGraph.js +200 -12
- package/dist/anim/locomotionGraph.d.ts +11 -3
- package/dist/anim/locomotionGraph.js +25 -15
- package/dist/audio/audioFalloff.d.ts +4 -0
- package/dist/audio/voiceAllocator.d.ts +46 -0
- package/dist/audio/voiceAllocator.js +76 -0
- package/dist/combat/abilityKit.d.ts +19 -0
- package/dist/combat/abilityKit.js +40 -4
- package/dist/combat/death.d.ts +4 -3
- package/dist/combat/death.js +3 -3
- package/dist/combat/magazine.d.ts +9 -0
- package/dist/combat/magazine.js +24 -2
- package/dist/combat/projectiles.d.ts +94 -2
- package/dist/combat/projectiles.js +282 -61
- package/dist/combat/regenShield.d.ts +14 -0
- package/dist/combat/regenShield.js +23 -7
- package/dist/crafting/recipeCatalog.d.ts +16 -0
- package/dist/crafting/recipeCatalog.js +55 -0
- package/dist/economy/auctionBook.d.ts +1 -1
- package/dist/economy/auctionBook.js +11 -1
- package/dist/economy/listingBook.d.ts +1 -1
- package/dist/economy/listingBook.js +11 -1
- package/dist/economy/resourceLedger.js +3 -3
- package/dist/economy/shopStock.js +7 -6
- package/dist/economy/techTree.js +7 -6
- package/dist/economy/unlockPoints.js +3 -6
- package/dist/editor/commandApply.js +89 -16
- package/dist/editor/commands.d.ts +38 -1
- package/dist/editor/commands.js +320 -3
- package/dist/editor/creatorStorage.d.ts +59 -0
- package/dist/editor/creatorStorage.js +164 -0
- package/dist/editor/document.d.ts +4 -0
- package/dist/editor/document.js +116 -17
- package/dist/editor/index.d.ts +3 -0
- package/dist/editor/index.js +1 -0
- package/dist/editor/liveSync.d.ts +4 -1
- package/dist/editor/liveSync.js +26 -10
- package/dist/editor/materialAuthoring.d.ts +14 -0
- package/dist/editor/materialAuthoring.js +38 -0
- package/dist/editor/modelNodeVisibility.d.ts +11 -0
- package/dist/editor/modelNodeVisibility.js +29 -0
- package/dist/editor/movementCatalog.d.ts +90 -0
- package/dist/editor/movementCatalog.js +144 -0
- package/dist/editor/simulation.d.ts +60 -0
- package/dist/editor/simulation.js +202 -0
- package/dist/editor/staticPrefab.d.ts +17 -0
- package/dist/editor/staticPrefab.js +51 -0
- package/dist/editor/terrainMaterials.d.ts +10 -0
- package/dist/editor/terrainMaterials.js +13 -0
- package/dist/editor/types.d.ts +11 -0
- package/dist/game/cameraConfig.d.ts +3 -1
- package/dist/game/clipRoles.d.ts +2 -1
- package/dist/game/clipRoles.js +7 -3
- package/dist/game/contentValidation.d.ts +69 -0
- package/dist/game/contentValidation.js +118 -0
- package/dist/game/controlGate.d.ts +8 -0
- package/dist/game/controlGate.js +43 -6
- package/dist/game/defineGame.d.ts +32 -3
- package/dist/game/dialogueGraph.d.ts +20 -0
- package/dist/game/dialogueGraph.js +107 -0
- package/dist/game/events.d.ts +5 -0
- package/dist/game/keyValueStore.d.ts +17 -4
- package/dist/game/keyValueStore.js +35 -23
- package/dist/game/lootModifiers.d.ts +17 -0
- package/dist/game/lootModifiers.js +26 -0
- package/dist/game/narrativeAuthoring.d.ts +67 -0
- package/dist/game/narrativeAuthoring.js +122 -0
- package/dist/game/playableGame.d.ts +52 -20
- package/dist/game/quest.d.ts +15 -0
- package/dist/game/quest.js +209 -63
- package/dist/game/questCatalog.d.ts +50 -0
- package/dist/game/questCatalog.js +205 -0
- package/dist/game/saveStore.d.ts +6 -5
- package/dist/game/saveStore.js +57 -57
- package/dist/game/systemRuntime.d.ts +2 -2
- package/dist/game/systemRuntime.js +84 -33
- package/dist/gameplay.d.ts +3 -0
- package/dist/gameplay.js +3 -0
- package/dist/interaction/promptRegistry.d.ts +5 -4
- package/dist/interaction/promptRegistry.js +8 -2
- package/dist/inventory/inventoryModel.js +9 -6
- package/dist/inventory/shapedGrid.d.ts +48 -9
- package/dist/inventory/shapedGrid.js +69 -10
- package/dist/item/durability.js +4 -0
- package/dist/item/itemInstanceRegistry.d.ts +32 -1
- package/dist/item/itemInstanceRegistry.js +111 -7
- package/dist/item/itemgen.d.ts +1 -1
- package/dist/item/itemgen.js +1 -1
- package/dist/item/modularItem.js +7 -1
- package/dist/material/appearanceSignals.d.ts +53 -0
- package/dist/material/appearanceSignals.js +47 -0
- package/dist/material/hairCards.d.ts +47 -0
- package/dist/material/hairCards.js +136 -0
- package/dist/material/materialAsset.d.ts +162 -0
- package/dist/material/materialAsset.js +382 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +348 -1
- package/dist/movement/characterController.d.ts +5 -2
- package/dist/movement/characterController.js +50 -23
- package/dist/movement/freeFlight.d.ts +2 -2
- package/dist/movement/freeFlight.js +3 -3
- package/dist/movement/movementModel.d.ts +24 -1
- package/dist/movement/movementModel.js +74 -7
- package/dist/movement/playerMovement.d.ts +28 -1
- package/dist/movement/playerMovement.js +425 -102
- package/dist/movement/poseState.d.ts +2 -0
- package/dist/movement/poseState.js +29 -1
- package/dist/movement/solidObstacles.d.ts +3 -1
- package/dist/movement/solidObstacles.js +8 -5
- package/dist/movement/terrainGrade.d.ts +13 -0
- package/dist/movement/terrainGrade.js +33 -0
- package/dist/movement/voxelController.d.ts +2 -2
- package/dist/movement/voxelController.js +3 -3
- package/dist/nav/navGrid.d.ts +19 -1
- package/dist/nav/navGrid.js +26 -10
- package/dist/nav/solidRoute.d.ts +50 -0
- package/dist/nav/solidRoute.js +144 -0
- package/dist/physics/ballisticSweep.d.ts +10 -3
- package/dist/physics/ballisticSweep.js +53 -29
- package/dist/physics/forceVolume.d.ts +41 -0
- package/dist/physics/forceVolume.js +88 -0
- package/dist/physics/kinematicVehicle.d.ts +8 -3
- package/dist/physics/kinematicVehicle.js +2 -2
- package/dist/physics/physicsWorldBackend.js +19 -3
- package/dist/physics/spatialGrid.d.ts +1 -1
- package/dist/physics/spatialGrid.js +8 -9
- package/dist/physics/vehicleDynamics.d.ts +3 -2
- package/dist/physics/vehicleDynamics.js +2 -2
- package/dist/physics/vehicleObstacles.d.ts +3 -2
- package/dist/physics/vehicleObstacles.js +29 -5
- package/dist/runtime/cameraDirector.d.ts +39 -2
- package/dist/runtime/cameraDirector.js +162 -0
- package/dist/runtime/commandDispatch.d.ts +14 -0
- package/dist/runtime/commandDispatch.js +19 -0
- package/dist/runtime/context/combat.d.ts +11 -4
- package/dist/runtime/context/combat.js +178 -11
- package/dist/runtime/context/deathLoot.d.ts +9 -8
- package/dist/runtime/context/deathLoot.js +16 -16
- package/dist/runtime/context/scene.d.ts +2 -0
- package/dist/runtime/context/scene.js +13 -21
- package/dist/runtime/descriptors/baseline.js +34 -3
- package/dist/runtime/descriptors/features.d.ts +2 -1
- package/dist/runtime/descriptors/features.js +18 -0
- package/dist/runtime/gameContext.js +79 -13
- package/dist/runtime/gameContextTypes.d.ts +14 -0
- package/dist/runtime/headlessRunner.d.ts +3 -1
- package/dist/runtime/headlessRunner.js +11 -11
- package/dist/runtime/hostedGameRunner.d.ts +24 -1
- package/dist/runtime/hostedGameRunner.js +156 -10
- package/dist/runtime/hostedWorldSession.d.ts +17 -5
- package/dist/runtime/hostedWorldSession.js +154 -16
- package/dist/runtime/hostedWorldStore.d.ts +2 -2
- package/dist/runtime/inputRecorder.d.ts +1 -1
- package/dist/runtime/inputRecorder.js +21 -1
- package/dist/runtime/inputSnapshot.d.ts +16 -4
- package/dist/runtime/inputSnapshot.js +31 -3
- package/dist/runtime/runtimeSave.d.ts +10 -6
- package/dist/runtime/runtimeSave.js +11 -5
- package/dist/runtime/simContext.d.ts +8 -3
- package/dist/runtime/simContext.js +7 -3
- package/dist/runtime/simSnapshot.js +2 -0
- package/dist/runtime/worldReplication.d.ts +2 -0
- package/dist/runtime/worldReplication.js +1 -1
- package/dist/scene/assetCatalog.d.ts +2 -0
- package/dist/scene/behaviorRuntime.d.ts +26 -5
- package/dist/scene/behaviorRuntime.js +263 -16
- package/dist/scene/behaviors.d.ts +30 -1
- package/dist/scene/behaviors.js +29 -0
- package/dist/scene/entityStore.d.ts +14 -3
- package/dist/scene/entityStore.js +45 -9
- package/dist/scene/mount.d.ts +9 -0
- package/dist/scene/mount.js +35 -0
- package/dist/scene/possession.d.ts +2 -0
- package/dist/scene/possession.js +21 -1
- package/dist/scene/sceneRaycast.d.ts +7 -0
- package/dist/scene/sceneRaycast.js +55 -0
- package/dist/scene/spatial.d.ts +20 -1
- package/dist/scene/spatial.js +94 -2
- package/dist/scene/vehicleSeat.d.ts +36 -2
- package/dist/scene/vehicleSeat.js +89 -2
- package/dist/stats/accumulatorMeter.d.ts +10 -0
- package/dist/stats/accumulatorMeter.js +6 -0
- package/dist/stats/eventMeter.d.ts +9 -2
- package/dist/stats/eventMeter.js +5 -2
- package/dist/store/defineStore.d.ts +3 -1
- package/dist/store/defineStore.js +3 -1
- package/dist/store/observableKeyedStore.d.ts +1 -1
- package/dist/store/observableKeyedStore.js +8 -6
- package/dist/time/simClock.d.ts +6 -2
- package/dist/time/simClock.js +5 -0
- package/dist/vfx/particleDirector.d.ts +58 -6
- package/dist/vfx/particleDirector.js +113 -22
- package/dist/vfx/particles.d.ts +68 -5
- package/dist/vfx/particles.js +284 -32
- package/dist/visibility/assetStreaming.d.ts +21 -3
- package/dist/visibility/assetStreaming.js +108 -24
- package/dist/visibility/settings.d.ts +8 -2
- package/dist/visibility/settings.js +3 -0
- package/dist/visibility/visibilitySystem.js +1 -0
- package/dist/work/expedition.d.ts +110 -0
- package/dist/work/expedition.js +195 -0
- package/dist/work/staffedStation.d.ts +29 -0
- package/dist/work/staffedStation.js +27 -0
- package/dist/world/authoredAnimation.d.ts +38 -0
- package/dist/world/authoredAnimation.js +99 -0
- package/dist/world/authoredObjects.d.ts +24 -4
- package/dist/world/authoredObjects.js +43 -16
- package/dist/world/authoredSimulation.d.ts +74 -0
- package/dist/world/authoredSimulation.js +302 -0
- package/dist/world/authoredWeather.d.ts +120 -0
- package/dist/world/authoredWeather.js +314 -0
- package/dist/world/buildingKit.d.ts +8 -3
- package/dist/world/buildingKit.js +21 -8
- package/dist/world/environmentForces.d.ts +21 -0
- package/dist/world/environmentForces.js +42 -0
- package/dist/world/features.d.ts +3 -3
- package/dist/world/footprintGrid.d.ts +55 -0
- package/dist/world/footprintGrid.js +201 -0
- package/dist/world/grassKind.d.ts +10 -0
- package/dist/world/grassKind.js +22 -0
- package/dist/world/pathTerrain.d.ts +1 -1
- package/dist/world/terraform.d.ts +2 -2
- package/dist/world/terraform.js +46 -18
- package/dist/world/weather.d.ts +17 -0
- package/dist/world/weather.js +116 -20
- package/dist/world/worldSolids.d.ts +11 -0
- package/dist/world/worldSolids.js +112 -1
- package/dist/world.d.ts +5 -5
- package/dist/world.js +4 -4
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +1 -1
- package/skills/game-design/SKILL.md +2 -0
- package/skills/jgengine/SKILL.md +23 -2
- package/skills/jgengine/api.md +28 -17
- package/skills/jgengine/recipes/minimal-game.md +7 -2
- package/skills/jgengine-combat/SKILL.md +8 -2
- package/skills/jgengine-combat/api.md +22 -10
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +60 -6
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +37 -3
- package/skills/jgengine-combat/reference.md +24 -2
- package/skills/jgengine-gameplay/SKILL.md +63 -0
- package/skills/jgengine-gameplay/api.md +131 -37
- package/skills/jgengine-gameplay/capabilities.md +68 -0
- package/skills/jgengine-gameplay/recipes/device-save-status.md +46 -0
- package/skills/jgengine-gameplay/recipes/expeditions.md +108 -0
- package/skills/jgengine-gameplay/recipes/narrative-authoring.md +76 -0
- package/skills/jgengine-gameplay/recipes/paged-item-storage.md +101 -0
- package/skills/jgengine-gameplay/recipes/quest-authoring.md +63 -0
- package/skills/jgengine-gameplay/recipes/shaped-inventory.md +50 -0
- package/skills/jgengine-gameplay/reference-content-validation.md +74 -0
- package/skills/jgengine-gameplay/reference-systems.md +119 -0
- package/skills/jgengine-world/SKILL.md +12 -3
- package/skills/jgengine-world/api.md +201 -58
- package/skills/jgengine-world/capabilities.md +116 -3
- package/skills/jgengine-world/recipes/character-animation.md +31 -8
- package/skills/jgengine-world/recipes/controls.md +4 -0
- package/skills/jgengine-world/recipes/npc-senses.md +12 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +3 -1
- package/skills/jgengine-world/reference.md +167 -5
- package/skills/jgengine-world/visibility.md +28 -1
package/dist/game/saveStore.js
CHANGED
|
@@ -1,47 +1,45 @@
|
|
|
1
|
-
import { defaultKeyValueStorage } from "./keyValueStore.js";
|
|
2
|
-
function readSafe(storage, key) {
|
|
3
|
-
if (storage === null)
|
|
4
|
-
return null;
|
|
5
|
-
try {
|
|
6
|
-
return storage.getItem(key);
|
|
7
|
-
}
|
|
8
|
-
catch {
|
|
9
|
-
return null;
|
|
10
|
-
}
|
|
11
|
-
}
|
|
1
|
+
import { defaultKeyValueStorage, reportStorageFailure } from "./keyValueStore.js";
|
|
12
2
|
/**
|
|
13
3
|
* A {@link SaveBackend} over a synchronous {@link KeyValueStorage} — the
|
|
14
4
|
* browser's `localStorage` by default (offline, on-device saves), a test stub,
|
|
15
|
-
* or `null` for
|
|
16
|
-
*
|
|
5
|
+
* or `null` for a no-op backend. Default fallback ignores failures; `errorMode: "throw"`
|
|
6
|
+
* rejects failed operations and unavailable storage, so SaveStore status/onError can report them.
|
|
7
|
+
* Use {@link memorySaveBackend} for deliberate ephemeral persistence.
|
|
17
8
|
*
|
|
18
9
|
* @capability local-save persist a game to on-device localStorage (offline)
|
|
19
10
|
*/
|
|
20
|
-
export function localSaveBackend(storage) {
|
|
21
|
-
const
|
|
11
|
+
export function localSaveBackend(storage, policy = {}) {
|
|
12
|
+
const resolve = () => {
|
|
13
|
+
const store = storage === undefined ? defaultKeyValueStorage((error) => { throw error; }) : storage;
|
|
14
|
+
if (store === null)
|
|
15
|
+
throw new Error("Local storage is unavailable");
|
|
16
|
+
return store;
|
|
17
|
+
};
|
|
22
18
|
return {
|
|
23
|
-
read
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
// storage rejected the write; the store keeps the value in memory
|
|
31
|
-
}
|
|
19
|
+
async read(key) {
|
|
20
|
+
try {
|
|
21
|
+
return resolve().getItem(key);
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
reportStorageFailure(policy, { operation: "read", key, error });
|
|
25
|
+
return null;
|
|
32
26
|
}
|
|
33
|
-
return Promise.resolve();
|
|
34
27
|
},
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
28
|
+
async write(key, value) {
|
|
29
|
+
try {
|
|
30
|
+
resolve().setItem(key, value);
|
|
31
|
+
}
|
|
32
|
+
catch (error) {
|
|
33
|
+
reportStorageFailure(policy, { operation: "write", key, error });
|
|
34
|
+
}
|
|
35
|
+
},
|
|
36
|
+
async remove(key) {
|
|
37
|
+
try {
|
|
38
|
+
resolve().removeItem(key);
|
|
39
|
+
}
|
|
40
|
+
catch (error) {
|
|
41
|
+
reportStorageFailure(policy, { operation: "remove", key, error });
|
|
43
42
|
}
|
|
44
|
-
return Promise.resolve();
|
|
45
43
|
},
|
|
46
44
|
};
|
|
47
45
|
}
|
|
@@ -143,10 +141,10 @@ export function createSaveStore(config) {
|
|
|
143
141
|
return parsed;
|
|
144
142
|
}
|
|
145
143
|
async function readSlots() {
|
|
144
|
+
const raw = await backend.read(indexKey());
|
|
145
|
+
if (raw === null)
|
|
146
|
+
return [];
|
|
146
147
|
try {
|
|
147
|
-
const raw = await backend.read(indexKey());
|
|
148
|
-
if (raw === null)
|
|
149
|
-
return [];
|
|
150
148
|
const parsed = JSON.parse(raw);
|
|
151
149
|
return Array.isArray(parsed) ? parsed.filter((entry) => typeof entry === "string") : [];
|
|
152
150
|
}
|
|
@@ -155,26 +153,16 @@ export function createSaveStore(config) {
|
|
|
155
153
|
}
|
|
156
154
|
}
|
|
157
155
|
async function rememberSlot(slot) {
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
await backend.write(indexKey(), JSON.stringify([...list, slot]));
|
|
163
|
-
}
|
|
164
|
-
catch (error) {
|
|
165
|
-
reportError(error);
|
|
166
|
-
}
|
|
156
|
+
const list = await readSlots();
|
|
157
|
+
if (list.includes(slot))
|
|
158
|
+
return;
|
|
159
|
+
await backend.write(indexKey(), JSON.stringify([...list, slot]));
|
|
167
160
|
}
|
|
168
161
|
async function forgetSlot(slot) {
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
await backend.write(indexKey(), JSON.stringify(list.filter((entry) => entry !== slot)));
|
|
174
|
-
}
|
|
175
|
-
catch (error) {
|
|
176
|
-
reportError(error);
|
|
177
|
-
}
|
|
162
|
+
const list = await readSlots();
|
|
163
|
+
if (!list.includes(slot))
|
|
164
|
+
return;
|
|
165
|
+
await backend.write(indexKey(), JSON.stringify(list.filter((entry) => entry !== slot)));
|
|
178
166
|
}
|
|
179
167
|
function clearTimer() {
|
|
180
168
|
if (pendingTimer !== null) {
|
|
@@ -243,7 +231,10 @@ export function createSaveStore(config) {
|
|
|
243
231
|
firstDirtyAt = null;
|
|
244
232
|
setStatus("idle");
|
|
245
233
|
emit();
|
|
246
|
-
void rememberSlot(currentSlot)
|
|
234
|
+
void rememberSlot(currentSlot).catch((error) => {
|
|
235
|
+
setStatus("error");
|
|
236
|
+
reportError(error);
|
|
237
|
+
});
|
|
247
238
|
return current;
|
|
248
239
|
}
|
|
249
240
|
catch (error) {
|
|
@@ -298,7 +289,16 @@ export function createSaveStore(config) {
|
|
|
298
289
|
firstDirtyAt = null;
|
|
299
290
|
return load();
|
|
300
291
|
},
|
|
301
|
-
slots
|
|
292
|
+
async slots() {
|
|
293
|
+
try {
|
|
294
|
+
return await readSlots();
|
|
295
|
+
}
|
|
296
|
+
catch (error) {
|
|
297
|
+
setStatus("error");
|
|
298
|
+
reportError(error);
|
|
299
|
+
return [];
|
|
300
|
+
}
|
|
301
|
+
},
|
|
302
302
|
subscribe(listener) {
|
|
303
303
|
listeners.add(listener);
|
|
304
304
|
return () => listeners.delete(listener);
|
|
@@ -15,14 +15,14 @@ export interface InstalledSystems {
|
|
|
15
15
|
tick(ctx: GameContext, dt: number): void;
|
|
16
16
|
/** Invoke every system's `reset` (scenario/run wipe). */
|
|
17
17
|
reset(ctx: GameContext): void;
|
|
18
|
-
/**
|
|
18
|
+
/** Retire once in reverse order; finish cleanup before rethrowing the first error. */
|
|
19
19
|
dispose(ctx: GameContext): void;
|
|
20
20
|
/** Run one manual system by id (no-op if missing or not manual). */
|
|
21
21
|
runManual(ctx: GameContext, id: string, dt?: number): void;
|
|
22
22
|
}
|
|
23
23
|
/**
|
|
24
24
|
* Install systems on a live context: `create` → bind events → register save/replicate → `start`.
|
|
25
|
-
* Call once per world boot
|
|
25
|
+
* Call once per world boot. Failed installation retires entered systems and listeners, then rethrows the startup error.
|
|
26
26
|
* @internal
|
|
27
27
|
*/
|
|
28
28
|
export declare function installSystems(ctx: GameContext, systems: readonly SystemDefinition[], options?: CompileSystemScheduleOptions & {
|
|
@@ -7,7 +7,7 @@ function resolveModule(spec, ctx) {
|
|
|
7
7
|
}
|
|
8
8
|
/**
|
|
9
9
|
* Install systems on a live context: `create` → bind events → register save/replicate → `start`.
|
|
10
|
-
* Call once per world boot
|
|
10
|
+
* Call once per world boot. Failed installation retires entered systems and listeners, then rethrows the startup error.
|
|
11
11
|
* @internal
|
|
12
12
|
*/
|
|
13
13
|
export function installSystems(ctx, systems, options) {
|
|
@@ -16,29 +16,41 @@ export function installSystems(ctx, systems, options) {
|
|
|
16
16
|
const unsubs = [];
|
|
17
17
|
const intervalAcc = new Map();
|
|
18
18
|
const fixedAcc = new Map();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
19
|
+
const acquired = [];
|
|
20
|
+
let disposed = false;
|
|
21
|
+
try {
|
|
22
|
+
for (const system of list) {
|
|
23
|
+
acquired.push(system);
|
|
24
|
+
system.create?.(ctx);
|
|
25
|
+
}
|
|
26
|
+
for (const system of list) {
|
|
27
|
+
if (system.events !== undefined) {
|
|
28
|
+
for (const [name, handler] of Object.entries(system.events)) {
|
|
29
|
+
if (handler === undefined)
|
|
30
|
+
continue;
|
|
31
|
+
const off = ctx.game.events.on(name, ((event) => {
|
|
32
|
+
handler(ctx, event);
|
|
33
|
+
}));
|
|
34
|
+
unsubs.push(off);
|
|
35
|
+
}
|
|
31
36
|
}
|
|
37
|
+
const save = resolveModule(system.save, ctx);
|
|
38
|
+
if (save !== undefined)
|
|
39
|
+
options?.modules?.registerSave(save);
|
|
40
|
+
const replicate = resolveModule(system.replicate, ctx);
|
|
41
|
+
if (replicate !== undefined)
|
|
42
|
+
options?.modules?.registerReplicate(replicate);
|
|
43
|
+
}
|
|
44
|
+
for (const system of list) {
|
|
45
|
+
system.start?.(ctx);
|
|
32
46
|
}
|
|
33
|
-
const save = resolveModule(system.save, ctx);
|
|
34
|
-
if (save !== undefined)
|
|
35
|
-
options?.modules?.registerSave(save);
|
|
36
|
-
const replicate = resolveModule(system.replicate, ctx);
|
|
37
|
-
if (replicate !== undefined)
|
|
38
|
-
options?.modules?.registerReplicate(replicate);
|
|
39
47
|
}
|
|
40
|
-
|
|
41
|
-
|
|
48
|
+
catch (error) {
|
|
49
|
+
try {
|
|
50
|
+
dispose(ctx);
|
|
51
|
+
}
|
|
52
|
+
catch { /* The startup error remains the first failure. */ }
|
|
53
|
+
throw error;
|
|
42
54
|
}
|
|
43
55
|
for (const group of schedule.fixed) {
|
|
44
56
|
fixedAcc.set(group.rate, 0);
|
|
@@ -52,7 +64,7 @@ export function installSystems(ctx, systems, options) {
|
|
|
52
64
|
}
|
|
53
65
|
}
|
|
54
66
|
function tick(ctx, dt) {
|
|
55
|
-
if (!(dt > 0))
|
|
67
|
+
if (disposed || !(dt > 0))
|
|
56
68
|
return;
|
|
57
69
|
for (const group of schedule.fixed) {
|
|
58
70
|
const step = 1 / group.rate;
|
|
@@ -79,6 +91,8 @@ export function installSystems(ctx, systems, options) {
|
|
|
79
91
|
}
|
|
80
92
|
}
|
|
81
93
|
function reset(ctx) {
|
|
94
|
+
if (disposed)
|
|
95
|
+
return;
|
|
82
96
|
for (const system of list)
|
|
83
97
|
system.reset?.(ctx);
|
|
84
98
|
for (const group of schedule.fixed)
|
|
@@ -87,14 +101,33 @@ export function installSystems(ctx, systems, options) {
|
|
|
87
101
|
intervalAcc.set(interval.id, 0);
|
|
88
102
|
}
|
|
89
103
|
function dispose(ctx) {
|
|
90
|
-
|
|
91
|
-
|
|
104
|
+
if (disposed)
|
|
105
|
+
return;
|
|
106
|
+
disposed = true;
|
|
107
|
+
let failure;
|
|
108
|
+
for (const off of unsubs) {
|
|
109
|
+
try {
|
|
110
|
+
off();
|
|
111
|
+
}
|
|
112
|
+
catch (error) {
|
|
113
|
+
failure ??= { error };
|
|
114
|
+
}
|
|
115
|
+
}
|
|
92
116
|
unsubs.length = 0;
|
|
93
|
-
for (let i =
|
|
94
|
-
|
|
117
|
+
for (let i = acquired.length - 1; i >= 0; i -= 1) {
|
|
118
|
+
try {
|
|
119
|
+
acquired[i]?.dispose?.(ctx);
|
|
120
|
+
}
|
|
121
|
+
catch (error) {
|
|
122
|
+
failure ??= { error };
|
|
123
|
+
}
|
|
95
124
|
}
|
|
125
|
+
if (failure !== undefined)
|
|
126
|
+
throw failure.error;
|
|
96
127
|
}
|
|
97
128
|
function runManual(ctx, id, dt = 0) {
|
|
129
|
+
if (disposed)
|
|
130
|
+
return;
|
|
98
131
|
if (!schedule.manual.includes(id))
|
|
99
132
|
return;
|
|
100
133
|
schedule.systemsById.get(id)?.update?.(ctx, dt);
|
|
@@ -119,17 +152,18 @@ export function composeGameLoop(systems, loop, options) {
|
|
|
119
152
|
if (systems === undefined || systems.length === 0) {
|
|
120
153
|
return loop ?? {};
|
|
121
154
|
}
|
|
122
|
-
|
|
155
|
+
const retired = new WeakSet();
|
|
123
156
|
const loopBefore = options?.loopBeforeSystems === true;
|
|
124
157
|
return {
|
|
125
158
|
onInit(ctx) {
|
|
159
|
+
retired.delete(ctx);
|
|
126
160
|
const modules = ctx.game.registerSave !== undefined && ctx.game.registerReplicate !== undefined
|
|
127
161
|
? {
|
|
128
162
|
registerSave: (m) => ctx.game.registerSave(m),
|
|
129
163
|
registerReplicate: (m) => ctx.game.registerReplicate(m),
|
|
130
164
|
}
|
|
131
165
|
: undefined;
|
|
132
|
-
installed = installSystems(ctx, systems, { ...options, modules });
|
|
166
|
+
const installed = installSystems(ctx, systems, { ...options, modules });
|
|
133
167
|
installedByCtx.set(ctx, installed);
|
|
134
168
|
loop?.onInit?.(ctx);
|
|
135
169
|
},
|
|
@@ -139,10 +173,10 @@ export function composeGameLoop(systems, loop, options) {
|
|
|
139
173
|
onTick(ctx, dt) {
|
|
140
174
|
if (loopBefore) {
|
|
141
175
|
loop?.onTick?.(ctx, dt);
|
|
142
|
-
|
|
176
|
+
installedByCtx.get(ctx)?.tick(ctx, dt);
|
|
143
177
|
}
|
|
144
178
|
else {
|
|
145
|
-
|
|
179
|
+
installedByCtx.get(ctx)?.tick(ctx, dt);
|
|
146
180
|
loop?.onTick?.(ctx, dt);
|
|
147
181
|
}
|
|
148
182
|
},
|
|
@@ -150,13 +184,30 @@ export function composeGameLoop(systems, loop, options) {
|
|
|
150
184
|
loop?.onPlayerLeave?.(ctx, player);
|
|
151
185
|
},
|
|
152
186
|
onReset(ctx) {
|
|
153
|
-
|
|
187
|
+
installedByCtx.get(ctx)?.reset(ctx);
|
|
154
188
|
loop?.onReset?.(ctx);
|
|
155
189
|
},
|
|
156
190
|
onDispose(ctx) {
|
|
157
|
-
|
|
191
|
+
if (retired.has(ctx))
|
|
192
|
+
return;
|
|
193
|
+
retired.add(ctx);
|
|
194
|
+
const installed = installedByCtx.get(ctx);
|
|
158
195
|
installedByCtx.delete(ctx);
|
|
159
|
-
|
|
196
|
+
let failure;
|
|
197
|
+
try {
|
|
198
|
+
installed?.dispose(ctx);
|
|
199
|
+
}
|
|
200
|
+
catch (error) {
|
|
201
|
+
failure = { error };
|
|
202
|
+
}
|
|
203
|
+
try {
|
|
204
|
+
loop?.onDispose?.(ctx);
|
|
205
|
+
}
|
|
206
|
+
catch (error) {
|
|
207
|
+
failure ??= { error };
|
|
208
|
+
}
|
|
209
|
+
if (failure !== undefined)
|
|
210
|
+
throw failure.error;
|
|
160
211
|
},
|
|
161
212
|
};
|
|
162
213
|
}
|
package/dist/gameplay.d.ts
CHANGED
|
@@ -31,6 +31,7 @@ export { medalFor, rankLeaderboard, type RankLeaderboardOptions, type RankableRo
|
|
|
31
31
|
export { createLevelSequence, type LevelSequence } from "./game/levelSequence.js";
|
|
32
32
|
export { createLoadouts, type LoadoutDef } from "./game/loadout.js";
|
|
33
33
|
export { evaluateLootFilter, lootFilter, type LootFilterRule } from "./game/lootFilter.js";
|
|
34
|
+
export { timeScaledRarity, type TimeScaledRarityOptions } from "./game/lootModifiers.js";
|
|
34
35
|
export { createLootPipeline, defineLootPipeline, type LootDropProvenance, type LootModifier, type LootPipeline, type LootPipelineDef, type LootPipelineDeps, type LootPlanEntry, type LootResolution, type LootResolveContext, type LootRollPlan, type LootStage, type LootStageKind, type LootStageStatus, type LootStageTrace, } from "./game/lootPipeline.js";
|
|
35
36
|
export { createLootRegistry, lootTable, type Drop, type LootTableDef } from "./game/lootTable.js";
|
|
36
37
|
export { resolveOneShotClip } from "./game/modelAnimation.js";
|
|
@@ -100,5 +101,7 @@ export { createMultiRegionHealth, type MultiRegionHealth } from "./survival/regi
|
|
|
100
101
|
export { createCommitController } from "./turn/commit.js";
|
|
101
102
|
export { createIntentBoard } from "./turn/intent.js";
|
|
102
103
|
export { createTurnLoop, type TurnLoop } from "./turn/turnLoop.js";
|
|
104
|
+
export { dispatchExpedition, recallExpedition, returnExpeditionNow, settleExpedition, type ExpeditionConfig, type ExpeditionDispatch, type ExpeditionEvent, type ExpeditionHazard, type ExpeditionState, type ExpeditionSupplyRule, } from "./work/expedition.js";
|
|
103
105
|
export { activeJobs, cancelJob, createWorkQueue, enqueue, fifoOrdering, jobById, jobProgress, pauseJob, priorityOrdering, queueSize, queuedJobs, resumeJob, tick, type CancelResult, type EnqueueOptions, type EnqueueResult, type Job, type JobId, type JobOrdering, type JobStatus, type JobValidation, type TickResult, type WorkQueueConfig, type WorkQueueEvent, type WorkQueueState, } from "./work/jobQueue.js";
|
|
106
|
+
export { stationOutputRate, type StaffedStation, type StationRateTuning, type StationWorker, } from "./work/staffedStation.js";
|
|
104
107
|
export { unitTrainingConfig, type ResourceCost, type TrainableUnitDef, type UnitReservation, type UnitSpawnOrder, type UnitTrainingOptions, type UnitTrainingSpec, } from "./work/unitTraining.js";
|
package/dist/gameplay.js
CHANGED
|
@@ -31,6 +31,7 @@ export { medalFor, rankLeaderboard, } from "./game/leaderboardRank.js";
|
|
|
31
31
|
export { createLevelSequence } from "./game/levelSequence.js";
|
|
32
32
|
export { createLoadouts } from "./game/loadout.js";
|
|
33
33
|
export { evaluateLootFilter, lootFilter } from "./game/lootFilter.js";
|
|
34
|
+
export { timeScaledRarity } from "./game/lootModifiers.js";
|
|
34
35
|
export { createLootPipeline, defineLootPipeline, } from "./game/lootPipeline.js";
|
|
35
36
|
export { createLootRegistry, lootTable } from "./game/lootTable.js";
|
|
36
37
|
export { resolveOneShotClip } from "./game/modelAnimation.js";
|
|
@@ -100,5 +101,7 @@ export { createMultiRegionHealth } from "./survival/regionHealth.js";
|
|
|
100
101
|
export { createCommitController } from "./turn/commit.js";
|
|
101
102
|
export { createIntentBoard } from "./turn/intent.js";
|
|
102
103
|
export { createTurnLoop } from "./turn/turnLoop.js";
|
|
104
|
+
export { dispatchExpedition, recallExpedition, returnExpeditionNow, settleExpedition, } from "./work/expedition.js";
|
|
103
105
|
export { activeJobs, cancelJob, createWorkQueue, enqueue, fifoOrdering, jobById, jobProgress, pauseJob, priorityOrdering, queueSize, queuedJobs, resumeJob, tick, } from "./work/jobQueue.js";
|
|
106
|
+
export { stationOutputRate, } from "./work/staffedStation.js";
|
|
104
107
|
export { unitTrainingConfig, } from "./work/unitTraining.js";
|
|
@@ -29,13 +29,14 @@ export interface PromptRegistrySnapshot {
|
|
|
29
29
|
export interface PromptRegistry {
|
|
30
30
|
/**
|
|
31
31
|
* Add (or replace, by id) a positioned prompt. Replacing an existing id keeps
|
|
32
|
-
* its registration slot so tie-break order is stable.
|
|
33
|
-
*
|
|
32
|
+
* its registration slot so tie-break order is stable. Changed active content
|
|
33
|
+
* notifies immediately; selection is recalculated by the next {@link resolve}.
|
|
34
34
|
*/
|
|
35
35
|
register(prompt: PositionedPrompt): void;
|
|
36
36
|
/**
|
|
37
37
|
* Patch a registered prompt in place. No-op if `id` is unknown. Only the fields
|
|
38
|
-
* present in `patch` change; the rest are preserved.
|
|
38
|
+
* present in `patch` change; the rest are preserved. Changed active content
|
|
39
|
+
* notifies immediately without recalculating selection.
|
|
39
40
|
*/
|
|
40
41
|
update(id: string, patch: PositionedPromptPatch): void;
|
|
41
42
|
/** Remove the prompt with this id, if present. Returns whether one was removed. */
|
|
@@ -54,7 +55,7 @@ export interface PromptRegistry {
|
|
|
54
55
|
resolve(playerPosition: PromptPoint): PositionedPrompt | null;
|
|
55
56
|
/** The prompt selected by the most recent {@link resolve}, or `null`. */
|
|
56
57
|
active(): PositionedPrompt | null;
|
|
57
|
-
/** Observe active-prompt changes
|
|
58
|
+
/** Observe active-prompt content changes, selection transitions, clear and restore. Returns an unsubscribe fn. */
|
|
58
59
|
subscribe(listener: () => void): () => void;
|
|
59
60
|
/** Serializable state for a save. */
|
|
60
61
|
snapshot(): PromptRegistrySnapshot;
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { resolveActivePrompt, } from "./proximityPrompt.js";
|
|
1
|
+
import { positionedPromptsEqual, resolveActivePrompt, } from "./proximityPrompt.js";
|
|
2
2
|
/** Structural copy of a positioned prompt so external mutation can't corrupt registry state. */
|
|
3
3
|
function clonePrompt(prompt) {
|
|
4
4
|
const copy = {
|
|
@@ -50,8 +50,12 @@ export function createPromptRegistry() {
|
|
|
50
50
|
}
|
|
51
51
|
return {
|
|
52
52
|
register(prompt) {
|
|
53
|
-
prompts.
|
|
53
|
+
const existing = prompts.get(prompt.id);
|
|
54
|
+
const next = clonePrompt(prompt);
|
|
55
|
+
prompts.set(prompt.id, next);
|
|
54
56
|
invalidate();
|
|
57
|
+
if (activeId === prompt.id && existing !== undefined && !positionedPromptsEqual(existing, next))
|
|
58
|
+
notify();
|
|
55
59
|
},
|
|
56
60
|
update(id, patch) {
|
|
57
61
|
const existing = prompts.get(id);
|
|
@@ -71,6 +75,8 @@ export function createPromptRegistry() {
|
|
|
71
75
|
}
|
|
72
76
|
prompts.set(id, merged);
|
|
73
77
|
invalidate();
|
|
78
|
+
if (activeId === id && !positionedPromptsEqual(existing, merged))
|
|
79
|
+
notify();
|
|
74
80
|
},
|
|
75
81
|
unregister(id) {
|
|
76
82
|
const removed = prompts.delete(id);
|
|
@@ -14,6 +14,9 @@ function violatesKind(layout, traits, itemId) {
|
|
|
14
14
|
return kind === null || !layout.accepts.includes(kind);
|
|
15
15
|
return kind !== layout.accepts;
|
|
16
16
|
}
|
|
17
|
+
function validSlot(state, slot) {
|
|
18
|
+
return Number.isInteger(slot) && slot >= 0 && slot < state.slots.length;
|
|
19
|
+
}
|
|
17
20
|
function computeCapacity(state, traits, itemId) {
|
|
18
21
|
const limit = traits.stackLimit(itemId);
|
|
19
22
|
let existingRoom = 0;
|
|
@@ -58,7 +61,7 @@ function autoPut(state, layout, traits, itemId, count) {
|
|
|
58
61
|
return { status: "ok", state: { slots } };
|
|
59
62
|
}
|
|
60
63
|
function explicitPut(state, layout, traits, itemId, count, slot) {
|
|
61
|
-
if (
|
|
64
|
+
if (!validSlot(state, slot))
|
|
62
65
|
return { status: "rejected", reason: "invalid-slot" };
|
|
63
66
|
if (violatesKind(layout, traits, itemId))
|
|
64
67
|
return { status: "rejected", reason: "wrong-kind" };
|
|
@@ -120,7 +123,7 @@ export function hasItem(state, itemId, count) {
|
|
|
120
123
|
}
|
|
121
124
|
/** Move the stack at `fromSlot` to `toSlot` (or auto-stack when omitted) — swapping, merging with remainder, or relocating across two inventories. Rejects on empty source, wrong kind, or no space. */
|
|
122
125
|
export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
|
|
123
|
-
if (
|
|
126
|
+
if (!validSlot(from, fromSlot))
|
|
124
127
|
return { status: "rejected", reason: "invalid-slot" };
|
|
125
128
|
const source = from.slots[fromSlot];
|
|
126
129
|
if (source === null)
|
|
@@ -138,7 +141,7 @@ export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
|
|
|
138
141
|
const toSlots = sameInventory ? fromSlots : to.slots.slice();
|
|
139
142
|
const limit = traits.stackLimit(source.itemId);
|
|
140
143
|
if (toSlot !== undefined) {
|
|
141
|
-
if (
|
|
144
|
+
if (!validSlot(to, toSlot))
|
|
142
145
|
return { status: "rejected", reason: "invalid-slot" };
|
|
143
146
|
const dest = toSlots[toSlot];
|
|
144
147
|
if (dest === null) {
|
|
@@ -179,12 +182,12 @@ export function moveItem(from, fromSlot, to, toLayout, traits, toSlot) {
|
|
|
179
182
|
* @capability inventory-split split a stack into a target or first-empty slot
|
|
180
183
|
*/
|
|
181
184
|
export function splitStack(state, slot, amount, toSlot) {
|
|
182
|
-
if (
|
|
185
|
+
if (!validSlot(state, slot))
|
|
183
186
|
return { status: "rejected", reason: "invalid-slot" };
|
|
184
187
|
const source = state.slots[slot];
|
|
185
188
|
if (source === null)
|
|
186
189
|
return { status: "rejected", reason: "empty-slot" };
|
|
187
|
-
if (amount <= 0 || amount >= source.count)
|
|
190
|
+
if (!Number.isFinite(amount) || amount <= 0 || amount >= source.count)
|
|
188
191
|
return { status: "rejected", reason: "invalid-amount" };
|
|
189
192
|
let target = toSlot;
|
|
190
193
|
if (target === undefined) {
|
|
@@ -193,7 +196,7 @@ export function splitStack(state, slot, amount, toSlot) {
|
|
|
193
196
|
return { status: "rejected", reason: "no-space" };
|
|
194
197
|
}
|
|
195
198
|
else {
|
|
196
|
-
if (
|
|
199
|
+
if (!validSlot(state, target))
|
|
197
200
|
return { status: "rejected", reason: "invalid-slot" };
|
|
198
201
|
if (target === slot)
|
|
199
202
|
return { status: "rejected", reason: "slot-occupied" };
|
|
@@ -18,7 +18,7 @@ export interface ShapedGrid<T> {
|
|
|
18
18
|
readonly height: number;
|
|
19
19
|
readonly placements: readonly Placement<T>[];
|
|
20
20
|
}
|
|
21
|
-
export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id";
|
|
21
|
+
export type ShapedRejection = "out-of-bounds" | "overlap" | "duplicate-id" | "unknown-id" | "invalid-footprint" | "invalid-origin";
|
|
22
22
|
export interface ShapedPlaceResult<T> {
|
|
23
23
|
status: "ok";
|
|
24
24
|
grid: ShapedGrid<T>;
|
|
@@ -36,19 +36,58 @@ export declare function rotateFootprint(footprint: Footprint, rotation: Rotation
|
|
|
36
36
|
/** @internal */
|
|
37
37
|
export declare function occupiedCells(footprint: Footprint, origin: Cell, rotation: Rotation): Cell[];
|
|
38
38
|
/**
|
|
39
|
-
*
|
|
39
|
+
* An immutable spatial inventory board holding caller-defined multi-cell items.
|
|
40
40
|
*
|
|
41
|
-
* @capability
|
|
42
|
-
|
|
43
|
-
*/
|
|
41
|
+
* @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
42
|
+
*/
|
|
44
43
|
export declare function createShapedGrid<T>(width: number, height: number): ShapedGrid<T>;
|
|
45
|
-
/**
|
|
44
|
+
/**
|
|
45
|
+
* Check bounds and overlap for a discrete footprint, optionally ignoring the item being moved.
|
|
46
|
+
* @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
47
|
+
*/
|
|
46
48
|
export declare function canPlace<T>(grid: ShapedGrid<T>, footprint: Footprint, origin: Cell, rotation: Rotation, ignoreId?: string): ShapedRejection | null;
|
|
47
|
-
/**
|
|
49
|
+
/** Search policy: rotation order first, then rows and columns; the work budget counts candidate origins. */
|
|
50
|
+
export interface ShapedPlacementSearchOptions {
|
|
51
|
+
/** Defaults to `[0, 1, 2, 3]`; a game can prefer or forbid rotations. */
|
|
52
|
+
rotations?: readonly Rotation[];
|
|
53
|
+
/** Ignore an existing placement while searching for its next position. */
|
|
54
|
+
ignoreId?: string;
|
|
55
|
+
/** Nonnegative integer candidate limit; defaults to 4096. */
|
|
56
|
+
maxChecks?: number;
|
|
57
|
+
}
|
|
58
|
+
/** A detached placement, a completed no-fit search, or an incomplete search that reached its budget. */
|
|
59
|
+
export type ShapedPlacementSearchResult = {
|
|
60
|
+
status: "found";
|
|
61
|
+
origin: Cell;
|
|
62
|
+
rotation: Rotation;
|
|
63
|
+
checks: number;
|
|
64
|
+
} | {
|
|
65
|
+
status: "no-space";
|
|
66
|
+
checks: number;
|
|
67
|
+
} | {
|
|
68
|
+
status: "budget-exceeded";
|
|
69
|
+
checks: number;
|
|
70
|
+
};
|
|
71
|
+
/**
|
|
72
|
+
* Find the first legal placement without mutating the board; occupancy is indexed once per search.
|
|
73
|
+
*
|
|
74
|
+
* @capability shaped-placement find a shaped inventory item's first fit with ordered rotations, row-major placement, and an explicit search budget
|
|
75
|
+
*/
|
|
76
|
+
export declare function findShapedPlacement<T>(grid: ShapedGrid<T>, footprint: Footprint, options?: ShapedPlacementSearchOptions): ShapedPlacementSearchResult;
|
|
77
|
+
/**
|
|
78
|
+
* Place a caller-defined item without mutating the source board; duplicate ids reject.
|
|
79
|
+
* @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
80
|
+
*/
|
|
48
81
|
export declare function placeShaped<T>(grid: ShapedGrid<T>, item: ShapedItem<T>, origin: Cell, rotation?: Rotation): ShapedResult<T>;
|
|
49
|
-
/**
|
|
82
|
+
/**
|
|
83
|
+
* Remove one placement without mutating the source board.
|
|
84
|
+
* @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
85
|
+
*/
|
|
50
86
|
export declare function removeShaped<T>(grid: ShapedGrid<T>, id: string): ShapedResult<T>;
|
|
51
|
-
/**
|
|
87
|
+
/**
|
|
88
|
+
* Move or rotate one placement, ignoring its current cells during overlap checks.
|
|
89
|
+
* @capability shaped-grid a serializable inventory board with arbitrary cell footprints, rotation, overlap checks, and immutable placement
|
|
90
|
+
*/
|
|
52
91
|
export declare function moveShaped<T>(grid: ShapedGrid<T>, id: string, origin: Cell, rotation?: Rotation): ShapedResult<T>;
|
|
53
92
|
/** @internal */
|
|
54
93
|
export declare function cellOccupant<T>(grid: ShapedGrid<T>, cell: Cell): string | null;
|