← All experiments

Field notes / Experiment guide

Does the excursion link survive a rule-level model?

Matched pairs suggest a relationship between extreme excursions and boundary occupation. Does a split-seed model find it across whole rules?

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

An area-adjusted occupation residual should predict the excursion cutoff in independent seed halves.

What we learned

The main registered association and prediction criteria fail. The sign stays negative, but the effect is weaker and less stable than predicted; exploratory subsets cannot rescue that verdict.

What this leaves open

The Gaussian constant-variance model is inadequate for the heavy-tailed response, and a puzzling pooled slope remains undiagnosed. These failures constrain any mechanistic reading.

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-cutoff-mixed-effects: does the kappa residual predict the excursion cutoff ratio, rule by rule, across seed halves?

Registered 2026-10-16 00:10 NZDT, base commit 997c9b2 (dirty tree), before any model was fitted and before scripts/mixed.py, scripts/calibrate.py or scripts/fit.py was run. This is the open, previously unregistered follow-up of experiments/2026-10-14-kappa-matched-area/ (noise-floor section, last sentence).

No new simulation. Only input: experiments/2026-10-14-t1-excursion-tomography/results/t1b_cutoff_seeds.csv (committed; sha256[:16] e813c6f21f63421c; patched-start windows k = 22 and 25, 44 rules, 6-8 seeds each, escaped windows excluded as in the parent experiment). Columns used: rule, seed, k, area, lnkappa, ln_ratio (ln_ratio = ln(L_99.9/R_eq), the excursion cutoff ratio). ln A := ln area.

Question

Matched-area pairs (parent experiment) showed the higher-kappa rule has the smaller cutoff ratio in ~80% of pairs, but that needed matching on noisy ln A, used only 62-67 non-independent pairs, and shared trajectories between the kappa and ln_ratio sides unless split by parity (N3). Is there a rule-level association between the kappa residual (kappa at given ln A) and ln_ratio, using all 44 rules, no matching, with kappa/ln A measured on one seed half and ln_ratio on the other half, and does it predict out of sample?

Model (fixed now)

Per k (22, 25) separately, and pooled over k as a secondary check. Seeds are split into two disjoint halves. Primary split: parity of the seed id (even / odd).

