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, and the first games instrumented this way reached the top conformance tiers in a single working session. 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/docs/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 (not inside a subfolder), 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 — a zip containing a single top-level folder won’t launch.- All asset references must be relative paths (no absolute URLs to your own dev server).
- Maximum size 1 GB.
Step 4 — Upload to dev.edu.games
The only platform step, and it’s the last one:
- Create a free developer account at dev.edu.games if you don’t have one.
- Go to Dashboard → Games → New Game, choose game type xAPI Package, fill in the listing details, and upload your ZIP.
- For later updates, edit the game and replace the file — same listing, same URL.
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. Once the dev portal’s Analytics Readiness inspector is available for your game, it 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.