Initial Commit

This commit is contained in:
2026-05-27 23:47:53 +01:00
parent fbc70f96e6
commit 9b28db724f
18103 changed files with 1600554 additions and 0 deletions

View File

@@ -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.