diff --git a/README.md b/README.md
--- a/README.md
+++ b/README.md
@@ -128,9 +128,57 @@
 `--max-steps` work on it unchanged. It may name any meta the entry bound,
 those of the two blocks below among them.
 
+### Rewriting a term into another shape
+
+There is a third block, `rewrite`, and it reduces nothing. Each line of it
+takes a term another meta of the entry is already bound to, named under `of`,
+applies a list of ordinary rules to it, spelled with the very `pattern`,
+`result`, `when` and meta variables a rule file uses, and binds an expression
+meta of its own to the outcome:
+
+```yaml
+- λ: 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- ⟧ ⟧ ) ⟧
+    𝑛4:
+      of: 𝑛2
+      rules: *bool
+  𝑛: 𝑛3
+```
+
+The meta under `of` is one bound by `morph` or by a `rewrite` line above it,
+and nothing else. A rule is tried at every position of the term, the outermost
+first; the first rule whose pattern matches a position rewrites it, and a
+rewritten position is not walked into again, so a rule whose result carries
+its own pattern never loops. Nothing is normalized afterwards: a rewrite is a
+substitution the entry vouches for and not a reduction, exactly as an answer
+is. A rule writing a `𝜎` into its result, or reading a meta its pattern never
+binds, is refused where the file is read.
+
+This is how a program brings two branches of a fork to one shape before they
+are compared. In EO a `Φ.false` is written `φ ↦ ξ.right` while a bool a firing
+answered carries a symbol, and the two are one value in two spellings that
+only the program knows to be one. The block runs before `symbolize` on purpose:
+a result may write the datum a literal stands for, as `⟦ Δ ⤍ 00- ⟧` above, and
+that datum is then stood into a known symbol the join can pair.
+
 ### Standing data into unknowns
 
-There is a third block, `symbolize`, and it reduces nothing. It takes a term
+There is a fourth block, `symbolize`, and it reduces nothing. It takes a term
 another meta of the entry is already bound to and binds an expression meta of
 its own to that same term with every datum in it standing for an unknown:
 
@@ -147,8 +195,8 @@
   𝑛: 𝑛3
 ```
 
-The right-hand side of a line names a meta bound by `morph` or by a
-`symbolize` line above it, and nothing else; a term nobody reduced has no data
+The right-hand side of a line names a meta bound by `morph`, by `rewrite` or by
+a `symbolize` line above it, and nothing else; a term nobody reduced has no data
 to stand. Every `Δ ⤍ b` binding of that term becomes a `λ ⤍ 𝜎k` naming a
 fresh symbol, one per occurrence, so `⟦ Δ ⤍ b ⟧` reads as `⟦ λ ⤍ 𝜎k ⟧` and a
 literal tuple gets several. A term carrying no datum passes through as it was.
@@ -175,7 +223,7 @@
 A branching λ function answers neither of its branches. Which one the program
 takes is decided by a value nobody worked out, so handing one of them through
 would drop the branch point from the program altogether and a reader would see
-the condition computed and thrown away. `join` is the fourth block, and it
+the condition computed and thrown away. `join` is the fifth block, and it
 reduces nothing either: it takes two metas the entry has bound already and
 binds one of its own to the two terms joined into one.
 
@@ -194,11 +242,11 @@
   𝑛: 𝑛5
 ```
 
-A line names two metas bound by `morph`, by `symbolize` or by a `join` line
-above it, and never three: it stands for a choice between two branches, and a
-walk over three terms in parallel is no such choice. The meta it binds is one
-like any other, so the answer may name it alone, as above, or stand it inside a
-larger term.
+A line names two metas bound by `morph`, by `rewrite`, by `symbolize` or by a
+`join` line above it, and never three: it stands for a choice between two
+branches, and a walk over three terms in parallel is no such choice. The meta it
+binds is one like any other, so the answer may name it alone, as above, or stand
+it inside a larger term.
 
 `phino` takes the two terms and requires them to match verbatim, with one
 exception: where `⟦ λ ⤍ 𝜎A ⟧` in one meets a different `⟦ λ ⤍ 𝜎B ⟧` in the
@@ -229,8 +277,19 @@
 the way a λ function no entry answers does, so `--partial` parks it rather than
 aborting the run. A fork whose branches differ in structure, such as a `Φ.true`
 and a `Φ.false` written as `φ ↦ ξ.left` against `φ ↦ ξ.right`, is stuck, and
-bringing two such branches to one shape is the program's job and not `phino`'s.
+bringing two such branches to one shape is the program's job and not `phino`'s,
+which its entry does in a `rewrite` block.
 
+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
+its meta to the other term as it stands and writes on which side the program
+raises, naming the condition by what the first `dataize` operand of the entry
+came down to, as `raise-if(𝔻(𝜎2:λ), right)  # 𝑛4` in the text format and
+`<raise-if symbol="𝜎2" branch="right"/>` in the markup. The deep walk fires
+such a fork too, since that `⊥` is an argument the program wrote rather than
+one the reduction made.
+
 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.
@@ -265,7 +324,7 @@
 $ cat sum.phi
 ⟦
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
-  number ↦ ⟦ φ ↦ ∅, plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
   φ ↦ 5.plus( 6 )
 ⟧
 $ phino dataize --symbolic=atoms.yaml --sweet --hide-rho sum.phi
@@ -469,7 +528,7 @@
 ⟦
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
   bool ↦ ⟦ if ↦ ∅ ⟧,
-  number ↦ ⟦ φ ↦ ∅, plus(x) ↦ ⟦ λ ⤍ L_plus ⟧, gt(x) ↦ ⟦ λ ⤍ L_gt ⟧ ⟧,
+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_plus ⟧, gt(ρ, x) ↦ ⟦ λ ⤍ L_gt ⟧ ⟧,
   foo(x) ↦ ⟦
     φ ↦ ξ.x.gt( 0 ).if( ξ.x.plus( ξ.x.plus( 1 ) ), ξ.x.plus( ξ.x ) ).plus( 5 )
   ⟧,
@@ -662,7 +721,7 @@
 $ cat universe.phi
 ⟦
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
-  number ↦ ⟦ φ ↦ ∅, plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧
+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧
 ⟧
 $ phino dataize --symbolic=atoms.yaml --inside='5.plus( 6 )' universe.phi
 40-45-00-00-00-00-00-00
@@ -689,8 +748,8 @@
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
   number ↦ ⟦
     φ ↦ ∅,
-    plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
-    times(x) ↦ ⟦ λ ⤍ L_number_times ⟧,
+    plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
+    times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧,
     as-bool ↦ ⟦ λ ⤍ L_number_as_bool ⟧
   ⟧,
   φ ↦ 2.times( 3 ).plus( 4 ).as-bool
@@ -733,6 +792,13 @@
 that nothing asked for before the run got stuck is left as it is in the
 residual program, for the next iteration.
 
+An operand of a firing that reaches the terminator `⊥`, or a term no
+dataization rule matches, such as a formation whose `φ` is a void nothing
+filled, never comes down to data either, and `--partial` parks that firing
+the same way, writing the dead end into the protocol as `?(⊥)` with the term
+that could not be dataized beside it. Dataization aimed at `⊥` itself still
+fails, with or without `--partial`, since there is no firing to park.
+
 The nested morphing and dataization recursion is bounded by the
 `--max-steps` option (default `1000`): when the budget is exhausted, the run
 fails with `Dataization did not finish before reaching the limit of steps`.
@@ -757,7 +823,7 @@
 $ cat two.phi
 ⟦
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
-  number ↦ ⟦ φ ↦ ∅, plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
+  number ↦ ⟦ φ ↦ ∅, plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧ ⟧,
   φ ↦ 5.plus( 6 ).plus( 7 )
 ⟧
 $ phino dataize --symbolic=atoms.yaml --sweet --hide-rho two.phi
@@ -798,7 +864,7 @@
 $ cat gap.phi
 ⟦
   bytes ↦ ⟦ φ ↦ ∅ ⟧,
-  number ↦ ⟦ φ ↦ ∅, times(x) ↦ ⟦ λ ⤍ L_number_times ⟧ ⟧,
+  number ↦ ⟦ φ ↦ ∅, times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧ ⟧,
   bar(x) ↦ ⟦ λ ⤍ L_bar ⟧,
   demo ↦ ⟦ foo ↦ ⟦ n ↦ 3, φ ↦ Φ.bar( ξ.n.times( 5 ).times( 7 ) ) ⟧ ⟧
 ⟧
@@ -984,10 +1050,7 @@
 ```bash
 $ echo '[[ @ -> Q.io.stdout("hello") ]]' | phino rewrite
 ⟦
-  φ ↦ Φ.io.stdout(
-    α0 ↦ Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F, ρ ↦ ∅ ⟧ ) )
-  ),
-  ρ ↦ ∅
+  φ ↦ Φ.io.stdout( α0 ↦ Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-65-6C-6C-6F ⟧ ) ) )
 ⟧
 ```
 
@@ -1002,11 +1065,27 @@
 ```
 
 The colon binds as tightly as a dot, so `ξ.a:φ.b` is `⟦ φ ↦ ξ.a ⟧.b`.
-With `--sweet`, `phino` prints every such formation this way, the implicit
-`ρ ↦ ∅` aside, so `⟦ x ↦ ⟦ φ ↦ ξ.a ⟧ ⟧` comes out as `a:φ:x`. A formation
+With `--sweet`, `phino` prints every such formation this way, so
+`⟦ x ↦ ⟦ φ ↦ ξ.a ⟧ ⟧` comes out as `a:φ:x`. A formation
 with inline voids keeps its brackets, as in `x(a) ↦ ⟦ φ ↦ a ⟧`, and so does
 every formation in the salty syntax and in [LaTeX][latex].
 
+A formation has a receiver `ρ` only when it declares one among its voids, the
+way EO declares `^`: `⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧`, or `a(ρ) ↦ ⟦ t ↦ ξ.ρ.k ⟧` with
+the void inline. `phino` adds none of its own, and a dispatch into a formation
+that declares none hands it no `ρ` (the `skip` rule), so `ξ.ρ` there is `⊥`:
+
+<!-- markdownlint-disable MD013 -->
+
+```bash
+$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, a ↦ ⟦ ρ ↦ ∅, t ↦ ξ.ρ.k ⟧ ⟧.a.t ⟧' | phino rewrite --normalize --sweet
+01-:Δ:x
+$ echo '⟦ x ↦ ⟦ k ↦ ⟦ Δ ⤍ 01- ⟧, b ↦ ⟦ t ↦ ξ.ρ.k ⟧ ⟧.b.t ⟧' | phino rewrite --normalize --sweet
+⊥:x
+```
+
+<!-- markdownlint-enable MD013 -->
+
 ## Merge
 
 You can merge several 𝜑-expressions into a single one by merging their
@@ -1041,7 +1120,7 @@
 
 ```bash
 $ phino match --pattern='⟦ Δ ⤍ !d, !B ⟧' hello.phi
