Field notes / Experiment guide
Is the ant unusual against the right comparison?
Ant boundary occupation sits below a simple uniform reference. Do rotor and random walkers also deviate when each gets its own growth-adjusted reference?
New to these ants? Start here.
An ant moves on a grid of coloured squares. Each colour tells it to turn left (L) or right (R). It advances that square to the next colour, then steps forward. A rule such as RLR is the list of turns, starting at colour zero. On a blank grid the same rule always makes the same trajectory.
Territory means every cell visited so far. Its boundary consists of visited cells with an unvisited neighbour. A highway is a repeating pattern of motion that carries the ant away. A tether means repeated returns to a small home region.
Growing like the cube root of time means taking eight times as many steps to double a linear size. The rotor walker is a comparison model that follows and rotates arrows stored on cells. A seed makes a random starting pattern reproducible; the ant itself can still move deterministically.
We distinguish c, the chance of discovering a cell per boundary step, from kappa (κ), time at the boundary normalized by perimeter divided by area. Explaining their values is harder than using them to account for growth.
A closer look

The idea under test
A fair comparison must use each walker’s measured growth and perimeter scaling rather than giving all walkers the ant’s baseline.
What we learned
Every tested comparison walker stays above its reference, while the typical census ant sits below. Small rotor noise produces an occupation overshoot; the predicted monotone response and small-noise growth robustness both fail.
What this leaves open
The reference assumes usable power-law windows. Escapes force shorter noisy-walker windows, several rows are nonstationary, and ant single-run variation differs from walker between-seed uncertainty.
Read the evidence and reproduction recipe ↗
Inside the experiment
Methods, registered predictions, exact commands and saved analysis. These are the working research documents; expand them for the detail behind the introduction.
Methods and original experiment record
kappa of the calibration walkers against their own uniform reference
Registered 2026-10-19 (+13), base commit 6de71f2 (dirty workspace from other experiments; this folder is untracked and self-contained), before any new run.
Question
experiments/2026-10-16-kappa-uniform-baseline showed the probe's kappa for a walker that is
uniform on its territory at every instant is kappa_unif(a, beta) = 2 * int_{1/2}^{1} x^(-a(1-beta)) dx
(1.110 at a = 2/3, beta = 1/2), and restated the ant (median census kappa/kappa_unif = 0.81,
5-95% 0.44-1.33, IQR 0.66-0.96, n = 2185 stationary blob rules, single run each). The rotor
(kappa ~2.0) and memoryless walkers (1.3-1.45) were only compared against 1.11, which is
justified only for a ~ 2/3, beta ~ 1/2 (deviation entry 2026-10-16 01:00). Recompute their
ratios with each walker's own measured (a, beta), then ask whether the ant location is ant-specific.
Prior numbers (from EXISTING data, computed before the new runs)
scripts/prior_ab.py on 2026-10-13-a1-kappa/results/a1d_probe.csv (pw, 6 seeds) and
2026-10-13-calib-instrument-calibration/results/calib_rotor.csv (rotor, 64 seeds), windows k = 12..22:
| walker | a | beta | kappa | kappa_unif(a,beta) | ratio |
|---|---|---|---|---|---|
| pw:0.25 | 0.891 | 0.882 | 1.254 | 1.033 | 1.21 |
| pw:0.5 | 0.913 | 0.907 | 1.358 | 1.027 | 1.32 |
| pw:0.75 | 0.930 | 0.931 | 1.287 | 1.020 | 1.26 |
| rotor | 0.724 | 0.564 | 1.856 | 1.104 | 1.68 |
Correction to the premise in the task/deviation entry. "Memoryless walkers have a ~ 0.95 and a reference near 1.16" used the beta = 1/2 column of the formula (a = 0.95 -> 1.159). Their territory is porous and the perimeter (visited cells with an unvisited 4-neighbour, holes included) scales as P ~ A^0.9, so a(1-beta) ~ 0.09 and the reference is ~1.02-1.03, below 1.11, not above. Recomputing therefore moves the memoryless ratios up (1.2-1.3 vs 1.2-1.3 against 1.11: unchanged to within 0.1), not down.
Predictions (registered)
New runs, fresh seeds, same probe: ratio_w = kappa_w / kappa_unif(a_w, beta_w), with a_w, beta_w
fitted per seed (log-log, windows k = 12..22) from the same run as kappa (kappa = mean over the
same windows of the per-window kappa, as in scripts/analyse.py of the baseline experiment).
- Memoryless walkers (
pw:0.25,pw:0.5,pw:0.75,rw): a in 0.88-0.96, beta in 0.86-0.95, kappa_unif in 1.01-1.05, ratio in 1.15-1.40 (rw has no prior run in this window; I expect it alongside pw:0.5). Between-seed sd of the ratio <= 0.1. - Rotor: a in 0.68-0.74, beta in 0.53-0.58, kappa_unif ~1.10, ratio 1.6-1.8 over k = 12..22. Over k = 20..26 (the window the census uses), kappa ~2.0 and ratio ~1.8; the difference between the two windows is reported as the window bias.
- Mean-field walker (anchor, already measured, not rerun): ratio 1.01 (1.118 / 1.107).
- Location test. Ant median 0.81, IQR 0.66-0.96, 5-95% 0.44-1.33.
- Ant-specific location is supported if every non-uniform walker's ratio is above the ant IQR (> 0.96) by more than 3 between-seed sd, and none lies in 0.66-0.96. I predict this: all walkers 1.2-1.8.
- Same ratio for all walkers would be shown by walker ratios inside 0.66-0.96 (the ant IQR). I predict this does not happen.
- Not discriminating if some walkers sit in 0.96-1.33. That is inside the ant 5-95% range (so "outside the ant range" is the wrong test for those), but above the median. I predict the memoryless walkers land here (1.2-1.3) and only the rotor is outside (> 1.33). If so: the verdict is that the ant is lower than every walker tested and that walkers do not discriminate within 0.96-1.33; it says nothing about mechanism.
- Rule-level caveat: the ant ratios are single run, rule-dependent, noise inflated (ln-kappa reliability ~0.6). Only a walker-ratio far from the ant IQR carries weight.
Design
rw,pw:0.25,pw:0.5,pw:0.75: seeds 2026101901..08,probe 23 16384(board 16384, 2^23 steps; windows k = 12..22). Escaped windows are dropped and counted; seeds with any escape are reported.rotor: seeds 2026101901..08,probe 27 8192; fit over k = 12..22 and k = 20..26 separately.- No estimator or fit change relative to the baseline experiment's
analyse.py(same formulas). A secondary, descriptive-only estimate with per-window local (a_k, beta_k) from adjacent window ends is also reported, because a single power law is a poor description of porous walkers.
Commands / results
Filled in as it runs (NOTEBOOK.md, results/).
Results (2026-10-19, run at commit 34fbf45; workspace dirty with unrelated changes, no Rust changed)
Commands (WSL, repo root): bash experiments/2026-10-19-kappa-walker-reference/scripts/run.sh (builds probe; specs in results/specs_mem.txt,
results/specs_rotor.txt; seeds 2026101901..08 for each of rw, pw:0.25, pw:0.5, pw:0.75, rotor; memoryless
probe 23 16384, rotor probe 27 8192; outputs results/probe_mem.csv, results/probe_rotor.csv),
then uv run experiments/2026-10-19-kappa-walker-reference/scripts/analyse.py -> results/analysis.txt. 8 seeds per walker, no escaped windows.
Mean (between-seed sd); a, beta fitted per seed, then averaged.
| walker (windows) | a | beta | kappa | kappa_unif(a,beta) | ratio | vs ant (med 0.81, IQR 0.66-0.96, 5-95 0.44-1.33) |
|---|---|---|---|---|---|---|
| rw (12..22) | 0.920 (.028) | 0.917 (.041) | 1.307 (.030) | 1.024 | 1.28 (.04) | above IQR, inside 5-95 |
| pw:0.25 | 0.903 (.017) | 0.898 (.021) | 1.271 (.027) | 1.029 | 1.24 (.03) | same |
| pw:0.5 | 0.920 (.030) | 0.909 (.037) | 1.350 (.036) | 1.026 | 1.32 (.04) | same, at the 95% edge |
| pw:0.75 | 0.911 (.026) | 0.907 (.027) | 1.287 (.051) | 1.026 | 1.26 (.06) | same |
| rotor (12..22) | 0.730 (.020) | 0.575 (.030) | 1.858 (.039) | 1.102 | 1.69 (.04) | above ant 95% |
| rotor (20..26) | 0.685 (.006) | 0.530 (.026) | 1.989 (.034) | 1.106 | 1.80 (.03) | above ant 95% |
| mean-field (baseline expt) | 0.677 | 0.520 | 1.118 (.009) | 1.107 | 1.01 (.01) | anchor |
The local-exponent reference (per-window a_k, beta_k from adjacent window ends) agrees with the
power-law reference to <= 0.006 in every row (columns in analysis.txt).
Against the registered predictions
- Memoryless: a 0.90-0.92 (pred 0.88-0.96), beta 0.90-0.92 (0.86-0.95), kappa_unif 1.02-1.03 (1.01-1.05), ratio 1.24-1.32 (1.15-1.40), sd <= 0.06 (<= 0.1): held.
- Rotor: a, beta, ratio inside the registered ranges in both windows (1.69 over 12..22, 1.80 over 20..26): held. Window bias of the ratio between the two windows is 0.11 (rotor a still relaxing towards 2/3).
- Location test: every non-uniform walker is above the ant IQR (by 5-25 between-seed sd of the walker); none is in 0.66-0.96. By the registered criterion the ant location is lower than every walker tested. As registered, the memoryless walkers fall in the not-discriminating band 0.96-1.33 (inside the ant 5-95% range; about 20% of ant rules are >= 1); only the rotor is outside the ant range.
What this does and does not show
- The recomputed ratios: memoryless ~1.25-1.3 (not changed by the correction: their reference is 1.03, not 1.16, because beta ~0.9), rotor 1.7-1.8. The ant median 0.81 is not the value a generic walker gets; uniform-on-territory (mean-field) is 1.0, and no non-uniform walker tested sits below 1.
- It does not say why. The walkers are above 1 and the typical ant is below 1; the ant range reaches 1.33 and the memoryless walkers sit at its top edge, so 'inside the ant range' is a weak statement. Accounting plus a control.
- Caveats: memoryless fits use k = 12..22 (board limits; a ~0.92 is not asymptotic, the census uses later windows); the rotor shows the window effect is ~0.1 in the ratio. Ant ratios are single-run and rule-dependent; walker ratios are 8-seed means, so the comparison is walker mean vs ant distribution, not like with like. Window labelling: probe CSV k is the window end (t = 2^k), the baseline mean-field CSV labels by window start, so 'k=12..22' there is ends 2^13..2^23; same estimator, different label (corrected in the notebook).
- Next: a walker with the ant's a ~ 2/3, beta ~ 1/2 but intermediate memory (noisy rotor
nrotor:EPS, Arm B of A1) to see where between 1.8 and 1.0 the ratio crosses the ant median; that discriminates 'memory in the environment lowers kappa' from 'it is ant-specific', which is not tested here.
Next steps (detailed; none of these has been run)
Status of the question: the ant median ratio (0.81) is below every non-uniform walker tested (memoryless 1.24-1.32, rotor 1.7-1.8) and the mean-field walker is 1.0. What is open is where and why the ratio moves between those walkers and the ants. Each step below is written so that its prediction can be registered in this README (dated, before the run) and can fail.
0. Housekeeping this experiment still owes (do first, small)
notebook.ipynbfromexperiments/_template/(required by AGENTS.md): question, thekappa_unifformula, provenance cell (commit, sha256 ofresults/probe_*.csvand the two scripts), the results table read fromresults/analysis.txt, caveats, next steps. It must run top to bottom from a clean kernel underuv, reading only committedresults/. Jupyter is not yet inpyproject.toml; seeexperiments/README.mdfor the setup, and mention anyuv addin the commit.- Add a short pointer in
notes/open-questions-hypotheses.mdwith the correction (memoryless reference is 1.03, not 1.16) and the ratio table. That file has uncommitted edits from other work: commit only the new hunk (git add -p) or ask first. - Append a new entry to the
2026-10-16-kappa-uniform-baselineNOTEBOOK saying its window labels are start-of-window while the probe CSV labels by window end. Its numbers are unaffected (own CSV, own estimator), but "k=12..22" differs by one octave between the two experiments. - Find which window the census kappa actually is (
probe_summary.csv,experiments/2026-10-13-probe-growth-census: last octave? which k?) and state it here. The rotor's ratio moves by 0.11 between k=12..22 and k=20..26, so the window match matters for every comparison.
1. Close the window confound for the memoryless walkers
- Question: is the memoryless ratio window-dependent like the rotor's? At the census window would it be nearer 1.1 or nearer 1.4?
- Obstacle: reaching 2^27 steps needs R ~ 35 000 cells, a ~1.2 GB board per walker, which is
borderline with 6 in parallel. Options, in order of preference: (a) a sparse/hash-set walker used only
for this control, with a property test against
probe_walkerstep-for-step on a small board (AGENTS.md rule 4); (b) 2^25 steps on a 16384 board, one walker at a time, windows k=20..24, with escapes reported (rms R ~ 5800, some will leave); (c) accept k <= 22 and say so. - Run:
rwandpw:0.5, 8 new seeds 2026102001..08. - Prediction to register: ratio at k=20..24 within +-0.1 of the k=12..22 value (1.24-1.32). Falsified if the shift exceeds 0.15; the memoryless walkers could then drift into or out of the ant IQR and the "above every ant-typical value" statement must be revised.
- Cost: (b) is minutes; (a) is about a day.
2. Noisy rotor: locate the ratio between 1.8 and the ant's 0.8 (main discriminating experiment)
- Rationale:
NoisyRotor(nrotor:EPS:SEED,src/walkers.rs) interpolates from the rotor (EPS=0, a ~ 2/3, beta ~ 1/2, ratio 1.8) to a memoryless walker that never turns back on its own board (EPS=1, a ~ 0.9, ratio ~ 1.25). It stays on the a ~ 2/3 branch for small EPS, sokappa_unifstays ~1.10 and the comparison with the ant is clean. Earlier Arm B work found kappa drifts with EPS (non-stationary; 3-4 runs per EPS;notes/frontier-2026-10-13-results.md), so report the driftg_kas well as the level, and apply the census stationarity cut (|g_k| < 0.05) before comparing with the ant distribution. - Design: EPS in {0, 0.01, 0.02, 0.05, 0.1, 0.2, 0.4, 0.7, 1.0}, 8 seeds each,
probe 27 8192, windows k=20..26 (census-matched) and k=12..22 (this experiment). 72 runs; rotor runs took about a minute each. - Predictions to register: (i) ratio(EPS) is monotone non-increasing from 1.8 towards ~1.25. (ii) The ant median 0.81 is not reached at any EPS: the noisy rotor stays >= 1.0. (iii) a(EPS) stays <= 0.75 up to EPS ~ 0.1, i.e. the cube-root-like regime survives weak noise (consistent with D3).
- Reading the outcomes: if no EPS gets below 1, neither "remove environment memory" nor "add noise" lowers the ratio below 1, and the ant deficit must come from something a rotor cannot do (a deterministic, rule-written colour field). If some EPS crosses 0.81 at a ~ 2/3, a stochastic walker reproduces the ant median and "ant-specific" is refuted for the location.
- Log, do not hide: runs leaving the board, runs where kappa drifts, and any disagreement between EPS=1
and
pw:0.5(their ratios should agree; a mismatch is a code bug, not a finding).
3. Calibrate what a ratio means (accounting for the formula itself)
- The
kappa_unifformula assumes uniform occupancy. Test it where the answer is known by construction: a mean-field variant that resamples only from the outer fraction f of the visited cells (by age or by distance from the centre), f = 1, 0.5, 0.25, 0.1. f = 1 reproduces the 1.01 already measured. - Prediction: ratio rises monotonically as f falls, and reaches ~1.8 at some f in 0.1-0.4. This maps a ratio r to an effective sampled layer, so the ant's 0.81 reads as "samples a layer wider than uniform occupancy implies". It does not assert that the rotor or the ant does this; many occupancy profiles give the same ratio.
- Needs a new example named with the slug prefix (
examples/kappa_walker_reference_*.rs) with the same brute-force recount assert askappa_uniform_baseline.rs.
4. Ant side: reduce the noise of the ant distribution before comparing
- The ant ratios are single run (ln-kappa reliability ~0.6), so the 0.44-1.33 range is inflated and the comparison is walker mean vs ant distribution, which is not like with like.
- Use the 8 patched seeds per rule already held in
2026-10-14-t1-excursion-tomographyand2026-10-13-patched-starts; compute per-rule mean ratios with each rule's own (a, beta) and report the distribution of rule means. Prediction: the spread narrows (sd of ln ratio from 0.35 towards 0.25) and the median stays 0.8 +- 0.05. If the median moves above 0.96, "ant below every walker" was a single-run artefact. - Split the ants by their own a. Rules with a near 0.9, if any, should be compared with the memoryless 1.25, not with the a ~ 2/3 rotor. Pre-register the sign of the within-ant slope of ln ratio on a; the walkers are not monotone in a (rotor a 0.69 -> 1.8, memoryless a 0.92 -> 1.25, mean-field a 0.68 -> 1.0), so a within-ant slope would not be explained by a alone.
5. Stopping rules and what I will not claim
- No mechanism is inferred from the ratio alone, consistent with the failed entries in the kappa-mechanisms ledger (P1, return gates, cutoff-ratio mixed model, square-loop test).
- Stop after steps 0-2 if step 2 shows no EPS below 1. The finding then stands as "walker ratios >= 1 at all memory levels tested; ants below 1", and the next experiment belongs on the rule-space side (what in a deterministic rule-written colour field lowers the ratio), not in more walkers.
- If step 1 or 4 overturns "ant below every walker", append a correcting NOTEBOOK entry and add a dated addendum below the results verdict; do not edit the original text.
Step 2 registration: noisy-rotor scan (2026-10-19 21:35 NZST, base commit f414649, before the run)
Executes "Next steps" item 2 above as written; nothing in the design changes. Specs results/specs_nrotor.txt:
nrotor:EPS:SEED, EPS in {0, 0.01, 0.02, 0.05, 0.1, 0.2, 0.4, 0.7, 1.0}, seeds 2026102001..08 (72 runs),
probe 27 8192, script scripts/run_nrotor.sh, analysis scripts/analyse_nrotor.py (same estimators as analyse.py;
windows k = 12..22 and k = 20..26; ratio per seed with the seed's own a, beta).
Drift definition (fixed now): g = least-squares slope of ln(kappa_k) on k over the window set (per octave);
stationary if |g| < 0.05 (the census cut). Runs with any escaped window are dropped from means and counted.
Predictions (i)-(iii) are those in item 2, plus numbers I commit to now:
- (i) mean ratio (k=20..26) is monotone non-increasing in EPS within 1 between-seed sd; EPS=0: 1.7-1.9; EPS=1.0: 1.15-1.40.
- (ii) no EPS has mean ratio < 1.0 at k=20..26. Falsified if any EPS mean is < 0.96 with a <= 0.75.
- (iii) a(EPS) <= 0.75 for EPS <= 0.1 (k=20..26).
- (iv) EPS=1.0 agrees with
pw:0.5(1.32 +- 0.1 at k=12..22). A mismatch > 0.15 is a code bug to be chased, not a finding. - (v) the EPS=0 run reproduces the plain rotor (ratio 1.69 +- 0.1 at k=12..22).
Step 2 results: noisy-rotor scan (2026-10-19, run at f414649; sha256 in results/probe_bin_nrotor.sha256)
Commands: bash $E/scripts/run_nrotor.sh then uv run $E/scripts/analyse_nrotor.py -> results/analysis_nrotor.txt.
72 runs, 8 seeds per EPS. Mean (between-seed sd), a/beta fitted per seed.
Deviation from the registration. The probe's escaped column is a run-level flag repeated on every row; an escaping run
simply ends, and its complete windows before that are valid. My first analysis dropped every run with the flag, which left
nothing for EPS >= 0.02. Fixed to keep complete windows (no data touched). Noisy walkers have a ~ 0.9 and leave the 8192 board
at 2^23-2^26 steps (EPS=0 never escapes), so the registered census-matched window k=20..26 exists only for EPS=0 (8/8) and
EPS=0.01 (6/8; 2/8 at 0.02). Windows k=12..22 (and k=12..20 for all runs) are the comparison. This is a window deviation
decided after seeing the escapes, not before.
| EPS | a (12..22) | beta | kappa | kunif | ratio (12..22) | drift g | stationary | ratio (12..20) |
|---|---|---|---|---|---|---|---|---|
| 0 | 0.734 (.016) | 0.588 | 1.852 | 1.099 | 1.69 (.02) | 0.018 | 8/8 | 1.66 |
| 0.01 | 0.871 (.022) | 0.708 | 2.800 | 1.082 | 2.59 (.09) | 0.099 | 0/8 | 2.29 |
| 0.02 | 0.905 (.033) | 0.782 | 2.557 | 1.063 | 2.41 (.17) | 0.079 | 0/8 | 2.21 |
| 0.05 | 0.935 (.015) | 0.859 | 2.196 | 1.042 | 2.11 (.15) | 0.053 | 4/8 | 2.00 |
| 0.10 | 0.931 (.024) | 0.878 | 1.900 | 1.035 | 1.84 (.09) | 0.038 | 7/8 | 1.77 |
| 0.20 | 0.936 (.011) | 0.902 | 1.663 | 1.029 | 1.62 (.05) | 0.024 | 7/7 usable | 1.56 |
| 0.40 | 0.922 (.016) | 0.898 | 1.465 | 1.029 | 1.42 (.05) | 0.018 | 7/7 usable | 1.41 |
| 0.70 | 0.933 (.017) | 0.920 | 1.339 | 1.023 | 1.31 (.03) | 0.008 | 8/8 | 1.30 |
| 1.00 | 0.931 (.028) | 0.924 | 1.290 | 1.022 | 1.26 (.03) | 0.010 | 8/8 | 1.26 |
(EPS=0.2, 0.4 have 7 of 8 runs complete through k=22.) At k=20..26: EPS=0 ratio 1.79 (.03); EPS=0.01 (n=6) 4.6 with g=0.095, a=0.92.
Against the registered predictions
- (i) monotone non-increasing from 1.8 to ~1.25: falsified. Ratio rises to 2.4-2.6 at EPS 0.01-0.02, then falls monotonically to 1.26 for EPS >= 0.02. Endpoints held (EPS=0 1.69, EPS=1 1.26).
- (ii) no EPS below 1.0: held (minimum 1.26). Neither removing environment memory nor adding noise gets near the ant median 0.81 or even 1.
- (iii) a <= 0.75 for EPS <= 0.1: falsified. a is already 0.87 at EPS=0.01 (0.92 at k=20..26). The cube-root-like regime does not survive even 1% noise over these windows; this is not consistent with D3 as I stated it.
- (iv) EPS=1 vs pw:0.5: 1.26 vs 1.32, difference 0.06, within the 0.15 bar: held (the walkers differ in turn rule, so exact equality was not expected).
- (v) EPS=0 reproduces the rotor: 1.69 vs 1.69: held.
What this does and does not show
- Observation: for 0.01 <= EPS <= 0.05 kappa/kappa_unif is above the rotor's, and these runs are not stationary (g 0.05-0.10 per octave, 0/8 at EPS 0.01-0.02), so the level there is a transient between two regimes, not a fixed point. The drift is positive: kappa is still rising, so the 12..22 value understates where it is heading (k=20..26 gives 4.6 at EPS=0.01).
- Interpretation (unverified): a small amount of noise breaks the rotor's compact-blob structure fast (a 0.69 -> 0.87) while the territory keeps a memory-like rough edge, which inflates boundary revisits; I have not tested this and do not claim a mechanism.
- The ratio never goes below 1 at any EPS. Per the pre-registered stopping rule (item 5) the finding stands as "walker ratios >= 1 at all memory levels tested; ants below 1", and the next experiment belongs on the rule-space side. Steps 1, 3, 4 (memoryless window control, f-layer calibration, ant-side noise reduction) are not run.
- Caveats: windows are k <= 22 for everything but EPS=0 (a ~ 0.9 is not asymptotic); 8 seeds; non-stationary rows (EPS <= 0.05) are not comparable with the stationary ant distribution; the board limit made the registered census window unavailable.
Close-out (2026-10-19, commit 8b290d8 + this one)
Housekeeping owed in "Next steps" item 0 is done; steps 1, 3 and 4 are not run (stopping rule, item 5).
notebook.ipynbwritten and executed top to bottom from a clean kernel underuv(uv run --with jupyter --with nbconvert --with ipykernel jupyter nbconvert --to notebook --execute --inplace notebook.ipynb; Jupyter is still not inpyproject.toml). It reads only committedresults/, recomputes both tables, and agrees with the tables above. While building it I caught that the spec strings arenrotor:1.0:(not1); fixed in the notebook, no data affected.- Census window (answers the open item):
experiments/_lib/analyse_probe.pyfits a, beta, g_c, g_k over k = 20..26 but takeskappafrom the last window only (k = 26, t in (2^25, 2^26]), not a window mean. The walker tables here use the mean over k = 12..22 (k = 20..26 for the rotor). For the rotor the last-window and 20..26-mean ratios agree (1.800 vs 1.799), but for the non-stationary noisy rotors the last window of the available range is far above the mean (EPS 0.01: 4.15 vs 2.59), and for the memoryless walkers (last available window k = 22, not 26) it is -0.01 to +0.08 from the mean (rw 1.355 vs 1.277, pw:0.5 1.304 vs 1.316). So the memoryless-walker comparison with the ants is not window-matched, as flagged above, and the rotor one is. - Tests:
cargo test --release(no Rust changed; result in NOTEBOOK). - Pointers added:
notes/open-questions-hypotheses.md(kappa reference bullet), a correction entry in2026-10-16-kappa-uniform-baseline/NOTEBOOK.md(window labelling), blog homepage card andblog/_posts/2026-10-19-the-ant-below-every-walker.md.
Verdict. Registered predictions 1-3 (memoryless, rotor, location) held. In the noisy-rotor scan (i) and (iii) failed; (ii), (iv), (v) held. Every walker tested, including every EPS of the noisy rotor, has ratio >= 1.26; the ant median is 0.81. The ant deficit is therefore not a generic property of walkers with matched exponents. Unexplained: why the ants are low; why a leaves the 2/3 branch at EPS = 0.01; why the ratio overshoots the rotor at small EPS (non-stationary).
Analysis notebook: notebook.ipynb
4 saved outputs · 4 code cells. Cells are not executed during publication. Download notebook
kappa of the calibration walkers against their own uniform reference
Question and why it matters
The probe's kappa for a walker that is uniform on its territory at every instant is kappa_unif(a, beta) = 2 * int_{1/2}^{1} x^(-a(1-beta)) dx (1.110 at a = 2/3, beta = 1/2). Census ants sit at a median of 0.81x that reference (single run per rule, 5-95% 0.44-1.33). Is that location ant-specific, i.e. do other walkers with a known growth law get a different ratio when each is compared with its own (a, beta)? Links: README.md, NOTEBOOK.md, experiments/2026-10-16-kappa-uniform-baseline.
Assumptions and definitions
- ratio = kappa / kappa_unif(a, beta), with a (area vs t) and beta (perimeter vs area) fitted per run by log-log regression over the window set, kappa = mean over those windows of (boundary_steps / (t/2)) / (perimeter / area).
tin the probe CSV is the window end (window (t/2, t]). - Walker territory is assumed to follow single power laws over the window; a local-exponent variant agrees to <= 0.006 (README).
- Ant reference numbers (median 0.814, IQR 0.664-0.957, 5-95% 0.443-1.332) are copied from the baseline experiment's
census_spread.txt; they are single-run and rule-dependent, the walkers are 8-seed means.
Provenance
Prints the git commit, date, seed and a SHA-256 for every input file.
Code
import hashlib, subprocess, datetime
from pathlib import Path
import numpy as np, polars as pl
SEED = 2026101901 # first walker seed; walkers use 2026101901..08, noisy rotors 2026102001..08. Nothing in this notebook is random.
np.random.seed(SEED)
ROOT = next(p for p in [Path.cwd(), *Path.cwd().parents] if (p / "AGENTS.md").exists())
EXP = next(p for p in [Path.cwd(), *Path.cwd().parents] if (p / "NOTEBOOK.md").exists())
R = EXP / "results"
INPUTS = [R / f for f in ["probe_mem.csv", "probe_rotor.csv", "probe_nrotor.csv", "specs_mem.txt", "specs_rotor.txt", "specs_nrotor.txt", "commit.txt", "commit_nrotor.txt", "probe_bin_nrotor.sha256"]]
missing = [p for p in INPUTS if not p.exists()]
assert not missing, f"missing inputs: {missing}"
sha = subprocess.run(["git", "rev-parse", "--short", "HEAD"], cwd=ROOT, capture_output=True, text=True).stdout.strip()
print("git", sha, "| run", datetime.datetime.now().isoformat(timespec="minutes"), "| seed", SEED)
print("producing commits:", (R / "commit.txt").read_text().strip(), "(walkers),", (R / "commit_nrotor.txt").read_text().strip(), "(noisy rotor)")
for p in INPUTS:
print(hashlib.sha256(p.read_bytes()).hexdigest()[:16], p.relative_to(ROOT))
Data and method
scripts/run.sh (rw, pw:0.25/0.5/0.75 at probe 23 16384; rotor at probe 27 8192; 8 seeds each) and scripts/run_nrotor.sh (nrotor:EPS:SEED, 9 EPS x 8 seeds, probe 27 8192), then scripts/analyse.py, scripts/analyse_nrotor.py. The cells below recompute the tables from the committed CSVs with the same estimators.
Code
def kunif(a, b):
p = a * (1 - b); return 2 * (1 - 0.5 ** (1 - p)) / (1 - p)
def run_stats(g, lo, hi):
w = g.filter((pl.col("k") >= lo) & (pl.col("k") <= hi))
if w.height < hi - lo + 1: return None # escaped runs end early; only complete windows count
t = w["t"].to_numpy().astype(float); A = w["area"].to_numpy().astype(float); P = w["perimeter"].to_numpy().astype(float)
kap = (w["boundary_steps"].to_numpy() / (t / 2)) / (P / A)
a = np.polyfit(np.log(t), np.log(A), 1)[0]; b = np.polyfit(np.log(A), np.log(P), 1)[0]
return dict(a=a, beta=b, kappa=kap.mean(), ku=kunif(a, b), ratio=kap.mean() / kunif(a, b),
g=np.polyfit(w["k"].to_numpy().astype(float), np.log(kap), 1)[0], ratio_last=kap[-1] / kunif(a, b))
def table(d, groups, lo, hi):
out = []
for name, pre in groups:
runs = sorted(r for r in d["rule"].unique() if r.startswith(pre))
rows = [s for r in runs if (s := run_stats(d.filter(pl.col("rule") == r).sort("k"), lo, hi))]
f = lambda k: np.array([x[k] for x in rows])
out.append(dict(walker=name, window=f"{lo}..{hi}", n=len(rows), a=f("a").mean(), beta=f("beta").mean(), kappa=f("kappa").mean(),
kunif=f("ku").mean(), ratio=f("ratio").mean(), ratio_sd=f("ratio").std(ddof=1), drift_g=f("g").mean(), ratio_lastwindow=f("ratio_last").mean()))
return pl.DataFrame(out)
mem = pl.read_csv(R / "probe_mem.csv"); rot = pl.read_csv(R / "probe_rotor.csv"); nrot = pl.read_csv(R / "probe_nrotor.csv")
pl.Config.set_tbl_rows(40); pl.Config.set_tbl_cols(12); pl.Config.set_float_precision(3)
table_walkers = pl.concat([table(mem, [("rw", "rw:"), ("pw:0.25", "pw:0.25:"), ("pw:0.5", "pw:0.5:"), ("pw:0.75", "pw:0.75:")], 12, 22),
table(rot, [("rotor", "rotor:")], 12, 22), table(rot, [("rotor", "rotor:")], 20, 26)])
table_walkers
Memoryless walkers have ratio 1.24-1.32 (their own reference is ~1.03 because beta ~ 0.9, not 1.16), the rotor 1.7-1.8, and the mean-field walker (baseline experiment, not rerun) 1.01. Shows: every non-uniform walker tested lies above the ant IQR (0.66-0.96), so the ant median 0.81 is not the value a generic walker gets. Does not show: why, or that the ant is below these walkers for a common reason; walker values are 8-seed means against a single-run ant distribution, and the memoryless walkers sit at the ant's 95th percentile.
Code
table_nrotor = table(nrot, [(f"eps={e}", f"nrotor:{e}:") for e in ["0", "0.01", "0.02", "0.05", "0.1", "0.2", "0.4", "0.7", "1.0"]], 12, 22) # eps strings must match specs_nrotor.txt ("1.0", not "1")
table_nrotor
Shows: the noisy-rotor ratio is non-monotone: 1.69 (EPS 0), a peak of ~2.6 at EPS 0.01, then monotone down to 1.26 at EPS 1; a is already 0.87 at EPS 0.01. Runs at EPS <= 0.05 drift (drift_g is the slope of ln kappa per octave; the census stationarity cut is 0.05). The minimum ratio is 1.26, so noise never brings it near 0.81. Does not show: a mechanism for the low-EPS peak (a non-stationary transient); and these are windows k = 12..22, not the census window (noisy walkers leave the 8192 board at 2^23-2^26 steps, so k = 20..26 exists only for EPS 0 and 0.01).
Code
# Census window: experiments/_lib/analyse_probe.py fits exponents over k = 20..26 but takes the kappa of the LAST
# window (k = 26), not the window mean used here. For the rotor, last-window ratio vs the 20..26 window mean:
table(rot, [("rotor", "rotor:")], 20, 26).select("walker", "window", "kappa", "ratio", "ratio_lastwindow")
Caveats and controls
- Registered predictions 1-3 (memoryless, rotor, location) held. For the noisy-rotor scan, (i) monotone decrease and (iii) a <= 0.75 up to EPS 0.1 failed; (ii) no ratio below 1, (iv) EPS 1 ~ pw:0.5 and (v) EPS 0 = rotor held.
- A first analysis took the window start for the window end and halved kappa (caught because it disagreed with an independent script, see
NOTEBOOK.md). A second dropped every run withescaped=true, a run-level flag (README, deviation). Neither touched data. - Window mismatch: memoryless and noisy walkers use k = 12..22 (a ~ 0.9 is not asymptotic); the rotor's ratio changes by 0.11 between k = 12..22 and 20..26. The census takes the last window k = 26, so walkers at k <= 22 are not window-matched to the ants.
- The mean-field anchor and the ant distribution come from
2026-10-16-kappa-uniform-baselineand were not rerun here.
What this changes, next steps, open questions
The ant deficit (median 0.81) is not reproduced by memoryless walkers, the rotor, or any rotor/noise mixture tested; it must come from something these stochastic walkers lack (a deterministic, rule-written colour field), or be an artefact of single-run noise. Not run: memoryless walkers at census-matched windows (README step 1), the occupancy-layer calibration (step 3), ant ratios averaged over seeds (step 4).
Read the dated lab log
Lab notebook: kappa of the calibration walkers against their own uniform reference
Append-only. Newest entry last. Corrections are new entries. Tags: intent, assumption, observation, interpretation, surprise, decision, dead-end, question, deviation.
2026-10-19 (+13) · 6de71f2 · intent
Recompute rotor and memoryless-walker kappa/kappa_unif with each walker's own measured (a, beta); decide whether the ant location (median 0.81) is ant-specific. README registered with predictions before any new run. Informs the open item in the 2026-10-16 01:00 deviation entry.
2026-10-19 (+13) · 6de71f2 · observation
scripts/prior_ab.py on existing CSVs (a1d_probe.csv, calib_rotor.csv; k=12..22): pw a 0.89-0.93, beta 0.88-0.93, kappa_unif 1.02-1.03, ratio 1.21-1.32; rotor a 0.724, beta 0.564, kappa_unif 1.104, ratio 1.68.
2026-10-19 (+13) · 6de71f2 · surprise
The task premise "memoryless reference near 1.16" does not hold: the 1.16 value assumes beta = 1/2, but porous memoryless territories have beta ~0.9, giving kappa_unif ~1.02-1.03. Registered in README before the run. The ratios therefore stay ~1.2-1.3 rather than dropping.
2026-10-19 (+13) · 34fbf45 · observation
Ran scripts/run.sh (8 seeds 2026101901..08 each of rw, pw:0.25/0.5/0.75 at probe 23 16384, rotor at probe 27 8192); no escaped windows. Raw output in results/probe_{mem,rotor}.csv.
2026-10-19 (+13) · 34fbf45 · deviation
First analyse.py gave kappa 0.64-0.93 (ratios 0.6-0.9), half the existing values (rotor 2.0, rw 1.37). Cause: I took t_end = 2^(k+1) (the baseline experiment's window-start labelling) but the probe CSV's t column is already the window end. Fixed to use the CSV t; no data touched, the buggy analysis.txt was overwritten (never committed). Caught only because the output disagreed with the prior_ab.py numbers; the rerun matches them.
2026-10-19 (+13) · 34fbf45 · observation
Corrected analysis: rw 1.28, pw 1.24/1.32/1.26, rotor 1.69 (k12-22) / 1.80 (k20-26), all with seed sd <= 0.06, vs kappa_unif 1.02-1.03 (memoryless) and 1.10 (rotor). See README table. All registered predictions 1-3 held; walker ratios all exceed the ant IQR.
2026-10-19 (+13) · 34fbf45 · interpretation
Ant median 0.81 is below every non-uniform walker tested, and the mean-field walker sits at 1.0, so the ratio is not walker-universal. Memoryless walkers (1.24-1.32) are inside the ant 5-95% range at its top, so the control is weak there; the rotor (1.7-1.8) is clearly outside. No mechanism claimed. Confounds not excluded: window (memoryless at k<=22, a not asymptotic), single-run ant noise, 8-seed walker means vs ant distribution.
2026-10-19 (+13) · 34fbf45 · decision
Stop at accounting. Next step proposed: noisy rotor (eps scan) to locate where ratio crosses 0.81. Release tests pass (cargo test --release, no Rust changed).
2026-10-19 (+13) · 0169fee · interpretation
Addendum to the interpretation above (second reader, no new data). (1) The "(mean - ant q75)/seed-sd" figures in analysis.txt (5-25) compare a walker's 8-seed mean to a quantile of single-run, rule-dependent ant ratios (ln-kappa reliability ~0.6); they measure walker seed noise only and are not significance tests. (2) The walker ratios are > 1 for every non-uniform walker and the ant median is < 1, but the ant 5-95% range spans 0.44-1.33, so rule-level structure, not the median, is the better lever. (3) An earlier version of this session's summary quoted the halved-kappa analysis.txt (ratios 0.62-0.90); the deviation entry above gives the cause (window-end t taken as window start) and those numbers must not be quoted. (4) Paper edit not made; proposal in notes/paper-and-supplementary-plan-2026-10-19.md.
2026-10-19 (+13) · f414649 · intent
Step 2 of the README next steps: noisy-rotor scan (9 EPS x 8 seeds, probe 27 8192). Predictions (i)-(v) registered in the README at 21:35 NZST before the run. Fresh target dir, binary sha256 logged.
2026-10-19 (+13) · f414649 · deviation
First analysis dropped every run with escaped=true, leaving no data for EPS >= 0.02. The flag is run-level and repeated on each row; complete windows before the escape are valid. Fixed analyse_nrotor.py to keep them. Also: noisy walkers (a ~ 0.9) leave the 8192 board at 2^23-2^26, so the registered window k=20..26 exists only for EPS 0 and 0.01; comparison moved to k=12..22 and k=12..20 after seeing this. No data touched.
2026-10-19 (+13) · f414649 · observation
Ratio (k=12..22): EPS 0 1.69, 0.01 2.59, 0.02 2.41, 0.05 2.11, 0.1 1.84, 0.2 1.62, 0.4 1.42, 0.7 1.31, 1.0 1.26. a is 0.87 at EPS=0.01. EPS=0.01-0.05 runs drift (g 0.05-0.10). Minimum ratio 1.26.
2026-10-19 (+13) · f414649 · surprise
(i) and (iii) falsified: the ratio is non-monotone (peak ~2.6 at EPS=0.01, above the rotor's 1.7) and a leaves the 2/3 branch at EPS=0.01. (ii), (iv), (v) held. I expected noise to interpolate smoothly between rotor and memoryless; it does not.
2026-10-19 (+13) · f414649 · interpretation
No EPS gets below 1, so the ant deficit (median 0.81) is not reproduced by any rotor/noise mixture tested. Stopping rule from README item 5 applies. The low-EPS peak is a non-stationary transient; mechanism untested.
2026-10-19 (+13) · 8b290d8 · observation
Census window: experiments/_lib/analyse_probe.py takes kappa from the last window (k=26) and fits exponents over k=20..26. This experiment's window mean differs from the last window by ~0 (rotor), -0.01 to +0.08 (memoryless, last window k=22 not 26) and up to 1.5 (non-stationary noisy rotor, EPS 0.01: 4.15 vs 2.59). Memoryless comparison with the ants is not window-matched (a at k<=22).
2026-10-19 (+13) · 8b290d8 · deviation
Executing notebook.ipynb the first time gave an empty eps=1 row: the notebook selected nrotor:1: but the specs say nrotor:1.0:. Notebook bug only (analyse_nrotor.py groups by parsed float and was right). Fixed; reran from a clean kernel.
2026-10-19 (+13) · 8b290d8 · decision
Closing out per AGENTS.md: notebook.ipynb, notes pointer, baseline-experiment window-label note, homepage card, blog post. Steps 1, 3, 4 of the README plan not run (stopping rule: no EPS below 1).