skillprint-js-sdk 1.1.0-beta.6 โ†’ 1.1.0-beta.8

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 CHANGED
@@ -14,6 +14,7 @@ A professional JavaScript & TypeScript SDK for integrating Skillprint's real-tim
14
14
  - ๐Ÿ“ธ **Automated Screenshot Capture**: Asynchronous, throttled gameplay screenshot captures for Canvas and WebGL contexts without dropping game frame rates.
15
15
  - ๐Ÿ”„ **Real-Time Dynamic Parameters**: Receive, convert, clamp, and apply AI-driven parameter modifications seamlessly during active sessions.
16
16
  - ๐ŸŽฎ **Built-In Engine Adapters**: Ready-to-use adapter wrappers for Phaser.js, Three.js, PixiJS, and Generic Canvas.
17
+ - ๐Ÿ“Š **Discrete Telemetry Events**: `logEvent()` for universal and game-specific gameplay events, sent with the screenshots and timestamped from session start. See [Discrete Telemetry Events](#discrete-telemetry-events).
17
18
  - ๐ŸŒ **WebGL & URL Extraction**: Automatic query parameter extraction for web deployments (`mood`, `playerId`).
18
19
  - ๐Ÿงช **100% Test Coverage Ready**: Fully tested with Vitest and jsdom.
19
20
 
@@ -188,6 +189,71 @@ manager.startGameSessionWithOverrides(
188
189
 
189
190
  ---
190
191
 
192
+ ## Discrete Telemetry Events
193
+
194
+ Screenshots are the SDK's main telemetry and cover every game automatically. `logEvent()` adds discrete events: what the player did and when. Events and screenshots are both stamped with the **milliseconds since the session started**, so Skillprint can line each event up with the frames around it.
195
+
196
+ ```typescript
197
+ import { GameEvent } from 'skillprint-js-sdk';
198
+
199
+ manager.logEvent(GameEvent.LEVEL_START, { level: 3 });
200
+ manager.logEvent('ROTATE_CLOCKWISE');
201
+ manager.logEvent(GameEvent.LEVEL_COMPLETE, { level: 3, score: 1200 });
202
+ ```
203
+
204
+ Events are queued and sent with the next screenshot upload, or on their own when there is no screenshot to send. `stopGameSession()` sends the rest with the closing upload, so they arrive before the session closes. `logEvent` never throws, and with no active session it only logs a warning.
205
+
206
+ ### Universal and game-specific events
207
+
208
+ **Universal events** (`GameEvent`) mean the same thing in every game. Send the ones that apply to yours:
209
+
210
+ | Event | When | Scoring signal |
211
+ |---|---|---|
212
+ | `GAME_START`, `GAME_END` | The game begins or ends | |
213
+ | `GAME_PAUSE`, `GAME_RESUME` | Play is paused or resumed | |
214
+ | `LEVEL_START`, `LEVEL_COMPLETE` | A level begins or is completed | Positive |
215
+ | `LEVEL_QUIT`, `LEVEL_FAILED`, `LEVEL_RESTART` | A level is abandoned, lost or restarted | Negative |
216
+ | `MATCH`, `UNMATCH` | A match is made or undone, in games built on matching | |
217
+ | `HINT` | The player asks for a hint | Negative |
218
+ | `GENERIC_POSITIVE`, `GENERIC_NEGATIVE` | Anything else clearly good or bad for the player | Positive / Negative |
219
+
220
+ **Game-specific events** are any other name, for actions only your game has. For Hextris, for example:
221
+
222
+ ```typescript
223
+ manager.logEvent('ROTATE_CLOCKWISE');
224
+ manager.logEvent('ROTATE_ANTICLOCKWISE');
225
+ ```
226
+
227
+ They don't need registering; send what your game already tracks, in `UPPER_SNAKE_CASE`. `isUniversalEvent(name)` tells the two kinds apart.
228
+
229
+ Each event can carry data (`{ level: 3, score: 1200 }`). `event` and `timestamp` are set by the SDK and can't be overridden.
230
+
231
+ ### What is sent
232
+
233
+ Each screenshot upload can carry, beside the images:
234
+ - `offset_ms<n>`: when the n-th screenshot was captured, in ms from session start
235
+ - `events`: a JSON array of the events since the last upload (at most 1000), e.g. `[{"event": "ROTATE_CLOCKWISE", "timestamp": 1250}]`, where `timestamp` is ms from session start
236
+
237
+ ### Pages that upload their own frames
238
+
239
+ A page that captures its own screenshots and calls `SkillprintAPIClient` directly (see [Host mode](#skillprintapiclient)) keeps a `SessionTimeline` itself:
240
+
241
+ ```typescript
242
+ import { SessionTimeline, SkillprintAPIClient } from 'skillprint-js-sdk';
243
+
244
+ const timeline = new SessionTimeline();
245
+ await client.startSession(sessionId, 'focus', null, 'hextris');
246
+ timeline.start();
247
+
248
+ const offset = timeline.offsetMs(); // when a frame is captured
249
+ timeline.record('ROTATE_CLOCKWISE'); // when the player acts
250
+
251
+ await client.postScreenshots(sessionId, [frame], false, {
252
+ offsetsMs: [offset],
253
+ events: timeline.takeEvents()
254
+ });
255
+ ```
256
+
191
257
  ## API Reference
192
258
 
193
259
  ### Core Classes
@@ -202,7 +268,8 @@ manager.startGameSessionWithOverrides(
202
268
  - `static getInstance(): SkillprintManager | null`
203
269
  - `registerParameterModifier(parameterName: string, updateAction: ParameterModifierAction, expectedType?: string | null): void`
204
270
  - `startGameSession(targetMood: Mood | string, customPlayerId?: string | null): Promise<void>`
205
- - `stopGameSession(): void`
271
+ - `stopGameSession(): Promise<void>` โ€” stops capture, uploads any queued screenshots with the final batch marked `is_last_chunk` so the backend scores it and closes the session; never rejects
272
+ - `logEvent(event: GameEvent | string, data?: Record<string, unknown>): Promise<void>` โ€” see [Discrete Telemetry Events](#discrete-telemetry-events) below
206
273
  - `getCurrentSessionId(): string | null`
207
274
  - `getConfig(): SkillprintConfig`
208
275
 
@@ -211,6 +278,21 @@ manager.startGameSessionWithOverrides(
211
278
  - `isValid(value: unknown): boolean`
212
279
  - `convertValue(rawValue: unknown): number | boolean | string | null`
213
280
 
281
+ #### `SkillprintAPIClient`
282
+ The HTTP client `SkillprintManager` uses. Use it directly when your page already captures its own frames, or when you need session scores.
283
+ - `constructor(baseUrl: string, partnerApiKey?: string, logger?: SDKLogger)`
284
+ - `startSession(sessionId, targetMood, customPlayerId?, gameName?, gameParameters?: ParameterInfo[], options?: { deviceContext? })`
285
+ - `postScreenshots(sessionId, screenshots: Blob[], isLastChunk?, options?: { gameStates?, inputCount?, offsetsMs?, events? })`: `offsetsMs[n]` is when the n-th screenshot was captured and `events` are the events since the last upload, both in ms from session start (see [Discrete Telemetry Events](#discrete-telemetry-events)). `gameStates[n]` (for example `{ score: 1200 }`) is sent with the n-th screenshot, and the latest `score` becomes the session's score. `inputCount` is how many player inputs the batch covers; a batch with 0 is left out of skill scores. `isLastChunk: true` scores the batch and closes the session.
286
+ - `getSession(sessionId): Promise<SessionResult>`: the session's `state` (`OPEN`, then `CLOSED` once scored), `skillScores`, `moodScores`, `score`, `telemetry` (adjustments and logged events) and `parameterUpdates`
287
+ - `pollParameterResults(sessionId)`: only the parameter updates
288
+ - `getUserProfile()`: the player's skill profile; needs a player token
289
+ - `setPlayerToken(token: string | null)`: the token of a player your page has already signed in, used when `startSession` gets no `customPlayerId`
290
+ - `createOrGetUserToken(customPlayerId)`, `createUser(internalId)`, `getUserToken(internalId)`: need a partner key
291
+
292
+ A failed call throws `SkillprintApiError`, which carries the HTTP `status` and response `body`.
293
+
294
+ **Host mode.** A page on an origin the Skillprint API allows, such as the Skillprint portal, can sign its own players in and skip the partner key: pass `''` as `partnerApiKey` and call `setPlayerToken()`. The client then sends no `Api-Key`, and sends the player token on every session call. Sessions started this way belong to the player, not to a partner organization. When the token expires (a `401`), get a new one, call `setPlayerToken()` and retry.
295
+
214
296
  ---
215
297
 
216
298
  ## Development & Testing