All papers
EssayBioTechChemistryMedicineEngineering

Target Detection as the Rate-Limiting and Specificity-Limiting Step in CRISPR Genome Editing

Oleksii Vergunenko

Published August 31, 2026· Updated September 16, 2026· 37 min read

CRISPR-Cas genome editing is usually described as a cutting technology. It is more accurately described as a search technology with a cutting terminus. The chemistry of phosphodiester hydrolysis is unremarkable; nucleases have performed it for billions of years. What is remarkable — and what remains unsolved — is that a single ribonucleoprotein must locate one 20-base-pair address among 6.4 × 10⁹ candidate positions, decide whether it has arrived, and commit irreversibly, using no external energy source during the decision.

This paper argues that the three canonical problems of the field — off-target editing, slow or incomplete on-target editing, and the difficulty of verifying either — are not three problems. They are three projections of one problem: the target detection problem, and they are coupled by conservation-like relations that make independent optimization impossible.

We establish four quantitative results. First, an information-budget accounting showing that a 20-nt spacer plus NGG PAM carries 42 bits against a 32.6-bit requirement, but that the effective information Cas9 actually reads is 22–30 bits — a deficit of 3–10 bits, which is precisely the deficit that manifests as 10²–10³ genome-wide decoy sites per guide. Second, a search-time budget showing that the ~1.3 mM effective concentration of PAM decoys in a human nucleus imposes a hard ceiling on useful PAM affinity, making weak PAM binding a design requirement rather than a design flaw. Third, a discrimination ceiling theorem: for a single-checkpoint, energy-unsupplied proofreader, the specificity gain per mismatch is bounded above by exp(ε/k_BT), and approaching that bound requires on-target efficiency to approach zero. Fourth, a detection-floor argument showing that most published claims of "no detectable off-target activity" are statements about assay sensitivity near 10⁻³, while therapeutic cell doses are 10⁸–10⁹ — leaving a five-to-six order-of-magnitude blind spot that no amount of sequencing depth alone can close.

The unifying frame is a permission structure. None of these constraints is a prohibition on precise editing. Each one names an exact currency — search time, copy-number × exposure, discarded near-misses, sequencing error rate, external free energy — and states the price. Every fidelity advance in the field's history, without exception, has been a payment in one of these currencies. We make that claim falsifiable and specify the experiment that would break it.

Every load-bearing assertion is tagged [T] theorem/derived, [E] empirical/established, [C] conjecture, or [O] open problem, and consolidated in a Claims Ledger (§10).

An essay. A thesis put forward by its author — an argument, specification, or design — and not a verified result.

1. Introduction: the wrong noun

The dominant metaphor for CRISPR is surgical. "Molecular scissors." The metaphor is not merely imprecise; it is inverted. It places the difficulty at the blade, where there is none, and hides the difficulty in the approach, where all of it lives.

Consider the actual sequence of events. A Cas9 ribonucleoprotein (RNP) enters a nucleus. It has one job: find a specific 20-bp sequence. It does not know where the sequence is. It has no map, no index, no directory, no ATP-driven motor to walk the genome systematically. It has thermal motion and a 3-bp recognition motif. The genome it must search is approximately 2 metres of DNA compacted into a sphere roughly 10 μm across, of which ~75–80% is wrapped around nucleosomes and physically inaccessible [E].

The RNP will collide with DNA on the order of 10⁵–10⁷ times before it finds its target. Each collision is a decision: probe or leave. Most of those decisions must be made in under 10 milliseconds, or the search never completes. And each decision is made on radically incomplete information — at the moment of collision, the RNP has read at most 3 bases.

Then, having found something that looks right, it must decide whether it is right, using a discrimination mechanism that runs on no fuel and must reach a verdict before performing an irreversible act.

This is the target detection problem. The cut is an afterthought.

1.1 The permission-structure reading

There is a natural way to state the apparent impossibility:

You cannot reliably address one site in 6.4 × 10⁹ using a 20-nucleotide guide and a 3-nucleotide motif, in a crowded nucleus, in minutes, with thermal energy alone.

Stated that way, it sounds like a prohibition. It is not. It is a specification with an unpriced term. The correct reading identifies the currency:

Apparent impossibility Actual specification Currency paid "Too many decoys to search" Search completes if PAM binding is weak enough that decoys do not sequester the enzyme Enzyme copy number × exposure time "Guide has enough bits but still misfires" The machine does not read all 40 bits at equal weight; only the seed is fully priced Effective address length; distal bases are nearly free "Binding energy cannot discriminate a 1-in-20 mismatch" Correct — equilibrium binding cannot. Kinetic gating can, at a cost On-target rate; discarded near-misses "Cannot exceed single-checkpoint discrimination" Correct for one checkpoint. Add an independent second gate External free energy, or a second molecular event (dimerization, nicking) "Cannot prove absence of off-targets" Correct at 10⁻³ floor. Not correct in principle Sequencing chemistry error rate; molecular consensus depth

Every row of that table is a place where the field has already paid, or where it has not yet noticed there is a bill. The rest of this paper prices each one.

1.2 What this paper does not claim

We do not claim that CRISPR is unsafe, or that clinical editing is unjustified. Approved therapies exist and work [E]. We claim something narrower and more structural: that the verification of specificity is bounded by physics and chemistry in ways that are not fully acknowledged in the standard reporting conventions, and that the boundedness is quantifiable rather than vague.

2. The addressing problem

2.1 Information budget

Let L=3.2×109 be the haploid human genome length. A target may lie on either strand, so the address space contains 2L=6.4×109 candidate positions.

Bits required to specify one position uniquely:

  • 𝐼
  • req
  • =
  • log
  • 2
  • (
  • 2
  • 𝐿
  • )
  • =
  • log
  • 2
  • (
  • 6.4
  • ×
  • 10
  • 9
  • )
  • =
  • 32.58
  • bits
  • I
  • req

=log 2

(2L)=log 2

(6.4×10 9 )=32.58 bits

Bits nominally carried by an SpCas9 guide:

  • 𝐼
  • nom
  • =
  • 2
  • 𝑛
  • spacer
  • +
  • 𝐼
  • PAM
  • =
  • 2
  • (
  • 20
  • )
  • +
  • 2
  • =
  • 42
  • bits
  • I
  • nom

=2n spacer

+I PAM

=2(20)+2=42 bits

where the NGG PAM contributes log⁡216=4 bits over a random trinucleotide... more carefully: NGG constrains 2 of 3 positions, so IPAM=2×2=4 bits. (We use 4 bits below; the 42-bit total is unchanged only if we count the spacer as 40. Using IPAM=4: Inom=44 bits.)

Nominal margin: 44−32.6=11.4 bits ≈ a factor of 2,700. On paper, an SpCas9 guide is massively over-specified. Uniqueness should be trivial.

[T-1] The nominal information content of a 20-nt spacer plus NGG PAM exceeds the human genome addressing requirement by ~11 bits. Sequence information is not the limiting resource.

This is the first and most important reframing. The off-target problem is not an information-shortage problem. The information is there. It is not read.

2.2 The effective address: positional weighting

Cas9 does not weight the 20 spacer positions equally. Mismatches in the PAM-proximal "seed" (roughly positions 1–10 counting from the PAM) are strongly rejected; PAM-distal mismatches are frequently tolerated [E]. Single-molecule FRET measurements quantify this directly: the dissociation rate of Cas9–RNA from a fully matched target is below 0.006 s⁻¹, but exceeds 2 s⁻¹ when mismatches are introduced near the PAM — a >300-fold shift in rejection kinetics driven by mismatch position, not mismatch count [E].

Define a positional weight wi∈[0,1] for each spacer position, representing the fraction of the 2 bits at that position that actually contributes to discrimination:

  • 𝐼
  • eff
  • =
  • 2
  • ∑
  • 𝑖
  • =
  • 1
  • 20
  • 𝑤
  • 𝑖
  • +
  • 𝐼
  • PAM
  • I
  • eff

=2 i=1 ∑ 20

w i

+I PAM

Empirical mismatch-tolerance profiles imply wi≈1 for i≲10 and decaying to wi≈0.1– 0.4 0.4 for i≳14 [E, approximate]. Integrating:

  • 𝐼
  • eff
  • ≈
  • 2
  • (
  • 10
  • +
  • 4
  • ×
  • 0.3
  • )
  • +
  • 4
  • ≈
  • 26
  • bits
  • I
  • eff

≈2(10+4×0.3)+4≈26 bits

with a plausible range of 22–30 bits depending on guide sequence, chromatin context, and nuclease variant.

[T-2] The effective address length of SpCas9 is 22–30 bits against a 32.6-bit requirement. The system is under-specified by 3–10 bits, corresponding to an expected decoy multiplicity of 23 to 210, i.e. 8 to 1,000 genome-wide sites indistinguishable to the enzyme's discrimination machinery.

This single inequality — Ieff<Ireq

— is the origin of off-target editing. Everything else is detail.

2.3 Decoy census

We can compute the decoy population directly. The number of distinct 20-mers at exactly k mismatches from a reference is ( 20 𝑘 ) 3 𝑘 ( k 20

)3 k , out of 420=1.10×1012. Multiplying by the number of NGG-flanked positions in the genome ( 2L/16=4.0×108):

Mismatches k Distinct variants Expected genome-wide sites Cumulative ≤ k 0 1 0.00036 0.0004 1 60 0.022 0.022 2 1,710 0.62 0.64 3 30,780 11.2 11.8 4 392,445 143 155 5 3,767,472 1,370 1,525 6 28,256,040 10,300 11,800

Reading the table. At k=0 the expected count is 0.0004 — a perfect-match site is essentially unique, confirming T-1. But Cas9 routinely cleaves at 3–5 mismatches when those mismatches are PAM-distal [E]. At that tolerance, the expected decoy population is 10²–10³ sites per guide.

Observed off-target counts from unbiased cellular assays are typically 0–150 per guide [E] — one to two orders of magnitude below the naive census. The gap is real and has three components, which we quantify in later sections: chromatin inaccessibility (§7), sub-threshold kinetics that bind but never cleave (§5), and assay detection floors (§6). The third component is the dangerous one, because it is a property of the measurement, not of the biology.

[C-1] The discrepancy between the ~155-site census at k≤4 and the typical 0–150 observed sites is not primarily explained by biology. We conjecture that the dominant term is the detection floor, and that duplex-consensus resequencing of the full k≤4 census would recover a substantial fraction of currently "undetected" sites at frequencies of 10⁻⁴–10⁻⁶.

2.4 Bulges: the term everyone drops

The census above assumes ungapped alignment. RNA and DNA bulges — insertions or deletions in the heteroduplex — are tolerated by Cas9 and produce validated off-target cleavage [E]. Allowing a single bulge expands the candidate space by roughly a factor of 2×20×3≈120 in the worst case, though thermodynamic penalties suppress most of them.

Most in-silico off-target predictors handle bulges poorly or not at all, and most experimental validation panels are constructed from those predictors. This creates a closed loop: predictors define the panel, the panel confirms the predictor, and the bulge class never enters either.

[O-1] The genome-wide contribution of bulged off-target sites to total unintended editing is unquantified. No unbiased assay has been analyzed with a gapped-alignment pipeline at therapeutic-scale sensitivity.

2.5 The PAM-relaxation trade

Engineered PAM-relaxed variants (VQR, EQR, VRER, SpG, SpRY) expand the addressable fraction of the genome [E]. The cost is exactly computable. Relaxing NGG (frequency 1/16) to NRN (~1/8 in practice for the most permissive variants) multiplies the PAM-site population by ~8:

NPAM:4.0×108→3.2×109

Every row of the decoy table scales by the same factor. A near-PAMless nuclease at k≤4 faces ~1,200 expected decoys instead of ~155.

[T-3] PAM relaxation purchases addressability at a directly proportional cost in decoy census and in search-time tax (§4). The exchange rate is linear in PAM degeneracy. There is no free relaxation.

Conversely, Cas12a's TTTV PAM (frequency ~3/256 ≈ 1/85 per strand) yields a decoy census ~5× smaller than SpCas9's before any consideration of its longer 23-nt spacer or its distinct kinetics [T]. Part of Cas12a's reported specificity advantage is not mechanism at all — it is arithmetic.

3. Interlude: what the enzyme actually knows

Before analyzing search kinetics, it is worth being concrete about the epistemic state of the enzyme at each moment.

Stage Bases read Bits acquired Cumulative Decision Free diffusion 0 0 0 — Nonspecific dsDNA contact 0 0 0 slide or release PAM engagement 3 4 4 unwind or release Seed R-loop (1–10) 10 20 24 propagate or reject Full R-loop (11–20) 20 40 44 dock HNH or reject Cleavage — — — irreversible

The critical observation: at the moment the enzyme commits to unwinding the duplex — an energetically expensive act — it has acquired 4 bits against a 32.6-bit requirement. It is operating at 12% of the information it needs. The unwinding decision is therefore made almost blind, and must be cheap enough to be made 10⁸ times.

This is why PAM binding must be weak. Not because weak binding is elegant, but because a decision made on 4 bits must be reversible on a millisecond timescale or the search deadlocks. §4 makes this quantitative.

4. The search problem

4.1 The diffusion-limited ceiling

For a small target of radius b≈1 nm and an RNP diffusion constant D≈3μm2 s−1, the Smoluchowski association rate is

k3D=4πDbNA=2.3×107

M − 1 s − 1 k 3D

=4πDbN A

=2.3×10 7 M −1 s −1

A single target locus in a nuclear volume of ~500 μm³ has an effective concentration of

ctarget=1VNA=3.3pMctarget

= VN A

1

=3.3 pM

giving an idealized search time for a single RNP molecule of

τideal=1k3Dctarget=1.3×104s≈3.7hoursτideal

= k 3D

c target

1

=1.3×10 4 s≈3.7 hours

[T-4] Under pure 3D diffusion with no decoys and no chromatin, one Cas9 RNP requires ~4 hours to locate one genomic target. This is the floor, not the estimate.

4.2 The decoy tax

The nucleus is not empty. Total DNA concentration is ~21 mM base pairs. The NGG PAM population is

