Kontraktionen: ask it backwards, as a two-tap word cloud
"hinter + das = ?" with options am / im / uebers / hinters was no test at
all — only one option could start with "hinter", so you never had to know
which article was fused in. The whole level was answerable on the prefix.
Flipped it. The fused form is given and you take it apart:
- The form is shown large ("im"), and you pick the preposition from a word
cloud, then the article from a second one. Graded on both parts.
- The preposition step is a real question for the truncated forms — im vs
am, vom, zum/zur — which are exactly the ones that get mixed up.
- The article step is the point: -s means das, -m means dem, -r means der.
Distractors are always real articles (das/dem/der/die/den), never whole
contraction forms, so elimination on shape no longer works.
- Feedback names which half was wrong and states the ending rule, then
highlights the article in the definite-article table.
- The chosen preposition chip is tappable to change before committing;
backspace does the same on desktop. On reveal the line resolves to the
correct pair.
Tests: the old flaw is pinned shut (nothing on offer is ever a contraction
form), both choice sets are checked, and a data test asserts the -s/-m/-r
ending predicts the article for all 16 contractions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
This commit is contained in:
+50
-2
@@ -3,7 +3,7 @@ import {
|
||||
DEF, INDEF, PREPS, PREPS_BY_CASE, COMMON_PREPS, RARE_PREPS, ALL_PREPS,
|
||||
NOUNS, nounForm, LEVELS, CONTRACTIONS, CONTRACTABLE, CHUNK_PREPS,
|
||||
POSS_ENDINGS, combinePoss, PRONOUNS, VERBS, PREP_FRAMES,
|
||||
createSession, gen, genContractionContextQ, newState, buildChunkChoices, PREPS_BY_CASE as _PBC,
|
||||
createSession, gen, genContractionContextQ, genContractionQ, newState, buildChunkChoices,
|
||||
bumpStreak, currentStreak, weekDots,
|
||||
} from './engine.js';
|
||||
|
||||
@@ -278,6 +278,54 @@ describe('contractions', () => {
|
||||
expect(correct).toBe('an die');
|
||||
});
|
||||
|
||||
it('Kontraktionen is asked backwards: the fused form is given, you take it apart', () => {
|
||||
const state = newState();
|
||||
const seenForms = new Set();
|
||||
for (let i = 0; i < 300; i++) {
|
||||
const q = genContractionQ(state);
|
||||
const c = CONTRACTIONS.find(x => x.form === q.ans);
|
||||
expect(c, `${q.ans} is a real contraction`).toBeTruthy();
|
||||
seenForms.add(q.ans);
|
||||
|
||||
// the question is graded on the two parts, not the form
|
||||
expect(q.answerKey).toBe(`${c.prep} ${c.art}`);
|
||||
|
||||
expect(q.prepChoices).toContain(c.prep);
|
||||
expect(q.prepChoices.length).toBe(6);
|
||||
expect(new Set(q.prepChoices).size).toBe(6);
|
||||
|
||||
expect(q.artChoices).toContain(c.art);
|
||||
expect(q.artChoices.length).toBe(4);
|
||||
expect(new Set(q.artChoices).size).toBe(4);
|
||||
|
||||
// the old flaw: offering whole contraction forms meant only one option
|
||||
// could start with the given preposition. Nothing on offer is a form now.
|
||||
const forms = new Set(CONTRACTIONS.map(x => x.form));
|
||||
for (const w of [...q.prepChoices, ...q.artChoices]) expect(forms.has(w)).toBe(false);
|
||||
}
|
||||
expect(seenForms.size).toBe(CONTRACTIONS.length);
|
||||
});
|
||||
|
||||
it('the article step is a real discrimination, never answerable by elimination', () => {
|
||||
const state = newState();
|
||||
for (let i = 0; i < 300; i++) {
|
||||
const q = genContractionQ(state);
|
||||
// at least one wrong article that a careless learner would plausibly pick
|
||||
const wrong = q.artChoices.filter(a => a !== q.art);
|
||||
expect(wrong.length).toBe(3);
|
||||
expect(wrong.every(a => ['das','dem','der','die','den'].includes(a))).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('the -s/-m/-r ending always predicts the article', () => {
|
||||
const byEnding = { s:'das', m:'dem', r:'der' };
|
||||
for (const c of CONTRACTIONS) {
|
||||
const expected = byEnding[c.form.slice(-1)];
|
||||
expect(expected, `${c.form} has an unexpected ending`).toBeTruthy();
|
||||
expect(c.art, `${c.form} = ${c.prep} + ${c.art}`).toBe(expected);
|
||||
}
|
||||
});
|
||||
|
||||
it('dedicated Kontraktionen level always yields a preferred contraction', () => {
|
||||
const state = newState();
|
||||
for (let i = 0; i < 200; i++) {
|
||||
@@ -339,7 +387,7 @@ describe('pronoun questions', () => {
|
||||
});
|
||||
|
||||
describe('one-tap choices on every question type', () => {
|
||||
it.each([11, 12, 13, 14, 15, 16, 17, 19])('level %s: 4 unique choices containing the answer', (id) => {
|
||||
it.each([11, 12, 13, 14, 15, 16, 17])('level %s: 4 unique choices containing the answer', (id) => {
|
||||
for (const q of runSession(id, 200)) {
|
||||
expect(q.choices.length).toBe(4);
|
||||
expect(new Set(q.choices).size).toBe(4);
|
||||
|
||||
Reference in New Issue
Block a user