fix: module 10 review round 1 (module-local SpellGrid cards, cast target check, CLI without panic hook)

- SpellGrid cards move to course/10-spellgrid/cards; start-module and
  verify-references stage course/cards plus module-local cards up to the module,
  so M5-M9 workspaces get exactly the pre-M10 card set.
- cast() rejects a chosen-target spell without a target (checkCast/checkPlay);
  new test_10_targeted_spell_needs_target.
- Rust CLI pre-validates instead of swapping the global panic hook.
- Catalog: CRIER noun; DEATHRATTLE/BATTLECRY note on lexicon vs catalog role.
- Lesson: design note on mid-movement reparse.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas Winther
2026-10-29 05:11:09 +01:00
co-authored by Claude Opus 5.5
parent a0cf9be214
commit 6f576940b7
34 changed files with 295 additions and 79 deletions
+20
View File
@@ -243,6 +243,26 @@ fn test_10_spawned_text_triggers_reparse() {
assert!(g.won());
}
#[test]
fn test_10_targeted_spell_needs_target() {
let mut g = new_spell_grid(
level(3, 1, &[], &[], &[("Bramble Wall", 0, 0)], &["Spark"]),
vec![],
);
let bramble = minion(&g, 0, 0);
let cast = std::panic::catch_unwind(std::panic::AssertUnwindSafe(|| g.cast("Spark", None)));
assert!(cast.is_err());
assert_eq!(g.hand(), vec!["Spark"]);
assert_eq!(g.health(bramble), 3);
g.cast("Spark", Some(bramble));
assert_eq!(g.health(bramble), 2);
assert_eq!(g.hand(), Vec::<String>::new());
}
#[test]
fn test_10_level_1_solution() {
let mut g = new_spell_grid(
@@ -206,6 +206,21 @@ test("test_10_spawned_text_triggers_reparse", () => {
expect(g.won()).toBe(true);
});
test("test_10_targeted_spell_needs_target", () => {
const g = newSpellGrid(level(3, 1, { cards: [["Bramble Wall", 0, 0]], hand: ["Spark"] }), []);
const bramble = minion(g, 0, 0);
expect(() => g.cast("Spark")).toThrow();
expect(g.hand()).toEqual(["Spark"]);
expect(g.health(bramble)).toBe(3);
g.cast("Spark", bramble);
expect(g.health(bramble)).toBe(2);
expect(g.hand()).toEqual([]);
});
test("test_10_level_1_solution", () => {
const g = newSpellGrid(l1 as LevelDef, []);
const keg = minion(g, 0, 4);