cPAM=2L/16VNA=1.33mMcPAM

= VN A

2L/16

=1.33 mM

If PAM engagement has dissociation constant KDPAM

, the fraction of RNP molecules free at any instant is

ree=(1+cPAMKDPAM+cnsKDns)−1ffree

=(1+ K D PAM

c PAM

+ K D ns

c ns

) −1

and the search time is inflated by 1/ffree

:

KDPAM
cPAM/KD

Search-time multiplier 10 mM 0.13 1.1× 1 mM 1.3 2.3× 100 μM 13 14× 10 μM 133 134× 1 μM 1,330 >1,300×

[T-5] (PAM affinity ceiling). The genome-wide PAM concentration of ~1.3 mM imposes an upper bound on useful PAM affinity. A nuclease with micromolar PAM affinity would spend >99.9% of its residence time on decoys and would not complete a genome search within a cell cycle. Weak PAM binding is not a compromise; it is a hard requirement derived from decoy concentration.

This is the cleanest permission-structure result in the paper. An engineer looking to improve Cas9 might reasonably propose tightening PAM recognition to reduce spurious engagement. The calculation says the opposite: tighten PAM affinity and the enzyme never arrives. The constraint does not prohibit high-affinity recognition — it specifies exactly where the affinity must be spent, which is on the heteroduplex, after commitment, not on the motif, before it.

4.3 Facilitated diffusion: how much does sliding buy?

Whether Cas9 uses one-dimensional sliding was contested. Early DNA-curtain work found predominantly 3D collision with no long-range sliding [E]. Subsequent single-molecule FRET and ensemble kinetics established short-range 1D diffusion with a sliding length of ~20–27 bp and a mean nonspecific dwell of ~11 ms on PAM-free duplex, accelerating target location by roughly 10-fold [E]. The sliding is biased 3′→5′ along the non-target strand under physiological salt, mediated by nonspecific contacts ~8 bp downstream of the PAM [E].

Applying the Halford–Marko combined-mode expression, the search time scales as

τsearch∼VD
lsl⋅LDNAsomething(schematically)

⇒

𝜏 ∝ 1 ℓ 𝑠 𝑙 τ search

∼Dlsl

V

⋅ something L DNA

(schematically)⇒τ∝lsl

1

with the antenna enhancement of order ℓ 𝑠 𝑙 / 𝑏 ≈ 27 bp / 3 bp ≈ 9 ℓ sl

/b≈27 bp/3 bp≈9, consistent with the measured ~10× [E, T].

[T-6] Facilitated diffusion contributes an order-of-magnitude search acceleration for Cas9 — not the 10²–10³ enhancement seen for classical sliders like lac repressor. Cas9 is a poor facilitated diffuser. It compensates by copy number.

4.4 The full budget

Assembling: with a 10× sliding benefit, a ~2–14× decoy tax, and a ~4× accessibility penalty from chromatin (§7):

𝜏 search ≈ 𝜏 ideal 𝑁 RNP × decoy tax × chromatin penalty sliding gain τ search

≈ N RNP

τ ideal

× sliding gain decoy tax×chromatin penalty

For NRNP=104 (a typical RNP electroporation) and mid-range parameters:

τsearch≈1.3×104s104×5×410≈2.6sτsearch

≈ 10 4 1.3×10 4 s

× 10 5×4

≈2.6 s

For NRNP=102 (a poorly delivered viral vector, or an RNP that has been mostly degraded):

τsearch≈260sτsearch

≈260 s

[T-7] (The dose–specificity coupling). On-target search time scales as 1/NRNP

. But off-target cleavage probability scales as NRNP×texposure

, because off-target sites are reached by the same search process and are cleaved by the same enzyme with a lower per-encounter success probability. Therefore:

on-target completeness ↑

⇔

𝑁 RNP 𝑡 exp ↑

⇔

off-target burden ↑ on-target completeness↑⇔N RNP

t exp

↑⇔off-target burden↑

Dose and specificity are not independent knobs. They are the same knob with a sign flip.

This is the mechanistic basis of the empirically well-known observation that RNP delivery (short exposure, rapid degradation) outperforms plasmid delivery (sustained expression) on specificity at matched on-target efficiency [E]. The paper's contribution here is to show that this is a theorem about the search process, not a delivery-modality quirk.

5. The discrimination problem

5.1 Why equilibrium binding cannot work

Naively, discrimination should follow from binding energy: the correct target binds more tightly, so it is preferentially cleaved. The ratio would be

ηeq=exp⁡(ΔΔG/kBT)

For one RNA:DNA mismatch, ΔΔG≈1–3 kcal/mol ≈ 1.7–5 kBT, giving ηeq≈5–150.

But this argument fails for a reason that is easy to miss. The full 20-bp R-loop has ΔG≈−20 to −30 kcal/mol. Both the target and a 4-mismatch decoy are bound far past saturation. In the saturated regime, the occupancy ratio collapses toward 1 even when the affinity ratio is large. Equilibrium binding discriminates affinity, but cleavage responds to occupancy, and occupancy is a saturating function of affinity.

[T-8] Equilibrium binding discrimination is structurally incapable of delivering the required specificity, because the on-target complex is saturated. Discrimination must be kinetic: it must occur during R-loop propagation, before saturation is reached.

This is a genuine no-go, and it reads as a specification: the decision must be made in the transient, not at the endpoint.

5.2 R-loop propagation as a biased random walk

Model R-loop formation as a one-dimensional walk on states n=0,1,...,N where n is the number of base pairs of heteroduplex formed, n=0 is PAM-bound-but-unwound, and n=N=20 triggers the irreversible conformational commitment (HNH docking and cleavage). Let G(n) be the free energy of state n relative to n=0.

The probability that a walk starting at n=0 reaches N before dissociating is the classical gambler's-ruin splitting probability:

Pcleave=[1+∑j=1Ne
G(j)/kBT]−1≡11+SPcleave

=[1+ j=1 ∑ N

e G(j)/k B

T ] −1 ≡ 1+S 1

where S=∑jeG(j)/kBT.

Now introduce a mismatch at position m. It raises the free energy of every state j≥m by ϵ. Writing S<=∑j<meG(j)/kBT and S≥=∑j≥meG(j)/kBT:

η(m)=PonPoff=1+S<+eϵ/kBTS≥1+S<+S≥=1+(eϵ/kBT−1)S≥1+S<+S≥η(m)=Poff

P on

= 1+S <

+S ≥

1+S <

+e ε/k B

T S ≥

=1+(e ε/k B

T −1) 1+S <

+S ≥

S ≥

This equation derives the seed rule. For a downhill (favorable) propagation landscape, G(j) decreases with j, so eG(j)/kBT is dominated by small j. Therefore:

  • PAM-proximal mismatch (small m):
  • S≥

contains the dominant terms → S≥/(1+S)→1 → η→eϵ/kBT (maximal discrimination) PAM-distal mismatch (large m): S≥

contains only subdominant terms → S≥/(1+S)→0 → η→1 (no discrimination)

[T-9] The seed effect is not a property of protein contacts or structural recognition. It is a topological consequence of a downhill free-energy landscape in a splitting-probability problem. Any nuclease with a monotonically favorable R-loop propagation landscape will exhibit a seed, regardless of its structure.

This result is consistent with, and here derived independently of, published hybridization-kinetics models of CRISPR off-targeting [E].

5.3 The discrimination ceiling theorem

Read the expression for η(m) again. The prefactor (eϵ/kBT−1) is a hard bound:

η(m)≤eϵ/kBTforallmη(m)≤eϵ/kB

T for all m

Equality requires S≥/(1+S)→1, i.e. S<→0 and S≫1. But Pon=1/(1+S), so:

𝑆 ≫ 1

⇒

Pon→1S→0S≫1⇒Pon

→ S 1

→0

[T-10] (Discrimination ceiling). For a single-checkpoint proofreader with no external free-energy input, the per-mismatch discrimination factor is bounded by ηmax⁡=eϵ/kBT, and

η→ηmax

⟺

Pon→0η→ηmax

⟺P on

→0

There is no landscape shape that achieves maximal discrimination at finite on-target efficiency. Specificity and activity are conjugate variables in a single-checkpoint nuclease.

With ϵ≈2–4 kBT, ηmax⁡≈7–55 per effectively read mismatch. A 4-mismatch decoy, if all four fall in the dominant region, is suppressed by ∼103 – 106. If they fall PAM-distal, suppression approaches 1.

This theorem explains, in one line, the entire empirical record of high-fidelity variants. eSpCas9, SpCas9-HF1, HypaCas9, evoCas9, Sniper-Cas9 all work by the same mechanism: removing stabilizing nonspecific contacts, which raises the R-loop propagation landscape (increases S), which increases η and decreases Pon

[E, T]. The observed pattern — improved specificity accompanied by reduced or context-dependent on-target activity, particularly for suboptimal guides — is not a series of independent engineering compromises. It is T-10, measured five times.

[C-2] All existing high-fidelity SpCas9 variants lie on a single one-parameter curve in (η,Pon) space, parameterized by the mean shift in R-loop propagation free energy. Variants that appear to beat the curve are either (a) exploiting a second checkpoint, or (b) measured with insufficient off-target sensitivity.

C-2 is directly falsifiable and is the central testable claim of this paper.

5.4 Escaping the ceiling: the price of a second gate

Hopfield–Ninio kinetic proofreading achieves discrimination ηn for n sequential checkpoints, each with an irreversible discard branch, at a cost of one free-energy quantum per discard.

Cas9 has essentially one checkpoint: the conformational transition in which the HNH nuclease domain docks onto the scissile phosphate, allosterically gated by REC3 sensing of the PAM-distal heteroduplex [E]. And Cas9 hydrolyzes no ATP during interrogation. Its only irreversible step is cleavage itself — which is after the decision, not before it. Cas9 is an energy-unsupplied, single-checkpoint proofreader. T-10 applies with full force.

The escape routes are therefore predictable from the theory, and all of them have been found empirically:

  • Strategy Mechanism in T-10 terms Measured gain Price
  • Paired nickases (Cas9n D10A ×2) Two independent searches must both succeed:
  • η1η2

50–1,500× [E] 2 guides; reduced efficiency; risk of unpaired nicks dCas9–FokI dimers Obligate dimerization = independent AND gate ~10³–10⁴× [E] Strict spacing constraints; narrow targeting range Truncated guides (17–18 nt) Reduces S<

, steepens effective landscape 10–10³× [E] Lower on-target activity (T-10 again) High-fidelity variants Raises landscape; increases S 10–10³× [E] On-target loss, guide-dependent RNP + short exposure Reduces Ntexp⁡

(T-7), not η 2–20× [E] Delivery constraints Anti-CRISPR timed shutoff Truncates texp⁡

variable [E] Timing control required

[T-11] Every specificity gain exceeding the single-checkpoint ceiling eϵ/kBT per mismatch has been achieved by introducing a second independent molecular requirement — a second guide, an obligate dimerization, or a temporal gate. There is no counterexample in the literature.

T-11 is the paper's strongest structural claim and the one most worth attacking.

5.5 The unexploited currency

T-10's escape clause names a currency the field has not spent: external free energy during interrogation.

Type I CRISPR systems do this. Cascade surveils, and Cas3 — an ATP-dependent helicase-nuclease — is recruited only upon a validated R-loop. The ATP hydrolysis provides exactly the irreversible discard energy that Hopfield's scheme requires. Type I systems are reported to exhibit high specificity, and the theory says they should [E, C].

[C-3] An engineered Cas9 coupled to an ATP-consuming conformational cycle — a fused helicase, a chaperone-like reset, or a designed futile cycle that discards non-committed complexes — could exceed the single-checkpoint ceiling without the on-target loss mandated by T-10, because the discard energy comes from ATP rather than from the binding landscape. This is the one route to breaking the specificity–activity conjugacy that does not require a second guide.

[O-2] No engineered ATP-driven proofreading module has been demonstrated for a Class 2 nuclease. Whether the required conformational coupling is achievable by fusion or requires ground-up design is open.

6. The detection problem

Sections 2–5 concern what the enzyme does. This section concerns what we can know about what it did. It is the harder problem, and it is where the field's reporting conventions are weakest.

6.1 A taxonomy with failure modes Class Methods Sensitivity Blind to In silico Cas-OFFinder, CFD, Elevation, CRISPR-Net, DeepCRISPR, GuideScan2, language-model predictors N/A (ranking only) Bulges (mostly); chromatin; novel motifs; anything outside training distribution Cell-free biochemical CIRCLE-seq, SITE-seq, Digenome-seq, ONE-seq, CHANGE-seq Very high (~10⁻⁵ of input DNA) Chromatin entirely; over-predicts by 10–100× Cell-based DSB capture GUIDE-seq, DISCOVER-seq, BLISS, TAG-seq ~0.1% [E] Rare events; non-DSB editors; structural variants Targeted deep sequencing rhAmpSeq, amplicon NGS ~0.1–1% (error-limited) Anything not on the panel; large events (dropout) Consensus sequencing Duplex-seq, UMI consensus, CRISPR-amplification enrichment 10⁻⁶–10⁻⁷ Anything not on the panel; cost-limited to small panels Structural CAST-seq, LAM-HTGTS, optical mapping, long-read Moderate Small indels Single-cell scDNA-seq of edited populations Cell-limited Genome-wide depth per cell

The critical structural observation: no method in this table is simultaneously genome-wide, chromatin-aware, and sensitive below 10⁻⁴. The cell-free assays give you genome-wide and sensitive but not chromatin-aware. The cell-based assays give you genome-wide and chromatin-aware but not sensitive. The consensus methods give you chromatin-aware and sensitive but not genome-wide — they require a candidate list, which comes from the methods that lack sensitivity.

[T-12] (Detection trilemma). Genome-wide coverage, chromatin fidelity, and sub-10⁻⁴ sensitivity cannot currently be obtained simultaneously. Every published specificity claim occupies one vertex of this triangle and is silent about the other two.

6.2 The detection floor is set by chemistry, not depth