Covariate half X (even seeds), response half Y (odd seeds), "direction A":

  1. Rule-level covariates from half X: A_i = mean_s ln A, K_i = mean_s lnkappa (rule means over that half's seeds).
  2. Kappa residual: OLS of K on A across the 44 rules -> r_i = K_i - (a + b A_i). Then z-score: zA = (A - mean)/sd, zr = (r - mean)/sd over rules (mean/sd from this half).
  3. Random-intercept model on half Y window rows (one row per rule x seed x k in Y): ln_ratio_is = b0 + bA zA_i + br zr_i + u_i + e_is, u_i ~ N(0, s_u^2) per rule, e_is ~ N(0, s_e^2). Fitted by REML (own implementation: profile likelihood over lambda = s_u^2 / s_e^2, closed-form GLS for the fixed effects; scripts/mixed.py). Pooled model: same with rows from both k, a k indicator, covariates computed per k, one random intercept per rule.
  4. Effect size: br (ln_ratio change per 1 SD of the kappa residual) and Std := br / sd_rules(mean ln_ratio in Y). Also s_u^2, s_e^2, and the share of the between-rule variance of u removed by adding zr to the zA-only model.

Out-of-sample ("fit on one half, evaluate on the other"): coefficients and the transformation (a, b, means, sds) are frozen from the fit above; applied to the covariates computed from the odd seeds, and compared with rule-mean ln_ratio of the even seeds. R2_oos(M) = 1 - sum_i (ybar_i - yhat_i)^2 / sum_i (ybar_i - mean ybar)^2 over the 44 rules, fixed part only (no BLUP). M0 = [1, zA], M1 = [1, zA, zr] (each fitted on its own); dR2 = R2_oos(M1) - R2_oos(M0). "Direction B" swaps the roles of the halves (sensitivity; reported, not tuned).

Uncertainty: (i) rule-cluster bootstrap with a fresh random balanced seed split in every replicate (B = 1000, seed 20261016; rules resampled with replacement and treated as distinct groups; within each resampled rule the seeds are randomly permuted and split floor/ceil in halves), which carries both rule sampling and between-seed assignment variance; (ii) a separate spread over R = 500 random balanced splits at the full 44 rules (mean, sd of br, fraction negative) as the between-seed-split sd. Intervals are 2.5 / 97.5 percentiles.

Negative controls: (a) permutation of zr across rules holding zA and y fixed (1000 permutations, seed 20261016): two-sided p of |br|; (b) whole-pipeline label shuffle: ln_ratio rows permuted as whole rule blocks across rules (200 shuffles) and the bootstrap-CI-excludes-0 rate recorded.

Instrument calibration (scripts/calibrate.py, synthetic, known answer, before the real fit is interpreted): 44 rules, 3-4 seeds per half, s_e = 0.13, s_u = 0.30, true br in {-0.5, 0}, bA = 0.3; 300 simulated datasets (seed 20261016). Also a measurement-noise variant (latent residual observed with noise sd 0.089/sqrt(4) and a matched ln A noise) to report the attenuation of br.

Predictions (fixed now)

Rationale: parent M3 gave partial Spearman -0.56 (k=22), -0.70 (k=25) on rule means, but with shared trajectories; the parity-split N3 pair fraction was 0.79/0.81 against 0.82/0.76 un-split, so most of the signal should survive decoupling, attenuated by the noise in 3-4-seed half means.

  • C (calibration, gate for everything else). On synthetic data: bias of br within 10% of truth (true -0.5, noise-free covariates); bootstrap-CI coverage in [0.90, 0.99]; rejection rate at true br = 0 in [0.02, 0.10]; REML s_u, s_e within 15% of truth on average. Whole-pipeline label shuffle on real data: CI excludes 0 in <= 10% of the 200 shuffles. If C fails, fix the instrument or declare the real fit uninterpretable; do not report it as evidence.
  • P1 (sign and size, direction A, per k). br < 0 and Std <= -0.30 at both k = 22 and k = 25, with the bootstrap 95% interval excluding 0 at both.
  • P2 (out of sample). dR2 >= +0.05 at both k, and >= 90% of bootstrap replicates have dR2 > 0.
  • P3 (permutation). Permutation p <= 0.01 at both k.
  • P4 (pooled over k). Pooled br < 0 with interval excluding 0 and Std <= -0.30.
  • P5 (direction B). Same sign as P1 at both k (sensitivity).
  • Expected magnitude: Std between -0.4 and -0.7 (attenuated relative to the full-seed rank correlations). A Std weaker than -0.30 counts as a failed prediction even if the interval excludes 0.

Decision rules

  • C, P1, P2, P3 all hold: the kappa residual at fixed ln A is a rule-level predictor of the excursion cutoff ratio that survives seed-decoupling and out-of-sample evaluation; a transplant intervention (T2) on kappa-contrasting rules at matched area is then justified (still observational until then).
  • P1 holds, P2 fails: association is in-sample only (does not generalise to the other seed half); no T2.
  • P1 fails at either k: the matched-pair pattern is not a rule-level association once trajectories are decoupled; the kappa-cutoff hint from T1b is closed as a size/coupling effect.
  • Observational only; no causal claim follows from any outcome. A k = 22 vs 25 disagreement is reported as such.

Commands

export UV_PROJECT_ENVIRONMENT=$HOME/.venvs/turmite UV_LINK_MODE=copy
uv run experiments/2026-10-15-kappa-cutoff-mixed-effects/scripts/calibrate.py   # -> results/calibration.txt
uv run experiments/2026-10-15-kappa-cutoff-mixed-effects/scripts/fit.py         # -> results/fit.txt, results/fit.json

Status

Closed 2026-10-16: registered hypothesis FAILED (P1, P2, P3, P4 failed; calibration gate C held; P5 held). No T2 on this basis. notebook.ipynb executed from a clean kernel under uv and agrees with this README.

Results (2026-10-16; predictions above unchanged; base commit 997c9b2, dirty tree; posthoc.py ran at 269288e)

Commands as above plus uv run .../scripts/posthoc.py (exploratory, unregistered) -> results/{calibration.txt,fit.txt,fit.json,posthoc.txt}. Input sha256[:16] e813c6f21f63421c, 1399 windows, 44 rules, 6-8 seeds per rule. Seed 20261016 throughout. REML costs ~4 ms per fit; calibrate.py and fit.py each took roughly 15-30 min.

C (calibration gate): held. Noise-free covariates, true br = -0.5: mean estimate -0.489 (ratio 0.977), s_u 0.297 / s_e 0.130 (truth 0.30 / 0.13), bootstrap CI coverage 0.903 (edge of the [0.90, 0.99] band). Null (br = 0): rejection 0.077, estimate 0.000. Whole-pipeline label shuffle on the real data: CI excludes 0 in 0.075 (k=22) and 0.045 (k=25) of 200 shuffles (bootstrap B=200 there). Noisy-covariate variant (seed sd 0.089 on ln kappa, 0.10 on ln A, 3-4 seeds per half): br attenuated to 0.895 of truth and the CI covers the unattenuated truth only 0.787 of the time; not a gate, but it means real-data Std is a mild underestimate of a latent effect under that noise model. The calibration is Gaussian and homoscedastic, which the real data are not (see below).

Real data, direction A (kappa and ln A from even seeds, ln_ratio from odd seeds), registered fit:

k=22 k=25 registered
br (ln_ratio per SD of kappa residual) -0.415, boot95 [-0.662, -0.135] -0.570, [-1.447, -0.200] < 0, CI excludes 0
Std = br / sd(rule-mean ln_ratio) -0.275 [-0.436, -0.091] -0.194 [-0.482, -0.072] <= -0.30
between-split sd of br (R=500 random halves) 0.075 0.161 reported
dR2_oos (frozen transform, other half) +0.118 (boot95 [-0.101, +0.155]) +0.073 ([-0.085, +0.208]) >= +0.05
bootstrap fraction dR2 > 0 0.875 0.866 >= 0.90
permutation p (zr across rules, 1000) 0.020 0.096 <= 0.01
share of s_u^2 removed by zr 0.147 0.052 reported
direction B Std -0.374 -0.313 same sign

Pooled over k (k indicator, one rule intercept): br = -0.014, Std = -0.006, boot95 [-0.890, 0.287]. The pooled slope is ~0 although both per-k slopes are negative; I did not diagnose this (the calibration covered single-k fits only, so a pooled-fit bug is not excluded).

Verdicts: P1 FAILED (sign and interval held, but Std -0.275 and -0.194 are weaker than the registered -0.30; the expected range was -0.4 to -0.7). P2 FAILED (dR2 point estimates pass, the 0.90 bootstrap criterion does not). P3 FAILED (p = 0.02 and 0.096 vs 0.01). P4 FAILED. P5 held. Per the decision rule ("P1 fails at either k"), the matched-pair pattern is not established as a rule-level association once trajectories are decoupled.

Reading (interpretation). The sign is negative in every analysis (all bootstrap and split fractions, both directions, both k) but the effect is weaker than predicted, imprecise at k=25 and not stable under permutation there; the matched-pair 76-82% overstated the rule-level association. Between-seed uncertainty is large: split sd of br 0.075 / 0.161 on top of rule sampling. Misspecification is probably the main caveat: ln_ratio is heavy-tailed. The median within-rule seed sd of ln_ratio is 0.13, but its 90th percentile is 1.8-2.4 (max about 3.7) and ln_ratio reaches -7; REML s_e is 1.06 (k=22) and 1.39 (k=25), i.e. a minority of bursty/censored rules dominates a homoscedastic Gaussian likelihood.

Post hoc, exploratory, not registered, cannot rescue the verdict (results/posthoc.txt, commit 269288e): (i) dropping rules whose all-seed ln_ratio sd > 0.5 (27 rules at k=22, 29 at k=25; a cut on response dispersion, not on its mean): Std -0.428 [-0.673, -0.188] / -0.392 [-0.587, -0.221], dR2 +0.254 / +0.173, bootstrap fraction > 0 of 0.996 / 1.000, permutation p 0.007 / 0.001, so every registered threshold would be met on that subset. (ii) Normal-score ranks of ln_ratio, all 44 rules: Std -0.265 / -0.197, p 0.020 / 0.054, still below threshold. The association looks cleaner among rules with stable cutoffs, but this is a 60% subset chosen after seeing the failure on the same data; it needs a pre-registered replication (e.g. the k = 23, 24 windows in the same file) before being called a finding.

Not resolved: why pooled br ~ 0; whether a heteroscedastic or robust model changes the all-rule answer; the sign/size of the ln A slope differing between k (bA -1.0 vs -2.0 per SD); how much attenuation 3-4-seed half means cause on real data (about 10% under the synthetic noise settings). Not used: windows k = 23, 24 in the input file (registered k = 22, 25 only).

Analysis notebook: notebook.ipynb

5 saved outputs · 5 code cells. Cells are not executed during publication. Download notebook

Does the kappa residual predict the excursion cutoff ratio, rule by rule?

Question and why it matters

Matched-area pairs (experiments/2026-10-14-kappa-matched-area) suggested the higher-kappa rule has the smaller ln(L_99.9/R_eq) in ~80% of pairs. This experiment tests the rule-level version with all 44 rules, no matching, kappa/ln A from one seed half and ln_ratio from the other, with a random rule intercept (own REML). See README.md (registration) and NOTEBOOK.md.

Assumptions

Seed halves are independent replicates (untested); a linear kappa-on-ln A residual defines 'kappa given area' (untested); rule-cluster bootstrap with duplicated rules as distinct groups is adequate (checked by synthetic coverage 0.90); the Gaussian homoscedastic model is adequate for ln_ratio (false: heavy-tailed, see below).

Provenance

Code
In [1]:
import hashlib, subprocess, datetime, json
from pathlib import Path
SEED = 20261016  # used by all scripts; recorded in README
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())
INPUTS = [EXP/'results'/f for f in ('calibration.txt','fit.txt','fit.json','posthoc.txt')]
assert all(p.exists() for p in INPUTS)
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)
for p in INPUTS: print(hashlib.sha256(p.read_bytes()).hexdigest()[:16], p.relative_to(ROOT))
print((EXP/'results'/'fit.txt').read_text().splitlines()[1])  # sha of the T1b seeds file used upstream
git 269288e | run 2026-10-16T00:33 | seed 20261016
d47044732a62657e experiments/2026-10-15-kappa-cutoff-mixed-effects/results/calibration.txt
8a599f3884b30b9d experiments/2026-10-15-kappa-cutoff-mixed-effects/results/fit.txt
d4defcc9dbdf910f experiments/2026-10-15-kappa-cutoff-mixed-effects/results/fit.json
aacc1c7c33a0dffe experiments/2026-10-15-kappa-cutoff-mixed-effects/results/posthoc.txt
# input sha256[:16] e813c6f21f63421c | rows 1399

