Wechsel im Kontext: blank the article, make the verb decide, add the rule
The level showed the article already in the sentence ("unter dem Stuhl"),
so it was answerable by recognising "dem" — it never tested wohin/wo at
all. And the frame pool was tiny: one cat, one lamp, eight nouns.
- The article is now blanked. The only evidence is the verb and whether it
has a direct object, which is the actual skill. The solved sentence is
revealed on answer, contracted where German really contracts (ins Regal,
am Bahnhof), with a note naming the fusion.
- Frames rebuilt around what decides the case: 27 new nouns, 18 new verbs,
and every frame one-way so no verb+noun pairing licenses both readings.
- Motion verbs now appear on the DATIVE side on purpose — schwimmen im See,
tanzen in der Kueche, spielen auf der Strasse — because "is it moving?" is
the wrong test and that is the main thing learners get wrong.
- The put/be pairs are drilled properly: legen/liegen, stellen/stehen,
setzen/sitzen, plus haengen and stecken, which keep the same present tense
either way so only the direct object tells you.
- ankommen added as the lexical exception: Dativ despite obvious arrival.
- New "Wohin oder wo?" info panel: the rule, the before-and-after test, the
movement trap, the verb-pair table and the exception. Reachable from the
practice screen, from the answer feedback, and from Charts.
- Feedback now explains the specific sentence rather than restating theory.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
This commit is contained in:
+70
-1
@@ -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,
|
||||
createSession, gen, genContractionContextQ, newState, buildChunkChoices, PREPS_BY_CASE as _PBC,
|
||||
bumpStreak, currentStreak, weekDots,
|
||||
} from './engine.js';
|
||||
|
||||
@@ -189,6 +189,75 @@ describe('wechsel in context (level 18)', () => {
|
||||
expect(q.sentence.endsWith('.')).toBe(true);
|
||||
}
|
||||
});
|
||||
|
||||
it('blanks the article so the verb is the only evidence', () => {
|
||||
for (const q of runSession(18, 300)) {
|
||||
expect(q.sentence).toContain('_____');
|
||||
expect(q.article).toBe(DEF[q.noun.g][q.ans]);
|
||||
// the blanked sentence must never leak the answer article next to the prep
|
||||
expect(q.sentence).not.toContain(` ${q.prepKey} ${q.article} `);
|
||||
}
|
||||
});
|
||||
|
||||
it('the solved sentence contracts exactly where German contracts', () => {
|
||||
for (const q of runSession(18, 400)) {
|
||||
const fused = CONTRACTABLE[q.prepKey]?.[q.article] || null;
|
||||
expect(q.contraction).toBe(fused);
|
||||
expect(q.answerSentence).not.toContain('_____');
|
||||
if (fused) expect(q.answerSentence).toContain(` ${fused} `);
|
||||
else expect(q.answerSentence).toContain(` ${q.prepKey} ${q.article} `);
|
||||
}
|
||||
});
|
||||
|
||||
it('drills far more than the handful of cats and lamps it used to', () => {
|
||||
const qs = runSession(18, 400);
|
||||
expect(new Set(qs.map(q => q.verb)).size).toBeGreaterThanOrEqual(20);
|
||||
expect(new Set(qs.map(q => q.noun.w)).size).toBeGreaterThanOrEqual(25);
|
||||
// no single sentence should dominate the round
|
||||
const counts = {};
|
||||
for (const q of qs) counts[q.answerSentence] = (counts[q.answerSentence] || 0) + 1;
|
||||
expect(Math.max(...Object.values(counts))).toBeLessThanOrEqual(12);
|
||||
});
|
||||
|
||||
it('teaches that movement alone is not Akkusativ', () => {
|
||||
const qs = runSession(18, 600);
|
||||
// motion verbs used with a dative "arena" reading
|
||||
const arena = qs.filter(q => ['schwimmen','tanzen','spielen','fliegen'].includes(q.verb) && q.ans === 'dative');
|
||||
expect(arena.length).toBeGreaterThan(0);
|
||||
// ankommen is the lexical exception: always dative
|
||||
const ank = qs.filter(q => q.verb === 'ankommen');
|
||||
expect(ank.length).toBeGreaterThan(0);
|
||||
for (const q of ank) expect(q.ans).toBe('dative');
|
||||
});
|
||||
|
||||
it('the put/be verb pairs land on the case their transitivity demands', () => {
|
||||
// Unambiguously transitive "put it there" verbs vs unambiguously stative ones.
|
||||
// hängen and stecken are deliberately excluded — see the next test.
|
||||
const PUT = ['legen','stellen','setzen','werfen','schieben','packen','tragen','kleben','schreiben'];
|
||||
const BE = ['liegen','stehen','sitzen','wohnen','leben','schlafen','warten','arbeiten','sein'];
|
||||
for (const q of runSession(18, 600)) {
|
||||
if (PUT.includes(q.verb)) expect(q.ans, `${q.answerSentence}`).toBe('accusative');
|
||||
if (BE.includes(q.verb)) expect(q.ans, `${q.answerSentence}`).toBe('dative');
|
||||
}
|
||||
});
|
||||
|
||||
it('hängen and stecken appear in BOTH cases — only the direct object decides', () => {
|
||||
const qs = runSession(18, 800);
|
||||
for (const v of ['hängen', 'stecken']) {
|
||||
const mine = qs.filter(q => q.verb === v);
|
||||
expect(mine.some(q => q.ans === 'accusative'), `${v} never showed transitive`).toBe(true);
|
||||
expect(mine.some(q => q.ans === 'dative'), `${v} never showed intransitive`).toBe(true);
|
||||
// transitive = something is being moved, so the sentence carries an object
|
||||
// before the preposition; intransitive sentences start with that thing.
|
||||
for (const q of mine) {
|
||||
const beforePrep = q.answerSentence.split(` ${q.prepKey} `)[0];
|
||||
const fusedHead = q.contraction ? q.answerSentence.split(` ${q.contraction} `)[0] : beforePrep;
|
||||
const head = q.contraction ? fusedHead : beforePrep;
|
||||
const hasObject = /\b(das|den|die|dem)\b/.test(head);
|
||||
expect(hasObject, `${q.answerSentence} (${q.ans})`).toBe(q.ans === 'accusative');
|
||||
}
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// ── Chunk / contraction questions ───────────────────────────
|
||||
|
||||
Reference in New Issue
Block a user