@jgengine/core 0.11.0 → 0.13.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 +52 -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/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- 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/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -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/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -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/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- 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/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -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 +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- 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 +60 -0
- package/dist/runtime/context/scene.js +329 -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 +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -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 +7 -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 +79 -0
- package/dist/scene/colliders.js +170 -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 +19 -15
- 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/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -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/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- 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/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -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/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -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/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- 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/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/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- 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 +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -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
|
@@ -13,7 +13,7 @@ import { type EntityStore } from "../scene/entityStore.js";
|
|
|
13
13
|
import type { StoreHandle } from "../store/defineStore.js";
|
|
14
14
|
import type { TimeConfig } from "../time/simClock.js";
|
|
15
15
|
import type { WorldFeature } from "../world/features.js";
|
|
16
|
-
/** Tunes offline whole-world save (`
|
|
16
|
+
/** Tunes offline whole-world save (`defineGameDefinition({ persist })`). Offline games already autosave to `localStorage` by default — pass this only to tune it (or `persist: false` to opt out). Defaults: continuous `autosave` to `localStorage`, one slot, no version. */
|
|
17
17
|
export interface PersistConfig {
|
|
18
18
|
/** `"autosave"` (default) writes on a debounce after any change; `"manual"` writes only on `ctx.game.save.checkpoint()` (save points / quest triggers). */
|
|
19
19
|
mode?: RuntimeSaveMode;
|
|
@@ -140,7 +140,7 @@ export interface LifecycleConfig<TState = unknown> {
|
|
|
140
140
|
restart?: string;
|
|
141
141
|
};
|
|
142
142
|
}
|
|
143
|
-
/** Fully-resolved game description produced by {@link
|
|
143
|
+
/** Fully-resolved game description produced by {@link defineGameDefinition} — assets, scene, and opted-in subsystems. */
|
|
144
144
|
export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> {
|
|
145
145
|
name: string;
|
|
146
146
|
assets: AssetCatalog<TAssetRef>;
|
|
@@ -156,7 +156,13 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
|
|
|
156
156
|
* Classic `loop` hooks still run for incremental migration (systems tick first, then `loop.onTick`).
|
|
157
157
|
*/
|
|
158
158
|
systems?: readonly SystemDefinition[];
|
|
159
|
+
/**
|
|
160
|
+
* The place this game happens in — preferred shape is `world()` from `@jgengine/core/world/place`
|
|
161
|
+
* (ground + per-place laws; dressing lives in the editor's scene document). Omit entirely for
|
|
162
|
+
* pure UI/rules games that are not a spatial place.
|
|
163
|
+
*/
|
|
159
164
|
world?: WorldFeature;
|
|
165
|
+
/** Game-level default physics laws; a place world's own `physics` resolves over this. */
|
|
160
166
|
physics?: PhysicsConfig;
|
|
161
167
|
/** Simulation clock: real→game time scale, selectable speeds, calendar. Exposed as `ctx.time`; the shell feeds its scaled dt to `loop.onTick`. */
|
|
162
168
|
time?: TimeConfig;
|
|
@@ -165,8 +171,9 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
|
|
|
165
171
|
inventories?: Record<string, InventoryDeclaration>;
|
|
166
172
|
input?: ActionCodesMap;
|
|
167
173
|
server?: GameServerConfig;
|
|
174
|
+
/** Hosted per-player save policy, read only by an authoritative multiplayer host. Solo games leave this unset (nothing persists by default) and use `persist` when they want a save at all. */
|
|
168
175
|
save?: SaveConfig;
|
|
169
|
-
/**
|
|
176
|
+
/** The one save knob for solo games: offline whole-world save. **On by default** — an offline game autosaves the entire world to `localStorage` and binds `ctx.game.save` with no config at all; the game only drives save points and restore (`ctx.game.save.load()` at boot). Pass `false` to opt out entirely, or a config object to tune the mode/cadence/target (`storage: "memory"` for in-session-only). Ignored for multiplayer worlds (the host persists via `save`). */
|
|
170
177
|
persist?: boolean | PersistConfig;
|
|
171
178
|
ui?: unknown;
|
|
172
179
|
loop?: GameLoop<GameContext>;
|
|
@@ -180,14 +187,14 @@ export interface GameDefinition<TAssetRef extends ModelAssetRef = ModelAssetRef,
|
|
|
180
187
|
*/
|
|
181
188
|
replication?: ReplicationPolicy;
|
|
182
189
|
}
|
|
183
|
-
/** Input to {@link
|
|
190
|
+
/** Input to {@link defineGameDefinition} — a `GameDefinition` with `scene` derived and `assets` optional. */
|
|
184
191
|
export type GameDefinitionConfig<TAssetRef extends ModelAssetRef = ModelAssetRef, TMultiplayer = unknown> = Omit<GameDefinition<TAssetRef, TMultiplayer>, "scene" | "assets"> & {
|
|
185
192
|
assets?: AssetCatalog<TAssetRef>;
|
|
186
193
|
};
|
|
187
194
|
/**
|
|
188
|
-
*
|
|
189
|
-
* OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic
|
|
190
|
-
*
|
|
191
|
-
*
|
|
195
|
+
* Host-level constructor for a bare `GameDefinition`: fills in `scene` and default `assets`, validates
|
|
196
|
+
* `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic
|
|
197
|
+
* hooks. Games author through `defineGame` from `@jgengine/shell/defineGame`, which calls this and adds
|
|
198
|
+
* the presentation layer; call this directly only from headless hosts that mount no shell.
|
|
192
199
|
*/
|
|
193
|
-
export declare function
|
|
200
|
+
export declare function defineGameDefinition<TAssetRef extends ModelAssetRef, TMultiplayer>(config: GameDefinitionConfig<TAssetRef, TMultiplayer>): GameDefinition<TAssetRef, TMultiplayer>;
|
package/dist/game/defineGame.js
CHANGED
|
@@ -2,6 +2,7 @@ import { mergeSystemFeatures } from "./defineSystem.js";
|
|
|
2
2
|
import { composeGameLoop } from "./systemRuntime.js";
|
|
3
3
|
import { createAssetCatalog } from "../scene/assetCatalog.js";
|
|
4
4
|
import { createEntityStore } from "../scene/entityStore.js";
|
|
5
|
+
import { resolveWorldPhysics } from "../world/place.js";
|
|
5
6
|
/** @internal */
|
|
6
7
|
export function createDisposer() {
|
|
7
8
|
const cleanups = [];
|
|
@@ -25,19 +26,26 @@ export function createDisposer() {
|
|
|
25
26
|
};
|
|
26
27
|
}
|
|
27
28
|
/**
|
|
28
|
-
*
|
|
29
|
-
* OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic
|
|
30
|
-
*
|
|
31
|
-
*
|
|
29
|
+
* Host-level constructor for a bare `GameDefinition`: fills in `scene` and default `assets`, validates
|
|
30
|
+
* `name`, OR-merges `features` from installed systems, and composes `loop` from `systems` + any classic
|
|
31
|
+
* hooks. Games author through `defineGame` from `@jgengine/shell/defineGame`, which calls this and adds
|
|
32
|
+
* the presentation layer; call this directly only from headless hosts that mount no shell.
|
|
32
33
|
*/
|
|
33
|
-
export function
|
|
34
|
+
export function defineGameDefinition(config) {
|
|
34
35
|
if (config.name.trim().length === 0) {
|
|
35
|
-
throw new Error("
|
|
36
|
+
throw new Error("defineGameDefinition: name must be non-empty");
|
|
36
37
|
}
|
|
38
|
+
// Boolean `features: { quest: true }` is sugar for the same activation map systems produce via
|
|
39
|
+
// `feature: "quest"`. Both OR-merge here; `createGameContext` only reads this final map through
|
|
40
|
+
// featureDescriptors.enabled — one enable path, no dual checks at install time.
|
|
37
41
|
const features = mergeSystemFeatures(config.features, config.systems);
|
|
38
42
|
const loop = composeGameLoop(config.systems, config.loop);
|
|
43
|
+
// A place world carries the laws of that place: its physics resolves over the game-level default,
|
|
44
|
+
// so every consumer of definition.physics (movement, combat, shell) sees the active world's laws.
|
|
45
|
+
const physics = resolveWorldPhysics(config.world, config.physics);
|
|
39
46
|
return {
|
|
40
47
|
...config,
|
|
48
|
+
...(physics === undefined ? {} : { physics }),
|
|
41
49
|
features,
|
|
42
50
|
loop,
|
|
43
51
|
scene: createEntityStore(),
|
|
@@ -78,6 +78,8 @@ export interface SystemDefinition {
|
|
|
78
78
|
/**
|
|
79
79
|
* Declare a composable game system. Pure data + hooks — the engine compiles the schedule and
|
|
80
80
|
* installs lifecycle when the game boots.
|
|
81
|
+
*
|
|
82
|
+
* @capability define-system composable game behavior — pure data + hooks the engine schedules; pass via `defineGame({ systems })`
|
|
81
83
|
*/
|
|
82
84
|
export declare function defineSystem(definition: SystemDefinition): SystemDefinition;
|
|
83
85
|
/** Collect feature flags implied by a system list (OR-merge; installing a system activates its capability).
|
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* Declare a composable game system. Pure data + hooks — the engine compiles the schedule and
|
|
3
3
|
* installs lifecycle when the game boots.
|
|
4
|
+
*
|
|
5
|
+
* @capability define-system composable game behavior — pure data + hooks the engine schedules; pass via `defineGame({ systems })`
|
|
4
6
|
*/
|
|
5
7
|
export function defineSystem(definition) {
|
|
6
8
|
if (definition.id.trim().length === 0) {
|
package/dist/game/events.d.ts
CHANGED
|
@@ -191,6 +191,31 @@ export interface AudioMusicEvent {
|
|
|
191
191
|
theme: string | null;
|
|
192
192
|
transpose?: number;
|
|
193
193
|
}
|
|
194
|
+
/**
|
|
195
|
+
* Start (or idempotently keep) the retained, id-keyed audio loop `id` from catalog `sound`, optionally
|
|
196
|
+
* anchored at world `at`. Restarting with the same `sound` does not restart the source (no click); a
|
|
197
|
+
* different `sound` replaces it. Drives RPM-pitched engine loops and slip-scaled tire squeal (#1051).
|
|
198
|
+
*/
|
|
199
|
+
export interface AudioLoopStartEvent {
|
|
200
|
+
id: string;
|
|
201
|
+
sound: string;
|
|
202
|
+
at?: readonly [number, number, number];
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Live-update the retained loop `id`: `rate` re-pitches it (1 = authored, the shell clamps to 0.25–4),
|
|
206
|
+
* `gain` rescales its volume (0–1), and `at` repositions its emitter. Emitted every tick to track a
|
|
207
|
+
* live signal (RPM, tire slip); the shell smooths rate/gain and ignores an unknown `id` (#1051).
|
|
208
|
+
*/
|
|
209
|
+
export interface AudioLoopSetEvent {
|
|
210
|
+
id: string;
|
|
211
|
+
rate?: number;
|
|
212
|
+
gain?: number;
|
|
213
|
+
at?: readonly [number, number, number];
|
|
214
|
+
}
|
|
215
|
+
/** Stop and dispose the retained loop `id`; an unknown `id` is ignored (#1051). */
|
|
216
|
+
export interface AudioLoopStopEvent {
|
|
217
|
+
id: string;
|
|
218
|
+
}
|
|
194
219
|
export interface GameEventMap {
|
|
195
220
|
"entity.died": EntityDiedEvent;
|
|
196
221
|
"entity.floatText": EntityFloatTextEvent;
|
|
@@ -221,6 +246,9 @@ export interface GameEventMap {
|
|
|
221
246
|
"audio.play": AudioPlayEvent;
|
|
222
247
|
"audio.music": AudioMusicEvent;
|
|
223
248
|
"audio.resume": AudioResumeEvent;
|
|
249
|
+
"audio.loopStart": AudioLoopStartEvent;
|
|
250
|
+
"audio.loopSet": AudioLoopSetEvent;
|
|
251
|
+
"audio.loopStop": AudioLoopStopEvent;
|
|
224
252
|
"entity.animation": EntityAnimationEvent;
|
|
225
253
|
}
|
|
226
254
|
export type GameEventHandler<TPayload> = (payload: TPayload) => void;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedural animation for part-composed (rig-less) models — characters kit-bashed from
|
|
3
|
+
* primitives/`ModelPart`s with no skeleton or clips. Pure, deterministic curve sampling:
|
|
4
|
+
* the same (time, speed, phase, flinch, death) input always yields the same pose, so
|
|
5
|
+
* replays and multi-client renders stay in lockstep and the hot path allocates nothing
|
|
6
|
+
* when an `out` pose is reused.
|
|
7
|
+
*/
|
|
8
|
+
/** Semantic motion role of one `ModelPart` in a part-composed character. */
|
|
9
|
+
export type PartRole = "head" | "arm.l" | "arm.r" | "leg.l" | "leg.r" | "tail" | "wing.l" | "wing.r";
|
|
10
|
+
/** Tuning for {@link samplePartPose}/{@link sampleBodyPose}; every field has a sensible default. */
|
|
11
|
+
export interface PartMotionParams {
|
|
12
|
+
/** Full stride cycles per second at walking speed. Default 1.6. */
|
|
13
|
+
strideHz?: number;
|
|
14
|
+
/** Limb swing amplitude in radians at full walk. Default 0.7. */
|
|
15
|
+
swingRad?: number;
|
|
16
|
+
/** Body bob height in model units at full walk. Default 0.04. */
|
|
17
|
+
bobAmp?: number;
|
|
18
|
+
/** Idle sway/breathe amplitude in radians. Default 0.05. */
|
|
19
|
+
swayRad?: number;
|
|
20
|
+
/** Speed (world units/sec) treated as a full walk; swing/bob scale linearly below it. Default 2. */
|
|
21
|
+
walkSpeed?: number;
|
|
22
|
+
/** Backward flinch pitch in radians at flinch = 1. Default 0.35. */
|
|
23
|
+
flinchRad?: number;
|
|
24
|
+
/** Sideways topple roll in radians at death = 1. Default PI / 2. */
|
|
25
|
+
toppleRad?: number;
|
|
26
|
+
/**
|
|
27
|
+
* Squash-and-stretch amplitude for soft characters (blobs, slimes): footfall squash while
|
|
28
|
+
* walking, jelly breathe while idle, a squash pulse on flinch. Volume-conserving — the body
|
|
29
|
+
* bulges horizontally as it compresses. Default 0 (rigid; scale stays exactly 1).
|
|
30
|
+
*/
|
|
31
|
+
squashAmp?: number;
|
|
32
|
+
/** How death reads: `"topple"` rolls sideways (default, hard characters); `"splat"` flattens the body out — right for blobs. */
|
|
33
|
+
deathStyle?: "topple" | "splat";
|
|
34
|
+
}
|
|
35
|
+
/** Sampled offsets to add onto a part's authored transform (or the character root). `scale` is multiplicative (1 = unchanged). */
|
|
36
|
+
export interface PartPose {
|
|
37
|
+
position: [number, number, number];
|
|
38
|
+
rotation: [number, number, number];
|
|
39
|
+
scale: [number, number, number];
|
|
40
|
+
}
|
|
41
|
+
/** Live signals a driver feeds the samplers each frame. All values are render-side only. */
|
|
42
|
+
export interface PartMotionInput {
|
|
43
|
+
/** Seconds since the driver started (any monotonic clock). */
|
|
44
|
+
timeSec: number;
|
|
45
|
+
/** Horizontal speed in world units/sec (smoothed by the driver). */
|
|
46
|
+
speed: number;
|
|
47
|
+
/** Per-character phase offset in [0, 1) so crowds don't stride in sync — see {@link partMotionPhase}. */
|
|
48
|
+
phase: number;
|
|
49
|
+
/** Hit-flinch envelope in [0, 1]; the driver sets 1 on a hit and decays it. */
|
|
50
|
+
flinch: number;
|
|
51
|
+
/** Death ramp in [0, 1]; the driver eases it to 1 after the entity dies. */
|
|
52
|
+
death: number;
|
|
53
|
+
}
|
|
54
|
+
/** A reusable identity pose (zero offsets, unit scale). */
|
|
55
|
+
export declare function createPartPose(): PartPose;
|
|
56
|
+
/**
|
|
57
|
+
* Deterministic per-character stride phase in [0, 1) from an id (e.g. the entity instance id),
|
|
58
|
+
* so a crowd of identical part-composed characters doesn't animate in lockstep.
|
|
59
|
+
*/
|
|
60
|
+
export declare function partMotionPhase(id: string): number;
|
|
61
|
+
/**
|
|
62
|
+
* Sample the character-root pose: walk bob, idle breathe/sway, backward hit flinch, and death —
|
|
63
|
+
* a sideways topple by default, or a flatten-out splat with `deathStyle: "splat"`. With
|
|
64
|
+
* `squashAmp > 0` the body also squashes and stretches (footfall squash, jelly idle breathe,
|
|
65
|
+
* flinch squash pulse), volume-conserving. A driver applies this to the group wrapping the
|
|
66
|
+
* whole composition (base model plus parts) — limbs ride along, as they would on a real torso.
|
|
67
|
+
*
|
|
68
|
+
* @capability part-motion sample the root bob/sway/squash/flinch/death pose for a rig-less part-composed character
|
|
69
|
+
*/
|
|
70
|
+
export declare function sampleBodyPose(input: PartMotionInput, params?: PartMotionParams, out?: PartPose): PartPose;
|
|
71
|
+
/**
|
|
72
|
+
* Sample one part's pose offset by role: counter-phase leg/arm swing driven by speed, a head
|
|
73
|
+
* counter-sway, tail wag, and wing flap. Offsets are added onto the part's authored
|
|
74
|
+
* position/rotation. Motion fades to zero as `death` ramps so the topple reads as one piece.
|
|
75
|
+
*
|
|
76
|
+
* @capability part-motion sample a limb/head/tail/wing pose offset for a role-tagged ModelPart
|
|
77
|
+
*/
|
|
78
|
+
export declare function samplePartPose(role: PartRole, input: PartMotionInput, params?: PartMotionParams, out?: PartPose): PartPose;
|
|
@@ -0,0 +1,147 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Procedural animation for part-composed (rig-less) models — characters kit-bashed from
|
|
3
|
+
* primitives/`ModelPart`s with no skeleton or clips. Pure, deterministic curve sampling:
|
|
4
|
+
* the same (time, speed, phase, flinch, death) input always yields the same pose, so
|
|
5
|
+
* replays and multi-client renders stay in lockstep and the hot path allocates nothing
|
|
6
|
+
* when an `out` pose is reused.
|
|
7
|
+
*/
|
|
8
|
+
const DEFAULT_STRIDE_HZ = 1.6;
|
|
9
|
+
const DEFAULT_SWING_RAD = 0.7;
|
|
10
|
+
const DEFAULT_BOB_AMP = 0.04;
|
|
11
|
+
const DEFAULT_SWAY_RAD = 0.05;
|
|
12
|
+
const DEFAULT_WALK_SPEED = 2;
|
|
13
|
+
const DEFAULT_FLINCH_RAD = 0.35;
|
|
14
|
+
const DEFAULT_TOPPLE_RAD = Math.PI / 2;
|
|
15
|
+
const IDLE_BREATHE_HZ = 0.45;
|
|
16
|
+
const TWO_PI = Math.PI * 2;
|
|
17
|
+
function clamp01(value) {
|
|
18
|
+
return value < 0 ? 0 : value > 1 ? 1 : value;
|
|
19
|
+
}
|
|
20
|
+
function zeroPose(out) {
|
|
21
|
+
out.position[0] = 0;
|
|
22
|
+
out.position[1] = 0;
|
|
23
|
+
out.position[2] = 0;
|
|
24
|
+
out.rotation[0] = 0;
|
|
25
|
+
out.rotation[1] = 0;
|
|
26
|
+
out.rotation[2] = 0;
|
|
27
|
+
out.scale[0] = 1;
|
|
28
|
+
out.scale[1] = 1;
|
|
29
|
+
out.scale[2] = 1;
|
|
30
|
+
return out;
|
|
31
|
+
}
|
|
32
|
+
/** Volume-conserving vertical squash: compress y by `amount`, bulge x/z by half of it. */
|
|
33
|
+
function applySquash(out, amount) {
|
|
34
|
+
out.scale[1] *= 1 - amount;
|
|
35
|
+
const bulge = 1 + amount * 0.5;
|
|
36
|
+
out.scale[0] *= bulge;
|
|
37
|
+
out.scale[2] *= bulge;
|
|
38
|
+
}
|
|
39
|
+
/** A reusable identity pose (zero offsets, unit scale). */
|
|
40
|
+
export function createPartPose() {
|
|
41
|
+
return { position: [0, 0, 0], rotation: [0, 0, 0], scale: [1, 1, 1] };
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Deterministic per-character stride phase in [0, 1) from an id (e.g. the entity instance id),
|
|
45
|
+
* so a crowd of identical part-composed characters doesn't animate in lockstep.
|
|
46
|
+
*/
|
|
47
|
+
export function partMotionPhase(id) {
|
|
48
|
+
let hash = 0;
|
|
49
|
+
for (let index = 0; index < id.length; index += 1)
|
|
50
|
+
hash = (hash * 31 + id.charCodeAt(index)) >>> 0;
|
|
51
|
+
return (hash % 1000) / 1000;
|
|
52
|
+
}
|
|
53
|
+
const SPLAT_FLATTEN = 0.7;
|
|
54
|
+
/**
|
|
55
|
+
* Sample the character-root pose: walk bob, idle breathe/sway, backward hit flinch, and death —
|
|
56
|
+
* a sideways topple by default, or a flatten-out splat with `deathStyle: "splat"`. With
|
|
57
|
+
* `squashAmp > 0` the body also squashes and stretches (footfall squash, jelly idle breathe,
|
|
58
|
+
* flinch squash pulse), volume-conserving. A driver applies this to the group wrapping the
|
|
59
|
+
* whole composition (base model plus parts) — limbs ride along, as they would on a real torso.
|
|
60
|
+
*
|
|
61
|
+
* @capability part-motion sample the root bob/sway/squash/flinch/death pose for a rig-less part-composed character
|
|
62
|
+
*/
|
|
63
|
+
export function sampleBodyPose(input, params, out = createPartPose()) {
|
|
64
|
+
zeroPose(out);
|
|
65
|
+
const walkSpeed = params?.walkSpeed ?? DEFAULT_WALK_SPEED;
|
|
66
|
+
const strideHz = params?.strideHz ?? DEFAULT_STRIDE_HZ;
|
|
67
|
+
const bobAmp = params?.bobAmp ?? DEFAULT_BOB_AMP;
|
|
68
|
+
const swayRad = params?.swayRad ?? DEFAULT_SWAY_RAD;
|
|
69
|
+
const squashAmp = params?.squashAmp ?? 0;
|
|
70
|
+
const death = clamp01(input.death);
|
|
71
|
+
const alive = 1 - death;
|
|
72
|
+
const walk = clamp01(input.speed / walkSpeed) * alive;
|
|
73
|
+
const idle = (1 - walk) * alive;
|
|
74
|
+
const stride = TWO_PI * (input.timeSec * strideHz + input.phase);
|
|
75
|
+
const breathe = TWO_PI * (input.timeSec * IDLE_BREATHE_HZ + input.phase);
|
|
76
|
+
const step = Math.abs(Math.sin(stride));
|
|
77
|
+
// Double-bounce bob: the body rises on each step, twice per stride cycle.
|
|
78
|
+
out.position[1] = step * bobAmp * walk + Math.sin(breathe) * bobAmp * 0.5 * idle;
|
|
79
|
+
out.rotation[2] = Math.sin(breathe) * swayRad * idle;
|
|
80
|
+
out.rotation[0] = -clamp01(input.flinch) * (params?.flinchRad ?? DEFAULT_FLINCH_RAD) * alive;
|
|
81
|
+
if (squashAmp > 0) {
|
|
82
|
+
// Footfall squash: deepest where the bob is lowest (step ≈ 0), released at the top of the step.
|
|
83
|
+
applySquash(out, (1 - step) * squashAmp * walk);
|
|
84
|
+
// Jelly breathe: idle scale oscillation, gentler than a footfall.
|
|
85
|
+
applySquash(out, Math.sin(breathe) * squashAmp * 0.4 * idle);
|
|
86
|
+
// Flinch reads as a squash pulse on a soft body.
|
|
87
|
+
applySquash(out, clamp01(input.flinch) * squashAmp * alive);
|
|
88
|
+
}
|
|
89
|
+
if ((params?.deathStyle ?? "topple") === "splat") {
|
|
90
|
+
applySquash(out, death * SPLAT_FLATTEN);
|
|
91
|
+
}
|
|
92
|
+
else {
|
|
93
|
+
out.rotation[2] += death * (params?.toppleRad ?? DEFAULT_TOPPLE_RAD);
|
|
94
|
+
}
|
|
95
|
+
return out;
|
|
96
|
+
}
|
|
97
|
+
/**
|
|
98
|
+
* Sample one part's pose offset by role: counter-phase leg/arm swing driven by speed, a head
|
|
99
|
+
* counter-sway, tail wag, and wing flap. Offsets are added onto the part's authored
|
|
100
|
+
* position/rotation. Motion fades to zero as `death` ramps so the topple reads as one piece.
|
|
101
|
+
*
|
|
102
|
+
* @capability part-motion sample a limb/head/tail/wing pose offset for a role-tagged ModelPart
|
|
103
|
+
*/
|
|
104
|
+
export function samplePartPose(role, input, params, out = createPartPose()) {
|
|
105
|
+
zeroPose(out);
|
|
106
|
+
const walkSpeed = params?.walkSpeed ?? DEFAULT_WALK_SPEED;
|
|
107
|
+
const strideHz = params?.strideHz ?? DEFAULT_STRIDE_HZ;
|
|
108
|
+
const swingRad = params?.swingRad ?? DEFAULT_SWING_RAD;
|
|
109
|
+
const swayRad = params?.swayRad ?? DEFAULT_SWAY_RAD;
|
|
110
|
+
const alive = 1 - clamp01(input.death);
|
|
111
|
+
const walk = clamp01(input.speed / walkSpeed) * alive;
|
|
112
|
+
const idle = (1 - walk) * alive;
|
|
113
|
+
const stride = TWO_PI * (input.timeSec * strideHz + input.phase);
|
|
114
|
+
const breathe = TWO_PI * (input.timeSec * IDLE_BREATHE_HZ + input.phase);
|
|
115
|
+
switch (role) {
|
|
116
|
+
case "leg.l":
|
|
117
|
+
out.rotation[0] = Math.sin(stride) * swingRad * walk;
|
|
118
|
+
break;
|
|
119
|
+
case "leg.r":
|
|
120
|
+
out.rotation[0] = Math.sin(stride + Math.PI) * swingRad * walk;
|
|
121
|
+
break;
|
|
122
|
+
case "arm.l":
|
|
123
|
+
// Arms counter their same-side leg, at slightly lower amplitude.
|
|
124
|
+
out.rotation[0] = Math.sin(stride + Math.PI) * swingRad * 0.8 * walk;
|
|
125
|
+
out.rotation[2] = Math.sin(breathe) * swayRad * idle;
|
|
126
|
+
break;
|
|
127
|
+
case "arm.r":
|
|
128
|
+
out.rotation[0] = Math.sin(stride) * swingRad * 0.8 * walk;
|
|
129
|
+
out.rotation[2] = -Math.sin(breathe) * swayRad * idle;
|
|
130
|
+
break;
|
|
131
|
+
case "head":
|
|
132
|
+
out.rotation[2] = -Math.sin(breathe) * swayRad * idle;
|
|
133
|
+
out.rotation[1] = Math.sin(stride) * swayRad * 0.5 * walk;
|
|
134
|
+
out.rotation[0] = -clamp01(input.flinch) * 0.5 * (params?.flinchRad ?? DEFAULT_FLINCH_RAD) * alive;
|
|
135
|
+
break;
|
|
136
|
+
case "tail":
|
|
137
|
+
out.rotation[1] = Math.sin(stride) * swingRad * 0.5 * walk + Math.sin(breathe) * swayRad * 2 * idle;
|
|
138
|
+
break;
|
|
139
|
+
case "wing.l":
|
|
140
|
+
out.rotation[2] = Math.abs(Math.sin(stride)) * swingRad * walk + Math.sin(breathe) * swayRad * idle;
|
|
141
|
+
break;
|
|
142
|
+
case "wing.r":
|
|
143
|
+
out.rotation[2] = -Math.abs(Math.sin(stride)) * swingRad * walk - Math.sin(breathe) * swayRad * idle;
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
return out;
|
|
147
|
+
}
|