General accelerator — seven-action real-ELF campaign, 2026-08-27

This records what the General family's real-ELF campaign executes, what it measures, and the one measured fact that keeps its census row from flipping.

What this is. solana-program-test execution of the real dclutch_general_accelerator_sbf.so, invoked by CPI from a real caller ELF, across all seven General actions at runtime widths N = 1 and N = 258, plus the joined seven-action artifact graph those actions authenticate against.

What this is not. It is not local-validator execution and it is not devnet or mainnet evidence. census observe has recorded nothing from it, and general-accelerator/process_instruction remains NEVER-EXECUTED in the census. See Why this is not a fast lane — the reason is a measurement in this document, not an omission.

Reproducing it

programs/dclutch-general-accelerator-sbf/program-test/run-program-test.sh

At bc5da76, with dclutch_general_accelerator_sbf.so built by cargo build-sbf at SHA-256 f71fecbc162961a6528027c4352fc3b0f98ac2bc59cb88d67e26a605aa1a6dbb and the test caller at 6358c4f506582eb1d79547d9823092adf4be465ab7ba28084c7c9cbf4f4cf9c3. Both builds emit zero SBF stack-frame diagnostics. The accelerator digest moved from 46ad714b... when the root-lifecycle conjunct landed; the caller, which authenticates no artifact, is byte-identical.

What executes

suitecases
program-test/tests/lifecycle.rs4
program-test/tests/freeze.rs2
program-test/src/joined_artifacts.rs (unit)3

Measurements

Compute limit 1,400,000, heap 32,768 (ProgramTest default; the campaign requests no heap frame, so this is the canonical ceiling and not an adjusted one). legacy_packet is one short-vector signature-count byte, one signature, and the exact canonical message bytes.

N = 1

actionCUaccountslegacy packetscratch pages
Consider56,180338113
Freeze32,608317453
InitializeSettlement81,392898673
Collect56,924 / 58,106 / 58,129708483
Materialize53,125688143
Distribute56,876 / 58,112 / 58,081708483
Close61,290878333

N = 258

actionCUaccountslegacy packetscratch pages
Consider528,648471,27317
Freeze65,019451,20717
InitializeSettlement618,2291031,32917
Collect146,880 / 147,307 / 148,101841,31017
Materialize141,356821,27617
Distribute144,519 / 146,542 / 145,740841,31017
Close155,7441011,29517

Repeated rows are the three settlement orders the campaign drives, in order.

The CU column is no longer joint, and it has been re-taken. The caveat this paragraph used to carry was that the run sat on a tree holding GEN-V3ACT's uncommitted GENERAL_HOT_COMMON_SCALARS_V3 88 -> 90. That work is committed (37d873f, and the conjunct it made room for in 2e890d4), and every number above is from one clean run at that HEAD. The accounts, legacy packet and scratch pages columns did not move at all, which is the load-bearing part: two more scalars is sixteen more bytes of bank and it crossed no boundary.

What the root-lifecycle refusal costs is 16 CU per action. Five actions at N=1 and four at N=258 moved by exactly +16 against the previous run, which is the price of one ProjectDataU8, one load_const and one scalar_eq in a shared prelude. Materialize at N=258 moved +18 and Close at N=258 moved -20; the repeated settlement rows cannot be compared positionally because the prior table's row order within an action is not recoverable. Neither outlier was chased: the worst action is 44% of the ceiling and no decision in this family turns on twenty compute units.

Re-measured after the Custody callee coordinate landed. The five actions that route to Custody carry one more account than the tables above did when TA-GEN first wrote them, because the topology now declares the release-selected Custody program the Hot executor resolves those routes through; before that they could not be invoked at all. Consider and Freeze route to no child, carry no callee, and their account counts are unchanged -- their CU moved by exactly +2, the cost of the extra checked add in general_effect_account_count_v3. Every other delta is +1 account, +1 legacy byte, and a few hundred CU.

Compute is not this family's blocker. The worst action at the canonical width is InitializeSettlement at 618,229 CU — 44% of the 1,400,000 ceiling, and that is the accelerator's own consumption inside a caller CPI, not the whole Trading transaction. The N=1 → N=258 growth is sublinear in every action except the two that fold the whole candidate (Consider, Initialize).

