@niro-npm/game-sdk-dev
v0.11.1
Published
Dev tools for @niro-npm/game-sdk: shell emulator for local testing and a CLI to bundle a game project.
Readme
@niro-npm/game-sdk-dev
Dev tools for @niro-npm/game-sdk: a local shell emulator for
running a game without the real platform, and a CLI that packages a game
project into bundle.zip for upload via the Partner API.
Install
@niro-npm/game-sdk must be installed in the game project (game-sdk-dev
reads node_modules/@niro-npm/game-sdk/package.json to stamp
sdk_version into the manifest):
npm install --save @niro-npm/game-sdk
npm install --save-dev @niro-npm/game-sdk-devgame-sdk bundle
Package a game directory into bundle.zip.
npx game-sdk bundle [--dir <path>] [--out <path>] [--dry-run]Flags:
| Flag | Default | Description |
|---|---|---|
| --dir <path> | . | Project root containing manifest.json and assets. |
| --out <path> | ./bundle.zip | Output zip path. |
| --dry-run | off | Validate only, do not write the zip. |
Project layout
my-game/
├── manifest.json # required, at the root
├── index.html # or the path set in manifest.entry
├── assets/... # whitelisted static assets
└── node_modules/
└── @niro-npm/
└── game-sdk/ # peer dependency, must be installedmanifest.json
Required fields: name, version, entry, display_name, pools[]. Optional
permissions (default []). The CLI reads the installed SDK version and
writes it into manifest.sdk_version in the packaged archive — do not set
this field manually; if you do, the CLI overrides it and prints a warning.
{
"name": "coin-flip",
"version": "1.0.0",
"entry": "index.html",
"display_name": "Coin Flip",
"permissions": [],
"pools": [
{
"slug": "default",
"name": "Default",
"ticket_price": "100",
"rtp": "0.85",
"tickets_per_draw": 100,
"strategy_type": "tiered",
"strategy_params": { "tiers": [{ "prize": 1000, "count": 1 }] }
}
]
}pools[] is the source of truth for the game's prize pools. Each pool is
addressed by slug in ticket.buy / ticket.buy_batch. The emulator reads
pools[] from this manifest and exposes the exact same shape to the game via
session.init.
Validation
The CLI mirrors the server-side checks so you fail fast locally:
manifest.jsonis valid JSON with all required fields.manifest.entrypoints to an existing file.manifest.pools[]is non-empty; each pool has uniqueslug(regex^[a-z][a-z0-9-]{0,63}$),ticket_price > 0,rtpin(0, 1),tickets_per_draw >= 1, allowedstrategy_type, objectstrategy_params.- File extensions are on the whitelist:
html,js,css,json,png,jpg,jpeg,webp,svg,woff,woff2,mp3,ogg,wasm,glb. - No symlinks, no absolute paths, no
..traversal. - ≤1000 files, ≤200 MB uncompressed, ≤50 MB zipped.
node_modules/ and .git/ are skipped during the scan.
Exit codes
0 on success, non-zero on any validation or I/O error with a human-readable
message pointing at the offending file or manifest field.
game-sdk dev
Run the game locally inside an emulated shell. No real platform, no WS — the
emulator speaks the same postMessage envelope that shell uses in production
and mocks out session.init / ticket.buy / ticket.buy_batch /
ticket.reveal / ticket.reveal_batch / balance.get in-process.
npx game-sdk dev [--dir <path>] [--port <n>] [--balance <n>]Flags:
| Flag | Default | Description |
|---|---|---|
| --dir <path> | . | Directory to serve as the game (e.g. your Vite dist/). |
| --port <n> | 5173 | Port for the web server. |
| --balance <n> | 1000 | Starting balance for the mocked wallet. |
The pool catalogue is taken from --dir's manifest.json (pools[]). It is
the same source the bundle CLI validates and the same shape the game's
session.init response returns — what you see in dev is what the game will
see in production.
Open http://localhost:<port>/ and you will see the emulator shell wrapping
your game in an iframe. The sidebar exposes:
- pools (from manifest): list of all pools with
slug, name,ticket_price,rtp,strategy_type,strategy_params, and the per-draw available counter (available <current> / <tickets_per_draw>). Counter decrements on every successfulticket.buy/ticket.buy_batchfor that pool; when it hits 0, the next buy on the same slug returnspool_empty. Other slugs are unaffected —pool_sold_outis per-pool. - balance.updated button: push a
balance.updatedevent with an editable delta — triggerssdk.onBalanceUpdate. - session.terminated dropdown: send a
session.terminatedpush with a reason (draw_exhausted,account_blocked,idle_timeout, ...) — triggerssdk.onSessionEnd. - force ticket.buy error: pick an error code from the dropdown
(
insufficient_funds,rate_limited,pool_empty,pool_not_found, ...) and arm it — the nextsdk.buyTicket()/sdk.buyTicketsBatch()call returns that error, then the forcing disarms. Pick— none —and arm to clear. - Log: live trace of every envelope crossing the MessagePort.
Game project layout for dev
The server serves --dir as-is under /game/ and the emulator iframe points
at /game/. Your game's build output (e.g. Vite dist/) must use
relative asset paths so that references resolve under the /game/
subpath. In Vite this means base: "./" in vite.config.ts. Without HMR
integration, keep vite build --watch running in one shell and
npx game-sdk dev --dir dist in another.
The emulator prints the installed @niro-npm/game-sdk version and the
discovered pool slugs on startup so you can sanity-check what the game is
linked against.