-B >> ⟦ ρ ↦ ∅ ⟧
+B >> ⟦⟧
 d >> 68-65-6C-6C-6F
 ```
 
diff --git a/benchmark/demo.phi b/benchmark/demo.phi
--- a/benchmark/demo.phi
+++ b/benchmark/demo.phi
@@ -3,11 +3,11 @@
   bool ↦ ⟦ if ↦ ∅ ⟧,
   number ↦ ⟦
     φ ↦ ∅,
-    plus(x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
-    times(x) ↦ ⟦ λ ⤍ L_number_times ⟧,
-    gt(x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
-    neg ↦ ⟦ φ ↦ ξ.ρ.times( -1 ) ⟧,
-    minus(x) ↦ ⟦ φ ↦ ξ.ρ.plus( ξ.x.neg ) ⟧
+    plus(ρ, x) ↦ ⟦ λ ⤍ L_number_plus ⟧,
+    times(ρ, x) ↦ ⟦ λ ⤍ L_number_times ⟧,
+    gt(ρ, x) ↦ ⟦ λ ⤍ L_number_gt ⟧,
+    neg(ρ) ↦ ⟦ φ ↦ ξ.ρ.times( -1 ) ⟧,
+    minus(ρ, x) ↦ ⟦ φ ↦ ξ.ρ.plus( ξ.x.neg ) ⟧
   ⟧,
   demo ↦ ⟦
     gap(a, b) ↦ ⟦ φ ↦ ξ.a.minus( ξ.b ).times( ξ.a.minus( ξ.b ) ) ⟧,
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.138
+version: 0.0.139
 license: MIT
 synopsis: Command-Line Manipulator of 𝜑-Calculus Expressions
 description: Please see the README on GitHub at <https://github.com/objectionary/phino#readme>
diff --git a/resources/normalize/miss.yaml b/resources/normalize/miss.yaml
--- a/resources/normalize/miss.yaml
+++ b/resources/normalize/miss.yaml
@@ -1,11 +1,20 @@
 # SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
 # SPDX-License-Identifier: MIT
 ---
+# An application of an attribute the formation does not declare is ⊥, save
+# ρ: a formation declares a receiver only when it lists ρ among its voids, and
+# one that does not merely ignores the ρ a dispatch hands it, which is what
+# the 'skip' rule says (#1407).
 name: miss
 pattern: ⟦𝐵1⟧(𝜏1 ↦ 𝑒)
 result: ⊥
 when:
-  not:
-    in:
-      - 𝜏1
-      - 𝐵1
+  and:
+    - not:
+        in:
+          - 𝜏1
+          - 𝐵1
+    - not:
+        eq:
+          - 𝜏1
+          - ρ
diff --git a/resources/normalize/skip.yaml b/resources/normalize/skip.yaml
new file mode 100644
--- /dev/null
+++ b/resources/normalize/skip.yaml
@@ -0,0 +1,15 @@
+# SPDX-FileCopyrightText: Copyright (c) 2025 Objectionary.com
+# SPDX-License-Identifier: MIT
+---
+# A formation that declares no ρ has no receiver, so the ρ 'dot' and 'dotg'
+# hand every dispatched body is dropped once that body turns out to be such a
+# formation. 'copy' fills a ρ the formation declares void, and 'stay' keeps one
+# it has already bound (#1407).
+name: skip
+pattern: ⟦𝐵1⟧(ρ ↦ 𝑒1)
+result: ⟦𝐵1⟧
+when:
+  not:
+    in:
+      - ρ
+      - 𝐵1
diff --git a/src/AST.hs b/src/AST.hs
--- a/src/AST.hs
+++ b/src/AST.hs
@@ -297,8 +297,8 @@
     matchesBytes (ExPhiAgain _ _ (BaseObject "bytes")) = True
     matchesBytes _ = False
     matchFormation :: Expression -> Maybe Bytes
-    matchFormation (ExFormation [BiDelta bts, BiVoid AtRho]) = Just bts
-    matchFormation (ExPhiAgain _ _ (ExFormation [BiDelta bts, BiVoid AtRho])) = Just bts
+    matchFormation (ExFormation [BiDelta bts]) = Just bts
+    matchFormation (ExPhiAgain _ _ (ExFormation [BiDelta bts])) = Just bts
     matchFormation _ = Nothing
 matchDataObject _ = Nothing
 
@@ -314,7 +314,7 @@
     DataObject label bts =
       ExApplication (BaseObject label) (ArTau AtPhi (dataBytes bts))
 
--- The bytes object Φ.bytes(φ ↦ ⟦ Δ ⤍ …, ρ ↦ ∅ ⟧) — what a 'bytes' atom
+-- The bytes object Φ.bytes(φ ↦ ⟦ Δ ⤍ … ⟧) — what a 'bytes' atom
 -- yields and what a 'DataObject' carries under its φ argument.
 -- The payload is bound to 'φ', the void that the real 'bytes' object
 -- declares ([@] > bytes), so that every dispatch on the literal can bind
@@ -323,4 +323,4 @@
 dataBytes bts =
   ExApplication
     (BaseObject "bytes")
-    (ArTau AtPhi (ExFormation [BiDelta bts, BiVoid AtRho]))
+    (ArTau AtPhi (ExFormation [BiDelta bts]))
diff --git a/src/CLI/Validators.hs b/src/CLI/Validators.hs
--- a/src/CLI/Validators.hs
+++ b/src/CLI/Validators.hs
@@ -75,15 +75,17 @@
    in validateBoolOpts (zip bools' (map (printf "The --%s can be used only with --output=xmir") opts))
 
 -- Check that an expression is printable as XMIR: its top level must be a
--- single binding followed by ρ ↦ ∅ (the shape 'expressionToXMIR' accepts).
--- Called right after parsing, so a bad shape fails before any rewriting or
--- dataization work instead of at print time (issue #1082).
+-- single binding, optionally next to a void ρ (the shape 'expressionToXMIR'
+-- accepts). Called right after parsing, so a bad shape fails before any
+-- rewriting or dataization work instead of at print time (issue #1082).
 validateXmirTopLevel :: IOFormat -> Expression -> IO ()
+validateXmirTopLevel XMIR (ExFormation [_]) = pure ()
 validateXmirTopLevel XMIR (ExFormation [_, BiVoid AtRho]) = pure ()
+validateXmirTopLevel XMIR (ExFormation [BiVoid AtRho, _]) = pure ()
 validateXmirTopLevel XMIR expr =
   invalidCLIArguments
     ( printf
-        "Expression cannot be printed with --output=xmir: its top level must be a single binding followed by ρ ↦ ∅, but got: %s"
+        "Expression cannot be printed with --output=xmir: its top level must be a single binding, but got: %s"
         (printExpression expr)
     )
 validateXmirTopLevel _ _ = pure ()
diff --git a/src/CST.hs b/src/CST.hs
--- a/src/CST.hs
+++ b/src/CST.hs
@@ -97,8 +97,10 @@
   | D'' -- d
   | F -- 𝑓
   | F' -- F
+  | F'' -- f
   | S -- 𝜎
   | S' -- S
+  | S'' -- \sigma
   deriving (Eq, Show)
 
 data EXCLAMATION = EXCL | NO_EXCL
@@ -347,19 +349,18 @@
   toCST (ExBytes bts) ctx = EX_BYTES (toCST bts ctx)
   toCST (ExPhiMeet prefix idx expr) ctx = EX_PHI_MEET prefix idx (toCST expr ctx)
   toCST (ExPhiAgain prefix idx expr) ctx = EX_PHI_AGAIN prefix idx (toCST expr ctx)
-  toCST (ExFormation [BiVoid AtRho]) ctx = toCST (ExFormation []) ctx
   toCST (ExFormation []) _ = EX_FORMATION LSB NO_EOL NO_TAB (BI_EMPTY NO_TAB) NO_EOL NO_TAB RSB
   -- A formation of a single binding is sugared into its asset, a colon and
   -- the attribute, as `FF-:Δ`, `Plus:λ`, `∅:a` or `ξ.a:φ` (see #1385). The
   -- full formation is kept next to it, for the notations that have no such
   -- sugar: the salty one, LaTeX and the one '--hide-rho' strips.
   toCST (ExFormation bds) ctx@(tabs, eol) =
-    maybe full (`EX_SINGLE` full) (single (withoutLastVoidRho bds))
+    maybe full (`EX_SINGLE` full) (single bds)
     where
       full :: EXPRESSION
       full =
         let next = tabs + 1
-            bds' = toCST (withoutLastVoidRho bds) (next, eol) :: BINDING
+            bds' = toCST bds (next, eol) :: BINDING
          in EX_FORMATION
               LSB
               EOL
@@ -370,21 +371,22 @@
               RSB
       -- The asset of the only binding, laid out where the formation stands,
       -- unless it has no sugar: a meta binding, a τ binding whose attribute
-      -- the parser takes for a Δ or a λ, or one that carries a formation with
-      -- inline voids, which reads better as 'x(a) ↦ ⟦ … ⟧'
+      -- the parser takes for a Δ or a λ, one that carries a formation with
+      -- inline voids, which reads better as 'x(a) ↦ ⟦ … ⟧', or the void ρ a
+      -- formation declares as its receiver, which reads better as '⟦ ρ ↦ ∅ ⟧'
       single :: [Binding] -> Maybe PAIR
       single [BiTau AtDelta _] = Nothing
       single [BiTau AtLambda _] = Nothing
-      single [BiTau _ (ExFormation (BiVoid attr : rest))] | not (null rest) || attr /= AtRho = Nothing
+      single [bd@(BiTau _ ExFormation{})] | inlined (toCST bd ctx) = Nothing
       single [BiTau attr expr] = Just (PA_TAU (toCST attr ctx) ARROW (toCST expr ctx))
+      single [BiVoid AtRho] = Nothing
       single [bd@(BiVoid _)] = Just (toCST bd ctx)
       single [bd@(BiDelta _)] = Just (toCST bd ctx)
       single [bd@(BiLambda _)] = Just (toCST bd ctx)
       single _ = Nothing
-      withoutLastVoidRho :: [Binding] -> [Binding]
-      withoutLastVoidRho [] = []
-      withoutLastVoidRho [BiVoid AtRho] = []
-      withoutLastVoidRho (bd : bds') = bd : withoutLastVoidRho bds'
+      inlined :: PAIR -> Bool
+      inlined PA_FORMATION{voids = _ : _} = True
+      inlined _ = False
   toCST (DataString bts) (tabs, _) | sweetString bts = EX_STRING (btsToStr bts) (TAB tabs) []
   -- The three canonical non-finite doubles have no sweet numeric literal, so
   -- they become the root dispatches `Φ.nan`, `Φ.pinf` and `Φ.ninf`. Any other
@@ -518,28 +520,34 @@
 
 instance ToCST Binding PAIR where
   toCST (BiTau attr exp@(ExFormation bds)) ctx =
-    let voids' = voids bds
+    let (head', rest) = span positionless bds
+        voids' = [void | BiVoid void <- head']
+        others = filter (not . isVoid) head'
         attr' = toCST attr ctx
      in if null voids'
           then PA_TAU attr' ARROW (toCST exp ctx)
           else
-            let (_voids, _bds) = if length voids' == length bds && last voids' == AtRho then (init voids', []) else (voids', drop (length voids') bds)
-             in PA_FORMATION
-                  attr'
-                  (map (`toCST` ctx) _voids)
-                  ARROW
-                  (unsugared (toCST (ExFormation _bds) ctx))
+            PA_FORMATION
+              attr'
+              (map (`toCST` ctx) voids')
+              ARROW
+              (unsugared (toCST (ExFormation (others ++ rest)) ctx))
     where
       -- Inline voids open a formation, which no one-binding sugar may stand
       -- for, so 'x(a) ↦ ⟦ φ ↦ ξ.a ⟧' is never printed as 'x(a) ↦ a:φ'
       unsugared :: EXPRESSION -> EXPRESSION
       unsugared EX_SINGLE{..} = formation
       unsugared expr = expr
-      voids :: [Binding] -> [Attribute]
-      voids [] = []
-      voids (bd : bds) = case bd of
-        BiVoid attr -> attr : voids bds
-        _ -> []
+      -- Neither λ nor Δ is an attribute, so neither holds a position among
+      -- the voids, and 'x ↦ ⟦ λ ⤍ F, a ↦ ∅ ⟧' is still printed as 'x(a) ↦ ⟦ λ ⤍ F ⟧'
+      positionless :: Binding -> Bool
+      positionless BiVoid{} = True
+      positionless BiLambda{} = True
+      positionless BiDelta{} = True
+      positionless _ = False
+      isVoid :: Binding -> Bool
+      isVoid BiVoid{} = True
+      isVoid _ = False
   toCST (BiTau attr exp) ctx = PA_TAU (toCST attr ctx) ARROW (toCST exp ctx)
   toCST (BiVoid attr) ctx = PA_VOID (toCST attr ctx) ARROW EMPTY
   toCST (BiDelta bts) ctx = PA_DELTA (toCST bts ctx)
diff --git a/src/Dataize.hs b/src/Dataize.hs
--- a/src/Dataize.hs
+++ b/src/Dataize.hs
@@ -16,12 +16,13 @@
 import AST
 import Builder (buildBytesThrows, buildExpressionThrows)
 import Control.Exception (throwIO, try)
-import Control.Monad (foldM)
+import Control.Monad (foldM, unless)
 import Data.List (find)
 import Data.List.NonEmpty (NonEmpty (..))
 import qualified Data.List.NonEmpty as NE
 import Data.Maybe (listToMaybe)
-import Deps (Judgment (..), State (..))
+import qualified Data.Text as T
+import Deps (Evaluation (..), Judgment (..), State (..))
 import Locator (locatedExpression)
 import Matcher (Subst, matchExpression')
 import Morph (Morphed, ReduceContext (..), ReduceException (..), ReductionFunc, deeper, excluding, execBuildTerm, insideUniverse, leadsTo, morph', normalized, parking, producer, sidePremise, universed, unvisited, verb)
@@ -78,7 +79,10 @@
 -- it dataizes ⊥. The terminator ⊥ signals an error and lies outside 𝔻's domain,
 -- so it matches no clause (there is no 'end' rule mapping it to empty bytes) and
 -- dataization stops there; a data-less formation therefore fails through the
--- same path (see #955).
+-- same path (see #955). The dead end is signalled as 'Undataizable', whose
+-- message names the terminator where that is what was reached rather than
+-- reporting the generic "no dataization rule matched", and which an operand of
+-- a firing parks on under '_partial' (see 'reduction', #1401).
 -- 'box' contextualizes the φ-body and keeps dataizing (its step is labelled by
 -- its 'contextualize' side-computation), and 'norm' reduces through morphing,
 -- splicing the morphing steps into the chain. The clauses are disjoint (see
@@ -106,7 +110,7 @@
       matched <- firstMatch ctx rules
       case matched of
         Just (rule, subst) -> reduce ctx rule subst
-        Nothing -> throwIO (userError (unmatched expr))
+        Nothing -> throwIO (Undataizable expr state)
   where
     -- The symbol a formation carries in place of a λ name, if any. Such a
     -- formation is what a λ function answered with where it could not work the
@@ -125,13 +129,6 @@
     manufactured idx ctx = do
       seq' <- leadsTo seq "symbol" (ExBytes datum) ctx
       pure ((datum, NE.toList seq'), state{_manufactured = Just idx})
-    -- 𝔻 is partial: the terminator ⊥ signals an error and lies outside its
-    -- domain (see #955), so it matches no clause and lands here. Name it in the
-    -- message rather than reporting the generic "no dataization rule matched",
-    -- which would otherwise hide that the computation reached a dead end.
-    unmatched :: Expression -> String
-    unmatched ExTermination = "dataization reached the terminator ⊥, which signals an error and cannot be dataized"
-    unmatched _ = "no dataization rule matched"
     firstMatch :: ReduceContext -> [Y.DataizeRule] -> IO (Maybe (Y.DataizeRule, Subst))
     firstMatch _ [] = pure Nothing
     firstMatch ctx (rule : rest) = do
@@ -213,12 +210,29 @@
 -- the run bounds the nesting, and the state 𝑠 goes in and comes back out, so
 -- the symbols this reduction mints are counted in the same sequence as the ones
 -- around it.
+--
+-- An operand reaching a term outside the domain of 𝔻 — the terminator ⊥, or a
+-- term no dataization rule matches, such as a formation whose φ is a void
+-- nothing filled — never comes down to data either, and under '_partial' it
+-- leaves the firing stuck the same way rather than ending the run: an unfilled
+-- void or an error object is as much a property of the program as a λ function
+-- nobody answers (#1401). The protocol records the dead end as a stuck site
+-- named '⊥', written with the term 𝔻 could not dataize, and the name travels
+-- back in the state as the one the firing got stuck on (see '_stuck'). A run
+-- of 𝔻 that is not an operand still fails on it, '_partial' or not (#955).
 reduction :: ReductionFunc
 reduction univ ctx expr state = do
   (universe, aiming) <- insideUniverse expr univ ctx
-  (outcome, _, state') <- dataize universe state aiming
-  pure (reached outcome, state')
+  result <- try (dataize universe state aiming)
+  case result of
+    Right (outcome, _, state') -> pure (reached outcome, state')
+    Left (Undataizable term state') | ctx._partial -> do
+      unless (dead `elem` ctx._parked) (ctx._saveEval (EvStuck ctx._nesting dead Dataization term))
+      pure (Nothing, state'{_stuck = Just dead})
+    Left failure -> throwIO failure
   where
+    dead :: T.Text
+    dead = "⊥"
     reached :: Outcome -> Maybe Bytes
     reached (Dataized bytes) = Just bytes
     reached (Residual _) = Nothing
diff --git a/src/Deps.hs b/src/Deps.hs
--- a/src/Deps.hs
+++ b/src/Deps.hs
@@ -14,6 +14,7 @@
 
 import AST
 import Data.IORef (IORef, readIORef, writeIORef)
+import Data.List (intercalate)
 import qualified Data.Map.Strict as Map
 import Data.Maybe (fromMaybe)
 import qualified Data.Text as T
@@ -163,7 +164,9 @@
     -- commented with, since nothing of the calculus runs here: the line names
     -- a meta the entry bound above it, the way a 'join' line names the two it
     -- joined, where an operand line names the judgment that reduced it
-    -- (#1306).
+    -- (#1306). A 'rewrite' line is written the same way and for the same
+    -- reason: it applies the rules of the entry to a meta bound above it and
+    -- reduces nothing either (#1409).
     EvSymbolize Int T.Text Expression Expression
   | -- What is known about a symbol a 'symbolize' line minted: dataizing the
     -- formation the symbol names answers these bytes. It is a fact about the
@@ -185,6 +188,14 @@
     -- knows which of them belongs to which branch. It is a fact about the
     -- symbol and no binding of it, exactly as 'EvKnown' is (#1246).
     EvJoined Int Int (Int, Int)
+  | -- A 'join' line one of whose two terms is ⊥, which is how a program
+    -- spells "raise unless the condition holds": what the condition of the
+    -- fork came down to — the first operand the entry dataized, a symbol or
+    -- data, where it dataized any — the side that raises, 'left' or 'right' in
+    -- the order the entry wrote the two metas, and the meta holding the ⊥. It
+    -- stands ahead of the 'join' line, which binds the other side, so a reader
+    -- renders the record as a throw on that side of the condition (#1405).
+    EvRaiseIf Int (Maybe (Either Int Bytes)) T.Text T.Text
   | -- A fresh symbol the answer of the firing asked for, one record per bare 𝜎
     -- the entry wrote it with. It is a fact about the firing and no property of
     -- any one term of it, since an answer may carry several symbols or none and
@@ -368,6 +379,13 @@
       left <- render (standing one)
       right <- render (standing two)
       pure (protocol, Just (indented depth (printf "𝔻(%s) ∈ { 𝔻(%s), 𝔻(%s) }" form left right)))
+    written (EvRaiseIf depth condition side raised) protocol = do
+      cond <- maybe (pure []) (fmap pure . spelled) condition
+      pure (protocol, Just (indented depth (printf "raise-if(%s)  # %s" (intercalate ", " (cond ++ [T.unpack side])) (T.unpack raised))))
+      where
+        spelled :: Either Int Bytes -> IO String
+        spelled (Left symbol) = printf "𝔻(%s)" <$> render (standing symbol)
+        spelled (Right bytes) = pure (printBytes bytes)
     written (EvMinted _ _) protocol = pure (protocol, Nothing)
     written (EvBuilt depth term) protocol = do
       value <- borrowed protocol term
@@ -546,6 +564,17 @@
         (kept, closers) = closed depth nesting._closing
         joint :: String
         joint = printf "<joined symbol=\"%s\">%s %s</joined>" (sigma fresh) (sigma one) (sigma two)
+    elements (EvRaiseIf depth condition side _) nesting =
+      pure (nesting{_closing = kept}, closers ++ [indented depth raise])
+      where
+        (kept, closers) = closed depth nesting._closing
+        -- The condition a symbol stands for is named by it, the way 'joined'
+        -- names one, and data the condition came down to is the text.
+        raise :: String
+        raise = case condition of
+          Just (Left symbol) -> printf "<raise-if symbol=\"%s\" branch=\"%s\"/>" (sigma symbol) (quoted side)
+          Just (Right bytes) -> printf "<raise-if branch=\"%s\">%s</raise-if>" (quoted side) (escapeXMLText (printBytes bytes))
+          Nothing -> printf "<raise-if branch=\"%s\"/>" (quoted side)
     elements (EvMinted depth symbol) nesting =
       pure (nesting{_closing = kept}, closers ++ [indented depth (printf "<minted>%s</minted>" (sigma symbol))])
       where
diff --git a/src/Evaluate.hs b/src/Evaluate.hs
--- a/src/Evaluate.hs
+++ b/src/Evaluate.hs
@@ -22,15 +22,17 @@
 import Control.Monad (foldM, unless)
 import Data.List (partition)
 import Data.List.NonEmpty (NonEmpty (..))
-import Data.Maybe (fromMaybe, isNothing)
+import Data.Maybe (fromMaybe, isNothing, listToMaybe)
 import qualified Data.Text as T
 import Deps (BuildTermMethodS, Evaluation (..), State (..), Term (..))
 import Lambdas (Lambda (..), Meta (..), joined, matched, minted, symbolized)
 import Matcher (MetaValue (..), Subst, combine, substEmpty, substSingle, substSlot)
 import Morph (ReduceContext (..), ReduceException (..), deeper, morph', morphing, normalized, unparked)
 import Printer (printFunction)
+import Rule (RuleContext (RuleContext), matchExpressionWithRule')
 import Text.Printf (printf)
 import Yaml (ExtraArgument (..))
+import qualified Yaml as Y
 
 -- The Evaluation function 𝔼(b, e, s): it fires the λ function of a formation
 -- 'b' against the global universe 'e', under the incoming state 𝑠, normalizes
@@ -129,10 +131,11 @@
   Just entry -> do
     caller._saveEval (EvFiring caller._nesting func caller._judgment caller._site)
     let ctx = caller{_nesting = caller._nesting + 1}
-    (bound, dataized) <- foldM (down ctx) (substEmpty, state) entry._dataized
+    (bound, dataized, conditions) <- foldM (down ctx) (substEmpty, state, []) entry._dataized
     (bound', morphed) <- foldM (through ctx) (bound, dataized) entry._morphed
-    (bound'', stood) <- foldM (masked ctx) (bound', morphed) entry._symbolized
-    (bound''', forked) <- foldM (paired ctx) (bound'', stood) entry._paired
+    rewrote <- foldM (reshaped ctx) bound' entry._rewritten
+    (bound'', stood) <- foldM (masked ctx) (rewrote, morphed) entry._symbolized
+    (bound''', forked) <- foldM (paired ctx (listToMaybe (reverse conditions))) (bound'', stood) entry._paired
     answered ctx entry bound''' forked
   where
     -- Bring one 'dataize' operand down through 𝔻 and bind the bytes meta that
@@ -153,15 +156,20 @@
     -- it and lets the spine frame around this firing attach its own, which is
     -- what keeps '--sequence' free of the synthetic attribute the operand was
     -- reduced under.
-    down :: ReduceContext -> (Subst, State) -> (Meta, Expression) -> IO (Subst, State)
-    down ctx (bound, state') (meta, term) = do
+    --
+    -- What the operand came down to goes on beside the substitution, the last
+    -- operand first, since the first of them is the condition a fork branches
+    -- on and a 'join' line one side of which reaches ⊥ names it (see 'paired').
+    down :: ReduceContext -> (Subst, State, [Either Int Bytes]) -> (Meta, Expression) -> IO (Subst, State, [Either Int Bytes])
+    down ctx (bound, state', conditions) (meta, term) = do
       (value, state'') <- unparked (ctx._reduce univ ctx (operand term) state'{_manufactured = Nothing, _stuck = Nothing})
       case value of
         Nothing -> throwIO (Stuck (fromMaybe func state''._stuck))
         Just bytes -> do
-          ctx._saveEval (EvData ctx._nesting meta._spelling term (maybe (Right bytes) Left state''._manufactured))
+          let datum = maybe (Right bytes) Left state''._manufactured
+          ctx._saveEval (EvData ctx._nesting meta._spelling term datum)
           bound' <- bind meta (MvBytes bytes) bound
-          pure (bound', state'')
+          pure (bound', state'', datum : conditions)
     -- Reduce one 'morph' operand through 𝕄 and bind the expression meta that
     -- names it. Unlike a dataized one it may stay an unknown: a term carrying a
     -- symbol is a perfectly good normal form, and standing it into the answer
@@ -172,6 +180,20 @@
       ctx._saveEval (EvTerm ctx._nesting meta._spelling term normal)
       bound' <- bind meta (MvExpression normal) bound
       pure (bound', state'')
+    -- Rewrite a term another line of the entry has bound with the rules of the
+    -- line and bind the expression meta naming what it becomes (see
+    -- 'rewritten'). Nothing is reduced and nothing is minted: a rewrite is a
+    -- substitution the entry vouches for, exactly as an answer is, so the term
+    -- it makes is written to the protocol and bound as it is, which is what
+    -- lets a program bring the branches of a fork to one shape before they
+    -- are compared (#1409). The line is commented with the meta it rewrote,
+    -- the way a 'symbolize' line is, since no judgment of the calculus made it.
+    reshaped :: ReduceContext -> Subst -> (Meta, (Meta, [Y.Rule])) -> IO Subst
+    reshaped ctx bound (meta, (source, rules)) = do
+      term <- buildExpressionThrows (ExMeta source._name) bound
+      shaped <- rewritten rules (RuleContext ctx._buildTerm) term
+      ctx._saveEval (EvSymbolize ctx._nesting meta._spelling (ExMeta source._name) shaped)
+      bind meta (MvExpression shaped) bound
     -- Stand the data of a term another line of the entry has bound into
     -- unknowns and bind the expression meta naming what it becomes. Nothing is
     -- reduced here: what changes is that every datum of the term becomes a
@@ -205,18 +227,43 @@
     -- the line binding the term, the way a 'symbolize' line writes what it
     -- knows, since a reader ties the join to the two values it was made from
     -- by that fact alone and never by diffing the terms.
-    paired :: ReduceContext -> (Subst, State) -> (Meta, (Meta, Meta)) -> IO (Subst, State)
-    paired ctx (bound, state') (meta, (left, right)) = do
+    --
+    -- One side reaching ⊥ is a join too, the one 'if. cond value ⊥' spells
+    -- "raise unless cond" with: the program raises on that side of the
+    -- condition and has a perfectly good value on the other. The protocol is
+    -- told on which side it raises, naming the condition by what the first
+    -- operand the entry dataized came down to, and the meta is bound to the
+    -- other side as it stands, which is the one value the fork can still
+    -- answer with. Both sides reaching ⊥ is no such case: they are one term
+    -- and join into ⊥ verbatim (#1405).
+    paired :: ReduceContext -> Maybe (Either Int Bytes) -> (Subst, State) -> (Meta, (Meta, Meta)) -> IO (Subst, State)
+    paired ctx condition (bound, state') (meta, (left, right)) = do
       one <- branch left
       two <- branch right
-      case joined one two state'._minted of
-        Nothing -> throwIO (Stuck func)
-        Just (term, made, spent) -> do
-          mapM_ (ctx._saveEval . fact) made
+      case (one, two) of
+        (ExTermination, ExTermination) -> both one two
+        (ExTermination, _) -> raising "left" left two
+        (_, ExTermination) -> raising "right" right one
+        _ -> both one two
+      where
+        -- The two sides joined symbol by symbol (see 'joined').
+        both :: Expression -> Expression -> IO (Subst, State)
+        both one two = case joined one two state'._minted of
+          Nothing -> throwIO (Stuck func)
+          Just (term, made, spent) -> do
+            mapM_ (ctx._saveEval . fact) made
+            ctx._saveEval (EvJoin ctx._nesting meta._spelling (left._spelling, right._spelling) term)
+            bound' <- bind meta (MvExpression term) bound
+            pure (bound', state'{_minted = spent})
+        -- The side that raises written down, named by the meta holding its ⊥,
+        -- and the other side bound as the join; nothing is minted, since one
+        -- value is left and a symbol would stand for nothing but it.
+        raising :: T.Text -> Meta -> Expression -> IO (Subst, State)
+        raising side raised term = do
+          ctx._saveEval (EvRaiseIf ctx._nesting condition side raised._spelling)
           ctx._saveEval (EvJoin ctx._nesting meta._spelling (left._spelling, right._spelling) term)
           bound' <- bind meta (MvExpression term) bound
-          pure (bound', state'{_minted = spent})
-      where
+          pure (bound', state')
         -- The term one side of the join is bound to, which is what a meta of
         -- the entry reads out of the substitution the firing has made (see
         -- 'earlier' in 'Lambdas': a 'join' line names metas bound above it and
@@ -275,6 +322,43 @@
         throwIO
           (userError (printf "The meta '%s' of λ function '%s' clashes with an existing binding" (T.unpack meta._spelling) (T.unpack func)))
 
+-- The term with the rules of a 'rewrite' line applied to it. Every position of
+-- the term is tried, the outermost first, and the first rule whose pattern
+-- matches a position as a whole rewrites it with the first match it made; the
+-- position rewritten is not walked into again, so a rule whose result carries
+-- its own pattern rewrites it once and never loops. A position no rule matches
+-- is walked into, every binding and every argument of it, ρ among them, since
+-- the shape of a branch is the program's to say and phino has no say in where
+-- that shape is written. Nothing is normalized afterwards: the rules are the
+-- program's word on what one term stands for, and reducing their outcome would
+-- have phino second-guess it (#1409).
+rewritten :: [Y.Rule] -> RuleContext -> Expression -> IO Expression
+rewritten rules ctx = goExpr
+  where
+    goExpr :: Expression -> IO Expression
+    goExpr expr = goRules rules
+      where
+        goRules :: [Y.Rule] -> IO Expression
+        goRules [] = inside expr
+        goRules (rule : rest) = do
+          substs <- matchExpressionWithRule' [substEmpty] expr rule ctx
+          case substs of
+            subst : _ -> buildExpressionThrows rule.result subst
+            [] -> goRules rest
+    inside :: Expression -> IO Expression
+    inside (ExFormation bds) = ExFormation <$> mapM goBinding bds
+    inside (ExApplication expr arg) = ExApplication <$> goExpr expr <*> goArgument arg
+    inside (ExDispatch expr attr) = (`ExDispatch` attr) <$> goExpr expr
+    inside (ExPhiMeet prefix idx expr) = ExPhiMeet prefix idx <$> goExpr expr
+    inside (ExPhiAgain prefix idx expr) = ExPhiAgain prefix idx <$> goExpr expr
+    inside expr = pure expr
+    goBinding :: Binding -> IO Binding
+    goBinding (BiTau attr expr) = BiTau attr <$> goExpr expr
+    goBinding bd = pure bd
+    goArgument :: Argument -> IO Argument
+    goArgument (ArTau attr expr) = ArTau attr <$> goExpr expr
+    goArgument (ArAlpha alpha expr) = ArAlpha alpha <$> goExpr expr
+
 -- Ask 𝕄 about a term and fire the λ of the formation it reaches, as long as an
 -- entry of the '--symbolic' file answers it, asking 𝕄 about every answer again:
 -- what comes back is the answer of the last firing, or nothing at all where
@@ -293,7 +377,7 @@
   morphed <- try (reduced ctx)
   case morphed of
     Right (ExFormation bds, state')
-      | demanded bds -> maybe (pure (Nothing, state')) (evaluated ctx state' (ExFormation bds)) (saturated bds)
+      | demanded bds -> maybe (pure (Nothing, state')) (evaluated ctx state' (ExFormation bds)) (saturated term bds)
     Right (_, state') -> pure (Nothing, state')
     Left failure -> parked state failure
   where
@@ -404,17 +488,56 @@
 -- fires only what dataization demands and nothing demands a method; the deep
 -- walk meets every one a program declares — the method table of the object
 -- model above all — so it asks first (see 'deepened').
-saturated :: [Binding] -> Maybe (T.Text, Expression)
-saturated bds = case lambda bds of
-  Just (func, ExFormation rest) | all filled rest -> Just (func, ExFormation rest)
+--
+-- A binding holding ⊥ counts as filled only where the term the walk was handed
+-- wrote that ⊥ as an argument itself. A ⊥ the reduction made is no argument:
+-- the deep walk reduces a body in the scope of the formation around it, and a
+-- formation declaring ρ and standing unapplied still holds ρ ↦ ∅, so a ξ.ρ in
+-- that body comes back as ⊥ rather than as the object the next dispatch
+-- supplies (#1196). A ⊥
+-- written as an argument is what the program meant, and 'if. cond value ⊥' is
+-- how it spells "raise unless cond", so a fork like that fires and its join
+-- says on which side it raises (#1405). An argument given by name covers the
+-- binding of that name; one given by position covers some binding, so there
+-- have to be as many of them as ⊥ bindings no name covers. A term handing
+-- nothing but ⊥ is still left alone, since there is no value for a firing to
+-- work with and all it could do is get stuck on one of them.
+saturated :: Expression -> [Binding] -> Maybe (T.Text, Expression)
+saturated term bds = case lambda bds of
+  Just (func, ExFormation rest)
+    | all filled rest && (not (any raising rest) || given rest) -> Just (func, ExFormation rest)
   _ -> Nothing
+  where
+    named :: [Attribute]
+    positional :: Int
+    valued :: Bool
+    (named, positional, valued) = written term
+    -- Whether every ⊥ of the bindings is one the term wrote, beside some
+    -- argument that is not ⊥.
+    given :: [Binding] -> Bool
+    given rest = valued && length (filter unwritten rest) <= positional
+    raising :: Binding -> Bool
+    raising (BiTau _ ExTermination) = True
+    raising _ = False
+    -- Whether a binding holds a ⊥ no argument given by name wrote.
+    unwritten :: Binding -> Bool
+    unwritten (BiTau attr ExTermination) = attr `notElem` named
+    unwritten _ = False
+    -- The attributes the application chain of a term hands a literal ⊥ by
+    -- name, how many literal ⊥ it hands by position, and whether it hands
+    -- anything but ⊥ at all.
+    written :: Expression -> ([Attribute], Int, Bool)
+    written (ExApplication expr (ArTau attr ExTermination)) =
+      let (attrs, count, other) = written expr in (attr : attrs, count, other)
+    written (ExApplication expr (ArAlpha _ ExTermination)) =
+      let (attrs, count, other) = written expr in (attrs, count + 1, other)
+    written (ExApplication expr _) =
+      let (attrs, count, _) = written expr in (attrs, count, True)
+    written _ = ([], 0, False)
 
 -- Whether a binding hands the formation something to work with. A void does
--- not: it names an argument the program has still to supply. Neither does ⊥:
--- the deep walk reduces a body in the scope of the formation around it, and a
--- formation standing unapplied still holds ρ ↦ ∅, so a ξ.ρ in that body comes
--- back as ⊥ rather than as the object the next dispatch supplies (#1196).
+-- not: it names an argument the program has still to supply. A ⊥ is told
+-- apart by 'saturated', which knows the term it was written in.
 filled :: Binding -> Bool
 filled (BiVoid _) = False
-filled (BiTau _ ExTermination) = False
 filled _ = True
diff --git a/src/Filter.hs b/src/Filter.hs
--- a/src/Filter.hs
+++ b/src/Filter.hs
@@ -38,7 +38,7 @@
   forms -> mergeForms forms
   where
     def :: Expression
-    def = ExFormation [BiVoid AtRho]
+    def = ExFormation []
     pick :: Expression -> Maybe Expression
     pick fqn = do
       attrs <- fqnToAttrs fqn
@@ -47,12 +47,12 @@
     mergeForms forms =
       let bds = concat [bs | ExFormation bs <- forms]
           bds' = filter (\bd -> attributeFromBinding bd /= Just AtRho) bds
-       in ExFormation (withVoidRho bds')
+       in ExFormation bds'
     includedFormation :: Expression -> [Attribute] -> Maybe Expression
     includedFormation (ExFormation bindings) [at] =
       let bs = [bd | bd <- bindings, attributeFromBinding bd == Just at]
-       in if null bs then Nothing else Just (ExFormation (withVoidRho bs))
-    includedFormation (ExFormation bindings) atts = includedBindings bindings atts >>= (Just . ExFormation . (: [BiVoid AtRho]))
+       in if null bs then Nothing else Just (ExFormation bs)
+    includedFormation (ExFormation bindings) atts = includedBindings bindings atts >>= (Just . ExFormation . pure)
       where
         includedBindings :: [Binding] -> [Attribute] -> Maybe Binding
         includedBindings ((BiTau at' form@(ExFormation _)) : bs) as@(at'' : rs)
diff --git a/src/LaTeX.hs b/src/LaTeX.hs
--- a/src/LaTeX.hs
+++ b/src/LaTeX.hs
@@ -333,8 +333,10 @@
   toLaTeX B = B'
   toLaTeX D = D'
   toLaTeX D'' = D'
-  toLaTeX F = F'
-  toLaTeX S = S'
+  toLaTeX F = F''
+  toLaTeX F' = F''
+  toLaTeX S = S''
+  toLaTeX S' = S''
   toLaTeX mh = mh
 
 instance ToLaTeX BYTES where
diff --git a/src/Lambdas.hs b/src/Lambdas.hs
--- a/src/Lambdas.hs
+++ b/src/Lambdas.hs
@@ -27,14 +27,23 @@
 -- under '𝑛' is what the firing answers with, and a bare 𝜎 in it mints a fresh
 -- symbol.
 --
--- 'symbolize' is the third block and reduces nothing at all. It takes a term
+-- 'rewrite' is the third block and reduces nothing either. Each line of it
+-- names a meta the entry has bound already under 'of' and a list of ordinary
+-- rules under 'rules', spelled the way a rule file spells one, and binds an
+-- expression meta of its own to that term with the rules applied to it (see
+-- 'rewritten' in 'Evaluate'). It is how a program brings two branches of a
+-- fork to one shape before they are compared: one literal of a bool and the
+-- answer of a firing are one value in two spellings, and nothing but the
+-- program knows that (#1409).
+--
+-- 'symbolize' is the fourth block and reduces nothing at all. It takes a term
 -- an earlier block of the very same entry has already bound and binds an
 -- expression meta of its own to that term with every datum in it standing for
 -- an unknown, 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 (see 'symbolized').
 --
--- 'join' is the fourth block and reduces nothing either. It takes two metas the
+-- 'join' is the fifth block and reduces nothing either. It takes two metas the
 -- entry has bound already and binds one of its own to the two terms joined,
 -- which is what a branching λ function answers with: a fork stands for either
 -- of its branches and no one branch stands for both, so the shape both of them
@@ -69,7 +78,7 @@
 import AST
 import Control.Exception (Exception, throwIO)
 import Control.Monad (void)
-import Data.Aeson (FromJSON (parseJSON), Key, Object, withObject, (.!=), (.:), (.:?))
+import Data.Aeson (FromJSON (parseJSON), Key, Object, Value (Object), withObject, (.!=), (.:), (.:?))
 import Data.List (find)
 import Data.Map.Strict (Map)
 import qualified Data.Map.Strict as Map
@@ -78,12 +87,14 @@
 import Data.Text.Encoding (encodeUtf8)
 import qualified Data.Yaml as Yaml
 import Logger (logDebug)
+import Metas (Metas (metas))
 import Parser (parseBytes, parseExpression)
 import Slots (Slots (slots))
 import Text.Printf (printf)
 import Text.Regex.PCRE (matchTest)
 import Text.Regex.PCRE.ByteString (Regex, compUTF8, compile, execBlank)
 import Yaml (referenceless)
+import qualified Yaml as Y
 
 -- One meta an entry of the file binds: the name the file spells it with, which
 -- is the name the protocol of '--protocol' reports it back under, and the name
@@ -98,13 +109,14 @@
 
 -- One λ function phino may fire, as the file spells it: the key it is
 -- registered under, the operands it brings down to data, the operands it
--- reduces to a normal form, the terms of those it stands the data of into
--- unknowns, the pairs of those it joins into one term and the term it answers
+-- reduces to a normal form, the terms of those it rewrites with rules of its
+-- own, the terms of those it stands the data of into unknowns, the pairs of those it joins into one term and the term it answers
 -- with.
 data Lambda = Lambda
   { _key :: Text
   , _dataized :: [(Meta, Expression)]
   , _morphed :: [(Meta, Expression)]
+  , _rewritten :: [(Meta, (Meta, [Y.Rule]))]
   , _symbolized :: [(Meta, Expression)]
   , _paired :: [(Meta, (Meta, Meta))]
   , _answer :: Expression
@@ -133,6 +145,7 @@
       Lambda key
         <$> operands key bytesMeta entry "dataize"
         <*> operands key expressionMeta entry "morph"
+        <*> rewrites (T.unpack key) entry
         <*> operands key expressionMeta entry "symbolize"
         <*> pairs (T.unpack key) entry
         <*> entry .: "𝑛"
@@ -188,17 +201,73 @@
                   (T.unpack meta)
                   key
               )
-      -- Every 'symbolize' and 'join' line reads terms the entry has bound
-      -- already: a 'morph' operand, a line above it in its own block or, for a
-      -- 'join' line, a 'symbolize' one, since nothing else of an entry is a
+      -- The metas a 'rewrite' block binds, each paired with the meta whose
+      -- term it rewrites and the rules it rewrites that term with, ordered by
+      -- the name of the meta the way every other block is. A rule is read the
+      -- way a rule file reads one, and on top of that a rule writing a symbol
+      -- into its result or reading a meta its match never bound is refused
+      -- here: a symbol is minted by a firing and never spelled by hand, and a
+      -- meta nothing bound is one the rule cannot be built with.
+      rewrites :: String -> Object -> Yaml.Parser [(Meta, (Meta, [Y.Rule]))]
+      rewrites key entry = do
+        mapping <- entry .:? "rewrite" .!= (Map.empty :: Map Text Object)
+        mapM line (Map.toAscList mapping)
+        where
+          line :: (Text, Object) -> Yaml.Parser (Meta, (Meta, [Y.Rule]))
+          line (meta, body) = do
+            named <- expressionMeta meta
+            source <- body .: "of" >>= expressionMeta
+            written <- body .: "rules"
+            rules <- mapM rule written
+            pure (named, (source, rules))
+          rule :: Object -> Yaml.Parser Y.Rule
+          rule body = do
+            result <- body .: "result"
+            symbolless result
+            parsed <- parseJSON (Object body)
+            bound parsed
+            pure parsed
+          -- A bare 𝜎 or a numbered one written into a result: the one names a
+          -- symbol a rewrite has no business minting, since it is a
+          -- substitution the entry vouches for and no firing, and the other
+          -- one nobody minted at all.
+          symbolless :: Expression -> Yaml.Parser ()
+          symbolless result
+            | null (symbols result) && null [kind | Slot kind _ <- slots result, kind == "S"] = pure ()
+            | otherwise = fail (printf "A rule of the 'rewrite' block of λ function '%s' writes a symbol 𝜎 into its result" key)
+          -- Every meta a result reads is one the pattern, the 'e-match' or a
+          -- 'where' extension of the very same rule binds.
+          bound :: Y.Rule -> Yaml.Parser ()
+          bound parsed = case filter (`notElem` known) (metas parsed.result) of
+            [] -> pure ()
+            meta : _ ->
+              fail
+                ( printf
+                    "The rule '%s' of the 'rewrite' block of λ function '%s' reads the meta '%s' it never binds"
+                    parsed.name
+                    key
+                    (T.unpack meta)
+                )
+            where
+              known :: [Text]
+              known = metas parsed.pattern ++ metas parsed.ematch ++ concatMap (metas . (.meta)) (concat parsed.where_)
+      -- Every 'rewrite', 'symbolize' and 'join' line reads terms the entry has
+      -- bound already: a 'morph' operand, a line above it in its own block or
+      -- a line of a block above its own, since nothing else of an entry is a
       -- normal form yet and the blocks run in the order the entry lists them
       -- here. A line naming anything else names a term nobody reduced, and the
       -- file is wrong where it is read rather than half-way through a firing.
       earlier :: String -> Lambda -> Yaml.Parser ()
       earlier key lambda = do
-        stood <- go (map (_name . fst) lambda._morphed) lambda._symbolized
+        rewrote <- goRewrites (map (_name . fst) lambda._morphed) lambda._rewritten
+        stood <- go rewrote lambda._symbolized
         void (goJoins stood lambda._paired)
         where
+          goRewrites :: [Text] -> [(Meta, (Meta, [Y.Rule]))] -> Yaml.Parser [Text]
+          goRewrites reduced [] = pure reduced
+          goRewrites reduced ((meta, (source, _)) : rest)
+            | source._name `elem` reduced = goRewrites (meta._name : reduced) rest
+            | otherwise = unbound source
           go :: [Text] -> [(Meta, Expression)] -> Yaml.Parser [Text]
           go reduced [] = pure reduced
           go reduced ((meta, term) : rest) = case term of
@@ -440,9 +509,9 @@
 -- binding one of them carries and the other does not — is no join, and nothing
 -- comes back: a fork whose branches differ in structure is stuck the way a λ
 -- function no entry answers is, and bringing two such branches to one shape is
--- the program's business rather than phino's. This is why a datum is never
--- joined with anything and why a branch carrying one goes through 'symbolized'
--- first (#1246).
+-- the program's business rather than phino's, which its entry does in a
+-- 'rewrite' block (#1409). This is why a datum is never joined with anything
+-- and why a branch carrying one goes through 'symbolized' first (#1246).
 --
 -- What each fresh symbol stands for comes back beside the term, the two
 -- symbols it was minted for in the order the branches were given, since
diff --git a/src/Misc.hs b/src/Misc.hs
--- a/src/Misc.hs
+++ b/src/Misc.hs
@@ -3,9 +3,7 @@
 
 -- This module provides commonly used helper functions for other modules
 module Misc
-  ( withVoidRho
-  , recoverFormations
-  , toDouble
+  ( toDouble
   , fqnToAttrs
   , attributesFromBindings
   , attributesFromBindings'
@@ -70,41 +68,6 @@
         | attr `Set.member` seen -> Just attr
         | otherwise -> duplicated rest (Set.insert attr seen)
       Nothing -> duplicated rest seen
-
--- Add void rho binding to the end of the list of any rho binding is not present
-withVoidRho :: [Binding] -> [Binding]
-withVoidRho bds = go bds False
-  where
-    go :: [Binding] -> Bool -> [Binding]
-    go [] hasRho = [BiVoid AtRho | not hasRho]
-    go (bd : rest) hasRho =
-      case bd of
-        BiMeta _ -> bd : rest
-        BiAny _ -> bd : rest
-        BiVoid (AtMeta _) -> bd : rest
-        BiVoid (AtAny _) -> bd : rest
-        BiTau (AtMeta _) _ -> bd : rest
-        BiTau (AtAny _) _ -> bd : rest
-        BiVoid AtRho -> bd : go rest True
-        BiTau AtRho _ -> bd : go rest True
-        _ -> bd : go rest hasRho
-
--- Recursively ensure all formations have a BiVoid AtRho binding (ρ ↦ ∅).
--- Fixes in-memory ExFormation [] to ExFormation [BiVoid AtRho] after rewriting,
--- keeping the invariant that the parser enforces via withVoidRho.
-recoverFormations :: Expression -> Expression
-recoverFormations (ExFormation bindings) = ExFormation (withVoidRho (map recoverFormations' bindings))
-recoverFormations (ExDispatch expr attr) = ExDispatch (recoverFormations expr) attr
-recoverFormations (ExApplication expr arg) = ExApplication (recoverFormations expr) (recoverArgument arg)
-recoverFormations expr = expr
-
-recoverFormations' :: Binding -> Binding
-recoverFormations' (BiTau attr expr) = BiTau attr (recoverFormations expr)
-recoverFormations' binding = binding
-
-recoverArgument :: Argument -> Argument
-recoverArgument (ArTau attr expr) = ArTau attr (recoverFormations expr)
-recoverArgument (ArAlpha alpha expr) = ArAlpha alpha (recoverFormations expr)
 
 -- Transform dispatch to list of attributes
 -- >>> fqnToAttrs (ExDispatch (ExDispatch (ExDispatch ExRoot (AtLabel "org")) (AtLabel "eolang")) (AtLabel "number"))
diff --git a/src/Morph.hs b/src/Morph.hs
--- a/src/Morph.hs
+++ b/src/Morph.hs
@@ -208,6 +208,14 @@
     -- working expression — the term that came back left exactly where it stood,
     -- the way an exhausted budget stops on the last step it could afford.
     LoopingAt Expression (NonEmpty Rewritten) State
+  | -- 𝔻 was handed a term outside its domain: the terminator ⊥, which signals
+    -- an error (see #955), or a term no dataization rule matches, such as a
+    -- formation whose φ is a void nothing filled. It carries the term and the
+    -- state the frame that met it had reached. A run of 𝔻 fails on it, with or
+    -- without '_partial', but an operand of a firing that meets it parks that
+    -- firing under '_partial' the way an unanswered λ function does, since the
+    -- dead end is a property of the program rather than of phino (#1401).
+    Undataizable Expression State
   deriving anyclass (Exception)
 
 instance Show ReduceException where
@@ -218,6 +226,8 @@
   show (StuckAt func _ _) = show (Stuck func)
   show (Looping term) = printf "Reduction came back to a term it is already reducing: %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"
 
 -- Charge one step of the 𝕄/𝔻 recursion to the budget, refusing to descend once
 -- it is gone. '--max-cycles' and '--max-depth' bound only the normalization run
@@ -476,9 +486,9 @@
     -- bound in has its own ρ bound: ρ is the parameter a dispatch binds, and
     -- 'dot' binds it in every copy a reduction reaches, so a formation still
     -- holding ρ ↦ ∅ inside such a copy is a method nobody dispatched, and its
-    -- ξ.ρ can only collapse to ⊥ (#1397). The program as it was written holds
-    -- ρ ↦ ∅ all the way down, since nothing dispatched it either, and its
-    -- objects are walked the way they always were.
+    -- ξ.ρ can only collapse to ⊥ (#1397). In the program as it was written
+    -- nothing is dispatched yet, so the void ρ an object declares there keeps
+    -- it from nothing and its objects are walked the way they always were.
     parts :: Bool -> Maybe Expression -> Expression -> Expression -> State -> ReduceContext -> IO (Expression, State)
     parts attached _ _ term@(ExFormation bds) state' _
       | any abstract bds = pure (term, state')
diff --git a/src/Parser.hs b/src/Parser.hs
--- a/src/Parser.hs
+++ b/src/Parser.hs
@@ -315,7 +315,7 @@
         _ <- arrow
         bs <- formationBindings
         bds <- validatedBindings (voids ++ bs)
-        return (ExFormation (withVoidRho bds))
+        return (ExFormation bds)
     ]
   where
     rb :: Parser String
@@ -341,7 +341,7 @@
 -- could be taken for and opened by a look at a character it must start with,
 -- so the heads a program is mostly made of never try it.
 alone :: Parser Binding -> Parser Expression
-alone bd = ExFormation . withVoidRho . pure <$> bd
+alone bd = ExFormation . pure <$> bd
 
 -- `FF-AA:Δ`, `--:D` or `𝛿1:Δ`
 deltaHead :: Parser Expression
@@ -487,7 +487,7 @@
   choice
     [ do
         bs <- formationBindings >>= validatedBindings
-        return (ExFormation (withVoidRho bs))
+        return (ExFormation bs)
     , do
         _ <- choice [symbol "$", symbol "ξ"]
         return ExXi
@@ -542,7 +542,7 @@
                 return (application expr bds)
             , do
                 _ <- colon
-                ExFormation . withVoidRho . pure . (`BiTau` expr) <$> attribute
+                ExFormation . pure . (`BiTau` expr) <$> attribute
             ]
             <?> "dispatch or application"
         exTail next
diff --git a/src/Render.hs b/src/Render.hs
--- a/src/Render.hs
+++ b/src/Render.hs
@@ -133,8 +133,10 @@
   render D'' = "d"
   render F = "𝑓"
   render F' = "F"
+  render F'' = "f"
   render S = "𝜎"
   render S' = "S"
+  render S'' = "\\sigma"
 
 instance Render META where
   render META{..} = render excl <> render hd <> render rest
diff --git a/src/Rewriter.hs b/src/Rewriter.hs
--- a/src/Rewriter.hs
+++ b/src/Rewriter.hs
@@ -22,7 +22,6 @@
 import Locator (locatedExpression, withLocatedExpression)
 import Logger (logDebug)
 import Matcher (Subst)
-import Misc (recoverFormations)
 import Must (Must (..), exceedsUpperBound, inRange)
 import Printer (printExpression)
 import Replacer (ReplaceContext (ReplaceCtx), ReplaceExpressionFunc, replaceExpression, replaceExpressionFast)
@@ -214,7 +213,7 @@
                     else pure (_rewrittens, _unique, False)
                 matched -> do
                   logDebug (printf "Rule '%s' has been matched, applying..." ruleName)
-                  expr <- recoverFormations <$> tryBuildAndReplaceFast (expression, ptn, res, matched) (ReplaceCtx _maxDepth)
+                  expr <- tryBuildAndReplaceFast (expression, ptn, res, matched) (ReplaceCtx _maxDepth)
                   if expression == expr
                     then do
                       logDebug (printf "Applied '%s', no changes made" ruleName)
diff --git a/src/Sugar.hs b/src/Sugar.hs
--- a/src/Sugar.hs
+++ b/src/Sugar.hs
@@ -18,32 +18,11 @@
 withSugarType SWEET node = node
 withSugarType SALTY node = toSalty node
 
-voidRho :: PAIR
-voidRho = PA_VOID (AT_RHO RHO) ARROW EMPTY
-
-bdWithVoidRho :: BINDING -> BINDING
-bdWithVoidRho BI_EMPTY{..} = BI_PAIR voidRho (BDS_EMPTY tab) tab
-bdWithVoidRho bd@BI_PAIR{pair = PA_VOID{attr = AT_RHO _}} = bd
-bdWithVoidRho bd@BI_PAIR{pair = PA_TAU{attr = AT_RHO _}} = bd
-bdWithVoidRho bd@BI_PAIR{pair = PA_FORMATION{attr = AT_RHO _}} = bd
-bdWithVoidRho BI_PAIR{..} = BI_PAIR pair (bdsWithVoidRho bindings) tab
-  where
-    bdsWithVoidRho :: BINDINGS -> BINDINGS
-    bdsWithVoidRho BDS_EMPTY{..} = BDS_PAIR EOL tab voidRho (BDS_EMPTY tab)
-    bdsWithVoidRho bds@BDS_PAIR{pair = PA_VOID{attr = AT_RHO _}} = bds
-    bdsWithVoidRho bds@BDS_PAIR{pair = PA_TAU{attr = AT_RHO _}} = bds
-    bdsWithVoidRho bds@BDS_PAIR{pair = PA_FORMATION{attr = AT_RHO _}} = bds
-    bdsWithVoidRho BDS_PAIR{..} = BDS_PAIR eol tab pair (bdsWithVoidRho bindings)
-    bdsWithVoidRho bds@BDS_META{} = bds
-bdWithVoidRho bd@BI_META{} = bd
-
 data SugarType = SWEET | SALTY
   deriving (Eq, Show)
 
 -- Drop every ρ binding (ρ ↦ ∅, ρ ↦ e and ρ(…) ↦ e) from a rendered CST, the
--- effect of the '--hide-rho' switch. It runs after 'withSugarType', so it also
--- removes the ρ ↦ ∅ that 'bdWithVoidRho' re-inserts into every formation on the
--- SALTY path. Both formation bindings and application arguments are stripped;
+-- effect of the '--hide-rho' switch. Both formation bindings and application arguments are stripped;
 -- dispatches such as ξ.ρ are left untouched. A formation left empty by the
 -- strip collapses to the compact '⟦⟧' layout, and an application left with no
 -- argument collapses to its bare callee (no leftover 'e()'). In the SWEET
@@ -120,7 +99,14 @@
     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 (unsugared (goExpr expr))
+    goPair PA_FORMATION{..} = case filter (not . rho) voids of
+      [] -> PA_TAU attr arrow (goExpr expr)
+      voids' -> PA_FORMATION attr voids' arrow (unsugared (goExpr expr))
+      where
+        -- A void ρ the formation declares is listed among its inline voids
+        rho :: ATTRIBUTE -> Bool
+        rho AT_RHO{} = True
+        rho _ = False
     goPair pair = pair
     -- Inline voids open a formation, which the sugar may not stand for
     unsugared :: EXPRESSION -> EXPRESSION
@@ -149,7 +135,6 @@
 --  | a -> 42                    | Q.number(Q.bytes([[ D> 40-45-00-00-00-00-00-00 ]])) |
 --  | a -> Q.nan                 | Q.number(Q.bytes([[ D> 7F-F8-00-00-00-00-00-00 ]])) |
 --  | a -> "Hey"                 | Q.number(Q.bytes([[ D> 48-65-79 ]]))                |
---  | [[ B ]]                    | [[ B, ^ -> ? ]], if rho is absent in 'B'            |
 --  | e:a                        | [[ a -> e ]], and so for D, L and ? (see #1385)     |
 --  | a1(a2, a3, ...) -> [[ B ]] | a1 -> [[ a2 -> ?, a3 -> ?, ..., B ]]                |
 --  | e(e0, e1, ...)             | e(~0 -> e0, ~1 -> e1, ...)                          |
@@ -161,8 +146,8 @@
 instance ToSalty EXPRESSION where
   toSalty EX_ATTR{..} = EX_DISPATCH (EX_XI XI) NO_SPACE attr
   toSalty EX_DISPATCH{..} = EX_DISPATCH (toSalty expr) space attr
-  toSalty EX_FORMATION{lsb, binding = bd@BI_EMPTY{}, rsb} = EX_FORMATION lsb NO_EOL TAB' (toSalty (bdWithVoidRho bd)) NO_EOL TAB' rsb
-  toSalty EX_FORMATION{..} = EX_FORMATION lsb eol tab (toSalty (bdWithVoidRho binding)) eol' tab' rsb
+  toSalty formation@EX_FORMATION{binding = BI_EMPTY{}} = formation
+  toSalty EX_FORMATION{..} = EX_FORMATION lsb eol tab (toSalty binding) eol' tab' rsb
   toSalty EX_APPLICATION{argument = AA_TAU tau, ..} = EX_APPLICATION (toSalty expr) space EOL (TAB indent) (AA_TAU (toSalty tau)) EOL (TAB (indent - 1)) indent
   toSalty EX_APPLICATION{argument = AA_TAUS taus, ..} =
     foldl
diff --git a/src/XMIR.hs b/src/XMIR.hs
--- a/src/XMIR.hs
+++ b/src/XMIR.hs
@@ -170,7 +170,7 @@
 formationBinding (BiDelta bytes) _ = pure (Just (NodeContent (T.pack (printBytes bytes))))
 formationBinding (BiLambda (Function name)) XmirContext{..} =
   pure (Just (object (maybe [] (\atom -> [("atom", atom)]) (M.lookup name _atoms) ++ [("name", show AtLambda)]) [NodeContent name]))
-formationBinding (BiVoid AtRho) _ = pure Nothing
+formationBinding (BiVoid AtRho) _ = pure (Just (object [("name", show AtRho), ("base", "∅")] []))
 formationBinding (BiVoid AtPhi) _ = pure (Just (object [("name", show AtPhi), ("base", "∅")] []))
 formationBinding (BiVoid (AtLabel label)) _ = pure (Just (object [("name", T.unpack label), ("base", "∅")] []))
 formationBinding binding _ = throwIO (UnsupportedBinding binding)
@@ -184,7 +184,7 @@
   pure (object [("name", name), ("base", base)] children)
 
 -- Render a formation's bindings as child nodes, honoring '--hide-rho' by
--- dropping every bound ρ before it reaches the nodes (#1076)
+-- dropping every ρ, void or bound, before it reaches the nodes (#1076)
 nestedBindings :: [Binding] -> XmirContext -> IO [Node]
 nestedBindings bds ctx@XmirContext{..} = catMaybes <$> mapM (`formationBinding` ctx) bds'
   where
@@ -192,15 +192,17 @@
     bds' = if _hideRho then filter (not . isRho) bds else bds
     isRho :: Binding -> Bool
     isRho (BiTau AtRho _) = True
+    isRho (BiVoid AtRho) = True
     isRho _ = False
 
 expressionToXMIR :: Expression -> XmirContext -> IO Document
-expressionToXMIR expr@(ExFormation [BiTau (AtLabel _) arg, BiVoid AtRho]) ctx = case arg of
-  ExFormation _ -> programToXMIR expr ctx
-  ExApplication _ _ -> programToXMIR expr ctx
-  ExDispatch _ _ -> programToXMIR expr ctx
-  ExRoot -> programToXMIR expr ctx
-  _ -> throwIO (UnsupportedTopExpression expr)
+expressionToXMIR expr@(ExFormation bds) ctx
+  | [BiTau (AtLabel _) arg] <- withoutVoidRho bds = case arg of
+      ExFormation _ -> programToXMIR expr ctx
+      ExApplication _ _ -> programToXMIR expr ctx
+      ExDispatch _ _ -> programToXMIR expr ctx
+      ExRoot -> programToXMIR expr ctx
+      _ -> throwIO (UnsupportedTopExpression expr)
 -- The top of a '--partial' residual and the result of 'merge' are arbitrary
 -- formations: several τ/λ bindings, voids and a bound ρ. The schema allows a
 -- single <o> under <object>, so the formation goes beneath one attribute-free
@@ -218,6 +220,12 @@
     isElement _ = False
 expressionToXMIR expr _ = throwIO (UnsupportedTopExpression expr)
 
+-- The bindings of a formation on the package spine, without the void ρ it may
+-- declare: the spine holds no object a dispatch could bind ρ in, so a ρ ↦ ∅
+-- there has nowhere to go in XMIR and is not what tells a program apart
+withoutVoidRho :: [Binding] -> [Binding]
+withoutVoidRho = filter (/= BiVoid AtRho)
+
 -- A program document: the package spine is peeled off the top level into
 -- <metas> and the single binding left becomes the root <o> element
 programToXMIR :: Expression -> XmirContext -> IO Document
@@ -230,17 +238,27 @@
     -- - X: list of package parts
     -- - Y: root object expression
     getPackage :: Expression -> IO ([String], Expression)
-    getPackage (ExFormation [BiTau (AtLabel label) (ExFormation [bd, BiLambda (Function "Package"), BiVoid AtRho]), BiVoid AtRho]) = do
-      (pckg, expr') <- getPackage (ExFormation [bd, BiLambda (Function "Package"), BiVoid AtRho])
-      pure (T.unpack label : pckg, expr')
-    getPackage (ExFormation [BiTau (AtLabel label) (ExFormation [bd, BiLambda (Function "Package"), BiVoid AtRho]), BiLambda (Function "Package"), BiVoid AtRho]) = do
-      (pckg, expr') <- getPackage (ExFormation [bd, BiLambda (Function "Package"), BiVoid AtRho])
+    getPackage ex@(ExFormation bds) = case withoutVoidRho bds of
+      [BiTau (AtLabel label) inner@(ExFormation inner')] | packaged inner' -> nested label inner
+      [BiTau (AtLabel label) inner@(ExFormation inner'), BiLambda (Function "Package")] | packaged inner' -> nested label inner
+      [BiTau at body, BiLambda (Function "Package")] -> pure ([], ExFormation [BiTau at body])
+      [bd] -> pure ([], ExFormation [bd])
+      _ -> unpackaged ex
+    getPackage ex = unpackaged ex
+    nested :: T.Text -> Expression -> IO ([String], Expression)
+    nested label inner = do
+      (pckg, expr') <- getPackage inner
       pure (T.unpack label : pckg, expr')
-    getPackage (ExFormation [BiTau at ex, BiLambda (Function "Package"), BiVoid AtRho]) = pure ([], ExFormation [BiTau at ex, BiVoid AtRho])
-    getPackage (ExFormation [bd, BiVoid AtRho]) = pure ([], ExFormation [bd, BiVoid AtRho])
-    getPackage ex = throwIO (userError (printf "Can't extract package from given expression:\n %s" (printExpression ex)))
+    -- A formation of one binding and the λ marking a package, whatever void ρ
+    -- it may declare besides
+    packaged :: [Binding] -> Bool
+    packaged bds = case withoutVoidRho bds of
+      [_, BiLambda (Function "Package")] -> True
+      _ -> False
+    unpackaged :: Expression -> IO ([String], Expression)
+    unpackaged ex = throwIO (userError (printf "Can't extract package from given expression:\n %s" (printExpression ex)))
     rootNodes :: Expression -> XmirContext -> IO [Node]
-    rootNodes (ExFormation [bd, BiVoid AtRho]) c = nestedBindings [bd] c
+    rootNodes (ExFormation [bd]) c = nestedBindings [bd] c
     rootNodes ex _ = throwIO (UnsupportedExpression ex)
 
 -- Assemble the <object> document: timing attributes, the listing, <metas>
@@ -471,11 +489,11 @@
                   if null pckg
                     then do
                       bd <- xmirToFormationBinding o []
-                      pure (ExFormation (withVoidRho [bd]))
+                      pure (ExFormation [bd])
                     else do
                       obj <- xmirToFormationBinding o []
-                      let bd = foldr (\part acc -> BiTau (AtLabel (T.pack part)) (ExFormation [acc, BiLambda (Function "Package"), BiVoid AtRho])) obj pckg
-                      pure (ExFormation [bd, BiVoid AtRho])
+                      let bd = foldr (\part acc -> BiTau (AtLabel (T.pack part)) (ExFormation [acc, BiLambda (Function "Package")])) obj pckg
+                      pure (ExFormation [bd])
           | otherwise -> throwIO (InvalidXMIRFormat "Expected single <object> element" doc)
         _ -> throwIO (InvalidXMIRFormat "NodeElement is expected as root element" doc)
 
@@ -558,7 +576,7 @@
 xmirToFormation cur fqn = do
   nested <- mapM (`xmirToFormationBinding` fqn) (cur C.$/ C.element (toName "o"))
   bds <- if hasText cur then (: nested) <$> delta else pure nested
-  ExFormation . withVoidRho <$> uniqueBindings' bds
+  ExFormation <$> uniqueBindings' bds
   where
     delta :: IO Binding
     delta = BiDelta . bytesToBts . T.unpack . T.strip . T.pack <$> getText cur
@@ -618,11 +636,11 @@
             | not (hasAttr "base" arg) && not (hasText arg) = do
                 bds <- mapM (`xmirToFormationBinding` fqn) (arg C.$/ C.element (toName "o"))
                 key <- asToKey arg idx
-                pure (ExApplication expr (mkArg key (ExFormation (withVoidRho bds))))
+                pure (ExApplication expr (mkArg key (ExFormation bds)))
             | not (hasAttr "base" arg) && hasText arg = do
                 key <- asToKey arg idx
                 bytes <- getText arg
-                pure (ExApplication expr (mkArg key (ExFormation [BiDelta (bytesToBts bytes), BiVoid AtRho])))
+                pure (ExApplication expr (mkArg key (ExFormation [BiDelta (bytesToBts bytes)])))
             | otherwise = do
                 key <- asToKey arg idx
                 arg' <- xmirToExpression arg fqn
diff --git a/test/ASTSpec.hs b/test/ASTSpec.hs
--- a/test/ASTSpec.hs
+++ b/test/ASTSpec.hs
@@ -296,7 +296,7 @@
       dataBytes (BtOne "48")
         `shouldBe` ExApplication
           (ExDispatch ExRoot (AtLabel "bytes"))
-          (ArTau AtPhi (ExFormation [BiDelta (BtOne "48"), BiVoid AtRho]))
+          (ArTau AtPhi (ExFormation [BiDelta (BtOne "48")]))
 
   describe "DataObject/DataString/DataNumber pattern" $ do
     it "constructs the phi-named, unwrapped form" $
@@ -331,7 +331,7 @@
                 (AtLabel "as-bytes")
                 ( ExApplication
                     (ExDispatch ExRoot (AtLabel "bytes"))
-                    (ArTau (AtLabel "data") (ExPhiAgain Nothing 1 (ExFormation [BiDelta (BtOne "05"), BiVoid AtRho])))
+                    (ArTau (AtLabel "data") (ExPhiAgain Nothing 1 (ExFormation [BiDelta (BtOne "05")])))
                 )
             )
         , Just (BtOne "05")
@@ -344,7 +344,7 @@
                 (Alpha 0)
                 ( ExApplication
                     (ExDispatch ExRoot (AtLabel "bytes"))
-                    (ArAlpha (Alpha 0) (ExFormation [BiDelta (BtOne "05"), BiVoid AtRho]))
+                    (ArAlpha (Alpha 0) (ExFormation [BiDelta (BtOne "05")]))
                 )
             )
         , Just (BtOne "05")
@@ -376,7 +376,7 @@
                 (AtLabel "as-bytes")
                 ( ExApplication
                     (ExDispatch ExRoot (AtLabel "other"))
-                    (ArTau AtPhi (ExFormation [BiDelta (BtOne "48"), BiVoid AtRho]))
+                    (ArTau AtPhi (ExFormation [BiDelta (BtOne "48")]))
                 )
             )
         )
@@ -388,7 +388,7 @@
                 (AtLabel "as-bytes")
                 ( ExApplication
                     (ExDispatch ExRoot (AtLabel "bytes"))
-                    (ArTau (AtLabel "data") (ExFormation [BiDelta (BtOne "48")]))
+                    (ArTau (AtLabel "data") (ExFormation [BiDelta (BtOne "48"), BiVoid AtRho]))
                 )
             )
         )
diff --git a/test/CLISpec.hs b/test/CLISpec.hs
--- a/test/CLISpec.hs
+++ b/test/CLISpec.hs
@@ -220,7 +220,7 @@
           ( "with --output=xmir on a non-top-level expression"
           , "⟦ x ↦ 1, ρ ↦ 2 ⟧"
           , ["rewrite", "--output=xmir"]
-          , ["[ERROR]:", "its top level must be a single binding followed by ρ ↦ ∅"]
+          , ["[ERROR]:", "its top level must be a single binding"]
           )
         ]
         (\(desc, input, args, expected) -> it desc (withStdin input (testCLIFailed args expected)))
@@ -405,7 +405,7 @@
 
     it "saves dataize steps to dir with --steps-dir" $
       withTempDirectory "phino-steps-dataize" $ \dir ->
-        withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]" $ do
+        withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]" $ do
           testCLISucceeded
             ["dataize", symbolic, "--steps-dir=" ++ dir, "--sweet"]
             ["40-45"]
@@ -434,11 +434,11 @@
     it "desugares without any rules flag from file" $
       testCLISucceeded
         ["rewrite", resource "desugar.phi"]
-        ["⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧"]
+        ["⟦ foo ↦ ξ.x ⟧"]
 
     it "desugares with without any rules flag from stdin" $
       withStdin "[[foo ↦ x]]" $
-        testCLISucceeded ["rewrite"] ["⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧"]
+        testCLISucceeded ["rewrite"] ["⟦ foo ↦ ξ.x ⟧"]
 
     it "keeps the bytes of a string intact while desugaring it" $
       withStdin "⟦ φ ↦ Φ.string(as-bytes ↦ Φ.bytes(data ↦ ⟦ Δ ⤍ 65-0A-65, ρ ↦ ∅ ⟧)), ρ ↦ ∅ ⟧" $
@@ -466,12 +466,8 @@
         [ unlines
             [ "⟦"
             , "  x ↦ ⟦"
-            , "    ρ ↦ ⟦"
-            , "      y ↦ ⟦ ρ ↦ ∅ ⟧,"
-            , "      ρ ↦ ∅"
-            , "    ⟧"
-            , "  ⟧,"
-            , "  ρ ↦ ∅"
+            , "    ρ ↦ ⟦ y ↦ ⟦ ρ ↦ ∅ ⟧ ⟧"
+            , "  ⟧"
             , "⟧"
             ]
         ]
@@ -480,22 +476,13 @@
       withStdin "⟦ k ↦ ⟦ m ↦ ⟦ Δ ⤍ 01- ⟧ ⟧.m, j ↦ ⟦ λ ⤍ Marker ⟧ ⟧" $
         testCLISucceeded
           ["rewrite", "--normalize", rule "marker.yaml", "--sweet"]
-          ["⟦ k ↦ ⟦ Δ ⤍ 01-, ρ ↦ 01-:Δ:m ⟧, j ↦ FF-:Δ ⟧"]
+          ["⟦ k ↦ 01-:Δ, j ↦ FF-:Δ ⟧"]
 
     it "normalizes from stdin" $
       withStdin "⟦ a ↦ ⟦ b ↦ ∅ ⟧ (b ↦ [[ ]]) ⟧" $
         testCLISucceeded
           ["rewrite", "--normalize", "--margin=20"]
-          [ unlines
-              [ "⟦"
-              , "  a ↦ ⟦"
-              , "    b ↦ ⟦ ρ ↦ ∅ ⟧,"
-              , "    ρ ↦ ∅"
-              , "  ⟧,"
-              , "  ρ ↦ ∅"
-              , "⟧"
-              ]
-          ]
+          ["⟦ a ↦ ⟦ b ↦ ⟦⟧ ⟧ ⟧"]
 
     it "rewrites with --sweet flag" $
       withStdin "[[ x -> 5]]" $
@@ -662,10 +649,10 @@
               , "=== Step #1"
               , "\"foo\":x"
               , ""
-              , "=== Step #2, Rule 'first', 31t -> 30t"
+              , "=== Step #2, Rule 'first', 23t -> 26t"
               , "Φ.x( y ↦ \"foo\" )"
               , ""
-              , "=== Step #3, Rule 'second', 30t -> 31t"
+              , "=== Step #3, Rule 'second', 26t -> 23t"
               , "\"foo\":x"
               ]
           ]
@@ -694,9 +681,9 @@
               [ "\\begin{phiquation}"
               , "% === Step #1"
               , "[[ |x| -> \"foo\" ]] \\leadsto_{\\nameref{r:first}}"
-              , "% === Step #2, Rule 'first', 31t -> 30t"
+              , "% === Step #2, Rule 'first', 23t -> 26t"
               , "  \\leadsto Q . |x| ( |y| -> \"foo\" ) \\leadsto_{\\nameref{r:second}}"
-              , "% === Step #3, Rule 'second', 30t -> 31t"
+              , "% === Step #3, Rule 'second', 26t -> 23t"
               , "  \\leadsto [[ |x| -> \"foo\" ]]{.}"
               , "\\end{phiquation}"
               ]
@@ -730,12 +717,12 @@
           ["rewrite", "--normalize", "--sweet", "--sequence", "--output=latex", "--flat", "--compress", "--meet-prefix=foo"]
           [ unlines
               [ "\\begin{phiquation}"
-              , "[[ |x| -> ?, |y| -> |x| ]] ( |x| -> \\phinoMeet{foo:1}{ [[ D> |42-| ]] } ) . |y| \\leadsto_{\\nameref{r:copy}}"
-              , "  \\leadsto \\phinoMeet{foo:2}{ [[ |x| -> \\phinoAgain{foo:1}, |y| -> |x| ]] } . |y| \\leadsto_{\\nameref{r:dot}}"
-              , "  \\leadsto \\phinoMeet{foo:3}{ [[ |x| -> \\phinoAgain{foo:1} ]] } . |x| ( \\phiTerminal{\\rho} -> \\phinoAgain{foo:2} ) \\leadsto_{\\nameref{r:dot}}"
-              , "  \\leadsto \\phinoAgain{foo:1} ( \\phiTerminal{\\rho} -> \\phinoAgain{foo:3}, \\phiTerminal{\\rho} -> \\phinoAgain{foo:2} ) \\leadsto_{\\nameref{r:copy}}"
-              , "  \\leadsto [[ D> |42-|, \\phiTerminal{\\rho} -> \\phinoAgain{foo:3} ]] ( \\phiTerminal{\\rho} -> \\phinoAgain{foo:2} ) \\leadsto_{\\nameref{r:stay}}"
-              , "  \\leadsto [[ D> |42-|, \\phiTerminal{\\rho} -> \\phinoAgain{foo:3} ]]{.}"
+              , "[[ |x| -> ?, |y| -> |x| ]] ( |x| -> [[ D> |42-| ]] ) . |y| \\leadsto_{\\nameref{r:copy}}"
+              , "  \\leadsto \\phinoMeet{foo:1}{ [[ |x| -> [[ D> |42-| ]], |y| -> |x| ]] } . |y| \\leadsto_{\\nameref{r:dot}}"
+              , "  \\leadsto [[ |x| -> [[ D> |42-| ]] ]] . |x| ( \\phiTerminal{\\rho} -> \\phinoAgain{foo:1} ) \\leadsto_{\\nameref{r:dot}}"
+              , "  \\leadsto [[ D> |42-| ]] ( \\phiTerminal{\\rho} -> [[ |x| -> [[ D> |42-| ]] ]], \\phiTerminal{\\rho} -> \\phinoAgain{foo:1} ) \\leadsto_{\\nameref{r:skip}}"
+              , "  \\leadsto [[ D> |42-| ]] ( \\phiTerminal{\\rho} -> \\phinoAgain{foo:1} ) \\leadsto_{\\nameref{r:skip}}"
+              , "  \\leadsto [[ D> |42-| ]]{.}"
               , "\\end{phiquation}"
               ]
           ]
@@ -746,12 +733,12 @@
           ["rewrite", "--normalize", "--sweet", "--sequence", "--output=latex", "--flat", "--compress"]
           [ unlines
               [ "\\begin{phiquation}"
-              , "[[ |x| -> ?, |y| -> |x| ]] ( |x| -> \\phinoMeet{1}{ [[ D> |42-| ]] } ) . |y| \\leadsto_{\\nameref{r:copy}}"
-              , "  \\leadsto \\phinoMeet{2}{ [[ |x| -> \\phinoAgain{1}, |y| -> |x| ]] } . |y| \\leadsto_{\\nameref{r:dot}}"
-              , "  \\leadsto \\phinoMeet{3}{ [[ |x| -> \\phinoAgain{1} ]] } . |x| ( \\phiTerminal{\\rho} -> \\phinoAgain{2} ) \\leadsto_{\\nameref{r:dot}}"
-              , "  \\leadsto \\phinoAgain{1} ( \\phiTerminal{\\rho} -> \\phinoAgain{3}, \\phiTerminal{\\rho} -> \\phinoAgain{2} ) \\leadsto_{\\nameref{r:copy}}"
-              , "  \\leadsto [[ D> |42-|, \\phiTerminal{\\rho} -> \\phinoAgain{3} ]] ( \\phiTerminal{\\rho} -> \\phinoAgain{2} ) \\leadsto_{\\nameref{r:stay}}"
-              , "  \\leadsto [[ D> |42-|, \\phiTerminal{\\rho} -> \\phinoAgain{3} ]]{.}"
+              , "[[ |x| -> ?, |y| -> |x| ]] ( |x| -> [[ D> |42-| ]] ) . |y| \\leadsto_{\\nameref{r:copy}}"
+              , "  \\leadsto \\phinoMeet{1}{ [[ |x| -> [[ D> |42-| ]], |y| -> |x| ]] } . |y| \\leadsto_{\\nameref{r:dot}}"
+              , "  \\leadsto [[ |x| -> [[ D> |42-| ]] ]] . |x| ( \\phiTerminal{\\rho} -> \\phinoAgain{1} ) \\leadsto_{\\nameref{r:dot}}"
+              , "  \\leadsto [[ D> |42-| ]] ( \\phiTerminal{\\rho} -> [[ |x| -> [[ D> |42-| ]] ]], \\phiTerminal{\\rho} -> \\phinoAgain{1} ) \\leadsto_{\\nameref{r:skip}}"
+              , "  \\leadsto [[ D> |42-| ]] ( \\phiTerminal{\\rho} -> \\phinoAgain{1} ) \\leadsto_{\\nameref{r:skip}}"
+              , "  \\leadsto [[ D> |42-| ]]{.}"
               , "\\end{phiquation}"
               ]
           ]
@@ -1033,7 +1020,7 @@
           ["rewrite", "--log-level=debug", "--log-lines=1", "--normalize"]
           [ intercalate
               "\n"
-              [ "[DEBUG]: Applied 'copy' (44 nodes -> 39 nodes)"
+              [ "[DEBUG]: Applied 'copy' (32 nodes -> 27 nodes)"
               , "---| log is limited by --log-lines=1 option |---"
               ]
           ]
@@ -1046,7 +1033,7 @@
         testCLISucceeded
           ["rewrite", rule "raising-condition.yaml", "--log-level=debug", "--flat"]
           [ "raised and was treated as not met: user error (Only data objects and bytes are supported"
-          , "⟦ x ↦ ⟦ y ↦ ∅, ρ ↦ ∅ ⟧, ρ ↦ ∅ ⟧"
+          , "⟦ x ↦ ⟦ y ↦ ∅ ⟧ ⟧"
           ]
 
     it "canonizes expression" $
@@ -1065,7 +1052,7 @@
       withStdin "[[ x -> ?, y -> $.x ]](x -> [[ D> 42- ]]).y" $
         testCLISucceeded
           ["rewrite", "--sweet", "--flat", "--normalize", "--breakpoint=stop", "--log-level=debug"]
-          [ "Applied 'copy' (30 nodes -> 25 nodes)"
+          [ "Applied 'copy' (22 nodes -> 17 nodes)"
           , "Rule 'stop' is a breakpoint, dropping down all the previous rewritings..."
           , "⟦ x ↦ ∅, y ↦ x ⟧( x ↦ 42-:Δ ).y"
           ]
@@ -1155,8 +1142,8 @@
               , "[[ @ -> [[ |x| -> [[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) ]] . |x| ]] \\leadsto_{\\nameref{r:contextualize}}"
               , "  \\leadsto [[ |x| -> [[ D> |01-|, |y| -> ? ]] ( |y| -> [[]] ) ]] . |x| \\leadsto_{\\nameref{r:copy}}"
               , "  \\leadsto [[ |x| -> [[ D> |01-|, |y| -> [[]] ]] ]] . |x| \\leadsto_{\\nameref{r:dot}}"
-              , "  \\leadsto [[ D> |01-|, |y| -> [[]] ]] ( \\phiTerminal{\\rho} -> [[ |x| -> [[ D> |01-|, |y| -> [[]] ]] ]] ) \\leadsto_{\\nameref{r:copy}}"
-              , "  \\leadsto [[ D> |01-|, |y| -> [[]], \\phiTerminal{\\rho} -> [[ |x| -> [[ D> |01-|, |y| -> [[]] ]] ]] ]] \\leadsto_{\\nameref{r:delta}}"
+              , "  \\leadsto [[ D> |01-|, |y| -> [[]] ]] ( \\phiTerminal{\\rho} -> [[ |x| -> [[ D> |01-|, |y| -> [[]] ]] ]] ) \\leadsto_{\\nameref{r:skip}}"
+              , "  \\leadsto [[ D> |01-|, |y| -> [[]] ]] \\leadsto_{\\nameref{r:delta}}"
               , "  \\leadsto |01-|{.}"
               , "\\end{phiquation}"
               , "01-"
@@ -1182,13 +1169,13 @@
           ["01-:Δ\n01-"]
 
     it "focuses a compressed sequence whose meet replaces a step root" $
-      withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ x -> ?, L> L_number_plus ]] ]] ]]" $
+      withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]] ]] ]]" $
         testCLISucceeded
           ["dataize", symbolic, "--output=latex", "--sweet", "--nonumber", "--compress", "--canonize", "--meet-prefix=dataization", "--sequence", "--flat", "--quiet", "--hide=Q.bytes", "--hide=Q.number", "--locator=Q.@", "--focus=Q.@", "--meet-length=5", "--meet-popularity=1"]
           ["\\phinoMeet{dataization:1}{ [[ @ -> |c| . |plus| ( 32 ), |c| -> 25 ]] } \\leadsto_{\\nameref{r:contextualize}}"]
 
     it "compresses a canonized whole-expression sequence into a meet" $
-      withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ x -> ?, L> L_number_plus ]] ]] ]]" $
+      withStdin "[[ @ -> [[ @ -> $.c.plus( 32.0 ), c -> 25.0 ]], bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]] ]] ]]" $
         testCLISucceeded
           ["dataize", symbolic, "--output=latex", "--sweet", "--nonumber", "--compress", "--canonize", "--meet-prefix=dataization", "--sequence", "--flat", "--quiet", "--meet-length=5", "--meet-popularity=1"]
           ["\\phinoMeet{dataization:1}"]
@@ -1206,10 +1193,10 @@
     -- per answer it gave. Nothing but the symbols ties them together, so the
     -- lines a firing writes are what a reader of the file walks back (#1226).
     describe "--protocol" $ do
-      let sum' = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
-          chained = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]"
-          nested = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6.plus(7)) ]]"
-          mixed = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]], times(x) -> [[ L> L_number_times ]] ]], @ -> 5.plus(6).times(7) ]]"
+      let sum' = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
+          chained = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]"
+          nested = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6.plus(7)) ]]"
+          mixed = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]], times(^, x) -> [[ L> L_number_times ]] ]], @ -> 5.plus(6).times(7) ]]"
       it "opens the protocol with the run it is the protocol of" $
         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do
           hClose stream
@@ -1333,7 +1320,7 @@
       it "keeps the lines of a run that fails" $
         withTempFile "protocolXXXXXX.txt" $ \(path, stream) -> do
           hClose stream
-          withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]], nope -> [[ L> L_number_nope ]] ]], @ -> 5.plus(6).nope ]]" $
+          withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]], nope -> [[ ^ -> ?, L> L_number_nope ]] ]], @ -> 5.plus(6).nope ]]" $
             testCLIFailed
               ["dataize", symbolic, "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"]
               ["No entry of --symbolic answers the λ function 'L_number_nope'"]
@@ -1490,6 +1477,32 @@
                          , "</morph>"
                          ]
 
+        -- A 'join' line one side of which is ⊥ joins nothing: the program
+        -- raises on that side of the condition, so the markup names the
+        -- symbol the condition was dataized to and the side that raises, and
+        -- the meta is bound to the other side as it stands (#1405)
+        it "writes on which side of the condition a fork raises" $
+          withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do
+            hClose stream
+            withLambdasOf (T.pack "- λ: L_fork\n  dataize:\n    𝛿1: $.c\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n") $ \forks ->
+              withStdin "⟦ y ↦ ⟦ c ↦ ⟦ λ ⤍ 𝜎1 ⟧, a ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎2 ⟧ ⟧, b ↦ ⊥, λ ⤍ L_fork ⟧.φ ⟧" $
+                testCLISucceeded ["morph", "--symbolic=" ++ forks, "--locator=Q.y", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []
+            records <- readUtf8 path
+            lines records
+              `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
+                         , "<morph locator=\"Φ.y\">"
+                         , "  <evaluate λ=\"L_fork\" id=\"1\" judgment=\"morph\" locator=\"Φ.y\">"
+                         , "    <dataize meta=\"𝛿1.1\">𝜎1:λ</dataize>"
+                         , "    <bind meta=\"𝑛1.1\">𝜎2:λ:φ</bind>"
+                         , "    <bind meta=\"𝑛2.1\">⊥</bind>"
+                         , "    <raise-if symbol=\"𝜎1\" branch=\"right\"/>"
+                         , "    <bind meta=\"𝑛3.1\">𝜎2:λ:φ</bind>"
+                         , "    <built meta=\"𝑛.1.1\">𝜎2:λ:φ</built>"
+                         , "    <answer meta=\"𝑛.1.2\">𝜎2:λ:φ</answer>"
+                         , "  </evaluate>"
+                         , "</morph>"
+                         ]
+
         -- Which symbols a firing minted is a fact about the firing and not a
         -- property of one term of it, so each of them stands in a record of
         -- its own, the way what is known about a symbol does: an answer
