Adds IS NOT to the Part II parser and a resolution pass (negation, then
identity lock) that turns parsed rules into the rules in force; harness
gains activeRules() and hasProperty(). A leading NOT now breaks the
sentence instead of being ignored (carried from M6 review). Nouns say
which objects they name ("selects"), so the ALL Expansion Drop is one
line of data. Corrects lesson 06's NOT wording; harness.md M7 details.
Co-Authored-By: Claude Opus 5.5 <[email protected]>
319 lines
24 KiB
Markdown
319 lines
24 KiB
Markdown
# Module 7: Resolving rules: NOT, identity, meta-nouns
|
||
|
||
Module 6 built a parser that reports everything the board says. This module decides what the board *means* when its sentences disagree. `BABA IS YOU` and `BABA IS NOT YOU` can lie on the same board, and so can `BABA IS BABA` and `BABA IS ROCK`. The parser reads them all. A second stage, the resolution pass, reads the whole rule list at once and decides which rules are in force. Then every question an object asks ("am I YOU?") goes through one function that looks only at the rules in force.
|
||
|
||
Part I had a step like this too. In Module 4, Baron Rivendare changed what a Deathrattle *does* without the Deathrattle's code knowing he exists: an interceptor, consulted at the moment the trigger runs. `BABA IS NOT YOU` changes whether `BABA IS YOU` applies, and nothing in `BABA IS YOU` knows about it either. Keep both in mind while you build. The course's closing question asks you to compare them.
|
||
|
||
You need your Module 6 workspace with all Module 1–6 tests passing, under git.
|
||
|
||
## Observe
|
||
|
||
All quotes come from sources recorded in the course's research notes. The Baba Is You Wiki is an unofficial community wiki; the course treats it as the best available description of the game, not as Hempuli's own word.
|
||
|
||
What `NOT` does, from the wiki's [NOT](https://babaiswiki.fandom.com/wiki/NOT) page:
|
||
|
||
> When added after a verb it makes the corresponding rule not apply, overriding other instances of the rule it disables.
|
||
> ROCK IS NOT PUSH makes ROCK NOT PUSHable, even if ROCK IS PUSH exists elsewhere.
|
||
|
||
It works on conversions as well as properties: "ROCK IS NOT GRASS disables ROCK IS GRASS." The [IS](https://babaiswiki.fandom.com/wiki/IS) page says the same about properties: "BABA IS NOT YOU prevents BABA from having the property YOU, overriding any BABA IS YOU rule."
|
||
|
||
A noun can be its own complement. The [IS](https://babaiswiki.fandom.com/wiki/IS) page:
|
||
|
||
> BABA IS BABA prevents BABA from transforming into any object, including those not in ALL. It does not affect transforming objects into BABA.
|
||
|
||
And the negation of that, `X IS NOT X`, from the [NOT](https://babaiswiki.fandom.com/wiki/NOT) page:
|
||
|
||
> X IS NOT X makes an object disappear, similarly to X IS EMPTY but with particles. ... As it negates X IS X, it enables other X IS Y rules, which can result in transformation in addition to the disappearance.
|
||
|
||
The IS page puts it more briefly: "BABA IS NOT BABA causes BABA to vanish in a puff of logic."
|
||
|
||
Base rules are not exempt. The [Rule](https://babaiswiki.fandom.com/wiki/Rule) page says the base rules, `TEXT IS PUSH` among them, "can still be detected by FEELING and MIMIC and negated by NOT and BROKEN", and the [Advanced rulebook](https://babaiswiki.fandom.com/wiki/Advanced_rulebook) relies on it: "Text can be stacked if TEXT IS NOT PUSH can be formed with another way to move text".
|
||
|
||
Finally, where the game does this. Teikari, in the Game Developer article ([Designing Baba Is You's delightfully innovative rule-writing system](https://www.gamedeveloper.com/design/designing-i-baba-is-you-i-s-delightfully-innovative-rule-writing-system)), describes the last step of building the rules: "Finally, once everything is (hopefully) set, the game goes through the final set of rules and adjusts them if needed." And his example: "(For example, if the rule "Baba Is Not You" exists, "Baba Is You" is marked as being not valid.)" <!-- Task 17: these two quotes are fetch-tool excerpts; re-verify against the page. -->
|
||
|
||
Notice what he says and what he doesn't. The parser doesn't drop `BABA IS YOU` when it meets a `NOT`. The rule is read, kept, and *marked* not valid, in a pass over the final set.
|
||
|
||
## Predict
|
||
|
||
Write your answers down before you open the discussion, for example in `notes/07.md` in your workspace.
|
||
|
||
1. `BABA IS YOU` on the top row and `BABA IS NOT YOU` two rows below. Is Baba YOU? Would the answer change if the rows swapped places?
|
||
2. Three sentences: `BABA IS BABA`, `BABA IS ROCK`, `ROCK IS BABA`. Which of them are in force?
|
||
3. Three sentences: `BABA IS NOT BABA`, `BABA IS BABA`, `BABA IS ROCK`. Which are in force, and what happens to Baba?
|
||
4. `TEXT IS NOT PUSH` is on the board. Is the text spelling it pushable? Is a rock?
|
||
5. One row: `NOT BABA IS YOU`. Your Module 6 parser reads something from it. What, and is that right?
|
||
6. Back to Part I: Baron makes a Deathrattle activate twice; `BABA IS NOT YOU` makes `BABA IS YOU` not apply. In your engines, where is each of those decisions made, and when?
|
||
|
||
<details><summary>Discussion</summary>
|
||
|
||
1. No, and no. `NOT` overrides "other instances of the rule it disables", wherever they are. If your engine let the later sentence win, the answer would depend on which row the parser reads first, and moving a sentence up a row would change the game. Negation is a fact about the whole rule set, not about reading order.
|
||
|
||
2. `BABA IS BABA` and `ROCK IS BABA`. `BABA IS BABA` blocks Baba converting into anything, so `BABA IS ROCK` is out. It doesn't stop rocks becoming Baba.
|
||
|
||
3. `BABA IS NOT BABA` and `BABA IS ROCK`. `BABA IS NOT BABA` is the negation of `BABA IS BABA`, so it cancels it exactly as `BABA IS NOT YOU` cancels `BABA IS YOU`; with the lock gone, the conversion applies again. And every Baba vanishes. Which happens to a given Baba first, conversion or vanishing, is a question about the turn (Module 9); this module only has to know that both are in force.
|
||
|
||
4. No, and no. The base rule is a rule like any other; `NOT` cancels it, and no text on the board is pushable. A rock was never pushable: `TEXT` names text objects, not the things they name.
|
||
|
||
5. `BABA IS YOU`. The subject run walks back from `IS`, finds `BABA`, finds no `AND` before it, and stops: the `NOT` is ignored. That reads a sentence that isn't on the board. Module 7's parser has to do something with it. See Build for the course's choice and "How the real game differs" for the game's.
|
||
|
||
6. Baron is consulted *while a trigger runs*: the step asks the modifiers in play how many times to activate. `NOT` is consulted *when the rules are compiled*, before anyone asks a question: by the time an object asks "am I YOU?", the cancelled rule is already gone. Hold on to that difference; you'll come back to it at the end of the course.
|
||
|
||
</details>
|
||
|
||
## Spec
|
||
|
||
From the course repository, copy the module's tests into your workspace:
|
||
|
||
```sh
|
||
node tools/start-module.mjs 07 ~/my-engine
|
||
```
|
||
|
||
### The harness
|
||
|
||
Two new functions on `Baba`, and one change:
|
||
|
||
| TypeScript | Rust |
|
||
|---|---|
|
||
| `activeRules(): string[]` | `active_rules(&self) -> Vec<String>` |
|
||
| `hasProperty(id, prop): boolean` | `has_property(&self, id: Id, prop: &str) -> bool` |
|
||
|
||
- `rules()` still lists everything the text spells, before resolution. It now includes negated rules, written `"BABA IS NOT YOU"`.
|
||
- `activeRules()` lists the rules in force after resolution, in the same format: sorted, each distinct rule once, `"TEXT IS PUSH"` included unless `TEXT IS NOT PUSH` cancels it.
|
||
- `hasProperty(id, prop)` answers for one object. A thing is named by its noun (a `baba` object by `BABA`); a text object is named by `TEXT`.
|
||
|
||
### The tests
|
||
|
||
The full statements are in `course/07-rule-resolution/scenarios.md`.
|
||
|
||
- `test_07_not_overrides`: Predict question 1. Baba is YOU until `BABA IS NOT YOU` is written below `BABA IS YOU`. `rules()` shows both sentences; `activeRules()` shows only the negation and the base rule.
|
||
- `test_07_x_is_x_blocks_conversion`: Predict question 2, exactly.
|
||
- `test_07_x_is_not_x_deletes_and_unlocks`: Predict question 3. `activeRules()` contains `BABA IS ROCK` and not `BABA IS BABA`.
|
||
- `test_07_text_is_not_push`: Predict question 4, checked with `hasProperty` on a piece of text and on a rock, before and after.
|
||
- `test_07_leading_not_breaks_sentence`: Predict question 5. `NOT BABA IS YOU` spells nothing in the course's grammar.
|
||
|
||
The third test, which carries the module's subtlest point:
|
||
|
||
```rust
|
||
#[test]
|
||
fn test_07_x_is_not_x_deletes_and_unlocks() {
|
||
let mut b = new_baba(empty_level(4, 5));
|
||
write(
|
||
&mut b,
|
||
&[
|
||
("BABA", 0, 0),
|
||
("IS", 1, 0),
|
||
("NOT", 2, 0),
|
||
("BABA", 3, 0),
|
||
("BABA", 0, 2),
|
||
("IS", 1, 2),
|
||
("BABA", 2, 2),
|
||
("BABA", 0, 4),
|
||
("IS", 1, 4),
|
||
("ROCK", 2, 4),
|
||
],
|
||
);
|
||
|
||
assert_eq!(
|
||
b.rules(),
|
||
vec![
|
||
"BABA IS BABA",
|
||
"BABA IS NOT BABA",
|
||
"BABA IS ROCK",
|
||
"TEXT IS PUSH"
|
||
]
|
||
);
|
||
let active = b.active_rules();
|
||
assert!(active.contains(&"BABA IS ROCK".to_string()));
|
||
assert!(!active.contains(&"BABA IS BABA".to_string()));
|
||
}
|
||
```
|
||
|
||
```ts
|
||
test("test_07_x_is_not_x_deletes_and_unlocks", () => {
|
||
const b = newBaba(emptyLevel(4, 5));
|
||
write(b, [
|
||
["BABA", 0, 0],
|
||
["IS", 1, 0],
|
||
["NOT", 2, 0],
|
||
["BABA", 3, 0],
|
||
["BABA", 0, 2],
|
||
["IS", 1, 2],
|
||
["BABA", 2, 2],
|
||
["BABA", 0, 4],
|
||
["IS", 1, 4],
|
||
["ROCK", 2, 4],
|
||
]);
|
||
|
||
expect(b.rules()).toEqual(["BABA IS BABA", "BABA IS NOT BABA", "BABA IS ROCK", "TEXT IS PUSH"]);
|
||
expect(b.activeRules()).toContain("BABA IS ROCK");
|
||
expect(b.activeRules()).not.toContain("BABA IS BABA");
|
||
});
|
||
```
|
||
|
||
The test doesn't check that Baba vanishes. Nothing moves or vanishes until the turn exists (Module 9). This module decides that Baba is to vanish; Module 9 does it.
|
||
|
||
Run `cargo test` or `npm test`. The Module 7 tests fail to compile (Rust) or fail (TypeScript). Modules 1–6 still pass. That is RED.
|
||
|
||
## Build
|
||
|
||
Make the five tests pass, and keep Modules 1–6 green. Where code goes:
|
||
|
||
- **The parser learns `NOT`, and only reads.** After `IS`, a `NOT` negates the complement right after it: `BABA IS NOT YOU AND WIN` is `BABA IS NOT YOU` and `BABA IS WIN`. A rule now carries a flag, negated or not. The parser never cancels anything; it reports what is written.
|
||
- **A `NOT` before a subject breaks the sentence.** The course's grammar has no negated subjects. When the subject run meets a `NOT` in front of a noun, it stops and drops that noun. Reading `NOT BABA IS YOU` as `BABA IS YOU` is the one thing it must not do.
|
||
- **A separate resolution pass.** A function from the parsed rules to the rules in force, run after every parse. It sees the whole list at once, so where a sentence sits on the board can't matter.
|
||
- **One question, one place.** `hasProperty(id, prop)` looks only at the rules in force, and asks one more function: does this noun name this object? `BABA` names objects of kind `baba`; `TEXT` names every text object. `TEXT` is the first noun you meet that names a group rather than its own kind. Decide whether that is a special case in your engine or a fact about the word.
|
||
- **Keep the record.** `X IS NOT X` means "every X vanishes". Nothing vanishes yet, but the decision is made here, and the turn in Module 9 will need to find it.
|
||
|
||
<details><summary>Hint 1 — nudge</summary>
|
||
|
||
You will be tempted to do it in the parser: when you read `BABA IS NOT YOU`, delete `BABA IS YOU` from the list. Try Predict question 1 with the rows swapped. Then try question 3: `BABA IS NOT BABA` cancels `BABA IS BABA`, which would have cancelled `BABA IS ROCK`. How many orders of reading do you have to get right?
|
||
|
||
</details>
|
||
|
||
<details><summary>Hint 2 — concept</summary>
|
||
|
||
- **Two steps, in a fixed order, over the whole list.** First negation: drop every positive rule `X IS Y` for which a negated `X IS NOT Y` exists. Keep the negated rules: they are still in force. Then identity: for each positive `X IS X` that survived, drop every `X IS Y` where `Y` is a noun other than `X`.
|
||
- **The order does the work for `X IS NOT X`.** It negates `X IS X` in the first step, so in the second step there is no lock. There's no special case for it.
|
||
- **Telling conversions from properties.** The identity step needs to know whether a complement is a noun. The parser knows (the lexer gave every word a role): store it on the rule.
|
||
- **`hasProperty` is about objects, not rules.** Collect the rules in force whose complement is the property and whose subject names the object. The object has the property if one of them is positive and none is negated. Doing the negation per object, not only per rule, is what lets a negation about `BABA` beat a positive rule written about a larger group.
|
||
- **Base rules are rules.** If `TEXT IS PUSH` is in the parsed list like any other rule, `TEXT IS NOT PUSH` needs no code of its own.
|
||
|
||
</details>
|
||
|
||
<details><summary>Hint 3 — pseudo-code</summary>
|
||
|
||
```text
|
||
complements(tiles, at, step): // after IS
|
||
found = []
|
||
branches = [(not negated, at)] + [(negated, at + step) for each NOT in tiles[at]]
|
||
for (sign, p) in branches:
|
||
words = [w in tiles[p] where w is noun or property]
|
||
if words is empty: continue
|
||
found += words, each with this sign
|
||
if tiles[p + step] has an AND:
|
||
found += complements(tiles, p + 2 * step, step)
|
||
return found
|
||
|
||
subjects(tiles, at, step): // before IS, walking backwards
|
||
if tiles[at + step] has NOT: return []
|
||
... as in Module 6
|
||
|
||
resolve(rules):
|
||
kept = [r in rules where not exists n: n.negated and not r.negated
|
||
and n.subject == r.subject and n.complement == r.complement]
|
||
locked(x) = exists r in kept: not r.negated and r.subject == x and r.complement == x
|
||
return [r in kept where r.negated or r.complement is not a noun
|
||
or r.subject == r.complement or not locked(r.subject)]
|
||
|
||
hasProperty(id, prop):
|
||
about = [r in active where r.complement == prop and names(r.subject, object id)]
|
||
return any(r positive in about) and not any(r negated in about)
|
||
```
|
||
|
||
</details>
|
||
|
||
When your tests pass, compare with the reference engine in `course/07-rule-resolution/reference/rust/src/engine/puzzle/` or `course/07-rule-resolution/reference/ts/src/engine/puzzle/`: `parse` (now with `NOT`), the new `resolve`, `level` (`has_property` / `hasProperty`) and `word` (`Lexicon::names`).
|
||
|
||
Decisions in the reference that the tests don't force:
|
||
|
||
- **`activeRules()` keeps negated rules.** A negated rule is in force: it keeps overriding. That also makes `BABA IS NOT BABA` appear in `activeRules()`, and that entry is the reference's record that every Baba is to vanish. The sources say Baba vanishes; they don't say how an engine should record it, so the test doesn't check this.
|
||
- **`X IS NOT Y` with `Y` a noun is just a negation.** It cancels `X IS Y` and nothing else, as the NOT page says of `ROCK IS NOT GRASS`.
|
||
- **Negation is settled twice.** Once per rule in the resolution pass (`activeRules()` drops `BABA IS YOU`), and once per object in `hasProperty`. Only the second matters when the subjects differ, which the Expansion Drop shows.
|
||
- **Which objects a noun names is a fact about the word.** The reference's word list has an optional field, `"selects"`: `"named"` (the default: the noun's own things), `"text"` (every text object) or `"things"` (every object that isn't text). The engine registers `TEXT` with `"text"`. `Lexicon::names` / `Lexicon.names` reads the field.
|
||
- **One `NOT` only.** `IS NOT NOT PUSH` spells nothing (see the box below).
|
||
- **Duplicates as before.** The engine keeps a rule once per set of text objects, now including the `NOT`; the harness deduplicates.
|
||
|
||
> **How the real game differs**
|
||
> - `NOT` before a noun is real grammar in the game. The [TEXT](https://babaiswiki.fandom.com/wiki/TEXT) page says "TEXT is not included in ALL, but is included in NOT ALL", so `NOT ALL` names a group. The course's parser doesn't read negated subjects: it refuses the sentence rather than misread it. baba-is-auto makes the same cut: "Negated subjects, predicates, condition targets, repeated `NOT`, and nested forms are unsupported" ([`rule-language.md`](https://github.com/utilForever/baba-is-auto/blob/main/Documents/rule-language.md)).
|
||
> - `NOT` can stack: "NOT negates itself before negating anything else, so ROCK IS NOT NOT PUSH makes ROCK IS PUSH" ([NOT](https://babaiswiki.fandom.com/wiki/NOT)). The course reads one `NOT`.
|
||
> - `X IS NOT X` makes X disappear "similarly to X IS EMPTY but with particles" (same page). The course has no `EMPTY`, and the disappearance waits for Module 9's turn.
|
||
> - Base rules can also be negated by `BROKEN` ([Rule](https://babaiswiki.fandom.com/wiki/Rule)). The course has no `BROKEN`.
|
||
> - `BABA IS BABA` blocks every conversion of Baba, "including those not in ALL" ([IS](https://babaiswiki.fandom.com/wiki/IS)). The course has no conversion into `ALL`, and the wiki's [Order of Operations](https://babaiswiki.fandom.com/wiki/Order_of_Operations) gives `X IS ALL` its own place in the turn ("X IS ALL takes the lowest priority").
|
||
|
||
## Name it
|
||
|
||
The pattern is a **resolution pass** (a post-pass): parse everything into candidate rules, then resolve the conflicts in one deterministic pass over the whole set, and let every query read only the result. The parser stays simple because it never decides anything. The resolver stays simple because it never reads the board. And every system that asks "is Baba YOU?" gets the same answer, because there is exactly one place that answers.
|
||
|
||
The reference's whole resolver:
|
||
|
||
```rust
|
||
pub fn resolve(rules: &[Rule]) -> Vec<Rule> {
|
||
let survives_negation: Vec<&Rule> = rules
|
||
.iter()
|
||
.filter(|r| !rules.iter().any(|n| n.negates(r)))
|
||
.collect();
|
||
let locked = |subject: &str| {
|
||
survives_negation
|
||
.iter()
|
||
.any(|r| !r.negated && r.is_identity() && r.subject == subject)
|
||
};
|
||
survives_negation
|
||
.iter()
|
||
.filter(|r| r.negated || !r.is_conversion() || r.is_identity() || !locked(&r.subject))
|
||
.map(|&r| r.clone())
|
||
.collect()
|
||
}
|
||
```
|
||
|
||
```ts
|
||
export function resolve(rules: readonly Rule[]): Rule[] {
|
||
const survivesNegation = rules.filter((r) => !rules.some((n) => negates(n, r)));
|
||
const locked = (subject: string) =>
|
||
survivesNegation.some((r) => !r.negated && isIdentity(r) && r.subject === subject);
|
||
return survivesNegation.filter((r) => r.negated || !isConversion(r) || isIdentity(r) || !locked(r.subject));
|
||
}
|
||
```
|
||
|
||
The order of the two steps is part of the meaning. Swap them and `BABA IS NOT BABA` no longer unlocks `BABA IS ROCK`, because the lock would already have removed it.
|
||
|
||
Magic: The Gathering has the large version of this. When many continuous effects change the same object, rule 613.1 of the Comprehensive Rules says: "Then all applicable continuous effects are applied in a series of layers in the following order", and inside a layer, "determining which order effects are applied in is usually done using a timestamp system" (613.7) and "is sometimes done using a dependency system" (613.8) ([Comprehensive Rules](https://magic.wizards.com/en/rules)). The shape is the same as yours: collect every effect first, then apply them in an order fixed by the rules, not by where the cards sit. Your resolver has two layers, negation and identity, and needs no timestamps, because a rule's position on the board never matters.
|
||
|
||
Compare it with Module 4. Baron's interceptor is consulted at the moment a Deathrattle runs, and it changes *how many times* something happens. `NOT` is applied when the rules are compiled, and it changes *whether* a rule exists at all. Both keep the thing they modify ignorant of them. Which one would you rather debug? The course's closing question returns to Baron and `BABA IS NOT YOU`; keep your notes.
|
||
|
||
**Run the coupling audit** again: `node tools/coupling-audit.mjs ~/my-engine` should print `coupling-audit: ok`. The resolver names no noun: it compares subjects and complements, whatever they spell.
|
||
|
||
## Expansion Drop
|
||
|
||
A new word arrives: `ALL`, a meta-noun. As a subject it names every object on the board that is not text: the [TEXT](https://babaiswiki.fandom.com/wiki/TEXT) page is explicit that "TEXT is not included in ALL".
|
||
|
||
The constraint, as always: **do not edit anything under `src/engine/`**. The harness doesn't change either. Only your word list should.
|
||
|
||
1. Commit your workspace and note the commit: `git add -A && git commit -m "module 7 done"`.
|
||
2. Copy the expansion test for your track from `course/07-rule-resolution/expansion/tests/rust/test_07x.rs` or `course/07-rule-resolution/expansion/tests/ts/test_07x.test.ts` into your workspace's `tests/`. Run it and watch it fail.
|
||
3. Add `ALL` to your word list.
|
||
4. When the tests pass, run the check from the course repository:
|
||
|
||
```sh
|
||
node tools/expansion-check.mjs ~/my-engine HEAD
|
||
```
|
||
|
||
The test, `test_07x_all_meta_noun`, writes `ALL IS YOU` with a `baba`, a `rock` and a piece of text on the board. Baba and the rock are YOU; the text is not. Then it writes `BABA IS NOT YOU`: Baba stops being YOU, the rock doesn't. The full scenario is in `course/07-rule-resolution/expansion/README.md`.
|
||
|
||
<details><summary>Discussion: what this drop measures</summary>
|
||
|
||
If "which objects does this noun name" is a fact about the word, `ALL` is one line:
|
||
|
||
```json
|
||
{ "name": "ALL", "role": "noun", "selects": "things" }
|
||
```
|
||
|
||
That is the reference answer, in `course/07-rule-resolution/expansion/solution/words.json` (the Module 7 word list with that line added), the same file for both tracks.
|
||
|
||
Shapes that fail this drop:
|
||
|
||
- **`TEXT` as a branch in the engine.** If `hasProperty` says `if subject == TEXT { object is text } else { object's noun == subject }`, then `ALL` is a third branch, under `src/engine/`. The test can't pass without an engine change. The fix is to move the question "which objects does this noun name" into the word list, and let `TEXT` be the first entry that uses it. Make it a normal commit, then redo the drop.
|
||
- **Negation only per rule.** If your resolver cancels `X IS Y` only when a rule `X IS NOT Y` has the very same subject, and `hasProperty` then trusts any positive rule, `ALL IS YOU` survives and Baba stays YOU after `BABA IS NOT YOU`. The second half of the test fails. The negation has to be checked for the object, against every noun that names it.
|
||
- **Expanding `ALL` in the parser.** Turning `ALL IS YOU` into `BABA IS YOU`, `ROCK IS YOU`, ... for every noun in the word list works for this test, but the parser now needs to know the board's objects, and `activeRules()` shows rules nobody wrote. Keep the parser about text.
|
||
|
||
If the check fails, fix your engine (a normal commit, outside the drop), then redo the drop from step 1.
|
||
|
||
</details>
|
||
|
||
## Further reading
|
||
|
||
- The Baba Is You Wiki's [NOT](https://babaiswiki.fandom.com/wiki/NOT) and [IS](https://babaiswiki.fandom.com/wiki/IS) pages: every case in this module, and the ones the course leaves out (stacked `NOT`, `NOT` before nouns). Then [TEXT](https://babaiswiki.fandom.com/wiki/TEXT) for `TEXT IS PUSH` and `ALL`.
|
||
- John Harris, [*Designing Baba Is You's delightfully innovative rule-writing system*](https://www.gamedeveloper.com/design/designing-i-baba-is-you-i-s-delightfully-innovative-rule-writing-system), Game Developer, 8 May 2019: Teikari on the final pass over the rule set, the pass you just built.
|
||
- Wizards of the Coast, [Magic: The Gathering Comprehensive Rules](https://magic.wizards.com/en/rules), rule 613 (613.1, 613.7, 613.8): interaction of continuous effects, in layers, by timestamp and by dependency. A resolution pass for a game with thousands of cards.
|
||
- Argentum, [`docs/architecture-principles.md`](https://github.com/wingedsheep/argentum-engine/blob/main/docs/architecture-principles.md), §2.3 "Rule 613: Base State vs. Projected State": "the engine doesn't need to 'undo' +3/+3. It simply removes the floating effect." The same idea as recompiling the rules from the board.
|
||
- baba-is-auto, `Game::ParseRules` in [`Sources/baba-is-auto/Games/Game.cpp`](https://github.com/utilForever/baba-is-auto/blob/2305a3d/Sources/baba-is-auto/Games/Game.cpp) (around line 611) and `RuleManager::HasProperty` in [`Sources/baba-is-auto/Rules/RuleManager.cpp`](https://github.com/utilForever/baba-is-auto/blob/2305a3d/Sources/baba-is-auto/Rules/RuleManager.cpp): one place that answers "does this object have this property?". It is an example of rules as data, **not an authority** on the game: it has no `IS NOT` for properties or nouns and hard-codes text as pushable ([`rule-language.md`](https://github.com/utilForever/baba-is-auto/blob/main/Documents/rule-language.md)), so it can't run this module's tests.
|