@jgengine/core 0.8.0 → 0.10.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 +36 -0
- package/dist/ai/flock.d.ts +36 -0
- package/dist/ai/flock.js +182 -0
- package/dist/ai/groupAssist.d.ts +19 -0
- package/dist/ai/groupAssist.js +59 -0
- package/dist/ai/laneSelect.d.ts +21 -0
- package/dist/ai/laneSelect.js +37 -0
- package/dist/ai/mobBrain.d.ts +68 -0
- package/dist/ai/mobBrain.js +115 -0
- package/dist/ai/spawnDirector.d.ts +13 -0
- package/dist/ai/spawnDirector.js +41 -17
- package/dist/ai/spawnPoint.d.ts +5 -0
- package/dist/ai/spawnPoint.js +5 -0
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -0
- package/dist/audio/audioFalloff.d.ts +5 -2
- package/dist/audio/music.d.ts +53 -0
- package/dist/audio/music.js +34 -0
- package/dist/audio/synth.d.ts +60 -0
- package/dist/audio/synth.js +8 -0
- package/dist/authoring.d.ts +14 -0
- package/dist/authoring.js +14 -0
- package/dist/cartridge/runtime.d.ts +15 -0
- package/dist/cartridge/runtime.js +556 -0
- package/dist/cartridge/spec.d.ts +188 -0
- package/dist/cartridge/spec.js +19 -0
- package/dist/cartridge/validate.d.ts +2 -0
- package/dist/cartridge/validate.js +78 -0
- package/dist/combat/abilityKit.d.ts +17 -2
- package/dist/combat/abilityKit.js +62 -13
- package/dist/combat/castRunner.d.ts +40 -0
- package/dist/combat/castRunner.js +56 -0
- package/dist/combat/comboPoints.d.ts +14 -0
- package/dist/combat/comboPoints.js +52 -0
- package/dist/combat/defensiveWindow.d.ts +1 -1
- package/dist/combat/defensiveWindow.js +5 -3
- package/dist/combat/dotField.d.ts +43 -0
- package/dist/combat/dotField.js +48 -0
- package/dist/combat/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +214 -129
- package/dist/combat/regenShield.d.ts +35 -0
- package/dist/combat/regenShield.js +45 -0
- package/dist/combat/resistance.d.ts +16 -4
- package/dist/combat/resistance.js +43 -7
- package/dist/combat/resourcePool.d.ts +20 -0
- package/dist/combat/resourcePool.js +55 -0
- package/dist/combat/shotOrigin.d.ts +52 -0
- package/dist/combat/shotOrigin.js +86 -0
- package/dist/combat/telegraph.d.ts +25 -0
- package/dist/combat/telegraph.js +47 -0
- package/dist/crafting/recipe.js +4 -1
- package/dist/devtools/devtools.d.ts +72 -24
- package/dist/devtools/devtools.js +449 -101
- package/dist/devtools/transformTunables.js +3 -3
- package/dist/devtools/tunableSchema.d.ts +140 -0
- package/dist/devtools/tunableSchema.js +476 -0
- package/dist/editor/commands.d.ts +65 -0
- package/dist/editor/commands.js +193 -0
- package/dist/editor/document.d.ts +40 -0
- package/dist/editor/document.js +153 -0
- package/dist/editor/index.d.ts +30 -0
- package/dist/editor/index.js +6 -0
- package/dist/editor/types.d.ts +69 -0
- package/dist/editor/types.js +29 -0
- package/dist/faction/factions.d.ts +33 -0
- package/dist/faction/factions.js +76 -0
- package/dist/faction/reputation.d.ts +32 -0
- package/dist/faction/reputation.js +82 -0
- package/dist/game/chat.d.ts +2 -0
- package/dist/game/chat.js +9 -1
- package/dist/game/chatFilter.d.ts +15 -0
- package/dist/game/chatFilter.js +55 -0
- package/dist/game/connectedPlayers.d.ts +28 -0
- package/dist/game/connectedPlayers.js +21 -0
- package/dist/game/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +44 -1
- package/dist/game/defineGame.js +1 -0
- package/dist/game/events.d.ts +25 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/keyValueStore.d.ts +25 -0
- package/dist/game/keyValueStore.js +61 -0
- package/dist/game/levelSequence.d.ts +24 -2
- package/dist/game/levelSequence.js +51 -2
- package/dist/game/lootTable.d.ts +14 -1
- package/dist/game/lootTable.js +28 -7
- package/dist/game/modelAnimation.d.ts +8 -0
- package/dist/game/modelAnimation.js +18 -0
- package/dist/game/playableGame.d.ts +116 -4
- package/dist/game/playableGame.js +1 -1
- package/dist/game/quest.d.ts +7 -3
- package/dist/game/quest.js +17 -8
- package/dist/game/race.d.ts +39 -1
- package/dist/game/race.js +96 -11
- package/dist/game/recordBook.d.ts +27 -0
- package/dist/game/recordBook.js +86 -0
- package/dist/game/talents.d.ts +47 -0
- package/dist/game/talents.js +140 -0
- package/dist/index.d.ts +8 -0
- package/dist/index.js +8 -0
- package/dist/input/actionBindings.d.ts +2 -0
- package/dist/input/actionBindings.js +1 -0
- package/dist/input/axisInput.d.ts +33 -3
- package/dist/input/axisInput.js +60 -4
- package/dist/input/bindingOverrides.d.ts +25 -0
- package/dist/input/bindingOverrides.js +95 -0
- package/dist/input/pointerAxis.d.ts +30 -0
- package/dist/input/pointerAxis.js +31 -0
- package/dist/input/touchScheme.d.ts +6 -0
- package/dist/input/touchScheme.js +45 -7
- package/dist/interaction/contextMenu.d.ts +1 -0
- package/dist/interaction/contextMenu.js +1 -0
- package/dist/interaction/proximityPrompt.d.ts +3 -1
- package/dist/interaction/proximityPrompt.js +3 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +44 -1
- package/dist/movement/dash.d.ts +3 -0
- package/dist/movement/dash.js +19 -5
- package/dist/movement/glideModel.d.ts +44 -0
- package/dist/movement/glideModel.js +54 -0
- package/dist/movement/grappleSwing.d.ts +34 -0
- package/dist/movement/grappleSwing.js +56 -0
- package/dist/movement/leaderTrail.d.ts +29 -0
- package/dist/movement/leaderTrail.js +67 -0
- package/dist/movement/movementModel.d.ts +4 -0
- package/dist/movement/movementModel.js +13 -0
- package/dist/movement/playerMovement.d.ts +41 -0
- package/dist/movement/playerMovement.js +181 -0
- package/dist/movement/steering.d.ts +27 -0
- package/dist/movement/steering.js +40 -0
- package/dist/multiplayer/feedWriteGate.d.ts +10 -0
- package/dist/multiplayer/feedWriteGate.js +12 -0
- package/dist/multiplayer/presenceModel.d.ts +14 -2
- package/dist/multiplayer/presenceModel.js +15 -3
- package/dist/nav/corridors.d.ts +30 -0
- package/dist/nav/corridors.js +69 -0
- package/dist/nav/navGrid.js +30 -4
- package/dist/nav/railGraph.d.ts +66 -0
- package/dist/nav/railGraph.js +129 -0
- package/dist/nav/timetable.d.ts +41 -0
- package/dist/nav/timetable.js +94 -0
- package/dist/physics/buoyancy.d.ts +6 -0
- package/dist/physics/buoyancy.js +14 -0
- package/dist/physics/flowTube.d.ts +32 -0
- package/dist/physics/flowTube.js +64 -0
- package/dist/physics/forceVolume.d.ts +27 -0
- package/dist/physics/forceVolume.js +38 -0
- package/dist/physics/kinematicVehicle.d.ts +52 -0
- package/dist/physics/kinematicVehicle.js +81 -0
- package/dist/physics/physicsWorld.d.ts +13 -6
- package/dist/physics/physicsWorld.js +169 -40
- package/dist/physics/ragdoll.js +1 -1
- package/dist/physics/vehicleBody.js +2 -1
- package/dist/random/rng.d.ts +2 -0
- package/dist/random/rng.js +2 -0
- package/dist/random/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- package/dist/render/postProcessing.d.ts +56 -0
- package/dist/render/postProcessing.js +1 -0
- package/dist/runtime/adapter.d.ts +19 -0
- package/dist/runtime/adapter.js +11 -5
- package/dist/runtime/cameraDirector.d.ts +6 -1
- package/dist/runtime/cameraDirector.js +6 -0
- package/dist/runtime/commandRunner.d.ts +2 -2
- package/dist/runtime/commandRunner.js +2 -2
- package/dist/runtime/gameContext.d.ts +72 -8
- package/dist/runtime/gameContext.js +374 -61
- package/dist/runtime/gameRuntime.d.ts +5 -1
- package/dist/runtime/gameRuntime.js +13 -12
- package/dist/runtime/hostPersistence.d.ts +9 -1
- package/dist/runtime/hostPersistence.js +23 -8
- package/dist/runtime/hostedGameRunner.d.ts +49 -0
- package/dist/runtime/hostedGameRunner.js +62 -0
- package/dist/runtime/hostedWorldSession.d.ts +65 -0
- package/dist/runtime/hostedWorldSession.js +57 -0
- package/dist/runtime/inputSnapshot.d.ts +18 -0
- package/dist/runtime/inputSnapshot.js +7 -0
- package/dist/runtime/motionIntents.d.ts +16 -6
- package/dist/runtime/motionIntents.js +31 -2
- package/dist/runtime/snapshot.d.ts +1 -0
- package/dist/runtime/snapshot.js +1 -1
- package/dist/runtime/visibility.d.ts +74 -0
- package/dist/runtime/visibility.js +127 -0
- package/dist/runtime/worldChannel.d.ts +58 -0
- package/dist/runtime/worldChannel.js +69 -0
- package/dist/runtime/worldMirror.d.ts +24 -0
- package/dist/runtime/worldMirror.js +31 -0
- package/dist/runtime/worldReplication.d.ts +46 -0
- package/dist/runtime/worldReplication.js +185 -0
- package/dist/runtime/worldSnapshot.d.ts +17 -0
- package/dist/runtime/worldSnapshot.js +15 -0
- package/dist/scene/assetCatalog.d.ts +1 -0
- package/dist/scene/colliders.d.ts +50 -0
- package/dist/scene/colliders.js +101 -0
- package/dist/scene/entityStats.d.ts +4 -0
- package/dist/scene/entityStats.js +21 -0
- package/dist/scene/entityStore.d.ts +51 -5
- package/dist/scene/entityStore.js +119 -12
- package/dist/scene/objectQuery.d.ts +13 -2
- package/dist/scene/objectQuery.js +34 -2
- package/dist/scene/objectStore.d.ts +2 -0
- package/dist/scene/objectStore.js +31 -12
- package/dist/scene/sceneRaycast.d.ts +80 -0
- package/dist/scene/sceneRaycast.js +284 -0
- package/dist/scene/spatial.d.ts +11 -16
- package/dist/scene/spatial.js +99 -38
- package/dist/sensor/recordingBuffer.d.ts +6 -0
- package/dist/sensor/recordingBuffer.js +7 -0
- package/dist/sensor/replayLoop.d.ts +39 -0
- package/dist/sensor/replayLoop.js +64 -0
- package/dist/sensor/visionCone.d.ts +31 -0
- package/dist/sensor/visionCone.js +72 -0
- package/dist/session/extraction.d.ts +2 -2
- package/dist/session/extraction.js +13 -2
- package/dist/settings/settingsModel.d.ts +97 -0
- package/dist/settings/settingsModel.js +100 -0
- package/dist/stats/statModifiers.d.ts +5 -2
- package/dist/stats/statModifiers.js +32 -14
- package/dist/store/observableKeyedStore.d.ts +4 -0
- package/dist/store/observableKeyedStore.js +17 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- package/dist/tactics/fallingGrid.d.ts +2 -2
- package/dist/tactics/fallingGrid.js +2 -2
- package/dist/time/beatClock.d.ts +18 -0
- package/dist/time/beatClock.js +24 -0
- package/dist/time/idleProgress.d.ts +29 -0
- package/dist/time/idleProgress.js +31 -0
- package/dist/time/serverTick.d.ts +7 -1
- package/dist/time/serverTick.js +27 -4
- package/dist/time/stateSchedule.d.ts +57 -0
- package/dist/time/stateSchedule.js +121 -0
- package/dist/turn/commit.d.ts +2 -2
- package/dist/turn/commit.js +2 -2
- package/dist/ui/gameLayout.d.ts +125 -0
- package/dist/ui/gameLayout.js +129 -0
- package/dist/ui/hudLayout.d.ts +58 -0
- package/dist/ui/hudLayout.js +207 -0
- package/dist/ui/hudScale.d.ts +44 -0
- package/dist/ui/hudScale.js +55 -0
- package/dist/ui/orientation.d.ts +39 -0
- package/dist/ui/orientation.js +49 -0
- package/dist/ui/swingTimer.d.ts +42 -0
- package/dist/ui/swingTimer.js +38 -0
- package/dist/visibility/assetStreaming.d.ts +66 -0
- package/dist/visibility/assetStreaming.js +201 -0
- package/dist/visibility/bounds.d.ts +66 -0
- package/dist/visibility/bounds.js +113 -0
- package/dist/visibility/camera.d.ts +21 -0
- package/dist/visibility/camera.js +3 -0
- package/dist/visibility/config.d.ts +21 -0
- package/dist/visibility/config.js +1 -0
- package/dist/visibility/diagnostics.d.ts +26 -0
- package/dist/visibility/diagnostics.js +36 -0
- package/dist/visibility/distance.d.ts +11 -0
- package/dist/visibility/distance.js +30 -0
- package/dist/visibility/frustum.d.ts +47 -0
- package/dist/visibility/frustum.js +186 -0
- package/dist/visibility/occlusion.d.ts +36 -0
- package/dist/visibility/occlusion.js +45 -0
- package/dist/visibility/settings.d.ts +75 -0
- package/dist/visibility/settings.js +49 -0
- package/dist/visibility/simulationCulling.d.ts +34 -0
- package/dist/visibility/simulationCulling.js +36 -0
- package/dist/visibility/spatialIndex.d.ts +45 -0
- package/dist/visibility/spatialIndex.js +212 -0
- package/dist/visibility/visibilitySystem.d.ts +90 -0
- package/dist/visibility/visibilitySystem.js +257 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/buildings.js +152 -0
- package/dist/world/carve.d.ts +7 -2
- package/dist/world/carve.js +17 -7
- package/dist/world/cellStates.d.ts +45 -0
- package/dist/world/cellStates.js +103 -0
- package/dist/world/environmentSummary.d.ts +21 -4
- package/dist/world/environmentSummary.js +22 -3
- package/dist/world/features.d.ts +185 -8
- package/dist/world/features.js +64 -6
- package/dist/world/gridInstances.d.ts +7 -8
- package/dist/world/gridInstances.js +145 -7
- package/dist/world/lod.d.ts +17 -0
- package/dist/world/lod.js +66 -0
- package/dist/world/mapLayers.d.ts +60 -0
- package/dist/world/mapLayers.js +44 -0
- package/dist/world/massing.d.ts +45 -0
- package/dist/world/massing.js +581 -0
- package/dist/world/minimap.d.ts +13 -1
- package/dist/world/minimap.js +21 -2
- package/dist/world/roads.d.ts +47 -0
- package/dist/world/roads.js +146 -0
- package/dist/world/streets.d.ts +80 -0
- package/dist/world/streets.js +144 -0
- package/dist/world/terrain.d.ts +69 -3
- package/dist/world/terrain.js +309 -16
- package/dist/world/water.d.ts +7 -0
- package/dist/world/water.js +19 -4
- package/dist/world/weather.d.ts +3 -18
- package/dist/world/weather.js +4 -17
- package/dist/world/windZones.d.ts +47 -0
- package/dist/world/windZones.js +59 -0
- package/llms.txt +3013 -1726
- package/package.json +12 -2
package/dist/game/race.js
CHANGED
|
@@ -1,10 +1,24 @@
|
|
|
1
1
|
/**
|
|
2
2
|
* A race track is an ordered ring of checkpoint trigger volumes plus a lap count. The final checkpoint
|
|
3
3
|
* is the lap/finish line: a racer completes a lap by passing all checkpoints in order and hitting the
|
|
4
|
-
* last one.
|
|
4
|
+
* last one. `forks` splice alternate route segments between mainline checkpoints.
|
|
5
5
|
*/
|
|
6
6
|
export function raceTrack(config) {
|
|
7
|
-
|
|
7
|
+
const checkpoints = config.checkpoints;
|
|
8
|
+
const forks = config.forks ?? [];
|
|
9
|
+
for (const fork of forks) {
|
|
10
|
+
if (fork.afterIndex < 0 || fork.afterIndex >= checkpoints.length - 1) {
|
|
11
|
+
throw new Error(`race fork "${fork.id}" afterIndex ${fork.afterIndex} must sit before the finish line`);
|
|
12
|
+
}
|
|
13
|
+
if (fork.routes.length < 2)
|
|
14
|
+
throw new Error(`race fork "${fork.id}" needs at least two routes`);
|
|
15
|
+
for (const route of fork.routes) {
|
|
16
|
+
if (route.checkpoints.length === 0) {
|
|
17
|
+
throw new Error(`race fork "${fork.id}" route "${route.id}" needs at least one checkpoint`);
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
return { checkpoints, laps: Math.max(1, Math.floor(config.laps ?? 1)), forks };
|
|
8
22
|
}
|
|
9
23
|
function rankIds(standings) {
|
|
10
24
|
return standings.map((s) => s.racerId);
|
|
@@ -78,6 +92,10 @@ export class RaceState {
|
|
|
78
92
|
eliminated: false,
|
|
79
93
|
startTime,
|
|
80
94
|
splits: [],
|
|
95
|
+
fork: null,
|
|
96
|
+
routesTaken: {},
|
|
97
|
+
forksDone: {},
|
|
98
|
+
lastHit: null,
|
|
81
99
|
});
|
|
82
100
|
this.order.push(racerId);
|
|
83
101
|
}
|
|
@@ -116,6 +134,7 @@ export class RaceState {
|
|
|
116
134
|
return r === null || r === undefined ? null : this.snapshot(r);
|
|
117
135
|
}
|
|
118
136
|
snapshot(r) {
|
|
137
|
+
const activeFork = r.fork === null ? null : this.track.forks[r.fork.forkIndex];
|
|
119
138
|
return {
|
|
120
139
|
racerId: r.racerId,
|
|
121
140
|
lap: r.lap,
|
|
@@ -127,6 +146,10 @@ export class RaceState {
|
|
|
127
146
|
finishTime: r.finishTime,
|
|
128
147
|
eliminated: r.eliminated,
|
|
129
148
|
splits: [...r.splits],
|
|
149
|
+
routesTaken: { ...r.routesTaken },
|
|
150
|
+
activeRoute: activeFork === null || r.fork === null
|
|
151
|
+
? null
|
|
152
|
+
: { forkId: activeFork.id, routeId: activeFork.routes[r.fork.routeIndex].id },
|
|
130
153
|
};
|
|
131
154
|
}
|
|
132
155
|
standings() {
|
|
@@ -136,16 +159,24 @@ export class RaceState {
|
|
|
136
159
|
.sort((a, b) => this.compare(a, b, null))
|
|
137
160
|
.map((r) => this.snapshot(r));
|
|
138
161
|
}
|
|
162
|
+
expectedCheckpoint(r) {
|
|
163
|
+
if (r.fork !== null) {
|
|
164
|
+
const fork = this.track.forks[r.fork.forkIndex];
|
|
165
|
+
const route = fork.routes[r.fork.routeIndex];
|
|
166
|
+
return route.checkpoints[r.fork.nextRouteCheckpoint] ?? this.track.checkpoints[r.nextCheckpoint] ?? null;
|
|
167
|
+
}
|
|
168
|
+
return this.track.checkpoints[r.nextCheckpoint] ?? null;
|
|
169
|
+
}
|
|
139
170
|
resetToCheckpoint(racerId) {
|
|
140
171
|
const r = this.racers.get(racerId);
|
|
141
172
|
if (r === undefined)
|
|
142
173
|
return null;
|
|
143
|
-
const
|
|
144
|
-
const idx = r.lastCheckpoint >= 0 ? r.lastCheckpoint : 0;
|
|
145
|
-
const cp = cps[idx];
|
|
174
|
+
const cp = r.lastHit ?? this.track.checkpoints[0];
|
|
146
175
|
if (cp === undefined)
|
|
147
176
|
return null;
|
|
148
|
-
|
|
177
|
+
let next = this.expectedCheckpoint(r) ?? cp;
|
|
178
|
+
if (next === cp)
|
|
179
|
+
next = this.track.checkpoints[1 % this.track.checkpoints.length] ?? cp;
|
|
149
180
|
const heading = Math.atan2(next.center[0] - cp.center[0], next.center[2] - cp.center[2]);
|
|
150
181
|
return { position: [cp.center[0], cp.center[1], cp.center[2]], heading };
|
|
151
182
|
}
|
|
@@ -161,10 +192,11 @@ export class RaceState {
|
|
|
161
192
|
if (positions !== null) {
|
|
162
193
|
const pa = positions.get(a.racerId);
|
|
163
194
|
const pb = positions.get(b.racerId);
|
|
164
|
-
const
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
const
|
|
195
|
+
const ca = this.expectedCheckpoint(a);
|
|
196
|
+
const cb = this.expectedCheckpoint(b);
|
|
197
|
+
if (pa !== undefined && pb !== undefined && ca !== null && cb !== null) {
|
|
198
|
+
const da = distanceToNext(pa, ca);
|
|
199
|
+
const db = distanceToNext(pb, cb);
|
|
168
200
|
if (da !== db)
|
|
169
201
|
return da - db;
|
|
170
202
|
}
|
|
@@ -177,6 +209,8 @@ export class RaceState {
|
|
|
177
209
|
const cps = this.track.checkpoints;
|
|
178
210
|
const count = cps.length;
|
|
179
211
|
if (count > 0) {
|
|
212
|
+
const forks = this.track.forks;
|
|
213
|
+
const maxSteps = count + forks.reduce((sum, fork) => sum + Math.max(...fork.routes.map((route) => route.checkpoints.length)), 0) + 2;
|
|
180
214
|
for (const id of this.order) {
|
|
181
215
|
const r = this.racers.get(id);
|
|
182
216
|
if (r.finished || r.eliminated)
|
|
@@ -185,10 +219,61 @@ export class RaceState {
|
|
|
185
219
|
if (pos === undefined)
|
|
186
220
|
continue;
|
|
187
221
|
let guard = 0;
|
|
188
|
-
while (guard <
|
|
222
|
+
while (guard < maxSteps) {
|
|
189
223
|
guard += 1;
|
|
224
|
+
if (r.fork !== null) {
|
|
225
|
+
const fork = forks[r.fork.forkIndex];
|
|
226
|
+
const route = fork.routes[r.fork.routeIndex];
|
|
227
|
+
const cp = route.checkpoints[r.fork.nextRouteCheckpoint];
|
|
228
|
+
if (!insideAabb(pos, cp))
|
|
229
|
+
break;
|
|
230
|
+
r.lastHit = cp;
|
|
231
|
+
r.progress += 1 / route.checkpoints.length;
|
|
232
|
+
r.splits.push(now - r.startTime);
|
|
233
|
+
events.push({
|
|
234
|
+
type: "checkpoint.hit",
|
|
235
|
+
racerId: id,
|
|
236
|
+
checkpoint: fork.afterIndex,
|
|
237
|
+
lap: r.lap,
|
|
238
|
+
time: now,
|
|
239
|
+
fork: { forkId: fork.id, routeId: route.id, index: r.fork.nextRouteCheckpoint },
|
|
240
|
+
});
|
|
241
|
+
r.fork.nextRouteCheckpoint += 1;
|
|
242
|
+
if (r.fork.nextRouteCheckpoint >= route.checkpoints.length) {
|
|
243
|
+
r.forksDone[fork.id] = true;
|
|
244
|
+
r.fork = null;
|
|
245
|
+
}
|
|
246
|
+
continue;
|
|
247
|
+
}
|
|
248
|
+
let tookRoute = false;
|
|
249
|
+
for (let forkIndex = 0; forkIndex < forks.length; forkIndex += 1) {
|
|
250
|
+
const fork = forks[forkIndex];
|
|
251
|
+
if (fork.afterIndex !== r.lastCheckpoint || r.nextCheckpoint !== fork.afterIndex + 1)
|
|
252
|
+
continue;
|
|
253
|
+
if (r.forksDone[fork.id] === true)
|
|
254
|
+
continue;
|
|
255
|
+
for (let routeIndex = 0; routeIndex < fork.routes.length; routeIndex += 1) {
|
|
256
|
+
const route = fork.routes[routeIndex];
|
|
257
|
+
if (!insideAabb(pos, route.checkpoints[0]))
|
|
258
|
+
continue;
|
|
259
|
+
r.fork = { forkIndex, routeIndex, nextRouteCheckpoint: 0 };
|
|
260
|
+
r.routesTaken[fork.id] = route.id;
|
|
261
|
+
events.push({ type: "fork.taken", racerId: id, forkId: fork.id, routeId: route.id, time: now });
|
|
262
|
+
tookRoute = true;
|
|
263
|
+
break;
|
|
264
|
+
}
|
|
265
|
+
if (tookRoute)
|
|
266
|
+
break;
|
|
267
|
+
}
|
|
268
|
+
if (tookRoute)
|
|
269
|
+
continue;
|
|
270
|
+
const cp = cps[r.nextCheckpoint];
|
|
271
|
+
if (!insideAabb(pos, cp))
|
|
272
|
+
break;
|
|
190
273
|
const hit = r.nextCheckpoint;
|
|
191
274
|
r.lastCheckpoint = hit;
|
|
275
|
+
r.lastHit = cp;
|
|
276
|
+
r.forksDone = {};
|
|
192
277
|
r.progress += 1;
|
|
193
278
|
r.splits.push(now - r.startTime);
|
|
194
279
|
events.push({ type: "checkpoint.hit", racerId: id, checkpoint: hit, lap: r.lap, time: now });
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { KeyValueStorage } from "./keyValueStore.js";
|
|
2
|
+
/** The structural storage backend a record book persists through — an alias of the shared {@link KeyValueStorage} seam (browser `localStorage`, a test stub, or `null`). */
|
|
3
|
+
export type RecordStorage = KeyValueStorage;
|
|
4
|
+
export type RecordDirection = "lower" | "higher";
|
|
5
|
+
export interface RecordBookConfig<K extends string> {
|
|
6
|
+
readonly key: string;
|
|
7
|
+
readonly fields: Readonly<Record<K, RecordDirection>>;
|
|
8
|
+
readonly storage?: RecordStorage | null;
|
|
9
|
+
}
|
|
10
|
+
export interface RecordSubmission<K extends string> {
|
|
11
|
+
readonly improved: readonly K[];
|
|
12
|
+
readonly best: Readonly<Partial<Record<K, number>>>;
|
|
13
|
+
}
|
|
14
|
+
export interface RecordBook<K extends string> {
|
|
15
|
+
best(): Readonly<Partial<Record<K, number>>>;
|
|
16
|
+
bestOf(field: K): number | null;
|
|
17
|
+
submit(run: Readonly<Partial<Record<K, number>>>): RecordSubmission<K>;
|
|
18
|
+
clear(): void;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* A personal-best record book: named numeric fields each racing toward "lower"
|
|
22
|
+
* (times) or "higher" (scores, streaks), persisted through a structural
|
|
23
|
+
* key-value storage (pass `localStorage` in a browser, a stub in tests, or
|
|
24
|
+
* `null` for in-memory only). Corrupt or unavailable storage degrades to an
|
|
25
|
+
* empty book — a record write never throws into a game tick.
|
|
26
|
+
*/
|
|
27
|
+
export declare function createRecordBook<K extends string>(config: RecordBookConfig<K>): RecordBook<K>;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
function loadStored(config, storage) {
|
|
2
|
+
if (storage === null)
|
|
3
|
+
return {};
|
|
4
|
+
let raw;
|
|
5
|
+
try {
|
|
6
|
+
raw = storage.getItem(config.key);
|
|
7
|
+
}
|
|
8
|
+
catch {
|
|
9
|
+
return {};
|
|
10
|
+
}
|
|
11
|
+
if (raw === null)
|
|
12
|
+
return {};
|
|
13
|
+
let parsed;
|
|
14
|
+
try {
|
|
15
|
+
parsed = JSON.parse(raw);
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return {};
|
|
19
|
+
}
|
|
20
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
21
|
+
return {};
|
|
22
|
+
const best = {};
|
|
23
|
+
for (const field of Object.keys(config.fields)) {
|
|
24
|
+
const value = parsed[field];
|
|
25
|
+
if (typeof value === "number" && Number.isFinite(value))
|
|
26
|
+
best[field] = value;
|
|
27
|
+
}
|
|
28
|
+
return best;
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* A personal-best record book: named numeric fields each racing toward "lower"
|
|
32
|
+
* (times) or "higher" (scores, streaks), persisted through a structural
|
|
33
|
+
* key-value storage (pass `localStorage` in a browser, a stub in tests, or
|
|
34
|
+
* `null` for in-memory only). Corrupt or unavailable storage degrades to an
|
|
35
|
+
* empty book — a record write never throws into a game tick.
|
|
36
|
+
*/
|
|
37
|
+
export function createRecordBook(config) {
|
|
38
|
+
const storage = config.storage ?? null;
|
|
39
|
+
let best = loadStored(config, storage);
|
|
40
|
+
function persist() {
|
|
41
|
+
if (storage === null)
|
|
42
|
+
return;
|
|
43
|
+
try {
|
|
44
|
+
storage.setItem(config.key, JSON.stringify(best));
|
|
45
|
+
}
|
|
46
|
+
catch {
|
|
47
|
+
return;
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
return {
|
|
51
|
+
best() {
|
|
52
|
+
return { ...best };
|
|
53
|
+
},
|
|
54
|
+
bestOf(field) {
|
|
55
|
+
return best[field] ?? null;
|
|
56
|
+
},
|
|
57
|
+
submit(run) {
|
|
58
|
+
const improved = [];
|
|
59
|
+
for (const field of Object.keys(config.fields)) {
|
|
60
|
+
const value = run[field];
|
|
61
|
+
if (value === undefined || !Number.isFinite(value))
|
|
62
|
+
continue;
|
|
63
|
+
const current = best[field];
|
|
64
|
+
const beats = current === undefined || (config.fields[field] === "lower" ? value < current : value > current);
|
|
65
|
+
if (beats) {
|
|
66
|
+
best[field] = value;
|
|
67
|
+
improved.push(field);
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
if (improved.length > 0)
|
|
71
|
+
persist();
|
|
72
|
+
return { improved, best: { ...best } };
|
|
73
|
+
},
|
|
74
|
+
clear() {
|
|
75
|
+
best = {};
|
|
76
|
+
if (storage === null)
|
|
77
|
+
return;
|
|
78
|
+
try {
|
|
79
|
+
storage.removeItem(config.key);
|
|
80
|
+
}
|
|
81
|
+
catch {
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
},
|
|
85
|
+
};
|
|
86
|
+
}
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import type { StatModifierSet } from "../stats/statModifiers.js";
|
|
2
|
+
export type TalentRequirement = string | {
|
|
3
|
+
nodeId: string;
|
|
4
|
+
rank: number;
|
|
5
|
+
};
|
|
6
|
+
export interface TalentNodeDef<TStat extends string = string> {
|
|
7
|
+
id: string;
|
|
8
|
+
branch?: string;
|
|
9
|
+
maxRank: number;
|
|
10
|
+
requires?: readonly TalentRequirement[];
|
|
11
|
+
requiresPointsInBranch?: number;
|
|
12
|
+
modifiersPerRank?: StatModifierSet<TStat>;
|
|
13
|
+
grantsAbilities?: readonly string[];
|
|
14
|
+
}
|
|
15
|
+
export type TalentAllocateReason = "unknown-node" | "max-rank" | "no-points" | "requires" | "branch-points";
|
|
16
|
+
export type TalentAllocateResult = {
|
|
17
|
+
ok: true;
|
|
18
|
+
} | {
|
|
19
|
+
ok: false;
|
|
20
|
+
reason: TalentAllocateReason;
|
|
21
|
+
};
|
|
22
|
+
export interface ResolvedTalents<TStat extends string = string> {
|
|
23
|
+
stats: StatModifierSet<TStat>;
|
|
24
|
+
abilities: readonly string[];
|
|
25
|
+
}
|
|
26
|
+
export interface TalentSnapshot {
|
|
27
|
+
points: number;
|
|
28
|
+
ranks: Record<string, number>;
|
|
29
|
+
}
|
|
30
|
+
export interface TalentTreeConfig<TStat extends string = string> {
|
|
31
|
+
nodes: readonly TalentNodeDef<TStat>[];
|
|
32
|
+
points?: number;
|
|
33
|
+
}
|
|
34
|
+
export interface TalentTree<TStat extends string = string> {
|
|
35
|
+
rank(nodeId: string): number;
|
|
36
|
+
pointsAvailable(): number;
|
|
37
|
+
pointsSpent(): number;
|
|
38
|
+
pointsInBranch(branch: string): number;
|
|
39
|
+
grantPoints(amount: number): void;
|
|
40
|
+
canAllocate(nodeId: string): TalentAllocateResult;
|
|
41
|
+
allocate(nodeId: string): TalentAllocateResult;
|
|
42
|
+
resolved(): ResolvedTalents<TStat>;
|
|
43
|
+
reset(): void;
|
|
44
|
+
snapshot(): TalentSnapshot;
|
|
45
|
+
hydrate(snapshot: TalentSnapshot): void;
|
|
46
|
+
}
|
|
47
|
+
export declare function createTalentTree<TStat extends string = string>(config: TalentTreeConfig<TStat>): TalentTree<TStat>;
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
const DEFAULT_BRANCH = "";
|
|
2
|
+
export function createTalentTree(config) {
|
|
3
|
+
const nodesById = new Map();
|
|
4
|
+
for (const node of config.nodes)
|
|
5
|
+
nodesById.set(node.id, node);
|
|
6
|
+
const ranks = new Map();
|
|
7
|
+
let points = Math.max(0, config.points ?? 0);
|
|
8
|
+
let cache = null;
|
|
9
|
+
function rank(nodeId) {
|
|
10
|
+
return ranks.get(nodeId) ?? 0;
|
|
11
|
+
}
|
|
12
|
+
function branchOf(node) {
|
|
13
|
+
return node.branch ?? DEFAULT_BRANCH;
|
|
14
|
+
}
|
|
15
|
+
function pointsInBranch(branch) {
|
|
16
|
+
let total = 0;
|
|
17
|
+
for (const node of nodesById.values()) {
|
|
18
|
+
if (branchOf(node) === branch)
|
|
19
|
+
total += rank(node.id);
|
|
20
|
+
}
|
|
21
|
+
return total;
|
|
22
|
+
}
|
|
23
|
+
function pointsSpent() {
|
|
24
|
+
let total = 0;
|
|
25
|
+
for (const value of ranks.values())
|
|
26
|
+
total += value;
|
|
27
|
+
return total;
|
|
28
|
+
}
|
|
29
|
+
function requirementUnmet(requirement) {
|
|
30
|
+
const reqNodeId = typeof requirement === "string" ? requirement : requirement.nodeId;
|
|
31
|
+
const reqRank = typeof requirement === "string" ? 1 : requirement.rank;
|
|
32
|
+
return rank(reqNodeId) < reqRank;
|
|
33
|
+
}
|
|
34
|
+
function canAllocate(nodeId) {
|
|
35
|
+
const node = nodesById.get(nodeId);
|
|
36
|
+
if (!node)
|
|
37
|
+
return { ok: false, reason: "unknown-node" };
|
|
38
|
+
const currentRank = rank(nodeId);
|
|
39
|
+
if (currentRank >= node.maxRank)
|
|
40
|
+
return { ok: false, reason: "max-rank" };
|
|
41
|
+
if (points < 1)
|
|
42
|
+
return { ok: false, reason: "no-points" };
|
|
43
|
+
for (const requirement of node.requires ?? []) {
|
|
44
|
+
if (requirementUnmet(requirement))
|
|
45
|
+
return { ok: false, reason: "requires" };
|
|
46
|
+
}
|
|
47
|
+
if (node.requiresPointsInBranch !== undefined) {
|
|
48
|
+
const inBranch = pointsInBranch(branchOf(node)) - currentRank;
|
|
49
|
+
if (inBranch < node.requiresPointsInBranch)
|
|
50
|
+
return { ok: false, reason: "branch-points" };
|
|
51
|
+
}
|
|
52
|
+
return { ok: true };
|
|
53
|
+
}
|
|
54
|
+
function resolve() {
|
|
55
|
+
const stats = {};
|
|
56
|
+
const abilities = [];
|
|
57
|
+
const seenAbilities = new Set();
|
|
58
|
+
for (const node of nodesById.values()) {
|
|
59
|
+
const nodeRank = rank(node.id);
|
|
60
|
+
if (nodeRank <= 0)
|
|
61
|
+
continue;
|
|
62
|
+
if (node.modifiersPerRank) {
|
|
63
|
+
for (const stat of Object.keys(node.modifiersPerRank)) {
|
|
64
|
+
const modifier = node.modifiersPerRank[stat];
|
|
65
|
+
if (!modifier)
|
|
66
|
+
continue;
|
|
67
|
+
const entry = stats[stat] ?? {};
|
|
68
|
+
if (modifier.add !== undefined)
|
|
69
|
+
entry.add = (entry.add ?? 0) + modifier.add * nodeRank;
|
|
70
|
+
if (modifier.multiply !== undefined) {
|
|
71
|
+
entry.multiply = (entry.multiply ?? 1) * Math.pow(modifier.multiply, nodeRank);
|
|
72
|
+
}
|
|
73
|
+
stats[stat] = entry;
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (node.grantsAbilities) {
|
|
77
|
+
for (const ability of node.grantsAbilities) {
|
|
78
|
+
if (seenAbilities.has(ability))
|
|
79
|
+
continue;
|
|
80
|
+
seenAbilities.add(ability);
|
|
81
|
+
abilities.push(ability);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
}
|
|
85
|
+
return { stats, abilities };
|
|
86
|
+
}
|
|
87
|
+
return {
|
|
88
|
+
rank,
|
|
89
|
+
pointsAvailable() {
|
|
90
|
+
return points;
|
|
91
|
+
},
|
|
92
|
+
pointsSpent,
|
|
93
|
+
pointsInBranch,
|
|
94
|
+
grantPoints(amount) {
|
|
95
|
+
if (amount > 0)
|
|
96
|
+
points += amount;
|
|
97
|
+
},
|
|
98
|
+
canAllocate,
|
|
99
|
+
allocate(nodeId) {
|
|
100
|
+
const result = canAllocate(nodeId);
|
|
101
|
+
if (!result.ok)
|
|
102
|
+
return result;
|
|
103
|
+
points -= 1;
|
|
104
|
+
ranks.set(nodeId, rank(nodeId) + 1);
|
|
105
|
+
cache = null;
|
|
106
|
+
return { ok: true };
|
|
107
|
+
},
|
|
108
|
+
resolved() {
|
|
109
|
+
if (cache === null)
|
|
110
|
+
cache = resolve();
|
|
111
|
+
return cache;
|
|
112
|
+
},
|
|
113
|
+
reset() {
|
|
114
|
+
points += pointsSpent();
|
|
115
|
+
ranks.clear();
|
|
116
|
+
cache = null;
|
|
117
|
+
},
|
|
118
|
+
snapshot() {
|
|
119
|
+
const snapshotRanks = {};
|
|
120
|
+
for (const [nodeId, value] of ranks) {
|
|
121
|
+
if (value > 0)
|
|
122
|
+
snapshotRanks[nodeId] = value;
|
|
123
|
+
}
|
|
124
|
+
return { points, ranks: snapshotRanks };
|
|
125
|
+
},
|
|
126
|
+
hydrate(snapshot) {
|
|
127
|
+
ranks.clear();
|
|
128
|
+
points = snapshot.points;
|
|
129
|
+
for (const [nodeId, value] of Object.entries(snapshot.ranks)) {
|
|
130
|
+
const node = nodesById.get(nodeId);
|
|
131
|
+
if (!node)
|
|
132
|
+
continue;
|
|
133
|
+
const clamped = Math.max(0, Math.min(value, node.maxRank));
|
|
134
|
+
if (clamped > 0)
|
|
135
|
+
ranks.set(nodeId, clamped);
|
|
136
|
+
}
|
|
137
|
+
cache = null;
|
|
138
|
+
},
|
|
139
|
+
};
|
|
140
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export * from "./runtime/save.js";
|
|
2
2
|
export * from "./runtime/snapshot.js";
|
|
3
|
+
export * from "./runtime/worldSnapshot.js";
|
|
4
|
+
export * from "./runtime/worldReplication.js";
|
|
5
|
+
export * from "./game/connectedPlayers.js";
|
|
6
|
+
export * from "./runtime/hostedGameRunner.js";
|
|
7
|
+
export * from "./runtime/hostedWorldSession.js";
|
|
8
|
+
export * from "./runtime/worldMirror.js";
|
|
9
|
+
export * from "./runtime/worldChannel.js";
|
|
3
10
|
export * from "./runtime/commandRunner.js";
|
|
4
11
|
export * from "./runtime/gameRuntime.js";
|
|
5
12
|
export * from "./runtime/adapter.js";
|
|
6
13
|
export * from "./runtime/transport.js";
|
|
14
|
+
export * from "./runtime/visibility.js";
|
package/dist/index.js
CHANGED
|
@@ -1,6 +1,14 @@
|
|
|
1
1
|
export * from "./runtime/save.js";
|
|
2
2
|
export * from "./runtime/snapshot.js";
|
|
3
|
+
export * from "./runtime/worldSnapshot.js";
|
|
4
|
+
export * from "./runtime/worldReplication.js";
|
|
5
|
+
export * from "./game/connectedPlayers.js";
|
|
6
|
+
export * from "./runtime/hostedGameRunner.js";
|
|
7
|
+
export * from "./runtime/hostedWorldSession.js";
|
|
8
|
+
export * from "./runtime/worldMirror.js";
|
|
9
|
+
export * from "./runtime/worldChannel.js";
|
|
3
10
|
export * from "./runtime/commandRunner.js";
|
|
4
11
|
export * from "./runtime/gameRuntime.js";
|
|
5
12
|
export * from "./runtime/adapter.js";
|
|
6
13
|
export * from "./runtime/transport.js";
|
|
14
|
+
export * from "./runtime/visibility.js";
|
|
@@ -29,6 +29,7 @@ export type ActionCodes<TCode extends string = string> = readonly TCode[] | {
|
|
|
29
29
|
toggle?: readonly TCode[];
|
|
30
30
|
repeatMs?: number;
|
|
31
31
|
};
|
|
32
|
+
/** Maps each game action name to the input codes (hold/toggle keys, repeat rate) that trigger it. */
|
|
32
33
|
export type ActionCodesMap<TAction extends string = string, TCode extends string = string> = Record<TAction, ActionCodes<TCode>>;
|
|
33
34
|
export declare function actionRepeatMs(codes: ActionCodes | undefined): number | undefined;
|
|
34
35
|
export interface ShouldDispatchActionInput {
|
|
@@ -53,6 +54,7 @@ export declare function isHotbarSlotAction(action: string): boolean;
|
|
|
53
54
|
* yield null so the shell can handle them itself.
|
|
54
55
|
*/
|
|
55
56
|
export declare function resolveActionCommand(action: string, has: (command: string) => boolean, reserved: ReadonlySet<string>): string | null;
|
|
57
|
+
/** Short display label for a raw key/button code (e.g. `"KeyW"` → `"W"`). */
|
|
56
58
|
export declare function bindingLabel(code: string): string;
|
|
57
59
|
/** Short display label for an action's first bound code, or null when unbound. */
|
|
58
60
|
export declare function actionLabel(map: ActionCodesMap, action: string): string | null;
|
|
@@ -127,6 +127,7 @@ const CODE_LABELS = {
|
|
|
127
127
|
ArrowLeft: "←",
|
|
128
128
|
ArrowRight: "→",
|
|
129
129
|
};
|
|
130
|
+
/** Short display label for a raw key/button code (e.g. `"KeyW"` → `"W"`). */
|
|
130
131
|
export function bindingLabel(code) {
|
|
131
132
|
const direct = CODE_LABELS[code];
|
|
132
133
|
if (direct !== undefined)
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { type PointerAxisBinding, type PointerAxisState } from "./pointerAxis.js";
|
|
1
2
|
export interface AxisInput {
|
|
2
3
|
throttle: number;
|
|
3
4
|
brake: number;
|
|
@@ -9,6 +10,8 @@ export type AxisName = keyof AxisInput;
|
|
|
9
10
|
export interface AxisBinding {
|
|
10
11
|
positive: readonly string[];
|
|
11
12
|
negative?: readonly string[];
|
|
13
|
+
/** Pointer-position source for this axis (#293) — takes over from the key lists whenever a pointer is active. */
|
|
14
|
+
pointer?: PointerAxisBinding;
|
|
12
15
|
}
|
|
13
16
|
export type AxisBindingMap = Record<AxisName, AxisBinding>;
|
|
14
17
|
export interface AxisChannelConfig {
|
|
@@ -25,8 +28,9 @@ export declare function rampToward(current: number, target: number, rate: number
|
|
|
25
28
|
/**
|
|
26
29
|
* Analog control channel — distinct from the digital action bindings. Throttle/brake/steer/handbrake
|
|
27
30
|
* are continuous values ramped from held keys (a keyboard feels like a pedal) or driven directly from
|
|
28
|
-
* a gamepad axis via `setAnalog`. `sample(dt, isDown)` folds the current held-key state into
|
|
29
|
-
* smoothed value; a `
|
|
31
|
+
* a gamepad axis via `setAnalog`. `sample(dt, isDown, pointer?)` folds the current held-key state into
|
|
32
|
+
* the smoothed value; a binding's `pointer` source takes over from its keys while a pointer is active,
|
|
33
|
+
* and a `setAnalog` override replaces both for that axis until cleared.
|
|
30
34
|
*/
|
|
31
35
|
export declare class AxisChannel {
|
|
32
36
|
private readonly bindings;
|
|
@@ -38,6 +42,32 @@ export declare class AxisChannel {
|
|
|
38
42
|
clearAnalog(axis: AxisName): void;
|
|
39
43
|
reset(): void;
|
|
40
44
|
get value(): AxisInput;
|
|
41
|
-
sample(dt: number, isDown: (code: string) => boolean): AxisInput;
|
|
45
|
+
sample(dt: number, isDown: (code: string) => boolean, pointer?: PointerAxisState | null): AxisInput;
|
|
42
46
|
}
|
|
43
47
|
export declare const DRIVE_AXIS_BINDINGS: AxisBindingMap;
|
|
48
|
+
export interface GenericAxisConfig<TAxes extends string> {
|
|
49
|
+
bindings: Record<TAxes, AxisBinding>;
|
|
50
|
+
/** Per-axis value range; unlisted axes default to bipolar `[-1, 1]` (use `{ min: 0, max: 1 }` for pedals). */
|
|
51
|
+
ranges?: Partial<Record<TAxes, AxisRange>>;
|
|
52
|
+
smoothing?: number;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* The held-key-ramping analog channel for any axis schema (#282.7) — drones (pitch/roll/strafe),
|
|
56
|
+
* boats, mechs — not just the four car axes `AxisChannel` hardcodes. Same semantics: keys ramp,
|
|
57
|
+
* `setAnalog` overrides, a binding's `pointer` source takes over while a pointer is active.
|
|
58
|
+
*/
|
|
59
|
+
export interface GenericAxisChannel<TAxes extends string> {
|
|
60
|
+
sample(dt: number, isDown: (code: string) => boolean, pointer?: PointerAxisState | null): Record<TAxes, number>;
|
|
61
|
+
setAnalog(axis: TAxes, value: number): void;
|
|
62
|
+
clearAnalog(axis: TAxes): void;
|
|
63
|
+
reset(): void;
|
|
64
|
+
readonly value: Record<TAxes, number>;
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Instantaneous (unsmoothed) analog sample of an axis binding map — the per-frame target an
|
|
68
|
+
* `AxisChannel` ramps toward, exposed as a pure read for `ctx.input.axis` and headless sampling.
|
|
69
|
+
* Each axis is its binding's positive-minus-negative held state (or its pointer source while a pointer
|
|
70
|
+
* is active), clamped to the axis range; axes without a listed range default to bipolar `[-1, 1]`.
|
|
71
|
+
*/
|
|
72
|
+
export declare function sampleAxisBindings<TAxes extends string>(bindings: Record<TAxes, AxisBinding>, isDown: (code: string) => boolean, pointer?: PointerAxisState | null, ranges?: Partial<Record<TAxes, AxisRange>>): Record<TAxes, number>;
|
|
73
|
+
export declare function createAxisChannel<TAxes extends string>(config: GenericAxisConfig<TAxes>): GenericAxisChannel<TAxes>;
|