All skills
dmccreary avatar

/add-xapi-events-to-microsim

@d493f4f

Use when the user wants an existing MicroSim or chapter quiz to record what students do with it. Typical requests are "add xAPI events", "add tracking or analytics", "make this sim feed the LRS or LRS-Lite", "instrument chapter 5's sims", or "what should this sim record?". Use it even if they never say "xAPI". Not for building a new MicroSim (use microsim-generator) or for LRS backend work.

  • 27 files
  • 234.6 KB
  • Updated 5 days ago
  • GitHub

Use this Skill: https://skilld.dev/gh/dmccreary/claude-skills/add-xapi-events-to-microsim

This session only. Nothing lands on disk.

referencesadaptersp5-canvas.md

≈2.8k tokens on demand. Your agent reads this file only when SKILL.md points to it.

Adapter: p5.js canvas interactions (hit-tested clicks and drags)

Status: PARTLY VERIFIED.

  • Canvas click-to-pick and predict → check: VERIFIED. Proven by learning-record-store/docs/sims/chaos-kill-test-simulator (commit a6c0062; check-xapi passes, with state and pixels identical before and after instrumenting). That run's corrections are below.
  • Click-to-inspect with Next/Previous/arrow-key stepping, and a canvas-drawn trail or answer buttons: VERIFIED. Proven by seven sims in eight-hour-entrepreneur (commit e2c3b94): competitive-landscape-map, costarters-canvas-explorer, customer-definition-funnel, four-phase-founder-pipeline, idea-trap-cycle, two-minute-pitch-structure-timer and symptom-root-cause-drilldown. That batch's corrections are the "Stepping", "Keyboard" and "Hover on drawn targets" sections below.
  • Hover on canvas-drawn targets: PILOTED in the same batch (idea-trap-cycle's arrows).
  • Drags remain UNVERIFIED. Their pilot is 3d-printing-course/docs/sims/ideation-sketch-canvas (mouseDragged hit-testing).

About 1,750 of the ~3,580 p5 sims use canvas mousePressed/mouseDragged, so this adapter matters. Verify each claim against the sim, and report corrections.

Applies when: the sketch defines global mousePressed(), mouseReleased(), mouseDragged(), mouseClicked(), doubleClicked(), keyPressed() or touchStarted(), and decides what was hit by comparing mouseX/mouseY with drawn shapes. It also applies to canvas-drawn "buttons" (a rect + text + a hit-test). DOM controls in the same sketch follow p5-dom-controls.md.

Find the hit-test, not the event

The global mousePressed() fires for every click, anywhere on the page. That includes clicks on DOM controls and clicks inside the xAPI panel below the canvas. Don't report from the top of mousePressed(). Report from inside the branch where the sim decided something was hit:

function mousePressed() {
  preds.forEach(function (p, i) {
    if (mouseIsInside(x, py, w, 34)) {
      prediction = p;
      if (lrs) predictionEv.study('select');     // inside the hit branch, never above it
    }
  });
}

Report only when the pick changes something. A re-click of the current choice is not new evidence.

Reporting inside the sim's own hit branch tracks the sim's state exactly, and it was enough in the pilot. There, the hit area (y 200–234) can't be reached from the DOM controls or the panel below the canvas. If a drawn target can overlap DOM controls or the panel in canvas coordinates, also check that the event target is the canvas (event && event.target && event.target.tagName === 'CANVAS'). A click on the panel's Simulate Done button reports mouseX/mouseY relative to the canvas.

Classes for canvas acts

Canvas act Class Notes
Click a drawn button that performs an action 3a button.press(action) key the button by what it does
Click a drawn choice (pick a prediction, choose a mode) 2 item.study('select') for the pick the pick is not yet an answer
The choice is checked against a right answer 5 question.answer() emit at the moment of checking, with response = the choice
Drag a handle that sets a value (a point on a curve, a vector's head) 1 slider on the value the drag controls one handle per draggable, feed every mouseDragged value, settle() on mouseReleased
Free drawing / free positioning with no single value 2, once per drag report on mouseReleased, mode: 'drag', ms = drag duration. A drag is one continuous move, never one statement per frame.
Click on empty canvas not evidence

Predict → check (a common canvas pattern)

For example, the chaos-kill-test simulator: pick a case (a service), pick a prediction, press Kill, and see whether the prediction was right. That's one class-5 answer, emitted at Kill:

// Key by WHAT is being predicted (the case run): 'q-kafka'. The concept is the one the
// question TESTS; this book has a failure-mode concept per service (334 for Kafka), and
// the page concept (353) belongs to the page and its generic controls.
const FAILURE_CONCEPT = { Kafka: 334, ClickHouse: 335, Neo4j: 336, Summarizer: 337, Redis: 340 };
questions[svc] = questions[svc] || lrs.question('q-' + LRS.slug(svc), {
  name: 'Predict the effect of killing ' + svc,
  concept: FAILURE_CONCEPT[svc] ? LRS.conceptId(FAILURE_CONCEPT[svc]) : undefined });
const key = svc + '|' + prediction;
if (key !== lastChecked) {                  // a double-click / Restore→Kill re-check is not a new attempt
  lastChecked = key;
  questions[svc].answer({ success: prediction === EFFECT[svc].answer,
                          response: LRS.slug(prediction), extensions: { 'attempt-number': ++attempts[svc] } });
}
// lastChecked resets when a new round starts. Follow the SIM's state machine: here choosing
// the service clears the prediction, so choosing the service starts the round (and Reset ends it).
  • The pick itself can additionally be a class-2 item (#prediction, #service-select). The pilot recorded both. In Compact they cost only a count. Don't emit an answer on the pick: the check is the evidence.
  • Check the answer key against the chapter before shipping. In the pilot, the Identity key contradicted chapter 19, so correct students would score success: false. Both eval runs caught it. Report such a key; don't silently fix or silently ship it.

Stepping: one selection, one statement (verified)

Many explorers let the student reach the same objects three ways: click a drawn block, press Next/Previous, or press an arrow key. All three are the same act (bringing an object into the detail panel), so report one class-2 inspection of the newly shown object, and let engagement-mode record the path: 'click', 'step' (a Next/Previous button) or 'keyboard'. Don't also press a #next-button; that counts one selection twice. The eight-hour-entrepreneur batch used this model in six sims. A press-only model would give a student who only ever pressed Next no per-object evidence at all.

  • Report only when the selection changes. Re-clicking the selected block, or pressing Next at the last step, is not new evidence.
  • The selection the sim makes on load (a first item pre-selected) is not a student act. Nor is the program moving on by itself: a timer crossing into the next section, or Start jumping to the section the clock is in.
  • Route it through one function: give the sim's select function an optional how argument and report inside it (selectBlock(i, how), emitting only when how is set). The load-time call passes nothing, so it emits nothing.
  • A function registered with btn.mousePressed(fn) receives the MouseEvent as its first argument. If you add an optional mode parameter to nextBlock(mode), a button press passes the event, not undefined. Normalize it: const how = typeof mode === 'string' ? mode : 'step';.

Keyboard: guard keyPressed against the panel (verified)

The global keyPressed() fires for keys pressed anywhere in the page, including inside the teaching panel. The panel's Full/Compact radios take arrow keys, so a sim that steps on arrow keys also steps when the student changes the xAPI mode. In p5 1.11 the global keyPressed receives the KeyboardEvent, so guard it at the top:

function keyPressed(e) {
  if (e && e.target && e.target.closest && e.target.closest('.xapi-panel')) return;   // the panel's own keys
  ...the sim's existing body...
}

The same applies to keyReleased/keyTyped and to a DOM document.addEventListener('keydown', …). In production the panel doesn't exist, so the guard never fires there.

Hover on canvas-drawn targets (piloted)

When the design reveals something on hover (idea-trap-cycle: "hover over (or tap) an arrow"), track the hover per visit, gated by LRSSim.HOVER_MS, as for DOM hovers. A canvas has one extra trap. p5 keeps mouseX/mouseY after the pointer leaves the canvas, so a hover key computed in draw() never "leaves": the target stays hovered, the dwell grows forever, and the sim's own tooltip even stays drawn. End the visit on the canvas's own mouseleave as well:

canvas.elt.addEventListener('mouseleave', endHoverVisit);   // NOT canvas.mouseOut(fn): p5 hooks replace
  • Compute the hovered target in draw() (or mouseMoved()) with the sim's own hit-test; when it changes, end the old visit (emit study('hover', ms) if ms ≥ HOVER_MS) and start a new one.
  • A click or tap on the same target during the visit reports study('click', msSoFar) at once and suppresses that visit's hover. A second click in the same visit emits nothing.
  • A quick sweep across several targets must emit 0 statements. Test it.

Continuous drags

mouseDragged() fires once per frame (about 60/s). Feed the value to a slider handle, whose deadband makes it about 60 statements per full-range sweep. There is no natural quantum for a 2-D position, so pick the value the drag means (an angle, a radius, a coefficient), and set min/max from its legal range. If the drag means two values, use two handles. When nothing meaningful is being set (free sketching), emit one item.study('drag', ms) per drag, on release.

Traps

  • mousePressed() also fires for touches on mobile (p5 maps touch to mouse). That's fine; it's the same act.
  • Hit-test coordinates depend on canvasWidth from updateCanvasSize(). Test at several widths (check-xapi drives at 900 px).
  • A sketch without redraw guards may re-run the hit-test every frame via mouseIsPressed in draw(). If so, report on the transition (a wasPressed flag), not per frame.

check-xapi actions

Canvas clicks need coordinates, taken relative to the canvas's top-left. Compute them the way the sketch does. It's often easiest to evaluate the sketch's own geometry:

[
  {"click_at": [100, 215], "on": "canvas"},
  {"select": "select", "option": "Kafka"},
  {"click": "button", "text": "Kill"},
  {"drag": [[120, 120], [260, 180]], "on": "canvas", "steps": 12}
]

click_at also accepts a JS expression string that returns [x, y], evaluated in the frame, e.g. "[12 + ((width - 24) / 3) * 1.5, 217]" for the middle of the second of three buttons.

click_at moves and clicks in one step, so on its own it tests only the tap path. To test "hover, then click in the same visit", hover without leaving and click the same point:

[
  {"hover_at": "[loopGeom.cx, loopGeom.cy - loopGeom.r]", "on": "canvas", "ms": 700, "leave": false},
  {"click_at": "[loopGeom.cx, loopGeom.cy - loopGeom.r]", "on": "canvas"}
]

That should give one click statement with the dwell so far, and no hover (verified on idea-trap-cycle: one click, PT0.72S). A key action goes to whatever has focus, so it reaches the sim's keyPressed only after a click/click_at inside the sim's frame.

Source: SKILL.md on GitHub

No third-party reports yet.

Signed by skilld at d493f4f. This ties the file your Agent reads to that commit on GitHub. It does not review the instructions.

Last checked against GitHub 19 hours ago.

Activeupdated 5 days ago
metadata
{
  "ibook.version": "0.2",
  "ibook.preferred-model": "opus"
}

README badge

README badge for dmccreary/claude-skills/add-xapi-events-to-microsim