Data and method

scripts/mixed.py (REML + split pipeline), scripts/calibrate.py, scripts/fit.py (registered), scripts/posthoc.py (exploratory). Input: committed t1b_cutoff_seeds.csv of the T1/T1b experiment (k=22, 25; 44 rules; 6-8 seeds). Heavy computation is in those scripts; this notebook only reads their committed outputs.

uv run experiments/2026-10-15-kappa-cutoff-mixed-effects/scripts/calibrate.py
uv run experiments/2026-10-15-kappa-cutoff-mixed-effects/scripts/fit.py
uv run experiments/2026-10-15-kappa-cutoff-mixed-effects/scripts/posthoc.py

Results: calibration gate (criterion C)

Code
In [2]:
print((EXP/'results'/'calibration.txt').read_text())
# calibrate.py; commit 997c9b2da6efa526fc6c5a57104d103e1345e916
## noise-free covariates, br=-0.5: 300 datasets, bootstrap B=200
  br estimate mean -0.489 (truth -0.5), sd 0.066; ratio est/truth 0.977
  s_u mean 0.297 (truth 0.30), s_e mean 0.130 (truth 0.13)
  bootstrap 95% CI covers the true br: 0.903;  CI excludes 0: 1.000
## noise-free covariates, br=0 (null): 300 datasets, bootstrap B=200
  br estimate mean 0.000 (truth 0.0), sd 0.049; ratio est/truth n/a
  s_u mean 0.301 (truth 0.30), s_e mean 0.129 (truth 0.13)
  bootstrap 95% CI covers the true br: 0.923;  CI excludes 0: 0.077
