Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)
Web TippsUse custom web fonts in Google Sheets charts(08.09.2026 um 17:05 Uhr)
Web TippsIntroducing the new 1Password App for Google Chat(08.09.2026 um 18:02 Uhr)

🔧 Programmierung 🕛 vor 3 Monaten 8 Min Lesezeit
0

SVG Diagrams and Parametric Generators: Testing 358 Questions Across 200 Seeds

↗ Quelle (dev.to)
🗣️ Stimme:
📑 Inhaltsübersicht

"Calculate the hypotenuse BC of the right triangle at A. AB = 12 cm, AC = 9 cm, BC = ?" The student reads the question, looks at the diagram… and the diagram says AB = 3 cm. Different triangle. The values in the question are randomly generated, but the diagram is static. This kind of bug doesn't crash anything — it just makes an educational tool unusable.



and : 32-bit, deterministic, fast. The same seed always produces the same question — which means we only store the seed in the history and reconstruct the exact problem for review.




CODE
// Pythagorean theorem generator — compute the hypotenuse
{ key:'pyt-1', gen: (rnd) => {
const TRIPLETS = [[3,4,5],[5,12,13],[8,15,17],[7,24,25],[6,8,10]];
const [a0, b0, c0] = TRIPLETS[Math.floor(rnd() * TRIPLETS.length)];
const k = 1 + Math.floor(rnd() * 3); // multiplier 1..3
const [a, b, c] = [a0*k, b0*k, c0*k];

return {
q: <>Calculate the hypotenuse BC:
<TriangleRectangle ab={`${a} cm`} ac={`${b} cm`} bc="?" /></>,
options: [`${c} cm`, `${a+b} cm`, `${a*a+b*b} cm`, `${Math.abs(a-b)} cm`],
correct: 0,
hint: `BC² = ${a}² + ${b}² = ${a*a+b*b} → BC = ${c} cm.`,
};
}}






Three things to note. First, the pool of Pythagorean triplets guarantees the hypotenuse is an integer — no √41 ≈ 6.403 in a middle school quiz. Second, the multiplier k gives varied values without leaving integer territory. Third, the distractors aren't random: a+b (classic mistake: adding instead of Pythagoras), a²+b² (forgot the square root), |a-b| (subtraction by reflex).






An SVG kit that follows the values



The <TriangleRectangle> component above isn't decorative. It's a React component that receives values as props and renders the matching diagram — with labeled sides, a marked right angle, and a "?" on the measurement to find.




CODE
// _svg-kit.tsx — Parametric right triangle
function TriangleRectangle({ ab, ac, bc }: {
ab?: string | number;
ac?: string | number;
bc?: string | number;
} = {}) {
const lAB = ab !== undefined ? `AB = ${ab}` : 'AB';
const lAC = ac !== undefined ? `AC = ${ac}` : 'AC';
const lBC = bc !== undefined ? `BC = ${bc}` : 'BC (hypotenuse)';

return (
<svg viewBox="0 0 250 160" role="img"
aria-label="Right triangle at A">
<polygon points="50,30 50,130 200,130"
fill="rgba(199,138,29,0.08)"
stroke="currentColor" strokeWidth={1.8} />
<rect x={50} y={118} width={12} height={12}
stroke="currentColor" /> {/* right angle */}
<text x={40} y={85} fill="#b45309">{lAB}</text>
<text x={125} y={148} fill="#b45309">{lAC}</text>
<text x={135} y={72} fill="#b45309">{lBC}</text>
</svg>
);
}






The same pattern applies across the kit: ConfigThales (6 props for segments AM, AB, AN, AC, MN, BC), TriangleTrigo (angle, opposite/adjacent/hypotenuse sides), GrapheAffine (slope, y-intercept). When the generator picks random values, it passes them to the SVG component — the diagram always shows the same numbers as the question.






Electrical circuits and 3D volumes



