This is the complete browser-game flow for ChizVerse. It covers games launched from the ChizVerse platform and games opened directly, followed by the tracked gameplay pattern used by Chizmis Crush.The ChizVerse website is optional, but the ChizVerse backend is still required. Activity tracking cannot start without a launch token issued by Game Manager.Access Modes#
| Step | Launched from ChizVerse | Standalone game |
|---|
| Player authentication | ChizVerse authenticates the player before launch. | The game uses ChizmisAuth directly. |
| ChizVerse bearer | ChizVerse already holds chz_access_token. | The game exchanges the Chizmis token through Core Auth. |
| Launch creation | ChizVerse calls createLaunch() and adds chz_launch_token to the game URL. | The game calls createLaunch() with its new ChizVerse bearer. |
| Session creation | The game reads the launch token from the URL and calls startSession(). | The game passes the returned launch token to init() and calls startSession(). |
| Gameplay and wallet | The game calls Game Manager and ChizPay. | The sameāthe game calls Game Manager and ChizPay directly. |
ChizVerse creates the launch and opens the game with t, chz_access_token, and chz_launch_token.Standalone Game Sequence#
The game authenticates with Chizmis, exchanges the Chizmis token through Core Auth for a ChizVerse bearer, and creates its own launch.Token and Wallet Linkage#
The Chizmis token from t authenticates Chizmis and ChizPay requests.
The ChizVerse bearer from chz_access_token authenticates protected ChizVerse requests such as createLaunch().
The launch token is separate from both bearer tokens and only bootstraps a tracked Game Manager session.
A standalone game must register its own Chizmis clientId and exact callback URL.
Use one Game Manager sessionId as the ChizPay gameSession value throughout a run.
Call debit() before accepting a paid game action. Any payout or refund from credit() must reference the related debitTx.id through debitTransactionId.
Generate unique referenceNumber and nonce values for every wallet operation.
1.
ChizVerse web creates a launch ticket.
2.
The player opens the game with ?chz_launch_token=....
3.
The game calls initChizVerse() and startSession().
4.
The game sends updateProgress() and trackScore() while playing.
5.
Refresh only calls flush(), so the same session can resume.
6.
End Run calls endSession({ finalScore }).
7.
Leaderboard updates from the ended-session event.
8.
New Game calls startNewSession() to create a separate session.
SDK Imports#
import {
endSession,
flush,
getCheckpoint,
getGameLeaderboard,
init as initChizVerse,
setPresence,
startSession,
startNewSession,
trackScore,
updateProgress,
} from '@ccci/chizverse-sdk'
1. Open the Game With a Launch Token#
This part belongs in the launcher, not inside the game. In ChizVerse web, create a launch, then append the launch token to the game URL.import { getLaunchUrl } from '@ccci/chizverse-sdk'
const launch = await createLaunch(gameId, {
allowedOrigin: gameUrl,
buildId,
gameVersionId,
launchSource: 'CATALOG_PLAY',
})
const url = new URL(getLaunchUrl(gameUrl, {
launchToken: launch.queryParams.chz_launch_token,
}))
// Optional: pass ChizVerse bearer token so the game can create a new
// launch later when the player clicks New Game.
url.searchParams.set('chz_access_token', rawChizVerseAccessToken)
window.location.href = url.toString()
Use chz_launch_token to start the tracked session. Use chz_access_token only if the game needs to create a new launch from inside the game.2. Read the ChizVerse Token in the Game#
Use one key for the URL query param and localStorage key.const CHIZVERSE_ACCESS_TOKEN_KEY = 'chz_access_token'
function normalizeAccessToken(token?: string | null) {
const trimmed = token?.trim()
return trimmed ? trimmed.replace(/^Bearer\s+/i, '') : null
}
function initializeChizVerseAccessToken() {
const url = new URL(window.location.href)
const queryToken = normalizeAccessToken(url.searchParams.get(CHIZVERSE_ACCESS_TOKEN_KEY))
if (queryToken) {
localStorage.setItem(CHIZVERSE_ACCESS_TOKEN_KEY, queryToken)
url.searchParams.delete(CHIZVERSE_ACCESS_TOKEN_KEY)
window.history.replaceState(window.history.state, '', `${url.pathname}${url.search}${url.hash}`)
return queryToken
}
return normalizeAccessToken(localStorage.getItem(CHIZVERSE_ACCESS_TOKEN_KEY))
}
const chizVerseAccessToken = initializeChizVerseAccessToken()
3. Start or Resume the Session#
The SDK automatically reads chz_launch_token from the URL or localStorage.let trackedGameId: number | null = null
let trackedBuildId: number | null = null
let trackedGameVersionId: number | null = null
let sessionStartedAt = Date.now()
const trackingActive = ref(false)
const runEnded = ref(false)
async function initSdkTracking() {
await initChizVerse({
autoStartSession: false,
debug: new URLSearchParams(window.location.search).has('chz_debug'),
})
await startSession({
levelId: 'main-board',
mode: 'campaign',
metadata: {
player: {
accountId: player.id,
displayName: player.displayName,
avatar: player.avatar,
},
},
})
const checkpoint = await getCheckpoint()
if (!checkpoint) return
trackedGameId = checkpoint.gameId
trackedBuildId = checkpoint.buildId
trackedGameVersionId = checkpoint.gameVersionId
trackingActive.value = true
restoreSavedProgress(checkpoint)
setPresence({ state: 'in_game', reason: 'game-loaded' })
}
If the page refreshes, startSession() resumes the saved session instead of creating a duplicate session.4. Restore Saved Progress#
getCheckpoint() returns the latest saved session state from Game Manager.checkpoint.currentScore to restore the score.
checkpoint.progressSnapshot.metadata to restore custom game state that you previously sent in updateProgress().
In Chizmis Crush, updateProgress() saves coins and wordsSpilled, so the restore code reads those same fields back.function restoreSavedProgress(checkpoint: any) {
// Score comes from trackScore().
score.value = checkpoint.currentScore ?? 0
// Metadata comes from updateProgress().
const saved = checkpoint.progressSnapshot?.metadata
coins.value = typeof saved?.coins === 'number' ? saved.coins : 0
wordsSpilled.value = typeof saved?.wordsSpilled === 'number' ? saved.wordsSpilled : 0
}
The important rule is simple: if you want a value to survive refresh, include it in updateProgress({ metadata: ... }).5. Track Gameplay#
Send progress and score whenever the player makes meaningful progress.function trackProgress(checkpointId: string) {
if (!trackingActive.value || runEnded.value) return
updateProgress({
checkpointId,
levelId: 'main-board',
completionPercent: Math.min(100, wordsSpilled.value * 5),
metadata: {
score: score.value,
coins: coins.value,
wordsSpilled: wordsSpilled.value,
},
})
}
function trackScoreUpdate(delta: number, reason: string) {
if (!trackingActive.value || runEnded.value) return
trackScore({
score: score.value,
delta,
reason,
metadata: {
coins: coins.value,
wordsSpilled: wordsSpilled.value,
},
})
}
score.value += totalPoints
coins.value += Math.floor(totalPoints / 20)
trackProgress('tile-clear')
trackScoreUpdate(totalPoints, 'match-clear')
6. Flush on Refresh#
Do this on refresh or page close:onBeforeUnmount(() => {
void flush({ keepalive: true })
})
Do not call endSession() on refresh. Ending the session prevents resume.7. End the Run#
Call endSession() only when the run is done. Leaderboard processing uses this ended-session event.async function finishTrackedRun() {
if (!trackingActive.value || runEnded.value) return
runEnded.value = true
await endSession({
reason: 'completed',
finalScore: score.value,
durationSeconds: Math.max(1, Math.round((Date.now() - sessionStartedAt) / 1000)),
metadata: {
coins: coins.value,
wordsSpilled: wordsSpilled.value,
},
})
await refreshLeaderboard()
}
8. Load Leaderboard#
async function refreshLeaderboard() {
if (!trackedGameId) return
const leaderboard = await getGameLeaderboard(trackedGameId, {
limit: 5,
type: 'ALL_TIME',
})
leaderboardEntries.value = leaderboard.entries
}
9. New Game Creates a New Session#
When the player starts a new game, end the current run, reset local state, then create a new launch and session.async function startFreshTrackedGame() {
if (!trackedGameId || !trackedBuildId || !trackedGameVersionId || !chizVerseAccessToken) {
resetLocalGame()
return
}
if (!runEnded.value) {
await finishTrackedRun()
}
resetLocalGame()
await startNewSession(
trackedGameId,
{
allowedOrigin: window.location.origin,
buildId: trackedBuildId,
gameVersionId: trackedGameVersionId,
launchSource: 'CATALOG_PLAY',
metadata: { source: 'new-game' },
},
chizVerseAccessToken,
{
levelId: 'main-board',
mode: 'campaign',
metadata: { player },
}
)
sessionStartedAt = Date.now()
runEnded.value = false
trackingActive.value = true
setPresence({ state: 'in_game', reason: 'new-game' })
}
Quick Rules#
Use chz_launch_token to start or resume the tracked session.
Use chz_access_token only when the game needs to create another launch.
Use trackScore() with the current total score, not only the delta.
Use updateProgress() for checkpoint data that should survive refresh.
Use getCheckpoint() after startSession() to restore the latest score and saved progress.
Use getGameLeaderboard(gameId) when you need to show scores for that game.
Use flush({ keepalive: true }) on refresh.
Use endSession({ finalScore }) only when the run is actually finished.
Use startNewSession() for New Game so every run has a separate session.
Modified atĀ 2026-07-30 06:57:16