A common misconception is that off-target sensitivity is a sequencing-depth problem: sequence deeper, see rarer events. This is false past a certain point.

Standard Illumina per-base error rates are ~10⁻³ per base. To call a variant at allele frequency f, the signal must exceed the error background. With N reads, the observed variant count is Binomial ( 𝑁 , 𝑓 + 𝜖 ) Binomial(N,f+ϵ) where ϵ is the error rate. As N→∞, the estimate converges to f+ϵ, not to f. Depth reduces variance; it does not reduce bias.

𝑓 detectable ≳ 𝜖 regardless of 𝑁 f detectable

≳ϵregardless of N

The floor is broken only by changing ϵ: unique molecular identifiers and duplex consensus require independent observation of both strands of an original molecule, driving ϵ to ~10⁻⁷–10⁻⁹ [E]. Prediction-guided enrichment approaches — using the nuclease itself to destroy wild-type molecules and enrich mutant ones before amplification — report 1.6–984-fold sensitivity gains over standard targeted amplicon sequencing [E].

[T-13] Off-target detection sensitivity is bounded below by the sequencing chemistry's error rate, not by read depth. Claims of "no detectable off-targets" from standard NGS are statements about a ~10⁻³ floor and carry no information about the 10⁻⁴–10⁻⁷ regime.

6.3 The therapeutic gap

This matters because of a simple arithmetic mismatch between assay scale and dose scale.

An autologous ex vivo cell therapy infuses on the order of 3×106 CD34⁺ cells/kg, i.e. ∼2×108 cells for an adult. Consider an off-target edit occurring at frequency f:

Off-target frequency f Detectable by Edited cells in a 2×10⁸ dose 10⁻¹ Anything 2 × 10⁷ 10⁻³ GUIDE-seq (marginal) 2 × 10⁵ 10⁻⁴ Duplex-seq only, if on panel 2 × 10⁴ 10⁻⁶ Duplex-seq only, if on panel 2 × 10² 10⁻⁸ Nothing currently 2

[T-14] A therapeutic dose delivers 10⁵ cells carrying an off-target edit that sits exactly at the detection floor of the most widely used unbiased assay. Absence of evidence at 10⁻³ is not evidence of absence at therapeutic scale.

[C-4] The clinically relevant risk regime is f∈[10−6,10−3] — above the level at which a single edited clone is improbable, below the level at which standard assays report anything. This regime is systematically under-measured. We conjecture that the majority of clinically deployed guides have ≥1 off-target site in this window.

C-4 is uncomfortable and should be tested rather than assumed. The test is specified in §9.

We state clearly what C-4 does not claim: it does not claim harm. Most off-target edits are in non-coding, non-regulatory DNA and are biologically inert. The claim is epistemic — that we currently do not know, and that our reporting conventions obscure the not-knowing.

6.4 Structural variants: the blind class

Short-amplicon sequencing has a failure mode that is worse than insensitivity: it is anti-correlated with the severity of the event.

CRISPR-induced DSBs produce, at appreciable frequency, kilobase-scale deletions, inversions, translocations, chromothripsis, and whole-chromosome-arm loss [E]. When such an event removes a primer binding site, the amplicon simply fails to form. The affected allele is not sequenced. The measured indel spectrum is computed only over surviving amplicons.

[T-15] Amplicon-based editing quantification is systematically biased against detecting the most consequential outcomes, because large deletions cause allelic dropout. The bias direction is: the larger the event, the less likely it is to appear in the data.

This is not a subtle statistical issue. It means the standard readout for editing outcome — indel frequency by amplicon NGS — is a measurement conditioned on the event being small.

6.5 Base and prime editors: the search problem becomes delocalized

Base editors and prime editors do not create DSBs, which removes the substrate that GUIDE-seq, DISCOVER-seq, and BLISS detect. Off-target detection for these tools requires entirely different assays.

Worse, base editors have an off-target class that is not sequence-addressed at all. Cytosine base editors exhibit Cas9-independent deamination: the deaminase acts on transiently single-stranded genomic DNA arising from replication and transcription, anywhere in the genome, uncorrelated with the guide [E]. Whole-genome sequencing of cells expressing evolved CBEs shows C→T and C→G mutations bearing an APOBEC mutational signature outside all predicted off-target sites [E]. Engineering has substantially mitigated this — narrowed-window variants such as YE1 show background-level Cas9-independent editing [E] — but the class exists and is invisible to any guide-directed assay.

[T-16] For base editors, the off-target set is not defined by sequence similarity to the guide. Guide-directed off-target search is therefore structurally incapable of bounding total off-target burden, regardless of its sensitivity. The problem changes from a localized search to a delocalized one, and requires whole-genome or signature-based assays.

Recent assay development responds directly to this. R-loop-detection methods — labeling transient single-stranded DNA genome-wide rather than detecting breaks — target the actual mechanistic intermediate that base editors exploit, and are being developed specifically because standard guide-directed workflows fail to capture the newly characterized editing outcomes or to satisfy emerging regulatory requirements [E]. Single-cell DNA sequencing approaches that resolve on- and off-target editing in the same cell add a dimension no bulk assay provides: whether off-target edits co-occur with on-target edits in the same genome [E].

[O-3] No assay currently bounds total genome-wide mutational burden from a base or prime editor at therapeutic sensitivity. Whole-genome duplex sequencing of edited primary cells at sufficient depth is technically possible and economically prohibitive at present.

7. Chromatin: the hidden variable that reconciles the assays

Cell-free assays over-predict off-targets by 10–100×. Cell-based assays under-detect them. The reconciling variable is chromatin.

Nucleosomes strongly impede Cas9 target engagement; naked DNA and nucleosomal DNA differ by roughly an order of magnitude in accessibility, and nucleosome breathing at entry/exit sites — not steady occupancy — governs much of the residual access [E]. Roughly 75–80% of genomic DNA is nucleosome-associated.

This produces a specific, testable structure:

  • Cell-free assay finds a site, cell assay does not → likely closed chromatin. Prediction: the site becomes editable if chromatin is opened (e.g., in a different cell type, during replication, or under a remodeler perturbation).
  • Cell assay finds a site, cell-free assay does not → likely a bulged or non-canonical site missed by the biochemical pipeline's alignment.

[C-5] The observed 10–100× over-prediction by cell-free assays is quantitatively accounted for by nucleosome occupancy alone, with no additional free parameters, if occupancy is measured in the same cell type by ATAC-seq or MNase-seq. This is directly testable and, to our knowledge, has not been done as a closed quantitative comparison.

The clinical implication is uncomfortable and follows immediately: off-target profiles are cell-type-specific. A guide validated in HEK293T carries no guarantee in CD34⁺ HSPCs, and a guide validated in quiescent cells carries no guarantee in cycling cells where replication transiently exposes the entire genome.

[T-17] Because chromatin gates accessibility and chromatin differs by cell type and cell-cycle phase, off-target specificity is not a property of a guide. It is a property of a (guide, cell type, cell state) triple. Reporting specificity as a per-guide scalar is a category error.

8. Synthesis: the coupling structure

The results above are not independent. They form a coupled system with a small number of controls and no free lunches.

Controls available to the engineer:

NRNP×texp⁡

— dose and exposure Landscape steepness S — nuclease variant, guide truncation PAM degeneracy — nuclease choice Checkpoint count n — paired nickase, dimerization, timed shutoff Chromatin state — cell type, cycle phase (largely not controllable) Assay sensitivity ϵ — measurement only, does not change biology

Coupling relations:

Push this Get this Pay this Source

Ntexp⁡

↑ on-target completeness ↑ off-target burden ↑ (linear) T-7

S ↑ (hi-fi variant) η ↑ (bounded by eϵ) Pon

↓ T-10 PAM degeneracy ↑ addressability ↑ decoy census ↑, search tax ↑ T-3, T-5

KDPAM

↓ (tighter) — nothing search time ↑↑ T-5 checkpoints n ↑ ηn a second guide, a dimerization, or ATP T-11, C-3

ϵ ↓ knowledge ↑ cost; panel-limited, not genome-wide T-12, T-13

Row four is the notable one. Tightening PAM affinity buys nothing and costs a great deal — a rare case of a control with no favorable direction.

Row five is the only route that increases η without paying in Pon

, and it is the only row whose currency (external free energy) has never been spent in a Class 2 system.

The central thesis, stated plainly: the target detection problem admits no solution that improves specificity without payment. But every payment is identified, finite, and in a named currency. The problem is not that precise editing is forbidden. The problem is that the field has been paying in Pon

(the expensive currency, per T-10) when a cheaper currency — ATP, per C-3 — has been sitting unspent because Class 2 systems inherited an architecture that never needed it.

9. A falsification program

Consistent with the discipline that a claim without a failure condition is not a claim, here are the specific experiments that would break the arguments above.

9.1 Test of C-2 (the single-curve conjecture)

Design. Measure (η,Pon) for a panel of ≥8 SpCas9 variants (WT, eSpCas9, HF1, Hypa, evo, Sniper, plus two designed intermediates) across ≥20 guides spanning a range of predicted R-loop stability, in a single cell type, using a matched off-target assay at ≥10⁻⁵ sensitivity.

Prediction. All variants collapse onto a one-parameter curve η(Pon) with the functional form implied by Pon=1/(1+S), η=1+(eϵ−1)S≥/(1+S).

Falsified if. Any single-checkpoint variant lies significantly above the curve at matched Pon

, and the excess is not attributable to a second molecular requirement.

9.2 Test of T-5 (the PAM affinity ceiling)

Design. Engineer a graded series of PAM-interacting-domain variants with KDPAM

spanning 10 μM to 10 mM (measurable by ITC or single-molecule dwell-time on PAM-only substrates). Measure genome-wide search completion time by live-cell single-particle tracking and time-to-half-maximal editing.

Prediction. Search time scales as (1+cPAM/KD), with the ~1.3 mM crossover clearly visible. Variants at KD<100μ M show >10× slower target location despite unchanged catalytic parameters.

Falsified if. Tighter PAM binders search as fast or faster — which would imply that PAM decoy sequestration is not rate-limiting and that some unmodeled release mechanism operates.

9.3 Test of C-1 and C-4 (the detection-floor conjecture)

Design. For 5 clinically deployed guides, enumerate the full k≤4 decoy census computationally with gapped alignment (addressing O-1). Assay every site by duplex-consensus sequencing at ≥10−6 sensitivity in the relevant primary cell type. Compare against the published GUIDE-seq/CIRCLE-seq call sets for the same guides.

Prediction. ≥3× more true off-target sites are recovered than appear in the published call sets, concentrated in the 10⁻⁵–10⁻³ frequency band, with an enrichment of bulged sites among the newly detected.

Falsified if. The census is essentially exhausted by existing methods — which would mean the biology, not the assay floor, is doing the suppressing, and C-1 is wrong.

9.4 Test of C-5 (chromatin reconciliation)

Design. Same cell type, same guide, same day: CIRCLE-seq (chromatin-free), GUIDE-seq (chromatin-aware), ATAC-seq (accessibility). Fit a single accessibility-weighting parameter.

Prediction. A one-parameter accessibility model reconciles the two off-target lists to within a factor of 2 across ≥80% of sites.

Falsified if. Residual discrepancy requires site-specific parameters — implying an additional unmodeled variable.

9.5 Test of C-3 (the ATP escape route)

Design. Construct a Cas9 fused to an ATP-dependent conformational module positioned to reset non-committed R-loops (candidate architectures: helicase fusion at the REC lobe; designed futile cycle coupled to the HNH gate). Measure (η,Pon) against the C-2 curve.

Prediction. The construct lies above the curve — higher η at matched Pon

— with the offset scaling with ATP concentration and vanishing in a Walker-B-dead mutant.

Falsified if. No offset, or an offset that persists in the ATPase-dead control (implying the gain came from steric effects, not proofreading energy).

This is the experiment we consider most valuable and least likely to have been attempted.

10. Claims Ledger

Tags: T = theorem / derived from stated premises · E = empirical, established in literature · C = conjecture, testable, not established · O = open problem

Claim Tag Load-bearing for Falsifier

T-1 Guide + PAM carry 44 bits vs 32.6 required; information is not limiting T §2 entire Arithmetic T-2 Effective address is 22–30 bits; deficit of 3–10 bits T (from E weights) Off-target origin Mismatch-tolerance profiles showing uniform positional weighting T-3 PAM relaxation costs decoy census linearly T Nuclease choice — T-4 Single-RNP idealized search time ≈ 3.7 h T §4 budget Diffusion constant off by >10× T-5 PAM affinity ceiling at ~1.3 mM decoy concentration T §4.2; design guidance §9.2 T-6 Cas9 sliding gain ≈ 10×, not 10²–10³ T + E Search budget Measured lsl≫27 bp T-7 Dose and specificity are conjugate: both scale with Nt T Delivery strategy Regime where off-target saturates but on-target does not T-8 Equilibrium binding cannot discriminate (saturation argument) T §5 entire — T-9 Seed effect derives from landscape topology, not structure T Generality across nucleases A nuclease with downhill landscape and no seed T-10 Discrimination ceiling: η≤eϵ/kBT; η→ηmax⁡⇒Pon→0 T Whole paper §9.1 T-11 All super-ceiling gains came from a second independent requirement T (empirical survey) §8 synthesis A single-checkpoint counterexample T-12 Detection trilemma: genome-wide ∧ chromatin-aware ∧ <10⁻⁴ unattainable T §6 entire An assay achieving all three T-13 Detection floor set by ϵ, not depth T Interpretation of null results — T-14 Therapeutic dose delivers ~10⁵ cells at the assay floor T Clinical framing Dose arithmetic T-15 Amplicon assays biased against large events (dropout) T + E Outcome measurement — T-16 Base-editor off-target set is not sequence-addressed T + E §6.5 Demonstration that Cas9-independent class is negligible in all deaminases T-17 Specificity is a (guide, cell type, cell state) property, not a guide property T Reporting conventions Cell-type-invariant off-target profiles C-1 Detection floor, not biology, dominates the census–observation gap C §6.3 §9.3 C-2 All hi-fi variants lie on one (η,Pon) curve C §5.3 §9.1 C-3 ATP-driven proofreading escapes the specificity–activity conjugacy C §5.5, §8 §9.5 C-4 Clinically relevant risk regime 10−6<f<10−3 is systematically under-measured C §6.3 §9.3 C-5 Nucleosome occupancy alone reconciles cell-free vs cellular assays C §7 §9.4 O-1 Genome-wide contribution of bulged off-targets is unquantified O §2.4 — O-2 No ATP-driven proofreading module demonstrated for Class 2 O §5.5 — O-3 No assay bounds total mutational burden for base/prime editors O §6.5 — O-4 The value of ϵ (per-mismatch penalty) is not independently measured in the R-loop context — literature values are inferred from cleavage data, making T-10 partly circular O T-10 quantitation Direct thermodynamic measurement