## noisy covariates (seed sd lnk 0.089, lnA 0.10), br=-0.5: 300 datasets, bootstrap B=200
  br estimate mean -0.447 (truth -0.5), sd 0.077; ratio est/truth 0.895
  s_u mean 0.363 (truth 0.30), s_e mean 0.130 (truth 0.13)
  bootstrap 95% CI covers the true br: 0.787;  CI excludes 0: 1.000

Shows the estimator recovers a known br (ratio 0.98), s_u, s_e, and rejects 7.7% under the null; the CI coverage 0.903 is at the edge of the registered band. It does not show the model fits the real data: the synthetic noise is Gaussian and homoscedastic.

Results: registered fit

Code
In [3]:
j = json.loads((EXP/'results'/'fit.json').read_text())
crit = dict(Std=-0.30, dR2=0.05, frac=0.90, p=0.01)
print(f"{'':4s}{'br':>8s}{'Std':>8s}{'Std CI':>18s}{'dR2':>8s}{'frac>0':>8s}{'p_perm':>8s}{'StdB':>8s}")
for k in ('22','25'):
    r = j[k]; print(f"k={k} {r['br']:8.3f}{r['Std']:8.3f}  [{r['ci_Std'][0]:6.3f},{r['ci_Std'][1]:6.3f}]{r['dR2']:8.3f}{r['frac_dR2_pos']:8.3f}{r['p_perm']:8.3f}{r['StdB']:8.3f}")
