Field notes / Experiment guide
Does an ant’s opening instruction predict its boundary time?
A longer opening run of identical turns appears linked to boundary occupation after adjusting for area. Does it replicate on fresh rules, and does changing one letter reproduce it?
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.
The idea under test
Fresh-rule prediction and paired mutations can distinguish a repeatable association from an apparently causal effect of opening-run length.
What we learned
The conditional association replicates, but mutation effects are asymmetric. Later fresh-ground analysis shows why conditioning on area can create much of the apparent direct occupation effect.
What this leaves open
Letter direction, composition, regime selection and growth are intertwined. The replicated conditional association is not a causal estimate.
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 leading-run test on fresh, lead-stratified rules
Registered 2026-10-16 (+13:00), base commit d4fd349, before any probe run on these rules.
Follow-up to ../2026-10-16-kappa-prefix-decode (post hoc finding: on the 162 stationary
pool rules, the number of identical opening letters ("leading run" lead) gives +0.33
held-out R^2 over a ln A(22) spline, beating the prefix-8 lookup's +0.20; residual rises
~+0.1 per step, same slope in census and pool).
Question
Is the leading-run effect on ln kappa real and out-of-sample, at fixed area? The decode
result was found on data already scored, and lead correlates -0.56 with ln A(22).
Design
300 fresh rules, lengths 13/14, stratified on lead = 1..6 (25 rules per lead x length cell,
R^lead L + uniform random tail; scripts/sample.py, numpy default_rng(20261016 + 1)). Excludes every
W3b/W3c rule (census is length <= 12). Eight patched seeds 601..608 each = 2400 runs,
probe 27 8192, same instrument and per_rule pipeline as W3c (core band
alpha_w in [0.25, 0.42], kappa > 0, >= 6 core-band runs, stationary = |g_k| < 0.05).
Stratification de-correlates lead from rule composition; lead-1 would be 50% of an
unstratified sample, lead >= 4 about 12%.
Model (fixed now): trained only on the 2183 stationary census rules, as in decode:
ln kappa = spline(ln A(22)) [knots at 10/25/50/75/90% quantiles, as prefix_audit.basis]
- b * min(lead, 6). Evaluated on the fresh stationary rules. gain = held-out R^2 gain of the lead term over the spline-only model, strict against the census training mean, bootstrap over fresh rules (2000, seed 20261016).
Predictions (thresholds are my design choices)
- L1 gain >= 0.15 and 95% CI lower bound > 0 (decode's post hoc +0.33 is optimistic; expect shrinkage). ~65%.
- L2 the census-fitted slope b lies within 0.07-0.13 per step, and the slope refit on fresh rules (same spline + lead, OLS) has CI containing b_census and excluding 0. ~60%.
- L3 (confound check) split fresh rules into ln A(22) tertiles; the within-tertile slope of the residual on lead is > 0 in all three and the pooled within-tertile estimate has CI above 0. ~70%.
- L4 (control) lead shuffled across fresh rules (200 permutations): median gain within +-0.02, 95th pct <= +0.05.
- L5 stationarity and core-band retention do not depend on lead: share of rules kept differs by < 0.20 between lead 1 and lead 6 strata. If violated, selection biases L1-L3; report only.
- L6 (descriptive, no threshold) compare
leadagainst R-count-in-first-8 and letter-2 alone (decode's ablations) as fresh-sample gains.
A failure of L1 and L3 together means the decode finding was an area/composition proxy that did not survive fresh data.
Commands (WSL, repo root)
uv run experiments/2026-10-16-kappa-leadrun-fresh/scripts/sample.py # already run, results/rules.txt, specs.txt
bash experiments/2026-10-16-kappa-leadrun-fresh/run.sh # ~22 min, dumps/probe.csv (gitignored)
uv run experiments/2026-10-16-kappa-leadrun-fresh/scripts/analyse.py # results/analysis.txt, analysis.json
Results
Run 2026-10-16 ~20:15, commit 58b4f38 (dirty tree), seed 20261016, 2000 bootstraps; results/analysis.txt, analysis.json.
2400 runs, 365 runs escaped the board, 183/300 rules kept (>= 6 core-band runs), 176 stationary (test set). Per lead: 34/32/34/31/26/19.
| pred | outcome |
|---|---|
| L1 gain >= 0.15, CI > 0 | HELD: +0.651 [0.498, 0.828] (decode's post hoc +0.33 was not optimistic; here the spline-only baseline is weak, R^2 0.124, because stratifying on lead puts many large-lead rules in the test set; corr(lead, ln A22) = -0.72) |
| L2 census slope in 0.07-0.13 and fresh refit CI covers it | FAILED as registered: census slope is +0.195/step (my range was wrong: it came from the unconditioned residual table, not the spline-adjusted regression); fresh refit +0.152 [0.129, 0.174], excludes 0.195, so the slope shrinks ~22% out of sample but is clearly > 0 |
| L3 within-ln A tertile slopes > 0, pooled CI > 0 | HELD: tertile slopes 0.151 / 0.155 / 0.149, pooled +0.152 [0.128, 0.177] |
| L4 shuffled lead gain median within +-0.02, p95 <= 0.05 | FAILED as registered: median -1.43. The criterion was mis-specified: shuffling lead keeps the large census coefficient and injects variance, so gain is very negative, not ~0. It shows the lead term carries the signal; it is not the "pipeline gives ~0" control I intended. A proper null would refit the coefficient on shuffled lead (not done). |
| L5 retention differs < 0.20 (lead 1 vs 6) | FAILED: stationary share 0.68 (lead 1) falling monotonically to 0.38 (lead 6); kept share 0.74 -> 0.40. Selection depends on lead (mostly board escapes / too few core-band runs). L1-L3 hold on survivors only. |
| L6 alternatives | lead +0.651 [0.50, 0.83] vs letter-2-is-R +0.319 [0.25, 0.40] vs R-count-first-8 +0.290 [0.20, 0.39]: lead is better than both, CIs separate from the R-count at the top end only mildly |
Mean residual by lead (fresh, vs census spline-only): -0.148, +0.053, +0.127, +0.188, +0.377, +0.418 (census/pool table: -0.11 .. +0.45): same monotone shape.
Conclusion. The leading-run effect replicates on fresh, lead-stratified rules, and it holds at fixed area (L3), so it is not simply the ln A(22) proxy. Not established: causation (no mutation test), and survivor bias (L5 failed: high-lead rules escape or drop out more, so the 0.15-0.20 slope is for rules that stay on the board; selection could inflate or deflate it). Two registered predictions (L2 range, L4 control) were badly specified by me and are reported as failed, not repaired.
Follow-up registered 2026-10-16 (+13:00), base commit 78bcafd, before running either part
Part A is analysis-only on data already scored, so it is NOT a blind prediction: the design below is fixed before I read its output, but the probe data were seen. Part B needs new probe runs and is a genuine prediction.
Part A: nulls and selection bounds (scripts/followup.py, no new runs)
- A1 refit-on-shuffle null. Permute
leadamong census training rules AND among fresh test rules, refit the coefficient, score the gain (200 permutations). A second version permutes within quintiles of ln A(22) (pooled census+fresh quantiles) so corr(lead, ln A) is preserved. Expect both medians near 0 (|median| <= 0.02), with the observed +0.651 beyond the 99th percentile. Replaces the failed L4. - A2 selection bounds. Slope refit on the fresh stationary rules using (i) only leads 1-3 (retention highest), (ii) each rule length separately, (iii) inverse-retention weights by lead. Prediction: slope stays > 0 with CI excluding 0 in all, and the leads 1-3 slope is within 0.05 of the all-lead slope (0.152). Also tabulate who drops out (escaped runs per lead and length).
- Exports
results/fresh_rules.csv(one row per sampled rule) so the notebook reads committed data only.
Part B: mutation dose-response (scripts/mutants.py, run_mutants.sh)
For each fresh stationary rule with lead k = 1..5, single-letter mutants (same seeds 601..608, probe 27 8192, same pipeline):
- arm E (extend lead): flip index k (L->R), so the lead grows by >= 1;
- arm S (shorten lead): for k >= 2 flip index k-1 (R->L), lead becomes k-1;
- arm C (control): flip a random L->R at index >= 8, lead unchanged (same composition change as E, no lead change). Paired difference d = ln kappa(mutant) - ln kappa(base) over pairs where both are kept and stationary (>= 6 core runs, |g_k| < 0.05); dA = ln A22 difference. Bootstrap over pairs (2000, seed 20261016).
- M1 arm E: mean d > 0 with 95% CI lower bound > 0, and the pooled paired regression d ~ bdlead + cdA has b > 0 with CI excluding 0 (~55%).
- M2 E - C difference in mean d has CI lower bound > 0, i.e. the effect is the lead and not the L->R composition change (~60%).
- M3 arm S: mean d < 0 with CI upper bound < 0 (~50%; W6 found early-letter effects at fixed area go both ways).
- M4 (descriptive) pair retention (mutant kept and stationary, given base was) by arm. Failure of M1 and M2 together would mean the lead association is not causal at the single-flip scale, i.e. a correlate of something else in the opening.
Part A results (run 2026-10-16, commit 78bcafd + dirty tree, results/followup.txt, followup.json, fresh_rules.csv)
- A1 HELD. Refit-on-shuffle gain: unrestricted median -0.001 (p99 +0.038, max +0.049); within ln A quintile median +0.000 (p99 +0.051, max +0.058). Observed +0.651 is far outside both. The pipeline produces ~0 gain from a lead label with no information, including when the label keeps its correlation with area.
- A2 slope stays positive everywhere but the "within 0.05" part FAILED. Fresh refit slope: all leads +0.152 [0.130, 0.174]; leads 1-3 +0.235 [0.196, 0.271] (differs from all-lead by 0.083 > 0.05); leads 1-4 +0.197; length 13 +0.144, length 14 +0.157; inverse-retention weights +0.144 [0.123, 0.167]. So the effect is not carried by one length and is robust to reweighting by lead retention, but the slope depends on the lead range: it is steeper at small leads (consistent with saturation/cap at 6; mean residual by lead rises -0.15 -> +0.42, concave after lead 5). The single "slope per step" is therefore a coarse summary.
- Dropouts. Kept/stationary per 50 rules by lead 1..6: 37/34, 32/32, 35/34, 31/31, 28/26, 20/19. Rules with >= 1 escaped run: 3, 12, 7, 4, 9, 16. Escapes are not monotone in lead and explain only part of the drop for lead 1-5; most missing rules fail the >= 6 core-band runs condition (non-core alpha_w, e.g. highway/other-class runs), not stationarity (only 0-3 kept rules per stratum are non-stationary). Retention falls sharply only at lead 6 (0.40 kept). Lead >= 5 estimates carry the most selection risk; the lead 1-4 slope (+0.197) is the least selected.
- Not done: a bound on what dropped rules would have had; IPW only reweights by lead, not by unobserved reasons for dropout.
Part B results (rerun 2026-10-18, commit fa73570; files results/*.rerun-2026-10-18.*. The original 2026-10-16 run, results/mutants_probe.log, mutants_analysis.txt, mutant_pairs.csv, gives the same numbers up to float ULPs; see NOTEBOOK 2026-10-19 merge entry)
The first launch (2026-10-16) died at 600/3336 runs. It was rerun from scratch with the same command and specs (NOTEBOOK, 2026-10-18 deviation).
The probe made 3336 runs and finished at 19:51 (results/mutants_probe.log). Its git field is empty because WSL git cannot resolve this
worktree's gitdir; the actual commit is fa73570. Probe code is unchanged since the base run at d4fd349 (the only additions are gpu/ and onset.rs).
Instrument identity check (scripts/check_overlap.py, results/overlap_check.txt): the 30 mutants that are also base-sample rules
reproduce fresh_rules.csv exactly. n_runs, ln kappa, ln A22 and g_k all differ by 0.0, and 18/30 are kept in both runs. analyse_mutants.py was run once, unmodified, with no crash.
There are 417 mutants of 157 stationary fresh bases (lead 1-5): 269 kept, 257 kept and stationary pairs. CIs are from a bootstrap over pairs (2000 resamples, seed 20261016).
| arm | pairs | mean d = dln kappa | 95% CI | mean dln A22 | mean dlead |
|---|---|---|---|---|---|
| E (extend lead) | 81 | -0.062 | [-0.152, +0.025] | -0.292 [-0.380, -0.205] | +1.64 |
| S (shorten lead) | 73 | -0.198 | [-0.290, -0.110] | +0.013 [-0.068, +0.089] | -1.00 |
| C (late L->R) | 103 | -0.006 | [-0.049, +0.037] | -0.000 [-0.037, +0.036] | 0 |
| pred | outcome |
|---|---|
| M1 E mean d > 0 (CI > 0) AND pooled b > 0 | FAILED as registered. The first part fails: E mean d is -0.062 [-0.152, +0.025]. The second part holds: pooled d ~ c0 + b dlead + c dA gives b = +0.158 [+0.139, +0.179], c = +1.037 [+0.957, +1.119], c0 = -0.023 |
| M2 E - C mean d CI > 0 | FAILED. -0.056 [-0.159, +0.040] |
| M3 S mean d < 0 (CI upper < 0) | HELD. -0.198 [-0.290, -0.110], with area unchanged (dA +0.013) |
| M4 retention (kept+stationary / mutants) | E 81/152 (0.53), S 73/113 (0.65), C 103/152 (0.68). E loses the most, consistent with L5 (longer leads drop out) |
Descriptive output (registered as such) for area-matched pairs with |dA| < 0.15: E n=26, mean d +0.193; S n=37, mean d -0.237.
Post hoc (scripts/describe_mutants.py, results/mutants_describe.txt, written after seeing the above): the area-adjusted residual
d - 1.037 dA averages +0.241 (E), -0.211 (S) and -0.005 (C). E's total effect falls as dlead grows: dlead 1 (n=41) gives +0.04 with dA -0.13;
dlead 2 (n=31) gives -0.13 with dA -0.40; dlead 3-4 (n=9) have dA from -0.46 to -0.74.
Reading. Shortening the lead by one letter lowers ln kappa by about 0.2 while area stays the same. This matches the cross-sectional slope (0.15-0.24 per step) and is the cleanest causal evidence here. Extending the lead has no net effect on ln kappa: the same flip also shrinks the pattern (dln A22 about -0.29), and ln kappa moves with ln A at slope about 1. Net of area, extension raises ln kappa by about +0.24 per mutant (+0.16 per lead step), symmetric with S. So the lead effect is causal at fixed area, in both directions. Its total effect is sign-mixed: on the extension side it is cancelled by an area loss that has no counterpart on the shortening side. The E/S asymmetry in dA is unexplained.
Caveats.
- kappa = B·A/P at k=26 contains A, so the c ≈ 1 area slope is partly definitional, and dA shares run noise with d. "At fixed area" therefore means conditional on the measured ln A22, as in the registered census model, not a clean structural decomposition. dA is a mediator, not a confounder, so b is a direct effect and E's mean d is the total effect.
- The pooled-regression CI treats pairs as independent, but up to 3 pairs share a base (and its measurement error), so that CI is somewhat too narrow. The within-arm CIs have one pair per base.
- Selection: only 53% of E mutants survive the core-band and stationarity filter, against 65-68% for S and C. If the dropped E mutants differ in kappa, E's mean d is biased in an unknown direction.
- No arm isolates "early flip, lead unchanged". C flips late letters, so M2 controls composition, not position. W6 found early-letter effects of both signs at fixed area, so S could partly be an early-position effect rather than a lead effect.
- The area-adjusted E/S symmetry is post hoc.
Addendum 2026-10-18 20:15 (+13:00): correction to the Reading above
The registration says: "Failure of M1 and M2 together would mean the lead association is not causal at the single-flip scale." M1 and M2 both failed. By the criterion fixed before the run, single-flip causality of the lead association is not established. The Reading paragraph above ("causal at fixed area, in both directions") overrode that criterion post hoc and is withdrawn as a conclusion. Revision, labelled as such: the registered half of M1 (pooled area-adjusted b = +0.158 [+0.139, +0.179]) and M3 (S: -0.198 [-0.290, -0.110] at unchanged area) both point the same way as the cross-sectional effect. In hindsight, the total-effect criterion for E could not separate a lead effect from the area loss that comes with lengthening the lead. The +0.24 area-adjusted E residual is post hoc, and c ≈ 1 is partly definitional. So the evidence suggests a direct (area-conditional) effect, and the registered test should be replaced by one designed for it. This is not a result. Further caveat on M3: arm S has no composition control. C is a late L->R flip, while S is an early R->L flip, so S's -0.198 combines "one fewer R" with "early position", and no arm tests either factor alone. M3 alone therefore does not establish causality either.
Proposed next steps, written 2026-10-19 (PROPOSALS ONLY: not registered, nothing run)
Both follow from the Part B addendum. Neither fixes a threshold, a sample or a prediction; each must be registered here (with predictions, analysis script and base commit) before any run. Choices marked open are undecided.
Proposal 1: lead-preserving early flips (position and composition controls)
Gap it addresses. No arm flips an early letter without changing the lead (caveat 4), so M3's -0.198 mixes "one fewer
R", "early position" and "shorter lead". C controls composition only at late positions. W6 found early-letter effects of both
signs at fixed area, so an early-position effect could masquerade as a lead effect.
Design sketch. Mutate the base's letter at index k+1 or k+2, where k is the base's lead run length, so the flip lies just past the
run and the lead stays k. Flip both directions (L->R and R->L) so letter composition changes in both senses. Paired against the base,
same seeds, same probe 27 8192 settings and survivor filter as Parts A/B.
What it would discriminate. If the lead is the active factor, lead-preserving early flips should shift ln kappa by about zero at
fixed ln A, with no net sign. If early position or composition drives it, they should shift ln kappa by a size comparable to S
(about 0.2 in magnitude), with sign depending on flip direction (W6 mixed signs).
Statistic. Area-adjusted effect (regression of d ln kappa on dA), as in the Part B addendum, with bootstrap over bases (not pairs) to
handle shared bases.
Open. Which bases (all 157, or lead strata with room past the run); how many flips per base; index k+1 vs k+2 as separate arms;
whether to add a composition-matched lead-extending control (the missing S counterpart); required effect size and the sample size
that resolves it; whether "unchanged" needs an equivalence margin and what it is.
Proposal 2: bound the arm-E dropout bias
Gap it addresses. Only 53% of extend-lead mutants pass the core-band and stationarity filter (S 65%, C 68%); if the dropped ones
differ in kappa, E's mean d is biased in an unknown direction (caveat 3). L5 already showed lead-dependent attrition in Part A.
Design sketch (no new runs). From the existing mutants.csv / mutant_pairs.csv, classify why each E mutant dropped (escape,
alpha_w outside band, non-stationary, too few seeds), then report bounds on E's mean d instead of a point estimate: for example
inverse-probability weighting by base lead and rule length, and worst-case (Manski-style) bounds from imputing the dropped E mutants
at the observed extremes of the kept ones. Repeat for S and C so the comparison is like for like.
What it would discriminate. Whether the failed M1/M2 conclusion (E not distinguishable from 0 or from C) is robust to attrition, or
whether plausible dropout behaviour could flip it either way. A bound that includes both signs means M1/M2 are uninformative, not failed.
Open. Which imputation range is defensible; whether dropped-for-escape mutants are censored (they never reach the measurement
area) rather than missing; whether non-stationary mutants need a separate arm because the model for them differs. Any choice made
after looking at the dropped rules' kappa must be labelled post hoc.
Outcome (2026-10-20, post hoc, unregistered; scripts/dropout_bounds.py, results/dropout_bounds.txt). IPW by base lead x length
changes nothing (E -0.058 [-0.149, +0.024], S -0.198, C -0.002). Worst-case bounds (dropped imputed at kept 5th/95th pct): E
[-0.35, +0.19], S [-0.42, +0.02], C [-0.12, +0.08]. E's bound includes both signs, so M1/M2 are uninformative about E, not cleanly
failed; S's negative sign holds unless the dropped S mutants averaged about +0.36. Dropout reasons were not separable without the
gitignored dumps.
Ordering
Proposal 2 costs no compute and could be registered and run first; its outcome tells us whether the E arm is worth repeating inside Proposal 1. Proposal 1 needs a new probe run (about 30 min at Part B's scale) and a registered sample size.
Analysis notebook: notebook.ipynb
5 saved outputs · 5 code cells. Cells are not executed during publication. Download notebook
Leading-run effect on ln kappa: fresh-rule replication and single-flip mutants
Question and why it matters
Among chaotic generalised Langton's ants, the amplitude kappa in dA/dt = c·kappa·P/A varies from rule to rule. A post hoc decode (../2026-10-16-kappa-prefix-decode) found that the length of the opening run of identical letters (lead) predicts ln kappa beyond ln A(22). This experiment asks two things: (1) does that replicate on fresh rules stratified by lead, at fixed area? (2) is it causal, i.e. does flipping one letter so as to lengthen or shorten the lead move ln kappa by the predicted amount?
The formal record, with the predictions registered before each run, is in README.md; the thinking is in NOTEBOOK.md. Background: notes/open-questions-hypotheses.md.
Assumptions and definitions
lead= number of identical opening letters, capped at 6 in models. Rules areR^lead L+ a uniform random tail, lengths 13/14 (scripts/sample.py).- Rule-level ln kappa = mean of ln kappa over core-band runs (alpha_w in [0.25, 0.42], kappa > 0); a rule is kept with >= 6 such runs out of 8 seeds (601-608) and stationary if |g_k| < 0.05. kappa = B·A/P at k = 26 (
experiments/_lib/analyse_probe.py), so it contains A explicitly. - Assumption (NOTEBOOK 2026-10-16 21:16): most dropouts are rules without >= 6 core-band runs, not escapes. Checked only by counts. Untested at the level of individual rules.
- Assumption: the mutant rerun (commit fa73570) used the same instrument as the base run (d4fd349). Tested, held: 30 rules probed in both runs agree exactly (
results/overlap_check.txt).
Provenance
Prints the git commit, date, seed and a SHA-256 for every input file. Inputs are committed results/ only.
Code
import hashlib, subprocess, datetime, json
from pathlib import Path
import numpy as np, polars as pl
SEED = 20261016
rng = np.random.default_rng(SEED)
ROOT = next(p for p in [Path.cwd(), *Path.cwd().parents] if (p / "AGENTS.md").exists())
EXP = ROOT / "experiments/2026-10-16-kappa-leadrun-fresh"
R = EXP / "results"
INPUTS = [R / f for f in ["analysis.json", "followup.json", "fresh_rules.csv", "mutants.csv", "mutant_pairs.csv",
"mutants_analysis.json", "overlap_check.txt", "mutants_describe.txt"]]
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() or "unknown (WSL cannot resolve worktree gitdir)"
print("git", sha, "| run", datetime.date.today().isoformat(), "| seed", SEED)
for p in INPUTS:
print(hashlib.sha256(p.read_bytes()).hexdigest()[:16], p.relative_to(ROOT))
Data and method
- Part 1 (L1-L6): 300 fresh rules, 2400 runs,
bash run.sh(probe 27 8192),scripts/analyse.py. Model trained only on the 2183 stationary census rules: ln kappa = spline(ln A22) + b·min(lead, 6), scored on the 176 fresh stationary rules. - Part A (A1, A2):
scripts/followup.py, analysis only on Part 1 data (not blind). - Part B (M1-M4): 417 single-flip mutants of the 157 stationary fresh rules with lead 1-5 (
scripts/mutants.py). Arm E flips index k L->R (lead grows), arm S flips index k-1 R->L (lead shrinks by 1), arm C flips a random late L (index >= 8) to R. 3336 runs,bash run_mutants.sh,scripts/analyse_mutants.py. Paired d = ln kappa(mutant) - ln kappa(base), dA = the ln A22 difference. Bootstrap over pairs, 2000 resamples. Heavy computation is in the Rustprobebinary. This notebook only reads the committed outputs.
Results
Part 1: replication on fresh rules
Code
a, f = json.loads((R / "analysis.json").read_text()), json.loads((R / "followup.json").read_text())
rows = [("L1 held-out R^2 gain of lead term", a["L1"]["gain"], *a["L1"]["ci"]),
("L2/L3 fresh slope per lead step", a["L2"]["slope_fresh"], *a["L2"]["ci"]),
("L3 pooled within-ln A tertile slope", a["L3"]["pooled"], *a["L3"]["ci"]),
("A1 refit-on-shuffle null gain, median (p99 as upper)", f["A1 unrestricted"]["median"], float("nan"), f["A1 unrestricted"]["p99"]),
("A2 slope, leads 1-3", f["A2 leads 1-3"]["slope"], *f["A2 leads 1-3"]["ci"]),
("A2 slope, inverse-retention weights", f["A2 all, inverse-retention weights"]["slope"], *f["A2 all, inverse-retention weights"]["ci"])]
print(pl.DataFrame(rows, schema=["quantity", "est", "lo", "hi"], orient="row"))
fr = pl.read_csv(R / "fresh_rules.csv")
print(fr.group_by("lead").agg(rules=pl.len(), kept=pl.col("kept").sum(), stationary=pl.col("stationary").sum()).sort("lead"))
This shows that the lead term predicts ln kappa on unseen rules (gain +0.65, against a null of about 0) and that it does so inside ln A tertiles. It does not show causation. It also holds only for survivors: retention falls from 0.68 (lead 1) to 0.38 (lead 6) stationary, so L5 failed. L2 and L4 failed because I specified them badly (see README); A1 replaces L4.
Part B: single-flip mutants
Code
m = json.loads((R / "mutants_analysis.json").read_text())
print(pl.DataFrame([(k[-1], v["n"], v["mean_d"], *v["ci"], v["mean_dA"], v["mean_dlead"]) for k, v in m.items() if k.startswith("arm")],
schema=["arm", "pairs", "mean_d", "lo", "hi", "mean_dA", "mean_dlead"], orient="row"))
r = m["M1 regression"]
print(f"pooled d ~ c0 + b*dlead + c*dA: b={r['b_dlead']:+.3f} {np.round(r['ci_b'], 3)}, c={r['c_dA']:+.3f} {np.round(r['ci_c'], 3)}, c0={r['intercept']:+.3f}")
print(f"M2 E - C: {m['M2']['diff']:+.3f} {np.round(m['M2']['ci'], 3)}")
for arm in "ESC":
v = m["M4 " + arm]; print(f"M4 {arm}: {v['kept_stat']}/{v['n']} = {v['kept_stat']/v['n']:.2f} kept+stationary")
verdict = dict(M1=m["arm E"]["ci"][0] > 0 and r["ci_b"][0] > 0, M2=m["M2"]["ci"][0] > 0, M3=m["arm S"]["ci"][1] < 0)
print({k: "HELD" if v else "FAILED" for k, v in verdict.items()})
This shows that shortening the lead lowers ln kappa by about 0.2 with area unchanged (M3 held), although S has no composition control. Extending the lead has no net effect (M1 and M2 failed) because it also shrinks A22 by about 0.29. It does not separate a lead effect from an early-position effect, because no arm flips an early letter without changing the lead.
Code
import matplotlib
matplotlib.use("Agg")
import matplotlib.pyplot as plt
from IPython.display import Image, display
p = pl.read_csv(R / "mutant_pairs.csv")
fig, ax = plt.subplots(figsize=(5.5, 4))
for arm, col in zip("ESC", ["tab:red", "tab:blue", "0.6"]):
q = p.filter(pl.col("arm") == arm)
ax.scatter(q["dA"], q["d"], s=10, c=col, alpha=.7, label=f"{arm} (n={q.height})")
x = np.linspace(p["dA"].min(), p["dA"].max(), 2)
ax.plot(x, r["intercept"] + r["c_dA"] * x, "k--", lw=1, label=f"c0 + c dA (dlead=0), c={r['c_dA']:.2f}")
ax.axhline(0, c="k", lw=.5); ax.axvline(0, c="k", lw=.5)
ax.set_xlabel("dln A22 (mutant - base)"); ax.set_ylabel("d = dln kappa"); ax.legend(fontsize=8)
fig.tight_layout(); out = EXP / "scratch" / "pairs.png"; out.parent.mkdir(exist_ok=True); fig.savefig(out, dpi=70); plt.close(fig)
display(Image(filename=str(out)))
This shows that E pairs (red) sit left of zero in dA (extension shrinks the pattern) and above the area line, while S pairs (blue) sit at dA ≈ 0 and below zero. It does not show that the slope-1 area line is structural: ln kappa contains ln A by construction and shares measurement noise with dA.
Code
print((R / "mutants_describe.txt").read_text())
This post hoc table (written after seeing M1-M4) shows the area-adjusted residual: E +0.24, S -0.21, C -0.01, which is roughly symmetric and close to the cross-sectional 0.15-0.2 per step. It does not count as a registered test.
Caveats and controls
- Positive/negative controls. Arm C (late flip, lead unchanged) gives d ≈ 0 and dA ≈ 0. The refit-on-shuffle null (A1) gives gain ≈ 0. The instrument identity check is exact on 30 duplicated rules. The kappa estimator itself is calibrated elsewhere (
notes/calibration.md); nothing new was calibrated here. - Failed predictions: L2, L4, L5, the A2 "within 0.05" part, M1 and M2. M3 held.
- Selection: only 53% of E mutants are kept and stationary, against 65-68% for S and C. Survivors only.
- Definitional coupling: kappa = B·A/P, so the c ≈ 1 area adjustment is partly mechanical. dA is a mediator, so b is a direct effect and E's mean d is the total effect.
- Clustered pairs: the pooled-regression CI ignores that up to 3 pairs share a base, so it is somewhat too narrow.
- Rerun: the first mutant launch died at 600/3336 runs and was rerun unchanged (NOTEBOOK 2026-10-18).
What this changes, next steps, open questions
M1 and M2 both failed, and the README registered that this joint failure means the lead association is not established as causal at the single-flip scale. What remains is suggestive only: M3 and the area-adjusted b point the same way as the cross-sectional effect, but the E decomposition is post hoc and S lacks a composition control (README addendum 2026-10-18 20:15). The total effect is sign-mixed, because lengthening the lead also shrinks the pattern. Next steps: a registered arm that flips an early letter with the lead unchanged (index k+1 or k+2), to separate lead from position; censoring or a larger board to bound E dropouts. Open question: why does growing the leading R-run shrink A22 while shrinking it does not?
Read the dated lab log
Lab notebook: kappa leading-run test on fresh, lead-stratified rules
Append-only. Newest entry last. Never edit an old entry; correct it with a new one
(corrects: <timestamp>). Tags: intent, assumption, observation, interpretation,
surprise, decision, dead-end, question, deviation. See experiments/README.md.
2026-10-16 19:40 (+13:00) · d4fd349 · intent
Previous loop iteration left the tail-budget experiment finished and committed (its HANDOVER "still undone" list was stale: decomposition and onset sensitivities are in baac481). Picked up the open item from 2026-10-16-kappa-prefix-decode: the leading-run finding is post hoc on scored data and confounded with ln A(22) (corr -0.56). Test it on fresh rules stratified by lead run. Predictions L1-L6 registered in README before any probe run.
2026-10-16 19:46 (+13:00) · d4fd349 · observation
scripts/sample.py wrote 300 rules (6 lead strata x 2 lengths x 25) and 2400 specs (seeds 601-608); no overlap with W3b/W3c by construction. run.sh launched in WSL (same probe 27 8192 as W3c, ~22 min expected). scripts/analyse.py written while it runs, before any output exists; it implements L1-L6 as registered, with one choice the README left open: L2's fresh refit uses a spline basis with knots from the fresh sample's own ln A quantiles.
2026-10-16 20:10 (+13:00) · d4fd349 · observation
Probe run finished 20:07 (exit 0, 2400 runs, results/probe.log; binary sha256 recorded there). A user "pause" request arrived after completion; nothing was running. analyse.py NOT yet run, so no outcome has been read. Work-in-progress commit requested by the user.
2026-10-16 20:15 (+13:00) · 58b4f38 · observation (analyse.py, results/analysis.txt)
L1 HELD (gain +0.651 [0.498, 0.828]); L3 HELD (tertile slopes 0.151/0.155/0.149); L2 FAILED (census slope 0.195 outside my 0.07-0.13; fresh refit 0.152 [0.129, 0.174]); L4 FAILED (shuffled median -1.43); L5 FAILED (stationary share 0.68 at lead 1 vs 0.38 at lead 6). Per-lead residual means -0.148..+0.418, monotone. Single run of the script, no variants.
2026-10-16 20:16 (+13:00) · 58b4f38 · interpretation, surprise
Leading run is real out of sample and at fixed area, not a ln A proxy. Surprise: L5, high-lead rules drop out of the stationary core band, which can bias the slope. L2 and L4 failures are my specification errors (L2 range taken from the unadjusted table; L4 shuffle without refitting the coefficient can never be near 0). Not repaired after seeing data. Next: mutation dose-response at fixed area (causal), and a refit-on-shuffle null; handle escapes by a larger board / longer-run censoring analysis.
2026-10-16 21:05 (+13:00) · 78bcafd · intent
Resumed from HANDOVER.md (read AGENTS.md and experiments/README.md first). Plan: (A) refit-on-shuffle null and selection bounds from existing data, scripts/followup.py; (B) single-flip mutation dose-response, new runs, registered in README before running; then notebook.ipynb and notes. Predictions A1/A2/M1-M4 written to the README before any of this ran (A is on already-seen data, flagged as such).
2026-10-16 21:15 (+13:00) · 78bcafd · observation (scripts/followup.py, results/followup.txt)
A1: shuffled-label gain median -0.001 (unrestricted), +0.000 (within ln A quintile); max +0.049/+0.058; observed +0.651. A2: slope all leads +0.152, leads 1-3 +0.235, leads 1-4 +0.197, len13 +0.144, len14 +0.157, IPW-by-lead +0.144. Dropouts: escaped-run counts not monotone in lead (3,12,7,4,9,16 of 50); kept share falls mainly at lead 6.
2026-10-16 21:16 (+13:00) · 78bcafd · interpretation, deviation
A1 replaces the failed L4: the instrument gives ~0 on a noninformative label, so the +0.65 is not an artefact of spline-plus-label fitting. A2 sub-prediction (leads 1-3 slope within 0.05 of all-lead) FAILED: the dependence on lead is concave, so "slope per step" depends on the range. Also corrects my 20:16 interpretation: escapes are not the main dropout route; most dropouts are rules without >= 6 core-band runs (checked only via counts, not by inspecting the individual dropped rules' alpha_w; could be checked). Confidence moderate.
2026-10-16 21:25 (+13:00) · 78bcafd · observation
scripts/mutants.py produced 417 mutants (E 152, S 113, C 152; 30 coincide with rules in the base sample), 3336 runs, results/mutants.csv, mutant_specs.txt. Launched run_mutants.sh (same probe 27 8192 as the base run) in WSL, nothing else using the machine at launch. Mutant analysis script to be written while it runs and run only after it finishes.
2026-10-16 20:45 (+13:00) · 78bcafd · question, intent (hypothesis registered BEFORE any measurement; mutant run still in progress, not read)
User's framing: kappa has gone from "something about the prefix" to "the opening same-letter run is real and area-independent", but no mechanism. Candidate mechanism, derived by hand, not yet tested: Square-loop trap. A rule starting R^k (all-R lead) on fresh (colour 0) cells traces a closed 2x2 loop: O -> E -> SE -> S -> O, 4 steps/lap, each lap raises all four cells by one colour. The loop persists while all four cells have colour < k, so a fully fresh 2x2 block holds the ant for k laps = 4k steps, then the first L-turn breaks it. (Same for L^k by mirror symmetry.) Longer lead = longer dwell in a loop on young cells, and young cells are exactly the frontier. Hypothesis H-loop: lead raises kappa because the ant spends more of its frontier steps in short-period (4-cycle) loops on young cells, which keeps it at the boundary instead of in the interior. Predictions (thresholds are my design choices):
- P1 (rule-only, no probe data): on a blank board, the first k-lap loop occurs for every lead-k rule: the opening 4k steps repeat period 4 exactly. Trivial check, should be 100% (sanity of the derivation).
- P2: in the probe window, fraction of steps spent inside period-4 R-loop segments (position repeats with lag 4) rises monotonically with lead (leads 1..6) on the 176 fresh stationary rules, at fixed ln A22 tertile; Spearman(frac, lead) > 0.5 within each tertile.
- P3 (mechanism vs correlate): across rules, the per-rule loop fraction predicts the lead-term residual: adding loop fraction to spline+lead removes >= half of the lead coefficient (mediation). If the lead coefficient is unchanged, loops are not the carrier.
- P4: loops are concentrated on boundary cells: P(loop step | cell on boundary) > P(loop step | interior) by a factor >= 2. Failure of P2 kills H-loop outright; P2 holding with P3 failing means loops are a symptom of lead, not the carrier. Needs a small Rust/example instrument (loop-segment counter in the step loop, tested against reference) — not written yet.
2026-10-16 20:46 (+13:00) · 9dce376 · deviation (corrects: 20:45)
The 20:45 entry was tagged 78bcafd; HEAD is actually 9dce376. Content unaffected.
2026-10-16 21:10 (+13:00) · 5b97961 · observation (scripts/analyse_mutants.py, results/mutants_analysis.txt)
Mutant probe finished 20:53:47, exit 0. 257 kept+stationary pairs. Arm E mean d -0.062 [-0.152,+0.025] (mean dA -0.292); arm S -0.198 [-0.290,-0.110] (dA +0.013); arm C -0.006 [-0.049,+0.037] (dA 0). Pooled regression b(dlead) = +0.158 [0.139,0.179], c(dA) = +1.037. Area-matched (|dA|<0.15): E +0.193 (n=26), S -0.237 (n=37). Registered: M1 FAILED (conjunction, raw E part), M2 FAILED, M3 HELD. Single run of the script.
2026-10-16 21:12 (+13:00) · 5b97961 · interpretation, surprise
Surprise: extending the lead lowers early area, and ln kappa tracks ln A22 with coefficient ~1, cancelling the lead effect in the raw E difference. Interpretation (moderate confidence): at fixed area the lead effect is real and causal-looking (S arm, matched E, regression b equal to the cross-sectional 0.152) and control flips are null; but ln A22 is a post-treatment mediator, so these adjusted estimates are not clean causal effects. Alternatives: letter at index k matters rather than run length; kappa and A22 are nearly redundant measurements. My M1 conjunction mixed an unadjusted and an adjusted claim, which I should have separated at registration. Not repaired.
2026-10-16 21:14 (+13:00) · 5b97961 · question
Is ln kappa mostly determined by ln A22 (c ~ 1)? If so "kappa at fixed area" is a thin residual. A cleaner test: use a mediator-free outcome, e.g. kappa defined from windows k >= 24 only, or flip letters that leave early area unchanged. Also a GPU port of the probe statistics was handed to a subagent (separate worktree) to make such bigger mutant sweeps cheap.
2026-10-18 19:36 (+13:00) · fa73570 · deviation
The 2026-10-16 mutant run (launched 21:25 entry above) was interrupted: results/mutants_probe.log stops at the "600" progress line of 3336 runs, has no finished line, dumps/mutants.done is absent and no probe process is running. Cause not established (the session ended; the partial dumps/mutants.csv is not in this worktree, dumps/ being gitignored). Decision (instructed by the user via the launching agent): rerun from scratch with the same command, bash experiments/2026-10-16-kappa-leadrun-fresh/run_mutants.sh, in worktree paper-figure-proposals at fa73570, no change to mutant_specs.txt, mutants.py, analyse_mutants.py or predictions M1-M4. The partial output was never analysed. dumps/ created empty first because the script redirects into it. The binary is rebuilt from the current tree (fa73570, not 78bcafd); intervening commits are paper/notes work, and the probe sha256 is logged at the end for comparison with results/probe.log. mutants_probe.log will be overwritten by the rerun (the old content is in git history).
2026-10-18 19:52 (+13:00) · fa73570 · observation
Rerun finished (19:34 -> 19:51, exit 0, 3336 runs, dumps/mutants.done written, results/mutants_probe.log). The log's git field is empty because WSL git cannot resolve this worktree's gitdir (fatal: not a git repository); the actual commit is fa73570. The binary sha256 is 6234da50..., which differs from the base run's 38f8bba8... . A different build path changes the hash, so this alone is not evidence of a code change. git diff d4fd349 HEAD -- src Cargo.* shows only additive gpu/ and onset.rs changes. Empirical check (scripts/check_overlap.py, written before the analysis was run, results/overlap_check.txt): the 30 mutants that are also base-sample rules reproduce fresh_rules.csv exactly (all diffs 0.0, 18/30 kept in both). The paired d therefore compares like with like.
2026-10-18 19:53 (+13:00) · fa73570 · observation (scripts/analyse_mutants.py, results/mutants_analysis.txt)
Single run, unmodified, no crash. 257 kept+stationary pairs. E n=81: d -0.062 [-0.152, +0.025], dA -0.292. S n=73: d -0.198 [-0.290, -0.110], dA +0.013. C n=103: d -0.006 [-0.049, +0.037], dA 0.000. Pooled regression: b +0.158 [+0.139, +0.179], c (dA) +1.037 [+0.957, +1.119]. M1 FAILED (E mean d CI includes 0; the b part holds). M2 FAILED (E - C -0.056 [-0.159, +0.040]). M3 HELD. M4 retention E 0.53, S 0.65, C 0.68. Area-matched (|dA|<0.15): E +0.193 (n=26), S -0.237 (n=37).
2026-10-18 19:55 (+13:00) · fa73570 · observation, deviation (post hoc)
After reading the above I wrote scripts/describe_mutants.py (results/mutants_describe.txt), which is post hoc and descriptive only. The area-adjusted residual d - 1.037 dA is E +0.241, S -0.211, C -0.005. E's dA grows more negative with dlead (dlead 1: -0.13, n=41; dlead 2: -0.40, n=31; dlead 3-4: n=9). Checked experiments/_lib/analyse_probe.py: kappa = B·A/P at k=26, so ln kappa contains ln A explicitly.
2026-10-18 19:56 (+13:00) · fa73570 · interpretation, surprise
The lead effect looks causal at matched area in both directions: S lowers ln kappa by about 0.2 with area unchanged, and E net of area raises it by about 0.24. Both are close to the cross-sectional 0.15-0.2 per step. Its total effect is sign-mixed. Surprise: extending the lead also cuts A22 by about 0.3 (more for bigger jumps), but shortening it does not raise A22. This asymmetry cancels E's total effect and is why M1/M2 failed. The asymmetry is unexplained. Confidence: moderate for S (registered, clean, area unchanged), lower for the E decomposition (post hoc; c ≈ 1 is partly definitional and shares noise with d; E has the worst retention at 0.53, so selection could move it). Alternative for S: an early-position effect rather than a lead effect, since no arm flips an early letter without changing the lead (W6 found early-letter effects of both signs).
2026-10-18 19:57 (+13:00) · fa73570 · question
Why does growing the leading R-run shrink the pattern (A22) while shrinking the run leaves it unchanged? A registered test would add an arm that flips index k+1 or k+2 with the lead unchanged (early position, no lead change), plus a larger board or censoring analysis for E dropouts.
2026-10-18 20:05 (+13:00) · fa73570 · decision, observation
notebook.ipynb has been filled and executed (uv run --with nbconvert --with ipykernel --with nbformat jupyter nbconvert --to notebook --execute --inplace ..., no errors). Its verdicts and numbers match the README. It reads only committed results/. It was generated by a builder in the gitignored scratch/, so the committed notebook is the artefact. Its provenance cell prints "git unknown" when executed from WSL in this worktree (same gitdir issue as above). Closing status: Part B is complete. notes/open-questions-hypotheses.md has not been updated (left for a later session; see HANDOVER).
2026-10-18 20:15 (+13:00) · f5033ab · interpretation, deviation
corrects: 2026-10-18 19:56. The README registered that a joint failure of M1 and M2 means the lead association is not causal at the single-flip scale. Both failed, so by the registered criterion causality is not established. My 19:56 entry ("causal at matched area in both directions") overrode that post hoc. It is withdrawn as a conclusion and kept only as a hypothesis: the registered half of M1 (b = +0.158) and M3 point the same way, and the E total-effect criterion was in hindsight unable to separate lead from the accompanying area loss. Additional caveat: S has no composition control (early R->L flip against late L->R for C), so M3 does not isolate the lead either. The README has a dated addendum. HANDOVER and the notebook's final cell are reworded to match, and the notebook is re-executed.
2026-10-18 20:18 (+13:00) · f5033ab · decision
corrects: 2026-10-18 20:05 (builder location). The notebook builder moved from scratch/ to scripts/build_notebook.py and is committed, so the notebook source is reproducible: uv run --with nbformat python .../scripts/build_notebook.py, then the nbconvert execute command. Re-executed with no errors.
2026-10-19 (+13:00) · 95bd647 · decision
Documented two follow-up proposals in README ("Proposed next steps"): P1 lead-preserving early flips at index k+1/k+2 in both directions; P2 attrition bounds for arm E from existing data. Both are unregistered: no thresholds, samples or predictions fixed, nothing run. Separately, corrected a date in notes/open-questions-hypotheses.md: the mutant rerun was 2026-10-18, not 2026-10-19 as that note first said (note not yet pushed anywhere).
2026-10-19 (+13:00) · 0c3e4a7 · decision
Closing the experiment. All registered parts (L1-L6, A1-A2, M1-M4) are run, analysed and reported in README, NOTEBOOK, notebook.ipynb and notes/open-questions-hypotheses.md. HANDOVER item (4) was stale (the note was already updated) and is corrected. Follow-up proposals P1/P2 stay unregistered and are not part of this experiment.
2026-10-19 (+13:00) · merge · deviation (merge of worktree-paper-figure-proposals into main)
Two lines of work on this folder met in the merge. Main-line: the mutant probe finished 2026-10-16 20:53 (log results/mutants_probe.log, binary sha 38f8bba8...), analysed in the 21:10 entry. The branch's launch of the same run died at 600/3336, was rerun 2026-10-18 19:34-19:51 (binary sha 6234da50..., different build path) and analysed in the 19:53 entry. Both results exist and agree: arm means and CIs are identical, mutant_pairs.csv rows differ only in the last float digit (19 rows, ULP-level, numpy/formatting). Kept: main-line files under their original names (the square-loop experiment's C3 pairs were built from them); the branch's rerun is saved as results/*.rerun-2026-10-18.*. The rerun is an independent replication of the mutant run with a different binary. README Part B results, HANDOVER, notebook.ipynb and scripts/build_notebook.py are the branch's later versions, which carry the withdrawal of the 19:56 'causal at matched area' reading (20:15 entry). The main-line 21:10-21:14 entries above predate that correction and should be read with it.
2026-10-19 (+13:00) · 8f4b978 · interpretation
Co-reading note appended at the 2026-10-19 merge (the entry corrects nothing above). ../2026-10-19-c-law-mechanism/ Stage 4 (registered before computation; Q1-Q4, P-1 held; re-run of 4a/4b at merge reproduced the numbers) shows the fixed-area lead-on-ln-kappa slope (+0.15) is reproduced by conditioning on the collider ln A22 = f(ln c, ln kappa) with no direct effect (null +0.145 [0.137, 0.153]; adding ln c gives +0.014 [-0.045, +0.062]). Read the interpretation and any 'causal at fixed area' wording above as superseded in part: association conditional on area, not evidence of a direct lead effect. Unexplained and still open: the S-arm drop (-0.198) and its confounding with flip direction. See notes/open-questions-hypotheses.md, Reconciliation 2026-10-19.
2026-10-20 00:10 (+13:00) · 0de4d38 · observation (Proposal 2, post hoc, unregistered)
Ran scripts/dropout_bounds.py (no new runs; results/dropout_bounds.txt). Denominator: 417 mutants with a fresh base; observed pairs 257. IPW by base lead x rule length leaves every arm's mean essentially unchanged: E -0.058 [-0.149, +0.024] (naive -0.062), S -0.198 [-0.300, -0.104], C -0.002 [-0.045, +0.038]. Manski bounds (dropped mutants imputed at the kept 5th/95th percentile of d in the same arm): E [-0.346, +0.193], S [-0.420, +0.015], C [-0.123, +0.079]. Break-even: E's dropped mutants (47% of the arm) would need mean d = +0.071 to bring E's full-population mean to 0, only +0.13 above the kept mean; S would need +0.36.
2026-10-20 00:10 (+13:00) · 0de4d38 · interpretation
E's null (M1/M2 failed) is not robust to attrition in either direction: the bounds include both signs, so M1/M2 are uninformative about E rather than cleanly failed. S's M3 (negative) is sturdier but not sign-identified under the worst-case 5th/95th bound (upper end +0.015). Limits: dropout reasons (escape vs core-band vs non-stationary) are not recoverable from committed files (need the gitignored dumps/), so escape-censoring is not separated from missingness; IPW adjusts only for observed lead and length.
2026-10-20 01:30 (+13:00) · 768f267 · observation
Re-executed notebook.ipynb top to bottom from a clean kernel (jupyter nbconvert --to notebook --execute --inplace, ipykernel installed into the WSL project venv with uv pip install, not added to pyproject.toml; kernelspec python3 registered with --sys-prefix). 0 error outputs; the diff against the committed notebook is 23 lines of the 50 KB file. The notebook was not re-built by build_notebook.py, so the Proposal 2 dropout bounds are not in it (README and this NOTEBOOK carry them).
2026-10-20 02:05 (+13:00) · 0eb4822 · observation
Checked the notebook re-run diff (git diff 768f267 0eb4822): no numeric output line changed. What did change is run date, execution timestamps and counts, and the provenance cell's input hashes for mutant_pairs.csv (3a17c762... -> 213bf784...) and mutants_analysis.json (5552246e... -> ab82c60c...). Cause (inference, not re-verified byte by byte): the notebook was last executed against the branch's rerun files, whereas the committed results/mutant_pairs.csv and mutants_analysis.json are the main-line versions kept at the 2026-10-19 merge; the merge entry above records that the two differ only in the last float digit of 19 rows. Numbers in the notebook therefore agree with the README to the printed precision.