How we proved our kills never halt
All 18 kills are proved the same way at heart. Each one is a Rocq file, checked against the unmodified busycoq framework, that ends in the statement "this machine, started on a blank tape, never halts".
This page gives the common recipe, a table of the 18 kills, what is special about each kind of proof, and a few sentences you can use to describe a proof in chat.
1. The common recipe
Two words first.
- A macro rule is a proved shortcut: "from any tape of this shape, with any block sizes a, b, c, the machine reaches that other shape after finitely many exact steps". It is proved once for all sizes, usually by induction, so it covers rounds no simulation could ever reach.
- An invariant is a property of the tape that, if it holds before a round, still holds after it. Example: "every digit of the counter still has at least one unit of fuel".
The recipe has five steps.
-
Find the repeating structure. Simulate, and look at the tape each time the head comes back to the same spot. In every kill it is a counter of some kind: a binary counter whose digits are blocks, a bouncer that piles up blocks, or a stack of blocks that call each other.
-
Write one round as a macro rule. Usually a handful of rules, one per kind of round (an ordinary count, an overflow, a merge).
-
Find an invariant that excludes halting. The halting transition can only fire from a few tape shapes that no rule covers. The invariant is chosen so that, while it holds, some rule always applies, and every rule keeps it.
-
Show the run starts inside it. Simulate the opening exactly (from a few steps to about 92,000) until the tape has the round shape, and check the invariant there.
-
Let Rocq check every step. busycoq's
multistep_nonhalthandles the opening: if the start reaches tape c in exact steps and c never halts, the start never halts. Itsprogress_nonhalt_condhandles the rest: if every tape with the property moves, by at least one real step, to another tape with the property, and the first tape has it, the machine never halts.
Every macro rule is an exact run of the real transition table, so no halting step can hide inside a round.
Two tricks come back again and again.
- Do not predict the unpredictable. Many runs depend on bits that look random: a parity, which digit runs dry first, which answer a tail gives. The invariant is chosen to survive every branch, so the proof never needs to know which one happens.
- Compute the small cases. When the invariant only works for large sizes, Rocq simply computes the early rounds. For #30's table that is about 700 rounds.
2. The 18 kills
Status: accepted means our busycoq pull request is merged; submitted means it is in an open pull request; claimed means the Rocq proof passes all our checks but is not submitted yet. Currently that is 1 accepted, 4 submitted and 13 claimed.
Each label links to the machine's card. #11b, #25, #24 and #12b have no card of their own, so they link to their kill page. Rows are grouped by family, and within a family by transition table; the first row of each table is its parent proof.
| Kill | Machine | Family | Approach | Status |
|---|---|---|---|---|
| #4 | 1RB0LA_0RC1RD_0LD1RE_1LA0RB_0RD1RF_1RC--- |
Sync bouncer counter, #4's table | a safe region for the block counts that both even and odd cycles keep | claimed |
| #4b | 1RB---_0LC1RF_1LE0RD_0RB1RC_1RD0LE_0RC1RA |
Sync bouncer counter, #4's table | same proof as #4, with renamed states and a new start | claimed |
| #8 | 1RB0LF_0RC---_1RD0RA_1LE0RC_0LA0LE_1LE1LB |
Sync bouncer counter | a window kept at every overflow outside a pause, for a spread-out binary counter | claimed |
| #11 | 1RB0LE_1RC0RC_1LD0RB_1LA0LE_1LD1LF_1LD--- |
Skelet 1-like Class 2, #11's table | an invariant showing an increment never reaches a 0 digit with no fuel left | claimed |
| #33 | 1RB0RE_1LC0RE_1LD0LD_1RA0LC_1RA1RF_1RA--- |
Skelet 1-like Class 2, #11's table | #11's proof mirrored, new start (#11's state D) | claimed |
| #33b | 1RB0LD_1RC0RE_1LD0RE_1LA0LA_1RB1RF_1RB--- |
Skelet 1-like Class 2, #11's table | same proof as #33 (#11 mirrored), new start | claimed |
| #11b | 1RB0RB_1LC0RA_1LE0LD_1LC1LF_1RA0LD_1LC--- |
Skelet 1-like Class 2, #11's table | same proof as #11, new start (#11's state B) | claimed |
| #3 | 1RB0LD_1RC0RA_1RD1RF_1LA0LE_1RE1LD_0RB--- |
Skelet 1-like Class 2, #3's table | an invariant on a binary down-counter that regroups its blocks | submitted (PR #15 open) |
| #25 | 1RB1RF_1LC0LE_1RD0LB_1RA0RC_1RE1LB_0RD--- |
Skelet 1-like Class 2, #3's table | same proof as #3, new start (one block 2 longer) | submitted (PR #15 open) |
| #24 | 1RB0RE_1LC0RA_1LD0LB_1LA1LF_1LE1RA_0LC--- |
Skelet 1-like Class 2, #3's table | #3's proof from a new start, carried over by a mirror and a renaming | submitted (PR #15 open) |
| #3b | 1RB0RD_1RC1RF_1LD0LE_1RA0LC_1RE1LC_0RA--- |
Skelet 1-like Class 2, #3's table | same proof as #3, new start on its own path | submitted (PR #15 open) |
| #12 | 1RB1LC_1RC1LB_1LD0RB_0LE1RD_---0LF_0LA1LF |
Counter Class 4 | exact cost-counting for a self-similar call stack | claimed |
| #12b | 1RB1LA_1LC0RA_0LD1RC_---0LE_0LF1LE_1RF1LB |
Counter Class 4 | same proof as #12, one changed transition and a 2-call edge budget | claimed |
| #2 | 1RB0RF_1LC0RE_0LC0LD_1RD1LB_0RF1RA_0RB--- |
Bell eats counter | a proved neighbour's sweep recipe plus a +1 step, shown never to get stuck | accepted (PR #14 merged) |
| #30 | 1RB0RF_1RC1RF_1LD0RE_---0LE_1LA0LE_0RA0LC |
Bell eats counter, #30's table | an invariant showing each round's recursive cleanup fits in its budget | claimed |
| #7 | 1RB0RA_1LC0LD_1LE1LD_0LB0RE_1RF0LA_---0RA |
Bell eats counter, #30's table | same proof as #30, mirrored, new start (#30's state E) | claimed |
| #31 (W4-1) | 1RB1RF_1LC0RD_---0LD_1LE0LD_1RA0RF_0RE0LB |
Bell eats counter, #30's table | same proof as #30, renamed states, new start (#30's state B) | claimed |
| W4-2 | 1RB1LF_0RC0RB_0LD1LA_1LC0LE_0LA---_1LA1LF |
Bell eats counter | a size invariant tying the counter to the length of the last round | claimed |
Only 8 of the 18 needed a new idea (#2, #4, #8, #11, #3, #12, #30, W4-2). The other 10 reuse a parent proof.
3. What is special about each kind of proof
Counters whose digits carry fuel (#11's and #3's tables)
The tape is a binary counter whose digits are pairs of blocks, such as (10)^u (01)^v. Each count step changes the block lengths of every digit it touches.
In #11 (counting up), each increment burns one 01 of "fuel" from every digit it touches. In #3 (counting down), each decrement lengthens one block of a pair and shortens the other.
At an overflow (for #3, every second time the count reaches zero) the blocks re-pair: each block joins a neighbour to form a new digit, so the roles of the two kinds of block swap, and the counter gains a digit.
The danger is a block running dry at the wrong moment. The invariant combines parity (a block that runs dry has the right parity, so it just merges with its neighbour, which a rule covers) with lower bounds (the other blocks never get too short). It never needs to know when the merges happen.
Bell eats counter (#2, #30's table, W4-2)
One side of the tape is a counter. The other side is a budget, or a stretch of filler blocks. When the budget runs out, the machine eats the low end of the counter and refills the budget. Halting needs an eat to meet a tape in a bad shape.
- #2. busycoq already proves a neighbour (TM7 in its SOMDCv6 file) that differs in one transition; ours adds a +1 pass each time a sweep enters a filler block. The invariant is "the sweep recipe applies to this tape". The work is showing that every tape the recipe produces is again one it applies to, counting the pending +1s.
- #30's table (#30, #7, #31). Each round first digests a messy leftover from the last round, recursively: one bounce can run a whole mini-round. The invariant keeps the leftover small, so digesting it costs about the square root of the budget. From budget 17,705 on it always fits; Rocq computes the roughly 700 rounds before that.
- W4-2. The counter switches between two encodings and can be left messy after an eat. Linear bounds tie the counter's size to the last round's length (about 1 to 2.5 times it, the exact cap set by parity). They keep rounds long enough that a bite on a still-messy counter never lands at a bad moment.
Sync bouncer counters: a safe region (#4, #8)
A binary counter sits at one end, a row of blocks grows at the other, and the head bounces between them adding 1 per bounce. The numbers that matter are the block counts at each overflow.
- #4. One cycle changes the counts by one of two linear formulas, picked by a parity that looks random. Scaled by the counter's size, there is a six-sided region that both formulas map into itself. So the parity never matters. The run enters the region at step 17,215.
- #8. The same idea with an interval. A small tail at the right end answers each overflow with "wrap to 0" or "pause and rebuild", leaving four possible routes from one overflow to the next. A window for the left stretch's length, measured against the counter, survives all four.
- #8's tail is tracked only through its two front bits. Where they allow both answers, the proof accepts both.
A call stack with an exact cost (#12)
The tape is a stack of blocks of 1s, and each block acts like a function call that costs its caller one 1. Each round starts at the left edge with 3 calls to spend; halting needs a 4th.
A round replays the machine's own earlier history one level down, with some extra lone 1s added. An induction says exactly which inner rounds cost 3 instead of 2. The real top level has no extra lone 1s, so every top round costs exactly 2. This is exact counting, not an estimate.
Siblings: same table, new start
Nine kills share a transition table with a parent kill. They differ only in the state letters (for #33, #33b, #24 and #7 also by a mirror) and in which state the run starts from.
The same table means the same macro rules and the same invariant. The only new piece is the opening: simulate from the new start until the tape has the round shape, and check the parent's invariant there. For example, #3b reaches a tape with #3's property after 38,732 steps.
Some siblings shadow the parent's run (#25's far block is always 2 longer than #3's). Others take their own path through the same rules (#3b, #4b, #33b, #7, #31). Both are fine, because the invariant is about the shape of the tape, not about one particular run. Most sibling files are the parent's proof with the state names swapped, plus a new opening.
Mirrored siblings
A mirrored machine swaps L and R in every transition, so its run is the parent's run seen in a mirror.
busycoq already proves that mirroring a machine (flip_halts) or renaming its states (perm_halts) does not change whether it halts. So for #24 and #7, the file proves the parent's table never halts from the matching start state. It then checks that the mirrored, renamed table is exactly the parent's, and these two lemmas carry the result over.
33 is done the other way. Its tape rules are rewritten in mirrored form directly, and #11's invariant is used word for word, since it only talks about digit sizes, never about direction. #33b then reuses #33's file with its states renamed.
Neighbours: one transition changed (#12b)
After renaming, #12b differs from #12 in one transition. That transition only fires when a call starts at the left edge, where it takes a 6-step detour to the same tape #12 reaches in 2 steps.
So #12's whole picture carries over. But #12b's edge budget is 2 calls instead of 3, so it needs #12's exact result that every top round costs 2, with zero slack.
Adapted templates
busycoq holds proofs of hundreds of machines. For each new parent proof we started from the closest one and kept its architecture: how a tape shape is written, how one round is proved, and the final non-halting call. The rules, the invariant and the arithmetic are new.
| Kill | Template in busycoq | How close |
|---|---|---|
| #2 | SOMDCv6, TM7 | one transition away |
| #4 | SBCv7, TM4 | one transition away |
| #11, #3 | SBCv6, TM21 | same kind of counter, different table |
| #8 | CounterClass3v2, TM1 | same architecture, unrelated table |
| #12 | CounterClass2v1 | overall shape only |
| #30 | FractalBEC, tm10 | mirrored, one transition away; only its bounce rule is kept |
| W4-2 | BECv5, TM1 and TM2 | one transition away |
"Adapted" means few lines in common. Counting the non-blank lines of our file that also appear, word for word, somewhere in the template's file, only 10% to 34% match. One changed transition can change the behaviour a lot (#4 is one transition from its template, yet only 18% of its lines match). By the same count, 81% to 95% of a sibling's lines also appear in its parent's file.
4. How to say it in one breath
Examples you can paste or adapt.
- #3: "It's a binary down-counter whose digits are pairs of blocks; each decrement grows one block and shrinks the other in every pair it touches, and every second zero re-pairs the blocks. A parity-plus-size invariant shows no block runs dry at a bad moment; it holds after 78,710 steps, and Rocq checks every rule."
- #4: "Each cycle the block counts follow one of two linear maps, picked by a parity we can't predict, but there's a hexagon (in the counts scaled by the counter size) that both maps send into itself. The run is inside it from step 17,215, so whichever parities come, it never halts."
- #30's table: "Every round has a budget N and first has to digest the last round's leftover. The digestion is recursive but costs about √N, so from N ≈ 17,700 it always fits, and Rocq simply computes the ~700 rounds before that."
- A sibling (#3b): "Same table as #3 with the states renamed, just started in another state. The rules and invariant are #3's word for word; the only new part is the opening, which lands inside #3's invariant after 38,732 steps." For a mirrored twin such as #24, add: "busycoq's flip and permute lemmas carry #3's proof across the mirror."