**Superseded on 2026-08-27 by The hash was 74% of the worst action below. The sentence was true, and it was true for a reason nobody had checked: 86% of Consider and 74% of InitializeSettlement were a software SHA-256 that did not need to be in the program at all. The worst action is now 164,400 CU, 11.7%** of the ceiling. The numbers in this section are the pre-conversion ones and are kept because the addendum is a comparison against them.

Why this is not a fast lane

tools/gauntlet/TIERS.md admits solana-program-test behind a tier's fast lane only when the tier states that every clause holds. One does not.

The tier does not depend on packet serialisation limits. ProgramTest does not submit a packet, so it cannot catch a 1,242-byte frame against a 1,232-byte limit. Found31 is exactly this defect; it survived every fixture test.

Solana's legacy packet maximum is 1,232 bytes. Six of the seven N=258 actions exceed it in this transport — 1,273, 1,276, 1,295, 1,310, 1,310 and 1,330 bytes. Only Freeze, at 1,207, fits. The campaign submits a legacy message deliberately, so the accelerator can see every scratch page directly; the production operator's plan is ALT-backed v0, where the account keys do not ride inline. That plan is not exercised here, so this campaign cannot discharge the packet clause for itself — and it is the exact defect class that put Found31 ten bytes over the limit after passing every fixture test.

At N = 1 every packet is 745–866 bytes and the clause is satisfied, so a fast lane restricted to N = 1 is admissible on this clause.

The machinery for such a lane now exists: ea4954a landed tools/gauntlet/program-test-evidence, deliberately general rather than Series-specific, whose record emits one file per transaction in the shape the census consumes. What a General N = 1 fast lane still needs is exactly four things: record called from the campaign's submit; a bindings.json binding each label to general-accelerator/process_instruction; a witnesses.json whose provenance is independent of the code under test; and a stage in tools/gauntlet/run.sh.

