diff --git a/course/00-rewrite-trap/lesson.md b/course/00-rewrite-trap/lesson.md
new file mode 100644
index 0000000..13f4a30
--- /dev/null
+++ b/course/00-rewrite-trap/lesson.md
@@ -0,0 +1,267 @@
+# Module 0: The Rewrite Trap
+
+This module is an orientation. You get a small engine that is written the obvious way, you add one famous card to it, and you keep a record of what that cost you. Then you throw the engine away. Your real engine starts in [Module 1](../01-handles/lesson.md).
+
+Module 0 has no Spec, Build or Expansion Drop beats and no reference solution. Nothing here "fixes" the naive engine. The point is to feel the problem before the course names it.
+
+You will need:
+
+- the course repository, checked out;
+- Rust (`cargo`) or Node 24 with npm, whichever track you plan to take. You can try both.
+
+## Observe
+
+Baron Rivendare is a 4-mana 1/7 Legendary minion from Curse of Naxxramas. His card text ([Baron Rivendare, hearthstone.wiki.gg](https://hearthstone.wiki.gg/wiki/Baron_Rivendare)):
+
+> Your minions trigger their Deathrattles twice.
+
+Loot Hoarder is a 2-mana 2/1 with this text ([Loot Hoarder, hearthstone.wiki.gg](https://hearthstone.wiki.gg/wiki/Loot_Hoarder)):
+
+> Deathrattle: Draw a card.
+
+With Baron on your board, a dying Loot Hoarder draws two cards. The wiki says the Deathrattle is queued once and that "the second activation of the Deathrattle is immediately after the first activation resolves" ([Baron Rivendare](https://hearthstone.wiki.gg/wiki/Baron_Rivendare)).
+
+Baron does not change Loot Hoarder, and Loot Hoarder knows nothing about Baron. One card changes how a whole mechanic behaves. Players think of this as a single sentence of card text. In code, that sentence has to land somewhere.
+
+## Predict
+
+Write your answers down before you open the discussion. A file such as `notes/00.md` in your own workspace is fine.
+
+1. Before you read the engine: how many functions do you expect to change to add Baron Rivendare?
+2. You have Acolyte of Pain (played first) and Loot Hoarder on the board. You cast Whirlwind. Write the order of events you expect: each damage, each trigger, each draw, each death.
+
+The three cards involved ([Whirlwind](https://hearthstone.wiki.gg/wiki/Whirlwind), [Acolyte of Pain](https://hearthstone.wiki.gg/wiki/Acolyte_of_Pain), [Loot Hoarder](https://hearthstone.wiki.gg/wiki/Loot_Hoarder)):
+
+- Whirlwind, 1-mana spell: "Deal 1 damage to ALL minions."
+- Acolyte of Pain, 3-mana 1/4: "Whenever this minion takes damage, draw a card."
+- Loot Hoarder, 2-mana 2/1: "Deathrattle: Draw a card."
+
+Discussion: the Whirlwind order
+
+The real game deals all of the damage first, then resolves the damage triggers, and only then processes deaths:
+
+1. Damage to Acolyte of Pain, then damage to Loot Hoarder. For area damage, "all of the Damage/Healing Events are created in play order" and then resolved in play order ([Advanced rulebook](https://hearthstone.wiki.gg/wiki/Advanced_rulebook)). The rulebook's Frothing Berserker example puts it plainly: "all the damage occurs, then each 'On Damage' Event is resolved based on the order of play."
+2. Acolyte of Pain's trigger: draw a card.
+3. Loot Hoarder dies. A minion at 0 Health is only *mortally wounded* until the outermost Phase ends: "mortally wounded and pending destroy are ONLY converted into dead once the outermost Phase ends!" ([Advanced rulebook](https://hearthstone.wiki.gg/wiki/Advanced_rulebook)).
+4. Loot Hoarder's Deathrattle: draw a card. Deathrattles resolve in a Death Phase after the Death Creation Step ([Advanced rulebook](https://hearthstone.wiki.gg/wiki/Advanced_rulebook)).
+
+The naive engine's test suite states exactly this order in `test_00_real_rules_whirlwind_order`:
+
+```rust
+game.summon("Acolyte of Pain"); // played first
+game.summon("Loot Hoarder");
+game.cast("Whirlwind");
+assert_eq!(
+ game.log,
+ vec![
+ "damaged Acolyte of Pain 1",
+ "damaged Loot Hoarder 1",
+ "triggered Acolyte of Pain",
+ "drew a card",
+ "died Loot Hoarder",
+ "deathrattle Loot Hoarder",
+ "drew a card",
+ ]
+);
+```
+
+```ts
+game.summon("Acolyte of Pain"); // played first
+game.summon("Loot Hoarder");
+game.cast("Whirlwind");
+expect(game.log).toEqual([
+ "damaged Acolyte of Pain 1",
+ "damaged Loot Hoarder 1",
+ "triggered Acolyte of Pain",
+ "drew a card",
+ "died Loot Hoarder",
+ "deathrattle Loot Hoarder",
+ "drew a card",
+]);
+```
+
+The naive engine cannot pass it, so the test is marked expected-to-fail (`#[should_panic]` in Rust, `test.fails` in TypeScript). Remove the marker and run the tests to see the order the engine really produces: Acolyte is damaged and draws before Loot Hoarder is even hit. If your prediction matched the engine rather than the game, that is the point of this module. The obvious code gives an order that looks reasonable and is wrong.
+
+
+
+## Run the naive engine
+
+The naive engine lives in `naive-engine/rust/` and `naive-engine/ts/` in the course repository; each has a README. Each is one engine file of about 100 lines plus a test file. Read the engine file end to end before you change anything. It is short.
+
+```sh
+cd naive-engine/rust
+cargo test
+```
+
+```sh
+cd naive-engine/ts
+npm install
+npx vitest run
+```
+
+Both suites should report 2 passing tests: `test_00_naive_runs` passes normally, and `test_00_real_rules_whirlwind_order` passes because it fails as expected.
+
+Note what the engine does:
+
+- Minions are shared references (`Rc>` in Rust, plain objects in TypeScript).
+- `deal_damage` / `dealDamage` lowers Health, runs the minion's damage trigger at once, and if Health is 0 or less, removes the minion and runs its Deathrattle at once.
+- Every card is defined inside the engine, in `make_minion` / `makeMinion` and `cast`.
+
+## Add Baron Rivendare
+
+In the track of your choice, add Baron Rivendare to the naive engine:
+
+- `summon("Baron Rivendare")` puts a 1/7 Baron on the board.
+- While Baron is on the board, Loot Hoarder's Deathrattle happens twice when it dies, so it draws two cards.
+- Write a test for it next to the existing ones (any name you like) and make it pass.
+
+Do the simplest thing that works. Don't redesign the engine; the redesign is the rest of the course.
+
+As you work, keep a log in your notes. Write down every file and every function you touch, and why:
+
+| File | Function | What you changed and why |
+|---|---|---|
+| | | |
+
+> **How the real game differs**
+> The naive engine has one player, no hand and no deck: "draw a card" only counts draws. It resolves damage, triggers and deaths in the wrong order on purpose (see Predict). Baron's "Your minions" has no meaning yet because nothing records who owns a minion. Your real engine fixes all of this from Module 1 on.
+
+## Reflect
+
+Look at your log, then answer these before opening each discussion.
+
+1. Which functions did you change? Is one of them the function that runs Deathrattles? Does the engine now contain the words "Baron Rivendare" outside the card definitions?
+
+Discussion
+
+Most learners end up with a check inside the death code: "if a Baron is on the board, run the Deathrattle again". It works, and the test passes. But the engine now knows about one specific card, and the function that runs Deathrattles is the place every future Deathrattle rule has to be squeezed into.
+
+From Module 1 on, the course enforces the **Zero-Coupling Law**: engine code never names a specific card. A coupling audit checks it. The fix you just wrote would fail that audit.
+
+
+
+2. Now picture the next cards. Brann Bronzebeard: "Your Battlecries trigger twice." ([Brann Bronzebeard](https://hearthstone.wiki.gg/wiki/Brann_Bronzebeard)). A card that doubles only the Deathrattles of one minion type. A card that makes Deathrattles trigger three times. How many of these would need another edit to the same function?
+
+Discussion
+
+Each of them is another branch in the code that runs triggers. They all share a shape ("when a trigger of kind X fires, fire it N times") but the naive engine has nowhere to put a shape. It only has places to put special cases. Module 4 turns "trigger twice" into data that one generic piece of the engine consults.
+
+
+
+3. Put a second Baron Rivendare on the board. What does your engine do when Loot Hoarder dies? What does the real game do?
+
+Discussion
+
+In the real game, "Rivendare's ability is not duplicated or multiplied by having more than one Baron Rivendare in play." ([Baron Rivendare](https://hearthstone.wiki.gg/wiki/Baron_Rivendare)). If your fix loops over the board and fires the Deathrattle once per Baron, you draw three cards instead of two. If you check "is any Baron on the board", you got this one right, perhaps by luck.
+
+
+
+4. Twisting Nether: "Destroy all minions and locations." ([Twisting Nether](https://hearthstone.wiki.gg/wiki/Twisting_Nether)). Suppose you added it to the naive engine the obvious way, killing each minion in board order. Baron and Loot Hoarder are on the board. How many cards do you draw? Does the answer depend on which of them is further left?
+
+Discussion
+
+In the real game you draw one card: "if he dies at the same time as a minion with a Deathrattle, the Deathrattle is not doubled." ([Baron Rivendare](https://hearthstone.wiki.gg/wiki/Baron_Rivendare)). The rules behind this: deaths are processed together when the outermost Phase ends, and Baron's doubling is an aura that is updated after the Death Creation Step, so it is already gone when the Deathrattle resolves ([Advanced rulebook](https://hearthstone.wiki.gg/wiki/Advanced_rulebook)).
+
+In the naive engine there is no "at the same time". Each minion dies alone, the instant its Health hits 0. If Baron dies first you draw one card; if Loot Hoarder dies first you draw two. The result depends on board position, which the real game never consults here.
+
+
+
+5. Why can't you make `test_00_real_rules_whirlwind_order` pass with a small edit?
+
+Discussion
+
+Because one function call does three jobs. In the naive engine, dealing damage *is* running the trigger *is* killing the minion:
+
+```rust
+pub fn deal_damage(&mut self, target: &MinionRef, amount: i32) {
+ // ...lower health, log "damaged"...
+ if let Some(effect) = on_damaged {
+ effect(self, target); // trigger runs now
+ }
+ if target.borrow().health <= 0 {
+ self.kill(target); // death and Deathrattle run now
+ }
+}
+```
+
+```ts
+dealDamage(target: Minion, amount: number): void {
+ // ...lower health, log "damaged"...
+ if (target.onDamaged) {
+ target.onDamaged(this, target); // trigger runs now
+ }
+ if (target.health <= 0) {
+ this.kill(target); // death and Deathrattle run now
+ }
+}
+```
+
+To get the real order you have to separate asking for something from doing it (Module 2) and decide death at defined checkpoints instead of the moment Health reaches 0 (Module 3). Neither fits inside the existing function. Both need a different engine shape.
+
+One more thing to notice: Whirlwind loops over a copy of the board holding direct references. A minion that has already died is still in that copy. If a later effect in the loop hit it again, the engine would happily kill it a second time. Module 1 replaces references with IDs for this reason.
+
+
+
+## The trap in the wild
+
+The naive engine is not a strawman. Two open-source Hearthstone simulators handle Baron Rivendare the same way. Read the linked code; it is not reproduced here.
+
+**Fireplace** (Python, [jleclanche/fireplace](https://github.com/jleclanche/fireplace), read at commit `47a2572`):
+
+- Baron is an aura that sets a player-level flag: `class FP1_031` in [`fireplace/cards/naxxramas/collectible.py`](https://github.com/jleclanche/fireplace/blob/47a2572/fireplace/cards/naxxramas/collectible.py) refreshes `GameTag.EXTRA_DEATHRATTLES` on its controller.
+- The doubling happens inside the Deathrattle action itself: `class Deathrattle` in [`fireplace/actions.py`](https://github.com/jleclanche/fireplace/blob/47a2572/fireplace/actions.py) has an `if target.controller.extra_deathrattles:` branch that queues the Deathrattle's actions a second time.
+
+It works. But it is the same shape as your fix: a special case inside the code that runs Deathrattles. "Trigger three times" or "only for one minion type" means editing that branch again.
+
+**SabberStone** (C#, [HearthSim/SabberStone](https://github.com/HearthSim/SabberStone), `master`, last commit January 2021):
+
+- Baron's aura sets `GameTag.EXTRA_MINION_DEATHRATTLES_BASE`: [`SabberStoneCore/src/CardSets/NaxxCardsGen.cs`](https://github.com/HearthSim/SabberStone/blob/master/SabberStoneCore/src/CardSets/NaxxCardsGen.cs).
+- The Deathrattle task checks a *different* tag, `GameTag.EXTRA_DEATHRATTLES_BASE`, before running the Deathrattle again: [`SabberStoneCore/src/Tasks/SimpleTasks/ActivateDeathrattleTask.cs`](https://github.com/HearthSim/SabberStone/blob/master/SabberStoneCore/src/Tasks/SimpleTasks/ActivateDeathrattleTask.cs).
+- The controller aura lookup in [`SabberStoneCore/src/Enchants/AuraEffects.cs`](https://github.com/HearthSim/SabberStone/blob/master/SabberStoneCore/src/Enchants/AuraEffects.cs) has no case for either tag.
+- The Rivendare test in [`SabberStoneCoreTest/src/CardSets/NaxxCardsGenTest.cs`](https://github.com/HearthSim/SabberStone/blob/master/SabberStoneCoreTest/src/CardSets/NaxxCardsGenTest.cs) is marked `[Fact(Skip = "ignore")]`.
+
+From reading the code, the doubling appears broken; its Rivendare test is skipped. (We have not built or run SabberStone to confirm.) The producer of the rule and its consumer are two hand-written special cases in different files, and nothing connects them except a tag name that has to match by hand.
+
+Both projects are written by capable people. The trap is not carelessness. It is what you get when "trigger twice" has nowhere to live except inside the code that runs triggers.
+
+## Before Module 1: choose your track
+
+Your real engine is a new project, built from a template. The naive engine stays behind.
+
+**Pick a track.** Rust or TypeScript. The tests, test names and lessons are the same in both. Choose the language you would rather think in. Rust's borrow checker pushes you towards IDs and explicit ownership early, which suits this course. TypeScript lets you move faster and makes it easier to fall into the habits this course warns about, which is also instructive.
+
+**Copy the template** to a directory outside the course repository, and put it under git. Expansion Drops are checked with `git diff`, so your workspace needs its own history.
+
+```sh
+cp -r workspace-template/rust ~/my-engine
+cd ~/my-engine
+git init && git add -A && git commit -m "start from template"
+cargo test
+```
+
+```sh
+cp -r workspace-template/ts ~/my-engine
+cd ~/my-engine
+git init && git add -A && git commit -m "start from template"
+npm install
+npm test -- --passWithNoTests
+```
+
+Copy the whole directory with `cp -r` so the hidden `.course.json` file (which records your track) comes along. With no tests yet, `cargo test` reports 0 tests, and `npm test` needs `--passWithNoTests` to exit cleanly.
+
+**Read the template's README.** Your engine goes in `src/engine/`, your cards in `src/content/`, and `src/harness` is the thin adapter the course's tests call.
+
+**When you start Module 1**, run this from the course repository to copy its tests into your workspace:
+
+```sh
+node tools/start-module.mjs 01 ~/my-engine
+```
+
+Module 0 is not started this way; `start-module` refuses `00`, because this module uses the naive engine instead of your workspace.
+
+Keep your Module 0 log. You will want it when you reach Module 4 and add Baron Rivendare again, this time without touching the engine.
+
+## Further reading
+
+- [Advanced rulebook, hearthstone.wiki.gg](https://hearthstone.wiki.gg/wiki/Advanced_rulebook): the community's specification of Sequences, Phases, Queues and death processing. Skim the sections on Phases and the Death Creation Step now; Modules 2 to 4 go through them in detail.
+- [Baron Rivendare, hearthstone.wiki.gg](https://hearthstone.wiki.gg/wiki/Baron_Rivendare): the card page, with notes on when the doubling applies.
diff --git a/naive-engine/rust/Cargo.lock b/naive-engine/rust/Cargo.lock
new file mode 100644
index 0000000..c50396f
--- /dev/null
+++ b/naive-engine/rust/Cargo.lock
@@ -0,0 +1,7 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+version = 4
+
+[[package]]
+name = "naive_engine"
+version = "0.1.0"
diff --git a/naive-engine/rust/Cargo.toml b/naive-engine/rust/Cargo.toml
new file mode 100644
index 0000000..e8b3b77
--- /dev/null
+++ b/naive-engine/rust/Cargo.toml
@@ -0,0 +1,7 @@
+[package]
+name = "naive_engine"
+version = "0.1.0"
+edition = "2021"
+license = "MIT"
+
+[dependencies]
diff --git a/naive-engine/rust/README.md b/naive-engine/rust/README.md
new file mode 100644
index 0000000..22a6fe6
--- /dev/null
+++ b/naive-engine/rust/README.md
@@ -0,0 +1,16 @@
+# Naive engine: Rust
+
+The throwaway engine for [Module 0, The Rewrite Trap](../../course/00-rewrite-trap/lesson.md). It is written the obvious way on purpose: minions are shared references, damage applies at once, triggers fire inline, and a Deathrattle runs the instant a minion's Health reaches 0. Cards are defined inside the engine (`make_minion` and `cast` in `src/lib.rs`).
+
+Run the tests from this directory:
+
+```
+cargo test
+```
+
+You should see two passing tests:
+
+- `test_00_naive_runs` passes normally.
+- `test_00_real_rules_whirlwind_order - should panic ... ok` is expected to fail. It states the real game's event order, which this engine cannot produce, so it is marked `#[should_panic]`. To see the order the engine actually produces, delete the `#[should_panic(...)]` line and run `cargo test` again.
+
+Do not use this crate in Module 1. Your real engine starts from `workspace-template/rust/`.
diff --git a/naive-engine/rust/src/lib.rs b/naive-engine/rust/src/lib.rs
new file mode 100644
index 0000000..4cc60cc
--- /dev/null
+++ b/naive-engine/rust/src/lib.rs
@@ -0,0 +1,116 @@
+//! The naive engine for Module 0, The Rewrite Trap.
+//!
+//! It is written the "obvious" way on purpose, and it is wrong in ways worth
+//! studying. Read course/00-rewrite-trap/lesson.md before you change it.
+//! You throw this engine away at the end of Module 0.
+
+use std::cell::RefCell;
+use std::rc::Rc;
+
+/// A minion is shared by reference: the board and anyone holding it see the same one.
+pub type MinionRef = Rc>;
+
+/// What a card does, written as a plain function the engine calls directly.
+pub type Effect = fn(&mut Game, &MinionRef);
+
+pub struct Minion {
+ pub name: String,
+ pub attack: i32,
+ pub health: i32,
+ /// Called the moment this minion takes damage.
+ pub on_damaged: Option,
+ /// Called the moment this minion dies.
+ pub deathrattle: Option,
+}
+
+#[derive(Default)]
+pub struct Game {
+ /// The minions on the board, left to right. There is only one player.
+ pub board: Vec,
+ pub cards_drawn: u32,
+ /// What happened, in order, as plain text.
+ pub log: Vec,
+}
+
+impl Game {
+ pub fn new() -> Self {
+ Self::default()
+ }
+
+ /// Puts a minion on the right end of the board and returns it.
+ pub fn summon(&mut self, card: &str) -> MinionRef {
+ let minion = Rc::new(RefCell::new(make_minion(card)));
+ self.board.push(Rc::clone(&minion));
+ minion
+ }
+
+ pub fn cast(&mut self, card: &str) {
+ match card {
+ // Deal 1 damage to ALL minions.
+ "Whirlwind" => {
+ for minion in self.board.clone() {
+ self.deal_damage(&minion, 1);
+ }
+ }
+ other => panic!("unknown spell: {other}"),
+ }
+ }
+
+ /// Damage happens now. The damage trigger fires now. Death happens now.
+ pub fn deal_damage(&mut self, target: &MinionRef, amount: i32) {
+ let (name, on_damaged) = {
+ let mut minion = target.borrow_mut();
+ minion.health -= amount;
+ (minion.name.clone(), minion.on_damaged)
+ };
+ self.log.push(format!("damaged {name} {amount}"));
+ if let Some(effect) = on_damaged {
+ self.log.push(format!("triggered {name}"));
+ effect(self, target);
+ }
+ if target.borrow().health <= 0 {
+ self.kill(target);
+ }
+ }
+
+ /// Removes the minion and runs its Deathrattle straight away.
+ fn kill(&mut self, target: &MinionRef) {
+ self.board.retain(|minion| !Rc::ptr_eq(minion, target));
+ let (name, deathrattle) = {
+ let minion = target.borrow();
+ (minion.name.clone(), minion.deathrattle)
+ };
+ self.log.push(format!("died {name}"));
+ if let Some(effect) = deathrattle {
+ self.log.push(format!("deathrattle {name}"));
+ effect(self, target);
+ }
+ }
+
+ pub fn draw(&mut self) {
+ self.cards_drawn += 1;
+ self.log.push("drew a card".to_string());
+ }
+
+ pub fn board_names(&self) -> Vec {
+ self.board.iter().map(|minion| minion.borrow().name.clone()).collect()
+ }
+}
+
+/// Every minion the game knows. A new card means a new branch here.
+fn make_minion(card: &str) -> Minion {
+ let stats = |attack, health| Minion {
+ name: card.to_string(),
+ attack,
+ health,
+ on_damaged: None,
+ deathrattle: None,
+ };
+ match card {
+ // Whenever this minion takes damage, draw a card.
+ "Acolyte of Pain" => Minion { on_damaged: Some(|game, _| game.draw()), ..stats(1, 4) },
+ // Deathrattle: Draw a card.
+ "Loot Hoarder" => Minion { deathrattle: Some(|game, _| game.draw()), ..stats(2, 1) },
+ other => panic!("unknown minion: {other}"),
+ }
+}
diff --git a/naive-engine/rust/tests/test_00.rs b/naive-engine/rust/tests/test_00.rs
new file mode 100644
index 0000000..b3f3989
--- /dev/null
+++ b/naive-engine/rust/tests/test_00.rs
@@ -0,0 +1,36 @@
+//! Module 0 tests for the naive engine. See course/00-rewrite-trap/lesson.md.
+use naive_engine::Game;
+
+#[test]
+fn test_00_naive_runs() {
+ let mut game = Game::new();
+ let hoarder = game.summon("Loot Hoarder");
+ game.deal_damage(&hoarder, 1);
+ assert!(game.board_names().is_empty(), "Loot Hoarder should be gone");
+ assert_eq!(game.cards_drawn, 1, "its Deathrattle draws one card");
+}
+
+// EXPECTED TO FAIL. This test states the order the real game uses (all damage
+// first, then damage triggers in order of play, then deaths and Deathrattles).
+// The naive engine cannot produce it. Read course/00-rewrite-trap/lesson.md,
+// "Predict", before changing anything here.
+#[test]
+#[should_panic(expected = "left == right")]
+fn test_00_real_rules_whirlwind_order() {
+ let mut game = Game::new();
+ game.summon("Acolyte of Pain"); // played first
+ game.summon("Loot Hoarder");
+ game.cast("Whirlwind");
+ assert_eq!(
+ game.log,
+ vec![
+ "damaged Acolyte of Pain 1",
+ "damaged Loot Hoarder 1",
+ "triggered Acolyte of Pain",
+ "drew a card",
+ "died Loot Hoarder",
+ "deathrattle Loot Hoarder",
+ "drew a card",
+ ]
+ );
+}
diff --git a/naive-engine/ts/README.md b/naive-engine/ts/README.md
new file mode 100644
index 0000000..a0e86ef
--- /dev/null
+++ b/naive-engine/ts/README.md
@@ -0,0 +1,19 @@
+# Naive engine: TypeScript
+
+The throwaway engine for [Module 0, The Rewrite Trap](../../course/00-rewrite-trap/lesson.md). It is written the obvious way on purpose: minions are shared object references, damage applies at once, triggers fire inline, and a Deathrattle runs the instant a minion's Health reaches 0. Cards are defined inside the engine (`makeMinion` and `cast` in `src/engine.ts`).
+
+Run the tests from this directory:
+
+```
+npm install
+npx vitest run
+```
+
+You should see two passing tests:
+
+- `test_00_naive_runs` passes normally.
+- `test_00_real_rules_whirlwind_order` is expected to fail. It states the real game's event order, which this engine cannot produce, so it is marked `test.fails`. To see the order the engine actually produces, change `test.fails(` to `test(` and run the tests again.
+
+`npm run typecheck` runs the TypeScript compiler.
+
+Do not use this package in Module 1. Your real engine starts from `workspace-template/ts/`.
diff --git a/naive-engine/ts/package-lock.json b/naive-engine/ts/package-lock.json
new file mode 100644
index 0000000..c884366
--- /dev/null
+++ b/naive-engine/ts/package-lock.json
@@ -0,0 +1,1497 @@
+{
+ "name": "naive-engine",
+ "version": "0.1.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "naive-engine",
+ "version": "0.1.0",
+ "license": "MIT",
+ "devDependencies": {
+ "typescript": "^5.6.0",
+ "vitest": "^2.1.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz",
+ "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz",
+ "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz",
+ "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz",
+ "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz",
+ "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz",
+ "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz",
+ "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz",
+ "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz",
+ "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz",
+ "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz",
+ "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz",
+ "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==",
+ "cpu": [
+ "mips64el"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz",
+ "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz",
+ "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz",
+ "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz",
+ "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz",
+ "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz",
+ "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz",
+ "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz",
+ "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz",
+ "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.6.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.6.0.tgz",
+ "integrity": "sha512-T7jf+5zgsZHwNJ4lvQ7/aezbyk0nNX+zJVWpmHA7VYsEx7a7qr5Rg5IbtJFqkgze5Y2sruq1RUY8Q837Od7iFw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@napi-rs/lzma-linux-x64-gnu": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/@napi-rs/lzma-linux-x64-gnu/-/lzma-linux-x64-gnu-1.5.1.tgz",
+ "integrity": "sha512-oTXEIha4SsuXdTA4Iyskj0kpdx2yVXdhd75c2v3xGrHFfVMsbhTPZU/nMPL4sWKo4pBHm3aucLaqGlF696dTyQ==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": "^22.20 || ^24.12 || >=25"
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.66.0.tgz",
+ "integrity": "sha512-7y4XeoUHPecjfUDRmap8U0q6bjfLAPn7uOGT8BKyqy8uNDOjsGS8rlBhOx6sZLVwwjN6hapvGl2mW9/IS6UNww==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.66.0.tgz",
+ "integrity": "sha512-e8WugYZXpgqrlMXLCudYVRAxAcK7AIY36JxRDwFRD38L9q62G1VsEUW6ijXxzjxYjNB8i1zo3B/ycIQ/qjLC/Q==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.66.0.tgz",
+ "integrity": "sha512-6vkDCfyDjNLokh+Rt1D2KtNgFFMEPdkWha/mbtP4cSjh9xiN8T/JBS/A7nDYKjmMAXHrvR9J3AzUk2LtFbIBLg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.66.0.tgz",
+ "integrity": "sha512-/mzZAX0+Q6eXxvkR0HB0RZCCrsbmFD8DpsvCQoMYABDfq/DjqkbfytuJ7GvZ4AhR/Mgis2fXbZrGqfbcK2Z5fA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.66.0.tgz",
+ "integrity": "sha512-JQJx9E8tUjGqed1RevDmoHaS3V30ODdV8uyvGxlwd4K5se6bkPrMeltSybqAtpJ+ltDRfo7FORQaJG2Hd5nGog==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-freebsd-x64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.66.0.tgz",
+ "integrity": "sha512-GEaUQQjMaG7X1304qMgqLjzCKv6CdDirzJSS+UqUs3xhJ9X2Kjzu0JDbLoUU42FWxwmZqz+SsgU1SlQv9GNRVA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "freebsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.66.0.tgz",
+ "integrity": "sha512-DgmAU9N0NhDQDzHgFUocDio7zRO8jR/nY/obDGvCR8zbJ6J77Fg6X49yPuExJUpYPbC82EOaRhG969pWzUSnHQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-musleabihf": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.66.0.tgz",
+ "integrity": "sha512-zG09YZ+sk/HoHq+P9gYB0EseQ3M20RbDuKGWey6fbiMDV1LxfXvhgSW9jDaCcvFPMgfLXpimzXY+UYvA8R2mLQ==",
+ "cpu": [
+ "arm"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.66.0.tgz",
+ "integrity": "sha512-vlArhgyXbcVc1D4WZPoiYuqJIaMiSHBj6etRXua0KlfjlZeuiQyVAPYi0/N49O4mSp3u1iGUBLEjNa8ec81XZg==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.66.0.tgz",
+ "integrity": "sha512-RxJZtk0G87YPotDaRDvRqStV8cbqLSC5TroHvQHHKsqBg0ok64RxKgtXeFsa7FBcy7mb4crBrG3xK8USWIqWNA==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.66.0.tgz",
+ "integrity": "sha512-qlGcw959PrLLkZhiBxF3OZ6SQvj+OKY85mCqGNgSRs4LZlDJ6ICqmz6UdxtINdhcXYwNIbZoapQVBAmZlD2hUQ==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-loong64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.66.0.tgz",
+ "integrity": "sha512-TvTI/7Cy0WvRA1SXgwzu6bwXBnMpmeT/ln5q7WvSpqmXy1VbmEnY8HVIDQNBiaHJvoWqRr0M4FNo7qiqtyMaFw==",
+ "cpu": [
+ "loong64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.66.0.tgz",
+ "integrity": "sha512-UZEDkSoDRZ50NyLj70nO6mpIK3dvs2cPTTvQb1bA/PBDmr9fvn5KiESuGfh6srd7Zi6Lt9Sv2sVrFr1QoHLvPQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-ppc64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.66.0.tgz",
+ "integrity": "sha512-VRbxTvagLh3HRcysx1CNq+HHcvJbSebNI6fCbk7jwmmWHkXjrSIV3igXBpRHUTFdg0pagWGu50xoSZXnrZHjVQ==",
+ "cpu": [
+ "ppc64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.66.0.tgz",
+ "integrity": "sha512-uWdJpvYqh0LE/i+YBL3sMX0a2kUkICENjmrPmyBmMOFc3jMrs6MSQH/pMD+VIpGEoUcr3XkHGLLfymzIqPSRRg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.66.0.tgz",
+ "integrity": "sha512-3FUd88pxgWAjK60OgCrika1AcNG+u8zaCdWjg4TlcZTDkt5TGyci8ZU64l3OJ9ax0MEjw3P80B/3PoSQDCLwJQ==",
+ "cpu": [
+ "riscv64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-s390x-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.66.0.tgz",
+ "integrity": "sha512-qT2rMmgyej62g5AdwktK+Bt2lBu497pzWZe12TTQ5JmZjOYPnEwcPEAOSLTBxBV+6gIES75rXm219v/QkT397A==",
+ "cpu": [
+ "s390x"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.66.0.tgz",
+ "integrity": "sha512-E46rnZgp7J84Qwe8RMlF+rgimnDo/9fuiGbUArGB0ZkMWf97O7yeSCfKls5x/YJOF0RcuSe9RctuKe1hlxICiA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "glibc"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.66.0.tgz",
+ "integrity": "sha512-Uz1HiZe+qBzqgdPsON+z0wRx3WdATuyzwvGT6cr2VZVrqqko0ZJVfkrQJtwDMuDcAfXYdo7XucaxJ7lTLjEcgg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "libc": [
+ "musl"
+ ],
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-openbsd-x64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.66.0.tgz",
+ "integrity": "sha512-Ph7TX3Ex5qdfcb7Tc6eEPugVkRfJ501eTovO4T5029YObfpZ7dzn3RiwpFvGp50nk2QUhvBMZ0PFGlEj6pOYTg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openbsd"
+ ]
+ },
+ "node_modules/@rollup/rollup-openharmony-arm64": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.66.0.tgz",
+ "integrity": "sha512-nZYPEya7ZkeNxUEiF7qmNZbyLTyRN3MK5cO22KqsaoneUzXJuzBIQRw+7BEQy9LGj2O5vrCE9V3EpK06FYo1tQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "openharmony"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.66.0.tgz",
+ "integrity": "sha512-u7aTfS3R6C4hRsqXSzaO6qLZYLVuMkHMvcWlux4IM8IJif1VCl2cgPBSo5gSNoFoStQypQP+qwq8W1IyFBf1QQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.66.0.tgz",
+ "integrity": "sha512-e+i9ueHrH1Bafb0cf0woAIVJbYcoC8vRaD+vz+Xi8w6jz9cYJSV3vyKO6ELGSZ6g4W4MJOm2WPLN4/nQf77h7w==",
+ "cpu": [
+ "ia32"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-gnu": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.66.0.tgz",
+ "integrity": "sha512-KfTCbrtr2zXTqH2E1F+uYlFTrYibxHKqPAHobpd8Kfg49cEHmuHebOidCTDRgqG1suaPFX7oejFQeGE71zWKfA==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.66.0.tgz",
+ "integrity": "sha512-xA+neBh1bqkqaS3b48T2UHZnDsTIpb9B0h+TdBzynjF/ymCpYndhfY10RqW0qSKB1HqqQA83mINVhAdwA+mETg==",
+ "cpu": [
+ "x64"
+ ],
+ "dev": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.9",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.9.tgz",
+ "integrity": "sha512-GhdPgy1el4/ImP05X05Uw4cw2/M93BCUmnEvWZNStlCzEKME4Fkk+YpoA5OiHNQmoS7Cafb8Xa3Pya8m1Qrzeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/@vitest/expect": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-2.1.9.tgz",
+ "integrity": "sha512-UJCIkTBenHeKT1TTlKMJWy1laZewsRIzYighyYiJKZreqtdxSos/S1t+ktRMQWu2CKqaarrkeszJx1cgC5tGZw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/mocker": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/mocker/-/mocker-2.1.9.tgz",
+ "integrity": "sha512-tVL6uJgoUdi6icpxmdrn5YNo3g3Dxv+IHJBr0GXHaEdTcw3F+cPKnsXFhli6nO+f/6SDKPHEK1UN+k+TQv0Ehg==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/spy": "2.1.9",
+ "estree-walker": "^3.0.3",
+ "magic-string": "^0.30.12"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "msw": "^2.4.9",
+ "vite": "^5.0.0"
+ },
+ "peerDependenciesMeta": {
+ "msw": {
+ "optional": true
+ },
+ "vite": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vitest/pretty-format": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/pretty-format/-/pretty-format-2.1.9.tgz",
+ "integrity": "sha512-KhRIdGV2U9HOUzxfiHmY8IFHTdqtOhIzCpd8WRdJiE7D/HUcZVD0EgQCVjm+Q9gkUXWgBvMmTtZgIG48wq7sOQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/runner": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-2.1.9.tgz",
+ "integrity": "sha512-ZXSSqTFIrzduD63btIfEyOmNcBmQvgOVsPNPe0jYtESiXkhd8u2erDLnMxmGrDCwHCCHE7hxwRDCT3pt0esT4g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/utils": "2.1.9",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/snapshot": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-2.1.9.tgz",
+ "integrity": "sha512-oBO82rEjsxLNJincVhLhaxxZdEtV0EFHMK5Kmx5sJ6H9L183dHECjiefOAdnqpIgT5eZwT04PoggUnW88vOBNQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/spy": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-2.1.9.tgz",
+ "integrity": "sha512-E1B35FwzXXTs9FHNK6bDszs7mtydNi5MIfUWpceJ8Xbfb1gBMscAnwLbEu+B44ed6W3XjL9/ehLPHR1fkf1KLQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "tinyspy": "^3.0.2"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/@vitest/utils": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-2.1.9.tgz",
+ "integrity": "sha512-v0psaMSkNJ3A2NMrUEHFRzJtDPFn+/VWZ5WxImB21T9fjucJRmS7xCS3ppEnARb9y11OAzaD+P2Ps+b+BGX5iQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/pretty-format": "2.1.9",
+ "loupe": "^3.1.2",
+ "tinyrainbow": "^1.2.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/assertion-error": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz",
+ "integrity": "sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/cac": {
+ "version": "6.7.14",
+ "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz",
+ "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/chai": {
+ "version": "5.3.3",
+ "resolved": "https://registry.npmjs.org/chai/-/chai-5.3.3.tgz",
+ "integrity": "sha512-4zNhdJD/iOjSH0A05ea+Ke6MU5mmpQcbQsSOkgdaUMJ9zTlDTD/GYlwohmIE2u0gaxHYiVHEn1Fw9mZ/ktJWgw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "assertion-error": "^2.0.1",
+ "check-error": "^2.1.1",
+ "deep-eql": "^5.0.1",
+ "loupe": "^3.1.0",
+ "pathval": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=18"
+ }
+ },
+ "node_modules/check-error": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/check-error/-/check-error-2.1.3.tgz",
+ "integrity": "sha512-PAJdDJusoxnwm1VwW07VWwUN1sl7smmC3OKggvndJFadxxDRyFJBX/ggnu/KE4kQAB7a3Dp8f/YXC1FlUprWmA==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 16"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.4.3",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz",
+ "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "ms": "^2.1.3"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/deep-eql": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-5.0.2.tgz",
+ "integrity": "sha512-h5k/5U50IJJFpzfL6nO9jaaumfjO/f2NjK/oYB2Djzm4p9L+3T9qWpZqZ2hAbLPuuYq9wrU08WQyBTL5GbPk5Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/es-module-lexer": {
+ "version": "1.7.0",
+ "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-1.7.0.tgz",
+ "integrity": "sha512-jEQoCwk8hyb2AZziIOLhDqpm5+2ww5uIE6lkO/6jcOCusfk6LhMHpXXfBLXTZ7Ydyt0j4VoUQv6uGNYbdW+kBA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/esbuild": {
+ "version": "0.21.5",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz",
+ "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.21.5",
+ "@esbuild/android-arm": "0.21.5",
+ "@esbuild/android-arm64": "0.21.5",
+ "@esbuild/android-x64": "0.21.5",
+ "@esbuild/darwin-arm64": "0.21.5",
+ "@esbuild/darwin-x64": "0.21.5",
+ "@esbuild/freebsd-arm64": "0.21.5",
+ "@esbuild/freebsd-x64": "0.21.5",
+ "@esbuild/linux-arm": "0.21.5",
+ "@esbuild/linux-arm64": "0.21.5",
+ "@esbuild/linux-ia32": "0.21.5",
+ "@esbuild/linux-loong64": "0.21.5",
+ "@esbuild/linux-mips64el": "0.21.5",
+ "@esbuild/linux-ppc64": "0.21.5",
+ "@esbuild/linux-riscv64": "0.21.5",
+ "@esbuild/linux-s390x": "0.21.5",
+ "@esbuild/linux-x64": "0.21.5",
+ "@esbuild/netbsd-x64": "0.21.5",
+ "@esbuild/openbsd-x64": "0.21.5",
+ "@esbuild/sunos-x64": "0.21.5",
+ "@esbuild/win32-arm64": "0.21.5",
+ "@esbuild/win32-ia32": "0.21.5",
+ "@esbuild/win32-x64": "0.21.5"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz",
+ "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "^1.0.0"
+ }
+ },
+ "node_modules/expect-type": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/expect-type/-/expect-type-1.4.0.tgz",
+ "integrity": "sha512-KfYbmpRm0VbLjEvVa9yGwCi9GI34xvi7A/HXYWQO65CSD2u3MczUJSuwXKFIxlGsgBQizV9q5J9NHj4VG0n+pA==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "engines": {
+ "node": ">=12.0.0"
+ }
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "dev": true,
+ "hasInstallScript": true,
+ "license": "MIT",
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/loupe": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/loupe/-/loupe-3.2.1.tgz",
+ "integrity": "sha512-CdzqowRJCeLU72bHvWqwRBBlLcMEtIvGrlvef74kMnV2AolS9Y8xUv1I0U/MNAWMhBlKIoyuEgoJ0t/bbwHbLQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.21",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz",
+ "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.5.5"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.20",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz",
+ "integrity": "sha512-uKdg2G3GNCKQn9byYOpxbGqrT2fGO5KRt5J/8b3pok8rT6qxGWF6hxMyJiEYtAf+FVyYuD9hRaDqX5uPFYJ4ZQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/pathe": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz",
+ "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/pathval": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/pathval/-/pathval-2.0.1.tgz",
+ "integrity": "sha512-//nshmD55c46FuFw26xV/xFAaB5HF9Xdap7HJBBnrKdAd6/GxDBaNA1870O79+9ueg61cZLSVc+OaFlfmObYVQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">= 14.16"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz",
+ "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/postcss": {
+ "version": "8.5.32",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.32.tgz",
+ "integrity": "sha512-LJlKCCAUFKOuZxipUdYw1NzIITEsPcgyOG9LkvRJvuKj/bRzbuRY0ARXvL0pWGpsZvn8jku+LMep2eU0seHp8g==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "nanoid": "^3.3.20",
+ "picocolors": "^1.1.1",
+ "source-map-js": "^1.2.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.66.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.66.0.tgz",
+ "integrity": "sha512-Onq6/qO4ARhxIs+jYiHE0pYehadLyMECAiGEqQ+0/aWND3+2PmifJIIqBLYtuBODqHTlOUEQFtIVPyCy0Lck7A==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@types/estree": "1.0.9"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@napi-rs/lzma-linux-x64-gnu": "1.5.1",
+ "@rollup/rollup-android-arm-eabi": "4.66.0",
+ "@rollup/rollup-android-arm64": "4.66.0",
+ "@rollup/rollup-darwin-arm64": "4.66.0",
+ "@rollup/rollup-darwin-x64": "4.66.0",
+ "@rollup/rollup-freebsd-arm64": "4.66.0",
+ "@rollup/rollup-freebsd-x64": "4.66.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.66.0",
+ "@rollup/rollup-linux-arm-musleabihf": "4.66.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.66.0",
+ "@rollup/rollup-linux-arm64-musl": "4.66.0",
+ "@rollup/rollup-linux-loong64-gnu": "4.66.0",
+ "@rollup/rollup-linux-loong64-musl": "4.66.0",
+ "@rollup/rollup-linux-ppc64-gnu": "4.66.0",
+ "@rollup/rollup-linux-ppc64-musl": "4.66.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.66.0",
+ "@rollup/rollup-linux-riscv64-musl": "4.66.0",
+ "@rollup/rollup-linux-s390x-gnu": "4.66.0",
+ "@rollup/rollup-linux-x64-gnu": "4.66.0",
+ "@rollup/rollup-linux-x64-musl": "4.66.0",
+ "@rollup/rollup-openbsd-x64": "4.66.0",
+ "@rollup/rollup-openharmony-arm64": "4.66.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.66.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.66.0",
+ "@rollup/rollup-win32-x64-gnu": "4.66.0",
+ "@rollup/rollup-win32-x64-msvc": "4.66.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/siginfo": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz",
+ "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==",
+ "dev": true,
+ "license": "ISC"
+ },
+ "node_modules/source-map-js": {
+ "version": "1.2.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.2.tgz",
+ "integrity": "sha512-KGj/8Y43x35aZVDtt+J4mK1hoLGHULMYfSkODJNQjNDC3oW1PqPoxMwo0pLUsWM/UEGzON/NxeHywEfNXNP3Vw==",
+ "dev": true,
+ "license": "BSD-3-Clause",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/stackback": {
+ "version": "0.0.2",
+ "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz",
+ "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/std-env": {
+ "version": "3.10.0",
+ "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz",
+ "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinybench": {
+ "version": "2.9.0",
+ "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz",
+ "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinyexec": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/tinyexec/-/tinyexec-0.3.2.tgz",
+ "integrity": "sha512-KQQR9yN7R5+OSwaK0XQoj22pwHoTlgYqmUscPYoknOoWCWfj/5/ABTMRi69FrKU5ffPVh5QcFikpWJI/P1ocHA==",
+ "dev": true,
+ "license": "MIT"
+ },
+ "node_modules/tinypool": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-1.1.1.tgz",
+ "integrity": "sha512-Zba82s87IFq9A9XmjiX5uZA/ARWDrB03OHlq+Vw1fSdt0I+4/Kutwy8BP4Y/y/aORMo61FQ0vIb5j44vSo5Pkg==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ }
+ },
+ "node_modules/tinyrainbow": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/tinyrainbow/-/tinyrainbow-1.2.0.tgz",
+ "integrity": "sha512-weEDEq7Z5eTHPDh4xjX789+fHfF+P8boiFB+0vbWzpbnbsEr/GRaohi/uMKxg8RZMXnl1ItAi/IUHWMsjDV7kQ==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/tinyspy": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-3.0.2.tgz",
+ "integrity": "sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==",
+ "dev": true,
+ "license": "MIT",
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "5.9.3",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz",
+ "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==",
+ "dev": true,
+ "license": "Apache-2.0",
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=14.17"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.4.21",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz",
+ "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "esbuild": "^0.21.3",
+ "postcss": "^8.4.43",
+ "rollup": "^4.20.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "sass-embedded": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "sass-embedded": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vite-node": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-2.1.9.tgz",
+ "integrity": "sha512-AM9aQ/IPrW/6ENLQg3AGY4K1N2TGZdR5e4gu/MmmR2xR3Ll1+dib+nook92g4TV3PXVyeyxdWwtaCAiUL0hMxA==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "cac": "^6.7.14",
+ "debug": "^4.3.7",
+ "es-module-lexer": "^1.5.4",
+ "pathe": "^1.1.2",
+ "vite": "^5.0.0"
+ },
+ "bin": {
+ "vite-node": "vite-node.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ }
+ },
+ "node_modules/vitest": {
+ "version": "2.1.9",
+ "resolved": "https://registry.npmjs.org/vitest/-/vitest-2.1.9.tgz",
+ "integrity": "sha512-MSmPM9REYqDGBI8439mA4mWhV5sKmDlBKWIYbA3lRb2PTHACE0mgKwA8yQ2xq9vxDTuk4iPrECBAEW2aoFXY0Q==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "@vitest/expect": "2.1.9",
+ "@vitest/mocker": "2.1.9",
+ "@vitest/pretty-format": "^2.1.9",
+ "@vitest/runner": "2.1.9",
+ "@vitest/snapshot": "2.1.9",
+ "@vitest/spy": "2.1.9",
+ "@vitest/utils": "2.1.9",
+ "chai": "^5.1.2",
+ "debug": "^4.3.7",
+ "expect-type": "^1.1.0",
+ "magic-string": "^0.30.12",
+ "pathe": "^1.1.2",
+ "std-env": "^3.8.0",
+ "tinybench": "^2.9.0",
+ "tinyexec": "^0.3.1",
+ "tinypool": "^1.0.1",
+ "tinyrainbow": "^1.2.0",
+ "vite": "^5.0.0",
+ "vite-node": "2.1.9",
+ "why-is-node-running": "^2.3.0"
+ },
+ "bin": {
+ "vitest": "vitest.mjs"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/vitest"
+ },
+ "peerDependencies": {
+ "@edge-runtime/vm": "*",
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "@vitest/browser": "2.1.9",
+ "@vitest/ui": "2.1.9",
+ "happy-dom": "*",
+ "jsdom": "*"
+ },
+ "peerDependenciesMeta": {
+ "@edge-runtime/vm": {
+ "optional": true
+ },
+ "@types/node": {
+ "optional": true
+ },
+ "@vitest/browser": {
+ "optional": true
+ },
+ "@vitest/ui": {
+ "optional": true
+ },
+ "happy-dom": {
+ "optional": true
+ },
+ "jsdom": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/why-is-node-running": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz",
+ "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==",
+ "dev": true,
+ "license": "MIT",
+ "dependencies": {
+ "siginfo": "^2.0.0",
+ "stackback": "0.0.2"
+ },
+ "bin": {
+ "why-is-node-running": "cli.js"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ }
+ }
+}
diff --git a/naive-engine/ts/package.json b/naive-engine/ts/package.json
new file mode 100644
index 0000000..ed8376f
--- /dev/null
+++ b/naive-engine/ts/package.json
@@ -0,0 +1,15 @@
+{
+ "name": "naive-engine",
+ "version": "0.1.0",
+ "private": true,
+ "type": "module",
+ "license": "MIT",
+ "scripts": {
+ "test": "vitest run",
+ "typecheck": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "typescript": "^5.6.0",
+ "vitest": "^2.1.0"
+ }
+}
diff --git a/naive-engine/ts/src/engine.ts b/naive-engine/ts/src/engine.ts
new file mode 100644
index 0000000..dca6a94
--- /dev/null
+++ b/naive-engine/ts/src/engine.ts
@@ -0,0 +1,93 @@
+// The naive engine for Module 0, The Rewrite Trap.
+//
+// It is written the "obvious" way on purpose, and it is wrong in ways worth
+// studying. Read course/00-rewrite-trap/lesson.md before you change it.
+// You throw this engine away at the end of Module 0.
+
+/** What a card does, written as a plain function the engine calls directly. */
+export type Effect = (game: Game, self: Minion) => void;
+
+/** A minion is shared by reference: the board and anyone holding it see the same object. */
+export interface Minion {
+ name: string;
+ attack: number;
+ health: number;
+ /** Called the moment this minion takes damage. */
+ onDamaged?: Effect;
+ /** Called the moment this minion dies. */
+ deathrattle?: Effect;
+}
+
+export class Game {
+ /** The minions on the board, left to right. There is only one player. */
+ board: Minion[] = [];
+ cardsDrawn = 0;
+ /** What happened, in order, as plain text. */
+ log: string[] = [];
+
+ /** Puts a minion on the right end of the board and returns it. */
+ summon(card: string): Minion {
+ const minion = makeMinion(card);
+ this.board.push(minion);
+ return minion;
+ }
+
+ cast(card: string): void {
+ switch (card) {
+ // Deal 1 damage to ALL minions.
+ case "Whirlwind":
+ for (const minion of [...this.board]) {
+ this.dealDamage(minion, 1);
+ }
+ break;
+ default:
+ throw new Error(`unknown spell: ${card}`);
+ }
+ }
+
+ /** Damage happens now. The damage trigger fires now. Death happens now. */
+ dealDamage(target: Minion, amount: number): void {
+ target.health -= amount;
+ this.log.push(`damaged ${target.name} ${amount}`);
+ if (target.onDamaged) {
+ this.log.push(`triggered ${target.name}`);
+ target.onDamaged(this, target);
+ }
+ if (target.health <= 0) {
+ this.kill(target);
+ }
+ }
+
+ /** Removes the minion and runs its Deathrattle straight away. */
+ private kill(target: Minion): void {
+ this.board = this.board.filter((minion) => minion !== target);
+ this.log.push(`died ${target.name}`);
+ if (target.deathrattle) {
+ this.log.push(`deathrattle ${target.name}`);
+ target.deathrattle(this, target);
+ }
+ }
+
+ draw(): void {
+ this.cardsDrawn += 1;
+ this.log.push("drew a card");
+ }
+
+ boardNames(): string[] {
+ return this.board.map((minion) => minion.name);
+ }
+}
+
+/** Every minion the game knows. A new card means a new case here. */
+function makeMinion(card: string): Minion {
+ switch (card) {
+ // Whenever this minion takes damage, draw a card.
+ case "Acolyte of Pain":
+ return { name: card, attack: 1, health: 4, onDamaged: (game) => game.draw() };
+ // Deathrattle: Draw a card.
+ case "Loot Hoarder":
+ return { name: card, attack: 2, health: 1, deathrattle: (game) => game.draw() };
+ default:
+ throw new Error(`unknown minion: ${card}`);
+ }
+}
diff --git a/naive-engine/ts/tests/test_00.test.ts b/naive-engine/ts/tests/test_00.test.ts
new file mode 100644
index 0000000..2c1a13d
--- /dev/null
+++ b/naive-engine/ts/tests/test_00.test.ts
@@ -0,0 +1,31 @@
+// Module 0 tests for the naive engine. See course/00-rewrite-trap/lesson.md.
+import { expect, test } from "vitest";
+import { Game } from "../src/engine";
+
+test("test_00_naive_runs", () => {
+ const game = new Game();
+ const hoarder = game.summon("Loot Hoarder");
+ game.dealDamage(hoarder, 1);
+ expect(game.boardNames()).toEqual([]); // Loot Hoarder is gone
+ expect(game.cardsDrawn).toBe(1); // its Deathrattle draws one card
+});
+
+// EXPECTED TO FAIL. This test states the order the real game uses (all damage
+// first, then damage triggers in order of play, then deaths and Deathrattles).
+// The naive engine cannot produce it. Read course/00-rewrite-trap/lesson.md,
+// "Predict", before changing anything here.
+test.fails("test_00_real_rules_whirlwind_order", () => {
+ const game = new Game();
+ game.summon("Acolyte of Pain"); // played first
+ game.summon("Loot Hoarder");
+ game.cast("Whirlwind");
+ expect(game.log).toEqual([
+ "damaged Acolyte of Pain 1",
+ "damaged Loot Hoarder 1",
+ "triggered Acolyte of Pain",
+ "drew a card",
+ "died Loot Hoarder",
+ "deathrattle Loot Hoarder",
+ "drew a card",
+ ]);
+});
diff --git a/naive-engine/ts/tsconfig.json b/naive-engine/ts/tsconfig.json
new file mode 100644
index 0000000..2099df7
--- /dev/null
+++ b/naive-engine/ts/tsconfig.json
@@ -0,0 +1,12 @@
+{
+ "compilerOptions": {
+ "target": "ES2022",
+ "module": "ESNext",
+ "moduleResolution": "Bundler",
+ "strict": true,
+ "noEmit": true,
+ "skipLibCheck": true,
+ "esModuleInterop": true
+ },
+ "include": ["src", "tests"]
+}
diff --git a/naive-engine/ts/vitest.config.ts b/naive-engine/ts/vitest.config.ts
new file mode 100644
index 0000000..00988cf
--- /dev/null
+++ b/naive-engine/ts/vitest.config.ts
@@ -0,0 +1,5 @@
+import { defineConfig } from "vitest/config";
+
+export default defineConfig({
+ test: { include: ["tests/**/*.test.ts"] },
+});