diff --git a/FIX.org b/FIX.org
index d0b9333f..ca4acdca 100644
--- a/FIX.org
+++ b/FIX.org
@@ -6079,3 +6079,92 @@ table names. Harmless today only because check.ml refuses a dyn field in a
struct — the stopgap item 2 of the M2 queue lifts. Whoever lifts it has to
root this buffer, or a collection that runs inside the construction will not
see what has been built so far.
+* Closures, 2026-09-21 — two rulings, and what this lane built
+Two rulings, both in the author's words.
+
+The first, on what to build:
+
+ "do both, capture by value and handle escaping closures, allocated on the
+ GC side"
+
+This lane is the first half: capture by value into a stack environment,
+spec-memory.md's case 2, non-escaping only. The second half — an environment
+the collector allocates, and with it the escaping closure — is a separate lane,
+and every refusal this one prints names it.
+
+The second, on the calling convention, after the first design put an
+environment parameter on every Flan signature:
+
+ "while it's dyn first, static side should never have to pay the price for
+ the existence of the dyn side... if you fully opt out, for instance, using
+ --no-gc flag, then we should be operating under Odin/C semantics and never
+ paying any runtime costs"
+
+So there are two function types, Rust's and Swift's shape:
+
+ (Fn [T ...] R) captures; {code, env}; the common case, short name
+ (CFn [T ...] R) the bare address; one word; cannot capture
+
+** The name, ruled 2026-09-21
+CFn, because the C carries information rather than being decoration: a value
+with no environment is the only kind that could ever cross to C, and under the
+--no-conditions direction below it becomes literally a C function pointer. The
+name points at what the type is and at where it is going.
+
+Rejected: Closure, too long. Proc, because "procedure" is a word we disagree
+with Odin about. Fun and Func, because beside Fn they differ only in length,
+so nothing tells a reader which one captures. Fnptr, as ugly.
+
+The thing to be careful about, and it is written into the crossable refusal so
+a reader meets it where they would otherwise be misled: CFn is not the type
+for C interop *today*. A declare cannot take a function type at all, because a
+Flan signature ends with the transfer channel.
+
+** Nobody needs CFn, and one of the four reasons is the common one
+An Fn accepts everything a CFn does, so the narrow one is always reached for
+on purpose:
+
+1. handing a function to C — later, per the item below;
+2. a table of bare addresses;
+3. forbidding capture at a boundary, where the type is the statement;
+4. performance, which is likeliest in practice. A *named* function passed to
+ an Fn parameter goes through the widening thunk and pays an indirect hop
+ per call; a CFn parameter is a direct call. (map-in-place s double) is the
+ example. The prelude's four stay Fn on purpose — a capturing predicate is
+ what people want — so this is the cost someone would opt out of by writing
+ their own signature, not one the prelude should have avoided.
+
+** What the escaping lane inherits, and what it changes
+Unchanged by it: a (Fn ...) is two words; the environment is the last
+parameter; it is declared by exactly the bodies an Fn value can reach — a
+lifted literal in an Fn position, every handler clause, and the widening
+thunks. An ordinary defn declares none and is byte-for-byte what it was.
+
+Changed by it: where the environment points. It is a Make into a frame slot
+today and becomes a collector allocation; Check.escape_check goes away with it,
+and with it the refusals on returning, storing, pointing at and pushing a
+capturing value.
+
+One thing for the collector to know now rather than discover: a widening
+thunk's environment holds a *code pointer*, not a frame address and not a GC
+object. When env becomes collector-allocated the marker needs a way not to
+follow a thunk's.
+
+Also still refused and belonging to the second half: capturing a dyn. The
+struct dyn field is no longer refused — per-type descriptors landed — but a
+synthesised environment has no descriptor, so the refusal stands until it does.
+
+** Recorded, not built: CFn and C's convention
+A CFn is one word and is the right shape for a C callback, and it is still
+not one: a Flan function's signature ends with the transfer channel and a C
+caller knows nothing about one. Under a future --no-conditions flag a CFn
+signature could drop the channel and reach C's exact convention, which is the
+direction the author is interested in. The refusal in [crossable] names it.
+
+** Also worth an item: CFn in a struct or a fixed array
+no_zeroed_fn refuses a function value in any position ZII would conjure one,
+and it refuses a CFn for the same reason it refuses an Fn: a zeroed function
+value is a null pointer, which is the one zero that is not a value the type can
+have. But "a table of function pointers" is exactly what CFn is for, and that
+objection is about ZII rather than about capture — an (Option (CFn ...)) field
+is already legal and is the shape that works. Its own item.
diff --git a/docs/BUILT.md b/docs/BUILT.md
index 56c23650..bb08a35a 100644
--- a/docs/BUILT.md
+++ b/docs/BUILT.md
@@ -1861,15 +1861,17 @@ stack does *not* observe a redefinition of its own clause; the next entry to the
Which gives the two refusals, both by the house rule rather than by accident:
-- **A handler cannot see the establishing function's locals.** That is a closure with an explicit environment, so a
-reference to one is refused *for that reason* rather than reported as an unknown name. Globals and the condition are in
-scope, which is what the accumulation case needs.
+- **A handler cannot see the establishing function's locals.** *Superseded: it can, by value — see "Capture by value"
+below. What is still refused is a `set` into one, because a captured name is a copy.* The original note read: that is a
+closure with an explicit environment, so a reference to one is refused *for that reason* rather than reported as an
+unknown name; globals and the condition are in scope, which is what the accumulation case needs.
-What it needs is narrower than it looks, and worth getting right before anyone schedules it: a handler frame does not
-outlive the function that established it, so this is spec-memory.md's **case 2** — a non-escaping `fn` capturing by
-value into a stack environment — and *not* the escaping closure that plan.org's open decision #5 defers until a concrete
-use case. Case 2 is settled, and #5 says in as many words that without it "conditions are not worth building". So the
-biggest usability limit in conditions is not behind the thing that was just deferred.
+The reading that turned out to be right: a handler frame does not outlive the function that established it, so this is
+spec-memory.md's **case 2** — a non-escaping `fn` capturing by value into a stack environment — and *not* the escaping
+closure that plan.org's open decision #5 defers until a concrete use case. Case 2 was settled, was built, and brought
+the handler along with it for one struct field and one argument. #5 says in as many words that without it "conditions
+are not worth building", and the biggest usability limit in conditions was indeed never behind the thing that was
+deferred.
- **`return` inside a `handler-bind` body is refused.** The frames are popped on the way out and an early exit would
leave them on the stack pointing into a function that has gone. Same shape as `defer` inside a block.
@@ -2702,9 +2704,11 @@ spec together; neither is a cleanup, and until one is taken the word is carried
`(Map K V)` is built — see below. What is left: `drop` and with it the transitive move-only rule, recursive teardown,
and the refusal to construct a drop-carrying container against an allocator without `can-free`; `(Result T E)` and
`try`; generics; the macro expander.
-And the **accumulation pattern** — `(fn [c] (push errors c) ...)` over an enclosing Vec — which `Vec` does not buy:
-capture does not exist at all, and the spec's captured-`Vec`-by-pointer rule has never had to exist because every
-capturable type today is a value type. It is its own item and should be planned as one.
+And the **accumulation pattern** — `(fn [c] (push errors c) ...)` over an enclosing Vec — which `Vec` does not buy.
+Capture exists now (see "Capture by value" below) and this is still not it: capture is by *value*, so the `fn` would
+push into its own copy of the header and leave the enclosing one at the length it had. The spec's
+captured-`Vec`-by-pointer rule is exactly the thing that has still never had to exist. It is its own item and should
+be planned as one.
## `(Map K V)`, which is Odin's map
@@ -3817,9 +3821,10 @@ cannot share a name** — which is exactly what makes the bare name safe to read
`double` it could have meant instead, so the sharp quote would be punctuation answering a question the language does
not ask.
-A `Types.Fn` is one pointer. There is no environment beside it, so the type resolves to `ptr` and lays out as eight
-bytes, and a call through one is byte-for-byte the call a name would have produced — a Flan function's emitted
-signature is its parameters followed by the transfer channel whether it was reached by name or by pointer. That is
+A `Types.Fn` was one pointer when this lane landed. It is two words since capture, and the one-word version has a
+name of its own now — `(CFn [T ...] R)`; see "Capture by value" below. A call through either is the call a name
+would have produced, with the environment appended for a `Fn`: a Flan function's emitted signature is its
+parameters, then the transfer channel, and then the environment on the bodies that can be reached that way. That is
why a handler established across a `fold` still catches a signal raised by the function the fold was handed:
`programs/fn-values.flan` does exactly that, and it is the case that would fail if an indirect call skipped the
guard.
@@ -3840,11 +3845,8 @@ implemented.
**Refused, each with its own reason and its own program:**
-- **Capture does not exist** (`fn-capture.flan`). An `fn` is lifted into a function of its own and handed nothing but
- its parameters; a reference to a local of the enclosing function is refused by name. This is the same refusal a
- handler clause has always carried, and the two now share one message with the construct's name in it.
- `spec-memory.md`'s capture cases, and **escaping closures with them, stay deferred** — deliberately, and this is
- what keeps a function value a bare code address that cannot outlive anything.
+- **Capture does not exist.** *Superseded — see "Capture by value" below. It exists, the program that was this
+ refusal's witness now runs, and what is refused in its place is the **escape**.*
- **An `fn` with nothing to say what it takes** (`fn-no-type.flan`), above.
- **A position that would zero one** (`fn-in-struct.flan`): a struct field, a global, a fixed array's element,
`(zeroed)`. ZII fills an omitted field with all-bytes-zero, and **a zeroed function value is a null pointer, which
@@ -3855,9 +3857,276 @@ implemented.
past its length and `flan_map_alloc` zeroes only the hash run, so neither conjures an element nobody pushed or
put. A function value as a map *key* is refused already, by `Types.keyable` — hashing an address is a different
operation from hashing what it points at.
-- **A foreign function's address** (`fn-extern.flan`). A Flan function's signature ends with the transfer channel and
- a C one does not, and an aggregate crossing the boundary is flattened by a generated shim the raw symbol knows
- nothing about. Wrap it in a `defn` and pass that.
+- **A foreign function's address** (`fn-extern.flan`). A Flan function's signature ends with the environment and the
+ transfer channel and a C one does not, and an aggregate crossing the boundary is flattened by a generated shim the
+ raw symbol knows nothing about. Wrap it in a `defn` and pass that. Capture widened this gap rather than closing
+ it: a Flan function value is now two words and a C symbol is one.
+
+## Capture by value, and what "non-escaping" had to mean
+
+`spec-memory.md`'s **case 2**, which the section above listed as the headline refusal and which is now the headline
+feature. This compiles:
+
+```
+(let [bonus 10]
+ (apply2 (fn [x] (+ x bonus)) 5))
+```
+
+`bonus` is **copied** into an environment on the enclosing function's frame at the instant the `fn` value is made,
+and the lifted body reads the copy. Not a reference: `fn-capture.flan` changes the local through a pointer *after*
+the value exists and *before* it is called, and the `fn` still answers with the old one. That test is the whole
+claim, and it is the one no evaluation order can fake.
+
+### Two function types, because the static side does not pay for the dynamic side
+
+```
+(Fn [i32] i32) ; captures; {code, env}; the common case
+(CFn [i32] i32) ; the bare code address; one word; cannot capture
+```
+
+The forcing constraint first. A callee that takes a `(Fn [i32] i32)` and calls it knows nothing about where the
+value came from — `fold` is handed a value and calls it — so **the environment has to travel with the value** or
+there is nowhere to put it. A `Fn` is therefore `{code, env}`: sixteen bytes, classified as an aggregate in both
+backends exactly as a slice is.
+
+The first design put an environment parameter on **every** Flan signature, uniform for the reason the transfer
+channel is uniform. The author ruled against it, and the ruling is the principle rather than the case: *"while it's
+dyn first, static side should never have to pay the price for the existence of the dyn side… if you fully opt out,
+for instance, using `--no-gc`, then we should be operating under Odin/C semantics and never paying any runtime
+costs."* A uniform environment taxes every function in every program for a feature most of them never use.
+
+So there are two types, Rust's and Swift's shape. `Fn` keeps the short name because it is what almost every
+higher-order signature wants; `CFn` is the narrow one.
+
+**The `C` is information and not decoration**, which is what settled the name. A value with no environment is the
+only kind that could ever cross to C, and under the `--no-conditions` direction FIX.org records — where a signature
+that cannot transfer drops the transfer channel too — one becomes literally a C function pointer. The name points at
+what the type *is* and at where it is going. `Closure` was rejected as too long; `Proc` because "procedure" is a
+word this language disagrees with Odin about; `Fun` and `Func` because beside `Fn` they differ only in length, so
+nothing tells a reader which one captures; `Fnptr` as ugly.
+
+**It is not a capability today, and the diagnostic says so.** A `declare` cannot take a function type at all — a
+Flan signature ends with the transfer channel and a C caller knows nothing about one — so anyone reaching for `CFn`
+straight after writing a `declare-c` is reaching too early. `crossable`'s refusal names that in as many words: *"the
+C in CFn is about having no environment, which is what a C function pointer would need, and not about crossing
+today."*
+
+**And nobody ever needs it.** `Fn` accepts everything a `CFn` does, so the narrow one is reached for on purpose, for
+one of four reasons:
+
+1. handing a function to C — later, as above;
+2. a table of bare addresses;
+3. forbidding capture at a boundary, where the type is the statement;
+4. **performance, which is likeliest in practice.** A *named* function passed to an `Fn` parameter goes through the
+ widening thunk and pays an indirect hop per call; a `CFn` parameter is a direct call. `(map-in-place s double)`
+ is the example — and the prelude's four stay `Fn`, because a capturing predicate is exactly what people want.
+
+**An ordinary `defn` keeps its exact signature.** Verified rather than asserted: the LLVM for `calc-me.flan` and
+fourteen corpus programs was diffed against the same compiler without this lane. Exactly three kinds of difference
+appear, and no fourth:
+
+- two type declarations in the preamble — `%fnv`, and `%handler`'s new `env` field;
+- the prelude's `sort-by-slice-u8`, whose *parameter* is now `%fnv` because it is declared `(Fn [$t $t] bool)` and
+ pays two words for the value it asked for; and the lifted literal it is handed, which gains a trailing `ptr %env`
+ because an `Fn` value can reach it;
+- in a program with a `handler-bind`, its clauses gain the same trailing `ptr %env` — every clause declares one,
+ see below.
+
+**No ordinary `defn` gained a parameter, in any program.** `flan_rt.c`'s hash and equality typedefs are untouched;
+`main`, the macro thunk, the startup call and the reload thunk emit the calls they always emitted.
+
+### The environment is the last argument, on exactly the bodies an `Fn` can reach
+
+The environment is the last parameter, after the transfer channel, and it is declared by **exactly the bodies a
+`(Fn ...)` value can reach**: a lifted `fn` literal written into an `Fn` position, capturing or not; every handler
+clause, because `flan_signal` passes one to whichever clause matched and cannot know which of them captured; and the
+widening thunks below, which exist to read it. Nothing else declares it, which is where an ordinary `defn` keeps
+costing nothing.
+
+So **every indirect call is exactly typed** and nowhere does a caller pass an argument the callee did not declare.
+
+That was not the first attempt. The first put the environment last and let a body that never asked for one simply
+ignore the register it arrived in — legal under SysV, where argument N is classified from arguments 1..N alone, and
+exactly **Swift's thin-vs-thick convention**, where a thin function converts to a thick one by pairing with a null
+context the thin body ignores. It works on x86-64 and it is dead on **wasm32**, where `call_indirect` compares the
+signature at the call site: a spare argument is a trap, not a register nobody reads. `test_web`'s two cases and the
+headless `sand` build failed with *"null function or function signature mismatch"*, and the honest reading is that
+being exactly typed is checkable by a verifier rather than argued from a calling convention, which is the better
+property to have wanted.
+
+**So there is one adapter, and it is per *signature* rather than per function.** `CFn` → `Fn` is `Tast.Thicken`,
+and the pair it builds is `{thunk, the address}`: the thunk's code, with the bare address stored where an
+environment would be. The thunk — `thick/
(Handle T)i64(Ptr T)(Option T)Some / None(Fn [T ...] R)(Fn [T ...] R)(CFn [T ...] R)C is what a C function pointer would need, not a way to reach C todayAllocator$t