@jgengine/core 0.18.0 → 0.18.1

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.
Files changed (174) hide show
  1. package/CHANGELOG.md +114 -2
  2. package/dist/ai/decisionGraph.d.ts +52 -0
  3. package/dist/ai/decisionGraph.js +82 -0
  4. package/dist/ai/mobBrainGraph.d.ts +3 -0
  5. package/dist/ai/mobBrainGraph.js +25 -0
  6. package/dist/ai/tacticalQueries.d.ts +30 -0
  7. package/dist/ai/tacticalQueries.js +45 -0
  8. package/dist/anim/animGraph.d.ts +144 -0
  9. package/dist/anim/animGraph.js +286 -0
  10. package/dist/anim/ikSolver.d.ts +51 -0
  11. package/dist/anim/ikSolver.js +124 -0
  12. package/dist/anim/locomotionGraph.d.ts +26 -0
  13. package/dist/anim/locomotionGraph.js +33 -0
  14. package/dist/assets/spriteAtlas.d.ts +21 -0
  15. package/dist/assets/spriteAtlas.js +103 -0
  16. package/dist/audio/audioFalloff.d.ts +10 -0
  17. package/dist/economy/currency.d.ts +16 -2
  18. package/dist/economy/currency.js +55 -14
  19. package/dist/economy/wallet.d.ts +5 -4
  20. package/dist/economy/wallet.js +8 -4
  21. package/dist/editor/commandApply.js +1 -0
  22. package/dist/editor/commands.d.ts +4 -1
  23. package/dist/editor/document.d.ts +2 -1
  24. package/dist/editor/document.js +89 -0
  25. package/dist/editor/environment.d.ts +3 -0
  26. package/dist/editor/environment.js +16 -0
  27. package/dist/editor/index.d.ts +4 -2
  28. package/dist/editor/index.js +1 -1
  29. package/dist/editor/types.d.ts +16 -0
  30. package/dist/game/cameraConfig.d.ts +8 -0
  31. package/dist/game/chat.d.ts +22 -1
  32. package/dist/game/chat.js +48 -19
  33. package/dist/game/controlGate.d.ts +6 -0
  34. package/dist/game/controlGate.js +30 -1
  35. package/dist/game/defineGame.d.ts +10 -1
  36. package/dist/game/events.d.ts +8 -1
  37. package/dist/game/pixelPerfect.d.ts +25 -0
  38. package/dist/game/pixelPerfect.js +29 -0
  39. package/dist/game/playableGame.d.ts +97 -3
  40. package/dist/game/systemRuntime.js +6 -1
  41. package/dist/input/actionBindings.d.ts +2 -1
  42. package/dist/input/actionBindings.js +10 -8
  43. package/dist/input/actionContexts.d.ts +21 -0
  44. package/dist/input/actionContexts.js +56 -0
  45. package/dist/input/gamepadModel.d.ts +45 -0
  46. package/dist/input/gamepadModel.js +69 -0
  47. package/dist/meta/changelog.d.ts +1 -1
  48. package/dist/meta/changelog.js +85 -1
  49. package/dist/movement/characterController.d.ts +76 -0
  50. package/dist/movement/characterController.js +216 -0
  51. package/dist/movement/freeFlight.d.ts +119 -0
  52. package/dist/movement/freeFlight.js +294 -0
  53. package/dist/movement/playerMovement.d.ts +10 -2
  54. package/dist/movement/playerMovement.js +218 -4
  55. package/dist/movement/serverStep.d.ts +5 -0
  56. package/dist/movement/serverStep.js +6 -0
  57. package/dist/movement/solidObstacles.d.ts +30 -7
  58. package/dist/movement/solidObstacles.js +27 -4
  59. package/dist/multiplayer/poseSyncGate.d.ts +3 -1
  60. package/dist/multiplayer/poseSyncGate.js +9 -1
  61. package/dist/multiplayer/presenceContract.d.ts +6 -0
  62. package/dist/multiplayer/presenceModel.d.ts +2 -0
  63. package/dist/multiplayer/presenceModel.js +11 -0
  64. package/dist/nav/navMesh.d.ts +33 -0
  65. package/dist/nav/navMesh.js +164 -0
  66. package/dist/physics/flightDynamics.d.ts +8 -0
  67. package/dist/physics/kinematicVehicle.d.ts +6 -1
  68. package/dist/physics/physicsBackend.d.ts +191 -0
  69. package/dist/physics/physicsBackend.js +49 -0
  70. package/dist/physics/physicsBackendConformance.d.ts +27 -0
  71. package/dist/physics/physicsBackendConformance.js +117 -0
  72. package/dist/physics/physicsWorldBackend.d.ts +44 -0
  73. package/dist/physics/physicsWorldBackend.js +485 -0
  74. package/dist/physics/worldColliders.d.ts +13 -0
  75. package/dist/physics/worldColliders.js +96 -0
  76. package/dist/random/rng.d.ts +12 -1
  77. package/dist/random/rng.js +20 -3
  78. package/dist/render/environment.d.ts +23 -0
  79. package/dist/render/environment.js +1 -0
  80. package/dist/render/lookPreset.d.ts +11 -5
  81. package/dist/render/lookPreset.js +29 -4
  82. package/dist/render/postProcessing.d.ts +10 -1
  83. package/dist/render/sprite2d.d.ts +38 -0
  84. package/dist/render/sprite2d.js +40 -0
  85. package/dist/runtime/adapter.d.ts +20 -1
  86. package/dist/runtime/adapter.js +8 -1
  87. package/dist/runtime/commandInput.d.ts +15 -0
  88. package/dist/runtime/commandInput.js +13 -0
  89. package/dist/runtime/commandRunner.d.ts +12 -7
  90. package/dist/runtime/commandRunner.js +7 -5
  91. package/dist/runtime/gameContext.js +11 -1
  92. package/dist/runtime/gameContextTypes.d.ts +14 -4
  93. package/dist/runtime/gameRuntime.d.ts +20 -4
  94. package/dist/runtime/gameRuntime.js +17 -4
  95. package/dist/runtime/headlessRunner.d.ts +10 -0
  96. package/dist/runtime/headlessRunner.js +26 -8
  97. package/dist/runtime/hostPolicy.d.ts +3 -2
  98. package/dist/runtime/hostPolicy.js +8 -10
  99. package/dist/runtime/hostedGameRunner.js +34 -6
  100. package/dist/runtime/hostedWorldSession.d.ts +12 -16
  101. package/dist/runtime/hostedWorldSession.js +29 -6
  102. package/dist/runtime/hostedWorldStore.d.ts +20 -0
  103. package/dist/runtime/hostedWorldStore.js +1 -0
  104. package/dist/runtime/inputRecorder.d.ts +30 -0
  105. package/dist/runtime/inputRecorder.js +59 -0
  106. package/dist/runtime/inputSnapshot.d.ts +8 -0
  107. package/dist/runtime/inputSnapshot.js +1 -0
  108. package/dist/runtime/poseInterpolation.d.ts +36 -0
  109. package/dist/runtime/poseInterpolation.js +91 -0
  110. package/dist/runtime/prediction.d.ts +45 -0
  111. package/dist/runtime/prediction.js +62 -0
  112. package/dist/runtime/simContext.d.ts +45 -0
  113. package/dist/runtime/simContext.js +65 -0
  114. package/dist/runtime/simLoop.d.ts +52 -0
  115. package/dist/runtime/simLoop.js +72 -0
  116. package/dist/runtime/simSnapshot.d.ts +32 -0
  117. package/dist/runtime/simSnapshot.js +60 -0
  118. package/dist/runtime/snapshot.d.ts +8 -0
  119. package/dist/runtime/snapshotBuffer.d.ts +33 -0
  120. package/dist/runtime/snapshotBuffer.js +58 -0
  121. package/dist/runtime/transport.d.ts +32 -2
  122. package/dist/runtime/worldChunks.d.ts +2 -0
  123. package/dist/runtime/worldChunks.js +14 -0
  124. package/dist/runtime/worldProjection.d.ts +9 -0
  125. package/dist/runtime/worldProjection.js +17 -1
  126. package/dist/runtime/worldSnapshot.d.ts +6 -0
  127. package/dist/runtime/worldSnapshot.js +3 -0
  128. package/dist/scene/behaviorRuntime.d.ts +19 -1
  129. package/dist/scene/behaviorRuntime.js +55 -4
  130. package/dist/scene/behaviors.d.ts +8 -1
  131. package/dist/scene/builtinSceneKinds.js +10 -0
  132. package/dist/sensor/perception.d.ts +60 -0
  133. package/dist/sensor/perception.js +142 -0
  134. package/dist/settings/graphicsProfile.d.ts +19 -0
  135. package/dist/settings/graphicsProfile.js +36 -0
  136. package/dist/time/accrueSince.d.ts +10 -0
  137. package/dist/time/accrueSince.js +9 -0
  138. package/dist/time/rateWindow.d.ts +16 -0
  139. package/dist/time/rateWindow.js +14 -0
  140. package/dist/time/serverTick.d.ts +21 -3
  141. package/dist/time/serverTick.js +14 -13
  142. package/dist/vfx/particleDirector.d.ts +68 -0
  143. package/dist/vfx/particleDirector.js +81 -0
  144. package/dist/world/buildings.d.ts +12 -0
  145. package/dist/world/buildings.js +43 -3
  146. package/dist/world/cityContent.js +103 -25
  147. package/dist/world/cityKind.d.ts +132 -0
  148. package/dist/world/cityKind.js +315 -15
  149. package/dist/world/features.d.ts +27 -1
  150. package/dist/world/features.js +7 -2
  151. package/dist/world/placement.d.ts +7 -0
  152. package/dist/world/placement.js +2 -0
  153. package/dist/world/placementController.d.ts +1 -1
  154. package/dist/world/placementController.js +7 -0
  155. package/dist/world/raceCircuit.d.ts +168 -0
  156. package/dist/world/raceCircuit.js +797 -0
  157. package/dist/world/terrain.d.ts +5 -1
  158. package/dist/world/terrain.js +5 -0
  159. package/dist/world/territory.d.ts +97 -0
  160. package/dist/world/territory.js +182 -0
  161. package/dist/world.d.ts +5 -3
  162. package/dist/world.js +4 -2
  163. package/package.json +1 -1
  164. package/skills/ce-handoff/SKILL.md +1 -1
  165. package/skills/game-design/SKILL.md +11 -1
  166. package/skills/jgengine/SKILL.md +2 -0
  167. package/skills/jgengine/api.md +94 -19
  168. package/skills/jgengine/capabilities.md +28 -0
  169. package/skills/jgengine-gameplay/SKILL.md +10 -0
  170. package/skills/jgengine-gameplay/api.md +40 -18
  171. package/skills/jgengine-gameplay/capabilities.md +12 -0
  172. package/skills/jgengine-world/SKILL.md +6 -0
  173. package/skills/jgengine-world/api.md +312 -12
  174. package/skills/jgengine-world/capabilities.md +102 -0