The kit goes beyond geometry. For 8th-grade physics, electricity questions need circuit diagrams. Instead of static PNG images, I built composable SVG primitives:




CODE
// Primitives: Fil, Pile, Resistance, Lampe, Amperemetre, Voltmetre
// Compositions: CircuitSerie, CircuitParallele, CircuitCourtCircuit…

function CircuitSerie() {
return (
<svg viewBox="0 0 240 115" role="img"
aria-label="Series circuit">
<Fil points={[[30,85],[30,30],[210,30],[210,85],[30,85]]} />
<Pile cx={30} cy={58} />
<Resistance cx={100} cy={30} label="R₁" />
<Resistance cx={170} cy={30} label="R₂" />
<text x={120} y={105} fill="#b45309">
same I everywhere · U = U + U
</text>
</svg>
);
}






A <Fil> draws a polyline between points. A <Resistance> draws a rectangle with an optional label. A <Mesureur> draws a circle with a letter inside — "A" for ammeter, "V" for voltmeter. Compositions assemble these building blocks into complete circuits with annotated formulas.



For volumes (7th-grade math), same approach: Cube3D, Pave3D, Cylindre3D, Sphere3D, Cone3D components in cavalier perspective. And for geometric transformations (8th-grade math): SymetrieAxiale, SymetrieCentrale, Translation with a stylized F figure and its image. In total, 20 SVG components in a single _svg-kit.tsx file — 300 lines.






The duplicate distractor trap



When distractors are computed, there's a nasty edge case: a distractor can land on the same value as the correct answer. Example: a square with side 4, area = 16, perimeter = 16. If the distractor is "perimeter instead of area", you get 16 twice in the options.



The first instinct would be to re-roll the values. But with a seeded PRNG, you can't discard rolls — it breaks determinism. The solution: a Set of already-used values, and a systematic bump on collisions.




CODE
// Anti-duplicate pattern in every gen
const good = computeAnswer(a, b);
const used = new Set([good]);
const opts = [good];

for (const distractor of [wrongSign, wrongFormula, wrongOp]) {
let v = distractor;
while (used.has(v)) v += (v >= 0 ? 1 : -1);
used.add(v);
opts.push(v);
}






