{"job_id":"anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d","request_id":"bulk-2026-05-20T10-21-44-856Z-7-physics.09.work-energy.work-energy-theorem","status":"complete","asset":{"primary_url":"https://storage.googleapis.com/pupiltree-animation-assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d/walkthrough.mp4","thumbnail_url":"https://storage.googleapis.com/pupiltree-animation-assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d/thumb.jpg","transcript_url":"https://storage.googleapis.com/pupiltree-animation-assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d/transcript.vtt","interactive_url":"https://storage.googleapis.com/pupiltree-animation-assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d/index.html","scenefile_url":"https://storage.googleapis.com/pupiltree-animation-assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d/scene-source.json","duration_seconds":15,"byte_size":472663,"renderer":"html_three_js_local","parameters":{"style":"auto","executor":"native_render_executor","localPath":"/app/storage/assets/anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d","interactivity":"none","renderer_style":"three_js","durationSeconds":15,"render_manifest":{"jobId":"anim-job-d7421941-a5dc-4359-b046-e71ea9d0d36d","request":{"gap":{"topic":"work_energy-work_energy_theorem","severity":"moderate","error_type":"concept_misunderstanding","memory_state":"fragile","display_topic":"Work-Energy Theorem","common_wrong_answer":"The concept works the same way in all conditions.","confidence_at_error":"high","correct_understanding":"The concept depends on constraints, assumptions, and variable relationships."},"target":{"style":"auto","render":{"fps":30,"format":"mp4","resolution":"1920x1080","include_thumbnail":true,"include_transcript":true},"audience":{"tone":"neutral_instructional","grade":"9","language":"en"},"interactivity":"none","duration_seconds":15},"context":{"chapter":{"name":"Work & Energy","ncert_class":9},"sub_topics":[{"topic":"work_energy-work_energy_theorem","key_concepts":["Work-Energy Theorem","Work & Energy"]}]},"metadata":{"priority":"normal","trace_id":"bulk-run-2026-05-20T10-21-44-856Z"},"exam_type":"neet","asset_type":"simulation","request_id":"bulk-2026-05-20T10-21-44-856Z-7-physics.09.work-energy.work-energy-theorem","subject_area":"physics"},"renderer":"html_three_js_local","storyboard":{"beats":[{"label":"Topic","visual":"Show the topic title and the key physical context.","narration":"Work-Energy Theorem"},{"label":"Mistake","visual":"Show the wrong approach and why it seems plausible.","narration":"The concept works the same way in all conditions."},{"label":"Correction","visual":"Show the right approach step by step.","narration":"The concept depends on constraints, assumptions, and variable relationships."},{"label":"Concept","visual":"Highlight the key formula and the governing relationship.","narration":"Work-Energy Theorem. Work & Energy"}],"title":"Work-Energy Theorem","sceneCode":"<!doctype html>\n<html lang=\"en\">\n<head>\n  <meta charset=\"utf-8\"/>\n  <meta name=\"viewport\" content=\"width=device-width, initial-scale=1\"/>\n  <title>Work-Energy Theorem: Context Matters</title>\n  <style>\n    :root{--bg:#0e121a;--panel:#1c232e;--line:#303449;--accent:#fc884f;--text:#faf9f8;--muted:#9f9ea1;--blue:#627da5;--green:#8decb0;--red:#df8279;}\n    *{box-sizing:border-box;}\n    body{margin:0;font-family:\"Plus Jakarta Sans\",\"Manrope\",\"Inter\",ui-sans-serif,system-ui,sans-serif;background:radial-gradient(120% 90% at 20% -5%,#1c232e 0%,var(--bg) 55%);color:var(--text);}\n    main{min-height:100vh;display:grid;grid-template-columns:minmax(300px,380px) 1fr;}\n    aside{border-right:1px solid var(--line);background:linear-gradient(180deg,rgba(28,35,46,.86),rgba(14,18,26,.94));padding:24px;overflow:auto;backdrop-filter:blur(6px);}\n    section{padding:24px;display:grid;place-items:center;}\n    h1{margin:0 0 10px;font-size:1.65rem;line-height:1.15;}\n    .sub{margin:0 0 16px;color:var(--muted);}\n    .formula{margin:0 0 14px;padding:10px;border:1px solid var(--line);border-radius:10px;background:#11191e;color:var(--accent);font-family:ui-monospace,monospace;font-size:0.9rem;box-shadow:inset 0 0 28px rgba(229,186,103,.08);}\n    .control{display:grid;gap:6px;margin:12px 0;}\n    .control input{width:100%;accent-color:var(--accent);}\n    ul{margin:14px 0 0 18px;padding:0;color:var(--muted);display:grid;gap:6px;}\n    .stage{width:min(1020px,95%);aspect-ratio:16/9;border:1px solid #445862;border-radius:14px;position:relative;overflow:hidden;background:radial-gradient(circle at 50% 38%,#2f4a57,#0f1418 72%);box-shadow:0 18px 60px rgba(0,0,0,.45), inset 0 0 90px rgba(255,255,255,.03);}\n    .stage::before{content:\"\";position:absolute;inset:0;background:linear-gradient(180deg,rgba(255,255,255,.06),transparent 34%);pointer-events:none;}\n    .legend{position:absolute;right:12px;top:12px;padding:8px 10px;border-radius:10px;border:1px solid #465a64;background:rgba(9,14,18,.58);font-size:0.8rem;color:var(--muted);backdrop-filter:blur(4px);}\n    @media(max-width:900px){main{grid-template-columns:1fr;}aside{border-right:0;border-bottom:1px solid var(--line);}}\n  </style>\n</head>\n<body>\n<main>\n  <aside>\n    <h1>Work-Energy Theorem</h1>\n    <p class=\"sub\">Context changes how it applies</p>\n    <p class=\"formula\">W = ΔKE = ½m(v²_f - v²_i)</p>\n    <label class=\"control\"><span>Mass (kg): <strong id=\"v-mass\">2</strong></span><input id=\"c-mass\" type=\"range\" min=\"0.5\" max=\"5\" step=\"0.5\" value=\"2\"/></label>\n    <label class=\"control\"><span>Applied Force (N): <strong id=\"v-force\">8</strong></span><input id=\"c-force\" type=\"range\" min=\"1\" max=\"20\" step=\"1\" value=\"8\"/></label>\n    <label class=\"control\"><span>Friction Coeff: <strong id=\"v-friction\">0</strong></span><input id=\"c-friction\" type=\"range\" min=\"0\" max=\"0.8\" step=\"0.1\" value=\"0\"/></label>\n    <label class=\"control\"><span>Scenario: <strong id=\"v-scenario\">Frictionless</strong></span><input id=\"c-scenario\" type=\"range\" min=\"0\" max=\"2\" step=\"1\" value=\"0\"/></label>\n    <ul>\n      <li>⚠ Misconception: \"Same force always yields same KE gain\"</li>\n      <li>✓ Reality: Friction, constraints change NET work done</li>\n      <li>✓ Watch: Same F, different scenarios → different ΔKE</li>\n      <li>Key: W = F·d applies to NET force, not just applied force</li>\n    </ul>\n  </aside>\n  <section>\n    <div class=\"stage\" id=\"stage\">\n      <div class=\"legend\" id=\"legend\">Ready</div>\n      <canvas id=\"sim-cv\" style=\"position:absolute;inset:0;width:100%;height:100%;\"></canvas>\n    </div>\n  </section>\n</main>\n<script>\nconst controls = [\n  {\"key\":\"mass\",\"label\":\"Mass\",\"min\":0.5,\"max\":5,\"step\":0.5,\"value\":2,\"units\":\"kg\"},\n  {\"key\":\"force\",\"label\":\"Applied Force\",\"min\":1,\"max\":20,\"step\":1,\"value\":8,\"units\":\"N\"},\n  {\"key\":\"friction\",\"label\":\"Friction Coeff\",\"min\":0,\"max\":0.8,\"step\":0.1,\"value\":0,\"units\":\"μ\"},\n  {\"key\":\"scenario\",\"label\":\"Scenario\",\"min\":0,\"max\":2,\"step\":1,\"value\":0,\"units\":\"\"}\n];\nfor(const c of controls){\n  const inp=document.getElementById('c-'+c.key);\n  const out=document.getElementById('v-'+c.key);\n  inp?.addEventListener('input',()=>{\n    if(c.key==='scenario'){\n      const scenarios=['Frictionless','With Friction','Against Friction'];\n      out.textContent=scenarios[parseInt(inp.value)];\n    }else{\n      out.textContent=inp.value;\n    }\n    window.updateModel?.();\n  });\n}\n(function(){\n  const cv=document.getElementById('sim-cv');\n  const ctx=cv.getContext('2d');\n  function resize(){const r=cv.getBoundingClientRect();if(r.width>0){cv.width=r.width;cv.height=r.height;}}\n  resize();new ResizeObserver(resize).observe(cv);\n  \n  // State variables\n  let simState = {\n    pos: 0,\n    vel: 0,\n    time: 0,\n    workApplied: 0,\n    workFriction: 0,\n    workNet: 0,\n    keInitial: 0,\n    keFinal: 0,\n    phase: 'intro', // intro, running, result\n    phaseTime: 0,\n    trailPoints: [],\n    vHistory: [],\n    energyHistory: []\n  };\n  \n  const maxDist = 400; // pixels to move\n  const historyLen = 120;\n  \n  let lastTs = 0;\n  \n  function glowDot(x, y, r, color, a) {\n    const g = ctx.createRadialGradient(x, y, 0, x, y, r * 2.6);\n    g.addColorStop(0, color.replace('ALPHA', String(a)));\n    g.addColorStop(1, color.replace('ALPHA', '0'));\n    ctx.fillStyle = g;\n    ctx.beginPath();\n    ctx.arc(x, y, r * 2.6, 0, Math.PI * 2);\n    ctx.fill();\n  }\n  \n  function arrow(x1, y1, x2, y2, color, width = 2) {\n    const dx = x2 - x1, dy = y2 - y1, l = Math.sqrt(dx * dx + dy * dy);\n    if (l < 2) return;\n    const nx = dx / l, ny = dy / l, s = 10;\n    ctx.strokeStyle = color;\n    ctx.lineWidth = width;\n    ctx.beginPath();\n    ctx.moveTo(x1, y1);\n    ctx.lineTo(x2, y2);\n    ctx.stroke();\n    ctx.fillStyle = color;\n    ctx.beginPath();\n    ctx.moveTo(x2, y2);\n    ctx.lineTo(x2 - s * nx + s * 0.4 * ny, y2 - s * ny - s * 0.4 * nx);\n    ctx.lineTo(x2 - s * nx - s * 0.4 * ny, y2 - s * ny + s * 0.4 * nx);\n    ctx.fill();\n  }\n  \n  function drawMisconceptionIcon(x, y, size) {\n    // Draw warning triangle\n    ctx.fillStyle = '#df8279';\n    ctx.beginPath();\n    ctx.moveTo(x, y - size);\n    ctx.lineTo(x + size, y + size);\n    ctx.lineTo(x - size, y + size);\n    ctx.closePath();\n    ctx.fill();\n    ctx.fillStyle = '#0e121a';\n    ctx.font = 'bold ' + Math.round(size * 1.2) + 'px sans-serif';\n    ctx.textAlign = 'center';\n    ctx.textBaseline = 'middle';\n    ctx.fillText('!', x, y + size * 0.1);\n  }\n  \n  function drawCorrectionIcon(x, y, size) {\n    // Draw checkmark circle\n    ctx.strokeStyle = '#8decb0';\n    ctx.lineWidth = 3;\n    ctx.beginPath();\n    ctx.arc(x, y, size, 0, Math.PI * 2);\n    ctx.stroke();\n    ctx.strokeStyle = '#8decb0';\n    ctx.beginPath();\n    ctx.moveTo(x - size * 0.3, y);\n    ctx.lineTo(x - size * 0.05, y + size * 0.35);\n    ctx.lineTo(x + size * 0.35, y - size * 0.25);\n    ctx.stroke();\n  }\n  \n  function tick(ts) {\n    const dt = Math.min(0.04, (ts - lastTs) / 1000);\n    lastTs = ts;\n    \n    const W = cv.width, H = cv.height;\n    const mass = parseFloat(document.getElementById('c-mass').value);\n    const appliedForce = parseFloat(document.getElementById('c-force').value);\n    const muFriction = parseFloat(document.getElementById('c-friction').value);\n    const scenario = parseInt(document.getElementById('c-scenario').value);\n    \n    const g = 9.8;\n    const N = mass * g; // normal force\n    const frictionForce = muFriction * N;\n    \n    // Determine net force based on scenario\n    let netForce = appliedForce;\n    let scenarioName = 'Frictionless';\n    let frictionDir = 0; // 0: none, -1: opposing, 1: assisting (rare)\n    \n    if (scenario === 1) {\n      // Friction opposes motion\n      scenarioName = 'With Friction';\n      netForce = appliedForce - frictionForce;\n      frictionDir = -1;\n    } else if (scenario === 2) {\n      // Force must overcome friction (static)\n      scenarioName = 'Against Friction';\n      if (appliedForce < frictionForce) {\n        netForce = 0; // Block doesn't move\n      } else {\n        netForce = appliedForce - frictionForce;\n      }\n      frictionDir = -1;\n    }\n    \n    simState.phaseTime += dt;\n    \n    // Phase: intro (0-2s), running (2-10s), result (10-15s)\n    if (simState.phaseTime > 12) {\n      simState.time = 0;\n      simState.pos = 0;\n      simState.vel = 0;\n      simState.workApplied = 0;\n      simState.workFriction = 0;\n      simState.workNet = 0;\n      simState.keInitial = 0;\n      simState.keFinal = 0;\n      simState.trailPoints = [];\n      simState.vHistory = [];\n      simState.energyHistory = [];\n      simState.phaseTime = 0;\n      simState.phase = 'intro';\n    } else if (simState.phaseTime < 2) {\n      simState.phase = 'intro';\n    } else if (simState.phaseTime < 10) {\n      simState.phase = 'running';\n      // Physics update\n      const a = netForce / mass;\n      simState.vel += a * dt;\n      \n      // Clamp distance if needed\n      const newPos = simState.pos + simState.vel * dt;\n      if (newPos > maxDist) {\n        simState.pos = maxDist;\n        simState.vel = 0;\n      } else {\n        simState.pos = newPos;\n      }\n      \n      simState.time += dt;\n      \n      // Track work done\n      const dist = simState.vel * dt;\n      simState.workApplied += appliedForce * dist;\n      if (frictionDir !== 0) {\n        simState.workFriction += -frictionForce * dist;\n      }\n      simState.workNet = simState.workApplied + simState.workFriction;\n      \n      // Track KE\n      simState.keInitial = 0.5 * mass * 0 * 0; // always 0 at start\n      simState.keFinal = 0.5 * mass * simState.vel * simState.vel;\n      \n      // Trail\n      simState.trailPoints.push({x: simState.pos, y: 0});\n      if (simState.trailPoints.length > 40) simState.trailPoints.shift();\n      \n      // Velocity history\n      simState.vHistory.push(simState.vel);\n      if (simState.vHistory.length > historyLen) simState.vHistory.shift();\n      \n      // Energy history\n      simState.energyHistory.push(simState.keFinal);\n      if (simState.energyHistory.length > historyLen) simState.energyHistory.shift();\n    } else {\n      simState.phase = 'result';\n    }\n    \n    // Draw background\n    ctx.fillStyle = 'rgba(15, 20, 24, 0.8)';\n    ctx.fillRect(0, 0, W, H);\n    \n    // Draw grid\n    ctx.strokeStyle = 'rgba(48, 52, 73, 0.3)';\n    ctx.lineWidth = 1;\n    const gridSpacing = 40;\n    for (let i = 0; i < W; i += gridSpacing) {\n      ctx.beginPath();\n      ctx.moveTo(i, 0);\n      ctx.lineTo(i, H);\n      ctx.stroke();\n    }\n    for (let i = 0; i < H; i += gridSpacing) {\n      ctx.beginPath();\n      ctx.moveTo(0, i);\n      ctx.lineTo(W, i);\n      ctx.stroke();\n    }\n    \n    const centerY = H * 0.45;\n    const startX = W * 0.1;\n    const scaleX = (W * 0.65) / maxDist;\n    \n    // Draw ground","remediationGoal":"Close concept_misunderstanding for physics."},"rendererStyle":"three_js"}}},"metadata":{"rendered_at":"2026-05-20T10:54:44.652Z","cache_hit":false,"cost_usd":0},"error":null}