print('registered: Std <= -0.30, dR2 >= 0.05, frac>0 >= 0.90, p <= 0.01')
print('pooled over k:', {a: j['pooled'][a] for a in ('br','Std','ci_br')})
print('P1', all(j[k]['Std'] <= -0.30 and j[k]['ci_br'][1] < 0 for k in ('22','25')),
      'P2', all(j[k]['dR2'] >= 0.05 and j[k]['frac_dR2_pos'] >= 0.90 for k in ('22','25')),
      'P3', all(j[k]['p_perm'] <= 0.01 for k in ('22','25')),
      'P4', j['pooled']['br'] < 0 and j['pooled']['ci_br'][1] < 0 and j['pooled']['Std'] <= -0.30)
          br     Std            Std CI     dR2  frac>0  p_perm    StdB
k=22   -0.415  -0.275  [-0.436,-0.091]   0.118   0.875   0.020  -0.374
k=25   -0.570  -0.194  [-0.482,-0.072]   0.073   0.866   0.096  -0.313
registered: Std <= -0.30, dR2 >= 0.05, frac>0 >= 0.90, p <= 0.01
pooled over k: {'br': -0.013950091931779854, 'Std': -0.006274791657703334, 'ci_br': [-0.8896540874063015, 0.2869464829892926]}
P1 False P2 False P3 False P4 False