@@ -1572,7 +1585,7 @@
         it "records a λ function no entry answers as a childless element" $
           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do
             hClose stream
-            withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(x) -> [[ L> L_number_times ]], nope -> [[ L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]" $
+            withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(^, x) -> [[ L> L_number_times ]], nope -> [[ ^ -> ?, L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]" $
               testCLISucceeded ["dataize", symbolic, "--partial", "--protocol=" ++ path, "--quiet", "--sweet", "--hide-rho"] []
             records <- readUtf8 path
             lines records
@@ -1602,7 +1615,7 @@
             lines records
               `shouldBe` [ "<?xml version=\"1.0\" encoding=\"UTF-8\"?>"
                          , "<morph locator=\"Φ.x\">"
-                         , "  <stuck λ=\"L_number_nope\" judgment=\"morph\">⟦ λ ⤍ L_number_nope, ρ ↦ ∅ ⟧</stuck>"
+                         , "  <stuck λ=\"L_number_nope\" judgment=\"morph\">⟦ λ ⤍ L_number_nope ⟧</stuck>"
                          , "</morph>"
                          ]
 
@@ -1613,7 +1626,7 @@
         it "closes the document even when the run fails" $
           withTempFile "protocolXXXXXX.xml" $ \(path, stream) -> do
             hClose stream
-            withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(x) -> [[ L> L_number_times ]], nope -> [[ L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]" $
+            withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(^, x) -> [[ L> L_number_times ]], nope -> [[ ^ -> ?, L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]" $
               testCLIFailed ["dataize", symbolic, "--protocol=" ++ path] ["No entry of --symbolic answers"]
             records <- readUtf8 path
             lines records
@@ -1623,10 +1636,10 @@
                          , "    <bind meta=\"𝛿1.1\">40-00-00-00-00-00-00-00</bind>"
                          , "    <bind meta=\"𝛿2.1\">40-08-00-00-00-00-00-00</bind>"
                          , "    <minted>𝜎1</minted>"
-                         , "    <built meta=\"𝑛.1.1\">Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1, ρ ↦ ∅ ⟧ )</built>"
-                         , "    <answer meta=\"𝑛.1.2\">⟦ φ ↦ ⟦ λ ⤍ 𝜎1, ρ ↦ ∅ ⟧, times ↦ ⟦ x ↦ ∅, λ ⤍ L_number_times, ρ ↦ ∅ ⟧, nope ↦ ⟦ λ ⤍ L_number_nope, ρ ↦ ∅ ⟧, ρ ↦ Φ ⟧</answer>"
+                         , "    <built meta=\"𝑛.1.1\">Φ.number( φ ↦ ⟦ λ ⤍ 𝜎1 ⟧ )</built>"
+                         , "    <answer meta=\"𝑛.1.2\">⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, times ↦ ⟦ ρ ↦ ∅, x ↦ ∅, λ ⤍ L_number_times ⟧, nope ↦ ⟦ ρ ↦ ∅, λ ⤍ L_number_nope ⟧ ⟧</answer>"
                          , "  </evaluate>"
-                         , "  <stuck λ=\"L_number_nope\" judgment=\"dataize\">⟦ λ ⤍ L_number_nope, ρ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1, ρ ↦ ∅ ⟧, times ↦ ⟦ x ↦ ∅, λ ⤍ L_number_times, ρ ↦ ∅ ⟧, nope ↦ ⟦ λ ⤍ L_number_nope, ρ ↦ ∅ ⟧, ρ ↦ Φ ⟧ ⟧</stuck>"
+                         , "  <stuck λ=\"L_number_nope\" judgment=\"dataize\">⟦ ρ ↦ ⟦ φ ↦ ⟦ λ ⤍ 𝜎1 ⟧, times ↦ ⟦ ρ ↦ ∅, x ↦ ∅, λ ⤍ L_number_times ⟧, nope ↦ ⟦ ρ ↦ ∅, λ ⤍ L_number_nope ⟧ ⟧, λ ⤍ L_number_nope ⟧</stuck>"
                          , "</dataize>"
                          ]
 
@@ -1671,8 +1684,8 @@
     -- an operation the caller left out of its file on purpose. The run used
     -- to die on it, discarding what it had already evaluated (#1060)
     describe "--partial" $ do
-      let stuck = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(x) -> [[ L> L_number_times ]], nope -> [[ L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]"
-          dispatched = "[[ foo -> [[ bar -> [[ L> L_number_nope ]] ]], @ -> Q.foo.bar ]]"
+      let stuck = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ times(^, x) -> [[ L> L_number_times ]], nope -> [[ ^ -> ?, L> L_number_nope ]] ]], @ -> 2.times(3).nope ]]"
+          dispatched = "[[ foo -> [[ bar -> [[ ^ -> ?, L> L_number_nope ]] ]], @ -> Q.foo.bar ]]"
       it "fails on a λ function that cannot fire without the flag" $
         withStdin stuck $
           testCLIFailed
@@ -1710,7 +1723,7 @@
                        ]
 
       it "still prints bytes when nothing gets stuck" $
