Emission Profile
The edu.games xAPI Emission Profile v1 defines the statement shapes your game should emit to power the platform’s Learning Intelligence visualisations. Games that follow more of this profile unlock more presets for their buyers — the conformance level is shown on your game’s listing as a sales incentive, not a gate to publish. You can publish a game with zero xAPI traffic; following this profile just makes it more attractive to teachers shopping for data-backed games.
Levels
Each level is additive — Timed, Choice, Confidence, and Full all assume Core is already in place. The higher the level, the more preset visualisations light up on the game’s insights dashboard.
| Level | Requirements | Unlocks |
|---|---|---|
| Core (required to list analytics at all) | actor echo, launched/attempted/passed/failed verbs, object IRIs per objective | P02 P03 P06* P07 P08 P11–P13 P15 P16 P18–P20, radar/mastery (AI infers from core verbs) |
| Timed | + result.duration per attempt | P05, P17 (accurate) |
| Choice | + decision statements | P04, P14 |
| Confidence | + confidence extension | P10 |
| Full | all of the above + state + hints | everything, "Full Learning Intelligence" badge |
* P06 needs hint/retry verbs — part of Full, degraded otherwise.
1. Actor Echo (Core)
The player delivers a launch context to your game — query params for HTML games, window.EDUGAMES_LAUNCH for embedded games, or a { type: 'edugames:launch' } postMessage for iframe games:
{
"actor": { "objectType": "Agent",
"account": { "homePage": "https://edu.games", "name": "<opaque-student-id>" } },
"registration": "<uuid>",
"endpoint": "https://lrs.edu.games/xapi",
"auth": "Bearer <launch-token>"
}Hard rules:
- Games MUST use this actor and registration verbatim in every statement (
context.registration). - Never invent actors; never put student names or emails in statements. Anonymous play (no actor delivered) uses an
account.nameprefixedanon:so it can never collide with real student ids. - Tag every statement with the profile version in
context.contextActivities.category:
{ "id": "https://edu.games/xapi/profiles/emission/v1",
"definition": { "type": "http://adlnet.gov/expapi/activities/profile" } }1a. Launch-Channel Security (mandatory)
The postMessage channel is spoofable by other frames — a hostile frame could otherwise redirect student data to an attacker’s endpoint. Your game MUST:
- accept
edugames:launchonly whenevent.source === window.parent; - treat the first launch context as final — ignore later launch messages;
- never let a postMessage override an
endpoint/authalready set by the injected globals (messages may only fill in missing actor/registration); - POST only to the endpoint obtained from the launch context — never one taken from any other message or parameter;
- allowlist postMessage endpoints: a hostile page embedding your game is itself
window.parent, so an endpoint received via postMessage must behttps:with hostedu.games, ending in.edu.games, oredugames.dev(the developer sandbox). A message with a non-allowlisted endpoint is rejected in its entirety — take nothing from it, including actor and registration (an embedder that injects an evil endpoint can’t be trusted for identity either); keep waiting for a valid launch or fall back to anonymous. Endpoints from the injected globals are exempt (only the real player can inject those).
Player guarantee: the edu.games player injects the globals into <head> before any game script runs. Treat the globals as the primary channel and postMessage as a fallback for other embedders — inside the player, the wait-for-postMessage path never executes.
1b. Delivery & Attribution (mandatory)
- The player’s game frame (served by
/api/play-frame, sandboxed withoutallow-same-origin) has an opaque origin — storage APIs throw. Queue statements in memory; flush every 5–10 s and onpagehide/visibilitychange: hiddenwithfetch(..., { keepalive: true }), capping the final batch at ~20 statements / under 60 KB (browsers cap keepalive bodies at ~64 KB). - Never re-attribute statements. Queued or persisted statements keep the actor and registration they were created with — re-stamping a backlog with a newly arrived launch context misattributes data on shared classroom devices. Discard persisted statements with no actor.
- Late launch context: hold statements (with true timestamps) until the launch resolves — injected globals immediately, else wait ~3 s for the postMessage, else anonymous fallback. Never switch actors mid-session. (Inside the player the globals are guaranteed, so this path is fallback-only.)
- Cross-load persistence is not expected — the sandbox makes it impossible, and the conformance checker does not penalize its absence. In-memory queue + regular flushes is full compliance.
- Anonymous identity is per-load: a fresh
anon:<uuid>actor per page load is expected and compliant. Do not build your own visitor tracking to make anon ids stable — if per-visitor anonymous aggregation is ever needed, the player will inject a stable visitor token (planned). - Anonymous registration: anonymous sessions MUST carry a game-generated UUID v4 in
context.registration, one per session. The conformance checker treats a non-platform registration as valid when the actor isanon:-prefixed. - LRS retry guarantee: lrs.edu.games dedupes by statement id — resubmitting a batch containing already-stored ids succeeds (no batch-level conflict), so retry the identical batch, ids included, and your retry loop is guaranteed to drain.
2. Verbs (Core)
Use these exact IRIs:
| Meaning | IRI |
|---|---|
| session start | http://adlnet.gov/expapi/verbs/launched |
| attempt start | http://adlnet.gov/expapi/verbs/attempted |
| objective success | http://adlnet.gov/expapi/verbs/passed |
| objective failure | http://adlnet.gov/expapi/verbs/failed |
| content exposure | http://adlnet.gov/expapi/verbs/experienced |
| completion | http://adlnet.gov/expapi/verbs/completed |
| mastery | https://w3id.org/xapi/dod-isd/verbs/mastered |
| hint requested | https://edu.games/xapi/verbs/asked-for-hint |
| retry | https://edu.games/xapi/verbs/retried |
| decision made | https://edu.games/xapi/verbs/chose |
| session/attempt abandoned | http://adlnet.gov/expapi/verbs/terminated |
| non-learning telemetry | https://edu.games/xapi/verbs/logged |
Abandonment: quitting mid-game is terminated — never completed with success: false (that reads as a failed completion). Emit it from your final-flush handler when the session ends before completion.
Session semantics: one session = one launch context. Emit exactly one launched per load and keep one registration throughout, even across “Play Again” replays. Each play is bracketed by its first attempted and ends with completed (finished) or terminated (abandoned). Never re-emit launched or mint a new registration per play. A load that never starts a play (title-screen bounce) emits no terminated — launched with nothing after it is the bounce signal.
3. Objects / Objectives (Core)
Every learning objective is an Activity with a stable IRI:
https://edu.games/games/<game-slug>/objectives/<objective-slug>with definition.name and definition.type = http://adlnet.gov/expapi/activities/objective. These IRIs are exactly what you map to competencies in the dev portal, so keep them stable once you ship them.
3a. Non-Learning Telemetry (side channel)
Gameplay signals that are neither objectives nor content — hazard collisions, power-ups, deaths — use verb https://edu.games/xapi/verbs/logged with objects under the reserved namespace https://edu.games/games/<game-slug>/events/<event-slug>. These are stored but ignored by all presets, so log freely. Do not use experienced for telemetry — it is reserved for genuine content exposure (screens, tutorials, media), and telemetry sent under it pollutes exposure analytics.
4. Results
result.success(boolean) on passed/failed.result.score.scaled(−1..1) where scoring exists. Semantics: proportion of attainable performance on the attempt (fraction correct, orraw/maxwith a defined per-objective max, clamped). Never raw points, and never collapse to 1/0 when a finer proportion exists — this number is compared across games. When no finer proportion exists, 1/0 is correct: a single multiple-choice question is simply1.0right /0.0wrong — do not invent granularity (speed bonuses, streaks) to look continuous.- Timed:
result.durationas an ISO8601 duration (e.g.PT47S) per attempt — active time only (pause the timer while paused or hidden). - Confidence:
result.extensions["https://edu.games/xapi/ext/confidence"]— a number 0..1, captured from the learner before revealing correctness. Only for games with a natural pre-reveal moment; real-time/action games skip this tier rather than inserting a dialog. - Retry semantics:
retriedfires when a new attempt starts at an objective already failed this session (any later attempt), alongside that attempt’sattemptedstatement — never instead of it, never across sessions. - Mastery default: 3 consecutive passes at an objective, unless the game has an explicit built-in mastery mechanic.
5. Decision Statements (Choice)
Emit one statement per branch point, using the chose verb:
{ "verb": { "id": "https://edu.games/xapi/verbs/chose" },
"object": { "id": "https://edu.games/games/<slug>/decisions/<point>/<choice>",
"definition": { "type": "http://adlnet.gov/expapi/activities/choice" } },
"context": { "registration": "<launch registration>",
"extensions": {
"https://edu.games/xapi/ext/decision-point": "<point-slug>",
"https://edu.games/xapi/ext/sequence-index": 4,
"https://edu.games/xapi/ext/parent-point": "<previous-point-slug|null>"
} } }The chosen option is extracted from the last path segment of the object IRI, so point and choice slugs must match [a-z0-9-]+ (a / in a slug corrupts extraction). sequence-index is 0-based per session.
6. State Statements (Full)
On objective state change, emit the completed/mastered verbs, or set context.extensions["https://edu.games/xapi/ext/state"] to one of not_started, in_progress, completed, mastered.
7. Checking Your Conformance
Once your game has an edu.games credential, head to dev.edu.games → your game → Analytics Readiness to check how your emitted statements measure up against this profile:
- A live statement inspector — the last 50 statements from your game’s credential, parsed against this profile with per-statement pass/warn/fail annotations (e.g. “actor is not the platform actor”, “duration missing on passed”).
- Capability flags as observed nightly, each shown with the preset list it unlocks, greyed out until data arrives and lit once it does.
- A test-harness page that launches your game with a sandbox credential and a fake student, so you can watch statements arrive in real time before you publish.
Presets check saved_visualizations.requires ⊆ game_capabilities. If your game hasn’t declared a capability, the corresponding chart renders greyed out with “Requires decision tracking — this game hasn’t declared it. See Emission Profile.” instead of an empty chart.
Amendments — 27 July 2026
The profile was amended after the first full game instrumentation surfaced gaps in v1 as originally published. If you integrated before this date, review the following — all are live in the platform now:
- Security (§1a, new): launch messages accepted only from
window.parent, first launch wins, and postMessage-delivered endpoints must be onedu.games/*.edu.games/edugames.dev— a message with a non-allowlisted endpoint is rejected in its entirety, identity included. - Delivery & attribution (§1b, new): in-memory queueing (the sandbox has no storage), capped keepalive final flush, never re-attribute queued statements, anonymous
anon:actors with game-minted UUID registrations, and a documented LRS dedupe guarantee for retries. - Profile version (§1): every statement now carries
https://edu.games/xapi/profiles/emission/v1incontext.contextActivities.category. - New verbs (§2):
terminatedfor abandonment (its own analytics class — never sendcompletedwithsuccess: falsefor a quit) andloggedfor non-learning telemetry under the/events/namespace (§3a). - Session semantics (§2): one
launchedand one registration per launch context, plays bracketed byattempted→completed/terminated; a title-screen bounce emits noterminated. - Comparability (§4):
score.scaleddefined as proportion-of-attainable (with the single-question 1/0 case spelled out),durationis active time, retry and mastery semantics pinned, confidence scoped to games with a natural pre-reveal moment. - Decision slugs (§5): constrained to
[a-z0-9-]+;sequence-indexis 0-based.