The sign is negative and the bootstrap interval for br excludes 0 at both k, but the standardised effect (-0.28, -0.19) is below the registered -0.30 threshold, the held-out gain is not reliably positive (0.87 of replicates), the permutation p is 0.02 and 0.10, and the pooled slope is about 0. The registered hypothesis failed. It does not show there is no association: the sign agrees in every analysis, in both split directions, and between-split sd of br (0.075, 0.161) is as large as the gap to the threshold.

The full text output (with s_u, s_e, split-sd, shuffle control):

Code
In [4]:
print((EXP/'results'/'fit.txt').read_text())
# fit.py; commit 997c9b2da6efa526fc6c5a57104d103e1345e916
# input sha256[:16] e813c6f21f63421c | rows 1399
rules 44; seeds per rule/k min 6 max 8
parity split: X = even seeds, Y = odd seeds; direction A = covariates from X, ln_ratio from Y
## k=22
direction A (parity): br=-0.4152 (REML SE 0.1632) bA=-1.0116 b0=4.4023; ysd(rule means)=1.5108 -> Std=-0.275
  s_u=0.9294 s_e=1.0576; s_u^2 zA-only 1.0131 -> with zr 0.8638 (share removed 0.147)
  out-of-sample R2 (44 rule means, frozen transform): M0 0.564, M1 0.681, dR2 +0.118
direction B (parity, sensitivity): br=-0.5455 (SE 0.1232) Std=-0.374; dR2 +0.055
rule-cluster bootstrap B=1000, fresh split per replicate: br 95% [-0.6622, -0.1347]; Std 95% [-0.436, -0.091]; dR2 95% [-0.101, +0.155], frac dR2>0 0.875
random balanced splits R=500 (44 rules fixed): br mean -0.4199 sd 0.0751 (between-split sd), frac<0 1.000; Std mean -0.279 sd 0.046; dR2 mean +0.051 sd 0.046
permutation of zr across rules (1000): null br sd 0.1774; two-sided p = 0.0200
## k=25
direction A (parity): br=-0.5700 (REML SE 0.3249) bA=-1.9909 b0=3.5610; ysd(rule means)=2.9356 -> Std=-0.194
  s_u=2.0136 s_e=1.3937; s_u^2 zA-only 4.2791 -> with zr 4.0547 (share removed 0.052)
  out-of-sample R2 (44 rule means, frozen transform): M0 0.441, M1 0.514, dR2 +0.073
direction B (parity, sensitivity): br=-0.9288 (SE 0.3160) Std=-0.313; dR2 +0.002
rule-cluster bootstrap B=1000, fresh split per replicate: br 95% [-1.4471, -0.2003]; Std 95% [-0.482, -0.072]; dR2 95% [-0.085, +0.208], frac dR2>0 0.866
random balanced splits R=500 (44 rules fixed): br mean -0.8510 sd 0.1605 (between-split sd), frac<0 1.000; Std mean -0.287 sd 0.052; dR2 mean +0.041 sd 0.069
permutation of zr across rules (1000): null br sd 0.3334; two-sided p = 0.0959
## pooled over k=22,25 (k indicator, one random intercept per rule)
direction A: br=-0.0140 (REML SE 0.1683) Std=-0.006; s_u=1.5261 s_e=1.4620
bootstrap B=1000: br 95% [-0.8897, 0.2869]; Std 95% [-0.417, 0.125]
## negative control: whole-pipeline label shuffle (200 shuffles, bootstrap B=200 each; k=(22, 25))
  groups of rules with identical seed lists (sizes): [1, 1, 42]
  k=22: shuffled br mean -0.0050 sd 0.2396; CI excludes 0 in 0.075 of shuffles
  k=25: shuffled br mean +0.4024 sd 0.3942; CI excludes 0 in 0.045 of shuffles
