@woosh/meep-engine 2.146.0 → 2.148.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/package.json +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts +4 -4
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite.js +48 -52
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts +23 -21
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_2d.js +41 -406
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts +5 -4
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.d.ts.map +1 -1
- package/src/core/math/spline/spline3_hermite_intersection_spline3_hermite_nd.js +400 -395
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts +0 -11
- package/src/engine/control/first-person/FirstPersonPlayerController.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts +8 -6
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.d.ts.map +1 -1
- package/src/engine/control/first-person/FirstPersonPlayerControllerConfig.js +552 -551
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts +8 -3
- package/src/engine/control/first-person/abilities/LedgeGrab.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/LedgeGrab.js +213 -199
- package/src/engine/control/first-person/abilities/Mantle.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/Mantle.js +195 -188
- package/src/engine/control/first-person/abilities/WallRun.d.ts.map +1 -1
- package/src/engine/control/first-person/abilities/WallRun.js +183 -175
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts +9 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensors.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensors.js +87 -77
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts +8 -0
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.d.ts.map +1 -1
- package/src/engine/control/first-person/sensors/FirstPersonSensorsSystem.js +229 -196
- package/src/engine/ecs/EntityManager.d.ts +34 -11
- package/src/engine/ecs/EntityManager.d.ts.map +1 -1
- package/src/engine/ecs/EntityManager.js +71 -42
- package/src/engine/interpolation/BinaryInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.d.ts +48 -0
- package/src/engine/interpolation/Interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/Interpoland.js +49 -0
- package/src/engine/interpolation/Interpolated.d.ts +101 -0
- package/src/engine/interpolation/Interpolated.d.ts.map +1 -0
- package/src/engine/interpolation/Interpolated.js +149 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.d.ts +1 -1
- package/src/engine/interpolation/InterpolationLog.d.ts.map +1 -0
- package/src/engine/{network/sim → interpolation}/InterpolationLog.js +2 -2
- package/src/engine/interpolation/InterpolationSystem.d.ts +116 -0
- package/src/engine/interpolation/InterpolationSystem.d.ts.map +1 -0
- package/src/engine/interpolation/InterpolationSystem.js +233 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts +17 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/PoseInterpolationAdapter.js +61 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts +35 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.d.ts.map +1 -0
- package/src/engine/interpolation/TransformPoseSerializationAdapter.js +57 -0
- package/src/engine/interpolation/pose_interpoland.d.ts +18 -0
- package/src/engine/interpolation/pose_interpoland.d.ts.map +1 -0
- package/src/engine/interpolation/pose_interpoland.js +27 -0
- package/src/engine/network/NetworkSession.d.ts +2 -2
- package/src/engine/network/NetworkSession.d.ts.map +1 -1
- package/src/engine/network/NetworkSession.js +2 -2
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/QuaternionInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/TransformInterpolationAdapter.js +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.d.ts.map +1 -1
- package/src/engine/network/adapters/Vector3InterpolationAdapter.js +1 -1
- package/src/engine/physics/INTEPOLATION_SYSTEM_PLAN.md +287 -0
- package/src/engine/physics/PLAN.md +10 -9
- package/src/engine/physics/body/SolverBodyState.d.ts +142 -0
- package/src/engine/physics/body/SolverBodyState.d.ts.map +1 -0
- package/src/engine/physics/body/SolverBodyState.js +251 -0
- package/src/engine/physics/broadphase/generate_pairs.d.ts +2 -1
- package/src/engine/physics/broadphase/generate_pairs.d.ts.map +1 -1
- package/src/engine/physics/broadphase/generate_pairs.js +5 -3
- package/src/engine/physics/constraint/solve_constraints.d.ts.map +1 -1
- package/src/engine/physics/constraint/solve_constraints.js +691 -673
- package/src/engine/physics/ecs/PhysicsSystem.d.ts +82 -15
- package/src/engine/physics/ecs/PhysicsSystem.d.ts.map +1 -1
- package/src/engine/physics/ecs/PhysicsSystem.js +387 -87
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts +23 -0
- package/src/engine/physics/inertia/world_inverse_inertia.d.ts.map +1 -1
- package/src/engine/physics/inertia/world_inverse_inertia.js +116 -77
- package/src/engine/physics/integration/integrate_position.d.ts +11 -1
- package/src/engine/physics/integration/integrate_position.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_position.js +97 -79
- package/src/engine/physics/integration/integrate_velocity.d.ts +12 -3
- package/src/engine/physics/integration/integrate_velocity.d.ts.map +1 -1
- package/src/engine/physics/integration/integrate_velocity.js +201 -160
- package/src/engine/physics/narrowphase/box_box_manifold.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_box_manifold.js +750 -665
- package/src/engine/physics/narrowphase/box_triangle_contact.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/box_triangle_contact.js +19 -46
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts +16 -0
- package/src/engine/physics/narrowphase/clip_against_axis_uv.d.ts.map +1 -0
- package/src/engine/physics/narrowphase/clip_against_axis_uv.js +49 -0
- package/src/engine/physics/narrowphase/narrowphase_step.d.ts.map +1 -1
- package/src/engine/physics/narrowphase/narrowphase_step.js +52 -4
- package/src/engine/physics/queries/raycast.d.ts.map +1 -1
- package/src/engine/physics/queries/raycast.js +7 -4
- package/src/engine/physics/solver/solve_contacts.d.ts +2 -2
- package/src/engine/physics/solver/solve_contacts.d.ts.map +1 -1
- package/src/engine/physics/solver/solve_contacts.js +1341 -1173
- package/src/engine/network/sim/BinaryInterpolationAdapter.d.ts.map +0 -1
- package/src/engine/network/sim/InterpolationLog.d.ts.map +0 -1
- /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.d.ts +0 -0
- /package/src/engine/{network/sim → interpolation}/BinaryInterpolationAdapter.js +0 -0
|
@@ -1,551 +1,552 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tunable parameters for {@link FirstPersonPlayerController}. The full
|
|
3
|
-
* config is the only serialized portion of the component — everything else
|
|
4
|
-
* (intent, state, pose, signals) is transient and rebuilt on link.
|
|
5
|
-
*
|
|
6
|
-
* Defaults aim for a baseline "human soldier" feel — see DESIGN.md §4.1.
|
|
7
|
-
* Every value is exposed so designers can mod it to taste.
|
|
8
|
-
*/
|
|
9
|
-
export class FirstPersonPlayerControllerConfig {
|
|
10
|
-
body = {
|
|
11
|
-
/** Standing eye height, meters above body origin. */
|
|
12
|
-
height: 1.80,
|
|
13
|
-
/** Crouched eye height. */
|
|
14
|
-
crouchHeight: 0.8,
|
|
15
|
-
/** Prone eye height (used by slide). Body is horizontal — eye
|
|
16
|
-
* just above ground level, head turned toward the direction
|
|
17
|
-
* of travel. */
|
|
18
|
-
proneHeight: 0.4,
|
|
19
|
-
/** Capsule radius, for downstream physics. */
|
|
20
|
-
radius: 0.34,
|
|
21
|
-
/** Kilograms — affects bob, jump, landing, accel, and exertion via massRatio. */
|
|
22
|
-
mass: 80,
|
|
23
|
-
/** Reference mass against which `mass` is normalized (massRatio = mass/refMass). */
|
|
24
|
-
referenceMass: 80,
|
|
25
|
-
/**
|
|
26
|
-
* How strongly `mass` couples into derived effects. 0 = mass is
|
|
27
|
-
* cosmetic; 1.0 = full coupling (heavier ⇒ lower jump, lower accel,
|
|
28
|
-
* harder landing, faster exhaustion). At 1.0 a 25% mass increase
|
|
29
|
-
* yields:
|
|
30
|
-
* jump peak ≈ 0.83× (∝ 1/√massRatio — kinematically motivated)
|
|
31
|
-
* ground accel ≈ 0.80× (∝ 1/massRatio — F = ma at fixed force)
|
|
32
|
-
* landing dip ≈ 1.25× (∝ massRatio — higher momentum to absorb)
|
|
33
|
-
* exertion rise ≈ 1.25× (∝ massRatio — more body to move)
|
|
34
|
-
*/
|
|
35
|
-
massCouplingStrength: 1.0,
|
|
36
|
-
};
|
|
37
|
-
|
|
38
|
-
// Defaults are tuned toward an "athlete with juice" feel — plausible
|
|
39
|
-
// but biased for fun: snappy accel, fast top speed, generous air
|
|
40
|
-
// control, satisfying jump. Realistic-soldier values would roughly
|
|
41
|
-
// halve walk/sprint/airControl and use peakHeight ≈ 1.0–1.2.
|
|
42
|
-
motion = {
|
|
43
|
-
walkSpeed: 4.5, // brisk jog — quicker than a real walk
|
|
44
|
-
sprintSpeed: 9.0, // fast but sub-Olympic (~12 m/s is human peak)
|
|
45
|
-
crouchSpeed: 2.2,
|
|
46
|
-
/** [0..1] how much intent influences velocity while airborne. */
|
|
47
|
-
airControl: 0.5,
|
|
48
|
-
/**
|
|
49
|
-
* Half-life (seconds) for the mono-exponential approach to top
|
|
50
|
-
* sprint speed on the ground. The controller's ground-accel
|
|
51
|
-
* model is Hill-based:
|
|
52
|
-
* v(t) = v_max · (1 − e^(−t / τ))
|
|
53
|
-
* with τ = halfLife / ln 2. At each tick the velocity halves
|
|
54
|
-
* the remaining gap to `targetSpeed` every `halfLife` seconds.
|
|
55
|
-
*
|
|
56
|
-
* Default `0.116 s` gives ~95 % of `sprintSpeed` reached in
|
|
57
|
-
* 500 ms — the Apex Legends / Titanfall / modern CoD ramp
|
|
58
|
-
* window. The curve is naturally non-uniform: roughly 45 %
|
|
59
|
-
* of top speed in the first 100 ms, then the last 5 % takes
|
|
60
|
-
* 300 ms — so a quick tap-and-go still feels snappy while
|
|
61
|
-
* follow-up moves (slide, sprint-jump) require committed
|
|
62
|
-
* sprint time to be at top speed.
|
|
63
|
-
*
|
|
64
|
-
* Literature: Hill 1927 / Furusawa-Hill 1928 (force-velocity
|
|
65
|
-
* curve, original analytical formulation); Morin & Samozino
|
|
66
|
-
* 2016 (modern sprint profiling using the same mono-exp fit).
|
|
67
|
-
*/
|
|
68
|
-
groundAccelHalfLife: 0.116,
|
|
69
|
-
/** m/s^2 horizontal deceleration toward 0 when intent is zero. */
|
|
70
|
-
groundDecel: 75, // crisp stops, no skating
|
|
71
|
-
/** m/s^2 horizontal acceleration while airborne. */
|
|
72
|
-
airAccel: 14, // mid-air agility for course-correction
|
|
73
|
-
/**
|
|
74
|
-
* Top-speed multiplier applied while the move intent has a backward
|
|
75
|
-
* component (intent.move.y < 0). Backpedaling is mechanically slower
|
|
76
|
-
* than running forward — this also enforces a hard cap rather than
|
|
77
|
-
* letting "backward sprint" reach the same speed as forward sprint.
|
|
78
|
-
*/
|
|
79
|
-
backwardSpeedFactor: 0.65,
|
|
80
|
-
/**
|
|
81
|
-
* Acceleration multiplier while moving backward. Body has worse
|
|
82
|
-
* mechanical advantage when reversing — slower to start, slower to
|
|
83
|
-
* stop, sells the "fighting the gait" feel.
|
|
84
|
-
*/
|
|
85
|
-
backwardAccelFactor: 0.65,
|
|
86
|
-
};
|
|
87
|
-
|
|
88
|
-
jump = {
|
|
89
|
-
/** Apex height of a clean jump, meters. */
|
|
90
|
-
peakHeight: 1.8, // matches eye height — the iconic "athlete leap"
|
|
91
|
-
/** Seconds to reach apex from launch. Together these two derive g. */
|
|
92
|
-
timeToApex: 0.35, // snappier rise
|
|
93
|
-
coyoteTime: 0.14, // generous for forgiving platforming
|
|
94
|
-
bufferTime: 0.16,
|
|
95
|
-
/** Gravity multiplier while falling (vy <= 0). */
|
|
96
|
-
fallGravityMult: 2.4, // fast fall — classic Mario/Sonic feel
|
|
97
|
-
/** Gravity multiplier when jump released early during ascent. */
|
|
98
|
-
cutGravityMult: 2.0, // sharper variable-height differentiation
|
|
99
|
-
anticipation: {
|
|
100
|
-
duration: 0.06, // short — keeps jumps responsive
|
|
101
|
-
dipAmount: 0.08, // but visibly squashes
|
|
102
|
-
},
|
|
103
|
-
};
|
|
104
|
-
|
|
105
|
-
landing = {
|
|
106
|
-
/** m/s impact threshold; below = soft landing signal. */
|
|
107
|
-
softThreshold: 3.0,
|
|
108
|
-
/** m/s impact threshold; above = hard landing signal. */
|
|
109
|
-
hardThreshold: 7.5,
|
|
110
|
-
recovery: {
|
|
111
|
-
/** Meters of eye dip per m/s of impact velocity. */
|
|
112
|
-
dipPerVy: 0.016, // beefier squash on landing
|
|
113
|
-
/** Clamp on total dip. */
|
|
114
|
-
dipMax: 0.24,
|
|
115
|
-
spring: {
|
|
116
|
-
halfLife: 0.11,
|
|
117
|
-
/** zeta < 1 → rings once before settling, reads as compression. */
|
|
118
|
-
zeta: 0.50, // a touch more bounce
|
|
119
|
-
},
|
|
120
|
-
},
|
|
121
|
-
};
|
|
122
|
-
|
|
123
|
-
crouch = {
|
|
124
|
-
/** Seconds to lerp eye height between standing and crouched. */
|
|
125
|
-
transitionTime: 0.18,
|
|
126
|
-
/** "hold" or "toggle" */
|
|
127
|
-
mode: "hold",
|
|
128
|
-
};
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
* Wall-run ability — airborne + lateral wall + speed → run along the
|
|
132
|
-
* wall with reduced gravity. Camera tilts into the wall via the
|
|
133
|
-
* shared lean spring (consistent with the rest of the camera).
|
|
134
|
-
* Fields here are consumed by the {@link WallRun} ability when
|
|
135
|
-
* registered.
|
|
136
|
-
*/
|
|
137
|
-
wallRun = {
|
|
138
|
-
/**
|
|
139
|
-
* Minimum horizontal speed (m/s) to engage wall-run. Set ABOVE
|
|
140
|
-
* walkSpeed (4.5) so a wall-run is earned by a committed RUN — a walk
|
|
141
|
-
* into the wall, or falling alongside it with leftover momentum,
|
|
142
|
-
* shouldn't grab on. Below the ~7 m/s a sprint-into-wall carries.
|
|
143
|
-
*/
|
|
144
|
-
minSpeed: 5.5,
|
|
145
|
-
/** Player must be airborne at least this long before wall-run can
|
|
146
|
-
* engage — prevents wall-run triggering off a jump's first frames. */
|
|
147
|
-
minAirborneTime: 0.1,
|
|
148
|
-
/** Maximum wall-run duration (s). Auto-exits at the end. */
|
|
149
|
-
maxDuration: 2.0,
|
|
150
|
-
/** Multiplier on base gravity while wall-running. Lower = longer hang. */
|
|
151
|
-
gravityFactor: 0.25,
|
|
152
|
-
/** Camera tilt magnitude (deg) into the wall. Disabled by the
|
|
153
|
-
* motion-sickness toggle alongside the standard lean. */
|
|
154
|
-
cameraRollDeg: 12,
|
|
155
|
-
};
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* Wall-jump ability — instantaneous off-wall impulse. Fires from
|
|
159
|
-
* either an active wall-run state or from near-wall airborne (a
|
|
160
|
-
* short window where the player is close enough to push off).
|
|
161
|
-
*/
|
|
162
|
-
wallJump = {
|
|
163
|
-
/** Impulse magnitude away from the wall (m/s, applied along wall normal). */
|
|
164
|
-
outwardImpulse: 6.0,
|
|
165
|
-
/** Upward component multiplier on jumpInitialVy. */
|
|
166
|
-
upFactor: 0.9,
|
|
167
|
-
/** Max distance to wall (m) at which a near-wall-airborne wall-jump
|
|
168
|
-
* is allowed (when not already wall-running). */
|
|
169
|
-
nearWallMaxDistance: 0.6,
|
|
170
|
-
};
|
|
171
|
-
|
|
172
|
-
/**
|
|
173
|
-
* Ledge-grab ability — snap to a forward ledge while descending and
|
|
174
|
-
* hang from it until the player releases. Climbing fatigue is
|
|
175
|
-
* modelled via the shared exertion channel: hang too long and you
|
|
176
|
-
* slip. Fields here are consumed by the {@link LedgeGrab} ability
|
|
177
|
-
* if registered; ignored otherwise.
|
|
178
|
-
*/
|
|
179
|
-
ledgeGrab = {
|
|
180
|
-
/** Body Y offset from ledge top while hanging (negative — feet
|
|
181
|
-
* below the ledge so the body looks like it's gripping the
|
|
182
|
-
* edge). */
|
|
183
|
-
hangOffsetY: -1.4,
|
|
184
|
-
/**
|
|
185
|
-
*
|
|
186
|
-
*
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
*
|
|
197
|
-
*
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
* the
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
*
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
* on the
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
*
|
|
228
|
-
*
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
*
|
|
234
|
-
*
|
|
235
|
-
*
|
|
236
|
-
*
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
*
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
*
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
*
|
|
278
|
-
*
|
|
279
|
-
*
|
|
280
|
-
*
|
|
281
|
-
*
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
*
|
|
287
|
-
*
|
|
288
|
-
*
|
|
289
|
-
*
|
|
290
|
-
*
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
*
|
|
298
|
-
*
|
|
299
|
-
*
|
|
300
|
-
*
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
*
|
|
306
|
-
*
|
|
307
|
-
*
|
|
308
|
-
*
|
|
309
|
-
*
|
|
310
|
-
*
|
|
311
|
-
*
|
|
312
|
-
*
|
|
313
|
-
*
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
*
|
|
321
|
-
*
|
|
322
|
-
*
|
|
323
|
-
*
|
|
324
|
-
*
|
|
325
|
-
*
|
|
326
|
-
*
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
*
|
|
343
|
-
*
|
|
344
|
-
*
|
|
345
|
-
*
|
|
346
|
-
*
|
|
347
|
-
*
|
|
348
|
-
*
|
|
349
|
-
*
|
|
350
|
-
*
|
|
351
|
-
*
|
|
352
|
-
*
|
|
353
|
-
*
|
|
354
|
-
*
|
|
355
|
-
*
|
|
356
|
-
*
|
|
357
|
-
*
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
*
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
*
|
|
382
|
-
*
|
|
383
|
-
*
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
|
|
397
|
-
|
|
398
|
-
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
*
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
*
|
|
419
|
-
*
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
*
|
|
428
|
-
*
|
|
429
|
-
*
|
|
430
|
-
*
|
|
431
|
-
*
|
|
432
|
-
|
|
433
|
-
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
447
|
-
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
*
|
|
452
|
-
*
|
|
453
|
-
*
|
|
454
|
-
*
|
|
455
|
-
*
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
461
|
-
|
|
462
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
*
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
479
|
-
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
|
|
504
|
-
if (json.
|
|
505
|
-
if (json.
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
|
|
514
|
-
|
|
515
|
-
|
|
516
|
-
|
|
517
|
-
|
|
518
|
-
|
|
519
|
-
|
|
520
|
-
if (json.
|
|
521
|
-
if (json.
|
|
522
|
-
if (json.
|
|
523
|
-
if (json.
|
|
524
|
-
if (json.
|
|
525
|
-
if (json.
|
|
526
|
-
if (json.
|
|
527
|
-
if (json.
|
|
528
|
-
if (json.
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
if (json.
|
|
535
|
-
if (json.
|
|
536
|
-
if (json.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
542
|
-
|
|
543
|
-
|
|
544
|
-
|
|
545
|
-
|
|
546
|
-
|
|
547
|
-
|
|
548
|
-
c
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
}
|
|
1
|
+
/**
|
|
2
|
+
* Tunable parameters for {@link FirstPersonPlayerController}. The full
|
|
3
|
+
* config is the only serialized portion of the component — everything else
|
|
4
|
+
* (intent, state, pose, signals) is transient and rebuilt on link.
|
|
5
|
+
*
|
|
6
|
+
* Defaults aim for a baseline "human soldier" feel — see DESIGN.md §4.1.
|
|
7
|
+
* Every value is exposed so designers can mod it to taste.
|
|
8
|
+
*/
|
|
9
|
+
export class FirstPersonPlayerControllerConfig {
|
|
10
|
+
body = {
|
|
11
|
+
/** Standing eye height, meters above body origin. */
|
|
12
|
+
height: 1.80,
|
|
13
|
+
/** Crouched eye height. */
|
|
14
|
+
crouchHeight: 0.8,
|
|
15
|
+
/** Prone eye height (used by slide). Body is horizontal — eye
|
|
16
|
+
* just above ground level, head turned toward the direction
|
|
17
|
+
* of travel. */
|
|
18
|
+
proneHeight: 0.4,
|
|
19
|
+
/** Capsule radius, for downstream physics. */
|
|
20
|
+
radius: 0.34,
|
|
21
|
+
/** Kilograms — affects bob, jump, landing, accel, and exertion via massRatio. */
|
|
22
|
+
mass: 80,
|
|
23
|
+
/** Reference mass against which `mass` is normalized (massRatio = mass/refMass). */
|
|
24
|
+
referenceMass: 80,
|
|
25
|
+
/**
|
|
26
|
+
* How strongly `mass` couples into derived effects. 0 = mass is
|
|
27
|
+
* cosmetic; 1.0 = full coupling (heavier ⇒ lower jump, lower accel,
|
|
28
|
+
* harder landing, faster exhaustion). At 1.0 a 25% mass increase
|
|
29
|
+
* yields:
|
|
30
|
+
* jump peak ≈ 0.83× (∝ 1/√massRatio — kinematically motivated)
|
|
31
|
+
* ground accel ≈ 0.80× (∝ 1/massRatio — F = ma at fixed force)
|
|
32
|
+
* landing dip ≈ 1.25× (∝ massRatio — higher momentum to absorb)
|
|
33
|
+
* exertion rise ≈ 1.25× (∝ massRatio — more body to move)
|
|
34
|
+
*/
|
|
35
|
+
massCouplingStrength: 1.0,
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Defaults are tuned toward an "athlete with juice" feel — plausible
|
|
39
|
+
// but biased for fun: snappy accel, fast top speed, generous air
|
|
40
|
+
// control, satisfying jump. Realistic-soldier values would roughly
|
|
41
|
+
// halve walk/sprint/airControl and use peakHeight ≈ 1.0–1.2.
|
|
42
|
+
motion = {
|
|
43
|
+
walkSpeed: 4.5, // brisk jog — quicker than a real walk
|
|
44
|
+
sprintSpeed: 9.0, // fast but sub-Olympic (~12 m/s is human peak)
|
|
45
|
+
crouchSpeed: 2.2,
|
|
46
|
+
/** [0..1] how much intent influences velocity while airborne. */
|
|
47
|
+
airControl: 0.5,
|
|
48
|
+
/**
|
|
49
|
+
* Half-life (seconds) for the mono-exponential approach to top
|
|
50
|
+
* sprint speed on the ground. The controller's ground-accel
|
|
51
|
+
* model is Hill-based:
|
|
52
|
+
* v(t) = v_max · (1 − e^(−t / τ))
|
|
53
|
+
* with τ = halfLife / ln 2. At each tick the velocity halves
|
|
54
|
+
* the remaining gap to `targetSpeed` every `halfLife` seconds.
|
|
55
|
+
*
|
|
56
|
+
* Default `0.116 s` gives ~95 % of `sprintSpeed` reached in
|
|
57
|
+
* 500 ms — the Apex Legends / Titanfall / modern CoD ramp
|
|
58
|
+
* window. The curve is naturally non-uniform: roughly 45 %
|
|
59
|
+
* of top speed in the first 100 ms, then the last 5 % takes
|
|
60
|
+
* 300 ms — so a quick tap-and-go still feels snappy while
|
|
61
|
+
* follow-up moves (slide, sprint-jump) require committed
|
|
62
|
+
* sprint time to be at top speed.
|
|
63
|
+
*
|
|
64
|
+
* Literature: Hill 1927 / Furusawa-Hill 1928 (force-velocity
|
|
65
|
+
* curve, original analytical formulation); Morin & Samozino
|
|
66
|
+
* 2016 (modern sprint profiling using the same mono-exp fit).
|
|
67
|
+
*/
|
|
68
|
+
groundAccelHalfLife: 0.116,
|
|
69
|
+
/** m/s^2 horizontal deceleration toward 0 when intent is zero. */
|
|
70
|
+
groundDecel: 75, // crisp stops, no skating
|
|
71
|
+
/** m/s^2 horizontal acceleration while airborne. */
|
|
72
|
+
airAccel: 14, // mid-air agility for course-correction
|
|
73
|
+
/**
|
|
74
|
+
* Top-speed multiplier applied while the move intent has a backward
|
|
75
|
+
* component (intent.move.y < 0). Backpedaling is mechanically slower
|
|
76
|
+
* than running forward — this also enforces a hard cap rather than
|
|
77
|
+
* letting "backward sprint" reach the same speed as forward sprint.
|
|
78
|
+
*/
|
|
79
|
+
backwardSpeedFactor: 0.65,
|
|
80
|
+
/**
|
|
81
|
+
* Acceleration multiplier while moving backward. Body has worse
|
|
82
|
+
* mechanical advantage when reversing — slower to start, slower to
|
|
83
|
+
* stop, sells the "fighting the gait" feel.
|
|
84
|
+
*/
|
|
85
|
+
backwardAccelFactor: 0.65,
|
|
86
|
+
};
|
|
87
|
+
|
|
88
|
+
jump = {
|
|
89
|
+
/** Apex height of a clean jump, meters. */
|
|
90
|
+
peakHeight: 1.8, // matches eye height — the iconic "athlete leap"
|
|
91
|
+
/** Seconds to reach apex from launch. Together these two derive g. */
|
|
92
|
+
timeToApex: 0.35, // snappier rise
|
|
93
|
+
coyoteTime: 0.14, // generous for forgiving platforming
|
|
94
|
+
bufferTime: 0.16,
|
|
95
|
+
/** Gravity multiplier while falling (vy <= 0). */
|
|
96
|
+
fallGravityMult: 2.4, // fast fall — classic Mario/Sonic feel
|
|
97
|
+
/** Gravity multiplier when jump released early during ascent. */
|
|
98
|
+
cutGravityMult: 2.0, // sharper variable-height differentiation
|
|
99
|
+
anticipation: {
|
|
100
|
+
duration: 0.06, // short — keeps jumps responsive
|
|
101
|
+
dipAmount: 0.08, // but visibly squashes
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
|
|
105
|
+
landing = {
|
|
106
|
+
/** m/s impact threshold; below = soft landing signal. */
|
|
107
|
+
softThreshold: 3.0,
|
|
108
|
+
/** m/s impact threshold; above = hard landing signal. */
|
|
109
|
+
hardThreshold: 7.5,
|
|
110
|
+
recovery: {
|
|
111
|
+
/** Meters of eye dip per m/s of impact velocity. */
|
|
112
|
+
dipPerVy: 0.016, // beefier squash on landing
|
|
113
|
+
/** Clamp on total dip. */
|
|
114
|
+
dipMax: 0.24,
|
|
115
|
+
spring: {
|
|
116
|
+
halfLife: 0.11,
|
|
117
|
+
/** zeta < 1 → rings once before settling, reads as compression. */
|
|
118
|
+
zeta: 0.50, // a touch more bounce
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
};
|
|
122
|
+
|
|
123
|
+
crouch = {
|
|
124
|
+
/** Seconds to lerp eye height between standing and crouched. */
|
|
125
|
+
transitionTime: 0.18,
|
|
126
|
+
/** "hold" or "toggle" */
|
|
127
|
+
mode: "hold",
|
|
128
|
+
};
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Wall-run ability — airborne + lateral wall + speed → run along the
|
|
132
|
+
* wall with reduced gravity. Camera tilts into the wall via the
|
|
133
|
+
* shared lean spring (consistent with the rest of the camera).
|
|
134
|
+
* Fields here are consumed by the {@link WallRun} ability when
|
|
135
|
+
* registered.
|
|
136
|
+
*/
|
|
137
|
+
wallRun = {
|
|
138
|
+
/**
|
|
139
|
+
* Minimum horizontal speed (m/s) to engage wall-run. Set ABOVE
|
|
140
|
+
* walkSpeed (4.5) so a wall-run is earned by a committed RUN — a walk
|
|
141
|
+
* into the wall, or falling alongside it with leftover momentum,
|
|
142
|
+
* shouldn't grab on. Below the ~7 m/s a sprint-into-wall carries.
|
|
143
|
+
*/
|
|
144
|
+
minSpeed: 5.5,
|
|
145
|
+
/** Player must be airborne at least this long before wall-run can
|
|
146
|
+
* engage — prevents wall-run triggering off a jump's first frames. */
|
|
147
|
+
minAirborneTime: 0.1,
|
|
148
|
+
/** Maximum wall-run duration (s). Auto-exits at the end. */
|
|
149
|
+
maxDuration: 2.0,
|
|
150
|
+
/** Multiplier on base gravity while wall-running. Lower = longer hang. */
|
|
151
|
+
gravityFactor: 0.25,
|
|
152
|
+
/** Camera tilt magnitude (deg) into the wall. Disabled by the
|
|
153
|
+
* motion-sickness toggle alongside the standard lean. */
|
|
154
|
+
cameraRollDeg: 12,
|
|
155
|
+
};
|
|
156
|
+
|
|
157
|
+
/**
|
|
158
|
+
* Wall-jump ability — instantaneous off-wall impulse. Fires from
|
|
159
|
+
* either an active wall-run state or from near-wall airborne (a
|
|
160
|
+
* short window where the player is close enough to push off).
|
|
161
|
+
*/
|
|
162
|
+
wallJump = {
|
|
163
|
+
/** Impulse magnitude away from the wall (m/s, applied along wall normal). */
|
|
164
|
+
outwardImpulse: 6.0,
|
|
165
|
+
/** Upward component multiplier on jumpInitialVy. */
|
|
166
|
+
upFactor: 0.9,
|
|
167
|
+
/** Max distance to wall (m) at which a near-wall-airborne wall-jump
|
|
168
|
+
* is allowed (when not already wall-running). */
|
|
169
|
+
nearWallMaxDistance: 0.6,
|
|
170
|
+
};
|
|
171
|
+
|
|
172
|
+
/**
|
|
173
|
+
* Ledge-grab ability — snap to a forward ledge while descending and
|
|
174
|
+
* hang from it until the player releases. Climbing fatigue is
|
|
175
|
+
* modelled via the shared exertion channel: hang too long and you
|
|
176
|
+
* slip. Fields here are consumed by the {@link LedgeGrab} ability
|
|
177
|
+
* if registered; ignored otherwise.
|
|
178
|
+
*/
|
|
179
|
+
ledgeGrab = {
|
|
180
|
+
/** Body Y offset from ledge top while hanging (negative — feet
|
|
181
|
+
* below the ledge so the body looks like it's gripping the
|
|
182
|
+
* edge). */
|
|
183
|
+
hangOffsetY: -1.4,
|
|
184
|
+
/** Gap (m) the hanging body's front sits OUT from the wall face —
|
|
185
|
+
* added to the capsule radius to form the standoff along the wall's
|
|
186
|
+
* outward normal. Small positive = front just clears the face so the
|
|
187
|
+
* body grips the lip without clipping the wall. */
|
|
188
|
+
hangOffsetForward: 0.05,
|
|
189
|
+
/** Exertion rise per second while hanging — drives slip-by-
|
|
190
|
+
* fatigue. The base exertion rise scale (massRatios) applies. */
|
|
191
|
+
exertionRiseRate: 0.15,
|
|
192
|
+
/** Player must be airborne at least this long before a ledge-
|
|
193
|
+
* grab can engage — prevents auto-catching mid-jump. */
|
|
194
|
+
minAirborneTime: 0.3,
|
|
195
|
+
/** Vertical kick (multiplier on jumpInitialVy) given on a
|
|
196
|
+
* mantle-up release. Just enough to put the body above the
|
|
197
|
+
* ledge so mantle's ledgeAhead probe still resolves correctly
|
|
198
|
+
* on the next tick. */
|
|
199
|
+
mantleUpUpFactor: 0.3,
|
|
200
|
+
};
|
|
201
|
+
|
|
202
|
+
/**
|
|
203
|
+
* Mantle ability — auto-triggers when the player approaches a
|
|
204
|
+
* reachable obstacle with forward intent. The body follows a scripted
|
|
205
|
+
* path (horizontal + vertical eases) onto the surface; player input
|
|
206
|
+
* is suspended for the path's duration. Fields here are consumed by
|
|
207
|
+
* the {@link Mantle} ability if registered; ignored otherwise.
|
|
208
|
+
*/
|
|
209
|
+
mantle = {
|
|
210
|
+
/** Below this height (m above feet), the obstacle is just a step
|
|
211
|
+
* the player can walk over — no mantle. */
|
|
212
|
+
minHeight: 0.4,
|
|
213
|
+
/** Above this, the surface is out of reach — no mantle. ~1.4m
|
|
214
|
+
* is chest height for a 1.8m body, which is the canonical
|
|
215
|
+
* "obstacle you can climb on but not over without help". */
|
|
216
|
+
maxHeight: 1.4,
|
|
217
|
+
/** Total scripted-path duration (seconds). Short enough that the
|
|
218
|
+
* brief loss of player control isn't frustrating. */
|
|
219
|
+
duration: 0.5,
|
|
220
|
+
/** How far past the obstacle edge the player lands (m). Slight
|
|
221
|
+
* forward bias so the player ends on top of the surface, not
|
|
222
|
+
* on the edge. */
|
|
223
|
+
forwardOffsetOnLand: 0.4,
|
|
224
|
+
};
|
|
225
|
+
|
|
226
|
+
/**
|
|
227
|
+
* Slide ability — activates from sprint+crouch when grounded. The
|
|
228
|
+
* fields here are read by the {@link Slide} ability if it's registered
|
|
229
|
+
* on the controller; unused if Slide is not in use.
|
|
230
|
+
*/
|
|
231
|
+
slide = {
|
|
232
|
+
/**
|
|
233
|
+
* Required horizontal speed to start a slide. Below this,
|
|
234
|
+
* crouch + sprint just transitions to a normal crouched walk.
|
|
235
|
+
*
|
|
236
|
+
* The default — `7.5 m/s` — sits between walkSpeed (4.5) and
|
|
237
|
+
* sprintSpeed (9), at ~83 % of sprint. Earning the slide is
|
|
238
|
+
* enforced by the ground-accel model: with
|
|
239
|
+
* `groundAccelHalfLife = 0.116 s`, reaching this threshold
|
|
240
|
+
* from standstill takes ~300 ms of sustained sprint — well
|
|
241
|
+
* past a normal reaction window. Walking off a ledge or
|
|
242
|
+
* accelerating down a slope into a slide is explicitly
|
|
243
|
+
* supported: anything that brings the player's horizontal
|
|
244
|
+
* speed above this threshold lets crouch engage a slide.
|
|
245
|
+
* (Following Apex Legends' velocity-only gate at 200 hu/s.)
|
|
246
|
+
*/
|
|
247
|
+
minEntrySpeed: 7.5,
|
|
248
|
+
/** Auto-exit threshold — slide ends when speed drops below this. */
|
|
249
|
+
endSpeed: 2.0,
|
|
250
|
+
/** Horizontal deceleration (m/s²) while sliding. Lower than the
|
|
251
|
+
* normal groundDecel so slides preserve momentum longer. */
|
|
252
|
+
friction: 3.0,
|
|
253
|
+
/** Non-interruptible window (seconds) after slide start — higher-
|
|
254
|
+
* priority abilities can't preempt during this window so the
|
|
255
|
+
* player isn't yanked out the instant they commit to a slide. */
|
|
256
|
+
startupWindow: 0.15,
|
|
257
|
+
/** Multiplier on the jump initial Vy for slide-jump. 1.0 = same
|
|
258
|
+
* height as a standing jump; >1 gives slide-jump some extra
|
|
259
|
+
* reward for chaining the input. */
|
|
260
|
+
slideJumpUpFactor: 1.05,
|
|
261
|
+
/** Vertical-impact-spring kick (m/s) at slide entry — the
|
|
262
|
+
* satisfying "thud" of dropping into the slide. */
|
|
263
|
+
entryImpactVelocity: 0.6,
|
|
264
|
+
};
|
|
265
|
+
|
|
266
|
+
bob = {
|
|
267
|
+
stepFreqAtWalk: 1.8,
|
|
268
|
+
stepFreqExp: 0.75, // sub-linear so sprint isn't frantic
|
|
269
|
+
verticalAmpAtWalk: 0.055,
|
|
270
|
+
lateralAmpAtWalk: 0.030,
|
|
271
|
+
/** Extra amplitude per kg above 80kg (reference mass). */
|
|
272
|
+
ampMassScale: 0.005,
|
|
273
|
+
rollAtWalkDeg: 0.8, // a touch more visible head sway
|
|
274
|
+
/** Speeds below this don't tick the stride at all. */
|
|
275
|
+
minStepSpeed: 0.2,
|
|
276
|
+
/**
|
|
277
|
+
* Half-life of the bob-amplitude envelope (seconds). When the player
|
|
278
|
+
* starts/stops moving, the bob amplitude springs toward its natural
|
|
279
|
+
* value rather than cutting on/off — so releasing a movement key
|
|
280
|
+
* doesn't whiplash the head from full sway back to neutral. The
|
|
281
|
+
* stride phase itself still freezes when stationary; only the
|
|
282
|
+
* AMPLITUDE is temporally smoothed.
|
|
283
|
+
*/
|
|
284
|
+
intensityHalfLife: 0.22,
|
|
285
|
+
/**
|
|
286
|
+
* Vertical impact spring — kicked downward at each footfall. The
|
|
287
|
+
* spring's response shapes the actual vertical motion of the head:
|
|
288
|
+
* a sharp dip (foot arresting the head's fall), recovery through
|
|
289
|
+
* neutral, and a slight overshoot above neutral as the standing
|
|
290
|
+
* leg injects energy into the body (the "push" phase). Models the
|
|
291
|
+
* gait COM trajectory more accurately than a smooth sinusoid.
|
|
292
|
+
*/
|
|
293
|
+
impactSpringHalfLife: 0.14,
|
|
294
|
+
/** zeta < 1 → spring overshoots after impact, sells the leg-push. */
|
|
295
|
+
impactSpringZeta: 0.50,
|
|
296
|
+
/**
|
|
297
|
+
* Multiplier converting "desired peak dip" (verticalAmpAtWalk + mass
|
|
298
|
+
* boost, scaled by intensity and back-pedal factor) into the actual
|
|
299
|
+
* velocity impulse delivered at footfall. Empirical — tuned so the
|
|
300
|
+
* default verticalAmpAtWalk produces the expected peak dip with the
|
|
301
|
+
* spring params above.
|
|
302
|
+
*/
|
|
303
|
+
impactKickMultiplier: 30,
|
|
304
|
+
/**
|
|
305
|
+
* Multipliers applied to bob amplitude when the player is moving
|
|
306
|
+
* backward (interpolated by "backwardness" — the negative dot of
|
|
307
|
+
* velocity onto screen-forward, normalized to sprint speed). At
|
|
308
|
+
* full back-pedal these are fully applied; at rest they have no
|
|
309
|
+
* effect.
|
|
310
|
+
*
|
|
311
|
+
* Lateral is intentionally larger than vertical: real back-gait
|
|
312
|
+
* has worse side-to-side balance than vertical compression, so
|
|
313
|
+
* the visual wobble should track that. Roll boosts each footfall's
|
|
314
|
+
* head-tilt for the same reason — sells "unstable footing".
|
|
315
|
+
*/
|
|
316
|
+
backwardVerticalAmpFactor: 1.2,
|
|
317
|
+
backwardLateralAmpFactor: 1.4,
|
|
318
|
+
backwardRollFactor: 1.3,
|
|
319
|
+
/**
|
|
320
|
+
* Bob roll × lean coupling: when the body is leaning into a turn
|
|
321
|
+
* (lean spring displaced), the per-stride head roll is biased
|
|
322
|
+
* toward the lean direction. At 0 the bob roll is symmetric
|
|
323
|
+
* regardless of lean. At 1.0 the bob roll excursions in the lean
|
|
324
|
+
* direction are doubled and the opposite-direction excursions go to
|
|
325
|
+
* zero. Typical 0.3–0.6 reads as a cohesive "leaning while running"
|
|
326
|
+
* gait. Normalized against cfg.lean.maxRollDeg so the coupling
|
|
327
|
+
* stays proportional regardless of how aggressive lean is tuned.
|
|
328
|
+
*/
|
|
329
|
+
leanCouplingFactor: 0.45,
|
|
330
|
+
};
|
|
331
|
+
|
|
332
|
+
breath = {
|
|
333
|
+
rateRestHz: 0.23,
|
|
334
|
+
rateMaxHz: 0.70,
|
|
335
|
+
amplitudeRestM: 0.004,
|
|
336
|
+
amplitudeMaxM: 0.022,
|
|
337
|
+
pitchAmpRestDeg: 0.10,
|
|
338
|
+
pitchAmpMaxDeg: 0.55,
|
|
339
|
+
/** ±20% amplitude noise rides on the sine. */
|
|
340
|
+
noiseAmount: 0.20,
|
|
341
|
+
/**
|
|
342
|
+
* Locomotor-respiratory coupling strength at full exertion. The
|
|
343
|
+
* breath-rate target is blended between purely metabolic (driven by
|
|
344
|
+
* exertion) and stride-aligned (an integer N strides per breath
|
|
345
|
+
* cycle). At 0 no coupling; at 1 breath rate is fully pulled toward
|
|
346
|
+
* the stride-aligned target.
|
|
347
|
+
*
|
|
348
|
+
* Coupling is SCALED by exertion — low exertion means the breath is
|
|
349
|
+
* dictated by metabolic demand alone, high sustained effort lets
|
|
350
|
+
* the diaphragm couple to gait impacts (mechanistically: heavier
|
|
351
|
+
* breathing means deeper chest excursions, which the gait's
|
|
352
|
+
* vertical accelerations bias toward integer ratios). Matches
|
|
353
|
+
* empirical findings in dynamic activity (Bernasconi & Kohl 1993,
|
|
354
|
+
* Bramble & Carrier 1983) — much weaker than steady-state running.
|
|
355
|
+
*
|
|
356
|
+
* Frequency-coupled only, NOT phase-locked: breath rate matches a
|
|
357
|
+
* sub-multiple of stride rate, but phase drifts. Holds the "loose"
|
|
358
|
+
* character the user asked for.
|
|
359
|
+
*/
|
|
360
|
+
locomotorCouplingMax: 0.45,
|
|
361
|
+
/** Coupling does not engage below this stride frequency (Hz). At
|
|
362
|
+
* walking pace your gait is too slow to mechanically influence
|
|
363
|
+
* breath; coupling really shows up at jog+ pace. */
|
|
364
|
+
couplingMinStrideFreqHz: 1.5,
|
|
365
|
+
};
|
|
366
|
+
|
|
367
|
+
exertion = {
|
|
368
|
+
// -- Sustained drivers (continuous accumulation per second) ---
|
|
369
|
+
/** Per-second accumulation while sprinting (mass-scaled internally). */
|
|
370
|
+
sprintRiseRate: 0.20,
|
|
371
|
+
/** Per-second decay toward 0 when not under sustained load. */
|
|
372
|
+
idleDecayRate: 0.12,
|
|
373
|
+
|
|
374
|
+
// -- Impulse drivers (one-shot bumps on discrete events) ------
|
|
375
|
+
/** Bump on jump fire — the muscular impulse of pushing off. */
|
|
376
|
+
jumpRise: 0.08,
|
|
377
|
+
/** Bump on crouch-enter — the knee-grip on absorbing into a crouch.
|
|
378
|
+
* Small; we don't want crouch-spamming to instantly exhaust. */
|
|
379
|
+
crouchEnterRise: 0.03,
|
|
380
|
+
/**
|
|
381
|
+
* Per-(m/s) impulse on landing — proportional to vertical impact
|
|
382
|
+
* speed. A clean low-jump landing barely registers; a hard fall
|
|
383
|
+
* delivers a noticeable bump. Clamped internally so an impossible
|
|
384
|
+
* fall doesn't max out exertion in one tick.
|
|
385
|
+
*/
|
|
386
|
+
landImpulsePerVy: 0.012,
|
|
387
|
+
/** Max impulse magnitude added by any single landing. */
|
|
388
|
+
landImpulseMax: 0.20,
|
|
389
|
+
|
|
390
|
+
// -- Breath coupling (existing — unchanged contract) ----------
|
|
391
|
+
/** How long after exertion subsides before breath rate normalizes. */
|
|
392
|
+
rateDecayHalfLife: 4.0,
|
|
393
|
+
/** How long after exertion subsides before breath amplitude normalizes. */
|
|
394
|
+
ampDecayHalfLife: 2.0,
|
|
395
|
+
|
|
396
|
+
// -- Output coupling: exertion affects how the body moves -----
|
|
397
|
+
/** At full exertion, bob lateral amp is multiplied by 1 + this.
|
|
398
|
+
* Sells "tired wobble" — modest effect (10–20% recommended). */
|
|
399
|
+
bobLateralBoostAtMax: 0.20,
|
|
400
|
+
/** At full exertion, the head droops forward by this many degrees.
|
|
401
|
+
* Sells visual fatigue without compromising aim. Subtle. */
|
|
402
|
+
headDroopAtMaxDeg: 1.5,
|
|
403
|
+
/** Spring half-life for the head-droop transition. */
|
|
404
|
+
headDroopHalfLife: 0.6,
|
|
405
|
+
};
|
|
406
|
+
|
|
407
|
+
lean = {
|
|
408
|
+
enabled: true,
|
|
409
|
+
/** Degrees of roll per (lateral m/s^2 / 9.81). */
|
|
410
|
+
maxRollDeg: 3.2, // more visible lean into turns — sells the athleticism
|
|
411
|
+
spring: { halfLife: 0.16, zeta: 1.0 },
|
|
412
|
+
/**
|
|
413
|
+
* Yaw-rate "look-lean": camera banks into mouse-driven turns even
|
|
414
|
+
* without strafe input. Disable for motion-sickness sensitivity.
|
|
415
|
+
*/
|
|
416
|
+
lookLeanEnabled: true,
|
|
417
|
+
/**
|
|
418
|
+
* Degrees of roll per rad/s of yaw rate. ~3 means a 180°/s turn
|
|
419
|
+
* (slow camera pan) gives ~16.5° of bank; a fast mouse flick at
|
|
420
|
+
* 5 rad/s (≈286°/s) would saturate against the clamp below.
|
|
421
|
+
*/
|
|
422
|
+
lookLeanDegPerRadPerSec: 3.0,
|
|
423
|
+
/** Yaw rate is clamped to ±this (rad/s) before scaling — prevents
|
|
424
|
+
* single-frame flicks from spiking the lean target. */
|
|
425
|
+
lookLeanYawRateClamp: 4.0,
|
|
426
|
+
/**
|
|
427
|
+
* Multiplier applied to look-lean (yaw-rate banking) when crouched.
|
|
428
|
+
* Crouched stance is low and stable; banking from a mouse turn
|
|
429
|
+
* reads as unmotivated. Default 0 fully disables; dial up to add
|
|
430
|
+
* a subtle tilt without it feeling like you're running. The lateral-
|
|
431
|
+
* acceleration lean is NOT affected by this — it scales naturally
|
|
432
|
+
* with crouch speeds since accel magnitudes drop.
|
|
433
|
+
*/
|
|
434
|
+
crouchLookLeanFactor: 0.0,
|
|
435
|
+
};
|
|
436
|
+
|
|
437
|
+
fov = {
|
|
438
|
+
base: 75, // wider baseline — more energetic
|
|
439
|
+
sprintAdd: 7, // dramatic punch on sprint (Apex/Titanfall feel)
|
|
440
|
+
crouchAdd: -3,
|
|
441
|
+
smoothHalfLife: 0.18, // slightly snappier transitions
|
|
442
|
+
};
|
|
443
|
+
|
|
444
|
+
look = {
|
|
445
|
+
pitchMinDeg: -85,
|
|
446
|
+
pitchMaxDeg: 85,
|
|
447
|
+
invertY: false,
|
|
448
|
+
};
|
|
449
|
+
|
|
450
|
+
/**
|
|
451
|
+
* Postural shifts driven by intent — distinct from look (which is
|
|
452
|
+
* player-driven) and from bob (which is procedural). Models
|
|
453
|
+
* Mirror's Edge-style "commitment to motion": sprinting tilts the head
|
|
454
|
+
* forward as the body leans into the run. Slow spring so it builds up
|
|
455
|
+
* over a second or so rather than snapping — it's a *posture* change,
|
|
456
|
+
* not a flick.
|
|
457
|
+
*/
|
|
458
|
+
posture = {
|
|
459
|
+
/** Forward pitch (deg) applied to the eye at full sprint. Positive = look-down. */
|
|
460
|
+
sprintForwardPitchDeg: 4.0,
|
|
461
|
+
/** Spring half-life for the sprint pitch transition. */
|
|
462
|
+
sprintForwardPitchHalfLife: 0.32,
|
|
463
|
+
/** Small forward eye-position shift (m) at full sprint — head leads the hips. */
|
|
464
|
+
sprintForwardShiftM: 0.04,
|
|
465
|
+
};
|
|
466
|
+
|
|
467
|
+
gravity = {
|
|
468
|
+
/** Default gravity magnitude (m/s^2). Overridden by computeJumpFromApex once
|
|
469
|
+
* the jump tunables are resolved; kept here so non-jumping entities (NPCs,
|
|
470
|
+
* paused players) still have a sensible fall behavior. */
|
|
471
|
+
magnitude: 9.81,
|
|
472
|
+
};
|
|
473
|
+
|
|
474
|
+
toJSON() {
|
|
475
|
+
return {
|
|
476
|
+
body: { ...this.body },
|
|
477
|
+
motion: { ...this.motion },
|
|
478
|
+
jump: { ...this.jump, anticipation: { ...this.jump.anticipation } },
|
|
479
|
+
landing: {
|
|
480
|
+
...this.landing,
|
|
481
|
+
recovery: {
|
|
482
|
+
...this.landing.recovery,
|
|
483
|
+
spring: { ...this.landing.recovery.spring },
|
|
484
|
+
},
|
|
485
|
+
},
|
|
486
|
+
crouch: { ...this.crouch },
|
|
487
|
+
ledgeGrab: { ...this.ledgeGrab },
|
|
488
|
+
mantle: { ...this.mantle },
|
|
489
|
+
wallRun: { ...this.wallRun },
|
|
490
|
+
wallJump: { ...this.wallJump },
|
|
491
|
+
slide: { ...this.slide },
|
|
492
|
+
bob: { ...this.bob },
|
|
493
|
+
breath: { ...this.breath },
|
|
494
|
+
exertion: { ...this.exertion },
|
|
495
|
+
lean: { ...this.lean, spring: { ...this.lean.spring } },
|
|
496
|
+
fov: { ...this.fov },
|
|
497
|
+
look: { ...this.look },
|
|
498
|
+
posture: { ...this.posture },
|
|
499
|
+
gravity: { ...this.gravity },
|
|
500
|
+
};
|
|
501
|
+
}
|
|
502
|
+
|
|
503
|
+
fromJSON(json) {
|
|
504
|
+
if (json.body) Object.assign(this.body, json.body);
|
|
505
|
+
if (json.motion) Object.assign(this.motion, json.motion);
|
|
506
|
+
if (json.jump) {
|
|
507
|
+
const { anticipation, ...rest } = json.jump;
|
|
508
|
+
Object.assign(this.jump, rest);
|
|
509
|
+
if (anticipation) Object.assign(this.jump.anticipation, anticipation);
|
|
510
|
+
}
|
|
511
|
+
if (json.landing) {
|
|
512
|
+
const { recovery, ...rest } = json.landing;
|
|
513
|
+
Object.assign(this.landing, rest);
|
|
514
|
+
if (recovery) {
|
|
515
|
+
const { spring, ...rrest } = recovery;
|
|
516
|
+
Object.assign(this.landing.recovery, rrest);
|
|
517
|
+
if (spring) Object.assign(this.landing.recovery.spring, spring);
|
|
518
|
+
}
|
|
519
|
+
}
|
|
520
|
+
if (json.crouch) Object.assign(this.crouch, json.crouch);
|
|
521
|
+
if (json.ledgeGrab) Object.assign(this.ledgeGrab, json.ledgeGrab);
|
|
522
|
+
if (json.mantle) Object.assign(this.mantle, json.mantle);
|
|
523
|
+
if (json.wallRun) Object.assign(this.wallRun, json.wallRun);
|
|
524
|
+
if (json.wallJump) Object.assign(this.wallJump, json.wallJump);
|
|
525
|
+
if (json.slide) Object.assign(this.slide, json.slide);
|
|
526
|
+
if (json.bob) Object.assign(this.bob, json.bob);
|
|
527
|
+
if (json.breath) Object.assign(this.breath, json.breath);
|
|
528
|
+
if (json.exertion) Object.assign(this.exertion, json.exertion);
|
|
529
|
+
if (json.lean) {
|
|
530
|
+
const { spring, ...rest } = json.lean;
|
|
531
|
+
Object.assign(this.lean, rest);
|
|
532
|
+
if (spring) Object.assign(this.lean.spring, spring);
|
|
533
|
+
}
|
|
534
|
+
if (json.fov) Object.assign(this.fov, json.fov);
|
|
535
|
+
if (json.look) Object.assign(this.look, json.look);
|
|
536
|
+
if (json.posture) Object.assign(this.posture, json.posture);
|
|
537
|
+
if (json.gravity) Object.assign(this.gravity, json.gravity);
|
|
538
|
+
}
|
|
539
|
+
|
|
540
|
+
/**
|
|
541
|
+
* @param {FirstPersonPlayerControllerConfig} other
|
|
542
|
+
*/
|
|
543
|
+
copy(other) {
|
|
544
|
+
this.fromJSON(other.toJSON());
|
|
545
|
+
}
|
|
546
|
+
|
|
547
|
+
clone() {
|
|
548
|
+
const c = new FirstPersonPlayerControllerConfig();
|
|
549
|
+
c.copy(this);
|
|
550
|
+
return c;
|
|
551
|
+
}
|
|
552
|
+
}
|