# Amendments to the two pre-lock checks: October 13, 2026

**First written at 09:33 Halifax**, after Colson's decision and before the remaining pieces ran. **Corrected at about
10:25**, after every re-run had finished (see "Correction" just below). At both times, no calibration outcome (C1 to
C4) and no feasibility outcome (F1 to F4) had been computed for any start. The plan, prelock_checks_plan.md, stays
word for word as written: its file has not changed since 08:07 Halifax (SHA-256
f291b9de49df0034c4a2cd0221315a325295831e1ccd0cd5badc18bb5d7da522). These amendments sit beside it. The cause test's
decision is unchanged: FAIL, as registered. This is a toy model, so whatever it shows applies only to these runs.

Colson's decision (October 13, in his words): "approve Option 1: revise V1 to use the unchanged Cause Test
implementation as the same-machine reference and finish both pre-lock checks." He asked that the original V1 failure,
its cause and the revision be recorded openly, and that the revised check never be presented as the original
preregistered one.

## Correction (about 10:25 Halifax)

The first version of this amendment said the cause was a different machine ("So the departures come from the
machine"). That was my reading of starts 1 to 7, made before starts 8 to 18 had run, and the later starts do not
support it. Starts 13 to 18 reproduce their saved runs exactly on this machine; starts 1 to 12 do not. The departures
follow how each saved run was made: the two batches made in paused pieces depart, and the batch made in one go does
not (see "The cause" below). The revised check is unchanged by this correction, since it never depended on the cause.

## Timeline (Halifax)

| time | what |
|---|---|
| 08:07 | the plan written, before either check ran |
| 08:25, 08:27 | the two dry runs, on old start 1 (output kept in dryruns/) |
| 08:29 to 08:42 | calibration pieces 1 to 4: old starts 1 to 7 finished; V1 as preregistered not met for any of them |
| 08:52 | the first reference run: the cause test's unchanged base-run code departs from the saved runs at the same times |
| about 09:00 | the calibration paused; prelock_status.md written; the choice put to Colson |
| before 09:24 | Colson approved the revision (quoted above) |
| 09:33 | this amendment's first version and the code changes that record the environment, before any further piece |
| 09:33 to 10:00 | calibration pieces 5 to 11: old starts 8 to 18 finished |
| 10:01 to 10:04 | feasibility's base runs, old starts 13 to 18, in one piece |
| 10:06 to 10:16 | reference runs for old starts 8 to 18 |
| about 10:25 | the correction above |

## Amendment 1: the exactness check (both checks)

### The check as preregistered (kept in the record)

Calibration: "V1: every re-run reproduces its saved run exactly" (coherence as saved, to complex64, every log growth
of the gap, and the settling trace). "If V1 or V2 fails, the calibration gives no reading and is reported as it
stands." Feasibility: "Each base run is run again (it must reproduce its saved run exactly)."

### What happened

In all 18 re-runs, the settling matched its saved run exactly. For old starts 1 to 12, the gap growth then departed
from the saved run, and later coherence did too. For old starts 13 to 18, every number matched the saved run to the
end.

| old start | first gap difference | first coherence difference |
|---|---|---|
| 1 | t = 1,185 | t = 2,151 |
| 2 | t = 2,065 | t = 3,396 |
| 3 | t = 1,680 | t = 2,258 |
| 4 | t = 2,300 | t = 3,479 |
| 5 | t = 2,440 | t = 3,150 |
| 6 | t = 2,625 | t = 3,047 |
| 7 | t = 320 | t = 1,834 |
| 8 | t = 625 | t = 1,527 |
| 9 | t = 1,440 | t = 1,845 |
| 10 | t = 1,020 | t = 1,615 |
| 11 | t = 1,765 | t = 2,812 |
| 12 | t = 1,985 | t = 2,694 |
| 13 to 18 | none | none |

After the first difference, later numbers differ and the differences grow. **V1 as preregistered is not met in the
calibration (6 of 18 starts).** Under it the calibration gives no reading, and that stays in the record: its report
gives this reading beside the revised one. **In the feasibility check, V1 as preregistered is met.** Its base runs are
old starts 13 to 18, and all six reproduce their saved runs exactly.

### The cause: how the saved runs were made

The saved runs come from three files, all made on October 11 by the nudge test's run code (nudge_test.py's lyapunov),
which pauses at most every 230 s:

| old starts | file | finished (Halifax) | how it ran | running time | today |
|---|---|---|---|---|---|
| 1 to 6 | nudge-test-2026-10-10/sim_lyapunov4000_long.npz | about 10:48 | all six in one task, in pieces of at most 230 s, picked up again from saved progress | 3,834 s for the six together | departs (first gap difference t = 1,185 to 2,625) |
| 7 to 12 | coherence-growth-2026-10-11/sim_fresh4000.npz | about 19:01 | one task per start, in pieces of at most 230 s, picked up again from saved progress | 667 to 1,289 s per start | departs (t = 320 to 1,985) |
| 13 to 18 | coherence-growth-2026-10-11/sim_replication4000.npz | 20:10 | one task per start; all six finished in the first piece (20:08 to 20:10), with no pause | 153 to 155 s per start | reproduces exactly |