-        withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]" $
+        withStdin "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]" $
           testCLISucceeded ["dataize", symbolic, "--partial"] ["40-45-00-00-00-00-00-00"]
 
       -- The residual is an arbitrary formation, and a multi-binding <object>
@@ -1748,7 +1761,7 @@
     -- Which λ functions exist is not phino's business: the file given with
     -- '--symbolic' decides, and phino carries none of its own
     describe "--symbolic" $ do
-      let sum' = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
+      let sum' = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
       -- Nothing is worked out: the entry answers a number standing for the sum
       -- and the run brings that symbol down to the datum every symbol answers
       it "fires the λ function an entry of the file answers" $
@@ -1776,7 +1789,7 @@
     -- aims the run at it, which is what the 'dataize' block of a λ function
     -- does for every operand it names
     describe "--inside" $ do
-      let universe = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> [[ D> 01- ]] ]]"
+      let universe = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> [[ D> 01- ]] ]]"
       it "dataizes an expression the input does not contain" $
         withStdin universe $
           testCLISucceeded ["dataize", symbolic, "--inside=5.plus( 6 )"] ["40-45-00-00-00-00-00-00"]
@@ -1866,7 +1879,7 @@
     -- Two chained λ function calls: the inner fires under 'ml', because '.plus'
     -- is dispatched on its result, while the outer application is saturated but
     -- bare, so 'mf' hands it back and firing it is 𝔻's job
