/machines/1RB0RD_1LC0RA_---1RD_1RA1LE_1RC0LF_1LE0LB/rocq-plan.md · raw

C2-552 halting proof: Rocq plan

Target: Lemma halt: halts tm c0. in machines/1RB0RD_1LC0RA_---1RD_1RA1LE_1RC0LF_1LE0LB/1RB0RD_1LC0RA_---1RD_1RA1LE_1RC0LF_1LE0LB.v, passing tools/check_kill.sh --halt. Written 2026-10-12, after a design pass with small prototypes.

Verdict

Feasible, with a known architecture and no new framework. The proof is a checked certificate runner in the style of verify/Sk1Machines.v (TM91/TM94), run in the mirrored machine tmf := Eval compute in (flip tm). In the mirror the counter sits to the right of the head, which is the orientation busycoq's call-stream framework (segRLs/sideRLs in Individual.v, sideRLs_halt in LongitudinalHalt.v) is written for.

The riskiest unknowns were checked and hold: 1. Every unit boundary is crossed by a uniform call protocol. 2. solve_seg/es prove the parametric window lemmas in the flipped machine. 3. The root, batch, halting and flip glue compose. 4. native_compute runs 150k checker-sized iterations in seconds.

1. Orientation and the call protocol (the key fact)

The cut of a unit is the left edge (in tm coordinates) of its boundary block 1^(3g+2). This covers plain 11 and glued 1^5, 1^8, … alike. The tail's boundary has a cut too.