The cause test's own base-run code (ct.base_run), unchanged and run in one go on this machine, departs from the saved
runs of starts 1 to 12 at exactly the same times as the checks' re-runs, and it matches the saved runs of starts 13 to
18 exactly (machine_check.txt). On October 12 it also repeated starts 13 to 18 exactly, to t = 5,495
(cause_targets.txt). So the departures do not come from the checks' code.

The two batches made in paused pieces depart, and the batch made in one go does not. The nudge test's run code says,
in its own description, that picking up again "changes the numbers only at the level of rounding", and that "a run
that paused follows an equally valid path that slowly parts from the unbroken one". It picks up from saved progress
without rebuilding the arrays' memory layout. These checks' pieces do rebuild it, and they agree with the unbroken run
to the last digit. So the most likely cause is the pauses in the first two batches.

What is not settled:

- The two paused batches also ran 4 to 8 times slower than today, so a different or busier machine may have played a
  part. Their files record no machine, so this cannot be checked.
- The pause points were not recorded, so no start's first difference can be matched to a pause.
- Starts 1 to 6 ran as one task, so they paused at the same moments. Yet their first differences fall at different
  times (t = 1,185 to 2,625). A pick-up that changes a result only rarely would give that pattern, but this was not
  tested.

A short test could check the mechanism on this machine: run one old start with the nudge test's code, unchanged, once
unbroken and once with a pause, and see whether the two part. It has not been run.

### The revised check: V1-revised (not the preregistered check)

**V1-revised:** each re-run agrees to the last digit with the cause test's base-run code (ct.base_run, unchanged), run
in one go for the same start on the same machine. Every piece of the re-run, and its reference run, must be made in
one numerical environment.

- Calibration: coherence (as saved, to complex64), every log growth of the gap, and the settling trace, over all 6,000
  units.
- Feasibility: coherence at full precision at every time unit, and every kept state (T − 500, T − 100 and T of each
  moment).
- One numerical environment means the same processor model, Python, C library, numpy, and numpy's processor routines.
  Each piece checks this before it starts and stops if anything differs. A start that has not run wholly in one
  environment is run again from the beginning, and its reference run is repeated.

If V1-revised is not met, the check gives no reading, as the plan says for V1.

**Why it tests what V1 was for.** V1 was there to show that each re-run is the cause test's base run itself: that the
fresh copies (calibration), the kept states (feasibility), and stopping and going on (both) leave its arithmetic
untouched. Agreement with the unchanged code, run unbroken on the same machine, shows exactly that. Agreement with a
saved file shows it only if that file was itself made unbroken and with the same arithmetic. The files for starts 1
to 12 were not made unbroken.

### What is the same in the comparison (verified)

| | re-run | reference | how it was verified |
|---|---|---|---|
| seeds | starting phases from nt.starting_phases(N, slow, seed, 1); the gap's direction from (20261011, seed) | the same calls | read in both codes; the settling equals the saved run's in all 18 starts |
| model and settings | N = 4,000 per group, K = 1.90, eps = 0.1, step 0.025, 300 units of settling, 6,000 units of run, gap 10⁻⁷ put back every 5 units, all taken from the cause test's code; nudge_test.py's model and step function | the same | imported, not copied, and recorded with every reference run. The loop around the step is the checks' own, so that is what this comparison tests |
| what is added | the fresh copies use their own seeds (20261013, 1, seed, T, j) and their own arrays; feasibility keeps copies of states | nothing added | they draw nothing from the base run's generators; agreement to the last digit over the whole run |
| numerical environment | this container | this container | it has run without a restart since 07:40:54 Halifax, before piece 1. Recorded with every piece from calibration piece 5 and feasibility piece 1, and with every reference run |
| stopping and going on | calibration: stopped about 190 s into each piece, with the exact state kept at the last put-back and the memory layout rebuilt on going on (four or five pieces per start); feasibility's base runs: one piece each, no stop | one go, no stops | agreement to the last digit over the whole run |

One difference is recorded. The reference runs use one BLAS thread per worker, while the re-runs use the library's
default (their scripts load numpy before the setting). The step calls that library once per step, for a small 2 × 2
coupling product. All 18 starts agree to the last digit across the two settings over all 6,000 units, so the setting
had no effect here.

### Bitwise reproducibility is not numerical accuracy

V1-revised shows bitwise reproducibility on one machine: the same code gives the same numbers, and the fresh copies
and the stops leave it alone. It says nothing about numerical accuracy, meaning how closely a computed path follows the
exact solution of the model's equations. With a fixed step in a chaotic model, every computed path drifts away from
the exact solution over time. The saved paths that were paused and today's unbroken paths from the same seed are
equally valid computations that differ only by rounding amplified by the chaos. Accuracy was not tested here; that
would need, for example, runs with a smaller step. Both checks, like any later experiment, compare quantities within
each run and summarise over starts. So they rest on the model's statistics, not on any one path.

