@jgengine/core 0.8.0 → 0.9.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 +16 -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.js +1 -1
- package/dist/ai/threat.d.ts +2 -0
- package/dist/ai/threat.js +34 -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/projectiles.d.ts +33 -12
- package/dist/combat/projectiles.js +201 -129
- 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 +40 -0
- package/dist/combat/shotOrigin.js +57 -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/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/controlGate.d.ts +4 -0
- package/dist/game/controlGate.js +7 -0
- package/dist/game/defineGame.d.ts +2 -0
- package/dist/game/events.d.ts +10 -0
- package/dist/game/gamePhase.d.ts +20 -0
- package/dist/game/gamePhase.js +21 -0
- package/dist/game/lootTable.d.ts +10 -1
- package/dist/game/lootTable.js +27 -7
- package/dist/game/playableGame.d.ts +50 -1
- 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 +29 -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 +1 -0
- package/dist/index.js +1 -0
- package/dist/input/axisInput.d.ts +26 -3
- package/dist/input/axisInput.js +44 -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/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +23 -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/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/seedLink.d.ts +5 -0
- package/dist/random/seedLink.js +35 -0
- 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 +21 -0
- package/dist/runtime/gameContext.js +184 -9
- 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/inputSnapshot.d.ts +5 -0
- package/dist/runtime/inputSnapshot.js +5 -0
- package/dist/runtime/motionIntents.d.ts +14 -6
- package/dist/runtime/motionIntents.js +20 -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/scene/colliders.d.ts +44 -0
- package/dist/scene/colliders.js +77 -0
- package/dist/scene/entityStore.d.ts +22 -5
- package/dist/scene/entityStore.js +59 -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.js +8 -2
- package/dist/survival/decayMeter.d.ts +1 -1
- package/dist/survival/decayMeter.js +0 -1
- 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/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/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 +198 -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 +10 -2
- package/dist/world/environmentSummary.js +11 -2
- package/dist/world/features.d.ts +56 -8
- package/dist/world/features.js +24 -3
- 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/terrain.d.ts +42 -3
- package/dist/world/terrain.js +197 -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 +2585 -1704
- package/package.json +12 -2
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import type { Waypoint } from "./pathFollow.js";
|
|
2
|
+
export interface TimetableStop {
|
|
3
|
+
at: Waypoint;
|
|
4
|
+
/** Seconds the mover holds at this waypoint before departing. */
|
|
5
|
+
dwellSeconds?: number;
|
|
6
|
+
}
|
|
7
|
+
export interface RouteTimetableConfig {
|
|
8
|
+
waypoints: readonly (Waypoint | TimetableStop)[];
|
|
9
|
+
/** World units per second between stops. */
|
|
10
|
+
speed: number;
|
|
11
|
+
/** Wrap from the last waypoint back to the first (a circular line). Default `false` — the mover holds at the terminus. */
|
|
12
|
+
loop?: boolean;
|
|
13
|
+
/** Default dwell applied at every waypoint without its own `dwellSeconds`; default `0`. */
|
|
14
|
+
dwellSeconds?: number;
|
|
15
|
+
/** Shifts the whole timetable so `t = 0` lands mid-route — staggers several movers on one route. */
|
|
16
|
+
offsetSeconds?: number;
|
|
17
|
+
}
|
|
18
|
+
export interface TimetablePose {
|
|
19
|
+
position: Waypoint;
|
|
20
|
+
/** Yaw (radians) of the travel direction, matching engine `rotationY`; holds through dwells. */
|
|
21
|
+
heading: number;
|
|
22
|
+
/** Index of the segment being travelled (or the stop being dwelt at). */
|
|
23
|
+
segment: number;
|
|
24
|
+
/** True while holding at a stop. */
|
|
25
|
+
dwelling: boolean;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* A deterministic mover over an authored route: position is a pure function of absolute time, so a
|
|
29
|
+
* forecast preview (`poseAt(t + 30)`) and the live mover can never disagree — the seam `nav/pathFollow`'s
|
|
30
|
+
* dt-incremental state can't offer. Trains, ferries, patrol shuttles, crushers: anything on a timetable.
|
|
31
|
+
*/
|
|
32
|
+
export interface RouteTimetable {
|
|
33
|
+
/** Seconds for one full cycle (travel + dwells); non-loop routes hold at the terminus after this. */
|
|
34
|
+
readonly cycleSeconds: number;
|
|
35
|
+
readonly distance: number;
|
|
36
|
+
poseAt(t: number): TimetablePose;
|
|
37
|
+
positionAt(t: number): Waypoint;
|
|
38
|
+
/** Cycle progress in `[0, 1]` — clamps at `1` on a non-looping route's terminus. */
|
|
39
|
+
progressAt(t: number): number;
|
|
40
|
+
}
|
|
41
|
+
export declare function createRouteTimetable(config: RouteTimetableConfig): RouteTimetable;
|
|
@@ -0,0 +1,94 @@
|
|
|
1
|
+
function stopOf(entry) {
|
|
2
|
+
return Array.isArray(entry) ? { at: entry } : entry;
|
|
3
|
+
}
|
|
4
|
+
export function createRouteTimetable(config) {
|
|
5
|
+
if (!(config.speed > 0))
|
|
6
|
+
throw new Error(`timetable speed must be positive, got ${config.speed}`);
|
|
7
|
+
const stops = config.waypoints.map(stopOf);
|
|
8
|
+
if (stops.length < 2)
|
|
9
|
+
throw new Error("timetable needs at least two waypoints");
|
|
10
|
+
const loop = config.loop ?? false;
|
|
11
|
+
const defaultDwell = Math.max(0, config.dwellSeconds ?? 0);
|
|
12
|
+
const offset = config.offsetSeconds ?? 0;
|
|
13
|
+
const legs = [];
|
|
14
|
+
let clock = 0;
|
|
15
|
+
let distance = 0;
|
|
16
|
+
const legCount = loop ? stops.length : stops.length - 1;
|
|
17
|
+
for (let i = 0; i < legCount; i += 1) {
|
|
18
|
+
const from = stops[i];
|
|
19
|
+
const to = stops[(i + 1) % stops.length];
|
|
20
|
+
const dwell = Math.max(0, from.dwellSeconds ?? defaultDwell);
|
|
21
|
+
const dx = to.at[0] - from.at[0];
|
|
22
|
+
const dy = to.at[1] - from.at[1];
|
|
23
|
+
const dz = to.at[2] - from.at[2];
|
|
24
|
+
const length = Math.hypot(dx, dy, dz);
|
|
25
|
+
const heading = length > 1e-9 ? Math.atan2(dx, dz) : 0;
|
|
26
|
+
legs.push({
|
|
27
|
+
from: from.at,
|
|
28
|
+
to: to.at,
|
|
29
|
+
heading,
|
|
30
|
+
startSeconds: clock,
|
|
31
|
+
travelSeconds: length / config.speed,
|
|
32
|
+
dwellSeconds: dwell,
|
|
33
|
+
});
|
|
34
|
+
clock += dwell + length / config.speed;
|
|
35
|
+
distance += length;
|
|
36
|
+
}
|
|
37
|
+
const cycleSeconds = clock;
|
|
38
|
+
const terminus = stops[stops.length - 1].at;
|
|
39
|
+
const lastHeading = legs[legs.length - 1].heading;
|
|
40
|
+
function poseAt(t) {
|
|
41
|
+
let local = t + offset;
|
|
42
|
+
if (loop) {
|
|
43
|
+
local = ((local % cycleSeconds) + cycleSeconds) % cycleSeconds;
|
|
44
|
+
}
|
|
45
|
+
else {
|
|
46
|
+
local = Math.max(0, local);
|
|
47
|
+
if (local >= cycleSeconds) {
|
|
48
|
+
return { position: terminus, heading: lastHeading, segment: legs.length - 1, dwelling: true };
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
let leg = legs[legs.length - 1];
|
|
52
|
+
let segment = legs.length - 1;
|
|
53
|
+
for (let i = 0; i < legs.length; i += 1) {
|
|
54
|
+
const candidate = legs[i];
|
|
55
|
+
if (local < candidate.startSeconds + candidate.dwellSeconds + candidate.travelSeconds) {
|
|
56
|
+
leg = candidate;
|
|
57
|
+
segment = i;
|
|
58
|
+
break;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
const intoLeg = local - leg.startSeconds;
|
|
62
|
+
if (intoLeg < leg.dwellSeconds) {
|
|
63
|
+
const previous = legs[(segment - 1 + legs.length) % legs.length];
|
|
64
|
+
const heading = segment === 0 && !loop ? leg.heading : previous.heading;
|
|
65
|
+
return { position: leg.from, heading, segment, dwelling: true };
|
|
66
|
+
}
|
|
67
|
+
const travelled = intoLeg - leg.dwellSeconds;
|
|
68
|
+
const fraction = leg.travelSeconds <= 0 ? 1 : Math.min(1, travelled / leg.travelSeconds);
|
|
69
|
+
return {
|
|
70
|
+
position: [
|
|
71
|
+
leg.from[0] + (leg.to[0] - leg.from[0]) * fraction,
|
|
72
|
+
leg.from[1] + (leg.to[1] - leg.from[1]) * fraction,
|
|
73
|
+
leg.from[2] + (leg.to[2] - leg.from[2]) * fraction,
|
|
74
|
+
],
|
|
75
|
+
heading: leg.heading,
|
|
76
|
+
segment,
|
|
77
|
+
dwelling: false,
|
|
78
|
+
};
|
|
79
|
+
}
|
|
80
|
+
return {
|
|
81
|
+
cycleSeconds,
|
|
82
|
+
distance,
|
|
83
|
+
poseAt,
|
|
84
|
+
positionAt: (t) => poseAt(t).position,
|
|
85
|
+
progressAt(t) {
|
|
86
|
+
if (cycleSeconds <= 0)
|
|
87
|
+
return 1;
|
|
88
|
+
const local = t + offset;
|
|
89
|
+
if (loop)
|
|
90
|
+
return (((local % cycleSeconds) + cycleSeconds) % cycleSeconds) / cycleSeconds;
|
|
91
|
+
return Math.max(0, Math.min(1, local / cycleSeconds));
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
@@ -15,6 +15,10 @@ export interface BuoyantBodyConfig {
|
|
|
15
15
|
topSpeed?: number;
|
|
16
16
|
turnRate?: number;
|
|
17
17
|
turnSpeedRef?: number;
|
|
18
|
+
/** Water-current acceleration (units/s²) sampled at the hull each tick and applied while submerged — feed a `windZones`/`flowTube` sample or a fixed vector. */
|
|
19
|
+
current?: (x: number, z: number, time: number) => readonly [number, number];
|
|
20
|
+
/** How much more current a broadside hull catches than a bow-on one (`1` = uniform, default; `2` = side-on drifts twice as hard). */
|
|
21
|
+
currentBroadside?: number;
|
|
18
22
|
}
|
|
19
23
|
/**
|
|
20
24
|
* Floats a {@link PhysicsWorld} body on a CPU {@link WaterSurface}: each hull sample point pushes the
|
|
@@ -38,6 +42,8 @@ export declare class BuoyantBody {
|
|
|
38
42
|
private readonly topSpeed;
|
|
39
43
|
private readonly turnRate;
|
|
40
44
|
private readonly turnSpeedRef;
|
|
45
|
+
private readonly current;
|
|
46
|
+
private readonly currentBroadside;
|
|
41
47
|
private submergedFlag;
|
|
42
48
|
constructor(world: PhysicsWorld, config: BuoyantBodyConfig);
|
|
43
49
|
get position(): [number, number, number];
|
package/dist/physics/buoyancy.js
CHANGED
|
@@ -26,6 +26,8 @@ export class BuoyantBody {
|
|
|
26
26
|
topSpeed;
|
|
27
27
|
turnRate;
|
|
28
28
|
turnSpeedRef;
|
|
29
|
+
current;
|
|
30
|
+
currentBroadside;
|
|
29
31
|
submergedFlag = false;
|
|
30
32
|
constructor(world, config) {
|
|
31
33
|
this.world = world;
|
|
@@ -42,6 +44,8 @@ export class BuoyantBody {
|
|
|
42
44
|
this.topSpeed = config.topSpeed ?? 14;
|
|
43
45
|
this.turnRate = config.turnRate ?? 1.2;
|
|
44
46
|
this.turnSpeedRef = config.turnSpeedRef ?? 4;
|
|
47
|
+
this.current = config.current ?? null;
|
|
48
|
+
this.currentBroadside = config.currentBroadside ?? 1;
|
|
45
49
|
}
|
|
46
50
|
get position() {
|
|
47
51
|
return [this.world.posX[this.body], this.world.posY[this.body], this.world.posZ[this.body]];
|
|
@@ -81,6 +85,16 @@ export class BuoyantBody {
|
|
|
81
85
|
const drag = Math.max(0, 1 - this.linearDrag * dt);
|
|
82
86
|
w.velX[b] *= drag;
|
|
83
87
|
w.velZ[b] *= drag;
|
|
88
|
+
if (this.current !== null) {
|
|
89
|
+
const [cx, cz] = this.current(w.posX[b], w.posZ[b], time);
|
|
90
|
+
const magnitude = Math.hypot(cx, cz);
|
|
91
|
+
if (magnitude > 0) {
|
|
92
|
+
const alignment = Math.abs((cx * sin + cz * cos) / magnitude);
|
|
93
|
+
const scale = this.currentBroadside + (1 - this.currentBroadside) * alignment;
|
|
94
|
+
w.velX[b] += cx * scale * dt;
|
|
95
|
+
w.velZ[b] += cz * scale * dt;
|
|
96
|
+
}
|
|
97
|
+
}
|
|
84
98
|
this.submergedFlag = true;
|
|
85
99
|
w.wake(b);
|
|
86
100
|
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
export type FlowVec3 = readonly [number, number, number];
|
|
2
|
+
export interface FlowTubeConfig {
|
|
3
|
+
from: FlowVec3;
|
|
4
|
+
to: FlowVec3;
|
|
5
|
+
/** Tube radius; flow strength fades from the axis to zero here. */
|
|
6
|
+
radius: number;
|
|
7
|
+
/** Axial flow speed on the core at full spool. */
|
|
8
|
+
strength: number;
|
|
9
|
+
/** Radial falloff exponent — `1` linear, `2` (default) keeps a fat core with soft edges. */
|
|
10
|
+
falloff?: number;
|
|
11
|
+
/** Extra reach past each end where the axial envelope fades to zero; default `0` (hard caps). */
|
|
12
|
+
capFalloff?: number;
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* An axial corridor of directional flow with radial core falloff and a spool scalar — fan tunnels,
|
|
16
|
+
* updraft shafts, river narrows, thruster wash. Pure math: sample `velocityAt(point, spool)` and add
|
|
17
|
+
* it to whatever integrator moves the body (walk controller drift, `BuoyantBody`, a custom sim).
|
|
18
|
+
*/
|
|
19
|
+
export interface FlowTube {
|
|
20
|
+
readonly axis: FlowVec3;
|
|
21
|
+
readonly length: number;
|
|
22
|
+
/** Flow velocity at a world point, scaled by `spool` in `[0, 1]` (default `1`); zero outside the tube. */
|
|
23
|
+
velocityAt(point: FlowVec3, spool?: number): FlowVec3;
|
|
24
|
+
/** The radial × axial envelope in `[0, 1]` at a world point — feed particle density, audio gain, rumble. */
|
|
25
|
+
intensityAt(point: FlowVec3): number;
|
|
26
|
+
}
|
|
27
|
+
export declare function createFlowTube(config: FlowTubeConfig): FlowTube;
|
|
28
|
+
/** Sum several tubes' flow at one point — each entry carries its own spool. */
|
|
29
|
+
export declare function combineFlowVelocity(tubes: readonly (FlowTube | {
|
|
30
|
+
tube: FlowTube;
|
|
31
|
+
spool: number;
|
|
32
|
+
})[], point: FlowVec3): FlowVec3;
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export function createFlowTube(config) {
|
|
2
|
+
if (!(config.radius > 0))
|
|
3
|
+
throw new Error(`flow tube radius must be positive, got ${config.radius}`);
|
|
4
|
+
const [fx, fy, fz] = config.from;
|
|
5
|
+
const dx = config.to[0] - fx;
|
|
6
|
+
const dy = config.to[1] - fy;
|
|
7
|
+
const dz = config.to[2] - fz;
|
|
8
|
+
const length = Math.hypot(dx, dy, dz);
|
|
9
|
+
if (length <= 1e-9)
|
|
10
|
+
throw new Error("flow tube needs distinct from/to points");
|
|
11
|
+
const axis = [dx / length, dy / length, dz / length];
|
|
12
|
+
const falloff = config.falloff ?? 2;
|
|
13
|
+
const capFalloff = Math.max(0, config.capFalloff ?? 0);
|
|
14
|
+
function envelopeAt(point) {
|
|
15
|
+
const px = point[0] - fx;
|
|
16
|
+
const py = point[1] - fy;
|
|
17
|
+
const pz = point[2] - fz;
|
|
18
|
+
const along = px * axis[0] + py * axis[1] + pz * axis[2];
|
|
19
|
+
if (along < -capFalloff || along > length + capFalloff)
|
|
20
|
+
return 0;
|
|
21
|
+
const axial = capFalloff <= 0
|
|
22
|
+
? along >= 0 && along <= length
|
|
23
|
+
? 1
|
|
24
|
+
: 0
|
|
25
|
+
: along < 0
|
|
26
|
+
? 1 + along / capFalloff
|
|
27
|
+
: along > length
|
|
28
|
+
? 1 - (along - length) / capFalloff
|
|
29
|
+
: 1;
|
|
30
|
+
if (axial <= 0)
|
|
31
|
+
return 0;
|
|
32
|
+
const rx = px - axis[0] * along;
|
|
33
|
+
const ry = py - axis[1] * along;
|
|
34
|
+
const rz = pz - axis[2] * along;
|
|
35
|
+
const radial = Math.hypot(rx, ry, rz);
|
|
36
|
+
if (radial >= config.radius)
|
|
37
|
+
return 0;
|
|
38
|
+
return axial * Math.pow(1 - radial / config.radius, falloff);
|
|
39
|
+
}
|
|
40
|
+
return {
|
|
41
|
+
axis,
|
|
42
|
+
length,
|
|
43
|
+
velocityAt(point, spool = 1) {
|
|
44
|
+
const scale = envelopeAt(point) * config.strength * Math.max(0, Math.min(1, spool));
|
|
45
|
+
return scale <= 0 ? [0, 0, 0] : [axis[0] * scale, axis[1] * scale, axis[2] * scale];
|
|
46
|
+
},
|
|
47
|
+
intensityAt: envelopeAt,
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/** Sum several tubes' flow at one point — each entry carries its own spool. */
|
|
51
|
+
export function combineFlowVelocity(tubes, point) {
|
|
52
|
+
let x = 0;
|
|
53
|
+
let y = 0;
|
|
54
|
+
let z = 0;
|
|
55
|
+
for (const entry of tubes) {
|
|
56
|
+
const tube = "velocityAt" in entry ? entry : entry.tube;
|
|
57
|
+
const spool = "velocityAt" in entry ? 1 : entry.spool;
|
|
58
|
+
const [vx, vy, vz] = tube.velocityAt(point, spool);
|
|
59
|
+
x += vx;
|
|
60
|
+
y += vy;
|
|
61
|
+
z += vz;
|
|
62
|
+
}
|
|
63
|
+
return [x, y, z];
|
|
64
|
+
}
|
|
@@ -27,6 +27,33 @@ export declare class ForceVolume {
|
|
|
27
27
|
constructor(config: ForceVolumeConfig);
|
|
28
28
|
apply(world: PhysicsWorld, dt: number): void;
|
|
29
29
|
}
|
|
30
|
+
/** One tick's force math for a body outside `PhysicsWorld` — apply the returned velocity in any custom integrator. */
|
|
31
|
+
export declare function applyVolumeForce(velocity: readonly [number, number, number], force: readonly [number, number, number], mode: ForceMode, dt: number): readonly [number, number, number];
|
|
32
|
+
export interface VolumeTriggerConfig {
|
|
33
|
+
bounds: PhysicsBounds;
|
|
34
|
+
}
|
|
35
|
+
export interface VolumeTriggerStep<TId> {
|
|
36
|
+
/** Ids inside this tick that were outside last tick — the boost-pad edge. */
|
|
37
|
+
entered: readonly TId[];
|
|
38
|
+
/** Every id inside this tick. */
|
|
39
|
+
inside: readonly TId[];
|
|
40
|
+
/** Ids inside last tick that left. */
|
|
41
|
+
exited: readonly TId[];
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* The enter-once membership tracking from `ForceVolume`, freed from `PhysicsWorld`'s body indices
|
|
45
|
+
* (#286.8): feed any integrator's `{ id, position }` list each tick and act on the edges — apply
|
|
46
|
+
* `applyVolumeForce` on `entered` for a boost pad, on `inside` for a fan.
|
|
47
|
+
*/
|
|
48
|
+
export interface VolumeTrigger<TId> {
|
|
49
|
+
readonly bounds: PhysicsBounds;
|
|
50
|
+
step(bodies: Iterable<{
|
|
51
|
+
id: TId;
|
|
52
|
+
position: readonly [number, number, number];
|
|
53
|
+
}>): VolumeTriggerStep<TId>;
|
|
54
|
+
reset(): void;
|
|
55
|
+
}
|
|
56
|
+
export declare function createVolumeTrigger<TId = string>(config: VolumeTriggerConfig): VolumeTrigger<TId>;
|
|
30
57
|
export interface PlatformCarryConfig {
|
|
31
58
|
/** Vertical gap between a rider's base and the platform top counted as "standing on". Default 0.12. */
|
|
32
59
|
contactTolerance?: number;
|
|
@@ -63,6 +63,44 @@ export class ForceVolume {
|
|
|
63
63
|
this.members = next;
|
|
64
64
|
}
|
|
65
65
|
}
|
|
66
|
+
/** One tick's force math for a body outside `PhysicsWorld` — apply the returned velocity in any custom integrator. */
|
|
67
|
+
export function applyVolumeForce(velocity, force, mode, dt) {
|
|
68
|
+
if (mode === "velocity")
|
|
69
|
+
return force;
|
|
70
|
+
if (mode === "accelerate") {
|
|
71
|
+
return [velocity[0] + force[0] * dt, velocity[1] + force[1] * dt, velocity[2] + force[2] * dt];
|
|
72
|
+
}
|
|
73
|
+
return [velocity[0] + force[0], velocity[1] + force[1], velocity[2] + force[2]];
|
|
74
|
+
}
|
|
75
|
+
export function createVolumeTrigger(config) {
|
|
76
|
+
let members = new Set();
|
|
77
|
+
return {
|
|
78
|
+
bounds: config.bounds,
|
|
79
|
+
step(bodies) {
|
|
80
|
+
const entered = [];
|
|
81
|
+
const insideNow = [];
|
|
82
|
+
const next = new Set();
|
|
83
|
+
for (const body of bodies) {
|
|
84
|
+
if (!inside(config.bounds, body.position[0], body.position[1], body.position[2]))
|
|
85
|
+
continue;
|
|
86
|
+
next.add(body.id);
|
|
87
|
+
insideNow.push(body.id);
|
|
88
|
+
if (!members.has(body.id))
|
|
89
|
+
entered.push(body.id);
|
|
90
|
+
}
|
|
91
|
+
const exited = [];
|
|
92
|
+
for (const id of members) {
|
|
93
|
+
if (!next.has(id))
|
|
94
|
+
exited.push(id);
|
|
95
|
+
}
|
|
96
|
+
members = next;
|
|
97
|
+
return { entered, inside: insideNow, exited };
|
|
98
|
+
},
|
|
99
|
+
reset() {
|
|
100
|
+
members = new Set();
|
|
101
|
+
},
|
|
102
|
+
};
|
|
103
|
+
}
|
|
66
104
|
/**
|
|
67
105
|
* Carries bodies standing on a moving platform by composing their transform with the platform's
|
|
68
106
|
* per-`step` delta — moving/rotating lifts and conveyor floors (Fall Guys, Gang Beasts). The
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import type { AxisInput } from "../input/axisInput.js";
|
|
2
|
+
import { type GripCurve } from "./vehicleBody.js";
|
|
3
|
+
export interface KinematicVehicleTuning {
|
|
4
|
+
engineAccel: number;
|
|
5
|
+
brakeAccel: number;
|
|
6
|
+
topSpeed: number;
|
|
7
|
+
reverseSpeed: number;
|
|
8
|
+
turnRate: number;
|
|
9
|
+
turnSpeedRef: number;
|
|
10
|
+
grip?: GripCurve;
|
|
11
|
+
gripStrength: number;
|
|
12
|
+
handbrakeGrip: number;
|
|
13
|
+
/** Forward-speed decay per second when coasting (sleds, boats); default `0`. */
|
|
14
|
+
rollingResistance?: number;
|
|
15
|
+
}
|
|
16
|
+
export interface KinematicVehicleOptions {
|
|
17
|
+
position?: readonly [number, number, number];
|
|
18
|
+
heading?: number;
|
|
19
|
+
/** Grip multiplier by world position — off-track gravel, ice patches; default `1` everywhere. */
|
|
20
|
+
surfaceFriction?: (x: number, z: number) => number;
|
|
21
|
+
/** Extra velocity damping per second by world position (rough ground drag); default `0`. */
|
|
22
|
+
dragAt?: (x: number, z: number) => number;
|
|
23
|
+
}
|
|
24
|
+
export interface KinematicVehicleStep {
|
|
25
|
+
position: readonly [number, number, number];
|
|
26
|
+
heading: number;
|
|
27
|
+
/** Signed speed along the heading, world units/s. */
|
|
28
|
+
forwardSpeed: number;
|
|
29
|
+
lateralSpeed: number;
|
|
30
|
+
/** Lateral/forward slip ratio driving the grip curve — drift indicators key off it. */
|
|
31
|
+
slip: number;
|
|
32
|
+
/** The `surfaceFriction` sample used this tick (`< 1` = off the ideal surface). */
|
|
33
|
+
surface: number;
|
|
34
|
+
}
|
|
35
|
+
/**
|
|
36
|
+
* The pure-kinematic arcade car every racing game hand-rolled (#282.1): steer-yaw scaled by speed,
|
|
37
|
+
* throttle/brake acceleration, and a grip-curve lateral-slip bleed — no `PhysicsWorld`, no wheels,
|
|
38
|
+
* just the drift-friendly integration the three shipped racers proved out. Games keep their flavor
|
|
39
|
+
* (drift meters, boost, off-track rules) via `surfaceFriction`/`dragAt` hooks and the returned slip.
|
|
40
|
+
*/
|
|
41
|
+
export interface KinematicVehicle {
|
|
42
|
+
tick(dt: number, axis: AxisInput): KinematicVehicleStep;
|
|
43
|
+
pose(): {
|
|
44
|
+
position: readonly [number, number, number];
|
|
45
|
+
heading: number;
|
|
46
|
+
};
|
|
47
|
+
velocity(): readonly [number, number];
|
|
48
|
+
/** Scale the current velocity (boost pads, hard collisions). */
|
|
49
|
+
scaleVelocity(factor: number): void;
|
|
50
|
+
resetTo(position: readonly [number, number, number], heading: number): void;
|
|
51
|
+
}
|
|
52
|
+
export declare function createKinematicVehicle(tuning: KinematicVehicleTuning, options?: KinematicVehicleOptions): KinematicVehicle;
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
import { steerYaw } from "../movement/steering.js";
|
|
2
|
+
import { DEFAULT_GRIP_CURVE, sampleGripCurve } from "./vehicleBody.js";
|
|
3
|
+
export function createKinematicVehicle(tuning, options = {}) {
|
|
4
|
+
const grip = tuning.grip ?? DEFAULT_GRIP_CURVE;
|
|
5
|
+
const rollingResistance = tuning.rollingResistance ?? 0;
|
|
6
|
+
const surfaceFriction = options.surfaceFriction ?? (() => 1);
|
|
7
|
+
const dragAt = options.dragAt ?? (() => 0);
|
|
8
|
+
let x = options.position?.[0] ?? 0;
|
|
9
|
+
let y = options.position?.[1] ?? 0;
|
|
10
|
+
let z = options.position?.[2] ?? 0;
|
|
11
|
+
let heading = options.heading ?? 0;
|
|
12
|
+
let vx = 0;
|
|
13
|
+
let vz = 0;
|
|
14
|
+
function forward() {
|
|
15
|
+
return [Math.sin(heading), Math.cos(heading)];
|
|
16
|
+
}
|
|
17
|
+
return {
|
|
18
|
+
tick(dt, axis) {
|
|
19
|
+
const [fx0, fz0] = forward();
|
|
20
|
+
const speed = vx * fx0 + vz * fz0;
|
|
21
|
+
const steerScale = Math.min(1, Math.abs(speed) / tuning.turnSpeedRef);
|
|
22
|
+
const direction = speed >= 0 ? 1 : -1;
|
|
23
|
+
heading = steerYaw(heading, axis.steer * steerScale * direction, tuning.turnRate, dt);
|
|
24
|
+
const [fx, fz] = forward();
|
|
25
|
+
let accel = 0;
|
|
26
|
+
if (axis.throttle > 0 && speed < tuning.topSpeed)
|
|
27
|
+
accel += axis.throttle * tuning.engineAccel;
|
|
28
|
+
if (axis.brake > 0) {
|
|
29
|
+
if (speed > 0.2)
|
|
30
|
+
accel -= axis.brake * tuning.brakeAccel;
|
|
31
|
+
else if (speed > -tuning.reverseSpeed)
|
|
32
|
+
accel -= axis.brake * tuning.engineAccel;
|
|
33
|
+
}
|
|
34
|
+
vx += fx * accel * dt;
|
|
35
|
+
vz += fz * accel * dt;
|
|
36
|
+
let forwardSpeed = vx * fx + vz * fz;
|
|
37
|
+
const lateralSpeed = -vx * fz + vz * fx;
|
|
38
|
+
const surface = surfaceFriction(x, z);
|
|
39
|
+
const slip = Math.abs(lateralSpeed) / (Math.abs(forwardSpeed) + 1);
|
|
40
|
+
const handbrakeFactor = 1 - axis.handbrake * (1 - tuning.handbrakeGrip);
|
|
41
|
+
const gripValue = sampleGripCurve(grip, slip) * surface * handbrakeFactor;
|
|
42
|
+
const keep = Math.max(0, 1 - gripValue * tuning.gripStrength * dt);
|
|
43
|
+
const keptLateral = lateralSpeed * keep;
|
|
44
|
+
if (rollingResistance > 0)
|
|
45
|
+
forwardSpeed *= Math.max(0, 1 - rollingResistance * dt);
|
|
46
|
+
vx = fx * forwardSpeed - fz * keptLateral;
|
|
47
|
+
vz = fz * forwardSpeed + fx * keptLateral;
|
|
48
|
+
const drag = dragAt(x, z);
|
|
49
|
+
let dragDamp = 1;
|
|
50
|
+
if (drag > 0) {
|
|
51
|
+
dragDamp = Math.max(0, 1 - drag * dt);
|
|
52
|
+
vx *= dragDamp;
|
|
53
|
+
vz *= dragDamp;
|
|
54
|
+
}
|
|
55
|
+
x += vx * dt;
|
|
56
|
+
z += vz * dt;
|
|
57
|
+
return {
|
|
58
|
+
position: [x, y, z],
|
|
59
|
+
heading,
|
|
60
|
+
forwardSpeed: vx * fx + vz * fz,
|
|
61
|
+
lateralSpeed: keptLateral * dragDamp,
|
|
62
|
+
slip,
|
|
63
|
+
surface,
|
|
64
|
+
};
|
|
65
|
+
},
|
|
66
|
+
pose: () => ({ position: [x, y, z], heading }),
|
|
67
|
+
velocity: () => [vx, vz],
|
|
68
|
+
scaleVelocity(factor) {
|
|
69
|
+
vx *= factor;
|
|
70
|
+
vz *= factor;
|
|
71
|
+
},
|
|
72
|
+
resetTo(position, nextHeading) {
|
|
73
|
+
x = position[0];
|
|
74
|
+
y = position[1];
|
|
75
|
+
z = position[2];
|
|
76
|
+
heading = nextHeading;
|
|
77
|
+
vx = 0;
|
|
78
|
+
vz = 0;
|
|
79
|
+
},
|
|
80
|
+
};
|
|
81
|
+
}
|
|
@@ -72,11 +72,15 @@ export type AddBodyOptions = BoxBodyOptions | SphereBodyOptions;
|
|
|
72
72
|
export declare const SHAPE_BOX = 0;
|
|
73
73
|
export declare const SHAPE_SPHERE = 1;
|
|
74
74
|
/**
|
|
75
|
-
* `
|
|
76
|
-
*
|
|
77
|
-
* holds a fixed separation
|
|
75
|
+
* Hard bilateral: `fixed` pins the shared anchor (3-DOF), `hinge` pins only the plane
|
|
76
|
+
* perpendicular to `axis` (free along the axis — translational cylindrical), `distance`
|
|
77
|
+
* holds a fixed separation. `spring` is a soft PD drive toward `restLength`.
|
|
78
78
|
*/
|
|
79
79
|
export type JointKind = "hinge" | "distance" | "spring" | "fixed";
|
|
80
|
+
export declare const JOINT_FIXED = 0;
|
|
81
|
+
export declare const JOINT_HINGE = 1;
|
|
82
|
+
export declare const JOINT_DISTANCE = 2;
|
|
83
|
+
export declare const JOINT_SPRING = 3;
|
|
80
84
|
export interface JointOptions {
|
|
81
85
|
/** First body index. */
|
|
82
86
|
bodyA: number;
|
|
@@ -94,7 +98,7 @@ export interface JointOptions {
|
|
|
94
98
|
damping?: number;
|
|
95
99
|
/** Clamp on the per-substep spring impulse (a follow-point that never yanks). Default unbounded. */
|
|
96
100
|
maxImpulse?: number;
|
|
97
|
-
/**
|
|
101
|
+
/** Unit hinge axis (world). Required semantics for `hingeJoint`; free motion along this axis. Default [0,1,0]. */
|
|
98
102
|
axis?: readonly [number, number, number];
|
|
99
103
|
}
|
|
100
104
|
/** A contact reported to `onCollision`. The object is reused each call — read/copy, never retain. */
|
|
@@ -157,12 +161,12 @@ export declare class PhysicsWorld {
|
|
|
157
161
|
private readonly numCells;
|
|
158
162
|
private readonly cellStart;
|
|
159
163
|
private readonly cursor;
|
|
160
|
-
private
|
|
164
|
+
private sorted;
|
|
161
165
|
private readonly bodyCell;
|
|
162
166
|
private readonly awakeList;
|
|
163
167
|
private readonly awakeSlot;
|
|
164
168
|
private awakeCount;
|
|
165
|
-
private readonly
|
|
169
|
+
private readonly jKind;
|
|
166
170
|
private readonly jActive;
|
|
167
171
|
private readonly jBodyA;
|
|
168
172
|
private readonly jBodyB;
|
|
@@ -172,6 +176,9 @@ export declare class PhysicsWorld {
|
|
|
172
176
|
private readonly jAnchorBX;
|
|
173
177
|
private readonly jAnchorBY;
|
|
174
178
|
private readonly jAnchorBZ;
|
|
179
|
+
private readonly jAxisX;
|
|
180
|
+
private readonly jAxisY;
|
|
181
|
+
private readonly jAxisZ;
|
|
175
182
|
private readonly jRest;
|
|
176
183
|
private readonly jStiffness;
|
|
177
184
|
private readonly jDamping;
|