Note on O-4. This is the weakest joint in the paper and should be stated plainly. T-10's form is derived and secure. Its numerical ceiling depends on ϵ, which is extracted from the same cleavage assays the theorem is meant to explain. An independent measurement — magnetic-tweezer R-loop stability on matched and mismatched substrates, or calorimetry on isolated heteroduplexes — would remove the circularity. Until then, treat ηmax⁡≈7–55 as an estimate, not a derived constant.

11. Open problems, ranked by leverage

[O-2 / C-3] The ATP question. Can a Class 2 nuclease be given an external free-energy source for proofreading? This is the only identified route past the specificity–activity conjugacy that does not cost a second guide. Highest leverage in the paper. [O-4] Independent measurement of ϵ. Removes the circularity in the discrimination ceiling and converts T-10 from a structural theorem to a quantitative one. [T-12] Breaking the detection trilemma. A genome-wide, in-cellulo, sub-10⁻⁵ assay. Whole-genome duplex sequencing is the brute-force answer; the interesting question is whether a smarter one exists — e.g., enrichment on the mechanistic intermediate (R-loops, single-stranded DNA) rather than on a predicted candidate list. [O-1] Gapped-alignment census. Cheap, computational, and nobody has closed it at therapeutic sensitivity. [T-17] Cell-state-resolved specificity. If specificity is a triple, the field needs a specificity surface, not a specificity number. This is a data-infrastructure problem as much as a biology problem. [Unlisted] The functional-consequence question. Most off-target edits are inert. Nothing in this paper addresses which ones are not. A specificity theory that cannot rank consequences is incomplete — a 10⁻³ edit in a gene desert and a 10⁻⁶ edit in a tumor suppressor are not comparable quantities, and we have been treating them as though they were.

12. Closing: the cartoon closes, the problem stays open

The analysis above is internally consistent. The information budget balances. The search-time budget balances. The discrimination ceiling follows from the splitting-probability algebra, and the escape routes it predicts are exactly the escape routes the field found empirically without knowing they were predicted.

That internal consistency is worth something, and it is worth less than it feels like it is worth.

The model treats the R-loop as a one-dimensional walk on 20 states with additive mismatch penalties. Real R-loop propagation involves DNA supercoiling, protein conformational states that are not slaved to n, sequence-dependent stacking, and a genome that is not a naked polymer but a phase-separated, protein-dense, actively transcribed and replicated object. The chromatin term in §7 is a single scalar standing in for the entire field of nuclear organization. The ϵ in T-10 is fitted, not measured (O-4).

What the analysis establishes is not that CRISPR specificity is understood. It is that the structure of the problem is a permission structure: a set of coupled constraints, each of which names a currency and a price, none of which forbids the outcome. The apparent impossibility — addressing one site in 6.4 billion with a 20-letter word and no fuel — dissolves into a bill. The bill is large, itemized, and in several places has not yet been read.

The most consequential line on it is the one nobody has paid: Cas9 proofreads for free, and gets what free proofreading buys.

Author

  • Oleksii Vergunenko

References

Foundational mechanism

  1. 1.Jinek M, Chylinski K, Fonfara I, Hauer M, Doudna JA, Charpentier E. Science 337:816 (2012)
  2. 2.Cong L et al. Science 339:819 (2013)
  3. 3.Mali P et al. Science 339:823 (2013)
  4. 4.Zetsche B et al. Cell 163:759 (2015) — Cas12a

Target search

  1. 5.Sternberg SH, Redding S, Jinek M, Greene EC, Doudna JA. Nature 507:62 (2014) — DNA curtains, PAM-dependent interrogation
  2. 6.Knight SC et al. Science 350:823 (2015) — live-cell single-particle tracking
  3. 7.Globyte V, Lee SH, Bae T, Kim JS, Joo C. EMBO J 38:e99466 (2019) — lateral diffusion PAM search
  4. 8.Berg OG, Winter RB, von Hippel PH. Biochemistry 20:6929 (1981) — facilitated diffusion
  5. 9.Halford SE, Marko JF. Nucleic Acids Res 32:3040 (2004) — combined 3D/1D search time
  6. 10.Sun W et al. Chem Sci 12:12776 (2021) — biased 1D diffusion,
  7. 11.𝑠
  8. 12.𝑙
  9. 13.≈
  10. 14.27
  11. 15.ℓ
  12. 16.sl
  13. 17.≈27 bp, τ ≈ 11 ms, ~10× acceleration

Recognition and proofreading

  1. 18.Singh D, Sternberg SH, Fei J, Doudna JA, Ha T. Nat Commun 7:12778 (2016) — smFRET recognition/rejection kinetics
  2. 19.Sternberg SH, LaRue B, Mekler V, Doudna JA. Nature 527:110 (2015) — conformational control
  3. 20.Dagdas YS, Chen JS, Sternberg SH, Doudna JA, Yildiz A. Sci Adv 3:eaao0027 (2017) — conformational checkpoint
  4. 21.Chen JS et al. Nature 550:407 (2017) — enhanced proofreading, HypaCas9
  5. 22.Klein M, Eslami-Mossallam B, Gonzalez Arroyo D, Depken M. Cell Rep 22:1413 (2018) — hybridization kinetics explains off-targeting rules
  6. 23.Bisaria N, Jarmoskaite I, Herschlag D. Cell Syst 4:21 (2017) — enzyme-kinetics specificity principles
  7. 24.Strohkendl I, Saifuddin FA, Rybarski JR, Finkelstein IJ, Russell R. Mol Cell 71:816 (2018) — Cas12a kinetic specificity
  8. 25.Hopfield JJ. PNAS 71:4135 (1974)
  9. 26.Ninio J. Biochimie 57:587 (1975) — kinetic proofreading
  10. 27.Jones SK Jr et al. Nat Biotechnol 39:84 (2021) — massively parallel kinetic profiling

Engineered specificity

  1. 28.Slaymaker IM et al. Science 351:84 (2016) — eSpCas9
  2. 29.Kleinstiver BP et al. Nature 529:490 (2016) — SpCas9-HF1
  3. 30.Kleinstiver BP et al. Nature 523:481 (2015) — PAM variants
  4. 31.Walton RT, Christie KA, Whittaker MN, Kleinstiver BP. Science 368:290 (2020) — SpRY
  5. 32.Fu Y, Sander JD, Reyon D, Cascio VM, Joung JK. Nat Biotechnol 32:279 (2014) — truncated guides
  6. 33.Ran FA et al. Cell 154:1380 (2013) — paired nickases
  7. 34.Guilinger JP, Thompson DB, Liu DR. Nat Biotechnol 32:577 (2014) — dCas9-FokI
  8. 35.Kim S et al. Nat Biotechnol 37:945 (2019) — attenuated cleavage kinetics

Off-target detection

  1. 36.Tsai SQ et al. Nat Biotechnol 33:187 (2015) — GUIDE-seq
  2. 37.Tsai SQ et al. Nat Methods 14:607 (2017) — CIRCLE-seq
  3. 38.Cameron P et al. Nat Methods 14:600 (2017) — SITE-seq
  4. 39.Kim D et al. Nat Methods 12:237 (2015) — Digenome-seq
  5. 40.Wienert B et al. Science 364:286 (2019) — DISCOVER-seq
  6. 41.Turchiano G et al. Cell Stem Cell 28:1136 (2021) — CAST-seq
  7. 42.Bae S, Park J, Kim JS. Bioinformatics 30:1473 (2014) — Cas-OFFinder
  8. 43.Doench JG et al. Nat Biotechnol 34:184 (2016) — CFD score
  9. 44.Schmidt H et al. Genome Biol 26 (2025) — GuideScan2
  10. 45.Du W et al. Commun Biol (2025) — CCLMoff language-model off-target prediction
  11. 46.Cao M, Brennan A, Lee CM, Park SH, Bao G. Small Methods 9:e2500122 (2025) — deep-learning off-target prediction review
  12. 47.Kim D et al. Nat Commun 11 (2020) — prediction-guided CRISPR-amplification enrichment
  13. 48.Schmitt MW et al. PNAS 109:14508 (2012) — duplex sequencing
  14. 49.Kalter N et al. Mol Ther Methods Clin Dev (2025) — single-cell on/off-target detection
  15. 50.Lee CM. Mol Ther Methods Clin Dev (2025) — commentary on single-cell resolution
  16. 51.Wang T et al. (2025), bioRxiv 2025.09.25.678665 — beCasKAS, direct R-loop detection in primary cells

Structural outcomes

  1. 52.Kosicki M, Tomberg K, Bradley A. Nat Biotechnol 36:765 (2018) — large deletions
  2. 53.Leibowitz ML et al. Nat Genet 53:895 (2021) — chromothripsis
  3. 54.Nahmad AD et al. Nat Biotechnol 40:1807 (2022) — chromosomal truncations

Chromatin

  1. 55.Horlbeck MA et al. eLife 5:e12677 (2016) — nucleosomes impede Cas9
  2. 56.Isaac RS et al. eLife 5:e13450 (2016) — nucleosome breathing

Base and prime editing

  1. 57.Komor AC et al. Nature 533:420 (2016)
  2. 58.Gaudelli NM et al. Nature 551:464 (2017)
  3. 59.Anzalone AV et al. Nature 576:149 (2019) — prime editing
  4. 60.Zuo E et al. Science 364:289 (2019)
  5. 61.Jin S et al. Science 364:292 (2019) — Cas9-independent deamination
  6. 62.Doman JL, Raguram A, Newby GA, Liu DR. Nat Biotechnol 38:620 (2020) — YE1, minimized Cas9-independent editing
  7. 63.Grünewald J et al. Nature 569:433 (2019) — transcriptome-wide RNA off-targets
  8. 64.Richter MF et al. Nat Biotechnol 38:883 (2020) — ABE8e

Clinical

  1. 65.Frangoul H et al. N Engl J Med 384:252 (2021) — exa-cel
  2. 66.Kalter N, Fuster-García C, Turchiano G et al. Mol Ther Nucleic Acids 36:102636 (2025) — off-target effects in human therapeutics

Experimentation loop

0. Executive summary

The simulation was built to confirm the paper. It did not.

Three tests passed (A1, A2, A5). Five failed. Two of the failures (A3, A4) resolve into a sharper claim than the original. Three (A6, A7, A8) are genuine falsifications that force claim withdrawal. One claim (T-11) was upgraded from an empirical survey to a derived result. One open problem (O-4) was converted from "unmeasured" into a specific numeric prediction that the experimental loop can test.

The single most important output is negative:

Two distinct non-equilibrium proofreading architectures — an ATP-driven discard channel and an ATP-driven reset-and-retry cycle — produce a specificity advantage of exactly 1.00× over the equilibrium frontier, at every on-target efficiency tested.

C-3, which the paper called "the one route to breaking the specificity–activity conjugacy," is dead in both forms tested. The reason it is dead turns out to be more useful than the conjecture was.

1. Model

R-loop propagation as a birth–death chain on n = 0..20 bp of heteroduplex.

U <--k_off(n)-- [0] <=> [1] <=> ... <=> [20] --k_cat--> C (k+ = k_f(n), k- = k_f·exp(Δg_n)) Δg_n = −δ per matched bp, +ε at a mismatch, +barrier at the nucleation step k_off(n) = k_off0·exp(−γn) — progressive contact stabilization k_f(n) reduced by nuc_slow over the first n_nuc steps — slow nucleation, fast propagation Detailed balance enforced on k± throughout; non-equilibrium variants add channels not tied to Δg, which is what makes them require external free energy

Splitting probabilities and mean absorption times solved exactly by linear system (no Monte Carlo on the chain). Genome census sampled by Monte Carlo (20,000 decoys, weighted to the analytic expectation).

Files: rloop_engine.py, acceptance.py (frozen before first run), run_sim.py, neq_frontier.py, final_run.py.

2. Acceptance test results

Thresholds were frozen in acceptance.py before the first production run and were not adjusted afterward.

Test Claim Threshold Result Verdict A1 T-9 monotone AND η(1)/η(20) > 10 monotone, ratio 16.2 — but only at P_on = 0.152 PASS, with a fatal caveat (§3.1) A2 T-10a max η ≤ 1.05·e^ε 0.97 of ceiling; never exceeded PASS A3 T-10b Spearman ρ < −0.90 pooled −0.598; per-guide −1.000 (all 8) FAIL pooled, exact per-guide A4 C-2 R² > 0.95 pooled 0.404; per-guide min 0.975 FAIL pooled, pass per-guide A5 T-5 rel. err < 5% AND mult(10 µM) > 10 err 0.0%, multiplier 134× PASS A6 C-1 0 ≤ n(>10⁻³) ≤ 150 AND n(>10⁻⁶) ≥ 3× 2,249 sites above 10⁻³ FAIL A7 T-13 floor(1e4)/floor(1e6) < 2 11.7× — depth helps as d^(−1/2) FAIL A8 C-3 η_neq/η_eq > 2.0 at matched P_on 1.00× at every level, both architectures FAIL

3 passed, 5 failed. Not all simultaneously satisfiable — the model is rejected as a complete account and accepted as a bounding instrument.

3. What the failures mean 3.1 T-9 falsified: the seed is not free

The paper claimed the seed effect is "a topological consequence of a downhill free-energy landscape" requiring no position-specific parameters. The simulation says otherwise.

