Lookahead Ghost

A three-phase visualisation of Nesterov momentum on the parabola L(x) = (x − xOpt)². On every Step, a translucent ghost slides forward to the lookahead position, the gradient is drawn at the ghost (not at the current position), the learner pauses to compare it against the gradient that would be at the dot, and finally the real dot springs along the freshly-computed velocity.

The aha moment is the second phase. When the ghost crosses the minimum, the gradient evaluated at the ghost reverses sign — and Nesterov uses that gradient, not the present one. Overshoots get caught preemptively rather than after the fact.

Nesterov-momentum lookahead-ghost visualization.
Step 0. Position x = 9, velocity = 0.00. Stepping.

Classical momentum computes the gradient at your current position. Nesterov asks: why not check where you're about to land?

Customize
Lookahead
9.0
3.0
0.10
0.90

Installation

npx shadcn@latest add https://craftbits.dev/r/lookahead-ghost.json

Usage

import { LookaheadGhost } from "@craft-bits/viz/lookahead-ghost";
 
<LookaheadGhost />

Start the dot somewhere else and reshape the bowl:

<LookaheadGhost defaultX={11} xOpt={4} />

Push β toward overshoot territory:

<LookaheadGhost beta={0.95} learningRate={0.12} />

Subscribe to step events to drive an external chart:

<LookaheadGhost
  onStep={(history) => {
    /* read history.map((h) => h.loss) */
  }}
/>

Understanding the component

  1. The bowl. A plot renders the quadratic L(x) = (x − xOpt)² as a smooth path with grid, axes, and a dashed crosshair at xOpt marking the minimum. The starting x defaults to 9 so the early steps are dramatic.
  2. Phase 1 — ghost-appear. When the learner clicks Step, a translucent ghost dot fades in at the current position, then slides along the parabola to the lookahead curX + β · curV. A dashed connector from the real dot to the ghost makes the displacement obvious.
  3. Phase 2 — gradient-show. The gradient arrow grows out of the ghost position with a snappy spring. Two annotations appear: gradient here: (at the ghost, the one Nesterov actually uses) and gradient would be: (at the current dot, the one classical momentum would use). When the ghost has already crossed the minimum, a past minimum! warning surfaces.
  4. Phase 3 — step-move. A brief beat lets the learner read the annotations, then the ghost, line, and arrow fade out and the real dot springs to its new position. The animation is driven by motion's animate() writing raw SVG attributes so the SVG never re-renders mid-frame.
  5. Narration machine. The component derives a narration phase from the step count and whether the dot has crossed the minimum: observefirst-ghostcorrection / overshootinsight. The phase drives the narration prose, the accent stroke colour of the readouts, and the live-region copy.
  6. Trail. Every previously visited position is rendered as a small dot connected by a dashed polyline; opacity fades in from oldest to newest so the eye reads the trajectory.
  7. Idle pulse. During the observe phase only, a soft pulsing ring radiates from the dot to invite the first click. The pulse disables under prefers-reduced-motion.
  8. Reduced motion. Under prefers-reduced-motion: reduce, all three phases collapse to instant attribute updates and the pulse disables.

Props

PropTypeDefaultDescription
defaultXnumber9Initial position. Clamped to the plot domain.
xOptnumber3Location of the minimum. L(x) = (x − xOpt)².
learningRatenumber0.1Step size applied to the gradient at the ghost.
betanumber0.9Nesterov momentum coefficient. 0 collapses to vanilla GD.
ghostTransitionTransitionSPRINGS.smoothOverride the ghost slide animation.
stepTransitionTransitionSPRINGS.smoothOverride the real-dot step animation.
arrowTransitionTransitionSPRINGS.snapOverride the gradient-arrow grow animation.
onStep(history) => voidFires after each completed step with the full history.
onReset() => voidFires when the user clicks Reset.
classNamestringMerged onto the root via cn().

Accessibility

  • The plot SVG is role="img" with an aria-label summarising position, velocity, step count, learning rate, and β.
  • The Step button is the sole interactive control; it disables for the duration of the three-phase animation and re-labels per phase (Ghost appearing…, Gradient at ghost…, Stepping…).
  • A live region below the buttons announces position, velocity, and the current narration phase after each step.
  • The narration paragraph itself is aria-live="polite" and reads as plain prose — the canonical explanation per phase.
  • Phase is never encoded by colour alone: the narration prose and live-region status text also identify the phase.
  • Motion respects prefers-reduced-motion: reduce — the three animation phases collapse to instant attribute updates and the observe-phase pulse disables.

Credits

  • Extracted from: craftingattention (app/src/lessons/primitives/math/LookaheadGhost.tsx). The source consumed lesson-only chrome (SvgLabel, ChallengeBtn), depended on lesson palette tokens, and inlined a custom SPRINGS.gentle name not in the canonical motion module. The viz extract drops the lesson chrome, maps the palette onto the cb-* semantic tokens so consumer themes repaint freely, and re-keys every animation to the canonical SPRINGS.smooth / SPRINGS.snap so all motion comes from the same place as every other craft-bits component. The xOpt, learningRate, and beta are now configurable props instead of hardcoded constants.