NIM paper · interactive edition Interactive edition · paper, explanations and Lean proofs.

Section 5.5, “Serialising positions and retaining state”Serialisation: rows of each position, a separator, aligned indices; the separator resets only the accumulator

Checked definition or construction Lean contains the definition or construction, with checked properties.

1 Paper text

Serialising positions and retaining state. Write position Pt=(xt,1,…,xt,N)P_t=(x_{t,1},\ldots,x_{t,N}), with each heap represented by one BB-bit row. Feed successive positions as the stream x1,1,…,x1,N⏟P1, SEP,x2,1,…,x2,N⏟P2, SEP,….\underbrace{x_{1,1},\ldots,x_{1,N}}_{P_1},\ \mathtt{SEP},\quad \underbrace{x_{2,1},\ldots,x_{2,N}}_{P_2},\ \mathtt{SEP},\quad\ldots . Each row arrives as a block of BB bits together with a row/separator tag. A separator marks the end of a complete position; the agent chooses a move only after that position has been read. The next observed position is then appended to the stream. For comparisons of individual heaps, indices remain aligned across positions, including rows for empty heaps. A scan of the current board suffices for nim-sum evaluation without earlier game positions; cross-position memory additionally supports the comparisons below. The hidden state is carried across rows and position boundaries. A separator resets the accumulator for the next scan while preserving selected information about the completed position; it does not reset the entire memory.

Generated from paper_jmlr.tex, lines 827–834.

2 Explain this

The input protocol. Positions are fed one row (heap) at a time, in a fixed heap order. After all rows of each position comes a separator token SEP. Rows for empty heaps are still sent, so heap ii is always the ii-th row.

What the separator does. It marks a complete position. It resets the accumulator for the next scan but keeps selected information about the finished position (its summary). It does not wipe the whole memory.

Example. Positions (3,5,6)(3,5,6) then (3,4,6)(3,4,6) become the stream 3,5,6,SEP,3,4,6,SEP3,5,6,\mathtt{SEP},3,4,6,\mathtt{SEP}.

Row-wise recurrence: registers, separators and replay

Exact construction trace Every value is computed from the construction’s definitions for the example shown.

The board arrives as a stream of rows, one heap per token, with separators (SEP) between positions and a reset at the start of a game. Step through the replay controller: in scan mode it XORs each row into the accumulator; the first separator outputs the summary, stores it and switches to replay mode; during replay the same rows arrive again and each is tested against the stored summary, keeping the first cancelling candidate and a fallback; the second separator outputs the move. The board-storage variant instead writes the rows into memory and chooses the move at its single separator. In the replay controller a separator clears the accumulator and keeps the summary of the position just completed, with its validity bit; an extra separator is read as an empty position, whose summary is 0. Only a reset returns every register, validity included, to its initial value. The board-storage separator clears its accumulator and pointer and keeps the stored board. The third protocol shows the summary mechanism of Proposition 5.13 alone: it reports summaries and differences but chooses no move.

The current NIM board already determines optimal play. The memory shown here makes that answer computationally accessible by retaining partial results across rows; an earlier game position is needed only for the optional comparison of consecutive positions.

  1. RESETreset
  2. row 3scan
  3. row 4scan
  4. row 6scan
  5. SEPsummary
  6. row 3replay
  7. row 4replay
  8. row 6replay
  9. SEPmove
  10. SEPsummary
  11. SEPmove

11 / 11 SEP: separator after replay: output the move, return to scan mode

Registers after this update

mode
scan
accumulator
0 000
previous summary
0 000
stored summary used in replay
0
heap counter
0
first candidate
no move
first fallback
no move

Outputs so far

  • after token 5: summary σ = 1 (no previous summary)
  • after token 9: move heap 0 → 2
  • after token 10: summary σ = 0, difference from the previous summary 1
  • after token 11: move no move

Edge case outside the protocol: the extra separator is read as an empty position. No theorem is stated for such streams; the update rule is simply applied.

Counting the work

  • One decision on 3 heaps takes 2N + 2 = 8 updates: N scan rows, a separator, the same N rows again, a separator (plus one reset at the start of a game). The board is supplied twice; the controller does not store it.
  • Memory: accumulator, previous summary, stored summary, counter, candidate and fallback registers: 4 + 5B + 3r bits with N < 2r; here B = 3, r = 2, so 25 bits.
  • Each single update is a shallow circuit (replay controller: Boolean depth ≤ 8, ≤ 18 threshold layers). The number of sequential updates grows with N. That no shallow family computes the complete board-to-move map is a separate statement, conditional on the named classical input that parity is not in AC0.
