diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -34,7 +34,7 @@
 
 ```bash
 cabal update
-cabal install --overwrite-policy=always phino-0.0.148
+cabal install --overwrite-policy=always phino-0.0.151
 phino --version
 ```
 
@@ -59,7 +59,7 @@
 for the runner and puts it on the `PATH`:
 
 ```yaml
-- uses: objectionary/phino@0.0.150
+- uses: objectionary/phino@0.0.151
   with:
     version-file: phino-version.txt
 - run: phino --version
@@ -313,6 +313,42 @@
 none of them. A formation whose `φ` chain ends in a datum, or which has no `φ`
 at all, does not join with a bare symbol.
 
+A fork whose one branch calls a method often meets a decorator. In
+`if. (value.gte 0) (0.plus value) value.neg` the first branch comes to a
+number `X`, while `value.neg` is a method, `times -1 > @`, and comes to
+`⟦ φ ↦ X ⟧`, with the number one level down. The two differ in shape, so the
+join is refused. An entry that says `lenient: true` beside its `join` block
+lets it through:
+
+```yaml
+- λ: L_fork
+  morph:
+    𝑛1: $.left
+    𝑛2: $.right
+  join:
+    𝑛3: [𝑛1, 𝑛2]
+  lenient: true
+  𝑛: 𝑛3
+```
+
+Every `join` line of such an entry joins a term with a formation whose `φ` is
+that term, in either order, and answers `⟦ φ ↦ X ⟧`, with symbols minted inside
+`X` the usual way. Where both terms are formations, the one with the longer `φ`
+chain is taken for the decorator. The answer drops the `ρ` and the other
+attributes of the decorator, so it is not exactly the object either branch
+made, which is why an entry is strict unless it says otherwise. Dataization
+still reaches the same data through it. An entry that says `lenient: true` and
+has no `join` line is refused when the file is read.
+
+A lenient entry also joins a bare symbol with a term that holds exactly one
+symbol anywhere inside. In `bool.and` one branch comes to a bare
+`⟦ λ ⤍ 𝜎1 ⟧` and the other to `⟦ φ ↦ Φ.bool( if ↦ 𝑘 ) ⟧`, where `𝑘` is
+`⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧`. The `φ` of the second is a call, so its
+`φ` chain ends in no symbol and a strict join stops there. A lenient one
+answers a copy of the second term with `𝜎2` replaced by a fresh `𝜎3`, which
+stands for either `𝜎1` or `𝜎2`. The copy keeps every binding of the term, `ρ`
+included. A term with no symbol, or with two, is still refused.
+
 One term being `⊥` is the exception, since `if. cond value ⊥` is how EO spells
 "raise unless `cond`": the program raises on that side of the condition and
 has a perfectly good value on the other. The join then mints nothing, binds
@@ -323,6 +359,32 @@
 such a fork too, since that `⊥` is an argument the program wrote rather than
 one the reduction made.
 
+EO raises with an object too, a copy of `⟦ message ↦ ∅, λ ⤍ L_throw ⟧`, whose
+λ function the file leaves out on purpose, so that an error of the program
+stays an error at runtime. An entry names such functions under `terminators`:
+
+```yaml
+- λ: L_fork
+  dataize:
+    𝛿1: $.φ
+  morph:
+    𝑛1: $.left
+    𝑛2: $.right
+  join:
+    𝑛3: [𝑛1, 𝑛2]
+  terminators: [L_throw]
+  𝑛: 𝑛3
+```
+
+A `join` line of such an entry takes a formation with `λ ⤍ L_throw`, or an
+application of one, for a branch that raises, exactly as it takes `⊥`: it binds
+its meta to the other term and writes `terminate(…)`. The morph of an operand
+of the entry that comes to such a term stops there, and `--deep` walks none of
+its bindings, since its message is what the program reports at runtime and
+there is nothing to reduce in it. A name under `terminators` that a key of the
+file matches is refused when the file is read, since that function fires
+rather than raises.
+
 Every symbol a join mints is written into the protocol as a fact of its own,
 so a reader ties it to the two it stands for without diffing the terms; the
 section on `--protocol` below shows one.
@@ -2405,119 +2467,119 @@
 === parse/phi ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      1094396.788 μs
-  avg:        109439.679 μs
-  min:        101010.304 μs
-  max:        128313.535 μs
-  std dev:    10811.808 μs
+  total:      2094387.514 μs
+  avg:        209438.751 μs
+  min:        192641.231 μs
+  max:        232222.007 μs
+  std dev:    15432.075 μs
 === parse/xmir ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      5710303.145 μs
-  avg:        571030.314 μs
-  min:        504770.068 μs
-  max:        662347.068 μs
-  std dev:    46900.536 μs
+  total:      9539874.986 μs
+  avg:        953987.499 μs
+  min:        891243.312 μs
+  max:        1017605.607 μs
+  std dev:    44263.498 μs
 === rewrite/normalize ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      11.392 μs
-  avg:        1.139 μs
-  min:        0.970 μs
-  max:        1.628 μs
-  std dev:    0.177 μs
+  total:      20.489 μs
+  avg:        2.049 μs
+  min:        1.713 μs
+  max:        2.604 μs
+  std dev:    0.281 μs
 === print/sweet/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      2401437.519 μs
-  avg:        240143.752 μs
-  min:        213136.984 μs
-  max:        272722.750 μs
-  std dev:    16775.737 μs
+  total:      4017888.697 μs
+  avg:        401788.870 μs
+  min:        367355.222 μs
+  max:        451665.532 μs
+  std dev:    27750.906 μs
 === print/sweet/flat ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      2416626.820 μs
-  avg:        241662.682 μs
-  min:        233315.703 μs
-  max:        254494.103 μs
-  std dev:    5589.090 μs
+  total:      4129328.874 μs
+  avg:        412932.887 μs
+  min:        381222.773 μs
+  max:        440759.690 μs
+  std dev:    15935.378 μs
 === print/salty/multiline ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      7328848.511 μs
-  avg:        732884.851 μs
-  min:        692672.535 μs
-  max:        772742.324 μs
-  std dev:    23401.078 μs
+  total:      12486021.615 μs
+  avg:        1248602.162 μs
+  min:        1210030.931 μs
+  max:        1315219.913 μs
+  std dev:    29724.975 μs
 === morph/symbolic/demo/e1 ===
   warmup:     3 iterations
-  batches:    10 x 3
-  total:      128456.280 μs
-  avg:        4281.876 μs
-  min:        4253.475 μs
-  max:        4312.886 μs
-  std dev:    20.874 μs
+  batches:    10 x 2
+  total:      169315.306 μs
+  avg:        8465.765 μs
+  min:        8399.346 μs
+  max:        8543.902 μs
+  std dev:    48.118 μs
 === morph/symbolic/demo/e2 ===
   warmup:     3 iterations
-  batches:    10 x 5
-  total:      198825.013 μs
-  avg:        3976.500 μs
-  min:        3937.010 μs
-  max:        4123.529 μs
-  std dev:    54.483 μs
+  batches:    10 x 2
+  total:      161881.224 μs
+  avg:        8094.061 μs
+  min:        7995.987 μs
+  max:        8224.079 μs
+  std dev:    55.390 μs
 === morph/symbolic/demo/e3 ===
   warmup:     3 iterations
-  batches:    10 x 3
-  total:      171306.263 μs
-  avg:        5710.209 μs
-  min:        5671.534 μs
-  max:        5828.818 μs
-  std dev:    45.464 μs
+  batches:    10 x 2
+  total:      234257.502 μs
+  avg:        11712.875 μs
+  min:        11591.756 μs
+  max:        11897.267 μs
+  std dev:    100.821 μs
 === morph/symbolic/demo/e4 ===
   warmup:     3 iterations
-  batches:    10 x 6
-  total:      214186.131 μs
-  avg:        3569.769 μs
-  min:        3556.854 μs
-  max:        3596.001 μs
-  std dev:    11.389 μs
+  batches:    10 x 3
+  total:      211951.865 μs
+  avg:        7065.062 μs
+  min:        6976.291 μs
+  max:        7108.785 μs
+  std dev:    43.882 μs
 === morph/symbolic/demo/e5 ===
   warmup:     3 iterations
-  batches:    10 x 15
-  total:      190902.360 μs
-  avg:        1272.682 μs
-  min:        1266.976 μs
-  max:        1276.589 μs
-  std dev:    3.073 μs
+  batches:    10 x 9
+  total:      201954.992 μs
+  avg:        2243.944 μs
+  min:        2209.663 μs
+  max:        2273.110 μs
+  std dev:    20.511 μs
 === morph/symbolic/native/e5 ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      130864.617 μs
-  avg:        13086.462 μs
-  min:        12534.939 μs
-  max:        13730.337 μs
-  std dev:    404.303 μs
+  total:      252909.974 μs
+  avg:        25290.997 μs
+  min:        24128.018 μs
+  max:        26030.789 μs
+  std dev:    686.354 μs
 === morph/symbolic/accum/0 ===
   warmup:     3 iterations
-  batches:    10 x 3
-  total:      217771.785 μs
-  avg:        7259.060 μs
-  min:        7183.014 μs
-  max:        7458.925 μs
-  std dev:    90.688 μs
+  batches:    10 x 2
+  total:      203382.050 μs
+  avg:        10169.102 μs
+  min:        10090.947 μs
+  max:        10299.047 μs
+  std dev:    60.479 μs
 === morph/symbolic/accum/400 ===
   warmup:     3 iterations
   batches:    10 x 1
-  total:      210258.554 μs
-  avg:        21025.855 μs
-  min:        20666.196 μs
-  max:        23018.525 μs
-  std dev:    672.165 μs
+  total:      336426.864 μs
+  avg:        33642.686 μs
+  min:        33345.536 μs
+  max:        33999.930 μs
+  std dev:    201.060 μs
 ```
 
 The results were calculated in [this GHA job][benchmark-gha]
-on 2026-09-29 at 12:02,
+on 2026-10-09 at 14:39,
 on Linux with 4 CPUs.
 
 <!-- benchmark_end -->
@@ -2568,4 +2630,4 @@
 [jna-native]: https://github.com/java-native-access/jna/blob/master/src/com/sun/jna/Native.java
 [jeo]: https://github.com/objectionary/jeo-maven-plugin
 [issue-1291]: https://github.com/objectionary/phino/issues/1291
-[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/36565203496
+[benchmark-gha]: https://github.com/objectionary/phino/actions/runs/37945133395
diff --git a/benchmark/Main.hs b/benchmark/Main.hs
--- a/benchmark/Main.hs
+++ b/benchmark/Main.hs
@@ -87,6 +87,7 @@
     []
     Map.empty
     lambdas
+    []
     (building linked)
     reduction
     evaluation
diff --git a/compiled/generated/Compiled.hs b/compiled/generated/Compiled.hs
--- a/compiled/generated/Compiled.hs
+++ b/compiled/generated/Compiled.hs
@@ -33,12 +33,12 @@
 
 -- The steps of the built-in rules of normalization, in the order of the rules.
 normalization :: [Ru.Step]
-normalization = [stepAlpha, stepAmiss, stepCopy, stepDc, stepDca, stepDd, stepDl, stepDot, stepMiss, stepNull, stepOver, stepOvera, stepSkip, stepStay, stepStop]
+normalization = [stepAlpha, stepAmiss, stepCopy, stepDc, stepDca, stepDd, stepDl, stepDot, stepMiss, stepNorho, stepNull, stepOver, stepOvera, stepSkip, stepStay, stepStop]
 
 -- The steps of every rule compiled, by the text of the rule.
 steps :: Map.Map String Ru.Step
 steps =
-  Map.fromList [("Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepAlpha), ("Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", stepAmiss), ("Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", stepCopy), ("Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDc), ("Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDca), ("Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDd), ("Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", stepDl), ("Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", stepDot), ("Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepMiss), ("Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepNull), ("Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", stepOver), ("Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepOvera), ("Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", stepSkip), ("Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", stepStay), ("Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", stepStop)]
+  Map.fromList [("Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepAlpha), ("Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", stepAmiss), ("Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", stepCopy), ("Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDc), ("Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDca), ("Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepDd), ("Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", stepDl), ("Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", stepDot), ("Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepMiss), ("Rule {name = \"norho\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) \961, result = ExTermination, when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", stepNorho), ("Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", stepNull), ("Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", stepOver), ("Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", stepOvera), ("Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", stepSkip), ("Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", stepStay), ("Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", stepStop)]
 
 -- The rules of 𝕄, in the order of their files.
 morphings :: [In.Inference Expression]
@@ -50,7 +50,7 @@
 
 -- The texts of the built-in rules of all four judgments this module is made of.
 sources :: [String]
-sources = ["Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", "Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", "Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", "Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", "Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", "ContextualizeRule {name = \"ca\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"caa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cd\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, cmatch = ExMeta \"k1\", cresult = ExDispatch (ExMeta \"n2\") !t1, premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], cmatch = ExMeta \"k1\", cresult = ExFormation [BiMeta \"B1\"], premises = []}", "ContextualizeRule {name = \"cg\", label = Nothing, match = ExRoot, cmatch = ExMeta \"k1\", cresult = ExRoot, premises = []}", "ContextualizeRule {name = \"ct\", label = Nothing, match = ExTermination, cmatch = ExMeta \"k1\", cresult = ExTermination, premises = []}", "ContextualizeRule {name = \"cxi\", label = Nothing, match = ExXi, cmatch = ExMeta \"k1\", cresult = ExMeta \"k1\", premises = []}", "MorphRule {name = \"dead\", label = Nothing, match = ExTermination, ematch = ExMeta \"e1\", nresult = ExTermination, when = Nothing, premises = []}", "MorphRule {name = \"ma\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArAlpha \945!i (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"mad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArTau !t (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"md\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Just (Not (IsFormation (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExDispatch (ExMeta \"n2\") !t1)},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", nresult = ExFormation [BiMeta \"B1\"], when = Nothing, premises = []}", "MorphRule {name = \"mg\", label = Nothing, match = ExRoot, ematch = ExRoot, nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination ExRoot}]}", "MorphRule {name = \"ml\", label = Just \"\\\\lambda\", match = ExDispatch (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n3\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpNormalize (ExDispatch (ExMeta \"n1\") !t1)},Premise {result = \"n3\", operation = OpMorph (ExMeta \"n2\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mphi\", label = Just \"\\\\varphi\", match = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (And [In [\966] [BiMeta \"B1\"],Disjoint [!t1,\955] [BiMeta \"B1\"]]), premises = [Premise {result = \"n1\", operation = OpNormalize (ExDispatch (ExDispatch (ExFormation [BiMeta \"B1\"]) \966) !t1)},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"universe\", label = Just \"\\\\Phi\", match = ExRoot, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Eq (CmpExpr (ExMeta \"e1\")) (CmpExpr ExRoot))), premises = [Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"xi\", label = Nothing, match = ExXi, ematch = ExMeta \"e1\", nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"box\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"e3\", operation = OpContextualize (ExMeta \"e2\") (ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"])},Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e3\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"delta\", label = Just \"\\\\Delta\", match = ExFormation [BiMeta \"B1\",BiDelta (BtMeta \"d1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = []}", "DataizeRule {name = \"fire\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"none\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955,\966] [BiMeta \"B1\"]), premises = [Premise {result = \"d1\", operation = OpDataize ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"norm\", label = Nothing, match = ExMeta \"n1\", ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (And [Not (IsFormation (ExMeta \"n1\")),Not (Eq (CmpExpr (ExMeta \"n1\")) (CmpExpr ExTermination))]), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n2\") (ExMeta \"e1\")}]}"]
+sources = ["Rule {name = \"alpha\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e1\")), result = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e1\")), when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"amiss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArAlpha \945!i1 (ExAny (Slot \"e\" 11))), result = ExTermination, when = Just (Not (Gt (CmpNum (Domain (BiMeta \"B1\"))) (CmpNum (MetaIndex \"i1\")))), where_ = Nothing, having = Nothing}", "Rule {name = \"copy\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"k1\")), result = ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"k1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dc\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArTau !t (ExAny (Slot \"e\" 6))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dca\", label = Nothing, description = Nothing, pattern = ExApplication ExTermination (ArAlpha \945!i (ExAny (Slot \"e\" 7))), result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dd\", label = Nothing, description = Nothing, pattern = ExDispatch ExTermination !t, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"dl\", label = Nothing, description = Nothing, pattern = ExFormation [BiMeta \"B1\",BiLambda (FnAny (Slot \"F\" 9)),BiMeta \"B2\"], result = ExTermination, when = Just (In [\916] [BiMeta \"B1\",BiMeta \"B2\"]), where_ = Nothing, having = Nothing}", "Rule {name = \"dot\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"]) !t1, result = ExApplication (ExMeta \"e1\") (ArTau \961 (ExMeta \"e2\")), when = Just (Not (In [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"])), where_ = Just [Extra {meta = ArgExpression (ExMeta \"e1\"), function = \"contextualize\", args = [ArgExpression (ExMeta \"n1\"),ArgExpression (ExFormation [BiMeta \"B1\",BiMeta \"B2\"])]},Extra {meta = ArgExpression (ExMeta \"e2\"), function = \"named\", args = [ArgExpression (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"n1\"),BiMeta \"B2\"])]}], having = Nothing}", "Rule {name = \"miss\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau !t1 (ExAny (Slot \"e\" 10))), result = ExTermination, when = Just (And [Not (In [!t1] [BiMeta \"B1\"]),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"norho\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) \961, result = ExTermination, when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"null\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\",BiVoid !t1,BiMeta \"B2\"]) !t1, result = ExTermination, when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"over\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau !t1 (ExMeta \"e2\")), result = ExTermination, when = Just (Not (Eq (CmpAttr !t1) (CmpAttr \961))), where_ = Nothing, having = Nothing}", "Rule {name = \"overa\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau !t1 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArAlpha \945!i1 (ExMeta \"e2\")), result = ExTermination, when = Just (And [Eq (CmpNum (MetaIndex \"i1\")) (CmpNum (Domain (BiMeta \"B1\"))),Not (Eq (CmpAttr !t1) (CmpAttr \961))]), where_ = Nothing, having = Nothing}", "Rule {name = \"skip\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\"]) (ArTau \961 (ExMeta \"e1\")), result = ExFormation [BiMeta \"B1\"], when = Just (Not (In [\961] [BiMeta \"B1\"])), where_ = Nothing, having = Nothing}", "Rule {name = \"stay\", label = Nothing, description = Nothing, pattern = ExApplication (ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"]) (ArTau \961 (ExMeta \"e2\")), result = ExFormation [BiMeta \"B1\",BiTau \961 (ExMeta \"e1\"),BiMeta \"B2\"], when = Nothing, where_ = Nothing, having = Nothing}", "Rule {name = \"stop\", label = Nothing, description = Nothing, pattern = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, result = ExTermination, when = Just (Disjoint [!t1,\966,\955] [BiMeta \"B1\"]), where_ = Nothing, having = Nothing}", "ContextualizeRule {name = \"ca\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"caa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"e1\")), cmatch = ExMeta \"k1\", cresult = ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"n3\")), premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")},Premise {result = \"n3\", operation = OpContextualize (ExMeta \"e1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cd\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, cmatch = ExMeta \"k1\", cresult = ExDispatch (ExMeta \"n2\") !t1, premises = [Premise {result = \"n2\", operation = OpContextualize (ExMeta \"n1\") (ExMeta \"k1\")}]}", "ContextualizeRule {name = \"cf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], cmatch = ExMeta \"k1\", cresult = ExFormation [BiMeta \"B1\"], premises = []}", "ContextualizeRule {name = \"cg\", label = Nothing, match = ExRoot, cmatch = ExMeta \"k1\", cresult = ExRoot, premises = []}", "ContextualizeRule {name = \"ct\", label = Nothing, match = ExTermination, cmatch = ExMeta \"k1\", cresult = ExTermination, premises = []}", "ContextualizeRule {name = \"cxi\", label = Nothing, match = ExXi, cmatch = ExMeta \"k1\", cresult = ExMeta \"k1\", premises = []}", "MorphRule {name = \"dead\", label = Nothing, match = ExTermination, ematch = ExMeta \"e1\", nresult = ExTermination, when = Nothing, premises = []}", "MorphRule {name = \"ma\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArTau !t1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArTau !t1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maa\", label = Nothing, match = ExApplication (ExMeta \"n1\") (ArAlpha \945!i1 (ExMeta \"k1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Nothing, premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExApplication (ExMeta \"n2\") (ArAlpha \945!i1 (ExMeta \"k1\")))},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"maad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArAlpha \945!i (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"mad\", label = Nothing, match = ExApplication (ExAny (Slot \"n\" 0)) (ArTau !t (ExMeta \"n1\")), ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Absolute (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "MorphRule {name = \"md\", label = Nothing, match = ExDispatch (ExMeta \"n1\") !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n4\", when = Just (Not (IsFormation (ExMeta \"n1\"))), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"n3\", operation = OpNormalize (ExDispatch (ExMeta \"n2\") !t1)},Premise {result = \"n4\", operation = OpMorph (ExMeta \"n3\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mf\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", nresult = ExFormation [BiMeta \"B1\"], when = Nothing, premises = []}", "MorphRule {name = \"mg\", label = Nothing, match = ExRoot, ematch = ExRoot, nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination ExRoot}]}", "MorphRule {name = \"ml\", label = Just \"\\\\lambda\", match = ExDispatch (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n3\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpNormalize (ExDispatch (ExMeta \"n1\") !t1)},Premise {result = \"n3\", operation = OpMorph (ExMeta \"n2\") (ExMeta \"e1\")}]}", "MorphRule {name = \"mphi\", label = Just \"\\\\varphi\", match = ExDispatch (ExFormation [BiMeta \"B1\"]) !t1, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (And [In [\966] [BiMeta \"B1\"],Disjoint [!t1,\955] [BiMeta \"B1\"]]), premises = [Premise {result = \"n1\", operation = OpNormalize (ExDispatch (ExDispatch (ExFormation [BiMeta \"B1\"]) \966) !t1)},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"universe\", label = Just \"\\\\Phi\", match = ExRoot, ematch = ExMeta \"e1\", nresult = ExMeta \"n2\", when = Just (Not (Eq (CmpExpr (ExMeta \"e1\")) (CmpExpr ExRoot))), premises = [Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e1\")},Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")}]}", "MorphRule {name = \"xi\", label = Nothing, match = ExXi, ematch = ExMeta \"e1\", nresult = ExMeta \"n1\", when = Nothing, premises = [Premise {result = \"n1\", operation = OpMorph ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"box\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"e3\", operation = OpContextualize (ExMeta \"e2\") (ExFormation [BiMeta \"B1\",BiTau \966 (ExMeta \"e2\"),BiMeta \"B2\"])},Premise {result = \"n1\", operation = OpNormalize (ExMeta \"e3\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"delta\", label = Just \"\\\\Delta\", match = ExFormation [BiMeta \"B1\",BiDelta (BtMeta \"d1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\955] [BiMeta \"B1\",BiMeta \"B2\"]), premises = []}", "DataizeRule {name = \"fire\", label = Nothing, match = ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916] [BiMeta \"B1\",BiMeta \"B2\"]), premises = [Premise {result = \"n1\", operation = OpEvaluate (ExFormation [BiMeta \"B1\",BiLambda (FnMeta \"F1\"),BiMeta \"B2\"]) (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n1\") (ExMeta \"e1\")}]}", "DataizeRule {name = \"none\", label = Nothing, match = ExFormation [BiMeta \"B1\"], ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (Disjoint [\916,\955,\966] [BiMeta \"B1\"]), premises = [Premise {result = \"d1\", operation = OpDataize ExTermination (ExMeta \"e1\")}]}", "DataizeRule {name = \"norm\", label = Nothing, match = ExMeta \"n1\", ematch = ExMeta \"e1\", dresult = BtMeta \"d1\", when = Just (And [Not (IsFormation (ExMeta \"n1\")),Not (Eq (CmpExpr (ExMeta \"n1\")) (CmpExpr ExTermination))]), premises = [Premise {result = \"n2\", operation = OpMorph (ExMeta \"n1\") (ExMeta \"e1\")},Premise {result = \"d1\", operation = OpDataize (ExMeta \"n2\") (ExMeta \"e1\")}]}"]
 
 -- Whether the term is a normal form: no built-in rule of normalization
 -- matches anywhere inside it.
@@ -72,12 +72,13 @@
     , (6, True, rewriteDl)
     , (7, True, rewriteDot)
     , (8, True, rewriteMiss)
-    , (9, True, rewriteNull)
-    , (10, True, rewriteOver)
-    , (11, True, rewriteOvera)
-    , (12, True, rewriteSkip)
-    , (13, True, rewriteStay)
-    , (14, True, rewriteStop)
+    , (9, True, rewriteNorho)
+    , (10, True, rewriteNull)
+    , (11, True, rewriteOver)
+    , (12, True, rewriteOvera)
+    , (13, True, rewriteSkip)
+    , (14, True, rewriteStay)
+    , (15, True, rewriteStop)
     ]
 
 -- The Contextualization function 𝒞, the conclusion of the one rule matching
@@ -221,6 +222,19 @@
   | ExApplication x1 (ArTau x2 _) <- [term]
   , ExFormation x4 <- [x1]
   , (not (all (`Ru.presentIn` concat [x4]) [x2]) && not (x2 == AtRho))
+  ]
+
+-- The rule 'norho'.
+stepNorho :: Ru.Step
+stepNorho = R.direct "norho" True rewriteNorho
+
+rewriteNorho :: Maybe Expression -> Expression -> [Expression]
+rewriteNorho _ term =
+  [ ExTermination
+  | ExDispatch x1 x2 <- [term]
+  , x2 == AtRho
+  , ExFormation x3 <- [x1]
+  , not (all (`Ru.presentIn` concat [x3]) [AtRho])
   ]
 
 -- The rule 'null'.
diff --git a/phino.cabal b/phino.cabal
--- a/phino.cabal
+++ b/phino.cabal
@@ -1,6 +1,6 @@
 cabal-version: 3.0
 name: phino
-version: 0.0.151
+version: 0.0.152
 license: MIT
 synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions
 description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
@@ -12,7 +12,7 @@
 copyright: 2025 Objectionary.com
 category: Language, Code Analysis
 build-type: Simple
-extra-source-files: resources/normalize/*.yaml resources/morphing/*.yaml resources/dataization/*.yaml resources/contextualization/*.yaml benchmark/demo.phi benchmark/atoms.yaml benchmark/accum.phi benchmark/accum.yaml
+extra-source-files: resources/normalize/*.yaml resources/morphing/*.yaml resources/dataization/*.yaml resources/contextualization/*.yaml benchmark/demo.phi benchmark/atoms.yaml benchmark/accum.phi benchmark/accum.yaml test-resources/**/*.yaml test-resources/**/*.phi
 extra-doc-files: README.md
 
 source-repository head
@@ -152,6 +152,7 @@
   other-modules:
     AbridgeSpec
     ASTSpec
+    BrokenPipeSpec
     BuilderSpec
     BytesSpec
     CanonizerSpec
@@ -226,6 +227,9 @@
     time >=1.12 && <1.17,
     xml-conduit >=1.9 && <1.11,
     yaml >=0.11.8 && <0.12,
+
+  if !os(windows)
+    build-depends: unix >=2.8 && <2.9
 
   build-tool-depends:
     hspec-discover:hspec-discover >=2.11.0 && <2.12
diff --git a/resources/normalize/norho.yaml b/resources/normalize/norho.yaml
new file mode 100644
--- /dev/null
+++ b/resources/normalize/norho.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation declares a receiver only when it lists ρ among its voids, so a
+# dispatch of ρ on one that does not is ⊥. 'stop' leaves it alone while the
+# formation has a φ, and morphing would then read ρ through φ (#1859).
+name: norho
+pattern: ⟦𝐵1⟧.ρ
+result: ⊥
+when:
+  not:
+    in:
+      - ρ
+      - 𝐵1
diff --git a/src/Bytes.hs b/src/Bytes.hs
--- a/src/Bytes.hs
+++ b/src/Bytes.hs
@@ -92,8 +92,10 @@
               val = wordToDouble word
            in if isNaN val || isInfinite val || isNegativeZero val
                 then Right val
-                else case properFraction val of
-                  (n, 0.0) -> Left n
+                else case properFraction val :: (Integer, Double) of
+                  (n, 0.0)
+                    | n >= toInteger (minBound :: Int) && n <= toInteger (maxBound :: Int) ->
+                        Left (fromInteger n)
                   _ -> Right val
   where
     toWord64BE :: [Word8] -> Word64
@@ -193,7 +195,7 @@
                     chr (0x10000 + (code - 0xD800) * 0x400 + (low - 0xDC00)) : go rest'
               _ -> chr code : go rest
             else chr code : go rest
-    goUnicode rest = go rest
+    goUnicode rest = '\\' : 'u' : go rest
     hexQuad :: Char -> Char -> Char -> Char -> Maybe Int
     hexQuad a b c d = case readHex [a, b, c, d] of
       [(code, "")] -> Just code
@@ -244,11 +246,13 @@
 
 btsSlice :: Int -> Int -> Bytes -> Maybe Bytes
 btsSlice start len bts
-  | start < 0 || len < 0 || start + len > length octets = Nothing
+  | start < 0 || len < 0 || start > size || len > size - start = Nothing
   | otherwise = Just (word8ToBytes (take len (drop start octets)))
   where
     octets :: [Word8]
     octets = btsToWord8 bts
+    size :: Int
+    size = length octets
 
 btsShift :: Int -> Bytes -> Bytes
 btsShift bits bts
diff --git a/src/CLI.hs b/src/CLI.hs
--- a/src/CLI.hs
+++ b/src/CLI.hs
@@ -15,11 +15,13 @@
 import Data.List (dropWhileEnd)
 import Data.Version (showVersion)
 import Files (ensuredFile)
+import GHC.IO.Exception (IOErrorType (ResourceVanished))
 import Logger
 import Options.Applicative
 import Paths_phino (version)
 import System.Exit (ExitCode (..), exitFailure, exitWith)
 import System.IO (hPutStrLn, stderr)
+import System.IO.Error (ioeGetErrorType)
 
 runCLI :: [String] -> IO ()
 runCLI args = handle handler $ do
@@ -54,9 +56,11 @@
     handler e = case fromException e of
       Just ExitSuccess -> pure ()
       Just (ExitFailure _) -> exitFailure
-      _ -> do
-        logError (show e)
-        exitFailure
+      _ -> case fromException e of
+        Just (ioe :: IOError) | ioeGetErrorType ioe == ResourceVanished -> exitWith (ExitFailure 141)
+        _ -> do
+          logError (show e)
+          exitFailure
     setLogger :: Command -> IO ()
     setLogger cmd =
       let (level, lns) = case cmd of
diff --git a/src/CLI/Helpers.hs b/src/CLI/Helpers.hs
--- a/src/CLI/Helpers.hs
+++ b/src/CLI/Helpers.hs
@@ -15,14 +15,15 @@
 import Canonizer (canonizeExpr, lambdaNames)
 import Compiled (compiled)
 import Control.Exception
-import Control.Monad ((>=>))
-import Data.Char (toLower)
+import Control.Monad (when, (>=>))
+import Data.Char (isDigit, toLower)
 import Data.Functor ((<&>))
 import Data.IORef
 import Data.List (intercalate, nub)
 import qualified Data.Map.Strict as M
 import Data.Maybe
 import qualified Data.Text as T
+import qualified Data.Yaml as Yaml
 import Deps (Evaluation (EvRun), Judgment, SaveEvalFunc, SaveStepFunc, dontSaveEval, emptyNesting, emptyProgress, emptyProtocol, endEval, endEvalXml, progressed, saveEval, saveEvalXml, saveStep)
 import Encoding
 import Engine (Engine, fresh, yaml)
@@ -41,8 +42,8 @@
 import qualified Random as R
 import Rewriter (Rewrittens', stepHeaders)
 import Sugar (SugarType (SALTY), withoutRho)
-import System.Directory (createDirectoryIfMissing)
-import System.FilePath (takeDirectory, takeExtension)
+import System.Directory (createDirectoryIfMissing, doesDirectoryExist, doesFileExist, listDirectory, removeFile)
+import System.FilePath (splitExtension, takeDirectory, takeExtension, (</>))
 import System.IO (Handle, IOMode (WriteMode), getContents', hClose, hSetEncoding, openFile, utf8)
 import Text.Printf (printf)
 import XMIR (Atoms, expressionToXMIR, parseXMIRThrows, printXMIR, renameAtoms, xmirAtoms, xmirToPhi)
@@ -55,8 +56,27 @@
 justMeetLength :: Maybe Int -> Int
 justMeetLength = fromMaybe defaultMeetLength
 
+isStepFileName :: FilePath -> Bool
+isStepFileName name = length base == 5 && all isDigit base && ext `elem` [".phi", ".xmir", ".tex"]
+  where
+    (base, ext) = splitExtension name
+
+clearedStepsDir :: Maybe FilePath -> IO ()
+clearedStepsDir Nothing = pure ()
+clearedStepsDir (Just dir) = do
+  exists <- doesDirectoryExist dir
+  when exists (listDirectory dir >>= mapM_ removedIfStepFile)
+  where
+    removedIfStepFile :: FilePath -> IO ()
+    removedIfStepFile name = when (isStepFileName name) (removedFile (dir </> name))
+    removedFile :: FilePath -> IO ()
+    removedFile path = do
+      isFile <- doesFileExist path
+      when isFile (removeFile path)
+
 saveStepFunc :: Maybe FilePath -> PrintContext -> [Expression] -> [Expression] -> IO SaveStepFunc
 saveStepFunc stepsDir ctx@PrintCtx{..} included excluded = do
+  clearedStepsDir stepsDir
   counter <- newIORef (0 :: Int)
   let ioToExt :: String
       ioToExt
@@ -152,14 +172,19 @@
     (printf "The option --inside requires the input expression to be a formation, but given: %s" (P.printExpression expr))
 
 readInput :: Maybe FilePath -> IO String
-readInput inputFile' = case inputFile' of
-  Just pth -> do
-    logDebug (printf "Reading from file: '%s'" pth)
-    readFile =<< ensuredFile pth
-  Nothing -> do
-    logDebug "Reading from stdin"
-    getContents' `catch` (\(e :: SomeException) -> throwIO (CouldNotReadFromStdin (show e)))
+readInput inputFile' =
+  withoutBOM <$> case inputFile' of
+    Just pth -> do
+      logDebug (printf "Reading from file: '%s'" pth)
+      readFile =<< ensuredFile pth
+    Nothing -> do
+      logDebug "Reading from stdin"
+      getContents' `catch` (\(e :: SomeException) -> throwIO (CouldNotReadFromStdin (show e)))
 
+withoutBOM :: String -> String
+withoutBOM ('\xFEFF' : rest) = rest
+withoutBOM input = input
+
 parseInput :: String -> IOFormat -> IO Expression
 parseInput phi PHI = parseExpressionThrows phi
 parseInput xmir XMIR = parseXMIRThrows xmir >>= xmirToPhi
@@ -236,7 +261,7 @@
 
 printCtxToLatexCtx :: PrintContext -> LatexContext
 printCtxToLatexCtx PrintCtx{..} =
-  LatexContext _sugar _line _margin _nonumber _compress _canonize _meetPopularity _meetLength _focus _expression _label _meetPrefix _headers
+  LatexContext _sugar _line _margin _nonumber _compress _canonize _meetPopularity _meetLength _focus _expression _label _meetPrefix _headers _hideRho
 
 getRules :: Bool -> Bool -> [FilePath] -> IO [Y.Rule]
 getRules normalize shuffle rules = do
@@ -261,8 +286,16 @@
       | otherwise = do
           logDebug (printf "Using rules from files: [%s]" (intercalate ", " rules))
           yamls <- mapM ensuredFile (nub rules)
-          mapM (Y.yamlRule >=> validateRewriteRule) yamls
+          mapM (loadRule >=> validateRewriteRule) yamls
 
+loadRule :: FilePath -> IO Y.Rule
+loadRule file =
+  catch (Y.yamlRule file) reportInvalidRule
+  where
+    reportInvalidRule :: Yaml.ParseException -> IO Y.Rule
+    reportInvalidRule err =
+      invalidCLIArguments (printf "Could not load rule from '%s'\n%s" file (Yaml.prettyPrintParseException err))
+
 validateRewriteRule :: Y.Rule -> IO Y.Rule
 validateRewriteRule rule =
   let used = maybe [] (map Y.function) rule.where_
@@ -281,7 +314,7 @@
     putStrLn content
   Just file -> do
     logDebug (printf "The option '--target' is specified, printing to '%s'..." file)
-    overwrite file content
+    overwrite file (content ++ "\n")
     logDebug (printf "The command result was saved in '%s'" file)
 
 engine :: IO Engine
diff --git a/src/CLI/Parsers.hs b/src/CLI/Parsers.hs
--- a/src/CLI/Parsers.hs
+++ b/src/CLI/Parsers.hs
@@ -23,6 +23,13 @@
   | cmp num = return num
   | otherwise = readerError msg
 
+boundedIntOption :: ReadM Int
+boundedIntOption = do
+  num <- auto :: ReadM Integer
+  if num < toInteger (minBound :: Int) || num > toInteger (maxBound :: Int)
+    then readerError (printf "value must be between %d and %d" (minBound :: Int) (maxBound :: Int))
+    else return (fromInteger num)
+
 optLogLevel :: Parser LogLevel
 optLogLevel =
   option
@@ -46,7 +53,7 @@
 optLogLines :: Parser Int
 optLogLines =
   option
-    (auto >>= validateIntOption (>= -1) "--log-lines must be >= -1")
+    (boundedIntOption >>= validateIntOption (>= -1) "--log-lines must be >= -1")
     (long "log-lines" <> metavar "LINES" <> help "Amount of lines printed to console per each log operation (0 - print nothing, -1 - no limits)" <> value 25 <> showDefault)
 
 optRule :: Parser [FilePath]
@@ -74,26 +81,26 @@
 optMaxDepth :: Parser Int
 optMaxDepth =
   option
-    (auto >>= validateIntOption (> 0) "--max-depth must be positive")
+    (boundedIntOption >>= validateIntOption (> 0) "--max-depth must be positive")
     (long "max-depth" <> metavar "DEPTH" <> help "Maximum number of rewriting iterations per rule" <> value 25 <> showDefault)
 
 optMaxCycles :: Parser Int
 optMaxCycles =
   option
-    (auto >>= validateIntOption (> 0) "--max-cycles must be positive")
+    (boundedIntOption >>= validateIntOption (> 0) "--max-cycles must be positive")
     (long "max-cycles" <> metavar "CYCLES" <> help "Maximum number of rewriting cycles across all rules" <> value 25 <> showDefault)
 
 optMaxSteps :: Parser Int
 optMaxSteps =
   option
-    (auto >>= validateIntOption (> 0) "--max-steps must be positive")
+    (boundedIntOption >>= validateIntOption (> 0) "--max-steps must be positive")
     (long "max-steps" <> metavar "STEPS" <> help "Maximum number of nested morphing and dataization steps" <> value 1000 <> showDefault)
 
 optMaxFirings :: Parser (Maybe Int)
 optMaxFirings =
   optional
     ( option
-        (auto >>= validateIntOption (> 0) "--max-firings must be positive")
+        (boundedIntOption >>= validateIntOption (> 0) "--max-firings must be positive")
         (long "max-firings" <> metavar "FIRINGS" <> help "Maximum number of λ functions the whole run may fire, unlimited unless given")
     )
 
@@ -101,21 +108,21 @@
 optMaxSeconds =
   optional
     ( option
-        (auto >>= validateIntOption (> 0) "--max-seconds must be positive")
+        (boundedIntOption >>= validateIntOption (> 0) "--max-seconds must be positive")
         (long "max-seconds" <> metavar "SECONDS" <> help "Maximum number of seconds the whole run may take, unlimited unless given")
     )
 
 optMargin :: Parser Int
 optMargin =
   option
-    (auto >>= validateIntOption (> 0) "--margin must be positive")
+    (boundedIntOption >>= validateIntOption (> 0) "--margin must be positive")
     (long "margin" <> help "The maximum right margin for the printed 𝜑-expressions" <> value defaultMargin <> showDefault)
 
 optMeetPopularity :: Parser (Maybe Int)
 optMeetPopularity =
   optional
     ( option
-        ( auto
+        ( boundedIntOption
             >>= validateIntOption (> 0) "--meet-popularity must be positive"
             >>= validateIntOption (<= 100) "--meet-popularity must be <= 100"
         )
@@ -129,7 +136,7 @@
 optMeetLength =
   optional
     ( option
-        (auto >>= validateIntOption (> 0) "--meet-length must be positive")
+        (boundedIntOption >>= validateIntOption (> 0) "--meet-length must be positive")
         ( long "meet-length"
             <> metavar "NODES"
             <> help (printf "The minimum length of an expression that fits into \\phinoMeet{}, in AST nodes (default: %d)" defaultMeetLength)
@@ -229,7 +236,7 @@
 optJobs :: Parser Int
 optJobs =
   option
-    (auto >>= validateIntOption (> 0) "--jobs must be positive")
+    (boundedIntOption >>= validateIntOption (> 0) "--jobs must be positive")
     (long "jobs" <> metavar "JOBS" <> help "Number of workers the --deep walk morphs the bindings of the formation it starts at on, side by side, each with a memo, a tally and fresh names of its own" <> value 1 <> showDefault)
 
 optAcyclic :: Parser (Maybe Acyclic)
@@ -251,7 +258,9 @@
               \over λ names under \"λ\", naming the operands it brings down to data under \"dataize\", the ones \
               \it reduces to a normal form under \"morph\", the terms it rewrites by rules of its own under \
               \\"rewrite\", the terms of those it stands the data of into unknowns under \"symbolize\" and the \
-              \pairs of branches it joins into one term under \"join\", and answering with the term under \"𝑛\""
+              \pairs of branches it joins into one term under \"join\", whether a join takes a term and a \
+              \formation whose φ is that term for one under \"lenient\", the λ functions whose objects a join \
+              \takes for a branch that raises under \"terminators\", and answering with the term under \"𝑛\""
         )
     )
 
@@ -340,7 +349,7 @@
     auto
     ( long "must"
         <> metavar "RANGE"
-        <> help "Must-rewrite range (e.g., '3', '..5', '3..', '3..5'). Stops execution if number of rules applied is not in range. Use 0 to disable."
+        <> help "Must-rewrite range (e.g., '3', '..5', '3..', '3..5'). Stops execution if number of rewriting cycles is not in range. Use 0 to disable."
         <> value MtDisabled
         <> showDefaultWith show
     )
@@ -539,8 +548,8 @@
     <$> ( OptsMatch
             <$> optLogLevel
             <*> optLogLines
-            <*> optSugar
-            <*> optLineFormat
+            <*> optSugar'
+            <*> optLineFormat'
             <*> optional (strOption (long "pattern" <> metavar "EXPRESSION" <> help "Pattern expression to match against"))
             <*> optional (strOption (long "when" <> metavar "CONDITION" <> help "Predicate for matched substitutions"))
             <*> argInputFile
diff --git a/src/CLI/Runners.hs b/src/CLI/Runners.hs
--- a/src/CLI/Runners.hs
+++ b/src/CLI/Runners.hs
@@ -20,7 +20,7 @@
 import Data.List (intercalate)
 import qualified Data.List.NonEmpty as NE
 import qualified Data.Map.Strict as Map
-import Data.Maybe (fromJust, isJust, isNothing)
+import Data.Maybe (fromJust, isJust, isNothing, listToMaybe)
 import qualified Data.Text as T
 import Dataize
 import Deps (Judgment (..), dontSaveMade)
@@ -34,6 +34,7 @@
 import Logger
 import Margin (defaultMargin)
 import Merge (merge)
+import Metas (Metas (..))
 import Morph
 import Parser (parseExpressionThrows)
 import qualified Printer as P
@@ -41,8 +42,9 @@
 import Rewriter
 import Rule (RuleContext (..), matchExpressionWithRule)
 import Slots (anonymous)
-import System.Directory (doesFileExist, getModificationTime)
+import System.Directory (canonicalizePath, doesFileExist, getModificationTime)
 import System.Exit (exitSuccess)
+import System.FilePath (takeDirectory, (</>))
 import System.Random (mkStdGen, setStdGen)
 import Tau (seedTaus)
 import Text.Printf (printf)
@@ -86,10 +88,13 @@
     validateOpts = do
       when (_inPlace && isNothing _inputFile) (invalidCLIArguments "The option --in-place requires an input file")
       when (_inPlace && isJust _targetFile) (invalidCLIArguments "The options --in-place and --target cannot be used together")
-      when (_inPlace && _outputFormat /= PHI) (invalidCLIArguments "The option --in-place can only be used together with --output=phi")
+      when (_inPlace && _outputFormat /= _inputFormat) (invalidCLIArguments "The option --in-place requires the output format to match the input format")
       when (_inPlace && _sequence) (invalidCLIArguments "The options --in-place and --sequence cannot be used together, since the file must keep one program")
-      when (_inPlace && _focus /= "Q") (invalidCLIArguments "The options --in-place and --focus cannot be used together, since the file must keep the whole program")
+      focusedExpr <- parseExpressionThrows _focus
+      when (_inPlace && focusedExpr /= ExRoot) (invalidCLIArguments "The options --in-place and --focus cannot be used together, since the file must keep the whole program")
       when (_inPlace && not (null _show)) (invalidCLIArguments "The options --in-place and --show cannot be used together, since the file must keep the whole program")
+      when (_inPlace && not (null _hide)) (invalidCLIArguments "The options --in-place and --hide cannot be used together, since the file must keep the whole program")
+      when (_inPlace && _hideRho) (invalidCLIArguments "The options --in-place and --hide-rho cannot be used together, since the file must keep the whole program")
       when (_update && _inPlace) (invalidCLIArguments "The options --update and --in-place cannot be used together")
       when (_update && isNothing _targetFile) (invalidCLIArguments "The option --update requires --target")
       when (_update && isNothing _inputFile) (invalidCLIArguments "The option --update requires an input file")
@@ -122,13 +127,13 @@
     output target expr = case (_inPlace, target, _inputFile) of
       (True, _, Just file) -> do
         logDebug (printf "The option '--in-place' is specified, writing back to '%s'..." file)
-        overwrite file expr
+        overwrite file (expr ++ "\n")
         logDebug (printf "The file '%s' was modified in-place" file)
       (True, _, Nothing) ->
         error "The option --in-place requires an input file"
       (False, Just file, _) -> do
         logDebug (printf "The option '--target' is specified, printing to '%s'..." file)
-        overwrite file expr
+        overwrite file (expr ++ "\n")
         logDebug (printf "The command result was saved in '%s'" file)
       (False, Nothing, _) -> do
         logDebug "The option '--target' is not specified, printing to console..."
@@ -183,7 +188,7 @@
       _protocol
       printCtx
       ( \record -> do
-          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Dataization, loc)) _depthSensitive _shuffle _partial False 1 _acyclic Dataization [] Map.empty lambdas (building linked) reduction evaluation fired save record linked
+          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Dataization, loc)) _depthSensitive _shuffle _partial False 1 _acyclic Dataization [] Map.empty lambdas [] (building linked) reduction evaluation fired save record linked
           (universe, aiming) <- aimed printCtx Dataization _inside expr ctx
           started universe aiming
           dataize universe emptyState aiming
@@ -265,7 +270,7 @@
       _protocol
       printCtx
       ( \record -> do
-          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Morphing, loc)) _depthSensitive _shuffle _partial _deep _jobs _acyclic Morphing [] Map.empty lambdas (building linked) reduction evaluation fired save record linked
+          let ctx = ReduceContext loc loc Nothing _maxDepth _maxCycles (Steps _maxSteps 0) tally minted deadline memo 1 (Just (1, Morphing, loc)) _depthSensitive _shuffle _partial _deep _jobs _acyclic Morphing [] Map.empty lambdas [] (building linked) reduction evaluation fired save record linked
           (universe, aiming) <- aimed printCtx Morphing _inside expr ctx
           started universe aiming
           morph universe emptyState aiming
@@ -400,6 +405,7 @@
       ptn <- parseExpressionThrows (fromJust _pattern)
       condition <- traverse parseConditionThrows _when
       traverse_ (throwIO . AnonymousMetaInCondition . T.unpack) (anonymous condition)
+      traverse_ (throwIO . UnboundMetaInCondition . T.unpack) (listToMaybe (unboundMetas ptn condition))
       linked <- engine
       substs <- matchExpressionWithRule expr (rule ptn condition) (RuleContext (building linked) Nothing linked._normal)
       if null substs
@@ -408,6 +414,10 @@
   where
     rule :: Expression -> Maybe Y.Condition -> Y.Rule
     rule ptn cnd = Y.Rule "custom" Nothing Nothing ptn ExRoot cnd Nothing Nothing
+    unboundMetas :: Expression -> Maybe Y.Condition -> [T.Text]
+    unboundMetas ptn condition = filter (`notElem` namedMetas ptn) (namedMetas condition)
+    namedMetas :: (Metas a) => a -> [T.Text]
+    namedMetas term = filter ((> 1) . T.length) (metas term)
 
 runCompile :: OptsCompile -> IO ()
 runCompile OptsCompile{..} = do
@@ -415,9 +425,23 @@
   source <- either (throwIO . CouldNotCompile) pure (emitted Y.normalizationRules custom Y.contextualizationRules Y.morphingRules Y.dataizationRules current)
   overwrite _targetFile source
   logInfo (printf "The rules were compiled into '%s'" _targetFile)
-  exists <- doesFileExist "cabal.project.local"
-  if exists
-    then putStrLn "The file 'cabal.project.local' exists, so add these lines to it to link the compiled rules in:\npackage phino\n  flags: +compiled"
-    else do
-      overwrite "cabal.project.local" "package phino\n  flags: +compiled\n"
-      logInfo "The file 'cabal.project.local' was written, so the next build links the compiled rules in"
+  root <- projectRootOf (takeDirectory _targetFile)
+  case root of
+    Nothing -> putStrLn "package phino\n  flags: +compiled"
+    Just dir -> do
+      let localFile = dir </> "cabal.project.local"
+      exists <- doesFileExist localFile
+      if exists
+        then putStrLn (printf "The file '%s' exists, so add these lines to it to link the compiled rules in:\npackage phino\n  flags: +compiled" localFile)
+        else do
+          overwrite localFile "package phino\n  flags: +compiled\n"
+          logInfo (printf "The file '%s' was written, so the next build links the compiled rules in" localFile)
+  where
+    projectRootOf :: FilePath -> IO (Maybe FilePath)
+    projectRootOf dir = do
+      full <- canonicalizePath dir
+      found <- doesFileExist (full </> "phino.cabal")
+      let parent = takeDirectory full
+      if found
+        then pure (Just full)
+        else if parent == full then pure Nothing else projectRootOf parent
diff --git a/src/CLI/Types.hs b/src/CLI/Types.hs
--- a/src/CLI/Types.hs
+++ b/src/CLI/Types.hs
@@ -45,6 +45,7 @@
   | CouldNotPrintExpressionInXMIR
   | EmptySubstsOnMatch
   | AnonymousMetaInCondition String
+  | UnboundMetaInCondition String
   | VersionMismatch String String
   | CouldNotCompile String
   | StaleEngine
@@ -58,6 +59,8 @@
   show EmptySubstsOnMatch = "Provided pattern was not matched, no substitutions are built"
   show (AnonymousMetaInCondition kind) =
     printf "Anonymous meta '!%s' cannot be referenced in --when, only a named one can" kind
+  show (UnboundMetaInCondition kind) =
+    printf "The --when condition reads the meta '!%s' it never binds, since neither --pattern binds it" kind
   show (VersionMismatch expected actual) =
     printf "Version mismatch: --pin requires '%s', but this is phino %s" expected actual
   show (CouldNotCompile reason) = reason
diff --git a/src/CLI/Validators.hs b/src/CLI/Validators.hs
--- a/src/CLI/Validators.hs
+++ b/src/CLI/Validators.hs
@@ -15,6 +15,7 @@
 import Must
 import Parser (parseExpressionThrows)
 import Printer
+import Sugar (revealXi)
 import Text.Printf (printf)
 
 invalidCLIArguments :: String -> IO a
@@ -34,7 +35,7 @@
             ( printf
                 "Only dispatch expression started with Φ (or Q) can be used in --%s, but given: %s"
                 opt
-                (printExpression' expr logPrintConfig)
+                (printExpressionWith revealXi expr logPrintConfig)
             )
 
 validateNoOverlap :: String -> [Expression] -> String -> [Expression] -> IO ()
@@ -85,7 +86,9 @@
 validateMust' must = for_ (validateMust must) invalidCLIArguments
 
 validateXmirOptions :: IOFormat -> [(Bool, String)] -> String -> IO ()
-validateXmirOptions XMIR _ focus = when (focus /= "Q") (invalidCLIArguments "Only --focus=Q is allowed to be used with --output=xmir")
+validateXmirOptions XMIR _ focus = do
+  parsed <- parseExpressionThrows focus
+  when (parsed /= ExRoot) (invalidCLIArguments "Only --focus=Q is allowed to be used with --output=xmir")
 validateXmirOptions _ bools _ =
   let (bools', opts) = unzip bools
    in validateBoolOpts (zip bools' (map (printf "The --%s can be used only with --output=xmir") opts))
diff --git a/src/Canonizer.hs b/src/Canonizer.hs
--- a/src/Canonizer.hs
+++ b/src/Canonizer.hs
@@ -4,52 +4,61 @@
 module Canonizer (canonize, canonizeExpr, lambdaNames) where
 
 import AST
+import qualified Data.Map as Map
 import qualified Data.Text as T
 import Rewriter (Rewritten)
 
-canonizeBindings :: [Binding] -> Int -> ([Binding], Int)
-canonizeBindings [] idx = ([], idx)
-canonizeBindings ((BiLambda (Function name)) : rest) idx
+type CanonState = (Map.Map T.Text T.Text, Int)
+
+canonizeBindings :: [Binding] -> CanonState -> ([Binding], CanonState)
+canonizeBindings [] st = ([], st)
+canonizeBindings ((BiLambda (Function name)) : rest) st
   | name == T.pack "Package" =
-      let (bds', idx') = canonizeBindings rest idx
-       in (BiLambda (Function name) : bds', idx')
-canonizeBindings ((BiLambda (Function _)) : rest) idx =
-  let (bds', idx') = canonizeBindings rest (idx + 1)
-   in (BiLambda (Function (T.pack ("Fn" <> show idx))) : bds', idx')
-canonizeBindings (BiTau attr expr : rest) idx =
-  let (expr', idx') = canonizeExpression expr idx
-      (bds', idx'') = canonizeBindings rest idx'
-   in (BiTau attr expr' : bds', idx'')
-canonizeBindings (bd : rest) idx =
-  let (bds', idx') = canonizeBindings rest idx
-   in (bd : bds', idx')
+      let (bds', st') = canonizeBindings rest st
+       in (BiLambda (Function name) : bds', st')
+canonizeBindings ((BiLambda (Function name)) : rest) (names, idx) =
+  case Map.lookup name names of
+    Just canonical ->
+      let (bds', st') = canonizeBindings rest (names, idx)
+       in (BiLambda (Function canonical) : bds', st')
+    Nothing ->
+      let canonical = T.pack ("Fn" <> show idx)
+          (bds', st') = canonizeBindings rest (Map.insert name canonical names, idx + 1)
+       in (BiLambda (Function canonical) : bds', st')
+canonizeBindings (BiTau attr expr : rest) st =
+  let (expr', st') = canonizeExpression expr st
+      (bds', st'') = canonizeBindings rest st'
+   in (BiTau attr expr' : bds', st'')
+canonizeBindings (bd : rest) st =
+  let (bds', st') = canonizeBindings rest st
+   in (bd : bds', st')
 
-canonizeExpression :: Expression -> Int -> (Expression, Int)
-canonizeExpression (ExFormation bds) idx =
-  let (bds', idx') = canonizeBindings bds idx
-   in (ExFormation bds', idx')
-canonizeExpression (ExDispatch expr attr) idx =
-  let (expr', idx') = canonizeExpression expr idx
-   in (ExDispatch expr' attr, idx')
-canonizeExpression (ExApplication expr arg) idx =
-  let (expr', idx') = canonizeExpression expr idx
-      (arg', idx'') = canonizeArgument arg idx'
-   in (ExApplication expr' arg', idx'')
-canonizeExpression (ExPhiMeet prefix num expr) idx =
-  let (expr', idx') = canonizeExpression expr idx
-   in (ExPhiMeet prefix num expr', idx')
-canonizeExpression (ExPhiAgain prefix num expr) idx =
-  let (expr', idx') = canonizeExpression expr idx
-   in (ExPhiAgain prefix num expr', idx')
-canonizeExpression expr idx = (expr, idx)
+canonizeExpression :: Expression -> CanonState -> (Expression, CanonState)
+canonizeExpression (ExFormation bds) st =
+  let (bds', st') = canonizeBindings bds st
+   in (ExFormation bds', st')
+canonizeExpression (ExDispatch expr attr) st =
+  let (expr', st') = canonizeExpression expr st
+   in (ExDispatch expr' attr, st')
+canonizeExpression (ExApplication expr arg) st =
+  let (expr', st') = canonizeExpression expr st
+      (arg', st'') = canonizeArgument arg st'
+   in (ExApplication expr' arg', st'')
+canonizeExpression (ExPhiMeet prefix num expr) st =
+  let (expr', st') = canonizeExpression expr st
+   in (ExPhiMeet prefix num expr', st')
+canonizeExpression (ExPhiAgain prefix num expr) st =
+  let (expr', st') = canonizeExpression expr st
+   in (ExPhiAgain prefix num expr', st')
+canonizeExpression expr st = (expr, st)
 
-canonizeArgument :: Argument -> Int -> (Argument, Int)
-canonizeArgument (ArTau attr expr) idx =
-  let (expr', idx') = canonizeExpression expr idx
-   in (ArTau attr expr', idx')
-canonizeArgument (ArAlpha alpha expr) idx =
-  let (expr', idx') = canonizeExpression expr idx
-   in (ArAlpha alpha expr', idx')
+canonizeArgument :: Argument -> CanonState -> (Argument, CanonState)
+canonizeArgument (ArTau attr expr) st =
+  let (expr', st') = canonizeExpression expr st
+   in (ArTau attr expr', st')
+canonizeArgument (ArAlpha alpha expr) st =
+  let (expr', st') = canonizeExpression expr st
+   in (ArAlpha alpha expr', st')
 
 lambdaNames :: Expression -> [T.Text]
 lambdaNames (ExFormation bds) = concatMap named bds
@@ -66,7 +75,7 @@
 lambdaNames _ = []
 
 canonizeExpr :: Expression -> Expression
-canonizeExpr expr = fst (canonizeExpression expr 1)
+canonizeExpr expr = fst (canonizeExpression expr (Map.empty, 1))
 
 canonize :: [Rewritten] -> [Rewritten]
 canonize [] = []
diff --git a/src/Emit.hs b/src/Emit.hs
--- a/src/Emit.hs
+++ b/src/Emit.hs
@@ -12,7 +12,7 @@
 import Data.Char (isAlphaNum, isDigit, toLower, toUpper)
 import Data.List (intercalate, nub)
 import qualified Data.Map.Strict as Map
-import Data.Maybe (fromMaybe, isJust)
+import Data.Maybe (fromMaybe, isJust, isNothing)
 import qualified Data.Set as Set
 import qualified Data.Text as T
 import Deps (Judgment)
@@ -217,7 +217,7 @@
     refused :: Either String ()
     refused
       | isJust rule.having = Left (printf "The rule '%s' cannot be compiled, since it has a 'having' condition" rule.name)
-      | fast rule.pattern rule.result = Left (printf "The rule '%s' cannot be compiled, since it rewrites a formation into a formation the fast way" rule.name)
+      | isNothing rule.when && fast rule.pattern rule.result = Left (printf "The rule '%s' cannot be compiled, since it rewrites a formation into a formation the fast way" rule.name)
       | rooted rule.pattern = Left (printf "The rule '%s' cannot be compiled, since its pattern applies Φ to a ρ" rule.name)
       | otherwise = Right ()
     walked :: Emitting a -> Either String a
diff --git a/src/Evaluate.hs b/src/Evaluate.hs
--- a/src/Evaluate.hs
+++ b/src/Evaluate.hs
@@ -18,7 +18,7 @@
 import qualified Data.Text as T
 import Deps (Evaluation (..), Judgment (Morphing), State (..), resited)
 import Engine (Engine (..))
-import Lambdas (Lambda (..), Meta (..), joined, matched, minted, symbolized)
+import Lambdas (Lambda (..), Meta (..), joined, matched, minted, symbolized, thrown)
 import Matcher (MetaValue (..), Subst, combine, substEmpty, substSingle, substSlot)
 import Morph (Answer, Firing (..), Kept (..), ReduceContext (..), ReduceException (..), Refused (..), Steps (..), admitted, charged, counted, deeper, isLambda, lambda, morphing, normalized, opening, recalled, refused, remember, remembered, retained, settled, starved, unparked)
 import Printer (printFunction)
@@ -70,7 +70,7 @@
       let ctx = caller{_nesting = caller._nesting + 1}
       outcome <- try $ do
         (bound, dataized, conditions) <- foldM (down ctx) (substEmpty, state, []) entry._dataized
-        (bound', morphed, normals) <- foldM (through ctx) (bound, dataized, []) entry._morphed
+        (bound', morphed, normals) <- foldM (through ctx{_throwing = entry._terminators}) (bound, dataized, []) entry._morphed
         let firing = Firing func (reverse conditions) (reverse normals)
         known <- remembered caller._memo firing
         maybe (worked ctx entry firing bound' morphed) (\answer -> (answer, morphed) <$ shown ctx._nesting answer) known
@@ -87,7 +87,7 @@
     worked ctx entry firing@(Firing _ operands _) bound state' = do
       rewrote <- foldM (reshaped ctx) bound entry._rewritten
       stood <- foldM (masked ctx) rewrote entry._symbolized
-      forked <- foldM (paired ctx (listToMaybe operands)) stood entry._paired
+      forked <- foldM (paired ctx entry (listToMaybe operands)) stood entry._paired
       (answer, state'') <- answered ctx entry operands forked state'
       remember caller._memo firing answer
       pure (answer, state'')
@@ -156,19 +156,21 @@
       where
         fact :: (Int, Bytes) -> Evaluation
         fact (fresh, bytes) = EvKnown ctx._nesting fresh bytes
-    paired :: ReduceContext -> Maybe (Either Int Bytes) -> Subst -> (Meta, (Meta, Meta)) -> IO Subst
-    paired ctx condition bound (meta, (left, right)) = do
+    paired :: ReduceContext -> Lambda -> Maybe (Either Int Bytes) -> Subst -> (Meta, (Meta, Meta)) -> IO Subst
+    paired ctx entry condition bound (meta, (left, right)) = do
       one <- branch left
       two <- branch right
-      case (one, two) of
-        (ExTermination, ExTermination) -> both one two
-        (ExTermination, _) -> terminating "left" left two
-        (_, ExTermination) -> terminating "right" right one
+      case (raising one, raising two) of
+        (True, False) -> terminating "left" left two
+        (False, True) -> terminating "right" right one
         _ -> both one two
       where
+        raising :: Expression -> Bool
+        raising ExTermination = True
+        raising term = thrown entry._terminators term
         both :: Expression -> Expression -> IO Subst
         both one two = do
-          outcome <- joined one two <$> readIORef ctx._minted
+          outcome <- joined entry._lenient one two <$> readIORef ctx._minted
           case outcome of
             Nothing -> throwIO (Stuck func)
             Just (term, made, spent) -> do
diff --git a/src/Files.hs b/src/Files.hs
--- a/src/Files.hs
+++ b/src/Files.hs
@@ -17,16 +17,22 @@
 data FsException
   = FileDoesNotExist {_file :: FilePath}
   | DirectoryDoesNotExist {_dir :: FilePath}
+  | PathIsDirectory {_path :: FilePath}
   deriving (Exception)
 
 instance Show FsException where
   show FileDoesNotExist{..} = printf "File '%s' does not exist" _file
   show DirectoryDoesNotExist{..} = printf "Directory '%s' does not exist" _dir
+  show PathIsDirectory{..} = printf "'%s' is a directory, while a file is expected" _path
 
 ensuredFile :: FilePath -> IO FilePath
 ensuredFile pth = do
-  exists <- doesFileExist pth
-  if exists then pure pth else throwIO (FileDoesNotExist pth)
+  isDir <- doesDirectoryExist pth
+  if isDir
+    then throwIO (PathIsDirectory pth)
+    else do
+      exists <- doesFileExist pth
+      if exists then pure pth else throwIO (FileDoesNotExist pth)
 
 overwrite :: FilePath -> String -> IO ()
 overwrite path content = do
diff --git a/src/Filter.hs b/src/Filter.hs
--- a/src/Filter.hs
+++ b/src/Filter.hs
@@ -7,12 +7,13 @@
 
 import AST
 import Control.Exception (throwIO)
-import Locator (LocatorException (CanNotFindObjectByLocator, InvalidLocatorProvided))
+import Locator (LocatorException (CanNotFindObjectByLocator, CanNotHideWholeProgram, InvalidLocatorProvided))
 import Misc
 import Rewriter
 
 exclude' :: Expression -> [Expression] -> IO Expression
 exclude' expr [] = pure expr
+exclude' _ (ExRoot : _) = throwIO (CanNotHideWholeProgram ExRoot)
 exclude' expr (fqn : remaining) = case fqnToAttrs fqn of
   Just attrs@(_ : _) -> maybe (throwIO (CanNotFindObjectByLocator fqn)) (`exclude'` remaining) (excludedFormation expr attrs)
   _ -> throwIO (InvalidLocatorProvided fqn)
diff --git a/src/Functions.hs b/src/Functions.hs
--- a/src/Functions.hs
+++ b/src/Functions.hs
@@ -13,6 +13,7 @@
 import Control.Monad (when)
 import qualified Data.ByteString.Char8 as B
 import Data.Functor
+import qualified Data.Map.Strict as Map
 import qualified Data.Set as Set
 import qualified Data.Text as T
 import qualified Data.Text.Encoding as T
@@ -21,7 +22,7 @@
 import Logger (logDebug)
 import Matcher
 import Misc
-import Parser (parseAttributeThrows, parseNumberThrows)
+import Parser (ParserException (CouldNotParseAttribute), parseAttributeThrows, parseNumberThrows)
 import Printer (printAttribute, printExpression, printExtraArg)
 import Random (randomString)
 import Regexp
@@ -101,6 +102,7 @@
   case expr' of
     DataObject _ bytes -> pure (TeBytes bytes)
     ExApplication (BaseObject "bytes") (ArTau AtPhi (ExFormation bds)) | Just bytes <- delta bds -> pure (TeBytes bytes)
+    ExApplication (BaseObject "bytes") (ArAlpha (Alpha 0) (ExFormation bds)) | Just bytes <- delta bds -> pure (TeBytes bytes)
     ExFormation bds | Just bytes <- delta bds -> pure (TeBytes bytes)
     _ -> throwIO (userError "Only data objects and bytes are supported by 'dataize' function now")
   where
@@ -142,21 +144,24 @@
     parse input =
       case B.stripPrefix "s/" input of
         Just body ->
-          let (pat, rest) = nextUntilSlash body B.empty False
-              (rep, flag) = nextUntilSlash rest B.empty True
-           in case flag of
-                "g" -> pure (pat, rep, True)
-                "" -> pure (pat, rep, False)
-                _ -> throwIO (userError "sed pattern must be in format s/pat/rep/[g]")
+          let (pat, rest, separator) = nextUntilSlash body B.empty False
+           in if separator
+                then
+                  let (rep, flag, _) = nextUntilSlash rest B.empty True
+                   in case flag of
+                        "g" -> pure (pat, rep, True)
+                        "" -> pure (pat, rep, False)
+                        _ -> throwIO (userError "sed pattern must be in format s/pat/rep/[g]")
+                else throwIO (userError "sed pattern must be in format s/pat/rep/[g]")
         _ -> throwIO (userError "sed pattern must start with s/")
-    nextUntilSlash :: B.ByteString -> B.ByteString -> Bool -> (B.ByteString, B.ByteString)
+    nextUntilSlash :: B.ByteString -> B.ByteString -> Bool -> (B.ByteString, B.ByteString, Bool)
     nextUntilSlash input acc escape = case B.uncons input of
-      Nothing -> (acc, B.empty)
+      Nothing -> (acc, B.empty, False)
       Just (h, rest)
         | h == '\\' -> case B.uncons rest of
             Just (h', rest') -> nextUntilSlash rest' ((if escape then acc else B.snoc acc '\\') `B.append` B.singleton h') escape
-            Nothing -> (if escape then acc else B.snoc acc '\\', B.empty)
-        | h == '/' -> (acc, rest)
+            Nothing -> (if escape then acc else B.snoc acc '\\', B.empty, False)
+        | h == '/' -> (acc, rest, True)
         | otherwise -> nextUntilSlash rest (B.snoc acc h) escape
 
 _randomString :: BuildTermMethod
@@ -175,8 +180,13 @@
 _tau :: BuildTermMethod
 _tau [Y.ArgExpression expr] subst = do
   TeBytes bts <- _dataize [Y.ArgExpression expr] subst
-  attr <- parseAttributeThrows (btsToUnescapedStr bts)
-  pure (TeAttribute attr)
+  let str = btsToUnescapedStr bts
+  attr <- parseAttributeThrows str
+  let notReal = throwIO (CouldNotParseAttribute (printf "'%s' is a meta attribute, not a real one" str))
+  case attr of
+    AtMeta _ -> notReal
+    AtAny _ -> notReal
+    _ -> pure (TeAttribute attr)
 _tau _ _ = throwIO (userError "Function tau() requires exactly 1 argument as expression")
 
 _string :: BuildTermMethod
@@ -221,37 +231,85 @@
 _join :: BuildTermMethod
 _join [] _ = pure (TeBindings [])
 _join args subst = do
-  bds <- buildBindings args
+  bds <- buildBindingGroups args
   TeBindings <$> go bds Set.empty
   where
-    buildBindings :: [Y.ExtraArgument] -> IO [Binding]
-    buildBindings [] = pure []
-    buildBindings (Y.ArgBinding bd : args') = do
+    buildBindingGroups :: [Y.ExtraArgument] -> IO [[Binding]]
+    buildBindingGroups [] = pure []
+    buildBindingGroups (Y.ArgBinding bd : args') = do
       bds <- buildBindingThrows bd subst
-      next <- buildBindings args'
-      pure (bds ++ next)
-    buildBindings _ = throwIO (userError "Function join() can work with bindings only")
-    go :: [Binding] -> Set.Set Attribute -> IO [Binding]
+      next <- buildBindingGroups args'
+      pure (bds : next)
+    buildBindingGroups _ = throwIO (userError "Function join() can work with bindings only")
+    go :: [[Binding]] -> Set.Set Attribute -> IO [Binding]
     go [] _ = pure []
-    go (bd : bds) attrs =
+    go (group : groups) attrs = do
+      (renamed, next) <- renameGroup group attrs
+      rest <- go groups next
+      pure (renamed ++ rest)
+    renameGroup :: [Binding] -> Set.Set Attribute -> IO ([Binding], Set.Set Attribute)
+    renameGroup bds attrs = do
+      let groupAttrs = Set.fromList [attr | bd <- bds, Just attr <- [attributeFromBinding bd]]
+      (renamed, used, changes) <- renameBindings bds attrs (Set.union attrs groupAttrs) Map.empty
+      pure (map (renameValue changes) renamed, used)
+    renameBindings ::
+      [Binding] ->
+      Set.Set Attribute ->
+      Set.Set Attribute ->
+      Map.Map Attribute Attribute ->
+      IO ([Binding], Set.Set Attribute, Map.Map Attribute Attribute)
+    renameBindings [] attrs _ changes = pure ([], attrs, changes)
+    renameBindings (bd : bds) attrs unavailable changes =
       case attributeFromBinding bd of
         Just attr
-          | Set.member attr attrs ->
-              if attr == AtRho || attr == AtDelta || attr == AtLambda
-                then go bds attrs
-                else do
-                  new <- case bd of
-                    BiTau _ ex -> (`BiTau` ex) <$> freshAttr
-                    BiVoid _ -> BiVoid <$> freshAttr
-                    other -> pure other
-                  (new :) <$> go bds attrs
-          | otherwise -> (bd :) <$> go bds (Set.insert attr attrs)
-        Nothing -> (bd :) <$> go bds attrs
-    freshAttr :: IO Attribute
-    freshAttr = do
+          | Set.member attr attrs && (attr == AtRho || attr == AtDelta || attr == AtLambda) ->
+              renameBindings bds attrs unavailable changes
+          | Set.member attr attrs -> do
+              new <- freshAttr unavailable
+              let renamed = case bd of
+                    BiTau _ ex -> BiTau new ex
+                    BiVoid _ -> BiVoid new
+                    other -> other
+              (rest, used, next) <-
+                renameBindings
+                  bds
+                  (Set.insert new attrs)
+                  (Set.insert new unavailable)
+                  (Map.insert attr new changes)
+              pure (renamed : rest, used, next)
+          | otherwise -> do
+              (rest, used, next) <-
+                renameBindings
+                  bds
+                  (Set.insert attr attrs)
+                  unavailable
+                  changes
+              pure (bd : rest, used, next)
+        Nothing -> do
+          (rest, used, next) <- renameBindings bds attrs unavailable changes
+          pure (bd : rest, used, next)
+    renameValue :: Map.Map Attribute Attribute -> Binding -> Binding
+    renameValue changes (BiTau attr expr) = BiTau attr (renameExpression changes expr)
+    renameValue _ binding = binding
+    renameExpression :: Map.Map Attribute Attribute -> Expression -> Expression
+    renameExpression changes expression = case expression of
+      ExFormation _ -> expression
+      ExApplication expr arg -> ExApplication (renameExpression changes expr) (renameArgument changes arg)
+      ExDispatch ExXi attr -> ExDispatch ExXi (Map.findWithDefault attr attr changes)
+      ExDispatch expr attr -> ExDispatch (renameExpression changes expr) attr
+      ExPhiMeet prefix idx expr -> ExPhiMeet prefix idx (renameExpression changes expr)
+      ExPhiAgain prefix idx expr -> ExPhiAgain prefix idx (renameExpression changes expr)
+      other -> other
+    renameArgument :: Map.Map Attribute Attribute -> Argument -> Argument
+    renameArgument changes (ArTau attr expr) = ArTau attr (renameExpression changes expr)
+    renameArgument changes (ArAlpha alpha expr) = ArAlpha alpha (renameExpression changes expr)
+    freshAttr :: Set.Set Attribute -> IO Attribute
+    freshAttr unavailable = do
       term <- _randomTau [] subst
       case term of
-        TeAttribute attr' -> pure attr'
+        TeAttribute attr'
+          | Set.member attr' unavailable -> freshAttr unavailable
+          | otherwise -> pure attr'
         _ -> throwIO (userError "random-tau() did not return an attribute, internal invariant violated")
 
 _unsupported :: BuildTermFunc
diff --git a/src/LaTeX.hs b/src/LaTeX.hs
--- a/src/LaTeX.hs
+++ b/src/LaTeX.hs
@@ -28,6 +28,7 @@
 import Bytes (nonFiniteName)
 import CST
 import Canonizer (canonize, canonizeExpr)
+import Data.Char (isAscii, ord)
 import Data.List (intercalate, nub, zipWith4)
 import Data.Maybe (isJust)
 import qualified Data.Text as T
@@ -35,14 +36,14 @@
 import Encoding
 import Lining
 import Locator (locatedExpression)
-import Margin (WithMargin, defaultMargin, withMargin)
+import Margin (WithMargin, defaultMargin, withLines)
 import Matcher
 import Metas (lonely)
 import Misc
 import Render (Render (render))
 import Replacer (replaceExpression)
 import Rewriter (Rewritten, Rewrittens', stepHeaders)
-import Sugar (SugarType (SWEET), ToSalty, withSugarType)
+import Sugar (SugarType (SWEET), ToSalty, withSugarType, withoutRho)
 import Text.Printf (printf)
 import Text.Read (readMaybe)
 import qualified Yaml as Y
@@ -61,10 +62,11 @@
   , _label :: Maybe String
   , _meetPrefix :: Maybe String
   , _headers :: Bool
+  , _hideRho :: Bool
   }
 
 defaultLatexContext :: LatexContext
-defaultLatexContext = LatexContext SWEET SINGLELINE defaultMargin False False False defaultMeetPopularity defaultMeetLength ExRoot Nothing Nothing Nothing False
+defaultLatexContext = LatexContext SWEET SINGLELINE defaultMargin False False False defaultMeetPopularity defaultMeetLength ExRoot Nothing Nothing Nothing False False
 
 defaultMeetPopularity :: Int
 defaultMeetPopularity = 50
@@ -133,7 +135,7 @@
     popularity = toDouble _meetPopularity / 100.0
 
 renderToLatex :: (ToSalty a, ToASCII a, ToSingleLine a, ToLaTeX a, WithMargin a, Render a) => a -> LatexContext -> String
-renderToLatex renderable LatexContext{..} = T.unpack $ render (toLaTeX $ withLineFormat _line $ withMargin _margin $ withEncoding ASCII $ withSugarType _sugar renderable)
+renderToLatex renderable LatexContext{..} = T.unpack $ render (toLaTeX $ withLines _line _margin $ withEncoding ASCII $ withSugarType _sugar renderable)
 
 phiquation :: LatexContext -> String
 phiquation LatexContext{_nonumber = True} = "phiquation*"
@@ -220,7 +222,7 @@
   pure
     ( concat
         [ preamble ctx
-        , body (stepComments rewrittens ctx) (canonizedRewrittens (compressedRewrittens rewrittens ctx) ctx) (\tabs expr -> renderToLatex (expressionToCSTFrom tabs expr) ctx)
+        , body (stepComments rewrittens ctx) (canonizedRewrittens (compressedRewrittens rewrittens ctx) ctx) (\tabs expr -> renderToLatex (shown ctx (expressionToCSTFrom tabs expr)) ctx)
         , ending exceeded (last (arrows (map snd rewrittens))) ctx
         ]
     )
@@ -230,16 +232,21 @@
   pure
     ( concat
         [ preamble ctx
-        , body (stepComments rewrittens ctx) (zip (canonizedExpressions (compressedExpressions focused ctx) ctx) rules) (\tabs expr -> renderToLatex (expressionToCSTFrom tabs expr) ctx)
+        , body (stepComments rewrittens ctx) (zip (canonizedExpressions (compressedExpressions focused ctx) ctx) rules) (\tabs expr -> renderToLatex (shown ctx (expressionToCSTFrom tabs expr)) ctx)
         , ending exceeded (last (arrows (map snd rewrittens))) ctx
         ]
     )
 
+shown :: LatexContext -> EXPRESSION -> EXPRESSION
+shown LatexContext{..}
+  | _hideRho = withoutRho _sugar
+  | otherwise = id
+
 expressionToLaTeX :: Expression -> LatexContext -> String
 expressionToLaTeX ex ctx =
   concat
     [ preamble ctx
-    , renderToLatex (expressionToCST ex) ctx
+    , renderToLatex (shown ctx (expressionToCST ex)) ctx
     , period ctx
     ]
 
@@ -357,7 +364,12 @@
       escape '{' = "\\char123{}"
       escape '}' = "\\char125{}"
       escape '~' = "\\char126{}"
-      escape ch = T.singleton ch
+      escape 'ρ' = render RHO'
+      escape 'ξ' = render XI'
+      escape 'φ' = "\\phiTerminal{\\varphi}"
+      escape ch
+        | isAscii ch = T.singleton ch
+        | otherwise = T.pack (printf "[U+%04X]" (ord ch))
 
 instance ToLaTeX SET where
   toLaTeX ST_BINDING{..} = ST_BINDING (toLaTeX binding)
diff --git a/src/Lambdas.hs b/src/Lambdas.hs
--- a/src/Lambdas.hs
+++ b/src/Lambdas.hs
@@ -18,15 +18,17 @@
   , readLambdas
   , symbolized
   , taken
+  , thrown
   )
 where
 
 import AST
+import Control.Applicative ((<|>))
 import Control.Exception (Exception, throwIO)
 import Control.Monad (void)
 import Data.Aeson (FromJSON (parseJSON), Key, Object, Value (Object), withObject, (.!=), (.:), (.:?))
 import Data.Char (isDigit)
-import Data.List (find, sortOn)
+import Data.List (find, nub, sortOn)
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as Map
 import Data.Maybe (listToMaybe)
@@ -57,6 +59,8 @@
   , _rewritten :: [(Meta, (Meta, [Y.Rule]))]
   , _symbolized :: [(Meta, Expression)]
   , _paired :: [(Meta, (Meta, Meta))]
+  , _lenient :: Bool
+  , _terminators :: [Text]
   , _answer :: Expression
   }
 
@@ -81,6 +85,8 @@
         <*> rewrites (T.unpack key) entry
         <*> operands key expressionMeta entry "symbolize"
         <*> pairs (T.unpack key) entry
+        <*> entry .:? "lenient" .!= False
+        <*> entry .:? "terminators" .!= []
         <*> pure answer
     sigmas (T.unpack key) lambda._answer
     dataless (T.unpack key) lambda._answer
@@ -89,6 +95,7 @@
     mapM_ (metaless (T.unpack key) "dataize") lambda._dataized
     mapM_ (metaless (T.unpack key) "morph") lambda._morphed
     answered (T.unpack key) lambda
+    lonely (T.unpack key) lambda
     pure lambda
     where
       operands :: Text -> (Text -> Yaml.Parser Meta) -> Object -> Key -> Yaml.Parser [(Meta, Expression)]
@@ -236,6 +243,10 @@
               ++ map (_name . fst) lambda._rewritten
               ++ map (_name . fst) lambda._symbolized
               ++ map (_name . fst) lambda._paired
+      lonely :: String -> Lambda -> Yaml.Parser ()
+      lonely key lambda
+        | lambda._lenient && null lambda._paired = fail (printf "The λ function '%s' is lenient but has no 'join' line to be lenient in" key)
+        | otherwise = pure ()
       dataless :: String -> Expression -> Yaml.Parser ()
       dataless key answer
         | computes answer = fail (printf "The '𝑛' of λ function '%s' reads data, while a symbolic answer may mention nothing but 𝜎" key)
@@ -281,6 +292,7 @@
   mapM_ (unique entries) entries
   registered <- mapM keyed entries
   overlaps path registered
+  mapM_ (unanswered registered) entries
   logDebug (printf "Loaded %d λ function(s) from '%s'" (length entries) path)
   pure (Lambdas registered)
   where
@@ -297,6 +309,15 @@
     unreadable :: Text -> (a, String) -> IO b
     unreadable key (_, failure) =
       throwIO (BrokenLambdas path (printf "the key '%s' is not a regular expression: %s" (T.unpack key) failure))
+    unanswered :: [(Regex, Lambda)] -> Lambda -> IO ()
+    unanswered registered entry = case [(name, other) | name <- entry._terminators, Just other <- [matched (Lambdas registered) name]] of
+      [] -> pure ()
+      (name, other) : _ ->
+        throwIO
+          ( BrokenLambdas
+              path
+              (printf "the terminator '%s' of the key '%s' is answered by the key '%s'" (T.unpack name) (T.unpack entry._key) (T.unpack other._key))
+          )
 
     overlaps :: FilePath -> [(Regex, Lambda)] -> IO ()
     overlaps _ [_] = pure ()
@@ -325,6 +346,11 @@
 matched :: Lambdas -> Text -> Maybe Lambda
 matched (Lambdas entries) func = snd <$> find (\(key, _) -> matchTest key (encodeUtf8 func)) entries
 
+thrown :: [Text] -> Expression -> Bool
+thrown names (ExApplication expr _) = thrown names expr
+thrown names (ExFormation bds) = any (`elem` [BiLambda (Function name) | name <- names]) bds
+thrown _ _ = False
+
 minted :: Expression -> Int -> ([(Slot, Function)], Int)
 minted answer spent = (zip fresh [FnSymbol idx | idx <- [spent + 1 ..]], spent + length fresh)
   where
@@ -381,40 +407,62 @@
 
 type Joining = (Int, Map (Int, Int) Int, [(Int, (Int, Int))])
 
-joined :: Expression -> Expression -> Int -> Maybe (Expression, [(Int, (Int, Int))], Int)
-joined left right spent = taking <$> goExpr left right (spent, Map.empty, [])
+joined :: Bool -> Expression -> Expression -> Int -> Maybe (Expression, [(Int, (Int, Int))], Int)
+joined lenient left right spent = taking <$> goExpr left right (spent, Map.empty, [])
   where
     taking :: (Expression, Joining) -> (Expression, [(Int, (Int, Int))], Int)
     taking (term, (spent', _, made)) = (term, reverse made, spent')
     goExpr :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)
-    goExpr one@(ExFormation _) two@(ExFormation _) joining
+    goExpr one two joining = goAlike one two joining <|> goSole one two joining <|> goDecorated one two joining
+    goAlike :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)
+    goAlike one@(ExFormation _) two@(ExFormation _) joining
       | bare one /= bare two = do
           mine <- ending one
           theirs <- ending two
           (bd, joining') <- goBinding mine theirs joining
           pure (ExFormation [bd], joining')
-    goExpr (ExFormation one) (ExFormation two) joining = do
+    goAlike (ExFormation one) (ExFormation two) joining = do
       (bds, joining') <- goBindings one two joining
       pure (ExFormation bds, joining')
-    goExpr (ExApplication one arg) (ExApplication two arg') joining = do
+    goAlike (ExApplication one arg) (ExApplication two arg') joining = do
       (expr, joining') <- goExpr one two joining
       (applied, joining'') <- goArgument arg arg' joining'
       pure (ExApplication expr applied, joining'')
-    goExpr (ExDispatch one attr) (ExDispatch two attr') joining
+    goAlike (ExDispatch one attr) (ExDispatch two attr') joining
       | attr == attr' = do
           (expr, joining') <- goExpr one two joining
           pure (ExDispatch expr attr, joining')
-    goExpr (ExPhiMeet prefix idx one) (ExPhiMeet prefix' idx' two) joining
+    goAlike (ExPhiMeet prefix idx one) (ExPhiMeet prefix' idx' two) joining
       | prefix == prefix' && idx == idx' = do
           (expr, joining') <- goExpr one two joining
           pure (ExPhiMeet prefix idx expr, joining')
-    goExpr (ExPhiAgain prefix idx one) (ExPhiAgain prefix' idx' two) joining
+    goAlike (ExPhiAgain prefix idx one) (ExPhiAgain prefix' idx' two) joining
       | prefix == prefix' && idx == idx' = do
           (expr, joining') <- goExpr one two joining
           pure (ExPhiAgain prefix idx expr, joining')
-    goExpr one two joining
+    goAlike one two joining
       | one == two = Just (one, joining)
       | otherwise = Nothing
+    goSole :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)
+    goSole (ExFormation [BiLambda (FnSymbol mine)]) two joining
+      | lenient, [theirs] <- nub (symbols two) = Just (renamed theirs two (picked (mine, theirs) joining))
+    goSole one (ExFormation [BiLambda (FnSymbol theirs)]) joining
+      | lenient, [mine] <- nub (symbols one) = Just (renamed mine one (picked (mine, theirs) joining))
+    goSole _ _ _ = Nothing
+    renamed :: Int -> Expression -> (Int, Joining) -> (Expression, Joining)
+    renamed old term (fresh, joining) = (lifted (old - 1) (fresh - old) term, joining)
+    goDecorated :: Expression -> Expression -> Joining -> Maybe (Expression, Joining)
+    goDecorated one two joining
+      | bare one || bare two = Nothing
+      | lenient && depth one < depth two = peeled (goExpr one) two
+      | lenient && depth two < depth one = peeled (`goExpr` two) one
+      | otherwise = Nothing
+      where
+        peeled :: (Expression -> Joining -> Maybe (Expression, Joining)) -> Expression -> Maybe (Expression, Joining)
+        peeled pairing decorator = do
+          inner <- decorated decorator
+          (expr, joining') <- pairing inner joining
+          pure (ExFormation [BiTau AtPhi expr], joining')
     goBindings :: [Binding] -> [Binding] -> Joining -> Maybe ([Binding], Joining)
     goBindings [] [] joining = Just ([], joining)
     goBindings (one : rest) (two : rest') joining = do
@@ -439,8 +487,12 @@
     bare _ = False
     ending :: Expression -> Maybe Binding
     ending (ExFormation [bd@(BiLambda (FnSymbol _))]) = Just bd
-    ending (ExFormation bds) = listToMaybe [body | BiTau AtPhi body <- bds] >>= ending
-    ending _ = Nothing
+    ending term = decorated term >>= ending
+    decorated :: Expression -> Maybe Expression
+    decorated (ExFormation bds) = listToMaybe [body | BiTau AtPhi body <- bds]
+    decorated _ = Nothing
+    depth :: Expression -> Int
+    depth term = maybe 0 (succ . depth) (decorated term)
     goArgument :: Argument -> Argument -> Joining -> Maybe (Argument, Joining)
     goArgument (ArTau attr one) (ArTau attr' two) joining
       | attr == attr' = do
diff --git a/src/Locator.hs b/src/Locator.hs
--- a/src/Locator.hs
+++ b/src/Locator.hs
@@ -16,6 +16,7 @@
 data LocatorException
   = InvalidLocatorProvided {fqn :: Expression}
   | CanNotFindObjectByLocator {fqn :: Expression}
+  | CanNotHideWholeProgram {fqn :: Expression}
   deriving (Exception)
 
 instance Show LocatorException where
@@ -24,6 +25,10 @@
       "Invalid locator is provided. 'Q' or dispatch started with 'Q' expected, but got: '%s'"
       (printExpression fqn)
   show CanNotFindObjectByLocator{..} = printf "Can't find object by locator: '%s'" (printExpression fqn)
+  show CanNotHideWholeProgram{..} =
+    printf
+      "The --hide locator must name an attribute, '%s' would hide the whole program"
+      (printExpression fqn)
 
 locatedExpression :: Expression -> Expression -> IO Expression
 locatedExpression ExRoot expr = pure expr
diff --git a/src/Margin.hs b/src/Margin.hs
--- a/src/Margin.hs
+++ b/src/Margin.hs
@@ -5,11 +5,11 @@
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
-module Margin (defaultMargin, withMargin, WithMargin) where
+module Margin (defaultMargin, withMargin, withLines, WithMargin) where
 
 import CST
 import qualified Data.Text as T
-import Lining (ToSingleLine (..))
+import Lining (LineFormat (..), ToSingleLine (..))
 import Render (Render (..))
 
 defaultMargin :: Int
@@ -18,6 +18,10 @@
 withMargin :: (WithMargin a) => Int -> a -> a
 withMargin margin = withMargin' (0, margin)
 
+withLines :: (WithMargin a, ToSingleLine a) => LineFormat -> Int -> a -> a
+withLines SINGLELINE _ = toSingleLine
+withLines MULTILINE margin = withMargin margin
+
 class WithMargin a where
   withMargin' :: (Int, Int) -> a -> a
 
@@ -102,4 +106,38 @@
   withMargin' _ = id
 
 lengthOf :: Render a => a -> Int
-lengthOf renderable = T.length (render renderable)
+lengthOf renderable = displayWidth (render renderable)
+
+displayWidth :: T.Text -> Int
+displayWidth = T.foldl' addWidth 0
+  where
+    addWidth :: Int -> Char -> Int
+    addWidth acc character = acc + charWidth character
+
+charWidth :: Char -> Int
+charWidth character
+  | isWideCodePoint (fromEnum character) = 2
+  | otherwise = 1
+  where
+    isWideCodePoint :: Int -> Bool
+    isWideCodePoint codePoint = any (inRange codePoint) wideRanges
+
+    inRange :: Int -> (Int, Int) -> Bool
+    inRange codePoint (lo, hi) = codePoint >= lo && codePoint <= hi
+
+    wideRanges :: [(Int, Int)]
+    wideRanges =
+      [ (0x1100, 0x115F)
+      , (0x2E80, 0x303E)
+      , (0x3041, 0x33FF)
+      , (0x3400, 0x4DBF)
+      , (0x4E00, 0x9FFF)
+      , (0xA000, 0xA4CF)
+      , (0xAC00, 0xD7A3)
+      , (0xF900, 0xFAFF)
+      , (0xFE30, 0xFE4F)
+      , (0xFF00, 0xFF60)
+      , (0xFFE0, 0xFFE6)
+      , (0x1F300, 0x1FAFF)
+      , (0x20000, 0x3FFFD)
+      ]
diff --git a/src/Morph.hs b/src/Morph.hs
--- a/src/Morph.hs
+++ b/src/Morph.hs
@@ -31,7 +31,7 @@
 import Engine (Engine (..))
 import GHC.Clock (getMonotonicTime)
 import qualified Inference as In
-import Lambdas (Lambdas, emptyLambdas)
+import Lambdas (Lambdas, emptyLambdas, thrown)
 import Locator (locatedExpression, withLocatedExpression)
 import Matcher (substEmpty)
 import Must (Must (..))
@@ -110,6 +110,7 @@
   , _parked :: [T.Text]
   , _entered :: Seen
   , _symbolic :: Lambdas
+  , _throwing :: [T.Text]
   , _buildTerm :: BuildTermFunc
   , _reduce :: ReductionFunc
   , _evaluate :: EvaluationFunc
@@ -120,7 +121,7 @@
   }
 
 data Budget
-  = Depth Int
+  = Depth Judgment Int
   | Firings Int
   | Cycles Int
 
@@ -137,8 +138,8 @@
   deriving anyclass (Exception)
 
 instance Show ReduceException where
-  show (OutOfSteps (Depth limit)) =
-    printf "Dataization did not finish before reaching the limit of steps: --max-steps=%d" limit
+  show (OutOfSteps (Depth judgment limit)) =
+    printf "%s did not finish before reaching the limit of steps: --max-steps=%d" (show judgment) limit
   show (OutOfSteps (Firings limit)) =
     printf "Evaluation did not finish before reaching the limit of firings: --max-firings=%d" limit
   show (OutOfSteps (Cycles limit)) =
@@ -151,7 +152,7 @@
   show (Looping term) = printf "Reduction entered a formation it is already inside: %s" (printExpression term)
   show (LoopingAt term _ _) = show (Looping term)
   show (Undataizable ExTermination _) = "dataization reached the terminator ⊥, which signals an error and cannot be dataized"
-  show (Undataizable _ _) = "no dataization rule matched"
+  show (Undataizable term _) = printf "no dataization rule matched, while dataization expects a normal form: %s" (printExpression term)
   show (Unmorphable term) = printf "Morphing expects a normal form, but no morphing rule matches: %s" (printExpression term)
 
 data Refused = Refused Acyclic Expression State
@@ -172,7 +173,7 @@
   when (spent >= limit) $ do
     starve ctx._memo
     ctx._saveEval (EvStarved ctx._nesting limit ctx._judgment ctx._site)
-    throwIO (OutOfSteps (Depth limit))
+    throwIO (OutOfSteps (Depth ctx._judgment limit))
   pure ctx{_steps = Steps limit (spent + 1)}
   where
     starve :: Maybe Memo -> IO ()
@@ -400,7 +401,7 @@
     marked ctx func = (walking ctx){_parked = func : _parked}
     walked :: ReduceContext -> Expression -> NonEmpty Rewritten -> State -> IO (Expression, [Rewritten], State)
     walked walker morphed seq state'
-      | not _deep = pure (morphed, reverse (NE.toList seq), state')
+      | not _deep || thrown _throwing morphed = pure (morphed, reverse (NE.toList seq), state')
       | otherwise = do
           (deep, state'') <- deepened Nothing morphed universe state' walker
           seq' <- leadsTo seq (Morphing, "deep") deep walker
@@ -555,36 +556,39 @@
           (entered, state'') <- pooled caller._jobs jobs (gathered floor') ([], state')
           pure (ExFormation (reverse entered), state'')
       where
-        planned :: Int -> Maybe (Expression, [Attribute]) -> (Int, Binding) -> IO (IO ([Evaluation], Int, Either SomeException (Int -> IO (Binding, Maybe State))))
+        planned :: Int -> Maybe (Expression, [Attribute]) -> (Int, Binding) -> IO (IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State))))
         planned floor' alias (idx, BiTau attr body)
           | attr /= AtRho = do
               new <- if closed body then fresh alias attr caller else pure True
               pure (if new then worker floor' idx attr body else kept (BiTau attr body))
         planned _ _ (_, bd) = pure (kept bd)
-        kept :: Binding -> IO ([Evaluation], Int, Either SomeException (Int -> IO (Binding, Maybe State)))
-        kept bd = pure ([], 0, Right (const (pure (bd, Nothing))))
-        worker :: Int -> Int -> Attribute -> Expression -> IO ([Evaluation], Int, Either SomeException (Int -> IO (Binding, Maybe State)))
+        kept :: Binding -> IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State)))
+        kept bd = pure ([], [], 0, Right (const (pure (bd, Nothing))))
+        worker :: Int -> Int -> Attribute -> Expression -> IO ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State)))
         worker floor' idx attr body = do
           buffer <- newIORef []
+          steps <- newIORef []
           tau <- tausOf idx
           tally <- tallied (fmap (\(Tally cap _) -> cap) caller._tally)
           minted <- newIORef floor'
           memo <- memoized caller._acyclic
           copy <- newIORef =<< readIORef world
           (store, path) <- home standing copy form
-          let own = caller{_jobs = 1, _tally = tally, _minted = minted, _memo = memo, _saveEval = modifyIORef' buffer . (:), _buildTerm = minting tau caller._buildTerm}
+          let own = caller{_jobs = 1, _tally = tally, _minted = minted, _memo = memo, _saveEval = modifyIORef' buffer . (:), _saveStep = modifyIORef' steps . (:), _buildTerm = minting tau caller._buildTerm}
           outcome <- try (try (go (fmap (`ExDispatch` attr) standing) Nothing (Frame copy store path (Just attr)) body state' own))
           records <- reverse <$> readIORef buffer
+          taken' <- reverse <$> readIORef steps
           spent <- subtract floor' <$> readIORef minted
-          pure (records, spent, fmap (either (severed floor') (\(term, walked) offset -> pure (BiTau attr (lifted floor' offset term), Just (moved floor' offset walked)))) outcome)
+          pure (records, taken', spent, fmap (either (severed floor') (\(term, walked) offset -> pure (BiTau attr (lifted floor' offset term), Just (moved floor' offset walked)))) outcome)
         severed :: Int -> Severed -> Int -> IO (Binding, Maybe State)
         severed floor' (Severed answer reached) offset = throwIO (Severed (lifted floor' offset answer) (moved floor' offset reached))
         moved :: Int -> Int -> State -> State
         moved floor' offset walked = walked{_manufactured = fmap (\sym -> if sym > floor' then sym + offset else sym) walked._manufactured}
-        gathered :: Int -> ([Binding], State) -> ([Evaluation], Int, Either SomeException (Int -> IO (Binding, Maybe State))) -> IO ([Binding], State)
-        gathered floor' (done, current) (records, spent, outcome) = do
+        gathered :: Int -> ([Binding], State) -> ([Evaluation], [Expression], Int, Either SomeException (Int -> IO (Binding, Maybe State))) -> IO ([Binding], State)
+        gathered floor' (done, current) (records, taken', spent, outcome) = do
           offset <- subtract floor' <$> readIORef caller._minted
           mapM_ (caller._saveEval . renumbered floor' offset) records
+          mapM_ caller._saveStep taken'
           modifyIORef' caller._minted (+ spent)
           (bd, walked) <- either throwIO ($ offset) outcome
           pure (bd : done, fromMaybe current walked)
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -9,6 +9,7 @@
   , parseExpressionThrows
   , parseAttribute
   , parseAttributeThrows
+  , ParserException (..)
   , parseAlpha
   , parseIndex
   , parseNumber
@@ -284,18 +285,26 @@
           choice
             [ rb >> return []
             , do
-                voids' <- map BiVoid <$> void' `sepBy1` symbol ","
+                voids' <- ((,) <$> getOffset <*> (BiVoid <$> void')) `sepBy1` symbol ","
                 rb >> return voids'
             ]
         _ <- arrow
-        opened <- expression
-        case opened of
-          ExFormation bds -> ExFormation <$> validatedBindings (voids ++ bds)
-          _ -> fail "Inline voids open a formation, so nothing but a formation may follow their arrow"
+        choice
+          [ do
+              bds <- formationBindingsOffsets
+              tailed <- option False (True <$ lookAhead (oneOf ".(:"))
+              if tailed
+                then reportDuplicates bds >>= exTail . ExFormation >>= opening voids
+                else ExFormation <$> reportDuplicates (voids ++ bds)
+          , expression >>= opening voids
+          ]
     ]
   where
     rb :: Parser String
     rb = symbol ")"
+    opening :: [(Int, Binding)] -> Expression -> Parser Expression
+    opening voids (ExFormation bds) = ExFormation <$> validatedBindings (map snd voids ++ bds)
+    opening _ _ = fail "Inline voids open a formation, so nothing but a formation may follow their arrow"
 
 lambdaName :: Parser Function
 lambdaName = choice [Function . T.pack <$> function, try (either FnAny FnMeta <$> metaVar 'F' "𝑓"), sigma]
@@ -394,31 +403,37 @@
   Left msg -> fail msg
   Right bds' -> return bds'
 
-formationBindings :: Parser [Binding]
-formationBindings = do
+reportDuplicates :: [(Int, Binding)] -> Parser [Binding]
+reportDuplicates bs = do
+  either (parseError . FancyError (repeating [] bs) . Set.singleton . ErrorFail) (const (pure ())) (uniqueBindings (map snd bs))
+  return (map snd bs)
+  where
+    repeating :: [Attribute] -> [(Int, Binding)] -> Int
+    repeating _ [] = 0
+    repeating seen ((offset, bd) : rest)
+      | any (`elem` seen) (attributesFromBindings [bd]) = offset
+      | otherwise = repeating (seen ++ attributesFromBindings [bd]) rest
+
+formationBindingsOffsets :: Parser [(Int, Binding)]
+formationBindingsOffsets = do
   _ <- choice [symbol "[[", symbol "⟦"]
   choice
     [ rsb >> return []
     , do
         bs <- ((,) <$> getOffset <*> binding) `sepBy1` symbol ","
-        either (parseError . FancyError (repeating [] bs) . Set.singleton . ErrorFail) (const (pure ())) (uniqueBindings (map snd bs))
-        rsb >> return (map snd bs)
+        rsb >> return bs
     ]
   where
     rsb :: Parser String
     rsb = choice [symbol "]]", symbol "⟧"]
-    repeating :: [Attribute] -> [(Int, Binding)] -> Int
-    repeating _ [] = 0
-    repeating seen ((offset, bd) : rest)
-      | any (`elem` seen) (attributesFromBindings [bd]) = offset
-      | otherwise = repeating (seen ++ attributesFromBindings [bd]) rest
 
+formationBindings :: Parser [Binding]
+formationBindings = formationBindingsOffsets >>= reportDuplicates
+
 exHead :: Parser Expression
 exHead =
   choice
-    [ do
-        bs <- formationBindings >>= validatedBindings
-        return (ExFormation bs)
+    [ ExFormation <$> formationBindings
     , do
         _ <- choice [symbol "$", symbol "ξ"]
         return ExXi
diff --git a/src/Printer.hs b/src/Printer.hs
--- a/src/Printer.hs
+++ b/src/Printer.hs
@@ -29,7 +29,7 @@
 import qualified Data.Text as T
 import Encoding
 import Lining
-import Margin (defaultMargin, withMargin)
+import Margin (defaultMargin, withLines)
 import Matcher
 import Render
 import Sugar
@@ -52,7 +52,7 @@
 
 printExpressionWith :: (SugarType -> EXPRESSION -> EXPRESSION) -> Expression -> PrintConfig -> String
 printExpressionWith hide ex (sugar, encoding, line, margin) =
-  T.unpack $ render (withLineFormat line $ withMargin margin $ withEncoding encoding $ hide sugar $ withSugarType sugar $ expressionToCST ex)
+  T.unpack $ render (withLines line margin $ withEncoding encoding $ hide sugar $ withSugarType sugar $ expressionToCST ex)
 
 printExpression :: Expression -> String
 printExpression ex = printExpression' ex defaultPrintConfig
diff --git a/src/Regexp.hs b/src/Regexp.hs
--- a/src/Regexp.hs
+++ b/src/Regexp.hs
@@ -11,27 +11,37 @@
 import Text.Read (readMaybe)
 import qualified Text.Regex.PCRE.ByteString as R
 
-compile :: B.ByteString -> IO R.Regex
+data Regex = Regex R.Regex R.Regex
+
+compile :: B.ByteString -> IO Regex
 compile pat = do
-  compiled <- R.compile R.compBlank R.execBlank pat
-  case compiled of
-    Left (_, err) -> throwIO (userError ("Regex compilation failed: " ++ err))
-    Right regex -> pure regex
+  first <- compileWith R.execBlank
+  continuation <- compileWith R.execNotBOL
+  pure (Regex first continuation)
+  where
+    compileWith option = do
+      compiled <- R.compile R.compUTF8 option pat
+      case compiled of
+        Left (_, err) -> throwIO (userError ("Regex compilation failed: " ++ err))
+        Right regex -> pure regex
 
 match :: B.ByteString -> B.ByteString -> IO Bool
 match pattern input = do
-  regex <- compile pattern
+  Regex regex _ <- compile pattern
   res <- R.execute regex input
   case res of
-    Left _ -> pure False
+    Left err -> regexExecutionFailed err
     Right Nothing -> pure False
     Right (Just _) -> pure True
 
-extractGroups :: R.Regex -> B.ByteString -> IO [B.ByteString]
-extractGroups regex input = do
+extractGroups :: Regex -> B.ByteString -> IO [B.ByteString]
+extractGroups (Regex regex _) input = extractGroupsWith regex input
+
+extractGroupsWith :: R.Regex -> B.ByteString -> IO [B.ByteString]
+extractGroupsWith regex input = do
   result <- R.execute regex input
   case result of
-    Left _ -> pure []
+    Left err -> regexExecutionFailed err
     Right Nothing -> pure []
     Right (Just arr) ->
       let (start, end) = bounds arr
@@ -62,42 +72,43 @@
       | i >= 0 && i < length xs = Just (xs !! i)
       | otherwise = Nothing
 
-replaceFirst :: R.Regex -> B.ByteString -> B.ByteString -> IO B.ByteString
-replaceFirst regex rep input = do
+replaceFirst :: Regex -> B.ByteString -> B.ByteString -> IO B.ByteString
+replaceFirst (Regex regex _) rep input = do
   result <- R.execute regex input
   case result of
-    Left _ -> return input
+    Left err -> regexExecutionFailed err
     Right Nothing -> return input
     Right (Just arr) -> do
-      groups <- extractGroups regex input
+      groups <- extractGroupsWith regex input
       let (off, len) = arr ! 0
           (before, rest) = B.splitAt off input
           (_, after) = B.splitAt len rest
           replacement = substituteGroups rep groups
       return $ B.concat [before, replacement, after]
 
-replaceAll :: R.Regex -> B.ByteString -> B.ByteString -> IO B.ByteString
-replaceAll regex rep input = go input B.empty
+replaceAll :: Regex -> B.ByteString -> B.ByteString -> IO B.ByteString
+replaceAll (Regex first continuation) rep input = go True input B.empty
   where
-    go :: B.ByteString -> B.ByteString -> IO B.ByteString
-    go bs acc = do
+    go :: Bool -> B.ByteString -> B.ByteString -> IO B.ByteString
+    go initial bs acc = do
+      let regex = if initial then first else continuation
       result <- R.execute regex bs
       case result of
-        Left _ -> return $ B.append acc bs
+        Left err -> regexExecutionFailed err
         Right Nothing -> return $ B.append acc bs
         Right (Just arr) -> do
           let (off, len) = arr ! 0
               (before, rest1) = B.splitAt off bs
               (_, rest2) = B.splitAt len rest1
-          groups <- extractGroups regex bs
+          groups <- extractGroupsWith regex bs
           let replacement = substituteGroups rep groups
           if len == 0
             then
               let next = B.take (utf8Width rest2) rest2
                in if B.null next
                     then return $ B.concat [acc, before, replacement]
-                    else go (B.drop (B.length next) rest2) (B.concat [acc, before, replacement, next])
-            else go rest2 (B.concat [acc, before, replacement])
+                    else go False (B.drop (B.length next) rest2) (B.concat [acc, before, replacement, next])
+            else go False rest2 (B.concat [acc, before, replacement])
     utf8Width :: B.ByteString -> Int
     utf8Width bytes =
       case B.uncons bytes of
@@ -115,3 +126,6 @@
         else 1
     isContinuation :: Char -> Bool
     isContinuation byte = ord byte >= 0x80 && ord byte < 0xC0
+
+regexExecutionFailed :: Show err => err -> IO value
+regexExecutionFailed err = throwIO (userError ("Regex execution failed: " ++ show err))
diff --git a/src/Render.hs b/src/Render.hs
--- a/src/Render.hs
+++ b/src/Render.hs
@@ -255,8 +255,8 @@
   render ST_ATTRIBUTES{..} = "[ " <> T.intercalate " \\char44{} " (map render attrs) <> " ]"
 
 instance Render LOGIC_OPERATOR where
-  render AND = "\\;\\text{and}\\;"
-  render OR = "\\;\\text{or}\\;"
+  render AND = "\\phiText{and}"
+  render OR = "\\phiText{or}"
 
 instance Render NUMBER where
   render IDX_META{..} = render meta
diff --git a/src/Rewriter.hs b/src/Rewriter.hs
--- a/src/Rewriter.hs
+++ b/src/Rewriter.hs
@@ -158,7 +158,7 @@
       R.matchExpressionWithRule expr rule ctx >>= \case
         [] -> pure Nothing
         matched
-          | isNothing rule.when -> Just <$> tryBuildAndReplaceFast (expr, rule.pattern, rule.result, matched)
+          | isNothing rule.when && isNothing rule.having -> Just <$> tryBuildAndReplaceFast (expr, rule.pattern, rule.result, matched)
           | otherwise -> Just <$> buildAndReplace' (expr, rule.pattern, rule.result, matched) replaceExpression
 
 direct :: String -> Bool -> (Maybe Expression -> Expression -> [Expression]) -> Step
diff --git a/src/Sugar.hs b/src/Sugar.hs
--- a/src/Sugar.hs
+++ b/src/Sugar.hs
@@ -7,7 +7,7 @@
 -- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 -- SPDX-License-Identifier: MIT
 
-module Sugar (toSalty, withSugarType, withoutRho, SugarType (..), ToSalty) where
+module Sugar (toSalty, withSugarType, withoutRho, revealXi, SugarType (..), ToSalty) where
 
 import AST
 import Bytes (nonFiniteBts, numToBts, strToBts, unescapeStr)
@@ -115,6 +115,39 @@
     isRho PA_TAU{attr = AT_RHO _} = True
     isRho PA_FORMATION{attr = AT_RHO _} = True
     isRho _ = False
+
+revealXi :: SugarType -> EXPRESSION -> EXPRESSION
+revealXi _ = goExpr
+  where
+    goExpr :: EXPRESSION -> EXPRESSION
+    goExpr EX_ATTR{..} = EX_DISPATCH (EX_XI XI) NO_SPACE attr
+    goExpr EX_DISPATCH{..} = EX_DISPATCH (goExpr expr) space attr
+    goExpr EX_APPLICATION{..} = EX_APPLICATION (goExpr expr) space eol tab (goArgument argument) eol' tab' indent
+    goExpr EX_FORMATION{..} = EX_FORMATION lsb eol tab (goBinding binding) eol' tab' rsb
+    goExpr EX_PHI_MEET{..} = EX_PHI_MEET prefix idx (goExpr expr)
+    goExpr EX_PHI_AGAIN{..} = EX_PHI_AGAIN prefix idx (goExpr expr)
+    goExpr EX_SINGLE{..} = EX_SINGLE (goPair pair) space (goExpr formation)
+    goExpr expr = expr
+    goArgument :: APP_ARGUMENT -> APP_ARGUMENT
+    goArgument (AA_TAU (APP_BINDING pair)) = AA_TAU (APP_BINDING (goPair pair))
+    goArgument (AA_TAUS binding) = AA_TAUS (goBinding binding)
+    goArgument (AA_EXPRS arg) = AA_EXPRS (goAppArg arg)
+    goAppArg :: APP_ARG -> APP_ARG
+    goAppArg APP_ARG{..} = APP_ARG (goExpr expr) (goAppArgs args)
+    goAppArgs :: APP_ARGS -> APP_ARGS
+    goAppArgs AAS_EMPTY = AAS_EMPTY
+    goAppArgs AAS_EXPR{..} = AAS_EXPR eol tab (goExpr expr) (goAppArgs args)
+    goBinding :: BINDING -> BINDING
+    goBinding BI_PAIR{..} = BI_PAIR (goPair pair) (goBindings bindings) tab
+    goBinding binding = binding
+    goBindings :: BINDINGS -> BINDINGS
+    goBindings BDS_PAIR{..} = BDS_PAIR eol tab (goPair pair) (goBindings bindings)
+    goBindings bindings = bindings
+    goPair :: PAIR -> PAIR
+    goPair PA_TAU{..} = PA_TAU attr arrow (goExpr expr)
+    goPair PA_ALPHA{..} = PA_ALPHA alpha arrow (goExpr expr)
+    goPair PA_FORMATION{..} = PA_FORMATION attr voids arrow (goExpr expr)
+    goPair pair = pair
 
 class ToSalty a where
   toSalty :: a -> a
diff --git a/src/Yaml.hs b/src/Yaml.hs
--- a/src/Yaml.hs
+++ b/src/Yaml.hs
@@ -13,9 +13,11 @@
 
 import AST
 import Control.Applicative (asum, (<|>))
+import Control.Exception (throwIO)
 import Data.Aeson
 import qualified Data.Aeson.Key as Key
 import qualified Data.Aeson.KeyMap as KeyMap
+import Data.Aeson.Types (formatRelativePath)
 import qualified Data.ByteString as BS
 import Data.FileEmbed (embedDir)
 import Data.Maybe (fromMaybe)
@@ -24,6 +26,7 @@
 import qualified Data.Text as T
 import Data.Yaml (Parser)
 import qualified Data.Yaml as Yaml
+import Data.Yaml.Internal (Warning (DuplicateKey))
 import GHC.Generics (Generic)
 import Metas
 import Parser
@@ -489,7 +492,15 @@
 normalizationRules = map decodeRule $(embedDir "resources/normalize")
 
 yamlRule :: FilePath -> IO Rule
-yamlRule = Yaml.decodeFileThrow
+yamlRule path = do
+  decoded <- Yaml.decodeFileWithWarnings path
+  case decoded of
+    Left err -> throwIO err
+    Right (warnings, rule) -> rule <$ mapM_ reportDuplicateKey warnings
+  where
+    reportDuplicateKey :: Warning -> IO ()
+    reportDuplicateKey (DuplicateKey jsonPath) =
+      fail (printf "The key '%s' is repeated in the rule file '%s', only the last value is kept" (formatRelativePath jsonPath) path)
 
 data Premise = Premise
   { result :: Text
diff --git a/test-resources/atoms.yaml b/test-resources/atoms.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/atoms.yaml
@@ -0,0 +1,53 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The λ functions phino's own specs fire. phino implements none of them, so a
+# spec that needs one to answer brings this file in with '--symbolic'. Every
+# entry answers symbolically: the operands come down through 𝔻 or reach a
+# normal form through 𝕄, and the term under '𝑛' carries a fresh symbol 𝜎
+# standing for the value nobody worked out. Nothing here computes, which is
+# what makes the morphing symbolic.
+#
+# 'L_number_nope' is missing on purpose: it is the λ function no entry answers,
+# the one '--partial' parks on.
+
+# Arithmetic over two numbers answers a number nobody has worked out.
+- λ: L_number_(plus|times|div)
+  dataize:
+    𝛿1: $.ρ
+    𝛿2: $.x
+  𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+
+# Comparing two numbers answers a bool nobody has decided, which is what the
+# fork below then branches on.
+- λ: L_number_gt
+  dataize:
+    𝛿1: $.ρ
+    𝛿2: $.x
+  𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+
+# Comparing two byte arrays answers a bool of the same kind.
+- λ: L_bytes_eq
+  dataize:
+    𝛿1: $.ρ
+    𝛿2: $.b
+  𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+
+# Inverting a byte array answers an array nobody has worked out.
+- λ: L_bytes_not
+  dataize:
+    𝛿1: $.ρ
+  𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+
+# A branch is where a 'morph' operand earns its place: the condition comes
+# down through 𝔻, both branches reach a normal form through 𝕄, and the answer
+# is one of them. This one answers the first, which is what an entry naming a
+# meta it bound does; an entry whose answer is to stand for either branch joins
+# the two of them in a 'join' block first and answers the meta that makes.
+- λ: L_fork
+  dataize:
+    𝛿1: $.ρ
+  morph:
+    𝑛1: $.then
+    𝑛2: $.else
+  𝑛: 𝑛1
diff --git a/test-resources/cli/expressions/bytes.phi b/test-resources/cli/expressions/bytes.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/bytes.phi
@@ -0,0 +1,11 @@
+[[
+  org -> [[
+    eolang -> [[
+      bytes -> [[
+        data -> ?
+      ]],
+      L> Package
+    ]],
+    L> Package
+  ]]
+]]
diff --git a/test-resources/cli/expressions/desugar.phi b/test-resources/cli/expressions/desugar.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/desugar.phi
@@ -0,0 +1,5 @@
+
+  [[
+    foo ↦ x
+  ]]
+
diff --git a/test-resources/cli/expressions/dispatch.phi b/test-resources/cli/expressions/dispatch.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/dispatch.phi
@@ -0,0 +1,1 @@
+Q.org.eolang
diff --git a/test-resources/cli/expressions/foo.phi b/test-resources/cli/expressions/foo.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/foo.phi
@@ -0,0 +1,5 @@
+
+  [[
+    foo ↦ Q.org.eolang.x
+  ]]
+
diff --git a/test-resources/cli/expressions/normalize.phi b/test-resources/cli/expressions/normalize.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/normalize.phi
@@ -0,0 +1,3 @@
+  [[
+    x -> [[ y -> [[ ^ -> ? ]] ]].y
+  ]]
diff --git a/test-resources/cli/expressions/number.phi b/test-resources/cli/expressions/number.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/number.phi
@@ -0,0 +1,11 @@
+[[
+  org -> [[
+    eolang -> [[
+      number -> [[
+        @ -> ?
+      ]],
+      L> Package
+    ]],
+    L> Package
+  ]]
+]]
diff --git a/test-resources/cli/expressions/string.phi b/test-resources/cli/expressions/string.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/string.phi
@@ -0,0 +1,11 @@
+[[
+  org -> [[
+    eolang -> [[
+      string -> [[
+        @ -> ?
+      ]],
+      L> Package
+    ]],
+    L> Package
+  ]]
+]]
diff --git a/test-resources/cli/expressions/with-$this-attribute.phi b/test-resources/cli/expressions/with-$this-attribute.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/expressions/with-$this-attribute.phi
@@ -0,0 +1,18 @@
+⟦
+  j$kafka ↦ ⟦
+    j$server ↦ ⟦
+      j$KafkaRequestHandler ↦ ⟦
+        φ ↦ Φ.jeo.class,
+        j$$anonfun$run$1 ↦ ⟦
+          φ ↦ Φ.jeo.method,
+          params ↦ ⟦
+            φ ↦ Φ.jeo.params,
+            $this ↦ ⟦⟧
+          ⟧
+        ⟧
+      ⟧,
+      λ ⤍ Package
+    ⟧,
+    λ ⤍ Package
+  ⟧
+⟧
diff --git a/test-resources/cli/rules/evaluate-in-rewrite.yaml b/test-resources/cli/rules/evaluate-in-rewrite.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/evaluate-in-rewrite.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# This rule references the 'evaluate' function, which needs the evaluation
+# context and is therefore only available to dataization/morphing rules.
+# It is used to test that 'rewrite' rejects such rules at load time.
+name: uses-evaluate
+pattern: '!e2'
+result: '!e1'
+where:
+  - meta: '!e1'
+    function: evaluate
+    args:
+      - '!e2'
diff --git a/test-resources/cli/rules/first.yaml b/test-resources/cli/rules/first.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/first.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# This test rule is used with second.yaml test rule for testing that rewriting
+# fails if rules lead to repeating expressions across several iterations.
+# Here on the first cycle we rewrite formation with "0" to application with "1"
+# In second.yaml this application is rewritten formation with "1"
+# Then on the second cycle the formation with "1" is rewritten to application
+# with "0", nhen in the second.yaml the application is rewritten to formation
+# with "0". Here we get back to expression we started from and rewriting should
+# fail on this step.
+name: first
+pattern: ⟦ x -> !e2 ⟧
+result: Q.x(y -> !e1)
+where:
+  - meta: '!e1'
+    function: sed
+    args:
+      - '!e2'
+      - '"s/0/2/g"'
+      - '"s/1/0/g"'
+      - '"s/2/1/g"'
diff --git a/test-resources/cli/rules/infinite.yaml b/test-resources/cli/rules/infinite.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/infinite.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: infinite
+pattern: ⟦ x -> !e2 ⟧
+result: ⟦ x -> !e1 ⟧
+where:
+  - meta: '!e1'
+    function: concat
+    args: ['!e2', '"_hi"']
diff --git a/test-resources/cli/rules/join-broken.yaml b/test-resources/cli/rules/join-broken.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/join-broken.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# This rule passes a non-binding argument to the 'join' where-function.
+# It is used to test that the error message names the 'join' function
+# correctly (it used to say "'go" instead of "join()").
+name: uses-join-on-non-binding
+pattern: '!e2'
+result: '!e1'
+where:
+  - meta: '!e1'
+    function: join
+    args:
+      - 'some-text'
diff --git a/test-resources/cli/rules/labeled.yaml b/test-resources/cli/rules/labeled.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/labeled.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: copy
+label: \lambda
+pattern: ⟦ 𝐵1, 𝜏1 ↦ ∅, 𝐵2 ⟧(𝜏1 ↦ 𝑘1)
+result: ⟦ 𝐵1, 𝜏1 ↦ 𝑘1, 𝐵2 ⟧
diff --git a/test-resources/cli/rules/malformed-pattern.yaml b/test-resources/cli/rules/malformed-pattern.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/malformed-pattern.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# This rule has a 'pattern' that is not a valid phi-expression. It is used
+# to test that a broken --rule file is reported in plain text, with real
+# characters and the path of the failing file, instead of an escaped
+# AesonException dump.
+name: malformed
+pattern: '⟦ x ↦ ⟧'
+result: 'ξ'
diff --git a/test-resources/cli/rules/marker.yaml b/test-resources/cli/rules/marker.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/marker.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# This rule turns a λ-marked formation into data. It is used to check that
+# --rule is honoured together with --normalize, since none of the built-in
+# normalization rules touches a λ binding.
+name: marker
+pattern: ⟦ λ ⤍ Marker ⟧
+result: ⟦ Δ ⤍ FF- ⟧
diff --git a/test-resources/cli/rules/raising-condition.yaml b/test-resources/cli/rules/raising-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/raising-condition.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 'matches' dataizes its argument, and dataization of a formation (what
+# '!e1' is bound to in the test input) raises inside the condition. The
+# substitution must still be dropped (the policy #1079 questions), but the
+# raise must be visible in the debug log (#1079).
+name: raising-condition
+pattern: '[[ x -> !e1 ]]'
+result: '[[ x -> "hi" ]]'
+when:
+  matches: ['hello', '!e1']
diff --git a/test-resources/cli/rules/second.yaml b/test-resources/cli/rules/second.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/second.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: second
+pattern: Q.x(y -> !e1)
+result: ⟦ x -> !e1 ⟧
diff --git a/test-resources/cli/rules/simple.yaml b/test-resources/cli/rules/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/simple.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: foo
+pattern: ⟦ x ↦ "foo", 𝐵1 ⟧
+result: ⟦ x ↦ "bar", 𝐵1 ⟧
diff --git a/test-resources/cli/rules/swap-a.yaml b/test-resources/cli/rules/swap-a.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/swap-a.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: swap-a
+pattern: ⟦ x -> !e1 ⟧
+result: Q.a(!e1)
diff --git a/test-resources/cli/rules/swap-b.yaml b/test-resources/cli/rules/swap-b.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cli/rules/swap-b.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: swap-b
+pattern: ⟦ x -> !e1 ⟧
+result: Q.b(!e1)
diff --git a/test-resources/condition-packs/absolute-application.yaml b/test-resources/condition-packs/absolute-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-application.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> ? ]](y -> Q.z) ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-dispatch.yaml b/test-resources/condition-packs/absolute-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-dispatch.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.foo.bar ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-formation.yaml b/test-resources/condition-packs/absolute-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-formation.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> Q ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-global.yaml b/test-resources/condition-packs/absolute-global.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-global.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-of-a-dispatch-builds-its-meta.yaml b/test-resources/condition-packs/absolute-of-a-dispatch-builds-its-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-of-a-dispatch-builds-its-meta.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1.q'
diff --git a/test-resources/condition-packs/absolute-termination.yaml b/test-resources/condition-packs/absolute-termination.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-termination.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> T ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-this.yaml b/test-resources/condition-packs/absolute-this.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-this.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> $ ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!e1'
diff --git a/test-resources/condition-packs/absolute-unbound-meta.yaml b/test-resources/condition-packs/absolute-unbound-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/absolute-unbound-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> [[ y -> Q ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  absolute: '!eX'
diff --git a/test-resources/condition-packs/alpha-simple.yaml b/test-resources/condition-packs/alpha-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/alpha-simple.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ xxxx -> ? ]](~0 -> Q.y)'
+pattern: '[[ !B1, !t1 -> ?, !B2 ]](α𝑖1 ↦ !e1)'
+condition:
+  eq:
+    - 𝑖1
+    - length: '!B1'
diff --git a/test-resources/condition-packs/and-fails.yaml b/test-resources/condition-packs/and-fails.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/and-fails.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q.y ]]'
+pattern: '[[ !t1 -> Q.!t2, !B1 ]]'
+condition:
+  and:
+    - eq: ['!t1', 'x']
+    - eq: ['!t2', 'z']
diff --git a/test-resources/condition-packs/and-multiple.yaml b/test-resources/condition-packs/and-multiple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/and-multiple.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, z -> Q.w ]]'
+pattern: '[[ !t1 -> Q.!t2, !B1 ]]'
+condition:
+  and:
+    - eq: ['!t1', 'x']
+    - eq: ['!t2', 'y']
+    - not:
+        eq: ['!t1', '!t2']
diff --git a/test-resources/condition-packs/disjoint-clean.yaml b/test-resources/condition-packs/disjoint-clean.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/disjoint-clean.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ a -> [[ ]], x -> [[ ]], b -> [[ ]] ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  disjoint:
+    - [Δ, λ]
+    - [𝐵1, 𝐵2]
diff --git a/test-resources/condition-packs/disjoint-unbound-meta.yaml b/test-resources/condition-packs/disjoint-unbound-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/disjoint-unbound-meta.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    disjoint:
+      - [Δ, λ]
+      - ['𝐵unbound']
diff --git a/test-resources/condition-packs/domain-meta-missing.yaml b/test-resources/condition-packs/domain-meta-missing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/domain-meta-missing.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> $, w -> $ ]](α0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  not:
+    gt:
+      - domain: '!Bx'
+      - '𝑖1'
diff --git a/test-resources/condition-packs/eq-attr-both-meta-one-unbound.yaml b/test-resources/condition-packs/eq-attr-both-meta-one-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-attr-both-meta-one-unbound.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ a -> "a" ]] ]]'
+pattern: '[[ x -> [[ !t1 -> !e1, !B1 ]] ]]'
+condition:
+  not:
+    eq: ['!t1', '!tX']
diff --git a/test-resources/condition-packs/eq-attr-concrete-then-meta-unbound.yaml b/test-resources/condition-packs/eq-attr-concrete-then-meta-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-attr-concrete-then-meta-unbound.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ ^ -> Q.x ]]'
+pattern: '[[ !t1 -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - '^'
+      - '!tX'
diff --git a/test-resources/condition-packs/eq-attr-concrete-then-meta.yaml b/test-resources/condition-packs/eq-attr-concrete-then-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-attr-concrete-then-meta.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ ^ -> Q.x ]]'
+pattern: '[[ !t1 -> !e1 ]]'
+condition:
+  eq:
+    - '^'
+    - '!t1'
diff --git a/test-resources/condition-packs/eq-attr-meta-then-concrete-unbound.yaml b/test-resources/condition-packs/eq-attr-meta-then-concrete-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-attr-meta-then-concrete-unbound.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ ^ -> Q.x ]]'
+pattern: '[[ !t1 -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - '!tX'
+      - '^'
diff --git a/test-resources/condition-packs/eq-expr-both-meta-one-unbound.yaml b/test-resources/condition-packs/eq-expr-both-meta-one-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-expr-both-meta-one-unbound.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello", y -> "Hello" ]]'
+pattern: '[[ x -> !e1, y -> !e2 ]]'
+condition:
+  not:
+    eq: ['!e1', '!eX']
diff --git a/test-resources/condition-packs/eq-expr-concrete-then-meta-unbound.yaml b/test-resources/condition-packs/eq-expr-concrete-then-meta-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-expr-concrete-then-meta-unbound.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello, world" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - '"Hello, world"'
+      - '!eX'
diff --git a/test-resources/condition-packs/eq-expr-concrete-then-meta.yaml b/test-resources/condition-packs/eq-expr-concrete-then-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-expr-concrete-then-meta.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello, world" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  eq:
+    - '"Hello, world"'
+    - '!e1'
diff --git a/test-resources/condition-packs/eq-expr-meta-then-concrete-unbound.yaml b/test-resources/condition-packs/eq-expr-meta-then-concrete-unbound.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-expr-meta-then-concrete-unbound.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello, world" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - '!eX'
+      - '"Hello, world"'
diff --git a/test-resources/condition-packs/eq-mismatched-comparable-kinds.yaml b/test-resources/condition-packs/eq-mismatched-comparable-kinds.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-mismatched-comparable-kinds.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - foo
+      - '"Hello"'
diff --git a/test-resources/condition-packs/eq-to-rho.yaml b/test-resources/condition-packs/eq-to-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-to-rho.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ ^ -> Q.x ]]'
+pattern: '[[ !t1 -> !e1 ]]'
+condition:
+  eq:
+    - '!t1'
+    - '^'
diff --git a/test-resources/condition-packs/eq-with-attributes.yaml b/test-resources/condition-packs/eq-with-attributes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-with-attributes.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Φ.hone.o-filter ]]'
+pattern: '[[ x -> Q.hone.𝜏-filter ]]'
+condition:
+  or:
+    - eq: [𝜏-filter, 'x-filter']
+    - eq: [𝜏-filter, 'y-filter']
+    - eq: [𝜏-filter, 'o-filter']
diff --git a/test-resources/condition-packs/eq-with-expressions.yaml b/test-resources/condition-packs/eq-with-expressions.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-with-expressions.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello, world" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  eq:
+    - '!e1'
+    - '"Hello, world"'
diff --git a/test-resources/condition-packs/eq-with-formations.yaml b/test-resources/condition-packs/eq-with-formations.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-with-formations.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ a -> "hey", b -> "jeff" ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  eq:
+    - '!e1'
+    - '[[ a -> "hey", b -> "jeff" ]]'
diff --git a/test-resources/condition-packs/eq-with-two-meta-attributes.yaml b/test-resources/condition-packs/eq-with-two-meta-attributes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-with-two-meta-attributes.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ a -> "a" ]], y -> [[ a -> "b" ]] ]]'
+pattern: '[[ x -> [[ !t1 -> !e1, !B1 ]], y -> [[ !t2 -> !e2, !B2 ]] ]]'
+condition:
+  eq: ['!t1', '!t2']
diff --git a/test-resources/condition-packs/eq-with-two-meta-expressions.yaml b/test-resources/condition-packs/eq-with-two-meta-expressions.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/eq-with-two-meta-expressions.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello", y -> "Hello" ]]'
+pattern: '[[ x -> !e1, y -> !e2 ]]'
+condition:
+  eq: ['!e1', '!e2']
diff --git a/test-resources/condition-packs/formation-simple.yaml b/test-resources/condition-packs/formation-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/formation-simple.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> Q ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  formation: '!e1'
diff --git a/test-resources/condition-packs/formation-unbound-meta.yaml b/test-resources/condition-packs/formation-unbound-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/formation-unbound-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> [[ y -> Q ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  formation: '!eX'
diff --git a/test-resources/condition-packs/gt-domain.yaml b/test-resources/condition-packs/gt-domain.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/gt-domain.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> $, w -> $ ]](α0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  gt:
+    - domain: '!B1'
+    - '𝑖1'
diff --git a/test-resources/condition-packs/gt-non-numeric.yaml b/test-resources/condition-packs/gt-non-numeric.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/gt-non-numeric.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, z -> Q.z ]]'
+pattern: '[[ x -> !e1, z -> !e2 ]]'
+condition:
+  not:
+    gt: ['!e1', '!e2']
diff --git a/test-resources/condition-packs/in-every-attribute.yaml b/test-resources/condition-packs/in-every-attribute.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-every-attribute.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ L> Foo, x -> [[ ]], D> 00- ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  in:
+    - [Δ, λ]
+    - [𝐵1, 𝐵2]
diff --git a/test-resources/condition-packs/in-missing.yaml b/test-resources/condition-packs/in-missing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-missing.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q.y, z -> Q.z ]].foo'
+pattern: '[[ !B1 ]].!t1'
+condition:
+  in:
+    - '!t1'
+    - '!B1'
diff --git a/test-resources/condition-packs/in-one-of-attributes.yaml b/test-resources/condition-packs/in-one-of-attributes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-one-of-attributes.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ L> Foo, x -> [[ ]] ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  in:
+    - [Δ, λ]
+    - [𝐵1, 𝐵2]
+failure: true
diff --git a/test-resources/condition-packs/in-simple.yaml b/test-resources/condition-packs/in-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-simple.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, z -> Q.z ]].x'
+pattern: '[[ !B1 ]].!t1'
+condition:
+  in:
+    - '!t1'
+    - '!B1'
diff --git a/test-resources/condition-packs/in-unbound-attribute.yaml b/test-resources/condition-packs/in-unbound-attribute.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-unbound-attribute.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, z -> Q.z ]].foo'
+pattern: '[[ !B1 ]].!t1'
+condition:
+  not:
+    in:
+      - '!tUnbound'
+      - '!B1'
diff --git a/test-resources/condition-packs/in-void.yaml b/test-resources/condition-packs/in-void.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/in-void.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> ?, y -> Q.z ]].x'
+pattern: '[[ !B1 ]].!t1'
+condition:
+  in:
+    - '!t1'
+    - '!B1'
diff --git a/test-resources/condition-packs/is-formation.yaml b/test-resources/condition-packs/is-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/is-formation.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> Q ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  formation: '!e1'
diff --git a/test-resources/condition-packs/length-meta-missing.yaml b/test-resources/condition-packs/length-meta-missing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/length-meta-missing.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, w -> $ ]](~0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  not:
+    eq:
+      - length: '!Bx'
+      - 3
diff --git a/test-resources/condition-packs/length-non-meta-binding.yaml b/test-resources/condition-packs/length-non-meta-binding.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/length-non-meta-binding.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, w -> $ ]](~0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  not:
+    eq:
+      - length: '^ -> ?'
+      - 3
diff --git a/test-resources/condition-packs/length.yaml b/test-resources/condition-packs/length.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/length.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, w -> $ ]](~0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  eq:
+    - length: '!B1'
+    - 2
diff --git a/test-resources/condition-packs/matches-cyrillic-skips-latin.yaml b/test-resources/condition-packs/matches-cyrillic-skips-latin.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/matches-cyrillic-skips-latin.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ k -> [[ x -> "D" ]] ]]'
+pattern: '[[ !t1 -> !e1, !B1 ]]'
+condition:
+  matches: ['^ф$', '!e1']
diff --git a/test-resources/condition-packs/matches-cyrillic.yaml b/test-resources/condition-packs/matches-cyrillic.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/matches-cyrillic.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ k -> [[ x -> "ф" ]] ]]'
+pattern: '[[ !t1 -> !e1, !B1 ]]'
+condition:
+  matches: ['^ф$', '!e1']
diff --git a/test-resources/condition-packs/matches-unbound-meta.yaml b/test-resources/condition-packs/matches-unbound-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/matches-unbound-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> "Hello" ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    matches: ['Hello', '!eX']
diff --git a/test-resources/condition-packs/matches-with-line-bounds.yaml b/test-resources/condition-packs/matches-with-line-bounds.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/matches-with-line-bounds.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ k -> [[ x -> "()I" ]] ]]'
+pattern: '[[ !t1 -> !e1, !B1 ]]'
+condition:
+  matches: ['\(\)[IBZF]$', '!e1']
diff --git a/test-resources/condition-packs/matches.yaml b/test-resources/condition-packs/matches.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/matches.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ k -> [[ x -> "Hello world ()" ]] ]]'
+pattern: '[[ !t1 -> !e1, !B1 ]]'
+condition:
+  matches: ['Hello world \(\)', '!e1']
diff --git a/test-resources/condition-packs/metaindex-missing.yaml b/test-resources/condition-packs/metaindex-missing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/metaindex-missing.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ ^ -> Q.x ]]'
+pattern: '[[ !t1 -> !e1 ]]'
+condition:
+  not:
+    eq:
+      - 𝑖1
+      - 0
diff --git a/test-resources/condition-packs/nf-builds-the-term-from-its-meta.yaml b/test-resources/condition-packs/nf-builds-the-term-from-its-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-builds-the-term-from-its-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> [[ ]].b ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '[[ a -> !e1 ]]'
diff --git a/test-resources/condition-packs/nf-dispatch-global.yaml b/test-resources/condition-packs/nf-dispatch-global.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-dispatch-global.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.foo ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-dispatch-this.yaml b/test-resources/condition-packs/nf-dispatch-this.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-dispatch-this.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> $.foo ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-dot-recursive.yaml b/test-resources/condition-packs/nf-dot-recursive.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-dot-recursive.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '⟦ x ↦ [[ t -> ⊥.z ]].t ⟧.x'
+pattern: '[[ x -> !e1 ]].x'
+condition:
+  not:
+    nf: '!e1'
diff --git a/test-resources/condition-packs/nf-empty-formation.yaml b/test-resources/condition-packs/nf-empty-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-empty-formation.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-formation-with-delta.yaml b/test-resources/condition-packs/nf-formation-with-delta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-formation-with-delta.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ D> 00-01-02 ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-formation-with-void.yaml b/test-resources/condition-packs/nf-formation-with-void.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-formation-with-void.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> ? ]] ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-global.yaml b/test-resources/condition-packs/nf-global.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-global.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-of-a-dispatch-builds-its-meta.yaml b/test-resources/condition-packs/nf-of-a-dispatch-builds-its-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-of-a-dispatch-builds-its-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> [[ ]].b ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1.q'
diff --git a/test-resources/condition-packs/nf-on-dot.yaml b/test-resources/condition-packs/nf-on-dot.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-on-dot.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '⟦ x ↦ ξ.t, t ↦ ∅ ⟧.x'
+pattern: '[[ x -> !e1, !B1 ]].x'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-on-phi.yaml b/test-resources/condition-packs/nf-on-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-on-phi.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# PHI left normalization for morphing (#973), so dispatching an absent
+# attribute on a φ-decorated formation no longer reduces under 𝒩 — it is now a
+# stuck normal form. (Before #973 the PHI rule could fire on '!e1', so it was
+# not a normal form; this pack was 'not-nf-on-phi'.)
+expression: '[[ x -> [[ @ -> [[ ]] ]].y  ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-simple.yaml b/test-resources/condition-packs/nf-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-simple.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: 'Q.x'
+pattern: '!e1.!t1'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-termination.yaml b/test-resources/condition-packs/nf-termination.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-termination.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[x -> T]]'
+pattern: '[[x -> !e1]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-this.yaml b/test-resources/condition-packs/nf-this.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-this.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> $ ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!e1'
diff --git a/test-resources/condition-packs/nf-unbound-meta.yaml b/test-resources/condition-packs/nf-unbound-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/nf-unbound-meta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  nf: '!eX'
diff --git a/test-resources/condition-packs/not-disjoint-delta.yaml b/test-resources/condition-packs/not-disjoint-delta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-disjoint-delta.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ D> 00-01, x -> [[ ]], b -> [[ ]] ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  disjoint:
+    - [Δ, λ]
+    - [𝐵1, 𝐵2]
diff --git a/test-resources/condition-packs/not-disjoint-lambda.yaml b/test-resources/condition-packs/not-disjoint-lambda.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-disjoint-lambda.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ a -> [[ ]], x -> [[ ]], L> Foo ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  disjoint:
+    - [Δ, λ]
+    - [𝐵1, 𝐵2]
diff --git a/test-resources/condition-packs/not-eq.yaml b/test-resources/condition-packs/not-eq.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-eq.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: 'Q.bar'
+pattern: 'Q.!t1'
+condition:
+  not:
+    eq: ['!t1', 'foo']
diff --git a/test-resources/condition-packs/not-formation.yaml b/test-resources/condition-packs/not-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-formation.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  formation: '!e1'
diff --git a/test-resources/condition-packs/not-gt.yaml b/test-resources/condition-packs/not-gt.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-gt.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> $, w -> $ ]](α3 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ Q)'
+condition:
+  gt:
+    - domain: '!B1'
+    - '𝑖1'
diff --git a/test-resources/condition-packs/not-in-one-of-attributes.yaml b/test-resources/condition-packs/not-in-one-of-attributes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-in-one-of-attributes.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ ]], D> 00- ]]'
+pattern: '[[ !B1, x -> !e1, !B2 ]]'
+condition:
+  not:
+    in:
+      - [Δ, λ]
+      - [𝐵1, 𝐵2]
diff --git a/test-resources/condition-packs/not-nf-on-dc.yaml b/test-resources/condition-packs/not-nf-on-dc.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-nf-on-dc.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> T(y -> Q.z) ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    nf: '!e1'
diff --git a/test-resources/condition-packs/not-nf-on-dd.yaml b/test-resources/condition-packs/not-nf-on-dd.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/not-nf-on-dd.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> ⊥.𝜏1 ]]'
+pattern: '[[ x -> !e1 ]]'
+condition:
+  not:
+    nf: '!e1'
diff --git a/test-resources/condition-packs/or-first-match.yaml b/test-resources/condition-packs/or-first-match.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/or-first-match.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.x ]]'
+pattern: '[[ x -> Q.!t1 ]]'
+condition:
+  or:
+    - eq: ['!t1', 'x']
+    - eq: ['!t1', 'y']
diff --git a/test-resources/condition-packs/or.yaml b/test-resources/condition-packs/or.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/or.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q.x ]]'
+pattern: '[[ x -> Q.!t1 ]]'
+condition:
+  or:
+    - eq: ['!t1', 'y']
+    - eq: ['!t1', 'z']
+    - eq: ['!t1', 'o']
diff --git a/test-resources/condition-packs/ordinal.yaml b/test-resources/condition-packs/ordinal.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/ordinal.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y ]](~0 -> Q)'
+pattern: '[[ !B1 ]](α𝑖1 ↦ !e1)'
+condition:
+  eq:
+    - 𝑖1
+    - 0
diff --git a/test-resources/condition-packs/part-of-missing.yaml b/test-resources/condition-packs/part-of-missing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/part-of-missing.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+expression: '[[ x -> Q.y, z -> Q.z ]]'
+pattern: '[[ x -> !e1, !B1 ]]'
+condition:
+  part-of: ['!e1', '!B1']
diff --git a/test-resources/condition-packs/part-of-nested.yaml b/test-resources/condition-packs/part-of-nested.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/part-of-nested.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> [[ y -> Q.z ]], w -> Q.z ]]'
+pattern: '[[ x -> [[ !B1 ]], !B2 ]]'
+condition:
+  part-of: ['Q.z', '!B1']
diff --git a/test-resources/condition-packs/part-of-simple.yaml b/test-resources/condition-packs/part-of-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/part-of-simple.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y, z -> Q.y ]]'
+pattern: '[[ x -> !e1, !B1 ]]'
+condition:
+  part-of: ['!e1', '!B1']
diff --git a/test-resources/condition-packs/stop.yaml b/test-resources/condition-packs/stop.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/condition-packs/stop.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ x -> Q.y ]].y'
+pattern: '[[ !B1 ]].!t1'
+condition:
+  and:
+    - not:
+        in:
+          - '!t1'
+          - '𝐵1'
+    - not:
+        in:
+          - 'φ'
+          - '𝐵1'
+    - not:
+        in:
+          - 'λ'
+          - '𝐵1'
diff --git a/test-resources/cst/printing-packs/basic.yaml b/test-resources/cst/printing-packs/basic.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/basic.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  Q
+result: |-
+  Φ
diff --git a/test-resources/cst/printing-packs/dispatch-and-application-sugar-after-inline-voids.yaml b/test-resources/cst/printing-packs/dispatch-and-application-sugar-after-inline-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/dispatch-and-application-sugar-after-inline-voids.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  ⟦ x(y) ↦ ⟦ z ↦ ξ, w ↦ Φ ⟧.z:a, v(y) ↦ ⟦ z ↦ ∅, w ↦ Φ ⟧( z ↦ Φ ):b ⟧
+result: |-
+  ⟦ x(y) ↦ ⟦ z ↦ ξ, w ↦ Φ ⟧.z:a, v(y) ↦ ⟦ z ↦ ∅, w ↦ Φ ⟧( z ↦ Φ ):b ⟧
diff --git a/test-resources/cst/printing-packs/dispatch.yaml b/test-resources/cst/printing-packs/dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/dispatch.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  Q.org.bar.baz
+result: |-
+  Φ.org.bar.baz
diff --git a/test-resources/cst/printing-packs/formation-sugar-after-inline-voids.yaml b/test-resources/cst/printing-packs/formation-sugar-after-inline-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/formation-sugar-after-inline-voids.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  ⟦
+    output(φ) ↦ ⟦
+      write(buffer) ↦ ⟦ φ ↦ Φ.true ⟧
+    ⟧:dead
+  ⟧
+result: |-
+  ⟦ output(φ) ↦ ⟦ write(buffer) ↦ Φ.true:φ ⟧:dead ⟧
diff --git a/test-resources/cst/printing-packs/jeo.yaml b/test-resources/cst/printing-packs/jeo.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/jeo.yaml
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> Q.x(Φ.jeo.opcode.lconst_0(
+      9
+    ),
+    Φ.jeo.opcode.lstore(
+      55,
+      1
+    ),
+    Φ.jeo.opcode.iconst_0(
+      3
+    ),
+    Φ.jeo.opcode.istore(
+      54,
+      3
+    ),
+    Φ.jeo.label(
+      Φ.bytes(
+        ⟦
+          Δ ⤍ 4C-31-32-38-34-34-34-35-32-36-37
+        ⟧
+      )
+    ))
+  ]]
+result: |-
+  Φ.x(
+    Φ.jeo.opcode.lconst_0( 9 ),
+    Φ.jeo.opcode.lstore( 55, 1 ),
+    Φ.jeo.opcode.iconst_0( 3 ),
+    Φ.jeo.opcode.istore( 54, 3 ),
+    Φ.jeo.label( Φ.bytes( 4C-31-32-38-34-34-34-35-32-36-37:Δ ) )
+  ):x
diff --git a/test-resources/cst/printing-packs/lambda-sugar-after-inline-voids.yaml b/test-resources/cst/printing-packs/lambda-sugar-after-inline-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/lambda-sugar-after-inline-voids.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x(y) -> [[ L> Plus ]] ]]
+result: |-
+  ⟦ x(y) ↦ Plus:λ ⟧
diff --git a/test-resources/cst/printing-packs/non-finite-doubles.yaml b/test-resources/cst/printing-packs/non-finite-doubles.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/non-finite-doubles.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    nan -> Q.number(Q.bytes([[ D> 7F-F8-00-00-00-00-00-00 ]])),
+    pinf -> Q.number(Q.bytes([[ D> 7F-F0-00-00-00-00-00-00 ]])),
+    ninf -> Q.number(Q.bytes([[ D> FF-F0-00-00-00-00-00-00 ]])),
+    with-rho -> Q.number(~0 -> Q.bytes([[ D> 7F-F8-00-00-00-00-00-00 ]]), ^ -> Q.x.y.z),
+    payload -> Q.number(Q.bytes([[ D> 7F-F8-00-00-00-00-00-01 ]]))
+  ]]
+result: |-
+  ⟦
+    nan ↦ Φ.nan,
+    pinf ↦ Φ.pinf,
+    ninf ↦ Φ.ninf,
+    with-rho ↦ Φ.nan,
+    payload ↦ Φ.number( Φ.bytes( 7F-F8-00-00-00-00-00-01:Δ ) )
+  ⟧
diff --git a/test-resources/cst/printing-packs/non-primitive-data-object.yaml b/test-resources/cst/printing-packs/non-primitive-data-object.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/non-primitive-data-object.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> Q.foo(~0 -> Q.bytes([[ D> 3F-F0-00-00-00-00-00-00 ]])) ]]
+result: |-
+  Φ.foo( Φ.bytes( 3F-F0-00-00-00-00-00-00:Δ ) ):x
diff --git a/test-resources/cst/printing-packs/primitives-without-rhos.yaml b/test-resources/cst/printing-packs/primitives-without-rhos.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/primitives-without-rhos.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> Q.number(~0 -> Q.bytes([[ D> 3F-F0-00-00-00-00-00-00 ]]), ^ -> [[ t -> 42 ]], ^ -> Q.x.y.z),
+    y -> Q.string(~0 -> Q.bytes([[ D> 68-65-79 ]]), ^ -> Q.x.y.z)
+  ]]
+result: |-
+  ⟦ x ↦ 1, y ↦ "hey" ⟧
diff --git a/test-resources/cst/printing-packs/simple.yaml b/test-resources/cst/printing-packs/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/simple.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    foo -> [[
+      @ -> $.^.z,
+      bar -> Q.y(5, "foo")
+    ]]
+  ]]
+result: |-
+  ⟦ φ ↦ ρ.z, bar ↦ Φ.y( 5, "foo" ) ⟧:foo
diff --git a/test-resources/cst/printing-packs/sugar-after-inline-voids.yaml b/test-resources/cst/printing-packs/sugar-after-inline-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/sugar-after-inline-voids.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x(y) -> [[ a -> 42 ]] ]]
+result: |-
+  ⟦ x(y) ↦ 42:a ⟧
diff --git a/test-resources/cst/printing-packs/sweet-inlined.yaml b/test-resources/cst/printing-packs/sweet-inlined.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/sweet-inlined.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  ⟦
+    x ↦ Φ.foo(
+      x ↦ Φ.k
+    ).bar(
+      x ↦ Φ.k
+    )
+  ⟧
+result: |-
+  Φ.foo( x ↦ Φ.k ).bar( x ↦ Φ.k ):x
diff --git a/test-resources/cst/printing-packs/voids-after-delta.yaml b/test-resources/cst/printing-packs/voids-after-delta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/voids-after-delta.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> [[ D> 01-, a -> ?, ^ -> ?, b -> 2 ]] ]]
+result: |-
+  ⟦ x(a, ρ) ↦ ⟦ Δ ⤍ 01-, b ↦ 2 ⟧ ⟧
diff --git a/test-resources/cst/printing-packs/voids-after-lambda.yaml b/test-resources/cst/printing-packs/voids-after-lambda.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/voids-after-lambda.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ if -> [[ L> L_fork, left -> ?, right -> ? ]] ]]
+result: |-
+  ⟦ if(left, right) ↦ L_fork:λ ⟧
diff --git a/test-resources/cst/printing-packs/with-void-rho-between.yaml b/test-resources/cst/printing-packs/with-void-rho-between.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/with-void-rho-between.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> [[ ^ -> ?, x -> ?, y -> 5 ]], y -> [[ a -> ?, b -> 3, m -> ?, ^ -> ? ]] ]]
+result: |-
+  ⟦ x(ρ, x) ↦ 5:y, y(a) ↦ ⟦ b ↦ 3, m ↦ ∅, ρ ↦ ∅ ⟧ ⟧
diff --git a/test-resources/cst/printing-packs/xi-attribute.yaml b/test-resources/cst/printing-packs/xi-attribute.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/printing-packs/xi-attribute.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> y ]]
+result: |-
+  y:x
diff --git a/test-resources/cst/to-ascii-packs/bytes-meta.yaml b/test-resources/cst/to-ascii-packs/bytes-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-ascii-packs/bytes-meta.yaml
@@ -0,0 +1,5 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '⟦ Δ ⤍ 𝛿1 ⟧'
+result: '!d1:D'
diff --git a/test-resources/cst/to-ascii-packs/complex.yaml b/test-resources/cst/to-ascii-packs/complex.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-ascii-packs/complex.yaml
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  ⟦
+    number ↦ ⟦
+      a ↦ ∅,
+      b ↦ Φ.org.ρ.φ,
+      c ↦ ξ.xyz(
+        q ↦ Φ.y,
+        α0 ↦ ξ.a.b
+      ),
+      z ↦ ⟦
+        λ ⤍ Func,
+        Δ ⤍ 00-,
+        def ↦ Φ.txt.sprintf
+      ⟧,
+      error ↦ ⊥
+    ⟧
+  ⟧
+result: |-
+  [[
+    number(a) -> [[
+      b -> Q.org.^.@,
+      c -> xyz( q -> Q.y, ~0 -> a.b ),
+      z -> [[ L> Func, D> 00-, def -> Q.txt.sprintf ]],
+      error -> T
+    ]]
+  ]]
diff --git a/test-resources/cst/to-salty-packs/application-with-alphas.yaml b/test-resources/cst/to-salty-packs/application-with-alphas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/application-with-alphas.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> Q.y(1, 2) ]]
+result: |-
+  ⟦
+    x ↦ Φ.y(
+      α0 ↦ Φ.number(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦
+            Δ ⤍ 3F-F0-00-00-00-00-00-00
+          ⟧
+        )
+      )
+    )(
+      α1 ↦ Φ.number(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦
+            Δ ⤍ 40-00-00-00-00-00-00-00
+          ⟧
+        )
+      )
+    )
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/application.yaml b/test-resources/cst/to-salty-packs/application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/application.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> Q.y(z -> "hey", b -> 1) ]]
+result: |-
+  ⟦
+    x ↦ Φ.y(
+      z ↦ Φ.string(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦
+            Δ ⤍ 68-65-79
+          ⟧
+        )
+      )
+    )(
+      b ↦ Φ.number(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦
+            Δ ⤍ 3F-F0-00-00-00-00-00-00
+          ⟧
+        )
+      )
+    )
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/formation-sugar-after-inline-voids.yaml b/test-resources/cst/to-salty-packs/formation-sugar-after-inline-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/formation-sugar-after-inline-voids.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  ⟦
+    output(φ) ↦ ⟦
+      write(buffer) ↦ ⟦ φ ↦ Φ.true ⟧
+    ⟧:dead
+  ⟧
+result: |-
+  ⟦
+    output ↦ ⟦
+      φ ↦ ∅,
+      dead ↦ ⟦
+        write ↦ ⟦
+          buffer ↦ ∅,
+          φ ↦ Φ.true
+        ⟧
+      ⟧
+    ⟧
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/non-finite-doubles.yaml b/test-resources/cst/to-salty-packs/non-finite-doubles.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/non-finite-doubles.yaml
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    nan -> Q.nan,
+    pinf -> Q.pinf,
+    ninf -> Q.number(~0 -> Q.bytes([[D> FF-F0-00-00-00-00-00-00]]), ^ -> Q.x)
+  ]]
+result: |-
+  ⟦
+    nan ↦ Φ.number(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ 7F-F8-00-00-00-00-00-00
+        ⟧
+      )
+    ),
+    pinf ↦ Φ.number(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ 7F-F0-00-00-00-00-00-00
+        ⟧
+      )
+    ),
+    ninf ↦ Φ.number(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ FF-F0-00-00-00-00-00-00
+        ⟧
+      )
+    )(
+      ρ ↦ Φ.x
+    )
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/primitives-with-rhos.yaml b/test-resources/cst/to-salty-packs/primitives-with-rhos.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/primitives-with-rhos.yaml
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> Q.number(
+      ~0 -> Q.bytes([[D> 3F-F0-00-00-00-00-00-00]]), ^ -> Q.x, ^ -> [[ ]]
+    ),
+    y -> Q.string(
+      ~0 -> Q.bytes([[D> 48-65-79]]), ^ -> $.x
+    )
+  ]]
+result: |-
+  ⟦
+    x ↦ Φ.number(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ 3F-F0-00-00-00-00-00-00
+        ⟧
+      )
+    )(
+      ρ ↦ Φ.x
+    )(
+      ρ ↦ ⟦⟧
+    ),
+    y ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ 48-65-79
+        ⟧
+      )
+    )(
+      ρ ↦ ξ.x
+    )
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/with-inlined-voids.yaml b/test-resources/cst/to-salty-packs/with-inlined-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/with-inlined-voids.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x(x, y) -> [[ z -> Q.x ]] ]]
+result: |-
+  ⟦
+    x ↦ ⟦
+      x ↦ ∅,
+      y ↦ ∅,
+      z ↦ Φ.x
+    ⟧
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/with-num-and-void-rho.yaml b/test-resources/cst/to-salty-packs/with-num-and-void-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/with-num-and-void-rho.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> 1, z -> [[ ]] ]]
+result: |-
+  ⟦
+    x ↦ Φ.number(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦
+          Δ ⤍ 3F-F0-00-00-00-00-00-00
+        ⟧
+      )
+    ),
+    z ↦ ⟦⟧
+  ⟧
diff --git a/test-resources/cst/to-salty-packs/xi-attr.yaml b/test-resources/cst/to-salty-packs/xi-attr.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-salty-packs/xi-attr.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> y ]]
+result: |-
+  ⟦
+    x ↦ ξ.y
+  ⟧
diff --git a/test-resources/cst/to-singleline-packs/application.yaml b/test-resources/cst/to-singleline-packs/application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-singleline-packs/application.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> Q.y(
+      z -> "hey",
+      b -> 1
+    ).z(
+      a -> [[
+        q -> [[ ^ -> Q.a ]]
+      ]]
+    )
+  ]]
+result: |-
+  Φ.y( z ↦ "hey", b ↦ 1 ).z( a ↦ Φ.a:ρ:q ):x
diff --git a/test-resources/cst/to-singleline-packs/empty-formation.yaml b/test-resources/cst/to-singleline-packs/empty-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-singleline-packs/empty-formation.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> [[]]
+  ]]
+result: |-
+  ⟦⟧:x
diff --git a/test-resources/cst/to-singleline-packs/with-inlined-voids.yaml b/test-resources/cst/to-singleline-packs/with-inlined-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/cst/to-singleline-packs/with-inlined-voids.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[
+    x -> [[ y -> ?, a -> 1, ^ -> ?, q -> ? ]]
+  ]]
+result: |-
+  ⟦ x(y) ↦ ⟦ a ↦ 1, ρ ↦ ∅, q ↦ ∅ ⟧ ⟧
diff --git a/test-resources/dataization-packs/dataizes-a-chain-of-firings-to-the-manufactured-datum.yaml b/test-resources/dataization-packs/dataizes-a-chain-of-firings-to-the-manufactured-datum.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-a-chain-of-firings-to-the-manufactured-datum.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The second addition is handed the symbol the first one answered with, brings
+# it down to the manufactured datum and answers a symbol of its own, so a chain
+# of firings ends where a single one does.
+model: true
+symbolic: true
+input: '5.plus( 6 ).plus( 7 )'
+result: '40-45-00-00-00-00-00-00'
diff --git a/test-resources/dataization-packs/dataizes-a-located-reference.yaml b/test-resources/dataization-packs/dataizes-a-located-reference.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-a-located-reference.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The run enters the program where the locator points, follows the reference
+# it finds there and dataizes what it lands on.
+location: 'Q.foo.bar'
+input: |
+  [[
+    foo -> [[
+      bar -> [[
+        @ -> Q.x
+      ]]
+    ]],
+    x -> [[ D> 42- ]]
+  ]]
+result: '42-'
diff --git a/test-resources/dataization-packs/dataizes-a-symbolic-sum-to-the-manufactured-datum.yaml b/test-resources/dataization-packs/dataizes-a-symbolic-sum-to-the-manufactured-datum.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-a-symbolic-sum-to-the-manufactured-datum.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The addition answers a number nobody worked out, so what 𝔻 reaches is the
+# datum every symbol is manufactured the same one of, and not the sum at all.
+model: true
+symbolic: true
+input: '5.plus( 6 )'
+result: '40-45-00-00-00-00-00-00'
diff --git a/test-resources/dataization-packs/dataizes-an-attribute-inherited-through-phi.yaml b/test-resources/dataization-packs/dataizes-an-attribute-inherited-through-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-an-attribute-inherited-through-phi.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Dispatching an absent attribute on a φ-decorated formation resolves the
+# inherited attribute through morphing 'mphi' (#973): PHI used to be a
+# normalization rule, but following the decoration is a semantic 𝕄 step, so it
+# moved into 'resources/morphing'. Here '.t' is missing from the outer
+# formation, so 𝕄 walks the '@' decoration to the parent that defines 't' and
+# dataizes its datum.
+input: '[[ @ -> [[ t -> [[ D> 2A- ]] ]] ]].t'
+result: '2A-'
diff --git a/test-resources/dataization-packs/dataizes-the-branch-an-undecided-condition-picked.yaml b/test-resources/dataization-packs/dataizes-the-branch-an-undecided-condition-picked.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-the-branch-an-undecided-condition-picked.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Nobody decided the comparison, so the fork cannot know which branch the
+# program takes; it answers the first one, whose value is a number the program
+# wrote and not a symbol at all.
+model: true
+symbolic: true
+input: '5.gt( 6 ).if( 1, 2 )'
+result: '3F-F0-00-00-00-00-00-00'
diff --git a/test-resources/dataization-packs/dataizes-the-number-five.yaml b/test-resources/dataization-packs/dataizes-the-number-five.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/dataizes-the-number-five.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A number is sugar for an application of 'Q.number', so the object model has
+# to declare 'number' and 'bytes' before 5 means anything.
+location: 'Q.x'
+input: |
+  [[
+    number ↦ ⟦ φ ↦ ∅ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅ ⟧,
+    x -> 5
+  ]]
+result: '40-14-00-00-00-00-00-00'
diff --git a/test-resources/dataization-packs/fails-to-dataize-an-empty-formation.yaml b/test-resources/dataization-packs/fails-to-dataize-an-empty-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/fails-to-dataize-an-empty-formation.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The 'none' rule dataizes ⊥ (𝔻(⟦⟧) → 𝔻(⊥)), which matches no clause now that
+# there is no 'end' rule, so an empty formation reduces through one labelled
+# 'dataize' step and then fails: it has nothing to dataize (#955).
+input: '[[ ]]'
+fails: 'terminator'
diff --git a/test-resources/dataization-packs/gets-stuck-on-a-lambda-no-entry-answers.yaml b/test-resources/dataization-packs/gets-stuck-on-a-lambda-no-entry-answers.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/dataization-packs/gets-stuck-on-a-lambda-no-entry-answers.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+model: true
+symbolic: true
+input: '5.nope'
+fails: "No entry of --symbolic answers the λ function 'L_number_nope'"
diff --git a/test-resources/evaluate-packs/answers-a-symbol-nobody-has-worked-out.yaml b/test-resources/evaluate-packs/answers-a-symbol-nobody-has-worked-out.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/answers-a-symbol-nobody-has-worked-out.yaml
@@ -0,0 +1,44 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# One firing, and nothing is computed: the operands come down to the numbers
+# they are, and what the entry answers with is a number standing for a sum
+# nobody has worked out.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.plus( 6 ) ⟧
+result: '⟦ φ ↦ 𝜎1:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )
+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
diff --git a/test-resources/evaluate-packs/answers-a-whole-family-of-names-with-one-entry.yaml b/test-resources/evaluate-packs/answers-a-whole-family-of-names-with-one-entry.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/answers-a-whole-family-of-names-with-one-entry.yaml
@@ -0,0 +1,62 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The key of an entry is a regular expression over λ names, so one entry
+# stands for the whole family of them a box numbers its functions with.
+symbolic: |
+  - λ: L_number_(plus|times)
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
+      times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧ ⟧,
+    @ ↦ 5.plus( 6 ).times( 7 ) ⟧
+result: |-
+  ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ, times(x) ↦ L_number_times:λ ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 6
+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.times( x ↦ 7 )
+    𝕄(Φ.φ.x):
+      𝑛·0·4 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )
+    𝔼(L_number_times):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵3):
+        𝕄(Φ.a🌵3):
+          𝑛·2·2 := Φ.number( φ ↦ 𝑛·0·4 )
+          𝑛·2·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+        𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0
+      𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)
+      𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·2·4):
+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )
+      𝑛·2·6 := 𝑛·2·5  # 𝕄(𝑛·2·4)
diff --git a/test-resources/evaluate-packs/answers-the-same-under-acyclic.yaml b/test-resources/evaluate-packs/answers-the-same-under-acyclic.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/answers-the-same-under-acyclic.yaml
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A program that does not loop answers the same with '_acyclic' as without it.
+# The guard remembers the terms the frames above have open, so only a genuine
+# return to one of them trips it; a term merely reduced twice in two places
+# does not.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+acyclic: proven
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.plus( 6 ) ⟧
+result: '⟦ φ ↦ 𝜎1:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )
+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
diff --git a/test-resources/evaluate-packs/blames-the-lambda-the-operand-was-parked-on.yaml b/test-resources/evaluate-packs/blames-the-lambda-the-operand-was-parked-on.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/blames-the-lambda-the-operand-was-parked-on.yaml
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A firing whose 'dataize' operand was parked gets stuck on the λ function
+# that parked it and never on its own: an entry answers 'L_entry', so blaming
+# it would name a λ function the file carries where the one nothing answers,
+# 'L_none', stands one reduction deeper. The spine parks that name, so the
+# deep walk over the residue writes no second 'unanswered(L_none)' for the one
+# site (#1288, #1300).
+symbolic: |
+  - λ: L_entry
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.x'
+deep: true
+partial: true
+input: '⟦ x ↦ ⟦ v ↦ ⟦ λ ⤍ L_none ⟧.foo, λ ⤍ L_entry, ρ ↦ ∅ ⟧.z ⟧'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_entry):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          unanswered(L_none)  # 𝕄(L_none:λ)
+      stuck(L_none)
+    𝔼(L_entry):
+      stuck(L_none)
diff --git a/test-resources/evaluate-packs/cuts-a-loop-over-unknown-data-on-its-second-round.yaml b/test-resources/evaluate-packs/cuts-a-loop-over-unknown-data-on-its-second-round.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/cuts-a-loop-over-unknown-data-on-its-second-round.yaml
@@ -0,0 +1,184 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A loop walking an index from zero up to a size that is a symbol, under
+# '--acyclic=plausible'. The first round holds the number '0', the second holds
+# the symbol 'plus' answered in its place, and a symbol stands for a term that
+# holds no symbol of its own, so the fork of the second round is cut against
+# the fork of the first one rather than a round later (#1491). The cut fork is
+# the φ of a copy of 'rec', so the cut answers that copy with a fresh symbol,
+# which the 'join' of the first fork pairs with the other branch (#1735).
+symbolic: |
+  - λ: L_id
+    morph:
+      𝑛1: $.x.φ
+    𝑛: 𝑛1
+  - λ: L_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_eq
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ) ⟧
+  - λ: L_fork
+    dataize:
+      𝛿1: $.φ
+    morph:
+      𝑛1: $.left
+      𝑛2: $.right
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.a'
+deep: true
+acyclic: plausible
+partial: true
+steps: 32
+input: >
+  ⟦ num ↦ ⟦ φ ↦ ∅, plus ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_plus ⟧, eq ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_eq ⟧ ⟧,
+    bool ↦ ⟦ φ ↦ ∅, if ↦ ∅ ⟧,
+    count ↦ ⟦ size ↦ ∅, φ ↦ ξ.rec( index ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 00- ⟧ ), accum ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 00- ⟧ ) ), rec ↦ ⟦ ρ ↦ ∅, index ↦ ∅, accum ↦ ∅, φ ↦ ξ.index.eq( ξ.ρ.size ).if( ξ.accum, ξ.ρ.rec( index ↦ ξ.index.plus( Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ) ), accum ↦ ξ.accum.plus( Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ) ) ) ) ⟧ ⟧,
+    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.count( size ↦ Φ.num( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ⟧ ⟧
+protocol: |
+  𝕄(Φ.a):
+    𝕄(Φ.a.x):
+      𝑛·0·1 := Φ.num( φ ↦ 𝜎1:λ )
+      𝑛·0·2 := Φ.count( size ↦ 𝑛·0·1 )
+    𝔼(L_id):
+      𝕄(Φ.a🌵0):
+        𝑛·1·1 := Φ.count( size ↦ 𝑛·0·1 )
+        𝑛·1·2 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( Φ.num( φ ↦ 01-:Δ ) ), accum ↦ accum.plus( Φ.num( φ ↦ 01-:Δ ) ) ) ) ⟧( index ↦ Φ.num( φ ↦ 00-:Δ ) )
+        𝑛·1·3 := 𝑛·1·2( accum ↦ Φ.num( φ ↦ 00-:Δ ) )
+        𝕄(Φ.a🌵0.index):
+          𝑛·1·4 := Φ.num( φ ↦ 00-:Δ )
+        𝕄(Φ.a🌵0.accum):
+          𝑛·1·5 := Φ.num( φ ↦ 00-:Δ )
+        𝕄(Φ.a🌵0.φ):
+          𝑛·1·6 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·7 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·8 := Φ.num( φ ↦ 𝜎1:λ )
+          𝑛·1·9 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·10 := 𝑛·1·9.eq( b ↦ 𝑛·1·8 )
+          𝔼(L_eq):
+            𝔻(Φ.a🌵1):
+              𝕄(Φ.a🌵1):
+                𝑛·2·1 := Φ.num( φ ↦ 00-:Δ )
+              𝛿·2·1 := 00-
+            𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+            𝔻(Φ.a🌵2):
+              𝕄(Φ.a🌵2):
+                𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )
+            𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.b)
+            𝑛·2·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝑛
+            𝑛·2·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(𝑛·2·3)
+          𝑛·1·11 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+          𝑛·1·12 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·13 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+          𝑛·1·14 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ 𝑛·1·12 )
+          𝑛·1·15 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·16 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·17 := Φ.num( φ ↦ 01-:Δ )
+          𝑛·1·18 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·19 := 𝑛·1·18.plus( b ↦ 𝑛·1·17 )
+          𝔼(L_plus):
+            𝔻(Φ.a🌵3):
+              𝕄(Φ.a🌵3):
+                𝑛·3·1 := Φ.num( φ ↦ 00-:Δ )
+              𝛿·3·1 := 00-
+            𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+            𝔻(Φ.a🌵4):
+              𝕄(Φ.a🌵4):
+                𝑛·3·2 := Φ.num( φ ↦ 01-:Δ )
+              𝛿·3·2 := 01-
+            𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.b)
+            𝑛·3·3 := Φ.num( φ ↦ 𝜎3:λ )  # 𝑛
+            𝕄(𝑛·3·3):
+              𝑛·3·4 := Φ.num( φ ↦ 𝜎3:λ )
+            𝑛·3·5 := 𝑛·3·4  # 𝕄(𝑛·3·3)
+          𝑛·1·20 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·3·2 ), accum ↦ accum.plus( 𝑛·3·2 ) ) ) ⟧( index ↦ 𝑛·3·4 )
+          𝑛·1·21 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·22 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·23 := Φ.num( φ ↦ 01-:Δ )
+          𝑛·1·24 := Φ.num( φ ↦ 00-:Δ )
+          𝑛·1·25 := 𝑛·1·24.plus( b ↦ 𝑛·1·23 )
+          𝔼(L_plus):
+            𝑛·4·1 := 𝑛·3·4  # 𝑛
+            𝑛·4·2 := 𝑛·3·5  # 𝕄(𝑛·4·1)
+          𝑛·1·26 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·1·23 ), accum ↦ accum.plus( 𝑛·1·23 ) ) ) ⟧( index ↦ 𝑛·3·4 )
+          𝑛·1·27 := 𝑛·1·26( accum ↦ 𝑛·3·4 )
+          𝑛·1·28 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·1·23 ), accum ↦ accum.plus( 𝑛·1·23 ) ) ) ⟧( index ↦ 𝑛·3·4 )
+          𝑛·1·29 := 𝑛·1·28( accum ↦ 𝑛·3·4 )
+          𝑛·1·30 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+          𝑛·1·31 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ 𝑛·1·24 )
+          𝑛·1·32 := 𝑛·1·31( right ↦ 𝑛·1·29 )
+          𝔼(L_fork):
+            𝛿1·5 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)
+            𝕄(Φ.a🌵6):
+              𝑛·5·1 := Φ.num( φ ↦ 00-:Δ )
+            𝑛1·5 := 𝑛·5·1  # 𝕄(ξ.left)
+            𝕄(Φ.a🌵7):
+              𝕄(Φ.a🌵7.φ):
+                𝑛·5·2 := Φ.num( φ ↦ 𝜎1:λ )
+                𝑛·5·3 := 𝑛·3·4.eq( b ↦ 𝑛·5·2 )
+                𝔼(L_eq):
+                  𝔻(Φ.a🌵8):
+                    𝕄(Φ.a🌵8):
+                      𝑛·6·1 := Φ.num( φ ↦ 𝜎3:λ )
+                  𝛿1·6 := 𝔻(𝜎3:λ)  # 𝔻(ξ.ρ)
+                  𝔻(Φ.a🌵9):
+                    𝕄(Φ.a🌵9):
+                      𝑛·6·2 := Φ.num( φ ↦ 𝜎1:λ )
+                  𝛿2·6 := 𝔻(𝜎1:λ)  # 𝔻(ξ.b)
+                  𝑛·6·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝑛
+                  𝑛·6·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝕄(𝑛·6·3)
+                𝑛·5·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )
+                𝑛·5·5 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )
+                𝑛·5·6 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎4:λ ⟧( left ↦ 𝑛·6·1 )
+                𝑛·5·7 := Φ.num( φ ↦ 01-:Δ )
+                𝑛·5·8 := 𝑛·6·1.plus( b ↦ 𝑛·5·7 )
+                𝔼(L_plus):
+                  𝔻(Φ.a🌵10):
+                    𝕄(Φ.a🌵10):
+                      𝑛·7·1 := Φ.num( φ ↦ 𝜎3:λ )
+                  𝛿1·7 := 𝔻(𝜎3:λ)  # 𝔻(ξ.ρ)
+                  𝔻(Φ.a🌵11):
+                    𝕄(Φ.a🌵11):
+                      𝑛·7·2 := Φ.num( φ ↦ 01-:Δ )
+                    𝛿·7·1 := 01-
+                  𝛿2·7 := 𝛿·7·1  # 𝔻(ξ.b)
+                  𝑛·7·3 := Φ.num( φ ↦ 𝜎5:λ )  # 𝑛
+                  𝕄(𝑛·7·3):
+                    𝑛·7·4 := Φ.num( φ ↦ 𝜎5:λ )
+                  𝑛·7·5 := 𝑛·7·4  # 𝕄(𝑛·7·3)
+                𝑛·5·9 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·7·2 ), accum ↦ accum.plus( 𝑛·7·2 ) ) ) ⟧( index ↦ 𝑛·7·4 )
+                𝑛·5·10 := Φ.num( φ ↦ 01-:Δ )
+                𝑛·5·11 := 𝑛·7·1.plus( b ↦ 𝑛·5·10 )
+                𝔼(L_plus):
+                  𝑛·8·1 := 𝑛·7·4  # 𝑛
+                  𝑛·8·2 := 𝑛·7·5  # 𝕄(𝑛·8·1)
+                𝑛·5·12 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·5·10 ), accum ↦ accum.plus( 𝑛·5·10 ) ) ) ⟧( index ↦ 𝑛·7·4 )
+                𝑛·5·13 := 𝑛·5·12( accum ↦ 𝑛·7·4 )
+                𝑛·5·14 := ⟦ index ↦ ∅, accum ↦ ∅, φ ↦ index.eq( ρ.size ).if( accum, ρ.rec( index ↦ index.plus( 𝑛·5·10 ), accum ↦ accum.plus( 𝑛·5·10 ) ) ) ⟧( index ↦ 𝑛·7·4 )
+                𝑛·5·15 := 𝑛·5·14( accum ↦ 𝑛·7·4 )
+                𝑛·5·16 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ )
+                𝑛·5·17 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎4:λ ⟧( left ↦ 𝑛·7·1 )
+                𝑛·5·18 := 𝑛·5·17( right ↦ 𝑛·5·15 )
+                looped(𝑛·1·32) := 𝜎6  # plausible
+            𝑛2·5 := 𝜎6:λ  # 𝕄(ξ.right)
+            𝔻(𝜎7:λ) == 00-
+            𝑛3·5 := ⟦ φ ↦ 𝜎7:λ, plus(b) ↦ L_plus:λ, eq(b) ↦ L_eq:λ ⟧  # 𝑛1
+            𝑛4·5 := 𝑛2·5  # 𝑛2
+            𝔻(𝜎8:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎6:λ) }
+            𝑛5·5 := 𝜎8:λ  # [𝑛3, 𝑛4]
+            𝑛·5·19 := 𝑛5·5  # 𝑛
+            𝑛·5·20 := 𝑛5·5  # 𝕄(𝑛·5·19)
+      𝑛1·1 := 𝑛·1·3  # 𝕄(ξ.x.φ)
+      𝑛·1·33 := 𝑛1·1  # 𝑛
+      𝑛·1·34 := 𝑛1·1  # 𝕄(𝑛·1·33)
diff --git a/test-resources/evaluate-packs/cuts-a-recursion-that-differs-by-symbols-only.yaml b/test-resources/evaluate-packs/cuts-a-recursion-that-differs-by-symbols-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/cuts-a-recursion-that-differs-by-symbols-only.yaml
@@ -0,0 +1,171 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A factorial over a symbolic argument, under '--acyclic=proven'. Every round
+# of the recursion mints fresh symbols, so the deep walk fires 'mul' with
+# a ↦ 𝜎1 and b ↦ Φ.fact( n ↦ 𝜎3 ) on the first round and with a ↦ 𝜎3 and
+# b ↦ Φ.fact( n ↦ 𝜎5 ) on the second, and no two rounds are ever the same term.
+# They are the same formation up to a renaming of symbols, which is what the
+# guard compares by (#1420), and the walk enters the formation it fires (#1451),
+# so the second firing is cut before it opens and writes a 'looped' line where
+# its block would have stood, carrying the term of the round that was kept
+# (#1434). The budget is generous here, so what ends the run is the cut and not
+# the limit. The argument 'n' and every symbol 'dec' answers stand in a
+# carrier '⟦ φ ↦ 𝜎 ⟧', since the walk defers a copy one of whose arguments is
+# a bare symbol instead of entering it (#1729). The first 'L_if' gets stuck,
+# yet the symbols it minted stay taken, so the second one mints from 𝜎7 (#1738).
+symbolic: |
+  - λ: L_zero
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_dec
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧
+  - λ: L_mul
+    dataize:
+      𝛿1: $.a
+      𝛿2: $.b
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_if
+    dataize:
+      𝛿1: $.c
+    morph:
+      𝑛1: $.left
+      𝑛2: $.right
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.x'
+deep: true
+acyclic: proven
+partial: true
+steps: 250
+input: >
+  ⟦ if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,
+    zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,
+    dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,
+    mul ↦ ⟦ a ↦ ∅, b ↦ ∅, λ ⤍ L_mul ⟧,
+    fact ↦ ⟦ n ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ⟦ Δ ⤍ 01- ⟧, right ↦ Φ.mul( a ↦ ξ.n, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ) ) ) ) ⟧,
+    x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧ ) ⟧
+result: |-
+  ⟦
+    n ↦ 𝜎1:λ:φ,
+    φ ↦ Φ.if(
+      c ↦ 𝜎2:λ,
+      left ↦ 01-:Δ,
+      right ↦ Φ.mul( a ↦ n, b ↦ Φ.fact( n ↦ 𝜎3:λ:φ ) )
+    )
+  ⟧
+protocol: |
+  𝕄(Φ.x):
+    𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ:φ )
+    𝕄(Φ.x.φ):
+      𝑛·0·2 := Φ.zero( x ↦ 𝜎1:λ:φ )
+      𝔼(L_zero):
+        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+        𝑛·1·1 := 𝜎2:λ  # 𝑛
+        𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)
+      𝑛·0·3 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·4 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·5 := 𝑛·0·4( left ↦ 01-:Δ )
+      𝑛·0·6 := Φ.mul( a ↦ 𝜎1:λ:φ )
+      𝑛·0·7 := Φ.dec( x ↦ 𝜎1:λ:φ )
+      𝔼(L_dec):
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+        𝑛·2·1 := 𝜎3:λ:φ  # 𝑛
+        𝑛·2·2 := 𝜎3:λ:φ  # 𝕄(𝑛·2·1)
+      𝑛·0·8 := Φ.fact( n ↦ 𝜎3:λ:φ )
+      𝑛·0·9 := Φ.mul( a ↦ 𝜎1:λ:φ )
+      𝑛·0·10 := 𝑛·0·9( b ↦ 𝑛·0·8 )
+      𝔼(L_mul):
+        𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)
+        𝔻(Φ.a🌵3):
+          𝕄(Φ.a🌵3):
+            𝑛·3·1 := Φ.fact( n ↦ 𝜎3:λ:φ )
+            𝑛·3·2 := Φ.if( c ↦ Φ.zero( x ↦ 𝜎3:λ:φ ) )
+            𝑛·3·3 := 𝑛·3·2( left ↦ 01-:Δ )
+            𝑛·3·4 := 𝑛·3·3( right ↦ Φ.mul( a ↦ 𝜎3:λ:φ, b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) ) )
+          𝔼(L_if):
+            𝔻(Φ.a🌵4):
+              𝕄(Φ.a🌵4):
+                𝑛·4·1 := Φ.zero( x ↦ 𝜎3:λ:φ )
+              𝔼(L_zero):
+                𝛿1·5 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+                𝑛·5·1 := 𝜎4:λ  # 𝑛
+                𝑛·5·2 := 𝜎4:λ  # 𝕄(𝑛·5·1)
+            𝛿1·4 := 𝔻(𝜎4:λ)  # 𝔻(ξ.c)
+            𝑛1·4 := 01-:Δ  # 𝕄(ξ.left)
+            𝕄(Φ.a🌵7):
+              𝑛·4·2 := Φ.mul( a ↦ 𝜎3:λ:φ )
+              𝑛·4·3 := 𝑛·4·2( b ↦ Φ.fact( n ↦ Φ.dec( x ↦ 𝜎3:λ:φ ) ) )
+              𝕄(Φ.a🌵7.b):
+                𝑛·4·4 := Φ.dec( x ↦ 𝜎3:λ:φ )
+                𝔼(L_dec):
+                  𝛿1·6 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+                  𝑛·6·1 := 𝜎5:λ:φ  # 𝑛
+                  𝑛·6·2 := 𝜎5:λ:φ  # 𝕄(𝑛·6·1)
+                𝑛·4·5 := Φ.fact( n ↦ 𝜎5:λ:φ )
+              looped(𝑛·0·10)  # proven
+            𝑛2·4 := 𝑛·4·3  # 𝕄(ξ.right)
+            𝔻(𝜎6:λ) == 01-
+            𝑛3·4 := 𝜎6:λ  # 𝑛1
+            𝑛4·4 := 𝑛·4·3  # 𝑛2
+            stuck(L_if)
+        stuck(L_if)
+      𝑛·0·11 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·12 := 𝑛·0·11( left ↦ 01-:Δ )
+      𝑛·0·13 := 𝑛·0·12( right ↦ 𝑛·0·10 )
+      𝔼(L_if):
+        𝛿1·7 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)
+        𝑛1·7 := 01-:Δ  # 𝕄(ξ.left)
+        𝕄(Φ.a🌵11):
+          𝑛·7·1 := Φ.mul( a ↦ 𝜎1:λ:φ )
+          𝑛·7·2 := 𝑛·7·1( b ↦ 𝑛·3·1 )
+          𝕄(Φ.a🌵11.b):
+            𝑛·7·3 := Φ.fact( n ↦ 𝜎3:λ:φ )
+          𝔼(L_mul):
+            𝛿1·8 := 𝔻(𝜎1:λ)  # 𝔻(ξ.a)
+            𝔻(Φ.a🌵13):
+              𝕄(Φ.a🌵13):
+                𝑛·8·1 := Φ.fact( n ↦ 𝜎3:λ:φ )
+                𝑛·8·2 := Φ.if( c ↦ 𝑛·4·1 )
+                𝑛·8·3 := 𝑛·8·2( left ↦ 01-:Δ )
+                𝑛·8·4 := 𝑛·8·3( right ↦ 𝑛·4·3 )
+              𝔼(L_if):
+                𝔻(Φ.a🌵14):
+                  𝕄(Φ.a🌵14):
+                    𝑛·9·1 := Φ.zero( x ↦ 𝜎3:λ:φ )
+                  𝔼(L_zero):
+                    𝛿1·10 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+                    𝑛·10·1 := 𝜎7:λ  # 𝑛
+                    𝑛·10·2 := 𝜎7:λ  # 𝕄(𝑛·10·1)
+                𝛿1·9 := 𝔻(𝜎7:λ)  # 𝔻(ξ.c)
+                𝑛1·9 := 01-:Δ  # 𝕄(ξ.left)
+                𝕄(Φ.a🌵17):
+                  𝑛·9·2 := Φ.mul( a ↦ 𝜎3:λ:φ )
+                  𝑛·9·3 := 𝑛·9·2( b ↦ Φ.fact( n ↦ 𝑛·4·4 ) )
+                  𝕄(Φ.a🌵17.b):
+                    𝑛·9·4 := Φ.dec( x ↦ 𝜎3:λ:φ )
+                    𝔼(L_dec):
+                      𝛿1·11 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+                      𝑛·11·1 := 𝜎8:λ:φ  # 𝑛
+                      𝑛·11·2 := 𝜎8:λ:φ  # 𝕄(𝑛·11·1)
+                    𝑛·9·5 := Φ.fact( n ↦ 𝜎8:λ:φ )
+                  looped(𝑛·7·2)  # proven
+                𝑛2·9 := 𝑛·9·3  # 𝕄(ξ.right)
+                𝔻(𝜎9:λ) == 01-
+                𝑛3·9 := 𝜎9:λ  # 𝑛1
+                𝑛4·9 := 𝑛·9·3  # 𝑛2
+                stuck(L_if)
+            stuck(L_if)
+        𝑛2·7 := 𝑛·7·2  # 𝕄(ξ.right)
+        𝔻(𝜎10:λ) == 01-
+        𝑛3·7 := 𝜎10:λ  # 𝑛1
+        𝑛4·7 := 𝑛·7·2  # 𝑛2
+        stuck(L_if)
diff --git a/test-resources/evaluate-packs/defers-a-copy-whose-argument-is-a-bare-symbol.yaml b/test-resources/evaluate-packs/defers-a-copy-whose-argument-is-a-bare-symbol.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/defers-a-copy-whose-argument-is-a-bare-symbol.yaml
@@ -0,0 +1,167 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The loop of #1729: 'joined' folds a tuple whose tail is the bare symbol '𝜎3',
+# an object nobody knows. The right branch of the first fork is the copy
+# 'step( acc ↦ …, tup ↦ 𝜎3 )', and nothing inside its body can succeed, since
+# every round asks '𝜎3' for its 'length'. Before #1729 the walk entered that
+# body, got stuck six times, and left it as written, so the join had a symbol
+# on the left and a body on the right, and 'e' came to nothing. Now the walk
+# does not enter a copy one of whose arguments reads to a bare symbol: it
+# answers the copy with a fresh symbol and writes a 'deferred' line naming the
+# copy it stands for, the join pairs that symbol with the one the left branch
+# ends in, and 'e' comes to a symbol. Since #1732 that line spells the copy as
+# a call of 'Φ.joined.step', the object of the world it was made of, rather
+# than of 'Φ.e.step', the copy of 'joined' the walk wrote into 'e', since only
+# the first one has voids the copy fills. Reading 'items.length' through the
+# tuple, itself a copy over bare symbols, still reaches '𝜎1', since a copy with
+# no φ has no body to defer.
+symbolic: |
+  - λ: L_bytes_eq
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )
+  - λ: L_bytes_concat
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.φ
+    morph:
+      𝑛1: $.left
+      𝑛2: $.right
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.e'
+deep: true
+partial: true
+acyclic: plausible
+result: '⟦ items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ ), φ ↦ 𝜎9:λ, step(acc, tup) ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ):φ ⟧'
+input: |
+  ⟦
+    bool(if) ↦ ⟦ φ ↦ if( Φ.bytes( φ ↦ FF-:Δ ), Φ.bytes( φ ↦ 00-:Δ ) ) ⟧,
+    bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ, concat(ρ, b) ↦ L_bytes_concat:λ ⟧,
+    dataized(target) ↦ L_dataized:λ,
+    tuple(length, head, tail) ↦ ⟦⟧,
+    joined(items) ↦ ⟦
+      φ ↦ Φ.dataized( target ↦ items.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if(
+        Φ.bytes( φ ↦ --:Δ ),
+        step( acc ↦ Φ.dataized( target ↦ items.head ), tup ↦ items.tail )
+      ),
+      step(ρ, acc, tup) ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ).if(
+        acc,
+        ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail )
+      ):φ
+    ⟧,
+    e ↦ Φ.joined(
+      items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ )
+    )
+  ⟧
+protocol: |
+  𝕄(Φ.e):
+    𝑛·0·1 := Φ.joined( items ↦ Φ.tuple( length ↦ Φ.bytes( φ ↦ 𝜎1:λ ), head ↦ 𝜎2:λ, tail ↦ 𝜎3:λ ) )
+    𝕄(Φ.e.items):
+      𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·3 := Φ.tuple( length ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.tuple( length ↦ 𝑛·0·2 )
+      𝑛·0·5 := 𝑛·0·4( head ↦ 𝜎2:λ )
+      𝑛·0·6 := Φ.tuple( length ↦ 𝑛·0·2 )
+      𝑛·0·7 := 𝑛·0·6( head ↦ 𝜎2:λ )
+      𝑛·0·8 := 𝑛·0·7( tail ↦ 𝜎3:λ )
+    𝕄(Φ.e.φ):
+      𝑛·0·9 := Φ.tuple( length ↦ 𝑛·0·2 )
+      𝑛·0·10 := 𝑛·0·9( head ↦ 𝜎2:λ )
+      𝑛·0·11 := 𝑛·0·10( tail ↦ 𝜎3:λ )
+      𝑛·0·12 := Φ.tuple( length ↦ 𝑛·0·2 )
+      𝑛·0·13 := 𝑛·0·12( head ↦ 𝜎2:λ )
+      𝑛·0·14 := 𝑛·0·13( tail ↦ 𝜎3:λ )
+      𝑛·0·15 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·16 := Φ.dataized( target ↦ 𝑛·0·14.length )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.tuple( length ↦ 𝑛·0·15 )
+            𝑛·1·2 := 𝑛·1·1( head ↦ 𝜎2:λ )
+            𝑛·1·3 := 𝑛·1·2( tail ↦ 𝜎3:λ )
+            𝑛·1·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)
+        𝑛·1·5 := Φ.bytes( φ ↦ 𝜎4:λ )  # 𝑛
+        𝕄(𝑛·1·5):
+          𝑛·1·6 := Φ.bytes( φ ↦ 𝜎4:λ )
+        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+      𝑛·0·17 := Φ.bytes( φ ↦ 00-:Δ )
+      𝑛·0·18 := 𝑛·1·6.eq( b ↦ 𝑛·0·17 )
+      𝔼(L_bytes_eq):
+        𝔻(Φ.a🌵1):
+          𝕄(Φ.a🌵1):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎4:λ )
+        𝛿1·2 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·2 := Φ.bytes( φ ↦ 00-:Δ )
+          𝛿·2·1 := 00-
+        𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.b)
+        𝑛·2·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )  # 𝑛
+        𝕄(𝑛·2·3):
+          𝑛·2·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )
+        𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)
+      𝑛·0·19 := Φ.bytes( φ ↦ --:Δ )
+      𝑛·0·20 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ 𝑛·0·19 )
+      𝑛·0·21 := Φ.tuple( length ↦ 𝑛·1·4 )
+      𝑛·0·22 := 𝑛·0·21( head ↦ 𝜎2:λ )
+      𝑛·0·23 := 𝑛·0·22( tail ↦ 𝜎3:λ )
+      𝑛·0·24 := Φ.tuple( length ↦ 𝑛·1·4 )
+      𝑛·0·25 := 𝑛·0·24( head ↦ 𝜎2:λ )
+      𝑛·0·26 := 𝑛·0·25( tail ↦ 𝜎3:λ )
+      𝑛·0·27 := Φ.dataized( target ↦ 𝑛·0·26.head )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵3):
+          𝕄(Φ.a🌵3):
+            𝑛·3·1 := Φ.tuple( length ↦ 𝑛·1·4 )
+            𝑛·3·2 := 𝑛·3·1( head ↦ 𝜎2:λ )
+            𝑛·3·3 := 𝑛·3·2( tail ↦ 𝜎3:λ )
+        𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.target)
+        𝑛·3·4 := Φ.bytes( φ ↦ 𝜎6:λ )  # 𝑛
+        𝕄(𝑛·3·4):
+          𝑛·3·5 := Φ.bytes( φ ↦ 𝜎6:λ )
+        𝑛·3·6 := 𝑛·3·5  # 𝕄(𝑛·3·4)
+      𝑛·0·28 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )
+      𝑛·0·29 := Φ.tuple( length ↦ 𝑛·1·4 )
+      𝑛·0·30 := 𝑛·0·29( head ↦ 𝜎2:λ )
+      𝑛·0·31 := 𝑛·0·30( tail ↦ 𝜎3:λ )
+      𝑛·0·32 := Φ.tuple( length ↦ 𝑛·1·4 )
+      𝑛·0·33 := 𝑛·0·32( head ↦ 𝜎2:λ )
+      𝑛·0·34 := 𝑛·0·33( tail ↦ 𝜎3:λ )
+      𝑛·0·35 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )
+      𝑛·0·36 := 𝑛·0·35( tup ↦ 𝑛·0·34.tail )
+      𝑛·0·37 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ 𝑛·0·19 )
+      𝑛·0·38 := ⟦ acc ↦ ∅, tup ↦ ∅, φ ↦ Φ.dataized( target ↦ tup.length ).eq( b ↦ 𝑛·2·2 ).if( acc, ρ.step( acc ↦ acc.concat( b ↦ Φ.dataized( target ↦ tup.head ) ), tup ↦ tup.tail ) ) ⟧( acc ↦ 𝑛·3·5 )
+      𝑛·0·39 := 𝑛·0·38( tup ↦ 𝑛·0·34.tail )
+      𝑛·0·40 := 𝑛·0·37( right ↦ 𝑛·0·39 )
+      𝔼(L_fork):
+        𝛿1·4 := 𝔻(𝜎5:λ)  # 𝔻(ξ.φ)
+        𝕄(Φ.a🌵5):
+          𝑛·4·1 := Φ.bytes( φ ↦ --:Δ )
+        𝑛1·4 := 𝑛·4·1  # 𝕄(ξ.left)
+        𝕄(Φ.a🌵6):
+          deferred(𝜎7) := Φ.joined.step( acc ↦ 𝑛·3·5, tup ↦ 𝜎3:λ )
+        𝑛2·4 := 𝜎7:λ  # 𝕄(ξ.right)
+        𝔻(𝜎8:λ) == --
+        𝑛3·4 := ⟦ φ ↦ 𝜎8:λ, eq(b) ↦ L_bytes_eq:λ, concat(b) ↦ L_bytes_concat:λ ⟧  # 𝑛1
+        𝑛4·4 := 𝑛2·4  # 𝑛2
+        𝔻(𝜎9:λ) ∈ { 𝔻(𝜎8:λ), 𝔻(𝜎7:λ) }
+        𝑛5·4 := 𝜎9:λ  # [𝑛3, 𝑛4]
+        𝑛·4·2 := 𝑛5·4  # 𝑛
+        𝑛·4·3 := 𝑛5·4  # 𝕄(𝑛·4·2)
diff --git a/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-one-symbol-when-strict.yaml b/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-one-symbol-when-strict.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-one-symbol-when-strict.yaml
@@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The fork of 'bool.and' from #1817 under an entry that does not say
+# 'lenient: true'. A strict join pairs a bare symbol only with the symbol a φ
+# chain ends in, and the chain of the second branch stops at the call of
+# 'Φ.bool', so there is no symbol to pair it with and the firing gets stuck.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+deep: true
+input: |
+  ⟦
+    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,
+    y ↦ ⟦
+      r ↦ ⟦
+        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,
+        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,
+        λ ⤍ L_fork
+      ⟧
+    ⟧
+  ⟧
+fails: "No entry of --symbolic answers the λ function 'L_fork'"
+protocol: |
+  𝕄(Φ.y):
+    𝕄(Φ.y.r):
+      𝕄(Φ.y.r.b):
+        𝕄(Φ.y.r.b.φ):
+          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+      𝔼(L_fork):
+        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)
+        𝕄(Φ.a🌵1):
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)
+        stuck(L_fork)
diff --git a/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-two-symbols-when-lenient.yaml b/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-two-symbols-when-lenient.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-a-bare-symbol-and-a-branch-holding-two-symbols-when-lenient.yaml
@@ -0,0 +1,44 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A lenient join answers a copy of the branch a bare symbol stands against only
+# when that branch holds exactly one symbol (#1817), since only then is it
+# clear which symbol the bare one pairs with. Here the call of 'Φ.bool' holds
+# two, '𝜎2' and '𝜎3', so the join is refused and the firing gets stuck.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    lenient: true
+    𝑛: 𝑛3
+location: 'Q.y'
+deep: true
+input: |
+  ⟦
+    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,
+    y ↦ ⟦
+      r ↦ ⟦
+        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,
+        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, was ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ) ⟧,
+        λ ⤍ L_fork
+      ⟧
+    ⟧
+  ⟧
+fails: "No entry of --symbolic answers the λ function 'L_fork'"
+protocol: |
+  𝕄(Φ.y):
+    𝕄(Φ.y.r):
+      𝕄(Φ.y.r.b):
+        𝕄(Φ.y.r.b.φ):
+          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ )
+      𝔼(L_fork):
+        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)
+        𝕄(Φ.a🌵1):
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ, was ↦ 𝜎3:λ ⟧ )
+        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)
+        stuck(L_fork)
diff --git a/test-resources/evaluate-packs/fails-on-a-branch-and-a-decorator-of-it-when-strict.yaml b/test-resources/evaluate-packs/fails-on-a-branch-and-a-decorator-of-it-when-strict.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-a-branch-and-a-decorator-of-it-when-strict.yaml
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The fork the lenient join of #1815 brings together, under an entry that
+# does not say 'lenient: true'. A copy and a decorator of it differ in shape,
+# so the strict join refuses them and the firing gets stuck, as an entry is
+# strict unless it says otherwise.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+fails: "No entry of --symbolic answers the λ function 'L_fork'"
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.b)
+      stuck(L_fork)
diff --git a/test-resources/evaluate-packs/fails-on-a-fork-whose-branches-do-not-join.yaml b/test-resources/evaluate-packs/fails-on-a-fork-whose-branches-do-not-join.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-a-fork-whose-branches-do-not-join.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The very fork '--partial' parks, without the option: a firing with nothing to
+# answer fails the run, exactly as a λ function no entry of the '--symbolic'
+# file answers does. The protocol keeps what the firing had reached before it
+# got stuck, since it records what 𝔼 was asked for and not only what it could
+# answer.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'
+fails: "No entry of --symbolic answers the λ function 'L_fork'"
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)
+      stuck(L_fork)
diff --git a/test-resources/evaluate-packs/fails-on-a-lambda-no-entry-answers.yaml b/test-resources/evaluate-packs/fails-on-a-lambda-no-entry-answers.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-a-lambda-no-entry-answers.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A λ name no key of the file matches has nothing to answer it, so the run
+# fails and the protocol holds nothing but the run itself.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.x'
+input: '⟦ x ↦ ⟦ λ ⤍ L_none ⟧.foo ⟧'
+fails: "No entry of --symbolic answers the λ function 'L_none'"
+protocol: |
+  𝕄(Φ.x):
+    unanswered(L_none)  # 𝕄(L_none:λ)
diff --git a/test-resources/evaluate-packs/fails-on-an-operand-that-reaches-the-terminator.yaml b/test-resources/evaluate-packs/fails-on-an-operand-that-reaches-the-terminator.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fails-on-an-operand-that-reaches-the-terminator.yaml
@@ -0,0 +1,61 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The very operand '--partial' parks, without the option: an operand of a
+# firing that reaches the terminator ⊥ still fails the run, as it did before
+# #1401, and the protocol keeps what the run had reached when it did.
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.l🌵'
+deep: true
+acyclic: proven
+steps: 32
+input: >
+  ⟦
+    number ↦ ⟦ φ ↦ ∅ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅, size ↦ ⟦ λ ⤍ L_bytes_size ⟧ ⟧,
+    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,
+    block ↦ ⟦ data ↦ ∅, buffer ↦ ∅, read ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.data.size ).as-bytes ⟧ ⟧,
+    l🌵 ↦ ⟦
+      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,
+      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read )
+    ⟧
+  ⟧
+fails: 'terminator ⊥'
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.block( buffer ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.block( buffer ↦ 𝑛·0·4 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6.read )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.block( buffer ↦ 𝑛·0·4 )
+          𝑛·1·4 := Φ.dataized( target ↦ ⊥ )
+          𝔼(L_dataized):
diff --git a/test-resources/evaluate-packs/fires-a-formation-again-where-the-budget-stalled-it.yaml b/test-resources/evaluate-packs/fires-a-formation-again-where-the-budget-stalled-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-formation-again-where-the-budget-stalled-it.yaml
@@ -0,0 +1,52 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps no stall the step budget made. The
+# formation 'f' is first fired deep inside the operand of 'L_wrap', where the
+# budget runs out before 'arg' comes down, so the firing gets stuck. Before
+# #1514 the stall was kept, and the firings of the same formation under
+# 'Φ.q.φ' and 'Φ.f', where the budget is enough, were told it bare, with
+# nothing answered in between. Now the firing under 'Φ.q.φ' brings 'arg'
+# down and answers, and the walk leaves that answer in 'f', so it fires
+# nothing more when it reaches 'Φ.f' (#1720).
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_wrap
+    dataize:
+      𝛿1: ξ.w
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+partial: true
+acyclic: plausible
+steps: 10
+input: >
+  ⟦ x ↦ ⟦ w ↦ Φ.p, λ ⤍ L_wrap ⟧,
+    p ↦ ⟦ φ ↦ Φ.q ⟧,
+    q ↦ ⟦ φ ↦ Φ.f ⟧,
+    f ↦ ⟦ arg ↦ Φ.c, λ ⤍ L_outer ⟧,
+    c ↦ ⟦ φ ↦ Φ.d ⟧,
+    d ↦ ⟦ φ ↦ Φ.e ⟧,
+    e ↦ ⟦ Δ ⤍ 01- ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝔼(L_wrap):
+        𝔻(Φ.a🌵0):
+          𝔼(L_outer):
+            𝔻(Φ.a🌵1):
+              𝕄(Φ.a🌵1):
+                starved(10)
+            stuck(L_outer)
+        stuck(L_outer)
+    𝕄(Φ.q):
+      𝕄(Φ.q.φ):
+        𝔼(L_outer):
+          𝔻(Φ.a🌵2):
+            𝛿·3·1 := 01-
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.arg)
+          𝑛·3·1 := 𝜎1:λ  # 𝑛
+          𝑛·3·2 := 𝜎1:λ  # 𝕄(𝑛·3·1)
diff --git a/test-resources/evaluate-packs/fires-a-formation-once-however-often-it-is-met.yaml b/test-resources/evaluate-packs/fires-a-formation-once-however-often-it-is-met.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-formation-once-however-often-it-is-met.yaml
@@ -0,0 +1,96 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible': 𝔼 is a function of the formation it
+# fires, so two bindings spelling one term are two firings of one formation,
+# and a run firing each of them afresh mints two symbols for one value and
+# fires everything under the second once more (#1476). Under the mode the
+# second takes what the first answered: the inner sum of 'b' is the inner sum
+# of 'a' fired again, and so is the outer one once the inner answers the
+# same, so the walk over 'b' reduces nothing, mints nothing and lands 'b' on
+# the very symbol 'a' came to. The protocol still writes the two firings
+# under 'b', at their own site, each with the answer the firing under 'a'
+# made, named after the line that made it, and with no operand line under
+# it, since none was reduced.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+deep: true
+acyclic: plausible
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    a ↦ 7.plus( 5.plus( 6 ) ),
+    b ↦ 7.plus( 5.plus( 6 ) ) ⟧
+result: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(x) ↦ L_number_plus:λ ⟧,
+    a ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧,
+    b ↦ ⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.a):
+      𝑛·0·1 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·4 )
+      𝑛·0·7 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·8 := Φ.number( φ ↦ 𝑛·0·7 )
+      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·4 )
+      𝑛·0·10 := 𝑛·0·9.plus( x ↦ 𝑛·0·8 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·4 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵1):
+          𝕄(Φ.a🌵1):
+            𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·7 )
+            𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+          𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+        𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+        𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+        𝕄(𝑛·1·5):
+          𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+      𝑛·0·11 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·12 := 𝑛·0·11.plus( x ↦ 𝑛·1·6 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·2·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+          𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0
+        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+        𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+        𝑛·2·3 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+        𝕄(𝑛·2·3):
+          𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )
+        𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)
+    𝕄(Φ.b):
+      𝑛·0·13 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·14 := Φ.number( φ ↦ 𝑛·0·13 )
+      𝑛·0·15 := Φ.number( φ ↦ 𝑛·0·13 )
+      𝑛·0·16 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+      𝑛·0·17 := Φ.number( φ ↦ 𝑛·0·16 )
+      𝑛·0·18 := Φ.number( φ ↦ 𝑛·0·16 )
+      𝑛·0·19 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·20 := Φ.number( φ ↦ 𝑛·0·19 )
+      𝑛·0·21 := Φ.number( φ ↦ 𝑛·0·16 )
+      𝑛·0·22 := 𝑛·0·21.plus( x ↦ 𝑛·0·20 )
+      𝔼(L_number_plus):
+        𝑛·3·1 := 𝑛·1·6  # 𝑛
+        𝑛·3·2 := 𝑛·1·7  # 𝕄(𝑛·3·1)
+      𝑛·0·23 := Φ.number( φ ↦ 𝑛·0·13 )
+      𝑛·0·24 := 𝑛·0·23.plus( x ↦ 𝑛·1·6 )
+      𝔼(L_number_plus):
+        𝑛·4·1 := 𝑛·2·4  # 𝑛
+        𝑛·4·2 := 𝑛·2·5  # 𝕄(𝑛·4·1)
diff --git a/test-resources/evaluate-packs/fires-a-lambda-once-however-many-bindings-read-it.yaml b/test-resources/evaluate-packs/fires-a-lambda-once-however-many-bindings-read-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-lambda-once-however-many-bindings-read-it.yaml
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The deep walk writes what it morphed back into the world (#1720). Before,
+# the world stayed as written, so 'b' read 'Φ.a' as 'Φ.foo.x' and fired 'Fn'
+# twice more, and the walk fired it once again when it reached 'foo'. Now the
+# read of 'Φ.foo' under 'a' leaves the answer in 'foo', the walk leaves what
+# it made of 'a' in 'a', and 'Fn' fires once, as it does in EO.
+symbolic: |
+  - λ: Fn
+    𝑛: ⟦ x ↦ ⟦ y ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ⟧
+deep: true
+partial: true
+acyclic: plausible
+input: >
+  ⟦ a ↦ Φ.foo.x, b ↦ Φ.a.y, foo ↦ ⟦ λ ⤍ Fn ⟧ ⟧
+result: >
+  ⟦ a ↦ ⟦ x ↦ ⟦ y ↦ 𝜎1:λ ⟧ ⟧.x, b ↦ Φ.a.y, foo ↦ ⟦ x ↦ ⟦ y ↦ 𝜎1:λ ⟧ ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.a):
+      𝔼(Fn):
+        𝑛·1·1 := 𝜎1:λ:y:x  # 𝑛
+        𝑛·1·2 := 𝜎1:λ:y:x  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/fires-a-read-once-in-the-copy-it-stands-in.yaml b/test-resources/evaluate-packs/fires-a-read-once-in-the-copy-it-stands-in.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-read-once-in-the-copy-it-stands-in.yaml
@@ -0,0 +1,62 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The deep walk writes what it morphed back into the copy it stands in, not
+# only into the world as written (#1720). Here 'm' lives in the copy
+# 'Φ.same( left ↦ … )', and 'φ' reads it twice before the walk reaches 'm'.
+# Before, every read fired 'L_bytes_eq' again, and so did the walk at 'm'. Now
+# the first read leaves the answer in 'm', so the second read and the walk at
+# 'm' fire nothing, and 'L_bytes_eq' fires twice: once for 'm' and once for
+# 'm.eq( b ↦ m )'.
+symbolic: |
+  - λ: L_bytes_eq
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.e'
+deep: true
+partial: true
+acyclic: plausible
+input: >
+  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,
+    same(left) ↦ ⟦ φ ↦ m.eq( b ↦ m ), m ↦ left.eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) ) ⟧,
+    e ↦ Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) ⟧
+result: >
+  ⟦ left ↦ Φ.bytes( φ ↦ 𝜎1:λ ),
+    φ ↦ ⟦ φ ↦ 𝜎3:λ, eq(b) ↦ L_bytes_eq:λ ⟧,
+    m ↦ ⟦ φ ↦ 𝜎2:λ, eq(b) ↦ L_bytes_eq:λ ⟧ ⟧
+protocol: |
+  𝕄(Φ.e):
+    𝑛·0·1 := Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )
+    𝕄(Φ.e.left):
+      𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝕄(Φ.e.φ):
+      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·4 := 𝑛·0·3.eq( b ↦ Φ.bytes( φ ↦ 00-:Δ ) )
+      𝔼(L_bytes_eq):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵1):
+          𝕄(Φ.a🌵1):
+            𝑛·1·2 := Φ.bytes( φ ↦ 00-:Δ )
+          𝛿·1·1 := 00-
+        𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.b)
+        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛
+        𝕄(𝑛·1·3):
+          𝑛·1·4 := Φ.bytes( φ ↦ 𝜎2:λ )
+        𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)
+      𝑛·0·5 := 𝑛·1·4.eq( b ↦ 𝑛·1·4 )
+      𝔼(L_bytes_eq):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )
+        𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+        𝛿2·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.b)
+        𝑛·2·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛
+        𝕄(𝑛·2·2):
+          𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )
+        𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/fires-a-sibling-at-the-depth-the-head-pass-began-at.yaml b/test-resources/evaluate-packs/fires-a-sibling-at-the-depth-the-head-pass-began-at.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-sibling-at-the-depth-the-head-pass-began-at.yaml
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The head pass of 𝕄 takes four steps through the four φ layers of 'Φ.e'
+# before it reads 'eq' of the inner formation, whose 'φ' reads its sibling
+# 'm'. Under '--deep' the head pass fires 'm' first and writes its answer back
+# (#1727). Before #1731 it fired 'm' at the depth of its fourth step, so the
+# firing ran out of ten steps, and 'L_dataized' fired three times without
+# reaching the answer. Now the head pass fires 'm' at the depth it began at,
+# the way the deep walk at the end of 𝕄 fires it, and 'm' fires once.
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.e'
+deep: true
+partial: true
+acyclic: plausible
+steps: 10
+input: >
+  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,
+    dataized(target) ↦ L_dataized:λ,
+    e ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ φ ↦ m, m ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ) ⟧ ⟧ ⟧ ⟧.eq ⟧
+result: '⟦ b ↦ ∅, λ ⤍ L_bytes_eq ⟧'
+protocol: |
+  𝕄(Φ.e):
+    𝑛·0·1 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )
+    𝔼(L_dataized):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)
+      𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·1·2):
+        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)
diff --git a/test-resources/evaluate-packs/fires-a-sibling-once-through-a-dispatch-into-its-object.yaml b/test-resources/evaluate-packs/fires-a-sibling-once-through-a-dispatch-into-its-object.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-sibling-once-through-a-dispatch-into-its-object.yaml
@@ -0,0 +1,56 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The term at 'Φ.e' reads 'φ' of the object 'Φ.same' makes, and that 'φ'
+# reads its sibling 'm' twice. Before #1727 the head pass of 𝕄 copied 'm'
+# into both reads before anything fired, so the deep walk later fired
+# 'L_dataized' again for each copy, four firings in all. Now, under '--deep',
+# the head pass first fires the reads of siblings in the binding it
+# dispatches into, the way the deep walk fires them, and writes their answers
+# back, so 'm' fires once and both reads take its answer: two firings, as
+# many as the same object costs without the '.φ'.
+symbolic: |
+  - λ: L_bytes_eq
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.e'
+deep: true
+partial: true
+acyclic: plausible
+input: >
+  ⟦ bytes(φ) ↦ ⟦ eq(ρ, b) ↦ L_bytes_eq:λ ⟧,
+    dataized(target) ↦ L_dataized:λ,
+    same(left) ↦ ⟦ φ ↦ m.eq( b ↦ m ), m ↦ Φ.dataized( target ↦ left ) ⟧,
+    e ↦ Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ).φ ⟧
+result: '⟦ φ ↦ 𝜎3:λ, eq(b) ↦ L_bytes_eq:λ ⟧'
+protocol: |
+  𝕄(Φ.e):
+    𝑛·0·1 := Φ.same( left ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )
+    𝑛·0·2 := Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )
+    𝔼(L_dataized):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)
+      𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·1·2):
+        𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)
+    𝑛·0·3 := 𝑛·1·3.eq( b ↦ 𝑛·1·3 )
+    𝔼(L_bytes_eq):
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝛿2·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.b)
+      𝑛·2·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·2·2):
+        𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )
+      𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/fires-a-stalled-formation-again-after-what-its-own-firing-answered.yaml b/test-resources/evaluate-packs/fires-a-stalled-formation-again-after-what-its-own-firing-answered.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-stalled-formation-again-after-what-its-own-firing-answered.yaml
@@ -0,0 +1,58 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' stamps a stall with the count of answers
+# the memo held when the firing began, not when it failed. The firing under
+# 'x' brings 'first' down through 'L_inner', which answers, and then gets
+# stuck on 'L_none'. Before #1507 the stall was stamped after that answer, so
+# the firing under 'y', the very same formation, was told the stall bare, as
+# if nothing was answered since. Now the answer made inside the first firing
+# counts as one made after it began, so the firing under 'y' reduces its
+# operands again.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.first
+      𝛿2: ξ.second
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_inner
+    𝑛: ⟦ Δ ⤍ 01- ⟧
+deep: true
+partial: true
+acyclic: plausible
+steps: 64
+input: >
+  ⟦ x ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,
+    y ↦ ⟦ first ↦ Φ.s, second ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,
+    s ↦ ⟦ φ ↦ Φ.g ⟧,
+    g ↦ ⟦ λ ⤍ L_inner ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝔼(L_outer):
+        𝔻(Φ.a🌵0):
+          𝔼(L_inner):
+            𝑛·2·1 := 01-:Δ  # 𝑛
+            𝑛·2·2 := 01-:Δ  # 𝕄(𝑛·2·1)
+          𝛿·1·1 := 01-
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.first)
+        𝔻(Φ.a🌵1):
+          unanswered(L_none)  # 𝔻(L_none:λ)
+        stuck(L_none)
+    𝕄(Φ.y):
+      𝔼(L_outer):
+        𝔻(Φ.a🌵2):
+          𝔼(L_inner):
+            𝑛·4·1 := 01-:Δ  # 𝑛
+            𝑛·4·2 := 01-:Δ  # 𝕄(𝑛·4·1)
+          𝛿·3·1 := 01-
+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.first)
+        𝔻(Φ.a🌵3):
+          unanswered(L_none)  # 𝔻(L_none:λ)
+        stuck(L_none)
+    𝕄(Φ.s):
+      𝕄(Φ.s.φ):
+        𝔼(L_inner):
+          𝑛·5·1 := 01-:Δ  # 𝑛
+          𝑛·5·2 := 01-:Δ  # 𝕄(𝑛·5·1)
diff --git a/test-resources/evaluate-packs/fires-a-stalled-formation-again-once-something-was-answered.yaml b/test-resources/evaluate-packs/fires-a-stalled-formation-again-once-something-was-answered.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-a-stalled-formation-again-once-something-was-answered.yaml
@@ -0,0 +1,54 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps a stall only while nothing new was
+# answered. The firing of 'f' under 'x' brings 'big' down inside the box of
+# 's', and 's' is within 'big', so the mode cuts the operand and the firing
+# gets stuck. Then 'g' answers, and 'f' is fired again at its own site, where
+# nothing cuts it. Before #1495 that firing took the stall the first one met
+# and reduced nothing; now it brings 'big' down and answers.
+symbolic: |
+  - λ: L_f
+    dataize:
+      𝛿1: $.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_g
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+partial: true
+acyclic: plausible
+steps: 32
+input: >
+  ⟦ x ↦ ⟦ λ ⤍ L_f, arg ↦ Φ.s ⟧,
+    g ↦ ⟦ λ ⤍ L_g ⟧,
+    f ↦ ⟦ λ ⤍ L_f, arg ↦ Φ.big ⟧,
+    s ↦ ⟦ φ ↦ Φ.f ⟧,
+    big ↦ ⟦ φ ↦ Φ.k( x ↦ Φ.f ) ⟧,
+    k ↦ ⟦ x ↦ ∅, φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝔼(L_f):
+        𝔻(Φ.a🌵0):
+          𝔼(L_f):
+            𝔻(Φ.a🌵1):
+              looped(Φ.f:φ)  # plausible
+            stuck(L_f)
+        stuck(L_f)
+    𝕄(Φ.g):
+      𝔼(L_g):
+        𝑛·3·1 := 𝜎1:λ  # 𝑛
+        𝑛·3·2 := 𝜎1:λ  # 𝕄(𝑛·3·1)
+    𝕄(Φ.f):
+      𝔼(L_f):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·4·1 := Φ.k( x ↦ Φ.f )
+          𝛿·4·1 := 01-
+        𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.arg)
+        𝑛·4·2 := 𝜎2:λ  # 𝑛
+        𝑛·4·3 := 𝜎2:λ  # 𝕄(𝑛·4·2)
+    𝕄(Φ.big):
+      𝕄(Φ.big.φ):
+        𝑛·0·1 := Φ.k( x ↦ Φ.f )
diff --git a/test-resources/evaluate-packs/fires-nothing-twice-in-a-binding-a-dispatch-reads.yaml b/test-resources/evaluate-packs/fires-nothing-twice-in-a-binding-a-dispatch-reads.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/fires-nothing-twice-in-a-binding-a-dispatch-reads.yaml
@@ -0,0 +1,65 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# Under '--deep' the head pass of 𝕄 fires the reads of siblings in the
+# binding it dispatches into before it reads that binding (#1727), and only
+# them. Here every sibling 'φ' reads is 'a', which is no λ, so nothing fires
+# before the head pass reads 'φ', and each of the two 'L_plus' fires once.
+# Walking the whole binding instead would settle 'a.twice.plus' on the way,
+# which fires the inner 'L_plus', and keep nothing of it, since what it
+# settles to is a 'plus' still waiting for 'x' and no answer of a λ. The head
+# pass would then fire the inner 'L_plus' again, three firings in all.
+symbolic: |
+  - λ: L_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.e'
+deep: true
+partial: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦ plus(ρ, x) ↦ L_plus:λ, twice(ρ) ↦ ⟦ φ ↦ ξ.ρ.plus( x ↦ ξ.ρ ) ⟧ ⟧,
+    pair(a) ↦ ⟦ φ ↦ a.twice.plus( x ↦ a ) ⟧,
+    e ↦ Φ.pair( Φ.bytes( φ ↦ 𝜎1:λ ) ).φ ⟧
+result: '⟦ φ ↦ 𝜎3:λ, plus(x) ↦ L_plus:λ, twice ↦ ρ.plus( x ↦ ρ ):φ ⟧'
+protocol: |
+  𝕄(Φ.e):
+    𝑛·0·1 := Φ.pair( a ↦ Φ.bytes( φ ↦ 𝜎1:λ ) )
+    𝑛·0·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝑛·0·3 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝑛·0·5 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝑛·0·6 := 𝑛·0·5.plus( x ↦ 𝑛·0·5 )
+    𝔼(L_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝛿2·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+      𝑛·1·3 := Φ.bytes( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·1·3):
+        𝑛·1·4 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)
+    𝑛·0·7 := 𝑛·1·4.plus( x ↦ 𝑛·1·2 )
+    𝕄(Φ.e.x):
+      𝑛·0·8 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·9 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·10 := Φ.bytes( φ ↦ 𝜎1:λ )
+    𝔼(L_plus):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵3):
+        𝕄(Φ.a🌵3):
+          𝑛·2·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝛿2·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+      𝑛·2·3 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·2·3):
+        𝑛·2·4 := Φ.bytes( φ ↦ 𝜎3:λ )
+      𝑛·2·5 := 𝑛·2·4  # 𝕄(𝑛·2·3)
diff --git a/test-resources/evaluate-packs/joins-a-bare-symbol-with-a-branch-holding-one-symbol-when-lenient.yaml b/test-resources/evaluate-packs/joins-a-bare-symbol-with-a-branch-holding-one-symbol-when-lenient.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-bare-symbol-with-a-branch-holding-one-symbol-when-lenient.yaml
@@ -0,0 +1,48 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The fork of 'bool.and' from #1817: one branch is a bare symbol, the other a
+# call of 'Φ.bool', whose φ is code and not a formation, so its φ chain ends in
+# no symbol. The call holds one symbol, '𝜎2', so a lenient join answers a copy
+# of the call with '𝜎2' replaced by a fresh '𝜎3', which stands for either
+# '𝜎1' or '𝜎2'. The join reads nothing outside the two branches.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    lenient: true
+    𝑛: 𝑛3
+location: 'Q.y'
+deep: true
+input: |
+  ⟦
+    bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧,
+    y ↦ ⟦
+      r ↦ ⟦
+        a ↦ ⟦ λ ⤍ 𝜎1 ⟧,
+        b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧,
+        λ ⤍ L_fork
+      ⟧
+    ⟧
+  ⟧
+result: 'Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ:r'
+protocol: |
+  𝕄(Φ.y):
+    𝕄(Φ.y.r):
+      𝕄(Φ.y.r.b):
+        𝕄(Φ.y.r.b.φ):
+          𝑛·0·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+      𝔼(L_fork):
+        𝑛1·1 := 𝜎1:λ  # 𝕄(ξ.a)
+        𝕄(Φ.a🌵1):
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+        𝑛2·1 := 𝑛·1·1:φ  # 𝕄(ξ.b)
+        𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+        𝑛3·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # [𝑛1, 𝑛2]
+        𝑛·1·2 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛
+        𝑛·1·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝕄(𝑛·1·2)
diff --git a/test-resources/evaluate-packs/joins-a-branch-reached-from-a-literal.yaml b/test-resources/evaluate-packs/joins-a-branch-reached-from-a-literal.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-branch-reached-from-a-literal.yaml
@@ -0,0 +1,38 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A datum is never joined with anything, which is why a branch carrying one
+# goes through 'symbolize' first: a value somebody worked out and a value
+# nobody did are a difference in kind and not in value, and after that stage
+# both branches carry ⟦ λ ⤍ 𝜎 ⟧ where they differ. The symbol the literal
+# stood into is a symbol like any other here, so what the fork answers is the
+# join of the two and what is known about the literal stays on the line the
+# 'symbolize' stage wrote it on. Both stages walk the φ chain and nothing
+# else, that being where the value of a branch is reached (#1293).
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+result: '𝜎9:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 01-:Δ:φ  # 𝕄(ξ.a)
+      𝑛2·1 := 𝜎7:λ:φ  # 𝕄(ξ.b)
+      𝔻(𝜎8:λ) == 01-
+      𝑛3·1 := 𝜎8:λ:φ  # 𝑛1
+      𝑛4·1 := 𝑛2·1  # 𝑛2
+      𝔻(𝜎9:λ) ∈ { 𝔻(𝜎8:λ), 𝔻(𝜎7:λ) }
+      𝑛5·1 := 𝜎9:λ:φ  # [𝑛3, 𝑛4]
+      𝑛·1·1 := 𝑛5·1  # 𝑛
+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/joins-a-branch-with-a-decorator-of-it-when-lenient.yaml b/test-resources/evaluate-packs/joins-a-branch-with-a-decorator-of-it-when-lenient.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-branch-with-a-decorator-of-it-when-lenient.yaml
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A fork whose one branch calls a method often meets a decorator: one branch
+# comes to a copy, the other to a formation whose φ is that copy. The two
+# differ in shape, so a strict join refuses them. An entry that says
+# 'lenient: true' joins them into the decorator, with a fresh symbol minted
+# inside it the usual way (#1815).
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    lenient: true
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+result: '⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.b)
+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+      𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛1, 𝑛2]
+      𝑛·1·1 := 𝑛3·1  # 𝑛
+      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/joins-a-decorator-with-the-branch-it-decorates-standing-second-when-lenient.yaml b/test-resources/evaluate-packs/joins-a-decorator-with-the-branch-it-decorates-standing-second-when-lenient.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-decorator-with-the-branch-it-decorates-standing-second-when-lenient.yaml
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The lenient join of #1815 does not care which branch is the decorator: with
+# the decorator on the left and the copy it decorates on the right, it answers
+# the same decorator. A copy is a decorator of its own symbol too, so the join
+# takes the branch with the longer φ chain for the decorator.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    lenient: true
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+result: '⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ ⟦⟧ ⟧:φ  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)
+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+      𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛1, 𝑛2]
+      𝑛·1·1 := 𝑛3·1  # 𝑛
+      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-raises.yaml b/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-raises.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-raises.yaml
@@ -0,0 +1,121 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A fork one branch of which is a literal ⊥ is how a program spells "raise
+# unless the condition holds", and the other branch is a perfectly good value.
+# The deep walk fires it, since that ⊥ is an argument the program wrote and no
+# ξ.ρ the walk reduced to nothing, and the join says on which side of the
+# condition the program raises and answers the other side (#1405).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ ⊥ ).plus( 6 ) ⟧
+result: |-
+  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ) )
+    𝑛·0·4 := 𝑛·0·3( else ↦ ⊥ )
+    𝔼(L_fork):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝕄(Φ.a🌵3):
+        𝑛·2·2 := 1
+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )
+        𝕄(Φ.a🌵3.x):
+          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵4):
+            𝕄(Φ.a🌵4):
+              𝑛·3·1 := 1
+              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵5):
+            𝕄(Φ.a🌵5):
+              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )
+              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0
+          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+          𝕄(𝑛·3·5):
+            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )
+          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)
+      𝑛2·2 := ⊥  # 𝕄(ξ.else)
+      𝑛3·2 := 𝑛1·2  # 𝑛1
+      𝑛4·2 := ⊥  # 𝑛2
+      terminate(𝔻(𝜎1:λ), right)  # 𝑛4
+      𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]
+      𝑛·2·6 := 𝑛5·2  # 𝑛
+      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)
+    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵7):
+        𝕄(Φ.a🌵7):
+          𝑛·4·1 := Φ.number( φ ↦ 𝜎2:λ )
+      𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵8):
+        𝕄(Φ.a🌵8):
+          𝑛·4·2 := Φ.number( φ ↦ 𝑛·0·6 )
+          𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)
+      𝑛·4·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·4·4):
+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )
+      𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)
diff --git a/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-throws.yaml b/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-throws.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-fork-one-branch-of-which-throws.yaml
@@ -0,0 +1,126 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# EO raises with a copy of an object whose λ function the file leaves out on
+# purpose, here 'L_throw', rather than with ⊥. The entry of the fork names it
+# among its terminators, so the join takes the branch that comes to such a
+# copy for one that raises, the way it takes ⊥, and answers the other branch.
+# The morph of that branch stops at the copy and never walks into its message,
+# which here would fire 'L_number_plus' for nothing (#1865).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    terminators: [L_throw]
+    𝑛: 𝑛5
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    error(message) ↦ ⟦ λ ⤍ L_throw ⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( then ↦ 1.plus( 2 ), else ↦ Φ.error( message ↦ 7.plus( 8 ) ) ).plus( 6 ) ⟧
+result: |-
+  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ) )
+    𝑛·0·4 := 𝑛·0·3( else ↦ Φ.error( message ↦ 7.plus( 8 ) ) )
+    𝔼(L_fork):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝕄(Φ.a🌵3):
+        𝑛·2·2 := 1
+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )
+        𝕄(Φ.a🌵3.x):
+          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵4):
+            𝕄(Φ.a🌵4):
+              𝑛·3·1 := 1
+              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵5):
+            𝕄(Φ.a🌵5):
+              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )
+              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0
+          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+          𝕄(𝑛·3·5):
+            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )
+          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)
+      𝕄(Φ.a🌵6):
+        𝑛·2·6 := Φ.error( message ↦ 7.plus( 8 ) )
+      𝑛2·2 := 𝑛·2·6  # 𝕄(ξ.else)
+      𝑛3·2 := 𝑛1·2  # 𝑛1
+      𝑛4·2 := 𝑛·2·6  # 𝑛2
+      terminate(𝔻(𝜎1:λ), right)  # 𝑛4
+      𝑛5·2 := 𝑛3·2  # [𝑛3, 𝑛4]
+      𝑛·2·7 := 𝑛5·2  # 𝑛
+      𝑛·2·8 := 𝑛5·2  # 𝕄(𝑛·2·7)
+    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵7):
+        𝕄(Φ.a🌵7):
+          𝑛·4·1 := Φ.number( φ ↦ 𝜎2:λ )
+      𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵8):
+        𝕄(Φ.a🌵8):
+          𝑛·4·2 := Φ.number( φ ↦ 𝑛·0·6 )
+          𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)
+      𝑛·4·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·4·4):
+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )
+      𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)
diff --git a/test-resources/evaluate-packs/joins-a-fork-that-raises-on-the-left.yaml b/test-resources/evaluate-packs/joins-a-fork-that-raises-on-the-left.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-fork-that-raises-on-the-left.yaml
@@ -0,0 +1,120 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The same fork with its branches given by position and the ⊥ on the other
+# side: a positional ⊥ is as much an argument the program wrote as a named one,
+# so the walk fires the fork all the same, and the join says the program raises
+# on the left and answers the right (#1405).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( ⊥, 1.plus( 2 ) ).plus( 6 ) ⟧
+result: |-
+  ⟦ φ ↦ 𝜎3:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ ⊥ )
+    𝑛·0·4 := 𝑛·0·3( else ↦ 1.plus( 2 ) )
+    𝔼(L_fork):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝑛1·2 := ⊥  # 𝕄(ξ.then)
+      𝕄(Φ.a🌵4):
+        𝑛·2·2 := 1
+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )
+        𝕄(Φ.a🌵4.x):
+          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵5):
+            𝕄(Φ.a🌵5):
+              𝑛·3·1 := 1
+              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵6):
+            𝕄(Φ.a🌵6):
+              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )
+              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0
+          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+          𝕄(𝑛·3·5):
+            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )
+          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+      𝑛2·2 := 𝑛·3·7  # 𝕄(ξ.else)
+      𝑛3·2 := ⊥  # 𝑛1
+      𝑛4·2 := 𝑛2·2  # 𝑛2
+      terminate(𝔻(𝜎1:λ), left)  # 𝑛3
+      𝑛5·2 := 𝑛4·2  # [𝑛3, 𝑛4]
+      𝑛·2·6 := 𝑛5·2  # 𝑛
+      𝑛·2·7 := 𝑛5·2  # 𝕄(𝑛·2·6)
+    𝑛·0·5 := 𝑛·3·6.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵7):
+        𝕄(Φ.a🌵7):
+          𝑛·4·1 := Φ.number( φ ↦ 𝜎2:λ )
+      𝛿1·4 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵8):
+        𝕄(Φ.a🌵8):
+          𝑛·4·2 := Φ.number( φ ↦ 𝑛·0·6 )
+          𝑛·4·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·4·1 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·4 := 𝛿·4·1  # 𝔻(ξ.x)
+      𝑛·4·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·4·4):
+        𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )
+      𝑛·4·6 := 𝑛·4·5  # 𝕄(𝑛·4·4)
diff --git a/test-resources/evaluate-packs/joins-a-fork-whose-branches-a-rewrite-brought-to-one-shape.yaml b/test-resources/evaluate-packs/joins-a-fork-whose-branches-a-rewrite-brought-to-one-shape.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-fork-whose-branches-a-rewrite-brought-to-one-shape.yaml
@@ -0,0 +1,42 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The very fork that does not join, with a 'rewrite' block in front of the
+# join: the second branch carries an 'x' the first does not, and a rule of the
+# entry drops it, so the two have one shape once they are compared and the
+# fork answers their join. Each 'rewrite' line binds a meta of its own, which
+# the protocol writes like a 'symbolize' one, commented with the meta it was
+# rewritten from; a branch no rule matches is bound as it was (#1409).
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    rewrite:
+      𝑛3:
+        of: 𝑛1
+        rules: &shape
+          - name: no-x
+            pattern: ⟦ !B1, x ↦ ⟦⟧, !B2 ⟧
+            result: ⟦ !B1, !B2 ⟧
+      𝑛4:
+        of: 𝑛2
+        rules: *shape
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+result: '𝜎3:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ φ ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)
+      𝑛3·1 := 𝑛1·1  # 𝑛1
+      𝑛4·1 := 𝜎2:λ:φ  # 𝑛2
+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+      𝑛5·1 := 𝜎3:λ:φ  # [𝑛3, 𝑛4]
+      𝑛·1·1 := 𝑛5·1  # 𝑛
+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/joins-a-negated-bool-whose-literal-a-rewrite-brought-to-one-shape.yaml b/test-resources/evaluate-packs/joins-a-negated-bool-whose-literal-a-rewrite-brought-to-one-shape.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-a-negated-bool-whose-literal-a-rewrite-brought-to-one-shape.yaml
@@ -0,0 +1,163 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The program '!(x > 0)' of eo-runtime: 'not' is 'if. false true', so one
+# branch of the fork is the literal 'false' and the other the literal 'true',
+# while what the fork answers is a bool carrying a symbol. The two spellings
+# are one value, and the 'rewrite' block of the fork says so, turning either
+# literal into the shape an answer has with the datum it stands for, which
+# 'symbolize' then stands into a known symbol, so the join pairs them and the
+# root dataizes the fork of 'not' over the fork of '>' (#1409).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ ) ⟧
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+  - λ: L_fork
+    dataize:
+      𝛿1: $.φ
+    morph:
+      𝑛1: $.left
+      𝑛2: $.right
+    rewrite:
+      𝑛3:
+        of: 𝑛1
+        rules: &bool
+          - name: false-literal
+            pattern: ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.right ⟧ ), !B2 ⟧
+            result: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ) ⟧
+          - name: true-literal
+            pattern: ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.left ⟧ ), !B2 ⟧
+            result: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ) ⟧
+          - name: bool-without-rho
+            pattern: ⟦ !B1, φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝑒1 ⟧ ), !B2 ⟧
+            result: ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝑒1 ⟧ ) ⟧
+      𝑛4:
+        of: 𝑛2
+        rules: *bool
+    symbolize:
+      𝑛5: 𝑛3
+      𝑛6: 𝑛4
+    join:
+      𝑛7: [𝑛5, 𝑛6]
+    𝑛: 𝑛7
+location: 'Q.l🌵'
+deep: true
+partial: true
+acyclic: proven
+steps: 32
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅, gt ↦ ⟦ b ↦ ∅, λ ⤍ L_number_gt, ρ ↦ ∅ ⟧ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅ ⟧,
+    bool ↦ ⟦ if ↦ ∅, φ ↦ ξ.if( left ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ FF- ⟧ ), right ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00- ⟧ ) ), not ↦ ξ.if( left ↦ Φ.false, right ↦ Φ.true ) ⟧,
+    true ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.left ⟧ ) ⟧,
+    false ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ ρ ↦ ∅, left ↦ ∅, right ↦ ∅, φ ↦ ξ.right ⟧ ) ⟧,
+    guard ↦ ⟦ x ↦ ∅, φ ↦ ξ.x.gt( b ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00-00-00-00-00-00-00-00 ⟧ ) ) ).not ⟧,
+    l🌵 ↦ ⟦ mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧, root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.guard( x ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) ) ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.guard( x ↦ 𝑛·0·5 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.guard( x ↦ 𝑛·0·5 )
+          𝑛·1·4 := Φ.number( φ ↦ 𝑛·0·4 )
+          𝑛·1·5 := Φ.number( φ ↦ 𝑛·0·4 )
+          𝑛·1·6 := 𝑛·1·5.gt( b ↦ 0 )
+          𝔼(L_number_gt):
+            𝔻(Φ.a🌵2):
+              𝕄(Φ.a🌵2):
+                𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·4 )
+                𝑛·2·2 := Φ.bytes( φ ↦ 𝜎1:λ )
+            𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+            𝔻(Φ.a🌵3):
+              𝕄(Φ.a🌵3):
+                𝑛·2·3 := 0
+                𝑛·2·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+              𝛿·2·1 := 00-00-00-00-00-00-00-00  # 0.0
+            𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.b)
+            𝑛·2·5 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝑛
+            𝑛·2·6 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ ):φ  # 𝕄(𝑛·2·5)
+          𝑛·1·7 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎2:λ ⟧ )
+          𝑛·1·8 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎2:λ ⟧( left ↦ Φ.false )
+          𝑛·1·9 := 𝑛·1·8( right ↦ Φ.true )
+          𝔼(L_fork):
+            𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.φ)
+            𝕄(Φ.a🌵5):
+              𝕄(Φ.a🌵5.φ):
+                𝑛·3·1 := Φ.bool( if(left, right) ↦ right:φ )
+            𝑛1·3 := 𝑛·3·1:φ  # 𝕄(ξ.left)
+            𝕄(Φ.a🌵6):
+              𝕄(Φ.a🌵6.φ):
+                𝑛·3·2 := Φ.bool( if(left, right) ↦ left:φ )
+            𝑛2·3 := 𝑛·3·2:φ  # 𝕄(ξ.right)
+            𝑛3·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 00-:Δ ⟧ ):φ  # 𝑛1
+            𝑛4·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ FF-:Δ ⟧ ):φ  # 𝑛2
+            𝔻(𝜎3:λ) == 00-
+            𝑛5·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ  # 𝑛3
+            𝔻(𝜎4:λ) == FF-
+            𝑛6·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎4:λ ⟧ ):φ  # 𝑛4
+            𝔻(𝜎5:λ) ∈ { 𝔻(𝜎3:λ), 𝔻(𝜎4:λ) }
+            𝑛7·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # [𝑛5, 𝑛6]
+            𝑛·3·3 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝑛
+            𝑛·3·4 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝕄(𝑛·3·3)
+        𝑛1·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ  # 𝕄(ξ.v.φ)
+        𝑛·1·10 := Φ.l🌵.root( Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )  # 𝑛
+        𝕄(𝑛·1·10):
+          𝑛·1·11 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ ):φ )
+        𝑛·1·12 := 𝑛·1·11  # 𝕄(𝑛·1·10)
+      𝔼(L_root):
+        𝔻(Φ.a🌵7):
+          𝕄(Φ.a🌵7):
+            𝑛·4·1 := Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎5:λ ⟧ )
+          𝑛·4·2 := ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ 𝜎5:λ ⟧( left ↦ Φ.bytes( φ ↦ FF-:Δ ) )
+          𝑛·4·3 := 𝑛·4·2( right ↦ Φ.bytes( φ ↦ 00-:Δ ) )
+          𝔼(L_fork):
+            𝛿1·5 := 𝔻(𝜎5:λ)  # 𝔻(ξ.φ)
+            𝕄(Φ.a🌵9):
+              𝑛·5·1 := Φ.bytes( φ ↦ FF-:Δ )
+            𝑛1·5 := 𝑛·5·1  # 𝕄(ξ.left)
+            𝕄(Φ.a🌵10):
+              𝑛·5·2 := Φ.bytes( φ ↦ 00-:Δ )
+            𝑛2·5 := 𝑛·5·2  # 𝕄(ξ.right)
+            𝑛3·5 := 𝑛·5·1  # 𝑛1
+            𝑛4·5 := 𝑛·5·2  # 𝑛2
+            𝔻(𝜎6:λ) == FF-
+            𝑛5·5 := 𝜎6:λ:φ  # 𝑛3
+            𝔻(𝜎7:λ) == 00-
+            𝑛6·5 := 𝜎7:λ:φ  # 𝑛4
+            𝔻(𝜎8:λ) ∈ { 𝔻(𝜎6:λ), 𝔻(𝜎7:λ) }
+            𝑛7·5 := 𝜎8:λ:φ  # [𝑛5, 𝑛6]
+            𝑛·5·3 := 𝑛7·5  # 𝑛
+            𝑛·5·4 := 𝑛7·5  # 𝕄(𝑛·5·3)
+        𝛿1·4 := 𝔻(𝜎8:λ)  # 𝔻(ξ.v)
+        𝑛·4·4 := ⟦⟧  # 𝑛
+        𝑛·4·5 := ⟦⟧  # 𝕄(𝑛·4·4)
diff --git a/test-resources/evaluate-packs/joins-the-branches-a-program-forks-into.yaml b/test-resources/evaluate-packs/joins-the-branches-a-program-forks-into.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-the-branches-a-program-forks-into.yaml
@@ -0,0 +1,146 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The same fork as the pack beside this one, answering the join of its two
+# branches instead of the first of them, with one more firing standing after
+# it: the branch point stays in the program, since the 'plus( 6 )' below the
+# fork reads the symbol the two branches were joined into and not the value of
+# either. The line saying what that symbol stands for is what ties it to the
+# two, and the firing of a branch stands one level deeper, under the fork that
+# asked for it.
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ).plus( 6 ) ⟧
+result: |-
+  ⟦ φ ↦ 𝜎5:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ) )
+    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )
+    𝔼(L_fork):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝕄(Φ.a🌵3):
+        𝑛·2·2 := 1
+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )
+        𝕄(Φ.a🌵3.x):
+          𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+          𝑛·2·5 := Φ.number( φ ↦ 𝑛·2·4 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵4):
+            𝕄(Φ.a🌵4):
+              𝑛·3·1 := 1
+              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵5):
+            𝕄(Φ.a🌵5):
+              𝑛·3·3 := Φ.number( φ ↦ 𝑛·2·4 )
+              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0
+          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+          𝕄(𝑛·3·5):
+            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )
+          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+      𝑛1·2 := 𝑛·3·7  # 𝕄(ξ.then)
+      𝕄(Φ.a🌵6):
+        𝑛·2·6 := 3
+        𝑛·2·7 := 𝑛·2·6.plus( x ↦ 4 )
+        𝕄(Φ.a🌵6.x):
+          𝑛·2·8 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )
+          𝑛·2·9 := Φ.number( φ ↦ 𝑛·2·8 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵7):
+            𝕄(Φ.a🌵7):
+              𝑛·4·1 := 3
+              𝑛·4·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )
+            𝛿·4·1 := 40-08-00-00-00-00-00-00  # 3.0
+          𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵8):
+            𝕄(Φ.a🌵8):
+              𝑛·4·3 := Φ.number( φ ↦ 𝑛·2·8 )
+              𝑛·4·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )
+            𝛿·4·2 := 40-10-00-00-00-00-00-00  # 4.0
+          𝛿2·4 := 𝛿·4·2  # 𝔻(ξ.x)
+          𝑛·4·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+          𝕄(𝑛·4·5):
+            𝑛·4·6 := Φ.number( φ ↦ 𝜎3:λ )
+          𝑛·4·7 := 𝑛·4·6  # 𝕄(𝑛·4·5)
+      𝑛2·2 := 𝑛·4·7  # 𝕄(ξ.else)
+      𝑛3·2 := 𝑛1·2  # 𝑛1
+      𝑛4·2 := 𝑛2·2  # 𝑛2
+      𝔻(𝜎4:λ) ∈ { 𝔻(𝜎2:λ), 𝔻(𝜎3:λ) }
+      𝑛5·2 := ⟦ φ ↦ 𝜎4:λ, gt(x) ↦ L_number_gt:λ, plus(x) ↦ L_number_plus:λ ⟧  # [𝑛3, 𝑛4]
+      𝑛·2·10 := 𝑛5·2  # 𝑛
+      𝑛·2·11 := 𝑛5·2  # 𝕄(𝑛·2·10)
+    𝑛·0·5 := Φ.number( φ ↦ 𝜎4:λ ).plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·6 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵9):
+        𝕄(Φ.a🌵9):
+          𝑛·5·1 := Φ.number( φ ↦ 𝜎4:λ )
+      𝛿1·5 := 𝔻(𝜎4:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵10):
+        𝕄(Φ.a🌵10):
+          𝑛·5·2 := Φ.number( φ ↦ 𝑛·0·6 )
+          𝑛·5·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·5·1 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)
+      𝑛·5·4 := Φ.number( φ ↦ 𝜎5:λ )  # 𝑛
+      𝕄(𝑛·5·4):
+        𝑛·5·5 := Φ.number( φ ↦ 𝜎5:λ )
+      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)
diff --git a/test-resources/evaluate-packs/joins-the-two-branches-of-a-fork.yaml b/test-resources/evaluate-packs/joins-the-two-branches-of-a-fork.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-the-two-branches-of-a-fork.yaml
@@ -0,0 +1,32 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A fork answers neither of its branches: nobody has picked between the two, so
+# handing one of them through would say that the branch point is not there at
+# all. What it answers with is the join of the two — the shape both of them
+# have, with a fresh symbol wherever they carry two different unknowns — and
+# what that fresh symbol stands for stands on a line of its own, since a 𝜎 is
+# the name of a λ function and nothing is assigned to it. The branches differ
+# under φ, that being where the value of a branch is reached and so the only
+# place the join looks at all (#1293).
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+result: '𝜎3:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 𝜎1:λ:φ  # 𝕄(ξ.a)
+      𝑛2·1 := 𝜎2:λ:φ  # 𝕄(ξ.b)
+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+      𝑛3·1 := 𝜎3:λ:φ  # [𝑛1, 𝑛2]
+      𝑛·1·1 := 𝑛3·1  # 𝑛
+      𝑛·1·2 := 𝑛3·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/joins-two-branches-nothing-tells-apart.yaml b/test-resources/evaluate-packs/joins-two-branches-nothing-tells-apart.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/joins-two-branches-nothing-tells-apart.yaml
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# Two branches nothing tells apart are the answer themselves: there is nothing
+# to pick between them, so the join mints no symbol at all and the firing
+# answers the very term both of them are. A fork is a branch point in the
+# program and not necessarily one in the value it stands for.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'
+result: '𝜎1:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)
+      𝑛2·1 := 𝜎1:λ:l  # 𝕄(ξ.b)
+      𝑛3·1 := 𝜎1:λ:l  # [𝑛1, 𝑛2]
+      𝑛·1·1 := 𝜎1:λ:l  # 𝑛
+      𝑛·1·2 := 𝜎1:λ:l  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/keeps-a-cut-met-inside-a-firing-for-the-next-firing.yaml b/test-resources/evaluate-packs/keeps-a-cut-met-inside-a-firing-for-the-next-firing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/keeps-a-cut-met-inside-a-firing-for-the-next-firing.yaml
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps a cut the way it keeps an answer.
+# The firing under 'x' brings its operand down through 𝔻, which goes round
+# 'cyc' until the mode cuts it, so the firing never answers; 'y' spells the
+# very same formation, and before #1480 its firing walked all the way down to
+# the same cut again, since only an answer was kept. Now the second firing
+# takes the cut the first one met: it is written at its own site with the
+# formation the cut carried, and nothing under it is reduced.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+acyclic: plausible
+steps: 64
+input: >
+  ⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧,
+    x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧,
+    y ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝕄(Φ.x.arg):
+        𝑛·0·1 := Φ.cyc( x ↦ ⟦⟧ )
+      𝔼(L_outer):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )
+            𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )
+          looped(𝑛·1·2)  # plausible
+    𝕄(Φ.y):
+      𝔼(L_outer):
+        looped(𝑛·1·2)  # 𝕄(Φ.y), plausible
diff --git a/test-resources/evaluate-packs/keeps-a-stuck-firing-for-the-next-firing.yaml b/test-resources/evaluate-packs/keeps-a-stuck-firing-for-the-next-firing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/keeps-a-stuck-firing-for-the-next-firing.yaml
@@ -0,0 +1,33 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps a stuck firing the way it keeps a
+# cut. The firing under 'x' brings its operand down through 𝔻, which gets
+# stuck on 'L_none', so the firing never answers; 'y' spells the very same
+# formation, and before #1493 its firing reduced its operand and got stuck on
+# 'L_none' again, since a stuck firing kept nothing. Now the second firing
+# takes the failure the first one met: it is written at its own site and
+# nothing under it is reduced.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+partial: true
+acyclic: plausible
+steps: 64
+input: >
+  ⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧,
+    y ↦ ⟦ arg ↦ ⟦ λ ⤍ L_none ⟧, λ ⤍ L_outer ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝔼(L_outer):
+        𝔻(Φ.a🌵0):
+          unanswered(L_none)  # 𝔻(L_none:λ)
+        stuck(L_none)
+    𝕄(Φ.y):
+      𝔼(L_outer):
+        stall(L_none)
diff --git a/test-resources/evaluate-packs/keeps-the-answer-a-firing-already-gave.yaml b/test-resources/evaluate-packs/keeps-the-answer-a-firing-already-gave.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/keeps-the-answer-a-firing-already-gave.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An entry answering a λ function no other entry answers: the firing happens,
+# and the run then parks on the answer instead of failing on it. What fired
+# before the run got stuck stays in the protocol.
+symbolic: |
+  - λ: L_answers
+    𝑛: ⟦ λ ⤍ L_none ⟧
+location: 'Q.x'
+partial: true
+input: '⟦ x ↦ ⟦ λ ⤍ L_answers ⟧.foo ⟧'
+result: 'L_none:λ.foo'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_answers):
+      𝑛·1·1 := L_none:λ  # 𝑛
+      𝑛·1·2 := L_none:λ  # 𝕄(𝑛·1·1)
+    unanswered(L_none)  # 𝕄(L_none:λ)
diff --git a/test-resources/evaluate-packs/keeps-the-name-of-an-object-the-walk-computed-inside.yaml b/test-resources/evaluate-packs/keeps-the-name-of-an-object-the-walk-computed-inside.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/keeps-the-name-of-an-object-the-walk-computed-inside.yaml
@@ -0,0 +1,64 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The '--deep' walk stands its answers in the place of what it computed, inside
+# the bindings of the object it walks, and makes no new object by doing so
+# (#1757). The operand of 'L_entry' is the object 'Φ.dbl' made, named 𝑛·1·1 by
+# its 'applied' line, and the walk computes its 'φ' and its 'v' before 𝕄 hands
+# it back. It is still 𝑛·1·1 then: in the line binding the operand, in the term
+# the entry wrote, in the argument 'root' was applied to, and in the formation
+# 𝔻 gets into when 'L_root' brings that argument down.
+symbolic: |
+  - λ: L_entry
+    morph:
+      𝑛1: $.x
+    𝑛: Φ.root( v ↦ 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_twice
+    dataize:
+      𝛿1: $.ρ
+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.y'
+deep: true
+input: >
+  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧ ⟧,
+    dbl ↦ ⟦ v ↦ ∅, φ ↦ ξ.v.twice ⟧,
+    root(v) ↦ ⟦ λ ⤍ L_root ⟧,
+    y ↦ ⟦ λ ⤍ L_entry, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ) ⟧ ⟧
+result: '𝜎2:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝕄(Φ.y.x):
+      𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )
+      𝑛·0·2 := Φ.dbl( v ↦ 𝑛·0·1 )
+    𝔼(L_entry):
+      𝕄(Φ.a🌵0):
+        𝑛·1·1 := Φ.dbl( v ↦ 𝑛·0·1 )
+        𝕄(Φ.a🌵0.v):
+          𝑛·1·2 := Φ.num( φ ↦ 2A-:Δ )
+        𝕄(Φ.a🌵0.φ):
+          𝑛·1·3 := Φ.num( φ ↦ 2A-:Δ )
+          𝑛·1·4 := Φ.num( φ ↦ 2A-:Δ )
+          𝔼(L_twice):
+            𝔻(Φ.a🌵1):
+              𝕄(Φ.a🌵1):
+                𝑛·2·1 := Φ.num( φ ↦ 2A-:Δ )
+              𝛿·2·1 := 2A-
+            𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+            𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛
+            𝕄(𝑛·2·2):
+              𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )
+            𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
+      𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)
+      𝑛·1·5 := Φ.root( v ↦ 𝑛·1·1 )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.root( v ↦ 𝑛·1·1 )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝔼(L_root):
+      𝛿1·3 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)
+      𝑛·3·1 := 𝜎2:λ  # 𝑛
+      𝑛·3·2 := 𝜎2:λ  # 𝕄(𝑛·3·1)
diff --git a/test-resources/evaluate-packs/keeps-the-rho-bindings-a-pack-asks-for.yaml b/test-resources/evaluate-packs/keeps-the-rho-bindings-a-pack-asks-for.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/keeps-the-rho-bindings-a-pack-asks-for.yaml
@@ -0,0 +1,23 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Every pack spells its terms the way '--hide-rho' spells them, since the ρ
+# chain is the universe an entry was fired inside and not the answer it gave.
+# A pack saying 'hide-rho: false' asks for the other spelling, and then the
+# result and every term of the protocol carry the ρ the calculus put there —
+# here the parent of the operand 𝕄 was asked to reduce.
+symbolic: |
+  - λ: L_keep
+    morph:
+      𝑛1: $.x
+    𝑛: ⟦ z ↦ 𝑛1 ⟧
+location: 'Q.y'
+hide-rho: false
+input: '⟦ y ↦ ⟦ x ↦ ⟦ λ ⤍ 𝜎1, ρ ↦ ∅ ⟧, λ ⤍ L_keep ⟧.z ⟧'
+result: '⟦ λ ⤍ 𝜎1, ρ ↦ ⟦ x(ρ) ↦ 𝜎1:λ ⟧ ⟧'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_keep):
+      𝑛1·1 := ⟦ λ ⤍ 𝜎1, ρ ↦ ⟦ x(ρ) ↦ 𝜎1:λ ⟧ ⟧  # 𝕄(ξ.x)
+      𝑛·1·1 := ⟦ λ ⤍ 𝜎1, ρ ↦ ⟦ x(ρ) ↦ 𝜎1:λ ⟧ ⟧:z  # 𝑛
+      𝑛·1·2 := ⟦ λ ⤍ 𝜎1, ρ ↦ ⟦ x(ρ) ↦ 𝜎1:λ ⟧ ⟧:z  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/leaves-a-method-of-a-copy-without-rho-unwalked.yaml b/test-resources/evaluate-packs/leaves-a-method-of-a-copy-without-rho-unwalked.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/leaves-a-method-of-a-copy-without-rho-unwalked.yaml
@@ -0,0 +1,78 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The same method nobody dispatched, in a copy of an object that declares no
+# ρ, the way eo-runtime spells 'text': 'skip' drops the ρ the dispatch hands
+# the copy, so the copy has no ρ of its own, yet 'length' still declares one
+# and still waits for its receiver. The deep walk hands it back as it was
+# written instead of firing 'L_dataized' on a ⊥ it stands on (#1414).
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.l🌵'
+deep: true
+partial: true
+acyclic: proven
+steps: 32
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅, as-bytes ↦ ⟦ φ ↦ ξ.ρ, ρ ↦ ∅ ⟧ ⟧,
+    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,
+    text ↦ ⟦ φ ↦ ∅, length ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.φ ).as-bytes, ρ ↦ ∅ ⟧ ⟧,
+    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,
+    l🌵 ↦ ⟦
+      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,
+      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )
+    ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )
+          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)
+        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛
+        𝕄(𝑛·1·7):
+          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )
+        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)
+      𝔼(L_root):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)
+        𝑛·2·2 := ⟦⟧  # 𝑛
+        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/leaves-a-rho-void-formation-unwalked.yaml b/test-resources/evaluate-packs/leaves-a-rho-void-formation-unwalked.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/leaves-a-rho-void-formation-unwalked.yaml
@@ -0,0 +1,78 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A method nobody dispatched is a formation whose ρ is still void: 'length' of
+# the copy of 'text' the entry of 'L_entry' morphs waits for a dispatch to bind
+# its ρ. The deep walk hands it back as it was written instead of entering it,
+# since the entry of 'L_dataized' inside it would dataize 'ξ.ρ.φ', which stands
+# for ⊥, and that ⊥ would end the whole run even under '--partial' (#1397).
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.l🌵'
+deep: true
+partial: true
+acyclic: proven
+steps: 32
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅, as-bytes ↦ ⟦ φ ↦ ξ.ρ, ρ ↦ ∅ ⟧ ⟧,
+    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,
+    text ↦ ⟦ φ ↦ ∅, length ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.φ ).as-bytes, ρ ↦ ∅ ⟧, ρ ↦ ∅ ⟧,
+    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,
+    l🌵 ↦ ⟦
+      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,
+      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )
+    ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )
+          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)
+        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛
+        𝕄(𝑛·1·7):
+          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )
+        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)
+      𝔼(L_root):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)
+        𝑛·2·2 := ⟦⟧  # 𝑛
+        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/leaves-an-abstract-formation-unwalked.yaml b/test-resources/evaluate-packs/leaves-an-abstract-formation-unwalked.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/leaves-an-abstract-formation-unwalked.yaml
@@ -0,0 +1,93 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A method nobody applied is an abstract formation, one holding a void of its
+# own: 'at' of the copy of 'text' the entry of 'L_entry' morphs waits for 'i'.
+# The deep walk hands it back as it was written instead of entering it, since
+# its body is parametric: the entry of 'L_dataized' inside it would dataize
+# 'ξ.i', which stands for nothing, and the ⊥ it reaches would end the whole run
+# even under '--partial' (#1393).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.b
+    𝑛: Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧ )
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.l🌵'
+deep: true
+partial: true
+acyclic: proven
+steps: 32
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅, gt ↦ ⟦ ρ ↦ ∅, b ↦ ∅, λ ⤍ L_number_gt ⟧ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅ ⟧,
+    bool ↦ ⟦ if ↦ ∅ ⟧,
+    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,
+    text ↦ ⟦
+      φ ↦ ∅,
+      at ↦ ⟦
+        i ↦ ∅,
+        φ ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 00-00-00-00-00-00-00-00 ⟧ ) ).gt( b ↦ Φ.dataized( target ↦ ξ.i ) )
+      ⟧
+    ⟧,
+    holder ↦ ⟦ buf ↦ ∅, φ ↦ ξ.buf ⟧,
+    l🌵 ↦ ⟦
+      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,
+      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark(
+        n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) )
+      )( v ↦ Φ.holder( buf ↦ Φ.text( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ) ) )
+    ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.text( φ ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.holder( buf ↦ 𝑛·0·5 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6 )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.holder( buf ↦ 𝑛·0·5 )
+          𝑛·1·4 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝑛·1·5 := Φ.text( φ ↦ 𝑛·0·4 )
+          𝕄(Φ.a🌵1.φ):
+            𝑛·1·6 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝑛1·1 := 𝑛·1·5  # 𝕄(ξ.v.φ)
+        𝑛·1·7 := Φ.l🌵.root( 𝑛·1·5 )  # 𝑛
+        𝕄(𝑛·1·7):
+          𝑛·1·8 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·5 )
+        𝑛·1·9 := 𝑛·1·8  # 𝕄(𝑛·1·7)
+      𝔼(L_root):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)
+        𝑛·2·2 := ⟦⟧  # 𝑛
+        𝑛·2·3 := ⟦⟧  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/leaves-the-literals-of-a-method-out-of-a-join.yaml b/test-resources/evaluate-packs/leaves-the-literals-of-a-method-out-of-a-join.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/leaves-the-literals-of-a-method-out-of-a-join.yaml
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# Each branch of a fork reaches the join as the whole formation of its carrier,
+# methods included, and a carrier of any size declares methods full of
+# literals. Neither stage has any business with them: a term carries the value
+# it stands for where its φ chain ends, so the '-1' of a 'neg' nobody has
+# called is code and not data, and standing it into an unknown would mint a
+# symbol nobody ever reads — dozens of them per fork over a real object model,
+# each one a line of the protocol and a fact a reader has to know to skip
+# (#1293).
+#
+# Here both branches carry a literal under φ, which is the value of the branch
+# and is stood, and another inside 'neg', which is not and is left alone. The
+# two branches differ inside 'neg' as well, and that difference is no fork:
+# the join takes the method of the first branch verbatim, mints one symbol for
+# the one pair of values that differ, and the answer keeps its methods so the
+# program can go on dispatching on it.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ Δ ⤍ 01- ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ 0A- ⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ Δ ⤍ 02- ⟧, neg ↦ ⟦ φ ↦ ⟦ Δ ⤍ 0B- ⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := ⟦ φ ↦ 01-:Δ, neg ↦ 0A-:Δ:φ ⟧  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ φ ↦ 02-:Δ, neg ↦ 0B-:Δ:φ ⟧  # 𝕄(ξ.b)
+      𝔻(𝜎1:λ) == 01-
+      𝑛3·1 := ⟦ φ ↦ 𝜎1:λ, neg ↦ 0A-:Δ:φ ⟧  # 𝑛1
+      𝔻(𝜎2:λ) == 02-
+      𝑛4·1 := ⟦ φ ↦ 𝜎2:λ, neg ↦ 0B-:Δ:φ ⟧  # 𝑛2
+      𝔻(𝜎3:λ) ∈ { 𝔻(𝜎1:λ), 𝔻(𝜎2:λ) }
+      𝑛5·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ 0A-:Δ:φ ⟧  # [𝑛3, 𝑛4]
+      𝑛·1·1 := 𝑛5·1  # 𝑛
+      𝑛·1·2 := 𝑛5·1  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/mints-a-fresh-symbol-for-every-firing.yaml b/test-resources/evaluate-packs/mints-a-fresh-symbol-for-every-firing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/mints-a-fresh-symbol-for-every-firing.yaml
@@ -0,0 +1,60 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# Two firings down a chain of dispatches: each mints a symbol of its own, and
+# the operand of the second is the answer of the first, which the protocol
+# names rather than brings down again — every symbol answers the same datum.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.plus( 6 ).plus( 7 ) ⟧
+result: '⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 6
+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.plus( x ↦ 7 )
+    𝕄(Φ.φ.x):
+      𝑛·0·4 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.number( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵3):
+        𝕄(Φ.a🌵3):
+          𝑛·2·2 := Φ.number( φ ↦ 𝑛·0·4 )
+          𝑛·2·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+        𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0
+      𝛿2·2 := 𝛿·2·1  # 𝔻(ξ.x)
+      𝑛·2·4 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·2·4):
+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )
+      𝑛·2·6 := 𝑛·2·5  # 𝕄(𝑛·2·4)
diff --git a/test-resources/evaluate-packs/mints-past-the-symbols-a-stuck-firing-minted.yaml b/test-resources/evaluate-packs/mints-past-the-symbols-a-stuck-firing-minted.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/mints-past-the-symbols-a-stuck-firing-minted.yaml
@@ -0,0 +1,34 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The firing of 'L_inner' mints 𝜎1 for its answer and then gets stuck, since
+# nothing answers a dispatch off a symbol, and '--partial' parks its site. The
+# protocol keeps what that firing wrote, so 𝜎1 is spelled there already. Before
+# #1738 the parked frame handed back the state it started from, the count of
+# minted symbols went back with it, and 'L_outer' minted 𝜎1 once more, for
+# another value. Now the count never goes back, and 'L_outer' mints 𝜎2.
+symbolic: |
+  - λ: L_inner
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧.foo
+  - λ: L_outer
+    morph:
+      𝑛1: $.a
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+location: 'Q.y'
+partial: true
+input: '⟦ y ↦ ⟦ a ↦ ⟦ λ ⤍ L_inner ⟧.v, λ ⤍ L_outer ⟧.z ⟧'
+result: '𝜎2:λ.z'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_outer):
+      𝕄(Φ.a🌵0):
+        𝔼(L_inner):
+          𝑛·2·1 := 𝜎1:λ.foo  # 𝑛
+          𝕄(𝑛·2·1):
+            unanswered(𝜎1)  # 𝕄(𝜎1:λ)
+          stuck(𝜎1)
+      𝑛1·1 := L_inner:λ.v  # 𝕄(ξ.a)
+      𝑛·1·1 := 𝜎2:λ  # 𝑛
+      𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)
+    unanswered(𝜎2)  # 𝕄(𝜎2:λ)
diff --git a/test-resources/evaluate-packs/morphs-a-program-that-already-carries-a-symbol.yaml b/test-resources/evaluate-packs/morphs-a-program-that-already-carries-a-symbol.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/morphs-a-program-that-already-carries-a-symbol.yaml
@@ -0,0 +1,43 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The program handed in is the answer an earlier run gave: its receiver is no
+# number but the symbol 𝜎1 that run minted. The firing brings it down all the
+# same, since a symbol dataizes to a fixed 42, and the protocol names the
+# unknown that 42 stood for instead of writing the bytes twice.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ).plus( 7 ) ⟧
+result: '⟦ φ ↦ 𝜎2:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := Φ.number( φ ↦ 𝜎1:λ )
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 7 )
+    𝕄(Φ.φ.x):
+      𝑛·0·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.number( φ ↦ 𝜎1:λ )
+      𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·2 := Φ.number( φ ↦ 𝑛·0·3 )
+          𝑛·1·3 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-1C-00-00-00-00-00-00  # 7.0
+      𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)
+      𝑛·1·4 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·1·4):
+        𝑛·1·5 := Φ.number( φ ↦ 𝜎2:λ )
+      𝑛·1·6 := 𝑛·1·5  # 𝕄(𝑛·1·4)
diff --git a/test-resources/evaluate-packs/morphs-two-branches-spelling-one-term-through-one-firing.yaml b/test-resources/evaluate-packs/morphs-two-branches-spelling-one-term-through-one-firing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/morphs-two-branches-spelling-one-term-through-one-firing.yaml
@@ -0,0 +1,116 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' serves the operands of a firing as much
+# as the walk: the two branches of this fork spell one term, so the sum the
+# second one starts from is the formation the first one fired, and the
+# second branch reaches the normal form the first reached, symbol and all,
+# through a firing that reduces nothing and answers what the first one did.
+# The branches are met as 'morph' operands of the fork and not as bindings
+# the deep walk went over first, since the fork is fired by the dispatch off
+# it (#1476).
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    𝑛: 𝑛1
+location: 'Q.@'
+acyclic: plausible
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ).plus( 3 ), 1.plus( 2 ).plus( 3 ) ).plus( 9 ) ⟧
+result: '⟦ x ↦ 9, λ ⤍ L_number_plus ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ).plus( 3 ) )
+    𝑛·0·4 := 𝑛·0·3( else ↦ 1.plus( 2 ).plus( 3 ) )
+    𝔼(L_fork):
+      𝔻(Φ.a🌵2):
+        𝕄(Φ.a🌵2):
+          𝑛·2·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝕄(Φ.a🌵3):
+        𝑛·2·2 := 1
+        𝑛·2·3 := 𝑛·2·2.plus( x ↦ 2 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵4):
+            𝕄(Φ.a🌵4):
+              𝑛·3·1 := 1
+              𝑛·3·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+            𝛿·3·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+          𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵5):
+            𝕄(Φ.a🌵5):
+              𝑛·3·3 := 2
+              𝑛·3·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+            𝛿·3·2 := 40-00-00-00-00-00-00-00  # 2.0
+          𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+          𝑛·3·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+          𝕄(𝑛·3·5):
+            𝑛·3·6 := Φ.number( φ ↦ 𝜎2:λ )
+          𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+        𝑛·2·4 := 𝑛·3·6.plus( x ↦ 3 )
+      𝑛1·2 := 𝑛·2·4  # 𝕄(ξ.then)
+      𝕄(Φ.a🌵6):
+        𝑛·2·5 := Φ.number( φ ↦ 𝑛·3·2 )
+        𝑛·2·6 := 𝑛·2·5.plus( x ↦ 𝑛·3·3 )
+        𝔼(L_number_plus):
+          𝑛·4·1 := 𝑛·3·6  # 𝑛
+          𝑛·4·2 := 𝑛·3·7  # 𝕄(𝑛·4·1)
+        𝑛·2·7 := 𝑛·3·6.plus( x ↦ 3 )
+      𝑛2·2 := 𝑛·2·7  # 𝕄(ξ.else)
+      𝑛·2·8 := 𝑛·2·7  # 𝑛
+      𝑛·2·9 := 𝑛·2·7  # 𝕄(𝑛·2·8)
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵7):
+        𝕄(Φ.a🌵7):
+          𝑛·5·1 := Φ.number( φ ↦ 𝜎2:λ )
+      𝛿1·5 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵8):
+        𝕄(Φ.a🌵8):
+          𝑛·5·2 := 3
+          𝑛·5·3 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )
+        𝛿·5·1 := 40-08-00-00-00-00-00-00  # 3.0
+      𝛿2·5 := 𝛿·5·1  # 𝔻(ξ.x)
+      𝑛·5·4 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+      𝕄(𝑛·5·4):
+        𝑛·5·5 := Φ.number( φ ↦ 𝜎3:λ )
+      𝑛·5·6 := 𝑛·5·5  # 𝕄(𝑛·5·4)
+    𝑛·0·5 := 𝑛·5·5.plus( x ↦ 9 )
diff --git a/test-resources/evaluate-packs/nests-the-firing-an-operand-brought-down.yaml b/test-resources/evaluate-packs/nests-the-firing-an-operand-brought-down.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/nests-the-firing-an-operand-brought-down.yaml
@@ -0,0 +1,59 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# An operand is brought down by a whole run of 𝔻, so a λ function it fires on
+# the way sits one level deeper than the firing waiting for it, and the metas
+# of the two are told apart by which firing of the entry each belongs to. The
+# judgment each firing is commented with says the same thing from the other
+# side: the outer one was fired while 𝕄 reduced the program and the inner one
+# by the 𝔻 bringing the operand down, under the binding it was bound to (#1306).
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.plus( 6.plus( 7 ) ).plus( 8 ) ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6.plus( 7 ) )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 6
+          𝑛·1·4 := 𝑛·1·3.plus( x ↦ 7 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵2):
+            𝕄(Φ.a🌵2):
+              𝑛·2·1 := 6
+              𝑛·2·2 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+            𝛿·2·1 := 40-18-00-00-00-00-00-00  # 6.0
+          𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵3):
+            𝕄(Φ.a🌵3):
+              𝑛·2·3 := 7
+              𝑛·2·4 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+            𝛿·2·2 := 40-1C-00-00-00-00-00-00  # 7.0
+          𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)
+          𝑛·2·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+          𝕄(𝑛·2·5):
+            𝑛·2·6 := Φ.number( φ ↦ 𝜎1:λ )
+          𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)
+      𝛿2·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎2:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.plus( x ↦ 8 )
diff --git a/test-resources/evaluate-packs/parks-a-dispatch-off-a-symbol.yaml b/test-resources/evaluate-packs/parks-a-dispatch-off-a-symbol.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-dispatch-off-a-symbol.yaml
@@ -0,0 +1,70 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A λ binding naming a symbol rather than a name, which is what a value nobody
+# worked out looks like: ⟦ λ ⤍ 𝜎1 ⟧. Dispatching an attribute off it asks 𝕄 to
+# reduce it, 'ml' fires 𝔼 on the target, and before #1287 𝔼 refused the
+# formation outright — a 'userError' nothing catches, so the whole run died on
+# the first such term and everything standing after it never ran. 𝔼 now gets
+# stuck on the symbol the way it gets stuck on a λ name no entry of the
+# '--symbolic' file answers, so '--partial' parks the site: 'a' is left exactly
+# as it was written, a taint with no root, and the walk goes on to 'b', which
+# fires and computes. The site is written to the protocol as 'unanswered(𝜎1)',
+# spelled the way every term carrying that symbol spells it.
+#
+# The site is recorded twice because the walk passes it twice — once for the
+# dispatch 'x.plus' and once for the application 'x.plus( 5 )' around it — which
+# is what a λ name nothing answers does on this shape too; nothing about the
+# symbol makes it write more than a name would.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ) )
+location: 'Q.demo'
+deep: true
+partial: true
+input: >
+  ⟦ bytes ↦ ⟦ φ ↦ ∅ ⟧,
+    number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    demo ↦ ⟦
+      a ↦ ⟦ λ ⤍ 𝜎1 ⟧.plus( 5 ),
+      b ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ).plus( 5 ) ⟧ ⟧
+result: |-
+  ⟦
+    a ↦ 𝜎1:λ.plus( 5 ),
+    b ↦ ⟦ φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ), plus(x) ↦ L_number_plus:λ ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.demo):
+    𝕄(Φ.demo.a):
+      unanswered(𝜎1)  # 𝕄(𝜎1:λ)
+      𝑛·0·1 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      unanswered(𝜎1)  # 𝕄(𝜎1:λ)
+    𝕄(Φ.demo.b):
+      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )
+      𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·3 )
+      𝑛·0·6 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·6 )
+      𝑛·0·8 := Φ.number( φ ↦ 𝑛·0·3 )
+      𝑛·0·9 := 𝑛·0·8.plus( x ↦ 𝑛·0·7 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·3 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 𝜎2:λ )
+        𝛿1·1 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵1):
+          𝕄(Φ.a🌵1):
+            𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·6 )
+            𝑛·1·4 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+        𝛿2·1 := 𝛿·1·1  # 𝔻(ξ.x)
+        𝑛·1·5 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) )  # 𝑛
+        𝕄(𝑛·1·5):
+          𝑛·1·6 := Φ.number( φ ↦ Φ.bytes( φ ↦ 𝜎3:λ ) )
+        𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
diff --git a/test-resources/evaluate-packs/parks-a-firing-whose-operand-dataizes-in-circles.yaml b/test-resources/evaluate-packs/parks-a-firing-whose-operand-dataizes-in-circles.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-firing-whose-operand-dataizes-in-circles.yaml
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A loop met while an operand is being brought down to data, with no frame of 𝕄
+# ever handed the same term twice: the body of 'cyc' dispatches 'cyc' again, so
+# every round 𝕄 stops at a formation of its own and only 𝔻 — 'box'
+# contextualizing that body and 'norm' morphing what comes out — comes back to
+# the term it started from. Before #1290 the guard of '--acyclic' was 𝕄's alone
+# and this went round until the step budget ran out; now 𝔻 asks it too, and the
+# spine around the firing parks with the term it had reached when the firing
+# started, exactly as it does for a loop 𝕄 catches.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+location: 'Q.x'
+acyclic: proven
+steps: 250
+input: >
+  ⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧,
+    x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.bar ⟧
+result: '⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.bar'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_outer):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )
+          𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )
+        looped(𝑛·1·2)  # proven
diff --git a/test-resources/evaluate-packs/parks-a-firing-whose-operand-loops.yaml b/test-resources/evaluate-packs/parks-a-firing-whose-operand-loops.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-firing-whose-operand-loops.yaml
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A loop met while an operand is being brought down to data, one whole run of 𝔻
+# below the spine. That run reduces the operand under a synthetic attribute of
+# its own, so what it parks on is no term of the program; the spine around the
+# firing is what answers instead, with the term it had reached when the firing
+# started (see 'down' in 'Morph'). The entry never fires, and the protocol keeps
+# the firing it opened and the loop it met underneath.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_loop
+    𝑛: ⟦ λ ⤍ L_loop ⟧
+location: 'Q.x'
+acyclic: proven
+steps: 250
+input: >
+  ⟦ x ↦ ⟦ arg ↦ ⟦ λ ⤍ L_loop ⟧.foo, λ ⤍ L_outer ⟧.bar ⟧
+result: '⟦ arg ↦ L_loop:λ.foo, λ ⤍ L_outer ⟧.bar'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_outer):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝔼(L_loop):
+            𝑛·2·1 := L_loop:λ  # 𝑛
+            𝑛·2·2 := L_loop:λ  # 𝕄(𝑛·2·1)
+          looped(L_loop:λ)  # proven
diff --git a/test-resources/evaluate-packs/parks-a-firing-whose-operand-never-came-down.yaml b/test-resources/evaluate-packs/parks-a-firing-whose-operand-never-came-down.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-firing-whose-operand-never-came-down.yaml
@@ -0,0 +1,24 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The operand of a firing is itself stuck on a λ function no entry answers, so
+# under '_partial' the block of the firing opens and binds nothing: 𝛿1 never
+# came down, and the whole term is handed back the way it was reached.
+symbolic: |
+  - λ: L_pair
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+location: 'Q.y'
+partial: true
+input: >
+  ⟦ y ↦ ⟦ x ↦ ⟦ λ ⤍ L_none ⟧.foo,
+          λ ⤍ L_pair ⟧.z ⟧
+result: '⟦ x ↦ L_none:λ.foo, λ ⤍ L_pair ⟧.z'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_pair):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          unanswered(L_none)  # 𝕄(L_none:λ)
+      stuck(L_none)
diff --git a/test-resources/evaluate-packs/parks-a-firing-whose-operand-no-rule-dataizes.yaml b/test-resources/evaluate-packs/parks-a-firing-whose-operand-no-rule-dataizes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-firing-whose-operand-no-rule-dataizes.yaml
@@ -0,0 +1,21 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The 'dataize' operand of 'L_pair' is a formation whose φ is a void nothing
+# filled, so no dataization rule matches it. Under '_partial' that parks the
+# firing just as the terminator ⊥ does, and the protocol writes the dead end
+# as 'unanswered(⊥)' with the formation beside it (#1401).
+symbolic: |
+  - λ: L_pair
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+location: 'Q.y'
+partial: true
+input: '⟦ y ↦ ⟦ x ↦ ⟦ φ ↦ ∅ ⟧, λ ⤍ L_pair ⟧.z ⟧'
+result: '⟦ x(φ) ↦ ⟦⟧, λ ⤍ L_pair ⟧.z'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_pair):
+      unanswered(⊥)  # 𝔻(∅:φ)
+      stuck(⊥)
diff --git a/test-resources/evaluate-packs/parks-a-firing-whose-operand-reaches-the-terminator.yaml b/test-resources/evaluate-packs/parks-a-firing-whose-operand-reaches-the-terminator.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-firing-whose-operand-reaches-the-terminator.yaml
@@ -0,0 +1,96 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# 'block' is applied to 'buffer' only, so its void 'data' stays unfilled and
+# the 'dataize' operand 'ρ.data.size' of 'L_dataized' reaches the terminator ⊥.
+# Under '_partial' that parks the firing the way an operand parked on a λ
+# function no entry answers does, rather than ending the run: the protocol
+# writes 'unanswered(⊥)' with the term 𝔻 could not dataize, and the walk goes on
+# to 'L_root', whose own operand dies the same way (#1401).
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_entry
+    dataize:
+      𝛿1: $.n
+    morph:
+      𝑛1: $.v.φ
+    𝑛: Φ.l🌵.root( 𝑛1 )
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q.l🌵'
+deep: true
+acyclic: proven
+partial: true
+steps: 32
+input: >
+  ⟦
+    number ↦ ⟦ φ ↦ ∅ ⟧,
+    bytes ↦ ⟦ φ ↦ ∅, size ↦ ⟦ λ ⤍ L_bytes_size ⟧ ⟧,
+    dataized ↦ ⟦ target ↦ ∅, λ ⤍ L_dataized ⟧,
+    block ↦ ⟦ data ↦ ∅, buffer ↦ ∅, read ↦ ⟦ φ ↦ Φ.dataized( target ↦ ξ.ρ.data.size ).as-bytes ⟧ ⟧,
+    l🌵 ↦ ⟦
+      mark ↦ ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧,
+      root ↦ ⟦ v ↦ ∅, λ ⤍ L_root ⟧,
+      e1 ↦ Φ.l🌵.mark( n ↦ Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧ ) ) )( v ↦ Φ.block( buffer ↦ Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ).read )
+    ⟧
+  ⟧
+result: |-
+  ⟦
+    mark(n, v) ↦ L_entry:λ,
+    root(v) ↦ L_root:λ,
+    e1 ↦ ⟦ v ↦ Φ.dataized( target ↦ ⊥ ).as-bytes, λ ⤍ L_root ⟧
+  ⟧
+protocol: |
+  𝕄(Φ.l🌵):
+    𝕄(Φ.l🌵.e1):
+      𝑛·0·1 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+      𝑛·0·3 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·4 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·5 := Φ.block( buffer ↦ 𝑛·0·4 )
+      𝑛·0·6 := Φ.block( buffer ↦ 𝑛·0·4 )
+      𝑛·0·7 := ⟦ n ↦ ∅, v ↦ ∅, λ ⤍ L_entry ⟧( n ↦ 𝑛·0·2 )
+      𝑛·0·8 := 𝑛·0·7( v ↦ 𝑛·0·6.read )
+      𝔼(L_entry):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+            𝑛·1·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·1·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.n)
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.block( buffer ↦ 𝑛·0·4 )
+          𝑛·1·4 := Φ.dataized( target ↦ ⊥ )
+          𝔼(L_dataized):
+            unanswered(⊥)  # 𝔻(⊥)
+            stuck(⊥)
+          𝑛·1·5 := Φ.dataized( target ↦ ⊥ )
+          𝑛·1·6 := Φ.dataized( target ↦ ⊥ )
+          𝔼(L_dataized):
+            stuck(⊥)
+          𝑛·1·7 := Φ.dataized( target ↦ ⊥ )
+          𝑛·1·8 := Φ.dataized( target ↦ ⊥ )
+          𝔼(L_dataized):
+            stuck(⊥)
+          𝑛·1·9 := Φ.dataized( target ↦ ⊥ )
+          𝔼(L_dataized):
+            stuck(⊥)
+        𝑛1·1 := 𝑛·1·9.as-bytes  # 𝕄(ξ.v.φ)
+        𝑛·1·10 := Φ.l🌵.root( 𝑛·1·9.as-bytes )  # 𝑛
+        𝕄(𝑛·1·10):
+          𝑛·1·11 := ⟦ v ↦ ∅, λ ⤍ L_root ⟧( v ↦ 𝑛·1·9.as-bytes )
+        𝑛·1·12 := 𝑛·1·11  # 𝕄(𝑛·1·10)
+      𝔼(L_root):
+        𝔻(Φ.a🌵6):
+          𝕄(Φ.a🌵6):
+            𝑛·6·1 := Φ.dataized( target ↦ ⊥ )
+            𝔼(L_dataized):
+              unanswered(⊥)  # 𝔻(⊥)
+              stuck(⊥)
+        stuck(⊥)
diff --git a/test-resources/evaluate-packs/parks-a-fork-whose-branches-do-not-join.yaml b/test-resources/evaluate-packs/parks-a-fork-whose-branches-do-not-join.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-fork-whose-branches-do-not-join.yaml
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# Branches differing anywhere but in a symbol are not joined, and that is the
+# whole rule: no fallback and no second alternative. Bringing two branches to
+# one shape is the program's business rather than phino's, so a fork whose
+# branches differ in structure gets stuck the way a λ function no entry answers
+# does and '--partial' parks the site instead of failing the run.
+symbolic: |
+  - λ: L_fork
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    join:
+      𝑛3: [𝑛1, 𝑛2]
+    𝑛: 𝑛3
+location: 'Q.y'
+partial: true
+input: '⟦ y ↦ ⟦ a ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, b ↦ ⟦ l ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l ⟧'
+result: '⟦ a ↦ 𝜎1:λ:l, b ↦ ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.l'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_fork):
+      𝑛1·1 := 𝜎1:λ:l  # 𝕄(ξ.a)
+      𝑛2·1 := ⟦ l ↦ 𝜎2:λ, x ↦ ⟦⟧ ⟧  # 𝕄(ξ.b)
+      stuck(L_fork)
diff --git a/test-resources/evaluate-packs/parks-a-lambda-no-entry-answers.yaml b/test-resources/evaluate-packs/parks-a-lambda-no-entry-answers.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-a-lambda-no-entry-answers.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same λ function under '_partial': the run ends on the residue the spine
+# had reached instead of failing.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.x'
+partial: true
+input: '⟦ x ↦ ⟦ λ ⤍ L_none ⟧.foo ⟧'
+result: 'L_none:λ.foo'
+protocol: |
+  𝕄(Φ.x):
+    unanswered(L_none)  # 𝕄(L_none:λ)
diff --git a/test-resources/evaluate-packs/parks-an-entry-that-answers-with-a-firing-of-itself.yaml b/test-resources/evaluate-packs/parks-an-entry-that-answers-with-a-firing-of-itself.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-an-entry-that-answers-with-a-firing-of-itself.yaml
@@ -0,0 +1,21 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same entry answering with a firing of itself, under '_acyclic': morphing
+# comes back to the very term it is already reducing, so it stops there instead
+# of going round until the budget is gone. The budget is generous here, so what
+# ends the run is the cut and not the limit.
+symbolic: |
+  - λ: L_loop
+    𝑛: ⟦ λ ⤍ L_loop ⟧
+location: 'Q.x'
+acyclic: proven
+steps: 250
+input: '⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧'
+result: 'L_loop:λ.foo'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_loop):
+      𝑛·1·1 := L_loop:λ  # 𝑛
+      𝑛·1·2 := L_loop:λ  # 𝕄(𝑛·1·1)
+    looped(L_loop:λ)  # proven
diff --git a/test-resources/evaluate-packs/parks-the-same-dataization-loop-under-partial.yaml b/test-resources/evaluate-packs/parks-the-same-dataization-loop-under-partial.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/parks-the-same-dataization-loop-under-partial.yaml
@@ -0,0 +1,33 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The very program of 'parks-a-firing-whose-operand-dataizes-in-circles', run
+# with '--partial' as well. The two flags reach the same place by different
+# roads: on its own '--acyclic' lets the loop travel up to the 𝕄 spine around
+# the firing, while under '--partial' the operand's own run of 𝔻 ends on the
+# residue it had reached and the firing is left with nothing to bind, so it gets
+# stuck the way one whose λ function cannot fire does. Either way the site is
+# parked and the answer is the same, which is what "--acyclic needs no
+# --partial" means for a loop 𝔻 catches (#1290).
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+location: 'Q.x'
+acyclic: proven
+partial: true
+steps: 250
+input: >
+  ⟦ cyc ↦ ⟦ x ↦ ∅, φ ↦ Φ.cyc( ξ.x ) ⟧,
+    x ↦ ⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.bar ⟧
+result: '⟦ arg ↦ Φ.cyc( ⟦⟧ ), λ ⤍ L_outer ⟧.bar'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_outer):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.cyc( x ↦ ⟦⟧ )
+          𝑛·1·2 := Φ.cyc( x ↦ ⟦⟧ )
+        looped(𝑛·1·2)  # proven
+      stuck(L_outer)
diff --git a/test-resources/evaluate-packs/records-a-parked-site-once.yaml b/test-resources/evaluate-packs/records-a-parked-site-once.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/records-a-parked-site-once.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same λ function no entry answers, under '_partial' and the deep walk: the
+# spine parks the site and the walk goes over the residue it parked on, so 𝔼 is
+# attempted on that one site twice. The protocol records it once, since what a
+# 'unanswered(…)' line counts is the sites 𝔼 got stuck on and not the passes the
+# walk made over them (#1300).
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.x'
+deep: true
+partial: true
+input: '⟦ x ↦ ⟦ λ ⤍ L_none ⟧.foo ⟧'
+result: 'L_none:λ.foo'
+protocol: |
+  𝕄(Φ.x):
+    unanswered(L_none)  # 𝕄(L_none:λ)
diff --git a/test-resources/evaluate-packs/reduces-a-carried-symbol-through-morphing.yaml b/test-resources/evaluate-packs/reduces-a-carried-symbol-through-morphing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/reduces-a-carried-symbol-through-morphing.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A 'morph' operand of the firing is a symbol the program already carried:
+# 𝕄 hands it back the way it found it, since a symbol is already a normal form
+# and there is nothing under it to reduce. The answer then mints after it.
+symbolic: |
+  - λ: L_keep
+    morph:
+      𝑛1: $.x
+    𝑛: ⟦ z ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ x ↦ ⟦ λ ⤍ 𝜎4 ⟧, λ ⤍ L_keep ⟧.z ⟧'
+result: '𝜎5:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_keep):
+      𝑛1·1 := 𝜎4:λ  # 𝕄(ξ.x)
+      𝑛·1·1 := 𝜎5:λ:z  # 𝑛
+      𝑛·1·2 := 𝜎5:λ:z  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/reduces-the-branches-of-a-fork-through-morphing.yaml b/test-resources/evaluate-packs/reduces-the-branches-of-a-fork-through-morphing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/reduces-the-branches-of-a-fork-through-morphing.yaml
@@ -0,0 +1,118 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A branch is where a 'morph' operand earns its place: the condition comes
+# down through 𝔻 and answers a symbol nobody has decided, both branches reach
+# a normal form through 𝕄, and the answer is one of them. Every line of the
+# firing points back at the line that minted the symbol it carries, which is
+# how the dependencies between firings are read.
+symbolic: |
+  - λ: L_number_gt
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+  - λ: L_fork
+    dataize:
+      𝛿1: $.ρ
+    morph:
+      𝑛1: $.then
+      𝑛2: $.else
+    𝑛: 𝑛1
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    bool(φ) ↦ ⟦ if ↦ ⟦ ρ ↦ ∅, then ↦ ∅, else ↦ ∅, λ ⤍ L_fork ⟧ ⟧,
+    number(φ) ↦ ⟦
+      gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+      plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ 5.gt( 0 ).if( 1.plus( 2 ), 3.plus( 4 ) ) ⟧
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.gt( x ↦ 0 )
+    𝔼(L_number_gt):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := 0
+          𝑛·1·4 := Φ.bytes( φ ↦ 00-00-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 00-00-00-00-00-00-00-00  # 0.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.bool( φ ↦ 𝜎1:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝑛·0·3 := 𝑛·1·6.if( then ↦ 1.plus( 2 ) )
+    𝑛·0·4 := 𝑛·0·3( else ↦ 3.plus( 4 ) )
+    𝕄(Φ.φ.then):
+      𝑛·0·5 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+      𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·5 )
+      𝑛·0·7 := Φ.number( φ ↦ 𝑛·0·5 )
+      𝑛·0·8 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·8 )
+      𝑛·0·10 := Φ.number( φ ↦ 𝑛·0·5 )
+      𝑛·0·11 := 𝑛·0·10.plus( x ↦ 𝑛·0·9 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·5 )
+            𝑛·2·2 := Φ.bytes( φ ↦ 3F-F0-00-00-00-00-00-00:Δ )
+          𝛿·2·1 := 3F-F0-00-00-00-00-00-00  # 1.0
+        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵3):
+          𝕄(Φ.a🌵3):
+            𝑛·2·3 := Φ.number( φ ↦ 𝑛·0·8 )
+            𝑛·2·4 := Φ.bytes( φ ↦ 40-00-00-00-00-00-00-00:Δ )
+          𝛿·2·2 := 40-00-00-00-00-00-00-00  # 2.0
+        𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)
+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+        𝕄(𝑛·2·5):
+          𝑛·2·6 := Φ.number( φ ↦ 𝜎2:λ )
+        𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)
+    𝕄(Φ.φ.else):
+      𝑛·0·12 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )
+      𝑛·0·13 := Φ.number( φ ↦ 𝑛·0·12 )
+      𝑛·0·14 := Φ.number( φ ↦ 𝑛·0·12 )
+      𝑛·0·15 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )
+      𝑛·0·16 := Φ.number( φ ↦ 𝑛·0·15 )
+      𝑛·0·17 := Φ.number( φ ↦ 𝑛·0·12 )
+      𝑛·0·18 := 𝑛·0·17.plus( x ↦ 𝑛·0·16 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵4):
+          𝕄(Φ.a🌵4):
+            𝑛·3·1 := Φ.number( φ ↦ 𝑛·0·12 )
+            𝑛·3·2 := Φ.bytes( φ ↦ 40-08-00-00-00-00-00-00:Δ )
+          𝛿·3·1 := 40-08-00-00-00-00-00-00  # 3.0
+        𝛿1·3 := 𝛿·3·1  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵5):
+          𝕄(Φ.a🌵5):
+            𝑛·3·3 := Φ.number( φ ↦ 𝑛·0·15 )
+            𝑛·3·4 := Φ.bytes( φ ↦ 40-10-00-00-00-00-00-00:Δ )
+          𝛿·3·2 := 40-10-00-00-00-00-00-00  # 4.0
+        𝛿2·3 := 𝛿·3·2  # 𝔻(ξ.x)
+        𝑛·3·5 := Φ.number( φ ↦ 𝜎3:λ )  # 𝑛
+        𝕄(𝑛·3·5):
+          𝑛·3·6 := Φ.number( φ ↦ 𝜎3:λ )
+        𝑛·3·7 := 𝑛·3·6  # 𝕄(𝑛·3·5)
+    𝔼(L_fork):
+      𝔻(Φ.a🌵6):
+        𝕄(Φ.a🌵6):
+          𝑛·4·1 := Φ.bool( φ ↦ 𝜎1:λ )
+      𝛿1·4 := 𝔻(𝜎1:λ)  # 𝔻(ξ.ρ)
+      𝑛1·4 := 𝑛·2·7  # 𝕄(ξ.then)
+      𝑛2·4 := 𝑛·3·7  # 𝕄(ξ.else)
+      𝑛·4·2 := 𝑛1·4  # 𝑛
+      𝑛·4·3 := 𝑛1·4  # 𝕄(𝑛·4·2)
diff --git a/test-resources/evaluate-packs/spends-the-step-budget-on-an-entry-answering-itself.yaml b/test-resources/evaluate-packs/spends-the-step-budget-on-an-entry-answering-itself.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/spends-the-step-budget-on-an-entry-answering-itself.yaml
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An entry answering with a firing of itself fires until the budget is gone.
+# Recursion is nothing phino prevents on its own — that is the object model's
+# business — so without '_acyclic' the step limit is what ends the run, and
+# every firing it paid for is in the protocol. This is the control the pack
+# asking for the guard stands against.
+symbolic: |
+  - λ: L_loop
+    𝑛: ⟦ λ ⤍ L_loop ⟧
+location: 'Q.x'
+steps: 6
+input: '⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo ⟧'
+fails: 'reaching the limit of steps'
+protocol: |
+  𝕄(Φ.x):
+    𝔼(L_loop):
+      𝑛·1·1 := L_loop:λ  # 𝑛
+      𝑛·1·2 := L_loop:λ  # 𝕄(𝑛·1·1)
+    𝔼(L_loop):
+      𝑛·2·1 := L_loop:λ  # 𝑛
+      𝑛·2·2 := L_loop:λ  # 𝕄(𝑛·2·1)
+    𝔼(L_loop):
+      𝑛·3·1 := L_loop:λ  # 𝑛
+      𝑛·3·2 := L_loop:λ  # 𝕄(𝑛·3·1)
+    𝔼(L_loop):
+      𝑛·4·1 := L_loop:λ  # 𝑛
+      𝑛·4·2 := L_loop:λ  # 𝕄(𝑛·4·1)
+    𝔼(L_loop):
+      𝑛·5·1 := L_loop:λ  # 𝑛
+      𝑛·5·2 := L_loop:λ  # 𝕄(𝑛·5·1)
+    𝔼(L_loop):
+      𝑛·6·1 := L_loop:λ  # 𝑛
+      𝕄(𝑛·6·1):
+        starved(6)
diff --git a/test-resources/evaluate-packs/stands-the-data-of-a-term-into-unknowns.yaml b/test-resources/evaluate-packs/stands-the-data-of-a-term-into-unknowns.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/stands-the-data-of-a-term-into-unknowns.yaml
@@ -0,0 +1,37 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A 'symbolize' line reduces nothing: it stands the data of a term another line
+# of the entry has already bound into unknowns, so a normal form reached from a
+# literal is written the way one reached from an unknown is written and the two
+# of them compare as expressions. Here 𝑛1 came from a literal and 𝑛2 from a
+# value nobody worked out: after the stage both of them carry ⟦ λ ⤍ 𝜎 ⟧ where
+# they differ. What is known about each fresh symbol stands on a line of its
+# own, since a 𝜎 is the name of a λ function and nothing binds bytes to it, and
+# a term that carried no datum at all passes through as it was. The comments of
+# the two pairs of lines are what tells them apart: a 'morph' operand is
+# commented with the judgment that reduced it and the term it was reduced from,
+# '𝕄(ξ.a)', where a 'symbolize' line names the meta it was told to stand and no
+# judgment at all, since none ran (#1306).
+symbolic: |
+  - λ: L_join
+    morph:
+      𝑛1: $.a
+      𝑛2: $.b
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    𝑛: ⟦ l ↦ 𝑛3 ⟧
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ a ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ λ ⤍ 𝜎7 ⟧, λ ⤍ L_join ⟧.l ⟧'
+result: '𝜎8:λ'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_join):
+      𝑛1·1 := 01-:Δ  # 𝕄(ξ.a)
+      𝑛2·1 := 𝜎7:λ  # 𝕄(ξ.b)
+      𝔻(𝜎8:λ) == 01-
+      𝑛3·1 := 𝜎8:λ  # 𝑛1
+      𝑛4·1 := 𝑛2·1  # 𝑛2
+      𝑛·1·1 := 𝜎8:λ:l  # 𝑛
+      𝑛·1·2 := 𝜎8:λ:l  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/starts-minting-after-the-symbols-the-program-carries.yaml b/test-resources/evaluate-packs/starts-minting-after-the-symbols-the-program-carries.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/starts-minting-after-the-symbols-the-program-carries.yaml
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A program written by an earlier run holds symbols of its own, so minting
+# starts after the last of them and a fresh symbol is never spelled like one
+# the input was already written with.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    old ↦ ⟦ λ ⤍ 𝜎3 ⟧,
+    @ ↦ 5.plus( 6 ) ⟧
+result: '⟦ φ ↦ 𝜎4:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := 5
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ 6 )
+    𝕄(Φ.φ.x):
+      𝑛·0·3 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+      𝑛·0·4 := Φ.number( φ ↦ 𝑛·0·3 )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := 5
+          𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+      𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·3 )
+          𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+      𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+      𝑛·1·5 := Φ.number( φ ↦ 𝜎4:λ )  # 𝑛
+      𝕄(𝑛·1·5):
+        𝑛·1·6 := Φ.number( φ ↦ 𝜎4:λ )
+      𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
diff --git a/test-resources/evaluate-packs/stops-an-accumulator-recursion-under-plausible.yaml b/test-resources/evaluate-packs/stops-an-accumulator-recursion-under-plausible.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/stops-an-accumulator-recursion-under-plausible.yaml
@@ -0,0 +1,128 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A factorial with an accumulator, under '--acyclic=plausible'. Every round
+# wraps the accumulator in one more 'pair', which no entry answers, so no two
+# rounds are ever the same formation up to a renaming of symbols and
+# '--acyclic=proven' goes round forever. The formation 'if' the deep walk fires
+# on the second round holds the one it fired on the first, under the wrapper it
+# gained, which is what the plausible guard compares by, so the second firing
+# is cut before it opens and its 'looped' line names the mode that cut it
+# (#1451). Every round is a copy of 'Φ.fact', but its φ reads the 'n' and the
+# 'acc' of the copy it stands in, so the walk enters it in every round rather
+# than once for the whole object (#1485). The argument 'n' and every symbol
+# 'dec' answers stand in a carrier '⟦ φ ↦ 𝜎 ⟧', since the walk defers a copy
+# one of whose arguments is a bare symbol instead of entering it (#1729).
+# The cut 'if' is the φ of a copy of 'Φ.fact', so the cut answers that copy
+# with a fresh symbol, which the 'join' of the first 'if' pairs with the datum
+# of the other branch, and the run answers a symbol instead of getting stuck
+# (#1735).
+symbolic: |
+  - λ: L_zero
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+  - λ: L_dec
+    dataize:
+      𝛿1: $.x
+    𝑛: ⟦ φ ↦ ⟦ λ ⤍ 𝜎 ⟧ ⟧
+  - λ: L_if
+    dataize:
+      𝛿1: $.c
+    morph:
+      𝑛1: $.left
+      𝑛2: $.right
+    symbolize:
+      𝑛3: 𝑛1
+      𝑛4: 𝑛2
+    join:
+      𝑛5: [𝑛3, 𝑛4]
+    𝑛: 𝑛5
+location: 'Q.x'
+deep: true
+acyclic: plausible
+partial: true
+steps: 250
+input: >
+  ⟦ if ↦ ⟦ c ↦ ∅, left ↦ ∅, right ↦ ∅, λ ⤍ L_if ⟧,
+    zero ↦ ⟦ x ↦ ∅, λ ⤍ L_zero ⟧,
+    dec ↦ ⟦ x ↦ ∅, λ ⤍ L_dec ⟧,
+    pair ↦ ⟦ head ↦ ∅, tail ↦ ∅ ⟧,
+    fact ↦ ⟦ n ↦ ∅, acc ↦ ∅, φ ↦ Φ.if( c ↦ Φ.zero( x ↦ ξ.n ), left ↦ ξ.acc, right ↦ Φ.fact( n ↦ Φ.dec( x ↦ ξ.n ), acc ↦ Φ.pair( head ↦ ξ.n, tail ↦ ξ.acc ) ) ) ⟧,
+    x ↦ Φ.fact( n ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, acc ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧
+result: |-
+  ⟦ n ↦ 𝜎1:λ:φ, acc ↦ 00-:Δ, φ ↦ 𝜎8:λ ⟧
+protocol: |
+  𝕄(Φ.x):
+    𝑛·0·1 := Φ.fact( n ↦ 𝜎1:λ:φ )
+    𝑛·0·2 := 𝑛·0·1( acc ↦ 00-:Δ )
+    𝕄(Φ.x.φ):
+      𝑛·0·3 := Φ.zero( x ↦ 𝜎1:λ:φ )
+      𝔼(L_zero):
+        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+        𝑛·1·1 := 𝜎2:λ  # 𝑛
+        𝑛·1·2 := 𝜎2:λ  # 𝕄(𝑛·1·1)
+      𝑛·0·4 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·5 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·6 := 𝑛·0·5( left ↦ 00-:Δ )
+      𝑛·0·7 := Φ.dec( x ↦ 𝜎1:λ:φ )
+      𝔼(L_dec):
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.x)
+        𝑛·2·1 := 𝜎3:λ:φ  # 𝑛
+        𝑛·2·2 := 𝜎3:λ:φ  # 𝕄(𝑛·2·1)
+      𝑛·0·8 := Φ.fact( n ↦ 𝜎3:λ:φ )
+      𝑛·0·9 := Φ.pair( head ↦ 𝜎1:λ:φ )
+      𝑛·0·10 := Φ.pair( head ↦ 𝜎1:λ:φ )
+      𝑛·0·11 := 𝑛·0·10( tail ↦ 00-:Δ )
+      𝑛·0·12 := Φ.fact( n ↦ 𝜎3:λ:φ )
+      𝑛·0·13 := 𝑛·0·12( acc ↦ 𝑛·0·11 )
+      𝑛·0·14 := Φ.if( c ↦ 𝜎2:λ )
+      𝑛·0·15 := 𝑛·0·14( left ↦ 00-:Δ )
+      𝑛·0·16 := 𝑛·0·15( right ↦ 𝑛·0·13 )
+      𝔼(L_if):
+        𝛿1·3 := 𝔻(𝜎2:λ)  # 𝔻(ξ.c)
+        𝑛1·3 := 00-:Δ  # 𝕄(ξ.left)
+        𝕄(Φ.a🌵4):
+          𝑛·3·1 := Φ.fact( n ↦ 𝜎3:λ:φ )
+          𝑛·3·2 := 𝑛·3·1( acc ↦ 𝑛·0·11 )
+          𝕄(Φ.a🌵4.acc):
+            𝑛·3·3 := Φ.pair( head ↦ 𝜎1:λ:φ )
+            𝑛·3·4 := Φ.pair( head ↦ 𝜎1:λ:φ )
+            𝑛·3·5 := 𝑛·3·4( tail ↦ 00-:Δ )
+          𝕄(Φ.a🌵4.φ):
+            𝑛·3·6 := Φ.zero( x ↦ 𝜎3:λ:φ )
+            𝔼(L_zero):
+              𝛿1·4 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+              𝑛·4·1 := 𝜎4:λ  # 𝑛
+              𝑛·4·2 := 𝜎4:λ  # 𝕄(𝑛·4·1)
+            𝑛·3·7 := Φ.if( c ↦ 𝜎4:λ )
+            𝑛·3·8 := Φ.pair( head ↦ 𝜎1:λ:φ )
+            𝑛·3·9 := 𝑛·3·8( tail ↦ 00-:Δ )
+            𝑛·3·10 := Φ.if( c ↦ 𝜎4:λ )
+            𝑛·3·11 := 𝑛·3·10( left ↦ 𝑛·3·9 )
+            𝑛·3·12 := Φ.dec( x ↦ 𝜎3:λ:φ )
+            𝔼(L_dec):
+              𝛿1·5 := 𝔻(𝜎3:λ)  # 𝔻(ξ.x)
+              𝑛·5·1 := 𝜎5:λ:φ  # 𝑛
+              𝑛·5·2 := 𝜎5:λ:φ  # 𝕄(𝑛·5·1)
+            𝑛·3·13 := Φ.fact( n ↦ 𝜎5:λ:φ )
+            𝑛·3·14 := Φ.pair( head ↦ 𝜎3:λ:φ )
+            𝑛·3·15 := Φ.pair( head ↦ 𝜎1:λ:φ )
+            𝑛·3·16 := 𝑛·3·15( tail ↦ 00-:Δ )
+            𝑛·3·17 := Φ.pair( head ↦ 𝜎3:λ:φ )
+            𝑛·3·18 := 𝑛·3·17( tail ↦ 𝑛·3·16 )
+            𝑛·3·19 := Φ.fact( n ↦ 𝜎5:λ:φ )
+            𝑛·3·20 := 𝑛·3·19( acc ↦ 𝑛·3·18 )
+            𝑛·3·21 := Φ.if( c ↦ 𝜎4:λ )
+            𝑛·3·22 := 𝑛·3·21( left ↦ 𝑛·3·16 )
+            𝑛·3·23 := 𝑛·3·22( right ↦ 𝑛·3·20 )
+            looped(𝑛·0·16) := 𝜎6  # plausible
+        𝑛2·3 := 𝜎6:λ  # 𝕄(ξ.right)
+        𝔻(𝜎7:λ) == 00-
+        𝑛3·3 := 𝜎7:λ  # 𝑛1
+        𝑛4·3 := 𝑛2·3  # 𝑛2
+        𝔻(𝜎8:λ) ∈ { 𝔻(𝜎7:λ), 𝔻(𝜎6:λ) }
+        𝑛5·3 := 𝜎8:λ  # [𝑛3, 𝑛4]
+        𝑛·3·24 := 𝑛5·3  # 𝑛
+        𝑛·3·25 := 𝑛5·3  # 𝕄(𝑛·3·24)
diff --git a/test-resources/evaluate-packs/takes-the-answer-of-a-firing-with-the-same-operands.yaml b/test-resources/evaluate-packs/takes-the-answer-of-a-firing-with-the-same-operands.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/takes-the-answer-of-a-firing-with-the-same-operands.yaml
@@ -0,0 +1,50 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps what a firing answered by the λ
+# function and the operands it brought down, beside the formation it fired.
+# Here 'a' and 'b' fire two formations, since one reads 'x' and the other
+# reads 'y', so the memo of formations misses the second. Yet both bring the
+# same datum down, and an entry answers from its operands alone. Before #1661
+# the firing under 'b' minted '𝜎2' for the very value '𝜎1' already named. Now
+# it brings its operand down, finds the firing already answered and takes
+# '𝜎1', minting nothing.
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+partial: true
+acyclic: plausible
+input: >
+  ⟦ dataized(target) ↦ L_dataized:λ,
+    a ↦ Φ.dataized( target ↦ Φ.x ),
+    b ↦ Φ.dataized( target ↦ Φ.y ),
+    x ↦ ⟦ Δ ⤍ 01- ⟧,
+    y ↦ ⟦ Δ ⤍ 01- ⟧ ⟧
+result: >
+  ⟦ dataized(target) ↦ L_dataized:λ,
+    a ↦ 𝜎1:λ,
+    b ↦ 𝜎1:λ,
+    x ↦ 01-:Δ,
+    y ↦ 01-:Δ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.a):
+      𝑛·0·1 := Φ.dataized( target ↦ Φ.x )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵0):
+          𝛿·1·1 := 01-
+        𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.target)
+        𝑛·1·1 := 𝜎1:λ  # 𝑛
+        𝑛·1·2 := 𝜎1:λ  # 𝕄(𝑛·1·1)
+    𝕄(Φ.b):
+      𝑛·0·2 := Φ.dataized( target ↦ Φ.y )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵1):
+          𝛿·2·1 := 01-
+        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.target)
+        𝑛·2·1 := 𝑛·1·2  # 𝑛
+        𝑛·2·2 := 𝑛·1·2  # 𝕄(𝑛·2·1)
diff --git a/test-resources/evaluate-packs/tells-a-budget-stall-to-a-firing-no-shallower.yaml b/test-resources/evaluate-packs/tells-a-budget-stall-to-a-firing-no-shallower.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/tells-a-budget-stall-to-a-firing-no-shallower.yaml
@@ -0,0 +1,252 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps a stall the step budget made beside
+# the steps its firing had spent, and tells it to a firing of the same
+# formation that has spent at least as many. The operand of 'x.arg' runs out
+# of steps two firings down, so the stall is kept. Before #1521 it was dropped,
+# and 'x' walked the nest again inside its own firing, while 'y.arg' and 'y'
+# fired it all over. Now the firing inside 'x' is told the stall, and so are
+# 'y.arg' and 'y', which stand as deep as 'x.arg' and 'x' did.
+symbolic: |
+  - λ: L_outer
+    dataize:
+      𝛿1: ξ.arg
+    𝑛: ⟦ λ ⤍ 𝜎 ⟧
+deep: true
+partial: true
+acyclic: plausible
+steps: 4
+input: >
+  ⟦ x ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧,
+    y ↦ ⟦ λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ λ ⤍ L_none ⟧ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝑛·0·1 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )
+    𝑛·0·2 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )
+    𝑛·0·3 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )
+    𝑛·0·4 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 ) ⟧( v ↦ 1 )
+    𝑛·0·5 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 ) ⟧( v ↦ 1 )
+    𝑛·0·6 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·7 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·2 ⟧( v ↦ 1 )
+    𝑛·0·8 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·3 ⟧( v ↦ 1 )
+    𝑛·0·9 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·4 ⟧( v ↦ 1 )
+    𝑛·0·10 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·5 ⟧( v ↦ 1 )
+    𝑛·0·11 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·6 ⟧( v ↦ 1 )
+    𝑛·0·12 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·13 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·9 ⟧( v ↦ 1 )
+    𝑛·0·14 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·10 ⟧( v ↦ 1 )
+    𝑛·0·15 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·11 ⟧( v ↦ 1 )
+    𝑛·0·16 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·12 ⟧( v ↦ 1 )
+    𝑛·0·17 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·18 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·14 ⟧( v ↦ 1 )
+    𝑛·0·19 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·15 ⟧( v ↦ 1 )
+    𝑛·0·20 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·16 ⟧( v ↦ 1 )
+    𝑛·0·21 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·17 ⟧( v ↦ 1 )
+    𝑛·0·22 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·23 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·20 ⟧( v ↦ 1 )
+    𝑛·0·24 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·21 ⟧( v ↦ 1 )
+    𝑛·0·25 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·22 ⟧( v ↦ 1 )
+    𝑛·0·26 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·27 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·24 ⟧( v ↦ 1 )
+    𝑛·0·28 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·25 ⟧( v ↦ 1 )
+    𝑛·0·29 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·26 ⟧( v ↦ 1 )
+    𝑛·0·30 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·31 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·29 ⟧( v ↦ 1 )
+    𝑛·0·32 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·30 ⟧( v ↦ 1 )
+    𝑛·0·33 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·34 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·32 ⟧( v ↦ 1 )
+    𝑛·0·35 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·33 ⟧( v ↦ 1 )
+    𝑛·0·36 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·37 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·36 ⟧( v ↦ 1 )
+    𝑛·0·38 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·39 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·38 ⟧( v ↦ 1 )
+    𝑛·0·40 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·41 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·42 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝕄(Φ.x):
+      𝕄(Φ.x.arg):
+        𝑛·0·43 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·27 ⟧( v ↦ 1 )
+        𝑛·0·44 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·34 ⟧( v ↦ 1 )
+        𝑛·0·45 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·39 ⟧( v ↦ 1 )
+        𝑛·0·46 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·42 ⟧( v ↦ 1 )
+        𝑛·0·47 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·48 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·45 ⟧( v ↦ 1 )
+        𝑛·0·49 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·46 ⟧( v ↦ 1 )
+        𝑛·0·50 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·47 ⟧( v ↦ 1 )
+        𝑛·0·51 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·52 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·50 ⟧( v ↦ 1 )
+        𝑛·0·53 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·51 ⟧( v ↦ 1 )
+        𝑛·0·54 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·55 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·54 ⟧( v ↦ 1 )
+        𝑛·0·56 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·57 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        starved(4)
+        starved(4)
+        starved(4)
+        starved(4)
+        𝑛·0·58 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·43 ⟧( v ↦ 1 )
+        𝑛·0·59 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·48 ⟧( v ↦ 1 )
+        𝑛·0·60 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·52 ⟧( v ↦ 1 )
+        𝑛·0·61 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·55 ⟧( v ↦ 1 )
+        𝑛·0·62 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·57 ⟧( v ↦ 1 )
+        𝑛·0·63 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·64 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·60 ⟧( v ↦ 1 )
+        𝑛·0·65 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·61 ⟧( v ↦ 1 )
+        𝑛·0·66 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·62 ⟧( v ↦ 1 )
+        𝑛·0·67 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·63 ⟧( v ↦ 1 )
+        𝑛·0·68 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·69 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·66 ⟧( v ↦ 1 )
+        𝑛·0·70 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·67 ⟧( v ↦ 1 )
+        𝑛·0·71 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·68 ⟧( v ↦ 1 )
+        𝑛·0·72 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·73 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·71 ⟧( v ↦ 1 )
+        𝑛·0·74 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·72 ⟧( v ↦ 1 )
+        𝑛·0·75 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·76 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·75 ⟧( v ↦ 1 )
+        𝑛·0·77 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·78 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝔼(L_outer):
+          𝔻(Φ.a🌵0):
+            𝔼(L_outer):
+              𝔻(Φ.a🌵1):
+                𝔼(L_outer):
+                  starved(4)  # 𝔻(Φ.a🌵2)
+              stuck(L_outer)
+          stuck(L_outer)
+      𝑛·0·79 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·64 ⟧( v ↦ 1 )
+      𝑛·0·80 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·69 ⟧( v ↦ 1 )
+      𝑛·0·81 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·73 ⟧( v ↦ 1 )
+      𝑛·0·82 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·76 ⟧( v ↦ 1 )
+      𝑛·0·83 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·78 ⟧( v ↦ 1 )
+      𝑛·0·84 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·85 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·81 ⟧( v ↦ 1 )
+      𝑛·0·86 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·82 ⟧( v ↦ 1 )
+      𝑛·0·87 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·83 ⟧( v ↦ 1 )
+      𝑛·0·88 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·84 ⟧( v ↦ 1 )
+      𝑛·0·89 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·90 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·87 ⟧( v ↦ 1 )
+      𝑛·0·91 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·88 ⟧( v ↦ 1 )
+      𝑛·0·92 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·89 ⟧( v ↦ 1 )
+      𝑛·0·93 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·94 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·92 ⟧( v ↦ 1 )
+      𝑛·0·95 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·93 ⟧( v ↦ 1 )
+      𝑛·0·96 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·97 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·96 ⟧( v ↦ 1 )
+      𝑛·0·98 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·99 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝔼(L_outer):
+        𝔻(Φ.a🌵3):
+          𝔼(L_outer):
+            stall(L_outer)
+        stuck(L_outer)
+    𝕄(Φ.y):
+      𝕄(Φ.y.arg):
+        𝑛·0·100 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·90 ⟧( v ↦ 1 )
+        𝑛·0·101 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·94 ⟧( v ↦ 1 )
+        𝑛·0·102 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·97 ⟧( v ↦ 1 )
+        𝑛·0·103 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·99 ⟧( v ↦ 1 )
+        𝑛·0·104 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·105 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·102 ⟧( v ↦ 1 )
+        𝑛·0·106 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·103 ⟧( v ↦ 1 )
+        𝑛·0·107 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·104 ⟧( v ↦ 1 )
+        𝑛·0·108 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·109 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·107 ⟧( v ↦ 1 )
+        𝑛·0·110 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·108 ⟧( v ↦ 1 )
+        𝑛·0·111 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·112 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·111 ⟧( v ↦ 1 )
+        𝑛·0·113 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·114 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        starved(4)
+        starved(4)
+        starved(4)
+        starved(4)
+        𝑛·0·115 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·100 ⟧( v ↦ 1 )
+        𝑛·0·116 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·105 ⟧( v ↦ 1 )
+        𝑛·0·117 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·109 ⟧( v ↦ 1 )
+        𝑛·0·118 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·112 ⟧( v ↦ 1 )
+        𝑛·0·119 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·114 ⟧( v ↦ 1 )
+        𝑛·0·120 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·121 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·117 ⟧( v ↦ 1 )
+        𝑛·0·122 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·118 ⟧( v ↦ 1 )
+        𝑛·0·123 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·119 ⟧( v ↦ 1 )
+        𝑛·0·124 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·120 ⟧( v ↦ 1 )
+        𝑛·0·125 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·126 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·123 ⟧( v ↦ 1 )
+        𝑛·0·127 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·124 ⟧( v ↦ 1 )
+        𝑛·0·128 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·125 ⟧( v ↦ 1 )
+        𝑛·0·129 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·130 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·128 ⟧( v ↦ 1 )
+        𝑛·0·131 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·129 ⟧( v ↦ 1 )
+        𝑛·0·132 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·133 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·132 ⟧( v ↦ 1 )
+        𝑛·0·134 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝑛·0·135 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+        𝔼(L_outer):
+          stall(L_outer)
+      𝑛·0·136 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·121 ⟧( v ↦ 1 )
+      𝑛·0·137 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·126 ⟧( v ↦ 1 )
+      𝑛·0·138 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·130 ⟧( v ↦ 1 )
+      𝑛·0·139 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·133 ⟧( v ↦ 1 )
+      𝑛·0·140 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·135 ⟧( v ↦ 1 )
+      𝑛·0·141 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·142 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·138 ⟧( v ↦ 1 )
+      𝑛·0·143 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·139 ⟧( v ↦ 1 )
+      𝑛·0·144 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·140 ⟧( v ↦ 1 )
+      𝑛·0·145 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·141 ⟧( v ↦ 1 )
+      𝑛·0·146 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·147 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·144 ⟧( v ↦ 1 )
+      𝑛·0·148 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·145 ⟧( v ↦ 1 )
+      𝑛·0·149 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·146 ⟧( v ↦ 1 )
+      𝑛·0·150 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·151 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·149 ⟧( v ↦ 1 )
+      𝑛·0·152 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·150 ⟧( v ↦ 1 )
+      𝑛·0·153 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·154 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·153 ⟧( v ↦ 1 )
+      𝑛·0·155 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝑛·0·156 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+      𝔼(L_outer):
+        stall(L_outer)
+    𝑛·0·157 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·142 ⟧( v ↦ 1 )
+    𝑛·0·158 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·147 ⟧( v ↦ 1 )
+    𝑛·0·159 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·151 ⟧( v ↦ 1 )
+    𝑛·0·160 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·154 ⟧( v ↦ 1 )
+    𝑛·0·161 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·156 ⟧( v ↦ 1 )
+    𝑛·0·162 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·163 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·158 ⟧( v ↦ 1 )
+    𝑛·0·164 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·159 ⟧( v ↦ 1 )
+    𝑛·0·165 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·160 ⟧( v ↦ 1 )
+    𝑛·0·166 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·161 ⟧( v ↦ 1 )
+    𝑛·0·167 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·162 ⟧( v ↦ 1 )
+    𝑛·0·168 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·169 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·165 ⟧( v ↦ 1 )
+    𝑛·0·170 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·166 ⟧( v ↦ 1 )
+    𝑛·0·171 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·167 ⟧( v ↦ 1 )
+    𝑛·0·172 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·168 ⟧( v ↦ 1 )
+    𝑛·0·173 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·174 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·170 ⟧( v ↦ 1 )
+    𝑛·0·175 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·171 ⟧( v ↦ 1 )
+    𝑛·0·176 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·172 ⟧( v ↦ 1 )
+    𝑛·0·177 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·173 ⟧( v ↦ 1 )
+    𝑛·0·178 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·179 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·176 ⟧( v ↦ 1 )
+    𝑛·0·180 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·177 ⟧( v ↦ 1 )
+    𝑛·0·181 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·178 ⟧( v ↦ 1 )
+    𝑛·0·182 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·183 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·180 ⟧( v ↦ 1 )
+    𝑛·0·184 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·181 ⟧( v ↦ 1 )
+    𝑛·0·185 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·182 ⟧( v ↦ 1 )
+    𝑛·0·186 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·187 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·185 ⟧( v ↦ 1 )
+    𝑛·0·188 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·186 ⟧( v ↦ 1 )
+    𝑛·0·189 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·190 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·188 ⟧( v ↦ 1 )
+    𝑛·0·191 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·189 ⟧( v ↦ 1 )
+    𝑛·0·192 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·193 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·192 ⟧( v ↦ 1 )
+    𝑛·0·194 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·195 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ 𝑛·0·194 ⟧( v ↦ 1 )
+    𝑛·0·196 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·197 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
+    𝑛·0·198 := ⟦ v ↦ ∅, λ ⤍ L_outer, arg ↦ L_none:λ ⟧( v ↦ 1 )
diff --git a/test-resources/evaluate-packs/tells-two-carried-symbols-apart-in-one-firing.yaml b/test-resources/evaluate-packs/tells-two-carried-symbols-apart-in-one-firing.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/tells-two-carried-symbols-apart-in-one-firing.yaml
@@ -0,0 +1,40 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Both operands of the firing are symbols the program came in with, and both
+# dataize to the very same 42. Only the protocol tells them apart: it writes
+# the symbol each datum was manufactured for, so 𝜎2 and 𝜎5 stay two unknowns
+# and not one.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q.@'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    @ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ).plus(
+          Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ) ⟧
+result: '⟦ φ ↦ 𝜎6:λ, plus(x) ↦ L_number_plus:λ ⟧'
+protocol: |
+  𝕄(Φ.φ):
+    𝑛·0·1 := Φ.number( φ ↦ 𝜎2:λ )
+    𝑛·0·2 := 𝑛·0·1.plus( x ↦ Φ.number( φ ↦ 𝜎5:λ ) )
+    𝕄(Φ.φ.x):
+      𝑛·0·3 := Φ.number( φ ↦ 𝜎5:λ )
+    𝔼(L_number_plus):
+      𝔻(Φ.a🌵0):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.number( φ ↦ 𝜎2:λ )
+      𝛿1·1 := 𝔻(𝜎2:λ)  # 𝔻(ξ.ρ)
+      𝔻(Φ.a🌵1):
+        𝕄(Φ.a🌵1):
+          𝑛·1·2 := Φ.number( φ ↦ 𝜎5:λ )
+      𝛿2·1 := 𝔻(𝜎5:λ)  # 𝔻(ξ.x)
+      𝑛·1·3 := Φ.number( φ ↦ 𝜎6:λ )  # 𝑛
+      𝕄(𝑛·1·3):
+        𝑛·1·4 := Φ.number( φ ↦ 𝜎6:λ )
+      𝑛·1·5 := 𝑛·1·4  # 𝕄(𝑛·1·3)
diff --git a/test-resources/evaluate-packs/tells-two-symbols-of-one-datum-apart-between-firings.yaml b/test-resources/evaluate-packs/tells-two-symbols-of-one-datum-apart-between-firings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/tells-two-symbols-of-one-datum-apart-between-firings.yaml
@@ -0,0 +1,54 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The memo of '--acyclic=plausible' keeps an answer by the operands a firing
+# brought down, and a symbol is an operand of its own: every symbol dataizes
+# to the one manufactured datum, so two firings over two symbols come down to
+# the same bytes, yet they stand for two values nobody worked out. A memo
+# keyed on the bytes would answer the second firing with the symbol the first
+# one minted and make the two values one; this one keys on the symbol, so each
+# firing mints a symbol of its own.
+symbolic: |
+  - λ: L_dataized
+    dataize:
+      𝛿1: $.target
+    𝑛: Φ.bytes( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+deep: true
+acyclic: plausible
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    dataized(target) ↦ L_dataized:λ,
+    x ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎1:λ ) ),
+    y ↦ Φ.dataized( target ↦ Φ.bytes( φ ↦ 𝜎2:λ ) ) ⟧
+result: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    dataized(target) ↦ L_dataized:λ,
+    x ↦ ⟦ φ ↦ 𝜎3:λ ⟧,
+    y ↦ ⟦ φ ↦ 𝜎4:λ ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      𝑛·0·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+      𝑛·0·2 := Φ.dataized( target ↦ 𝑛·0·1 )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            𝑛·1·1 := Φ.bytes( φ ↦ 𝜎1:λ )
+        𝛿1·1 := 𝔻(𝜎1:λ)  # 𝔻(ξ.target)
+        𝑛·1·2 := Φ.bytes( φ ↦ 𝜎3:λ )  # 𝑛
+        𝕄(𝑛·1·2):
+          𝑛·1·3 := Φ.bytes( φ ↦ 𝜎3:λ )
+        𝑛·1·4 := 𝑛·1·3  # 𝕄(𝑛·1·2)
+    𝕄(Φ.y):
+      𝑛·0·3 := Φ.bytes( φ ↦ 𝜎2:λ )
+      𝑛·0·4 := Φ.dataized( target ↦ 𝑛·0·3 )
+      𝔼(L_dataized):
+        𝔻(Φ.a🌵1):
+          𝕄(Φ.a🌵1):
+            𝑛·2·1 := Φ.bytes( φ ↦ 𝜎2:λ )
+        𝛿1·2 := 𝔻(𝜎2:λ)  # 𝔻(ξ.target)
+        𝑛·2·2 := Φ.bytes( φ ↦ 𝜎4:λ )  # 𝑛
+        𝕄(𝑛·2·2):
+          𝑛·2·3 := Φ.bytes( φ ↦ 𝜎4:λ )
+        𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
diff --git a/test-resources/evaluate-packs/walks-a-binding-of-an-object-once-however-many-copies-hold-it.yaml b/test-resources/evaluate-packs/walks-a-binding-of-an-object-once-however-many-copies-hold-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/walks-a-binding-of-an-object-once-however-many-copies-hold-it.yaml
@@ -0,0 +1,73 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The synonym of '--acyclic=plausible': every dispatch on 'Φ.num' copies the
+# object, and the deep walk entered the bindings of every copy as if they were
+# new, so the test of 'num' was brought to its normal form and fired once
+# under 'a', once under 'b' and once under 'c' (#1480). The copy goes by a
+# name in the world, 'Φ.num( φ ↦ … )', and the object part of that name is a
+# synonym of it, so the walk enters 'test' of 'Φ.num' in the first copy it
+# meets and leaves it as written in every later one. The φ each copy fills is
+# an argument of that copy and not a binding of 'Φ.num', so it is entered
+# every time.
+symbolic: |
+  - λ: L_id
+    morph:
+      𝑛1: $.x
+    𝑛: 𝑛1
+  - λ: L_twice
+    dataize:
+      𝛿1: $.ρ
+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+deep: true
+partial: true
+acyclic: plausible
+steps: 32
+input: >
+  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧, test ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 01- ⟧ ).twice ⟧,
+    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ⟧,
+    b ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2B- ⟧ ) ⟧,
+    c ↦ ⟦ λ ⤍ L_id, x ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2C- ⟧ ) ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.a):
+      𝕄(Φ.a.x):
+        𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )
+      𝔼(L_id):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.num( φ ↦ 2A-:Δ )
+          𝕄(Φ.a🌵0.test):
+            𝑛·1·2 := Φ.num( φ ↦ 01-:Δ )
+            𝑛·1·3 := Φ.num( φ ↦ 01-:Δ )
+            𝔼(L_twice):
+              𝔻(Φ.a🌵1):
+                𝕄(Φ.a🌵1):
+                  𝑛·2·1 := Φ.num( φ ↦ 01-:Δ )
+                𝛿·2·1 := 01-
+              𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+              𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛
+              𝕄(𝑛·2·2):
+                𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )
+              𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
+        𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)
+        𝑛·1·4 := 𝑛·1·1  # 𝑛
+        𝑛·1·5 := 𝑛·1·1  # 𝕄(𝑛·1·4)
+    𝕄(Φ.b):
+      𝕄(Φ.b.x):
+        𝑛·0·2 := Φ.num( φ ↦ 2B-:Δ )
+      𝔼(L_id):
+        𝕄(Φ.a🌵2):
+          𝑛·3·1 := Φ.num( φ ↦ 2B-:Δ )
+        𝑛1·3 := 𝑛·3·1  # 𝕄(ξ.x)
+        𝑛·3·2 := 𝑛·3·1  # 𝑛
+        𝑛·3·3 := 𝑛·3·1  # 𝕄(𝑛·3·2)
+    𝕄(Φ.c):
+      𝕄(Φ.c.x):
+        𝑛·0·3 := Φ.num( φ ↦ 2C-:Δ )
+      𝔼(L_id):
+        𝕄(Φ.a🌵3):
+          𝑛·4·1 := Φ.num( φ ↦ 2C-:Δ )
+        𝑛1·4 := 𝑛·4·1  # 𝕄(ξ.x)
+        𝑛·4·2 := 𝑛·4·1  # 𝑛
+        𝑛·4·3 := 𝑛·4·1  # 𝕄(𝑛·4·2)
diff --git a/test-resources/evaluate-packs/walks-a-binding-reading-its-copy-in-every-copy.yaml b/test-resources/evaluate-packs/walks-a-binding-reading-its-copy-in-every-copy.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/walks-a-binding-reading-its-copy-in-every-copy.yaml
@@ -0,0 +1,80 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The synonym of '--acyclic=plausible' lets the walk enter a binding of an
+# object of the world in the first copy it meets and leave it as written in
+# every later one (#1480). A body reading ξ reads the copy it stands in, and
+# two copies filling 'v' with different numbers make two different programs of
+# the φ of 'dbl', so that φ is walked in every copy and 'L_twice' fires under
+# 'a' and under 'b', each with a symbol of its own (#1485).
+symbolic: |
+  - λ: L_id
+    morph:
+      𝑛1: $.x
+    𝑛: 𝑛1
+  - λ: L_twice
+    dataize:
+      𝛿1: $.ρ
+    𝑛: Φ.num( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+deep: true
+partial: true
+acyclic: plausible
+steps: 32
+input: >
+  ⟦ num ↦ ⟦ φ ↦ ∅, twice ↦ ⟦ ρ ↦ ∅, λ ⤍ L_twice ⟧ ⟧,
+    dbl ↦ ⟦ v ↦ ∅, φ ↦ ξ.v.twice ⟧,
+    a ↦ ⟦ λ ⤍ L_id, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2A- ⟧ ) ) ⟧,
+    b ↦ ⟦ λ ⤍ L_id, x ↦ Φ.dbl( v ↦ Φ.num( φ ↦ ⟦ Δ ⤍ 2B- ⟧ ) ) ⟧ ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.a):
+      𝕄(Φ.a.x):
+        𝑛·0·1 := Φ.num( φ ↦ 2A-:Δ )
+        𝑛·0·2 := Φ.dbl( v ↦ 𝑛·0·1 )
+      𝔼(L_id):
+        𝕄(Φ.a🌵0):
+          𝑛·1·1 := Φ.dbl( v ↦ 𝑛·0·1 )
+          𝕄(Φ.a🌵0.v):
+            𝑛·1·2 := Φ.num( φ ↦ 2A-:Δ )
+          𝕄(Φ.a🌵0.φ):
+            𝑛·1·3 := Φ.num( φ ↦ 2A-:Δ )
+            𝑛·1·4 := Φ.num( φ ↦ 2A-:Δ )
+            𝔼(L_twice):
+              𝔻(Φ.a🌵1):
+                𝕄(Φ.a🌵1):
+                  𝑛·2·1 := Φ.num( φ ↦ 2A-:Δ )
+                𝛿·2·1 := 2A-
+              𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+              𝑛·2·2 := Φ.num( φ ↦ 𝜎1:λ )  # 𝑛
+              𝕄(𝑛·2·2):
+                𝑛·2·3 := Φ.num( φ ↦ 𝜎1:λ )
+              𝑛·2·4 := 𝑛·2·3  # 𝕄(𝑛·2·2)
+        𝑛1·1 := 𝑛·1·1  # 𝕄(ξ.x)
+        𝑛·1·5 := 𝑛1·1  # 𝑛
+        𝑛·1·6 := 𝑛1·1  # 𝕄(𝑛·1·5)
+    𝕄(Φ.b):
+      𝕄(Φ.b.x):
+        𝑛·0·3 := Φ.num( φ ↦ 2B-:Δ )
+        𝑛·0·4 := Φ.dbl( v ↦ 𝑛·0·3 )
+      𝔼(L_id):
+        𝕄(Φ.a🌵2):
+          𝑛·3·1 := Φ.dbl( v ↦ 𝑛·0·3 )
+          𝕄(Φ.a🌵2.v):
+            𝑛·3·2 := Φ.num( φ ↦ 2B-:Δ )
+          𝕄(Φ.a🌵2.φ):
+            𝑛·3·3 := Φ.num( φ ↦ 2B-:Δ )
+            𝑛·3·4 := Φ.num( φ ↦ 2B-:Δ )
+            𝔼(L_twice):
+              𝔻(Φ.a🌵3):
+                𝕄(Φ.a🌵3):
+                  𝑛·4·1 := Φ.num( φ ↦ 2B-:Δ )
+                𝛿·4·1 := 2B-
+              𝛿1·4 := 𝛿·4·1  # 𝔻(ξ.ρ)
+              𝑛·4·2 := Φ.num( φ ↦ 𝜎2:λ )  # 𝑛
+              𝕄(𝑛·4·2):
+                𝑛·4·3 := Φ.num( φ ↦ 𝜎2:λ )
+              𝑛·4·4 := 𝑛·4·3  # 𝕄(𝑛·4·2)
+        𝑛1·3 := 𝑛·3·1  # 𝕄(ξ.x)
+        𝑛·3·5 := 𝑛1·3  # 𝑛
+        𝑛·3·6 := 𝑛1·3  # 𝕄(𝑛·3·5)
diff --git a/test-resources/evaluate-packs/walks-on-past-a-binding-it-parked-on.yaml b/test-resources/evaluate-packs/walks-on-past-a-binding-it-parked-on.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/walks-on-past-a-binding-it-parked-on.yaml
@@ -0,0 +1,24 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A deep walk under '_partial': the binding stuck on a λ function no entry
+# answers is left as it is, and the walk goes on to the binding after it
+# instead of giving the whole run up.
+symbolic: |
+  - λ: L_answers
+    𝑛: ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎 ⟧
+location: 'Q'
+deep: true
+partial: true
+input: >
+  ⟦ x ↦ ⟦ λ ⤍ L_none, ρ ↦ ∅ ⟧.foo,
+    y ↦ ⟦ λ ⤍ L_answers, ρ ↦ ∅ ⟧.foo ⟧
+result: '⟦ x ↦ L_none:λ.foo, y ↦ L_answers:λ.foo ⟧'
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.x):
+      unanswered(L_none)  # 𝕄(L_none:λ)
+    𝕄(Φ.y):
+      𝔼(L_answers):
+        𝑛·1·1 := ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎1 ⟧  # 𝑛
+        𝑛·1·2 := ⟦ foo ↦ ⟦⟧, λ ⤍ 𝜎1 ⟧  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/walks-on-past-a-firing-it-could-not-make.yaml b/test-resources/evaluate-packs/walks-on-past-a-firing-it-could-not-make.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/walks-on-past-a-firing-it-could-not-make.yaml
@@ -0,0 +1,36 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A firing the deep walk opened and could not make: the entry of 'L_root'
+# dataizes what it was applied to, that operand never comes down to data, and
+# the firing is parked the way a term 𝕄 could not reduce is. The walk goes on
+# to the binding after it instead of ending the whole run, so one argument
+# nothing can dataize taints its own binding and nothing else (#1288).
+symbolic: |
+  - λ: L_root
+    dataize:
+      𝛿1: $.v
+    𝑛: ⟦ ⟧
+location: 'Q'
+deep: true
+partial: true
+input: >
+  ⟦ root(v) ↦ ⟦ λ ⤍ L_root ⟧,
+    e1 ↦ Φ.root( ⟦ λ ⤍ L_none ⟧.foo ),
+    e2 ↦ Φ.root( ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.e1):
+      unanswered(L_none)  # 𝕄(L_none:λ)
+      𝑛·0·1 := Φ.root( v ↦ L_none:λ.foo )
+      𝔼(L_root):
+        𝔻(Φ.a🌵0):
+          𝕄(Φ.a🌵0):
+            unanswered(L_none)  # 𝕄(L_none:λ)
+        stuck(L_none)
+    𝕄(Φ.e2):
+      𝑛·0·2 := Φ.root( v ↦ 𝜎1:λ )
+      𝔼(L_root):
+        𝛿1·2 := 𝔻(𝜎1:λ)  # 𝔻(ξ.v)
+        𝑛·2·1 := ⟦⟧  # 𝑛
+        𝑛·2·2 := ⟦⟧  # 𝕄(𝑛·2·1)
diff --git a/test-resources/evaluate-packs/writes-a-symbolized-copy-of-a-term-out-in-full.yaml b/test-resources/evaluate-packs/writes-a-symbolized-copy-of-a-term-out-in-full.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/writes-a-symbolized-copy-of-a-term-out-in-full.yaml
@@ -0,0 +1,30 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A name of the protocol stands for the very term it was given to and for no
+# other. A 'symbolize' line stands the data of a term into unknowns without
+# touching what the term carries first, so the copy it binds and the term it
+# was made of open with the same 𝜎 and differ further down — here 𝑛1 carries
+# '02-' under φ where 𝑛2 carries the symbol that datum was stood into — under
+# φ, since the value of a term is reached where its φ chain ends and a datum
+# standing anywhere else is left alone (#1293). The
+# line of 𝑛2 writes the term out, since naming it after 𝑛1 would say nothing
+# was replaced on the very line that replaced something, while the '𝔻(…) == …'
+# above it says something was (#1292).
+symbolic: |
+  - λ: L_stand
+    morph:
+      𝑛1: $.b
+    symbolize:
+      𝑛2: 𝑛1
+    𝑛: ⟦ l ↦ 𝑛2 ⟧
+location: 'Q.y'
+input: '⟦ y ↦ ⟦ b ↦ ⟦ λ ⤍ 𝜎7, φ ↦ ⟦ Δ ⤍ 02- ⟧ ⟧, λ ⤍ L_stand ⟧.l ⟧'
+protocol: |
+  𝕄(Φ.y):
+    𝔼(L_stand):
+      𝑛1·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 02-:Δ ⟧  # 𝕄(ξ.b)
+      𝔻(𝜎8:λ) == 02-
+      𝑛2·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧  # 𝑛1
+      𝑛·1·1 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧:l  # 𝑛
+      𝑛·1·2 := ⟦ λ ⤍ 𝜎7, φ ↦ 𝜎8:λ ⟧:l  # 𝕄(𝑛·1·1)
diff --git a/test-resources/evaluate-packs/writes-the-binding-a-deep-firing-stands-in.yaml b/test-resources/evaluate-packs/writes-the-binding-a-deep-firing-stands-in.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/evaluate-packs/writes-the-binding-a-deep-firing-stands-in.yaml
@@ -0,0 +1,77 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# The site a firing is written under, which the '--deep' walk refines as it
+# enters a binding: both firings are of the same entry and answer the same way,
+# so the locator standing beside them is the one thing telling a reader which
+# part of the program each of them belongs to. A locator reaches a binding and
+# no further: the λ function of the first one fires on a dispatch standing
+# inside 'mark', which no locator names, so the firing is written under
+# 'Φ.foo.mark' — the binding the walk had entered.
+symbolic: |
+  - λ: L_number_plus
+    dataize:
+      𝛿1: $.ρ
+      𝛿2: $.x
+    𝑛: Φ.number( φ ↦ ⟦ λ ⤍ 𝜎 ⟧ )
+location: 'Q'
+deep: true
+input: >
+  ⟦ bytes(φ) ↦ ⟦⟧,
+    number(φ) ↦ ⟦ plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+    foo ↦ ⟦ mark ↦ 5.plus( 6 ) ⟧,
+    bar ↦ 7.plus( 8 ) ⟧
+protocol: |
+  𝕄(Φ):
+    𝕄(Φ.foo):
+      𝕄(Φ.foo.mark):
+        𝑛·0·1 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+        𝑛·0·2 := Φ.number( φ ↦ 𝑛·0·1 )
+        𝑛·0·3 := Φ.number( φ ↦ 𝑛·0·1 )
+        𝑛·0·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+        𝑛·0·5 := Φ.number( φ ↦ 𝑛·0·4 )
+        𝑛·0·6 := Φ.number( φ ↦ 𝑛·0·1 )
+        𝑛·0·7 := 𝑛·0·6.plus( x ↦ 𝑛·0·5 )
+        𝔼(L_number_plus):
+          𝔻(Φ.a🌵0):
+            𝕄(Φ.a🌵0):
+              𝑛·1·1 := Φ.number( φ ↦ 𝑛·0·1 )
+              𝑛·1·2 := Φ.bytes( φ ↦ 40-14-00-00-00-00-00-00:Δ )
+            𝛿·1·1 := 40-14-00-00-00-00-00-00  # 5.0
+          𝛿1·1 := 𝛿·1·1  # 𝔻(ξ.ρ)
+          𝔻(Φ.a🌵1):
+            𝕄(Φ.a🌵1):
+              𝑛·1·3 := Φ.number( φ ↦ 𝑛·0·4 )
+              𝑛·1·4 := Φ.bytes( φ ↦ 40-18-00-00-00-00-00-00:Δ )
+            𝛿·1·2 := 40-18-00-00-00-00-00-00  # 6.0
+          𝛿2·1 := 𝛿·1·2  # 𝔻(ξ.x)
+          𝑛·1·5 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛
+          𝕄(𝑛·1·5):
+            𝑛·1·6 := Φ.number( φ ↦ 𝜎1:λ )
+          𝑛·1·7 := 𝑛·1·6  # 𝕄(𝑛·1·5)
+    𝕄(Φ.bar):
+      𝑛·0·8 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+      𝑛·0·9 := Φ.number( φ ↦ 𝑛·0·8 )
+      𝑛·0·10 := Φ.number( φ ↦ 𝑛·0·8 )
+      𝑛·0·11 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )
+      𝑛·0·12 := Φ.number( φ ↦ 𝑛·0·11 )
+      𝑛·0·13 := Φ.number( φ ↦ 𝑛·0·8 )
+      𝑛·0·14 := 𝑛·0·13.plus( x ↦ 𝑛·0·12 )
+      𝔼(L_number_plus):
+        𝔻(Φ.a🌵2):
+          𝕄(Φ.a🌵2):
+            𝑛·2·1 := Φ.number( φ ↦ 𝑛·0·8 )
+            𝑛·2·2 := Φ.bytes( φ ↦ 40-1C-00-00-00-00-00-00:Δ )
+          𝛿·2·1 := 40-1C-00-00-00-00-00-00  # 7.0
+        𝛿1·2 := 𝛿·2·1  # 𝔻(ξ.ρ)
+        𝔻(Φ.a🌵3):
+          𝕄(Φ.a🌵3):
+            𝑛·2·3 := Φ.number( φ ↦ 𝑛·0·11 )
+            𝑛·2·4 := Φ.bytes( φ ↦ 40-20-00-00-00-00-00-00:Δ )
+          𝛿·2·2 := 40-20-00-00-00-00-00-00  # 8.0
+        𝛿2·2 := 𝛿·2·2  # 𝔻(ξ.x)
+        𝑛·2·5 := Φ.number( φ ↦ 𝜎2:λ )  # 𝑛
+        𝕄(𝑛·2·5):
+          𝑛·2·6 := Φ.number( φ ↦ 𝜎2:λ )
+        𝑛·2·7 := 𝑛·2·6  # 𝕄(𝑛·2·5)
diff --git a/test-resources/explain-packs/contextualization/ca.yaml b/test-resources/explain-packs/contextualization/ca.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/ca.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{ca}
+    \phinoPremise{ \phinoContextualize{ n_1 }{ k }{ n_2 } }
+    \phinoPremise{ \phinoContextualize{ e }{ k }{ n_3 } }
+    \phinoConclusion{ \phinoContextualize{ n_1 ( \tau -> e ) }{ k }{ n_2 ( \tau -> n_3 ) } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/caa.yaml b/test-resources/explain-packs/contextualization/caa.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/caa.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{caa}
+    \phinoPremise{ \phinoContextualize{ n_1 }{ k }{ n_2 } }
+    \phinoPremise{ \phinoContextualize{ e }{ k }{ n_3 } }
+    \phinoConclusion{ \phinoContextualize{ n_1 ( \phiTerminal{\alpha_{i}} -> e ) }{ k }{ n_2 ( \phiTerminal{\alpha_{i}} -> n_3 ) } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/cd.yaml b/test-resources/explain-packs/contextualization/cd.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/cd.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{cd}
+    \phinoPremise{ \phinoContextualize{ n_1 }{ k }{ n_2 } }
+    \phinoConclusion{ \phinoContextualize{ n_1 . \tau }{ k }{ n_2 . \tau } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/cf.yaml b/test-resources/explain-packs/contextualization/cf.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/cf.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{cf}
+    \phinoConclusion{ \phinoContextualize{ [[ B ]] }{ k }{ [[ B ]] } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/cg.yaml b/test-resources/explain-packs/contextualization/cg.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/cg.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{cg}
+    \phinoConclusion{ \phinoContextualize{ Q }{ k }{ Q } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/ct.yaml b/test-resources/explain-packs/contextualization/ct.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/ct.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{ct}
+    \phinoConclusion{ \phinoContextualize{ T }{ k }{ T } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/contextualization/cxi.yaml b/test-resources/explain-packs/contextualization/cxi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/contextualization/cxi.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoContextualizationInference}
+    \phinoName{cxi}
+    \phinoConclusion{ \phinoContextualize{ \phiTerminal{\xi} }{ k }{ k } }
+  \end{phinoContextualizationInference}
diff --git a/test-resources/explain-packs/dataization/box.yaml b/test-resources/explain-packs/dataization/box.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/dataization/box.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoDataizationInference}
+    \phinoName{box}
+    \phinoCondition{ [ D \char44{} L ] \cap \lparen B_1 \cup B_2 \rparen = \emptyset }
+    \phinoPremise{ \phinoContextualize{ e_2 }{ [[ B_1, @ -> e_2, B_2 ]] }{ e_3 } }
+    \phinoPremise{ \phinoNormalize{ e_3 }{ n } }
+    \phinoPremise{ \phinoDataize{ n }{ e_1 }{ s_1 }{ \delta }{ s_2 } }
+    \phinoConclusion{ \phinoDataize{ [[ B_1, @ -> e_2, B_2 ]] }{ e_1 }{ s_1 }{ \delta }{ s_2 } }
+  \end{phinoDataizationInference}
diff --git a/test-resources/explain-packs/dataization/delta.yaml b/test-resources/explain-packs/dataization/delta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/dataization/delta.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoDataizationInference}
+    \phinoName{delta}
+    \phinoLabel{\Delta}
+    \phinoCondition{ [ L ] \cap \lparen B_1 \cup B_2 \rparen = \emptyset }
+    \phinoConclusion{ \phinoDataize{ [[ B_1, D> \delta, B_2 ]] }{ e }{ s }{ \delta }{ s } }
+  \end{phinoDataizationInference}
diff --git a/test-resources/explain-packs/dataization/fire.yaml b/test-resources/explain-packs/dataization/fire.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/dataization/fire.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoDataizationInference}
+    \phinoName{fire}
+    \phinoCondition{ [ D ] \cap \lparen B_1 \cup B_2 \rparen = \emptyset }
+    \phinoPremise{ \phinoEvaluate{ [[ B_1, L> f, B_2 ]] }{ e }{ s_1 }{ n }{ s_2 } }
+    \phinoPremise{ \phinoDataize{ n }{ e }{ s_2 }{ \delta }{ s_3 } }
+    \phinoConclusion{ \phinoDataize{ [[ B_1, L> f, B_2 ]] }{ e }{ s_1 }{ \delta }{ s_3 } }
+  \end{phinoDataizationInference}
diff --git a/test-resources/explain-packs/dataization/none.yaml b/test-resources/explain-packs/dataization/none.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/dataization/none.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoDataizationInference}
+    \phinoName{none}
+    \phinoCondition{ [ D \char44{} L \char44{} @ ] \cap B = \emptyset }
+    \phinoPremise{ \phinoDataize{ T }{ e }{ s_1 }{ \delta }{ s_2 } }
+    \phinoConclusion{ \phinoDataize{ [[ B ]] }{ e }{ s_1 }{ \delta }{ s_2 } }
+  \end{phinoDataizationInference}
diff --git a/test-resources/explain-packs/dataization/norm.yaml b/test-resources/explain-packs/dataization/norm.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/dataization/norm.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoDataizationInference}
+    \phinoName{norm}
+    \phinoCondition{ \phinoNotFormation{ n_1 } \phiText{and} n_1 \not= T }
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoPremise{ \phinoDataize{ n_2 }{ e }{ s_2 }{ \delta }{ s_3 } }
+    \phinoConclusion{ \phinoDataize{ n_1 }{ e }{ s_1 }{ \delta }{ s_3 } }
+  \end{phinoDataizationInference}
diff --git a/test-resources/explain-packs/morphing/dead.yaml b/test-resources/explain-packs/morphing/dead.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/dead.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{dead}
+    \phinoConclusion{ \phinoMorph{ T }{ e }{ s }{ T }{ s } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/ma.yaml b/test-resources/explain-packs/morphing/ma.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/ma.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{ma}
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoPremise{ \phinoNormalize{ n_2 ( \tau -> k ) }{ n_3 } }
+    \phinoPremise{ \phinoMorph{ n_3 }{ e }{ s_2 }{ n_4 }{ s_3 } }
+    \phinoConclusion{ \phinoMorph{ n_1 ( \tau -> k ) }{ e }{ s_1 }{ n_4 }{ s_3 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/maa.yaml b/test-resources/explain-packs/morphing/maa.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/maa.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{maa}
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoPremise{ \phinoNormalize{ n_2 ( \phiTerminal{\alpha_{i}} -> k ) }{ n_3 } }
+    \phinoPremise{ \phinoMorph{ n_3 }{ e }{ s_2 }{ n_4 }{ s_3 } }
+    \phinoConclusion{ \phinoMorph{ n_1 ( \phiTerminal{\alpha_{i}} -> k ) }{ e }{ s_1 }{ n_4 }{ s_3 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/maad.yaml b/test-resources/explain-packs/morphing/maad.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/maad.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{maad}
+    \phinoCondition{ \phinoNotAbsolute{ n_1 } }
+    \phinoPremise{ \phinoMorph{ T }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ n ( \phiTerminal{\alpha_{i}} -> n_1 ) }{ e }{ s_1 }{ n_2 }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/mad.yaml b/test-resources/explain-packs/morphing/mad.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/mad.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{mad}
+    \phinoCondition{ \phinoNotAbsolute{ n_1 } }
+    \phinoPremise{ \phinoMorph{ T }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ n ( \tau -> n_1 ) }{ e }{ s_1 }{ n_2 }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/md.yaml b/test-resources/explain-packs/morphing/md.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/md.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{md}
+    \phinoCondition{ \phinoNotFormation{ n_1 } }
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoPremise{ \phinoNormalize{ n_2 . \tau }{ n_3 } }
+    \phinoPremise{ \phinoMorph{ n_3 }{ e }{ s_2 }{ n_4 }{ s_3 } }
+    \phinoConclusion{ \phinoMorph{ n_1 . \tau }{ e }{ s_1 }{ n_4 }{ s_3 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/mf.yaml b/test-resources/explain-packs/morphing/mf.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/mf.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{mf}
+    \phinoConclusion{ \phinoMorph{ [[ B ]] }{ e }{ s }{ [[ B ]] }{ s } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/mg.yaml b/test-resources/explain-packs/morphing/mg.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/mg.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{mg}
+    \phinoPremise{ \phinoMorph{ T }{ Q }{ s_1 }{ n }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ Q }{ Q }{ s_1 }{ n }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/ml.yaml b/test-resources/explain-packs/morphing/ml.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/ml.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{ml}
+    \phinoLabel{\lambda}
+    \phinoPremise{ \phinoEvaluate{ [[ B_1, L> f, B_2 ]] }{ e }{ s_1 }{ n_1 }{ s_2 } }
+    \phinoPremise{ \phinoNormalize{ n_1 . \tau }{ n_2 } }
+    \phinoPremise{ \phinoMorph{ n_2 }{ e }{ s_2 }{ n_3 }{ s_3 } }
+    \phinoConclusion{ \phinoMorph{ [[ B_1, L> f, B_2 ]] . \tau }{ e }{ s_1 }{ n_3 }{ s_3 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/mphi.yaml b/test-resources/explain-packs/morphing/mphi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/mphi.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{mphi}
+    \phinoLabel{\varphi}
+    \phinoCondition{ @ \in B \phiText{and} [ \tau \char44{} L ] \cap B = \emptyset }
+    \phinoPremise{ \phinoNormalize{ [[ B ]] . @ . \tau }{ n_1 } }
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ [[ B ]] . \tau }{ e }{ s_1 }{ n_2 }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/universe.yaml b/test-resources/explain-packs/morphing/universe.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/universe.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{universe}
+    \phinoLabel{\Phi}
+    \phinoCondition{ e \not= Q }
+    \phinoPremise{ \phinoNormalize{ e }{ n_1 } }
+    \phinoPremise{ \phinoMorph{ n_1 }{ e }{ s_1 }{ n_2 }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ Q }{ e }{ s_1 }{ n_2 }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/morphing/xi.yaml b/test-resources/explain-packs/morphing/xi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/morphing/xi.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \begin{phinoMorphingInference}
+    \phinoName{xi}
+    \phinoPremise{ \phinoMorph{ T }{ e }{ s_1 }{ n }{ s_2 } }
+    \phinoConclusion{ \phinoMorph{ \phiTerminal{\xi} }{ e }{ s_1 }{ n }{ s_2 } }
+  \end{phinoMorphingInference}
diff --git a/test-resources/explain-packs/normalize/alpha.yaml b/test-resources/explain-packs/normalize/alpha.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/alpha.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{alpha}
+    { [[ B_1, \tau -> ?, B_2 ]] ( \phiTerminal{\alpha_{i}} -> e ) }
+    { [[ B_1, \tau -> ?, B_2 ]] ( \tau -> e ) }
+    { i = \vert \overline{ B_1 } \vert \phiText{and} \tau \not= \phiTerminal{\rho} }
+    { }
diff --git a/test-resources/explain-packs/normalize/amiss.yaml b/test-resources/explain-packs/normalize/amiss.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/amiss.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{amiss}
+    { [[ B ]] ( \phiTerminal{\alpha_{i}} -> e ) }
+    { T }
+    { \vert \overline{ B } \vert \leq i }
+    { }
diff --git a/test-resources/explain-packs/normalize/copy.yaml b/test-resources/explain-packs/normalize/copy.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/copy.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{copy}
+    { [[ B_1, \tau -> ?, B_2 ]] ( \tau -> k ) }
+    { [[ B_1, \tau -> k, B_2 ]] }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/dc.yaml b/test-resources/explain-packs/normalize/dc.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/dc.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{dc}
+    { T ( \tau -> e ) }
+    { T }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/dca.yaml b/test-resources/explain-packs/normalize/dca.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/dca.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{dca}
+    { T ( \phiTerminal{\alpha_{i}} -> e ) }
+    { T }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/dd.yaml b/test-resources/explain-packs/normalize/dd.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/dd.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{dd}
+    { T . \tau }
+    { T }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/dl.yaml b/test-resources/explain-packs/normalize/dl.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/dl.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{dl}
+    { [[ B_1, L> f, B_2 ]] }
+    { T }
+    { D \in \lparen B_1 \cup B_2 \rparen }
+    { }
diff --git a/test-resources/explain-packs/normalize/dot.yaml b/test-resources/explain-packs/normalize/dot.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/dot.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{dot}
+    { [[ B_1, \tau -> n, B_2 ]] . \tau }
+    { e_1 ( \phiTerminal{\rho} -> e_2 ) }
+    { [ D \char44{} L ] \not\subseteq \lparen B_1 \cup B_2 \rparen }
+    { \phinoContextualize{ n }{ [[ B_1, B_2 ]] }{ e_1 } \phiText{and} \phinoNamed{ e_2 }{ [[ B_1, \tau -> n, B_2 ]] } }
diff --git a/test-resources/explain-packs/normalize/miss.yaml b/test-resources/explain-packs/normalize/miss.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/miss.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{miss}
+    { [[ B ]] ( \tau -> e ) }
+    { T }
+    { \tau \notin B \phiText{and} \tau \not= \phiTerminal{\rho} }
+    { }
diff --git a/test-resources/explain-packs/normalize/norho.yaml b/test-resources/explain-packs/normalize/norho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/norho.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{norho}
+    { [[ B ]] . \phiTerminal{\rho} }
+    { T }
+    { \phiTerminal{\rho} \notin B }
+    { }
diff --git a/test-resources/explain-packs/normalize/null.yaml b/test-resources/explain-packs/normalize/null.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/null.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{null}
+    { [[ B_1, \tau -> ?, B_2 ]] . \tau }
+    { T }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/over.yaml b/test-resources/explain-packs/normalize/over.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/over.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{over}
+    { [[ B_1, \tau -> e_1, B_2 ]] ( \tau -> e_2 ) }
+    { T }
+    { \tau \not= \phiTerminal{\rho} }
+    { }
diff --git a/test-resources/explain-packs/normalize/overa.yaml b/test-resources/explain-packs/normalize/overa.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/overa.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{overa}
+    { [[ B_1, \tau -> e_1, B_2 ]] ( \phiTerminal{\alpha_{i}} -> e_2 ) }
+    { T }
+    { i = \vert \overline{ B_1 } \vert \phiText{and} \tau \not= \phiTerminal{\rho} }
+    { }
diff --git a/test-resources/explain-packs/normalize/skip.yaml b/test-resources/explain-packs/normalize/skip.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/skip.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{skip}
+    { [[ B ]] ( \phiTerminal{\rho} -> e ) }
+    { [[ B ]] }
+    { \phiTerminal{\rho} \notin B }
+    { }
diff --git a/test-resources/explain-packs/normalize/stay.yaml b/test-resources/explain-packs/normalize/stay.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/stay.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{stay}
+    { [[ B_1, \phiTerminal{\rho} -> e_1, B_2 ]] ( \phiTerminal{\rho} -> e_2 ) }
+    { [[ B_1, \phiTerminal{\rho} -> e_1, B_2 ]] }
+    { }
+    { }
diff --git a/test-resources/explain-packs/normalize/stop.yaml b/test-resources/explain-packs/normalize/stop.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/explain-packs/normalize/stop.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+latex: |-
+  \phinoNormalizationRule{stop}
+    { [[ B ]] . \tau }
+    { T }
+    { [ \tau \char44{} @ \char44{} L ] \cap B = \emptyset }
+    { }
diff --git a/test-resources/filter-packs/complex.yaml b/test-resources/filter-packs/complex.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/complex.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: |
+  [[
+    org -> [[ eolang -> [[ number -> [[]], bytes -> [[ ]] ]] ]], some -> Q
+  ]]
+hidden: ['Q.org']
+shown: []
+result: 'Φ:some'
diff --git a/test-resources/filter-packs/org-eolang.yaml b/test-resources/filter-packs/org-eolang.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/org-eolang.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ org -> [[ eolang -> [[]] ]] ]]'
+hidden: ['Q.org.eolang']
+shown: []
+result: '⟦⟧:org'
diff --git a/test-resources/filter-packs/show-hide.yaml b/test-resources/filter-packs/show-hide.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/show-hide.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+expression: '[[ org -> [[ eolang -> [[ number -> [[ ]], bytes -> [[ ]], string -> [[ ]] ]] ]], xyz -> [[ ]] ]]'
+shown: ['Q.org.eolang']
+hidden: ['Q.org.eolang.bytes', 'Q.org.eolang.string']
+result: '⟦⟧:number:eolang:org'
diff --git a/test-resources/filter-packs/show.yaml b/test-resources/filter-packs/show.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/show.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+expression: '[[ org -> [[ eolang -> [[ number -> [[ ]], bytes -> [[ ]], string -> [[ ]] ]] ]], xyz -> [[ abc -> [[ ]] ]] ]]'
+shown: ['Q.org.eolang.number']
+hidden: []
+result: '⟦⟧:number:eolang:org'
diff --git a/test-resources/filter-packs/simple.yaml b/test-resources/filter-packs/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/simple.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '[[ org -> Q.x ]]'
+hidden: ['Q.org']
+shown: []
+result: '⟦⟧'
diff --git a/test-resources/filter-packs/with-attributes.yaml b/test-resources/filter-packs/with-attributes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/with-attributes.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: '[[ t -> [[ xi🌵 -> ?, q -> ?, a -> ?, x -> [[ ]], k -> [[ xi🌵 -> ? ]] ]] ]]'
+hidden: ['Q.t.k.xi🌵']
+shown: []
+result: '⟦ t(xi🌵, q, a) ↦ ⟦ x ↦ ⟦⟧, k ↦ ⟦⟧ ⟧ ⟧'
diff --git a/test-resources/filter-packs/with-void.yaml b/test-resources/filter-packs/with-void.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/filter-packs/with-void.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+expression: '⟦ order ↦ ⟦ qty ↦ ∅, json ↦ ⟦ xi🌵 ↦ ξ ⟧ ⟧ ⟧'
+hidden: ['Q.order.json.xi🌵']
+shown: []
+result: '⟦ order(qty) ↦ ⟦ json ↦ ⟦⟧ ⟧ ⟧'
diff --git a/test-resources/latex-packs/delta-and-phi.yaml b/test-resources/latex-packs/delta-and-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/delta-and-phi.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ @ -> Q.x, y -> [[ D> 01-02 ]] ]]
+result: |-
+  \begin{phiquation}
+  [[ @ -> Q . |x|, |y| -> |01-02| : D ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/dispatch.yaml b/test-resources/latex-packs/dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/dispatch.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  Q.x.y.z
+result: |-
+  \begin{phiquation}
+  Q . |x| . |y| . |z|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/escaped-labels.yaml b/test-resources/latex-packs/escaped-labels.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/escaped-labels.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ my_attr -> Q.a@b ]]
+result: |-
+  \begin{phiquation}
+  Q . |a\char64{}b| : |my\char95{}attr|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/escaped-strings.yaml b/test-resources/latex-packs/escaped-strings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/escaped-strings.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> "a_b $c% &d #e ^g {h} @i ~j \\k" ]]
+result: |-
+  \begin{phiquation}
+  "a\char95{}b \char36{}c\char37{} \char38{}d \char35{}e \char94{}g \char123{}h\char125{} \char64{}i \char126{}j \char92{}\char92{}k" : |x|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/formation-with-voids.yaml b/test-resources/latex-packs/formation-with-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/formation-with-voids.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ foo(a, b) -> [[ @ -> $.a ]] ]]
+result: |-
+  \begin{phiquation}
+  [[ |foo|(|a|, |b|) -> |a| : @ ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/fractional-numbers.yaml b/test-resources/latex-packs/fractional-numbers.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/fractional-numbers.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> 3.14, y -> -2.5 ]]
+result: |-
+  \begin{phiquation}
+  [[ |x| -> |3.14|, |y| -> |-2.5| ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/lambda-and-void.yaml b/test-resources/latex-packs/lambda-and-void.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/lambda-and-void.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ L> Fn_plus, x -> ? ]]
+result: |-
+  \begin{phiquation}
+  [[ L> |Fn\char95{}plus|, |x| -> ? ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/named-application.yaml b/test-resources/latex-packs/named-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/named-application.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ f -> Q.foo(x -> Q.a, y -> $) ]]
+result: |-
+  \begin{phiquation}
+  Q . |foo| ( |x| -> Q . |a|, |y| -> \phiTerminal{\xi} ) : |f|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/non-ascii-label.yaml b/test-resources/latex-packs/non-ascii-label.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/non-ascii-label.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ t🌵1 -> Q ]]
+result: |-
+  \begin{phiquation}
+  Q : |t[U+1F335]1|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/one-binding-sugar.yaml b/test-resources/latex-packs/one-binding-sugar.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/one-binding-sugar.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> [[ y -> 42 ]] ]]
+result: |-
+  \begin{phiquation}
+  |42| : |y| : |x|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/phi-in-label.yaml b/test-resources/latex-packs/phi-in-label.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/phi-in-label.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ nameφ -> Q ]]
+result: |-
+  \begin{phiquation}
+  Q : |name\phiTerminal{\varphi}|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/positional-application.yaml b/test-resources/latex-packs/positional-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/positional-application.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ f -> Q.foo(Q.a, Q.b) ]]
+result: |-
+  \begin{phiquation}
+  Q . |foo| ( Q . |a|, Q . |b| ) : |f|{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/primitives.yaml b/test-resources/latex-packs/primitives.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/primitives.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ n -> 42, s -> "hello" ]]
+result: |-
+  \begin{phiquation}
+  [[ |n| -> |42|, |s| -> "hello" ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/rho-and-xi.yaml b/test-resources/latex-packs/rho-and-xi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/rho-and-xi.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  [[ x -> $.^.y, z -> $.w ]]
+result: |-
+  \begin{phiquation}
+  [[ |x| -> \phiTerminal{\rho} . |y|, |z| -> |w| ]]{.}
+  \end{phiquation}
diff --git a/test-resources/latex-packs/termination.yaml b/test-resources/latex-packs/termination.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/latex-packs/termination.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+expression: |
+  T
+result: |-
+  \begin{phiquation}
+  T{.}
+  \end{phiquation}
diff --git a/test-resources/merge-packs/absorbs-an-empty-formation.yaml b/test-resources/merge-packs/absorbs-an-empty-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/absorbs-an-empty-formation.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ ]]'
+right: '[[ x -> 1 ]]'
+result: '1:x'
diff --git a/test-resources/merge-packs/collapses-identical-bindings.yaml b/test-resources/merge-packs/collapses-identical-bindings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/collapses-identical-bindings.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> 1 ]]'
+right: '[[ x -> 1 ]]'
+result: '1:x'
diff --git a/test-resources/merge-packs/collapses-identical-deltas.yaml b/test-resources/merge-packs/collapses-identical-deltas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/collapses-identical-deltas.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ D> 42-, x -> [[ ]] ]]'
+right: '[[ D> 42-, y -> [[ ]] ]]'
+result: '⟦ Δ ⤍ 42-, x ↦ ⟦⟧, y ↦ ⟦⟧ ⟧'
diff --git a/test-resources/merge-packs/collapses-identical-voids.yaml b/test-resources/merge-packs/collapses-identical-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/collapses-identical-voids.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> ? ]]'
+right: '[[ x -> ? ]]'
+result: '∅:x'
diff --git a/test-resources/merge-packs/drops-the-package-lambda-from-an-object.yaml b/test-resources/merge-packs/drops-the-package-lambda-from-an-object.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/drops-the-package-lambda-from-an-object.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ bytes -> [[ @ -> ?, ^ -> ? ]] ]]'
+right: |
+  [[ bytes -> [[
+    as-bytes -> [[ ^ -> ?, @ -> $.^ ]], L> Package, ^ -> ?
+  ]] ]]
+result: '⟦ bytes(φ) ↦ ⟦ as-bytes(ρ) ↦ ρ:φ, ρ ↦ ∅ ⟧ ⟧'
diff --git a/test-resources/merge-packs/drops-the-package-lambda-from-the-left-side-too.yaml b/test-resources/merge-packs/drops-the-package-lambda-from-the-left-side-too.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/drops-the-package-lambda-from-the-left-side-too.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: |
+  [[ bytes -> [[
+    as-bytes -> [[ ^ -> ?, @ -> $.^ ]], L> Package, ^ -> ?
+  ]] ]]
+right: '[[ bytes -> [[ @ -> ?, ^ -> ? ]] ]]'
+result: '⟦ as-bytes(ρ) ↦ ρ:φ, φ ↦ ∅, ρ ↦ ∅ ⟧:bytes'
diff --git a/test-resources/merge-packs/fails-deep-inside-a-formation.yaml b/test-resources/merge-packs/fails-deep-inside-a-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-deep-inside-a-formation.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> [[ y -> Q ]] ]]'
+right: '[[ x -> [[ y -> $ ]] ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/fails-on-a-formation-against-a-dispatch.yaml b/test-resources/merge-packs/fails-on-a-formation-against-a-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-a-formation-against-a-dispatch.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> [[ ]] ]]'
+right: '[[ x -> Q ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/fails-on-a-non-formation.yaml b/test-resources/merge-packs/fails-on-a-non-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-a-non-formation.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: 'Q'
+right: '$'
+fails: 'Invalid expression format'
diff --git a/test-resources/merge-packs/fails-on-a-void-against-a-value.yaml b/test-resources/merge-packs/fails-on-a-void-against-a-value.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-a-void-against-a-value.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> ? ]]'
+right: '[[ x -> 1 ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/fails-on-conflicting-deltas.yaml b/test-resources/merge-packs/fails-on-conflicting-deltas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-conflicting-deltas.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ D> 42- ]]'
+right: '[[ D> 13- ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/fails-on-conflicting-lambdas.yaml b/test-resources/merge-packs/fails-on-conflicting-lambdas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-conflicting-lambdas.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ L> Atom ]]'
+right: '[[ L> Other ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/fails-on-conflicting-values.yaml b/test-resources/merge-packs/fails-on-conflicting-values.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/fails-on-conflicting-values.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> 1 ]]'
+right: '[[ x -> 2 ]]'
+fails: "Can't merge two bindings, conflict found"
diff --git a/test-resources/merge-packs/keeps-a-shared-void-in-its-place.yaml b/test-resources/merge-packs/keeps-a-shared-void-in-its-place.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/keeps-a-shared-void-in-its-place.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ f -> [[ x -> ?, y -> ?, @ -> $.x ]] ]]'
+right: '[[ f -> [[ x -> ? ]] ]]'
+result: '⟦ f ↦ ⟦ x ↦ ∅, y ↦ ∅, φ ↦ ξ.x ⟧ ⟧'
diff --git a/test-resources/merge-packs/keeps-the-order-of-the-right-side-voids.yaml b/test-resources/merge-packs/keeps-the-order-of-the-right-side-voids.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/keeps-the-order-of-the-right-side-voids.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ f -> [[ x -> ? ]] ]]'
+right: '[[ f -> [[ x -> ?, y -> ?, @ -> $.x ]] ]]'
+result: '⟦ f ↦ ⟦ x ↦ ∅, y ↦ ∅, φ ↦ ξ.x ⟧ ⟧'
diff --git a/test-resources/merge-packs/keeps-the-package-lambda-between-two-packages.yaml b/test-resources/merge-packs/keeps-the-package-lambda-between-two-packages.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/keeps-the-package-lambda-between-two-packages.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ org -> [[ number -> [[ ]], L> Package, ^ -> ? ]] ]]'
+right: '[[ org -> [[ text -> [[ ]], L> Package, ^ -> ? ]] ]]'
+result: '⟦ number ↦ ⟦⟧, text ↦ ⟦⟧, λ ⤍ Package, ρ ↦ ∅ ⟧:org'
diff --git a/test-resources/merge-packs/puts-a-right-side-void-before-the-shared-one.yaml b/test-resources/merge-packs/puts-a-right-side-void-before-the-shared-one.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/puts-a-right-side-void-before-the-shared-one.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ f -> [[ y -> ? ]] ]]'
+right: '[[ f -> [[ x -> ?, y -> ? ]] ]]'
+result: '⟦ f ↦ ⟦ x ↦ ∅, y ↦ ∅ ⟧ ⟧'
diff --git a/test-resources/merge-packs/puts-united-bindings-after-untouched-ones.yaml b/test-resources/merge-packs/puts-united-bindings-after-untouched-ones.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/puts-united-bindings-after-untouched-ones.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ a -> 1, k -> [[ p -> 1 ]] ]]'
+right: '[[ b -> 2, k -> [[ q -> 2 ]] ]]'
+result: '⟦ a ↦ 1, b ↦ 2, k ↦ ⟦ p ↦ 1, q ↦ 2 ⟧ ⟧'
diff --git a/test-resources/merge-packs/unites-disjoint-bindings.yaml b/test-resources/merge-packs/unites-disjoint-bindings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/unites-disjoint-bindings.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> 1 ]]'
+right: '[[ y -> 2 ]]'
+result: '⟦ x ↦ 1, y ↦ 2 ⟧'
diff --git a/test-resources/merge-packs/unites-nested-formations.yaml b/test-resources/merge-packs/unites-nested-formations.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/unites-nested-formations.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ x -> [[ y -> 1 ]] ]]'
+right: '[[ x -> [[ z -> 2 ]] ]]'
+result: '⟦ y ↦ 1, z ↦ 2 ⟧:x'
diff --git a/test-resources/merge-packs/unites-package-trees.yaml b/test-resources/merge-packs/unites-package-trees.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/merge-packs/unites-package-trees.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+left: '[[ org -> [[ eolang -> [[ number -> [[ ]] ]] ]] ]]'
+right: '[[ org -> [[ eolang -> [[ bytes -> [[ ]] ]] ]] ]]'
+result: '⟦ number ↦ ⟦⟧, bytes ↦ ⟦⟧ ⟧:eolang:org'
diff --git a/test-resources/morph-deep-packs/dont-fire-a-lambda-an-unapplied-rho-filled-with-bottom.yaml b/test-resources/morph-deep-packs/dont-fire-a-lambda-an-unapplied-rho-filled-with-bottom.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/dont-fire-a-lambda-an-unapplied-rho-filled-with-bottom.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same shape with nothing around the unapplied formation: the ⊥ still
+# fills the only void of 'plus', so 'saturated' would let it fire (#1196).
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ a -> [[ @ -> 5.plus( $.^ ) ]] ]]'
+result: '5.plus( ρ ):φ:a'
diff --git a/test-resources/morph-deep-packs/fails-on-a-stuck-atom-deep-on-a-spine.yaml b/test-resources/morph-deep-packs/fails-on-a-stuck-atom-deep-on-a-spine.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/fails-on-a-stuck-atom-deep-on-a-spine.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An atom deeper on a binding's spine gets stuck exactly as it does under 𝕄
+# alone: the run fails, unless '_partial' parks it.
+input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'
+fails: "No entry of --symbolic answers the λ function 'Sym_arg_0'"
diff --git a/test-resources/morph-deep-packs/fires-the-atom-nested-in-an-argument.yaml b/test-resources/morph-deep-packs/fires-the-atom-nested-in-an-argument.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/fires-the-atom-nested-in-an-argument.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The λ function nested in the argument fires before the one around it, so
+# the symbol standing for the whole sum is the second the run minted.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ x -> 5.plus( 6.plus( 7 ) ) ]]'
+result: |-
+  ⟦
+    φ ↦ 𝜎2:λ,
+    as-bytes ↦ φ,
+    plus(ρ, x) ↦ L_number_plus:λ,
+    times(ρ, x) ↦ L_number_times:λ,
+    div(ρ, x) ↦ L_number_div:λ,
+    gt(ρ, x) ↦ L_number_gt:λ,
+    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,
+    nope(ρ) ↦ L_number_nope:λ
+  ⟧:x
diff --git a/test-resources/morph-deep-packs/keeps-the-answer-of-the-last-atom-in-a-chain.yaml b/test-resources/morph-deep-packs/keeps-the-answer-of-the-last-atom-in-a-chain.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/keeps-the-answer-of-the-last-atom-in-a-chain.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Two firings down a chain of dispatches, and what stands is the symbol the
+# last of them minted: the first is what the second brought down.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ x -> 5.plus( 6 ).plus( 7 ) ]]'
+result: |-
+  ⟦
+    φ ↦ 𝜎2:λ,
+    as-bytes ↦ φ,
+    plus(ρ, x) ↦ L_number_plus:λ,
+    times(ρ, x) ↦ L_number_times:λ,
+    div(ρ, x) ↦ L_number_div:λ,
+    gt(ρ, x) ↦ L_number_gt:λ,
+    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,
+    nope(ρ) ↦ L_number_nope:λ
+  ⟧:x
diff --git a/test-resources/morph-deep-packs/leaves-a-lambda-no-entry-answers-alone.yaml b/test-resources/morph-deep-packs/leaves-a-lambda-no-entry-answers-alone.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/leaves-a-lambda-no-entry-answers-alone.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# No entry of the file answers 'L_number_nope', so there is nothing to fire
+# and the binding keeps the name it was written under.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ x -> 5.nope ]]'
+result: '5.nope:x'
diff --git a/test-resources/morph-deep-packs/leaves-a-lambda-still-waiting-for-arguments-alone.yaml b/test-resources/morph-deep-packs/leaves-a-lambda-still-waiting-for-arguments-alone.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/leaves-a-lambda-still-waiting-for-arguments-alone.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A λ-formation whose bindings are still void is a method waiting to be
+# applied, not an application waiting to be computed: nothing demands one,
+# so 𝔻 never meets one, while the walk meets every one the object model
+# declares. Both the void ρ of 'not' and the void 'b' of 'eq' keep their
+# atoms unfired here.
+location: 'Q.bytes'
+model: true
+symbolic: true
+input: '[[ ]]'
+result: '⟦ φ ↦ ∅, not(ρ) ↦ L_bytes_not:λ, eq(ρ, b) ↦ L_bytes_eq:λ ⟧'
diff --git a/test-resources/morph-deep-packs/leaves-an-unapplied-formation-reaching-its-rho-alone.yaml b/test-resources/morph-deep-packs/leaves-an-unapplied-formation-reaching-its-rho-alone.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/leaves-an-unapplied-formation-reaching-its-rho-alone.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation standing unapplied inside its parent still holds ρ ↦ ∅, so the
+# ξ.ρ its body reaches is an input the next dispatch supplies, not ⊥. The
+# walk reduces that body in the formation's own scope, where ρ is void, and
+# must not hand the ⊥ that comes back to the λ of 'plus' (#1196).
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ foo -> [[ n -> ?, a -> [[ @ -> 5.plus( $.^ ) ]] ]] ]]'
+result: '⟦ foo(n) ↦ 5.plus( ρ ):φ:a ⟧'
diff --git a/test-resources/morph-deep-packs/parks-a-stuck-atom-deep-on-a-spine.yaml b/test-resources/morph-deep-packs/parks-a-stuck-atom-deep-on-a-spine.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/parks-a-stuck-atom-deep-on-a-spine.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same stuck atom under '_partial': the binding stays as it was written
+# and the walk goes on.
+partial: true
+input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'
+result: 'Sym_arg_0:λ.foo:x'
diff --git a/test-resources/morph-deep-packs/resolves-a-xi-against-the-formation-holding-it.yaml b/test-resources/morph-deep-packs/resolves-a-xi-against-the-formation-holding-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/resolves-a-xi-against-the-formation-holding-it.yaml
@@ -0,0 +1,23 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The ξ of a binding reaches the formation the binding sits in, so 'n' is
+# what the firing brings down, and a symbol stands where its answer belongs.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ n -> 5, x -> $.n.plus( 6 ) ]]'
+result: |-
+  ⟦
+    n ↦ 5,
+    x ↦ ⟦
+      φ ↦ 𝜎1:λ,
+      as-bytes ↦ φ,
+      plus(ρ, x) ↦ L_number_plus:λ,
+      times(ρ, x) ↦ L_number_times:λ,
+      div(ρ, x) ↦ L_number_div:λ,
+      gt(ρ, x) ↦ L_number_gt:λ,
+      eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,
+      nope(ρ) ↦ L_number_nope:λ
+    ⟧
+  ⟧
diff --git a/test-resources/morph-deep-packs/stands-the-answer-of-a-bare-lambda-in-its-place.yaml b/test-resources/morph-deep-packs/stands-the-answer-of-a-bare-lambda-in-its-place.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/stands-the-answer-of-a-bare-lambda-in-its-place.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 'mf' hands a bare saturated λ-formation back and leaves firing it to 𝔻;
+# the walk fires it and stands the symbol it answered with in its place.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ x -> 5.plus( 6 ) ]]'
+result: |-
+  ⟦
+    φ ↦ 𝜎1:λ,
+    as-bytes ↦ φ,
+    plus(ρ, x) ↦ L_number_plus:λ,
+    times(ρ, x) ↦ L_number_times:λ,
+    div(ρ, x) ↦ L_number_div:λ,
+    gt(ρ, x) ↦ L_number_gt:λ,
+    eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,
+    nope(ρ) ↦ L_number_nope:λ
+  ⟧:x
diff --git a/test-resources/morph-deep-packs/walks-into-the-argument-of-an-atom-it-cannot-fire.yaml b/test-resources/morph-deep-packs/walks-into-the-argument-of-an-atom-it-cannot-fire.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-deep-packs/walks-into-the-argument-of-an-atom-it-cannot-fire.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Nothing demands the argument of a λ function that cannot fire, so 𝔻 never
+# reaches it; the walk does, and the λ function around it stays in place.
+location: 'Q.@'
+model: true
+symbolic: true
+input: '[[ x -> [[ y -> ?, L> L_bar ]]( y -> 6.plus( 7 ) ) ]]'
+result: |-
+  ⟦ y ↦ ∅, λ ⤍ L_bar ⟧(
+    y ↦ ⟦
+      φ ↦ 𝜎1:λ,
+      as-bytes ↦ φ,
+      plus(ρ, x) ↦ L_number_plus:λ,
+      times(ρ, x) ↦ L_number_times:λ,
+      div(ρ, x) ↦ L_number_div:λ,
+      gt(ρ, x) ↦ L_number_gt:λ,
+      eq(ρ, x) ↦ ρ.as-bytes.eq( x.as-bytes ):φ,
+      nope(ρ) ↦ L_number_nope:λ
+    ⟧
+  ):x
diff --git a/test-resources/morph-packs/answers-bottom-on-rho-of-a-formation-without-rho.yaml b/test-resources/morph-packs/answers-bottom-on-rho-of-a-formation-without-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/answers-bottom-on-rho-of-a-formation-without-rho.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation that declares no ρ has no receiver, so asking it for one is
+# dead and does not go through its φ (#1859).
+location: 'Q.x'
+input: '[[ x -> [[ @ -> $.y, y -> [[ ]] ]].^ ]]'
+result: '⊥'
diff --git a/test-resources/morph-packs/answers-bottom-where-no-formation-is-reachable.yaml b/test-resources/morph-packs/answers-bottom-where-no-formation-is-reachable.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/answers-bottom-where-no-formation-is-reachable.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 𝕄 is total where 𝔻 is not: the 'xi' axiom morphs ξ to ⊥, so the run ends
+# with an answer rather than with a failure.
+location: 'Q.x'
+input: '[[ x -> $ ]]'
+result: '⊥'
diff --git a/test-resources/morph-packs/fails-on-a-premise-that-runs-out-of-cycles.yaml b/test-resources/morph-packs/fails-on-a-premise-that-runs-out-of-cycles.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/fails-on-a-premise-that-runs-out-of-cycles.yaml
@@ -0,0 +1,34 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The 'normalize' premise of 'mphi' unrolls the loop of 'walk' one round per
+# cycle, since the 'eq' deciding the 'if' is a λ the normalizer cannot answer,
+# so it runs out of '--max-cycles'. The unfinished term is not a normal form
+# and no morphing rule takes it, so the run names the budget it spent instead
+# of failing because no morphing rule takes it (#1496).
+location: 'Q.x'
+input: |
+  ⟦
+    tuple(length, head, tail) ↦ ⟦ empty ↦ ⟦ ⟧ ⟧,
+    map(pairs) ↦ ⟦
+      φ ↦ ctor(
+        entries ↦ pairs.length.eq( 0 ).if(
+          Φ.tuple.empty,
+          walk( tup ↦ pairs ).φ
+        )
+      ),
+      ctor(entries) ↦ ⟦ keys ↦ entries ⟧,
+      walk(ρ, tup) ↦ ⟦
+        φ ↦ tup.length.eq( 0 ).if( Φ.tuple.empty, prev ),
+        prev ↦ ρ.walk( tup ↦ tup.tail ).φ
+      ⟧
+    ⟧,
+    x ↦ Φ.map(
+      pairs ↦ Φ.tuple(
+        length ↦ L_len:λ, head ↦ L_head:λ, tail ↦ L_tail:λ
+      )
+    ).keys
+  ⟧
+fails: >-
+  Normalization did not finish before reaching the limit of cycles:
+  --max-cycles=25
diff --git a/test-resources/morph-packs/fails-on-a-stuck-atom-under-ml.yaml b/test-resources/morph-packs/fails-on-a-stuck-atom-under-ml.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/fails-on-a-stuck-atom-under-ml.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 𝕄 never fires a bare λ-formation, so only an atom sitting under a dispatch
+# (the 'ml' rule) can get stuck, and without '_partial' it fails the run.
+location: 'Q.x'
+input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'
+fails: "No entry of --symbolic answers the λ function 'Sym_arg_0'"
diff --git a/test-resources/morph-packs/fails-on-an-input-that-is-not-a-normal-form.yaml b/test-resources/morph-packs/fails-on-an-input-that-is-not-a-normal-form.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/fails-on-an-input-that-is-not-a-normal-form.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 𝕄 maps normal forms to formations, and 'morph' does not normalize what it is
+# handed. A dispatch off a formation with neither φ nor λ is not a normal form,
+# since 'dot' of normalization eats it, and no morphing rule takes it, so the
+# run tells the user so instead of failing with a bare "user error" (#1442).
+location: 'Q.t'
+input: '⟦ t ↦ ⟦ x ↦ ⟦⟧ ⟧.x ⟧'
+fails: >-
+  Morphing expects a normal form, but no morphing rule matches:
+  ⟦⟧:x.x
diff --git a/test-resources/morph-packs/hands-the-top-formation-back-untouched.yaml b/test-resources/morph-packs/hands-the-top-formation-back-untouched.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/hands-the-top-formation-back-untouched.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The Q locator aims the run at the whole program, and a formation is already
+# what 'mf' hands back.
+input: '[[ D> 00- ]]'
+result: '00-:Δ'
diff --git a/test-resources/morph-packs/names-a-nested-object-by-its-path.yaml b/test-resources/morph-packs/names-a-nested-object-by-its-path.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/names-a-nested-object-by-its-path.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An object nested in an object of the world is named by the whole path it is
+# reached by: its ρ says which object it was dispatched off, so the name is
+# found among the objects that one declares and nowhere else (#1446).
+location: 'Q.t'
+input: |
+  ⟦
+    a ↦ ⟦ ρ ↦ ∅, b ↦ ⟦ ρ ↦ ∅, c ↦ ⟦ ρ ↦ ∅ ⟧, d ↦ ⟦ Δ ⤍ 01- ⟧ ⟧ ⟧,
+    t ↦ Φ.a.b.c
+  ⟧
+result: 'Φ.a.b:ρ'
diff --git a/test-resources/morph-packs/names-a-top-level-object-in-a-rho-instead-of-copying-it.yaml b/test-resources/morph-packs/names-a-top-level-object-in-a-rho-instead-of-copying-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/names-a-top-level-object-in-a-rho-instead-of-copying-it.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A dispatch off a top-level object of the world decorates the body it reaches
+# with the object it was dispatched on, and that object is 'Φ.number'. Writing
+# it out would copy every method it declares into the term, 'sin' included,
+# though only 'plus' is ever dispatched. The world is immutable, so the ρ
+# carries the path instead, the way the whole world is written Φ, and whoever
+# reads it resolves it to the very formation that would have stood there
+# (#1446).
+location: 'Q.t'
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅, plus ↦ ⟦ ρ ↦ ∅, x ↦ ∅ ⟧, sin ↦ ⟦ ρ ↦ ∅ ⟧ ⟧,
+    t ↦ Φ.number.plus
+  ⟧
+result: '⟦ ρ ↦ Φ.number, x ↦ ∅ ⟧'
diff --git a/test-resources/morph-packs/names-an-applied-top-level-object-in-a-rho.yaml b/test-resources/morph-packs/names-an-applied-top-level-object-in-a-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/names-an-applied-top-level-object-in-a-rho.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An object of the world applied to an argument is still named, not copied:
+# the formation 'Φ.number( φ ↦ … )' morphs to is 'number' with its void filled,
+# so the ρ of a method dispatched off it carries the application as it was
+# written, and the methods 'number' declares stay out of the term (#1446).
+location: 'Q.t'
+input: |
+  ⟦
+    number ↦ ⟦ φ ↦ ∅, plus ↦ ⟦ ρ ↦ ∅, x ↦ ∅ ⟧, sin ↦ ⟦ ρ ↦ ∅ ⟧ ⟧,
+    t ↦ Φ.number( φ ↦ ⟦ Δ ⤍ 01- ⟧ ).plus
+  ⟧
+result: '⟦ ρ ↦ Φ.number( φ ↦ 01-:Δ ), x ↦ ∅ ⟧'
diff --git a/test-resources/morph-packs/names-the-universe-in-a-rho-instead-of-holding-it.yaml b/test-resources/morph-packs/names-the-universe-in-a-rho-instead-of-holding-it.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/names-the-universe-in-a-rho-instead-of-holding-it.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A dispatch off Φ decorates the body it reaches, one declaring its ρ, with
+# the object it was dispatched on, and that object is the whole program.
+# Writing it out would copy the world into the term, and again into every term
+# the body then dispatches, so the copies compound until one term weighs
+# hundreds of times what the program does. Φ is the name of that object, so
+# the ρ carries the name instead, and whoever reads it resolves it through the
+# 'universe' rule to the very formation that would have stood there (#1318).
+location: 'Q.t'
+input: '⟦ a ↦ ⟦ b ↦ ⟦ Δ ⤍ 01- ⟧, ρ ↦ ∅ ⟧, t ↦ Φ.a ⟧'
+result: '⟦ b ↦ 01-:Δ, ρ ↦ Φ ⟧'
diff --git a/test-resources/morph-packs/parks-a-premise-that-runs-out-of-cycles.yaml b/test-resources/morph-packs/parks-a-premise-that-runs-out-of-cycles.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/parks-a-premise-that-runs-out-of-cycles.yaml
@@ -0,0 +1,46 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same premise under '_partial': the run parks where 𝕄 reached, the
+# dispatch whose 'normalize' premise ran out of '--max-cycles' (#1496).
+location: 'Q.x'
+partial: true
+input: |
+  ⟦
+    tuple(length, head, tail) ↦ ⟦ empty ↦ ⟦ ⟧ ⟧,
+    map(pairs) ↦ ⟦
+      φ ↦ ctor(
+        entries ↦ pairs.length.eq( 0 ).if(
+          Φ.tuple.empty,
+          walk( tup ↦ pairs ).φ
+        )
+      ),
+      ctor(entries) ↦ ⟦ keys ↦ entries ⟧,
+      walk(ρ, tup) ↦ ⟦
+        φ ↦ tup.length.eq( 0 ).if( Φ.tuple.empty, prev ),
+        prev ↦ ρ.walk( tup ↦ tup.tail ).φ
+      ⟧
+    ⟧,
+    x ↦ Φ.map(
+      pairs ↦ Φ.tuple(
+        length ↦ L_len:λ, head ↦ L_head:λ, tail ↦ L_tail:λ
+      )
+    ).keys
+  ⟧
+result: |
+  ⟦
+    pairs ↦ Φ.tuple(
+      length ↦ L_len:λ, head ↦ L_head:λ, tail ↦ L_tail:λ
+    ),
+    φ ↦ ctor(
+      entries ↦ pairs.length.eq( 0 ).if(
+        Φ.tuple.empty,
+        walk( tup ↦ pairs ).φ
+      )
+    ),
+    ctor(entries) ↦ ⟦ keys ↦ entries ⟧,
+    walk(ρ, tup) ↦ ⟦
+      φ ↦ tup.length.eq( 0 ).if( Φ.tuple.empty, prev ),
+      prev ↦ ρ.walk( tup ↦ tup.tail ).φ
+    ⟧
+  ⟧.keys
diff --git a/test-resources/morph-packs/parks-a-stuck-atom-under-ml.yaml b/test-resources/morph-packs/parks-a-stuck-atom-under-ml.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/morph-packs/parks-a-stuck-atom-under-ml.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The same atom under '_partial': the run ends on the residue the spine had
+# reached instead of failing.
+location: 'Q.x'
+partial: true
+input: '[[ x -> [[ L> Sym_arg_0 ]].foo ]]'
+result: 'Sym_arg_0:λ.foo'
diff --git a/test-resources/parser-packs/all-the-basics.phi b/test-resources/parser-packs/all-the-basics.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/all-the-basics.phi
@@ -0,0 +1,45 @@
+⟦
+  all-the-basics -> [[
+    no-xi ↦ a.b(c ↦ d).c,
+    no-xi-in-formation ↦ ⟦
+      φ ↦ ρ,
+      t ↦ φ
+    ⟧,
+    k ↦ ⟦
+      x ↦ ξ.t (z ↦ ξ.f) . the-атрибут (p ↦ ξ.ρ.ρ.ρ.f, t ↦ ξ.ρ),
+      t ↦ ∅,
+      Δ ⤍ 42-41-40
+    ⟧.thank.you (z ↦ ξ.f) . very (z ↦ ξ.f) . much (z ↦ ξ.f),
+    self ↦ ξ,
+    φ ↦ ξ.φ,
+    error-on-purpose ↦ ⊥,
+    the-атрибут ↦ Φ.ρ,
+    hi-大家 ↦ ⟦ ⟧,
+    sss ↦ Φ.r,
+    alpha-dispatch ↦ ξ.x.y,
+    k1 ↦ ⟦ λ ⤍ Function_name_i64, first ↦ ⟦ λ ⤍ Function_name, Δ ⤍ 42- ⟧ ⟧,
+    terminator-application ↦ ⊥ (t ↦ ξ.t),
+    terminator-dispatch ↦ ⊥.t,
+    string ↦ "Hello",
+    int ↦ 42,
+    float ↦ 12.3,
+    float-with-exponent ↦ 0.36e-10,
+    string-dot ↦ "Hello".print,
+    string-application ↦ "Hello"(z ↦ ξ.f),
+    int-dot ↦ 42.plus,
+    int-application ↦ 42(z ↦ ξ.f),
+    float-dot ↦ 52.1.print,
+    float-application ↦ 52.1(z ↦ ξ.f, k ↦ ξ.i),
+    minus ↦ -0,
+    inline-application ↦ Φ.x(
+      "Hello", 42, Φ.y("world", 11.plus(12)), 42.5
+    ),
+    default-package ↦ Φ.number(42),
+    inline-voids(text, φ) ↦ ⟦ ⟧,
+    empty-inline-voids() ↦ ⟦
+      num ↦ 42
+    ⟧,
+    with-void-phi-sweet(φ) ↦ ⟦⟧,
+    with-void-phi-salty ↦ ⟦ φ ↦ ∅ ⟧
+  ]]
+⟧
diff --git a/test-resources/parser-packs/ascii-with-braces.phi b/test-resources/parser-packs/ascii-with-braces.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/ascii-with-braces.phi
@@ -0,0 +1,21 @@
+
+  [[
+    number -> [[
+      a -> ?,
+      b -> Q.org.^.@,
+      c -> $.xyz(
+        q -> Q.y,
+        ~0 -> $.a.b
+      ),
+      z -> [[
+        L> Func,
+        D> 00-,
+        def -> Q.txt.sprintf,
+        num -> 42,
+        str -> "Hello, world!"
+      ]],
+      w -> x(-42, "Hi", y, Q.io.text),
+      error -> T
+    ]]
+  ]]
+
diff --git a/test-resources/parser-packs/ascii-with-global.phi b/test-resources/parser-packs/ascii-with-global.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/ascii-with-global.phi
@@ -0,0 +1,16 @@
+[[
+  number -> [[
+    a -> ?,
+    b -> Q.org.^.@,
+    c -> $.xyz(
+      q -> Q.y,
+      ~0 -> $.a.b
+    ),
+    z -> [[
+      L> Func,
+      D> 00-,
+      def -> Q.txt.sprintf
+    ]],
+    error -> T
+  ]]
+]]
diff --git a/test-resources/parser-packs/inline-voids.phi b/test-resources/parser-packs/inline-voids.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/inline-voids.phi
@@ -0,0 +1,1 @@
+⟦ k(text, φ) ↦ ⟦ ⟧⟧
diff --git a/test-resources/parser-packs/primitive.phi b/test-resources/parser-packs/primitive.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/primitive.phi
@@ -0,0 +1,1 @@
+⟦ k ↦ ⟦ Δ ⤍ 42- ⟧ ⟧
diff --git a/test-resources/parser-packs/salty-fibo.phi b/test-resources/parser-packs/salty-fibo.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/salty-fibo.phi
@@ -0,0 +1,41 @@
+
+  ⟦
+    eo ↦ ⟦
+      example ↦ ⟦
+        fibonacci ↦ ⟦
+          n ↦ ∅,
+          φ ↦ ξ.n.lt(
+            α0 ↦ Φ.number(
+              α0 ↦ Φ.bytes(
+                α0 ↦ ⟦ Δ ⤍ 40-00-00-00-00-00-00-00 ⟧
+              )
+            )
+          ).if(
+            α0 ↦ ξ.n,
+            α1 ↦ Φ.eo.example.fibonacci(
+              α0 ↦ ξ.n.minus(
+                α0 ↦ Φ.number(
+                  α0 ↦ Φ.bytes(
+                    α0 ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧
+                  )
+                )
+              )
+            ).plus(
+              α0 ↦ Φ.eo.example.fibonacci(
+                α0 ↦ ξ.n.minus(
+                  α0 ↦ Φ.number(
+                    α0 ↦ Φ.bytes(
+                      α0 ↦ ⟦ Δ ⤍ 40-00-00-00-00-00-00-00 ⟧
+                    )
+                  )
+                )
+              )
+            )
+          )
+        ⟧,
+        λ ⤍ Package
+      ⟧,
+      λ ⤍ Package
+    ⟧
+  ⟧
+
diff --git a/test-resources/parser-packs/simple.phi b/test-resources/parser-packs/simple.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/simple.phi
@@ -0,0 +1,1 @@
+[[foo ↦ Q.org.eolang]]
diff --git a/test-resources/parser-packs/sweet-fibo.phi b/test-resources/parser-packs/sweet-fibo.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/sweet-fibo.phi
@@ -0,0 +1,16 @@
+⟦
+  eo ↦ ⟦
+    example ↦ ⟦
+      fibonacci(n) ↦ ⟦
+        φ ↦ n.lt(2).if(
+          n,
+          Φ.eo.example.fibonacci(n.minus(1)).plus(
+            Φ.eo.example.fibonacci(n.minus(2))
+          )
+        )
+      ⟧,
+      λ ⤍ Package
+    ⟧,
+    λ ⤍ Package
+  ⟧
+⟧
diff --git a/test-resources/parser-packs/virtual-map.phi b/test-resources/parser-packs/virtual-map.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/parser-packs/virtual-map.phi
@@ -0,0 +1,65 @@
+⟦
+  j$Foo ↦ ⟦
+    φ ↦ Φ.jeo.class,
+    version ↦ 65,
+    access ↦ 33,
+    supername ↦ "java/lang/Object",
+    interfaces ↦ ⟦
+      φ ↦ Φ.jeo.seq.of0
+    ⟧,
+    name ↦ "Foo",
+    j$main ↦ ⟦
+      φ ↦ Φ.jeo.method,
+      access ↦ 9,
+      descriptor ↦ "([Ljava/lang/String;)V",
+      signature ↦ "",
+      maxs ↦ ⟦
+        φ ↦ Φ.jeo.maxs,
+        val26 ↦ 7,
+        val27 ↦ 4
+      ⟧,
+      params ↦ ⟦
+        φ ↦ Φ.jeo.params,
+        arg0 ↦ ⟦
+          φ ↦ Φ.jeo.param,
+          index ↦ 0,
+          access ↦ 0,
+          type ↦ "[Ljava/lang/String;"
+        ⟧
+      ⟧,
+      body ↦ ⟦
+        φ ↦ Φ.jeo.seq.of50,
+        instruction30 ↦ ⟦
+          φ ↦ Φ.jeo.opcode.iconst_5,
+          val31 ↦ 8
+        ⟧,
+        instruction178 ↦ ⟦
+          φ ↦ Φ.hone.map,
+          opcode ↦ "invokestatic",
+          class ↦ "Foo",
+          target-handle ↦ 6,
+          target-class ↦ "Foo",
+          target-method ↦ "lambda$main$0",
+          target-signature ↦ "(Ljava/lang/Object;)Ljava/lang/Integer;",
+          bridge-input ↦ "Ljava/lang/Integer;",
+          bridge-output ↦ "Ljava/lang/Integer;",
+          accepted ↦ "Ljava/lang/Integer;",
+          returned ↦ "Ljava/lang/Integer;"
+        ⟧,
+        instruction179 ↦ ⟦
+          φ ↦ Φ.hone.o-filter,
+          opcode ↦ "invokestatic",
+          class ↦ "Foo",
+          target-handle ↦ 6,
+          target-class ↦ "Foo",
+          target-method ↦ "lambda$main$1",
+          target-signature ↦ "(I)Z",
+          bridge-input ↦ "Ljava/lang/Integer;",
+          bridge-output ↦ "Ljava/lang/Integer;",
+          accepted ↦ "I",
+          returned ↦ "I"
+        ⟧
+      ⟧
+    ⟧
+  ⟧
+⟧
diff --git a/test-resources/phi-typos-packs/alpha-index-out-of-range.phi b/test-resources/phi-typos-packs/alpha-index-out-of-range.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/alpha-index-out-of-range.phi
@@ -0,0 +1,3 @@
+[[
+  r -> [[ x -> ? ]](~18446744073709551616 -> [[]])
+]]
diff --git a/test-resources/phi-typos-packs/attribute-with-quote.phi b/test-resources/phi-typos-packs/attribute-with-quote.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/attribute-with-quote.phi
@@ -0,0 +1,3 @@
+[[
+  a -> x"y
+]]
diff --git a/test-resources/phi-typos-packs/delta-in-application.phi b/test-resources/phi-typos-packs/delta-in-application.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/delta-in-application.phi
@@ -0,0 +1,3 @@
+[[
+  x -> Q.y(D> 00-).z
+]]
diff --git a/test-resources/phi-typos-packs/duplicated-attributes.phi b/test-resources/phi-typos-packs/duplicated-attributes.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/duplicated-attributes.phi
@@ -0,0 +1,4 @@
+[[
+  x -> 5,
+  x -> 6
+]]
diff --git a/test-resources/phi-typos-packs/symbol-numbered-minus-zero.phi b/test-resources/phi-typos-packs/symbol-numbered-minus-zero.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/symbol-numbered-minus-zero.phi
@@ -0,0 +1,3 @@
+[[
+  a -> [[ L> 𝜎-0 ]]
+]]
diff --git a/test-resources/phi-typos-packs/symbol-numbered-negative.phi b/test-resources/phi-typos-packs/symbol-numbered-negative.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/symbol-numbered-negative.phi
@@ -0,0 +1,3 @@
+[[
+  a -> [[ L> 𝜎-3 ]]
+]]
diff --git a/test-resources/phi-typos-packs/symbol-numbered-out-of-range.phi b/test-resources/phi-typos-packs/symbol-numbered-out-of-range.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/symbol-numbered-out-of-range.phi
@@ -0,0 +1,3 @@
+[[
+  a -> [[ L> 𝜎18446744073709551617 ]]
+]]
diff --git a/test-resources/phi-typos-packs/symbol-numbered-with-leading-zero.phi b/test-resources/phi-typos-packs/symbol-numbered-with-leading-zero.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/symbol-numbered-with-leading-zero.phi
@@ -0,0 +1,3 @@
+[[
+  a -> [[ L> 𝜎00 ]]
+]]
diff --git a/test-resources/phi-typos-packs/wrong-function-name.phi b/test-resources/phi-typos-packs/wrong-function-name.phi
new file mode 100644
--- /dev/null
+++ b/test-resources/phi-typos-packs/wrong-function-name.phi
@@ -0,0 +1,5 @@
+[[
+  x -> [[
+    L> SOME
+  ]]
+]]
diff --git a/test-resources/rewriter-packs/basic/alpha-first.yaml b/test-resources/rewriter-packs/basic/alpha-first.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-first.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ x -> ?, y -> [[ ]] ]](~0 -> Q.y)
+output: |-
+  ⟦ x ↦ ∅, y -> [[ ]] ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-fourth.yaml b/test-resources/rewriter-packs/basic/alpha-fourth.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-fourth.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ q -> $.y, w -> ?, z -> ?, x -> ?, y -> [[ ]] ]](~3 -> Q.y)
+output: |-
+  ⟦ q -> $.y, w -> ?, z -> ?, x -> ?, y -> [[ ]] ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-rho-middle-skipped.yaml b/test-resources/rewriter-packs/basic/alpha-rho-middle-skipped.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-rho-middle-skipped.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ a -> ?, ^ -> ?, x -> ? ]](~1 -> Q.y)
+output: |-
+  ⟦ a ↦ ∅, ρ ↦ ∅, x ↦ ∅ ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-rho-void-before-x.yaml b/test-resources/rewriter-packs/basic/alpha-rho-void-before-x.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-rho-void-before-x.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ ^ -> ?, x -> ? ]](~0 -> Q.y)
+output: |-
+  ⟦ ρ ↦ ∅, x ↦ ∅ ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-second.yaml b/test-resources/rewriter-packs/basic/alpha-second.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-second.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ q -> $.y, x -> ?, y -> [[ ]] ]](~1 -> Q.y)
+output: |-
+  ⟦ q -> $.y, x ↦ ∅, y -> [[ ]] ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-simple.yaml b/test-resources/rewriter-packs/basic/alpha-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-simple.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ x -> ? ]](~0 -> Q.y)
+output: |-
+  ⟦ x ↦ ∅ ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-skips-bound-rho.yaml b/test-resources/rewriter-packs/basic/alpha-skips-bound-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-skips-bound-rho.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ ^ -> Q.y, x -> ? ]](~0 -> Q.z)
+output: |-
+  ⟦ ρ ↦ Φ.y, x ↦ ∅ ⟧(x ↦ Φ.z)
diff --git a/test-resources/rewriter-packs/basic/alpha-skips-delta-asset.yaml b/test-resources/rewriter-packs/basic/alpha-skips-delta-asset.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-skips-delta-asset.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ D> 42-, x -> ? ]](~0 -> Q.y)
+output: |-
+  ⟦ D> 42-, x ↦ ∅ ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-hits-rho.yaml b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-hits-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-hits-rho.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ L> Fn, x -> ? ]](~1 -> Q.y)
+output: |-
+  ⟦ L> Fn, x ↦ ∅ ⟧(α1 ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-out-of-range.yaml b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-out-of-range.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset-out-of-range.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ L> Fn, x -> ? ]](~2 -> Q.y)
+output: |-
+  ⟦ L> Fn, x ↦ ∅ ⟧(~2 ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset.yaml b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-skips-lambda-asset.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ L> Fn, x -> ? ]](~0 -> Q.y)
+output: |-
+  ⟦ L> Fn, x ↦ ∅ ⟧(x ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/alpha-stops-at-idx.yaml b/test-resources/rewriter-packs/basic/alpha-stops-at-idx.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-stops-at-idx.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ x -> ? ]](~2 -> Q.y)
+output: |-
+  ∅:x( α2 ↦ Φ.y )
diff --git a/test-resources/rewriter-packs/basic/alpha-stops-at-length.yaml b/test-resources/rewriter-packs/basic/alpha-stops-at-length.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/alpha-stops-at-length.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['alpha']
+input: |
+  [[ x -> ?, y -> $.x ]](~1 -> Q.y)
+output: |-
+  ⟦ x -> ?, y -> $.x ⟧(~1 ↦ Φ.y)
diff --git a/test-resources/rewriter-packs/basic/copy-paper-e1.yaml b/test-resources/rewriter-packs/basic/copy-paper-e1.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/copy-paper-e1.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['copy']
+input: |
+  ⟦ k ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧(t ↦ ⟦ρ ↦ ⟦⟧⟧) ⟧
+output: |-
+  ⟦ k ↦ ⟦ x ↦ ξ.t, t ↦ ⟦ρ ↦ ⟦⟧⟧ ⟧ ⟧
diff --git a/test-resources/rewriter-packs/basic/copy-paper-e4-dispatch.yaml b/test-resources/rewriter-packs/basic/copy-paper-e4-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/copy-paper-e4-dispatch.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['copy']
+input: |
+  ⟦ k ↦ ⟦ x ↦ ∅, y ↦ ξ.x ⟧(x ↦ ⟦ρ ↦ ⟦⟧⟧).y ⟧
+output: |-
+  ⟦ k ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧.y ⟧
diff --git a/test-resources/rewriter-packs/basic/copy-simple.yaml b/test-resources/rewriter-packs/basic/copy-simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/copy-simple.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['copy']
+input: |
+  ⟦ a ↦ ⟦ b ↦ ∅ ⟧ (b ↦ ⟦⟧) ⟧
+output: |-
+  ⟦⟧:b:a
diff --git a/test-resources/rewriter-packs/basic/copy-with-tail.yaml b/test-resources/rewriter-packs/basic/copy-with-tail.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/copy-with-tail.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['copy']
+input: |
+  ⟦ a ↦ ⟦ b ↦ ∅ ⟧ (b ↦ ⟦⟧).a ⟧
+output: |-
+  ⟦⟧:b.a:a
diff --git a/test-resources/rewriter-packs/basic/dc-from-paper.yaml b/test-resources/rewriter-packs/basic/dc-from-paper.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dc-from-paper.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dc']
+input: |
+  ⊥(ρ ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/dc-in-subformation.yaml b/test-resources/rewriter-packs/basic/dc-in-subformation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dc-in-subformation.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 2
+rules:
+  basic: ['dc']
+input: |
+  ⟦ a ↦ ⟦ b ↦ ⊥(t ↦ ⟦⟧, u ↦ ⟦⟧) ⟧ ⟧
+output: |-
+  ⟦ a ↦ ⟦ b ↦ ⊥ ⟧ ⟧
diff --git a/test-resources/rewriter-packs/basic/dca-from-paper.yaml b/test-resources/rewriter-packs/basic/dca-from-paper.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dca-from-paper.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dca']
+input: |
+  ⊥(α0 ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/dd.yaml b/test-resources/rewriter-packs/basic/dd.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dd.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dd']
+input: |
+  ⟦ a ↦ ⊥.x ⟧
+output: |-
+  ⟦
+    a ↦ ⊥
+  ⟧
diff --git a/test-resources/rewriter-packs/basic/dot-lambda-delta-to-dl.yaml b/test-resources/rewriter-packs/basic/dot-lambda-delta-to-dl.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-lambda-delta-to-dl.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# With 'dot' tried before 'dl', a formation holding both λ and Δ still
+# collapses to ⊥ and never leaves a ρ ↦ ⊥ behind (#1395).
+rules:
+  basic: ['dot', 'dl', 'dd']
+repeat: 5
+input: |
+  ⟦ x ↦ Φ.y, λ ⤍ Foo, Δ ⤍ 00- ⟧.x
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/dot-lambda-only.yaml b/test-resources/rewriter-packs/basic/dot-lambda-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-lambda-only.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The λ-and-Δ guard of 'dot' still lets a formation with λ alone be
+# dispatched (#1395).
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ Φ.y, λ ⤍ Foo ⟧.x
+output: |-
+  Φ.y(
+    ρ ↦ ⟦
+      x ↦ Φ.y,
+      λ ⤍ Foo
+    ⟧
+  )
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e2.yaml b/test-resources/rewriter-packs/basic/dot-paper-e2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e2.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ ξ.t, t ↦ ∅ ⟧.x
+output: |-
+  ⟦ t ↦ ∅ ⟧.t(ρ ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧)
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e3-1.yaml b/test-resources/rewriter-packs/basic/dot-paper-e3-1.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e3-1.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ ⟦t ↦ ⟦ρ ↦ ⟦⟧ ⟧ ⟧.t ⟧.x
+output: |-
+  ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧(ρ ↦ ⟦t ↦ ⟦ρ ↦ ⟦⟧⟧⟧) ⟧.x
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e3-2.yaml b/test-resources/rewriter-packs/basic/dot-paper-e3-2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e3-2.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧ ⟧.x
+output: |-
+  ⟦ρ ↦ ⟦⟧⟧(ρ ↦ ⟦x ↦ ⟦ρ ↦ ⟦⟧⟧⟧)
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e4-1.yaml b/test-resources/rewriter-packs/basic/dot-paper-e4-1.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e4-1.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 2
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧.y
+output: |-
+  ⟦ρ ↦ ⟦⟧⟧(ρ ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧ ⟧)(ρ ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧)
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e4-2.yaml b/test-resources/rewriter-packs/basic/dot-paper-e4-2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e4-2.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧.x(ρ ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧)
+output: |-
+  ⟦ρ ↦ ⟦⟧⟧(ρ ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧)(ρ ↦ ⟦ x ↦ ⟦ρ ↦ ⟦⟧⟧, y ↦ ξ.x ⟧)
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e5-1.yaml b/test-resources/rewriter-packs/basic/dot-paper-e5-1.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e5-1.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧.x ⟧
+output: |-
+  ⟦ m ↦ ⟦ φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧.t(ρ ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧) ⟧
diff --git a/test-resources/rewriter-packs/basic/dot-paper-e5-2.yaml b/test-resources/rewriter-packs/basic/dot-paper-e5-2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-paper-e5-2.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+rules:
+  basic: ['dot']
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧.φ.t(ρ ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧) ⟧
+output: |-
+  ⟦ m ↦ ⟦ t ↦ ⟦⟧ ⟧(ρ ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧).t(ρ ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧) ⟧
diff --git a/test-resources/rewriter-packs/basic/dot-recursive.yaml b/test-resources/rewriter-packs/basic/dot-recursive.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-recursive.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  ⟦ a ↦ ξ.b ⟧.a
+output: |-
+  ⟦⟧.b (ρ ↦ ⟦ a ↦ ξ.b ⟧)
diff --git a/test-resources/rewriter-packs/basic/dot-skips-lambda-delta.yaml b/test-resources/rewriter-packs/basic/dot-skips-lambda-delta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-skips-lambda-delta.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 'dot' leaves a formation holding both λ and Δ to 'dl', so the normal form
+# does not hang on rule order (#1395).
+rules:
+  basic: ['dot']
+input: |
+  ⟦ x ↦ Φ.y, λ ⤍ Foo, Δ ⤍ 00- ⟧.x
+output: |-
+  ⟦
+    x ↦ Φ.y,
+    λ ⤍ Foo,
+    Δ ⤍ 00-
+  ⟧.x
diff --git a/test-resources/rewriter-packs/basic/dot-with-dd.yaml b/test-resources/rewriter-packs/basic/dot-with-dd.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-with-dd.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['dot']
+input: |
+  [[ t -> ⊥.z ]].t
+output: |-
+  [[ t -> ⊥.z ]].t
diff --git a/test-resources/rewriter-packs/basic/dot-xi.yaml b/test-resources/rewriter-packs/basic/dot-xi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/dot-xi.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# The ξ of the dispatched body stands for the formation without the
+# dispatched binding, on purpose, so a body that is ξ answers the formation
+# one attribute short rather than itself (#967, #1438).
+rules:
+  basic: ['dot']
+input: |
+  ⟦ a ↦ ξ ⟧.a
+output: |-
+  ⟦⟧ (ρ ↦ ⟦ a ↦ ξ ⟧)
diff --git a/test-resources/rewriter-packs/basic/miss-from-paper.yaml b/test-resources/rewriter-packs/basic/miss-from-paper.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/miss-from-paper.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['miss']
+input: |
+  ⟦ x ↦ ξ.t ⟧(t ↦ ξ)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/miss-on-empty.yaml b/test-resources/rewriter-packs/basic/miss-on-empty.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/miss-on-empty.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['miss']
+input: |
+  ⟦ ⟧ (t ↦ ξ)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/miss-spares-rho.yaml b/test-resources/rewriter-packs/basic/miss-spares-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/miss-spares-rho.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 'miss' answers ⊥ for an attribute the formation does not declare, save ρ,
+# which a formation without a receiver merely ignores (#1407).
+rules:
+  basic: ['miss']
+input: |
+  ⟦ a ↦ ⟦ k ↦ ∅ ⟧( ρ ↦ Φ.x ), b ↦ ⟦ k ↦ ∅ ⟧( t ↦ Φ.x ) ⟧
+output: |-
+  ⟦ a ↦ ⟦ k ↦ ∅ ⟧( ρ ↦ Φ.x ), b ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/basic/null-from-parer.yaml b/test-resources/rewriter-packs/basic/null-from-parer.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/null-from-parer.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['null']
+input: |
+  ⟦ x ↦ ξ.t, t ↦ ∅ ⟧.t(ρ ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧)
+output: |-
+  ⊥(
+    ρ ↦ ⟦
+      x ↦ ξ.t,
+      t ↦ ∅
+    ⟧
+  )
diff --git a/test-resources/rewriter-packs/basic/over-alone.yaml b/test-resources/rewriter-packs/basic/over-alone.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-alone.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['over']
+input: |
+  ⟦ x ↦ ξ.t ⟧(x ↦ ξ)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/over-from-paper.yaml b/test-resources/rewriter-packs/basic/over-from-paper.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-from-paper.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['over']
+input: |
+  ⟦ t ↦ ξ.k, x ↦ ξ.t, k ↦ ∅ ⟧(x ↦ ξ)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/over-in-back.yaml b/test-resources/rewriter-packs/basic/over-in-back.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-in-back.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['over']
+input: |
+  ⟦ k ↦ ∅, t ↦ ξ.k, x ↦ ξ.t ⟧(x ↦ ξ.e3.t55)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/over-in-front.yaml b/test-resources/rewriter-packs/basic/over-in-front.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-in-front.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['over']
+input: |
+  ⟦ x ↦ ξ.t, k ↦ ∅, t ↦ ξ.k ⟧(x ↦ Φ.p9( w ↦ ξ.g ))
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/over-inside.yaml b/test-resources/rewriter-packs/basic/over-inside.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-inside.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+rules:
+  basic: ['over']
+input: |
+  ⟦ f ↦ ⟦ x ↦ ξ.t ⟧(x ↦ ξ.t.t.t), k ↦ ∅ ⟧
+output: |-
+  ⟦
+    f ↦ ⊥,
+    k ↦ ∅
+  ⟧
diff --git a/test-resources/rewriter-packs/basic/over-long-binding.yaml b/test-resources/rewriter-packs/basic/over-long-binding.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-long-binding.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+rules:
+  basic: ['over']
+input: |
+  ⟦ f ↦ ∅, k ↦ ∅, t ↦ ξ.k, x ↦ ξ.t, u ↦ ξ.t( u ↦ ξ ), w ↦ ∅, p ↦ Φ.t.t.t ⟧(x ↦ ξ.t.t.t)
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/basic/over-with-rho.yaml b/test-resources/rewriter-packs/basic/over-with-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-with-rho.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+rules:
+  basic: ['over']
+input: |
+  ⟦ k ↦ ⟦ ρ ↦ ξ.t, k ↦ ∅ ⟧ (ρ ↦ ξ.k) ⟧
+output: |-
+  ⟦
+    k ↦ ⟦
+      ρ ↦ ξ.t,
+      k ↦ ∅
+    ⟧(
+      ρ ↦ ξ.k
+    )
+  ⟧
diff --git a/test-resources/rewriter-packs/basic/over-without-over.yaml b/test-resources/rewriter-packs/basic/over-without-over.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/over-without-over.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+rules:
+  basic: ['over']
+input: |
+  ⟦ x ↦ ∅, k ↦ ∅ ⟧ (x ↦ ξ.k)
+output: |-
+  ⟦
+    x ↦ ∅,
+    k ↦ ∅
+  ⟧(
+    x ↦ ξ.k
+  )
diff --git a/test-resources/rewriter-packs/basic/skip-drops-an-undeclared-rho.yaml b/test-resources/rewriter-packs/basic/skip-drops-an-undeclared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/skip-drops-an-undeclared-rho.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation that declares no ρ has no receiver, so a ρ applied to it is
+# dropped and the formation stays as it was written (#1407).
+rules:
+  basic: ['skip']
+input: |
+  ⟦ a ↦ ⟦ k ↦ ∅ ⟧( ρ ↦ Φ.x ) ⟧
+output: |-
+  ⟦ a ↦ ⟦ k ↦ ∅ ⟧ ⟧
diff --git a/test-resources/rewriter-packs/basic/skip-leaves-a-declared-rho.yaml b/test-resources/rewriter-packs/basic/skip-leaves-a-declared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/skip-leaves-a-declared-rho.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation that declares its ρ is filled by 'copy', never by 'skip' (#1407).
+rules:
+  basic: ['skip']
+input: |
+  ⟦ a ↦ ⟦ ρ ↦ ∅, k ↦ ∅ ⟧( ρ ↦ Φ.x ) ⟧
+output: |-
+  ⟦ a ↦ ⟦ ρ ↦ ∅, k ↦ ∅ ⟧( ρ ↦ Φ.x ) ⟧
diff --git a/test-resources/rewriter-packs/basic/stay.yaml b/test-resources/rewriter-packs/basic/stay.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/stay.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['stay']
+input: |
+  ⟦ x ↦ ⟦⟧, ρ ↦ ⟦ t ↦ Φ.number ⟧ ⟧ (ρ ↦ ⟦ w ↦ Φ.string ⟧)
+output: |-
+  ⟦
+    x ↦ ⟦⟧,
+    ρ ↦ ⟦
+      t ↦ Φ.number
+    ⟧
+  ⟧
diff --git a/test-resources/rewriter-packs/basic/stop-at-lambda.yaml b/test-resources/rewriter-packs/basic/stop-at-lambda.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/stop-at-lambda.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['stop']
+input: |
+  ⟦ x ↦ ξ.t, λ ⤍ Hello ⟧.z
+output: |-
+  ⟦
+    x ↦ ξ.t,
+    λ ⤍ Hello
+  ⟧.z
diff --git a/test-resources/rewriter-packs/basic/stop-at-phi.yaml b/test-resources/rewriter-packs/basic/stop-at-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/stop-at-phi.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['stop']
+input: |
+  ⟦ stop ↦ ⟦ φ ↦ ξ.x ⟧.no ⟧
+output: |-
+  ⟦ stop ↦ ⟦ φ ↦ ξ.x ⟧.no ⟧
diff --git a/test-resources/rewriter-packs/basic/stop-from-paper.yaml b/test-resources/rewriter-packs/basic/stop-from-paper.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/basic/stop-from-paper.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+rules:
+  basic: ['stop']
+input: |
+  ⟦ x ↦ ξ.t ⟧.z
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/custom/anonymous-binding-run.yaml b/test-resources/rewriter-packs/custom/anonymous-binding-run.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/anonymous-binding-run.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# An anonymous meta stands for a whole run of bindings just as a named one
+# does, and two of them in one pattern split the target independently. Here
+# the rule only cares that some binding sits between two runs it never reads
+# back, so neither run is given a name.
+input: '⟦ a ↦ Φ, b ↦ Φ.z, c ↦ ξ.a ⟧'
+output: '⟦ found ↦ Φ ⟧'
+rules:
+  custom:
+    - name: anonymous-binding-run
+      pattern: '⟦ 𝐵, b ↦ 𝑒, 𝐵 ⟧'
+      result: '⟦ found ↦ Φ ⟧'
diff --git a/test-resources/rewriter-packs/custom/anonymous-metas.yaml b/test-resources/rewriter-packs/custom/anonymous-metas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/anonymous-metas.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A meta written without an index is anonymous: it matches whatever term
+# stands in its place, and every occurrence matches on its own. The rule below
+# is the one from #218 — it needs two attributes and two expressions but names
+# none of them, which spelled with indexes would be rejected as a duplicated
+# attribute.
+input: '⟦ a ↦ Φ, ρ ↦ Φ.z ⟧'
+output: '⟦ x ↦ ⟦ Δ ⤍ 2A- ⟧ ⟧'
+rules:
+  custom:
+    - name: anonymous-metas
+      pattern: '⟦ 𝜏 ↦ 𝑒, 𝜏 ↦ 𝑒 ⟧'
+      result: '⟦ x ↦ ⟦ Δ ⤍ 2A- ⟧ ⟧'
diff --git a/test-resources/rewriter-packs/custom/applies-a-match-after-one-swallowed-by-another.yaml b/test-resources/rewriter-packs/custom/applies-a-match-after-one-swallowed-by-another.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/applies-a-match-after-one-swallowed-by-another.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 1
+input: '[[ a -> [[ x -> [[ x -> $ ]] ]], b -> [[ x -> Q ]] ]]'
+output: '[[ a -> [[ y -> Q ]], b -> [[ y -> Q ]] ]]'
+rules:
+  custom:
+    - name: rename-x
+      pattern: '[[ x -> !e1, !B1 ]]'
+      result: '[[ y -> Q, !B1 ]]'
diff --git a/test-resources/rewriter-packs/custom/applies-a-match-equal-to-one-swallowed-by-another.yaml b/test-resources/rewriter-packs/custom/applies-a-match-equal-to-one-swallowed-by-another.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/applies-a-match-equal-to-one-swallowed-by-another.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 1
+input: '[[ a -> [[ x -> [[ x -> Q ]] ]], b -> [[ x -> Q ]] ]]'
+output: '[[ a -> [[ y -> Q ]], b -> [[ y -> Q ]] ]]'
+rules:
+  custom:
+    - name: rename-x
+      pattern: '[[ x -> !e1, !B1 ]]'
+      result: '[[ y -> Q, !B1 ]]'
diff --git a/test-resources/rewriter-packs/custom/attribute-from-string.yaml b/test-resources/rewriter-packs/custom/attribute-from-string.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/attribute-from-string.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  ⟦ x -> "foo" ⟧
+output: |-
+  ⟦ x -> "foo", foo -> ⟦⟧ ⟧
+rules:
+  custom:
+    - name: attribute from string
+      pattern: |
+        [[ x -> !e1 ]]
+      result: |
+        [[ x -> !e1, !t1 -> [[ ]] ]]
+      where:
+        - meta: '!t1'
+          function: tau
+          args: ['!e1']
diff --git a/test-resources/rewriter-packs/custom/concat-from-expression.yaml b/test-resources/rewriter-packs/custom/concat-from-expression.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/concat-from-expression.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> "hello", y -> "world" ]]'
+output: |-
+  ⟦
+    z ↦ "hello world"
+  ⟧
+rules:
+  custom:
+    - name: concat from expression
+      pattern: '[[ x -> !e1, y -> !e2 ]]'
+      result: '[[ z -> !e3 ]]'
+      where:
+        - meta: '!e3'
+          function: concat
+          args:
+            - '!e1'
+            - '" "'
+            - '!e2'
diff --git a/test-resources/rewriter-packs/custom/concat-positional-bytes.yaml b/test-resources/rewriter-packs/custom/concat-positional-bytes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/concat-positional-bytes.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> Q.bytes([[ D> 68-69 ]]) ]]'
+output: |-
+  ⟦
+    s ↦ "hi"
+  ⟧
+rules:
+  custom:
+    - name: concat positional bytes
+      pattern: '[[ x -> !e1 ]]'
+      result: '[[ s -> !e2 ]]'
+      where:
+        - meta: '!e2'
+          function: concat
+          args:
+            - '!e1'
diff --git a/test-resources/rewriter-packs/custom/dataize-and-concat.yaml b/test-resources/rewriter-packs/custom/dataize-and-concat.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/dataize-and-concat.yaml
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> "hello", y -> "world" ]]'
+output: |-
+  ⟦
+    z ↦ "hello world"
+  ⟧
+rules:
+  custom:
+    - name: dataize & concat
+      pattern: '[[ x -> !e1, y -> !e2 ]]'
+      result: '[[ z -> !e3 ]]'
+      where:
+        - meta: '!d1'
+          function: dataize
+          args: ['" "']
+        - meta: '!d2'
+          function: dataize
+          args: ['!e1']
+        - meta: '!d3'
+          function: dataize
+          args: ['!e2']
+        - meta: '!e3'
+          function: concat
+          args:
+            - '!d2'
+            - '!d1'
+            - '!d3'
diff --git a/test-resources/rewriter-packs/custom/desugar-fibo.yaml b/test-resources/rewriter-packs/custom/desugar-fibo.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/desugar-fibo.yaml
@@ -0,0 +1,61 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  ⟦
+    eo ↦ ⟦
+      example ↦ ⟦
+        fibonacci(n) ↦ ⟦
+          φ ↦ n.lt(2).if(
+            n,
+            Φ.eo.example.fibonacci(n.minus(1)).plus(
+              Φ.eo.example.fibonacci(n.minus(2))
+            )
+          )
+        ⟧,
+        λ ⤍ Package
+      ⟧,
+      λ ⤍ Package
+    ⟧
+  ⟧
+output: |-
+  ⟦
+    eo ↦ ⟦
+      example ↦ ⟦
+        fibonacci ↦ ⟦
+          n ↦ ∅,
+          φ ↦ ξ.n.lt(
+            α0 ↦ Φ.number(
+              φ ↦ Φ.bytes(
+                φ ↦ ⟦ Δ ⤍ 40-00-00-00-00-00-00-00 ⟧
+              )
+            )
+          ).if(
+            α0 ↦ ξ.n
+          )(
+            α1 ↦ Φ.eo.example.fibonacci(
+              α0 ↦ ξ.n.minus(
+                α0 ↦ Φ.number(
+                  φ ↦ Φ.bytes(
+                    φ ↦ ⟦ Δ ⤍ 3F-F0-00-00-00-00-00-00 ⟧
+                  )
+                )
+              )
+            ).plus(
+              α0 ↦ Φ.eo.example.fibonacci(
+                α0 ↦ ξ.n.minus(
+                  α0 ↦ Φ.number(
+                    φ ↦ Φ.bytes(
+                      φ ↦ ⟦ Δ ⤍ 40-00-00-00-00-00-00-00 ⟧
+                    )
+                  )
+                )
+              )
+            )
+          )
+        ⟧,
+        λ ⤍ Package
+      ⟧,
+      λ ⤍ Package
+    ⟧
+  ⟧
diff --git a/test-resources/rewriter-packs/custom/desugar-strings.yaml b/test-resources/rewriter-packs/custom/desugar-strings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/desugar-strings.yaml
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  ⟦
+    str -> "hello",
+    ch -> "h",
+    uni -> "\u0001\u0002",
+    empty -> "",
+    new-line -> "h\ne"
+  ⟧
+output: |-
+  ⟦
+    str ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧
+      )
+    ),
+    ch ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦ Δ ⤍ 68- ⟧
+      )
+    ),
+    uni ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦ Δ ⤍ 01-02 ⟧
+      )
+    ),
+    empty ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦ Δ ⤍ -- ⟧
+      )
+    ),
+    new-line ↦ Φ.string(
+      φ ↦ Φ.bytes(
+        φ ↦ ⟦ Δ ⤍ 68-0A-65 ⟧
+      )
+    )
+  ⟧
diff --git a/test-resources/rewriter-packs/custom/desugares-without-match.yaml b/test-resources/rewriter-packs/custom/desugares-without-match.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/desugares-without-match.yaml
@@ -0,0 +1,24 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    foo ↦ ⟦
+      x -> 4294967295
+    ⟧
+  ]]
+output: |-
+  ⟦
+    foo ↦ ⟦
+      x ↦ Φ.number(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦ Δ ⤍ 41-EF-FF-FF-FF-E0-00-00 ⟧
+        )
+      )
+    ⟧
+  ⟧
+rules:
+  custom:
+    - name: desugar without match
+      pattern: Φ.x.y.z
+      result: Φ.a.b.c
diff --git a/test-resources/rewriter-packs/custom/desugares.yaml b/test-resources/rewriter-packs/custom/desugares.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/desugares.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    foo ↦ ⟦
+      x -> 4294967295
+    ⟧
+  ]]
+output: |-
+  ⟦
+    foo ↦ ⟦
+      x ↦ Φ.number(
+        φ ↦ Φ.bytes(
+          φ ↦ ⟦ Δ ⤍ 41-EF-FF-FF-FF-E0-00-00 ⟧
+        )
+      )
+    ⟧
+  ⟧
diff --git a/test-resources/rewriter-packs/custom/does-not-fail-on-ambiguous.yaml b/test-resources/rewriter-packs/custom/does-not-fail-on-ambiguous.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/does-not-fail-on-ambiguous.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ k -> [[ x -> "hello" ]] ]]'
+output: |-
+  ⟦
+    k -> [[ x -> "hi" ]]
+  ⟧
+rules:
+  custom:
+    - name: custom
+      pattern: '[[ x -> !e1 ]]'
+      result: '[[ x -> "hi" ]]'
+      when:
+        matches: ['hello', '!e1']
diff --git a/test-resources/rewriter-packs/custom/fast-replacing-respects-condition.yaml b/test-resources/rewriter-packs/custom/fast-replacing-respects-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/fast-replacing-respects-condition.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ a -> [[ x -> Q ]], b -> [[ z -> $, x -> Q ]] ]]'
+output: |-
+  ⟦
+    a ↦ ⟦ x ↦ Φ ⟧,
+    b ↦ ⟦ z ↦ ξ, y ↦ Φ ⟧
+  ⟧
+rules:
+  custom:
+    - name: rename-next-to-z
+      pattern: '[[ !B1, x -> !e1, !B2 ]]'
+      result: '[[ !B1, y -> !e1, !B2 ]]'
+      when:
+        in: ['z', '!B1']
diff --git a/test-resources/rewriter-packs/custom/fast-replacing-respects-having.yaml b/test-resources/rewriter-packs/custom/fast-replacing-respects-having.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/fast-replacing-respects-having.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ a -> [[ x -> Q ]], b -> [[ z -> $, x -> Q ]] ]]'
+output: |-
+  ⟦
+    a ↦ ⟦ x ↦ Φ ⟧,
+    b ↦ ⟦ z ↦ ξ, y ↦ Φ ⟧
+  ⟧
+rules:
+  custom:
+    - name: rename-next-to-z
+      pattern: '[[ !B1, x -> !e1, !B2 ]]'
+      result: '[[ !B1, y -> !e1, !B2 ]]'
+      having:
+        in: ['z', '!B1']
diff --git a/test-resources/rewriter-packs/custom/java-boxed-method.yaml b/test-resources/rewriter-packs/custom/java-boxed-method.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/java-boxed-method.yaml
@@ -0,0 +1,58 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 10
+input: |
+  ⟦
+    φ ↦ Φ.jeo.method,
+    body ↦ ⟦
+      φ ↦ Φ.jeo.seq.of2,
+      i95 ↦ ⟦
+        φ ↦ Φ.jeo.opcode.invokeinterface,
+        v96 ↦ "java/util/stream/IntStream",
+        v97 ↦ "boxed",
+        v98 ↦ "()Ljava/util/stream/Stream;",
+        v99 ↦ Φ.true
+      ⟧,
+      i96 ↦ ⟦
+        φ ↦ Φ.jeo.opcode.invokeinterface,
+        v96 ↦ "java/util/stream/LongStream",
+        v97 ↦ "boxed",
+        v98 ↦ "()Ljava/util/stream/Stream;",
+        v99 ↦ Φ.true
+      ⟧
+    ⟧,
+    name ↦ "main"
+  ⟧
+output: |-
+  ⟦
+    φ ↦ Φ.jeo.method,
+    body ↦ ⟦
+      φ ↦ Φ.jeo.seq.of2,
+      i95 ↦ ⟦
+        φ ↦ Φ.hone.box,
+        stream-class ↦ "java/util/stream/IntStream"
+      ⟧,
+      i96 ↦ ⟦
+        φ ↦ Φ.hone.box,
+        stream-class ↦ "java/util/stream/LongStream"
+      ⟧
+    ⟧,
+    name ↦ "main"
+  ⟧
+rules:
+  custom:
+    - name: java boxed method
+      pattern: |
+        ⟦
+          φ ↦ Φ.jeo.opcode.invokeinterface,
+          𝜏-stream-class ↦ 𝑒-stream-class,
+          𝜏-method ↦ "boxed",
+          𝜏-signature ↦ "()Ljava/util/stream/Stream;",
+          𝜏-flag ↦ Φ.true
+        ⟧
+      result: |
+        ⟦
+          φ ↦ Φ.hone.box,
+          stream-class ↦ 𝑒-stream-class
+        ⟧
diff --git a/test-resources/rewriter-packs/custom/join-renames-sibling-references.yaml b/test-resources/rewriter-packs/custom/join-renames-sibling-references.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/join-renames-sibling-references.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  ⟦ a ↦ ⟦ x ↦ "first" ⟧, b ↦ ⟦ x ↦ "second", y ↦ ξ.x ⟧ ⟧
+output: |-
+  ⟦ c ↦ ⟦ x ↦ "first", a🌵0 ↦ "second", y ↦ ξ.a🌵0 ⟧ ⟧
+rules:
+  custom:
+    - name: join keeps a sibling reference with its binding
+      pattern: |
+        ⟦ a ↦ ⟦ !B1 ⟧, b ↦ ⟦ !B2 ⟧ ⟧
+      result: |
+        ⟦ c ↦ ⟦ !B3 ⟧ ⟧
+      where:
+        - meta: '!B3'
+          function: join
+          args:
+            - '!B1'
+            - '!B2'
diff --git a/test-resources/rewriter-packs/custom/negative-condition.yaml b/test-resources/rewriter-packs/custom/negative-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/negative-condition.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[x -> Q.y]].z'
+output: |-
+  Φ.z
+rules:
+  custom:
+    - name: negative-condition
+      pattern: '[[!B1]].!t1'
+      result: 'Q.!t1'
+      when:
+        not:
+          in:
+            - '!t1'
+            - '!B1'
diff --git a/test-resources/rewriter-packs/custom/nested-part-of.yaml b/test-resources/rewriter-packs/custom/nested-part-of.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/nested-part-of.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 10
+input: '[[ x -> Q.y ]]'
+output: |-
+  ⟦
+    x ↦ Φ.y,
+    z -> [[ w -> [[ q -> "foo" ]] ]]
+  ⟧
+rules:
+  custom:
+    - name: nested part of
+      pattern: '[[ x -> Q.y, !B1 ]]'
+      result: '[[ x -> Q.y, z -> [[ w -> [[ q -> "foo" ]] ]], !B1]]'
+      when:
+        not:
+          part-of: ['"foo"', '!B1']
diff --git a/test-resources/rewriter-packs/custom/number.yaml b/test-resources/rewriter-packs/custom/number.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/number.yaml
@@ -0,0 +1,21 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> "5", y -> "42.1" ]]'
+output: |-
+  ⟦
+    x ↦ 5,
+    y -> 42.1
+  ⟧
+rules:
+  custom:
+    - name: number
+      pattern: '[[ x -> !e1, y -> !e2 ]]'
+      result: '[[ x -> !e11, y -> !e22 ]]'
+      where:
+        - meta: '!e11'
+          function: number
+          args: ['!e1']
+        - meta: '!e22'
+          function: number
+          args: ['!e2']
diff --git a/test-resources/rewriter-packs/custom/part-of-after.yaml b/test-resources/rewriter-packs/custom/part-of-after.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/part-of-after.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 10
+input: '[[ x -> Q.y ]]'
+output: |-
+  ⟦
+    x ↦ Φ.y,
+    z -> "hello world"
+  ⟧
+rules:
+  custom:
+    - name: part-of-after
+      pattern: '[[ x -> Q.y, !B1 ]]'
+      result: '[[ x -> Q.y, z -> !e1, !B1 ]]'
+      where:
+        - meta: '!e1'
+          function: concat
+          args: ['"hello "', '"world"']
+      having:
+        not:
+          part-of: ['!e1', '!B1']
diff --git a/test-resources/rewriter-packs/custom/part-of.yaml b/test-resources/rewriter-packs/custom/part-of.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/part-of.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 10
+input: '[[ x -> Q.y ]]'
+output: |-
+  ⟦
+    x ↦ Φ.y,
+    z -> "foo"
+  ⟧
+rules:
+  custom:
+    - name: part-of
+      pattern: '[[ x -> Q.y, !B1 ]]'
+      result: '[[ x -> Q.y, z -> "foo", !B1]]'
+      when:
+        not:
+          part-of: ['"foo"', '!B1']
diff --git a/test-resources/rewriter-packs/custom/random-tau-mints-fresh.yaml b/test-resources/rewriter-packs/custom/random-tau-mints-fresh.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/random-tau-mints-fresh.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  ⟦ x -> "foo" ⟧
+output: |-
+  ⟦ x -> "foo", a🌵0 -> ⟦⟧ ⟧
+rules:
+  custom:
+    - name: append fresh tau
+      pattern: |
+        [[ x -> !e1 ]]
+      result: |
+        [[ x -> !e1, !t1 -> [[ ]] ]]
+      where:
+        - meta: '!t1'
+          function: random-tau
diff --git a/test-resources/rewriter-packs/custom/random-tau-skips-program-wide.yaml b/test-resources/rewriter-packs/custom/random-tau-skips-program-wide.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/random-tau-skips-program-wide.yaml
@@ -0,0 +1,27 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Demonstrates that random-tau steers clear of names taken anywhere in the
+# document, not just in the formation being rewritten. The 'used' formation
+# already owns 'a🌵0' and 'a🌵1', so the freshly minted label for 'target'
+# must be 'a🌵2'.
+input: |
+  [[
+    used -> [[ a🌵0 -> ⟦⟧, a🌵1 -> ⟦⟧ ]],
+    target -> [[ x -> "foo" ]]
+  ]]
+output: |-
+  [[
+    used -> [[ a🌵0 -> ⟦⟧, a🌵1 -> ⟦⟧ ]],
+    target -> [[ x -> "foo", a🌵2 -> ⟦⟧ ]]
+  ]]
+rules:
+  custom:
+    - name: append fresh tau into target
+      pattern: |
+        [[ x -> !e1 ]]
+      result: |
+        [[ x -> !e1, !t1 -> [[ ]] ]]
+      where:
+        - meta: '!t1'
+          function: random-tau
diff --git a/test-resources/rewriter-packs/custom/replaces-all-in-inner-cycle.yaml b/test-resources/rewriter-packs/custom/replaces-all-in-inner-cycle.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/replaces-all-in-inner-cycle.yaml
@@ -0,0 +1,25 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 3
+must: 1
+input: |
+  [[
+    x -> "hello",
+    y -> "world",
+    z -> "!"
+  ]]
+output: |
+  [[ ]]
+rules:
+  custom:
+    - name: tau to void
+      pattern: |
+        [[ !B1, !t4 -> !e1, !B2 ]]
+      result: |
+        [[ !B1, !t4 -> ?, !B2 ]]
+    - name: voids to empty
+      pattern: |
+        [[ !t1 -> ?, !t2 -> ?, !t3 -> ? ]]
+      result: |
+        [[ ]]
diff --git a/test-resources/rewriter-packs/custom/replaces-formation-deeper-than-max-depth.yaml b/test-resources/rewriter-packs/custom/replaces-formation-deeper-than-max-depth.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/replaces-formation-deeper-than-max-depth.yaml
@@ -0,0 +1,28 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 1
+must: 1
+input: |
+  [[
+    first -> [[
+      second -> [[
+        target -> "hello"
+      ]]
+    ]]
+  ]]
+output: |
+  [[
+    first -> [[
+      second -> [[
+        target -> ?
+      ]]
+    ]]
+  ]]
+rules:
+  custom:
+    - name: void target
+      pattern: |
+        [[ !B1, target -> !e1, !B2 ]]
+      result: |
+        [[ !B1, target -> ?, !B2 ]]
diff --git a/test-resources/rewriter-packs/custom/replaces-formation-in-one-cycle.yaml b/test-resources/rewriter-packs/custom/replaces-formation-in-one-cycle.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/replaces-formation-in-one-cycle.yaml
@@ -0,0 +1,24 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+repeat: 1
+must: 1
+input: |
+  [[
+    x -> "hello",
+    y -> "world",
+    z -> "!"
+  ]]
+output: |
+  [[
+    x -> ?,
+    y -> ?,
+    z -> ?
+  ]]
+rules:
+  custom:
+    - name: tau to void
+      pattern: |
+        [[ !B1, !t1 -> !e1, !B2 ]]
+      result: |
+        [[ !B1, !t1 -> ?, !B2 ]]
diff --git a/test-resources/rewriter-packs/custom/same-exprs-in-formation.yaml b/test-resources/rewriter-packs/custom/same-exprs-in-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/same-exprs-in-formation.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> Q, ^ -> Q ]]'
+output: |-
+  [[ x -> $, ^ -> $ ]]
+rules:
+  custom:
+    - name: same-exrs
+      pattern: 'Q'
+      result: '$'
diff --git a/test-resources/rewriter-packs/custom/sed-global-anchored-match.yaml b/test-resources/rewriter-packs/custom/sed-global-anchored-match.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sed-global-anchored-match.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> "hh"
+  ]]
+output: |-
+  ⟦
+    x ↦ "Xh"
+  ⟧
+rules:
+  custom:
+    - name: sed global replacement keeps the start anchor at the start
+      pattern: '[[ x -> !e1 ]]'
+      result: '[[ x -> !e2 ]]'
+      where:
+        - meta: '!e2'
+          function: sed
+          args:
+            - '!e1'
+            - '"s/^h/X/g"'
diff --git a/test-resources/rewriter-packs/custom/sed-matches-unicode-characters.yaml b/test-resources/rewriter-packs/custom/sed-matches-unicode-characters.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sed-matches-unicode-characters.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> "héllo"
+  ]]
+output: |-
+  ⟦
+    x ↦ "XXXXX"
+  ⟧
+rules:
+  custom:
+    - name: sed matches one Unicode character with a dot
+      pattern: '[[ x -> !e1 ]]'
+      result: '[[ x -> !e2 ]]'
+      where:
+        - meta: '!e2'
+          function: sed
+          args:
+            - '!e1'
+            - '"s/./X/g"'
diff --git a/test-resources/rewriter-packs/custom/sed-with-many-arguments.yaml b/test-resources/rewriter-packs/custom/sed-with-many-arguments.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sed-with-many-arguments.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> "1"
+  ]]
+output: |-
+  ⟦
+    x ↦ "6"
+  ⟧
+rules:
+  custom:
+    - name: sed with many arguments
+      pattern: '[[ x -> !e2 ]]'
+      result: '[[ x -> !e1 ]]'
+      where:
+        - meta: '!e1'
+          function: sed
+          args:
+            - '!e2'
+            - '"s/1/2/g"'
+            - '"s/2/3/g"'
+            - '"s/3/4/g"'
+            - '"s/3/5/g"'
+            - '"s/4/6/g"'
diff --git a/test-resources/rewriter-packs/custom/sed-with-slashes.yaml b/test-resources/rewriter-packs/custom/sed-with-slashes.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sed-with-slashes.yaml
@@ -0,0 +1,45 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> "java/lang/Boolean",
+    y -> "()Ljava/lang/String;",
+    z -> "(Ljava/lang/String)Ljava/lang/Boolean",
+    w -> "java/lang/Integer"
+  ]]
+output: |-
+  ⟦
+    x ↦ "java/lang/Integer",
+    y ↦ "invokevirtual",
+    z ↦ "invokestatic",
+    w -> "java\\/lang\\/Integer"
+  ⟧
+rules:
+  custom:
+    - name: sed with slashes
+      pattern: '[[ x -> !e1, y -> !e2, z -> !e3, w -> !e4 ]]'
+      result: '[[ x -> !e11, y -> !e22, z -> !e33, w -> !e44 ]]'
+      where:
+        - meta: '!e11'
+          function: sed
+          args:
+            - '!e1'
+            - '"s/java\\/lang\\/Boolean/java\\/lang\\/Integer/g"'
+        - meta: '!e22'
+          function: sed
+          args:
+            - 𝑒2
+            - '"s/\\(\\).+/invokevirtual/g"'
+            - '"s/\\(.+\\).+/invokestatic/g"'
+        - meta: '!e33'
+          function: sed
+          args:
+            - '!e3'
+            - '"s/\\(.+\\).+/invokestatic/g"'
+            - '"s/\\(\\).+/invokevirtual/g"'
+        - meta: '!e44'
+          function: sed
+          args:
+            - '!e4'
+            - '"s/\\//\\\\\\//g"'
diff --git a/test-resources/rewriter-packs/custom/sed.yaml b/test-resources/rewriter-packs/custom/sed.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sed.yaml
@@ -0,0 +1,39 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> "  he llo  ",
+    y -> "1234567891011",
+    z -> "Hello, 2025, 2026",
+    w -> "hi (jeff)"
+  ]]
+output: |-
+  ⟦
+    x ↦ "hello",
+    y ↦ "11!",
+    z ↦ "Hello, 2025!, 2026!",
+    w ↦ "jeff hi",
+    o ↦ "((he(llo(("
+  ⟧
+rules:
+  custom:
+    - name: concat from expression
+      pattern: '[[ x -> !e1, y -> !e2, z -> !e10, w -> !e11 ]]'
+      result: '[[ x -> !e3, y -> !e4, z -> !e20, w -> !e21, o -> !e5 ]]'
+      where:
+        - meta: '!e3'
+          function: sed
+          args: ['!e1', '"s/\\s+//g"']
+        - meta: '!e4'
+          function: sed
+          args: ['!e2', '"s/(1)(2)(3)(4)(5)(6)(7)(8)(9)(10)(11)/$11!/g"']
+        - meta: '!e20'
+          function: sed
+          args: ['!e10', '"s/([0-9]+)/$1!/g"']
+        - meta: '!e21'
+          function: sed
+          args: ['!e11', '"s/(hi) \\((jeff)\\)/$2 $1"']
+        - meta: '!e5'
+          function: sed
+          args: ['!e1', '"s/\\s/(/g"']
diff --git a/test-resources/rewriter-packs/custom/sequential-rules-bug-714.yaml b/test-resources/rewriter-packs/custom/sequential-rules-bug-714.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sequential-rules-bug-714.yaml
@@ -0,0 +1,26 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Regression test for https://github.com/objectionary/phino/issues/714
+# Rule A (fast path) removes void bindings until the formation is empty.
+# Rule B must then match the empty formation [[ ]] and replace it.
+# Without the fix, rule A produces ExFormation [] (no rho binding) while
+# rule B's pattern is parsed as ExFormation [BiVoid AtRho], so they never match.
+repeat: 5
+must: 1
+input: |
+  [[ x -> ? ]]
+output: |
+  [[ x -> Q ]]
+rules:
+  custom:
+    - name: remove void
+      pattern: |
+        [[ !B1, !t1 -> ?, !B2 ]]
+      result: |
+        [[ !B1, !B2 ]]
+    - name: empty to filled
+      pattern: |
+        [[ ]]
+      result: |
+        [[ x -> Q ]]
diff --git a/test-resources/rewriter-packs/custom/size-of-bindings.yaml b/test-resources/rewriter-packs/custom/size-of-bindings.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/size-of-bindings.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[
+    x -> [[
+      y -> 1,
+      z -> 2,
+      w -> 3
+    ]]
+  ]]
+output: |-
+  3:x
+rules:
+  custom:
+    - name: size of bindings
+      pattern: '[[ x -> [[ !B1 ]] ]]'
+      result: '[[ x -> !e1 ]]'
+      where:
+        - meta: '!e1'
+          function: size
+          args: ['!B1']
diff --git a/test-resources/rewriter-packs/custom/string.yaml b/test-resources/rewriter-packs/custom/string.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/string.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |
+  [[ attr -> 55 ]]
+output: |-
+  ⟦ str ↦ "attr", num -> "55" ⟧
+rules:
+  custom:
+    - name: string function
+      pattern: '[[ !t1 -> !e1, !B1 ]]'
+      result: '[[ str -> !e-str, num -> !e-num, !B1 ]]'
+      where:
+        - meta: '!e-str'
+          function: string
+          args: ['!t1']
+        - meta: '!e-num'
+          function: string
+          args: ['!e1']
diff --git a/test-resources/rewriter-packs/custom/sum.yaml b/test-resources/rewriter-packs/custom/sum.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/sum.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> 5, y -> 6 ]]'
+output: |-
+  ⟦
+    z -> 12
+  ⟧
+rules:
+  custom:
+    - name: sum
+      pattern: '[[ x -> !e1, y -> !e2 ]]'
+      result: '[[ z -> !e3 ]]'
+      where:
+        - meta: '!e3'
+          function: sum
+          args:
+            - '!e1'
+            - '!e2'
+            - '1'
diff --git a/test-resources/rewriter-packs/custom/with-alpha-index.yaml b/test-resources/rewriter-packs/custom/with-alpha-index.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-alpha-index.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> ? ]](~0 -> Q.y)'
+output: '⟦ x ↦ ∅ ⟧(~0 ↦ Φ)'
+rules:
+  custom:
+    - name: with-alpha-index
+      pattern: '[[ !B1 ]](α𝑖1 ↦ !e1)'
+      result: '[[ !B1 ]](α𝑖1 ↦ Φ)'
+      when:
+        eq:
+          - 𝑖1
+          - 0
diff --git a/test-resources/rewriter-packs/custom/with-alpha-meta.yaml b/test-resources/rewriter-packs/custom/with-alpha-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-alpha-meta.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[x -> $.q, y -> Q.a]](~0 -> Q.x)'
+output: |-
+  ⟦
+    y ↦ Φ.a
+  ⟧
+rules:
+  custom:
+    - name: with-alpha-meta
+      pattern: '[[!t1 -> $.q, !B1]](α𝑖1 ↦ !e1)'
+      result: '[[!B1]]'
diff --git a/test-resources/rewriter-packs/custom/with-complex-condition.yaml b/test-resources/rewriter-packs/custom/with-complex-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-complex-condition.yaml
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[x -> [[a -> Q.z]], y -> [[b -> $.w]], L> Func ]].x'
+output: |-
+  ⟦
+    a ↦ Φ.z,
+    y ↦ ⟦
+      b ↦ ξ.w
+    ⟧,
+    λ ⤍ Func
+  ⟧
+rules:
+  custom:
+    - name: with-complex-condition
+      pattern: '[[ !t1 -> [[!B1]], !B2 ]].!t1'
+      result: '[[ !B1, !B2 ]]'
+      when:
+        and:
+          - in:
+              - 'a'
+              - '!B1'
+          - or:
+              - in:
+                  - 'Δ'
+                  - '!B2'
+              - in:
+                  - 'λ'
+                  - '!B2'
diff --git a/test-resources/rewriter-packs/custom/with-different-metas.yaml b/test-resources/rewriter-packs/custom/with-different-metas.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-different-metas.yaml
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[ x -> "hello", y -> "world" ]]'
+output: |-
+  ⟦
+    z ↦ "hello world"
+  ⟧
+rules:
+  custom:
+    - name: concat with metas
+      pattern: '[[ x -> !e_hello, y -> 𝑒-world, 𝐵-x ]]'
+      result: '[[ z -> !e_hello_world, 𝐵-x ]]'
+      where:
+        - meta: '!d-space'
+          function: dataize
+          args: ['" "']
+        - meta: '𝛿_HELLO'
+          function: dataize
+          args: ['!e_hello']
+        - meta: '!d-WORLD1'
+          function: dataize
+          args: ['𝑒-world']
+        - meta: '!e_hello_world'
+          function: concat
+          args:
+            - '𝛿_HELLO'
+            - '!d-space'
+            - '!d-WORLD1'
diff --git a/test-resources/rewriter-packs/custom/with-eq-condition.yaml b/test-resources/rewriter-packs/custom/with-eq-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-eq-condition.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[x -> $.q, z -> Q.a]]'
+output: |-
+  ⟦
+    z ↦ Φ.a
+  ⟧
+rules:
+  custom:
+    - name: with-eq-condition
+      pattern: '[[!t1 -> $.q, !t2 -> !e1, !B1]]'
+      result: '[[!t2 -> !e1, !B1]]'
+      when:
+        and:
+          - eq:
+              - '!t1'
+              - 'x'
+          - not:
+              eq:
+                - '!t2'
+                - 'y'
diff --git a/test-resources/rewriter-packs/custom/with-meta-names.yaml b/test-resources/rewriter-packs/custom/with-meta-names.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-meta-names.yaml
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: |-
+  ⟦
+    i1 ↦ ⟦
+      a ↦ "foo",
+      b ↦ "bar-1"
+    ⟧,
+    i2 ↦ ⟦
+      a ↦ "foo",
+      b ↦ "bar-2"
+    ⟧
+  ⟧
+output: '⟦⟧'
+rules:
+  custom:
+    - name: custom
+      pattern: |
+        ⟦
+          𝜏1 ↦ ⟦
+            a ↦ 𝑒-x,
+            b ↦ 𝑒-first
+          ⟧,
+          𝜏2 ↦ ⟦
+            a ↦ 𝑒-x,
+            b ↦ 𝑒-second
+          ⟧,
+          𝐵6
+        ⟧
+      result: ⟦⟧
diff --git a/test-resources/rewriter-packs/custom/with-simple-condition.yaml b/test-resources/rewriter-packs/custom/with-simple-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/custom/with-simple-condition.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+input: '[[x -> $.q, y -> Q.a]].x'
+output: |-
+  ⟦
+    x ↦ ξ.q,
+    y ↦ Φ.a
+  ⟧.φ.x
+rules:
+  custom:
+    - name: with-simple-condition
+      pattern: '[[!B1]].!t1'
+      result: '[[!B1]].@.!t1'
+      when:
+        and:
+          - in:
+              - '!t1'
+              - '!B1'
+          - in:
+              - 'y'
+              - '!B1'
diff --git a/test-resources/rewriter-packs/normalize/adds-no-rho.yaml b/test-resources/rewriter-packs/normalize/adds-no-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/adds-no-rho.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# No formation gets a ρ it never declared, so nested formations come back
+# just as they were written (#1407).
+normalize: true
+input: |
+  ⟦ x ↦ ⟦ y ↦ ⟦⟧ ⟧ ⟧
+output: |-
+  ⟦ x ↦ ⟦ y ↦ ⟦⟧ ⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/dispatches-rho-on-a-formation-without-rho.yaml b/test-resources/rewriter-packs/normalize/dispatches-rho-on-a-formation-without-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/dispatches-rho-on-a-formation-without-rho.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ φ ↦ ξ.y, y ↦ ⟦⟧ ⟧.ρ ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-ald.yaml b/test-resources/rewriter-packs/normalize/e-ald.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-ald.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ k ↦ ⟦ x ↦ ∅ ⟧ ( α1 ↦ ⟦ Δ ⤍ 42- ⟧ ).x ⟧
+output: |-
+  ⟦ k ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-alp.yaml b/test-resources/rewriter-packs/normalize/e-alp.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-alp.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ k ↦ ⟦ x ↦ ∅ ⟧ ( α0 ↦ ⟦ Δ ⤍ 42- ⟧ ).x ⟧
+output: |-
+  42-:Δ:k
diff --git a/test-resources/rewriter-packs/normalize/e-ami.yaml b/test-resources/rewriter-packs/normalize/e-ami.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-ami.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ∅ ⟧ ( α2 ↦ ⟦⟧ ) ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-app.yaml b/test-resources/rewriter-packs/normalize/e-app.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-app.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ k ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧ (t ↦ ⟦ Δ ⤍ 42- ⟧) ⟧
+output: |-
+  ⟦ k ↦ ⟦ x ↦ ξ.t, t ↦ ⟦ Δ ⤍ 42- ⟧ ⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-cpy-bot.yaml b/test-resources/rewriter-packs/normalize/e-cpy-bot.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-cpy-bot.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ r ↦ ∅ ⟧( r ↦ ⟦ x ↦ ∅ ⟧( x ↦ ⊥ ) )
+output: |-
+  ⊥:x:r
diff --git a/test-resources/rewriter-packs/normalize/e-cpy.yaml b/test-resources/rewriter-packs/normalize/e-cpy.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-cpy.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ∅, y ↦ ξ.x ⟧ (x ↦ ⟦ Δ ⤍ 42- ⟧).y ⟧
+output: |-
+  42-:Δ:m
diff --git a/test-resources/rewriter-packs/normalize/e-dl.yaml b/test-resources/rewriter-packs/normalize/e-dl.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-dl.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ Δ ⤍ 42-, λ ⤍ Fn ⟧ ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-dl2.yaml b/test-resources/rewriter-packs/normalize/e-dl2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-dl2.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# 'dl' must terminate regardless of the order of the Δ and λ bindings, so
+# this mirrors 'e-dl' with λ placed before Δ.
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ λ ⤍ Fn, Δ ⤍ 42- ⟧ ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-fnk.yaml b/test-resources/rewriter-packs/normalize/e-fnk.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-fnk.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ t ↦ ∅ ⟧ (t ↦ ξ.k), k ↦ ⟦ λ ⤍ Fn ⟧ ⟧.x.t.p ⟧
+output: |-
+  Fn:λ.p:m
diff --git a/test-resources/rewriter-packs/normalize/e-int.yaml b/test-resources/rewriter-packs/normalize/e-int.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-int.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ t ↦ ⟦ Δ ⤍ 42- ⟧ ⟧.t ⟧.x ⟧
+output: |-
+  42-:Δ:m
diff --git a/test-resources/rewriter-packs/normalize/e-lam.yaml b/test-resources/rewriter-packs/normalize/e-lam.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-lam.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ λ ⤍ Fn ⟧.k ⟧.x ⟧
+output: |-
+  Fn:λ.k( ρ ↦ Fn:λ.k:x ):m
diff --git a/test-resources/rewriter-packs/normalize/e-mut.yaml b/test-resources/rewriter-packs/normalize/e-mut.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-mut.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# Mutually self-referential attributes (a via ξ.b, b via ξ.a) also used to
+# diverge in DOT (see #967); each self-ξ now dispatches on a formation without
+# the dispatched attribute and collapses to ⊥, so normalization terminates.
+normalize: true
+input: |
+  ⟦ a ↦ ξ.b, b ↦ ξ.a ⟧.a
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/normalize/e-nf.yaml b/test-resources/rewriter-packs/normalize/e-nf.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-nf.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ⟦ t ↦ Φ.x ⟧ ⟧
+output: |-
+  ⟦ x ↦ ⟦ t ↦ Φ.x ⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-nk.yaml b/test-resources/rewriter-packs/normalize/e-nk.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-nk.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ξ.k, k ↦ ⟦ Δ ⤍ 42- ⟧ ⟧
+output: |-
+  ⟦ x ↦ ξ.k, k ↦ ⟦ Δ ⤍ 42- ⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-np.yaml b/test-resources/rewriter-packs/normalize/e-np.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-np.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ξ.t, λ ⤍ Fn ⟧
+output: |-
+  ⟦ x ↦ ξ.t, λ ⤍ Fn ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-nr.yaml b/test-resources/rewriter-packs/normalize/e-nr.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-nr.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ξ.k, t ↦ ⟦ Δ ⤍ 42- ⟧ ⟧
+output: |-
+  ⟦ x ↦ ξ.k, t ↦ ⟦ Δ ⤍ 42- ⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-nt.yaml b/test-resources/rewriter-packs/normalize/e-nt.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-nt.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ Δ ⤍ 42- ⟧, λ ⤍ Fn ⟧.y ⟧
+output: |-
+  ⟦ m ↦ ⟦ x ↦ ⟦ Δ ⤍ 42- ⟧, λ ⤍ Fn ⟧.y ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-ov2.yaml b/test-resources/rewriter-packs/normalize/e-ov2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-ov2.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ∅, y ↦ ∅ ⟧( α0 ↦ ⟦⟧ )( α0 ↦ ⟦⟧ )
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/normalize/e-ova.yaml b/test-resources/rewriter-packs/normalize/e-ova.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-ova.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ x ↦ ⟦⟧, ρ ↦ ∅ ⟧( α0 ↦ ⟦⟧ )
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/normalize/e-phi.yaml b/test-resources/rewriter-packs/normalize/e-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-phi.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# PHI is no longer a normalization rule (#973): resolving an inherited
+# attribute through the φ decoration is a semantic step, so it now lives in
+# morphing 𝕄. Dispatching the absent 't' therefore no longer expands via
+# '.φ.t'; normalization reaches a stuck normal form with the datum intact and
+# leaves the φ-decorated dispatch for 𝕄 to follow.
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.t, φ ↦ ⟦ t ↦ ⟦⟧ ⟧ ⟧.x ⟧
+output: |-
+  ⟦⟧:t:φ.t( ρ ↦ ⟦ x ↦ t, φ ↦ ⟦⟧:t ⟧ ):m
diff --git a/test-resources/rewriter-packs/normalize/e-rep.yaml b/test-resources/rewriter-packs/normalize/e-rep.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-rep.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.t (k ↦ ξ.f).k, t ↦ ⟦ k ↦ ∅ ⟧, f ↦ ⟦⟧ ⟧.x ⟧
+output: |-
+  ⟦⟧:m
diff --git a/test-resources/rewriter-packs/normalize/e-rha.yaml b/test-resources/rewriter-packs/normalize/e-rha.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-rha.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ ρ ↦ ∅ ⟧.ρ.k, k ↦ ⟦ Δ ⤍ 42- ⟧ ⟧.x ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-rhi.yaml b/test-resources/rewriter-packs/normalize/e-rhi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-rhi.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ t ↦ ⟦ p ↦ ξ.ρ.ρ.k ⟧.p ⟧.t, k ↦ ⟦ Δ ⤍ 42- ⟧ ⟧.x ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-rho.yaml b/test-resources/rewriter-packs/normalize/e-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-rho.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.ρ.ρ.t ⟧.x ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/e-self.yaml b/test-resources/rewriter-packs/normalize/e-self.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-self.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A self-referential object dispatched on its own attribute used to loop
+# forever in DOT (see #967); now the dispatched attribute is excluded from the
+# contextualization context, so the self-ξ collapses to ⊥ and normalization
+# terminates.
+normalize: true
+input: |
+  ⟦ a ↦ ξ.a ⟧.a
+output: |-
+  ⊥
diff --git a/test-resources/rewriter-packs/normalize/e-twi.yaml b/test-resources/rewriter-packs/normalize/e-twi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-twi.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ⟦ t ↦ ξ.ρ.k.ρ.t, ρ ↦ ∅ ⟧, k ↦ ⟦ ρ ↦ ∅ ⟧, t ↦ ⟦⟧ ⟧.x.t ⟧
+output: |-
+  ⟦⟧:m
diff --git a/test-resources/rewriter-packs/normalize/e-xxi.yaml b/test-resources/rewriter-packs/normalize/e-xxi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/e-xxi.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ x ↦ ξ.t, t ↦ ∅ ⟧.x ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/hides-the-dispatched-attribute-from-a-sibling.yaml b/test-resources/rewriter-packs/normalize/hides-the-dispatched-attribute-from-a-sibling.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/hides-the-dispatched-attribute-from-a-sibling.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A sibling reached through ξ is read from the formation without the
+# dispatched binding, and its own ξ sees that short formation too, so the
+# answer is the short formation of the short formation, not the whole one:
+# a body never reaches the attribute it is dispatched from (#967, #1438).
+normalize: true
+input: |
+  ⟦ t ↦ ⟦ a ↦ ξ, b ↦ ξ.a ⟧.b ⟧
+output: |-
+  ⟦ t ↦ ⟦⟧ ⟧
diff --git a/test-resources/rewriter-packs/normalize/misses-an-undeclared-rho.yaml b/test-resources/rewriter-packs/normalize/misses-an-undeclared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/misses-an-undeclared-rho.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation declaring no ρ has no receiver, so its ξ.ρ is ⊥ however it was
+# dispatched (#1407).
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ t ↦ ξ.ρ.k ⟧ ⟧.a.t ⟧
+output: |-
+  ⟦ m ↦ ⊥ ⟧
diff --git a/test-resources/rewriter-packs/normalize/reaches-a-declared-rho.yaml b/test-resources/rewriter-packs/normalize/reaches-a-declared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/rewriter-packs/normalize/reaches-a-declared-rho.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation declaring ρ among its voids gets the object it was dispatched
+# on as its receiver, so its ξ.ρ reaches the sibling k (#1407).
+normalize: true
+input: |
+  ⟦ m ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧ ⟧.a.t ⟧
+output: |-
+  ⟦ m ↦ ⟦ Δ ⤍ 01- ⟧ ⟧
diff --git a/test-resources/xmir-parsing-packs/alpha-dispatch-illegal.yaml b/test-resources/xmir-parsing-packs/alpha-dispatch-illegal.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/alpha-dispatch-illegal.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".α0">
+        <o base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/alpha-name-with-base.yaml b/test-resources/xmir-parsing-packs/alpha-name-with-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/alpha-name-with-base.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="α0" base="∅"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/alpha-name-without-base.yaml b/test-resources/xmir-parsing-packs/alpha-name-without-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/alpha-name-without-base.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="α0">
+        <o name="y" base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/alpha-non-integer-application-arg.yaml b/test-resources/xmir-parsing-packs/alpha-non-integer-application-arg.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/alpha-non-integer-application-arg.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".foo">
+        <o base="Φ"/>
+        <o as="αX" base="Φ.number"><o>40-14-00-00-00-00-00-00</o></o>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/application-arg-base-and-text.yaml b/test-resources/xmir-parsing-packs/application-arg-base-and-text.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/application-arg-base-and-text.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus">
+        <o base="Φ.number"/>
+        <o as="arg" base="Φ.x">01-02</o>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/application-arg-with-name.yaml b/test-resources/xmir-parsing-packs/application-arg-with-name.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/application-arg-with-name.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus">
+        <o base="Φ.number"/>
+        <o as="arg" name="bad" base="Φ.x"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/application-with-dispatch.yaml b/test-resources/xmir-parsing-packs/application-with-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/application-with-dispatch.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus">
+        <o base="Φ.number">
+          <o>00-00-00-01</o>
+        </o>
+        <o as="arg" base="Φ.number">
+          <o>00-00-00-02</o>
+        </o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q.number([[ D> 00-00-00-01 ]]).plus(arg -> Q.number([[ D> 00-00-00-02 ]])) ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/application.yaml b/test-resources/xmir-parsing-packs/application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/application.yaml
@@ -0,0 +1,19 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ.org.eolang">
+        <o base="Φ.x"/>
+        <o as="α1" base=".plus">
+          <o base="Φ.y"/>
+        </o>
+        <o as="arg">
+          <o name="y" base="ξ"/>
+        </o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q.org.eolang(α0 -> Q.x)(α1 -> Q.y.plus)(arg -> [[ y -> $ ]]) ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/as-with-colon.yaml b/test-resources/xmir-parsing-packs/as-with-colon.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/as-with-colon.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="a" base="Φ.x">
+        <o as="b:c" base="ξ"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/atoms.yaml b/test-resources/xmir-parsing-packs/atoms.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/atoms.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x">
+        <o name="void" base="∅"/>
+        <o name="λ"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> [[ void -> ?, L> L_app_x ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/attribute-uppercase.yaml b/test-resources/xmir-parsing-packs/attribute-uppercase.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/attribute-uppercase.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".Foo">
+        <o base="Φ.z"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/attribute-with-dot.yaml b/test-resources/xmir-parsing-packs/attribute-with-dot.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/attribute-with-dot.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".foo.bar">
+        <o base="Φ.z"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-dispatch.yaml b/test-resources/xmir-parsing-packs/base-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-dispatch.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ.org.eolang"/>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q.org.eolang ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/base-dot-no-children.yaml b/test-resources/xmir-parsing-packs/base-dot-no-children.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-dot-no-children.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-dot-only.yaml b/test-resources/xmir-parsing-packs/base-dot-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-dot-only.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="."/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-just-dot.yaml b/test-resources/xmir-parsing-packs/base-just-dot.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-just-dot.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="."/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-nonsense.yaml b/test-resources/xmir-parsing-packs/base-nonsense.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-nonsense.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="foo"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-part-with-colon.yaml b/test-resources/xmir-parsing-packs/base-part-with-colon.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-part-with-colon.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="a" base="ξ.b:c"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/base-root-dot-only.yaml b/test-resources/xmir-parsing-packs/base-root-dot-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/base-root-dot-only.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ."/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/bound-rho.yaml b/test-resources/xmir-parsing-packs/bound-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/bound-rho.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="k">
+        <o name="ρ">
+          <o name="y" base="∅"/>
+        </o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ k -> [[ ^ -> [[ y -> ? ]] ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/data-in-dispatched-formation.yaml b/test-resources/xmir-parsing-packs/data-in-dispatched-formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/data-in-dispatched-formation.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus">
+        <o>01-02</o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> [[ D> 01-02 ]].plus ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/data-in-named-binding.yaml b/test-resources/xmir-parsing-packs/data-in-named-binding.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/data-in-named-binding.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="k">
+        <o name="a">01-02</o>
+        <o name="ρ">03-04</o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ k -> [[ a -> [[ D> 01-02 ]], ^ -> [[ D> 03-04 ]] ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/declared-rho.yaml b/test-resources/xmir-parsing-packs/declared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/declared-rho.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A void ρ in XMIR is the receiver the formation declares, and a formation
+# without one reads back with no ρ at all (#1407).
+xmir: |
+  <object>
+    <o name="app">
+      <o name="m">
+        <o name="ρ" base="∅"/>
+        <o name="x" base="∅"/>
+      </o>
+      <o name="k">
+        <o name="y" base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ m -> [[ ^ -> ?, x -> ? ]], k -> [[ y -> ? ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/dispatch-rho.yaml b/test-resources/xmir-parsing-packs/dispatch-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/dispatch-rho.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".ρ">
+        <o base="Φ"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q.ρ ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/dot-only-base.yaml b/test-resources/xmir-parsing-packs/dot-only-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/dot-only-base.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".">
+        <o base="Φ.y"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/double-dot-invalid.yaml b/test-resources/xmir-parsing-packs/double-dot-invalid.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/double-dot-invalid.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ..x"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/empty-name-attribute-2.yaml b/test-resources/xmir-parsing-packs/empty-name-attribute-2.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/empty-name-attribute-2.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="" base="∅"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/empty-name-attribute.yaml b/test-resources/xmir-parsing-packs/empty-name-attribute.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/empty-name-attribute.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name=""/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/formation-dispatch.yaml b/test-resources/xmir-parsing-packs/formation-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/formation-dispatch.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".plus">
+        <o>
+          <o name="plus" base="Φ.number"/>
+        </o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> [[ plus -> Q.number ]].plus ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/formation.yaml b/test-resources/xmir-parsing-packs/formation.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/formation.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ"/>
+      <o name="y" base="ξ"/>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q, y -> $ ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/lambda-derived-name.yaml b/test-resources/xmir-parsing-packs/lambda-derived-name.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/lambda-derived-name.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="foo">
+      <o name="l🌵ab12">
+        <o base="∅" name="v0"/>
+        <o name="λ"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ foo -> [[ l🌵ab12 -> [[ v0 -> ?, L> L_foo_l_ab12 ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/lambda-with-name.yaml b/test-resources/xmir-parsing-packs/lambda-with-name.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/lambda-with-name.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x">
+        <o name="void" base="∅"/>
+        <o name="λ">Lorg_eolang_number_plus</o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> [[ void -> ?, L> Lorg_eolang_number_plus ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/multiple-root-formations.yaml b/test-resources/xmir-parsing-packs/multiple-root-formations.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/multiple-root-formations.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# <object> allows at most one direct <o> (XMIR.xsd, objectionary/eo#8548
+# consumers validate against it), so two sibling roots are malformed. A
+# residual formation (see #1076) is printed under ONE attribute-free <o>,
+# not as several roots.
+failure: true
+xmir: |
+  <object>
+    <o name="app"/>
+    <o name="other"/>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/name-at-sign.yaml b/test-resources/xmir-parsing-packs/name-at-sign.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/name-at-sign.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="@" base="ξ.x"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/name-capitalized.yaml b/test-resources/xmir-parsing-packs/name-capitalized.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/name-capitalized.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="X" base="ξ.x"/>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/name-with-colon.yaml b/test-resources/xmir-parsing-packs/name-with-colon.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/name-with-colon.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="a:b" base="ξ.x"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/non-package-meta.yaml b/test-resources/xmir-parsing-packs/non-package-meta.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/non-package-meta.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <metas>
+      <meta>
+        <head>other</head>
+        <tail>value</tail>
+        <part>value</part>
+      </meta>
+    </metas>
+    <o name="app"/>
+  </object>
+phi: '[[ app -> [[]] ]]'
diff --git a/test-resources/xmir-parsing-packs/package-capitalized.yaml b/test-resources/xmir-parsing-packs/package-capitalized.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/package-capitalized.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <metas>
+      <meta>
+        <head>package</head>
+        <tail>Org.X</tail>
+        <part>Org.X</part>
+      </meta>
+    </metas>
+    <o name="app">
+      <o name="x" base="Φ.org"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/package-with-empty-part.yaml b/test-resources/xmir-parsing-packs/package-with-empty-part.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/package-with-empty-part.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <metas>
+      <meta>
+        <head>package</head>
+        <tail>org..eolang</tail>
+        <part>org..eolang</part>
+      </meta>
+    </metas>
+    <o name="app">
+      <o name="x" base="Φ.org"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/phi-dot-only.yaml b/test-resources/xmir-parsing-packs/phi-dot-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/phi-dot-only.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ."/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/phi-with-application.yaml b/test-resources/xmir-parsing-packs/phi-with-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/phi-with-application.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ">
+        <o base="Φ.y"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/phi-without-base.yaml b/test-resources/xmir-parsing-packs/phi-without-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/phi-without-base.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="k">
+        <o name="φ">
+          <o name="y" base="∅"/>
+        </o>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ k -> [[ @ -> [[ y -> ? ]] ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/reverse-dispatch.yaml b/test-resources/xmir-parsing-packs/reverse-dispatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/reverse-dispatch.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base=".eolang">
+        <o base="Φ.org"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> Q.org.eolang ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/rho-with-base.yaml b/test-resources/xmir-parsing-packs/rho-with-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/rho-with-base.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="k">
+        <o name="ρ" base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ k -> [[ ^ -> ? ]] ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/root-application-illegal.yaml b/test-resources/xmir-parsing-packs/root-application-illegal.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/root-application-illegal.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="Φ">
+        <o base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/root-element-name-mismatch.yaml b/test-resources/xmir-parsing-packs/root-element-name-mismatch.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/root-element-name-mismatch.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <foo>
+    <o name="app"/>
+  </foo>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/root-not-object.yaml b/test-resources/xmir-parsing-packs/root-not-object.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/root-not-object.yaml
@@ -0,0 +1,7 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <foo/>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/simple.yaml b/test-resources/xmir-parsing-packs/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/simple.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object
+    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
+    xsi:noNamespaceSchemaLocation="https://raw.githubusercontent.com/objectionary/eo/refs/heads/gh-pages/XMIR.xsd"
+  >
+    <o name="app"/>
+  </object>
+phi: '[[ app -> [[]] ]]'
diff --git a/test-resources/xmir-parsing-packs/termination-base.yaml b/test-resources/xmir-parsing-packs/termination-base.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/termination-base.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="⊥"/>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> T ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/termination-with-application.yaml b/test-resources/xmir-parsing-packs/termination-with-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/termination-with-application.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="⊥">
+        <o base="Φ.number"/>
+      </o>
+    </o>
+  </object>
+phi: '[[ app -> [[ x -> T(Q.number) ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/unnamed-formation-child.yaml b/test-resources/xmir-parsing-packs/unnamed-formation-child.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/unnamed-formation-child.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/with-duplicate-attribute.yaml b/test-resources/xmir-parsing-packs/with-duplicate-attribute.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/with-duplicate-attribute.yaml
@@ -0,0 +1,12 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x"/>
+      <o name="x"/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/with-package.yaml b/test-resources/xmir-parsing-packs/with-package.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/with-package.yaml
@@ -0,0 +1,29 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+xmir: |
+  <object>
+    <metas>
+      <meta>
+        <head>package</head>
+        <tail>org.eolang</tail>
+        <part>org.eolang</part>
+      </meta>
+    </metas>
+    <o name="app">
+      <o name="x" base="Φ.org.eolang"/>
+    </o>
+  </object>
+phi: |
+  [[
+    org -> [[
+      eolang -> [[
+        app -> [[
+          x -> Q.org.eolang
+        ]],
+        L> Package
+      ]],
+      L> Package
+    ]]
+  ]]
diff --git a/test-resources/xmir-parsing-packs/with-phi.yaml b/test-resources/xmir-parsing-packs/with-phi.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/with-phi.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+xmir: |
+  <object>
+    <o name="app">
+      <o name="φ" base="ξ.φ"/>
+    </o>
+  </object>
+phi: '[[ app -> [[ φ -> $.φ ]] ]]'
diff --git a/test-resources/xmir-parsing-packs/with-pretty-printed-package.yaml b/test-resources/xmir-parsing-packs/with-pretty-printed-package.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/with-pretty-printed-package.yaml
@@ -0,0 +1,31 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+xmir: |
+  <object>
+    <metas>
+      <meta>
+        <head>package</head>
+        <tail>
+          org.eolang
+        </tail>
+        <part>org.eolang</part>
+      </meta>
+    </metas>
+    <o name="app">
+      <o name="x" base="Φ.org.eolang"/>
+    </o>
+  </object>
+phi: |
+  [[
+    org -> [[
+      eolang -> [[
+        app -> [[
+          x -> Q.org.eolang
+        ]],
+        L> Package
+      ]],
+      L> Package
+    ]]
+  ]]
diff --git a/test-resources/xmir-parsing-packs/xi-application-illegal.yaml b/test-resources/xmir-parsing-packs/xi-application-illegal.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/xi-application-illegal.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="ξ">
+        <o base="∅"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/xi-dot-only.yaml b/test-resources/xmir-parsing-packs/xi-dot-only.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/xi-dot-only.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="ξ."/>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-parsing-packs/xi-with-application.yaml b/test-resources/xmir-parsing-packs/xi-with-application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-parsing-packs/xi-with-application.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+failure: true
+xmir: |
+  <object>
+    <o name="app">
+      <o name="x" base="ξ">
+        <o base="Φ.y"/>
+      </o>
+    </o>
+  </object>
+phi: ''
diff --git a/test-resources/xmir-printing-packs/application.yaml b/test-resources/xmir-printing-packs/application.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/application.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    foo -> [[
+      @ -> $.^.z,
+      bar -> Q.y(5, "foo")
+    ]]
+  ]]
+xpaths:
+  - '/object/o[@name="foo" and o[2][@base="Φ.y"]]'
+  - '/object/o/o/o[@as="α0" and @base="Φ.number"]'
+  - '/object/o/o/o[@as="α1" and @base="Φ.string"]'
+  - '/object/o/o[@name="φ" and @base="ξ.ρ.z"]'
diff --git a/test-resources/xmir-printing-packs/declared-rho.yaml b/test-resources/xmir-printing-packs/declared-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/declared-rho.yaml
@@ -0,0 +1,17 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:line-length
+# A void ρ a formation declares is written as a void of its own, while a
+# formation declaring none gets no ρ in XMIR either (#1407).
+phi: |
+  [[
+    app -> [[
+      m -> [[ ^ -> ?, x -> ? ]],
+      k -> [[ y -> ? ]]
+    ]]
+  ]]
+xpaths:
+  - '/object/o[@name="app"]/o[@name="m"]/o[@name="ρ" and @base="∅"]'
+  - '/object/o[@name="app"]/o[@name="m"]/o[@name="x" and @base="∅"]'
+  - '/object/o[@name="app"]/o[@name="k"]/o[@name="y" and @base="∅"]'
diff --git a/test-resources/xmir-printing-packs/dispatch-chain.yaml b/test-resources/xmir-printing-packs/dispatch-chain.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/dispatch-chain.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    x -> [[ plus -> Q.number ]].plus.foo
+  ]]
+xpaths:
+  - '/object/o[@name="x" and @base=".foo"]/o[@base=".plus"]/o/o[@name="plus" and @base="Φ.number"]'
diff --git a/test-resources/xmir-printing-packs/document-author.yaml b/test-resources/xmir-printing-packs/document-author.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/document-author.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    x -> 5
+  ]]
+xpaths:
+  - '/object[@author="phino"]'
diff --git a/test-resources/xmir-printing-packs/formation-argument.yaml b/test-resources/xmir-printing-packs/formation-argument.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/formation-argument.yaml
@@ -0,0 +1,10 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    x -> Q.foo(bar -> [[ y -> ? ]])
+  ]]
+xpaths:
+  - '/object/o[@name="x"]/o[@as="bar"]/o[@name="y" and @base="∅"]'
diff --git a/test-resources/xmir-printing-packs/lambda-and-rho.yaml b/test-resources/xmir-printing-packs/lambda-and-rho.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/lambda-and-rho.yaml
@@ -0,0 +1,18 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    k -> [[
+      x -> ?,
+      L> Lorg_eolang_number_plus,
+      ^ -> [[
+        y -> ?
+      ]]
+    ]]
+  ]]
+xpaths:
+  - '/object/o[@name="k"]/o[@name="λ"]'
+  - '/object/o[@name="k"][o="Lorg_eolang_number_plus"]'
+  - '/object/o[@name="k"]/o[@name="ρ"]/o[@name="y" and @base="∅"]'
diff --git a/test-resources/xmir-printing-packs/legacy-positional-data-object.yaml b/test-resources/xmir-printing-packs/legacy-positional-data-object.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/legacy-positional-data-object.yaml
@@ -0,0 +1,16 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+# jeo-maven-plugin versions before 0.15.3 emit the as-bytes/data pair of a
+# primitive data object positionally (α0) instead of by name; 'matchDataObject'
+# in AST.hs still recognizes that legacy form, so it must print exactly like
+# the modern named form.
+phi: |
+  [[
+    x -> Q.number(~0 -> Q.bytes(~0 -> [[ D> 40-14-00-00-00-00-00-00 ]]))
+  ]]
+xpaths:
+  - '/object/o[@name="x" and @base="Φ.number"]'
+  - '/object/o/o[@as="φ" and @base="Φ.bytes"]'
+  - '/object/o/o/o[@as="φ"]'
diff --git a/test-resources/xmir-printing-packs/simple.yaml b/test-resources/xmir-printing-packs/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/xmir-printing-packs/simple.yaml
@@ -0,0 +1,20 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+# yamllint disable rule:line-length
+---
+phi: |
+  [[
+    org -> [[
+      eolang -> [[
+        foo -> [[
+          x -> 5
+        ]],
+        L> Package
+      ]],
+      L> Package
+    ]]
+  ]]
+xpaths:
+  - '/object/metas/meta[head="package" and tail="org.eolang"]'
+  - '/object/o[@name="foo"]'
+  - '/object/o/o[@name="x" and @base="Φ.number"]'
diff --git a/test-resources/yaml-packs/simple.yaml b/test-resources/yaml-packs/simple.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-packs/simple.yaml
@@ -0,0 +1,6 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: simple
+pattern: 'Q'
+result: 'Q'
diff --git a/test-resources/yaml-packs/special-attrs-in-condition.yaml b/test-resources/yaml-packs/special-attrs-in-condition.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-packs/special-attrs-in-condition.yaml
@@ -0,0 +1,14 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: special-attrs-in-condition
+pattern: '[[ !B1 ]]'
+result: 'Q'
+when:
+  or:
+    - in:
+        - 'λ'
+        - '!B1'
+    - in:
+        - 'Δ'
+        - '!B1'
diff --git a/test-resources/yaml-packs/when.yaml b/test-resources/yaml-packs/when.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-packs/when.yaml
@@ -0,0 +1,22 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: when
+pattern: '[[ !t1 -> [[ !B1 ]], !t2 -> [[ !B2 ]] ]]'
+result: 'Q.x'
+when:
+  not:
+    and:
+      - or:
+          - and:
+              - in:
+                  - '!t1'
+                  - '!B2'
+              - eq:
+                  - '!t2'
+                  - '^'
+      - or:
+          - and:
+              - in:
+                  - '^'
+                  - '!B1'
diff --git a/test-resources/yaml-packs/where-without-args.yaml b/test-resources/yaml-packs/where-without-args.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-packs/where-without-args.yaml
@@ -0,0 +1,13 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A 'where' extension may omit the 'args' key entirely; it defaults to an
+# empty list. 'random-tau' takes no arguments, so it is written argless.
+name: where-without-args
+pattern: |
+  [[ x -> !e1 ]]
+result: |
+  [[ x -> !e1, !t1 -> [[ ]], ^ -> ? ]]
+where:
+  - meta: '!t1'
+    function: random-tau
diff --git a/test-resources/yaml-typos/duplicate-key.yaml b/test-resources/yaml-typos/duplicate-key.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-typos/duplicate-key.yaml
@@ -0,0 +1,11 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# yamllint disable rule:key-duplicates
+name: duplicate-key
+pattern: 'Q'
+result: 'Q'
+when:
+  eq: [1, 1]
+when:
+  eq: [2, 2]
diff --git a/test-resources/yaml-typos/two-condition-objects.yaml b/test-resources/yaml-typos/two-condition-objects.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-typos/two-condition-objects.yaml
@@ -0,0 +1,9 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: two-condition-objects
+pattern: 'Q'
+result: 'Q'
+when:
+  eq: [1]
+  absolute: [1]
diff --git a/test-resources/yaml-typos/wrong-condition-name.yaml b/test-resources/yaml-typos/wrong-condition-name.yaml
new file mode 100644
--- /dev/null
+++ b/test-resources/yaml-typos/wrong-condition-name.yaml
@@ -0,0 +1,8 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+name: wrong-condition-name
+pattern: 'Q'
+result: 'Q'
+when:
+  eqx: [1]
diff --git a/test/BrokenPipeSpec.hs b/test/BrokenPipeSpec.hs
new file mode 100644
--- /dev/null
+++ b/test/BrokenPipeSpec.hs
@@ -0,0 +1,80 @@
+{-# LANGUAGE CPP #-}
+{-# OPTIONS_GHC -Wno-unused-do-bind #-}
+
+-- SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+-- SPDX-License-Identifier: MIT
+
+module BrokenPipeSpec (spec) where
+
+#ifndef mingw32_HOST_OS
+import CLI (runCLI)
+import Control.Exception (bracket, evaluate, finally, try)
+import GHC.IO.Handle (hDuplicate, hDuplicateTo)
+import System.Directory (removeFile)
+import System.Exit (ExitCode (ExitFailure))
+import System.IO
+import System.Posix.IO (closeFd, createPipe, fdToHandle)
+#endif
+import Test.Hspec
+
+#ifndef mingw32_HOST_OS
+withStdin :: String -> IO a -> IO a
+withStdin input action =
+  bracket (openTempFile "." "stdinXXXXXX.tmp") cleanup $ \(filePath, h) -> do
+    hSetEncoding h utf8
+    hPutStr h input
+    hFlush h
+    hClose h
+    withFile filePath ReadMode $ \hIn -> do
+      hSetEncoding hIn utf8
+      bracket (hDuplicate stdin) restoreStdin $ \_ -> do
+        hDuplicateTo hIn stdin
+        hSetEncoding stdin utf8
+        action
+  where
+    restoreStdin orig = hDuplicateTo orig stdin >> hClose orig
+    cleanup (fp, _) = removeFile fp
+
+withBrokenStdout :: IO a -> IO (String, a)
+withBrokenStdout action =
+  bracket
+    (openTempFile "." "stderrXXXXXX.tmp")
+    (\(fp, _) -> removeFile fp)
+    ( \(path, hErrTmp) -> do
+        hSetEncoding hErrTmp utf8
+        (readEnd, writeEnd) <- createPipe
+        closeFd readEnd
+        writePipe <- fdToHandle writeEnd
+        oldOut <- hDuplicate stdout
+        oldErr <- hDuplicate stderr
+        oldOutBuffering <- hGetBuffering stdout
+        hDuplicateTo writePipe stdout
+        hDuplicateTo hErrTmp stderr
+        hSetBuffering stdout NoBuffering
+
+        result <-
+          action `finally` do
+            hDuplicateTo oldOut stdout >> hClose oldOut
+            hSetBuffering stdout oldOutBuffering
+            hDuplicateTo oldErr stderr >> hClose oldErr
+            hClose writePipe
+            hClose hErrTmp
+
+        captured <- readFile path
+        _ <- evaluate (length captured)
+        return (captured, result)
+    )
+#endif
+
+spec :: Spec
+#ifndef mingw32_HOST_OS
+spec =
+  describe "CLI rewrite" $
+    it "exits quietly, without an [ERROR] line, when stdout is a closed pipe" $
+      withStdin "[[]]" $ do
+        (err, result) <- withBrokenStdout (try (runCLI ["rewrite"]) :: IO (Either ExitCode ()))
+        err `shouldNotContain` "[ERROR]"
+        result `shouldBe` Left (ExitFailure 141)
+#else
+spec = pure ()
+#endif
diff --git a/test/CLIHelpersSpec.hs b/test/CLIHelpersSpec.hs
--- a/test/CLIHelpersSpec.hs
+++ b/test/CLIHelpersSpec.hs
@@ -6,19 +6,37 @@
 module CLIHelpersSpec (spec) where
 
 import AST (Expression (ExRoot))
-import CLI.Helpers (getRules, parseInput, printExpression)
+import CLI.Helpers (getRules, parseInput, printExpression, readInput)
 import CLI.Types (IOFormat (LATEX, PHI, XMIR), PrintContext (PrintCtx))
-import Control.Exception (SomeException, try)
+import Control.Exception (SomeException, bracket, try)
 import Control.Monad (forM_)
+import Data.ByteString qualified as BS
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as TE
+import Data.Time.Clock.POSIX (getPOSIXTime)
 import Lining (LineFormat (MULTILINE))
 import Sugar (SugarType (SWEET))
-import Test.Hspec (Spec, describe, it, shouldBe, shouldSatisfy)
+import System.Directory (createDirectoryIfMissing, getTemporaryDirectory, removeDirectoryRecursive)
+import System.FilePath ((</>))
+import Test.Hspec (Spec, describe, it, shouldBe, shouldReturn, shouldSatisfy)
 import XMIR (defaultXmirContext)
 
 isLeft :: Either e a -> Bool
 isLeft (Left _) = True
 isLeft (Right _) = False
 
+withScratchDir :: (FilePath -> IO a) -> IO a
+withScratchDir =
+  bracket
+    ( do
+        tmp <- getTemporaryDirectory
+        stamp <- getPOSIXTime
+        let dir = tmp </> ("phino-cli-helpers-spec-" ++ show (floor (stamp * 1000000) :: Integer))
+        createDirectoryIfMissing True dir
+        pure dir
+    )
+    removeDirectoryRecursive
+
 {-# ANN testPrintContext ("HLint: ignore Eta reduce" :: String) #-}
 testPrintContext :: IOFormat -> PrintContext
 testPrintContext format =
@@ -49,3 +67,14 @@
     it "deduplicates the same --rule file listed twice" $ do
       rules <- getRules False False ["test-resources/cli/rules/simple.yaml", "test-resources/cli/rules/simple.yaml"]
       length rules `shouldBe` 1
+
+  describe "readInput" $ do
+    it "strips a leading UTF-8 byte order mark from a file" $ withScratchDir $ \dir -> do
+      let path = dir </> "bom.phi"
+      BS.writeFile path (TE.encodeUtf8 (T.pack "\xFEFF{⟦ a ↦ ⟦⟧ ⟧}"))
+      readInput (Just path) `shouldReturn` "{⟦ a ↦ ⟦⟧ ⟧}"
+
+    it "leaves a file without a byte order mark untouched" $ withScratchDir $ \dir -> do
+      let path = dir </> "no-bom.phi"
+      BS.writeFile path (TE.encodeUtf8 (T.pack "{⟦ a ↦ ⟦⟧ ⟧}"))
+      readInput (Just path) `shouldReturn` "{⟦ a ↦ ⟦⟧ ⟧}"
diff --git a/test/CLISpec.hs b/test/CLISpec.hs
--- a/test/CLISpec.hs
+++ b/test/CLISpec.hs
@@ -8,10 +8,11 @@
 
 import CLI (runCLI)
 import CLI.Types (CmdException (..), IOFormat (..))
+import Control.Concurrent (threadDelay)
 import Control.Exception
 import Control.Monad (forM_, unless)
 import Data.Char (isDigit)
-import Data.List (intercalate, isInfixOf, isPrefixOf, sort)
+import Data.List (find, intercalate, isInfixOf, isPrefixOf, sort)
 import Data.Text qualified as T
 import Data.Time.Clock (addUTCTime, getCurrentTime)
 import Data.Time.Clock.POSIX (getPOSIXTime)
@@ -91,7 +92,13 @@
   tmp <- getTemporaryDirectory
   stamp <- getPOSIXTime
   let dir = tmp </> (prefix ++ "-" ++ show (round (stamp * 1000000) :: Integer))
-  bracket (pure dir) removePathForcibly action
+  bracket (pure dir) removedEventually action
+  where
+    removedEventually :: FilePath -> IO ()
+    removedEventually = retrying (10 :: Int)
+      where
+        retrying 0 dir = removePathForcibly dir
+        retrying left dir = removePathForcibly dir `catch` \(_ :: IOException) -> threadDelay 200000 >> retrying (left - 1) dir
 
 testCLI' :: [String] -> [String] -> Either ExitCode () -> Expectation
 testCLI' args outputs exit = do
@@ -244,6 +251,12 @@
         , ["rewrite", "--flat", "--sweet", "--hide-rho"]
         , ["b( c, d ):a"]
         )
+      ,
+        ( "drops the rho from the LaTeX output too"
+        , "[[ n -> [[ ^ -> ?, k -> $ ]] ]]"
+        , ["rewrite", "--flat", "--hide-rho", "--output=latex"]
+        , ["[[ |n| -> [[ |k| -> \\phiTerminal{\\xi} ]] ]]"]
+        )
       ]
       (\(desc, input, args, expected) -> it desc (withStdin input (testCLISucceeded args expected)))
 
@@ -283,6 +296,15 @@
       testCLIFailed ["rewrite", "--log-level=verbose"] ["unknown log-level: verbose"]
 
   describe "rewriting" $ do
+    let xmir =
+          unlines
+            [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+            , "<object>"
+            , "  <o name=\"bar\">"
+            , "    <o base=\"∅\" name=\"x\"/>"
+            , "  </o>"
+            , "</object>"
+            ]
     describe "fails" $ do
       forM_
         [ ("with --input=latex", "", ["rewrite", "--input=latex"], ["The value 'latex' can't be used for '--input' option"])
@@ -290,6 +312,7 @@
         , ("with negative --max-depth", "", ["rewrite", "--max-depth=-1"], ["--max-depth must be positive"])
         , ("with zero --max-cycles", "", ["rewrite", "--max-cycles=0"], ["--max-cycles must be positive"])
         , ("with zero --meet-length", "", ["rewrite", "--output=latex", "--meet-length=0"], ["--meet-length must be positive"])
+        , ("with a --max-cycles value above maxBound Int", "", ["rewrite", "--max-cycles=18446744073709551617"], ["value must be between", "9223372036854775807"])
         ,
           ( "with --normalize and --must=1"
           , "[[ x -> [[ y -> 5 ]].y ]].x"
@@ -320,8 +343,14 @@
           hClose h
           testCLIFailed
             ["rewrite", "--in-place", "--output=latex", path]
-            ["--in-place can only be used together with --output=phi"]
+            ["The option --in-place requires the output format to match the input format"]
 
+      it "requires an XMIR input to be written back as XMIR" $
+        withTempFileContent "inplaceXXXXXX.xmir" xmir $ \path ->
+          testCLIFailed
+            ["rewrite", "--input=xmir", "--in-place", path]
+            ["The option --in-place requires the output format to match the input format"]
+
       it "does not leak a HasCallStack backtrace into errors" $ do
         (out, _) <- withStdout (try (runCLI ["rewrite", "--in-place"]) :: IO (Either ExitCode ()))
         out `shouldNotContain` "HasCallStack backtrace"
@@ -367,6 +396,18 @@
           , ["--update and --in-place cannot be used together"]
           )
         ,
+          ( "when --hide is used with --in-place"
+          , "[[ ]]"
+          , ["rewrite", "--hide=Q.y", "--in-place", "input.phi"]
+          , ["--in-place and --hide cannot be used together"]
+          )
+        ,
+          ( "when --hide-rho is used with --in-place"
+          , "[[ ]]"
+          , ["rewrite", "--hide-rho", "--in-place", "input.phi"]
+          , ["--in-place and --hide-rho cannot be used together"]
+          )
+        ,
           ( "with --depth-sensitive"
           , "[[ x -> \"x\"]]"
           , ["rewrite", "--depth-sensitive", "--max-depth=1", "--max-cycles=1", rule "infinite.yaml"]
@@ -469,6 +510,11 @@
         ["rewrite", "--help"]
         ["default: 0"]
 
+    it "describes --must as counting rewriting cycles, not rules applied" $
+      testCLISucceeded
+        ["rewrite", "--help"]
+        ["Stops execution if number of rewriting cycles is not"]
+
     it "reproduces the same shuffle order for the same --seed" $ do
       let args =
             [ "rewrite"
@@ -503,6 +549,21 @@
           doesFileExist (dir ++ "/00001.phi") `shouldReturn` True
           doesFileExist (dir ++ "/00003.phi") `shouldReturn` True
 
+    it "clears step files left from an earlier, longer run with --steps-dir" $
+      withTempDirectory "phino-steps-stale" $ \dir -> do
+        withStdin "[[ x -> \"hello\"]]" $
+          testCLISucceeded
+            ["rewrite", rule "infinite.yaml", "--max-cycles=2", "--max-depth=2", "--steps-dir=" ++ dir, "--sweet"]
+            ["hello_hi_hi"]
+        files <- listDirectory dir
+        length files `shouldBe` 4
+        withStdin "[[ x -> \"hello\"]]" $
+          testCLISucceeded
+            ["rewrite", rule "infinite.yaml", "--max-cycles=1", "--max-depth=2", "--steps-dir=" ++ dir, "--sweet"]
+            ["hello_hi"]
+        files2 <- listDirectory dir
+        sort files2 `shouldBe` ["00001.phi", "00002.phi"]
+
     it "gives the saved steps the --canonize and --hide of the printed ones" $
       withTempDirectory "phino-steps-filtered" $ \dir ->
         withStdin "[[ m -> [[ x -> [[ L> Plus ]], y -> $.x ]].y, k -> [[ L> Minus ]] ]]" $ do
@@ -568,6 +629,14 @@
           ["rewrite", rule "join-broken.yaml"]
           ["Function join() can work with bindings only"]
 
+    it "reports a broken rule file in plain text with its path" $
+      withStdin "⟦⟧" $
+        testCLIFailed
+          ["rewrite", rule "malformed-pattern.yaml"]
+          [ "Could not load rule from 'test-resources/cli/rules/malformed-pattern.yaml'"
+          , "unexpected '⟧'"
+          ]
+
     it "normalizes with --normalize flag" $
       testCLISucceeded
         ["rewrite", "--normalize", resource "normalize.phi", "--margin=25"]
@@ -604,6 +673,12 @@
           ["rewrite", "--output=xmir"]
           ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<object", "  <o base=\"Φ.y\" name=\"x\"/>"]
 
+    it "accepts --focus=Φ together with --output=xmir, since Φ and Q are the same locator" $
+      withStdin "[[ x -> Q.y ]]" $
+        testCLISucceeded
+          ["rewrite", "--focus=Φ", "--output=xmir"]
+          ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<object", "  <o base=\"Φ.y\" name=\"x\"/>"]
+
     it "emits a real revision and ms in XMIR" $ do
       (output, _) <- withStdin "[[ x -> Q.y ]]" $ withStdout (runCLI ["rewrite", "--output=xmir"])
       let attrValue :: String -> String -> String
@@ -1029,7 +1104,7 @@
           hClose h
           testCLISucceeded ["rewrite", "--sweet", printf "--target=%s" path] []
           content <- readFile path
-          content `shouldBe` "⟦⟧"
+          content `shouldBe` "⟦⟧\n"
 
     it "modifies file in-place" $
       withTempFile "inplaceXXXXXX.phi" $ \(path, h) -> do
@@ -1037,8 +1112,39 @@
         hClose h
         testCLISucceeded ["rewrite", rule "simple.yaml", "--in-place", "--sweet", path] []
         content <- readFile path
-        content `shouldBe` "\"bar\":x"
+        content `shouldBe` "\"bar\":x\n"
 
+    it "writes the same bytes to --target as it prints to the console" $
+      withTempFile "targetXXXXXX.tmp" $ \(path, h) -> do
+        hClose h
+        (console, _) <- withStdout (withStdin "[[ ]]" (runCLI ["rewrite", "--sweet"]))
+        withStdin "[[ ]]" (testCLISucceeded ["rewrite", "--sweet", printf "--target=%s" path] [])
+        content <- readFile path
+        content `shouldBe` console
+
+    it "keeps the trailing newline of the source file when nothing is rewritten by --in-place" $
+      withTempFile "inplaceXXXXXX.phi" $ \(path, h) -> do
+        hPutStr h "⟦⟧\n"
+        hClose h
+        testCLISucceeded ["rewrite", "--in-place", "--sweet", path] []
+        content <- readFile path
+        content `shouldBe` "⟦⟧\n"
+
+    it "modifies file in-place with --focus=Φ, since Φ and Q are the same locator" $
+      withTempFile "inplaceXXXXXX.phi" $ \(path, h) -> do
+        hPutStr h "[[ x -> \"foo\" ]]"
+        hClose h
+        testCLISucceeded ["rewrite", rule "simple.yaml", "--focus=Φ", "--in-place", "--sweet", path] []
+        content <- readFile path
+        content `shouldBe` "\"bar\":x\n"
+
+    it "keeps the XMIR format when rewriting an XMIR file in-place" $
+      withTempFileContent "inplaceXXXXXX.xmir" xmir $ \path -> do
+        testCLISucceeded ["rewrite", "--input=xmir", "--output=xmir", "--in-place", path] []
+        content <- readFile path
+        _ <- evaluate (length content)
+        content `shouldContain` "<object"
+
     it "skips rewriting with --update when target is newer than source" $
       withTempFileContent "src-XXXXXX.phi" "[[ x -> \"foo\" ]]" $ \src ->
         withTempFileContent "tgt-XXXXXX.phi" "ORIGINAL" $ \tgt -> do
@@ -1087,7 +1193,7 @@
             ["rewrite", rule "simple.yaml", "--update", "--sweet", "--target=" ++ tgt, src]
             []
           content <- readFile tgt
-          content `shouldBe` "\"bar\":x"
+          content `shouldBe` "\"bar\":x\n"
 
     it "rewrites with cycles" $
       withStdin "[[ x -> \"x\" ]]" $
@@ -1213,12 +1319,16 @@
     withStdin "[[ x -> Q.y ]]" $
       testCLIFailed ["rewrite", "--hide=Q.nope"] ["Can't find object by locator: 'Φ.nope'"]
 
+  it "names the term dataization cannot take, and says a normal form is expected" $
+    withStdin "⟦ x ↦ ⟦ b ↦ ⟦ Δ ⤍ 01- ⟧ ⟧.b ⟧" $
+      testCLIFailed ["dataize", "--locator=Q.x"] ["expects a normal form: 01-:Δ:b.b"]
+
   describe "dataize" $ do
     it "prints help" $
       testCLISucceeded ["dataize", "--help"] ["Dataize the 𝜑-expression"]
 
     it "names every block of a --symbolic entry in its help" $
-      testCLISucceeded ["dataize", "--help"] ["\"dataize\"", "\"morph\"", "\"rewrite\"", "\"symbolize\"", "\"join\""]
+      testCLISucceeded ["dataize", "--help"] ["\"dataize\"", "\"morph\"", "\"rewrite\"", "\"symbolize\"", "\"join\"", "\"lenient\"", "\"terminators\""]
 
     it "dataizes simple expression" $
       withStdin "[[ D> 01- ]]" $
@@ -1241,7 +1351,7 @@
         withStdin "⟦ @ ↦ ⟦ λ ⤍ L_loop ⟧ ⟧" $
           testCLIFailed
             ["dataize", "--symbolic=" ++ endless, "--max-steps=40"]
-            ["[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=40"]
+            ["[ERROR]: Morphing did not finish before reaching the limit of steps: --max-steps=40"]
 
     it "parks --max-steps on a residual with --partial" $
       loopingLambdas $ \endless ->
@@ -1263,7 +1373,7 @@
         withStdin circling $
           testCLIFailed
             ["dataize", "--locator=Q.t", "--max-steps=40"]
-            ["[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=40"]
+            ["[ERROR]: Morphing did not finish before reaching the limit of steps: --max-steps=40"]
 
       it "names the term it came back to with the flag" $
         withStdin circling $
@@ -2438,6 +2548,19 @@
           withStdin sum' $
             testCLIFailed ["dataize", "--symbolic=" ++ path] ["cannot be read"]
 
+    describe "a lenient join" $ do
+      let strict = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"
+          lenient = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: true\n  𝑛: 𝑛3\n"
+      it "gets stuck on a fork whose second branch decorates the first when the entry is strict" $
+        withLambdasOf strict $ \forks ->
+          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧ ⟧" $
+            testCLIFailed ["dataize", "--symbolic=" ++ forks, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"]
+
+      it "dataizes a fork whose second branch decorates the first" $
+        withLambdasOf lenient $ \forks ->
+          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧ ⟧" $
+            testCLISucceeded ["dataize", "--symbolic=" ++ forks, "--locator=Q.y"] ["40-45-00-00-00-00-00-00"]
+
     describe "--inside" $ do
       let universe = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> [[ D> 01- ]] ]]"
       it "dataizes an expression the input does not contain" $
@@ -2531,6 +2654,12 @@
             ["dataize", "--focus=Q.x(Q.y)"]
             ["[ERROR]:", "Only dispatch expression started with Φ (or Q) can be used in --focus"]
 
+      it "with wrong --locator option written through ξ, keeping ξ in the error" $
+        withStdin "" $
+          testCLIFailed
+            ["dataize", "--locator=ξ.x"]
+            ["[ERROR]:", "but given: ξ.x"]
+
     it "accepts --depth-sensitive" $
       withStdin "[[ D> 01- ]]" $
         testCLISucceeded ["dataize", "--depth-sensitive"] ["01-"]
@@ -2663,8 +2792,14 @@
       withStdin chained $
         testCLIFailed
           ["morph", "--locator=Q.@", "--max-steps=3"]
-          ["[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=3"]
+          ["[ERROR]: Morphing did not finish before reaching the limit of steps: --max-steps=3"]
 
+    it "names morphing, not dataization, when the budget is spent before dataization starts" $
+      withStdin "⟦ x ↦ Φ.x.y ⟧" $
+        testCLIFailed
+          ["morph", "--locator=Q.x", "--max-steps=5"]
+          ["[ERROR]: Morphing did not finish before reaching the limit of steps: --max-steps=5"]
+
     describe "--max-firings" $ do
       let splitting = withLambdasOf (T.pack "- λ: L_split\n  morph:\n    𝑛1: Φ.s.foo\n    𝑛2: Φ.s.foo\n  𝑛: ⟦ l ↦ 𝑛1, r ↦ 𝑛2 ⟧\n")
           split = "⟦ s ↦ ⟦ λ ⤍ L_split ⟧, x ↦ Φ.s.foo ⟧"
@@ -2713,6 +2848,12 @@
         withStdin rungs $
           testCLIFailed ["morph", "--max-seconds=0"] ["--max-seconds must be positive"]
 
+      it "fails with a --max-seconds value above maxBound Int, naming the limit" $
+        withStdin rungs $
+          testCLIFailed
+            ["morph", "--max-seconds=9223372036854775808"]
+            ["value must be between", "9223372036854775807"]
+
       it "fails once the --max-seconds budget is spent" $
         ladder $ \table ->
           bounded $
@@ -2815,6 +2956,19 @@
         many <- recorded ["--jobs=5"]
         many `shouldBe` few
 
+      it "writes the step files of a binding after those of the bindings before it" $
+        withTempDirectory "phino-steps-jobs" $ \dir ->
+          withStdin "[[ a -> [[ x -> 1, y -> $.x ]], b -> [[ x -> 2, y -> $.x ]] ]]" $ do
+            testCLISucceeded
+              ["morph", "--deep", "--steps-dir=" ++ dir, "--quiet", "--jobs=5", "--flat", "--hide-rho", "--sweet"]
+              []
+            files <- sort <$> listDirectory dir
+            contents <- mapM (\file -> readFile (dir ++ "/" ++ file)) files
+            contents `shouldSatisfy` elem "1"
+            contents `shouldSatisfy` elem "2"
+            let named value = fst <$> find ((== value) . snd) (zip files contents)
+            named "1" `shouldSatisfy` (< named "2")
+
       it "keeps a memo of its own for every binding under plausible" $
         withStdin twins $
           testCLISucceeded
@@ -2889,6 +3043,49 @@
         withStdin "[[ x -> [[ L> Sym_arg_0 ]].foo ]]" $
           testCLIFailed ["morph", "--deep"] ["No entry of --symbolic answers the λ function 'Sym_arg_0'"]
 
+    describe "a lenient join" $ do
+      let strict = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"
+          lenient = T.pack "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: true\n  𝑛: 𝑛3\n"
+      it "gets stuck on a fork whose second branch decorates the first when the entry is strict" $
+        withLambdasOf strict $ \forks ->
+          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $
+            testCLIFailed ["morph", "--symbolic=" ++ forks, "--locator=Q.y"] ["No entry of --symbolic answers the λ function 'L_fork'"]
+
+      it "joins a fork whose second branch decorates the first" $
+        withLambdasOf lenient $ \forks ->
+          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $
+            testCLISucceeded
+              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--sweet", "--hide-rho", "--flat"]
+              ["⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧"]
+
+      it "joins a fork whose first branch decorates the second" $
+        withLambdasOf lenient $ \forks ->
+          withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $
+            testCLISucceeded
+              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--sweet", "--hide-rho", "--flat"]
+              ["⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧"]
+
+      it "writes the decorator it joined into the protocol" $
+        withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do
+          hClose stream
+          withLambdasOf lenient $ \forks ->
+            withStdin "⟦ y ↦ ⟦ a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧, b ↦ ⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧, λ ⤍ L_fork ⟧.φ ⟧" $
+              testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []
+          records <- readProtocol path
+          lines records `shouldContain` ["    𝑛3·1 := ⟦ φ ↦ 𝜎3:λ, neg ↦ ⟦⟧ ⟧:φ  # [𝑛1, 𝑛2]"]
+
+      it "gets stuck on a fork pairing a bare symbol with a call holding one symbol when the entry is strict" $
+        withLambdasOf strict $ \forks ->
+          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ r ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧ ⟧ ⟧" $
+            testCLIFailed ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--deep"] ["No entry of --symbolic answers the λ function 'L_fork'"]
+
+      it "joins a bare symbol with a call holding one symbol into a copy of the call under --deep" $
+        withLambdasOf lenient $ \forks ->
+          withStdin "⟦ bool(if) ↦ ⟦ φ ↦ ξ.if( α0 ↦ ⟦ Δ ⤍ FF- ⟧, α1 ↦ ⟦ Δ ⤍ 00- ⟧ ) ⟧, y ↦ ⟦ r ↦ ⟦ a ↦ ⟦ λ ⤍ 𝜎1 ⟧, b ↦ ⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, left ↦ ∅, right ↦ ∅, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧, λ ⤍ L_fork ⟧ ⟧ ⟧" $
+            testCLISucceeded
+              ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--deep", "--sweet", "--hide-rho", "--flat"]
+              ["Φ.bool( if(left, right) ↦ ⟦ λ ⤍ L_fork, φ ↦ 𝜎3:λ ⟧ ):φ:r"]
+
     describe "--acyclic=plausible" $ do
       let twins = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], a -> 7.plus( 5.plus( 6 ) ), b -> 7.plus( 5.plus( 6 ) ) ]]"
           recorded :: String -> IO [String]
@@ -2937,7 +3134,7 @@
           withStdin looping $
             testCLIFailed
               ["morph", "--symbolic=" ++ endless, "--locator=Q.x", "--max-steps=40"]
-              ["[ERROR]: Dataization did not finish before reaching the limit of steps: --max-steps=40"]
+              ["[ERROR]: Morphing did not finish before reaching the limit of steps: --max-steps=40"]
 
       it "prints the residue and exits successfully with the flag" $
         loopingLambdas $ \endless ->
@@ -3187,19 +3384,37 @@
     it "turns the flag on in a new cabal.project.local" $
       withTempDirectory "phino-compile" $ \dir -> do
         createDirectoryIfMissing True dir
+        writeFile (dir </> "phino.cabal") ""
         withCurrentDirectory dir (runCLI ["compile", "--target=Compiled.hs"])
         readFile (dir </> "cabal.project.local") `shouldReturn` "package phino\n  flags: +compiled\n"
     it "prints the lines an existing cabal.project.local lacks" $
       withTempDirectory "phino-compile" $ \dir -> do
         createDirectoryIfMissing True dir
+        writeFile (dir </> "phino.cabal") ""
         writeFile (dir </> "cabal.project.local") "tests: True\n"
         withCurrentDirectory dir (testCLISucceeded ["compile", "--target=Compiled.hs"] ["package phino\n  flags: +compiled"])
     it "leaves an existing cabal.project.local as it is" $
       withTempDirectory "phino-compile" $ \dir -> do
         createDirectoryIfMissing True dir
+        writeFile (dir </> "phino.cabal") ""
         writeFile (dir </> "cabal.project.local") "tests: True\n"
         withStdout (withCurrentDirectory dir (runCLI ["compile", "--target=Compiled.hs"]))
         readFile (dir </> "cabal.project.local") `shouldReturn` "tests: True\n"
+    it "writes cabal.project.local next to the module's project, not the current directory" $
+      withTempDirectory "phino-compile" $ \dir -> do
+        createDirectoryIfMissing True (dir </> "project" </> "gen")
+        writeFile (dir </> "project" </> "phino.cabal") ""
+        createDirectoryIfMissing True (dir </> "elsewhere")
+        withCurrentDirectory
+          (dir </> "elsewhere")
+          (runCLI ["compile", "--target=" ++ (dir </> "project" </> "gen" </> "Compiled.hs")])
+        doesFileExist (dir </> "elsewhere" </> "cabal.project.local") `shouldReturn` False
+        readFile (dir </> "project" </> "cabal.project.local") `shouldReturn` "package phino\n  flags: +compiled\n"
+    it "only prints the lines when it cannot find the project of the module" $
+      withTempDirectory "phino-compile" $ \dir -> do
+        createDirectoryIfMissing True dir
+        withCurrentDirectory dir (testCLISucceeded ["compile", "--target=gen/Compiled.hs"] ["package phino\n  flags: +compiled"])
+        doesFileExist (dir </> "cabal.project.local") `shouldReturn` False
     it "refuses a rule it cannot compile" $
       withTempDirectory "phino-compile" $ \dir -> do
         createDirectoryIfMissing True dir
@@ -3212,6 +3427,15 @@
         ["match", "--help"]
         ["Pattern expression to match against", "Predicate for matched substitutions"]
 
+    it "describes --sweet and --flat as applying to the result only" $
+      testCLISucceeded
+        ["match", "--help"]
+        ["Print result 𝜑-expression using syntax sugar", "Print result 𝜑-expression in one line"]
+
+    it "rejects --sequence, which --help must not mention" $
+      withStdin "[[ ]]" $
+        testCLIFailed ["match", "--sequence", "--pattern=[[ !B ]]"] ["Invalid option"]
+
     it "takes from stdin" $
       withStdin "[[]]" $
         testCLISucceeded ["match", "--log-level=debug"] ["[DEBUG]"]
@@ -3272,6 +3496,12 @@
         testCLIFailed
           ["match", "--pattern=[[ !B ]]", "--when=eq(length(!B),1)"]
           ["[ERROR]: Anonymous meta '!B' cannot be referenced in --when"]
+
+    it "rejects a named meta in --when the pattern does not bind" $
+      withStdin "[[ x -> Q.y ]]" $
+        testCLIFailed
+          ["match", "--pattern=[[ !B1 ]]", "--when=eq(length(!B2),1)"]
+          ["[ERROR]: The --when condition reads the meta '!B2' it never binds, since neither --pattern binds it"]
 
     it "fails on parsing --when condition" $
       withStdin "[[]]" $
diff --git a/test/CanonizerSpec.hs b/test/CanonizerSpec.hs
--- a/test/CanonizerSpec.hs
+++ b/test/CanonizerSpec.hs
@@ -61,6 +61,19 @@
       , ("leaves a meta lambda binding untouched", metaLambda, metaLambda)
       , ("numbers several lambdas at different nesting depths in document order", nestedInput, nestedExpected)
       ,
+        ( "gives the same Fn label to two bindings sharing one lambda name"
+        , ExFormation
+            [ BiTau (AtLabel "a") (ExFormation [BiLambda (Function "L_plus")])
+            , BiTau (AtLabel "b") (ExFormation [BiLambda (Function "L_plus")])
+            , BiTau (AtLabel "c") (ExFormation [BiLambda (Function "L_minus")])
+            ]
+        , ExFormation
+            [ BiTau (AtLabel "a") (ExFormation [BiLambda (Function "Fn1")])
+            , BiTau (AtLabel "b") (ExFormation [BiLambda (Function "Fn1")])
+            , BiTau (AtLabel "c") (ExFormation [BiLambda (Function "Fn2")])
+            ]
+        )
+      ,
         ( "recurses through ExDispatch"
         , ExDispatch (ExFormation [BiLambda (Function "Wrapped")]) (AtLabel "attr")
         , ExDispatch (ExFormation [BiLambda (Function "Fn1")]) (AtLabel "attr")
diff --git a/test/DataizeSpec.hs b/test/DataizeSpec.hs
--- a/test/DataizeSpec.hs
+++ b/test/DataizeSpec.hs
@@ -173,14 +173,14 @@
       looping $ \endless -> do
         expr <- parseExpressionThrows "⟦ @ ↦ ⟦ λ ⤍ L_loop ⟧ ⟧"
         minted <- newIORef 0
-        dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty endless (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
+        dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty endless [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
           `shouldThrow` (\e -> "--max-steps=40" `isInfixOf` show (e :: SomeException))
 
     it "parks the step limit as a residual with --partial" $
       looping $ \endless -> do
         expr <- parseExpressionThrows "⟦ @ ↦ ⟦ λ ⤍ L_loop ⟧ ⟧"
         minted <- newIORef 0
-        (outcome, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True True False 1 Nothing Dataization [] Map.empty endless (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
+        (outcome, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 25 (Steps 40 0) Nothing minted Nothing Nothing 1 Nothing False True True False 1 Nothing Dataization [] Map.empty endless [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
         case outcome of
           Residual _ -> pure ()
           Dataized bts -> expectationFailure ("expected a residual, dataized to " ++ show bts)
@@ -265,12 +265,12 @@
     forM_
       [
         ( "--max-cycles"
-        , \minted -> ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked
+        , \minted -> ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked
         , "--max-cycles=0"
         )
       ,
         ( "--max-depth"
-        , \minted -> ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked
+        , \minted -> ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing True True False False 1 Nothing Dataization [] Map.empty emptyLambdas [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked
         , "--max-depth=0"
         )
       ]
@@ -283,12 +283,12 @@
     it "does not throw without --depth-sensitive even once --max-depth is exhausted" $ do
       expr <- parseExpressionThrows boxed
       minted <- newIORef 0
-      (value, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
+      (value, _, _) <- dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 0 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
       value `shouldBe` Dataized (BtOne "00")
     it "throws once --max-cycles is exhausted even without --depth-sensitive" $ do
       expr <- parseExpressionThrows boxed
       minted <- newIORef 0
-      dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
+      dataize expr emptyState (ReduceContext ExRoot ExRoot Nothing 25 0 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Dataization [] Map.empty emptyLambdas [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
         `shouldThrow` (\e -> "--max-cycles=0" `isInfixOf` show (e :: SomeException))
 
   describe "labels every step with a defined rule or operation" $ do
diff --git a/test/EmitSpec.hs b/test/EmitSpec.hs
--- a/test/EmitSpec.hs
+++ b/test/EmitSpec.hs
@@ -72,6 +72,9 @@
     it "asks the condition 'nf' the way the matcher does" $
       fromRight "" (emitted [] [Y.Rule "nc" Nothing Nothing (ExDispatch (ExMeta "e3") (AtLabel "jb")) ExRoot (Just (Y.NF (ExMeta "e3"))) Nothing Nothing] [] [] [] [])
         `shouldSatisfy` ("Ru.normalHeld nf x1" `isInfixOf`)
+    it "compiles a rule rewriting a formation into a formation the fast way, when it has a 'when' condition" $
+      fromRight "" (emitted [] [Y.Rule "fw" Nothing Nothing (ExFormation [BiMeta "B1", BiVoid (AtLabel "x"), BiMeta "B2"]) (ExFormation [BiMeta "B1", BiVoid (AtLabel "y"), BiMeta "B2"]) (Just (Y.In [AtLabel "z"] [BiMeta "B1"])) Nothing Nothing] [] [] [] [])
+        `shouldSatisfy` ("stepFw ::" `isInfixOf`)
   describe "emitted refuses a rule of contextualization" $
     it "with a premise that is no contextualization" $
       emitted [] [] [Y.ContextualizeRule "cm" Nothing (ExMeta "n1") (ExMeta "k1") (ExMeta "n2") [Y.Premise "n2" (Y.OpNormalize (ExMeta "n1"))]] [] [] []
diff --git a/test/FilesSpec.hs b/test/FilesSpec.hs
--- a/test/FilesSpec.hs
+++ b/test/FilesSpec.hs
@@ -31,6 +31,7 @@
 exceptionPath :: FsException -> FilePath
 exceptionPath (FileDoesNotExist file) = file
 exceptionPath (DirectoryDoesNotExist directory) = directory
+exceptionPath (PathIsDirectory path) = path
 
 withScratchDir :: (FilePath -> IO a) -> IO a
 withScratchDir =
@@ -46,12 +47,20 @@
 
 spec :: Spec
 spec = do
-  describe "ensuredFile" $
+  describe "ensuredFile" $ do
     it "returns the path of an existing file" $
       withScratchDir $ \dir -> do
         let path = dir </> "existing.txt"
         writeFile path "content"
         ensuredFile path >>= (`shouldBe` path)
+    it "throws PathIsDirectory for a directory" $
+      withScratchDir $ \dir -> do
+        let path = dir </> "subdir"
+        createDirectoryIfMissing True path
+        result <- try (ensuredFile path) :: IO (Either FsException FilePath)
+        case result of
+          Left exc -> exceptionPath exc `shouldBe` path
+          _ -> fail "expected a PathIsDirectory exception to be thrown"
 
   describe "overwrite" $ do
     it "replaces the content of an existing file with utf-8 bytes" $ withScratchDir $ \dir -> do
@@ -142,6 +151,11 @@
         ( "shows a readable message for DirectoryDoesNotExist"
         , DirectoryDoesNotExist "/no/such/dir"
         , "Directory '/no/such/dir' does not exist"
+        )
+      ,
+        ( "shows a readable message for PathIsDirectory"
+        , PathIsDirectory "/no/such/dir"
+        , "'/no/such/dir' is a directory, while a file is expected"
         )
       ]
       (\(desc, exc, message) -> it desc (show exc `shouldBe` message))
diff --git a/test/FilterSpec.hs b/test/FilterSpec.hs
--- a/test/FilterSpec.hs
+++ b/test/FilterSpec.hs
@@ -9,6 +9,7 @@
 module FilterSpec where
 
 import AST (Expression (ExRoot))
+import Control.Exception (SomeException, try)
 import Control.Monad (forM_)
 import Data.Aeson
 import Data.Yaml qualified as Yaml
@@ -17,6 +18,7 @@
 import Filter qualified as F
 import GHC.Generics (Generic)
 import Parser (parseExpressionThrows)
+import Rewriter (Rewritten)
 import System.FilePath
 import Test.Hspec
 
@@ -73,6 +75,13 @@
         excluded <- F.exclude [(first', Just (Normalization, "rule-a")), (second', Just (Evaluation, "rule-b"))] [fqn]
         map fst excluded `shouldBe` [expected, expected]
         map snd excluded `shouldBe` [Just (Normalization, "rule-a"), Just (Evaluation, "rule-b")]
+
+      it "reports a message naming the --hide option when the fqn is the whole program" $ do
+        expr <- parseExpressionThrows "[[ x -> ?, y -> ? ]]"
+        result <- try (F.exclude [(expr, Nothing)] [ExRoot]) :: IO (Either SomeException [Rewritten])
+        case result of
+          Left err -> show err `shouldBe` "The --hide locator must name an attribute, 'Φ' would hide the whole program"
+          Right _ -> expectationFailure "expected exclude to throw"
 
     describe "include" $ do
       forM_
diff --git a/test/Fixtures.hs b/test/Fixtures.hs
--- a/test/Fixtures.hs
+++ b/test/Fixtures.hs
@@ -55,7 +55,7 @@
 defaultReduceContext :: Expression -> IO ReduceContext
 defaultReduceContext loc = do
   minted <- newIORef 0
-  pure (ReduceContext loc loc Nothing 25 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Morphing [] Map.empty emptyLambdas (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
+  pure (ReduceContext loc loc Nothing 25 25 (Steps 250 0) Nothing minted Nothing Nothing 1 Nothing False True False False 1 Nothing Morphing [] Map.empty emptyLambdas [] (building linked) reduction evaluation fired dontSaveStep dontSaveEval linked)
 
 linked :: Engine
 linked = fromMaybe yaml compiled
diff --git a/test/FunctionsSpec.hs b/test/FunctionsSpec.hs
--- a/test/FunctionsSpec.hs
+++ b/test/FunctionsSpec.hs
@@ -215,6 +215,24 @@
         , "sed pattern must start with s/"
         )
       ,
+        ( "sed fails when the pattern has no replacement separator"
+        , "sed"
+        , [ArgExpression (DataString (strToBts "hello")), ArgExpression (DataString (strToBts "s/a"))]
+        , "sed pattern must be in format s/pat/rep/[g]"
+        )
+      ,
+        ( "sed fails when the pattern is only a delimiter"
+        , "sed"
+        , [ArgExpression (DataString (strToBts "hello")), ArgExpression (DataString (strToBts "s/"))]
+        , "sed pattern must be in format s/pat/rep/[g]"
+        )
+      ,
+        ( "sed fails when an escaped slash is mistaken for the replacement separator"
+        , "sed"
+        , [ArgExpression (DataString (strToBts "hello")), ArgExpression (DataString (strToBts "s/a\\/"))]
+        , "sed pattern must be in format s/pat/rep/[g]"
+        )
+      ,
         ( "sed fails when the pattern has an unknown trailing flag"
         , "sed"
         , [ArgExpression (DataString (strToBts "hello")), ArgExpression (DataString (strToBts "s/l/L/x"))]
@@ -223,6 +241,18 @@
       , ("random-string fails on the wrong number of arguments", "random-string", [], "random-string() requires exactly 1")
       , ("size fails on a non-meta binding argument", "size", [ArgBinding (BiVoid AtRho)], "size() requires exactly 1 meta binding")
       , ("tau fails on the wrong number of arguments", "tau", [], "tau() requires exactly 1 argument")
+      ,
+        ( "tau fails when the string is a meta attribute"
+        , "tau"
+        , [ArgExpression (DataString (strToBts "𝜏1"))]
+        , "is a meta attribute, not a real one"
+        )
+      ,
+        ( "tau fails when the string is an any-slot attribute"
+        , "tau"
+        , [ArgExpression (DataString (strToBts "!t1"))]
+        , "is a meta attribute, not a real one"
+        )
       ,
         ( "string fails on an expression that is neither a number nor a string"
         , "string"
diff --git a/test/LaTeXSpec.hs b/test/LaTeXSpec.hs
--- a/test/LaTeXSpec.hs
+++ b/test/LaTeXSpec.hs
@@ -369,8 +369,8 @@
           [ "\\phinoNormalizationRule[disp]{myrule}"
           , "{ n }"
           , "{ n }"
-          , "{ \\isnormal{ n } \\;\\text{and}\\; \\phinoIsFormation{ n } }"
-          , "{ \\phiTerminal{\\rho} \\coloneqq \\foo{ n, \\phiTerminal{\\rho} -> ?, 01-02- } \\;\\text{and}\\; @ \\coloneqq \\bar{ n } }"
+          , "{ \\isnormal{ n } \\phiText{and} \\phinoIsFormation{ n } }"
+          , "{ \\phiTerminal{\\rho} \\coloneqq \\foo{ n, \\phiTerminal{\\rho} -> ?, 01-02- } \\phiText{and} @ \\coloneqq \\bar{ n } }"
           ]
         )
       ,
diff --git a/test/LambdasSpec.hs b/test/LambdasSpec.hs
--- a/test/LambdasSpec.hs
+++ b/test/LambdasSpec.hs
@@ -12,7 +12,7 @@
 import Data.List (isInfixOf)
 import Data.Text qualified as T
 import Fixtures (withLambdasOf)
-import Lambdas (Lambda (..), Lambdas, Meta (..), emptyLambdas, joined, matched, minted, readLambdas, symbolized, taken)
+import Lambdas (Lambda (..), Lambdas, Meta (..), emptyLambdas, joined, matched, minted, readLambdas, symbolized, taken, thrown)
 import Parser (parseExpressionThrows)
 import Test.Hspec
 
@@ -121,6 +121,26 @@
       known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n    𝑛3: $.c\n  join:\n    𝑛4: [𝑛1, 𝑛2]\n    𝑛5: [𝑛4, 𝑛3]\n  𝑛: 𝑛5\n"
       joins known "L_fork" `shouldBe` [("𝑛4", ("𝑛1", "𝑛2")), ("𝑛5", ("𝑛4", "𝑛3"))]
 
+    it "reads an entry whose 'join' lines are lenient" $ do
+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: true\n  𝑛: 𝑛3\n"
+      fmap _lenient (matched known "L_fork") `shouldBe` Just True
+
+    it "reads an entry saying nothing of leniency as strict" $ do
+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"
+      fmap _lenient (matched known "L_fork") `shouldBe` Just False
+
+    it "reads the terminators an entry names beside its 'join' block" $ do
+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  terminators: [L_throw, L_halt]\n  𝑛: 𝑛3\n"
+      fmap _terminators (matched known "L_fork") `shouldBe` Just ["L_throw", "L_halt"]
+
+    it "reads an entry saying nothing of terminators as one naming none" $ do
+      known <- lambdasOf "- λ: L_fork\n  morph:\n    𝑛1: $.then\n    𝑛2: $.else\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n"
+      fmap _terminators (matched known "L_fork") `shouldBe` Just []
+
+    it "reads a terminator that no key of the file matches" $ do
+      known <- lambdasOf ("- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: [L_throw]\n  𝑛: 𝑛1\n" <> entry "L_throw_[0-9]+")
+      fmap _terminators (matched known "L_fork") `shouldBe` Just ["L_throw"]
+
     it "reads the lines of 'rewrite' with the metas they read and their rules" $ do
       known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "    𝑛3:\n      of: 𝑛2\n" <> rules <> "  𝑛: 𝑛3\n")
       map rewrote (maybe [] _rewritten (matched known "L_fork")) `shouldBe` [("𝑛2", "𝑛1", 1), ("𝑛3", "𝑛2", 1)]
@@ -185,7 +205,15 @@
       , ("an answer naming a meta no block binds", "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  𝑛: 𝑛7\n", "reads the meta 'n7' that no block binds")
       , ("an operand of 'dataize' reading a meta", "- λ: L_pair\n  dataize:\n    𝛿1: 𝑛5\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "of 'dataize' of λ function 'L_pair' reads the meta 'n5'")
       , ("an operand of 'morph' reading a meta", "- λ: L_pair\n  morph:\n    𝑛1: $.x.𝜏1\n  𝑛: 𝑛1\n", "of 'morph' of λ function 'L_pair' reads the meta 't1'")
+      , ("a lenient entry with no 'join' line to be lenient in", "- λ: L_pair\n  dataize:\n    𝛿1: $.ρ\n  lenient: true\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n", "is lenient but has no 'join' line")
+      , ("a 'lenient' key which is no boolean", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  lenient: often\n  𝑛: 𝑛3\n", "lenient: expected Bool")
       , ("a 'symbolize' line standing what a 'join' line made", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  symbolize:\n    𝑛5: 𝑛3\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛5\n", "names no meta bound by 'morph' or by a line above it")
+      , ("a 'terminators' key which is no list", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: L_throw\n  𝑛: 𝑛1\n", "expected Array")
+      ,
+        ( "a terminator an entry of the file answers"
+        , "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  terminators: [L_throw]\n  𝑛: 𝑛1\n" <> entry "L_th[a-z]+"
+        , "the terminator 'L_throw' of the key 'L_fork' is answered by the key 'L_th[a-z]+'"
+        )
       ]
       ( \(desc, text, message) ->
           it ("cannot read " ++ desc) $
@@ -267,79 +295,157 @@
       spent `shouldBe` 4
 
   describe "joined" $ do
-    let joining :: String -> String -> Int -> IO (Maybe (Expression, [(Int, (Int, Int))], Int))
-        joining left right spent = do
+    let joining :: Bool -> String -> String -> Int -> IO (Maybe (Expression, [(Int, (Int, Int))], Int))
+        joining lenient left right spent = do
           one <- parseExpressionThrows left
           two <- parseExpressionThrows right
-          pure (joined one two spent)
+          pure (joined lenient one two spent)
 
     it "joins two branches differing in one symbol into a fresh one" $ do
       term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧"
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4
       fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
 
     it "tells the two symbols every fresh one stands for" $ do
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧" 4
       fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 2))]
 
     it "counts every symbol it minted into the state" $ do
-      made <- joining "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4
+      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4
       fmap (\(_, _, spent) -> spent) made `shouldBe` Just 6
 
     it "mints one symbol per pair of differing symbols" $ do
-      made <- joining "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4
+      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎4 ⟧ ) ⟧" 4
       fmap (\(_, facts, _) -> facts) made `shouldBe` Just [(5, (1, 3)), (6, (2, 4))]
 
     it "mints one symbol for the pair it meets twice" $ do
       term <- parseExpressionThrows "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"
-      made <- joining "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4
+      made <- joining False "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) ⟧" "⟦ φ ↦ Φ.f( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( t ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4
       made `shouldBe` Just (term, [(5, (1, 2))], 5)
 
     it "carries a method from the first branch and mints nothing for it" $ do
       term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧ ⟧"
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎7 ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ⟧ ⟧" 4
       made `shouldBe` Just (term, [(5, (1, 2))], 5)
 
     it "refuses two branches whose bindings are named differently" $ do
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, m ↦ ⟦ x ↦ ∅ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, other ↦ ⟦ x ↦ ∅ ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, m ↦ ⟦ x ↦ ∅ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, other ↦ ⟦ x ↦ ∅ ⟧ ⟧" 4
       made `shouldBe` Nothing
 
     it "joins two branches that are one term into that very term" $ do
       term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )"
-      made <- joining "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" 4
+      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" 4
       made `shouldBe` Just (term, [], 4)
 
     it "joins two branches through the argument of an application" $ do
       term <- parseExpressionThrows "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ )"
-      made <- joining "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4
+      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4
       fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
 
     it "leaves what a ρ carries alone" $ do
       term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧"
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4
       made `shouldBe` Just (term, [(5, (1, 2))], 5)
 
     it "joins a bare symbol with the symbol the φ chain of a formation ends in" $ do
       term <- parseExpressionThrows "⟦ λ ⤍ 𝜎5 ⟧"
-      made <- joining "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, eq ↦ ⟦ b ↦ ∅ ⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧, eq ↦ ⟦ b ↦ ∅ ⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4
       made `shouldBe` Just (term, [(5, (1, 2))], 5)
 
     it "joins a formation with a bare symbol standing first" $ do
       term <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧"
-      made <- joining "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7
+      made <- joining False "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7
       made `shouldBe` Just (term, [(8, (3, 6))], 8)
 
     it "mints nothing for a bare symbol the φ chain of a formation ends in" $ do
       term <- parseExpressionThrows "⟦ λ ⤍ 𝜎2 ⟧"
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, m ↦ ⟦⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, m ↦ ⟦⟧ ⟧" "⟦ λ ⤍ 𝜎2 ⟧" 4
       made `shouldBe` Just (term, [], 4)
 
     it "joins two branches differing in their ρ alone" $ do
       term <- parseExpressionThrows "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧"
-      made <- joining "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4
+      made <- joining False "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ x ↦ ⟦ Δ ⤍ 00- ⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, ρ ↦ ⟦ y ↦ ⟦ Δ ⤍ FF- ⟧ ⟧ ⟧" 4
       made `shouldBe` Just (term, [], 4)
 
+    it "cannot join a term with a decorator of it unless the join is lenient" $ do
+      made <- joining False "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4
+      made `shouldBe` Nothing
+
+    it "joins a term with a decorator of it into the decorator when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"
+      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" 4
+      made `shouldBe` Just (term, [(5, (1, 2))], 5)
+
+    it "joins a decorator with the term it decorates standing second when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ) ⟧"
+      made <- joining True "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎3 ⟧ ) ⟧" "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎6 ⟧ )" 7
+      made `shouldBe` Just (term, [(8, (3, 6))], 8)
+
+    it "drops every binding of the decorator but its φ when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧"
+      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "joins a copy with a decorator of another copy into the decorator when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦⟧ ⟧ ⟧"
+      made <- joining True "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧" "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "joins a decorator of a copy with another copy standing second into the decorator when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎5 ⟧, neg ↦ ⟦⟧ ⟧ ⟧"
+      made <- joining True "⟦ φ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, neg ↦ ⟦⟧ ⟧ ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, neg ↦ ⟦⟧ ⟧" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "joins a term with a decorator of a decorator of it when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧ ⟧"
+      made <- joining True "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" "⟦ φ ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧ ⟧" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "joins a term with a decorator of it inside an argument when lenient" $ do
+      term <- parseExpressionThrows "Φ.f( x ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ) ⟧ )"
+      made <- joining True "Φ.f( x ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ) )" "Φ.f( x ↦ ⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧ )" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "joins a bare symbol with a term holding one symbol into a copy of that term when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ⟧ ) ⟧"
+      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧" 4
+      made `shouldBe` Just (term, [(5, (1, 2))], 5)
+
+    it "joins a term holding one symbol with a bare symbol standing second when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎8 ⟧ ⟧ ) ⟧"
+      made <- joining True "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎6 ⟧ ⟧ ) ⟧" "⟦ λ ⤍ 𝜎3 ⟧" 7
+      made `shouldBe` Just (term, [(8, (6, 3))], 8)
+
+    it "keeps every binding of the term holding one symbol when lenient" $ do
+      term <- parseExpressionThrows "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎5 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧"
+      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "⟦ φ ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦⟧ ⟧, neg ↦ ⟦⟧ ⟧" 4
+      fmap (\(joint, _, _) -> joint) made `shouldBe` Just term
+
+    it "replaces every place the one symbol of a term stands at when lenient" $ do
+      term <- parseExpressionThrows "Φ.f( a ↦ ⟦ λ ⤍ 𝜎5 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎5 ⟧ )"
+      made <- joining True "⟦ λ ⤍ 𝜎1 ⟧" "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎2 ⟧ )" 4
+      made `shouldBe` Just (term, [(5, (1, 2))], 5)
+
+    it "still joins a bare symbol with the symbol a φ chain ends in into a bare one when lenient" $ do
+      term <- parseExpressionThrows "⟦ λ ⤍ 𝜎8 ⟧"
+      made <- joining True "⟦ λ ⤍ 𝜎3 ⟧" "⟦ φ ↦ ⟦ λ ⤍ 𝜎6 ⟧, neg ↦ ⟦⟧ ⟧" 7
+      made `shouldBe` Just (term, [(8, (3, 6))], 8)
+
     forM_
+      [ ("a term with a formation carrying no φ" :: String, "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )" :: String, "⟦ neg ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧" :: String)
+      , ("a term with a decorator of a term of another forma", "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )", "⟦ φ ↦ Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ) ⟧")
+      , ("two decorators one of which carries a binding more", "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧, x ↦ ⟦⟧ ⟧")
+      , ("a bare symbol with a term holding two symbols", "⟦ λ ⤍ 𝜎1 ⟧", "Φ.f( a ↦ ⟦ λ ⤍ 𝜎2 ⟧ )( b ↦ ⟦ λ ⤍ 𝜎3 ⟧ )")
+      , ("a bare symbol with a term holding a second symbol in its ρ", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ Φ.bool( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ), ρ ↦ ⟦ x ↦ ⟦ λ ⤍ 𝜎3 ⟧ ⟧ ⟧")
+      , ("a bare symbol with a term holding no symbol", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, x ↦ ⟦⟧ ⟧")
+      ]
+      ( \(desc, left, right) ->
+          it ("cannot leniently join " ++ desc) $ do
+            made <- joining True left right 4
+            fmap (\(joint, _, _) -> joint) made `shouldBe` Nothing
+      )
+
+    forM_
       [ ("a datum with a symbol" :: String, "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧" :: String, "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧" :: String)
       , ("two different data", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧ ⟧", "⟦ φ ↦ ⟦ Δ ⤍ FF- ⟧ ⟧")
       , ("a symbol with a λ function nothing else names", "⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ ⟧", "⟦ φ ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧")
@@ -349,11 +455,34 @@
       , ("a bare symbol with a formation whose φ chain ends in a datum", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ ⟦ Δ ⤍ 00- ⟧, x ↦ ⟦⟧ ⟧")
       , ("a bare symbol with a formation carrying no φ", "⟦ a ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧", "⟦ λ ⤍ 𝜎1 ⟧")
       , ("a bare symbol with an application", "⟦ λ ⤍ 𝜎1 ⟧", "Φ.number( φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ )")
+      , ("a bare symbol with a decorator of an application", "⟦ λ ⤍ 𝜎1 ⟧", "⟦ φ ↦ Φ.bool( if ↦ ⟦ λ ⤍ L_fork, φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧ ) ⟧")
       ]
       ( \(desc, left, right) ->
           it ("cannot join " ++ desc) $ do
-            made <- joining left right 4
+            made <- joining False left right 4
             fmap (\(joint, _, _) -> joint) made `shouldBe` Nothing
+      )
+
+  describe "thrown" $ do
+    it "finds a terminator in a formation whose λ it names" $ do
+      term <- parseExpressionThrows "⟦ message ↦ Φ.number( φ ↦ ⟦ λ ⤍ 𝜎4 ⟧ ), λ ⤍ L_throw ⟧"
+      thrown ["L_halt", "L_throw"] term `shouldBe` True
+
+    it "finds a terminator in an application of a formation whose λ it names" $ do
+      term <- parseExpressionThrows "⟦ message ↦ ∅, code ↦ ∅, λ ⤍ L_throw ⟧( message ↦ ξ.x.printf( ξ.y ) )( code ↦ Φ.z )"
+      thrown ["L_throw"] term `shouldBe` True
+
+    forM_
+      [ ("a formation whose λ it does not name" :: String, "⟦ message ↦ ⟦⟧, λ ⤍ L_halt ⟧" :: String)
+      , ("a formation whose φ throws", "⟦ φ ↦ ⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧ ⟧")
+      , ("a dispatch from a formation whose λ it names", "⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧.message")
+      , ("an application whose argument throws", "Φ.f( x ↦ ⟦ message ↦ ⟦⟧, λ ⤍ L_throw ⟧ )")
+      , ("the terminator ⊥", "⊥")
+      ]
+      ( \(desc, text) ->
+          it ("finds no terminator in " ++ desc) $ do
+            term <- parseExpressionThrows text
+            thrown ["L_throw"] term `shouldBe` False
       )
 
   describe "taken" $ do
diff --git a/test/LocatorSpec.hs b/test/LocatorSpec.hs
--- a/test/LocatorSpec.hs
+++ b/test/LocatorSpec.hs
@@ -6,10 +6,10 @@
 module LocatorSpec where
 
 import AST (Attribute (AtLabel), Binding (BiTau, BiVoid), Expression (ExFormation, ExRoot, ExXi))
-import Control.Exception (SomeException, displayException, try)
+import Control.Exception (SomeException, displayException, fromException, try)
 import Control.Monad (forM_)
 import Data.List (intercalate)
-import Locator (locatedExpression, withLocatedExpression)
+import Locator (LocatorException, locatedExpression, withLocatedExpression)
 import Parser (parseExpressionThrows)
 import Printer (printExpression)
 import Test.Hspec (Spec, anyException, describe, expectationFailure, it, shouldBe, shouldThrow)
@@ -24,6 +24,11 @@
 canNotFindObjectMessage :: Expression -> String
 canNotFindObjectMessage locator = printf "Can't find object by locator: '%s'" (printExpression locator)
 
+locatorExceptionMessage :: SomeException -> IO String
+locatorExceptionMessage err = case fromException err :: Maybe LocatorException of
+  Just locErr -> pure (displayException locErr)
+  Nothing -> expectationFailure "expected a LocatorException" >> pure ""
+
 spec :: Spec
 spec = do
   describe "located expression" $ do
@@ -62,7 +67,8 @@
           case result of
             Left err -> do
               show err `shouldBe` messageOf locator'
-              displayException err `shouldBe` messageOf locator'
+              msg <- locatorExceptionMessage err
+              msg `shouldBe` messageOf locator'
             Right _ -> expectationFailure "expected locatedExpression to throw"
       )
 
@@ -116,6 +122,7 @@
           case result of
             Left err -> do
               show err `shouldBe` messageOf locator'
-              displayException err `shouldBe` messageOf locator'
+              msg <- locatorExceptionMessage err
+              msg `shouldBe` messageOf locator'
             Right _ -> expectationFailure "expected withLocatedExpression to throw"
       )
diff --git a/test/MarginSpec.hs b/test/MarginSpec.hs
--- a/test/MarginSpec.hs
+++ b/test/MarginSpec.hs
@@ -9,8 +9,8 @@
 import Bytes (numToBts, strToBts)
 import CST
 import Control.Monad (forM_)
-import Lining (toSingleLine)
-import Margin (withMargin)
+import Lining (LineFormat (..), toSingleLine)
+import Margin (withLines, withMargin)
 import Render (render)
 import Test.Hspec (Spec, describe, it, shouldBe, shouldNotBe)
 
@@ -33,6 +33,20 @@
 shortCalleeLongArg :: Expression
 shortCalleeLongArg = ExApplication (ExDispatch ExRoot (AtLabel "x")) (ArTau (AtLabel "y") bigFormation)
 
+wideStringFormation :: Expression
+wideStringFormation =
+  ExFormation
+    [ BiTau (AtLabel "b") (DataString (strToBts "你好世界你好世界"))
+    , BiTau (AtLabel "c") (ExFormation [])
+    ]
+
+narrowStringFormation :: Expression
+narrowStringFormation =
+  ExFormation
+    [ BiTau (AtLabel "b") (DataString (strToBts "abcdabcd"))
+    , BiTau (AtLabel "c") (ExFormation [])
+    ]
+
 spec :: Spec
 spec = do
   describe "withMargin on EX_FORMATION" $ do
@@ -183,3 +197,36 @@
     it "threads the margin into its body" $ do
       let again = expressionToCST (ExPhiAgain Nothing 3 bigFormation)
       withMargin 1 again `shouldNotBe` withMargin 1000 again
+
+  describe "withMargin measures display width rather than code points" $ do
+    it "wraps a formation whose string has wide characters that push it past the margin" $
+      render (withMargin 30 (expressionToCST wideStringFormation))
+        `shouldBe` "⟦\n  b ↦ \"你好世界你好世界\",\n  c ↦ ⟦⟧\n⟧"
+    it "keeps a same length narrow string on one line within the same margin" $
+      render (withMargin 30 (expressionToCST narrowStringFormation))
+        `shouldBe` "⟦ b ↦ \"abcdabcd\", c ↦ ⟦⟧ ⟧"
+
+  describe "withLines" $
+    forM_ terms $ \(desc, expression) ->
+      forM_ margins $ \margin -> do
+        it ("flattens " ++ desc ++ " at margin " ++ show margin ++ " as the margin pass would") $
+          withLines SINGLELINE margin (expressionToCST expression)
+            `shouldBe` toSingleLine (withMargin margin (expressionToCST expression))
+        it ("wraps " ++ desc ++ " at margin " ++ show margin ++ " as the margin pass does") $
+          withLines MULTILINE margin (expressionToCST expression)
+            `shouldBe` withMargin margin (expressionToCST expression)
+  where
+    terms :: [(String, Expression)]
+    terms =
+      [ ("an empty formation", ExFormation [])
+      , ("a wide formation", bigFormation)
+      , ("a nested formation", nestedFormation)
+      , ("a long callee with a short argument", longCalleeShortArg)
+      , ("a short callee with a long argument", shortCalleeLongArg)
+      , ("a phi meet", ExPhiMeet Nothing 3 bigFormation)
+      , ("a phi again", ExPhiAgain Nothing 3 bigFormation)
+      , ("a number literal", DataNumber (numToBts 42))
+      , ("a string literal", DataString (strToBts "hello"))
+      ]
+    margins :: [Int]
+    margins = [0, 1, 40, 80, 1000]
diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs
--- a/test/ParserSpec.hs
+++ b/test/ParserSpec.hs
@@ -413,6 +413,10 @@
     fromLeft "" (parseExpression "⟦\n  a ↦ ξ,\n  b ↦ ξ,\n  b ↦ Φ\n⟧\n\n\n")
       `shouldSatisfy` isInfixOf "expression:4:3:"
 
+  it "points at the binding that repeats an inline void's attribute, not past the formation" $
+    fromLeft "" (parseExpression "⟦\n  app(a) ↦ ⟦\n    b ↦ ξ.c,\n    a ↦ ξ.d,\n    e ↦ ξ.f\n  ⟧\n⟧\n\n\n")
+      `shouldSatisfy` isInfixOf "expression:4:5:"
+
   describe "an arrow ends an attribute name" $
     forM_
       [ ("⟦ a ↦ ξ.b(c↦ξ) ⟧", "⟦ a ↦ ξ.b(c ↦ ξ) ⟧")
@@ -723,6 +727,12 @@
       , ("⟦ x(y, z) ↦ ∅:a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, z ↦ ∅, a ↦ ∅ ⟧ ⟧")
       , ("[[ x(y) -> Plus:L ]]", "⟦ x ↦ ⟦ y ↦ ∅, λ ⤍ Plus ⟧ ⟧")
       , ("Q.x(y(z) ↦ FF-:Δ)", "Q.x(y ↦ ⟦ z ↦ ∅, Δ ⤍ FF- ⟧)")
+      , ("⟦ x(y) ↦ ⟦ z ↦ ξ ⟧:a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, a ↦ ⟦ z ↦ ξ ⟧ ⟧ ⟧")
+      , ("⟦ x(y) ↦ ⟦ ⟧ : a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, a ↦ ⟦ ⟧ ⟧ ⟧")
+      , ("⟦ x(y) ↦ ⟦ z ↦ ξ ⟧.z:a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, a ↦ ⟦ z ↦ ξ ⟧.z ⟧ ⟧")
+      , ("⟦ x(y) ↦ ⟦ z ↦ ∅ ⟧(z ↦ Φ):a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, a ↦ ⟦ z ↦ ∅ ⟧(z ↦ Φ) ⟧ ⟧")
+      , ("⟦ x(y) ↦ ⟦ z(w) ↦ ⟦ ⟧:b ⟧:a ⟧", "⟦ x ↦ ⟦ y ↦ ∅, a ↦ ⟦ z ↦ ⟦ w ↦ ∅, b ↦ ⟦ ⟧ ⟧ ⟧ ⟧ ⟧")
+      , ("Q.x(y(z) ↦ ⟦ w ↦ ξ ⟧:a)", "Q.x(y ↦ ⟦ z ↦ ∅, a ↦ ⟦ w ↦ ξ ⟧ ⟧)")
       ]
       ( \(sweet, plain) ->
           it sweet $ do
@@ -743,5 +753,5 @@
       parseExpression
       ( map
           (\ipt -> (ipt, Nothing :: Maybe Expression))
-          ["⟦ x(y) ↦ 42 ⟧", "⟦ x(y) ↦ 42:a.b ⟧", "⟦ x(y) ↦ 42:a(z) ⟧", "⟦ x(y) ↦ ξ.a ⟧", "Q.x(y(z) ↦ FF-:Δ.b)"]
+          ["⟦ x(y) ↦ 42 ⟧", "⟦ x(y) ↦ 42:a.b ⟧", "⟦ x(y) ↦ 42:a(z) ⟧", "⟦ x(y) ↦ ξ.a ⟧", "Q.x(y(z) ↦ FF-:Δ.b)", "⟦ x(y) ↦ ⟦ ⟧.a ⟧", "⟦ x(y) ↦ ⟦ ⟧(a ↦ ξ) ⟧", "⟦ x(y) ↦ ⟦ ⟧:y ⟧"]
       )
diff --git a/test/RegexpSpec.hs b/test/RegexpSpec.hs
--- a/test/RegexpSpec.hs
+++ b/test/RegexpSpec.hs
@@ -5,11 +5,16 @@
 
 import Control.Exception (SomeException, displayException, try)
 import Control.Monad (forM_, void)
-import Data.ByteString.Char8 qualified as B
+import Data.ByteString (ByteString)
 import Data.List (isInfixOf)
+import Data.Text qualified as T
+import Data.Text.Encoding qualified as TE
 import Regexp qualified as R
 import Test.Hspec (Spec, anyException, describe, it, shouldBe, shouldReturn, shouldSatisfy, shouldThrow)
 
+utf8 :: String -> ByteString
+utf8 = TE.encodeUtf8 . T.pack
+
 spec :: Spec
 spec = do
   describe "compile" $ do
@@ -20,16 +25,16 @@
       , ("compiles empty pattern", "", "anything")
       ]
       ( \(desc, pattern, input) -> it desc $ do
-          _ <- R.compile (B.pack pattern)
-          matched <- R.match (B.pack pattern) (B.pack input)
+          _ <- R.compile (utf8 pattern)
+          matched <- R.match (utf8 pattern) (utf8 input)
           matched `shouldBe` True
       )
 
     it "throws on invalid pattern" $
-      R.compile (B.pack "[invalid") `shouldThrow` anyException
+      R.compile (utf8 "[invalid") `shouldThrow` anyException
 
     it "throws with the underlying PCRE error embedded in the message" $ do
-      result <- try (void (R.compile (B.pack "[invalid"))) :: IO (Either SomeException ())
+      result <- try (void (R.compile (utf8 "[invalid"))) :: IO (Either SomeException ())
       case result of
         Left exc -> displayException exc `shouldSatisfy` isInfixOf "Regex compilation failed:"
         Right () -> fail "expected R.compile to fail"
@@ -51,7 +56,7 @@
       , ("handles unicode input", "日本語", "これは日本語です", True)
       , ("handles case sensitive match", "Hello", "hello", False)
       ]
-      (\(desc, pattern, input, expected) -> it desc $ R.match (B.pack pattern) (B.pack input) `shouldReturn` expected)
+      (\(desc, pattern, input, expected) -> it desc $ R.match (utf8 pattern) (utf8 input) `shouldReturn` expected)
 
   describe "extractGroups" $
     forM_
@@ -63,9 +68,9 @@
       , ("handles pattern without groups", "test", "this is a test", ["test"])
       ]
       ( \(desc, pattern, input, expected) -> it desc $ do
-          regex <- R.compile (B.pack pattern)
-          groups <- R.extractGroups regex (B.pack input)
-          groups `shouldBe` map B.pack expected
+          regex <- R.compile (utf8 pattern)
+          groups <- R.extractGroups regex (utf8 input)
+          groups `shouldBe` map utf8 expected
       )
 
   describe "substituteGroups" $
@@ -90,7 +95,7 @@
       , ("handles double dollar", "$$1", ["x", "y"], "$y")
       ]
       ( \(desc, template, groups, expected) ->
-          it desc $ R.substituteGroups (B.pack template) (map B.pack groups) `shouldBe` B.pack expected
+          it desc $ R.substituteGroups (utf8 template) (map utf8 groups) `shouldBe` utf8 expected
       )
 
   describe "replaceFirst" $
@@ -112,9 +117,9 @@
       , ("replaces entire string when pattern matches all", "^.*$", "replaced", "original", "replaced")
       ]
       ( \(desc, pattern, replacement, input, expected) -> it desc $ do
-          regex <- R.compile (B.pack pattern)
-          result <- R.replaceFirst regex (B.pack replacement) (B.pack input)
-          result `shouldBe` B.pack expected
+          regex <- R.compile (utf8 pattern)
+          result <- R.replaceFirst regex (utf8 replacement) (utf8 input)
+          result `shouldBe` utf8 expected
       )
 
   describe "replaceAll" $
@@ -129,12 +134,12 @@
       , ("replaces overlapping potential matches correctly", "aa", "X", "aaaa", "XX")
       , ("handles single character replacement", ".", "*", "abc", "***")
       , ("handles word boundary", "\\bword\\b", "WORD", "word in a word", "WORD in a WORD")
-      , ("terminates on an empty-match pattern (anchored ^)", "^", "X", "hello", "XhXeXlXlXoX")
+      , ("terminates on an empty-match pattern (anchored ^)", "^", "X", "hello", "Xhello")
       , ("terminates on an empty regex pattern", "", "X", "hello", "XhXeXlXlXoX")
       , ("keeps UTF-8 characters together after an empty match", "(?=.)", "X", "é", "Xé")
       ]
       ( \(desc, pattern, replacement, input, expected) -> it desc $ do
-          regex <- R.compile (B.pack pattern)
-          result <- R.replaceAll regex (B.pack replacement) (B.pack input)
-          result `shouldBe` B.pack expected
+          regex <- R.compile (utf8 pattern)
+          result <- R.replaceAll regex (utf8 replacement) (utf8 input)
+          result `shouldBe` utf8 expected
       )
diff --git a/test/RenderSpec.hs b/test/RenderSpec.hs
--- a/test/RenderSpec.hs
+++ b/test/RenderSpec.hs
@@ -265,8 +265,8 @@
 
   describe "render LOGIC_OPERATOR" $
     forM_
-      [ ("AND", AND, "\\;\\text{and}\\;")
-      , ("OR", OR, "\\;\\text{or}\\;")
+      [ ("AND", AND, "\\phiText{and}")
+      , ("OR", OR, "\\phiText{or}")
       ]
       (\(desc, node, expected) -> it desc (render node `shouldBe` expected))
 
@@ -303,12 +303,12 @@
       ,
         ( "CO_LOGIC joins multiple conditions"
         , CO_LOGIC [CO_NF xiExpr, CO_NF rootExpr] AND
-        , "\\isnormal{ ξ } \\;\\text{and}\\; \\isnormal{ Φ }"
+        , "\\isnormal{ ξ } \\phiText{and} \\isnormal{ Φ }"
         )
       ,
         ( "CO_LOGIC wraps a nested non-singleton CO_LOGIC in parens"
         , CO_LOGIC [CO_LOGIC [CO_NF xiExpr, CO_NF rootExpr] OR, CO_NF xiExpr] AND
-        , "\\lparen \\isnormal{ ξ } \\;\\text{or}\\; \\isnormal{ Φ } \\rparen \\;\\text{and}\\; \\isnormal{ ξ }"
+        , "\\lparen \\isnormal{ ξ } \\phiText{or} \\isnormal{ Φ } \\rparen \\phiText{and} \\isnormal{ ξ }"
         )
       , ("CO_NF", CO_NF xiExpr, "\\isnormal{ ξ }")
       , ("CO_ABSOLUTE in", CO_ABSOLUTE xiExpr IN, "\\phinoAbsolute{ ξ }")
diff --git a/test/YamlSpec.hs b/test/YamlSpec.hs
--- a/test/YamlSpec.hs
+++ b/test/YamlSpec.hs
@@ -61,7 +61,7 @@
               (yamlRule pth)
               ( \e ->
                   let msg = displayException (e :: SomeException)
-                   in "Unknown" `isInfixOf` msg || "Exactly one" `isInfixOf` msg
+                   in "Unknown" `isInfixOf` msg || "Exactly one" `isInfixOf` msg || "repeated" `isInfixOf` msg
               )
       )
 