## registered criteria
  P1 sign/size/CI: FAILED
  P2 out-of-sample: FAILED
  P3 permutation: FAILED
  P4 pooled: FAILED
  P5 direction B sign: HELD
  k=22: Std -0.275 (need <= -0.30); CI br excl 0: True; dR2 +0.118 (need >= +0.05), frac>0 0.875 (need >= 0.90); p_perm 0.0200 (need <= 0.01)
  k=25: Std -0.194 (need <= -0.30); CI br excl 0: True; dR2 +0.073 (need >= +0.05), frac>0 0.866 (need >= 0.90); p_perm 0.0959 (need <= 0.01)

Caveats and exploratory follow-up (not registered)

Code
In [5]:
print((EXP/'results'/'posthoc.txt').read_text())
# posthoc.py (exploratory); commit 269288eb62bf6b6ec125a3b420dd899057858be5
# input sha256[:16] e813c6f21f63421c
k=22 quiet: rules 27; A: br=-0.2073 Std=-0.428 boot Std95 [-0.673,-0.188]; s_u=0.329 s_e=0.103; dR2 +0.254 frac>0 0.996; perm p 0.0070; B: Std=-0.510
k=22 rank: rules 44; A: br=-0.2252 Std=-0.265 boot Std95 [-0.433,-0.081]; s_u=0.484 s_e=0.602; dR2 +0.090 frac>0 0.883; perm p 0.0200; B: Std=-0.323
k=25 quiet: rules 29; A: br=-0.3759 Std=-0.392 boot Std95 [-0.587,-0.221]; s_u=0.325 s_e=0.125; dR2 +0.173 frac>0 1.000; perm p 0.0010; B: Std=-0.420
k=25 rank: rules 44; A: br=-0.1849 Std=-0.197 boot Std95 [-0.424,-0.075]; s_u=0.602 s_e=0.406; dR2 +0.053 frac>0 0.824; perm p 0.0539; B: Std=-0.247

Among the 27-29 rules with stable cutoffs (seed sd of ln_ratio <= 0.5) every registered threshold would be met; ranks over all rules would not. This subset was chosen after the failure, on the same data, so it is hypothesis-generating only. REML s_e is ~1 against a median seed sd of 0.13: a minority of bursty rules makes the Gaussian homoscedastic model misspecified. The pooled slope (~0) is undiagnosed. Windows k=23, 24 were not used.

What this changes, next steps

No T2 on this basis. Next (to be registered first): replicate the quiet-rule subset on the k=23, 24 windows, fit a heteroscedastic or robust model for all rules, and add a two-k synthetic calibration to explain the pooled result.

Read the dated lab log

Kappa-cutoff mixed-effects lab notebook

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 00:01 (+13:00) · 997c9b2 · intent

Session start. Do the open follow-up of kappa-matched-area (noise-floor section): rule-level random-intercept regression of ln_ratio on the kappa residual given ln A, all 44 rules, no matching, seed halves decoupled. Informs whether T2 (transplant on kappa-contrasting matched-area rules) is justified. Read parent README/NOTEBOOK/noise_floor.py/results first.

2026-10-16 00:10 (+13:00) · 997c9b2 · assumption

(1) Seeds are exchangeable replicates of a rule and the same seed id at k=22 and 25 are the same trajectory family, so a split by seed id keeps covariate and response halves independent. Untested; the parity vs random-split comparison is a partial check. (2) A linear ln kappa ~ ln A residual (fit across rules within a half) is an adequate "kappa given area". Untested; parent M2 said slopes differ between and within rules. (3) Rule bootstrap with duplicated rules as distinct groups is adequate for the interval on br; checked only by synthetic coverage. (4) ln_ratio = ln(L_99.9/R_eq) shares R_eq with area in construction, so part of any lnA effect on y is mechanical; zA is in the model for that reason. Status of all: untested.