That last one is why this lane did not build it. run.sh, census/src/** and tier2/** are all under live uncommitted edits by the Claims/Custody lane as of this writing, tier3/ is already claimed, and TIERS.md says in as many words never to edit run.sh while a run is in flight. The correct handoff is this document plus the blocked.json entry, not a collision.

What would flip the row properly, fast lane or not: the accelerator deployed by a tier and invoked through the real Trading Hot path on a validator. A fast lane is always additional evidence — a route whose only observation came from one is recorded with that campaign name, and the report shows it. The Hot path is downstream of the Trading heap wall, which is not this family's to move.

Standing gap — CLOSED for construction, 2026-08-27

The N=258 account sets needed the ALT-backed v0 plan measured, not asserted. docs/evidence/GENERAL_ALT_PACKET_WITNESS_2026_08_27.md measures it: all seven actions compile packet-safe through compile_general_hot_v0, widest 918 of 1,232 bytes, and the same account set with no lookup table refuses PacketTooLarge. The derivation there reproduces every instruction-account count in the tables above, which is this campaign acting as its control.

What that closes is the construction clause — the wire the operator would submit. It is not execution, and it does not make this campaign a fast lane: this campaign still submits legacy messages, so the tier clause it fails is still failed here. The N = 1 fast lane described above remains admissible and unbuilt.

Addendum — the candidate now comes from a real batch (GEN-COLLECT, 2026-08-27)

Everything above was executed against an order flow that did not exist. The candidate's batch_id was the literal [0xb2; 32], and each Execution row's AuthenticatedOrderTermsV2 — the max_lots and max_quote_debit_per_lot the verifier enforces ExcessLots and QuoteLimit against — was a value the fixture asserted directly. Both are recorded as holes A and B of M-12 in docs/decisions/0009-general-batch-collection.md.

At e898d56 the same seven actions run against a batch that was really opened and orders that were really placed. terminal_fixture now opens a GeneralBatchV1 against a real GeneralRootV2 (consuming the root's exact next sequence), admits three GeneralOrderV1 records with maker funding checked against each order's worst case, closes the batch, and asserts the root returns to open_batches == 0. The candidate's batch_id is the digest of that opening; the row terms come from authenticate_order_execution_v1.

This does not re-take the tables above, because it did not move them. accounts, legacy packet and scratch pages are identical in all fourteen rows. That is the control: exchanging fabricated identities for real ones changes the inputs' provenance, not the frame's geometry, and without the table above there would have been nothing to check that against.

Two deltas, neither of which changes a conclusion here:

One defect found, of the kind only real identities can find. runtime_verify::le_numeric_id (:1345) orders a 32-byte identity as a little-endian 256-bit integer — byte 31 first — which is not the lexicographic order of [u8; 32]. The old fixture's identities were [low, 0, 0, …], where every high byte is zero and the two orderings agree, so the distinction was untestable. A real order_id is a SHA-256 digest, where they disagree almost always: sorting by Rust's own Ord refused three of the four suites outright with NonCanonicalOrder. A candidate builder must sort by the protocol's identity order, and the fixture now does, with the reason written beside the sort.

The census is unmoved and this addendum does not claim otherwise. This is still solana-program-test submitting legacy messages, so it still fails the same tier clause for the same measured reason — six of seven N=258 actions exceed the 1,232-byte limit. general-accelerator/process_instruction remains NEVER-EXECUTED. What changed is what the accelerator was fed, not the transport it was fed through.

The hash was 74% of the worst action (SHASEAM, 2026-08-27)

The accelerator computed SHA-256 in software. Not by decision: the General contract crates are deliberately SDK-free, and a no_std library that cannot name the Solana SDK cannot name the sol_sha256 syscall either, so they linked sha2. A software compression loop costs roughly 104.75 CU per byte; the runtime's syscall costs 85 CU plus about half a CU per byte. One digest of the 4,288-byte verified candidate is the difference between 456,008 CU and 2,234.

crates/dclutch-sha256-adapter is now the one named place that knows a runtime exists, and every General digest goes through it. SHA-256 is SHA-256, so every digest VALUE is unchanged, nothing was regenerated, and no identity, artifact or fixture moved.

Reproduced with the same command at 6d1ee60c, accelerator ELF ead59b2264647649456bbd787f59e777b319887803fee3d189298e72b324dcf9, caller 3e9621e99612fca5fca98d98c15d1bf2a46b0c8d6637a1427cf6511ce01b7038, both with zero SBF stack-frame diagnostics. Suites 4/4 + 2/2 + 3/3, unchanged.

N = 1 (re-taken)

actionCUΔaccountslegacy packetscratch pages
Consider36,071−20,109338113
Freeze32,643+35317453
InitializeSettlement61,267−20,125898673
Collect56,953 / 58,135 / 58,158+29708483
Materialize53,151+26688143
Distribute56,903 / 58,108 / 58,139+27708483
Close61,314+24878333

N = 258 (re-taken)

actionCUΔaccountslegacy packetscratch pages
Consider74,835−453,813 (−85.8%)471,27317
Freeze65,054+35451,20717
InitializeSettlement164,400−453,829 (−73.4%)1031,32917
Collect146,909 / 147,336 / 148,130+29841,31017
Materialize141,382+26821,27617
Distribute144,546 / 145,767 / 146,569—841,31017
Close155,768+241011,29517

accounts, legacy packet and scratch pages are identical in all fourteen rows, as they have been through every re-take of this table. Removing a hash implementation moves compute and nothing else, and the three unmoved columns are what says so.

**Superseded on 2026-08-29 by The rent-refund account below.** The two InitializeSettlement rows are stale by exactly one account and one legacy byte: f581af6b widened Custody InitializeReplay from twelve accounts to thirteen on 2026-08-28, and InitializeSettlement is the only General action that embeds that operation. The other twelve rows are still exact. These tables are kept because the Δ column above is a comparison against them.

The control is the slope, not the constant

Sixteen commits touched this program's surface between the baseline tables and this run, so a raw CU difference is not attributable to any one of them. The per-outcome slope is:

actionCU/outcome beforeafter
Consider1,838.4150.812.2× cheaper
InitializeSettlement2,088.9401.35.2× cheaper
Freeze126.1126.1unchanged
Materialize343.3343.3unchanged
Close367.5367.5unchanged

Five of the seven actions have a bit-identical slope and moved by a uniform +24 to +35 constant — that constant is the other sixteen commits, and it is not this change. The two that moved are exactly the two that fold the whole candidate, which are exactly the two that digest it. That is the attribution.

The independent prediction was 456,008 CU of software hash against 2,234 by syscall, i.e. 453,774 CU removable. Measured: 453,813 and 453,829. The two agree to within 55 CU on a 450,000 CU quantity, from opposite directions — instruction-count arithmetic over the compression loop, and a real ELF in a real runtime.

Headroom, and what actually binds

InitializeSettlement is the binding action at both ends:

beforeafter
N = 258 share of the 1,400,000 ceiling44.2%11.7%
outcomes before the ceiling (linear in N)~632~3,337

This headroom is not the operative limit and this document should not be read as if it were. Six of the seven N=258 actions still exceed Solana's 1,232-byte legacy packet maximum, unchanged by this work, and that is still why the census row does not flip. Compute stopped being close to a wall; the packet never stopped being one.

Binary size

The accelerator ELF is 193,968 → 139,136 bytes, −54,832 (−28.3%). The SHA-256 round-constant table is gone from .rodata and sol_sha256 appears in the dynamic imports. dclutch_core_sbf.so fell by the same order in the same change, 989,104 → 933,328. Trading still carries the implementation: three shadow_digest_v3 functions take unbounded slices whose preimages cannot be restated inside a 4,096-byte SBF frame, and they need a caller-supplied scratch rather than a bound. See the commit message at 6d1ee60c for the arithmetic.

Addendum, 2026-08-29 — the rent-refund account

One account joined InitializeSettlement, and this document could not say so. The tables above have carried a commit for the run since the first re-take, but never one per row, so a number could go stale without anything in the document changing. It did. From 2026-08-28 16:56 EDT until this re-take, the InitializeSettlement account count here described no code.

It was caught by the evidence-pinned control in crates/dclutch-operator/src/general_hot_v3.rs, the_derived_geometry_reproduces_the_executed_campaign_frame, whose seven literals are this document's numbers and are deliberately not derived from that crate. Six of the seven still reconciled; the seventh asserted 104 against a recorded 103. A control that agreed with the code would have said nothing — it is precisely because those literals come from outside the crate that the drift was visible at all.

Provenance

Re-taken at bb4e83ca, accelerator ELF ce180115ccf17a12d07a7108f425ace9329a9922cd6f80baab4cf34a7391afe7, caller 3e9621e99612fca5fca98d98c15d1bf2a46b0c8d6637a1427cf6511ce01b7038, both with zero SBF stack-frame diagnostics. The accelerator digest moved from ead59b22...; the caller is byte-identical to the 6d1ee60c run, as it has been through every re-take, because it authenticates no artifact.

This was a filtered run: --test lifecycle only, 4/4. freeze.rs (2) and the joined_artifacts unit suite (3) were not re-run, because neither contributes a row to these tables — lifecycle.rs alone drives all seven actions at both widths. The three suite counts quoted earlier in this document belong to their own runs and are not restated here.

N = 1 (re-taken at bb4e83ca)

actionCUaccountslegacy packetscratch pagescount set by
Consider36,113338113unmoved since 6d1ee60c
Freeze32,659317453unmoved since 6d1ee60c
InitializeSettlement61,753908683f581af6b
Collect56,991 / 58,196 / 58,173708483unmoved since 6d1ee60c
Materialize53,171688143unmoved since 6d1ee60c
Distribute56,942 / 58,147 / 58,178708483unmoved since 6d1ee60c
Close61,334878333unmoved since 6d1ee60c

N = 258 (re-taken at bb4e83ca)

actionCUaccountslegacy packetscratch pagescount set by
Consider74,877471,27317unmoved since 6d1ee60c
Freeze65,070451,20717unmoved since 6d1ee60c
InitializeSettlement164,9701041,33017f581af6b
Collect146,947 / 147,374 / 148,168841,31017unmoved since 6d1ee60c
Materialize141,402821,27617unmoved since 6d1ee60c
Distribute144,585 / 146,608 / 145,806841,31017unmoved since 6d1ee60c
Close155,7861011,29517unmoved since 6d1ee60c

The InitializeSettlement N=258 row is measured twice per run, by two independent test functions, and both report 164,970 / 104 / 1,330.

Why exactly one row moved

f581af6b ("direct: make replay setup and trade exterior callable") appended CustodyFrameRoleV1::RentRefund at coordinate 12 of Custody InitializeReplay and raised INITIALIZE_REPLAY_ACCOUNT_COUNT_V1 from 12 to 13. It is appended past the existing frame, so it renumbered nothing, and it is reached from General through one edge only:

general_effect_account_count_v3(InitializeSettlement)
  = general_child_account_start_v3        11   (8 prefix + 3 readonly evidence)
  + PROTOCOL_POSITION_ADMIT_ACCOUNT_COUNT_V1  26
  + INITIALIZE_REPLAY_ACCOUNT_COUNT_V1        13   <- 12 before f581af6b
  + OPEN_VAULT_ACCOUNT_COUNT_V1               16
  + general_custody_callee_account_count_v3    1
  = 67                                              (66 before)

and the campaign frame is 2 + ADMITTED_RUNTIME_ACCOUNTS_START_V3 + 67 + general_scratch_pages_v3(N). Close embeds CloseReplay, the three transfer actions embed Transfer, and Consider and Freeze route to no child at all — none of which f581af6b touched. Six of seven reconciling was not a puzzle; it was the signature of a single-operation frame change. It is also what lets the shared inputs be exonerated by arithmetic rather than by bisection: the prefix and the callee coordinate are shared with rows that still reconciled, so neither could have moved without moving those too.

What the account buys, and what it costs

The old path called create_account, which fails outright if the account already holds lamports — so any stranger could permanently block replay creation by sending dust to the PDA. The new path transfers any excess above exact rent to the named beneficiary, charges the payer only the shortfall, then allocates and assigns at exact rent. The program authenticates the account rather than trusting it: rent_refund.key.to_bytes() != request.rent_refund, and distinctness from both payer and replay, refuse with AccountFrame.

The price, isolated by this table: +1 account, +1 legacy byte, and +570 CU on InitializeSettlement at N=258 (164,400 → 164,970). Every other action moved by +16 to +42 CU, the shared-prelude drift of a day of unrelated commits, with accounts, legacy packet and scratch pages bit-identical. InitializeSettlement remains the binding action at 11.8% of the 1,400,000 ceiling.

The packet conclusion is unchanged and the number is not. Six of the seven N=258 actions still exceed the 1,232-byte legacy maximum; the largest is now 1,330 rather than 1,329. The census row does not flip for the same reason it did not flip before.

The blast radius is Custody-wide

f581af6b changed a Custody frame, not a General one. Any family frame or evidence document that embeds Custody InitializeReplay and was measured before 2026-08-28 16:56 EDT is stale by exactly one account. General's is the one that was found, and it was found only because General had a control pinned to evidence rather than to itself. A family with no such control has no signal — which is the argument for the count set by column above, and for putting one in every table that records a width.

Addendum, 2026-09-02: the frame table was corrected and the System program became an account

Every account count in every table above this line is stale by +31 at N=258 and +30 at N=1, and only one of the two causes is a General change.

How this was caught, and the lesson repeated. Not here. The harness in programs/dclutch-general-accelerator-sbf/program-test/tests/lifecycle.rs DERIVES the expected frame and asserts it against the real ELF, so it stayed green through both changes — correctly, because the frame it derives is the frame the ELF builds. What went red was the control in another crate, dclutch-operator::general_hot_v3::the_derived_geometry_reproduces_the_executed_campaign_frame, which pins the numbers THIS DOCUMENT records. That is the second time a control pinned to evidence rather than to itself has been the only signal, and it is the argument for the count set by column.

Re-run 2026-09-02 against the real ELF built from 57049067, filtered to real_sbf, --test-threads=1. All fifteen actions, both widths.

N = 1 (re-taken at 57049067)

actionCUaccountslegacy packetscratch pagescount set by
OpenBatch49,34463857468f7c849 + e3298c9a
CloseBatch51,52963889468f7c849 + e3298c9a
Consider55,24565923468f7c849 + e3298c9a
Freeze51,39563857468f7c849 + e3298c9a
SubmitCandidate58,383661,052468f7c849 + e3298c9a
VerifyCandidateRow76,212 / 80,699 / 87,746701,152468f7c849 + e3298c9a
CloseCandidate54,28163953468f7c849 only
PlaceOrder96,7691571,048468f7c849 + e3298c9a
InitializeSettlement81,199122980468f7c849 + e3298c9a
Collect76,384 / 77,610102960468f7c849 + e3298c9a
Materialize72,365100926468f7c849 + e3298c9a
Distribute76,393 / 77,574102960468f7c849 + e3298c9a
Close80,649119945468f7c849 + e3298c9a

N = 258 (re-taken at 57049067)

actionCUaccountslegacy packetscratch pagescount set by
OpenBatch84,840771,3191868f7c849 + e3298c9a
CloseBatch87,025771,3511868f7c849 + e3298c9a
Consider97,093791,3851868f7c849 + e3298c9a
Freeze86,891771,3191868f7c849 + e3298c9a
SubmitCandidate102,620801,5141868f7c849 + e3298c9a
VerifyCandidateRow419,251841,6141868f7c849 + e3298c9a
CloseCandidate89,777771,4151868f7c849 only
PlaceOrder220,3601711,5101868f7c849 + e3298c9a
InitializeSettlement187,5011361,4431868f7c849 + e3298c9a
Collect171,244 / 171,673 / 172,4651161,4221868f7c849 + e3298c9a
Materialize165,4781141,3881868f7c849 + e3298c9a
Distribute168,895 / 170,911 / 170,1161161,4221868f7c849 + e3298c9a
Close179,9841331,4081868f7c849 + e3298c9a

The packet conclusion is unchanged and worse in degree. This readonly CPI harness submits a LEGACY message on purpose so the accelerator sees every scratch page directly, and at N=258 every one of the thirteen actions now exceeds the 1,232-byte legacy maximum; the largest is VerifyCandidateRow at 1,614. The production path is the ALT-backed v0 plan, whose witness is dclutch-operator::general_hot_v3::every_action_is_alt_packet_safe_at_the_canonical_runtime_width — re-pinned in the same commit, widest 968 of 1,232, which is 264 bytes of margin and the only number that speaks to what a validator will accept.

Addendum, 2026-09-02: OpenBatch and CloseBatch stop declaring a tail they never read

Two rows of thirteen moved, and they moved to their own N=1 rows.

actionaccountslegacy packetscratch pagesCU
OpenBatch N=25877 → 631,319 → 85718 → 484,840 → 49,189
CloseBatch N=25877 → 631,351 → 88918 → 487,025 → 51,375

Every other action at N=258 is unchanged in accounts, packet and pages. Both moved rows are now byte-for-byte the same frame those two actions present at N=1, which is the whole claim: their register bank does not grow with the Product width, so nothing derived from its width grows either.

The packet conclusion changes for these two and only these two. Eleven of the thirteen N=258 actions still exceed the 1,232-byte legacy maximum in this readonly CPI harness. OpenBatch at 857 and CloseBatch at 889 no longer do — they are legacy-packet-safe at the maximum Product width, without a lookup table.

Why the tail went

transition_artifacts_v3::append_item said it in its own words before this landed: "The batch record has no per-outcome tail; the bound check alone." Their effect already declared zero item operations, and the one item instruction their transition emitted was the shared bound check OUTCOME < OUTCOME_COUNT — over a register whose only legal value is the coordinate it occupies, which hot_candidate_v3 refuses anything else for. Neither action read the tail; declaring it was the only thing making the bank grow.

Lean is the author. DClutchSemantics.GeneralTransitionV3.actionItemScalarStride and GeneralRequestProfilesV1.actionItemScalarStride decide the stride per action, and four artifacts carry the result — AccountProfile, RequestProfile, transition and effect — which artifacts_v3 joins and refuses to see disagree.

The Trading heap, measured through the same instrument

programs/dclutch-trading-sbf/program-test/general-hot, real ELFs, three widths, hot-cu-profile heap marks:

NscalarspagesaccountsCUpeak heap
2151459847,26758,324
13151459837,42758,324
258151459861,22558,324

Flat to the byte, 56 heap marks each, peaking at the same phase. Before this the peak was 59,376 + 528*(N - 2) of 65,536 — an identity that reproduced both measured peaks and predicted the abort, N = 13 committing at 65,184 and N = 14 dying needing 65,712 — and N = 258 refused earlier still with 0x4000 UnsupportedContent at hot_v3.rs:3986, because 151 + 6*258 = 1,699 scalars exceeded MAX_HOT_SCALARS_V3 = 512.

That constant needed no lift and did not get one. The scalar count is 151 at every width now, and the old line's intercept is the new flat value: 59,376 + 528*(N - 2) is 58,320 + 528*N, against 58,324 measured — four bytes of allocator rounding, and the 1,052-byte drop from 59,376 is exactly the two outcomes of tail that N = 2 itself used to carry.