@jgengine/core 0.11.0 → 0.12.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 +26 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat.d.ts +1 -0
- package/dist/combat.js +1 -0
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/playableGame.d.ts +34 -435
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +19 -0
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +24 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +130 -0
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +59 -0
- package/dist/runtime/context/scene.js +291 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +88 -501
- package/dist/runtime/gameContextTypes.d.ts +467 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +4 -2
- package/dist/runtime/headlessRunner.js +1 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +5 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +58 -0
- package/dist/scene/colliders.js +108 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +10 -14
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +59 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/features.d.ts +18 -3
- package/dist/world/features.js +10 -1
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/pathNetwork.d.ts +159 -0
- package/dist/world/pathNetwork.js +684 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/scatter.js +2 -20
- package/dist/world/streets.d.ts +29 -0
- package/dist/world/streets.js +55 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +45 -0
- package/dist/world/terrain.js +78 -0
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +7 -4
- package/skills/jgengine/api.md +172 -79
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +5 -1
- package/skills/jgengine-combat/api.md +36 -0
- package/skills/jgengine-combat/capabilities.md +44 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/reference.md +12 -2
- package/skills/jgengine-gameplay/api.md +152 -26
- package/skills/jgengine-gameplay/capabilities.md +78 -2
- package/skills/jgengine-gameplay/reference-systems.md +1 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +419 -15
- package/skills/jgengine-world/capabilities.md +108 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -0,0 +1,166 @@
|
|
|
1
|
+
import type { PhysicsConfig } from "../game/defineGame.js";
|
|
2
|
+
/**
|
|
3
|
+
* The place model: a world is the place you play in — substrate (ground) plus laws (physics,
|
|
4
|
+
* surface) — never a dressed diorama. Sky look, foliage scatter, props, and sculpt are content:
|
|
5
|
+
* they are authored in the editor (or by a content preset that writes into the scene document)
|
|
6
|
+
* and consumed through the authored-scene seams, not declared on the world.
|
|
7
|
+
*
|
|
8
|
+
* Hard rules the API enforces:
|
|
9
|
+
* - No `seed` anywhere in a world definition. Determinism comes from the runtime: the engine
|
|
10
|
+
* derives generator/scatter seeds from the world `id` plus the save/run seed (see
|
|
11
|
+
* {@link seedForPlace}), so the same place replays identically without the author owning RNG.
|
|
12
|
+
* - No sky, vegetation, or theme fields. A world with no authored sky renders the engine
|
|
13
|
+
* default sky; "void"/"space" is content, not a world knob.
|
|
14
|
+
* - No genre or franchise presets. `generator` carries algorithm parameters only.
|
|
15
|
+
*/
|
|
16
|
+
/**
|
|
17
|
+
* Matter/feel laws of a place's ground that systems, physics, and audio read — restitution for a
|
|
18
|
+
* metal chamber the pieces ping off, stickiness for a slime pit, a soft felt mat. The same rule
|
|
19
|
+
* systems play on any surface; swapping the surface (plus content audio) changes the feel without
|
|
20
|
+
* forking the rules. This is place law, not a theme kit: visual/audio dressing stays in content.
|
|
21
|
+
*/
|
|
22
|
+
export interface SurfaceLaws {
|
|
23
|
+
/** Named matter of the ground ("metal", "slime", "felt", …) — a label systems and audio cues key off. */
|
|
24
|
+
matter?: string;
|
|
25
|
+
/** Sliding friction scalar, 0 (ice) … 1+ (grippy). Interpreted by movement/physics systems. */
|
|
26
|
+
friction?: number;
|
|
27
|
+
/** Bounce energy retention, 0 (dead stop) … 1 (perfect bounce). Interpreted by physics/board systems. */
|
|
28
|
+
restitution?: number;
|
|
29
|
+
/** Free-form serializable place-law traits game systems read (e.g. `{ stuckTurns: 1 }` for slime). */
|
|
30
|
+
traits?: Readonly<Record<string, number | string | boolean>>;
|
|
31
|
+
}
|
|
32
|
+
/** Serializable algorithm parameters for a procedural ground generator. Never a genre kit. */
|
|
33
|
+
export interface GroundGenerator {
|
|
34
|
+
/** Algorithm identifier the game's generation system resolves (e.g. "fbm", "caves", "layers"). */
|
|
35
|
+
algorithm: string;
|
|
36
|
+
/**
|
|
37
|
+
* Algorithm tuning only — amplitudes, frequencies, thresholds, palettes of ids. Never a `seed`
|
|
38
|
+
* (the engine injects one from world id + save/run) and never a genre/franchise preset name.
|
|
39
|
+
*/
|
|
40
|
+
params?: Readonly<Record<string, number | string | boolean>>;
|
|
41
|
+
}
|
|
42
|
+
/**
|
|
43
|
+
* Size of a `flat` ground: extents in world units. `Infinity` on an axis means unbounded — an
|
|
44
|
+
* endless plain needs no bounds number invented for it. `y` optionally bounds vertical play space.
|
|
45
|
+
*/
|
|
46
|
+
export interface FlatGroundSize {
|
|
47
|
+
x: number;
|
|
48
|
+
/** Optional vertical bound of the play volume; omit for open sky. */
|
|
49
|
+
y?: number;
|
|
50
|
+
z: number;
|
|
51
|
+
radius?: never;
|
|
52
|
+
}
|
|
53
|
+
/** Size of a `round` ground: the planet/sphere radius in world units. Nothing else. */
|
|
54
|
+
export interface RoundGroundSize {
|
|
55
|
+
radius: number;
|
|
56
|
+
x?: never;
|
|
57
|
+
y?: never;
|
|
58
|
+
z?: never;
|
|
59
|
+
}
|
|
60
|
+
/** Size of a `voxel` ground: the generator's domain per axis; axes may be `Infinity` for streaming volumes. */
|
|
61
|
+
export interface VoxelGroundSize {
|
|
62
|
+
x: number;
|
|
63
|
+
y: number;
|
|
64
|
+
z: number;
|
|
65
|
+
radius?: never;
|
|
66
|
+
}
|
|
67
|
+
/** Size of a `board` ground: a 2D surface you look at, in cells or layout units. */
|
|
68
|
+
export interface BoardGroundSize {
|
|
69
|
+
x: number;
|
|
70
|
+
y: number;
|
|
71
|
+
z?: never;
|
|
72
|
+
radius?: never;
|
|
73
|
+
}
|
|
74
|
+
/** A 3D walkable plane/slab. `Infinity` axes are unbounded; no separate "infinite" mode exists. */
|
|
75
|
+
export interface FlatGround {
|
|
76
|
+
mode: "flat";
|
|
77
|
+
size: FlatGroundSize;
|
|
78
|
+
surface?: SurfaceLaws;
|
|
79
|
+
generator?: GroundGenerator;
|
|
80
|
+
}
|
|
81
|
+
/** A planet/sphere you play on the outside of. Sized by `radius` only. */
|
|
82
|
+
export interface RoundGround {
|
|
83
|
+
mode: "round";
|
|
84
|
+
size: RoundGroundSize;
|
|
85
|
+
surface?: SurfaceLaws;
|
|
86
|
+
generator?: GroundGenerator;
|
|
87
|
+
}
|
|
88
|
+
/** A procedural volume of blocks. `size` is the generator domain; `generator` is algorithm params only. */
|
|
89
|
+
export interface VoxelGround {
|
|
90
|
+
mode: "voxel";
|
|
91
|
+
size: VoxelGroundSize;
|
|
92
|
+
surface?: SurfaceLaws;
|
|
93
|
+
generator?: GroundGenerator;
|
|
94
|
+
}
|
|
95
|
+
/**
|
|
96
|
+
* A 2D surface you look at — grid games, solitaire, tabletop. Physics is often zero-gravity or
|
|
97
|
+
* omitted; the game owns the face it draws on the board.
|
|
98
|
+
*/
|
|
99
|
+
export interface BoardGround {
|
|
100
|
+
/** `"stage"` is accepted as an alias and normalized to `"board"`. */
|
|
101
|
+
mode: "board" | "stage";
|
|
102
|
+
size: BoardGroundSize;
|
|
103
|
+
surface?: SurfaceLaws;
|
|
104
|
+
}
|
|
105
|
+
/** The substrate of a place, discriminated by `mode` — TS rejects a `radius` on `flat` and `x`/`z` on `round`. */
|
|
106
|
+
export type GroundConfig = FlatGround | RoundGround | VoxelGround | BoardGround;
|
|
107
|
+
/** Canonical ground modes after normalization (`stage` → `board`). */
|
|
108
|
+
export type GroundMode = "flat" | "round" | "voxel" | "board";
|
|
109
|
+
/** {@link BoardGround} with the `stage` alias resolved away. */
|
|
110
|
+
export type ResolvedBoardGround = Omit<BoardGround, "mode"> & {
|
|
111
|
+
mode: "board";
|
|
112
|
+
};
|
|
113
|
+
/** A place's ground after `world()` normalization. */
|
|
114
|
+
export type ResolvedGround = FlatGround | RoundGround | VoxelGround | ResolvedBoardGround;
|
|
115
|
+
/** Input to {@link world}: place identity, substrate, and the laws of this place. */
|
|
116
|
+
export interface PlaceConfig {
|
|
117
|
+
/** Place identity — names the world for saves, seeds, and world switching. */
|
|
118
|
+
id: string;
|
|
119
|
+
/** The substrate: what you stand on / look at, discriminated by `mode`. */
|
|
120
|
+
ground: GroundConfig;
|
|
121
|
+
/**
|
|
122
|
+
* Laws of THIS place (gravity, jump). Each world carries its own physics, so a game with several
|
|
123
|
+
* places (a station and a moon) gives each different laws; `defineGameDefinition` resolves the
|
|
124
|
+
* active world's physics over the game-level default. The engine owns simulation.
|
|
125
|
+
*/
|
|
126
|
+
physics?: PhysicsConfig;
|
|
127
|
+
}
|
|
128
|
+
/** A declared place — the `world()` result carried on `GameDefinition.world`. */
|
|
129
|
+
export interface PlaceWorldFeature {
|
|
130
|
+
kind: "place";
|
|
131
|
+
id: string;
|
|
132
|
+
ground: ResolvedGround;
|
|
133
|
+
physics?: PhysicsConfig;
|
|
134
|
+
}
|
|
135
|
+
/**
|
|
136
|
+
* Declares the place a game is played in: substrate (`ground`) + laws (`physics`, `ground.surface`).
|
|
137
|
+
* The thin default start — `flat` with `Infinity` axes and default physics, `board` for 2D — is a
|
|
138
|
+
* complete world; dressing it (sky, foliage, props, sculpt) is editor-authored scene content.
|
|
139
|
+
* Multiple worlds per game are first-class: declare one `world()` per place, each with its own id,
|
|
140
|
+
* ground, and physics. Games that are not a spatial place (pure UI/rules) omit `world` entirely.
|
|
141
|
+
*
|
|
142
|
+
* @capability world-place declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
|
|
143
|
+
*/
|
|
144
|
+
export declare function world(config: PlaceConfig): PlaceWorldFeature;
|
|
145
|
+
/**
|
|
146
|
+
* The engine-side seed derivation for a place: a stable string from the world `id` plus the
|
|
147
|
+
* save/run seed. Generators and scatter receive this — never a `seed` field in the world
|
|
148
|
+
* definition — so a save replays its world deterministically and a fresh run can vary by
|
|
149
|
+
* `runSeed` alone.
|
|
150
|
+
*
|
|
151
|
+
* @capability world-seed derive a deterministic generator/scatter seed from world id + save/run seed
|
|
152
|
+
*/
|
|
153
|
+
export declare function seedForPlace(worldId: string, runSeed?: string): string;
|
|
154
|
+
/**
|
|
155
|
+
* Resolves the physics laws in effect for the active world: a place world's own `physics` wins
|
|
156
|
+
* field-by-field over the game-level default. Non-place worlds (and games with no world) keep the
|
|
157
|
+
* game-level config unchanged.
|
|
158
|
+
*/
|
|
159
|
+
export declare function resolveWorldPhysics(worldFeature: {
|
|
160
|
+
kind: string;
|
|
161
|
+
physics?: PhysicsConfig;
|
|
162
|
+
} | undefined, base: PhysicsConfig | undefined): PhysicsConfig | undefined;
|
|
163
|
+
/** Type guard for the place-model world feature. */
|
|
164
|
+
export declare function isPlaceWorld(worldFeature: {
|
|
165
|
+
kind: string;
|
|
166
|
+
} | undefined): worldFeature is PlaceWorldFeature;
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
const BANNED_WORLD_KEYS = ["sky", "vegetation", "terrain", "environment", "weather", "seed"];
|
|
2
|
+
function assertNoDressing(config) {
|
|
3
|
+
const record = config;
|
|
4
|
+
for (const key of BANNED_WORLD_KEYS) {
|
|
5
|
+
if (record[key] !== undefined) {
|
|
6
|
+
throw new Error(key === "seed"
|
|
7
|
+
? "world: seed does not belong in a world definition — the engine derives seeds from the world id and the save/run seed (seedForPlace)."
|
|
8
|
+
: `world: "${key}" does not belong in a world definition — a world is substrate + laws. Author sky, foliage, and dressing in the editor (or a content preset that writes scene data).`);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
function assertAxis(mode, axis, value, allowInfinite) {
|
|
13
|
+
const ok = value > 0 && (allowInfinite ? true : Number.isFinite(value));
|
|
14
|
+
if (!ok) {
|
|
15
|
+
throw new Error(`world: ground.size.${axis} for mode "${mode}" must be a positive${allowInfinite ? "" : " finite"} number, got ${value}`);
|
|
16
|
+
}
|
|
17
|
+
}
|
|
18
|
+
function resolveGround(ground) {
|
|
19
|
+
if (ground["seed"] !== undefined) {
|
|
20
|
+
throw new Error("world: seed does not belong on ground — the engine injects seeds into generators (seedForPlace).");
|
|
21
|
+
}
|
|
22
|
+
if ("generator" in ground && ground.generator?.params !== undefined && "seed" in ground.generator.params) {
|
|
23
|
+
throw new Error("world: generator.params.seed is not allowed — the engine injects the seed from world id + save/run (seedForPlace).");
|
|
24
|
+
}
|
|
25
|
+
switch (ground.mode) {
|
|
26
|
+
case "flat": {
|
|
27
|
+
assertAxis("flat", "x", ground.size.x, true);
|
|
28
|
+
assertAxis("flat", "z", ground.size.z, true);
|
|
29
|
+
if (ground.size.y !== undefined)
|
|
30
|
+
assertAxis("flat", "y", ground.size.y, true);
|
|
31
|
+
return ground;
|
|
32
|
+
}
|
|
33
|
+
case "round": {
|
|
34
|
+
assertAxis("round", "radius", ground.size.radius, false);
|
|
35
|
+
return ground;
|
|
36
|
+
}
|
|
37
|
+
case "voxel": {
|
|
38
|
+
assertAxis("voxel", "x", ground.size.x, true);
|
|
39
|
+
assertAxis("voxel", "y", ground.size.y, true);
|
|
40
|
+
assertAxis("voxel", "z", ground.size.z, true);
|
|
41
|
+
return ground;
|
|
42
|
+
}
|
|
43
|
+
case "board":
|
|
44
|
+
case "stage": {
|
|
45
|
+
assertAxis("board", "x", ground.size.x, false);
|
|
46
|
+
assertAxis("board", "y", ground.size.y, false);
|
|
47
|
+
return ground.mode === "board" ? ground : { ...ground, mode: "board" };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Declares the place a game is played in: substrate (`ground`) + laws (`physics`, `ground.surface`).
|
|
53
|
+
* The thin default start — `flat` with `Infinity` axes and default physics, `board` for 2D — is a
|
|
54
|
+
* complete world; dressing it (sky, foliage, props, sculpt) is editor-authored scene content.
|
|
55
|
+
* Multiple worlds per game are first-class: declare one `world()` per place, each with its own id,
|
|
56
|
+
* ground, and physics. Games that are not a spatial place (pure UI/rules) omit `world` entirely.
|
|
57
|
+
*
|
|
58
|
+
* @capability world-place declare the place a game happens in — flat/round/voxel/board ground, surface laws, per-place physics
|
|
59
|
+
*/
|
|
60
|
+
export function world(config) {
|
|
61
|
+
if (config.id.trim().length === 0) {
|
|
62
|
+
throw new Error("world: id must be non-empty — it names the place for saves, seeds, and world switching");
|
|
63
|
+
}
|
|
64
|
+
assertNoDressing(config);
|
|
65
|
+
return {
|
|
66
|
+
kind: "place",
|
|
67
|
+
id: config.id,
|
|
68
|
+
ground: resolveGround(config.ground),
|
|
69
|
+
...(config.physics === undefined ? {} : { physics: config.physics }),
|
|
70
|
+
};
|
|
71
|
+
}
|
|
72
|
+
/**
|
|
73
|
+
* The engine-side seed derivation for a place: a stable string from the world `id` plus the
|
|
74
|
+
* save/run seed. Generators and scatter receive this — never a `seed` field in the world
|
|
75
|
+
* definition — so a save replays its world deterministically and a fresh run can vary by
|
|
76
|
+
* `runSeed` alone.
|
|
77
|
+
*
|
|
78
|
+
* @capability world-seed derive a deterministic generator/scatter seed from world id + save/run seed
|
|
79
|
+
*/
|
|
80
|
+
export function seedForPlace(worldId, runSeed = "") {
|
|
81
|
+
return runSeed.length === 0 ? worldId : `${worldId}:${runSeed}`;
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Resolves the physics laws in effect for the active world: a place world's own `physics` wins
|
|
85
|
+
* field-by-field over the game-level default. Non-place worlds (and games with no world) keep the
|
|
86
|
+
* game-level config unchanged.
|
|
87
|
+
*/
|
|
88
|
+
export function resolveWorldPhysics(worldFeature, base) {
|
|
89
|
+
if (worldFeature === undefined || worldFeature.kind !== "place" || worldFeature.physics === undefined)
|
|
90
|
+
return base;
|
|
91
|
+
return base === undefined ? worldFeature.physics : { ...base, ...worldFeature.physics };
|
|
92
|
+
}
|
|
93
|
+
/** Type guard for the place-model world feature. */
|
|
94
|
+
export function isPlaceWorld(worldFeature) {
|
|
95
|
+
return worldFeature?.kind === "place";
|
|
96
|
+
}
|
package/dist/world/placeAsset.js
CHANGED
|
@@ -7,10 +7,12 @@ function normalizePosition(position) {
|
|
|
7
7
|
const t = position;
|
|
8
8
|
return { x: t[0], y: t[1], z: t[2] };
|
|
9
9
|
}
|
|
10
|
+
let placementSequence = 0;
|
|
10
11
|
function placementId(assetId, explicit) {
|
|
11
12
|
if (explicit !== undefined && explicit.length > 0)
|
|
12
13
|
return explicit;
|
|
13
|
-
|
|
14
|
+
placementSequence += 1;
|
|
15
|
+
return `placed_${assetId}_${Date.now().toString(36)}_${placementSequence.toString(36)}`;
|
|
14
16
|
}
|
|
15
17
|
/**
|
|
16
18
|
* Resolve a place-asset intent into a shared payload (editor + games, one verb).
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { type Vec3 } from "./catenary.js";
|
|
2
|
+
import { type ParamSchema, type ParsedParams, type SceneKindObject, type SceneKindResolveContext } from "../scene/sceneKinds.js";
|
|
3
|
+
/**
|
|
4
|
+
* The editor path kind marking a polyline as a pole/cable run.
|
|
5
|
+
* @capability pole-line editor-authorable poles + sagging cables along a path
|
|
6
|
+
*/
|
|
7
|
+
export declare const POLE_LINE_KIND = "pole_line";
|
|
8
|
+
/** One placed pole: grounded base position plus facing yaw along the run. */
|
|
9
|
+
export interface Pole {
|
|
10
|
+
id: string;
|
|
11
|
+
position: Vec3;
|
|
12
|
+
yaw: number;
|
|
13
|
+
}
|
|
14
|
+
/** One resolved cable span: a point string to loft into a tube, plus its radius. */
|
|
15
|
+
export interface Cable {
|
|
16
|
+
id: string;
|
|
17
|
+
points: Vec3[];
|
|
18
|
+
radius: number;
|
|
19
|
+
}
|
|
20
|
+
/** The renderable payload the resolver returns and the shell renderer consumes. */
|
|
21
|
+
export interface ResolvedPoleLine {
|
|
22
|
+
poles: Pole[];
|
|
23
|
+
cables: Cable[];
|
|
24
|
+
poleHeight: number;
|
|
25
|
+
poleAsset: string;
|
|
26
|
+
/** Wires per span — drives the renderer's crossarm width and insulator count. */
|
|
27
|
+
wireCount: number;
|
|
28
|
+
/** Lateral spread between adjacent wires, meters — the renderer hangs insulators at these offsets. */
|
|
29
|
+
wireSpacing: number;
|
|
30
|
+
}
|
|
31
|
+
/** The pole-line parameter schema — drives the inspector and `meta` parse via the studio seam. */
|
|
32
|
+
export declare const POLE_LINE_SCHEMA: ParamSchema;
|
|
33
|
+
/**
|
|
34
|
+
* Resolve poles + cables from a document object's points and parsed params. Poles come from the
|
|
35
|
+
* generic `placeAlongPath` sampler; between each consecutive pair, `wireCount` parallel cables are
|
|
36
|
+
* strung with lateral offset and quadratic-Bézier `sag` (deeper on longer spans). Pure data.
|
|
37
|
+
*/
|
|
38
|
+
export declare function resolvePoleLine(object: SceneKindObject, params: ParsedParams, context?: SceneKindResolveContext): ResolvedPoleLine;
|
|
39
|
+
/** Registers the pole-line scene kind (schema + resolver + inspector note). Called by `registerBuiltinSceneKinds`. @internal */
|
|
40
|
+
export declare function registerPoleLineKind(): void;
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `pole_line` studio: poles + sagging cables strung along an editor-drawn path — power lines, fences,
|
|
3
|
+
* streetlight runs, ziplines. Pure config here (spacing/height/wire params + a resolver built from the
|
|
4
|
+
* generic `placeAlongPath` and `sagCurve` primitives); the instanced pole/cable meshes live in the
|
|
5
|
+
* `shell` renderer. An engine environment kind (like water/grass_field/soil) so wire runs are
|
|
6
|
+
* drawable and slider-authorable in every editor session and persisted in `editor.scene.json` —
|
|
7
|
+
* promoted from `examples/studios` for studio/editor parity (#1101).
|
|
8
|
+
*/
|
|
9
|
+
import { placeAlongPath } from "./pathInstances.js";
|
|
10
|
+
import { sagCurve } from "./catenary.js";
|
|
11
|
+
import { registerSceneKind, } from "../scene/sceneKinds.js";
|
|
12
|
+
/**
|
|
13
|
+
* The editor path kind marking a polyline as a pole/cable run.
|
|
14
|
+
* @capability pole-line editor-authorable poles + sagging cables along a path
|
|
15
|
+
*/
|
|
16
|
+
export const POLE_LINE_KIND = "pole_line";
|
|
17
|
+
/** The pole-line parameter schema — drives the inspector and `meta` parse via the studio seam. */
|
|
18
|
+
export const POLE_LINE_SCHEMA = {
|
|
19
|
+
fields: [
|
|
20
|
+
{ type: "text", key: "poleAsset", label: "pole asset", default: "", group: "poles" },
|
|
21
|
+
{ type: "range", key: "spacing", label: "spacing", min: 2, max: 40, step: 0.5, default: 8, unit: "m", group: "poles" },
|
|
22
|
+
{ type: "range", key: "poleHeight", label: "pole height", min: 1, max: 20, step: 0.25, default: 6, unit: "m", group: "poles" },
|
|
23
|
+
{ type: "bool", key: "snapToTerrain", label: "snap to ground", default: true, group: "poles" },
|
|
24
|
+
{ type: "action", key: "randomizePoles", label: "randomize poles", action: "randomize", group: "poles" },
|
|
25
|
+
{ type: "range", key: "wireCount", label: "wires", min: 0, max: 8, step: 1, default: 3, group: "cables" },
|
|
26
|
+
{ type: "range", key: "wireSpacing", label: "wire spread", min: 0, max: 3, step: 0.05, default: 0.5, unit: "m", group: "cables" },
|
|
27
|
+
{ type: "range", key: "cableRadius", label: "cable radius", min: 0.01, max: 0.5, step: 0.01, default: 0.05, unit: "m", group: "cables" },
|
|
28
|
+
{ type: "range", key: "sag", label: "sag", min: 0, max: 6, step: 0.1, default: 0.8, unit: "m", group: "cables" },
|
|
29
|
+
{ type: "range", key: "sagPerMeter", label: "sag / m", min: 0, max: 0.3, step: 0.005, default: 0.04, group: "cables" },
|
|
30
|
+
{ type: "range", key: "cableSegments", label: "cable smoothness", min: 2, max: 48, step: 1, default: 12, group: "cables" },
|
|
31
|
+
{ type: "seed", key: "seed", label: "seed", default: "", group: "cables" },
|
|
32
|
+
{ type: "action", key: "randomizeCables", label: "randomize cables", action: "randomize", group: "cables" },
|
|
33
|
+
],
|
|
34
|
+
groups: [
|
|
35
|
+
{ id: "poles", label: "Poles" },
|
|
36
|
+
{ id: "cables", label: "Cables" },
|
|
37
|
+
],
|
|
38
|
+
};
|
|
39
|
+
/**
|
|
40
|
+
* Resolve poles + cables from a document object's points and parsed params. Poles come from the
|
|
41
|
+
* generic `placeAlongPath` sampler; between each consecutive pair, `wireCount` parallel cables are
|
|
42
|
+
* strung with lateral offset and quadratic-Bézier `sag` (deeper on longer spans). Pure data.
|
|
43
|
+
*/
|
|
44
|
+
export function resolvePoleLine(object, params, context) {
|
|
45
|
+
const spacing = params["spacing"];
|
|
46
|
+
const poleHeight = params["poleHeight"];
|
|
47
|
+
const wireCount = Math.round(params["wireCount"]);
|
|
48
|
+
const wireSpacing = params["wireSpacing"];
|
|
49
|
+
const cableRadius = params["cableRadius"];
|
|
50
|
+
const sag = params["sag"];
|
|
51
|
+
const sagPerMeter = params["sagPerMeter"];
|
|
52
|
+
const cableSegments = Math.round(params["cableSegments"]);
|
|
53
|
+
const snap = params["snapToTerrain"];
|
|
54
|
+
const placed = placeAlongPath(object.points ?? [], {
|
|
55
|
+
spacing,
|
|
56
|
+
...(snap && context?.sampleHeight !== undefined ? { sampleHeight: context.sampleHeight } : {}),
|
|
57
|
+
});
|
|
58
|
+
const poles = placed.map((instance) => ({ id: `${object.id}/pole/${instance.index}`, position: instance.position, yaw: instance.yaw }));
|
|
59
|
+
const cables = [];
|
|
60
|
+
if (wireCount > 0) {
|
|
61
|
+
for (let i = 0; i + 1 < poles.length; i += 1) {
|
|
62
|
+
const a = poles[i];
|
|
63
|
+
const b = poles[i + 1];
|
|
64
|
+
const dx = b.position[0] - a.position[0];
|
|
65
|
+
const dz = b.position[2] - a.position[2];
|
|
66
|
+
const span = Math.hypot(dx, dz) || 1;
|
|
67
|
+
const perpX = -dz / span;
|
|
68
|
+
const perpZ = dx / span;
|
|
69
|
+
const drop = sag + sagPerMeter * span;
|
|
70
|
+
const half = (wireCount - 1) / 2;
|
|
71
|
+
for (let w = 0; w < wireCount; w += 1) {
|
|
72
|
+
const offset = (w - half) * wireSpacing;
|
|
73
|
+
const topA = [a.position[0] + perpX * offset, a.position[1] + poleHeight, a.position[2] + perpZ * offset];
|
|
74
|
+
const topB = [b.position[0] + perpX * offset, b.position[1] + poleHeight, b.position[2] + perpZ * offset];
|
|
75
|
+
cables.push({ id: `${object.id}/cable/${i}/${w}`, points: sagCurve(topA, topB, drop, cableSegments), radius: cableRadius });
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
return { poles, cables, poleHeight, poleAsset: params["poleAsset"], wireCount, wireSpacing };
|
|
80
|
+
}
|
|
81
|
+
/** Registers the pole-line scene kind (schema + resolver + inspector note). Called by `registerBuiltinSceneKinds`. @internal */
|
|
82
|
+
export function registerPoleLineKind() {
|
|
83
|
+
registerSceneKind({
|
|
84
|
+
kind: POLE_LINE_KIND,
|
|
85
|
+
target: "path",
|
|
86
|
+
label: "Pole line / cables",
|
|
87
|
+
pathShape: "line",
|
|
88
|
+
addCategory: "Studios",
|
|
89
|
+
accent: "#fbbf24",
|
|
90
|
+
schema: POLE_LINE_SCHEMA,
|
|
91
|
+
resolve: (object, params, context) => resolvePoleLine(object, params, context),
|
|
92
|
+
note: (object, params) => {
|
|
93
|
+
const resolved = resolvePoleLine(object, params);
|
|
94
|
+
return `${resolved.poles.length} poles · ${resolved.cables.length} cables`;
|
|
95
|
+
},
|
|
96
|
+
});
|
|
97
|
+
}
|
package/dist/world/polyline.js
CHANGED
|
@@ -62,6 +62,10 @@ export function closestPoint(line, query) {
|
|
|
62
62
|
return { point: points[0], distanceAlong: 0, segment: 0, lateral: distance(points[0], query) };
|
|
63
63
|
}
|
|
64
64
|
let best = { point: points[0], distanceAlong: 0, segment: 0, lateral: Infinity };
|
|
65
|
+
// Track the unsigned nearest distance separately: `best.lateral` carries a *signed* offset for
|
|
66
|
+
// the caller, so comparing the next candidate's unsigned distance against it would freeze the
|
|
67
|
+
// search the moment a negative-side segment lands (unsigned >= 0 never beats a stored negative).
|
|
68
|
+
let bestDistance = Infinity;
|
|
65
69
|
for (let i = 1; i < points.length; i += 1) {
|
|
66
70
|
const a = points[i - 1];
|
|
67
71
|
const b = points[i];
|
|
@@ -71,9 +75,12 @@ export function closestPoint(line, query) {
|
|
|
71
75
|
const t = segLenSq <= 0 ? 0 : Math.max(0, Math.min(1, ((query[0] - a[0]) * abx + (query[1] - a[1]) * aby) / segLenSq));
|
|
72
76
|
const point = [a[0] + abx * t, a[1] + aby * t];
|
|
73
77
|
const lateral = distance(point, query);
|
|
74
|
-
if (lateral <
|
|
78
|
+
if (lateral < bestDistance) {
|
|
79
|
+
bestDistance = lateral;
|
|
75
80
|
const along = cumulative[i - 1] + t * Math.sqrt(segLenSq);
|
|
76
|
-
const
|
|
81
|
+
const unit = normalize([abx, aby]);
|
|
82
|
+
const normal = perp(unit);
|
|
83
|
+
const side = Math.sign(normal[0] * (query[0] - point[0]) + normal[1] * (query[1] - point[1]));
|
|
77
84
|
best = { point, distanceAlong: along, segment: i - 1, lateral: lateral * (side === 0 ? 1 : side) };
|
|
78
85
|
}
|
|
79
86
|
}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** A quantity that is either fixed or a uniformly sampled `[min, max]` range resolved by the field rng. */
|
|
2
|
+
export type Quantity = number | readonly [number, number];
|
|
3
|
+
/** One resource kind a node can yield and its base per-harvest amount (before tool bias and harvester multiplier). */
|
|
4
|
+
export interface ResourceYield {
|
|
5
|
+
/** Generic resource kind id — "wood", "ore", "fiber", "hide". The field never interprets it. */
|
|
6
|
+
kind: string;
|
|
7
|
+
/** Base amount granted per harvest hit; a `[min, max]` range samples deterministic variance from the field rng. */
|
|
8
|
+
amount: Quantity;
|
|
9
|
+
}
|
|
10
|
+
/** A harvestable node definition: a finite budget, the resources it yields, and how it respawns. */
|
|
11
|
+
export interface ResourceNodeDef {
|
|
12
|
+
id: string;
|
|
13
|
+
/** Total harvestable budget (hit points / finite quantity). Each harvest consumes the tool's `power`. */
|
|
14
|
+
budget: number;
|
|
15
|
+
/** Resource kinds this node can yield; a tool's bias selects and scales among them. */
|
|
16
|
+
resources: readonly ResourceYield[];
|
|
17
|
+
/** Seconds to refill after depletion. Omit for a node that never respawns (a one-shot deposit). */
|
|
18
|
+
respawn?: number;
|
|
19
|
+
/** Budget restored on respawn; defaults to `budget`. */
|
|
20
|
+
respawnBudget?: number;
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* How a tool extracts from a node — its per-hit power and per-resource yield bias. Two profiles over the
|
|
24
|
+
* same multi-resource node return different resources and amounts: the generic pick-vs-hatchet mechanic.
|
|
25
|
+
*/
|
|
26
|
+
export interface ToolProfile {
|
|
27
|
+
/** Budget consumed per harvest hit; defaults to 1. Clamped to the node's remaining budget. */
|
|
28
|
+
power?: number;
|
|
29
|
+
/** Per-resource-kind yield multiplier. A kind absent here falls back to `defaultBias`. */
|
|
30
|
+
biases?: Readonly<Record<string, number>>;
|
|
31
|
+
/** Multiplier for resource kinds not listed in `biases`; defaults to 1 (set 0 for a tool that only extracts listed kinds). */
|
|
32
|
+
defaultBias?: number;
|
|
33
|
+
}
|
|
34
|
+
/** Options for a single harvest. */
|
|
35
|
+
export interface HarvestOptions {
|
|
36
|
+
/** Scalar applied to every granted amount — a stronger creature/harvester yields more; defaults to 1. */
|
|
37
|
+
multiplier?: number;
|
|
38
|
+
}
|
|
39
|
+
/** One resource kind granted by a harvest and its resolved amount. */
|
|
40
|
+
export interface ResourceGrant {
|
|
41
|
+
kind: string;
|
|
42
|
+
amount: number;
|
|
43
|
+
}
|
|
44
|
+
/** Outcome of one harvest attempt. */
|
|
45
|
+
export interface HarvestResult {
|
|
46
|
+
nodeId: string;
|
|
47
|
+
/** Resources granted this harvest; empty when the node was already depleted or the tool extracted nothing. */
|
|
48
|
+
granted: readonly ResourceGrant[];
|
|
49
|
+
/** Budget actually consumed (0 when the node was already depleted). */
|
|
50
|
+
spent: number;
|
|
51
|
+
/** Whether the node still had budget and produced a harvest. */
|
|
52
|
+
harvested: boolean;
|
|
53
|
+
/** Whether this harvest reduced the node to empty. */
|
|
54
|
+
depleted: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** Serializable per-node runtime state — round-trips through save/load and multiplayer sync. */
|
|
57
|
+
export interface ResourceNodeState {
|
|
58
|
+
id: string;
|
|
59
|
+
/** Remaining budget. */
|
|
60
|
+
budget: number;
|
|
61
|
+
/** Budget the node refills to on respawn — the nominal "full" value for `fraction`. */
|
|
62
|
+
maxBudget: number;
|
|
63
|
+
depleted: boolean;
|
|
64
|
+
/** Seconds remaining until respawn; 0 when full or when the node never respawns. */
|
|
65
|
+
respawnTimer: number;
|
|
66
|
+
/** Explicit respawn-block flag (a structure claimed the spot); OR-ed with the field's suppression predicate. */
|
|
67
|
+
respawnBlocked: boolean;
|
|
68
|
+
/** 0..1 remaining fraction of `maxBudget`. */
|
|
69
|
+
fraction: number;
|
|
70
|
+
}
|
|
71
|
+
/** Construction options for {@link createResourceNodeField}: node definitions and an optional RNG/suppression hook. */
|
|
72
|
+
export interface ResourceNodeFieldConfig {
|
|
73
|
+
nodes: readonly ResourceNodeDef[];
|
|
74
|
+
/** Injected [0,1) generator for deterministic yield variance; defaults to `Math.random`. */
|
|
75
|
+
rng?: () => number;
|
|
76
|
+
/**
|
|
77
|
+
* Freeze respawn for a node while this returns true — a structure within radius, a nearby occupant.
|
|
78
|
+
* Consulted every tick alongside the per-node `respawnBlocked` flag (either one suppresses).
|
|
79
|
+
*/
|
|
80
|
+
respawnSuppressed?: (state: ResourceNodeState) => boolean;
|
|
81
|
+
}
|
|
82
|
+
/** A set of harvestable nodes: harvest with tool profiles, tick respawn timers, snapshot/hydrate for save-load. */
|
|
83
|
+
export interface ResourceNodeField {
|
|
84
|
+
/** Apply one harvest hit with `tool`; returns the granted resources and decrements the node's budget. */
|
|
85
|
+
harvest(nodeId: string, tool: ToolProfile, options?: HarvestOptions): HarvestResult;
|
|
86
|
+
/** Count down respawn timers by `dt`; refill any node whose timer elapses and is not suppressed. */
|
|
87
|
+
tick(dt: number): void;
|
|
88
|
+
state(nodeId: string): ResourceNodeState;
|
|
89
|
+
isDepleted(nodeId: string): boolean;
|
|
90
|
+
/** Set the explicit respawn-block flag; persisted in state and honored by `tick`. */
|
|
91
|
+
setRespawnBlocked(nodeId: string, blocked: boolean): void;
|
|
92
|
+
ids(): readonly string[];
|
|
93
|
+
snapshot(): Record<string, ResourceNodeState>;
|
|
94
|
+
/** Restore mutable per-node state (budget, depletion, timers, block flag) from a {@link snapshot}. */
|
|
95
|
+
hydrate(snapshot: Record<string, ResourceNodeState>): void;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* A field of depletable, respawning harvest nodes with tool-dependent yields — the generic rock / tree /
|
|
99
|
+
* bush / vein / corpse. Each node holds a finite budget across one or more resource kinds. A harvest applies
|
|
100
|
+
* a {@link ToolProfile} whose per-resource biases decide *which* resources and *how much* the same node
|
|
101
|
+
* returns (pick favors ore, hatchet favors wood), scaled by a creature/harvester multiplier and optional
|
|
102
|
+
* deterministic rng variance; the budget drops by the tool's power. When a node empties it starts a respawn
|
|
103
|
+
* timer that `tick(dt)` counts down and refills — unless respawn is suppressed by the per-node flag or the
|
|
104
|
+
* injected predicate (a structure claimed the spot). All state is plain and serializable for save-load and
|
|
105
|
+
* multiplayer sync via {@link snapshot}/{@link hydrate}.
|
|
106
|
+
*
|
|
107
|
+
* @capability resource-node depletable, respawning harvest nodes with tool-dependent yields
|
|
108
|
+
*/
|
|
109
|
+
export declare function createResourceNodeField(config: ResourceNodeFieldConfig): ResourceNodeField;
|