A systematic search over five mechanistic hypotheses, each swept across its full parameter range:

Hypothesis Max seed ratio at P_on > 0.7 Verdict Landscape steepness δ alone 2.1 insufficient Fast commitment (k_cat ↑ to 10⁶ s⁻¹) 10.3 insufficient, and unphysical Distal energetic gate (REC3 analogue) 1.1 makes it worse Progressive dissociation ladder γ 6.5 insufficient Slow nucleation + ladder (joint optimum) 16.6 insufficient

Empirical Cas9 shows position-dependent rejection spanning >300-fold in dissociation rate. No configuration of the single-coordinate model reaches it at physiological on-target efficiency.

The deeper finding is that the paper's own T-8 (saturation) and T-9 are mutually inconsistent. T-9's derivation assumed a strongly downhill landscape so that the weight sum S is dominated by early terms. But strongly downhill means P_cleave → 1 for target and decoy alike — the saturation regime T-8 correctly identifies as non-discriminating. The regime where the seed argument works is the regime where nothing discriminates.

T-9′ (replacement). Seed strength and on-target efficiency are conjugate, exactly as discrimination magnitude is (T-10). The observed seed profile is not free structure; it is purchased at the same price as everything else. A1's nominal pass at ratio 16.2 occurs at P_on = 0.152 — the model bought a seed by throwing away 85% of on-target activity.

3.2 C-2 and T-10b: right within a guide, wrong across guides

This is the most immediately useful correction for practitioners.

Within a single mismatch configuration: Spearman ρ = −1.000 (all eight guides), curve fit R² ≥ 0.975. The conjugacy is exact and the one-parameter curve is real. Pooled across guides: ρ = −0.598, R² = 0.404. The collapse fails completely.

Different mismatch configurations sit on different curves. A variant that looks superior on guide A can be inferior on guide B at matched on-target efficiency, with no inconsistency.

C-2′ (replacement). The (η, P_on) curve is one-parameter per mismatch configuration, not globally. Ranking nucleases on a single specificity–activity curve is a category error — the same error as T-17's per-guide specificity scalar, appearing one level up. Variant comparisons are only valid within a matched guide/mismatch set.

This predicts that published high-fidelity variant rankings should reorder when the guide panel changes. They do, and it has been attributed to guide-specific idiosyncrasy. The simulation says it is structural.

3.3 T-13 falsified: depth does help

The paper asserted that off-target detection sensitivity is bounded by sequencing error rate and that depth cannot improve it. Wrong. Sequencing error is stochastic, so the background count has Poisson noise ∝ √(depth·ε). Increasing depth 100-fold improved the floor 11.7-fold — very close to the √100 = 10 the statistics demand.

Depth ε = 10⁻² ε = 10⁻³ ε = 10⁻⁷ (duplex) 10⁴ ~3.7×10⁻³ 1.17×10⁻³ — 10⁶ ~3.2×10⁻⁴ 9.97×10⁻⁵ 2.6×10⁻⁶

T-13′ (replacement). Detection floor ≈ max( k·√(ε/depth), ε_systematic ). Depth buys sensitivity as d^(−1/2) until systematic error — context-specific miscalls, PCR jackpotting, index hopping — dominates. The floor is real but sits at the systematic error rate, not the raw per-base rate.

The paper's conclusion (that null results at ~10⁻³ carry no information about 10⁻⁴–10⁻⁶) survives in practice, because standard amplicon panels are not run at 10⁶× depth. But the stated reason was wrong, and the correct version tells you what to buy: depth and molecular consensus, with the crossover computable in advance.

3.4 C-3 falsified — and replaced by something better

Two non-equilibrium architectures, each optimized by differential evolution against the equilibrium frontier:

Architecture Advantage over equilibrium at matched P_on ATP-driven discard channel, k_dis(n) on incomplete R-loops 1.00× (P_on ≥ 0.3, 0.5, 0.7, 0.85) ATP-driven reset-and-retry, collapse to n=0, enzyme stays bound 1.00× (P_on ≥ 0.5, 0.7, 0.85)

The optimizer drove the energy-consuming rate toward zero in every arm. It found that spending ATP was strictly wasteful.

Diagnosing why produced the session's most valuable result. Varying the depth to which the complex resets:

Reset target P_on η(mm@3) η(mm@10) η(mm@17) n = 0 (PAM-bound) 0.109 4.83 1.00 1.48 n = 3 0.139 5.89 1.01 1.14 n = 6 0.152 4.66 1.01 1.00 n = 10 0.152 4.64 1.01 1.00

No reset depth produces proofreading gain. Compare the one operation that does:

Architecture η(mm@3) P_on Single complex 4.64 0.152 Two independent complexes (paired nickase / obligate dimer) 21.56 = η² 0.023 = P_on²

Exact squaring, in both quantities.

T-18 (new, derived). Kinetic proofreading requires the discard branch to return the system to a state whose re-entry is itself discriminating. Cas9's discard can only return it to the PAM-bound state, and the PAM carries zero spacer information. Re-entry is therefore mismatch-blind, the second test is not independent of the first, and no amount of external free energy purchases a second checkpoint.

Corollary. Cas9 cannot be made to proofread by adding ATP. The Hopfield mechanism is unavailable to this architecture for an informational reason, not an energetic one.

Design consequence. A proofreading-capable RNA-guided nuclease requires a spacer-dependent re-entry step — PAM recognition coupled to guide identity, or a two-site binding architecture. This is the specification that C-3 was groping toward and got wrong.

This simultaneously upgrades T-11 from an empirical survey ("every super-ceiling gain came from a second independent requirement") to a derived necessity. Paired nickases and FokI dimers are not one clever trick among many. They are the only available implementation of a discriminating re-entry, which is why nothing else has ever worked.

3.5 A6: the model under-discriminates, and that is the point

Predicted 2,249 sites above the 10⁻³ detection threshold against an observed 0–150. Chromatin contributed only 4.8× suppression, versus the 10–100× the literature implies.

The model over-predicts off-targets by roughly 15× — because at ε = 3 k_BT it cannot discriminate hard enough. This is the same deficit that appears in the seed analysis (§3.1), from a completely independent direction. Two unrelated acceptance tests failing in the same direction with the same magnitude is a coupling signal, not two bugs.

Sweeping the mismatch penalty to find what the model would need:

ε (k_BT) Max seed ratio at P_on ≥ 0.7 3.0 19.2 4.0 52.0 5.0 141.4 6.0 384.2 7.0 1,044

Prediction P1 (converts O-4 into a measurement). The effective per-mismatch free-energy penalty in the Cas9 R-loop context is ε ≥ 5 k_BT (≈ 3.0 kcal/mol), at or above the top of the range inferred from free RNA:DNA duplex thermodynamics (~1–3 kcal/mol). If direct measurement returns ε ≤ 4 k_BT, the single-coordinate model is falsified outright and Cas9 specificity requires a mechanism entirely outside R-loop thermodynamics.

The paper flagged O-4 as its weakest joint — ε being fitted from the same cleavage data it was meant to explain. The simulation independently demanded exactly that measurement, and attached a number to it. That convergence is the strongest internal evidence the framework is doing real work.

4. Figures

F1_discrimination.png — (a) seed profile of the calibrated model against the e^ε ceiling; (b) equilibrium vs non-equilibrium frontier across P_on constraints, with the empirical Cas9 seed strength marked far above both; (c) per-guide (η, P_on) curves showing the failure of pooled collapse.

F2_search_detection.png — (a) PAM affinity ceiling, search-time multiplier vs K_D with the 1.33 mM decoy concentration marked; (b) detection floor vs depth at three error rates, showing the d^(−1/2) scaling that falsified T-13; (c) genome-wide edit-frequency distribution with and without chromatin, against the GUIDE-seq and duplex thresholds.

F3_failure_propagation.png — the standard failure-propagation panel. A single knob (landscape shift, k_BT per bp) is swept while four acceptance criteria are monitored. P_on, seed ratio, off-target count, and per-mismatch η break in opposite directions, confirming that these are not independent modules with separate tuning parameters but one coupled system. There is no shift value satisfying all four.

5. Revised Claims Ledger # Claim Prior Post-simulation Evidence T-5 PAM affinity ceiling ~1.3 mM T CONFIRMED A5, exact T-8 Equilibrium binding cannot discriminate T CONFIRMED, and it kills T-9 §3.1 T-9 Seed from landscape topology alone T FALSIFIED → T-9′ 5 hypotheses, all insufficient T-10a η ≤ e^(ε/kT) T CONFIRMED A2, 0.97 of ceiling, never exceeded T-10b η ↑ ⟺ P_on ↓ T CONFIRMED per-guide (ρ = −1.000), refuted pooled A3 T-11 Super-ceiling gains need a second requirement T (survey) UPGRADED to derived T-18; paired gate = exact η², P_on² T-13 Floor set by ε, not depth T FALSIFIED → T-13′ (d^(−1/2)) A7, 11.7× T-18 Proofreading needs discriminating re-entry; PAM is mismatch-blind — NEW, derived §3.4 C-1 Detection floor dominates census gap C UNRESOLVED; model under-discriminates 15× A6 C-2 All variants on one curve C FALSIFIED pooled → C-2′ (per-configuration) A4, R² 0.404 vs 0.975 C-3 ATP escapes the conjugacy C FALSIFIED, both architectures A8, 1.00× O-4 ε unmeasured, T-10 partly circular O → Prediction P1: ε ≥ 5 k_BT §3.5 6. Closed experimental loop

Each experiment maps to a specific claim, has a decision rule fixed in advance, and feeds a defined revision back into the model. No experiment is listed unless a negative result changes something.

E1 — Direct measurement of ε (highest priority)

Tests: P1, O-4, and the circularity in T-10.

Design. Magnetic-tweezer or smFRET measurement of R-loop stability on matched vs single-mismatch substrates, with dCas9 to remove cleavage. Measure the equilibrium R-loop length distribution under controlled supercoiling; extract per-position Δg directly. Mismatch panel: positions 1, 3, 6, 10, 14, 17, 20 × 3 mismatch identities. Orthogonal check by isothermal titration calorimetry on isolated RNA:DNA heteroduplexes.

Decision rule.

ε ≥ 5 k_BT → single-coordinate model retained; T-10 becomes fully quantitative; ceiling e^ε ≈ 148 per mismatch. 4 < ε < 5 → model retained as a bound only; a second coordinate is required to close the residual. ε ≤ 4 k_BT → single-coordinate model falsified. Cas9 specificity is not explained by R-loop thermodynamics; rebuild around protein conformational coordinates.

Feeds back to: the ε parameter, which propagates to A1, A2, A6 simultaneously (see F3).

E2 — The re-entry test (tests T-18 directly)

Tests: T-18, the corollary that ATP cannot rescue Cas9 proofreading.

Design. Two arms, matched on-target efficiency.

Arm A (negative control for T-18). Cas9 fused to an ATP-consuming module that drives R-loop collapse without dissociation — a helicase fused at the REC lobe, or a designed futile cycle. Compare (η, P_on) against the WT curve for a matched guide.

Arm B (positive test of T-18). Engineer a spacer-dependent re-entry: a nuclease whose PAM engagement is allosterically coupled to guide identity, so that re-binding after discard is itself discriminating. Simplest tractable proxy: a split system where the second binding event reads a distinct spacer segment.

Decision rule.

Arm A shows no gain above the WT curve, Arm B shows gain → T-18 confirmed. Proofreading is informationally, not energetically, blocked. This is the paper's central redesign specification. Arm A shows gain → T-18 falsified; C-3 revives in modified form; the simulation's discard model was incomplete. Neither arm shows gain → both T-18 and C-3 are wrong and the conjugacy is deeper than either account.

Feeds back to: T-11/T-18, and the entire §8 coupling table of the paper.

E3 — Cross-guide variant reordering (tests C-2′)

Tests: C-2′, and by extension T-17.

Design. Panel of ≥6 SpCas9 variants (WT, eSpCas9, HF1, Hypa, evo, Sniper) × ≥24 guides chosen to span predicted R-loop stability, in one cell type, one delivery modality, one day. Measure on-target editing and off-target burden at ≥10⁻⁵ sensitivity.

Decision rule.

Variant rank order changes across guides at matched P_on, and per-guide (η, P_on) fits give R² > 0.95 while the pooled fit gives R² < 0.7 → C-2′ confirmed, and the field's single-curve variant comparisons are invalid. Rank order is stable and pooled R² > 0.95 → C-2′ falsified; the original C-2 stands; the simulation's guide-dependence is an artifact of the additive-penalty assumption.

Cost note. This is the cheapest decisive experiment in the loop and requires no new molecular engineering.

E4 — Census recovery at depth (tests C-1, T-13′)

Tests: C-1, T-13′, and the A6 discrepancy.

Design. For 3 guides, enumerate the full k ≤ 4 census with gapped alignment (addressing O-1; ~155 sites/guide plus bulged candidates). Assay every site by duplex-consensus sequencing in the relevant primary cell type at two depths — 10⁴ and 10⁶ — to test the d^(−1/2) scaling directly. Compare recovered sites against published GUIDE-seq/CIRCLE-seq calls for the same guides.

Decision rule.

Floor improves by 8–12× between depths → T-13′ confirmed quantitatively. ≥3× more true off-target sites recovered than in published call sets, concentrated at 10⁻⁵–10⁻³ → C-1 confirmed; assay floor, not biology, explains the census gap. Census essentially exhausted by existing methods → C-1 falsified; biology suppresses the decoys, and the model's 15× over-prediction is a real model deficiency requiring larger ε (links back to E1). Floor plateaus before 10× improvement → the systematic-error term in T-13′ dominates; measure and report it, since it sets the true industry floor. E5 — Chromatin reconciliation (tests C-5, and the A6 4.8× shortfall)

Design. Same cell type, same guide, same day: CIRCLE-seq (chromatin-free), GUIDE-seq (chromatin-aware), ATAC-seq (accessibility). Fit a single accessibility-weighting parameter across all sites.

Decision rule.

