Pika StudioRun and debug Pika Lua game packs in the native simulator directly from VS Code. Features
Requirements
Quick Start
Commands
Extension Settings
|
| Field | Source on the robot | Control |
|---|---|---|
language |
Host-side (game_get_language), the robot's current UI language. |
Dropdown — the robot's full language_t enum (kLangIso[]), 10 codes. Out of range → vi. |
is_a2a |
Host-side, true when launched inside an A2A talk flow. | Dropdown — true / false. |
level, difficulty, … |
The backend's GAME_START data object, forwarded verbatim. |
Add-a-row, name is a combobox with presets. |
Those two host fields are the only ones the firmware defines by name, which is why only they get a closed list. They are attached after the server object is decoded, so a server field of the same name cannot override them.
Everything else is server-defined: back stashes the whole data object without
inspecting it, precisely so a new server field needs no firmware change. The
presets (difficulty → easy/medium/hard, level, game_type, hero_label)
are conventions drawn from packs and docs, not a contract — so the name field
stays typeable and any new server field remains testable here.
Only an A2A launch carries server params. A menu launch sends GameStartMsg_st,
which holds nothing but game_id. Setting params with is_a2a off therefore
describes a launch the robot cannot perform, and the section warns when you do.
The 128-byte budget includes game_id
conversation.cpp serializes the whole data object into the char[128]
params_json field, and game_id is part of that object — so the pack name eats
into the same budget (and costs one of the 32 nodes). The meter accounts for it:
the Configuration section sizes against the longest installed pack name, and the
runner sizes against the pack actually being launched.
This matters because the overflow is silent on the device: back logs a warning
but strncpys anyway, the clipped JSON no longer parses, and the game receives an
empty table — indistinguishable in-game from "the server sent nothing". Studio
therefore refuses to save an over-budget payload and drops it rather than
truncating at launch.
Ranking on PC (pika.rankMode)
On the robot, back closes the ranking loop by itself: Ranking.report() arrives
as IPC_CMD_GAME_RESULT, back POSTs /games/result, then GETs /games/rank,
unwraps data, and sends it down as IPC_CMD_GAME_RANK_RESULT. Head latches
that reply and Ranking.get_result() consumes it once.
The simulator runs no back, so nothing closes that loop on its own and
Ranking.get_result() would return nil forever — which looks exactly like a
broken pack. pika.rankMode decides how it gets closed:
| Mode | Ranking.report() |
Ranking.get_result() |
|---|---|---|
real (default) |
POSTs a real score row | real GET /games/rank |
fetch |
logged only, not uploaded | real GET /games/rank |
mock |
logged to the dashboard | whatever you inject by hand |
The default is real so the PC behaves like the robot without being configured
into it — a leaderboard branch that works here works there.
It writes to the live board. Every Ranking.report() in real mode appends
a score row children will see. When you are iterating on a pack and don't want
that trail, switch to fetch: same real board data, no upload. mock needs no
credentials at all and is the offline fallback.
Note that all three are inert without pika.gameVoice.* credentials — an
unconfigured install falls back to mock injection and says so in the log.
Both real modes reuse the pika.gameVoice.* credentials (robot id + session or
master key) and send the same robot-id / challenge / request-token headers
back sends. They are independent of pika.voiceMode: fetching a leaderboard
does not turn on the microphone.
Note that packs poll for a short window after reporting — word_maze gives up
after 6 s — so in mock mode the injected board has to land inside that window.
The real modes have no such race.
The POST body is built field-for-field against Application::PostGameResult
(robot_id, game_id, version, entry, ended_reason, score, duration),
and both calls carry firmware_version in the query like every other device API
call back makes. Both modes were verified end-to-end against the live backend:
fetch returns the real board, and real gets {"recorded":true} back with the
board updating in the same run.
Two behaviours worth knowing before reading a real-mode result: the backend
keeps only your best score, so posting below your current best changes
nothing visible even though the row was written; and duration is 0 unless the
pack actually ran that long, since the engine computes it from VM start.
Debug
Add a launch.json configuration:
{
"type": "pika-lua",
"request": "launch",
"name": "Pika: Debug Game",
"gameId": "${command:pika.pickGame}"
}
Or press F5 — Pika Studio will prompt you to pick a game pack.
Icons
| File | Role |
|---|---|
media/icon-source.svg |
Vector master (colour badge). Edit this one. |
media/icon.png |
Marketplace icon, 512×512, rendered from the master. |
media/icon.svg |
Activity-bar icon. VS Code renders it as a monochrome mask, so it is a flat silhouette — colour and gradients there are discarded. |
icon.png is a build product: after changing icon-source.svg, re-render it or
the shipped icon silently keeps the old art.
npx sharp-cli -i media/icon-source.svg -o media/icon.png resize 512 512
License
MIT