Commit Graph
4 Commits
Author SHA1 Message Date
georgeandClaude Opus 5 eae889c52b Add the Verbs tab, and stop Daily rounds repeating a preposition
The possessive and pronoun levels already build sentences with no
preposition in them - "Ich kenne ___ Mann", "Ich helfe ___ Mann" - where
the case is decided purely by the verb. Six verbs were doing that silently
across seven levels (sehen, kennen, lieben, hoeren as accusative, helfen as
dative, geben as both) and nothing in the app had ever taught or tested it.

The framing the whole section rests on: there is no accusative list to
learn. Accusative is the default for hundreds of transitive verbs, so you
learn the dative exceptions - the same shape as the preposition lists the
app already teaches.

39 verbs of data, in four groups:
- accusative foils, including the ones English makes feel dative
  (fragen, anrufen, treffen, unterstuetzen, moegen)
- 12 dative verbs: helfen, danken, antworten, glauben, folgen, gratulieren,
  vertrauen, verzeihen, begegnen, zuhoeren, raten, schaden
- 4 that flip subject and object against English: gefallen, schmecken,
  fehlen, gehoeren
- 8 two-object verbs, all covered by one rule: person Dativ, thing Akkusativ

Six lessons plus Daily Practice: Welcher Fall? / Die Fallen / Gefallen & Co.
/ Dativ + Akkusativ / Im Satz / Alles gemischt. The tab sits second, right
after Prepositions, so case government is learned before the forms that
depend on it.

Near-synonyms that disagree are modelled as a `trap` link, and a test
asserts every trap points at a real verb taking the other case and that the
link is symmetric - so fragen/antworten, treffen/begegnen, anrufen/zuhoeren,
unterstuetzen/helfen and moegen/gefallen cannot drift apart.

The inverted verbs carry an explicit English reordering rule rather than a
literal gloss, so "Das Fenster gefaellt der Aerztin" renders as "The doctor
likes the window" and never "The window pleases the doctor". Tested.

Verb sentences are enumerated into corpus.txt like everything else, keyed on
verb x noun, which is what caught "I lend the teacher the bed" and "I
recommend the girl the cupboard" before they shipped - two-object verbs now
name the things that can actually be handed over, and the English uses the
"to the person" order.

167 tests pass.

Known gap: a handful of verbs take the genitive (gedenken, beduerfen,
beschuldigen). They are formal and rare, so the rule panel mentions them and
the app does not drill them.

Daily rounds could serve the same preposition twice, even back-to-back

Reported as "für" twice in a row in the Prepositions daily. Measured before
the fix: 14% of prep-daily rounds had a back-to-back repeat and 51% repeated
some preposition.

A Daily round mixes several lessons, and each one was handed its own
newState(). The anti-repeat memory therefore only spanned one lesson's slice
of the round, so level 8 drawing "für" and level 10 drawing "für" could not
see each other. drawBag also only avoided the previous item when the bag
refilled, not on every draw.

- newState(shared) lets several lesson states run off one memory, and the
  Daily hands the same one to every lesson it mixes.
- drawBag now skips anything already used this cycle, and never repeats the
  previous item, checked on every draw rather than only at a refill.
- Kontraktionen drew through a different bag ('contr') and so bypassed the
  preposition memory entirely, letting "im" follow "in". It now consults and
  records the shared memory.
- Adjective frames were chosen with a plain pick(), so a Daily could repeat
  a whole carrier sentence. They are dealt from the shared memory too.

Rejected draws are put BACK into the bag rather than discarded. The first
version burned them, which skewed the Kontraktionen deal 1.5x toward the
forms whose preposition is not shared with a sibling form; reinsertion holds
the spread at 1.04x.

After: every Daily and every preposition-drilling level is at 0% for both
back-to-back and within-round repeats. Two cases remain non-zero and are
arithmetic rather than bugs - a round is 10 questions, so Wechsel im Kontext
(9 prepositions) must repeat one, and the Stark adjective level has exactly
one prepositional carrier frame. The adjective tab is held to frame variety
rather than preposition variety, since there the preposition is scenery and
the ending is the question.

