5.0 KiB
Packets
(no submodules)
Purpose
Compact little-endian binary codecs for the two highest-volume payloads in a universe scene: catalogues of observed stars and lists of observed celestial bodies. The codecs trade negligible accuracy for an order-of-magnitude reduction in bytes-on-the-wire compared to a JSON encoding, which matters when streaming thousands of stars to a renderer or syncing scene snapshots over a constrained transport.
Each codec is a pure pair of pack(...) / unpack(...) functions. They are deterministic, allocation-free aside from the one output Uint8Array, and produce identical bytes for identical input regardless of platform endianness because every DataView write is explicit littleEndian = true.
Features
packStars/unpackStars— encodeStarPacket[]with quantised RA, Dec, magnitude, altitude, azimuth, and a UTF-8 spectral class.packBodies/unpackBodies— encodeCelestialBodyPacket[]with fullFloat64precision for altitude, azimuth, angular diameter, and apparent magnitude plus UTF-8 name and type.- Length-prefixed strings (per-element
uint32length header) so variable-width identifiers are unambiguous. - Quantised star fields: RA in 1/10⁷° (uint32), Dec in 1/10⁷° (int32), magnitude × 100 with a +5 offset (uint16), altitude as int8 degrees, azimuth as uint8 (≈1.41° resolution).
- Empty-array handling: 4-byte count-only buffer.
- Self-describing count header so a receiver can preallocate.
Usage: Basic Example
import { packStars, unpackStars, type StarPacket } from "@suaveplan/universe";
const stars: StarPacket[] = [
{
ra: 10,
dec: 20,
magnitude: 3.5,
altitude: 45,
azimuth: 180,
spectralClass: "G2V",
},
];
const bytes = packStars(stars);
const round = unpackStars(bytes);
// round[0].spectralClass === "G2V"
Usage: Advanced Example
import {
packBodies,
packStars,
unpackBodies,
unpackStars,
type CelestialBodyPacket,
type StarPacket,
} from "@suaveplan/universe";
function snapshot(
stars: StarPacket[],
bodies: CelestialBodyPacket[]
): Uint8Array {
const s = packStars(stars);
const b = packBodies(bodies);
const out = new Uint8Array(4 + s.length + 4 + b.length);
new DataView(out.buffer).setUint32(0, s.length, true);
out.set(s, 4);
new DataView(out.buffer).setUint32(4 + s.length, b.length, true);
out.set(b, 4 + s.length + 4);
return out;
}
function restore(buf: Uint8Array): {
stars: StarPacket[];
bodies: CelestialBodyPacket[];
} {
const view = new DataView(buf.buffer, buf.byteOffset, buf.byteLength);
const sLen = view.getUint32(0, true);
const stars = unpackStars(buf.subarray(4, 4 + sLen));
const bodies = unpackBodies(buf.subarray(4 + sLen + 4));
return { stars, bodies };
}
API Reference
interface StarPacket
| Field | Type | Quantisation |
|---|---|---|
ra |
number |
uint32, (ra + 180) × 10⁷ |
dec |
number |
int32, dec × 10⁷ |
magnitude |
number |
uint16, (mag + 5) × 100 |
altitude |
number |
int8 degrees |
azimuth |
number |
uint8, scaled by 255/360 |
spectralClass |
string |
UTF-8, length-prefixed |
packStars(stars: StarPacket[]): Uint8Array
Returns the packed buffer. Layout: uint32 count then per-element uint32 specLen headers, then the payload region containing each spectral-class string followed by the 12-byte fixed-width quantised fields.
unpackStars(bytes: Uint8Array): StarPacket[]
Inverse of packStars. Reverses every quantisation; small accuracy losses are inherent (azimuth ≈ ±0.7°, magnitude ≈ ±0.005, RA/Dec ≈ ±5×10⁻⁸°).
interface CelestialBodyPacket
| Field | Type | Encoding |
|---|---|---|
name |
string |
UTF-8, length-prefixed |
type |
string |
UTF-8, length-prefixed |
altitude |
number |
float64 |
azimuth |
number |
float64 |
angularDiameterArcmin |
number |
float64 |
apparentMagnitude |
number |
float64 |
packBodies(bodies: CelestialBodyPacket[]): Uint8Array
Layout: uint32 count, per-element uint32 nameLen + uint32 typeLen headers, then per-element name + type bytes followed by 32 bytes of float64 fields.
unpackBodies(bytes: Uint8Array): CelestialBodyPacket[]
Inverse of packBodies — fully lossless for the float64 fields.
Implementation Notes
Star encoding is quantised because telescope-grade precision is not the use case; renderers, mini-maps, and HUDs are. The + 180 offset on RA and + 5 offset on magnitude let the smallest unsigned integer types cover the full physical range without negative-number gymnastics. Body encoding stays at float64 because the population is small (handfuls of objects per scene) and keeping ephemeris precision avoids visible jitter when the same body is repeatedly encoded and decoded over a long-running session. Both codecs use little-endian writes unconditionally so the wire format is platform-independent.