Instrumenting with Claude Code
You don’t have to hand-write the Emission Profile integration. If you have an existing HTML5 game, an AI coding agent like Claude Code can add full xAPI instrumentation for you — the profile was written to be machine-readable. The whole job happens on your machine: instrument, test against a simulated launch, and package. The output is one ZIP file, ready to upload to dev.edu.games as the final step.
What You Need
- Your existing HTML5 game as a local project folder with an
index.htmlentry point. No xAPI code required — that’s what you’re about to add. - Claude Code installed (
npm install -g @anthropic-ai/claude-code, or the desktop app). Any capable coding agent works; the prompts below assume Claude Code. - Nothing from the platform. No credentials, no API keys, no test account. When your game eventually runs on edu.games, the player injects the LRS endpoint, auth, and student identity at launch — your code just reads the launch context, so everything can be built and verified against a local simulation of it.
Step 1 — Run Claude Code Against the Profile
Open a terminal in your game’s project folder, run claude, and paste the prompt below. It tells the agent to fetch the live Emission Profile and instrument against it — so you always integrate against the current version of the contract rather than a copy that may drift.
Instrument this HTML5 game for the edu.games xAPI Emission Profile.
I have no access to the edu.games platform from here — everything must
work locally, and the end product is the instrumented game itself,
ready to zip and upload.
1. Fetch and read https://docs.edu.games/xapi/emission-profile — it
is the authoritative contract. Follow it literally, including the
launch-channel security rules, delivery & attribution rules, and the
exact verb IRIs and extension keys. Do not invent verbs or extensions.
2. Do not change gameplay. Only add telemetry. Build one small wrapper
module (e.g. xapi.js) that:
- reads the launch context from the injected globals
(window.EDUGAMES_LAUNCH, window.XAPI_ENDPOINT, window.XAPI_AUTH)
with the postMessage channel as fallback, applying the profile's
security rules (parent-source check, first-launch-wins, endpoint
allowlist with whole-message rejection);
- never hardcodes an endpoint, auth value, or actor — all of that
arrives from the platform at launch. If no launch context appears,
the game must run normally in anonymous mode per the profile;
- queues statements in memory, flushes every 5-10s and on pagehide
with fetch keepalive (max ~20 statements per final flush);
- stamps every statement with the launch actor, registration, and the
profile-version category activity.
3. Identify this game's learning objectives (levels, puzzles, skills) and
give each a stable objective IRI per the profile. Then instrument:
launched (once per load), attempted, passed/failed with
result.success + score.scaled + active-time duration, retried,
completed, terminated on abandonment (including from the pagehide
handler when a play is open), and chose at any branch points.
Use the logged verb + /events/ namespace for gameplay telemetry that
is not a learning event. Skip the confidence extension unless the
game has a natural pre-reveal moment.
4. Anonymous play must still work: anon:-prefixed actor, game-minted
UUID registration, never any student PII.
5. When done, list every statement type the game now emits, which
conformance tier that reaches (Core / Timed / Choice / Confidence /
Full), and anything you intentionally skipped and why.Claude Code will read your codebase, find the right hook points (level start, win/lose, quit, choices), and wire everything through the wrapper. Answer its questions about what counts as an objective in your game — that mapping is the one thing only you know.
Step 2 — Verify With a Simulated Launch
You can prove the integration works without ever touching edu.games, because the platform’s entire contribution at runtime is the launch context — and that is trivial to fake locally. Ask Claude Code:
Build a local test harness that simulates the edu.games player, and use
it to verify the integration end to end:
- an HTML page that loads the game in an iframe, injecting a fake
EDUGAMES_LAUNCH global (test actor, registration UUID, endpoint) into
the iframe document before the game scripts run — exactly as the real
player does;
- a tiny local mock LRS (a few lines of node) that accepts POST
/statements, logs every statement body, and returns an array of ids;
- then play one full session and one abandoned session through the
harness, print every statement received, and check each one against
the Emission Profile: actor echoed verbatim, registration on every
statement, profile-version category present, durations plausible,
quitting emits terminated (never completed with success:false), a
title-screen bounce emits nothing after launched;
- also verify the security rules: a postMessage launch offering a
non-edu.games endpoint must be rejected in its entirety, and with no
launch context at all the game must run in anonymous mode with an
anon: actor.
The harness and mock LRS are dev tools only — keep them out of the game
folder (or delete them before packaging) so they don't ship in the zip.When the harness run is clean, the game is conformant. There is nothing the real platform adds that this doesn’t exercise — the endpoint, auth, and actor your game will receive in production are just different values in the same launch context.
Step 3 — Package the ZIP
Ask Claude Code to produce the final deliverable:
Package the game for upload: create a zip of the game folder with
index.html at the root of the archive (or inside exactly one top-level
folder, e.g. build/index.html — never nested deeper), all assets on
relative paths, and no dev files — no test harness, mock LRS,
node_modules, source maps, or .git. Then list the zip contents so I can
confirm.index.htmlmust be at the root of the archive or exactly one folder down (a build-output directory is fine) — anything nested deeper is rejected. See Packaging Your Game for the full layout rules.- All asset references must be relative paths (no absolute URLs to your own dev server).
- Maximum size 1 GB per zip — the other size and quota rules apply too.
Step 4 — Upload to dev.edu.games
The only platform step, and it’s the last one:
- Sign in to dev.edu.games. Developer accounts currently onboard by invite — once you have an invite (or if the waitlist has opened developer signups), register at edu.games/auth/register with the Developer role. See the Quick Start.
- Go to My Games → Upload New, tick the Browser build slot, choose xAPI Package (rather than Standard HTML5), fill in the listing details, and upload your ZIP.
- For later updates, open the game’s Edit page and replace the file — same listing, same URL. On a published game the replacement is staged for review before it goes live (see Review & Game Lifecycle).
From the moment it’s published, the player delivers the real launch context to your build: an LRS credential scoped to your game, and the student’s opaque identity for signed-in play. To sanity-check it live, play your own game and watch the browser DevTools network tab — POSTs to lrs.edu.games/xapi/statements returning 200 with an array of statement IDs. Every game has an Analytics Readiness page in the dev portal (My Games → Analytics); its inspector shows your last 50 statements parsed against the profile with per-statement pass/warn/fail annotations, and which conformance tier — and therefore which Learning Intelligence presets — your game has unlocked.
Iterating
- If the inspector flags something (“duration missing on passed”), paste the annotation straight back into Claude Code, re-run the harness, re-zip, replace the file.
- Keep objective IRIs stable across versions. If you rework levels, add new IRIs rather than renaming old ones — teachers map them to competencies.
- The profile is amended over time (see the changelog at the bottom of the Emission Profile). Re-running the Step 1 prompt against an already-instrumented game is safe: it fetches the live profile and updates only what changed.