One-parameter model reconciles the lists to within 2× across ≥80% of sites → C-5 confirmed; the simulation's accessibility distribution should be replaced by the measured one, which will raise the suppression from 4.8× toward the observed 10–100× and may resolve A6 without changing ε. Residual requires site-specific parameters → C-5 falsified; chromatin is not a scalar and T-17 needs strengthening rather than the model needing recalibration.

Note. E5 and E1 are the two candidate explanations for the A6 failure. They are distinguishable: E5 fixes A6 without touching the seed, E1 fixes A6 and the seed together. If E5 alone resolves A6 but the seed deficit remains, ε is genuinely ~3 k_BT and the seed requires a second coordinate. That is the loop's most informative branch.

E6 — PAM affinity ceiling (tests T-5)

Design. Graded PAM-interacting-domain variants spanning K_D 10 µM to 10 mM, measured by single-molecule dwell time on PAM-only substrates. Assay genome search completion by live-cell single-particle tracking and time-to-half-maximal editing.

Decision rule. Search time scales as (1 + c_PAM/K_D) with the ~1.3 mM crossover visible, and K_D = 10 µM variants show >10× slower target location at unchanged catalytic parameters → T-5 confirmed. Tighter binders search as fast or faster → T-5 falsified and an unmodeled release mechanism exists.

Priority note. T-5 passed cleanly in simulation (A5, 0.0% error) but the simulation only reproduced its own analytic model — this test carries no independent information until measured. Flagged to avoid mistaking internal consistency for evidence.

7. Loop closure logic
E1 (ε)  ──┬── ε ≥ 5      → model retained, T-10 quantitative
          ├── 4 < ε < 5  → bound only, second coordinate needed
          └── ε ≤ 4      → single-coordinate model dead ─────┐
                                                             │
E5 (chromatin) ──┬── resolves A6 alone → ε is ~3 ────────────┤
                 └── doesn't → ε is the deficit → E1 branch  │
                                                             ▼
                                            REBUILD around protein
E2 (re-entry) ─┬── Arm A null, Arm B gain → T-18 CONFIRMED   conformational
               │      → design spec: spacer-dependent re-entry  coordinates
               └── Arm A gain → C-3 revives, T-18 dead
E3 (cross-guide) ─┬── reorder → C-2′ confirmed → field's variant
                  │              rankings invalid as published
                  └── stable → C-2 stands, model artifact
E4 (census) ──┬── recovery → C-1 confirmed, clinical gap real
              └── exhausted → C-1 dead, biology suppresses (→ E1)