@@ -0,0 +1,119 @@
1
+ import { type AnalogMoveIntent, type CollisionObstacle, type MovementKeysState } from "./movementModel.js";
2
+ import { type AxisBinding } from "../input/axisInput.js";
3
+ import type { PointerAxisState } from "../input/pointerAxis.js";
4
+ import type { ChaseCameraTuning } from "../runtime/cameraDirector.js";
5
+ /** Free-flight families — character-scale 6DOF movement, distinct from vehicle aerodynamics. */
6
+ export type FreeFlightMode = "creative" | "spectator" | "noclip" | "hover";
7
+ /** Per-axis bindings for free-flight — which actions drive each flight axis. Positive is forward/right/up. */
8
+ export interface FreeFlightBindings {
9
+ /** Forward/back — default `moveForward` / `moveBack` (W/S). */
10
+ forward?: AxisBinding;
11
+ /** Strafe right/left — default `moveRight` / `moveLeft` (D/A). */
12
+ strafe?: AxisBinding;
13
+ /** Ascend/descend — default `jump` / `crouch` (Space / Ctrl+C). */
14
+ vertical?: AxisBinding;
15
+ /** Sprint — default `sprint` (Shift) held. */
16
+ sprint?: AxisBinding;
17
+ }
18
+ /** Data-first tuning for one free-flight profile. */
19
+ export interface FreeFlightTuning {
20
+ mode: FreeFlightMode;
21
+ /** Horizontal base speed (units/s) before sprint. Default 8. */
22
+ speed?: number;
23
+ /** Vertical base speed for ascend/descend. Default = `speed`. */
24
+ verticalSpeed?: number;
25
+ /** Sprint multiplier while shift is held. Default 1.8. */
26
+ sprintMultiplier?: number;
27
+ /** Lerp rate toward target velocity. Default 20. */
28
+ acceleration?: number;
29
+ /** Extra damping applied when no input (0 = rely on lerp). Default 0. */
30
+ damping?: number;
31
+ /** Downward acceleration for hover mode. Default 0 (weightless). */
32
+ gravity?: number;
33
+ /** Upward thrust acceleration while ascending in hover. Default 32. */
34
+ thrust?: number;
35
+ /** When true, forward moves along the look pitch (spectator). Default false. */
36
+ alignWithLook?: boolean;
37
+ /** Top vertical fall speed in hover. Default 20. */
38
+ maxFallSpeed?: number;
39
+ /** Whether to slide against world solids. Default true for creative/hover, false for spectator/noclip. */
40
+ collide?: boolean;
41
+ /** Axis bindings for this flight profile — which actions drive each axis. Unlisted axes keep the defaults above. */
42
+ bindings?: FreeFlightBindings;
43
+ /** Camera POV while this flight is active — chase tuning overlay applied on mount, cleared on dismount. Null clears. */
44
+ camera?: ChaseCameraTuning | null;
45
+ }
46
+ /** Velocity state for a free-flight actor — serializable and ownable by the caller. */
47
+ export interface FreeFlightState {
48
+ vx: number;
49
+ vy: number;
50
+ vz: number;
51
+ }
52
+ /** Intent for one free-flight tick — forward/right from WASD/analog, vertical from jump/crouch. */
53
+ export interface FreeFlightIntent {
54
+ forward: number;
55
+ right: number;
56
+ vertical: number;
57
+ sprint: boolean;
58
+ moving: boolean;
59
+ }
60
+ /** World displacement produced by one free-flight tick. */
61
+ export interface FreeFlightStep {
62
+ stepX: number;
63
+ stepY: number;
64
+ stepZ: number;
65
+ }
66
+ /** Stateful handle for any free-flight actor with snapshot/restore/retune. */
67
+ export interface FreeFlightController {
68
+ tick(dt: number, intent: FreeFlightIntent, yaw: number, pitch?: number): FreeFlightStep;
69
+ velocity(): readonly [number, number, number];
70
+ snapshot(): FreeFlightState;
71
+ restore(next: FreeFlightState): void;
72
+ retune(next: FreeFlightTuning): void;
73
+ reset(): void;
74
+ }
75
+ /** Create an empty flight velocity state. */
76
+ export declare function createFreeFlightState(): FreeFlightState;
77
+ /**
78
+ * Translate held keys + analog into a free-flight intent.
79
+ * Vertical is `space - (control|c)` so jump ascends and crouch descends — the same
80
+ * keys that walk/jump/crouch use, re-mapped for flight without re-binding.
81
+ */
82
+ export declare function resolveFreeFlightIntent(keys: MovementKeysState, analog: AnalogMoveIntent | null | undefined): FreeFlightIntent;
83
+ /**
84
+ * Translate live input (held actions + analog + pointer) into a flight intent using
85
+ * per-profile axis bindings. Unlisted bindings fall back to the defaults above so
86
+ * existing games keep WASD/Space/Ctrl/Shift without declaring anything.
87
+ */
88
+ export declare function resolveFreeFlightIntentFromInput(isDown: (action: string) => boolean, value: (action: string) => number, pointer: PointerAxisState | null, bindings?: FreeFlightBindings): FreeFlightIntent;
89
+ /**
90
+ * Advance one frame of free-flight kinematics.
91
+ * Horizontal uses yaw-relative strafe (A = left, D = right) — never roll/bank.
92
+ * Vertical is independent of pitch unless `alignWithLook` is true (spectator).
93
+ */
94
+ export declare function advanceFreeFlight(state: FreeFlightState, intent: FreeFlightIntent, yaw: number, pitch: number | undefined, dt: number, tuning: FreeFlightTuning): FreeFlightStep;
95
+ /** Resolve a flight step against world solids (XZ slide + Y clamp). */
96
+ export declare function resolveFlightStep(position: readonly [number, number, number], stepX: number, stepY: number, stepZ: number, obstacles: readonly CollisionObstacle[], radius?: number): {
97
+ stepX: number;
98
+ stepY: number;
99
+ stepZ: number;
100
+ };
101
+ /**
102
+ * Totally free flight for any actor — character, camera, drone, or debug rig.
103
+ * Horizontal is always yaw-relative strafe; vertical is jump/crouch. Use `alignWithLook`
104
+ * for a 6DOF spectator that flies where the camera looks.
105
+ * @capability free-flight camera-relative creative/spectator/noclip/hover flight with correct strafe, sprint, and optional gravity
106
+ */
107
+ export declare function createFreeFlightController(initialTuning: FreeFlightTuning, options?: {
108
+ yaw?: number;
109
+ pitch?: number;
110
+ state?: FreeFlightState;
111
+ }): FreeFlightController;
112
+ /** Preset for Minecraft-like creative flight — weightless, collides, yaw-relative strafe. */
113
+ export declare const CREATIVE_FLIGHT_TUNING: FreeFlightTuning;
114
+ /** Preset for spectator — weightless, noclips, flies where the camera looks. */
115
+ export declare const SPECTATOR_FLIGHT_TUNING: FreeFlightTuning;
116
+ /** Preset for noclip — weightless, noclips, yaw-relative with independent vertical. */
117
+ export declare const NOCLIP_FLIGHT_TUNING: FreeFlightTuning;
118
+ /** Preset for hover/jetpack — gravity + thrust, useful for short bursts. */
119
+ export declare const HOVER_FLIGHT_TUNING: FreeFlightTuning;
@@ -0,0 +1,294 @@
1
+ import { MOVEMENT_TUNING } from "./movementModel.js";
2
+ import { DEFAULT_OBSTACLE_PLAYER_RADIUS } from "./movementModel.js";
3
+ import { sampleAxisBindings } from "../input/axisInput.js";
4
+ const ANALOG_EPSILON = 0.02;
5
+ /** Create an empty flight velocity state. */
6
+ export function createFreeFlightState() {
7
+ return { vx: 0, vy: 0, vz: 0 };
8
+ }
9
+ const DEFAULT_FORWARD_BINDING = { positive: ["moveForward"], negative: ["moveBack"] };
10
+ const DEFAULT_STRAFE_BINDING = { positive: ["moveRight"], negative: ["moveLeft"] };
11
+ const DEFAULT_VERTICAL_BINDING = { positive: ["jump"], negative: ["crouch", "control", "c"] };
12
+ const DEFAULT_SPRINT_BINDING = { positive: ["sprint"] };
13
+ /**
14
+ * Translate held keys + analog into a free-flight intent.
15
+ * Vertical is `space - (control|c)` so jump ascends and crouch descends — the same
16
+ * keys that walk/jump/crouch use, re-mapped for flight without re-binding.
17
+ */
18
+ export function resolveFreeFlightIntent(keys, analog) {
19
+ let forward = (keys.w ? 1 : 0) - (keys.s ? 1 : 0);
20
+ let right = (keys.d ? 1 : 0) - (keys.a ? 1 : 0);
21
+ if (analog !== undefined && analog !== null) {
22
+ forward = Math.abs(analog.forward) < ANALOG_EPSILON ? 0 : Math.max(-1, Math.min(1, analog.forward));
23
+ right = Math.abs(analog.right) < ANALOG_EPSILON ? 0 : Math.max(-1, Math.min(1, analog.right));
24
+ }
25
+ const vertical = (keys.space ? 1 : 0) - (keys.control || keys.c ? 1 : 0);
26
+ const sprint = keys.shift && vertical >= 0;
27
+ const moving = forward !== 0 || right !== 0 || vertical !== 0;
28
+ return { forward, right, vertical, sprint, moving };
29
+ }
30
+ /**
31
+ * Translate live input (held actions + analog + pointer) into a flight intent using
32
+ * per-profile axis bindings. Unlisted bindings fall back to the defaults above so
33
+ * existing games keep WASD/Space/Ctrl/Shift without declaring anything.
34
+ */
35
+ export function resolveFreeFlightIntentFromInput(isDown, value, pointer, bindings) {
36
+ const forwardBinding = bindings?.forward ?? DEFAULT_FORWARD_BINDING;
37
+ const strafeBinding = bindings?.strafe ?? DEFAULT_STRAFE_BINDING;
38
+ const verticalBinding = bindings?.vertical ?? DEFAULT_VERTICAL_BINDING;
39
+ const sprintBinding = bindings?.sprint ?? DEFAULT_SPRINT_BINDING;
40
+ const forward = sampleAxisBindings({ forward: forwardBinding }, isDown, pointer, undefined, value).forward;
41
+ const right = sampleAxisBindings({ strafe: strafeBinding }, isDown, pointer, undefined, value).strafe;
42
+ const vertical = sampleAxisBindings({ vertical: verticalBinding }, isDown, pointer, undefined, value).vertical;
43
+ const sprintVal = sampleAxisBindings({ sprint: sprintBinding }, isDown, pointer, { sprint: { min: 0, max: 1 } }, value).sprint;
44
+ const sprint = sprintVal > 0.5;
45
+ const moving = forward !== 0 || right !== 0 || vertical !== 0;
46
+ return { forward, right, vertical, sprint, moving };
47
+ }
48
+ /**
49
+ * Advance one frame of free-flight kinematics.
50
+ * Horizontal uses yaw-relative strafe (A = left, D = right) — never roll/bank.
51
+ * Vertical is independent of pitch unless `alignWithLook` is true (spectator).
52
+ */
53
+ export function advanceFreeFlight(state, intent, yaw, pitch, dt, tuning) {
54
+ const delta = Math.min(dt, MOVEMENT_TUNING.maxFrameSeconds);
55
+ if (delta <= 0)
56
+ return { stepX: 0, stepY: 0, stepZ: 0 };
57
+ const speed = tuning.speed ?? 8;
58
+ const verticalSpeed = tuning.verticalSpeed ?? speed;
59
+ const sprintMult = tuning.sprintMultiplier ?? 1.8;
60
+ const accel = tuning.acceleration ?? 20;
61
+ const damping = tuning.damping ?? 0;
62
+ const gravity = tuning.gravity ?? 0;
63
+ const thrust = tuning.thrust ?? 32;
64
+ const maxFall = tuning.maxFallSpeed ?? 20;
65
+ const align = tuning.alignWithLook === true && pitch !== undefined;
66
+ const sprintScale = intent.sprint ? sprintMult : 1;
67
+ let targetX = 0;
68
+ let targetZ = 0;
69
+ let targetY = 0;
70
+ if (align) {
71
+ const cp = Math.cos(pitch);
72
+ const sp = Math.sin(pitch);
73
+ const sy = Math.sin(yaw);
74
+ const cy = Math.cos(yaw);
75
+ const fx = sy * cp;
76
+ const fy = sp;
77
+ const fz = cy * cp;
78
+ const rx = cy;
79
+ const rz = -sy;
80
+ const rawX = fx * intent.forward + rx * intent.right;
81
+ const rawY = fy * intent.forward;
82
+ const rawZ = fz * intent.forward + rz * intent.right;
83
+ const hLen = Math.hypot(rawX, rawY, rawZ);
84
+ if (hLen > 1e-6) {
85
+ const scale = Math.min(1, hLen) / hLen;
86
+ const sx = rawX * scale;
87
+ const sy2 = rawY * scale;
88
+ const sz = rawZ * scale;
89
+ const horizScale = speed * sprintScale;
90
+ const vertScale = verticalSpeed * sprintScale;
91
+ targetX = sx * horizScale;
92
+ targetY = sy2 * horizScale + intent.vertical * vertScale;
93
+ targetZ = sz * horizScale;
94
+ }
95
+ else {
96
+ targetY = intent.vertical * verticalSpeed * sprintScale;
97
+ }
98
+ }
99
+ else {
100
+ const sy = Math.sin(yaw);
101
+ const cy = Math.cos(yaw);
102
+ let fx = sy;
103
+ let fz = cy;
104
+ const lenSq = fx * fx + fz * fz;
105
+ if (lenSq < 1e-6) {
106
+ fx = 0;
107
+ fz = -1;
108
+ }
109
+ else {
110
+ const inv = 1 / Math.sqrt(lenSq);
111
+ fx *= inv;
112
+ fz *= inv;
113
+ }
114
+ const rx = -fz;
115
+ const rz = fx;
116
+ let rawX = fx * intent.forward + rx * intent.right;
117
+ let rawZ = fz * intent.forward + rz * intent.right;
118
+ const len = Math.hypot(rawX, rawZ);
119
+ if (len > 1e-6) {
120
+ const scale = Math.min(1, len) / len;
121
+ rawX *= scale;
122
+ rawZ *= scale;
123
+ }
124
+ else {
125
+ rawX = 0;
126
+ rawZ = 0;
127
+ }
128
+ targetX = rawX * speed * sprintScale;
129
+ targetZ = rawZ * speed * sprintScale;
130
+ targetY = intent.vertical * verticalSpeed * sprintScale;
131
+ }
132
+ if (gravity > 0) {
133
+ const alpha = 1 - Math.exp(-accel * delta);
134
+ state.vx += (targetX - state.vx) * alpha;
135
+ state.vz += (targetZ - state.vz) * alpha;
136
+ if (damping > 0 && !intent.moving) {
137
+ const damp = Math.exp(-damping * delta);
138
+ state.vx *= damp;
139
+ state.vz *= damp;
140
+ }
141
+ const thrustAccel = intent.vertical * thrust;
142
+ state.vy += (thrustAccel - gravity) * delta;
143
+ if (intent.vertical !== 0) {
144
+ const vyTarget = targetY;
145
+ const vyAlpha = 1 - Math.exp(-accel * delta);
146
+ state.vy += (vyTarget - state.vy) * vyAlpha * 0.5;
147
+ }
148
+ if (damping > 0 && intent.vertical === 0) {
149
+ state.vy *= Math.exp(-damping * delta * 0.5);
150
+ }
151
+ if (state.vy < -maxFall)
152
+ state.vy = -maxFall;
153
+ }
154
+ else {
155
+ const alpha = 1 - Math.exp(-accel * delta);
156
+ state.vx += (targetX - state.vx) * alpha;
157
+ state.vy += (targetY - state.vy) * alpha;
158
+ state.vz += (targetZ - state.vz) * alpha;
159
+ if (damping > 0 && !intent.moving) {
160
+ const damp = Math.exp(-damping * delta);
161
+ state.vx *= damp;
162
+ state.vy *= damp;
163
+ state.vz *= damp;
164
+ }
165
+ }
166
+ return { stepX: state.vx * delta, stepY: state.vy * delta, stepZ: state.vz * delta };
167
+ }
168
+ /** Resolve a flight step against world solids (XZ slide + Y clamp). */
169
+ export function resolveFlightStep(position, stepX, stepY, stepZ, obstacles, radius = DEFAULT_OBSTACLE_PLAYER_RADIUS) {
170
+ const feetY = position[1];
171
+ const headY = feetY + 1.8;
172
+ let clampedY = stepY;
173
+ const nextY = feetY + stepY;
174
+ const nextHead = nextY + 1.8;
175
+ for (const ob of obstacles) {
176
+ const ox = ob.position[0] + (ob.offset?.[0] ?? 0);
177
+ const oy = ob.position[1] + (ob.offset?.[1] ?? 0);
178
+ const oz = ob.position[2] + (ob.offset?.[2] ?? 0);
179
+ const hx = ob.halfExtents?.[0] ?? 0.5;
180
+ const hy = ob.halfExtents?.[1] ?? 0.5;
181
+ const hz = ob.halfExtents?.[2] ?? 0.5;
182
+ if (ob.boxes !== undefined && ob.boxes.length > 0) {
183
+ for (const box of ob.boxes) {
184
+ const minX = ox + box.min[0] - radius;
185
+ const maxX = ox + box.max[0] + radius;
186
+ const minY = oy + box.min[1];
187
+ const maxY = oy + box.max[1];
188
+ const minZ = oz + box.min[2] - radius;
189
+ const maxZ = oz + box.max[2] + radius;
190
+ const insideXZ = position[0] > minX && position[0] < maxX && position[2] > minZ && position[2] < maxZ;
191
+ if (!insideXZ)
192
+ continue;
193
+ if (stepY > 0 && feetY < maxY && nextHead > minY && nextHead > maxY && feetY < minY) {
194
+ clampedY = Math.min(clampedY, minY - headY - 1e-3);
195
+ }
196
+ else if (stepY < 0 && headY > minY && nextY < maxY && nextY < minY && headY > maxY) {
197
+ clampedY = Math.max(clampedY, maxY - feetY + 1e-3);
198
+ }
199
+ }
200
+ continue;
201
+ }
202
+ const minX = ox - hx - radius;
203
+ const maxX = ox + hx + radius;
204
+ const minY = oy - hy;
205
+ const maxY = oy + hy;
206
+ const minZ = oz - hz - radius;
207
+ const maxZ = oz + hz + radius;
208
+ const insideXZ = position[0] > minX && position[0] < maxX && position[2] > minZ && position[2] < maxZ;
209
+ if (!insideXZ)
210
+ continue;
211
+ if (stepY > 0 && feetY < maxY && nextHead > minY && nextHead > maxY && feetY < minY) {
212
+ clampedY = Math.min(clampedY, minY - headY - 1e-3);
213
+ }
214
+ else if (stepY < 0 && headY > minY && nextY < maxY && nextY < minY && headY > maxY) {
215
+ clampedY = Math.max(clampedY, maxY - feetY + 1e-3);
216
+ }
217
+ }
218
+ return { stepX, stepY: clampedY, stepZ };
219
+ }
220
+ /**
221
+ * Totally free flight for any actor — character, camera, drone, or debug rig.
222
+ * Horizontal is always yaw-relative strafe; vertical is jump/crouch. Use `alignWithLook`
223
+ * for a 6DOF spectator that flies where the camera looks.
224
+ * @capability free-flight camera-relative creative/spectator/noclip/hover flight with correct strafe, sprint, and optional gravity
225
+ */
226
+ export function createFreeFlightController(initialTuning, options = {}) {
227
+ let tuning = initialTuning;
228
+ const state = options.state !== undefined ? { ...options.state } : createFreeFlightState();
229
+ let yaw = options.yaw ?? 0;
230
+ let pitch = options.pitch ?? 0;
231
+ return {
232
+ tick(dt, intent, nextYaw, nextPitch) {
233
+ yaw = nextYaw;
234
+ if (nextPitch !== undefined)
235
+ pitch = nextPitch;
236
+ return advanceFreeFlight(state, intent, yaw, pitch, dt, tuning);
237
+ },
238
+ velocity: () => [state.vx, state.vy, state.vz],
239
+ snapshot: () => ({ ...state }),
240
+ restore(next) {
241
+ state.vx = next.vx;
242
+ state.vy = next.vy;
243
+ state.vz = next.vz;
244
+ },
245
+ retune(next) {
246
+ tuning = next;
247
+ },
248
+ reset() {
249
+ state.vx = 0;
250
+ state.vy = 0;
251
+ state.vz = 0;
252
+ },
253
+ };
254
+ }
255
+ /** Preset for Minecraft-like creative flight — weightless, collides, yaw-relative strafe. */
256
+ export const CREATIVE_FLIGHT_TUNING = {
257
+ mode: "creative",
258
+ speed: 8,
259
+ verticalSpeed: 8,
260
+ sprintMultiplier: 2.2,
261
+ acceleration: 22,
262
+ collide: true,
263
+ };
264
+ /** Preset for spectator — weightless, noclips, flies where the camera looks. */
265
+ export const SPECTATOR_FLIGHT_TUNING = {
266
+ mode: "spectator",
267
+ speed: 12,
268
+ verticalSpeed: 12,
269
+ sprintMultiplier: 2.5,
270
+ acceleration: 18,
271
+ collide: false,
272
+ alignWithLook: true,
273
+ };
274
+ /** Preset for noclip — weightless, noclips, yaw-relative with independent vertical. */
275
+ export const NOCLIP_FLIGHT_TUNING = {
276
+ mode: "noclip",
277
+ speed: 14,
278
+ verticalSpeed: 14,
279
+ sprintMultiplier: 3,
280
+ acceleration: 18,
281
+ collide: false,
282
+ alignWithLook: false,
283
+ };
284
+ /** Preset for hover/jetpack — gravity + thrust, useful for short bursts. */
285
+ export const HOVER_FLIGHT_TUNING = {
286
+ mode: "hover",
287
+ speed: 6,
288
+ verticalSpeed: 6,
289
+ sprintMultiplier: 1.6,
290
+ acceleration: 16,
291
+ gravity: 18,
292
+ thrust: 34,
293
+ collide: true,
294
+ };
@@ -2,14 +2,21 @@ import type { PhysicsConfig } from "../game/defineGame.js";
2
2
  import type { PlayerMovementConfig, VoxelCollisionConfig } from "../game/playableGame.js";