Tests pin all of it: no Daily may repeat a preposition, no level may repeat
one back-to-back where it has a choice, and no Daily may repeat a carrier
sentence back-to-back.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-05 06:12:27 +00:00
georgeandClaude Opus 5 e448045e52 ein-Wörter was quietly dropping three of the nine dative prepositions
Reported as "not a single dative in the ein-Wörter section". The engine did
still produce dative questions, but only from 6 of the 9 dative
prepositions, so the dative was thinner than the lesson claimed and leant
on Wechsel preps landing on dative rather than on dative preps themselves.

The indefOnly frame filter was throwing three away:

- bei — a regression from the "bei der Schwester" fix. That commit added
  `!f.enPossS` to the filter, which killed bei's only non-possessive frame
  outright. "bei einem Arzt" is perfectly good German and reads as "at a
  doctor's" in English, so there was never a reason to exclude it. von and
  zu survived but lost their person frames the same way, halving them.

- nach, seit — excluded as "definite idioms" because their frames carry an
  enDet override ("nach der Schule" is "after school", not "after the
  school"). But "nach einer Woche" and "seit einer Woche" are textbook
  German; the engine's own PREPS entry for seit uses "seit einer Woche" as
  its example.

The enDet exclusion existed to stop "während einer Woche", the sentence
reported a while back, so it could not simply be deleted. Frames can now
name `indefNouns`: the nouns that DO work once an ein-word is in front,
overriding the blanket noIndef flag for that frame only. nach and seit
re-admit Woche; während re-admits nothing, so "während einer Woche" stays
impossible. seit also gets enPrepIndef, because "seit einer Woche" is "FOR
a week" in English, not "since a week".

trotz stays out, but honestly: it only governs uncountables here ("trotz
eines Wetters" is not German), so it is removed from level 7's preps list
rather than left advertised and unreachable — the same treatment zu got in
the Dativ lesson.

The guard that should have caught this: a new test asserts that every level
generates every preposition it lists. A prep that can never be asked about
is now a test failure, not a silent hole. Two older tests that asserted the
buggy behaviour (that nach/seit never appear, that no noIndef noun ever
appears) are rewritten to assert the actual rule instead of being loosened.

Dative coverage in ein-Wörter: 6/9 prepositions before, 9/9 after. Case mix
47% dative / 32% accusative / 21% genitive. A round of 10 with no dative at
all went from 0.2% to 0.06%.

142 tests pass.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-10-01 06:39:45 +00:00
georgeandClaude Opus 5 b9a4e16892 Add the Adjectives section, and explain why the noun itself changes
Two gaps, both visible from one screenshot of the Genitiv level.

1. Adjective endings were half-present and entirely untested. STRONG and
   WEAK existed in engine.js with correct data, but only as the S/W columns
   of a reference chart — nothing generated them, nothing tested them, and
   the MIXED table (the one "in seinem eigenen Kontext" actually needs) did
   not exist at all. There was not a single adjective in the codebase.

2. Nothing explained why "Regen" becomes "Regens" after trotz.

New Adjectives tab, six lessons plus Daily Practice:

  Schwach          after der/diese/jede — only -e and -en
  Gemischt         after ein/kein/mein — weak plus three exceptions
  Stark            no determiner — the adjective carries the case
  Welche Tabelle?  der-word, ein-word or nothing? (the meta-skill)
  Nach Präposition the full chain: preposition -> case -> table
  Alles gemischt   all three tables, including Genitiv

The three tables are built on one idea: the case has to be signalled once
in the noun phrase, and whoever can show it does. MIXED is therefore not
hand-typed — it is DERIVED from POSS_ENDINGS as "weak, except in the three
slots where the ein-word has no ending of its own, where it borrows the
strong one". A test asserts that relationship as well as all 48 cells, so
the rule cannot silently rot.

Two info panels:
- "Which ending table?" — the one-signal idea, the three tables, plus the
  two traps: strong genitive masc/neut is -en not -es (the noun already
  carries the -(e)s), and predicate adjectives take no ending at all.
- "Why Regens?" — appears wherever the case changes the noun itself:
  the -es/-s split, that feminines and plurals never change, the dative
  plural -n, and a note on the N-declension nouns Beug does not have yet.

Notable decisions:
- Nominative is now tested, for adjectives only. der gute Mann vs ein
  guter Mann is the clearest illustration of the whole weak/mixed split and
  it exists only in the nominative, so the app-wide "no nominative" rule is
  explicitly scoped to non-adjective levels rather than quietly dropped.
- The tab bar measured 369px of content in a 350px bar with four tabs, so
  a fifth could not fit. The row now scrolls instead of squeezing labels.
- Adjectives are gated like Possessives and Pronouns.

The corpus caught its keep again. Adjective sentences are enumerated into
corpus.txt keyed on the semantic pairing (frame x adjective x noun), which
keeps it at ~2000 lines instead of exploding over every determiner and
case. Reading it surfaced, and this commit fixes: "I live in the old lake"
(places split into buildings/settlements/landscape), "I play with the blue
sofa", "I like the bad kitchen" (frames that assert liking now exclude
negative adjectives), "I buy the good time" and "I cook with the good
money" (pools narrowed to what you can buy and cook with), grosser/kleiner
of a person rendering as "big/small" rather than "tall/short", and
"in dem alten Hotel" failing to fuse into "im alten Hotel".

130 tests pass; 12000 generated questions across all 30 levels.

Known gap, stated in the rule panel: Beug has no plural nouns, so the
plural column of all three tables is shown for reference but not drilled.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-22 06:06:59 +00:00
georgeandClaude Opus 5 0dfc2955db Enumerate every possible sentence, review it, and lock it with a snapshot
"Wir wohnen bei der Schwester. / We live at the sister." was two bugs at
once, and both were the same shape: a template applied to a pairing nobody
had ever read.

Root cause: sentences are built combinatorially (frames x verbs x nouns x
subjects). The space is ~2900 sentences and only a sample had ever been
looked at, so every fix so far has been whack-a-mole on whichever bad one
happened to surface.

The fix is structural, not another special case:

- composeFrame() is now pure, and enumerateCorpus() walks every frame,
  verb, noun and case for every consumer (fill-the-article, ein-Woerter,
  possessive, Wechsel im Kontext, fused). src/corpus.txt is that full
  enumeration, checked in and reviewed.
- Two tests make it foolproof rather than decorative:
    * the snapshot test fails if generation drifts from corpus.txt, so no
      new sentence can reach a learner unreviewed. Regenerate with
      `npm run corpus` and the diff IS the review.
    * a coverage test runs the real generators over every sentence level
      and asserts each question it produces has a key in the corpus — so
      the file is provably the live output, not a parallel implementation.
  Two more assert no un-fused contraction and no German leaking into an
  English line.

Bugs the audit then found and fixed:

- zu/von/bei + a person means "at/to/from their PLACE". English needs the
  -'s ("at the doctor's"), and with a bare definite article German only
  allows roles you visit (beim Arzt), not family — "bei der Schwester" is
  wrong, "bei meiner Schwester" is right, so family frames are now
  possessive-only. Added Aerztin/Lehrerin/Freundin so the role case has a
  non-fusing feminine form.
- "zu dem Arzt", "in dem Haus": a fill-the-article sentence may no longer
  show a two-word form German obligatorily fuses. Those pairings belong to
  the Kontraktionen questions, which already teach them. "zu" therefore
  leaves the Dativ lesson entirely, the way "bis" already had.
- mein Mann / meine Frau mean my husband / my wife, not my man / my woman.
- "mein Maedchen" is not idiomatic and was reachable from the possessive
  templates; the coverage test caught it.
- You cannot drive into a kitchen, a cellar or a library: the "in" motion
  frame is split into drivable places and on-foot-only places.
- "stellt den Stuhl an die Wand" is "against the wall", not "to the wall";
  haengen/schreiben/kleben take "on"; wohnen am See is "by the lake".

Also: questions now carry the verb and case they were built from, the
article redraw is depth-bounded so a data mistake degrades to one odd
question instead of hanging, and level 3's advertised prep list matches
what it can actually generate.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017Uza6mhTpAvSwMQuFc4Cz8
2026-09-18 07:24:29 +00:00