Every increment is one call from the root (the tail and the head) into the units to its left, so the calls nest the way a binary counter carries: - In: the head crosses a cut leftward exactly once, in state B, onto a cell holding 0. - Out: it crosses back rightward exactly once, in state D, onto a cell holding 0 (the boundary's first cell, zeroed on the way in).

Evidence: - Empirical (rocq/tools/cuts.py, log rocq/tools/cuts_3e7.txt): from the blank tape to 3·10⁷ steps, about 4.5k increments and 33,739 cut observations. Every crossed cut follows exactly [(L,B,0),(R,D,0)], with 0 deviations. This covers plain and glued boundaries b2 … b41 and the resets, glue, unglue, absorb and edge events of that window. - Inside a unit the same holds at the left edge of every passive wall or stack 1^(3j+1): in F onto 0, out D onto 0 (rocq/tools/walls_cross.py). So passive chains can be cut there as well, giving per-block induction. - Proved in Coq (below): with hin := (B,[]), hout := (D,[]) and h := [(hin,hout)], the touch lemmas are plain segRL/segRLs facts. Since the protocol is a single call type, every forwarded stream is h^^p. TM91 needed two call types, h and h'; this machine needs only one.

Words are lists in root-first order (nearest the head first, i.e. tm right-to-left). A segRR output is far-first, which is busycoq's convention for l <* w.

2. Prototype results (compiled, Coq 8.20.1, all < 2 s, all Closed under the global context except the flip transfer)

Files are in rocq/proto/. Compile them with the standard flags from a temp dir.

file content result
root_k1_batch.v root t X := 0inf <{{A}} ([0;1]^^t ++ [0;1;1]) *> X; root_in, root_out, root_step (one call via sideRL tmf hin hout), root_steps (d calls, induction: root t X -->* root (d+t) X'); k=1 digit words w1 a c (lead 1111, x=2a+1, y=2c+1) and w0 a c (lead 00, x=2a, y=2c); stop1 : segRL tmf hin hout (w0 (S a) (S c)) (w1 a c); pass1 : segRLs tmf h h (w1 a c) (w0 (S (S a)) c) (via segRLs_lrcons of pass1_RR/pass1_LL); halt0 : halts tmf (l {{B}}> w0 a 0 *> r); pair1, pairs1 n : segRLs tmf (h^^(n*2)) (h^^n) (w0 (S a) (n+c)) (w0 (S (n+a)) c) (the Sk1 total_spec pattern) all Qed
blocks.v passive block blk m j := [0] ++ [1;0]^^m ++ stack j (odd gap 2m+1, stack 1^(3j+1)); blockRR : segRR tmf (F,[]) (F,[]) (blk m j) ([0;1] ++ [0;1;1]^^j ++ [0;1]^^m), blockLL : segLL tmf (D,[]) (D,[]) (mid (S m) j) ([0] ++ [1;0]^^m ++ stack j ++ [0;1]), parametric in m and j Qed by solve_seg
halt_flip.v root_halt : sideRLs_halt tmf [(hin,hout)] X -> halts tmf (root t X) (LongitudinalHalt); a toy halting root; flipc0 : halts tmf c0 -> halts tm c0 by apply flip_halts_iff; exact H (tmf ≡ flip tm, flip_conf c0 ≡ c0 by conversion) Qed. Adds only functional_extensionality_dep, which is in the baseline
perf.v synthetic checker-shaped loop: 150k N_iter_until iterations over 25 records with 70–75-bit N fields, a 1300-element list rebuilt every 2nd iteration native_check_eq: 11–16 s wall for the whole file, 450 MB RSS. vm_check_eq: 59 s. Negative control (fuel too small) fails as it should

Tactic notes are logged in atlas/rocq-notes.md under "C2-552 … Rocq design".

3. (a) Tape representation and the rule lemmas

Checker data (all numbers N, pair counts so parity is structural)

Words (Prop side only; never computed on the big data)

Mapping of every rules3.py case to lemmas

pairs = Sk1-style n-touch induction. Every lemma is parametric (lpow on nat; N.to_nat at the interface).

rules3 case lemma(s), flipped machine status
tail t += 2 per increment root_in, root_out, root_step, root_steps proved
M stop, k=1 (lead 00, any boundary: leave the boundary in r) stop1 proved
M pass, k=1 (lead 1111, boundary 11), including y 1→0 (the fuel-out shape [x,00,0]) pass1 (c arbitrary, c=0 allowed) proved
call into [x,00,0] halts (F reads 1 after 00) halt0 + sideRLs_halt + segRLs_sideRLs_halt_concat + root_halt proved
M touches k ≥ 2 yblockRR/LL (y-gap + last passive, entered in B) ∘ chainRR/LL (induction over mids from blockRR/LL) ∘ leadRR/LL (pass: m1, 1111, x, 11, out in B) or lead_stop (00 turnaround, F→D); composition lemmas RR∘RL∘LL = RL and RR∘(call)∘LL = segRLs_lrcons blocks proved, rest to do
leadless unit (all walls +2) yblock ∘ chain ∘ xblockRR/LL (first wall passive) to do
empty unit emptyRR/LL on [y, 11] to do
glue (last gap 1→−1) for digit, leadless, U0, and right after an unglue yglueRR/LL: the y-block with c=0. The last stack shifts 2 cells toward the root and ends 1 cell past the cut (captured: 01111 → 11101). Then a re-cut equality that merges it into the root-side boundary to do
unglue g=1 and g≥2 (boundary 1^(3g+2), lead 1111, a pass): [x,1111,…] → [2,00,x+1,1111,…] / [2,00,0,1^(3g−2),x,1111,…] unglue1RR/LL, ungluegRR/LL (stack shift over [1;1;1]^^g; g up to 6·10¹⁴, so symbolic) to do
U0-1111 (U0 digit, lead moves like a passive, edge e≡1) U0 side lemma + edge1 to do
U0-stop the stop lemmas (the turn never reaches the edge) reuse
edge e≡0 (overflow: e += 3, U0's first gap −2) with U0 empty / leadless edge0_empty, edge0_leadless (sideRL); edge0s by induction for m overflows (sideRLs (h^^m)) to do
edge e≡1 fossilization (old edge becomes U0's first stack, new 111 two cells out) edge1 (sideRL, uses 0inf cells) to do
absorb (empty U0's gap −1, so e += r−1) absorb (sideRL) + re-cut to do
reset first half (tail boundary 1^5) … 1^5 [t] → … 11 [2,00,t+2], call continues root_reset1 : sideRL tmf hin hout X X' -> rootG t X -->+ rootN (digit 2 00 (t+2)) X' to do
reset second half (no tail) … [2,00,t+2] → … [1,1111,t] 11 [1] root_reset2 (no side call: the last unit stops the carry) to do

About 30 lemma families in all. - Words for each family come from the capture tools in rocq/tools/ (block.py L R, seg.py, rootcap.py). - They also come from solve_seg itself: when the stated output is wrong, the error message prints the computed word. That is how blockRR's middle word was found.

4. (b) Running about 54k structural events and the batches inside Coq

The task brief estimated about 60k structural events. Rerunning fast.py this session gives the exact counts: - 53,973 structural single increments to the fuel-out at increment 11,631,266,794,472,450,654,090. - Events among them: - glue 28,314, unglue 28,234; - overflow-edge0 48,714, leadless-U0 48,654; - reset1 81, reset2 82; - absorb, edge1, U0-stop, U0-1111 and empty: 60 each.

Batch (d increments, no structural event)

Structural increment call1

Checker and soundness

Computation

5. (c) Opening and endgame

6. (d) Compile-time and memory risks

7. Work order (each stage non-blocking, about 2–4 h)

  1. proof.md (CLAUDE.md: before the .v). Include the protocol, the rule table above, and the batch closed forms. In parallel, work/coqmirror.py: a Python mirror of the Coq checker with the same records, guards, cases, proposal and canon. - Run it from cfg342 to Halted. - Assert equality with fast.py at all 82 resets (work/resets_long.jsonl) and at work/fuelout_config.json. - Output FUEL and per-case counts. This settles coverage before any Coq lemma is written: any Failed in the mirror is a missing lemma family.
  2. Lemma catalogue: capture the words of every family from small instances (rocq/tools/block.py). Then prove them in this order: - root and resets; - k=1 (done); - chain; - k≥2 stop/pass; - leadless and empty; - glue; - unglue g=1, g≥2; - U0/edge (edge0, edge1, absorb, U0-1111). Each file is compiled separately (-native-compiler no is enough).
  3. Batch, call1, canon, checker, soundness. Smoke-test on a prefix with a check_reach variant that proves c0 -[tmf]->* denote cfg_target, for example the reset-40 snapshot. Native-run timing comes from that test.
  4. Full run, the kill file and check_kill --halt. Then status.json and STATUS.md.

8. Risks, most serious first

  1. Lemma inventory and words for the structural families. These are unglue g≥2 with stacks, glue right after an unglue, the U0/edge cases and the resets. A shape no lemma covers makes the checker return Failed. Mitigation: the mirror decides coverage first, and the capture tools and solve_seg errors give the words.
  2. Re-cut (canon) equalities after glue, absorb, unglue and the resets: tedious lpow/N.to_nat list algebra. Write one normalization tactic early.
  3. The proposal must match fast.py. Fallback keeps the proof sound, but iteration counts can explode.
  4. es/solve_seg normal-form mismatches (seen once). Budget for manual shift_rule proofs.
  5. coqchk timeout (advisory, expected).
  6. Rule trust is not a risk to soundness. The proof never assumes rules3. If a rule is wrong far out (more than 10 units, never TM-checked), its lemma or the checker fails and the halt prediction is refuted, not certified.