Highest information per dollar: E3 (no new engineering, decisive on C-2′). Highest information overall: E1 (propagates to three acceptance tests simultaneously — see F3). Highest leverage if positive: E2 Arm B (converts T-18 from

Simulation

Resultsjson
{
 "calibrated": {
  "delta": 0.62,
  "eps": 3.0,
  "barrier": 3.4,
  "nuc_slow": 15.848931924611133,
  "n_nuc": 5.0,
  "gamma": 1.1,
  "k_cat": 158.48931924611142,
  "P_on": 0.15243643638003807
 },
 "mechanism_reset_depth": {
  "reset_to_PAM_bound(n=0)": {
   "P_on": 0.10910296573659461,
   "eta_mm3": 4.829373631800589,
   "eta_mm10": 1.0049485888912109,
   "eta_mm17": 1.4786362846702354
  },
  "reset_to_n=3": {
   "P_on": 0.13937161085802652,
   "eta_mm3": 5.891764105934768,
   "eta_mm10": 1.006321485402207,
   "eta_mm17": 1.1445362883117876
  },
  "reset_to_n=6": {
   "P_on": 0.15223484246386587,
   "eta_mm3": 4.661510998907319,
   "eta_mm10": 1.0069049236671537,
   "eta_mm17": 1.0025541937903213
  },
  "reset_to_n=10": {
   "P_on": 0.15241966763512468,
   "eta_mm3": 4.644697192716244,
   "eta_mm10": 1.0069133067920653,
   "eta_mm17": 1.0005141259644734
  }
 },
 "paired_gate": {
  "mm3": {
   "single": 4.643188129286384,
   "paired": 21.55919600394599,
   "P_on_single": 0.15243643638003807,
   "P_on_paired": 0.02323686713624539
  },
  "mm10": {
   "single": 1.0048190310562024,
   "paired": 1.0096612851727254,
   "P_on_single": 0.15243643638003807,
   "P_on_paired": 0.02323686713624539
  },
  "mm17": {
   "single": 1.000321112330902,
   "paired": 1.000642327774933,
   "P_on_single": 0.15243643638003807,
   "P_on_paired": 0.02323686713624539
  }
 },
 "acceptance": {
  "A1": {
   "passed": true,
   "monotone": true,
   "ratio": 16.19823080998587,
   "eta": [
    16.202616834478288,
    8.62010744988927,
    4.643188129286384,
    2.3835926398973375,
    1.0974303746562917,
    1.0534344921528913,
    1.0291662973604485,
    1.0159107527022126,
    1.0087131662149147,
    1.0048190310562024,
    1.0027168214741176,
    1.0015835065052459,
    1.0009730417890814,
    1.0006443835982932,
    1.0004674999936938,
    1.0003723207192263,
    1.000321112330902,
    1.0002935634840138,
    1.000278743720745,
    1.000270771823409
   ],
   "P_on": 0.15243643638003807
  },
  "A2": {
   "passed": true,
   "max_eta": 12.258950266976855,
   "ceiling": 20.085536923187668,
   "frac": 0.6103371950602207
  },
  "A3": {
   "passed": false,
   "rho_pooled": -0.5975025139971332,
   "rho_by_guide": {
    "(3,)": -0.9999999999999999,
    "(7,)": -0.9999999999999999,
    "(12,)": -0.9999999999999999,
    "(17,)": -0.9999999999999999,
    "(5, 14)": -0.9999999999999999,
    "(2, 9)": -0.9999999999999999,
    "(11, 18)": -0.9999999999999999,
    "(4, 8, 16)": -0.9999999999999999
   },
   "worst_by_guide": -0.9999999999999999
  },
  "A4": {
   "passed": false,
   "R2_pooled": 0.40424741978586853,
   "R2_by_guide": {
    "(3,)": 0.9999741041519393,
    "(7,)": 0.9896077595176062,
    "(12,)": 0.9854970193028102,
    "(17,)": 0.9860682781797224,
    "(5, 14)": 0.9819132319903298,
    "(2, 9)": 0.9929103795113066,
    "(11, 18)": 0.9749394027830053,
    "(4, 8, 16)": 0.9935056187878994
   },
   "R2_min_by_guide": 0.9749394027830053
  },
  "A5": {
   "passed": true,
   "max_rel_err": 0.0,
   "mult_10uM": 134.21999999999997,
   "mult_1mM": 2.7479999999999998
  },
  "A6": {
   "passed": false,
   "n_1e3": 2248.7950533104595,
   "n_1e4": 4826.94119316875,
   "n_1e6": 11787.581629832857,
   "n_1e3_chromatin_free": 10846.156929037534,
   "overprediction_factor": 4.823097112860893,
   "census_le4": 154.61264410987496
  },
  "A7": {
   "passed": false,
   "floor_1e4": 0.0011667579489831946,
   "floor_1e6": 9.966668462997826e-05,
   "improvement": 11.706599384888651,
   "floor_duplex_1e6": 2.5740603137235614e-06
  },
  "A8": {
   "passed": false,
   "best_advantage": 1.0003655055677667,
   "by_level": {
    "0.3": 0.9994465123415585,
    "0.5": 0.9987315646590418,
    "0.7": 1.0003655055677667,
    "0.85": 0.9966513868693666
   },
   "note": "discard-channel architecture; reset-and-retry also gave 1.00x"
  }
 },
 "summary": {
  "A1": true,
  "A2": true,
  "A3": false,
  "A4": false,
  "A5": true,
  "A6": false,
  "A7": false,
  "A8": false
 }
}
Codepython
"""
rloop_engine.py
Core kinetic engine for CRISPR target detection simulation.

Units: energies in k_B T, rates in s^-1.

Model
-----
R-loop propagation as a birth-death chain on n = 0..N (N=20 bp of heteroduplex).
  n = 0  : PAM-engaged, duplex not yet unwound
  n = N  : full R-loop -> irreversible commitment (HNH docking + cleavage) at k_cat
Absorbing states:
  U : unbound (reached from n=0 at k_off, or from any n via non-equilibrium discard)
  C : cleaved

Forward rate  k+_n = k_f                    (uniform bp-zipping attempt frequency)
Backward rate k-_n = k_f * exp(dg_n)        (detailed balance; dg_n = G(n) - G(n-1))
Discard rate  k_dis[n]                      (NOT tied to dg -> requires external free energy)

Setting k_dis = 0 everywhere recovers the equilibrium, single-checkpoint nuclease
whose discrimination ceiling is derived in the companion paper (T-10).
"""

import numpy as np

N_BP = 20


# ---------------------------------------------------------------- landscape

def landscape(mismatch_positions, delta=0.8, eps=3.0, barrier=2.0, n_bp=N_BP,
              distal_gate=0.0, gate_start=17):
    """
    Per-step free energy increments dg[1..n_bp] (dg[n] = G(n) - G(n-1)).

    delta      : favorable free energy per matched bp (dg = -delta)
    eps        : penalty added at a mismatched bp
    barrier    : extra cost of the first unwinding step (n=0 -> 1)
    distal_gate: OPTIONAL extra cost applied at positions >= gate_start,
                 standing in for REC3-mediated allosteric gating.
                 Default 0 -> no position-specific parameters (needed for test A1).
    mismatch_positions : 1-indexed positions counting FROM THE PAM.
    """
    dg = np.full(n_bp + 1, -delta, dtype=float)
    dg[0] = 0.0
    dg[1] += barrier
    for m in mismatch_positions:
        dg[m] += eps
    if distal_gate:
        dg[gate_start:] += distal_gate
    return dg


# ---------------------------------------------------------------- solver

def solve_chain(dg, k_f=1e4, k_off=100.0, k_cat=1.0, k_dis=None):
    """
    Returns (p_cleave, mfpt_cleave, mfpt_absorb) for a complex starting at n=0.

    p_cleave    : probability of reaching C before U, per PAM engagement
    mfpt_absorb : mean time to absorption (either outcome), seconds
    """
    n_bp = len(dg) - 1
    S = n_bp + 1                       # transient states n = 0..n_bp
    if k_dis is None:
        k_dis = np.zeros(S)
    k_dis = np.asarray(k_dis, dtype=float)

    kp = np.full(S, k_f); kp[n_bp] = 0.0          # no forward step out of N
    km = np.zeros(S)
    for n in range(1, S):
        km[n] = k_f * np.exp(dg[n])               # n -> n-1
    loss = np.zeros(S)
    loss[0] += k_off                              # dissociation from PAM state
    loss += k_dis                                 # energy-driven discard
    cat = np.zeros(S); cat[n_bp] = k_cat

    tot = kp + km + loss + cat
    # guard against numerical blowup from very steep uphill steps
    tot = np.where(tot > 0, tot, 1.0)

    # --- splitting probability: p_n = sum_m P(n->m) p_m ; p_U = 0, p_C = 1
    A = np.eye(S)
    b = np.zeros(S)
    for n in range(S):
        if kp[n] > 0:
            A[n, n + 1] -= kp[n] / tot[n]
        if km[n] > 0:
            A[n, n - 1] -= km[n] / tot[n]
        b[n] = cat[n] / tot[n]
    p = np.linalg.solve(A, b)

    # --- mean time to absorption: t_n = 1/tot_n + sum_m P(n->m) t_m
    b2 = 1.0 / tot
    t = np.linalg.solve(A, b2)

    return float(p[0]), float(t[0])


def p_cleave(mismatch_positions, **kw):
    lp = {k: kw.pop(k) for k in
          ('delta', 'eps', 'barrier', 'distal_gate', 'gate_start') if k in kw}
    dg = landscape(mismatch_positions, **lp)
    return solve_chain(dg, **kw)[0]


# ---------------------------------------------------------------- census

from math import comb


def census_table(genome_bp=3.2e9, pam_freq=1 / 16, n_bp=N_BP, kmax=6):
    """Expected number of genome-wide sites at exactly k mismatches (with PAM)."""
    sites = 2 * genome_bp * pam_freq
    tot = 4 ** n_bp
    out = {}
    for k in range(kmax + 1):
        variants = comb(n_bp, k) * 3 ** k
        out[k] = sites * variants / tot
    return out


def sample_decoys(rng, n_sample=20000, kmax=6, n_bp=N_BP, allow_bulge=False):
    """
    Monte-Carlo sample of the genome-wide decoy population.
    Returns (list_of_mismatch_position_tuples, weights) where weights sum to the
    total expected number of decoy sites.
    """
    tab = census_table(kmax=kmax)
    total = sum(tab.values())
    ks = np.array(sorted(tab))
    probs = np.array([tab[k] for k in ks]); probs = probs / probs.sum()
    draws = rng.choice(ks, size=n_sample, p=probs)
    decoys, weights = [], []
    for k in draws:
        pos = tuple(rng.choice(np.arange(1, n_bp + 1), size=int(k), replace=False)) if k else ()
        decoys.append(pos)
        weights.append(total / n_sample)
    return decoys, np.array(weights)


# ---------------------------------------------------------------- chromatin

def sample_accessibility(rng, n, open_frac=0.22):
    """
    Multiplicative accessibility factor per site.
    Open chromatin: near-unity. Closed: strongly suppressed but non-zero
    (nucleosome breathing / replication-fork exposure).
    """
    is_open = rng.random(n) < open_frac
    a = np.where(is_open,
                 rng.uniform(0.3, 1.0, n),
                 np.exp(rng.normal(np.log(0.02), 0.8, n)))
    return np.clip(a, 0, 1)


# ---------------------------------------------------------------- exposure

def edit_probability(p_cl, accessibility, flux):
    """
    flux = (encounters per site per second) * N_RNP * t_exposure, a single
    lumped dose parameter. P_edit = 1 - exp(-flux * a * p_cleave).
    """
    return 1.0 - np.exp(-flux * accessibility * p_cl)


# ---------------------------------------------------------------- PAM tax

def pam_search_multiplier(K_D_M, c_pam_M=1.328e-3, c_ns_M=2.1e-2, K_D_ns_M=5e-2):
    """Search-time inflation from decoy sequestration (T-5)."""
    return 1.0 + c_pam_M / K_D_M + c_ns_M / K_D_ns_M


# ---------------------------------------------------------------- detection

def detection_floor(f_true, depth, err_rate, alpha=1e-3):
    """
    Can an allele at frequency f_true be distinguished from sequencing error?
    One-sided Poisson test of observed count against the error background.
    Returns True if detectable.
    """
    from scipy.stats import poisson
    lam_bg = depth * err_rate
    thresh = poisson.ppf(1 - alpha, lam_bg)
    lam_sig = depth * (f_true + err_rate)
    # require >50% power at that threshold
    return (1 - poisson.cdf(thresh, lam_sig)) > 0.5


def min_detectable_f(depth, err_rate, alpha=1e-3):
    lo, hi = 1e-9, 1.0
    if not detection_floor(hi, depth, err_rate, alpha):
        return np.nan
    for _ in range(60):
        mid = np.sqrt(lo * hi)
        if detection_floor(mid, depth, err_rate, alpha):
            hi = mid
        else:
            lo = mid
    return hi

"""
acceptance.py  -- PRE-REGISTERED acceptance criteria.

Written and frozen BEFORE the first production run. Thresholds are not to be
adjusted after seeing results; a failed test is a result, not a bug to tune away.

All tests must pass SIMULTANEOUSLY for the model to be accepted as a faithful
instantiation of the paper's claims. Partial passes force claim revision.

  A1  seed emergence            tests T-9
  A2  discrimination ceiling    tests T-10 (first clause)
  A3  specificity-activity      tests T-10 (second clause)
  A4  single-curve collapse     tests C-2
  A5  PAM affinity ceiling      tests T-5
  A6  census vs observation     tests C-1
  A7  detection floor           tests T-13
  A8  non-equilibrium escape    tests C-3
"""

TESTS = {
    "A1": dict(
        claim="T-9",
        statement=(
            "With a strictly position-independent landscape (no distal gate), the "
            "single-mismatch discrimination eta(m) decreases monotonically with "
            "mismatch position m, and eta(1)/eta(20) > 10."
        ),
        threshold="monotone_decreasing AND ratio > 10",
    ),
    "A2": dict(
        claim="T-10a",
        statement=(
            "Across the full swept parameter space of single-checkpoint variants "
            "(k_dis = 0), the per-mismatch discrimination never exceeds exp(eps/kT) "
            "by more than 5% (numerical tolerance)."
        ),
        threshold="max_eta_single_mismatch <= 1.05 * exp(eps)",
    ),
    "A3": dict(
        claim="T-10b",
        statement=(
            "Across the equilibrium variant sweep, Spearman rho between log(eta) and "
            "log(P_on) is < -0.90. Specificity and activity are conjugate."
        ),
        threshold="spearman_rho < -0.90",
    ),
    "A4": dict(
        claim="C-2",
        statement=(
            "All equilibrium variants, across all guides, collapse onto a single "
            "one-parameter curve eta(P_on): a monotone fit in log-log space explains "
            "R^2 > 0.95 of the variance."
        ),
        threshold="R2 > 0.95",
    ),
    "A5": dict(
        claim="T-5",
        statement=(
            "Simulated search-time inflation matches the analytic prediction "
            "(1 + c_PAM/K_D + c_ns/K_ns) to within 5% across K_D from 1 uM to 10 mM, "
            "and the inflation at K_D = 10 uM exceeds 10x."
        ),
        threshold="max_rel_err < 0.05 AND multiplier(10uM) > 10",
    ),
    "A6": dict(
        claim="C-1",
        statement=(
            "Under nominal parameters, the number of off-target sites with edit "
            "frequency above the GUIDE-seq floor (1e-3) falls in the empirically "
            "observed range 0-150, WHILE the number above the duplex floor (1e-6) "
            "is at least 3x larger. Both conditions required."
        ),
        threshold="0 <= n_guideseq <= 150 AND n_duplex >= 3 * max(n_guideseq, 1)",
    ),
    "A7": dict(
        claim="T-13",
        statement=(
            "Minimum detectable allele frequency is set by sequencing error rate, "
            "not depth: increasing depth 100-fold (1e4 -> 1e6) at err=1e-3 improves "
            "the detection floor by less than 2-fold."
        ),
        threshold="floor(1e4)/floor(1e6) < 2",
    ),
    "A8": dict(
        claim="C-3",
        statement=(
            "A non-equilibrium kinetic-rejection variant (k_dis > 0 on incomplete "
            "R-loop states, not tied to detailed balance) achieves eta at least 2x "
            "above the equilibrium eta(P_on) curve at matched P_on."
        ),
        threshold="eta_neq / eta_eq_at_matched_Pon > 2.0",
    ),
}

"""
neq_frontier.py

Maps the achievable frontier in (seed strength, on-target efficiency) for:
  (a) EQUILIBRIUM single-coordinate models  -- detailed balance enforced
  (b) NON-EQUILIBRIUM models with an energy-driven discard channel k_dis(n)
      whose rate is NOT tied to the local free energy (requires ATP or an
      equivalent external free-energy source).

The equilibrium frontier is computed FIRST and serves as the analytic ceiling.
Any (b) point above it is a genuine non-equilibrium effect; any (b) point that
would also be reachable by (a) is not evidence for C-3.
"""

import numpy as np
from scipy.optimize import differential_evolution
from rloop_engine import landscape

N = 20
PROBES = (1, 3, 6, 10, 14, 17, 20)


def solve(dg, kf, koff, kdis, k_cat):
    S = N + 1
    kp = np.array(kf, float).copy(); kp[N] = 0.0
    km = np.zeros(S)
    for n in range(1, S):
        km[n] = kf[n - 1] * np.exp(np.clip(dg[n], -50, 50))
    cat = np.zeros(S); cat[N] = k_cat
    tot = kp + km + np.asarray(koff, float) + np.asarray(kdis, float) + cat
    tot = np.where(tot > 0, tot, 1.0)
    A = np.eye(S); b = np.zeros(S)
    for n in range(S):
        if kp[n] > 0: A[n, n + 1] -= kp[n] / tot[n]
        if km[n] > 0: A[n, n - 1] -= km[n] / tot[n]
        b[n] = cat[n] / tot[n]
    try:
        return float(np.linalg.solve(A, b)[0])
    except np.linalg.LinAlgError:
        return 0.0


def evaluate(delta, eps, barrier, log_nuc, n_nuc, gamma, log_kcat,
             log_kdis=None, n_c=None):
    kf = np.full(N + 1, 1e4)
    kf[:int(n_nuc)] = 1e4 / (10 ** log_nuc)
    koff = 100.0 * np.exp(-gamma * np.arange(N + 1))
    kdis = np.zeros(N + 1)
    if log_kdis is not None:
        kdis[1:int(n_c)] = 10 ** log_kdis
    kc = 10 ** log_kcat
    on = solve(landscape([], delta=delta, eps=eps, barrier=barrier), kf, koff, kdis, kc)
    if on <= 0:
        return 0.0, np.ones(len(PROBES))
    etas = []
    for m in PROBES:
        off = solve(landscape([m], delta=delta, eps=eps, barrier=barrier), kf, koff, kdis, kc)
        etas.append(on / off if off > 1e-300 else 1e12)
    return on, np.array(etas)


def frontier(neq, p_on_min, seed=0, maxiter=140):
    """Maximize seed ratio eta(m=1)/eta(m=20) subject to P_on >= p_on_min."""
    bounds = [(0.05, 2.0),    # delta
              (2.0, 4.0),     # eps  (held near literature range)
              (0.0, 6.0),     # barrier
              (0.0, 4.0),     # log10 nucleation slowdown
              (2.0, 8.0),     # n_nuc
              (0.0, 2.5),     # gamma
              (-1.0, 5.0)]    # log10 k_cat
    if neq:
        bounds += [(0.0, 5.0),    # log10 k_dis
                   (3.0, 20.0)]   # n_c

    def obj(x):
        on, etas = evaluate(*x) if not neq else evaluate(*x[:7], log_kdis=x[7], n_c=x[8])
        if on < p_on_min:
            return 1e3 + (p_on_min - on) * 1e3          # penalty
        return -np.log10(max(etas[0] / etas[-1], 1e-9))

    r = differential_evolution(obj, bounds, seed=seed, maxiter=maxiter,
                               popsize=22, tol=1e-8, polish=True)
    x = r.x
    on, etas = evaluate(*x) if not neq else evaluate(*x[:7], log_kdis=x[7], n_c=x[8])
    return dict(seed_ratio=float(etas[0] / etas[-1]), P_on=float(on),
                etas=etas.tolist(), x=x.tolist(), neq=neq, p_on_min=p_on_min)


if __name__ == "__main__":
    import json
    out = []
    for pmin in [0.3, 0.5, 0.7, 0.85]:
        eq = frontier(False, pmin)
        nq = frontier(True, pmin)
        out.append(dict(p_on_min=pmin, eq=eq, neq=nq,
                        advantage=nq['seed_ratio'] / max(eq['seed_ratio'], 1e-9)))
        print(f"P_on>={pmin:.2f}   EQ seed={eq['seed_ratio']:8.2f} (P_on={eq['P_on']:.3f})"
              f"   NEQ seed={nq['seed_ratio']:8.2f} (P_on={nq['P_on']:.3f})"
              f"   advantage={out[-1]['advantage']:.1f}x")
        print(f"            EQ  eta{PROBES} = {np.round(eq['etas'],2)}")
        print(f"            NEQ eta{PROBES} = {np.round(nq['etas'],2)}")
    json.dump(out, open('frontier.json', 'w'), indent=1)

"""final_run.py -- consolidated production run + figures."""

import json, numpy as np, matplotlib
matplotlib.use('Agg')
import matplotlib.pyplot as plt
from scipy.stats import spearmanr
from rloop_engine import (landscape, solve_chain, p_cleave, census_table,
                          sample_decoys, sample_accessibility, edit_probability,
                          pam_search_multiplier, min_detectable_f, N_BP)

rng = np.random.default_rng(20260831)
OUT = {}
PROBES = np.arange(1, 21)
plt.rcParams.update({'figure.dpi': 130, 'font.size': 8.5, 'axes.grid': True,
                     'grid.alpha': .25, 'axes.spines.top': False,
                     'axes.spines.right': False})
CB, CR, CG, CK = '#2E5EAA', '#C1442E', '#3B8F5A', '#444444'


def solve(dg, kf, koff, k_cat, kreset=None, kdis=None):
    S = N_BP + 1
    kp = np.array(kf, float).copy(); kp[N_BP] = 0.
    km = np.zeros(S)
    for n in range(1, S):
        km[n] = kf[n - 1] * np.exp(np.clip(dg[n], -50, 50))
    kr = np.zeros(S) if kreset is None else np.asarray(kreset, float).copy(); kr[0] = 0.
    kd = np.zeros(S) if kdis is None else np.asarray(kdis, float)
    cat = np.zeros(S); cat[N_BP] = k_cat
    tot = kp + km + np.asarray(koff, float) + kr + kd + cat
    tot = np.where(tot > 0, tot, 1.)
    A = np.eye(S); b = np.zeros(S)
    for n in range(S):
        if kp[n] > 0: A[n, n + 1] -= kp[n] / tot[n]
        if km[n] > 0: A[n, n - 1] -= km[n] / tot[n]
        if kr[n] > 0: A[n, 0] -= kr[n] / tot[n]
        b[n] = cat[n] / tot[n]
    try:
        return float(np.linalg.solve(A, b)[0])
    except np.linalg.LinAlgError:
        return 0.


# ---- CALIBRATED reference model (from frontier optimisation, P_on>=0.7 arm)
CAL = dict(delta=0.62, eps=3.0, barrier=3.4, nuc_slow=10 ** 1.2, n_nuc=5,
           gamma=1.1, k_cat=10 ** 2.2)


def kin(cal=CAL):
    kf = np.full(N_BP + 1, 1e4); kf[:int(cal['n_nuc'])] = 1e4 / cal['nuc_slow']
    koff = 100. * np.exp(-cal['gamma'] * np.arange(N_BP + 1))
    return kf, koff, cal['k_cat']


def pc(mm, cal=CAL, **kw):
    kf, koff, kc = kin(cal)
    dg = landscape(list(mm), delta=cal['delta'], eps=cal['eps'], barrier=cal['barrier'])
    return solve(dg, kf, koff, kc, **kw)


P_ON = pc(())
OUT['calibrated'] = dict(**{k: float(v) for k, v in CAL.items()}, P_on=P_ON)

# ================================================================ MECHANISM
# Why does non-equilibrium proofreading fail? Test the re-entry hypothesis:
# proofreading requires the reset state to be MISMATCH-SENSITIVE.
mech = {}
kf, koff, kc = kin()
for label, n_reset in [('reset_to_PAM_bound(n=0)', 0), ('reset_to_n=3', 3),
                       ('reset_to_n=6', 6), ('reset_to_n=10', 10)]:
    kr = np.zeros(N_BP + 1); kr[12:] = 30.0
    S = N_BP + 1

    def solve_r(dg, nres):
        kp = np.full(S, 1e4); kp[:int(CAL['n_nuc'])] = 1e4 / CAL['nuc_slow']; kp[N_BP] = 0.
        km = np.zeros(S)
        for n in range(1, S):
            km[n] = kp[n - 1] * np.exp(np.clip(dg[n], -50, 50)) if kp[n - 1] > 0 else 0
        cat = np.zeros(S); cat[N_BP] = kc
        krr = kr.copy(); krr[:nres + 1] = 0
        tot = kp + km + koff + krr + cat; tot = np.where(tot > 0, tot, 1.)
        A = np.eye(S); b = np.zeros(S)
        for n in range(S):
            if kp[n] > 0: A[n, n + 1] -= kp[n] / tot[n]
            if km[n] > 0: A[n, n - 1] -= km[n] / tot[n]
            if krr[n] > 0: A[n, nres] -= krr[n] / tot[n]
            b[n] = cat[n] / tot[n]
        return float(np.linalg.solve(A, b)[0])

    on = solve_r(landscape([], delta=CAL['delta'], eps=CAL['eps'], barrier=CAL['barrier']), n_reset)
    e = []
    for m in (3, 10, 17):
        off = solve_r(landscape([m], delta=CAL['delta'], eps=CAL['eps'], barrier=CAL['barrier']), n_reset)
        e.append(on / off if off > 0 else np.inf)
    mech[label] = dict(P_on=float(on), eta_mm3=float(e[0]), eta_mm10=float(e[1]),
                       eta_mm17=float(e[2]))
OUT['mechanism_reset_depth'] = mech

# paired-nickase / dimerisation: two INDEPENDENT binding events
eta_single = {m: P_ON / pc([m]) for m in (3, 10, 17)}
OUT['paired_gate'] = {f'mm{m}': dict(single=float(eta_single[m]),
                                     paired=float(eta_single[m] ** 2),
                                     P_on_single=float(P_ON),
                                     P_on_paired=float(P_ON ** 2))
                      for m in eta_single}

# ================================================================ A-TESTS
A = {}

# A1 (revised probe): seed profile of the calibrated model
eta_pos = np.array([P_ON / pc([m]) for m in PROBES])
A['A1'] = dict(passed=bool(np.all(np.diff(eta_pos) <= 1e-9) and eta_pos[0] / eta_pos[-1] > 10),
               monotone=bool(np.all(np.diff(eta_pos) <= 1e-9)),
               ratio=float(eta_pos[0] / eta_pos[-1]),
               eta=eta_pos.tolist(), P_on=float(P_ON))

# A2/A3/A4: variant sweep = landscape shift (the hi-fi variant axis)
shifts = np.linspace(-0.45, 0.55, 26)
guides = [(3,), (7,), (12,), (17,), (5, 14), (2, 9), (11, 18), (4, 8, 16)]
rows = []
for s in shifts:
    cal = dict(CAL); cal['delta'] = CAL['delta'] - s
    on = pc((), cal)
    if on < 1e-9: continue
    for g in guides:
        off = pc(g, cal)
        if off <= 0: continue
        rows.append(dict(shift=float(s), mm=list(g), k=len(g),
                         P_on=float(on), eta=float(on / off)))
sing = [r for r in rows if r['k'] == 1]
mx = max(r['eta'] for r in sing); ceil = float(np.exp(CAL['eps']))
A['A2'] = dict(passed=bool(mx <= 1.05 * ceil), max_eta=float(mx), ceiling=ceil,
               frac=float(mx / ceil))
lp = np.log([r['P_on'] for r in rows]); le = np.log([r['eta'] for r in rows])
rho = float(spearmanr(le, lp).statistic)
rho_g = {str(g): float(spearmanr([np.log(r['eta']) for r in rows if tuple(r['mm']) == g],
                                 [np.log(r['P_on']) for r in rows if tuple(r['mm']) == g]).statistic)
         for g in guides}
A['A3'] = dict(passed=bool(rho < -0.90), rho_pooled=rho, rho_by_guide=rho_g,
               worst_by_guide=float(max(rho_g.values())))
c = np.polyfit(lp, le, 3); r2p = float(1 - np.sum((le - np.polyval(c, lp)) ** 2) /
                                       np.sum((le - le.mean()) ** 2))
r2g = {}
for g in guides:
    x = np.log([r['P_on'] for r in rows if tuple(r['mm']) == g])
    y = np.log([r['eta'] for r in rows if tuple(r['mm']) == g])
    cc = np.polyfit(x, y, 3)
    r2g[str(g)] = float(1 - np.sum((y - np.polyval(cc, x)) ** 2) /
                        max(np.sum((y - y.mean()) ** 2), 1e-30))
A['A4'] = dict(passed=bool(r2p > 0.95), R2_pooled=r2p, R2_by_guide=r2g,
               R2_min_by_guide=float(min(r2g.values())))

# A5
KDs = np.logspace(-6, -2, 40); mult = np.array([pam_search_multiplier(k) for k in KDs])
ana = 1 + 1.328e-3 / KDs + 2.1e-2 / 5e-2
A['A5'] = dict(passed=bool(np.max(np.abs(mult - ana) / ana) < .05 and
                           pam_search_multiplier(1e-5) > 10),
               max_rel_err=float(np.max(np.abs(mult - ana) / ana)),
               mult_10uM=float(pam_search_multiplier(1e-5)),
               mult_1mM=float(pam_search_multiplier(1e-3)))

# A6 with the calibrated model
dec, w = sample_decoys(rng, 20000, kmax=6)
pcl = np.array([pc(d) for d in dec])
acc = sample_accessibility(rng, len(dec)); acc_on = 0.7
flux = -np.log(1 - .80) / (acc_on * P_ON)
pe = edit_probability(pcl, acc, flux)
n3 = float(np.sum(w[pe > 1e-3])); n4 = float(np.sum(w[pe > 1e-4])); n6 = float(np.sum(w[pe > 1e-6]))
# chromatin-free comparator (cell-free assay analogue)
pe_free = edit_probability(pcl, np.ones_like(acc), flux)
n3_free = float(np.sum(w[pe_free > 1e-3]))
A['A6'] = dict(passed=bool(0 <= n3 <= 150 and n6 >= 3 * max(n3, 1)),
               n_1e3=n3, n_1e4=n4, n_1e6=n6, n_1e3_chromatin_free=n3_free,
               overprediction_factor=float(n3_free / max(n3, 1e-9)),
               census_le4=float(sum(v for k, v in census_table().items() if k <= 4)))

# A7
f4 = min_detectable_f(1e4, 1e-3); f6 = min_detectable_f(1e6, 1e-3)
A['A7'] = dict(passed=bool(f4 / f6 < 2), floor_1e4=float(f4), floor_1e6=float(f6),
               improvement=float(f4 / f6),
               floor_duplex_1e6=float(min_detectable_f(1e6, 1e-7)))

# A8 -- both non-equilibrium architectures, from saved frontier
fr = json.load(open('frontier.json'))
adv = max(x['advantage'] for x in fr)
A['A8'] = dict(passed=bool(adv > 2.0), best_advantage=float(adv),
               by_level={str(x['p_on_min']): float(x['advantage']) for x in fr},
               note="discard-channel architecture; reset-and-retry also gave 1.00x")

OUT['acceptance'] = A
OUT['summary'] = {k: A[k]['passed'] for k in sorted(A)}

# ================================================================ FIGURES
# F1 seed + frontier
fig, ax = plt.subplots(1, 3, figsize=(10.5, 3.1))
ax[0].semilogy(PROBES, eta_pos, 'o-', color=CB, ms=4)
ax[0].axhline(np.exp(CAL['eps']), ls='--', c=CR, lw=1)
ax[0].text(11, np.exp(CAL['eps']) * 1.15, r'ceiling $e^{\epsilon/k_BT}$', color=CR, fontsize=7.5)
ax[0].set_xlabel('mismatch position from PAM'); ax[0].set_ylabel(r'discrimination $\eta$')
ax[0].set_title(f'A1: seed profile (ratio={eta_pos[0]/eta_pos[-1]:.1f})')
levels = [x['p_on_min'] for x in fr]
ax[1].plot(levels, [x['eq']['seed_ratio'] for x in fr], 'o-', c=CB, label='equilibrium')
ax[1].plot(levels, [x['neq']['seed_ratio'] for x in fr], 's--', c=CR, label='non-equilibrium')
ax[1].axhline(100, ls=':', c=CK); ax[1].text(.33, 108, 'empirical Cas9 seed (>100x)', fontsize=7)
ax[1].set_yscale('log'); ax[1].set_xlabel(r'$P_{on}$ constraint'); ax[1].set_ylabel('max seed ratio')
ax[1].set_title('A8: NEQ gives no advantage'); ax[1].legend(fontsize=7)
for g, mk in zip(guides[:4], 'os^v'):
    sub = [r for r in rows if tuple(r['mm']) == g]
    ax[2].loglog([r['P_on'] for r in sub], [r['eta'] for r in sub], mk + '-', ms=3, lw=.9,
                 label=f'mm@{g[0]}')
ax[2].set_xlabel(r'$P_{on}$'); ax[2].set_ylabel(r'$\eta$')
ax[2].set_title(f'A4: no single curve ($R^2$={r2p:.2f})'); ax[2].legend(fontsize=7)
plt.tight_layout(); plt.savefig('F1_discrimination.png'); plt.close()

# F2 search + detection
fig, ax = plt.subplots(1, 3, figsize=(10.5, 3.1))
ax[0].loglog(KDs * 1e6, mult, c=CB)
ax[0].axvline(1328, ls='--', c=CR); ax[0].text(1450, 3, r'$c_{PAM}$=1.33 mM', color=CR, fontsize=7)
ax[0].set_xlabel(r'$K_D^{PAM}$ ($\mu$M)'); ax[0].set_ylabel('search-time multiplier')
ax[0].set_title('A5: PAM affinity ceiling')
depths = np.logspace(2, 7, 25)
for err, c, lb in [(1e-2, CR, r'$\epsilon$=1e-2'), (1e-3, CB, r'$\epsilon$=1e-3'),
                   (1e-7, CG, 'duplex 1e-7')]:
    ax[1].loglog(depths, [min_detectable_f(d, err) for d in depths], c=c, label=lb)
ax[1].axhline(1e-6, ls=':', c=CK); ax[1].set_xlabel('read depth')
ax[1].set_ylabel('min detectable frequency'); ax[1].set_title('A7: depth DOES help ($\\propto d^{-1/2}$)')
ax[1].legend(fontsize=7)
bins = np.logspace(-9, 0, 40)
ax[2].hist(np.clip(pe, 1e-9, 1), bins=bins, weights=w, color=CB, alpha=.75, label='in chromatin')
ax[2].hist(np.clip(pe_free, 1e-9, 1), bins=bins, weights=w, color=CR, alpha=.45, label='chromatin-free')
ax[2].axvline(1e-3, ls='--', c=CK); ax[2].text(1.3e-3, 300, 'GUIDE-seq', fontsize=7, rotation=90)
ax[2].axvline(1e-6, ls=':', c=CG); ax[2].text(1.3e-6, 300, 'duplex', fontsize=7, rotation=90)
ax[2].set_xscale('log'); ax[2].set_yscale('log')
ax[2].set_xlabel('per-site edit frequency'); ax[2].set_ylabel('expected # genomic sites')
ax[2].set_title(f'A6: {n3:.0f} visible / {n6:.0f} real'); ax[2].legend(fontsize=7)
plt.tight_layout(); plt.savefig('F2_search_detection.png'); plt.close()

# F3 FAILURE PROPAGATION: one knob (landscape shift) breaks multiple tests
sw = np.linspace(-0.45, 0.55, 26)
pf, sr, ot, cw = [], [], [], []
for s in sw:
    cal = dict(CAL); cal['delta'] = CAL['delta'] - s
    on = pc((), cal)
    if on < 1e-12:
        pf.append(np.nan); sr.append(np.nan); ot.append(np.nan); cw.append(np.nan); continue
    e1, e20 = on / pc([1], cal), on / pc([20], cal)
    pf.append(on); sr.append(e1 / e20)
    p2 = np.array([pc(d, cal) for d in dec[:4000]])
    a2, w2 = acc[:4000], w[:4000] * (len(dec) / 4000)
    fl = -np.log(1 - .80) / (acc_on * on)
    ot.append(float(np.sum(w2[edit_probability(p2, a2, fl) > 1e-3])))
    cw.append(float(on / pc([3], cal)))
fig, ax = plt.subplots(1, 4, figsize=(12.5, 2.9), sharex=True)
for a, y, t, thr, lab in [(ax[0], pf, 'A1/A6: $P_{on}$', .70, 'req >0.70'),
                          (ax[1], sr, 'A1: seed ratio', 10, 'req >10'),
                          (ax[2], ot, 'A6: sites >1e-3', 150, 'req <150'),
                          (ax[3], cw, r'A2: $\eta$ (mm@3)', np.exp(CAL['eps']), 'ceiling')]:
    a.plot(sw, y, c=CB); a.axhline(thr, ls='--', c=CR, lw=1)
    a.set_title(t, fontsize=8.5); a.set_xlabel('landscape shift (kT/bp)')
    a.text(.03, .9, lab, transform=a.transAxes, fontsize=7, color=CR)
    if t.startswith(('A6', 'A2')): a.set_yscale('log')
ax[0].axvspan(-0.45, -0.02, color='grey', alpha=.12)
plt.suptitle('F3  Failure propagation: a single landscape knob breaks A1, A2 and A6 in opposite directions',
             fontsize=9, y=1.04)
plt.tight_layout(); plt.savefig('F3_failure_propagation.png', bbox_inches='tight'); plt.close()

json.dump(OUT, open('final_results.json', 'w'), indent=1)
print(json.dumps(OUT['summary'], indent=1))
print('\ncalibrated P_on = %.3f, seed ratio = %.1f' % (P_ON, eta_pos[0] / eta_pos[-1]))
print('\nMechanism (reset depth -> proofreading gain):')
for k, v in mech.items():
    print(f"  {k:26s} P_on={v['P_on']:.4f}  eta@3={v['eta_mm3']:.2f}  eta@10={v['eta_mm10']:.2f}  eta@17={v['eta_mm17']:.2f}")
print('\nPaired independent gate:', json.dumps(OUT['paired_gate'], indent=1))
print('\nA6:', {k: round(v, 1) if isinstance(v, float) else v for k, v in A['A6'].items()})
print('A4 R2 pooled %.3f  min-by-guide %.3f' % (r2p, min(r2g.values())))
print('A3 rho pooled %.3f  worst-by-guide %.3f' % (rho, max(rho_g.values())))
Figure 1 of 3