SDK
This SDK provides secure bidirectional communication between the game and the native clients (iOS and Android), enabling third‑party developers to invoke native capabilities.
Exposed global API name: window.GameTokSDK
Capabilities:
| Action | Description |
|---|---|
| GET_PROFILE | Get the current user profile |
| GET_PROFILES | Batch‑get user profiles (by uid array) |
| STORAGE_SET | Write to local key‑value storage (settings, progress, etc.) |
| STORAGE_GET | Read from local key‑value storage (retrieve previously saved data) |
| ADD_SCORE | Report a score (leaderboards, sharing, etc.) |
| GET_PERMISSION_MIC | Request microphone permission (Android) |
| TOPUP | Top up when in‑game currency is low; opens the client top‑up flow |
| ROUND_START | Round start notification (call once per round start; tied to platform round/energy policies) |
| ROUND_END | Round end notification (call when a round ends; round_id must match this round's ROUND_START) |
| SHOW_ENERGY_INSUFFICIENT_DIALOG | Energy is insufficient; notify the platform to show the energy-insufficient dialog (fire-and-forget) |
| onAudioSuspend | Listen for audio suspend event (triggered when the platform requires the game to mute) |
| offAudioSuspend | Remove audio suspend event listener |
| onAudioResume | Listen for audio resume event (triggered when the platform allows the game to restore sound) |
| offAudioResume | Remove audio resume event listener |
| onAutoStartGame | Listen for client "start game" command (same effect as clicking the in-game start button) |
| offAutoStartGame | Remove "start game" command listener |
| onExitAndResetGame | Listen for client "exit and reset game" command (end the game and return to initial state) |
| offExitAndResetGame | Remove "exit and reset game" command listener |
| REQUEST_EXIT | The game's own "back to home" button was tapped; notify the platform to return home (fire-and-forget; the platform then sends EXIT_AND_RESET_GAME) |
Integration examples
The following shows how to include the SDK on a page and call each capability.
1.1 Including the script
Option 1: Load the bundle via <script>
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
console.log('GameTokSDK version loaded:', !!window.GameTokSDK);
</script>Local development (not embedded in the app, debugging in a browser): After loading the SDK, call GameTokSDK.enableMock(). Promise‑based APIs will use built‑in mock data; no native client is required. Do not call this in production or store submission builds, or users will see fake data.
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
GameTokSDK.enableMock();
</script>Device testing (WebView inside the app): When you need logs, after loading the SDK call GameTokSDK.enableDebug() (returns a Promise; loads vConsole on the page and logs native bridge traffic). Do not use together with enableMock.
<script src="https://play.letskix.com/res/game/sdk-js/GameTokSDK.js"></script>
<script>
GameTokSDK.enableDebug();
</script>1.2 Usage examples
All examples use then / catch with Promises. On success, a unified response object is returned; on failure, the Promise rejects.
1.2.1 Get profile
// Example call
GameTokSDK.getProfile()
.then(({ action, error, data }) => {
console.log('Profile loaded:', data);
})
.catch((e) => {
console.error('Failed to get profile:', e);
});Sample response (JSON):
{
"action": "GET_PROFILE",
"error": false,
"data": {
"uid": 1024780,
"avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
"userName": "Guest",
"userCoins": 0,
"level": 0,
"gameLevel": 0,
"rankImg": "",
"gender": 0,
"testAccount": false,
"guest": true
}
}Key fields:
| Field | Type | Description |
|---|---|---|
| uid | number | User unique ID |
| avatar | string | Avatar URL |
| userName | string | Username / display name |
| userCoins | number | User's current coin balance |
| level | number | User growth level (account system) |
| gameLevel | number | Rank (based on game matches / ranked play); 0 means no rank (e.g., new player, has not participated in ranked) |
| rankImg | string | Rank image URL; empty string when the user has no rank |
| gender | number | Gender enum: 0 = unknown, 1 = male, 2 = female |
| testAccount | boolean | Whether a test account / platform review account |
| guest | boolean | Whether a guest (not logged in to a real account) |
1.2.2 Batch get user profiles
Use this for leaderboards, friend lists, match settlement, or any scenario that needs to fetch basic info for multiple users at once, avoiding high‑frequency looping calls to getProfile.
/**
* Batch get user profiles
* @param uids Required; array of uids to query (recommended: 1–50 per call)
*/
GameTokSDK.getProfiles({ uids: [1234567, 1234568, 1234569] })
.then(({ action, error, data }) => {
console.log('Batch fetch succeeded:', data);
// data is a Profile array; iterate or map by uid directly
const map = new Map(data.map(p => [p.uid, p]));
console.log('1234567 ->', map.get(1234567));
})
.catch((e) => {
console.error('Batch fetch failed:', e);
});Parameters:
| Field | Type | Required | Description |
|---|---|---|---|
| uids | number[] | Yes | List of uids to query; recommended length 1–50. If empty array, non-array, or contains invalid elements (e.g. "abc", null, negative numbers, decimals), SDK directly reject(SDKError), error code INVALID_PARAMS = 1003, will not send to native |
Sample response (JSON):
{
"action": "GET_PROFILES",
"error": false,
"data": [
{
"uid": 1024780,
"avatar": "https://game-load-sa.lobah.net/avatar/1.jpg",
"userName": "Guest",
"userCoins": 0,
"level": 0,
"gameLevel": 0,
"rankImg": "",
"gender": 0,
"testAccount": false,
"guest": true
},
{
"uid": 1024781,
"avatar": "https://game-load-sa.lobah.net/avatar/2.jpg",
"userName": "Alice",
"userCoins": 200,
"level": 3,
"gameLevel": 5,
"rankImg": "https://game-load-sa.lobah.net/rank/5.png",
"gender": 2,
"testAccount": false,
"guest": false
}
]
}1.2.3 Store key‑value
/**
* Store a key‑value pair
* @param key Custom key
* @param value Value; may be a string or an object
*/
GameTokSDK.storageSet({ key: 'settings', value: { theme: 'dark', volume: 0.8 } })
.then(() => {
console.log('Stored successfully');
})
.catch((e) => {
console.error('Store failed:', e);
});Sample response (JSON):
{
"action": "STORAGE_SET",
"error": false,
"data": null
}1.2.4 Read key‑value
/**
* Read a key‑value pair
* @param key Predefined key to read
*/
GameTokSDK.storageGet({ key: 'settings' })
.then((result) => {
console.log('Read OK:', result.data.value); // object or string
})
.catch((e) => {
console.error('Read failed:', e);
});Sample response (JSON):
{
"action": "STORAGE_GET",
"error": false,
"data": {
"value": {
"theme": "dark",
"volume": 0.8
}
}
}1.2.5 Report score
/**
* Depending on the game, you may report score, stage, or level.
* For score: { score: 300, scoreType: 'score', remark: 'score' }
* For level: { score: 1, scoreType: 'level', remark: 'level' }
* @param score Value to report; must be a positive integer
* @param scoreType Category (business‑defined label for the uploaded data)
* @param remark Optional note (may match scoreType)
*/
GameTokSDK.addScore({ score: 10, scoreType: 'score', remark: 'score' })
.then(() => {
console.log('Score reported');
})
.catch((e) => {
console.error('Report failed:', e);
});Sample response (JSON):
{
"action": "ADD_SCORE",
"error": false,
"data": null
}1.2.6 Microphone permission
/**
* Request microphone permission (Android)
* Use in‑game to prompt the user for mic access (e.g., voice features).
* No return value; only triggers the native permission UI.
*/
GameTokSDK.getPermissionMic();1.2.7 Top up (TOPUP)
Important: User cancel, payment failure, etc. still resolve via then. In then, check data.code for success. catch is only for SDK unavailability, timeouts, and similar errors.
/**
* Top up
* @param amount Amount (units as agreed with the client)
*/
GameTokSDK.topup({ amount: 100 })
.then(({ action, error, data }) => {
const code = data && data.code;
if (code === 200) {
console.log('Top‑up succeeded:', data);
} else {
console.warn('Top‑up incomplete or failed, code:', code, data);
}
})
.catch((e) => {
console.error('Top‑up request error (e.g., SDK unavailable):', e);
});Sample response (JSON, success):
{
"action": "TOPUP",
"error": false,
"data": {
"code": 200,
"message": "ok"
}
}
code values:
200 Success
401 Top-up failed (error)
402 Top-up failed (user cancelled)1.2.8 Round start (ROUND_START)
Call once after a round actually starts. round_id is optional and we recommend omitting it: when omitted, the SDK generates a globally unique round ID (prefixed round_), remembers it internally, and roundEnd() automatically carries the same value. Optional timestamp (ms; defaults to now if omitted). No Promise return — fire‑and‑forget.
// Recommended: pass nothing; the SDK generates round_id and binds it to this round
GameTokSDK.roundStart();
// Compatible usage: pass your own round_id (must be globally unique per round and never reused across rounds;
// never use session-local incrementing numbers like 1, 2, 3 — they collide with past rounds after a page refresh)
GameTokSDK.roundStart({ round_id: 'round-' + crypto.randomUUID() });When you need the current round ID (e.g. to pass to showEnergyInsufficientDialog for attribution):
const roundId = GameTokSDK.getCurrentRoundId(); // set after roundStart, null after roundEndIf
roundStartis called again without aroundEndfor the previous round, it is treated as a new round and the SDK simply overwrites the current round ID (the old round is treated as not ended normally; the platform tolerates this).
1.2.9 Round end (ROUND_END)
Call after the round ends (win, loss, or timeout all count as ending). round_id is optional: when omitted, the SDK automatically carries the same value as this round's roundStart; if you pass it yourself it must match the round start. Optional timestamp. No Promise return.
// Recommended: pair with roundStart() and never touch round_id
GameTokSDK.roundStart();
// ... round logic ...
GameTokSDK.roundEnd();
// Compatible usage: pass explicitly (must equal this round's roundStart round_id)
GameTokSDK.roundEnd({ round_id: myRoundId, timestamp: Date.now() });If there is no round in progress (never called roundStart, or already called roundEnd) and no explicit round_id is passed, the SDK logs a warning and does not send to native.
1.2.10 Energy-insufficient dialog (SHOW_ENERGY_INSUFFICIENT_DIALOG)
When the game learns that energy is insufficient via getEnergy() / onEnergyUpdate() (see 1.2.11) (e.g. balance < cost_per_round, so a new round cannot start), call this API to ask the platform to show the energy-insufficient dialog in the game container layer (it contains guidance such as check-in for energy, go get energy, etc.). The game does not evaluate energy rules or handle the dialog UI; it only triggers it. No Promise return — fire‑and‑forget.
Note: the platform itself also shows this dialog proactively at times such as round-start charging and round-end checks; the game-side call is a supplementary trigger (typical scenario: the player taps "Start" but the game already knows energy is insufficient, so it requests the dialog instead of silently doing nothing).
/**
* Energy-insufficient dialog
* @param scene Optional trigger scene (business-defined, e.g. round_start, continue)
* @param round_id Optional associated round ID for platform attribution (use getCurrentRoundId())
*/
GameTokSDK.showEnergyInsufficientDialog({
scene: 'round_start',
round_id: GameTokSDK.getCurrentRoundId() || undefined,
});
// Or call without parameters
GameTokSDK.showEnergyInsufficientDialog();KC League flow: before a match starts, the game server calls the platform server's
entry-check. When it returnscanStart: false, call this same API and attach the fields from the check response as-is (the fire-and-forget channel passes through arbitrary JSON fields, no SDK release needed). After the call the game stays on the cover screen and the START button becomes clickable again; if the platform wants to start the match directly after the player tops up / checks in, it will sendAUTO_START_GAME— the game does not need to retry on its own.javascriptGameTokSDK.showEnergyInsufficientDialog({ scene: 'kc_entry_check', energyBalance: 0, // balance returned by entry-check, passed through as-is blockReason: 'NO_ENERGY' // enum returned by entry-check, passed through as-is });
Sample payload (JSON):
{
"action": "SHOW_ENERGY_INSUFFICIENT_DIALOG",
"data": {
"scene": "round_start",
"round_id": "round-1732000000-abc123"
}
}1.2.11 Energy query and push (getEnergy / onEnergyUpdate)
Energy deduction, dialogs, and recharge are all handled by the platform. The game only needs to do two things: display the energy HUD, and grey out the "Start" button when energy is insufficient. There are two data sources:
getEnergy()(request-response): query once proactively during initialization;onEnergyUpdate(handler)(platform push): after every energy change (round-start charge, check-in, recharge, round-end check, etc.) the platform pushes the latest value, and the game refreshes the HUD accordingly.
Both return/push the same data structure:
| Field | Type | Description |
|---|---|---|
| balance | number | null | Energy balance; null when the energy feature is disabled — the game should hide the energy UI and run with unlimited energy |
| charge_enabled | boolean | Whether per-round deduction is enabled for this game |
| cost_per_round | number | Energy consumed per round |
| reason | string | Reason for the change (push only): initial / round_charge / round_end_check / checkin / recharge_check |
| seq | number | Monotonically increasing sequence number; consumers must discard out-of-order stale values by seq (pushes are not guaranteed to arrive in order) |
// Initialization: query once + register push listener
const { data } = await GameTokSDK.getEnergy();
if (data.balance === null) {
hideEnergyHud(); // energy feature disabled; degrade to unlimited energy
} else {
renderEnergyHud(data.balance, data.cost_per_round);
}
let lastSeq = -Infinity;
GameTokSDK.onEnergyUpdate((payload) => {
if (payload.seq <= lastSeq) return; // discard out-of-order stale values
lastSeq = payload.seq;
renderEnergyHud(payload.balance, payload.cost_per_round);
setStartButtonEnabled(payload.balance === null || payload.balance >= payload.cost_per_round);
});Last-value sync compensation: like the audio events, onEnergyUpdate's options.sync defaults to true — if the SDK has already cached an energy value at registration time (from an earlier push or a getEnergy() response), it re-emits it once asynchronously, so late registration does not miss the current state; pass { sync: false } to disable. offEnergyUpdate / onceEnergyUpdate (same signatures as the audio events) are also provided, along with a read-only cache:
const { last, seq } = GameTokSDK.getEnergyState(); // last is the latest energy data (monotonic by seq), null when no dataLocal debugging (Mock): after enableMock(), getEnergy() returns the built-in mock value { balance: 100, charge_enabled: true, cost_per_round: 8 }; you can override it with a custom handler:
GameTokSDK.enableMock({ GET_ENERGY: () => ({ balance: 3, charge_enabled: true, cost_per_round: 8, seq: Date.now() }) });ENERGY_UPDATE pushes come from the host and are absent in Mock mode; during debugging you can dispatch one manually from the console:
window.dispatchEvent(new MessageEvent('message', { data: JSON.stringify({
action: 'ENERGY_UPDATE',
data: { balance: 0, charge_enabled: true, cost_per_round: 8, reason: 'round_charge', seq: Date.now() }
})}));1.2.12 Audio event listeners (onAudioSuspend / offAudioSuspend / onAudioResume / offAudioResume)
When the platform requires the game to mute (e.g., the user enters a live room, a system call comes in, etc.), an audio suspend event is pushed to the game; when sound is allowed to resume, an audio resume event is pushed. The game should promptly mute/unmute all sound effects and background music upon receiving these events.
This is an event listener interface, not a Promise interface, and has no return value.
// Listen for audio suspend event
function handleAudioSuspend(payload) {
console.log('Mute notification received:', payload);
// Mute all sound effects and background music in the game
myGame.muteAll();
}
GameTokSDK.onAudioSuspend(handleAudioSuspend);
// Listen for audio resume event
function handleAudioResume(payload) {
console.log('Resume sound notification received:', payload);
// Restore all sound effects and background music in the game
myGame.unmuteAll();
}
GameTokSDK.onAudioResume(handleAudioResume);Remove listeners:
// Remove audio suspend listener (pass the same function reference used when registering)
GameTokSDK.offAudioSuspend(handleAudioSuspend);
// Remove audio resume listener
GameTokSDK.offAudioResume(handleAudioResume);options.sync parameter (state synchronization):
onAudioSuspend and onAudioResume support a second optional options parameter, where sync (default true) handles the case where the listener is registered after the event has already fired:
sync: true(default) — If audio is already in a suspended/resumed state when the listener is registered, the callback will be called once immediately in the next microtask, ensuring the game does not miss previous state changes.sync: false— Only listens for subsequent new event pushes; no state sync on registration.
// Default sync: true — if audio is already suspended, callback fires immediately once
GameTokSDK.onAudioSuspend((payload) => {
myGame.muteAll();
});
// Disable state sync, only listen for subsequent events
GameTokSDK.onAudioSuspend((payload) => {
myGame.muteAll();
}, { sync: false });Recommended usage: Register listeners as early as possible during game initialization and keep sync: true (default), so the game can correctly sync state even if the platform has already sent a mute instruction before registration.
1.2.13 Game control event listeners (onAutoStartGame / offAutoStartGame / onExitAndResetGame / offExitAndResetGame)
The client / shell can push two types of control commands to the game (client => H5):
- AUTO_START_GAME: Notifies the game to start automatically, with the same effect as clicking the in-game start button. There are two trigger scenarios: (1) the player taps "Play again" in the platform leaderboard overlay and the platform sends it after verifying energy; (2) auto-start on entry — when the player enters from the mini-program poster page by tapping "Start now", the platform sends it directly once the game is ready, so it may arrive right after the game finishes loading, before the player has performed any in-game action.
- EXIT_AND_RESET_GAME: The client notifies the game to end and return to the initial state (terminate the current round immediately, clear countdowns/animations, and return to the initial or ready-to-start screen).
⚠️ Games integrating the energy / leaderboard flow must implement both listeners: the platform's energy race-condition fallback (energy found insufficient only after the round started) relies on
EXIT_AND_RESET_GAMEto reclaim the round, and continuing after check-in / recharge as well as auto-start on entry rely onAUTO_START_GAMEto start the round; without them the platform cannot intercept the game when energy is insufficient.
This is an event listener interface, not a Promise interface, and has no return value. AUTO_START_GAME supports one-time re-emission on late registration: if no listener exists when the command arrives (e.g. auto-start on entry arrives before game initialization completes), the SDK stores the latest one and re-emits it once asynchronously when the first onAutoStartGame is registered, then clears it — so registering the listener during initialization as usual is enough and the entry command will not be missed. EXIT_AND_RESET_GAME is a purely instantaneous command with no compensation (it is only sent mid-round, when the game has necessarily finished initializing); register the listener as early as possible during game initialization.
// Listen for "start game" command
function handleAutoStart(payload) {
console.log('Start game command received:', payload);
myGame.start(); // same as clicking the in-game start button
}
GameTokSDK.onAutoStartGame(handleAutoStart);
// Listen for "exit and reset game" command
function handleExitReset(payload) {
console.log('Exit and reset command received:', payload);
myGame.exitAndReset(); // end current round and return to initial state
}
GameTokSDK.onExitAndResetGame(handleExitReset);Remove listeners:
// Pass the same function reference used when registering
GameTokSDK.offAutoStartGame(handleAutoStart);
GameTokSDK.offExitAndResetGame(handleExitReset);Listen once:
GameTokSDK.onceAutoStartGame((payload) => {
myGame.start();
});
GameTokSDK.onceExitAndResetGame((payload) => {
myGame.exitAndReset();
});Client push message format:
{ "action": "AUTO_START_GAME" }{ "action": "EXIT_AND_RESET_GAME" }1.2.14 Game's own back button (requestExit / REQUEST_EXIT)
If the game UI has its own "back to home / exit" button, call requestExit() when the player taps it to tell the platform "I want to go back home". The platform will then send EXIT_AND_RESET_GAME, and the game wraps up in its existing onExitAndResetGame listener (terminate the round, clear animations, return to the ready-to-start screen) — requestExit() itself does not change any round state, nor does it clear the current round_id.
This is a fire-and-forget interface: no Promise, no return value, no de-duplication (repeated taps are de-duplicated on the platform side). All parameters are optional and only used for troubleshooting correlation:
/**
* Player taps the in-game "back to home" button
* @param reason Optional trigger reason; defaults to 'player_button'
* @param state Optional game state at tap time (e.g. 'IDLE' / 'PLAY' / 'RESULT'), troubleshooting only
* @param timestamp Optional ms timestamp; defaults to now if omitted
*/
GameTokSDK.requestExit();
// With troubleshooting info
GameTokSDK.requestExit({ reason: 'player_button', state: 'PLAY' });
// The actual wrap-up still happens here (must be registered before integrating, see 1.2.13)
GameTokSDK.onExitAndResetGame(() => myGame.exitAndReset());If a round is in progress (after
roundStart, beforeroundEnd), the SDK automatically includesround_idso the platform can correlate this round; no need to pass it manually.
Sample payload (JSON):
{
"action": "REQUEST_EXIT",
"data": {
"reason": "player_button",
"state": "PLAY",
"round_id": "round_xxx",
"timestamp": 1717000000000
}
}Field reference:
| Field | Type | Description |
|---|---|---|
| reason | string | Trigger reason; defaults to player_button |
| state | string | Optional, game state at tap time, troubleshooting only |
| round_id | string | Optional, automatically included by the SDK when tapped mid-round |
| timestamp | number | ms timestamp; defaults to now |
⚠️ Do not reset the game yourself after
requestExit()— always wait forEXIT_AND_RESET_GAMEto arrive before wrapping up, to avoid the platform dialog / settlement getting out of sync with the game screen.
Parameters and response shape
- Typical call parameters (reference; see each API for exact fields):
- getProfile: `` (usually no extra parameters)
- getProfiles:
{ uids: number[] }(required, non-empty array; returnsProfile[], same structure asgetProfile.datain array form) - storageSet:
{ key: string, value: any } - storageGet:
{ key: string } - addScore:
{ score: number, scoreType: string } - getPermissionMic: `` (usually no extra parameters)
- topup:
{ amount: number, ... }(other fields per client protocol) - roundStart / roundEnd:
{ round_id: string, timestamp?: number } - showEnergyInsufficientDialog:
{ scene?: string, round_id?: string }(optional; fire-and-forget, no return value) - onAudioSuspend / onAudioResume:
(handler: Function, options?: { sync?: boolean }) - offAudioSuspend / offAudioResume:
(handler: Function) - onAutoStartGame / onExitAndResetGame:
(handler: Function) - offAutoStartGame / offExitAndResetGame:
(handler: Function) - requestExit:
{ reason?: string, state?: string, timestamp?: number }(all optional; fire-and-forget, no return value;round_idis included automatically mid-round)
- Successful responses share this shape:
{
action: string; // Action name for this call (e.g. 'GET_PROFILE')
error: false; // false on success (on failure the Promise rejects)
data: any; // Native payload; fields depend on the action
}- Failure behavior:
topup: Most business outcomes (success/failure/cancel) resolve inthenviadata.code;catchis for SDK errors and timeouts.- Other Promise interfaces: On failure the Promise rejects with an
Error; handle in.catch(log, retry, or show UI).
Important notes
Do not overwrite or remove these globals, or the SDK will break:
window.GameTokSDK(main SDK entry)
Example (do not do this):
// Dangerous — breaks iOS / Android bridge
window.GameTokSDK = {};
window.GameTokSDK = null;
delete window.GameTokSDK;Full example
// Get user profile
GameTokSDK.getProfile({})
.then((resp) => {
console.log('Profile:', resp.data);
})
.catch((e) => {
console.error('Failed to get profile:', e);
});
// Batch get user profiles (for leaderboards, friend lists, etc.)
GameTokSDK.getProfiles({ uids: [10001, 10002, 10003] })
.then(({ data }) => {
const map = new Map(data.map(p => [p.uid, p]));
console.log('10001 profile:', map.get(10001));
})
.catch((e) => {
console.error('Batch fetch failed:', e);
});
// Store
GameTokSDK.storageSet({ key: 'config', value: { lang: 'en' } })
.then(() => {
console.log('Stored successfully');
})
.catch((e) => {
console.error('Store failed:', e);
});
// Read
GameTokSDK.storageGet({ key: 'config' })
.then((resp) => {
console.log('Config:', resp.data.value);
})
.catch((e) => {
console.error('Read failed:', e);
});
// Score
GameTokSDK.addScore({ score: 5, scoreType: 'score' })
.then(() => {
console.log('Score added');
})
.catch((e) => {
console.error('Score report failed:', e);
});
// Microphone (Android, no return value)
GameTokSDK.getPermissionMic();
// Top up: check data.code in then (200 = success)
GameTokSDK.topup({ amount: 100 })
.then(({ data }) => {
if (data && data.code === 200) {
console.log('Top-up succeeded', data);
} else {
console.log('Top-up not successful', data);
}
})
.catch((e) => console.error('Top-up error', e));
// Round notifications (same round_id)
const rid = 'r-' + Date.now();
GameTokSDK.roundStart({ round_id: rid });
GameTokSDK.roundEnd({ round_id: rid });
// Audio event listeners (register during game initialization)
GameTokSDK.onAudioSuspend((payload) => {
console.log('Mute', payload);
myGame.muteAll();
});
GameTokSDK.onAudioResume((payload) => {
console.log('Resume sound', payload);
myGame.unmuteAll();
});
// Game's own "back to home" button: only notify the platform; wrap-up waits for EXIT_AND_RESET_GAME
document.getElementById('btn-home').onclick = () => GameTokSDK.requestExit({ state: 'PLAY' });