@woosh/meep-engine
Version:
Pure JavaScript game engine. Fully featured and production ready.
63 lines (56 loc) • 2.3 kB
JavaScript
import { assert } from "../../../core/assert.js";
import { clamp } from "../../../core/math/clamp.js";
/**
* Server-side computation of "tell client to run a bit faster or slower."
*
* The server maintains a target depth for its per-client input buffer. When the
* buffer is too shallow (client's inputs aren't arriving fast enough), the
* server tells the client to run the next few ticks at a slightly compressed
* tick duration ("speed up"). When the buffer is too deep (client is sending
* inputs faster than the server consumes them), the server asks for stretching.
*
* This is the same primitive used in Overwatch ("time dilation") and Rocket
* League. Adjustment is bounded to a few percent so it's imperceptible.
*
* Output is a multiplier on tick duration:
* factor < 1.0 → run faster (smaller tick interval)
* factor > 1.0 → run slower (larger tick interval)
* factor = 1.0 → run at nominal rate
*
* @author Alex Goldring
* @copyright Company Named Limited (c) 2025
*/
export class TimeDilation {
/**
* @param {{
* target_buffer_depth?: number,
* max_dilation?: number,
* gain?: number,
* }} [options]
*/
constructor({ target_buffer_depth = 2, max_dilation = 0.05, gain = 0.05 } = {}) {
assert.isNumber(target_buffer_depth, 'target_buffer_depth');
assert.isNumber(max_dilation, 'max_dilation');
assert.isNumber(gain, 'gain');
/** @readonly */
this.target_buffer_depth = target_buffer_depth;
/** @readonly */
this.max_dilation = max_dilation;
/** @readonly */
this.gain = gain;
}
/**
* Compute the dilation factor for a client whose current input buffer depth
* is `current_depth` (number of unconsumed input frames).
*
* @param {number} current_depth
* @returns {number} factor in `[1 - max_dilation, 1 + max_dilation]`
*/
compute(current_depth) {
assert.isNumber(current_depth, 'current_depth');
// depth > target ⇒ slow client down (factor > 1). depth < target ⇒ speed up (factor < 1).
const raw = (current_depth - this.target_buffer_depth) * this.gain;
const clamped = clamp(raw, -this.max_dilation, this.max_dilation);
return 1.0 + clamped;
}
}