-    let chained = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]"
+    let chained = "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6).plus(7) ]]"
     it "prints help" $
       testCLISucceeded ["morph", "--help"] ["Morph the 𝜑-expression"]
 
@@ -1996,7 +2009,7 @@
     describe "--deep" $ do
       let program =
             "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, \
-            \number(φ) -> [[ times(x) -> [[ L> L_number_times ]] ]], \
+            \number(φ) -> [[ times(^, x) -> [[ L> L_number_times ]] ]], \
             \bar(x) -> [[ L> L_bar ]], \
             \demo -> [[ foo -> [[ n -> 3, @ -> Q.bar( $.n.times( 5 ).times( 7 ) ) ]] ]] ]]"
       it "answers the formation as it was written without the flag" $
@@ -2079,7 +2092,7 @@
       -- costs is that binding and not the rest of the program
       it "parks the looping binding and keeps walking with --deep" $
         loopingLambdas $ \endless ->
-          withStdin "⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧" $
+          withStdin "⟦ x ↦ ⟦ λ ⤍ L_loop, ρ ↦ ∅ ⟧.foo, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧" $
             testCLISucceeded
               ["morph", "--symbolic=" ++ endless, "--deep", "--acyclic", "--max-steps=4000", "--flat", "--hide-rho"]
               ["⟦ x ↦ ⟦ λ ⤍ L_loop ⟧.foo, y ↦ ⟦ z ↦ ⟦⟧ ⟧ ⟧"]
