@jgengine/core 0.11.0 → 0.13.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +52 -0
- package/dist/ai/populationDirector.d.ts +103 -0
- package/dist/ai/populationDirector.js +190 -0
- package/dist/ai/pursuit.d.ts +56 -0
- package/dist/ai/pursuit.js +53 -0
- package/dist/authoring.d.ts +1 -1
- package/dist/authoring.js +1 -1
- package/dist/combat/effects.d.ts +11 -4
- package/dist/combat/effects.js +24 -17
- package/dist/combat/magazine.d.ts +13 -0
- package/dist/combat/magazine.js +27 -0
- package/dist/combat/vfxPresets.d.ts +176 -0
- package/dist/combat/vfxPresets.js +74 -0
- package/dist/combat/weaponFire.d.ts +114 -0
- package/dist/combat/weaponFire.js +96 -0
- package/dist/combat.d.ts +5 -2
- package/dist/combat.js +4 -1
- package/dist/devtools/controls.d.ts +38 -0
- package/dist/devtools/controls.js +181 -0
- package/dist/devtools/devtools.d.ts +5 -197
- package/dist/devtools/devtools.js +38 -774
- package/dist/devtools/discover.d.ts +32 -0
- package/dist/devtools/discover.js +240 -0
- package/dist/devtools/fallbackSeams.d.ts +50 -0
- package/dist/devtools/fallbackSeams.js +111 -0
- package/dist/devtools/frame.d.ts +15 -0
- package/dist/devtools/frame.js +247 -0
- package/dist/devtools/logs.d.ts +15 -0
- package/dist/devtools/logs.js +86 -0
- package/dist/devtools/overrides.d.ts +16 -0
- package/dist/devtools/overrides.js +66 -0
- package/dist/devtools/types.d.ts +212 -0
- package/dist/devtools/types.js +1 -0
- package/dist/devtools/urlFlags.d.ts +22 -0
- package/dist/devtools/urlFlags.js +62 -0
- package/dist/economy/auctionBook.d.ts +122 -0
- package/dist/economy/auctionBook.js +213 -0
- package/dist/economy/priceHistory.d.ts +44 -0
- package/dist/economy/priceHistory.js +89 -0
- package/dist/economy/unlockPoints.d.ts +92 -0
- package/dist/economy/unlockPoints.js +103 -0
- package/dist/editor/commandApply.d.ts +5 -0
- package/dist/editor/commandApply.js +755 -0
- package/dist/editor/commands.d.ts +48 -3
- package/dist/editor/commands.js +40 -754
- package/dist/editor/directives.d.ts +94 -0
- package/dist/editor/directives.js +206 -0
- package/dist/editor/document.d.ts +37 -2
- package/dist/editor/document.js +422 -6
- package/dist/editor/entityCatalog.d.ts +42 -0
- package/dist/editor/entityCatalog.js +101 -0
- package/dist/editor/environment.d.ts +74 -0
- package/dist/editor/environment.js +169 -0
- package/dist/editor/index.d.ts +26 -1
- package/dist/editor/index.js +8 -1
- package/dist/editor/minimap.d.ts +31 -0
- package/dist/editor/minimap.js +73 -0
- package/dist/editor/streamer.d.ts +66 -0
- package/dist/editor/streamer.js +68 -0
- package/dist/editor/types.d.ts +143 -0
- package/dist/editor/world.d.ts +136 -0
- package/dist/editor/world.js +303 -0
- package/dist/game/breeding.d.ts +161 -0
- package/dist/game/breeding.js +181 -0
- package/dist/game/cameraConfig.d.ts +451 -0
- package/dist/game/cameraConfig.js +1 -0
- package/dist/game/clipRoles.d.ts +51 -0
- package/dist/game/clipRoles.js +142 -0
- package/dist/game/defineGame.d.ts +16 -9
- package/dist/game/defineGame.js +14 -6
- package/dist/game/defineSystem.d.ts +2 -0
- package/dist/game/defineSystem.js +2 -0
- package/dist/game/events.d.ts +28 -0
- package/dist/game/partAnimation.d.ts +78 -0
- package/dist/game/partAnimation.js +147 -0
- package/dist/game/playableGame.d.ts +72 -440
- package/dist/game/playableGame.js +1 -1
- package/dist/game/progression.d.ts +35 -0
- package/dist/game/progression.js +22 -1
- package/dist/game/quest.js +1 -3
- package/dist/game/tribe.d.ts +184 -0
- package/dist/game/tribe.js +432 -0
- package/dist/gameplay.d.ts +3 -1
- package/dist/gameplay.js +3 -1
- package/dist/inventory/encumbrance.d.ts +69 -0
- package/dist/inventory/encumbrance.js +68 -0
- package/dist/inventory/inventoryModel.d.ts +24 -8
- package/dist/inventory/inventoryModel.js +47 -8
- package/dist/item/durability.js +1 -3
- package/dist/meta/changelog.d.ts +1 -1
- package/dist/meta/changelog.js +48 -1
- package/dist/movement/movementModel.d.ts +15 -1
- package/dist/movement/movementModel.js +33 -15
- package/dist/movement/playerMovement.js +102 -2
- package/dist/multiplayer/combatSnapshot.js +13 -8
- package/dist/multiplayer/presenceModel.d.ts +2 -0
- package/dist/multiplayer/presenceModel.js +8 -0
- package/dist/physics/drivableVehicle.d.ts +2 -0
- package/dist/physics/drivableVehicle.js +1 -1
- package/dist/physics/flightDynamics.d.ts +82 -0
- package/dist/physics/flightDynamics.js +165 -0
- package/dist/physics/gravityField.d.ts +25 -0
- package/dist/physics/gravityField.js +41 -0
- package/dist/physics/kinematicVehicle.d.ts +87 -0
- package/dist/physics/kinematicVehicle.js +193 -11
- package/dist/physics/physicsWorld.d.ts +26 -0
- package/dist/physics/physicsWorld.js +109 -117
- package/dist/physics/vehicleObstacles.d.ts +44 -0
- package/dist/physics/vehicleObstacles.js +115 -0
- package/dist/random/rng.d.ts +4 -1
- package/dist/random/rng.js +4 -1
- package/dist/runtime/adapter.d.ts +13 -0
- package/dist/runtime/adapter.js +13 -0
- package/dist/runtime/context/combat.d.ts +51 -0
- package/dist/runtime/context/combat.js +131 -0
- package/dist/runtime/context/combatFx.js +15 -5
- package/dist/runtime/context/deathLoot.d.ts +30 -0
- package/dist/runtime/context/deathLoot.js +39 -0
- package/dist/runtime/context/player.d.ts +48 -0
- package/dist/runtime/context/player.js +125 -0
- package/dist/runtime/context/scene.d.ts +60 -0
- package/dist/runtime/context/scene.js +329 -0
- package/dist/runtime/context/snapshotCodecs.d.ts +12 -0
- package/dist/runtime/context/snapshotCodecs.js +26 -0
- package/dist/runtime/descriptors/baseline.js +26 -3
- package/dist/runtime/descriptors/features.d.ts +5 -0
- package/dist/runtime/descriptors/features.js +20 -2
- package/dist/runtime/gameContext.d.ts +2 -428
- package/dist/runtime/gameContext.js +133 -500
- package/dist/runtime/gameContextTypes.d.ts +500 -0
- package/dist/runtime/gameContextTypes.js +1 -0
- package/dist/runtime/headlessRunner.d.ts +17 -2
- package/dist/runtime/headlessRunner.js +15 -0
- package/dist/runtime/hostPolicy.d.ts +51 -0
- package/dist/runtime/hostPolicy.js +68 -0
- package/dist/runtime/hostedGameRunner.d.ts +3 -1
- package/dist/runtime/hostedGameRunner.js +1 -0
- package/dist/runtime/hostedWorldSession.d.ts +3 -1
- package/dist/runtime/hostedWorldSession.js +1 -0
- package/dist/runtime/worldSnapshot.d.ts +13 -1
- package/dist/runtime/worldSnapshot.js +15 -3
- package/dist/scene/assetCatalog.d.ts +7 -0
- package/dist/scene/assetSpace.js +1 -3
- package/dist/scene/authoredTriggers.d.ts +50 -0
- package/dist/scene/authoredTriggers.js +114 -0
- package/dist/scene/builtinSceneKinds.js +8 -3
- package/dist/scene/colliders.d.ts +79 -0
- package/dist/scene/colliders.js +170 -0
- package/dist/scene/collisionMesh.d.ts +94 -0
- package/dist/scene/collisionMesh.js +699 -0
- package/dist/scene/companion.d.ts +147 -0
- package/dist/scene/companion.js +193 -0
- package/dist/scene/entityStats.d.ts +7 -9
- package/dist/scene/entityStats.js +19 -15
- package/dist/scene/sceneKinds.d.ts +16 -0
- package/dist/scene/sceneKinds.js +4 -0
- package/dist/scene/sceneRaycast.js +15 -0
- package/dist/stats/spawnLevelStats.d.ts +110 -0
- package/dist/stats/spawnLevelStats.js +140 -0
- package/dist/stats/statModifiers.d.ts +16 -0
- package/dist/stats/statModifiers.js +30 -1
- package/dist/stats/statPool.d.ts +70 -0
- package/dist/stats/statPool.js +62 -0
- package/dist/store/changeSignal.d.ts +3 -0
- package/dist/store/changeSignal.js +2 -0
- package/dist/store/observableKeyedStore.d.ts +2 -0
- package/dist/store/observableKeyedStore.js +1 -0
- package/dist/survival/decayMeter.js +1 -3
- package/dist/survival/regionHealth.js +1 -3
- package/dist/ui/hudLayout.d.ts +5 -0
- package/dist/ui/hudLayout.js +17 -1
- package/dist/ui/panelModel.d.ts +120 -0
- package/dist/ui/panelModel.js +168 -0
- package/dist/ui.d.ts +1 -0
- package/dist/ui.js +1 -0
- package/dist/world/authoredEntities.d.ts +29 -0
- package/dist/world/authoredEntities.js +29 -0
- package/dist/world/authoredObjects.d.ts +15 -1
- package/dist/world/authoredObjects.js +13 -1
- package/dist/world/authoredSpawn.d.ts +39 -0
- package/dist/world/authoredSpawn.js +37 -0
- package/dist/world/buildSockets.d.ts +152 -0
- package/dist/world/buildSockets.js +188 -0
- package/dist/world/buildingLots.d.ts +84 -0
- package/dist/world/buildingLots.js +119 -0
- package/dist/world/buildings.d.ts +6 -0
- package/dist/world/cityBlocks.d.ts +152 -0
- package/dist/world/cityBlocks.js +670 -0
- package/dist/world/cityContent.d.ts +77 -0
- package/dist/world/cityContent.js +261 -0
- package/dist/world/cityGenerator.d.ts +41 -0
- package/dist/world/cityGenerator.js +58 -0
- package/dist/world/cityGeometry.d.ts +114 -0
- package/dist/world/cityGeometry.js +573 -0
- package/dist/world/cityKind.d.ts +314 -0
- package/dist/world/cityKind.js +1578 -0
- package/dist/world/environmentSummary.js +57 -1
- package/dist/world/features.d.ts +53 -5
- package/dist/world/features.js +15 -3
- package/dist/world/grassKind.js +2 -2
- package/dist/world/markers.d.ts +50 -4
- package/dist/world/markers.js +49 -0
- package/dist/world/massing.js +1 -1
- package/dist/world/minimapBake.d.ts +78 -0
- package/dist/world/minimapBake.js +208 -0
- package/dist/world/minimapTrack.d.ts +17 -0
- package/dist/world/minimapTrack.js +22 -0
- package/dist/world/place.d.ts +166 -0
- package/dist/world/place.js +96 -0
- package/dist/world/placeAsset.js +3 -1
- package/dist/world/poleLineKind.d.ts +40 -0
- package/dist/world/poleLineKind.js +97 -0
- package/dist/world/polyline.js +9 -2
- package/dist/world/resourceNode.d.ts +109 -0
- package/dist/world/resourceNode.js +140 -0
- package/dist/world/roads.d.ts +19 -2
- package/dist/world/roads.js +60 -3
- package/dist/world/scatter.js +2 -20
- package/dist/world/streetGenerator.d.ts +159 -0
- package/dist/world/streetGenerator.js +684 -0
- package/dist/world/streets.d.ts +103 -1
- package/dist/world/streets.js +273 -0
- package/dist/world/terraform.js +58 -88
- package/dist/world/terrain.d.ts +65 -1
- package/dist/world/terrain.js +119 -2
- package/dist/world.d.ts +14 -5
- package/dist/world.js +12 -3
- package/package.json +1 -1
- package/skills/game-design/SKILL.md +28 -0
- package/skills/game-design/agents/openai.yaml +4 -0
- package/skills/game-design/references/application-playbook.md +170 -0
- package/skills/game-design/references/field-guide.md +288 -0
- package/skills/jgengine/SKILL.md +8 -4
- package/skills/jgengine/api.md +182 -81
- package/skills/jgengine/capabilities.md +104 -2
- package/skills/jgengine/recipes/minimal-game.md +88 -0
- package/skills/jgengine-combat/SKILL.md +19 -1
- package/skills/jgengine-combat/api.md +75 -2
- package/skills/jgengine-combat/capabilities.md +61 -1
- package/skills/jgengine-combat/recipes/portable-damage-effects.md +197 -0
- package/skills/jgengine-combat/recipes/portable-runtime-state.md +154 -0
- package/skills/jgengine-combat/recipes/portable-stat-pools.md +122 -0
- package/skills/jgengine-combat/recipes/portable-weapon-plumbing.md +156 -0
- package/skills/jgengine-combat/recipes/visible-attacks.md +81 -0
- package/skills/jgengine-combat/reference.md +33 -4
- package/skills/jgengine-gameplay/SKILL.md +8 -0
- package/skills/jgengine-gameplay/api.md +182 -26
- package/skills/jgengine-gameplay/capabilities.md +96 -2
- package/skills/jgengine-gameplay/recipes/portable-xp-leveling.md +168 -0
- package/skills/jgengine-gameplay/reference-systems.md +10 -5
- package/skills/jgengine-world/SKILL.md +13 -0
- package/skills/jgengine-world/api.md +453 -21
- package/skills/jgengine-world/capabilities.md +112 -2
- package/skills/jgengine-world/recipes/commanding-entities.md +128 -0
- package/skills/jgengine-world/reference.md +43 -4
- package/skills/jgengine/reference-cartridge.md +0 -72
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { randomSeedFrom, stepRandomSeed } from "../random/rng.js";
|
|
1
2
|
function cloneStats(stats) {
|
|
2
3
|
return { ...stats };
|
|
3
4
|
}
|
|
@@ -21,14 +22,18 @@ export function cloneSnapshot(snapshot) {
|
|
|
21
22
|
capturedAt: snapshot.capturedAt,
|
|
22
23
|
});
|
|
23
24
|
}
|
|
24
|
-
|
|
25
|
-
|
|
25
|
+
/**
|
|
26
|
+
* Deterministic per-replay PRNG built from the shared {@link randomSeedFrom} /
|
|
27
|
+
* {@link stepRandomSeed} primitives. `randomSeedFrom` takes the raw integer seed
|
|
28
|
+
* without string hashing, so the emitted sequence is byte-identical to the former
|
|
29
|
+
* inline mulberry32 — replay determinism across peers is preserved.
|
|
30
|
+
*/
|
|
31
|
+
function seededPrng(seed) {
|
|
32
|
+
let cursor = randomSeedFrom(seed);
|
|
26
33
|
return () => {
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
t = (t + Math.imul(t ^ (t >>> 7), 61 | t)) ^ t;
|
|
31
|
-
return ((t ^ (t >>> 14)) >>> 0) / 4294967296;
|
|
34
|
+
const [value, next] = stepRandomSeed(cursor);
|
|
35
|
+
cursor = next;
|
|
36
|
+
return value;
|
|
32
37
|
};
|
|
33
38
|
}
|
|
34
39
|
function toLive(snapshot, rules) {
|
|
@@ -43,7 +48,7 @@ export function replayCombat(a, b, rules) {
|
|
|
43
48
|
const maxRounds = rules.maxRounds ?? 100;
|
|
44
49
|
const critChance = rules.critChance ?? 0;
|
|
45
50
|
const critMultiplier = rules.critMultiplier ?? 2;
|
|
46
|
-
const prng =
|
|
51
|
+
const prng = seededPrng((a.seed ^ (b.seed * 0x9e3779b1)) >>> 0);
|
|
47
52
|
const liveA = toLive(a, rules);
|
|
48
53
|
const liveB = toLive(b, rules);
|
|
49
54
|
const blows = [];
|
|
@@ -32,6 +32,8 @@ export interface PoseSyncRules {
|
|
|
32
32
|
/** Unchanged poses refresh the keep-alive stamp at most this often. */
|
|
33
33
|
keepAliveRefreshMs: number;
|
|
34
34
|
}
|
|
35
|
+
/** Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex). */
|
|
36
|
+
export declare const DEFAULT_POSE_SYNC_RULES: PoseSyncRules;
|
|
35
37
|
export interface PoseSyncDecision {
|
|
36
38
|
position: {
|
|
37
39
|
x: number;
|
|
@@ -1,3 +1,11 @@
|
|
|
1
|
+
/** Canonical client-authoritative pose-sync tuning shared by every host transport (WS, Convex). */
|
|
2
|
+
export const DEFAULT_POSE_SYNC_RULES = {
|
|
3
|
+
maxSpeed: 12,
|
|
4
|
+
maxVerticalOffset: 3,
|
|
5
|
+
minElapsedSec: 0.05,
|
|
6
|
+
maxElapsedSec: 0.5,
|
|
7
|
+
keepAliveRefreshMs: 10_000,
|
|
8
|
+
};
|
|
1
9
|
function appearanceEqual(a, b) {
|
|
2
10
|
if (a === b)
|
|
3
11
|
return true;
|
|
@@ -12,6 +12,8 @@ export interface DrivableVehicleOptions {
|
|
|
12
12
|
export interface DrivableVehiclePose {
|
|
13
13
|
position: DrivableVehiclePosition;
|
|
14
14
|
rotationY: number;
|
|
15
|
+
rotationX: number;
|
|
16
|
+
rotationZ: number;
|
|
15
17
|
dt: number;
|
|
16
18
|
}
|
|
17
19
|
/** {@link tickDrivableVehicle}'s result — the ready-to-apply pose patch plus the raw sim step for HUD/telemetry reads. */
|
|
@@ -10,7 +10,7 @@ export function tickDrivableVehicle(vehicle, dt, axis, options = {}) {
|
|
|
10
10
|
const [x, y, z] = step.position;
|
|
11
11
|
const resolvedY = options.groundHeight === undefined ? y : options.groundHeight(x, z);
|
|
12
12
|
return {
|
|
13
|
-
pose: { position: [x, resolvedY, z], rotationY: step.heading, dt },
|
|
13
|
+
pose: { position: [x, resolvedY, z], rotationX: step.bodyPitch, rotationY: step.heading, rotationZ: step.bodyRoll, dt },
|
|
14
14
|
step,
|
|
15
15
|
};
|
|
16
16
|
}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
import { type GravityField } from "./gravityField.js";
|
|
2
|
+
/** Three-dimensional world-space vector used by the flight model. */
|
|
3
|
+
export type FlightVector = readonly [number, number, number];
|
|
4
|
+
/** Supported aerodynamic/propulsion families. */
|
|
5
|
+
export type AircraftKind = "fixedWing" | "rotorcraft" | "vtol";
|
|
6
|
+
/** Normalized pilot inputs for one flight-simulation tick. */
|
|
7
|
+
export interface FlightControlInput {
|
|
8
|
+
throttle: number;
|
|
9
|
+
pitch: number;
|
|
10
|
+
roll: number;
|
|
11
|
+
yaw: number;
|
|
12
|
+
collective?: number;
|
|
13
|
+
airbrake?: number;
|
|
14
|
+
afterburner?: number;
|
|
15
|
+
vectoring?: number;
|
|
16
|
+
}
|
|
17
|
+
/** Angular authority, response, damping, and self-leveling configuration. */
|
|
18
|
+
export interface FlightControlRates {
|
|
19
|
+
pitch: number;
|
|
20
|
+
roll: number;
|
|
21
|
+
yaw: number;
|
|
22
|
+
response: number;
|
|
23
|
+
damping: number;
|
|
24
|
+
stability: number;
|
|
25
|
+
}
|
|
26
|
+
/** Data-first physical tuning shared by all aircraft instances of one catalog type. */
|
|
27
|
+
export interface AircraftTuning {
|
|
28
|
+
kind: AircraftKind;
|
|
29
|
+
mass: number;
|
|
30
|
+
gravity?: number;
|
|
31
|
+
maxThrust: number;
|
|
32
|
+
maxSpeed: number;
|
|
33
|
+
drag: number;
|
|
34
|
+
sideDrag: number;
|
|
35
|
+
lift: number;
|
|
36
|
+
stallSpeed: number;
|
|
37
|
+
controls: FlightControlRates;
|
|
38
|
+
afterburnerMultiplier?: number;
|
|
39
|
+
hoverThrust?: number;
|
|
40
|
+
rotorResponse?: number;
|
|
41
|
+
groundEffectHeight?: number;
|
|
42
|
+
vtolTransitionSpeed?: number;
|
|
43
|
+
groundClearance?: number;
|
|
44
|
+
}
|
|
45
|
+
/** Spawn state and injectable world-field samplers for an aircraft instance. */
|
|
46
|
+
export interface AircraftOptions {
|
|
47
|
+
position?: FlightVector;
|
|
48
|
+
velocity?: FlightVector;
|
|
49
|
+
rotation?: FlightVector;
|
|
50
|
+
wind?: (position: FlightVector) => FlightVector;
|
|
51
|
+
groundHeight?: (x: number, z: number) => number;
|
|
52
|
+
gravityField?: GravityField;
|
|
53
|
+
}
|
|
54
|
+
/** Pose and aerodynamic telemetry returned after one flight tick. */
|
|
55
|
+
export interface AircraftStep {
|
|
56
|
+
position: FlightVector;
|
|
57
|
+
velocity: FlightVector;
|
|
58
|
+
rotation: FlightVector;
|
|
59
|
+
forwardSpeed: number;
|
|
60
|
+
airspeed: number;
|
|
61
|
+
verticalSpeed: number;
|
|
62
|
+
angleOfAttack: number;
|
|
63
|
+
liftFraction: number;
|
|
64
|
+
stalled: boolean;
|
|
65
|
+
grounded: boolean;
|
|
66
|
+
gForce: number;
|
|
67
|
+
}
|
|
68
|
+
/** Stateful six-degree-of-freedom aircraft simulation. */
|
|
69
|
+
export interface AircraftDynamics {
|
|
70
|
+
tick(dt: number, input: FlightControlInput): AircraftStep;
|
|
71
|
+
pose(): {
|
|
72
|
+
position: FlightVector;
|
|
73
|
+
rotation: FlightVector;
|
|
74
|
+
};
|
|
75
|
+
velocity(): FlightVector;
|
|
76
|
+
resetTo(position: FlightVector, rotation?: FlightVector): void;
|
|
77
|
+
setVelocity(velocity: FlightVector): void;
|
|
78
|
+
}
|
|
79
|
+
/** Six-degree-of-freedom arcade flight model for fixed-wing, helicopter, and VTOL aircraft.
|
|
80
|
+
* @capability flight-dynamics simulate fixed-wing, helicopter, and VTOL aircraft
|
|
81
|
+
*/
|
|
82
|
+
export declare function createAircraftDynamics(tuning: AircraftTuning, options?: AircraftOptions): AircraftDynamics;
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { uniformGravity } from "./gravityField.js";
|
|
2
|
+
const clamp = (value, min, max) => Math.max(min, Math.min(max, value));
|
|
3
|
+
const length = (v) => Math.hypot(v[0], v[1], v[2]);
|
|
4
|
+
function axes(rotation) {
|
|
5
|
+
const [pitch, yaw, roll] = rotation;
|
|
6
|
+
const sp = Math.sin(pitch);
|
|
7
|
+
const cp = Math.cos(pitch);
|
|
8
|
+
const sy = Math.sin(yaw);
|
|
9
|
+
const cy = Math.cos(yaw);
|
|
10
|
+
const sr = Math.sin(roll);
|
|
11
|
+
const cr = Math.cos(roll);
|
|
12
|
+
const forward = [sy * cp, sp, cy * cp];
|
|
13
|
+
const flatRight = [cy, 0, -sy];
|
|
14
|
+
const flatUp = [-sy * sp, cp, -cy * sp];
|
|
15
|
+
return {
|
|
16
|
+
forward,
|
|
17
|
+
right: [flatRight[0] * cr + flatUp[0] * sr, flatUp[1] * sr, flatRight[2] * cr + flatUp[2] * sr],
|
|
18
|
+
up: [-flatRight[0] * sr + flatUp[0] * cr, flatUp[1] * cr, -flatRight[2] * sr + flatUp[2] * cr],
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
function dot(a, b) {
|
|
22
|
+
return a[0] * b[0] + a[1] * b[1] + a[2] * b[2];
|
|
23
|
+
}
|
|
24
|
+
function wrapAngle(value) {
|
|
25
|
+
let angle = value;
|
|
26
|
+
while (angle > Math.PI)
|
|
27
|
+
angle -= Math.PI * 2;
|
|
28
|
+
while (angle < -Math.PI)
|
|
29
|
+
angle += Math.PI * 2;
|
|
30
|
+
return angle;
|
|
31
|
+
}
|
|
32
|
+
/** Six-degree-of-freedom arcade flight model for fixed-wing, helicopter, and VTOL aircraft.
|
|
33
|
+
* @capability flight-dynamics simulate fixed-wing, helicopter, and VTOL aircraft
|
|
34
|
+
*/
|
|
35
|
+
export function createAircraftDynamics(tuning, options = {}) {
|
|
36
|
+
let position = [...(options.position ?? [0, 0, 0])];
|
|
37
|
+
let velocity = [...(options.velocity ?? [0, 0, 0])];
|
|
38
|
+
let rotation = [...(options.rotation ?? [0, 0, 0])];
|
|
39
|
+
let angular = [0, 0, 0];
|
|
40
|
+
let rotor = 0;
|
|
41
|
+
let lastVerticalSpeed = velocity[1];
|
|
42
|
+
const gravity = Math.abs(tuning.gravity ?? 9.81);
|
|
43
|
+
const gravityField = options.gravityField ?? uniformGravity([0, -gravity, 0]);
|
|
44
|
+
const windAt = options.wind ?? (() => [0, 0, 0]);
|
|
45
|
+
const groundAt = options.groundHeight ?? (() => 0);
|
|
46
|
+
return {
|
|
47
|
+
tick(dt, input) {
|
|
48
|
+
const stepDt = clamp(dt, 0, 0.05);
|
|
49
|
+
const wind = windAt(position);
|
|
50
|
+
const gravityVector = gravityField.sample(position);
|
|
51
|
+
const gravityMagnitude = Math.max(0.001, length(gravityVector));
|
|
52
|
+
const relative = [velocity[0] - wind[0], velocity[1] - wind[1], velocity[2] - wind[2]];
|
|
53
|
+
const airspeed = length(relative);
|
|
54
|
+
const basis = axes(rotation);
|
|
55
|
+
const forwardSpeed = dot(relative, basis.forward);
|
|
56
|
+
const rightSpeed = dot(relative, basis.right);
|
|
57
|
+
const upSpeed = dot(relative, basis.up);
|
|
58
|
+
const angleOfAttack = Math.atan2(-upSpeed, Math.max(0.01, Math.abs(forwardSpeed)));
|
|
59
|
+
const speedAuthority = clamp(airspeed / Math.max(1, tuning.stallSpeed), 0.12, 1);
|
|
60
|
+
const rotorTarget = tuning.kind === "fixedWing" ? 0 : clamp(input.collective ?? input.throttle, 0, 1);
|
|
61
|
+
const rotorResponse = Math.max(0.01, tuning.rotorResponse ?? 3.5);
|
|
62
|
+
rotor += (rotorTarget - rotor) * (1 - Math.exp(-rotorResponse * stepDt));
|
|
63
|
+
const vectoring = tuning.kind === "vtol" ? clamp(input.vectoring ?? 1, 0, 1) : tuning.kind === "rotorcraft" ? 1 : 0;
|
|
64
|
+
const transition = tuning.kind === "vtol"
|
|
65
|
+
? clamp(Math.max(vectoring, 1 - airspeed / Math.max(1, tuning.vtolTransitionSpeed ?? 32)), 0, 1)
|
|
66
|
+
: vectoring;
|
|
67
|
+
const controlAuthority = transition + (1 - transition) * speedAuthority;
|
|
68
|
+
const rates = tuning.controls;
|
|
69
|
+
const targets = [
|
|
70
|
+
clamp(input.pitch, -1, 1) * rates.pitch * controlAuthority,
|
|
71
|
+
clamp(input.yaw, -1, 1) * rates.yaw * controlAuthority,
|
|
72
|
+
clamp(input.roll, -1, 1) * rates.roll * controlAuthority,
|
|
73
|
+
];
|
|
74
|
+
const response = 1 - Math.exp(-Math.max(0, rates.response) * stepDt);
|
|
75
|
+
angular[0] += (targets[0] - angular[0]) * response;
|
|
76
|
+
angular[1] += (targets[1] - angular[1]) * response;
|
|
77
|
+
angular[2] += (targets[2] - angular[2]) * response;
|
|
78
|
+
const damping = Math.max(0, 1 - rates.damping * stepDt);
|
|
79
|
+
angular[0] *= damping;
|
|
80
|
+
angular[1] *= damping;
|
|
81
|
+
angular[2] *= damping;
|
|
82
|
+
const stability = clamp(rates.stability * stepDt, 0, 1);
|
|
83
|
+
if (Math.abs(input.pitch) < 0.05)
|
|
84
|
+
angular[0] += -rotation[0] * stability;
|
|
85
|
+
if (Math.abs(input.roll) < 0.05)
|
|
86
|
+
angular[2] += -rotation[2] * stability;
|
|
87
|
+
rotation[0] = clamp(rotation[0] + angular[0] * stepDt, -Math.PI * 0.49, Math.PI * 0.49);
|
|
88
|
+
rotation[1] = wrapAngle(rotation[1] + angular[1] * stepDt);
|
|
89
|
+
rotation[2] = wrapAngle(rotation[2] + angular[2] * stepDt);
|
|
90
|
+
const updated = axes(rotation);
|
|
91
|
+
const throttle = clamp(input.throttle, 0, 1);
|
|
92
|
+
const afterburner = 1 + clamp(input.afterburner ?? 0, 0, 1) * ((tuning.afterburnerMultiplier ?? 1) - 1);
|
|
93
|
+
const thrustAccel = tuning.maxThrust * throttle * afterburner / Math.max(0.01, tuning.mass);
|
|
94
|
+
const hoverAccel = (tuning.hoverThrust ?? gravity * tuning.mass * 1.08) * rotor / Math.max(0.01, tuning.mass);
|
|
95
|
+
const ground = groundAt(position[0], position[2]);
|
|
96
|
+
const clearance = Math.max(0, position[1] - ground);
|
|
97
|
+
const groundEffectHeight = Math.max(0.1, tuning.groundEffectHeight ?? 8);
|
|
98
|
+
const groundEffect = transition > 0 ? 1 + clamp(1 - clearance / groundEffectHeight, 0, 1) * 0.18 : 1;
|
|
99
|
+
const liftFraction = clamp((Math.abs(forwardSpeed) / Math.max(0.1, tuning.stallSpeed)) ** 2, 0, 1.35);
|
|
100
|
+
const stallAngle = Math.PI * 0.22;
|
|
101
|
+
const aoaEfficiency = clamp(1 - Math.max(0, Math.abs(angleOfAttack) - stallAngle) / (Math.PI * 0.3), 0.08, 1);
|
|
102
|
+
const wingLift = tuning.lift * liftFraction * aoaEfficiency * (1 - transition);
|
|
103
|
+
const rotorLift = hoverAccel * groundEffect * transition;
|
|
104
|
+
const forwardThrust = thrustAccel * (1 - transition * 0.82);
|
|
105
|
+
const cyclicForward = transition * hoverAccel * Math.sin(rotation[0]) * 0.75;
|
|
106
|
+
const cyclicSide = -transition * hoverAccel * Math.sin(rotation[2]) * 0.75;
|
|
107
|
+
const dragScale = tuning.drag * (1 + clamp(input.airbrake ?? 0, 0, 1) * 4);
|
|
108
|
+
const speedRatio = airspeed / Math.max(1, tuning.maxSpeed);
|
|
109
|
+
const dragAccel = dragScale * airspeed * (1 + speedRatio * speedRatio);
|
|
110
|
+
const invAir = airspeed > 0.001 ? 1 / airspeed : 0;
|
|
111
|
+
velocity[0] += (updated.forward[0] * (forwardThrust + cyclicForward) + updated.right[0] * cyclicSide + updated.up[0] * wingLift - relative[0] * invAir * dragAccel - updated.right[0] * rightSpeed * tuning.sideDrag + updated.up[0] * rotorLift + gravityVector[0]) * stepDt;
|
|
112
|
+
velocity[1] += (updated.forward[1] * (forwardThrust + cyclicForward) + updated.right[1] * cyclicSide + updated.up[1] * wingLift - relative[1] * invAir * dragAccel - updated.right[1] * rightSpeed * tuning.sideDrag + updated.up[1] * rotorLift + gravityVector[1]) * stepDt;
|
|
113
|
+
velocity[2] += (updated.forward[2] * (forwardThrust + cyclicForward) + updated.right[2] * cyclicSide + updated.up[2] * wingLift - relative[2] * invAir * dragAccel - updated.right[2] * rightSpeed * tuning.sideDrag + updated.up[2] * rotorLift + gravityVector[2]) * stepDt;
|
|
114
|
+
const newSpeed = length(velocity);
|
|
115
|
+
if (newSpeed > tuning.maxSpeed * afterburner && newSpeed > 0) {
|
|
116
|
+
const scale = tuning.maxSpeed * afterburner / newSpeed;
|
|
117
|
+
velocity[0] *= scale;
|
|
118
|
+
velocity[1] *= scale;
|
|
119
|
+
velocity[2] *= scale;
|
|
120
|
+
}
|
|
121
|
+
position[0] += velocity[0] * stepDt;
|
|
122
|
+
position[1] += velocity[1] * stepDt;
|
|
123
|
+
position[2] += velocity[2] * stepDt;
|
|
124
|
+
const floor = ground + (tuning.groundClearance ?? 0.6);
|
|
125
|
+
let grounded = false;
|
|
126
|
+
if (position[1] < floor) {
|
|
127
|
+
position[1] = floor;
|
|
128
|
+
if (velocity[1] < 0)
|
|
129
|
+
velocity[1] *= -0.08;
|
|
130
|
+
velocity[0] *= 0.985;
|
|
131
|
+
velocity[2] *= 0.985;
|
|
132
|
+
grounded = true;
|
|
133
|
+
}
|
|
134
|
+
const verticalAcceleration = stepDt > 0 ? (velocity[1] - lastVerticalSpeed) / stepDt : 0;
|
|
135
|
+
lastVerticalSpeed = velocity[1];
|
|
136
|
+
return {
|
|
137
|
+
position: [...position],
|
|
138
|
+
velocity: [...velocity],
|
|
139
|
+
rotation: [...rotation],
|
|
140
|
+
forwardSpeed,
|
|
141
|
+
airspeed,
|
|
142
|
+
verticalSpeed: velocity[1],
|
|
143
|
+
angleOfAttack,
|
|
144
|
+
liftFraction,
|
|
145
|
+
stalled: transition < 0.5 && (Math.abs(forwardSpeed) < tuning.stallSpeed || aoaEfficiency < 0.35),
|
|
146
|
+
grounded,
|
|
147
|
+
gForce: (verticalAcceleration - gravityVector[1]) / gravityMagnitude,
|
|
148
|
+
};
|
|
149
|
+
},
|
|
150
|
+
pose: () => ({ position: [...position], rotation: [...rotation] }),
|
|
151
|
+
velocity: () => [...velocity],
|
|
152
|
+
resetTo(nextPosition, nextRotation = [0, 0, 0]) {
|
|
153
|
+
position = [...nextPosition];
|
|
154
|
+
velocity = [0, 0, 0];
|
|
155
|
+
rotation = [...nextRotation];
|
|
156
|
+
angular = [0, 0, 0];
|
|
157
|
+
rotor = 0;
|
|
158
|
+
lastVerticalSpeed = 0;
|
|
159
|
+
},
|
|
160
|
+
setVelocity(nextVelocity) {
|
|
161
|
+
velocity = [...nextVelocity];
|
|
162
|
+
lastVerticalSpeed = velocity[1];
|
|
163
|
+
},
|
|
164
|
+
};
|
|
165
|
+
}
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
/** Three-dimensional acceleration vector sampled from a gravity field. */
|
|
2
|
+
export type GravityVector = readonly [number, number, number];
|
|
3
|
+
/** Position-dependent gravity source used by movement and vehicle simulations. */
|
|
4
|
+
export interface GravityField {
|
|
5
|
+
sample(position: GravityVector): GravityVector;
|
|
6
|
+
}
|
|
7
|
+
/** Center, strength, and falloff for radial gravity. */
|
|
8
|
+
export interface PointGravityOptions {
|
|
9
|
+
center: GravityVector;
|
|
10
|
+
strength: number;
|
|
11
|
+
falloff?: "constant" | "inverseSquare";
|
|
12
|
+
minRadius?: number;
|
|
13
|
+
}
|
|
14
|
+
/** Constant vector gravity for ordinary worlds, space stations, and sideways-gravity levels.
|
|
15
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
16
|
+
*/
|
|
17
|
+
export declare function uniformGravity(vector?: GravityVector): GravityField;
|
|
18
|
+
/** Radial gravity toward one center, with optional inverse-square falloff for planetary worlds.
|
|
19
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
20
|
+
*/
|
|
21
|
+
export declare function pointGravity(options: PointGravityOptions): GravityField;
|
|
22
|
+
/** Adds several gravity sources into one field.
|
|
23
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
24
|
+
*/
|
|
25
|
+
export declare function combineGravity(fields: readonly GravityField[]): GravityField;
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/** Constant vector gravity for ordinary worlds, space stations, and sideways-gravity levels.
|
|
2
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
3
|
+
*/
|
|
4
|
+
export function uniformGravity(vector = [0, -9.81, 0]) {
|
|
5
|
+
return { sample: () => vector };
|
|
6
|
+
}
|
|
7
|
+
/** Radial gravity toward one center, with optional inverse-square falloff for planetary worlds.
|
|
8
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
9
|
+
*/
|
|
10
|
+
export function pointGravity(options) {
|
|
11
|
+
const minRadius = Math.max(0.001, options.minRadius ?? 1);
|
|
12
|
+
return {
|
|
13
|
+
sample(position) {
|
|
14
|
+
const dx = options.center[0] - position[0];
|
|
15
|
+
const dy = options.center[1] - position[1];
|
|
16
|
+
const dz = options.center[2] - position[2];
|
|
17
|
+
const distance = Math.max(minRadius, Math.hypot(dx, dy, dz));
|
|
18
|
+
const scale = options.falloff === "inverseSquare" ? options.strength / (distance * distance) : options.strength;
|
|
19
|
+
return [dx / distance * scale, dy / distance * scale, dz / distance * scale];
|
|
20
|
+
},
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
/** Adds several gravity sources into one field.
|
|
24
|
+
* @capability gravity-field sample configurable uniform, radial, or combined gravity
|
|
25
|
+
*/
|
|
26
|
+
export function combineGravity(fields) {
|
|
27
|
+
return {
|
|
28
|
+
sample(position) {
|
|
29
|
+
let x = 0;
|
|
30
|
+
let y = 0;
|
|
31
|
+
let z = 0;
|
|
32
|
+
for (const field of fields) {
|
|
33
|
+
const vector = field.sample(position);
|
|
34
|
+
x += vector[0];
|
|
35
|
+
y += vector[1];
|
|
36
|
+
z += vector[2];
|
|
37
|
+
}
|
|
38
|
+
return [x, y, z];
|
|
39
|
+
},
|
|
40
|
+
};
|
|
41
|
+
}
|
|
@@ -12,6 +12,77 @@ export interface KinematicVehicleTuning {
|
|
|
12
12
|
handbrakeGrip: number;
|
|
13
13
|
/** Forward-speed decay per second when coasting (sleds, boats); default `0`. */
|
|
14
14
|
rollingResistance?: number;
|
|
15
|
+
/** Optional higher-fidelity powertrain. Omit to preserve the direct arcade acceleration model. */
|
|
16
|
+
powertrain?: KinematicPowertrainTuning;
|
|
17
|
+
/** Optional bicycle-model steering with speed-sensitive lock and smoothed rack response. */
|
|
18
|
+
steering?: KinematicSteeringTuning;
|
|
19
|
+
/** Optional aerodynamic drag/downforce and electronic driver assists. */
|
|
20
|
+
dynamics?: KinematicDynamicsTuning;
|
|
21
|
+
/**
|
|
22
|
+
* Optional mass-and-force chassis layer (#1051). When present it supersedes `engineAccel`/`brakeAccel`
|
|
23
|
+
* with force/mass dynamics — a tire friction budget, lateral weight transfer, and engine braking — so
|
|
24
|
+
* vehicles of different mass, drive/brake force, and center-of-gravity drive measurably differently.
|
|
25
|
+
* Omit to preserve the direct arcade acceleration model exactly.
|
|
26
|
+
*/
|
|
27
|
+
chassis?: KinematicChassisTuning;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Mass-and-force chassis layer (#1051); when present it supersedes `engineAccel`/`brakeAccel` with
|
|
31
|
+
* force/mass dynamics. Drive/brake become forces divided by `massKg`, a per-tick tire friction budget
|
|
32
|
+
* (`tireGrip` * grip curve * surface * downforce * m * g) is split lateral-first then longitudinal so
|
|
33
|
+
* hard slides and launches saturate, and `comHeight`/`trackWidth` set weight-transfer washout and body
|
|
34
|
+
* lean. Coasting decelerates from physical road load, not a per-vehicle constant: rolling resistance
|
|
35
|
+
* `μ_rr · m · g` always, plus engine braking `ENGINE_BRAKE_FRACTION · engineForce` routed through the
|
|
36
|
+
* current gear (so it strengthens as the car downshifts) whenever the throttle is lifted and a
|
|
37
|
+
* `powertrain` is configured. All fields are required; omit the whole block for the legacy model.
|
|
38
|
+
*/
|
|
39
|
+
export interface KinematicChassisTuning {
|
|
40
|
+
/** Total vehicle mass, kg (1200 compact … 11000 bus); divides every drive/brake force into accel. */
|
|
41
|
+
massKg: number;
|
|
42
|
+
/** Peak drive force at the wheels, N — scaled by the powertrain torque curve and throttle. Also sets engine-braking magnitude. */
|
|
43
|
+
engineForce: number;
|
|
44
|
+
/** Peak service-brake force, N; still modulated by the existing ABS logic. */
|
|
45
|
+
brakeForce: number;
|
|
46
|
+
/** Peak tire friction coefficient μ on an ideal surface (~0.9 street, 1.1 sport, 0.7 bus). */
|
|
47
|
+
tireGrip: number;
|
|
48
|
+
/** Center-of-mass height, m (0.45 sports car … 1.4 bus); drives weight transfer and body lean. */
|
|
49
|
+
comHeight: number;
|
|
50
|
+
/** Lateral wheel spacing (track width), m; larger resists lateral weight transfer. */
|
|
51
|
+
trackWidth: number;
|
|
52
|
+
}
|
|
53
|
+
/** Data-first gearbox and torque-curve tuning for a kinematic ground vehicle. */
|
|
54
|
+
export interface KinematicPowertrainTuning {
|
|
55
|
+
idleRpm: number;
|
|
56
|
+
redlineRpm: number;
|
|
57
|
+
shiftUpRpm: number;
|
|
58
|
+
shiftDownRpm: number;
|
|
59
|
+
shiftSeconds: number;
|
|
60
|
+
finalDrive: number;
|
|
61
|
+
wheelRadius: number;
|
|
62
|
+
gears: readonly number[];
|
|
63
|
+
torqueCurve: GripCurve;
|
|
64
|
+
}
|
|
65
|
+
/** Bicycle-model steering settings; all angles are radians. */
|
|
66
|
+
export interface KinematicSteeringTuning {
|
|
67
|
+
wheelbase: number;
|
|
68
|
+
maxAngle: number;
|
|
69
|
+
highSpeedAngle: number;
|
|
70
|
+
highSpeedAt: number;
|
|
71
|
+
response: number;
|
|
72
|
+
yawDamping?: number;
|
|
73
|
+
}
|
|
74
|
+
/** Aerodynamic and electronic-assist settings layered over tire grip. */
|
|
75
|
+
export interface KinematicDynamicsTuning {
|
|
76
|
+
aerodynamicDrag?: number;
|
|
77
|
+
downforce?: number;
|
|
78
|
+
tractionControl?: number;
|
|
79
|
+
abs?: number;
|
|
80
|
+
stabilityControl?: number;
|
|
81
|
+
bodyPitchFactor?: number;
|
|
82
|
+
bodyRollFactor?: number;
|
|
83
|
+
maxBodyPitch?: number;
|
|
84
|
+
maxBodyRoll?: number;
|
|
85
|
+
attitudeResponse?: number;
|
|
15
86
|
}
|
|
16
87
|
export interface KinematicVehicleOptions {
|
|
17
88
|
position?: readonly [number, number, number];
|
|
@@ -51,6 +122,22 @@ export interface KinematicVehicleStep {
|
|
|
51
122
|
slip: number;
|
|
52
123
|
/** The `surfaceFriction` sample used this tick (`< 1` = off the ideal surface). */
|
|
53
124
|
surface: number;
|
|
125
|
+
/** One-based forward gear, or `0` while reversing/neutral. */
|
|
126
|
+
gear: number;
|
|
127
|
+
rpm: number;
|
|
128
|
+
steerAngle: number;
|
|
129
|
+
yawRate: number;
|
|
130
|
+
longitudinalAcceleration: number;
|
|
131
|
+
tractionLimited: boolean;
|
|
132
|
+
absActive: boolean;
|
|
133
|
+
/**
|
|
134
|
+
* Demanded drive force exceeded the tire friction budget left after cornering this tick — a
|
|
135
|
+
* traction-limited launch (#1051). Always `false` without a `chassis` block; distinct from
|
|
136
|
+
* `tractionLimited`, which stays owned by the traction-control assist.
|
|
137
|
+
*/
|
|
138
|
+
wheelspin: boolean;
|
|
139
|
+
bodyPitch: number;
|
|
140
|
+
bodyRoll: number;
|
|
54
141
|
}
|
|
55
142
|
/**
|
|
56
143
|
* The pure-kinematic arcade car every racing game hand-rolled (#282.1): steer-yaw scaled by speed,
|