2026-10-16 00:10 (+13:00) · 997c9b2 · decision

Registered model, predictions (C, P1-P5), pass/fail and decision rules in README.md before any fit. Implement REML myself (scripts/mixed.py), not statsmodels: the model is a single random intercept with rule-level covariates, so own code is small and testable on synthetic data; no new dependency.

2026-10-16 00:30 (+13:00) · 997c9b2 · intent

Launched scripts/calibrate.py (300 synthetic datasets x bootstrap B=200 per scenario) and, in parallel, scripts/fit.py. Per-fit cost is ~4 ms so both take tens of minutes. The real fit is not to be interpreted unless calibration criterion C holds.

2026-10-16 00:45 (+13:00) · 997c9b2 · observation

calibrate.py -> results/calibration.txt. Noise-free br=-0.5: est -0.489, s_u 0.297, s_e 0.130, coverage 0.903; null rejection 0.077; noisy covariates: br ratio 0.895, coverage 0.787 (vs unattenuated truth). Real-data label shuffle (in fit.py): CI excludes 0 in 0.075 / 0.045. Criterion C held, barely on coverage.

2026-10-16 00:45 (+13:00) · 997c9b2 · observation

fit.py -> results/fit.txt/.json. Direction A: Std -0.275 (k=22), -0.194 (k=25); br CIs exclude 0; dR2_oos +0.118/+0.073 but bootstrap frac>0 0.875/0.866; permutation p 0.020/0.096; pooled br -0.014 [-0.89,0.29]; direction B Std -0.374/-0.313. P1-P4 failed, P5 held.

2026-10-16 00:45 (+13:00) · 997c9b2 · surprise

REML s_e = 1.06 / 1.39 although the median seed sd of ln_ratio is 0.13. A minority of rules has seed sd 2-4 (ln_ratio down to -7): the homoscedastic Gaussian model is misspecified for those. Pooled br ~ 0 despite per-k slopes -0.4/-0.6 is also unexplained; not debugged (calibration covered single-k fits only, so a pooled-fit bug is not excluded).

2026-10-16 00:55 (+13:00) · 269288e · deviation

Ran an unregistered posthoc.py after seeing the failure (quiet-rule subset sd<=0.5; normal-score response). Labelled exploratory in README. HEAD had moved from 997c9b2 to 269288e between fit and posthoc (another session committed; input hash unchanged). Quiet subset meets every registered threshold (Std -0.43/-0.39, p 0.007/0.001); normal scores do not (Std -0.27/-0.20).

2026-10-16 00:55 (+13:00) · 269288e · interpretation

Registered hypothesis failed; the negative sign is robust but the all-rule effect is weaker than predicted and fragile at k=25. The matched-pair 76-82% overstated it. The subset result is post hoc on the same data (subset chosen on response dispersion): hypothesis-generating only. Confidence: moderate that the sign is real, low on magnitude. Alternatives: bursty rules carry real kappa-cutoff coupling the Gaussian model cannot see; or the association is a censoring artefact of the cutoff estimator.

2026-10-16 00:55 (+13:00) · 269288e · decision

No T2 on the basis of this fit. Next (needs registering): replication on quiet rules at k=23,24 plus a heteroscedastic/robust model for all rules; a two-k synthetic calibration to diagnose the pooled model first. notebook.ipynb written; open-questions entry added. No commit.

2026-10-15-kappa-cutoff-mixed-effects · Source revision ba38590b33e3

Original source ↗ · Input hashes

Introductions summarize the evidence; the expandable record preserves the original predictions, methods and caveats. Figures and notebooks are published as saved. Publication runs no simulations or notebook cells.