Except this pattern has its own bug. If the boundary is crossed (v <= 0 and we're decrementing), the while loop runs forever. Not theoretical: it happened on 7 generators when certain seeds produced values near zero. The fix: check the bump direction and reverse if heading out of valid space.






200 seeds per gen, in pre-commit



With 358 generators, manually checking that every value combination produces a valid quiz is impossible. I wrote a Node script (test-generators.js) that loads each quiz file through Babel, runs every gen across 200 seeds, and verifies:




  • 4 options present

  • correct in [0, 3]

  • No duplicates in options (after stringification and French decimal formatting)

  • Determinism: same seed → same output (verified on 5 sentinel seeds)

  • Variability: at least 10 distinct outputs across 200 seeds




CODE
// test-generators.js — excerpt
function testQuestion(quizKey, domainKey, q) {
const issues = [];
if (typeof q.gen !== 'function') return null;
const SEEDS = 200;
const outputs = new Set();

for (let i = 0; i < SEEDS; i++) {
const seed = (i * 2654435761) >>> 0; // Knuth multiplicative
const out = q.gen(mulberry32(seed));

// 4 options
if (out.options.length !== 4)
issues.push(`seed ${seed}: ${out.options.length} options`);

// Duplicates
const strings = out.options.map(serializeNode);
if (new Set(strings).size !== strings.length)
issues.push(`seed ${seed}: duplicates → [${strings.join(' | ')}]`);

// Variability
outputs.add(strings.join('¤') + '|' + out.correct);
}

if (outputs.size < 10)
issues.push(`low variability: ${outputs.size}/200`);

return { quizKey, key: q.key, issues, uniqueOutputs: outputs.size };
}






The script distinguishes bugs (duplicates, correct out of range, missing options) from warnings (low variability). Only bugs fail the pre-commit. Low variability is displayed but doesn't block — some generators have a naturally narrow input space (a "true or false, is this triangle right-angled" quiz only has two possible outputs by design).



Last run result: 358/358 generators, 0 bugs, 8 variability warnings. Pre-commit passes without --no-verify.






JSX serialization, the testing surprise



The biggest technical challenge in the test script wasn't the checks — it was serialization. Quiz options aren't always strings. A fraction renders with <F n={3} d={4} />, an exponent with <sup>, a math symbol with <M>. To compare two options, you need to reduce them to text.




CODE
// Serialize a compiled ReactNode (JSX → createElement → object)
function serializeNode(n) {
if (n == null || n === false) return '';
if (typeof n === 'string' || typeof n === 'number') return String(n);
if (Array.isArray(n)) return n.map(serializeNode).join('');
if (typeof n === 'object' && n.props) {
const children = n.props.children;
if (children == null)
return `<${typeof n.type === 'string' ? n.type : 'C'}/>`;
return serializeNode(children);
}
return '';
}






The script doesn't mount any DOM. It shims React.createElement to return plain objects, then walks down props.children recursively. The SVG kit components are stubbed — a <TriangleRectangle ab="12 cm" /> serializes to a flat string, enough to detect duplicates without mounting a virtual DOM.






French formatting that breaks comparisons



In France, we write 3,5 — not 3.5. The app applies a Frenchification pass on options before display. Problem: the test must reproduce this exact pass, otherwise a post-formatting duplicate goes unnoticed. Example: "3.0" and "3" both become "3" after .replace(/\.0$/, '').



The script applies the same regex as app.tsx before comparing. It's an exact copy — not a reimplementation, not a port, a line-by-line copy. Any divergence between the test and the app would produce false negatives, and that's exactly the kind of bug that would go unnoticed for months.






Conclusion



The thing I didn't anticipate is that building parametric questions is a constrained combinatorics problem, not a random generation one. Randomness is the easy part. The hard part is guaranteeing that every value combination produces a valid quiz — no duplicates, no division by zero, no negative result when the context is a length, no diagram contradicting the question.



The pre-commit hook that tests 200 seeds per gen caught 13 duplicate bugs and 7 infinite loops I would never have found manually. The cost: a 180-line script and 4 extra seconds per commit. The return on investment is absurd.

Vollständiger Original-Bericht
Ausführliche Details, Code-Beispiele & Hersteller-Stellungnahme auf dev.to.
↗ Original-Artikel auf dev.to lesen
Wie bewertest du diesen Beitrag?
1 Klick Feedback
Teilen mit Netzwerk & Team:

Community-Analysen & Experten-Meinungen 0

Verfasse deine eigene Analyse, teile Workarounds oder diskutiere diesen Vorfall im Blog.
Noch keine Community-Analyse verfasst. Markiere einen Textabschnitt oder klicke oben auf Eigene Analyse verfassen“!
Community Pulse: Relevanz-Einschätzung
1 Klick Experten-Votum
🔴 Akute Relevanz 0%
🟡 In Evaluierung 0%
🟢 Keine Auswirkung 0%
Spannende Innovation 0%
Verwandte Story-Cluster & Quellen (Vektor-KI)
Port 8095 Engine
3 Quellen
Use custom web fonts in Google Sheets charts
2 Quellen
Introducing the new 1Password App for Google Chat
1 Quelle
Context-aware access controls are available for Gemini Enterprise in the Admin console
Ähnliche Beiträge
🔍 Verwandte News

Auch interessante Nachrichten SVG Diagrams and Parametric Generators: Testing 358 Questions Across 200 Seeds

Thematisch verwandte Begriffe: Diagrams, Parametric, Generators, Testing · 6 Treffer

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...

Laden...

Beiträge werden geladen ...

Laden...

Videos werden geladen ...