feat: Module 11, Conditions (NOUN ON NOUN IS ..., checked per object at use; NEAR as the Expansion Drop)

Adds course/11-conditions: lesson, scenarios, nine test_11 tests in both
tracks, the cumulative reference (Module 10's plus conditions) and the NEAR
drop. The engine learns a condition role, rules carry conditions, and
Level.hasProperty and conversion evaluate content's condition checks against
the board whenever a rule is used. Resolution pairs only rules with the same
conditions. Catalog, harness contract, research notes and the diff-tracks
generator follow.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-11-11 13:00:26 +01:00
1 parent 570e5d6826
commit 34c655c1a4
89 files changed
+11703 -4

No files matched your search

+20
View File
@@ -89,6 +89,26 @@ test("diff_tracks_levels_form_sentences", () => {
assert.ok(withSentence.length > SEEDS.length * 0.8, `${withSentence.length} of ${SEEDS.length}`);
});
test("diff_tracks_writes_conditions_from_module_11", () => {
// Module 11's vocabulary has a condition word (ON): some sentences carry a condition.
// Module 10's has none, so its cases have no condition word in a sentence.
const vocab11 = loadVocab(join(course, "11-conditions", "reference", "ts", "src", "content", "words.json"), cardDirs("11"));
const conditions = new Set(vocab11.words.filter((w) => w.role === "condition").map((w) => w.name));
assert.ok(conditions.has("ON"));
// A condition word followed by a noun in a row or column, as a sentence writes it.
const withCondition = (v) => SEEDS.filter((seed) => {
const { level } = generateCase(seed, v);
const at = new Map(level.objects.filter((o) => o.kind.startsWith("text:")).map((o) => [`${o.x},${o.y}`, o.kind.slice(5)]));
const noun = (w) => w !== undefined && v.words.some((x) => x.name === w && x.role === "noun");
return [...at].some(([key, word]) => {
const [x, y] = key.split(",").map(Number);
return conditions.has(word) && (noun(at.get(`${x + 1},${y}`)) || noun(at.get(`${x},${y + 1}`)));
});
});
assert.ok(withCondition(vocab11).length > SEEDS.length * 0.3, `${withCondition(vocab11).length} of ${SEEDS.length}`);
assert.deepEqual(withCondition(vocab), []);
});
test("diff_tracks_verdict", () => {
const ok = (out, code = 1) => ({ out, err: "", code, signal: null });
assert.equal(verdict(ok("a\n"), ok("a\n")), "agree");