Initial Commit
This commit is contained in:
@@ -0,0 +1,84 @@
|
||||
# Game Quantization Helpers
|
||||
|
||||
<!-- begin:auto-docs -->
|
||||
_(no submodules)_
|
||||
<!-- end:auto-docs -->
|
||||
## Purpose
|
||||
|
||||
Coordinate-rounding helpers for game-state vectors and rotations. The functions in this module trade absolute precision for compact wire representations: `quantizePosition({ x: 1.234, y: 5.678, z: 9.012 }, 0.01)` rounds each component to two decimal places before packing, halving the bit-width versus a raw float. This is the standard tactic for bandwidth-constrained multiplayer games — a typical ±1024 m world quantized to 0.01 m precision packs into ~17 bits per axis instead of 32 bits.
|
||||
|
||||
The 2-D and 3-D variants exist because some games (top-down, side-scrolling) work in `Vector2` and would otherwise pay the cost of a wasted z component. `quantizeRotation` separately handles yaw and pitch with their own precisions; `interpolateAngle` handles the 360°/0° wrap so a transition from 350° to 10° interpolates through the short arc, not the long one. `validateRange` is a defensive check used at API boundaries.
|
||||
|
||||
## Features
|
||||
|
||||
- `quantizePosition(position, precision = 0.01)` — round a `Vector3` to a precision divisor
|
||||
- `quantizePosition2D(position, precision = 0.01)` — round a `Vector2`
|
||||
- `quantizeRotation(rotation, yawPrecision = 1, pitchPrecision = 1)` — round yaw + pitch
|
||||
- `interpolateAngle(a, b, t)` — interpolate angles in degrees, taking the short arc through the wrap
|
||||
- `validateRange(value, min, max, name?)` — throw if out of range, with an optional field name for the error
|
||||
|
||||
## Usage: Basic Example
|
||||
|
||||
```ts
|
||||
import { quantizePosition, quantizeRotation } from "@suaveplan/bitpacker-game";
|
||||
|
||||
const pos = quantizePosition({ x: 1.234, y: 5.678, z: 9.012 }, 0.01);
|
||||
// { x: 1.23, y: 5.68, z: 9.01 }
|
||||
|
||||
const rot = quantizeRotation({ yaw: 90.55, pitch: -45.4 }, 1, 1);
|
||||
// { yaw: 91, pitch: -45 }
|
||||
```
|
||||
|
||||
## Usage: Advanced Example
|
||||
|
||||
```ts
|
||||
import {
|
||||
quantizePosition,
|
||||
quantizeRotation,
|
||||
interpolateAngle,
|
||||
validateRange,
|
||||
} from "@suaveplan/bitpacker-game";
|
||||
|
||||
function tick(prev: { yaw: number }, target: { yaw: number }, t: number) {
|
||||
validateRange(t, 0, 1, "interpolation t");
|
||||
return { yaw: interpolateAngle(prev.yaw, target.yaw, t) };
|
||||
}
|
||||
|
||||
// Quantize before pack to match the schema's fixed-point precision exactly:
|
||||
function preparePacket(position: { x: number; y: number; z: number }) {
|
||||
const q = quantizePosition(position, 0.01);
|
||||
validateRange(q.x, -1024, 1024, "x");
|
||||
validateRange(q.y, -1024, 1024, "y");
|
||||
validateRange(q.z, -128, 128, "z");
|
||||
return q;
|
||||
}
|
||||
```
|
||||
|
||||
## API Reference
|
||||
|
||||
### `quantizePosition(position: Vector3, precision = 0.01): Vector3`
|
||||
|
||||
Returns a new `Vector3` with each component rounded to `precision` (e.g. `0.01` for two decimal places, `1` for whole units). The result is the nearest grid point on a precision-divisor lattice.
|
||||
|
||||
### `quantizePosition2D(position: Vector2, precision = 0.01): Vector2`
|
||||
|
||||
Same as `quantizePosition` for a 2-D vector. No z component.
|
||||
|
||||
### `quantizeRotation(rotation: Rotation, yawPrecision = 1, pitchPrecision = 1): Rotation`
|
||||
|
||||
Returns a new `Rotation` with `yaw` and `pitch` rounded to their respective precisions. The two axes have separate precisions because yaw typically needs less granularity than pitch (or vice versa).
|
||||
|
||||
### `interpolateAngle(a: number, b: number, t: number): number`
|
||||
|
||||
Returns the interpolation between `a` and `b` taking the short arc through the 360°/0° wrap. `t ∈ [0, 1]`. Equivalent to slerp on the unit circle for two-axis cases.
|
||||
|
||||
### `validateRange(value: number, min: number, max: number, name?: string): void`
|
||||
|
||||
Throws if `value` is outside `[min, max]`. The optional `name` is included in the error message to identify the offending field.
|
||||
|
||||
## Implementation Notes
|
||||
|
||||
- **Quantization is round-to-nearest-grid.** `Math.round(value / precision) * precision` produces banker's rounding behaviour at the exact half. Consumers that need round-half-up should pre-add a half-precision offset before quantizing.
|
||||
- **No clamp.** These helpers do not clamp out-of-range values; pair them with `validateRange` if your wire format has a hard cap. Quantizing without clamping can produce values outside the schema's bit-width and cause pack failures.
|
||||
- **`interpolateAngle` is not unwrap-aware.** It takes the short arc per pair; chains of three or more interpolated angles can drift unless the consumer normalizes between calls.
|
||||
- **Defensive-but-cheap.** `validateRange` is `O(1)` and emits a clear error message including the field name; it's appropriate at every API boundary, not just hot-path entries.
|
||||
Reference in New Issue
Block a user