@realnation/builder-shared-sdk 1.0.5 → 1.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,106 @@
1
+ /**
2
+ * Pure decoding helpers. No state, no I/O — all directly unit-testable.
3
+ */
4
+ import { GRID_H, GRID_W } from './protocol.js';
5
+ function base64ToBytes(b64) {
6
+ // atob in browsers, Buffer under node/vitest.
7
+ const g = globalThis;
8
+ if (typeof g.atob === 'function') {
9
+ const bin = g.atob(b64);
10
+ const out = new Uint8Array(bin.length);
11
+ for (let i = 0; i < bin.length; i++)
12
+ out[i] = bin.charCodeAt(i);
13
+ return out;
14
+ }
15
+ if (g.Buffer)
16
+ return new Uint8Array(g.Buffer.from(b64, 'base64'));
17
+ throw new Error('no base64 decoder available');
18
+ }
19
+ /**
20
+ * Unpacks the 1-bit mask.
21
+ *
22
+ * MSB is the leftmost pixel of each byte — the same order the runtime packs
23
+ * (qs_silhouette_v1). Getting this backwards mirrors every row, which looks
24
+ * plausible on a symmetric scene and is why the bit order is asserted in tests.
25
+ */
26
+ export function decodeSilhouette(msg) {
27
+ const w = msg.w || GRID_W;
28
+ const h = msg.h || GRID_H;
29
+ const bytes = base64ToBytes(msg.mask);
30
+ const cells = new Uint8Array(w * h);
31
+ const expected = Math.ceil((w * h) / 8);
32
+ if (bytes.length < expected) {
33
+ throw new Error(`silhouette mask truncated: got ${bytes.length} bytes, need ${expected}`);
34
+ }
35
+ for (let i = 0; i < w * h; i++) {
36
+ const byte = bytes[i >> 3];
37
+ const bit = 7 - (i & 7);
38
+ cells[i] = (byte >> bit) & 1;
39
+ }
40
+ const frame = {
41
+ w,
42
+ h,
43
+ cells,
44
+ coverage: msg.coverage ?? countSet(cells) / (w * h),
45
+ nearest: msg.nearest,
46
+ farthest: msg.farthest,
47
+ ts: msg.ts,
48
+ at(cx, cy) {
49
+ if (cx < 0 || cy < 0 || cx >= w || cy >= h)
50
+ return false;
51
+ return cells[cy * w + cx] === 1;
52
+ },
53
+ hit(x, y) {
54
+ return frame.at(Math.floor(x * w), Math.floor(y * h));
55
+ },
56
+ };
57
+ return frame;
58
+ }
59
+ function countSet(cells) {
60
+ let n = 0;
61
+ for (let i = 0; i < cells.length; i++)
62
+ n += cells[i];
63
+ return n;
64
+ }
65
+ /* --- QSBN binary frame header (design/02 §9) ------------------------------ */
66
+ export const QSBN_HEADER_BYTES = 16;
67
+ const QSBN_MAGIC = 0x5153424e; // "QSBN"
68
+ /**
69
+ * FNV-1a over the id string. Ids longer than 8 bytes are hashed by both sides
70
+ * with this same function, so the header stays fixed-width without giving up
71
+ * the ability to detect a mispaired frame.
72
+ */
73
+ export function fnv1a64(s) {
74
+ let hash = 0xcbf29ce484222325n;
75
+ const prime = 0x100000001b3n;
76
+ const mask = 0xffffffffffffffffn;
77
+ for (let i = 0; i < s.length; i++) {
78
+ hash = (hash ^ BigInt(s.charCodeAt(i) & 0xff)) & mask;
79
+ hash = (hash * prime) & mask;
80
+ }
81
+ return hash;
82
+ }
83
+ /** The id as it appears in a QSBN header: raw big-endian bytes, or the hash. */
84
+ export function binaryCorrelationId(id) {
85
+ if (id.length > 8)
86
+ return fnv1a64(id);
87
+ let v = 0n;
88
+ for (let i = 0; i < id.length; i++)
89
+ v = (v << 8n) | BigInt(id.charCodeAt(i) & 0xff);
90
+ return v;
91
+ }
92
+ export function parseBinaryFrame(buf) {
93
+ if (buf.byteLength < QSBN_HEADER_BYTES) {
94
+ throw new Error(`binary frame shorter than header: ${buf.byteLength} bytes`);
95
+ }
96
+ const view = new DataView(buf);
97
+ const magic = view.getUint32(0, false);
98
+ if (magic !== QSBN_MAGIC) {
99
+ throw new Error('binary frame missing QSBN magic');
100
+ }
101
+ return {
102
+ version: view.getUint16(4, false),
103
+ id: view.getBigUint64(8, false),
104
+ payload: buf.slice(QSBN_HEADER_BYTES),
105
+ };
106
+ }
@@ -0,0 +1,192 @@
1
+ /**
2
+ * The public surface: subscriptions, semantic commands, video.
3
+ *
4
+ * Two things are deliberately absent and must stay absent:
5
+ * - `publish` — a client publishing would break the runtime's control boundary.
6
+ * - a raw `request(name)` — that leaks an internal concept back out and
7
+ * re-enables the polling antipattern the command table exists to prevent.
8
+ * Adding a command means adding a named method here, not opening an escape hatch.
9
+ */
10
+ import type { Transport, ConnectionState } from './connection.js';
11
+ import type { SilhouetteFrame } from './decode.js';
12
+ import type { BodyUpdate, DeviceConnected, DeviceError, GestureUpdate, HandGestureUpdate, HandGestureDiagnostic, PointerUpdate, PlayerSilhouetteLost as PlayerSilhouetteLostEvent, RequestError, RuntimeConfig } from './protocol.js';
13
+ export type Unsubscribe = () => void;
14
+ export type GestureName = GestureUpdate['gesture'];
15
+ /** Motion (IMU). Carried as a generic event until the schema grows a definition. */
16
+ export interface MotionUpdate {
17
+ type: 'motion:update';
18
+ euler?: {
19
+ yaw: number;
20
+ pitch: number;
21
+ roll: number;
22
+ };
23
+ quat?: {
24
+ w: number;
25
+ x: number;
26
+ y: number;
27
+ z: number;
28
+ };
29
+ accel?: {
30
+ x: number;
31
+ y: number;
32
+ z: number;
33
+ };
34
+ battery?: {
35
+ voltage?: number;
36
+ percent?: number;
37
+ };
38
+ ts: number;
39
+ }
40
+ export declare class RuntimeCommandError extends Error {
41
+ readonly code: RequestError['code'];
42
+ constructor(err: RequestError);
43
+ }
44
+ export interface SnapshotResult {
45
+ mime: string;
46
+ data: string;
47
+ }
48
+ export interface DeviceInfo {
49
+ id: string;
50
+ plugin: string;
51
+ status: string;
52
+ }
53
+ export interface RuntimeStatus {
54
+ version: string;
55
+ uptimeMs: number;
56
+ clients: number;
57
+ }
58
+ export interface CameraConfig {
59
+ width: number;
60
+ height: number;
61
+ fps: number;
62
+ }
63
+ export interface CapabilityChange {
64
+ capabilities: unknown[];
65
+ lost: unknown[];
66
+ }
67
+ /** A selected foreground player, not generic depth occupancy. */
68
+ export interface PlayerSilhouetteFrame extends SilhouetteFrame {
69
+ player: {
70
+ id: number;
71
+ distance: number;
72
+ confidence: number;
73
+ };
74
+ }
75
+ export interface PlayerSilhouetteLost {
76
+ reason: PlayerSilhouetteLostEvent['reason'];
77
+ ts: number;
78
+ }
79
+ /** Aligns skeleton data to the video's playback clock (design/03 §4.5). */
80
+ export interface VideoAligner {
81
+ current(): BodyUpdate | null;
82
+ setOffset(ms: number): void;
83
+ readonly offsetMs: number;
84
+ dispose(): void;
85
+ }
86
+ interface VideoElementLike {
87
+ currentTime: number;
88
+ }
89
+ export declare class RuntimeFacade {
90
+ #private;
91
+ protected readonly transport: Transport;
92
+ private pending;
93
+ /** Ids whose JSON response said binary:true, in arrival order. */
94
+ private binaryQueue;
95
+ private requestSeq;
96
+ private disposers;
97
+ private readonly bodyCh;
98
+ private readonly gestureCh;
99
+ private readonly handGestureCh;
100
+ private readonly handGestureDiagnosticCh;
101
+ private readonly gestureByName;
102
+ private readonly pointerCh;
103
+ private readonly silhouetteCh;
104
+ private readonly playerSilhouetteCh;
105
+ private readonly playerSilhouetteLostCh;
106
+ private readonly motionCh;
107
+ private readonly deviceErrCh;
108
+ private readonly deviceConnCh;
109
+ private readonly playerLostCh;
110
+ private readonly capChangeCh;
111
+ private readonly stateCh;
112
+ /** Last `device:connected.config` seen. Read-through only — see effectiveConfig. */
113
+ private lastConfig;
114
+ private lastPlayerSilhouette;
115
+ private trackedPlayers;
116
+ private readonly setTimer;
117
+ private readonly clearTimer;
118
+ constructor(transport: Transport, timers?: {
119
+ setTimer?: (fn: () => void, ms: number) => unknown;
120
+ clearTimer?: (h: unknown) => void;
121
+ });
122
+ private dispatch;
123
+ private settle;
124
+ private dispatchBinary;
125
+ readonly camera: {
126
+ snapshot: () => Promise<SnapshotResult>;
127
+ getConfig: () => Promise<CameraConfig>;
128
+ start: () => Promise<void>;
129
+ stop: () => Promise<void>;
130
+ stream: () => Promise<MediaStream>;
131
+ stopStream: () => void;
132
+ };
133
+ readonly device: {
134
+ list: () => Promise<DeviceInfo[]>;
135
+ onError: (fn: (e: DeviceError) => void) => Unsubscribe;
136
+ onConnected: (fn: (e: DeviceConnected) => void) => Unsubscribe;
137
+ onPlayerLost: (fn: () => void) => Unsubscribe;
138
+ };
139
+ status(): Promise<RuntimeStatus>;
140
+ /**
141
+ * Live retuning. Applied by the runtime — the SDK never recomputes Pointer
142
+ * locally, or the browser and runtime would disagree about where the hand is.
143
+ */
144
+ reconfigure(config: RuntimeConfig): void;
145
+ /** Values the runtime reported as actually in effect, for HUDs. Read-only. */
146
+ get effectiveConfig(): Record<string, unknown>;
147
+ readonly body: {
148
+ onUpdate: (fn: (b: BodyUpdate) => void) => Unsubscribe;
149
+ };
150
+ readonly gesture: {
151
+ onUpdate: (fn: (g: GestureUpdate) => void) => Unsubscribe;
152
+ on: (name: GestureName, fn: (g: GestureUpdate) => void) => Unsubscribe;
153
+ };
154
+ /** Fine-grained fingers/hand-shape events from the optional Hand5 pipeline. */
155
+ readonly handGesture: {
156
+ onUpdate: (fn: (g: HandGestureUpdate) => void) => Unsubscribe;
157
+ onDiagnostic: (fn: (d: HandGestureDiagnostic) => void) => Unsubscribe;
158
+ };
159
+ readonly pointer: {
160
+ onUpdate: (fn: (p: PointerUpdate) => void) => Unsubscribe;
161
+ };
162
+ readonly silhouette: {
163
+ onUpdate: (fn: (f: SilhouetteFrame) => void) => Unsubscribe;
164
+ };
165
+ readonly playerSilhouette: {
166
+ onUpdate: (fn: (f: PlayerSilhouetteFrame) => void) => Unsubscribe;
167
+ onLost: (fn: (lost: PlayerSilhouetteLost) => void) => Unsubscribe;
168
+ current: () => PlayerSilhouetteFrame | null;
169
+ };
170
+ readonly motion: {
171
+ onUpdate: (fn: (m: MotionUpdate) => void) => Unsubscribe;
172
+ };
173
+ onCapabilityChanged(fn: (c: CapabilityChange) => void): Unsubscribe;
174
+ onStateChange(fn: (s: ConnectionState) => void): Unsubscribe;
175
+ private pc;
176
+ /**
177
+ * Establishes the WebRTC stream. The whole signalling exchange is hidden;
178
+ * callers get a MediaStream.
179
+ */
180
+ private openStream;
181
+ private closeStream;
182
+ /**
183
+ * Delays skeleton delivery to match the video's playback clock.
184
+ *
185
+ * Skeletons arrive one to two frames ahead of the picture, so drawing an
186
+ * overlay straight from body.onUpdate puts the lines in front of the player.
187
+ */
188
+ alignToVideo(video: VideoElementLike, offsetMs?: number): VideoAligner;
189
+ dispose(): void;
190
+ }
191
+ export {};
192
+ //# sourceMappingURL=facade.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"facade.d.ts","sourceRoot":"","sources":["../../src/runtime/facade.ts"],"names":[],"mappings":"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"}