Lesson: a rocket launch
An interactive science lesson built on real footage: NASA's slow-motion video of the Artemis I launch, on 16 November 2022. The video stops on each part of the rocket to explain it, then asks two questions, with feedback that depends on the answer.
The video stops on each part of the rocket as it lights up. Click a part to read about it again.
Video: NASA/Frank Michaux, Artemis I Slow Motion Liftoff. NASA footage is in the public domain; its use here doesn't imply NASA's endorsement.
What's in it
| Part of the lesson | How it's built |
|---|---|
| Rocket parts that light up one by one | Hotspots with staggered from times and the pulse variant. |
| A stop on each new part | The page, listening to the engine's hotspots event and pausing when a hotspot appears. |
| Both boosters as a single target | A polygon hotspot shaped like a U, around the core stage. |
| A target that climbs with the rocket | A hotspot with a keyframe per second, measured on the footage. |
| Feedback for each answer | Choices that goto short feedback scenes, whose next rejoins the lesson. |
| Fact cards, progress and score | The page, listening to iv-hotspot, iv-answer and iv-end. |
The story only describes the video. Everything under the player is the page reacting to the player's events, so the same story could feed a learning platform's own progress tracking instead.
Story
import type { HotspotKeyframe, Story } from "@ivkit/core";
import { video } from "./video";
/** Height of the top of the exhaust plume while the rocket climbs, as [second, y]. */
const climb: [number, number][] = [
[3, 0.55],
[4, 0.533],
[5, 0.496],
[6, 0.452],
[7, 0.4],
[8, 0.348],
[9, 0.296],
[10, 0.237],
[11, 0.178],
[12, 0.115],
[13, 0.048],
];
// A box around the nozzles, which sit at the top of the plume. It stops at the
// top edge, where areas must start.
const engines: HotspotKeyframe[] = climb.map(([at, y]) => ({
at,
area: { x: 0.425, y: Math.max(0, y - 0.05), width: 0.12, height: 0.12 },
}));
export const launch: Story = {
version: "0.1",
start: "pad",
scenes: {
pad: {
src: video("launch-pad"),
events: [
{
type: "hotspot",
id: "orion",
from: 0.5,
label: "Orion spacecraft",
variant: "pulse",
area: { x: 0.47, y: 0.05, width: 0.06, height: 0.24 },
},
{
type: "hotspot",
id: "core-stage",
from: 2.5,
label: "Core stage",
variant: "pulse",
area: { x: 0.479, y: 0.31, width: 0.042, height: 0.55 },
},
{
type: "hotspot",
id: "boosters",
from: 4.5,
label: "Solid rocket boosters",
variant: "pulse",
shape: "polygon",
// Both boosters as one U, open at the top for the core stage between them.
points: [
[0.452, 0.43],
[0.479, 0.43],
[0.479, 0.86],
[0.521, 0.86],
[0.521, 0.43],
[0.538, 0.43],
[0.538, 0.87],
[0.452, 0.87],
],
},
{
type: "hotspot",
id: "launcher",
from: 6.5,
label: "Mobile launcher",
variant: "pulse",
area: { x: 0.4, y: 0.1, width: 0.048, height: 0.76 },
},
{
type: "question",
id: "ignition",
at: 9,
text: "The four engines under the core stage are starting up. When do the two boosters light?",
options: [
{ id: "now", label: "Right now, with the engines", goto: "boosters-wrong" },
{ id: "liftoff", label: "A few seconds later, at liftoff", goto: "boosters-right" },
{ id: "air", label: "Once the rocket is in the air", goto: "boosters-wrong" },
],
},
],
},
"boosters-right": { src: video("launch-boosters-right"), next: "liftoff" },
"boosters-wrong": { src: video("launch-boosters-wrong"), next: "liftoff" },
liftoff: {
src: video("launch-liftoff"),
events: [
{
type: "hotspot",
id: "engines",
from: 3,
to: 13.4,
label: "Engines at full thrust",
variant: "pulse",
keyframes: engines,
},
{
type: "question",
id: "crew",
at: 14.5,
text: "Orion is on its way to the Moon. Who is on board?",
options: [
{ id: "four", label: "Four astronauts", goto: "crew-wrong" },
{ id: "nobody", label: "No one: it's a test flight", goto: "crew-right" },
{ id: "one", label: "One pilot", goto: "crew-wrong" },
],
},
],
},
"crew-right": { src: video("launch-crew-right") },
"crew-wrong": { src: video("launch-crew-wrong") },
},
};
/** What the page shows next to the player: a fact per hotspot, and the right answers. */
export const launchLesson = {
facts: {
orion: {
title: "Orion",
text: "The capsule built to carry four astronauts. The tower on top is the launch abort system, a small rocket that can pull Orion away from SLS in an emergency.",
},
"core-stage": {
title: "Core stage",
text: "The orange backbone of the rocket, 65 m (212 ft) tall. It holds about 2.7 million liters of liquid hydrogen and oxygen for the four RS-25 engines at its base.",
},
boosters: {
title: "Boosters",
text: "Two solid rocket boosters, 54 m (177 ft) tall each. They give more than 75% of the thrust at liftoff, and drop away after about two minutes.",
},
launcher: {
title: "Mobile launcher",
text: "The tower that carried the rocket to the pad and fed it propellant, power and air until the moment of liftoff.",
},
engines: {
title: "Full thrust",
text: "Together, the engines and boosters push with 8.8 million pounds (39 MN) of thrust: more than any rocket NASA had flown before.",
},
},
answers: { ignition: "liftoff", crew: "nobody" },
};Reacting to the lesson
Stop on each part as it appears, so it stays in view while the viewer reads about it. <ivkit-player> has no event for hotspots appearing, but its engine reports every change to the hotspots on screen. Subscribe after setting the story, since setting it creates a new engine:
import { launch, launchLesson } from "./launch.js";
const player = document.querySelector("ivkit-player");
const answered = new Set();
let shown = new Set();
let score = 0;
function explain(id) {
const fact = launchLesson.facts[id];
if (!fact) return;
player.pause();
showCard(fact.title, fact.text, () => player.play());
}
player.story = launch;
player.engine.on("hotspots", ({ hotspots }) => {
const appeared = hotspots.find((hotspot) => !shown.has(hotspot.id));
for (const hotspot of hotspots) shown.add(hotspot.id);
if (appeared) explain(appeared.id);
});
// Each visit to a scene shows its parts again.
player.addEventListener("iv-scene", () => {
shown = new Set();
});
// Clicking a part explains it again.
player.addEventListener("iv-hotspot", (event) => explain(event.detail.hotspot.id));
player.addEventListener("iv-answer", (event) => {
const { question, choice } = event.detail;
if (answered.has(question.id)) return;
answered.add(question.id);
if (launchLesson.answers[question.id] === choice.id) score++;
});
player.addEventListener("iv-end", () => {
reportCompletion({ score, total: Object.keys(launchLesson.answers).length });
});