@loova/game-sdk 0.1.0-alpha.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.
package/README.md ADDED
@@ -0,0 +1,104 @@
1
+ # @loova/game-sdk
2
+
3
+ TypeScript ESM SDK for browser games generated by Loova AI Agent. It speaks the
4
+ stable Loova realtime Protocol v1 and treats all server state as authoritative.
5
+
6
+ ## Install and build
7
+
8
+ ```bash
9
+ npm install
10
+ npm run proto
11
+ npm test
12
+ npm run build
13
+ ```
14
+
15
+ The protocol source of truth is
16
+ `../loova-game-realtime/proto/realtime/v1/realtime.proto`. Generated
17
+ Protobuf-ES bindings are committed under `src/gen`; published packages do not
18
+ need `buf` or `protoc` at runtime.
19
+
20
+ ## Runtime client
21
+
22
+ ```ts
23
+ import { InputAction, createLoovaGameSDK } from "@loova/game-sdk";
24
+
25
+ const sdk = createLoovaGameSDK({
26
+ apiUrl: "https://api.loova.example",
27
+ });
28
+
29
+ const room = await sdk.createRoom({ gameId: "my-game" });
30
+ const session = await sdk.joinRoom(room.roomId, { playerId: "player-1" });
31
+
32
+ await sdk.connect({
33
+ websocketUrl: session.websocketUrl,
34
+ ticket: session.ticket,
35
+ reconnectToken: session.reconnectToken,
36
+ playerId: session.playerId,
37
+ roomId: session.roomId,
38
+ });
39
+
40
+ sdk.sendInput({
41
+ moveX: 1,
42
+ moveY: 0,
43
+ aimX: 1,
44
+ aimY: 0,
45
+ actions: InputAction.Fire,
46
+ });
47
+ ```
48
+
49
+ `createRoom()` and `joinRoom()` use the trusted platform HTTP API. Tickets must
50
+ never be created inside an untrusted browser game.
51
+
52
+ ## Events
53
+
54
+ ```ts
55
+ sdk.onPlayerJoin(({ player }) => console.log("join", player.playerId));
56
+ sdk.onSnapshot(({ serverTick, players }) => render(players, serverTick));
57
+ sdk.onPlayerLeave(({ playerId }) => console.log("left", playerId));
58
+ sdk.onDamage(({ playerId, damage }) => showDamage(playerId, damage));
59
+ sdk.onDeath(({ playerId, killerPlayerId }) => showDeath(playerId, killerPlayerId));
60
+ sdk.onMatchEnd(({ winnerPlayerId }) => showWinner(winnerPlayerId));
61
+ ```
62
+
63
+ Every subscription returns an unsubscribe function.
64
+
65
+ ## Prediction and interpolation
66
+
67
+ - Sent inputs are retained in a bounded input buffer.
68
+ - `FullSnapshot` acknowledges processed input sequences.
69
+ - `ServerCorrection` resets the local player to authoritative state and replays
70
+ unacknowledged inputs.
71
+ - `getPredictedLocalPlayer()` returns the predicted local state.
72
+ - `getInterpolatedPlayers()` returns remote state sampled behind realtime by the
73
+ configured interpolation delay.
74
+
75
+ Prediction is visual only. The SDK never sends predicted positions, health,
76
+ hits, deaths, or match outcomes back to the server.
77
+
78
+ ## Heartbeat and reconnect
79
+
80
+ The SDK sends protobuf `Ping` frames on a configurable interval. Unexpected
81
+ disconnects use bounded exponential backoff when `reconnectToken` is present.
82
+ The first frame on the replacement socket is `Reconnect` with the last received
83
+ server sequence. Without a reconnect token, the SDK closes instead of replaying
84
+ the one-time Auth ticket.
85
+
86
+ ## Two-player demo
87
+
88
+ ```bash
89
+ npm run build
90
+ npm run demo:build
91
+ python3 -m http.server 4173
92
+ ```
93
+
94
+ Open `http://localhost:4173/demo/`, enter two one-time tickets for the same
95
+ room, and connect. Space fires for player A; Enter fires for player B.
96
+
97
+ ## Protocol stability
98
+
99
+ - `protocolVersion` is currently `1`.
100
+ - The SDK validates server protocol version and monotonic server sequence.
101
+ - Existing protobuf field numbers and enum values must never be changed or
102
+ reused.
103
+ - Breaking protocol changes require a new protobuf package and protocol major
104
+ version.
@@ -0,0 +1,8 @@
1
+ export type Unsubscribe = () => void;
2
+ export declare class EventChannel<T> {
3
+ #private;
4
+ subscribe(listener: (event: T) => void): Unsubscribe;
5
+ emit(event: T): void;
6
+ clear(): void;
7
+ }
8
+ //# sourceMappingURL=events.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.d.ts","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAAA,MAAM,MAAM,WAAW,GAAG,MAAM,IAAI,CAAC;AAErC,qBAAa,YAAY,CAAC,CAAC;;IAGzB,SAAS,CAAC,QAAQ,EAAE,CAAC,KAAK,EAAE,CAAC,KAAK,IAAI,GAAG,WAAW;IAOpD,IAAI,CAAC,KAAK,EAAE,CAAC,GAAG,IAAI;IAMpB,KAAK,IAAI,IAAI;CAGd"}
package/dist/events.js ADDED
@@ -0,0 +1,18 @@
1
+ export class EventChannel {
2
+ #listeners = new Set();
3
+ subscribe(listener) {
4
+ this.#listeners.add(listener);
5
+ return () => {
6
+ this.#listeners.delete(listener);
7
+ };
8
+ }
9
+ emit(event) {
10
+ for (const listener of this.#listeners) {
11
+ listener(event);
12
+ }
13
+ }
14
+ clear() {
15
+ this.#listeners.clear();
16
+ }
17
+ }
18
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../src/events.ts"],"names":[],"mappings":"AAEA,MAAM,OAAO,YAAY;IACd,UAAU,GAAG,IAAI,GAAG,EAAsB,CAAC;IAEpD,SAAS,CAAC,QAA4B;QACpC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,QAAQ,CAAC,CAAC;QAC9B,OAAO,GAAG,EAAE;YACV,IAAI,CAAC,UAAU,CAAC,MAAM,CAAC,QAAQ,CAAC,CAAC;QACnC,CAAC,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,KAAQ;QACX,KAAK,MAAM,QAAQ,IAAI,IAAI,CAAC,UAAU,EAAE,CAAC;YACvC,QAAQ,CAAC,KAAK,CAAC,CAAC;QAClB,CAAC;IACH,CAAC;IAED,KAAK;QACH,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,CAAC;IAC1B,CAAC;CACF"}