# 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` — encode `StarPacket[]` with quantised RA, Dec, magnitude, altitude, azimuth, and a UTF-8 spectral class. - `packBodies` / `unpackBodies` — encode `CelestialBodyPacket[]` with full `Float64` precision for altitude, azimuth, angular diameter, and apparent magnitude plus UTF-8 name and type. - Length-prefixed strings (per-element `uint32` length 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 ```ts 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 ```ts 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.