3
3
  import type { GameContext } from "../runtime/gameContext.js";
4
4
  import type { InputFrame } from "../runtime/inputSnapshot.js";
5
+ import { type CharacterControllerConfig } from "./characterController.js";
5
6
  import { type TerrainField } from "../world/terrain.js";
6
7
  import type { WorldFeature } from "../world/features.js";
8
+ import type { PhysicsBackend } from "../physics/physicsBackend.js";
7
9
  import { type MovementTuningOverrides } from "./movementModel.js";
8
10
  /** The resolved, per-world movement configuration {@link stepPlayerMovement} integrates against — the same inputs the shell FrameDriver used to read piecemeal, gathered into one struct so single-player and host movement run identical math. */
9
11
  export interface PlayerMovementTuning {
10
12
  collision?: VoxelCollisionConfig;
11
13
  movement?: PlayerMovementConfig;
12
14
  physics?: MovementTuningOverrides;
15
+ /** Optional physics-backed capsule controller; when present it replaces terrain/AABB walking collision. */
16
+ controller?: {
17
+ backend: PhysicsBackend;
18
+ capsule: CharacterControllerConfig;
19
+ };
13
20
  ground: TerrainField;
14
21
  hasTerrain: boolean;
15
22
  }
@@ -36,6 +43,7 @@ export declare function forgetPlayerMovement(ctx: GameContext, userId: string):
36
43
  * so single-player and server-authoritative movement are identical. Reads the player's controlled entity, terrain,
37
44
  * scene solids, and pending motion impulses; writes the entity pose via `setPose`. Retains heading + kinematic body
38
45
  * per `userId` on the `ctx`. Pass `heading` to override the internally-integrated yaw (the shell owns yaw for its
39
- * camera); omit it and the controller turns from the frame's `turnLeft`/`turnRight` actions.
46
+ * camera); omit it and the controller turns from the frame's `turnLeft`/`turnRight` actions. Pass `pitch` for
47
+ * 6DOF spectator flight when `alignWithLook` is set.
40
48
  */
41
- export declare function stepPlayerMovement(ctx: GameContext, userId: string, input: InputFrame, dt: number, tuning: PlayerMovementTuning, heading?: number): void;
49
+ export declare function stepPlayerMovement(ctx: GameContext, userId: string, input: InputFrame, dt: number, tuning: PlayerMovementTuning, heading?: number, pitch?: number): void;