@jgengine/core 0.18.0 → 0.19.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 +265 -2
- package/dist/ai/decisionGraph.d.ts +96 -0
- package/dist/ai/decisionGraph.js +212 -0
- package/dist/ai/groupAssist.d.ts +10 -0
- package/dist/ai/groupAssist.js +11 -0
- package/dist/ai/mobBrainGraph.d.ts +3 -0
- package/dist/ai/mobBrainGraph.js +25 -0
- package/dist/ai/pursuit.d.ts +1 -1
- package/dist/ai/pursuit.js +1 -1
- package/dist/ai/tacticalQueries.d.ts +30 -0
- package/dist/ai/tacticalQueries.js +45 -0
- package/dist/ai/threat.d.ts +8 -0
- package/dist/ai/threat.js +10 -0
- package/dist/anim/animGraph.d.ts +155 -0
- package/dist/anim/animGraph.js +414 -0
- package/dist/anim/boneTexture.d.ts +55 -0
- package/dist/anim/boneTexture.js +43 -0
- package/dist/anim/footPlacement.d.ts +57 -0
- package/dist/anim/footPlacement.js +67 -0
- package/dist/anim/ikSolver.d.ts +51 -0
- package/dist/anim/ikSolver.js +124 -0
- package/dist/anim/locomotionGraph.d.ts +35 -0
- package/dist/anim/locomotionGraph.js +62 -0
- package/dist/assets/spriteAtlas.d.ts +21 -0
- package/dist/assets/spriteAtlas.js +103 -0
- package/dist/audio/audioFalloff.d.ts +12 -0
- package/dist/audio/doppler.d.ts +21 -0
- package/dist/audio/doppler.js +32 -0
- package/dist/audio/engineLayers.d.ts +83 -0
- package/dist/audio/engineLayers.js +115 -0
- package/dist/combat/abilityKit.d.ts +1 -0
- package/dist/combat/abilityKit.js +1 -0
- package/dist/combat/death.d.ts +7 -0
- package/dist/combat/shotOrigin.d.ts +6 -0
- package/dist/combat/shotOrigin.js +6 -0
- package/dist/combat/weaponFeedback.d.ts +36 -0
- package/dist/combat/weaponFeedback.js +42 -0
- package/dist/combat/weaponHandling.d.ts +161 -0
- package/dist/combat/weaponHandling.js +202 -0
- package/dist/combat/weaponPresentation.d.ts +115 -0
- package/dist/combat/weaponPresentation.js +96 -0
- package/dist/devtools/warnOnce.d.ts +8 -0
- package/dist/devtools/warnOnce.js +18 -0
- package/dist/economy/currency.d.ts +16 -2
- package/dist/economy/currency.js +55 -14
- package/dist/economy/wallet.d.ts +5 -4
- package/dist/economy/wallet.js +8 -4
- package/dist/editor/commandApply.js +1 -0
- package/dist/editor/commands.d.ts +4 -1
- package/dist/editor/document.d.ts +2 -1
- package/dist/editor/document.js +92 -1
- package/dist/editor/environment.d.ts +3 -0
- package/dist/editor/environment.js +24 -0
- package/dist/editor/index.d.ts +4 -2
- package/dist/editor/index.js +1 -1
- package/dist/editor/types.d.ts +20 -0
- package/dist/faction/reputation.d.ts +6 -0
- package/dist/faction/reputation.js +11 -0
- package/dist/game/cameraConfig.d.ts +59 -1
- package/dist/game/chat.d.ts +22 -1
- package/dist/game/chat.js +48 -19
- package/dist/game/controlGate.d.ts +6 -0
- package/dist/game/controlGate.js +30 -1
- package/dist/game/defineGame.d.ts +10 -1
- package/dist/game/events.d.ts +14 -1
- package/dist/game/lootTable.d.ts +4 -0
- package/dist/game/lootTable.js +16 -0
- package/dist/game/pixelPerfect.d.ts +25 -0
- package/dist/game/pixelPerfect.js +29 -0
- package/dist/game/playableGame.d.ts +167 -6
- package/dist/game/systemRuntime.js +6 -1
- package/dist/game/toasts.d.ts +8 -0
- package/dist/game/toasts.js +7 -0
- package/dist/game/vfxInstance.d.ts +9 -0
- package/dist/game/vfxInstance.js +12 -1
- package/dist/game/viewports.d.ts +43 -0
- package/dist/game/viewports.js +46 -0
- package/dist/game/worldItem.d.ts +5 -1
- package/dist/game/worldItem.js +0 -1
- package/dist/input/actionBindings.d.ts +17 -2
- package/dist/input/actionBindings.js +24 -11
- package/dist/input/actionContexts.d.ts +45 -0
- package/dist/input/actionContexts.js +113 -0
- package/dist/input/axisInput.d.ts +4 -0
- package/dist/input/axisInput.js +4 -0
- package/dist/input/axisShaper.d.ts +73 -0
- package/dist/input/axisShaper.js +105 -0
- package/dist/input/gamepadModel.d.ts +79 -0
- package/dist/input/gamepadModel.js +108 -0
- package/dist/input/haptics.d.ts +50 -0
- package/dist/input/haptics.js +74 -0
- package/dist/input/inputBuffer.d.ts +40 -0
- package/dist/input/inputBuffer.js +59 -0
- package/dist/input/lookChannel.d.ts +10 -0
- package/dist/input/lookChannel.js +6 -0
- package/dist/input/pointer.d.ts +2 -0
- package/dist/input/pointer.js +4 -0
- package/dist/item/affix.d.ts +6 -0
- package/dist/item/affix.js +6 -0
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +196 -1
- package/dist/movement/characterController.d.ts +76 -0
- package/dist/movement/characterController.js +216 -0
- package/dist/movement/freeFlight.d.ts +119 -0
- package/dist/movement/freeFlight.js +294 -0
- package/dist/movement/glideModel.d.ts +3 -0
- package/dist/movement/glideModel.js +6 -0
- package/dist/movement/grappleSwing.d.ts +2 -0
- package/dist/movement/grappleSwing.js +5 -1
- package/dist/movement/movementModel.d.ts +39 -0
- package/dist/movement/movementModel.js +62 -19
- package/dist/movement/movementProbe.d.ts +51 -0
- package/dist/movement/movementProbe.js +119 -0
- package/dist/movement/playerMovement.d.ts +30 -3
- package/dist/movement/playerMovement.js +304 -7
- package/dist/movement/serverStep.d.ts +5 -0
- package/dist/movement/serverStep.js +6 -0
- package/dist/movement/solidObstacles.d.ts +39 -8
- package/dist/movement/solidObstacles.js +71 -6
- package/dist/movement/voxelController.js +7 -7
- package/dist/multiplayer/poseSyncGate.d.ts +3 -1
- package/dist/multiplayer/poseSyncGate.js +9 -1
- package/dist/multiplayer/presenceContract.d.ts +6 -0
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +11 -0
- package/dist/nav/navFromEnvironment.d.ts +9 -3
- package/dist/nav/navFromEnvironment.js +18 -12
- package/dist/nav/navMesh.d.ts +74 -0
- package/dist/nav/navMesh.js +570 -0
- package/dist/physics/aircraftDynamics.d.ts +390 -0
- package/dist/physics/aircraftDynamics.js +704 -0
- package/dist/physics/boatDynamics.d.ts +116 -0
- package/dist/physics/boatDynamics.js +160 -0
- package/dist/physics/drivableVehicle.d.ts +22 -8
- package/dist/physics/drivableVehicle.js +4 -2
- package/dist/physics/flightDynamics.d.ts +29 -4
- package/dist/physics/flightDynamics.js +28 -5
- package/dist/physics/handlingProbe.d.ts +249 -0
- package/dist/physics/handlingProbe.js +606 -0
- package/dist/physics/kinematicVehicle.d.ts +37 -2
- package/dist/physics/kinematicVehicle.js +15 -0
- package/dist/physics/physicsBackend.d.ts +203 -0
- package/dist/physics/physicsBackend.js +49 -0
- package/dist/physics/physicsBackendConformance.d.ts +27 -0
- package/dist/physics/physicsBackendConformance.js +151 -0
- package/dist/physics/physicsWorldBackend.d.ts +45 -0
- package/dist/physics/physicsWorldBackend.js +518 -0
- package/dist/physics/vehicleBackendLink.d.ts +49 -0
- package/dist/physics/vehicleBackendLink.js +102 -0
- package/dist/physics/vehicleDynamics.d.ts +374 -0
- package/dist/physics/vehicleDynamics.js +737 -0
- package/dist/physics/worldColliders.d.ts +13 -0
- package/dist/physics/worldColliders.js +128 -0
- package/dist/puzzle/cellGrid.d.ts +15 -9
- package/dist/puzzle/cellGrid.js +13 -9
- package/dist/random/rng.d.ts +12 -1
- package/dist/random/rng.js +20 -3
- package/dist/render/environment.d.ts +23 -0
- package/dist/render/environment.js +1 -0
- package/dist/render/lookPreset.d.ts +19 -6
- package/dist/render/lookPreset.js +56 -4
- package/dist/render/postProcessing.d.ts +40 -1
- package/dist/render/postProcessing.js +1 -1
- package/dist/render/sprite2d.d.ts +38 -0
- package/dist/render/sprite2d.js +40 -0
- package/dist/runtime/adapter.d.ts +20 -1
- package/dist/runtime/adapter.js +8 -1
- package/dist/runtime/cameraDirector.d.ts +17 -3
- package/dist/runtime/cameraDirector.js +25 -0
- package/dist/runtime/commandInput.d.ts +52 -0
- package/dist/runtime/commandInput.js +71 -0
- package/dist/runtime/commandRunner.d.ts +39 -7
- package/dist/runtime/commandRunner.js +35 -5
- package/dist/runtime/context/combat.d.ts +1 -0
- package/dist/runtime/context/combat.js +2 -1
- package/dist/runtime/context/deathLoot.d.ts +2 -0
- package/dist/runtime/context/deathLoot.js +1 -0
- package/dist/runtime/gameContext.js +18 -1
- package/dist/runtime/gameContextTypes.d.ts +33 -7
- package/dist/runtime/gameRuntime.d.ts +23 -5
- package/dist/runtime/gameRuntime.js +21 -4
- package/dist/runtime/headlessRunner.d.ts +10 -0
- package/dist/runtime/headlessRunner.js +26 -8
- package/dist/runtime/hostPolicy.d.ts +3 -2
- package/dist/runtime/hostPolicy.js +8 -10
- package/dist/runtime/hostedGameRunner.js +34 -6
- package/dist/runtime/hostedWorldSession.d.ts +12 -16
- package/dist/runtime/hostedWorldSession.js +29 -6
- package/dist/runtime/hostedWorldStore.d.ts +20 -0
- package/dist/runtime/hostedWorldStore.js +1 -0
- package/dist/runtime/inputRecorder.d.ts +30 -0
- package/dist/runtime/inputRecorder.js +59 -0
- package/dist/runtime/inputSnapshot.d.ts +14 -0
- package/dist/runtime/inputSnapshot.js +11 -0
- package/dist/runtime/localPlayers.d.ts +73 -0
- package/dist/runtime/localPlayers.js +119 -0
- package/dist/runtime/poseInterpolation.d.ts +36 -0
- package/dist/runtime/poseInterpolation.js +91 -0
- package/dist/runtime/prediction.d.ts +45 -0
- package/dist/runtime/prediction.js +62 -0
- package/dist/runtime/simContext.d.ts +45 -0
- package/dist/runtime/simContext.js +65 -0
- package/dist/runtime/simLoop.d.ts +52 -0
- package/dist/runtime/simLoop.js +72 -0
- package/dist/runtime/simSnapshot.d.ts +32 -0
- package/dist/runtime/simSnapshot.js +60 -0
- package/dist/runtime/snapshot.d.ts +8 -0
- package/dist/runtime/snapshotBuffer.d.ts +33 -0
- package/dist/runtime/snapshotBuffer.js +58 -0
- package/dist/runtime/transport.d.ts +41 -2
- package/dist/runtime/transport.js +7 -1
- package/dist/runtime/worldChunks.d.ts +2 -0
- package/dist/runtime/worldChunks.js +14 -0
- package/dist/runtime/worldProjection.d.ts +9 -0
- package/dist/runtime/worldProjection.js +17 -1
- package/dist/runtime/worldSnapshot.d.ts +6 -0
- package/dist/runtime/worldSnapshot.js +3 -0
- package/dist/scene/behaviorRuntime.d.ts +29 -1
- package/dist/scene/behaviorRuntime.js +71 -4
- package/dist/scene/behaviors.d.ts +12 -1
- package/dist/scene/builtinSceneKinds.js +10 -0
- package/dist/scene/sceneKinds.d.ts +9 -0
- package/dist/scene/vehicleSeat.d.ts +5 -1
- package/dist/scene/vehicleSeat.js +5 -1
- package/dist/sensor/concealment.d.ts +6 -0
- package/dist/sensor/concealment.js +8 -0
- package/dist/sensor/freezeMonitor.d.ts +6 -0
- package/dist/sensor/freezeMonitor.js +8 -0
- package/dist/sensor/perception.d.ts +70 -0
- package/dist/sensor/perception.js +189 -0
- package/dist/sensor/recordingBuffer.d.ts +3 -0
- package/dist/sensor/recordingBuffer.js +8 -0
- package/dist/session/roundState.d.ts +4 -0
- package/dist/session/roundState.js +11 -0
- package/dist/settings/frameRateLimit.d.ts +16 -0
- package/dist/settings/frameRateLimit.js +37 -0
- package/dist/settings/graphicsProfile.d.ts +21 -0
- package/dist/settings/graphicsProfile.js +48 -0
- package/dist/settings/graphicsSettings.d.ts +40 -0
- package/dist/settings/graphicsSettings.js +81 -0
- package/dist/settings/settingsModel.d.ts +9 -1
- package/dist/settings/settingsModel.js +10 -0
- package/dist/stats/eventMeter.d.ts +1 -1
- package/dist/stats/eventMeter.js +1 -1
- package/dist/time/accrueSince.d.ts +10 -0
- package/dist/time/accrueSince.js +9 -0
- package/dist/time/rateWindow.d.ts +16 -0
- package/dist/time/rateWindow.js +14 -0
- package/dist/time/serverTick.d.ts +21 -3
- package/dist/time/serverTick.js +14 -13
- package/dist/vfx/feedbackMixer.d.ts +61 -0
- package/dist/vfx/feedbackMixer.js +106 -0
- package/dist/vfx/particleDirector.d.ts +68 -0
- package/dist/vfx/particleDirector.js +81 -0
- package/dist/visibility/simulationCulling.d.ts +8 -1
- package/dist/visibility/simulationCulling.js +7 -0
- package/dist/visibility/spatialIndex.d.ts +21 -0
- package/dist/visibility/spatialIndex.js +16 -0
- package/dist/world/authoredSolids.d.ts +46 -0
- package/dist/world/authoredSolids.js +75 -0
- package/dist/world/buildingGenerator.js +2 -2
- package/dist/world/buildingKit.d.ts +3 -1
- package/dist/world/buildings.d.ts +46 -2
- package/dist/world/buildings.js +53 -3
- package/dist/world/cityContent.js +103 -25
- package/dist/world/cityKind.d.ts +139 -0
- package/dist/world/cityKind.js +367 -16
- package/dist/world/features.d.ts +56 -3
- package/dist/world/features.js +13 -2
- package/dist/world/footprintGrid.d.ts +7 -0
- package/dist/world/footprintGrid.js +16 -0
- package/dist/world/interiors.d.ts +1 -0
- package/dist/world/interiors.js +5 -3
- package/dist/world/lod.d.ts +6 -0
- package/dist/world/lod.js +8 -0
- package/dist/world/placement.d.ts +7 -0
- package/dist/world/placement.js +2 -0
- package/dist/world/placementController.d.ts +1 -1
- package/dist/world/placementController.js +7 -0
- package/dist/world/raceCircuit.d.ts +168 -0
- package/dist/world/raceCircuit.js +797 -0
- package/dist/world/terraform.d.ts +8 -0
- package/dist/world/terraform.js +7 -0
- package/dist/world/terrain.d.ts +5 -1
- package/dist/world/terrain.js +5 -0
- package/dist/world/territory.d.ts +97 -0
- package/dist/world/territory.js +182 -0
- package/dist/world/walls.d.ts +7 -0
- package/dist/world/walls.js +7 -0
- package/dist/world/worldSolids.d.ts +84 -0
- package/dist/world/worldSolids.js +240 -0
- package/dist/world.d.ts +5 -3
- package/dist/world.js +4 -2
- package/package.json +1 -1
- package/skills/ce-handoff/SKILL.md +2 -2
- package/skills/game-design/SKILL.md +11 -1
- package/skills/jgengine/SKILL.md +9 -5
- package/skills/jgengine/api.md +205 -101
- package/skills/jgengine/capabilities.md +56 -0
- package/skills/jgengine/recipes/minimal-game.md +3 -0
- package/skills/jgengine-combat/SKILL.md +5 -0
- package/skills/jgengine-combat/api.md +138 -103
- package/skills/jgengine-combat/capabilities.md +29 -1
- package/skills/jgengine-combat/recipes/weapon-feel.md +50 -0
- package/skills/jgengine-gameplay/SKILL.md +10 -0
- package/skills/jgengine-gameplay/api.md +501 -453
- package/skills/jgengine-gameplay/capabilities.md +36 -0
- package/skills/jgengine-gameplay/reference-systems.md +4 -0
- package/skills/jgengine-world/SKILL.md +21 -3
- package/skills/jgengine-world/api.md +1172 -625
- package/skills/jgengine-world/capabilities.md +229 -3
- package/skills/jgengine-world/recipes/character-animation.md +39 -0
- package/skills/jgengine-world/recipes/controls.md +44 -0
- package/skills/jgengine-world/recipes/flight-feel.md +76 -0
- package/skills/jgengine-world/recipes/npc-senses.md +43 -0
- package/skills/jgengine-world/recipes/vehicle-feel.md +85 -0
- package/skills/jgengine-world/reference.md +31 -2
- package/skills/jgengine-world/visibility.md +112 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/** One action's press history inside an {@link InputBuffer}. */
|
|
2
|
+
export interface BufferedAction {
|
|
3
|
+
pressedAt: number;
|
|
4
|
+
previousPressAt: number | null;
|
|
5
|
+
releasedAt: number | null;
|
|
6
|
+
consumed: boolean;
|
|
7
|
+
}
|
|
8
|
+
/** Serializable state for an {@link InputBuffer}. */
|
|
9
|
+
export interface InputBufferSnapshot {
|
|
10
|
+
windowMs: number;
|
|
11
|
+
actions: Record<string, BufferedAction>;
|
|
12
|
+
}
|
|
13
|
+
/** Remembers recent action presses so a press slightly early (a jump before landing) still counts. Times are caller-supplied ms, so it runs the same on a client, a host or in a replay. */
|
|
14
|
+
export interface InputBuffer {
|
|
15
|
+
/** Record a press edge. */
|
|
16
|
+
press(action: string, nowMs: number): void;
|
|
17
|
+
/** Record a release edge; {@link InputBuffer.hold} stops growing. */
|
|
18
|
+
release(action: string, nowMs: number): void;
|
|
19
|
+
/** `true` once for a press no older than `windowMs` (default: the buffer's window), then the press is spent. */
|
|
20
|
+
consume(action: string, nowMs: number, windowMs?: number): boolean;
|
|
21
|
+
/** `true` while `nowMs` is within `windowMs` of `groundedAtMs` — late-jump grace after leaving a ledge. `null` never qualifies. */
|
|
22
|
+
coyote(groundedAtMs: number | null, nowMs: number, windowMs: number): boolean;
|
|
23
|
+
/** How long the action has been held, ms; `0` when released or never pressed. */
|
|
24
|
+
hold(action: string, nowMs: number): number;
|
|
25
|
+
/** `true` when the last two presses were at most `gapMs` apart and the latest is no older than `gapMs`. */
|
|
26
|
+
doubleTap(action: string, nowMs: number, gapMs: number): boolean;
|
|
27
|
+
/** Change the default buffer window. */
|
|
28
|
+
retune(next: {
|
|
29
|
+
windowMs: number;
|
|
30
|
+
}): void;
|
|
31
|
+
snapshot(): InputBufferSnapshot;
|
|
32
|
+
restore(snapshot: InputBufferSnapshot): void;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Creates an input buffer for jump buffering, coyote time, hold duration and double taps.
|
|
36
|
+
* @capability input-buffer buffer early presses, coyote time, hold duration and double tap for responsive controls
|
|
37
|
+
*/
|
|
38
|
+
export declare function createInputBuffer(options: {
|
|
39
|
+
windowMs: number;
|
|
40
|
+
}): InputBuffer;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Creates an input buffer for jump buffering, coyote time, hold duration and double taps.
|
|
3
|
+
* @capability input-buffer buffer early presses, coyote time, hold duration and double tap for responsive controls
|
|
4
|
+
*/
|
|
5
|
+
export function createInputBuffer(options) {
|
|
6
|
+
let windowMs = options.windowMs;
|
|
7
|
+
let actions = new Map();
|
|
8
|
+
return {
|
|
9
|
+
press(action, nowMs) {
|
|
10
|
+
const previous = actions.get(action);
|
|
11
|
+
actions.set(action, {
|
|
12
|
+
pressedAt: nowMs,
|
|
13
|
+
previousPressAt: previous === undefined ? null : previous.pressedAt,
|
|
14
|
+
releasedAt: null,
|
|
15
|
+
consumed: false,
|
|
16
|
+
});
|
|
17
|
+
},
|
|
18
|
+
release(action, nowMs) {
|
|
19
|
+
const entry = actions.get(action);
|
|
20
|
+
if (entry !== undefined && entry.releasedAt === null)
|
|
21
|
+
entry.releasedAt = nowMs;
|
|
22
|
+
},
|
|
23
|
+
consume(action, nowMs, window = windowMs) {
|
|
24
|
+
const entry = actions.get(action);
|
|
25
|
+
if (entry === undefined || entry.consumed || nowMs - entry.pressedAt > window)
|
|
26
|
+
return false;
|
|
27
|
+
entry.consumed = true;
|
|
28
|
+
return true;
|
|
29
|
+
},
|
|
30
|
+
coyote(groundedAtMs, nowMs, window) {
|
|
31
|
+
return groundedAtMs !== null && nowMs - groundedAtMs <= window;
|
|
32
|
+
},
|
|
33
|
+
hold(action, nowMs) {
|
|
34
|
+
const entry = actions.get(action);
|
|
35
|
+
if (entry === undefined || entry.releasedAt !== null)
|
|
36
|
+
return 0;
|
|
37
|
+
return Math.max(0, nowMs - entry.pressedAt);
|
|
38
|
+
},
|
|
39
|
+
doubleTap(action, nowMs, gapMs) {
|
|
40
|
+
const entry = actions.get(action);
|
|
41
|
+
if (entry === undefined || entry.previousPressAt === null)
|
|
42
|
+
return false;
|
|
43
|
+
return entry.pressedAt - entry.previousPressAt <= gapMs && nowMs - entry.pressedAt <= gapMs;
|
|
44
|
+
},
|
|
45
|
+
retune(next) {
|
|
46
|
+
windowMs = next.windowMs;
|
|
47
|
+
},
|
|
48
|
+
snapshot() {
|
|
49
|
+
const copy = {};
|
|
50
|
+
for (const [action, entry] of actions)
|
|
51
|
+
copy[action] = { ...entry };
|
|
52
|
+
return { windowMs, actions: copy };
|
|
53
|
+
},
|
|
54
|
+
restore(snapshot) {
|
|
55
|
+
windowMs = snapshot.windowMs;
|
|
56
|
+
actions = new Map(Object.entries(snapshot.actions).map(([action, entry]) => [action, { ...entry }]));
|
|
57
|
+
},
|
|
58
|
+
};
|
|
59
|
+
}
|
|
@@ -9,6 +9,14 @@ export interface LookDeltas {
|
|
|
9
9
|
yaw: number;
|
|
10
10
|
pitch: number;
|
|
11
11
|
}
|
|
12
|
+
/** Plain JSON state of a {@link LookChannel}: the pending pixel deltas and the committed pose. */
|
|
13
|
+
export interface LookChannelState {
|
|
14
|
+
deltaX: number;
|
|
15
|
+
deltaY: number;
|
|
16
|
+
yaw: number;
|
|
17
|
+
pitch: number;
|
|
18
|
+
verticalOffset: number;
|
|
19
|
+
}
|
|
12
20
|
export interface LookChannel {
|
|
13
21
|
accumulate(dx: number, dy: number): void;
|
|
14
22
|
/** Yaw/pitch deltas (radians) for this frame; clears the accumulator. */
|
|
@@ -19,6 +27,8 @@ export interface LookChannel {
|
|
|
19
27
|
readPitch(): number;
|
|
20
28
|
setVerticalOffset(offset: number): void;
|
|
21
29
|
readVerticalOffset(): number;
|
|
30
|
+
snapshot(): LookChannelState;
|
|
31
|
+
restore(next: LookChannelState): void;
|
|
22
32
|
}
|
|
23
33
|
export interface LookChannelOptions {
|
|
24
34
|
/** Radians of rotation per pixel of pointer travel. */
|
|
@@ -40,5 +40,11 @@ export function createLookChannel({ sensitivity, maxVerticalOffset = Infinity })
|
|
|
40
40
|
readVerticalOffset() {
|
|
41
41
|
return verticalOffset;
|
|
42
42
|
},
|
|
43
|
+
snapshot() {
|
|
44
|
+
return { deltaX, deltaY, yaw, pitch, verticalOffset };
|
|
45
|
+
},
|
|
46
|
+
restore(next) {
|
|
47
|
+
({ deltaX, deltaY, yaw, pitch, verticalOffset } = next);
|
|
48
|
+
},
|
|
43
49
|
};
|
|
44
50
|
}
|
package/dist/input/pointer.d.ts
CHANGED
|
@@ -65,5 +65,7 @@ export interface DragCapture {
|
|
|
65
65
|
cancel(): void;
|
|
66
66
|
/** The current drag state, or `null` when idle. */
|
|
67
67
|
state(): DragState | null;
|
|
68
|
+
/** Resumes the drag a {@link DragCapture.state} described (its `origin` and `current`), or goes idle on `null`. */
|
|
69
|
+
restore(next: DragState | null): void;
|
|
68
70
|
}
|
|
69
71
|
export declare function createDragCapture(config?: DragCaptureConfig): DragCapture;
|
package/dist/input/pointer.js
CHANGED
|
@@ -61,5 +61,9 @@ export function createDragCapture(config = {}) {
|
|
|
61
61
|
current = null;
|
|
62
62
|
},
|
|
63
63
|
state: snapshot,
|
|
64
|
+
restore(next) {
|
|
65
|
+
origin = next === null ? null : [next.origin[0], next.origin[1], next.origin[2]];
|
|
66
|
+
current = next === null ? null : [next.current[0], next.current[1], next.current[2]];
|
|
67
|
+
},
|
|
64
68
|
};
|
|
65
69
|
}
|
package/dist/item/affix.d.ts
CHANGED
|
@@ -51,4 +51,10 @@ export interface AffixRoller {
|
|
|
51
51
|
roll(base: ItemBaseDef, rarityId: string, rng: () => number): RolledItem;
|
|
52
52
|
rollRandom(base: ItemBaseDef, rng: () => number): RolledItem;
|
|
53
53
|
}
|
|
54
|
+
/**
|
|
55
|
+
* Rolls randomized gear from a base item: a weighted rarity tier scales base stats and picks
|
|
56
|
+
* prefix/suffix affixes that name and modify the item. Deterministic for a given `rng`.
|
|
57
|
+
*
|
|
58
|
+
* @capability affix-roller roll randomized gear (guns, armor, loot) with rarity tiers, stat scaling and prefix/suffix affixes from a seed
|
|
59
|
+
*/
|
|
54
60
|
export declare function createAffixRoller(config: RollerConfig): AffixRoller;
|
package/dist/item/affix.js
CHANGED
|
@@ -20,6 +20,12 @@ function assertConfig(config) {
|
|
|
20
20
|
throw new Error(`rarity "${tier.id}" weight must be positive`);
|
|
21
21
|
}
|
|
22
22
|
}
|
|
23
|
+
/**
|
|
24
|
+
* Rolls randomized gear from a base item: a weighted rarity tier scales base stats and picks
|
|
25
|
+
* prefix/suffix affixes that name and modify the item. Deterministic for a given `rng`.
|
|
26
|
+
*
|
|
27
|
+
* @capability affix-roller roll randomized gear (guns, armor, loot) with rarity tiers, stat scaling and prefix/suffix affixes from a seed
|
|
28
|
+
*/
|
|
23
29
|
export function createAffixRoller(config) {
|
|
24
30
|
assertConfig(config);
|
|
25
31
|
const poolById = new Map(config.pools.map((p) => [p.id, p]));
|
package/dist/meta/changelog.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
|
|
2
|
-
export declare const VERSION = "0.
|
|
2
|
+
export declare const VERSION = "0.19.0";
|
|
3
3
|
/** One release's migrate steps plus added/changed/removed notes (typed mirror of CHANGELOG.md). */
|
|
4
4
|
export interface ChangelogEntry {
|
|
5
5
|
migrate: readonly string[];
|
package/dist/meta/changelog.js
CHANGED
|
@@ -1,7 +1,196 @@
|
|
|
1
1
|
/** Installed `@jgengine/core` semver — compare against {@link CHANGELOG} keys when migrating. */
|
|
2
|
-
export const VERSION = "0.
|
|
2
|
+
export const VERSION = "0.19.0";
|
|
3
3
|
/** Per-version engine changelog keyed by semver string (e.g. `"0.10.0"`). */
|
|
4
4
|
export const CHANGELOG = {
|
|
5
|
+
"0.19.0": {
|
|
6
|
+
migrate: [
|
|
7
|
+
"Bump lockstep SDK packages to `^0.19.0`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets,navbake}`. CLI `jgengine` is `0.16.0`; `@jgengine/github` is `0.6.0`.",
|
|
8
|
+
"`environment({ structures: building(...) })` buildings and `city` volume buildings now block the player, walking NPCs, `syncWorldColliders` physics backends and `populateNavGridFromSolids` through the new `ctx.world.solids`. Before, they were render-only. If a game re-authored generated buildings as placed scene objects to make them solid, drop those objects. To keep a set passable, pass `building({ solid: false })` or turn off `solid buildings` on the city volume. A mock `GameContext` needs `world.solids` (`createWorldSolids()` from `@jgengine/core/world/worldSolids`).",
|
|
9
|
+
"`createAircraftDynamics` and `AircraftTuning` (`physics/flightDynamics`) are deprecated. They command rotation from rates with pitch clamped, so aircraft can't loop, and mass and stall are fixed. Move to `createRigidAircraft` (`@jgengine/core/physics/aircraftDynamics`): - `mass` → `massKg`, plus an `inertia` tensor (roughly `m·(span/4)²` for roll and `m·(length/4)²` for pitch and yaw). - `fixedWing`: `lift`/`stallSpeed` → left and right wing `surfaces` (area, `liftSlope`, `stallAngle`, trim `incidence`), a tailplane and a fin; `controls.pitch/roll/yaw` → `control` authority on those surfaces plus `controls.<channel>.rate`; `maxThrust` → `engine.maxThrust`; `drag`/`maxSpeed` → `dragArea` and surface `cd0` (top speed is where thrust meets drag); `sideDrag` → the fin. - `rotorcraft`: `hoverThrust` → `rotor.maxThrust`, `rotorResponse` → `rotor.spoolRate`, `groundEffectHeight` → `rotor.radius`; add `rotor.torque` and a `rotor.tail`, and pass `input.collective`. - `vtol` has no one-block equivalent: combine `rotor`, `surfaces` and `engine`, or keep the deprecated model for now. - `controls.stability` → `assists.autoLevel` or `assists.sas`; `afterburner` → `modifiers.thrustScale`; `airbrake` → `modifiers.dragScale`; `groundClearance` → `gear.height`. - Euler `rotation` → step `heading`/`pitch`/`bank` and `orientation`; spawn with `aircraftAttitudeQuaternion(heading, pitch, bank)`. - Tune against `measureFlight` with the `jgengine-world` recipe `flight-feel.md`.",
|
|
10
|
+
"`KinematicVehicleTuning.chassis` and `.steering` are deprecated. Their heading is still commanded from steer, so a held steer at speed spins the car. For handling that matters, move to `createVehicleDynamics` (`@jgengine/core/physics/vehicleDynamics`). Map `massKg`/`engineForce`/`brakeForce`/`tireGrip`/`comHeight`/`trackWidth` to `massKg`, `powertrain: { kind: \"direct\", maxForce, maxPower }` or a gearbox, `brakeForce`, `front.peakGrip`, `comHeight` and `trackWidth`. Map `steering.wheelbase`/`maxAngle`/`highSpeedAngle`/`highSpeedAt` to the same `steering` fields plus a rack `rate`. Then tune against `measureHandling` with the `jgengine-world` recipe `vehicle-feel.md`. Plain `kinematicVehicle`, without those blocks, stays for karts, top-down and bumper cars.",
|
|
11
|
+
"The default third-person orbit camera sits lower and closer (`initialDistance` 9 → 7, `initialHeight` 5.5 → 2.4), so the horizon sits near the top third and the sky is in frame. To keep the old framing, set `camera: { initialDistance: 9, initialHeight: 5.5 }` in `defineGame`.",
|
|
12
|
+
],
|
|
13
|
+
added: [
|
|
14
|
+
"Split-screen viewports (#1686). `defineGame({ viewports })` draws one scissored viewport per joined local seat (`splitViewports`, `resolveViewports`, `viewportPixels` in `@jgengine/core/game/viewports`); extra seats follow their entity with a chase or top-down camera and walk relative to it. `ViewportHuds` (`@jgengine/shell/camera/Viewports`) renders a HUD root per seat. `bun run drive` gains `--param key=value`; `?gamepad=N` synthetic pads now hold Start. Dev demo: `couch`.",
|
|
15
|
+
"`createPerception` takes `maxStimuli` (default 256; the oldest are dropped) and adds `forget(observerId, targetId?)`. Stimuli insert in time order instead of re-sorting on every push.",
|
|
16
|
+
"Decision graphs (`@jgengine/core/ai/decisionGraph`) gain `invert`, `cooldown`, `wait` and `random` nodes and `memory` selectors/sequences that resume at their running child. `createDecisionGraphRuntime(graph, actions, { onAbort, rng })` calls `onAbort` when a branch pre-empts a running action, and `random` draws from the injected `rng` only. `running()` names the running action; snapshots now carry timers, memory and cooldowns (older `{ runningPath }` saves still restore).",
|
|
17
|
+
"Decision graph behaviors keep a persistent per-entity blackboard: seed it with `blackboard`, write facts from perception or game systems through `behaviorControl(ctx).blackboard(id)`, and it round-trips through `serialize`/`restore`. `thinkInterval` ticks the graph every N seconds, staggered across siblings, with the elapsed time as `dt`. `registerBehaviorActions(id, actions, { onAbort })`; `random` nodes draw from `ctx.rng`.",
|
|
18
|
+
"Editor Animation workspace (#1683). The rail button opens the bottom dock's new Graph mode for the selected placement, showing layers, states and transitions. - A scrub slider replays the graph runtime; parameter sliders and trigger buttons record presses on the timeline. - The rig at the camera focus is posed from the runtime's output. - Crossfade edits and Store graph save the graph to `marker.meta.animation.graph` as undoable edits. - Supporting APIs: `parseAnimGraph` (`@jgengine/core/anim/animGraph`) validates stored graphs, `animGraphFromConfig` (`@jgengine/core/anim/locomotionGraph`) resolves the graph a config plays, and `createGraphPose` (`@jgengine/shell/render/useModelAnimation`) poses a rig from graph output.",
|
|
19
|
+
"`npx jgengine recipe` adds `world-drops`, `click-target`, `ability-bar`, `hitscan-weapon`, `rolled-gear` and `enter-vehicle`, each type-checked against the SDK.",
|
|
20
|
+
"`SkinnedInstances` (`@jgengine/shell/render/SkinnedInstances`) renders hundreds of animated copies of one rig, each with its own clip, time offset and speed, in one instanced draw per material. Clips are baked once to a bone-matrix texture and sampled on the GPU. Shadows follow the pose. The layout and frame lookup are pure: `planBoneTexture` and `sampleBoneTexture` (`@jgengine/core/anim/boneTexture`). Dev runner demo: `bun run drive skinned-crowd`, with `skinned-crowd-mixers` as the one-mixer-per-character baseline (#1683).",
|
|
21
|
+
"Continuous haptics (#1769). `ctx.input.haptics(userId)` returns named rumble channels (`createHapticChannels`, `@jgengine/core/input/haptics`): `set` a level per tick, `pulse` a fading hit, mixed by priority so an impact ducks the engine hum. The shell drives each local seat's pad with refreshed short effects and resets it when silent; channels are local only. The `handling` demo feeds engine, road and impact channels.",
|
|
22
|
+
"Local seats for couch co-op (#1686). `defineGame({ localPlayers: { maxSlots, claimPrimary? } })` and `localPlayers(ctx)` (`@jgengine/core/runtime/localPlayers`): pads hot-join their own seat on a button press, the shell spawns each through `loop.onNewPlayer(ctx, { userId, isNew: true })`, routes that pad to the seat's own `InputSnapshot` (`local(slotId).input`), and moves its entity with the walk controller. The seat table has `snapshot`/`restore`/`retune`. `?gamepad=N` injects N synthetic pads for headless checks.",
|
|
23
|
+
"Action contexts rebind live input (#1769). `ActionContextStack` gains `subscribe`, `version`, `ids` and `activeAxes()`; a context may carry `axes` and serializable `shaping`. The shell re-binds keyboard, touch and gamepad when a context is pushed or popped, without remounting: keys held across the swap keep counting toward their new action, and context-only actions reach `ctx.input`. `ActionStateTracker` gains `rebind` and `actions`. The `handling` demo drives through a `driving` context (F parks it), with pad codes on every action. Recipe: `jgengine-world/recipes/controls.md`.",
|
|
24
|
+
"`createNavMeshQuery` (`@jgengine/core/nav/navMesh`) caches a nav mesh's adjacency and a uniform polygon grid, then answers `findPath`, `findPolygon`, `closestPoint` and `raycast` without full-mesh scans. Paths use a binary-heap A* priced by `areaCosts` per `NavMeshData.areas` id (non-finite blocks an area), stop at `maxNodes` with a `partial` route, and are straightened by a portal funnel. `retune`/`snapshot`/`restore` cover the costs, so a door or hazard can close an area during play. `NavMeshLink` gains optional `start`/`end` landing points.",
|
|
25
|
+
"`createWeaponFeedbackSignals` (`@jgengine/core/combat/weaponFeedback`) turns `WeaponHandling.fire()` shots, their recoil kick and impacts into one-tick pulses (`shot`, `kick`, `impact`) plus the frame's `recoil`, `cameraKick`, `spread` and `ads`, keyed for `createFeedbackMixer` routes to camera shake, audio and rumble. It has `snapshot`/`restore`/`reset`.",
|
|
26
|
+
"`measureWeapon` now reports `spreadByShot` and `shotsToKill`, fires in a given `stance` (e.g. ADS), and computes time-to-kill at a `range`. Each projectile (`pellets` per shot) counts by the share of its spread cone the `targetRadius` covers, scaled by the game's `damageAt(range)` falloff. The recipe is `jgengine-combat` `weapon-feel.md`.",
|
|
27
|
+
"`defineGame({ gamepad: { deadzone, curve, triggerDeadzone } })` sets pad feel for the shell's gamepad poll (#1769). Triggers now go through `triggerDeadzone` and the same curve as the sticks, and a trigger resting inside its deadzone no longer holds its action. `resolveGamepadFrame` takes a browser `Gamepad` directly and an optional reusable `out` frame, and the shell poll no longer allocates per frame. `gamepadFeelOptions` resolves the config with the old defaults (axial `0.12`/`0.95`, linear).",
|
|
28
|
+
"`measureFlight` (`@jgengine/core/physics/handlingProbe`) flies a `createRigidAircraft` through deterministic autopilot scenarios. It reports roll rate (deg/s), sustained turn rate, stall speed, climb rate, time to 90% throttle response and hands-off hover drift, so a flight feel target can be a test.",
|
|
29
|
+
"`RigidAircraftTuning.assists`: flight assists on the same actuators as the pilot, so full stick stays the pilot's. - `sas` per axis (`0..1`) damps rates and holds attitude or heading hands-off with a learned trim. On a helicopter it holds heading against rotor torque. - `autoLevel`, `maxAngleOfAttack` and `maxG` limiters, and `hoverHold` for helicopters. - A `policy(context, command)` callback has the last word. - The step adds `command` and `limited`. `RigidAircraftState` gains the hold, trim and drift integrals. - T toggles assists in `bun run drive flight` and `flight-heli`.",
|
|
30
|
+
"`RigidAircraftTuning.motor` makes `createRigidAircraft` a rocket. - Thrust follows `thrustCurve` over burn time. `propellantKg` leaves at `massFlow`, so mass and inertia fall and acceleration rises through the burn. The nozzle `gimbal` steers on the pitch and yaw inputs. - To stage, `retune` to the next stage: a new `motor` loads its propellant. - The step reports `massKg` and `motor` telemetry. `RigidAircraftState` gains `burnTime` and `propellantKg`. - Dev runner demo: `bun run drive flight-rocket`.",
|
|
31
|
+
"Jump shape on `movement.feel`: `jumpCutFactor` (releasing jump early cuts the rise, so a tap hops), `apexGravityScale`/`apexSpeed` (hang at the peak), `fallGravityScale` (drop faster than you rose) and `landingRecoveryMs`/`landingSpeedScale` (a short slowdown and jump lockout after landing). All default to no change. `measureMovement` adds `tapJumpHeight`. Dev runner demos `walk-floaty` and `walk-weighty` differ only in these fields (#1772).",
|
|
32
|
+
"`RigidAircraftTuning.rotor` turns `createRigidAircraft` into a helicopter: `input.collective` sets thrust, throttle spools the rotor, cyclic tilts the disc, and rotor drag torque yaws the body unless the `tail` rotor (pedal) cancels it. Thrust adds translational lift, drops while climbing through the disc, and grows in ground effect. The step reports `rotor` speed, thrust, torque, ground effect and translational lift. Dev runner demo: `bun run drive flight-heli`.",
|
|
33
|
+
"`snapshot()`/`restore(next)` on `createGlideModel`, `createLodScheduler`, `createSimulationCuller` and `createSpatialIndex`, and `restore(state)` on `createGrappleSwing`, so gliding, rope swings, LOD throttling and the spatial hash save, load and replay bit-exactly (#1775).",
|
|
34
|
+
"`snapshot()`/`restore(next)` on `createThreatTable`, `createAssistNetwork`, `createReputationLedger`, `createConcealmentSensor`, `createFreezeMonitor` and `createRecordingBuffer`, so AI aggro, faction standing and sensor dwell save, load and replay bit-exactly (#1775).",
|
|
35
|
+
"Engine audio depth (#1770): - `ctx.game.audio.setLoop` takes `lowpass` and `highpass` cutoffs in Hz, ramped like rate and gain. - `SoundDef.doppler` pitches a retained loop by listener and emitter velocity; pass the emitter's with `setLoop({ velocity })`. The shell tracks the listener's from the camera. `dopplerRate` (`@jgengine/core/audio/doppler`) is the pure formula. - `createEngineLayers` (`@jgengine/core/audio/engineLayers`): N rpm-keyed engine samples crossfaded at equal power, with on-load and off-load sets picked by smoothed `engineLoad`. `play(ctx.game.audio, ...)` drives one retained loop per layer. It has `snapshot`/`restore`/`retune`.",
|
|
36
|
+
"`snapshot()`/`restore(next)` on `createFootprintGrid`, `createWallDrawTool` and `createTerraformBrush`, so build-grid claims, wall drafts and brush settings save, load and replay bit-exactly (#1775).",
|
|
37
|
+
"`createInputBuffer` (`@jgengine/core/input/inputBuffer`): buffered presses, coyote time, hold duration and double tap, with `snapshot`/`restore`. The walk controller uses it for `movement.feel.jumpBufferMs` (a jump pressed just before landing fires on landing) and `movement.feel.coyoteMs` (a jump just after walking off a ledge still fires). Both default to 0, so walking is unchanged until a game sets them (#1686, #1772).",
|
|
38
|
+
"`snapshot()`/`restore(next)` on `createLootRegistry`, `createToastQueue`, `createVfxInstanceStore`, `createRoundState` and `createLookChannel`, and `restore(state)` on `createDragCapture`, so loot tables, toasts, retained VFX, match rounds and look/drag input save, load and replay bit-exactly. `RoundSnapshot` gains `pendingWinner` (#1775).",
|
|
39
|
+
"`measureMovement` (`@jgengine/core/movement/movementProbe`) reports a walking character's feel from the same `movement.feel` and `physics` fields a game sets: time to top speed, stop distance, turn-around time, jump height, apex and air time, and air-control reach. Deterministic, for tests (#1772).",
|
|
40
|
+
"Chase camera depth (`camera.chase`, #1770): - `fov.response` eases the speed FOV, and speed now comes from the followed entity's sim `velocity` when it publishes one, so FOV no longer jitters when render and sim rates differ. - `distanceBySpeed`, `pitchFollow` (boom follows body pitch on slopes), `lookBackAction` (hold to look behind), and `collision` (the boom stops short of objects, terrain and walls via `ctx.scene.raycast`; on by default). - `ctx.camera.kickFov(degrees)` queues a decaying FOV punch, tuned by `chase.fovKick`. - `ChaseCameraTuning` accepts `view` and the new fields; `nextChaseView` (`@jgengine/core/runtime/cameraDirector`) cycles chase, hood and cockpit.",
|
|
41
|
+
"`createRigidAircraft` (`@jgengine/core/physics/aircraftDynamics`): a quaternion rigid-body aircraft with an inertia tensor, lifting surfaces (area, lift slope, stall, post-stall lift, drag polar) placed on the body, rate-limited control surfaces, an engine with spool and optional gear. Loops, rolls and stalls come from the surfaces. Same contract as `vehicleDynamics`: fixed substeps, `snapshot`/`restore`/`retune`, per-tick modifiers, and telemetry for AoA, sideslip, g-load, airspeed and stall. Dev runner demo: `bun run drive flight`.",
|
|
42
|
+
"Graphics settings a player can feel: an Ultra tier, a frame-rate limit (V-Sync or 30/60/120/144 cap), texture filtering (anisotropy, now applied to every loaded texture), and an FPS counter. `GraphicsProfile` gains `anisotropy`; a game that overrides tiers with `defineGame({ graphics })` can set it per tier.",
|
|
43
|
+
"`postProcessing.stylize` (`outline`, `bands`, `pixelSize`) and two new looks, `look: \"comic\"` (ink outlines + cel bands, Borderlands-style) and `look: \"retro\"` (pixelated, posterized). Same models, different art style.",
|
|
44
|
+
"`PhysicsBackend.applyForce(handle, force, point?)` and `applyTorque(handle, torque)` act over the next step, and `BodyDesc.linearDamping`/`angularDamping` set damping per body. Both are implemented on `PhysicsWorld` (translation only) and Rapier, and covered by the conformance suite. `createVehicleBackendLink` (`@jgengine/core/physics/vehicleBackendLink`) collides a vehicle sim with a backend world: a kinematic chassis shoves props, `clampMove` stops the car at walls and slides it along them, and `lastHit()` reports the impact.",
|
|
45
|
+
"`VehicleAssistTuning.antiWheelie` and `.antiStoppie` (`0..1`) cap drive and braking below the pitch-over limit (`g·b/h` forward, `g·a/h` braking). With `suspension` they also back off as the front or rear goes light. They report through `tractionLimited` and `absActive`.",
|
|
46
|
+
"Motorcycles on `createVehicleDynamics`: a `VehicleDynamicsTuning.lean` block (`maxLean`, `leanRate`, `countersteer`, `directSteerBelow`). - Steer asks for a lean, capped at what grip can hold. The bar steers the balanced turn that lean needs, with a rider trim for tire slip and a countersteer tip-in. - The bar steers directly at walking pace, and there is no lateral load transfer. - The step adds `lean` (balance lean, `bodyRoll = -lean`) and `wheelie`/`stoppie` for all vehicles. - `measureLean` (`physics/handlingProbe`) reports steady lean, time to lean and counter-lean. - Dev runner demo: `bun run drive handling-bike`.",
|
|
47
|
+
"`measureCourse` (`@jgengine/core/physics/handlingProbe`): understeer gradient (deg/g), skidpad g at a set radius, and the fastest clean slalom, from simple deterministic drivers. `measureHandling` adds `handbrakeRecoverySeconds`.",
|
|
48
|
+
"`createWeaponHandling` (`@jgengine/core/combat/weaponHandling`): weapon feel as serializable state. - Recoil: a per-shot `pattern` or flat `pitch`, a random cone from an injected `random`, a `cameraShare` split between view punch and aim, `recoverRate`/`recoverDelay`, and `adsScale`. - Spread: `base`, `perShot` bloom, `max`, recovery, and stance/ADS multipliers. - ADS time, plus `snapshot`/`restore`/`retune`/`reset`. - `measureWeapon` reports first and tenth shot spread, burst climb, reset time, ADS time and time-to-kill.",
|
|
49
|
+
"`createWeaponPresentation` (`@jgengine/core/combat/weaponPresentation`): per-weapon viewmodel hip and ADS offsets, viewmodel FOV, ADS zoom, sway from look speed, bob from the movement probe, and recoil kick, from a `createWeaponHandling` frame. `camera.weapon` hands that frame to the rigs: first person poses its viewmodel and adds the recoil to the look, and `shoulder` takes its ADS blend and recoil from the same frame. Dev demos: `weapon-handling`, `weapon-handling-shoulder`.",
|
|
50
|
+
"`createFeedbackMixer` (`@jgengine/core/vfx/feedbackMixer`): declares how sim telemetry drives presentation. Routes map a signal through a piecewise `curve` with `attack`/`release` smoothing onto a named target, targets combine by `sum` or `max` over a `base`, and threshold `events` with cooldowns fire one-shots. No allocation per update, with `snapshot`/`restore`/`retune`/`reset`. `sampleFeedbackCurve` exposes the curve.",
|
|
51
|
+
"`snapshotPlayerMovement(ctx, userId)` / `restorePlayerMovement(ctx, userId, snapshot)` (`@jgengine/core/movement/playerMovement`): per-player walk state (heading, velocities, jump latch, capsule controller) as a plain JSON copy, so walking characters predict and roll back like vehicles. A snapshot restored before the capsule exists is applied when it is created.",
|
|
52
|
+
"`PlayerMovementConfig.feel` (`MovementFeelConfig`): `groundAcceleration`, `airAcceleration`, `groundFriction` (response rates, 1/s), `runMultiplier` and `crouchMultiplier` for the built-in walk controller and the voxel controller. They were hard-coded before, so every game walked the same. Omitted fields keep today's defaults.",
|
|
53
|
+
"`createAxisShaper` (`@jgengine/core/input/axisShaper`): per-axis response shaping between `ctx.input.axis(...)` and a sim. Keys and sticks get separate profiles (deadzone, saturation, curve, rise/fall/return rates), plus an optional `scale(signal)` callback for speed-sensitive steering, with `snapshot`/`restore`/`retune`. `analogAxes(bindings, ctx.input.analog())` picks the profile per frame, and `shapeAxisValue` exposes the deadzone and curve math. The `handling` dev demo uses it for keyboard steering that eases in and self-centres.",
|
|
54
|
+
"`VehicleDynamicsTuning.jump` (`speed`, `count`, `window`) with `car.jump()`, and `VehicleDynamicsTuning.air` (`pitchAccel`/`yawAccel`/`rollAccel`, `damping`, `maxRate`) with `modifiers.air` for air control, plus `applyAngularImpulse` for dodges and flips. The body angles use the true geometry of the tilt rather than a small-angle guess. `measureAir` (`physics/handlingProbe`) reports apex height and time, double-jump height and air rotation rates. Held brakes and the handbrake now grip statically at rest, so a parked car stays put on a slope.",
|
|
55
|
+
"`VehicleDynamicsTuning.suspension` (`springRate`, `damperRate`, `reboundRate`, `travel`, `rideHeight`, `antiRoll`) and a `groundHeight` option on `createVehicleDynamics`. The car rides four sprung corners over terrain: heave, pitch and roll come from real springs, axle loads come from spring forces, slopes pull downhill, and the car can leave the ground and land. The step adds `airborne`, `landingSpeed`, `wheelLoads` and a terrain-relative `airOffset`, and `applyImpulse` takes a vertical `dvy`. `measureRide` (`physics/handlingProbe`) reports settle time, heave overshoot, brake dive, roll gradient and landing bounces. Without the block, behaviour is unchanged.",
|
|
56
|
+
"`createKinematicVehicle` and `createAircraftDynamics` gain `snapshot()`/`restore()` (`KinematicVehicleState`, `AircraftDynamicsState`) for prediction, rollback and replay. `createAircraftDynamics` also gains `retune()`. The stateful-primitive ratchet no longer misses factories with a default parameter like `options = {}`, so 20 more handles without state-out are now tracked in its baseline.",
|
|
57
|
+
"Boat sim `createBoatDynamics` (`@jgengine/core/physics/boatDynamics`) on the same tick/snapshot/restore/retune contract as the car. Drag climbs toward hull speed and a planing hull sheds it; the keel resists sliding sideways; a `rudder` needs water flowing past it (speed or prop wash) while an `outboard` swings its own thrust and turns at rest. `waterHeight(x, z, t)` for waves and `current(x, z)` for drift. `measureHandling` and `tickDrivableVehicle` take it as-is.",
|
|
58
|
+
"Force-based car sim `createVehicleDynamics` (`@jgengine/core/physics/vehicleDynamics`): a two-axle model where yaw comes from tire forces, so understeer, oversteer, lift-off tuck, power slides, trail-braking and handbrake turns come out of physical knobs (mass, weight split, CoM height, per-axle Pacejka-style tires, roll-stiffness split, drive split, gearbox or direct drive, aero, ABS/TC/stability, caster self-align). Fixed substeps, `snapshot`/`restore`, `retune`, `thrust` modifier. `measureHandling` (`@jgengine/core/physics/handlingProbe`) drives any vehicle sim through fixed scenarios and returns 0–100, top speed, braking distance, lateral g, turn-in latency, sideslip, spin and lift-off numbers a test can assert. `tickDrivableVehicle` now accepts either sim. `drive --key` takes chords (`KeyW+KeyD:2000`). Dev runner demo: `bun run drive handling`.",
|
|
59
|
+
"`jgengine create --2d` scaffolds a board game with no 3D scene, editor document, or models: a `board` world, `presentation: \"hud\"`, a store slot holding a `CellGrid`, arrow/WASD/Space bound to commands with held-key repeat, and a GameUI that draws the grid. `--3d` names the default. 3D-only flags (`--player`, `--ground`, `--scene`, `--from-scene`, `--shape`, `--no-world`, `--no-editor`) are refused with `--2d` instead of ignored.",
|
|
60
|
+
"`@jgengine/core/puzzle/cellGrid` is documented public API (`cell-grid` capability): an immutable 2D board with `withCell`/`withCells`, `fullRows`/`clearRows`, `collapseColumns`, and `findRuns`. It was importable but tagged internal, so agents never found it.",
|
|
61
|
+
"HUD-only games (`presentation: \"hud\"`) set `data-jg-capture=\"ready\"` after their first painted frame unless a capture host owns the flag, so `shoot` and `drive` no longer time out waiting for a `<canvas>` that never exists.",
|
|
62
|
+
"The built-in Graphics settings tab now exposes render scale plus ambient occlusion, bloom, depth-of-field, and SMAA toggles alongside the quality tier; picking a tier re-applies its defaults. `@jgengine/core/settings/graphicsSettings` (`readGraphicsSettings`, `applyGraphicsQuality`, `GRAPHICS_POST_STAGES`) resolves the stored choices onto the `GraphicsProfile` the shell renders with, and `SETTING_IDS` gains `graphics.renderScale` and `graphics.post.*`. Every game gets the rows in its existing settings menu with no wiring (#1688).",
|
|
63
|
+
"Model triangle budget: the shared GLB loader counts each model's triangles as it lands and warns once per URL when one exceeds `DEFAULT_MODEL_TRIANGLE_BUDGET` (100k; retune with `setModelTriangleBudget`). `heavyModels()` lists the offenders and `debug_snapshot` reports them under `probes.heavyModels`, so a heavy prop shows up in evidence instead of only as a slow frame.",
|
|
64
|
+
"`bun run drive <game> --state <name>` boots into a `capture.states` entry the same way `shoot --state` does, so a staged scene can be measured with `--rpc debug_snapshot` or probed instead of clicked together by hand.",
|
|
65
|
+
"Building palettes take textured surfaces: a `BuildingPalette` part (and `BuildingKitPart.material`) may be `{ color?, maps?, repeat?, roughness?, metalness? }` with `maps` straight from `buildMaterialCatalog(...).resolve(id)!.maps`, not only a hex colour. Generated facade boxes and kit models tile the PBR maps per slot; unbound kinds keep their flat colour. `buildingSurfaceColor` / `resolveBuildingSurface` read either form.",
|
|
66
|
+
"`CommandDef.parse` and `defineCommand` (`@jgengine/core/runtime/commandRunner`). `parse` turns the untrusted wire value into typed input once. The runner refuses a `null` result with `MALFORMED_COMMAND_INPUT_REASON` before `scope`, `validate` or `apply` run, so those stages only see parsed input.",
|
|
67
|
+
"Bounded input readers in `@jgengine/core/runtime/commandInput`: `readInputNumber` (always finite, optional range and integer), `readInputString`, `readInputOneOf`, `readInputPoint2`, `readInputPoint3`, `readInputArray` and `isInputRecord`.",
|
|
68
|
+
"`helpers.ensureJoined(ctx, { gameId, userId, serverId? })` (`@jgengine/convex/server`) joins a trusted actor through the `joinServer` path and returns its outcome, so host mutations can run commands for users with no live session. It is idempotent and pins no client session.",
|
|
69
|
+
"`createTransportSessionId()` (`@jgengine/core/runtime/transport`) makes a fresh id for one client session's join/leave pair.",
|
|
70
|
+
"`CommandDef.access` (`@jgengine/core/runtime/commandRunner`). `\"server\"` marks a command whose input carries host-decided facts. The public Convex `runCommand` mutation and WebSocket client messages refuse it with `SERVER_ONLY_COMMAND_REASON`, while `helpers.runCommand` and `host.runCommand({ trusted: true })` still run it. `GameRuntime.commandAccess(name)` reports it.",
|
|
71
|
+
],
|
|
72
|
+
changed: [
|
|
73
|
+
"Graph playback no longer resets the rig's first bone to its bind position every frame. On rigs whose first bone is the hips, that flattened the walk bob in every clip. The root bone is pinned only while a `rootMotion` state is current (the new `AnimGraphOutput.rootMotion` flag), and only horizontally. Root travel now moves the entity along its facing and at the model's scale instead of in rig space, and its vertical part stays in the clip. The shell helper is `takeRootMotion` (#1683).",
|
|
74
|
+
"Perception judges each sound or damage stimulus once per observer, from where the observer stood when it first observed after the stimulus. Before, every `observe` re-scored past sounds from the observer's current position, so a guard walking toward an old noise grew more certain of it. `PerceptionSnapshot` gains `heard` and `nextSeq`; older saves re-judge their stimuli once.",
|
|
75
|
+
"Perception `loudness` now scales how far a sound carries (`hearingRange × loudness`) instead of only its confidence, so a gunshot is heard past normal hearing range and a footstep only close by. Loudness `1` behaves as before.",
|
|
76
|
+
"`npx jgengine find` now surfaces seams that had no capability row: `vehicle-seats`, `drivable-vehicle`, `kinematic-vehicle`, `affix-roller`, `hitscan-shot`, `loot-beams`, `world-item-pickup`, `on-death-drops`, `pointer-commands`, `current-target`, `fire-input`, `global-cooldown`. `PointerConfig.moveCommand` JSDoc now says it receives the clicked entity (click-to-target).",
|
|
77
|
+
"`npx jgengine find` matches whole stemmed words (`aim` no longer hits `claim`), drops stopwords, tries multi-word queries joined (`pick up` → `pickup`), returns the closest partial matches instead of \"no match\", and searches CLI recipes and skill recipe docs.",
|
|
78
|
+
"`raycastNav` and `NavMeshQuery.raycast` step into the polygon a segment enters when it starts on or crosses a shared vertex, so a funnel path's corner-to-corner legs raycast clear.",
|
|
79
|
+
"`bakeNavMesh` (`@jgengine/navbake`) now voxelizes geometry with recast: the walkable surface is eroded by `agentRadius`, cut under obstacles lower than `agentHeight`, and split at `maxSlope` and `maxClimb`, so a wall standing on a floor leaves a hole the path goes around. Call `await initNavBake()` once before baking; `navBakeReady()` reports it. Walkable triangles must wind counter-clockwise seen from above. Optional `cellSize`/`cellHeight` trade precision for speed. The editor warms the baker when a host starts.",
|
|
80
|
+
"`findPath`, `closestPoint` and `raycastNav` on `NavMeshData` share the cached query: paths bend only at portal corners, stacked floors resolve by height, and raycasts walk polygon edges instead of sampling.",
|
|
81
|
+
"Mouse look now asks for raw, unaccelerated mouse deltas (`requestPointerLock({ unadjustedMovement: true })`) in `createMouseLookTracker`, the first-person camera and the shoulder rig, and falls back to a plain lock where that is unsupported. The helper is `requestRawPointerLock` (`@jgengine/shell/input/pointerLock`).",
|
|
82
|
+
"Foot IK (`ModelConfig.ik`) keeps each clip's swing lift and moves feet by their ground's height relative to the model origin, instead of pinning every ankle to the ground. It lowers the pelvis so the lower leg can reach, keeps soles above the ground where a clip dips below it, bends toward the animated knee, tilts planted feet to the ground normal and fades out while airborne. `ik: \"auto\"` finds the legs by bone name, and `pelvis`, `maxAdjust` and `alignToGround` are optional. Probes hit terrain and blocking objects. The math is `placeFeet` and `inferLegChains` (`@jgengine/core/anim/footPlacement`) (#1683, #1758).",
|
|
83
|
+
"Sky and fog colors render as authored under any tone mapping. The sky dome and sky fog treat their colors as on-screen swatches and pre-invert the renderer's tone-mapping curve, so a `#cfe4f5` horizon no longer shows as grey `#a6b3c2` under the default `neutral` look (AgX). The dome now runs three's tone-mapping and color-space chunks, so it matches with or without a post chain. Looks with bloom (`cinematic`, `photoreal`) get a brighter sky that blooms more near the sun; raise `bloom.threshold` if that is too much.",
|
|
84
|
+
"The sky's sun shadows lose their stair-step: one 2048 map over a 70 m box that leads the camera (was 180 m centered on it), texel-snapped, with a 6-texel PCF radius. Time-of-day, biome and `DayNightSky` suns use the same rig instead of three's default 10 m box.",
|
|
85
|
+
"`jgengine create` seeds the sun lower and ahead-left of the spawn (bearing 128°, 20° up), so its glow is in the first frame.",
|
|
86
|
+
"Scaffolded `scripts/shoot.mjs` and `scripts/drive.mjs` refuse a viewport that stays one flat color for 10 s instead of saving it; nothing is written and the command fails.",
|
|
87
|
+
"`jgengine create` defaults to a 3D terrain world instead of an infinite flat slab. `editor.scene.json` seeds rolling hills as a terrain sculpt (deterministic per game id, flat around the spawn) and a bright `day` sky with a sun bearing and distance fog; `src/world.ts` renders them through `environment()` and `environmentContentFromDocument` and holds only the detail-shaded ground palette. Reshape the hills in the editor. `--ground flat` keeps the old `place()` slab (#1762).",
|
|
88
|
+
"`GameRuntimeTransport.joinServer`/`leaveServer` take an optional `sessionId`. The Convex `joinServer`/`leaveServer` mutations and the WebSocket `join`/`leave` messages accept it (at most 128 characters). `useServerSession` and the shell's multiplayer sync send a fresh one per mount.",
|
|
89
|
+
"Shared-topology `jgServerMembers` rows keep up to 16 live `sessionIds`. A leave with a `sessionId` ends only that session; the user leaves once none remain. A leave without one, or on a row without session ids, still leaves outright. Rooms topology is unchanged.",
|
|
90
|
+
"The WebSocket router tracks sessions per connection and only leaves the host when no other connection of the same user holds the server.",
|
|
91
|
+
"`helpers.resetPlayerProfile` (`@jgengine/convex/server`) now resets a player who has left the server but still has a profile, instead of throwing, so scheduled wipes and insolvency resets no longer abort a whole batch. A user with neither membership nor a profile still throws.",
|
|
92
|
+
"The `combat-loop` recipe drained health with `stats.delta`, which never reaches the death pipeline, so copied enemies never died. It now damages through `ctx.scene.entity.effect` and a catalog `receive` map. The `loot` recipe now registers on `ctx.game.loot` and rolls from catalog `onDeath`. `KinematicVehicleTuning.camera` and `DRIVE_AXIS_BINDINGS` JSDoc now state what they actually do.",
|
|
93
|
+
"Editor RPC `add_marker` and `set_marker` accept `catalogId`. It used to be dropped while the call still returned `ok`, so a headlessly authored `mob`/`boss` marker spawned nothing.",
|
|
94
|
+
"Agent bridge `editor_summon` now resolves once the editor has mounted (up to 15 s). It used to return immediately, so editor verbs later in the same `drive` batch failed with \"no live editor\".",
|
|
95
|
+
"Catalog `onDeath: { dropMode: \"world\" }` scattered drops with `Math.random`, so kill loot landed in different spots on replay and on each peer. The runtime now passes the world's seeded `ctx.rng` to the scatter.",
|
|
96
|
+
"The chase camera no longer falls behind fast vehicles. Its spring eased the camera's world position toward a moving target, so the lag grew with speed (about 9 m extra at 220 km/h); it now eases the boom offset from the target (#1770).",
|
|
97
|
+
"A `createVehicleDynamics` bike with both `lean` and `suspension` launched itself the moment it leaned: the springs read the lean as body roll across the narrow track. Springs now ignore lean, and lateral g keeps the same lag it has without springs.",
|
|
98
|
+
"`@jgengine/rapier`: a body created with `mass` now weighs exactly that. It used to add `mass` on top of the collider's density-derived mass, so a 2 kg sphere of radius 0.5 weighed 2.52 kg, and every force, impulse and joint on a massed body was off by the collider's volume.",
|
|
99
|
+
"`createVehicleDynamics` with `suspension` spawned and reset level even on a slope. The wheels started buried, so the springs fired the car into the air and it tumbled. It now fits pitch, roll and height to the terrain under its four wheels.",
|
|
100
|
+
"A sky authored in the scene document (`editor.scene.json` `environment`, written by the editor lighting workspace) now renders in `place()` worlds. `defineGame` never passed `skyFromDocument(editorLayers)` to the renderer, so those edits were silently dropped. A game's own `backdrop.sky` still wins, and a document holding only point lights leaves the world sky alone (#1762).",
|
|
101
|
+
"`jgengine create --scene starter` placed its props and goal behind the spawn, so the first frame showed empty ground and W walked away from the goal. The starter scene now sits ahead of the default +Z facing.",
|
|
102
|
+
"A standalone game's `bun run desktop` failed with `jgengine: command not found` because the CLI is not a dependency; the script now runs `npx jgengine desktop`.",
|
|
103
|
+
"`@jgengine/navbake` joins the published lockstep set. `@jgengine/editor@0.18.1` depended on it while nothing published it, so `bun install` on the released SDK failed. `check-release-set` (in `gate`, `check-types`, and the publish workflow) now fails when a published package depends on a workspace package outside the publish list or out of dependency order, or when the publish, version-bump, and changelog package lists disagree.",
|
|
104
|
+
"Publish has been red since that change because a CLI packaging test still hardcoded the old publish list, so navbake never reached npm and `npx jgengine create` scaffolds failed to install. The test now defers the order to `check-release-set`.",
|
|
105
|
+
"`jgengine-verify` was silently skipped by the skills installer for every new game: its `description` held an unquoted `: `, which is invalid YAML. `check-skills` now parses frontmatter as YAML and requires `name` to match the folder.",
|
|
106
|
+
"`jgengine create --help` printed usage instead of scaffolding a game named after the first argument, and `create` rejects unknown flags rather than ignoring them. The usage lists `--player`, `--ground`, and `--scene`.",
|
|
107
|
+
"`pickModel` / `resolveModelPlan` warn once in dev when a `ModelPick.fallbackModel` is not in the asset catalog (a typo or an undeclared pack), instead of silently shipping a fallback that can never take over.",
|
|
108
|
+
"Volumetric clouds no longer cut through distant buildings: the proxy dome writes per-fragment depth at the raymarch's first cloud hit, so geometry between the camera and the cloud slab occludes the clouds instead of the dome radius deciding. `STUDIO_STAGE_POST` ambient occlusion is softer (radius 1.1, intensity 1.5) so tower silhouettes stop streaking.",
|
|
109
|
+
"Separate SDK publication validation from external Games quality checks while retaining the full local and main-branch gates.",
|
|
110
|
+
"Correct foot-IK endpoint recomputation after hip rotation; verify reachable targets and clamping without changing bone lengths.",
|
|
111
|
+
"Restore release CI prerequisites and make catalog-loader tests independent of the external Games checkout.",
|
|
112
|
+
"Closing a second tab, or a join that resolved after a remount, no longer evicts a user whose live tab still shows them joined, which made every later command fail with \"Not a member of this server\".",
|
|
113
|
+
],
|
|
114
|
+
removed: [],
|
|
115
|
+
},
|
|
116
|
+
"0.18.1": {
|
|
117
|
+
migrate: [
|
|
118
|
+
"Bump lockstep SDK packages to `^0.18.1`: `@jgengine/{core,rapier,react,ws,node,sql,convex,shell,editor,assets}`. CLI `jgengine` is `0.15.1`; `@jgengine/github` is `0.5.1`.",
|
|
119
|
+
"Declare `topology: \"shared\"` on runtime definitions. Shared Convex hosts migrate the bounded legacy roster into indexed membership and per-player session rows; use `getServerCapacity` for stable lobby subscriptions.",
|
|
120
|
+
"Put every command's read scope on its definition. Omitted scopes now load the actor and no chunks; explicitly request whole-world reads only where needed. Trusted host mutations can compose writes through `helpers.runCommand`.",
|
|
121
|
+
"Handle typed join outcomes (`ok` and `reason`) or use `useServerSession` and `JoinGate`. Pass runtimes to `jgengineCronSpecs`; tick registration requires an actual tick hook.",
|
|
122
|
+
"Declare currency precision with `decimals` and remove game-side cent conversions. Public amounts stay in major units and wallet writes round through integer minor units.",
|
|
123
|
+
"Replace custom presence, chat, territory and tick batching with `createWorldPresenceStore`, chat helpers, chunk territory, and `forEachOnlinePlayer`. Legacy claim backfills must finish before removing their old schema.",
|
|
124
|
+
"`AudioEngine.setListenerPose` now accepts `{ position, forward, up }`. Bare `Vec3` positions remain compatible for one release and use forward `[0, 0, -1]` with up `[0, 1, 0]`.",
|
|
125
|
+
"Hosted world stores are asynchronous. Implement `HostedWorldStore.load()` and `save()` as `Promise`-returning methods and construct persisted sessions with `createHostedWorldSessionAsync`; synchronous tests can keep using `memoryWorldStore`.",
|
|
126
|
+
"Look presets now default to `neutral`. Games that relied on the previous implicit cinematic sky/post stack should set `look: \"photoreal\"` (or keep `look: \"cinematic\"`) explicitly.",
|
|
127
|
+
"`ServerTickPlan.due` is `{ id, runs }[]` instead of a repeated id list. `planServerTick` repeated a system id once per missed interval, and the obvious way to consume that — `due.includes(id)` or a `Set` — ran the system once while the returned anchor had already advanced by every repeat, silently dropping the catch-up work. A host now reads `runs` (or `tickRunCount(plan, id)`) and loops that many times.",
|
|
128
|
+
"`CommandDef.validate` / `CommandDef.apply` take a fourth argument, `nowMs`. Existing implementations that ignore it are unaffected. Commands that took a `now` in their `input` should read this instead — the client supplied the old one.",
|
|
129
|
+
],
|
|
130
|
+
added: [
|
|
131
|
+
"Indexed shared membership and capacity rows, bounded neighborhood poses with 100 ms server/client gates, durable rate limits, authenticated diagnostics and bounded retention.",
|
|
132
|
+
"Territory footprint planning and atomic placement claims, chat validation and headless chat/join/territory surfaces, elapsed accrual, safe quantities, and player-profile reset through the initializer.",
|
|
133
|
+
"An explicit `--patch` release option.",
|
|
134
|
+
"Added smooth interpolation for remote presence player poses.",
|
|
135
|
+
"WS ping/pong messages and client RTT sampling on the backend object.",
|
|
136
|
+
"Added skin-backed HUD chrome variables, font loading, and the procedural `HudSkinPreview` fixture.",
|
|
137
|
+
"Core gamepad snapshot model, deadzone/curve resolver, and controller glyph labels.",
|
|
138
|
+
"Shell gamepad source polls connected pads, merges analog actions, supports synthetic `?gamepad=1` input, and exposes `ctx.input.rumble`.",
|
|
139
|
+
"Core action context stacks layer gameplay, menus, and passthrough overlays with snapshot/restore; shell action tracking now respects the active context.",
|
|
140
|
+
"Shell audio now supports listener orientation and per-sound Web Audio panners. `setListenerPose` accepts `{ position, forward, up }`; bare positions remain supported for one release.",
|
|
141
|
+
"Core perception tracks deterministic sight, hearing, occlusion, and bounded observation memory for AI.",
|
|
142
|
+
"`syncWorldColliders` now keeps terrain and static object colliders synchronized with a physics backend.",
|
|
143
|
+
"Core decision graphs provide deterministic selector, sequence, condition, action, and utility runtimes with snapshot/restore.",
|
|
144
|
+
"Core behavior descriptors can run registered decision graphs at the interest-scheduler cadence.",
|
|
145
|
+
"Core navigation meshes provide deterministic polygon routing, off-mesh links, closest-point queries, and nav raycasts.",
|
|
146
|
+
"WS sessions now issue resume tickets and retain disconnected memberships for a 15-second grace window, allowing reconnects to rejoin without replacing player state.",
|
|
147
|
+
"Shell entity sprites can play atlas-backed sprite clips while preserving raw texture sprites.",
|
|
148
|
+
"Tilemap layers can render atlas-backed textured tiles as one instanced quad mesh with parallax.",
|
|
149
|
+
"Core pixel-perfect orthographic frustum and pixel-grid camera snapping are available to shell cameras.",
|
|
150
|
+
"Core sprite atlas adapters and deterministic 2D sprite clip playback primitives.",
|
|
151
|
+
"`src/art-direction.md` scaffold and `check-art-direction` gate for created games.",
|
|
152
|
+
"Host-authoritative shell sessions now expose each accepted world frame to the local prediction buffer, reconciling and snapping the possessed pose only when drift exceeds its threshold.",
|
|
153
|
+
"Shared GLTF loader configuration for Draco and KTX2-compressed assets, with CDN defaults and renderer capability detection.",
|
|
154
|
+
"Material map overrides now support metalness, emissive, and height maps on standard and physical materials.",
|
|
155
|
+
"Presentation and editor documents now accept gradient, HDRI/EXR, and cube-map environment sources.",
|
|
156
|
+
"Authored `light` markers now feed bounded point/spot punctual lights with an eight-light default budget and editor warning.",
|
|
157
|
+
"Graphics profiles now resolve per-tier render scale, shadow budgets, culling distance, particles, cascades, and post stages; games can override them with `defineGame({ graphics })`.",
|
|
158
|
+
"`defineGame({ physics: { backend } })` now adopts a capsule character controller for shell/headless movement and registers the backend simulation after movement, including floor collision and step-up behavior.",
|
|
159
|
+
"Added the `physics-probe` dev scene for Rapier crate, ragdoll, and vehicle motion.",
|
|
160
|
+
"Assets now expose import-spec validation and magic-byte classification for supported source files.",
|
|
161
|
+
"Hosted WS sessions now support spectator joins, viewer roles, throttled low-priority snapshot modules, and default command rate limits.",
|
|
162
|
+
"`@jgengine/navbake` bakes deterministic navigation meshes and editor documents can store named bake data.",
|
|
163
|
+
"Core tactical queries derive cover points and score bounded candidate positions.",
|
|
164
|
+
"Added createSnapshotBuffer for delayed snapshot interpolation.",
|
|
165
|
+
"Host-authoritative world replication over WS. Hosted sessions expose revision-cursor pulls, so server subscribers receive compact world diffs and automatically recover with a fresh baseline after missed revisions; viewer-specific projections still receive per-viewer baselines.",
|
|
166
|
+
"Animation graph — `ModelAnimationConfig.graph` and `@jgengine/core/anim/animGraph`. A serializable, layered animation state machine: `clip`, `blend1D`, and `blend2D` states, transitions as data (parameter comparisons, consumed triggers, exit times, crossfade durations), masked and additive layers, and clip events. `createAnimGraphRuntime` is headless and owns every clip's playback time and weight, so the shell's mixer only seeks and weights actions (`useModelAnimation` takes the graph path when `graph` is set) and a headless host or test advances the same graph without three.js. The shell feeds the entity's smoothed ground speed as `speed`, merges parameters from the entity blackboard key `ANIM_PARAMS_KEY`, arms triggers from `entity.animation` / `combat.hitReaction` / `entity.died`, and emits the new `animation.event` for clip events (foot plants, hit frames). `locomotionGraph()` builds the previous idle/walk/run plus one-shot behavior as one authored graph.",
|
|
167
|
+
"`PhysicsBackend` — one interface over interchangeable rigid-body solvers. `@jgengine/core/physics/physicsBackend` defines bodies (`BodyDesc` with a box / sphere / capsule / convex / trimesh / heightfield shape union, quaternion rotation, angular velocity, layers and mask, CCD flag), joints, `raycast`, `shapecast`, `overlap`, contact events, and `snapshot`/`restore` with stable handles; `capabilities` says what a backend really does. `createPhysicsWorldBackend` puts the zero-dependency `PhysicsWorld` behind it (translation-only, non-box shapes collide as their bounding box, warned once), and `runPhysicsBackendConformance` is the behavioral contract any adapter runs. `createCharacterController` (`@jgengine/core/movement/characterController`) is the first consumer: a capsule collide-and-slide controller with step-up, slope limit, ceiling test, crouch with headroom check, ground snapping, and moving-platform carry, pure over any backend and retunable during play.",
|
|
168
|
+
"Fixed-step simulation with render interpolation — `defineGame({ simulation: { hz } })` and `ctx.sim`. Every driver (shell frame, HUD-only, headless runner, authoritative host) now steps through one `SimLoop` (`@jgengine/core/runtime/simLoop`): a fixed `hz` accumulates real time and runs equal-dt steps behind a catch-up cap (`maxCatchUpSteps`, default 5) instead of spiralling; the default stays `\"variable\"` (one step per frame) so existing games are unchanged until they opt in. Under a fixed rate the shell renders entities at `ctx.sim.renderPose(id)` — the pose interpolated between the previous and current step (`@jgengine/core/runtime/poseInterpolation`), with teleports past `snapDistance` snapping — so a 20 Hz sim no longer stutters. `ctx.sim.tick()` counts steps and `InputFrame.tick` carries it over the wire; `ctx.sim.addStage({ phase, run })` inserts per-step work before movement, after movement, or after `onTick` (the hook a physics backend or netcode buffer registers through). System fixed-rate groups gain the same substep cap. For replay and rollback: `seededRng` streams expose `state()`/`restore()` (`rngStateOf`/`restoreRng` for any `ctx.rng`), `createSimSnapshotRegistry`/`createContextSimSnapshot` capture world, clock, loop, and rng cursor together, `createInputRecorder` logs frames by tick, and `HeadlessRunner` gains `snapshot()`/`restore()`/`tick()` — a test proves that replaying recorded input from a snapshot reproduces every entity pose.",
|
|
169
|
+
"City and street races share one generator. `extractCircuitRoute` lifts a sealed-off race lap from a city street network; city rules, editor baking, playground modes, and the shell renderer carry the same street dials through the resulting race surface and junction dressing.",
|
|
170
|
+
"`ctx.particles` — the game-reachable particle seam. Game logic queues one-shot bursts (`ctx.particles.burst(config, count)`) and keyed standing emitters (`attach`/`retune`/`detach`, with `follow` to track a scene entity) as plain serializable data (`@jgengine/core/vfx/particleDirector`); the shell auto-mounts a renderer that turns them into pooled GPU point clouds via the existing `createParticleSystem`/`ParticleField`, caps pool sizes by graphics quality (low 128 / medium 256 / high 512 per effect, degrading density instead of dropping effects), and reaps finished bursts. Dust, sparks, exhaust, and debris no longer require forking the shell; headless tests assert the requested effects straight off the director.",
|
|
171
|
+
"`resolveSourceWalkerStep(source, cache, position, stepX, stepZ, options)` (`@jgengine/core/movement/solidObstacles`) — `resolveWalkerStep` with no `GameContext`, for movement rules that live in a pure package with no React or three.js. `SolidObstacleSource`, `ObstacleReachCache`, `createObstacleReachCache`, `sourceObstacleReach`, `sourceObstaclesNear`, and `slideStep` are public alongside it instead of `@internal`, so a game no longer has to push the resolver up into its app layer and inject it back down. `resolveWalkerStep`'s doc now states that it already resolves X and Z separately, so a caller doing its own axis-split must hand it one axis per call.",
|
|
172
|
+
"`tickRunCount(plan, id)` (`@jgengine/core/time/serverTick`) — how many runs a plan owes a system, or 0 when it is not due.",
|
|
173
|
+
"`grass()` exposes the clump/lighting knobs its shader already had. `GrassEnvironmentConfig` gains optional `bladeBend`, `tuftRadius`, `colorVariation`, and `normalLift`, forwarded by the shell's environment renderer — a world descriptor can now author bushy wind-combed scrub instead of the fixed lawn-blade look; defaults unchanged.",
|
|
174
|
+
"`TerrainDetailConfig.sweeps` — the detail shader's two macro colour sweeps (sun-dried patches, cooler wet pockets) take per-world RGB multipliers instead of hardcoded meadow tints. The old green-leaning \"lush pocket\" default painted moss onto arid/ashen biomes; defaults are unchanged, so a desert world should set warm/neutral `sweeps` explicitly.",
|
|
175
|
+
"`@jgengine/rapier` — a Rapier-backed `PhysicsBackend` adapter with native capsules, shapecasts, rotation, CCD, joints, and collision queries.",
|
|
176
|
+
],
|
|
177
|
+
changed: [
|
|
178
|
+
"Hosted game runner queues authoritative inputs by simulation tick and applies the in-force frame each fixed step.",
|
|
179
|
+
"Create scaffolds now use a 60 Hz simulation with interpolation enabled by default.",
|
|
180
|
+
"Rigged `ModelConfig` entries can opt into shell foot IK with named thigh/shin/foot bones; the driver raycasts the shared scene terrain, solves each chain, and fades corrections while airborne.",
|
|
181
|
+
"Animation graph clips may carry root-bone position tracks; states with `rootMotion: true` return the sampled displacement and the shell applies it to the entity while restoring the root bone's bind translation.",
|
|
182
|
+
"Added renderer-free two-bone, FABRIK, and look-at inverse-kinematics solvers to `@jgengine/core`.",
|
|
183
|
+
"Shell model animation now routes states and one-shots through the shared animation graph runtime.",
|
|
184
|
+
"CI now clones the external games repository with `GAMES_CLONE_TOKEN` and runs the game shape, content, front-end, art-direction, and feel gates against it; those gates fail loudly in CI when the checkout is missing while remaining no-ops locally.",
|
|
185
|
+
"`jgengine create` now accepts `--player`, `--ground`, and `--scene` so generated games can choose their model, terrain ground, and empty or starter scene.",
|
|
186
|
+
"Directional shadow frustum config actually reaches the GPU. `shadowCameraSize` on a single (non-cascaded) shadow light was silently inert: the shell wrote the bounds through R3F pierced props and nothing refreshed the shadow camera's projection matrix, so three rendered depth with its default ~10-unit box at the world origin — the reason no open-world game had cast shadows away from spawn. The shell now syncs the projection (and reallocates the shadow map when `shadowMapSize` changes). `bloom.threshold`'s doc also now states it measures raw HDR before the exposure stage.",
|
|
187
|
+
"CSM (`cascades > 1`) no longer over-lights streamed meshes or clobbers material shaders. Cascade scoping is injected per material, but the scan ran every 30 frames, so a model that streamed in rendered lit by every cascade light at once (a cascades× sun) until the next scan — a sub-second flash on desktop, the whole shot on capture rigs. The scan now runs every frame behind a WeakSet, and `setupMaterial` chains with a material's own `onBeforeCompile` (rim light, detail maps) instead of replacing it.",
|
|
188
|
+
"The host's wall clock reaches the runtime hooks. `RuntimeInitContext` (so `onInit`, `onNewPlayer`, and `onTick` alike) carries `nowMs`, the host timestamp for that call. Anything keyed to real time — a UTC date rollover, a `lastTickAt` anchor read by non-tick code, an RNG seed or `createdAt` stamp — reads it rather than accumulating `dtSeconds` against an epoch persisted in the snapshot, which drifts by whatever the tick loses to stalls and `maxCatchUp` clamping. `GameRuntime.hydrate` takes an optional `nowMs`; `tick`, `joinPlayer`, and `runCommand` take an optional trailing `nowMs`. All default to `Date.now()`, and the shipped WS and Convex hosts pass the timestamp they already computed to plan the tick.",
|
|
189
|
+
"Imported models cast and receive shadows. `cloneModelScene` never set `castShadow`/`receiveShadow` on a GLB's meshes (only the magenta fallback box had them), so every catalog model in every game rendered shadowless however the lights were configured. Cloned models now default to `ModelConfig.shadows: \"both\"`; set `\"cast\"`, `\"receive\"`, or `\"none\"` per model (foliage cards, decals, viewmodels).",
|
|
190
|
+
"`jgengine create` installs `game-design` and `jgengine-ui` by default. The minimal skill set was intake, editor, and verify, so a real project never saw the greenfield design contract or the UI art-direction template unless someone ran `jgengine skills --all`; `MINIMAL_GAME_SKILLS` now carries both.",
|
|
191
|
+
],
|
|
192
|
+
removed: [],
|
|
193
|
+
},
|
|
5
194
|
"0.18.0": {
|
|
6
195
|
migrate: [
|
|
7
196
|
"Bump lockstep SDK packages to `^0.18.0`: `@jgengine/{core,react,ws,node,sql,convex,shell,editor,assets}`. CLI `jgengine` is `0.15.0`; `@jgengine/github` is `0.5.0`.",
|
|
@@ -9,11 +198,17 @@ export const CHANGELOG = {
|
|
|
9
198
|
"`presence.sync` returns `{ pose, lastSeenAt, displaced }` instead of `null`, and `presence.leave` marks the row revoked instead of deleting it (the reaper collects it once idle). A caller that ignored the return value is unaffected.",
|
|
10
199
|
],
|
|
11
200
|
added: [
|
|
201
|
+
"Polygon navigation meshes — `NavMeshData`, adjacency building, polygon A* paths, closest-point, and nav raycasts (`@jgengine/core/nav/navMesh`).",
|
|
202
|
+
"`createPerception` (`@jgengine/core/sensor/perception`) tracks sight, sound, and damage stimuli per observer, with cone/range/occlusion checks, linearly decaying memory, retuning, and snapshot/restore.",
|
|
203
|
+
"`syncWorldColliders(backend, ctx)` mirrors static scene-object colliders and the sampled ground heightfield into a physics backend, updating on object spawn, despawn, and pose changes.",
|
|
204
|
+
"Physics-backed player movement — `defineGame({ physics: { backend } })` installs the backend's `afterMovement` simulation stage and makes the shell/headless walk controller use a capsule for gravity, shapecast collision, slopes, and step-up, while retaining the existing input and impulse feel.",
|
|
12
205
|
"`createPresenceFunctions` gains the policy hooks the pose lane was missing — `resolveSpawn(ctx, { serverId, userId, kind })` for where a session with no row starts (async and ctx-bearing, so a spawn can read the world, replacing a hardcoded origin), `poseRules` as a function of the actor `kind` so an agent need not obey a human's speed cap, `idleCutoffMs` / `maxReapPerRun` for the reaper, and optional `sessionId` on `sync` / `leave`: a second tab is a second row, syncing revokes the others, and the displaced session learns it on its next call through `displaced: true`.",
|
|
13
206
|
"A command declares what it touches, so `runCommand` hydrates that and nothing else. `CommandDef` gains an optional `scope(input, actorUserId)` returning `{ players?, chunkKeys? }`, evaluated before hydration; the registered `runCommand` mutation also accepts an explicit `scope` argument, which wins over the declared one. A game that never sets either keeps the whole-world default. `ServerLoopHooks.joinScope(userId, isNew)` does the same for `onNewPlayer`.",
|
|
14
207
|
"`GameRuntime.commandScope` / `GameRuntime.joinScope` expose those declarations to any host, alongside the pure helpers `resolveCommandScope` / `scopeWithActor` / `commandScopeEscape` (`@jgengine/core/runtime/commandRunner`).",
|
|
15
208
|
],
|
|
16
209
|
changed: [
|
|
210
|
+
"Rigged `ModelConfig` entries can opt into shell foot IK with named thigh/shin/foot bones; the driver raycasts the shared scene terrain, solves each chain, and fades corrections while airborne.",
|
|
211
|
+
"Animation graph clips may carry root-bone position tracks; states with `rootMotion: true` return the sampled displacement and the shell applies it to the entity while restoring the root bone's bind translation.",
|
|
17
212
|
"`presence.sync` accepts a call with no `pose` at all, which holds the stored position and moves only the liveness stamp. `createConvexPresenceSync` sends one itself when the pose gate has suppressed writes for 10s, so a standing player keeps their row alive without a pose write — and without the reaper collecting a live session.",
|
|
18
213
|
"`PresencePoseRow` carries optional `sessionId` / `kind` / `label`, so a nameplate reads off the presence row instead of joining against users per frame.",
|
|
19
214
|
"`runCommand` no longer reads the whole world on every command. `LoadSnapshotScope` landed in 0.17 but only `helpers.loadSnapshot` accepted one, so the supported way to run a command — the registered `runCommand` mutation, a one-line delegation to `helpers.runCommand` — hydrated every member profile and every chunk however narrow the command was, and a game could only get bounded hydration by reimplementing the mutation. `runCommand`, `joinServer`, and `leaveServer` now hydrate a scope; a command whose `apply` dirties a player or chunk its scope never named is refused rather than persisted, since writing an unhydrated chunk overwrites the stored one with a blank. `flushSave` / `flushServerIfDue` / `tickActiveServers` / `flushDirtyServers` still hydrate everything, which is correct for what they write.",
|
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
import type { BodyHandle, BodyShape, PhysicsBackend, PhysicsVec3 } from "../physics/physicsBackend.js";
|
|
2
|
+
/** Capsule dimensions and the walk rules a controller applies; every field is retunable during play. */
|
|
3
|
+
export interface CharacterControllerConfig {
|
|
4
|
+
radius: number;
|
|
5
|
+
/** Standing height from feet to crown. */
|
|
6
|
+
height: number;
|
|
7
|
+
/** Height the feet may pop up to clear a ledge in one move. Default 0.35. */
|
|
8
|
+
stepHeight?: number;
|
|
9
|
+
/** Steepest walkable slope in degrees; steeper surfaces are walls to slide along. Default 50. */
|
|
10
|
+
maxSlopeDeg?: number;
|
|
11
|
+
/** Gap kept between the capsule and any surface so casts never start inside geometry. Default 0.02. */
|
|
12
|
+
skinWidth?: number;
|
|
13
|
+
/** Height while crouched. Default `height * 0.6`. */
|
|
14
|
+
crouchHeight?: number;
|
|
15
|
+
/** How far below the feet the ground may be while still counting as grounded (stairs down, small bumps). Default 0.3. */
|
|
16
|
+
snapDistance?: number;
|
|
17
|
+
/** Slide iterations per move. Default 4. */
|
|
18
|
+
maxSlides?: number;
|
|
19
|
+
/** Collision mask for the controller's casts. Default all layers. */
|
|
20
|
+
mask?: number;
|
|
21
|
+
}
|
|
22
|
+
/** The controller's serializable state. `position` is the feet point; the capsule stands above it. */
|
|
23
|
+
export interface CharacterControllerState {
|
|
24
|
+
position: [number, number, number];
|
|
25
|
+
verticalVelocity: number;
|
|
26
|
+
grounded: boolean;
|
|
27
|
+
groundNormal: [number, number, number];
|
|
28
|
+
groundBody: BodyHandle | null;
|
|
29
|
+
crouching: boolean;
|
|
30
|
+
}
|
|
31
|
+
/** One step of intent for {@link CharacterController.move}. */
|
|
32
|
+
export interface CharacterMoveInput {
|
|
33
|
+
/** Desired horizontal displacement this step, world units. */
|
|
34
|
+
motion: PhysicsVec3;
|
|
35
|
+
dt: number;
|
|
36
|
+
/** Downward acceleration applied to `verticalVelocity`. Default 0 (caller integrates vertical motion itself). */
|
|
37
|
+
gravity?: number;
|
|
38
|
+
/** Upward velocity to set this step; ignored unless grounded. */
|
|
39
|
+
jumpVelocity?: number;
|
|
40
|
+
/** Request crouch (`true`) or stand (`false`); standing waits until there is headroom. */
|
|
41
|
+
crouch?: boolean;
|
|
42
|
+
/** Body the controller itself is represented by, excluded from its own casts. */
|
|
43
|
+
self?: BodyHandle;
|
|
44
|
+
}
|
|
45
|
+
/** What one move did. */
|
|
46
|
+
export interface CharacterMoveResult {
|
|
47
|
+
moved: [number, number, number];
|
|
48
|
+
grounded: boolean;
|
|
49
|
+
groundNormal: [number, number, number];
|
|
50
|
+
hitWall: boolean;
|
|
51
|
+
hitCeiling: boolean;
|
|
52
|
+
steppedUp: boolean;
|
|
53
|
+
/** Set when a stand request was refused for lack of headroom. */
|
|
54
|
+
crouchBlocked: boolean;
|
|
55
|
+
}
|
|
56
|
+
/** The controller handle: config, state, and the per-step move. */
|
|
57
|
+
export interface CharacterController {
|
|
58
|
+
config(): Readonly<Required<CharacterControllerConfig>>;
|
|
59
|
+
retune(next: Partial<CharacterControllerConfig>): void;
|
|
60
|
+
state(): CharacterControllerState;
|
|
61
|
+
snapshot(): CharacterControllerState;
|
|
62
|
+
restore(next: CharacterControllerState): void;
|
|
63
|
+
/** Current capsule as a backend shape (crouch-aware). */
|
|
64
|
+
shape(): BodyShape;
|
|
65
|
+
/** Capsule center for the current state. */
|
|
66
|
+
center(): [number, number, number];
|
|
67
|
+
move(backend: PhysicsBackend, input: CharacterMoveInput): CharacterMoveResult;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Collide-and-slide capsule controller over any {@link PhysicsBackend}: horizontal slide along walls, step-up over
|
|
71
|
+
* ledges, slope limit, ceiling test, crouch with headroom check, ground snapping, and moving-platform carry read from
|
|
72
|
+
* the ground body's velocity. Pure over the backend's `shapecast`/`overlap`; the caller owns input and gravity policy.
|
|
73
|
+
*
|
|
74
|
+
* @capability character-controller capsule walk over the physics backend — slide, step-up, slopes, crouch, platforms
|
|
75
|
+
*/
|
|
76
|
+
export declare function createCharacterController(initial: CharacterControllerConfig): CharacterController;
|