/* Mastery map — the learner's relationship to the field Same graph substrate as PrerequisiteGraph but projected through learning state. FSRS retention is first-class. Domain-agnostic structure. */ const MasteryMap = () => { const [hovered, setHovered] = React.useState(null); const [filter, setFilter] = React.useState("all"); // Same calculus subgraph as v1 but with FSRS retention state added. // mastery: mastered | review | learning | frontier | locked // retention: 0..1 (FSRS-projected probability of recall right now) // due: days until/since due (negative = overdue) const nodes = [ // primitives — long mastered, high retention { id:"set", label:"Set", kind:"definition", x:0.08, y:0.10, mastery:"mastered", retention:0.96, due: 18 }, { id:"function", label:"Function", kind:"definition", x:0.20, y:0.08, mastery:"mastered", retention:0.94, due: 14 }, { id:"real_numbers", label:"Real numbers ℝ", kind:"axiom", x:0.32, y:0.14, mastery:"mastered", retention:0.91, due: 12 }, { id:"interval", label:"Open interval", kind:"definition", x:0.44, y:0.06, mastery:"mastered", retention:0.88, due: 9 }, { id:"absolute_val", label:"Absolute value", kind:"definition", x:0.55, y:0.13, mastery:"mastered", retention:0.93, due: 22 }, { id:"composition", label:"Composition", kind:"definition", x:0.68, y:0.08, mastery:"mastered", retention:0.81, due: 4 }, { id:"sequence", label:"Sequence", kind:"definition", x:0.80, y:0.14, mastery:"mastered", retention:0.62, due: -2 }, // overdue { id:"slope_line", label:"Slope of a line", kind:"definition", x:0.92, y:0.08, mastery:"mastered", retention:0.55, due: -5 }, // overdue → review // limits { id:"limit_seq", label:"Limit · sequence", kind:"definition", x:0.10, y:0.30, mastery:"mastered", retention:0.78, due: 2 }, { id:"limit_func", label:"Limit · function", kind:"definition", x:0.30, y:0.28, mastery:"mastered", retention:0.71, due: 1 }, { id:"epsilon_delta", label:"ε–δ definition", kind:"definition", x:0.48, y:0.34, mastery:"learning",retention:0.42, due: 0 }, { id:"limit_sum", label:"Limit · sum", kind:"lemma", x:0.66, y:0.28, mastery:"mastered", retention:0.84, due: 6 }, { id:"limit_product", label:"Limit · product", kind:"lemma", x:0.82, y:0.32, mastery:"learning",retention:0.51, due: -1 }, // review-due { id:"limit_compose", label:"Limit · composition", kind:"lemma", x:0.94, y:0.26, mastery:"learning",retention:0.46, due: -3 }, // review-due // continuity, secant, dq { id:"continuity", label:"Continuity", kind:"definition", x:0.18, y:0.48, mastery:"learning",retention:0.61, due: 0 }, { id:"secant", label:"Secant line", kind:"definition", x:0.40, y:0.50, mastery:"frontier",retention:null, due:null }, { id:"diff_quot", label:"Difference quotient", kind:"definition", x:0.58, y:0.46, mastery:"frontier",retention:null, due:null }, // DERIVATIVE — current frontier { id:"derivative", label:"Derivative", kind:"definition", x:0.50, y:0.62, mastery:"frontier", retention:null, due:null, focus:true }, // first-order theorems { id:"diff_implies_cont", label:"Diff ⇒ continuity", kind:"theorem", x:0.16, y:0.74, mastery:"locked", retention:null, due:null }, { id:"sum_rule", label:"Sum rule", kind:"theorem", x:0.34, y:0.78, mastery:"locked", retention:null, due:null }, { id:"product_rule", label:"Product rule", kind:"theorem", x:0.56, y:0.80, mastery:"locked", retention:null, due:null }, { id:"chain_rule", label:"Chain rule", kind:"theorem", x:0.74, y:0.74, mastery:"locked", retention:null, due:null }, // far downstream { id:"mvt", label:"Mean value theorem", kind:"theorem", x:0.10, y:0.92, mastery:"locked", retention:null, due:null }, { id:"l_hopital", label:"L'Hôpital's rule", kind:"theorem", x:0.30, y:0.94, mastery:"locked", retention:null, due:null }, { id:"taylor", label:"Taylor's theorem", kind:"theorem", x:0.56, y:0.94, mastery:"locked", retention:null, due:null }, { id:"newtons_method", label:"Newton's method", kind:"procedure", x:0.78, y:0.92, mastery:"locked", retention:null, due:null }, { id:"linear_approx", label:"Linear approximation",kind:"theorem", x:0.90, y:0.86, mastery:"locked", retention:null, due:null }, ]; const edges = [ ["set","function"],["function","limit_func"],["real_numbers","limit_seq"], ["limit_seq","limit_func"],["limit_func","epsilon_delta"],["limit_func","continuity"], ["limit_func","limit_sum"],["limit_func","limit_product"],["limit_func","limit_compose"], ["slope_line","secant"],["slope_line","diff_quot"],["function","secant"], ["composition","limit_compose"],["interval","derivative"],["absolute_val","continuity"], ["function","derivative"],["limit_func","derivative"],["diff_quot","derivative"], ["secant","derivative"],["continuity","derivative"], ["derivative","diff_implies_cont"],["derivative","sum_rule"],["derivative","product_rule"],["derivative","chain_rule"], ["limit_sum","sum_rule"],["limit_product","product_rule"],["limit_compose","chain_rule"], ["continuity","diff_implies_cont"], ["diff_implies_cont","mvt"],["derivative","mvt"],["derivative","l_hopital"], ["derivative","taylor"],["derivative","newtons_method"],["derivative","linear_approx"], ]; const nodeById = Object.fromEntries(nodes.map(n => [n.id, n])); const counts = { mastered: nodes.filter(n => n.mastery==="mastered" && n.retention >= 0.7).length, review: nodes.filter(n => n.due !== null && n.due < 0).length, learning: nodes.filter(n => n.mastery==="learning").length, frontier: nodes.filter(n => n.mastery==="frontier").length, locked: nodes.filter(n => n.mastery==="locked").length, }; // Effective state for visual encoding combines mastery + retention. const stateOf = (n) => { if (n.mastery === "frontier") return "frontier"; if (n.mastery === "locked") return "locked"; if (n.mastery === "learning") return "learning"; if (n.due !== null && n.due < 0) return "review"; return "mastered"; }; const matchFilter = (n) => filter === "all" || stateOf(n) === filter; const W = 1180, H = 720; const PAD_X = 60, PAD_Y = 40; const xToPx = (x) => PAD_X + x * (W - 2*PAD_X); const yToPx = (y) => PAD_Y + y * (H - 2*PAD_Y); const reviewDue = nodes.filter(n => n.due !== null && n.due < 0).sort((a,b) => a.due - b.due); const frontier = nodes.filter(n => n.mastery === "frontier"); return (
§ MAP / Mastery map · your relationship to · Calculus / Differentiation
Domain

{/* left rail — answers "what should I work on right now?" */} {/* main map */}
{/* depth rules — territory bands */} {[0.20, 0.42, 0.58, 0.83].map((y, i) => ( ))} FOUNDATIONS LIMITS CONTINUITY · SECANTS FRONTIER LOCKED · DOWNSTREAM {/* glow halos */} {nodes.filter(n => stateOf(n) === "frontier").map(n => ( ))} {nodes.filter(n => stateOf(n) === "review").map(n => ( ))} {/* edges */} {edges.map(([a, b], i) => { const na = nodeById[a], nb = nodeById[b]; if (!na || !nb) return null; const ax = xToPx(na.x), ay = yToPx(na.y); const bx = xToPx(nb.x), by = yToPx(nb.y); // both ends locked? mute const aSt = stateOf(na), bSt = stateOf(nb); const isLocked = aSt === "locked" || bSt === "locked"; const isFrontier = aSt === "frontier" || bSt === "frontier"; const stroke = isFrontier ? "var(--accent)" : isLocked ? "var(--rule-3)" : "var(--rule-2)"; const opacity = isLocked ? 0.5 : isFrontier ? 0.6 : 0.5; const isHov = hovered && (a === hovered || b === hovered); return ( ); })} {/* nodes */} {nodes.filter(matchFilter).map(n => { const st = stateOf(n); const cx = xToPx(n.x), cy = yToPx(n.y); const isHov = hovered === n.id; const dim = filter !== "all" && !matchFilter(n); const fill = st === "mastered" ? "var(--ink)" : st === "frontier" ? "var(--accent)" : st === "review" ? "var(--paper)" : st === "learning" ? "var(--paper)" : "var(--paper)"; const stroke = st === "locked" ? "var(--rule-2)" : st === "review" ? "var(--ink)" : st === "learning" ? "var(--ink)" : st === "frontier" ? "var(--accent)" : "var(--ink)"; const r = n.focus ? 7 : (n.kind==="theorem"||n.kind==="lemma") ? 0 : 4.5; return ( setHovered(n.id)} onMouseLeave={()=>setHovered(null)} style={{cursor:"pointer"}} opacity={dim ? 0.2 : 1}> {/* shape encodes type */} {n.kind === "theorem" || n.kind === "lemma" ? ( ) : n.kind === "axiom" ? ( ) : n.kind === "procedure" ? ( ) : ( )} {/* review hatch overlay */} {st === "review" && ( )} {/* frontier focus halo */} {n.focus && ( )} {/* retention bar — for nodes with retention */} {n.retention !== null && st !== "frontier" && ( )} {n.label} ); })}
{/* right rail — selected node detail */}

); }; window.MasteryMap = MasteryMap;