NUGANUGA Web SDK
Your game runs in a sandboxed iframe on https://play.nuganuga.com. The SDK is the only channel to the platform — you never implement auth, ranking, sharing, ads or analytics yourself.
1. Load the SDK
Always from this exact absolute path. The platform serves and versions it.
<script src="/sdk/nuga-sdk.js"></script>
<script src="./game.js"></script>2. The four calls
const ctx = await Nuga.ready(); // wait for the platform
await Nuga.game.start(); // when the player actually starts a run
await Nuga.score.submit({ score }); // exactly one numeric score per run
await Nuga.game.finish(); // the platform draws the Result screenDo not draw your own result screen, retry button, share button or leaderboard — the platform renders those after finish(), identically for every game.
3. Everything else
Nuga.game.getContext();
Nuga.player.get();
Nuga.challenge.get();
Nuga.storage.get(key);
Nuga.storage.set(key, value);
Nuga.audio.setMuted(true);
Nuga.locale.get(); // 'en' | 'ko' | ...
Nuga.event.track('level_up', { level: 3 });
Nuga.on('pause', () => pauseLoop());4. The manifest
nuga.json at the root of your bundle:
{
"schemaVersion": 1,
"name": "Reaction Hunter",
"slug": "reaction-hunter",
"description": "Click 10 targets as fast as possible.",
"rule": "Tap every target as fast as you can.",
"category": "REACTION",
"orientation": "PORTRAIT",
"duration": { "min": 5, "max": 30 },
"input": ["POINTER", "TOUCH"],
"ranking": { "type": "LOW_TIME", "unit": "ms" },
"entry": "index.html",
"locales": {
"ko": { "name": "리액션 헌터", "description": "10개의 타겟을 빠르게 클릭하세요." }
}
}Ranking types: HIGH_SCORE, LOW_SCORE, LOW_TIME, HIGH_TIME, CUSTOM. Categories: REACTION, TIMING, ACCURACY, MEMORY, PUZZLE, LUCK, ARCADE.
5. Sandbox rules
Static validation rejects a bundle that breaks any of these, before it ever reaches a player:
- No external URLs — no CDN, web font or remote image. Bundle everything.
- No
eval,new Function,document.writeordocument.cookie. - No network of your own:
fetchto an absolute URL,XMLHttpRequest,WebSocketand service workers are blocked by CSP as well as by validation. - No top-level navigation and no popups.
- Bundle under 5 MB; microgames should stay under 1 MB.
- At least one
Nuga.score.submit()call — a game that cannot produce a score cannot rank. - The platform shell is fixed: index.html, the shell stylesheet and the harness in game.js must be unchanged. Only the run() body and #field styles are yours.
6. Scores are verified
Every run is issued a server-side session. Your game never sees the session token — the platform shell holds it and submits on your behalf — and the server checks the score against the run’s wall-clock duration and the manifest’s declared bounds before it enters a leaderboard.
You can do better than that. Draw randomness from the run’s server-issued seed and record what actually happened, and the server re-scores the run from your events instead of trusting the number. A claim your replay does not support is refused outright.
const rng = Nuga.random(); // seeded by the server — reproducible, unlike Math.random()
const target = rng.int(0, 8);
Nuga.replay.record('hit', 172); // one score-relevant outcome, up to 400 per runDeclare the mechanic in nuga.json to switch it on. Without it your game still ranks, on duration and bounds checks alone.
"verification": { "kind": "REPLAY_V1", "mechanic": "REACTION",
"params": { "rounds": 5 } }7. Ship it
Zip the bundle root (not the folder) and upload it in the Creator Console. Static validation, build, runtime, responsive, gameplay and security suites all run automatically; publish unlocks once they pass.