@@ -2190,7 +2203,7 @@
             , "  { }"
             , "  { }"
             , "\\phinoNormalizationRule{dl}"
-            , "  { [[ B_1, L> F, B_2 ]] }"
+            , "  { [[ B_1, L> f, B_2 ]] }"
             , "  { T }"
             , "  { D \\in B_1 \\;\\text{or}\\; D \\in B_2 }"
             , "  { }"
@@ -2207,7 +2220,7 @@
             , "\\phinoNormalizationRule{miss}"
             , "  { [[ B ]] ( \\tau -> e ) }"
             , "  { T }"
-            , "  { \\tau \\notin B }"
+            , "  { \\tau \\notin B \\;\\text{and}\\; \\tau \\not= \\phiTerminal{\\rho} }"
             , "  { }"
             , "\\phinoNormalizationRule{null}"
             , "  { [[ B_1, \\tau -> ?, B_2 ]] . \\tau }"
@@ -2224,6 +2237,11 @@
             , "  { T }"
             , "  { i = \\vert \\overline{ B_1 } \\vert \\;\\text{and}\\; \\tau \\not= \\phiTerminal{\\rho} }"
             , "  { }"
+            , "\\phinoNormalizationRule{skip}"
+            , "  { [[ B ]] ( \\phiTerminal{\\rho} -> e ) }"
+            , "  { [[ B ]] }"
+            , "  { \\phiTerminal{\\rho} \\notin B }"
+            , "  { }"
             , "\\phinoNormalizationRule{stay}"
             , "  { [[ B_1, \\phiTerminal{\\rho} -> e_1, B_2 ]] ( \\phiTerminal{\\rho} -> e_2 ) }"
             , "  { [[ B_1, \\phiTerminal{\\rho} -> e_1, B_2 ]] }"
@@ -2291,10 +2309,10 @@
             , "\\begin{phinoMorphingInference}"
             , "  \\phinoName{ml}"
             , "  \\phinoLabel{\\lambda}"
-            , "  \\phinoPremise{ \\phinoEvaluate{ [[ B_1, L> F, B_2 ]] }{ e }{ s_1 }{ n_1 }{ s_2 } }"
+            , "  \\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 } }"
+            , "  \\phinoConclusion{ \\phinoMorph{ [[ B_1, L> f, B_2 ]] . \\tau }{ e }{ s_1 }{ n_3 }{ s_3 } }"
             , "\\end{phinoMorphingInference}"
             , "\\begin{phinoMorphingInference}"
             , "  \\phinoName{mphi}"
@@ -2339,9 +2357,9 @@
             , "\\end{phinoDataizationInference}"
             , "\\begin{phinoDataizationInference}"
             , "  \\phinoName{fire}"
-            , "  \\phinoPremise{ \\phinoEvaluate{ [[ B_1, L> F, B_2 ]] }{ e }{ s_1 }{ n }{ s_2 } }"
+            , "  \\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 } }"
+            , "  \\phinoConclusion{ \\phinoDataize{ [[ B_1, L> f, B_2 ]] }{ e }{ s_1 }{ \\delta }{ s_3 } }"
             , "\\end{phinoDataizationInference}"
             , "\\begin{phinoDataizationInference}"
             , "  \\phinoName{none}"
@@ -2479,7 +2497,7 @@
     it "merges and prints as XMIR, with the listing rendered from the merged expression" $
       testCLISucceeded
         ["merge", resource "desugar.phi", "--output=xmir"]
-        ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<listing>⟦ foo ↦ ξ.x, ρ ↦ ∅ ⟧</listing>", "<o base=\"ξ.x\" name=\"foo\"/>"]
+        ["<?xml version=\"1.0\" encoding=\"UTF-8\"?>", "<listing>⟦ foo ↦ ξ.x ⟧</listing>", "<o base=\"ξ.x\" name=\"foo\"/>"]
 
     -- The @atom of an EO atom is its result type, not the name of its λ
     -- function, so the merged 𝜑 names the function after its locator and
@@ -2547,13 +2565,13 @@
     it "builds substitutions with conditions" $
       withStdin "[[ x -> Q.y ]].x" $
         testCLISucceeded
-          ["match", "--pattern=[[ !t1 -> Q.y, !B1 ]].!t1", "--when=eq(length(!B1),1)"]
-          ["B1 >> ⟦ ρ ↦ ∅ ⟧\nt1 >> x"]
+          ["match", "--pattern=[[ !t1 -> Q.y, !B1 ]].!t1", "--when=eq(length(!B1),0)"]
+          ["B1 >> ⟦⟧\nt1 >> x"]
 
     it "builds with condition from file" $
       testCLISucceeded
-        ["match", "--pattern=[[ !B1 ]]", "--when=eq(length(!B1),2)", resource "foo.phi"]
-        ["B1 >> ⟦ foo ↦ Φ.org.eolang.x, ρ ↦ ∅ ⟧"]
+        ["match", "--pattern=[[ !B1 ]]", "--when=eq(length(!B1),1)", resource "foo.phi"]
+        ["B1 >> ⟦ foo ↦ Φ.org.eolang.x ⟧"]
 
     it "rejects an anonymous meta in --when" $
       withStdin "[[ x -> Q.y ]]" $
diff --git a/test/CSTSpec.hs b/test/CSTSpec.hs
--- a/test/CSTSpec.hs
+++ b/test/CSTSpec.hs
@@ -83,7 +83,7 @@
         bts = BaseObject "bytes"
         bt = ArAlpha (Alpha 0)
         app = ExApplication
-        form = ExFormation [BiDelta (BtMany ["40", "18", "00", "00", "00", "00", "00", "00"]), BiVoid AtRho]
+        form = ExFormation [BiDelta (BtMany ["40", "18", "00", "00", "00", "00", "00", "00"])]
         isCSTNumber (EX_NUMBER{}) = True
         isCSTNumber _ = False
     forM_
@@ -282,8 +282,8 @@
 
   describe "expressionToCST on formation and dispatch edge cases" $ do
     it
-      "a single void rho binding collapses to the same CST as an empty formation"
-      (expressionToCST (ExFormation [BiVoid AtRho]) `shouldBe` expressionToCST (ExFormation []))
+      "a single void rho binding stays apart from an empty formation"
+      (expressionToCST (ExFormation [BiVoid AtRho]) `shouldNotBe` expressionToCST (ExFormation []))
     it
       "dispatch on Xi becomes a bare attribute"
       (expressionToCST (ExDispatch ExXi (AtLabel "foo")) `shouldBe` EX_ATTR (AT_LABEL "foo"))
diff --git a/test/DataizeSpec.hs b/test/DataizeSpec.hs
--- a/test/DataizeSpec.hs
+++ b/test/DataizeSpec.hs
@@ -232,8 +232,7 @@
   -- already evaluated, while the protocol of '--protocol' keeps the firings
   -- that did answer.
   describe "partially evaluates around a λ function that cannot fire (--partial)" $ do
-    -- the parser gives every formation its void ρ
-    let placeholder = ExFormation [BiLambda (Function "Sym_arg_0"), BiVoid AtRho]
+    let placeholder = ExFormation [BiLambda (Function "Sym_arg_0")]
     it "fails on it without the flag, naming the λ function" $ do
       expr <- parseExpressionThrows (primitives "2.times(3).nope")
       dataize expr emptyState (withLambdas known (defaultReduceContext ExRoot))
@@ -260,8 +259,8 @@
           , "    𝛿1.1 := 40-00-00-00-00-00-00-00  # 𝔻(ξ.ρ)"
           , "    𝛿2.1 := 40-08-00-00-00-00-00-00  # 𝔻(ξ.x)"
           , "    𝑛.1.1 := Φ.number( φ ↦ 𝜎1:λ )  # 𝑛"
-          , "    𝑛.1.2 := ⟦ φ ↦ 𝜎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:λ, ρ ↦ Φ ⟧  # 𝕄(𝑛.1.1)"
-          , "  ?(L_number_nope)  # 𝔻(⟦ λ ⤍ L_number_nope, ρ ↦ ⟦ φ ↦ 𝜎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:λ, ρ ↦ Φ ⟧ ⟧)"
+          , "    𝑛.1.2 := ⟦ φ ↦ 𝜎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 ⟧ ⟧  # 𝕄(𝑛.1.1)"
+          , "  ?(L_number_nope)  # 𝔻(⟦ ρ ↦ ⟦ φ ↦ 𝜎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 ⟧ ⟧, λ ⤍ L_number_nope ⟧)"
           ]
     it "leaves an unanswered λ function dataized directly as the whole residue" $ do
       ((outcome, chain), protocol) <- partially known "[[ L> Sym_arg_0 ]]"
@@ -271,10 +270,15 @@
     it "still reaches the manufactured datum when nothing is stuck" $ do
       ((outcome, _), _) <- partially known "2.times(3)"
       outcome `shouldBe` Dataized (BtMany ["40", "45", "00", "00", "00", "00", "00", "00"])