All 11 updates as a table
#TokenRegisters afterOutput
1RESETscan, acc 0, prev 0*, idx 0, cand no move, fb no move
2row 3scan, acc 3, prev 0*, idx 0, cand no move, fb no move
3row 4scan, acc 7, prev 0*, idx 0, cand no move, fb no move
4row 6scan, acc 1, prev 0*, idx 0, cand no move, fb no move
5SEPreplay, acc 0, prev 1, idx 0, cand no move, fb no moveσ = 1
6row 3replay, acc 0, prev 1, idx 1, cand heap 0 → 2, fb heap 0 → 2
7row 4replay, acc 0, prev 1, idx 2, cand heap 0 → 2, fb heap 0 → 2
8row 6replay, acc 0, prev 1, idx 3, cand heap 0 → 2, fb heap 0 → 2
9SEPscan, acc 0, prev 1, idx 0, cand no move, fb no movemove heap 0 → 2
10SEPreplay, acc 0, prev 0, idx 0, cand no move, fb no moveσ = 0, diff 1
11SEPscan, acc 0, prev 0, idx 0, cand no move, fb no movemove no move

* no previous summary yet (validity bit 0).

Game: (3, 4, 6) —your move (δ = 1)→ (2, 4, 6) —opponent (δ = 7)→ (2, 4, 1)

Only the boards at your decision times

Position fedsummary σdifference
(3, 4, 6)1—
(2, 4, 1)76

The difference 6 = 1 ⊕ 7 spans two moves: yours and the opponent’s.

Including your own post-move board

Position fedsummary σdifference
(3, 4, 6)1—
(2, 4, 6)01
(2, 4, 1)77

Now the last difference is 7, the opponent’s move alone, because your post-move board (2, 4, 6) is the preceding frame.

Computed with the replay controller; feeding your post-move board costs another 2N + 2 updates, and the move it then outputs is discarded.

How this relates to the Lean proof. Each update is one step of Controller.cstep (replay) or BoardStore.bsStep (board storage) on the token streams Controller.dtoks and BoardStore.btoks. The whole-game theorems WholeGame.replay_wins and BoardStore.bits_store_wins prove that, from every board with nonzero nim-sum, iterating these updates wins against every legal opponent. The summary mechanism is Recurrent.step. The comparison of decision-time boards with the post-move board is Bypass.diff_with_postMove and Bypass.diff_decision_only.

What this does not show. Each single update is shallow; the number of updates per decision (2N+22N+2, or N+1N+1 with board storage) grows with the board. That no shallow family computes the complete board-to-move map is a separate statement, conditional on the named classical input that parity is not in AC0\mathrm{AC}^0. The edge cases (an extra separator, a reset) lie outside the protocol streams that the theorems quantify over; the update rule is simply applied. The recurrent strategies are proved in their own stateful game semantics; connecting them to the later history model of weak mastery is a separate adapter that has not been built (coverage record). No recurrent network was trained.

Depicted in Lean: Controller.cstep, Controller.dtoks, Recurrent.replayStep, Recurrent.step, Recurrent.sep_resets, ControllerBits.bstep_reset, BoardStore.bsStep, BoardStore.btoks, Controller.crun_decision, Controller.stream_fits, WholeGame.replay_wins, BoardStore.bits_store_wins, Bypass.diff_with_postMove, Bypass.diff_decision_only, Recurrent.circuit_resources, BoardStore.bsForms_res, BoardStore.bsOuts_res, BoardStore.bs_net.

Computed in your browser by a short re-implementation of the definitions above (tools/visuals/nimvis-core.js). It is not itself checked by Lean. At build time it agreed with Lean's own evaluation of those definitions on all 566 reference cases. The comparison compares every register and output after every token.

3 How this is formalised

Recurrent.serialize turns a list of positions into rows followed by SEP; Recurrent.step is one recurrent update. Recurrent.sep_resets: after SEP the accumulator is 00, the summary is retained and the validity bit is set; rows never change the summary. Recurrent.fold_invariant describes the outputs for any finite sequence of positions, including the first position and empty positions (consecutive separators).

Coverage record
Paper anchorpar-serialising-positions-and-retaining-state-p1 (website anchor; the manuscript has no label here)
Displayed asSection 5.5, “Serialising positions and retaining state”
ClaimSerialisation: rows of each position, a separator, aligned indices; the separator resets only the accumulator
Coverage categoryChecked definition or construction
Statement sourcepaper_jmlr.tex lines 827–834; SHA-256 of the normalised block source 1b991b2a8d9d385c…
Lean declarations
Explanationsexplain/c-stream, formal/c-stream
Build evidencefresh isolated rebuild, audit report

4 Lean statement and source

Principal declarations

def Recurrent.serialize

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 57–59) · file SHA-256 1f2994aefc75e7f5…

Serialise positions: rows then SEP, position after position.

Statement as elaborated and printed by Lean 4.26.0