### What changes in the data

For old starts 1 to 12, from its first rounding difference on, each re-run is a new path of the same model from the
same seed, not its saved path. For old starts 13 to 18, the re-runs are the saved paths, to the last digit. The saved
runs are kept unchanged. Earlier analyses that read the saved runs used those paths as saved. They are equally valid
paths of the model, and nothing here changes them. These checks use old starts only. The fresh starts set aside for
any later experiment are not touched.

## Amendment 2: what a calibration FAIL leads to

The plan says: "FAIL (any of C1 to C4): it is not used as the primary outcome, and the gap design goes back to Colson.
The revised design will propose the carried gap as primary (the natural link's own measure, as in the cause test),
shown for review."

Colson's instruction replaces the second sentence: "If the fresh-gap method fails calibration, do not automatically
substitute the carried gap as the primary endpoint. Present alternatives, including their potential
measurement-history biases, for review before any experimental lock." A failed calibration leads to a revised design.
Any new outcome then gets its own check on old starts before any lock. A PASS keeps the meaning the plan gives it.

## Seen before the checks were complete (disclosed)

By design, both dry runs printed numbers of the kinds the checks measure. They ran on old start 1, at times outside
the checks' moments (start 1 is not a feasibility start). They count toward no reading. Both were seen before either
amendment was made. Their full output is kept in dryruns/.

- **Calibration dry run (08:25; fresh copies made at t = 200 and 250).** G = 0.0049, 0.0055 and 0.0071 against
  W = 0.0028 (t = 200 copy) and 0.0024 (t = 250 copy). The two t = 250 copies' overlap with the carried gap at 5, 25,
  100 and 300 units was 0.041, 0.055, 0.076, 0.098 and 0.009, 0.086, 0.318, 0.255. So the fresh gaps had not lined
  up with the carried gap after 300 units, and they grew faster than it. This is preliminary: one start, early in
  the run.
- **Feasibility dry run (08:27; one moment at T = 600).** The 500-unit hold reached fractions up 0.976, down 0.962,
  sham +0.218, with up's 25-unit blocks from 0.15 to 1.39 and down's from 0.72 to 1.16. The 100-unit hold reached
  0.927 and 0.907, sham +0.031, with blocks from 0.75 to 1.12 (up) and 0.75 to 1.02 (down). Every kick and the pulse
  were carried out.

## Other changes in how the checks run (no criterion changed)

- In the feasibility dry run, the check that all three hold rows start on the untouched run was set to "differ by at
  most 10⁻¹⁵" rather than exact equality. The hold sums its free oscillators in a plain memory layout, and the base
  run in an interleaved one; the largest difference found was 6.9 × 10⁻¹⁸. This was my decision, made in the dry run
  before the checks ran.
- Feasibility's base runs can stop and go on across pieces (at 190 s), like the calibration's. This changes how they
  run, not what they compute. (In the event, all six finished in their first piece.)
- Each piece now records the numerical environment, from calibration piece 5 and feasibility piece 1 on. Pieces 1
  to 4 did not record it. They ran in this container, which has had no restart since 07:40:54 Halifax; every later
  record shows whether that still holds.
- The reference script (prelock_machine_check.py) now also compares feasibility's base runs, and records the settings
  and the full environment.

## Corrections, October 13, 2026, about 12:05 Halifax

Found while running the pause test and checking this record afterwards. The text above is kept as it was written.

- **Starts 1 to 6 ran as six tasks, not one.** The first row of the table under "The cause" says "all six in one
  task" and "3,834 s for the six together", and the third "not settled" bullet says they "paused at the same
  moments". They ran as six tasks, one start each, like starts 7 to 12; their file records only the sum of their
  running times, 3,834 s (pause_test_followup.txt). So each start paused on its own schedule, and the puzzle in that
  bullet does not arise.
- **A pick-up changes the numbers at once.** The same bullet suggested that "a pick-up that changes a result only
  rarely" might explain the pattern. The pause test ran old start 7 with the nudge test's run code, unchanged, once in
  one go and once with one pause at t = 180 (pause_test_results.txt). The paused run parts from the unbroken one at
  the first put-back after the pick-up (first gap difference at t = 185; first coherence difference at the saved
  precision at t = 1,720), and the difference then grows. So the pauses are enough to explain the paused batches'
  departures. That their pauses caused them is still not shown: the pause points were not recorded, and the machine
  they ran on cannot be checked. The short test proposed above has now been run in this form.
- **A description in prelock_calibration.py.** The description of shaped() inside calibrate() says the pulled
  oscillators' array is laid out "in C order". Like the free oscillators' array, it is interleaved by group (strides
  (123904, 16, 32), pause_test_results.json). The code copies the base run's own layout, whatever it is, so nothing
  computed is affected. The script is left as it is.