-    it "stops on the terminator ⊥ as before, since a data-less formation is not a stuck λ function" $ do
-      expr <- parseExpressionThrows (primitives "5.plus( ⟦ ⟧ )")
-      dataize expr emptyState ((withLambdas known (defaultReduceContext ExRoot)){_partial = True})
-        `shouldThrow` (\e -> "terminator" `isInfixOf` show (e :: SomeException))
+    -- An operand that reaches the terminator ⊥ never comes down to data, which
+    -- is a property of the program just as an unanswered λ function is, so it
+    -- parks the firing rather than ending the run (#1401)
+    it "parks a firing whose operand dataizes the terminator ⊥" $ do
+      ((outcome, _), protocol) <- partially known "5.plus( ⟦ ⟧ )"
+      case outcome of
+        Residual (ExFormation bds) -> bds `shouldContain` [BiLambda (Function "L_number_plus")]
+        other -> expectationFailure ("expected a residual formation, got " ++ show other)
+      protocol `shouldSatisfy` isInfixOf "?(⊥)  # 𝔻(⊥)"
 
   describe "ReduceContext's --max-depth/--max-cycles reach into the normalization it splices in" $ do
     let boxed = "[[ @ -> [[ D> 00- ]] ]]"
@@ -352,7 +356,7 @@
       labels <-
         labelsOf
           "Q"
-          "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
+          "[[ bytes ↦ ⟦ φ ↦ ∅ ⟧, number(φ) -> [[ plus(^, x) -> [[ L> L_number_plus ]] ]], @ -> 5.plus(6) ]]"
       labels
         `shouldBe` [ "contextualize"
                    , "maa"
@@ -365,4 +369,4 @@
                    ]
     it "dataizes a located reference through the expected rules" $ do
       labels <- labelsOf "Q.foo.bar" "[[ foo -> [[ bar -> [[ @ -> Q.x ]] ]], x -> [[ D> 42- ]] ]]"
-      labels `shouldBe` ["contextualize", "md", "dotg", "copy", "mf", "delta"]
+      labels `shouldBe` ["contextualize", "md", "dotg", "skip", "mf", "delta"]
diff --git a/test/EvaluateSpec.hs b/test/EvaluateSpec.hs
--- a/test/EvaluateSpec.hs
+++ b/test/EvaluateSpec.hs
@@ -194,5 +194,5 @@
       answered <- withLambdasOf "- λ: L_answer\n  𝑛: ⟦ Δ ⤍ FF- ⟧\n" readLambdas
       result <- execBuildTerm univ (withLambdas answered ctx) "evaluate" [ArgExpression form, ArgExpression univ] substEmpty
       case result of
-        TeExpression expr -> expr `shouldBe` ExFormation [BiDelta (BtOne "FF"), BiVoid AtRho]
+        TeExpression expr -> expr `shouldBe` ExFormation [BiDelta (BtOne "FF")]
         _ -> expectationFailure "expected TeExpression"
diff --git a/test/Fixtures.hs b/test/Fixtures.hs
--- a/test/Fixtures.hs
+++ b/test/Fixtures.hs
@@ -84,29 +84,30 @@
 -- composes it the same way, and 'bool.if' is where a branch meets the symbol
 -- its condition came down to. 'number.nope' is declared and left out of the
 -- file on purpose: it is the λ function that cannot fire, the one '--partial'
--- parks on.
+-- parks on. Every operation reads the object it is dispatched on, so each one
+-- declares ρ among its voids, the way EO declares '^' (#1407).
 primitives :: String -> String
 primitives src =
   unlines
     [ "[["
     , "  bytes -> [["
     , "    φ -> ?,"
-    , "    not -> [[ L> L_bytes_not ]],"
-    , "    eq -> [[ b -> ?, L> L_bytes_eq ]]"
+    , "    not -> [[ ^ -> ?, L> L_bytes_not ]],"
+    , "    eq -> [[ ^ -> ?, b -> ?, L> L_bytes_eq ]]"
     , "  ]],"
     , "  bool -> [["
     , "    φ -> ?,"
-    , "    if -> [[ then -> ?, else -> ?, L> L_fork ]]"
+    , "    if -> [[ ^ -> ?, then -> ?, else -> ?, L> L_fork ]]"
     , "  ]],"
     , "  number -> [["
     , "    φ -> ?,"
     , "    as-bytes -> $.φ,"
-    , "    plus -> [[ x -> ?, L> L_number_plus ]],"
-    , "    times -> [[ x -> ?, L> L_number_times ]],"
-    , "    div -> [[ x -> ?, L> L_number_div ]],"
-    , "    gt -> [[ x -> ?, L> L_number_gt ]],"
-    , "    eq -> [[ x -> ?, @ -> $.^.as-bytes.eq( x.as-bytes ) ]],"
-    , "    nope -> [[ L> L_number_nope ]]"
+    , "    plus -> [[ ^ -> ?, x -> ?, L> L_number_plus ]],"
+    , "    times -> [[ ^ -> ?, x -> ?, L> L_number_times ]],"
+    , "    div -> [[ ^ -> ?, x -> ?, L> L_number_div ]],"
+    , "    gt -> [[ ^ -> ?, x -> ?, L> L_number_gt ]],"
+    , "    eq -> [[ ^ -> ?, x -> ?, @ -> $.^.as-bytes.eq( x.as-bytes ) ]],"
+    , "    nope -> [[ ^ -> ?, L> L_number_nope ]]"
     , "  ]],"
     , "  @ -> " ++ src
     , "]]"
diff --git a/test/LaTeXSpec.hs b/test/LaTeXSpec.hs
--- a/test/LaTeXSpec.hs
+++ b/test/LaTeXSpec.hs
@@ -9,7 +9,7 @@
 -}
 module LaTeXSpec where
 
-import AST (Attribute (AtLabel, AtMeta, AtPhi, AtRho), Binding (BiDelta, BiMeta, BiTau, BiVoid), Bytes (BtMeta, BtOne), Expression (ExDispatch, ExFormation, ExMeta, ExPhiAgain, ExPhiMeet, ExRoot))
+import AST (Attribute (AtLabel, AtMeta, AtPhi, AtRho), Binding (BiDelta, BiLambda, BiMeta, BiTau, BiVoid), Bytes (BtMeta, BtOne), Expression (ExDispatch, ExFormation, ExMeta, ExPhiAgain, ExPhiMeet, ExRoot), Function (FnMeta, FnSymbol))
 import Control.Monad (forM_)
 import Data.List (intercalate)
 import Data.Text qualified as T
@@ -130,7 +130,7 @@
         `shouldBe` "\\begin{phiquation}\n[[ D> \\delta_7 ]]{.}\n\\end{phiquation}"
 
     it "escapes '@' and '^' in an attribute label, same as '$' and '_'" $ do
-      let weird = ExFormation [BiTau (AtLabel "a@b^c") ExRoot, BiVoid AtRho]
+      let weird = ExFormation [BiTau (AtLabel "a@b^c") ExRoot]
       expressionToLaTeX weird defaultLatexContext
         `shouldBe` "\\begin{phiquation}\n[[ |a\\char64{}b\\char94{}c| -> Q ]]{.}\n\\end{phiquation}"
 
@@ -167,7 +167,7 @@
           [ "\\begin{phiquation}"
           , "% === Step #1"
           , "[[ |x| -> Q . |y| ]]"
-          , "% === Step #2, Rule '?', 11t -> 11t"
+          , "% === Step #2, Rule '?', 7t -> 7t"
           , "  \\leadsto [[ |x| -> Q . |z| ]] \\leadsto_{\\nameref{r:myrule}}{.}"
           , "\\end{phiquation}"
           ]
@@ -270,6 +270,27 @@
           , "{ n }"
           , "{ \\isnormal{ n } \\;\\text{and}\\; \\phinoIsFormation{ n } }"
           , "{ \\phiTerminal{\\rho} \\coloneqq \\foo{ n, \\phiTerminal{\\rho} -> ?, 01-02- } and @ \\coloneqq \\bar{ n } }"
+          ]
+        )
+      ,
+        ( "keeps the λ metas 𝑓 and 𝜎 lowercase"
+        , Y.Rule
+            { name = "lambdas"
+            , label = Nothing
+            , description = Nothing
+            , ematch = Nothing
+            , pattern = ExFormation [BiMeta "B1", BiLambda (FnMeta "f"), BiMeta "B2"]
+            , result = ExFormation [BiLambda (FnSymbol 1)]
+            , when = Nothing
+            , having = Nothing
+            , where_ = Nothing
+            }
+        ,
+          [ "\\phinoNormalizationRule{lambdas}"
+          , "{ [[ B_1, L> f, B_2 ]] }"
+          , "{ [[ L> \\sigma_1 ]] }"
+          , "{ }"
+          , "{ }"
           ]
         )
       ,
diff --git a/test/LambdasSpec.hs b/test/LambdasSpec.hs
--- a/test/LambdasSpec.hs
+++ b/test/LambdasSpec.hs
@@ -40,6 +40,20 @@
     spelled :: (Meta, (Meta, Meta)) -> (T.Text, (T.Text, T.Text))
     spelled (meta, (left, right)) = (_spelling meta, (_spelling left, _spelling right))
 
+-- An entry morphing one operand under 𝑛1 and rewriting with the 'rewrite'
+-- lines given, followed by whatever else the entry spells
+rewriting :: T.Text -> T.Text
+rewriting lines' = "- λ: L_fork\n  morph:\n    𝑛1: $.a\n  rewrite:\n" <> lines'
+
+-- The rules of one 'rewrite' line, a single rule dropping an 'x' binding
+rules :: T.Text
+rules = "      rules:\n        - name: no-x\n          pattern: ⟦ !B1, x ↦ ⟦⟧, !B2 ⟧\n          result: ⟦ !B1, !B2 ⟧\n"
+
+-- A 'rewrite' line spelled the way the file spells it: the meta it binds, the
+-- meta it rewrites and how many rules it rewrites that term with
+rewrote :: (Meta, (Meta, [a])) -> (T.Text, T.Text, Int)
+rewrote (meta, (source, written)) = (_spelling meta, _spelling source, length written)
+
 spec :: Spec
 spec = do
   describe "readLambdas" $ do
@@ -122,6 +136,17 @@
       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"))]
 
+    -- A 'rewrite' line names the meta it rewrites and the rules it rewrites
+    -- that term with, and may rewrite what a line above it rewrote
+    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)]
+
+    -- 'symbolize' and 'join' run after 'rewrite', so both may read what it made
+    it "reads a 'symbolize' line standing the term a 'rewrite' line made" $ do
+      known <- lambdasOf (rewriting "    𝑛2:\n      of: 𝑛1\n" <> rules <> "  symbolize:\n    𝑛3: 𝑛2\n  𝑛: 𝑛3\n")
+      map (_spelling . fst) (maybe [] _symbolized (matched known "L_fork")) `shouldBe` ["𝑛3"]
+
     it "reads an entry naming no 'join' block at all" $ do
       known <- lambdasOf "- λ: L_pair\n  𝑛: ⟦ λ ⤍ 𝜎 ⟧\n"
       joins known "L_pair" `shouldBe` []
@@ -160,6 +185,15 @@
       , ("a 'join' line joining a meta the entry never bound", "- λ: L_fork\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛3: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by a line above it")
       , ("a 'join' line joining a meta that came down to data", "- λ: L_fork\n  dataize:\n    𝛿1: $.ρ\n  morph:\n    𝑛1: $.then\n  join:\n    𝑛2: [𝑛1, 𝛿1]\n  𝑛: 𝑛2\n", "is not an expression meta")
       , ("a 'join' line joining what a line below it made", "- λ: L_fork\n  morph:\n    𝑛1: $.a\n    𝑛2: $.b\n  join:\n    𝑛3: [𝑛1, 𝑛4]\n    𝑛4: [𝑛1, 𝑛2]\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by a line above it")