List (List ℕ) → List NimAudit.Recurrent.Token
Exact source text, lines 57–59
57/-- Serialise positions: rows then SEP, position after position. -/58def serialize (Ps : List (List Nat)) : List Token :=59  Ps.flatMap (fun P => P.map Token.row ++ [Token.sep])

Project declarations in the statement: Recurrent.Token

Project declarations used by the proof or definition body

Recurrent.Token, Recurrent.Token.row, Recurrent.Token.sep

def Recurrent.step

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 47–50) · file SHA-256 1f2994aefc75e7f5…

One recurrent update; SEP produces an output.

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State →
  NimAudit.Recurrent.Token → NimAudit.Recurrent.State × Option NimAudit.Recurrent.Out
Exact source text, lines 47–50
47/-- One recurrent update; SEP produces an output. -/48def step (s : State) : Token → State × Option Out49  | .row x => (⟨s.acc ^^^ x, s.prev, s.valid⟩, none)50  | .sep => (⟨0, s.acc, true⟩, some ⟨s.acc, if s.valid then some (s.prev ^^^ s.acc) else none⟩)

Project declarations in the statement: Recurrent.Out, Recurrent.State, Recurrent.Token

Project declarations used by the proof or definition body

Recurrent.Out, Recurrent.Out.mk, Recurrent.State, Recurrent.State.acc, Recurrent.State.mk, Recurrent.State.prev, Recurrent.State.valid, Recurrent.Token, Recurrent.Token.casesOn, Recurrent.Token.row, Recurrent.Token.sep

theorem Recurrent.sep_resets

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 130–133) · file SHA-256 1f2994aefc75e7f5… Axioms reported by the fresh build: propext

RESET BEHAVIOUR: after SEP the accumulator is 0, the summary is retained and the validity bit is set; rows never change prev or valid.

Statement as elaborated and printed by Lean 4.26.0

∀ (s : NimAudit.Recurrent.State),
  (NimAudit.Recurrent.step s NimAudit.Recurrent.Token.sep).1 =
    { acc := 0, prev := s.acc, valid := true }
Exact source text, lines 130–133
130/-- RESET BEHAVIOUR: after SEP the accumulator is 0, the summary is retained and the131    validity bit is set; rows never change prev or valid. -/132theorem sep_resets (s : State) :133    (step s Token.sep).1 = ⟨0, s.acc, true⟩ := rfl

Project declarations in the statement: Recurrent.Out, Recurrent.State, Recurrent.State.acc, Recurrent.State.mk, Recurrent.Token.sep, Recurrent.step

Project declarations used by the proof or definition body

Recurrent.Out, Recurrent.State, Recurrent.Token.sep, Recurrent.step

theorem Recurrent.fold_invariant

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 95–109) · file SHA-256 1f2994aefc75e7f5… Axioms reported by the fresh build: propext, Quot.sound

FOLD INVARIANT. From any state with empty accumulator, for ANY finite sequence of positions (empty positions allowed): the t-th output is (sigma(P_t), Delta) where Delta = prev XOR sigma(P_1) if the starting state is valid (none otherwise) for t = 1 and sigma(P_{t-1}) XOR sigma(P_t) for t ≥ 2; the final state retains the last summary with valid = true and acc = 0.

Statement as elaborated and printed by Lean 4.26.0

∀ (s : NimAudit.Recurrent.State),
  s.acc = 0 →
    ∀ (Ps : List (List ℕ)),
      NimAudit.Recurrent.run s (NimAudit.Recurrent.serialize Ps) =
        (NimAudit.Recurrent.expectedState s Ps, NimAudit.Recurrent.expectedOuts s.valid s.prev Ps)
Exact source text, lines 95–109
95/-- FOLD INVARIANT.  From any state with empty accumulator, for ANY finite sequence96    of positions (empty positions allowed): the t-th output is97    (sigma(P_t), Delta) where Delta = prev XOR sigma(P_1) if the starting state is98    valid (none otherwise) for t = 1 and sigma(P_{t-1}) XOR sigma(P_t) for t ≥ 2;99    the final state retains the last summary with valid = true and acc = 0. -/100theorem fold_invariant (s : State) (hs : s.acc = 0) (Ps : List (List Nat)) :101    run s (serialize Ps) = (expectedState s Ps, expectedOuts s.valid s.prev Ps) := by102  induction Ps generalizing s with103  | nil => simp [serialize, run, expectedState, expectedOuts]104  | cons P Ps ih =>105    have e : serialize (P :: Ps) = (P.map Token.row ++ [Token.sep]) ++ serialize Ps := by106      simp [serialize]107    rw [e, run_append, run_position, hs, Nat.zero_xor]108    rw [ih ⟨0, nimSum P, true⟩ rfl]109    simp [expectedState, expectedOuts]

Project declarations in the statement: Recurrent.Out, Recurrent.State, Recurrent.State.acc, Recurrent.State.prev, Recurrent.State.valid, Recurrent.expectedOuts, Recurrent.expectedState, Recurrent.run, Recurrent.serialize

