reze-engine 0.32.1 → 0.32.2

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.
@@ -90,6 +90,10 @@ export interface LocomotionOptions {
90
90
  turnTimeScale?: number;
91
91
  /** Playback rate for stop clips (default 1.25) — same reasoning. */
92
92
  stopTimeScale?: number;
93
+ /** Seconds a heading must be held before a release earns an authored stop
94
+ * (default 0.5). Below it, weaving direction changes blend to idle instead
95
+ * of skidding metres along the last-held heading. */
96
+ stopCommitTime?: number;
93
97
  /** Tank-mode steering rate, radians/second (default 2.5 ≈ 143°/s). */
94
98
  steerRate?: number;
95
99
  /** Backpedal speed as a fraction of run speed in tank mode (default 0.5). */
@@ -123,11 +127,15 @@ export declare class LocomotionController {
123
127
  private readonly turnClipMinAngle;
124
128
  private readonly turnTimeScale;
125
129
  private readonly stopTimeScale;
130
+ private readonly stopCommitTime;
126
131
  private turning;
127
132
  private runTurning;
128
133
  /** An interrupted authored clip fading out OVER resumed locomotion, so breaking
129
134
  * out of a stop is instantly responsive without a pose pop. */
130
135
  private exitGhost;
136
+ private headingHold;
137
+ private headingDirX;
138
+ private headingDirY;
131
139
  private stopping;
132
140
  private readonly turnEntries;
133
141
  private readonly yawOffset;
@@ -1 +1 @@
1
- {"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"locomotion.d.ts","sourceRoot":"","sources":["../src/locomotion.ts"],"names":[],"mappings":"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"}
@@ -23,6 +23,9 @@ export class LocomotionController {
23
23
  /** An interrupted authored clip fading out OVER resumed locomotion, so breaking
24
24
  * out of a stop is instantly responsive without a pose pop. */
25
25
  this.exitGhost = null;
26
+ this.headingHold = 0;
27
+ this.headingDirX = 0;
28
+ this.headingDirY = 0;
26
29
  this.stopping = null;
27
30
  this.inputX = 0;
28
31
  this.inputY = 0;
@@ -59,6 +62,7 @@ export class LocomotionController {
59
62
  this.turnClipMinAngle = options?.turnClipMinAngle ?? (100 * Math.PI) / 180;
60
63
  this.turnTimeScale = options?.turnTimeScale ?? 1.4;
61
64
  this.stopTimeScale = options?.stopTimeScale ?? 1.25;
65
+ this.stopCommitTime = options?.stopCommitTime ?? 0.5;
62
66
  this.turnEntries = [
63
67
  { name: clips.idle, time: 0, weight: 0 },
64
68
  { name: clips.idle, time: 0, weight: 1 },
@@ -176,12 +180,30 @@ export class LocomotionController {
176
180
  const m = Math.hypot(this.inputX, this.inputY);
177
181
  moving = m > 0.05;
178
182
  this.recentSpeed = Math.max(this.speedLevel, this.recentSpeed - dt * 1.5);
183
+ // How long the CURRENT heading has been held. An authored skid is the
184
+ // ending of a committed run; weaving through directions (D, then S+D,
185
+ // then S) rebuilds throttle in each new heading but earns no such
186
+ // ending — playing one there slides her metres along whichever way she
187
+ // happened to face last, which reads as drift.
188
+ if (moving) {
189
+ const dx = this.inputX / m;
190
+ const dy = this.inputY / m;
191
+ this.headingHold =
192
+ this.headingDirX * dx + this.headingDirY * dy > 0.94 ? this.headingHold + dt : 0;
193
+ this.headingDirX = dx;
194
+ this.headingDirY = dy;
195
+ }
179
196
  // Release at speed: play the authored stop instead of blending to idle.
180
197
  const stops = this.clips.stop;
181
198
  // Gate on the peak-hold speed: the release decay (throttle/analog) drains the
182
199
  // instantaneous level below any threshold before `moving` goes false. The
183
200
  // small floor on the actual level keeps standstill taps from faking a skid.
184
- if (!moving && stops && stops.length > 0 && this.recentSpeed >= 0.7 && this.speedLevel >= 0.35) {
201
+ if (!moving &&
202
+ stops &&
203
+ stops.length > 0 &&
204
+ this.recentSpeed >= 0.7 &&
205
+ this.speedLevel >= 0.35 &&
206
+ this.headingHold >= this.stopCommitTime) {
185
207
  const gear = this.recentSpeed > 1.4 ? "sprint" : "run";
186
208
  const foot = this.gaitPhase < 0.5 ? "L" : "R";
187
209
  // Gear must match exactly: supplying stops for only one gear means the
@@ -207,6 +229,13 @@ export class LocomotionController {
207
229
  dirX: this.dirX,
208
230
  dirZ: this.dirZ,
209
231
  startLevel: Math.min(this.recentSpeed, 2),
232
+ // How much of the authored skid this stop has earned. Each profile
233
+ // was captured at ITS gear's full speed (run = level 1, sprint =
234
+ // level 2), so compare like with like. Entering slower — or moments
235
+ // after a direction change, when the peak-hold still remembers the
236
+ // PREVIOUS heading's sprint — must not replay the full slide.
237
+ // Floored so a stop always still reads as a stop.
238
+ scale: Math.max(0.3, Math.min(1, this.speedLevel / (best.gear === "sprint" ? 2 : 1))),
210
239
  fromIdleW: Math.max(0, 1 - this.speedLevel),
211
240
  fromClip,
212
241
  fromTime: this.gaitPhase * this.clipDuration(fromClip),
@@ -512,7 +541,7 @@ export class LocomotionController {
512
541
  }
513
542
  t.time += dt * this.stopTimeScale;
514
543
  const clipT = Math.min(t.time, entry.exitTime);
515
- const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime);
544
+ const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime) * t.scale;
516
545
  this.position.x = t.startX + t.dirX * fwd;
517
546
  this.position.z = t.startZ + t.dirZ * fwd;
518
547
  this.speedLevel = t.startLevel * Math.max(0, 1 - t.time / entry.exitTime);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "reze-engine",
3
- "version": "0.32.1",
3
+ "version": "0.32.2",
4
4
  "description": "A lightweight WebGPU engine for real-time 3D MMD/PMX model rendering",
5
5
  "main": "./dist/index.js",
6
6
  "types": "./dist/index.d.ts",
package/src/locomotion.ts CHANGED
@@ -102,6 +102,10 @@ export interface LocomotionOptions {
102
102
  turnTimeScale?: number
103
103
  /** Playback rate for stop clips (default 1.25) — same reasoning. */
104
104
  stopTimeScale?: number
105
+ /** Seconds a heading must be held before a release earns an authored stop
106
+ * (default 0.5). Below it, weaving direction changes blend to idle instead
107
+ * of skidding metres along the last-held heading. */
108
+ stopCommitTime?: number
105
109
  /** Tank-mode steering rate, radians/second (default 2.5 ≈ 143°/s). */
106
110
  steerRate?: number
107
111
  /** Backpedal speed as a fraction of run speed in tank mode (default 0.5). */
@@ -148,6 +152,7 @@ export class LocomotionController {
148
152
  private readonly turnClipMinAngle: number
149
153
  private readonly turnTimeScale: number
150
154
  private readonly stopTimeScale: number
155
+ private readonly stopCommitTime: number
151
156
  private turning: { entry: TurnClipEntry; time: number } | null = null
152
157
  private runTurning: {
153
158
  entry: RunTurnClipEntry
@@ -160,6 +165,9 @@ export class LocomotionController {
160
165
  /** An interrupted authored clip fading out OVER resumed locomotion, so breaking
161
166
  * out of a stop is instantly responsive without a pose pop. */
162
167
  private exitGhost: { clip: string; clipTime: number; elapsed: number } | null = null
168
+ private headingHold = 0
169
+ private headingDirX = 0
170
+ private headingDirY = 0
163
171
  private stopping: {
164
172
  entry: StopClipEntry
165
173
  time: number
@@ -168,6 +176,8 @@ export class LocomotionController {
168
176
  dirX: number
169
177
  dirZ: number
170
178
  startLevel: number
179
+ /** Fraction of the authored deceleration distance this stop travels. */
180
+ scale: number
171
181
  /** Idle's share of the blend at release (level < 1 shows part idle) — the
172
182
  * crossfade source keeps this exact mix or the first stop frame snaps. */
173
183
  fromIdleW: number
@@ -226,6 +236,7 @@ export class LocomotionController {
226
236
  this.turnClipMinAngle = options?.turnClipMinAngle ?? (100 * Math.PI) / 180
227
237
  this.turnTimeScale = options?.turnTimeScale ?? 1.4
228
238
  this.stopTimeScale = options?.stopTimeScale ?? 1.25
239
+ this.stopCommitTime = options?.stopCommitTime ?? 0.5
229
240
  this.turnEntries = [
230
241
  { name: clips.idle, time: 0, weight: 0 },
231
242
  { name: clips.idle, time: 0, weight: 1 },
@@ -353,12 +364,32 @@ export class LocomotionController {
353
364
  const m = Math.hypot(this.inputX, this.inputY)
354
365
  moving = m > 0.05
355
366
  this.recentSpeed = Math.max(this.speedLevel, this.recentSpeed - dt * 1.5)
367
+ // How long the CURRENT heading has been held. An authored skid is the
368
+ // ending of a committed run; weaving through directions (D, then S+D,
369
+ // then S) rebuilds throttle in each new heading but earns no such
370
+ // ending — playing one there slides her metres along whichever way she
371
+ // happened to face last, which reads as drift.
372
+ if (moving) {
373
+ const dx = this.inputX / m
374
+ const dy = this.inputY / m
375
+ this.headingHold =
376
+ this.headingDirX * dx + this.headingDirY * dy > 0.94 ? this.headingHold + dt : 0
377
+ this.headingDirX = dx
378
+ this.headingDirY = dy
379
+ }
356
380
  // Release at speed: play the authored stop instead of blending to idle.
357
381
  const stops = this.clips.stop
358
382
  // Gate on the peak-hold speed: the release decay (throttle/analog) drains the
359
383
  // instantaneous level below any threshold before `moving` goes false. The
360
384
  // small floor on the actual level keeps standstill taps from faking a skid.
361
- if (!moving && stops && stops.length > 0 && this.recentSpeed >= 0.7 && this.speedLevel >= 0.35) {
385
+ if (
386
+ !moving &&
387
+ stops &&
388
+ stops.length > 0 &&
389
+ this.recentSpeed >= 0.7 &&
390
+ this.speedLevel >= 0.35 &&
391
+ this.headingHold >= this.stopCommitTime
392
+ ) {
362
393
  const gear = this.recentSpeed > 1.4 ? "sprint" : "run"
363
394
  const foot = this.gaitPhase < 0.5 ? "L" : "R"
364
395
  // Gear must match exactly: supplying stops for only one gear means the
@@ -383,6 +414,13 @@ export class LocomotionController {
383
414
  dirX: this.dirX,
384
415
  dirZ: this.dirZ,
385
416
  startLevel: Math.min(this.recentSpeed, 2),
417
+ // How much of the authored skid this stop has earned. Each profile
418
+ // was captured at ITS gear's full speed (run = level 1, sprint =
419
+ // level 2), so compare like with like. Entering slower — or moments
420
+ // after a direction change, when the peak-hold still remembers the
421
+ // PREVIOUS heading's sprint — must not replay the full slide.
422
+ // Floored so a stop always still reads as a stop.
423
+ scale: Math.max(0.3, Math.min(1, this.speedLevel / (best.gear === "sprint" ? 2 : 1))),
386
424
  fromIdleW: Math.max(0, 1 - this.speedLevel),
387
425
  fromClip,
388
426
  fromTime: this.gaitPhase * this.clipDuration(fromClip),
@@ -707,7 +745,7 @@ export class LocomotionController {
707
745
 
708
746
  t.time += dt * this.stopTimeScale
709
747
  const clipT = Math.min(t.time, entry.exitTime)
710
- const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime)
748
+ const fwd = LocomotionController.profileAt(entry.forward, clipT / entry.exitTime) * t.scale
711
749
  this.position.x = t.startX + t.dirX * fwd
712
750
  this.position.z = t.startZ + t.dirZ * fwd
713
751