+      , ("a 'rewrite' line rewriting a meta the entry never bound", rewriting "    𝑛2:\n      of: 𝑛5\n" <> rules <> "  𝑛: 𝑛2\n", "names no meta bound by 'morph' or by a line above it")
+      , ("a 'rewrite' line rewriting what a line below it made", rewriting "    𝑛2:\n      of: 𝑛3\n" <> rules <> "    𝑛3:\n      of: 𝑛1\n" <> rules <> "  𝑛: 𝑛2\n", "names no meta bound by 'morph' or by a line above it")
+      , ("a 'rewrite' line rewriting what a 'symbolize' line made", rewriting "    𝑛3:\n      of: 𝑛2\n" <> rules <> "  symbolize:\n    𝑛2: 𝑛1\n  𝑛: 𝑛3\n", "names no meta bound by 'morph' or by a line above it")
+      , ("a 'rewrite' line bound to what is no expression meta", rewriting "    𝛿1:\n      of: 𝑛1\n" <> rules <> "  𝑛: 𝑛1\n", "is not an expression meta")
+      , ("a 'rewrite' line with no rules at all", rewriting "    𝑛2:\n      of: 𝑛1\n  𝑛: 𝑛2\n", "cannot be read")
+      , ("a rule of 'rewrite' minting a fresh symbol", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: minting\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ λ ⤍ 𝜎 ⟧\n  𝑛: 𝑛2\n", "writes a symbol 𝜎 into its result")
+      , ("a rule of 'rewrite' writing a symbol nobody minted", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: naming\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ λ ⤍ 𝜎1 ⟧\n  𝑛: 𝑛2\n", "writes a symbol 𝜎 into its result")
+      , ("a rule of 'rewrite' reading a meta it never binds", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: unbound\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n          result: ⟦ φ ↦ 𝑒2 ⟧\n  𝑛: 𝑛2\n", "reads the meta 'e2' it never binds")
+      , ("a rule of 'rewrite' with no result", rewriting "    𝑛2:\n      of: 𝑛1\n      rules:\n        - name: empty\n          pattern: ⟦ φ ↦ 𝑒1 ⟧\n  𝑛: 𝑛2\n", "cannot be read")
       , ("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")
       ]
       ( \(desc, text, message) ->
diff --git a/test/MiscSpec.hs b/test/MiscSpec.hs
--- a/test/MiscSpec.hs
+++ b/test/MiscSpec.hs
@@ -15,61 +15,13 @@
   , attributesFromBindings'
   , fqnToAttrs
   , orThrow
-  , recoverFormations
   , toDouble
   , uniqueBindings
-  , withVoidRho
   )
-import Test.Hspec (Example (Arg), Expectation, Spec, SpecWith, describe, it, shouldBe, shouldContain, shouldReturn, shouldSatisfy)
-
-testWithVoidRho :: [(String, [Binding], [Binding])] -> SpecWith (Arg Expectation)
-testWithVoidRho useCases =
-  forM_ useCases $ \(desc, before, after) ->
-    it desc $ withVoidRho before `shouldBe` after
+import Test.Hspec (Spec, describe, it, shouldBe, shouldContain, shouldReturn, shouldSatisfy)
 
 spec :: Spec
 spec = do
-  describe "with void rho binding" $
-    testWithVoidRho
-      [
-        ( "[[x -> ?]] => [[x -> ?, ^ -> ?]]"
-        , [BiVoid (AtLabel "x")]
-        , [BiVoid (AtLabel "x"), BiVoid AtRho]
-        )
-      ,
-        ( "[[^ -> ?, x -> ?]] => [[^ -> ?, x -> ?]]"
-        , [BiVoid AtRho, BiVoid (AtLabel "x")]
-        , [BiVoid AtRho, BiVoid (AtLabel "x")]
-        )
-      ,
-        ( "[[^ -> Q.x, x -> $.y]] => [[^ -> Q.x, x -> $.y]]"
-        , [BiTau AtRho (ExDispatch ExRoot (AtLabel "x")), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        , [BiTau AtRho (ExDispatch ExRoot (AtLabel "x")), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        )
-      , ("[[!B]] => [[!B]]", [BiMeta "B"], [BiMeta "B"])
-      , ("[[x -> ?, !B]] => [[x -> ?, !B]]", [BiVoid (AtLabel "x"), BiMeta "B"], [BiVoid (AtLabel "x"), BiMeta "B"])
-      ,
-        ( "[[x -> ?, !B, y -> ?]] => [[x -> ?, !B, y -> ?]]"
-        , [BiVoid (AtLabel "x"), BiMeta "B", BiVoid (AtLabel "y")]
-        , [BiVoid (AtLabel "x"), BiMeta "B", BiVoid (AtLabel "y")]
-        )
-      ,
-        ( "[[^ -> ?, !B, y -> ?]] => [[^ -> ?, !B, y -> ?]]"
-        , [BiVoid AtRho, BiMeta "B", BiVoid (AtLabel "y")]
-        , [BiVoid AtRho, BiMeta "B", BiVoid (AtLabel "y")]
-        )
-      ,
-        ( "[[!t -> ?, x -> $.y]] => [[!t -> Q.x, x -> $.y]]"
-        , [BiVoid (AtMeta "t"), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        , [BiVoid (AtMeta "t"), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        )
-      ,
-        ( "[[!t -> Q.x, x -> $.y]] => [[!t -> Q.x, x -> $.y]]"
-        , [BiTau (AtMeta "t") (ExDispatch ExRoot (AtLabel "x")), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        , [BiTau (AtMeta "t") (ExDispatch ExRoot (AtLabel "x")), BiTau AtRho (ExDispatch ExTermination (AtLabel "y"))]
-        )
-      ]
-
   describe "unique bindings" $ do
     it "fails with duplicate attribute" $
       uniqueBindings [BiVoid AtRho, BiVoid AtRho] `shouldSatisfy` isLeft
@@ -109,33 +61,6 @@
   describe "attributesFromBindings'" $
     it "keeps a Nothing placeholder for each BiMeta entry" $
       attributesFromBindings' [BiVoid AtRho, BiMeta "B"] `shouldBe` [Just AtRho, Nothing]
-
-  describe "recoverFormations" $
-    forM_
-      [ ("adds a missing void rho to an empty formation", ExFormation [], ExFormation [BiVoid AtRho])
-      ,
-        ( "recovers a nested formation reached through a BiTau binding"
-        , ExFormation [BiTau (AtLabel "x") (ExFormation [])]
-        , ExFormation [BiTau (AtLabel "x") (ExFormation [BiVoid AtRho]), BiVoid AtRho]
-        )
-      ,
-        ( "recurses through ExDispatch"
-        , ExDispatch (ExFormation []) (AtLabel "y")
-        , ExDispatch (ExFormation [BiVoid AtRho]) (AtLabel "y")
-        )
-      ,
-        ( "recurses through ExApplication's tau argument"
-        , ExApplication ExRoot (ArTau (AtLabel "a") (ExFormation []))
-        , ExApplication ExRoot (ArTau (AtLabel "a") (ExFormation [BiVoid AtRho]))
-        )
-      ,
-        ( "recurses through ExApplication's alpha argument"
-        , ExApplication ExRoot (ArAlpha (Alpha 0) (ExFormation []))
-        , ExApplication ExRoot (ArAlpha (Alpha 0) (ExFormation [BiVoid AtRho]))
-        )
-      , ("leaves every other expression untouched", ExXi, ExXi)
-      ]
-      (\(desc, expr, expected) -> it desc (recoverFormations expr `shouldBe` expected))
 
   describe "fqnToAttrs" $
     forM_
diff --git a/test/MorphSpec.hs b/test/MorphSpec.hs
--- a/test/MorphSpec.hs
+++ b/test/MorphSpec.hs
@@ -135,9 +135,15 @@
       , ("$ => X", ExXi, ExRoot, ExTermination)
       , ("Q => X", ExRoot, ExRoot, ExTermination)
       ,
-        ( "Q.x (Q -> [[ x -> [[]] ]]) => [[ ρ -> Q ]]"
+        ( "Q.x (Q -> [[ x -> [[]] ]]) => [[]]"
         , ExDispatch ExRoot (AtLabel "x")
         , ExFormation [BiTau (AtLabel "x") (ExFormation [])]
+        , ExFormation []
+        )
+      ,
+        ( "Q.x (Q -> [[ x -> [[ ^ -> ? ]] ]]) => [[ ρ -> Q ]]"
+        , ExDispatch ExRoot (AtLabel "x")
+        , ExFormation [BiTau (AtLabel "x") (ExFormation [BiVoid AtRho])]
         , ExFormation [BiTau AtRho ExRoot]
         )
       , -- A void slot fed a non-absolute argument can never be filled, so 'copy'
@@ -244,7 +250,7 @@
           ,
             ( "a dispatch over a formation"
             , ExDispatch ExRoot (AtLabel "x")
-            , ExFormation [BiTau (AtLabel "x") (ExFormation [])]
+            , ExFormation [BiTau (AtLabel "x") (ExFormation [BiVoid AtRho])]
             , ExFormation [BiTau AtRho ExRoot]
             )
           ]
diff --git a/test/ParserSpec.hs b/test/ParserSpec.hs
--- a/test/ParserSpec.hs
+++ b/test/ParserSpec.hs
@@ -53,39 +53,39 @@
   describe "parse expression" $
     test
       parseExpression
-      [ ("[[]]", Just (ExFormation [BiVoid AtRho]))
+      [ ("[[]]", Just (ExFormation []))
       , ("T(x -> Q)", Just (ExApplication ExTermination (ArTau (AtLabel "x") ExRoot)))
       , ("Q.org.eolang", Just (ExDispatch (ExDispatch ExRoot (AtLabel "org")) (AtLabel "eolang")))
-      , ("[[x -> $, y -> ?]]", Just (ExFormation [BiTau (AtLabel "x") ExXi, BiVoid (AtLabel "y"), BiVoid AtRho]))
+      , ("[[x -> $, y -> ?]]", Just (ExFormation [BiTau (AtLabel "x") ExXi, BiVoid (AtLabel "y")]))
       , ("Q.!t1", Just (ExDispatch ExRoot (AtMeta "t1")))
-      , ("[[]](!t1 -> $)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArTau (AtMeta "t1") ExXi)))
+      , ("[[]](!t1 -> $)", Just (ExApplication (ExFormation []) (ArTau (AtMeta "t1") ExXi)))
       ,
         ( "[[]](~0 -> $)(~11 -> Q)"
         , Just
             ( ExApplication
                 ( ExApplication
-                    (ExFormation [BiVoid AtRho])
+                    (ExFormation [])
                     (ArAlpha (Alpha 0) ExXi)
                 )
                 (ArAlpha (Alpha 11) ExRoot)
             )
         )
-      , ("[[]](x -> $, y -> Q)", Just (ExApplication (ExApplication (ExFormation [BiVoid AtRho]) (ArTau (AtLabel "x") ExXi)) (ArTau (AtLabel "y") ExRoot)))
+      , ("[[]](x -> $, y -> Q)", Just (ExApplication (ExApplication (ExFormation []) (ArTau (AtLabel "x") ExXi)) (ArTau (AtLabel "y") ExRoot)))
       , ("[[!B1, !B2]]", Just (ExFormation [BiMeta "B1", BiMeta "B2"]))
       , ("[[!B2, !t2 -> $]]", Just (ExFormation [BiMeta "B2", BiTau (AtMeta "t2") ExXi]))
       , ("!e1", Just (ExMeta "e1"))
       , ("!k1", Just (ExMeta "k1"))
-      , ("[[x -> !k1]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "k1"), BiVoid AtRho]))
-      , ("[[x -> !e1]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "e1"), BiVoid AtRho]))
+      , ("[[x -> !k1]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "k1")]))
+      , ("[[x -> !e1]]", Just (ExFormation [BiTau (AtLabel "x") (ExMeta "e1")]))
       , ("[[!t1 -> !e1]]", Just (ExFormation [BiTau (AtMeta "t1") (ExMeta "e1")]))
-      , ("[[D> --]]", Just (ExFormation [BiDelta BtEmpty, BiVoid AtRho]))
-      , ("[[D> 1F-]]", Just (ExFormation [BiDelta (BtOne "1F"), BiVoid AtRho]))
-      , ("[[\n  L> Func,\n  D> 00-\n]]", Just (ExFormation [BiLambda (Function "Func"), BiDelta (BtOne "00"), BiVoid AtRho]))
-      , ("[[D> 1F-2A-00]]", Just (ExFormation [BiDelta (BtMany ["1F", "2A", "00"]), BiVoid AtRho]))
-      , ("[[D> !d1]]", Just (ExFormation [BiDelta (BtMeta "d1"), BiVoid AtRho]))
-      , ("[[L> Function]]", Just (ExFormation [BiLambda (Function "Function"), BiVoid AtRho]))
-      , ("[[L> !F3]]", Just (ExFormation [BiLambda (FnMeta "F3"), BiVoid AtRho]))
-      , ("[[x() -> [[]] ]]", Just (ExFormation [BiTau (AtLabel "x") (ExFormation [BiVoid AtRho]), BiVoid AtRho]))
+      , ("[[D> --]]", Just (ExFormation [BiDelta BtEmpty]))
+      , ("[[D> 1F-]]", Just (ExFormation [BiDelta (BtOne "1F")]))
+      , ("[[\n  L> Func,\n  D> 00-\n]]", Just (ExFormation [BiLambda (Function "Func"), BiDelta (BtOne "00")]))
+      , ("[[D> 1F-2A-00]]", Just (ExFormation [BiDelta (BtMany ["1F", "2A", "00"])]))
+      , ("[[D> !d1]]", Just (ExFormation [BiDelta (BtMeta "d1")]))
+      , ("[[L> Function]]", Just (ExFormation [BiLambda (Function "Function")]))
+      , ("[[L> !F3]]", Just (ExFormation [BiLambda (FnMeta "F3")]))
+      , ("[[x() -> [[]] ]]", Just (ExFormation [BiTau (AtLabel "x") (ExFormation [])]))
       ,
         ( "[[y(^,@,z) -> [[q -> Q.a]] ]]"
         , Just
@@ -99,7 +99,6 @@
                         , BiTau (AtLabel "q") (ExDispatch ExRoot (AtLabel "a"))
                         ]
                     )
-                , BiVoid AtRho
                 ]
             )
         )
@@ -138,7 +137,6 @@
                 , BiTau
                     (AtLabel "q")
                     (ExMeta "e1")
-                , BiVoid AtRho
                 ]
             )
         )
@@ -151,7 +149,7 @@
                         (ExDispatch ExRoot (AtLabel "x"))
                         (ArAlpha (Alpha 0) (ExDispatch ExXi (AtLabel "y")))
                     )
-                    (ArAlpha (Alpha 1) (ExDispatch (ExFormation [BiVoid AtRho]) (AtLabel "z")))
+                    (ArAlpha (Alpha 1) (ExDispatch (ExFormation []) (AtLabel "z")))
                 )
                 ( ArAlpha
                     (Alpha 2)
@@ -370,10 +368,10 @@
       , ("!B_test", Just (BiMeta "B_test"))
       , ("𝐵1", Just (BiMeta "B1"))
       , ("𝐵1", Just (BiMeta "B1"))
-      , ("x() -> [[]]", Just (BiTau (AtLabel "x") (ExFormation [BiVoid AtRho])))
+      , ("x() -> [[]]", Just (BiTau (AtLabel "x") (ExFormation [])))
       , ("y(^) -> [[]]", Just (BiTau (AtLabel "y") (ExFormation [BiVoid AtRho])))
       , ("z(^, @) -> [[]]", Just (BiTau (AtLabel "z") (ExFormation [BiVoid AtRho, BiVoid AtPhi])))
-      , ("x -> [[y -> $]]", Just (BiTau (AtLabel "x") (ExFormation [BiTau (AtLabel "y") ExXi, BiVoid AtRho])))
+      , ("x -> [[y -> $]]", Just (BiTau (AtLabel "x") (ExFormation [BiTau (AtLabel "y") ExXi])))
       , ("x ↦ ξ", Just (BiTau (AtLabel "x") ExXi))
       , ("x -> ", Nothing)
       , ("-> Q", Nothing)
@@ -511,8 +509,8 @@
       [ ("ξ", Just ExXi)
       , ("Φ", Just ExRoot)
       , ("⊥", Just ExTermination)
-      , ("⟦⟧", Just (ExFormation [BiVoid AtRho]))
-      , ("⟦ x ↦ ξ ⟧", Just (ExFormation [BiTau (AtLabel "x") ExXi, BiVoid AtRho]))
+      , ("⟦⟧", Just (ExFormation []))
+      , ("⟦ x ↦ ξ ⟧", Just (ExFormation [BiTau (AtLabel "x") ExXi]))
       , ("ξ.ρ", Just (ExDispatch ExXi AtRho))
       , ("ξ.φ", Just (ExDispatch ExXi AtPhi))
       ]
@@ -532,7 +530,7 @@
     test
       parseExpression
       [ ("[[^ -> ?]]", Just (ExFormation [BiVoid AtRho]))
-      , ("[[@ -> ?]]", Just (ExFormation [BiVoid AtPhi, BiVoid AtRho]))
+      , ("[[@ -> ?]]", Just (ExFormation [BiVoid AtPhi]))
       , ("[[^ -> ?, @ -> ?]]", Just (ExFormation [BiVoid AtRho, BiVoid AtPhi]))
       , ("[[^ -> Q, @ -> $]]", Just (ExFormation [BiTau AtRho ExRoot, BiTau AtPhi ExXi]))
       ]
@@ -540,12 +538,12 @@
   describe "parse applications with mixed bindings" $
     test
       parseExpression
-      [ ("[[]](Q)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (Alpha 0) ExRoot)))
-      , ("[[]](Q, T)", Just (ExApplication (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (Alpha 0) ExRoot)) (ArAlpha (Alpha 1) ExTermination)))
+      [ ("[[]](Q)", Just (ExApplication (ExFormation []) (ArAlpha (Alpha 0) ExRoot)))
+      , ("[[]](Q, T)", Just (ExApplication (ExApplication (ExFormation []) (ArAlpha (Alpha 0) ExRoot)) (ArAlpha (Alpha 1) ExTermination)))
       , ("Q.x(y -> $)", Just (ExApplication (ExDispatch ExRoot (AtLabel "x")) (ArTau (AtLabel "y") ExXi)))
-      , ("[[x -> ?]].x(Q)", Just (ExApplication (ExDispatch (ExFormation [BiVoid (AtLabel "x"), BiVoid AtRho]) (AtLabel "x")) (ArAlpha (Alpha 0) ExRoot)))
-      , ("[[]](~!i1 -> $)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (AlMeta "i1") ExXi)))
-      , ("[[]](α𝑖1 -> Q)", Just (ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (AlMeta "i1") ExRoot)))
+      , ("[[x -> ?]].x(Q)", Just (ExApplication (ExDispatch (ExFormation [BiVoid (AtLabel "x")]) (AtLabel "x")) (ArAlpha (Alpha 0) ExRoot)))
+      , ("[[]](~!i1 -> $)", Just (ExApplication (ExFormation []) (ArAlpha (AlMeta "i1") ExXi)))
+      , ("[[]](α𝑖1 -> Q)", Just (ExApplication (ExFormation []) (ArAlpha (AlMeta "i1") ExRoot)))
       , ("Q.foo(a1 -> Q.y)", Just (ExApplication (ExDispatch ExRoot (AtLabel "foo")) (ArTau (AtLabel "a1") (ExDispatch ExRoot (AtLabel "y"))))) -- #875: "a"-prefixed label in argument position is a named binding, not a positional alpha
       ]
 
diff --git a/test/PrinterSpec.hs b/test/PrinterSpec.hs
--- a/test/PrinterSpec.hs
+++ b/test/PrinterSpec.hs
@@ -29,7 +29,7 @@
       [ ("ξ renders as $", ExXi, "$")
       , ("Φ renders as Q", ExRoot, "Q")
       , ("⊥ renders as T", ExTermination, "T")
-      , ("ρ void becomes empty", ExFormation [BiVoid AtRho], "[[]]")
+      , ("ρ void stays a formation of its own", ExFormation [BiVoid AtRho], "[[ ^ -> ? ]]")
       , ("φ void", ExFormation [BiVoid AtPhi], "?:@")
       , ("label void", ExFormation [BiVoid (AtLabel "名前")], "?:名前")
       , ("x to Φ", ExFormation [BiTau (AtLabel "x") ExRoot], "Q:x")
@@ -50,7 +50,7 @@
         )
       ,
         ( "formation with app"
-        , ExApplication (ExFormation [BiVoid AtRho]) (ArAlpha (Alpha 0) ExRoot)
+        , ExApplication (ExFormation []) (ArAlpha (Alpha 0) ExRoot)
         , "[[]]( Q )"
         )
       , ("meta expr", ExMeta "e", "!e")
@@ -181,7 +181,7 @@
 
   describe "printExpression with default config" $
     forM_
-      [ ("empty formation", ExFormation [BiVoid AtRho], "⟦⟧")
+      [ ("empty formation", ExFormation [], "⟦⟧")
       , ("dispatch", ExDispatch ExRoot (AtLabel "org"), "Φ.org")
       ]
       ( \(desc, expr, expected) ->
@@ -424,8 +424,8 @@
       , ("⟦ x ↦ ⟦ φ ↦ ξ.a ⟧.b ⟧", SWEET, UNICODE, "a:φ.b:x")
       , ("⟦ x(a) ↦ ⟦ φ ↦ a ⟧ ⟧", SWEET, UNICODE, "⟦ x(a) ↦ ⟦ φ ↦ a ⟧ ⟧")
       , ("⟦ x ↦ ξ.a, y ↦ ∅ ⟧", SWEET, UNICODE, "⟦ x ↦ a, y ↦ ∅ ⟧")
-      , ("⟦ x ↦ ξ.a ⟧", SALTY, UNICODE, "⟦ x ↦ ξ.a, ρ ↦ ∅ ⟧")
-      , ("⟦ Δ ⤍ FF-AA ⟧", SALTY, ASCII, "[[ D> FF-AA, ^ -> ? ]]")
+      , ("⟦ x ↦ ξ.a ⟧", SALTY, UNICODE, "⟦ x ↦ ξ.a ⟧")
+      , ("⟦ Δ ⤍ FF-AA ⟧", SALTY, ASCII, "[[ D> FF-AA ]]")
       ]
       ( \(src, sugar, encoding, expected) ->
           it (src ++ " " ++ show sugar ++ " " ++ show encoding) $ do
diff --git a/test/RenderSpec.hs b/test/RenderSpec.hs
--- a/test/RenderSpec.hs
+++ b/test/RenderSpec.hs
@@ -90,6 +90,10 @@
       , (D'', "d")
       , (F, "𝑓")
       , (F', "F")
+      , (F'', "f")
+      , (S, "𝜎")
+      , (S', "S")
+      , (S'', "\\sigma")
       ]
       (\(metaHead, expected) -> it (show metaHead) (render (META NO_EXCL metaHead "") `shouldBe` expected))
 
diff --git a/test/SugarSpec.hs b/test/SugarSpec.hs
--- a/test/SugarSpec.hs
+++ b/test/SugarSpec.hs
@@ -55,19 +55,12 @@
         , EX_DISPATCH (EX_DISPATCH (EX_XI XI) NO_SPACE (AT_LABEL "y")) NO_SPACE (AT_LABEL "x")
         )
       ,
-        ( "EX_FORMATION with an empty binding collapses to the TAB' layout and gains a void rho"
+        ( "EX_FORMATION with an empty binding stays empty, gaining no void rho"
         , EX_FORMATION LSB NO_EOL NO_TAB (BI_EMPTY NO_TAB) NO_EOL NO_TAB RSB
-        , EX_FORMATION
-            LSB
-            NO_EOL
-            TAB'
-            (BI_PAIR (PA_VOID (AT_RHO RHO) ARROW EMPTY) (BDS_EMPTY NO_TAB) NO_TAB)
-            NO_EOL
-            TAB'
-            RSB
+        , EX_FORMATION LSB NO_EOL NO_TAB (BI_EMPTY NO_TAB) NO_EOL NO_TAB RSB
         )
       ,
-        ( "EX_FORMATION with a real binding keeps its layout and appends a trailing void rho"
+        ( "EX_FORMATION with a real binding keeps its layout and gains no void rho"
         , EX_FORMATION
             LSB
             EOL
@@ -80,11 +73,7 @@
             LSB
             EOL
             (TAB 1)
-            ( BI_PAIR
-                (PA_TAU (AT_LABEL "x") ARROW xiExpr)
-                (BDS_PAIR EOL (TAB 1) (PA_VOID (AT_RHO RHO) ARROW EMPTY) (BDS_EMPTY (TAB 1)))
-                (TAB 1)
-            )
+            (BI_PAIR (PA_TAU (AT_LABEL "x") ARROW xiExpr) (BDS_EMPTY (TAB 1)) (TAB 1))
             EOL
             (TAB 0)
             RSB
@@ -211,47 +200,47 @@
       ,
         ( "EX_NUMBER with no extra rho expands into the Q.number(Q.bytes(...)) form"
         , EX_NUMBER (Left 42) (TAB 1) []
-        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 40-45-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 40-45-00-00-00-00-00-00\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_NUMBER preserves an extra rho argument carried alongside the primitive"
         , EX_NUMBER (Left 42) (TAB 1) [ArTau AtRho (ExDispatch ExXi (AtLabel "y"))]
-        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 40-45-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
+        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 40-45-00-00-00-00-00-00\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
         )
       ,
         ( "EX_NONFINITE nan expands into the Q.number(Q.bytes(...)) form"
         , EX_NONFINITE Φ NfNan (TAB 1) []
-        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 7F-F8-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 7F-F8-00-00-00-00-00-00\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_NONFINITE pinf expands into the Q.number(Q.bytes(...)) form"
         , EX_NONFINITE Φ NfPinf (TAB 1) []
-        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 7F-F0-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 7F-F0-00-00-00-00-00-00\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_NONFINITE ninf keeps an extra rho argument carried alongside the primitive"
         , EX_NONFINITE Φ NfNinf (TAB 1) [ArTau AtRho (ExDispatch ExXi (AtLabel "y"))]
-        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ FF-F0-00-00-00-00-00-00,\n        ρ ↦ ∅\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
+        , "Φ.number(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ FF-F0-00-00-00-00-00-00\n      ⟧\n    )\n  )(\n    ρ ↦ ξ.y\n  )"
         )
       ,
         ( "EX_STRING expands into the Q.string(Q.bytes(...)) form"
         , EX_STRING "hi" (TAB 1) []
-        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 68-69,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 68-69\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_STRING unescapes a newline instead of taking its escape literally"
         , EX_STRING "e\\ne" (TAB 1) []
-        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 65-0A-65,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 65-0A-65\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_STRING unescapes a quote and a backslash into single bytes"
         , EX_STRING "\\\"\\\\" (TAB 1) []
-        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 22-5C,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 22-5C\n      ⟧\n    )\n  )"
         )
       ,
         ( "EX_STRING unescapes a hex escape back into its byte"
         , EX_STRING "\\x01" (TAB 1) []
-        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 01-,\n        ρ ↦ ∅\n      ⟧\n    )\n  )"
+        , "Φ.string(\n    φ ↦ Φ.bytes(\n      φ ↦ ⟦\n        Δ ⤍ 01-\n      ⟧\n    )\n  )"
         )
       ]
       (\(desc, sweetExpr, expected) -> it desc (render (toSalty sweetExpr) `shouldBe` expected))
@@ -311,7 +300,7 @@
         , PA_ALPHA (AL_IDX ALPHA 0) ARROW (EX_DISPATCH (EX_XI XI) NO_SPACE (AT_LABEL "y"))
         )
       ,
-        ( "PA_FORMATION with an empty object body joins its void params ahead of the body and gains a trailing rho"
+        ( "PA_FORMATION with an empty object body joins its void params ahead of the body"
         , PA_FORMATION (AT_LABEL "f") [AT_LABEL "p"] ARROW (EX_FORMATION LSB EOL (TAB 2) (BI_EMPTY (TAB 2)) EOL (TAB 1) RSB)
         , PA_TAU
             (AT_LABEL "f")
@@ -320,11 +309,7 @@
                 LSB
                 EOL
                 (TAB 2)
-                ( BI_PAIR
-                    (PA_VOID (AT_LABEL "p") ARROW EMPTY)
-                    (BDS_PAIR EOL (TAB 2) (PA_VOID (AT_RHO RHO) ARROW EMPTY) (BDS_EMPTY (TAB 2)))
-                    (TAB 2)
-                )
+                (BI_PAIR (PA_VOID (AT_LABEL "p") ARROW EMPTY) (BDS_EMPTY (TAB 2)) (TAB 2))
                 EOL
                 (TAB 1)
                 RSB
@@ -350,12 +335,7 @@
                         EOL
                         (TAB 2)
                         (PA_VOID (AT_LABEL "q") ARROW EMPTY)
-                        ( BDS_PAIR
-                            EOL
-                            (TAB 2)
-                            (PA_TAU (AT_LABEL "z") ARROW xiExpr)
-                            (BDS_PAIR EOL (TAB 2) (PA_VOID (AT_RHO RHO) ARROW EMPTY) (BDS_EMPTY (TAB 2)))
-                        )
+                        (BDS_PAIR EOL (TAB 2) (PA_TAU (AT_LABEL "z") ARROW xiExpr) (BDS_EMPTY (TAB 2)))
                     )
                     (TAB 2)
                 )
@@ -648,13 +628,13 @@
       $ do
         let number = DataNumber (BtMany ["40", "45", "00", "00", "00", "00", "00", "00"])
         printExpression' number (config SWEET) `shouldBe` "42"
-        printExpression' number (config SALTY) `shouldBe` "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 40-45-00-00-00-00-00-00, ρ ↦ ∅ ⟧ ) )"
+        printExpression' number (config SALTY) `shouldBe` "Φ.number( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 40-45-00-00-00-00-00-00 ⟧ ) )"
     it
       "a sweet string literal expands into Q.string(Q.bytes(...)) when salted"
       $ do
         let string = DataString (BtMany ["68", "69"])
         printExpression' string (config SWEET) `shouldBe` "\"hi\""
-        printExpression' string (config SALTY) `shouldBe` "Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-69, ρ ↦ ∅ ⟧ ) )"
+        printExpression' string (config SALTY) `shouldBe` "Φ.string( φ ↦ Φ.bytes( φ ↦ ⟦ Δ ⤍ 68-69 ⟧ ) )"
     it
       "an application with multiple positional arguments sugars/salts between e(e0, e1) and e(α0 ↦ e0)(α1 ↦ e1)"
       $ do
@@ -666,10 +646,10 @@
       $ do
         let nestedForm = ExFormation [BiTau (AtLabel "obj") (ExFormation [BiVoid (AtLabel "p"), BiVoid (AtLabel "q"), BiTau (AtLabel "z") ExXi])]
         printExpression' nestedForm (config SWEET) `shouldBe` "⟦ obj(p, q) ↦ ⟦ z ↦ ξ ⟧ ⟧"
-        printExpression' nestedForm (config SALTY) `shouldBe` "⟦ obj ↦ ⟦ p ↦ ∅, q ↦ ∅, z ↦ ξ, ρ ↦ ∅ ⟧, ρ ↦ ∅ ⟧"
+        printExpression' nestedForm (config SALTY) `shouldBe` "⟦ obj ↦ ⟦ p ↦ ∅, q ↦ ∅, z ↦ ξ ⟧ ⟧"
     it
       "a phi-meet/phi-again chain renders identically under both sugar types"
       $ do
         let meetChain = ExFormation [BiTau (AtLabel "x") (ExPhiMeet Nothing 2 (ExPhiAgain (Just "a") 1 (ExDispatch ExXi (AtLabel "y"))))]
         printExpression' meetChain (config SWEET) `shouldBe` "\\phinoMeet{2}{ \\phinoAgain{a:1} }:x"
-        printExpression' meetChain (config SALTY) `shouldBe` "⟦ x ↦ \\phinoMeet{2}{ \\phinoAgain{a:1} }, ρ ↦ ∅ ⟧"
+        printExpression' meetChain (config SALTY) `shouldBe` "⟦ x ↦ \\phinoMeet{2}{ \\phinoAgain{a:1} } ⟧"