Project declarations used by the proof or definition body

Recurrent.Out, Recurrent.Out.mk, Recurrent.State, Recurrent.State.acc, Recurrent.State.mk, Recurrent.State.prev, Recurrent.State.valid, Recurrent.Token, Recurrent.Token.row, Recurrent.Token.sep, Recurrent.expectedOuts, Recurrent.expectedState, Recurrent.run, Recurrent.run_append, Recurrent.run_position, Recurrent.serialize, nimSum

Definitions these statements use

inductive Recurrent.Token

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 32–34) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

Type
Exact source text, lines 32–34
32inductive Token where33  | row (x : Nat)34  | sep

Project declarations in the statement: none

Project declarations used by the proof or definition body

none

inductive Recurrent.Out

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 41–43) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

Type
Exact source text, lines 41–43
41structure Out where42  summary : Nat43  diff : Option Nat

Project declarations in the statement: none

Project declarations used by the proof or definition body

none

inductive Recurrent.State

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 36–39) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

Type
Exact source text, lines 36–39
36structure State where37  acc : Nat38  prev : Nat39  valid : Bool

Project declarations in the statement: none

Project declarations used by the proof or definition body

none

def Recurrent.State.acc

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 37–37) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State → ℕ
Exact source text, lines 37–37
37  acc : Nat

Project declarations in the statement: Recurrent.State

Project declarations used by the proof or definition body

Recurrent.State

def Recurrent.State.prev

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 38–38) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State → ℕ
Exact source text, lines 38–38
38  prev : Nat

Project declarations in the statement: Recurrent.State

Project declarations used by the proof or definition body

Recurrent.State

def Recurrent.State.valid

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 39–39) · file SHA-256 1f2994aefc75e7f5…

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State → Bool
Exact source text, lines 39–39
39  valid : Bool

Project declarations in the statement: Recurrent.State

Project declarations used by the proof or definition body

Recurrent.State

def Recurrent.expectedOuts

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 84–88) · file SHA-256 1f2994aefc75e7f5…

Expected outputs for a sequence of positions from a state with empty accumulator.

Statement as elaborated and printed by Lean 4.26.0

Bool → ℕ → List (List ℕ) → List NimAudit.Recurrent.Out
Exact source text, lines 84–88
84/-- Expected outputs for a sequence of positions from a state with empty accumulator. -/85def expectedOuts : Bool → Nat → List (List Nat) → List Out86  | _, _, [] => []87  | v, q, P :: Ps =>88    ⟨nimSum P, if v then some (q ^^^ nimSum P) else none⟩ :: expectedOuts true (nimSum P) Ps

Project declarations in the statement: Recurrent.Out

Project declarations used by the proof or definition body

Recurrent.Out, Recurrent.Out.mk, nimSum

def Recurrent.expectedState

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 90–93) · file SHA-256 1f2994aefc75e7f5…

Expected final state.

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State → List (List ℕ) → NimAudit.Recurrent.State
Exact source text, lines 90–93
90/-- Expected final state. -/91def expectedState : State → List (List Nat) → State92  | s, [] => s93  | _, P :: Ps => expectedState ⟨0, nimSum P, true⟩ Ps

Project declarations in the statement: Recurrent.State

Project declarations used by the proof or definition body

Recurrent.State, Recurrent.State.mk, nimSum

def Recurrent.run

Module NimRecurrent (lean/phase3/NimRecurrent.lean, lines 52–55) · file SHA-256 1f2994aefc75e7f5…

Run the recurrence over a token list, collecting outputs.

Statement as elaborated and printed by Lean 4.26.0

NimAudit.Recurrent.State →
  List NimAudit.Recurrent.Token → NimAudit.Recurrent.State × List NimAudit.Recurrent.Out
Exact source text, lines 52–55
52/-- Run the recurrence over a token list, collecting outputs. -/53def run : State → List Token → State × List Out54  | s, [] => (s, [])55  | s, t :: ts => ((run (step s t).1 ts).1, (step s t).2.toList ++ (run (step s t).1 ts).2)

Project declarations in the statement: Recurrent.Out, Recurrent.State, Recurrent.Token

Project declarations used by the proof or definition body

Recurrent.Out, Recurrent.State, Recurrent.Token, Recurrent.step

Full modules

ModuleFileLinesImports
NimRecurrentlean/phase3/NimRecurrent.lean555NimAttain, Circuit

Build evidence: fresh isolated rebuild of all 38 bundled modules with Lean (version 4.26.0, started 2026-10-10T15:21:56Z, finished 2026-10-10T15:27:00Z: 38/38 modules compiled, 1588 axiom reports, no errors. See Build for the commands and the independent audit of 2026-10-10 (dated evidence).