Component ledger: every piece to its own bar before anything trains jointly#
Opened at commit b4c6620. The method is component-wise validation first and
joint training last. Nothing trains jointly until every row below reads PASS or
carries an explicit, measured waiver saying why the joint run proceeds without
it.
The reason this ledger exists rather than a plan: this project trained a full
model four times before anyone measured whether its encoder aggregated context.
It does not. Every result produced before b4c6620 used an encoder that is
x_t -> s_t, whose docstring claimed x_{<=t} -> s_t, and whose scaling
exponent in the token count is therefore zero by construction. That single
unmeasured component is what retired the per-coordinate corner rule
(docs/CORNER_RULE_RETIREMENT.md). A joint run cannot tell you which of its
parts is the broken one; only a bar per part can.
Rules for a row to read PASS#
- Its own bar, stated before the run. The bar names a target the component is supposed to reach and a baseline it must beat.
- A planted negative that fails the bar. If the shipped component, or a trivial one, also clears the bar, the bar measures nothing and the row stays OPEN.
- Matched parameter counts, printed beside every arm. An unmatched win is a tie.
- RED first. The test fails before the component exists, verbatim failure recorded.
- A producer for every number, per
L-PROSEinMISTAKES.md, with the report's bound-over-reported ratio on its first line.
The pieces#
| id | component | bar it must clear | status |
|---|---|---|---|
| P1 | Encoder | aggregate context causally; clear a target a position-wise map provably cannot express, with the shipped Encoder shown failing it and a permutation control that moves the new one and leaves the shipped one bitwise identical |
PASS |
| P2 | Read / operator | matched head-to-head against a plain softmax attention head at equal parameter count | ANSWERED — costs 1.17–2.84x, buys 0.055 nats and zero decisions; the gate is dead at the shipped init |
| P3 | Head / probe | find the binding ceiling among probe rank, read width and encoder | ANSWERED — the encoder binds |
| P4 | Causal machinery | one synthetic-bed causal claim, measured on real human positions | FAIL — the claim was an identity; the learned read is at 0.9997 |
The joint gate#
Joint training starts when P1 through P4 read PASS, or when a row carries a waiver stating the measured reason it is being carried forward broken and what that costs the joint result. A waiver is a number and a sentence, not a judgement call.
Two things the joint run must carry regardless, both learned the hard way:
- A frozen-random arm, identical to the trained arm except that its encoder
receives no gradient. Without it a joint result cannot separate scale from
luck. On the synthetic bed the frozen arm beat the trained one; on 115,628
real games the trained arm won, 0.5998 nats against 0.2348 with a uniform
baseline of
2*ln(64) = 8.3178. Only the control made that difference readable. - A trivial baseline on the real target. Every arm of the T4 run lost to "guess the most common square" at every context length — 0.0823, 0.0256, 0.0123 for the trained arm at L = 16, 64, 128 against 0.0942, 0.0398, 0.0247. A joint run without that column beside it can report a win that is not one.
Limits#
Statuses are updated only from a report that carries its producer ratio. A row moved to PASS on prose is a row that is still OPEN.
P1 — PASS, 2026-09-14#
ceqjepa/causal_encoder.py, bound by tests/curvature/test_causal_encoder.py
(RED first: ModuleNotFoundError: No module named 'ceqjepa.causal_encoder';
now 12 passed in 227.37 s). Reported with a producer ratio of 24/24.
The bar is a nine-channel strict-prefix EMA bank over an i.i.d. Gaussian bed.
A position-wise map cannot clear it, and not as an optimisation accident: the
sum runs over j < t strictly and the bed is i.i.d., so y_t is independent of
x_t, E[y_t | x_t] = 0, and the MMSE of any measurable function of x_t
alone equals Var(y_t). The ceiling is R² <= 0 by information, not by
training. A plain prefix mean was rejected as the bar because it is
permutation-invariant, so an encoder clearing it perfectly should be
order-blind — the bar and the permutation control would have disagreed.
| arm | params | R² eval | R² train | MSE | MSE const |
|---|---|---|---|---|---|
shipped Encoder (planted negative) |
1769 | −0.1936 | 0.1543 | 1.1147 | 0.9339 |
shipped Encoder, 3.2× wider |
5577 | −0.6086 | 0.4027 | 1.5023 | 0.9339 |
CausalEncoder, span = self (null) |
1657 | −0.0544 | 0.0446 | 0.9847 | 0.9339 |
CausalEncoder |
1657 | +0.9863 | 0.9872 | 0.0128 | 0.9339 |
The shipped encoder scores below the constant predictor, and widening it 3.2× makes it strictly worse, which is what the argument predicts. The winner carries 112 fewer parameters than the arm it beats. Three seeds, non-overlapping: shipped −0.1936 / −0.1940 / −0.2040 against causal +0.9863 / +0.9890 / +0.9896.
Permutation control — varies the order of context positions 0..14, pins the
multiset, x_15, weights, seed, batch and dtype: shipped bitwise_identical =
True, max_abs_change = 0.000000e+00; CausalEncoder False,
4.647257e+00. Causality is asserted rather than assumed: perturbing x_k
moves every position t < k by exactly 0.000000e+00 in all four arms.
The self-span ablation is what isolates aggregation from everything else —
identical 1657 parameters, initialisation asserted parameter-by-parameter with
torch.equal, identical bed, optimiser, lr, steps and loss, varying only which
positions a query may see. The 1.041 R² gap is attributable to aggregation
alone, not to depth and not to positional encoding.
Adoption is deliberately not done yet. Swapping the class touches
pi_jepa.py:844-845, and :787-788 is a hard break: the collapse check reaches
through the encoder and iterates .net, so CausalEncoder exposes .net as
an nn.ModuleList for exactly that reason. :1307-1311 states "Encoder is a
position-wise map", which becomes false and must be rewritten rather than
deleted — it is the premise the corner-rule retirement rests on.
Limits: S = 16 only, so this says nothing about length generalisation; the bed
is synthetic i.i.d. by choice, because i.i.d. is what makes the position-wise
ceiling a theorem rather than a measurement; and the encoder's own
representational exponent was not measured, which is a different claim from
tracking nine timescales.
P3 — ANSWERED, 2026-09-14: the encoder binds, and the probe is exonerated#
Producer ratio 1.00 over 114 claims. Target is the exact committor from
chess_steps.oracle()["q"] (368,452 positions, residual 9.645e-13), not move
identity. Split by ply-15 position index, not by walk, with the train/test
position intersection asserted 0 — a by-walk split would score a lookup.
The rank worry was wrong and is retired. logit(q) as a single feature
reaches mse 2.253433e-15, skill 1.000000. The committor is a scalar function
of state, so d = 1 suffices; 7 columns are seven times more than needed and no
rank bottleneck of the 64-class kind exists on this label.
Skill = 1 − mse/mse(marginal), cluster bootstrap over 4,006 distinct test positions, 4,000 resamples.
| features | d | linear | kNN | GBT |
|---|---|---|---|---|
| six ordinal board coords (the bar) | 6 | −0.0002 | +0.6818 | +0.8851 |
| raw 769 one-hot board | 769 | +0.1875 | +0.4982 | |
| encoder hidden, frozen_random | 1024 | +0.2560 | +0.3702 | |
| read7, frozen_random | 7 | +0.0126 | −0.0153 | −0.0061 |
| read7, trained | 7 | +0.0095 | −0.0388 | −0.0397 |
Three candidates, one binds. Not the probe: on the 7 columns every nonlinear
reader scores below linear at every arm, while the same GBT reaches +0.8851
where structure exists — the reader works, the structure is absent. Not the
read width: opening the two coordinates pi_assign refuses moves skill by at
most +0.0085 against a gap of 0.8851. The encoder: +0.3702 at the 1024-wide
layer falls to −0.0080 at the 9-dim output of the very next layer, one
nn.Linear(1024, 9), and it is already 0.5154 behind the bar before that matrix.
Training never helps. Over a ladder of 0…256 fit steps the maximum skill is
+0.0163, reached after the representation has collapsed, while effective rank
falls monotonically 7.0038 → 1.0425. With the detector on, the trained arm
refuses at step 20: erank 1.4825 against floor 1.5 while std_min reads
5.5404e-03, 5.5x above its own floor — the third independent sighting of the
variance leg being blind to dimensional collapse.
Retired with a measured reason: the D_LATENT sweep. No width crosses, because a sweep over D cannot exceed what the layer feeding it carries, and that layer carries +0.3697 against a bar of +0.8851.
Reroute, one constant: 0.3869 of the reducible variance dies in the
12-plane × 64-square one-hot before any weight (raw769 +0.4982 against embed6
+0.8851, identical reader and rows). ceqjepa/chess_steps.py:549 embed already
ships the ordinal (file, rank) encoding that recovers it. Change X_DIM and the
feature builder, re-run the bed unchanged; the bar is pre-registered at +0.8851
and RED #7 in tests/curvature/test_committor_read_ceiling.py is the gate.
Reprice, before any further fit here: NU = 100.0 drives that collapse in 64
steps on a three-piece bed. Until pi_jepa.covariance_sweep() is run on this
bed, every trained number on it is a number about the degeneracy and
frozen_random is the honest arm.
Incidental, real, unfixed: the D_LATENT sweep crashes at D = 5 —
pi_jepa.py:878 AssertionError: the read carries a nonzero imaginary part.
P2 — ANSWERED, 2026-09-14: optimisation parity, and the gate is dead on arrival#
Producer ratio 61/61. Scope stated so it cannot be misread: CHARTER.md:53-56
already closes expressivity parity. This measures optimisation parity —
does gradient descent on the family reach where gradient descent on the fixed
corner reaches. Nobody had measured it. Metric is next-move prediction, which the
north star rules out as a target; it is admissible only because both arms are
bottlenecked identically and every comparison is arm-against-arm, never against
a baseline.
Bed: 6,844 real games at ≥33 plies, split by game with check_game_split,
6,148 train / 696 holdout, 22,272 held-out decisions. Tokens are moves only,
no board, so position is recoverable only by mixing over the prefix — which is
what makes the operator, not an encoder, the thing being scored. CIs are a paired
game-level bootstrap over the 696 games, 10,000 resamples.
Containment survives training bitwise. arm-beta1 against softmax:
Δexact +0.00000, zero-width CI, at all three seeds. They are the same trained
model.
The beta axis is a per-row scalar gain and nothing more. num_ij carries no
beta, so W(beta) = softmax · Z^(1−beta) exactly — worst
|softmax_row − linear_row| = 1.110223e-16 over a [3, 12, 12] operator once
rows are normalised. A free beta lowers held-out log-loss by 0.046–0.063 nats
(mean 0.055, p < 1e-4 at every seed) and moves exact accuracy not at all,
because dO_i/dbeta = −log(Z_i)·O_i is a per-row scale carrying no component
that re-ranks which j a row attends to. Beta lands at an interior optimum,
0.667 from above and 0.587 from below — not the softmax corner. A downstream
RMS norm absorbs the whole axis to 1e-12, which is a structural tension worth
naming: the committor bed wants a normaliser and the beta axis cannot survive
one.
The gate cannot train from the shipped init. See MISTAKES.md V-29: two
independent mechanisms, each exactly zero, and identity_heads sits on both. An
arm trained 2,000 Adam steps from it ends bitwise identical to softmax with
m_head_weight_absmax = 0.0, at 1.910x the wall clock. Woken with a 1e-3
offset on each bias, both gradients live (2.412696e+00, 1.005618e-02) and the
operator stays within 5e-2 of the corner — and it then ties softmax across
three seeds (−0.00310 / −0.00054 / +0.00153) at 1.910–2.839x the cost.
Round-robin timing, interleaved so background load falls on every arm equally:
softmax 23.845 ms/step; arm-beta-free 1.174x; arm-beta1 1.318x; arm-gate
1.910x; arm-gate-live 2.839x.
Retired with a measured reason: the committor bed at this capacity. Built
exactly (q_residual = 9.645e-13), 58,940 held-out scored positions split by
hash of the state index. Only one configuration has the mixing operator beating
its own deletion, at R² +0.06317, and its curve still oscillates
(−0.1267, −0.1020, −0.1767, +0.0601 at steps 500/1000/1500/2000). A 0.003-scale
arm difference cannot be read off an unconverged endpoint carrying 6% of the
variance. The fix is optimisation budget, not architecture — and every
architectural stabiliser is a normaliser, which deletes the beta axis.
Decided default: fix identity_heads before any further arm is trained.
Every gate result in this repo produced from it is a measurement of softmax
wearing 130 buffers.
Limits: three seeds, one bed, one layer, one head, d_model 64, S 32, 2,000
steps — this prices the family at small scale and says nothing at transformer
scale. p-values are uncorrected for 9 comparisons; Bonferroni at α = 0.0056
leaves no-mix and the three ΔCE results standing, which is the same
conclusion. Wall clock is CPU-only with softmax held in float64 too, so the
ratios price the operator and not the dtype.
P3 reroute — 2026-09-14: the encoding was the loss, and the bar has an axis#
The one-constant reroute ran with the bar pre-registered and the assertions locked. Producer ratio 1.00; every one-hot figure was re-derived bit-identically before anything changed.
| untrained / hid / gbt | skill | gap to bar |
|---|---|---|
| 769-dim one-hot (before) | +0.3696799025442123 | 0.5154409791866523 |
| 7-dim ordinal (after) | +0.8424222612206883 | 0.0426986205101764 |
the bar, embed6 + gbt |
+0.8851208817308647 | — |
91.7161% of the gap closed. RED #7 did not flip and the bar was not moved.
The residual is the bar's coordinate system, not lost information. An exactly
orthogonal change of basis — ||Q'Q - I||inf at 6.661e-16, destroying nothing by
construction — costs the same reader far more than the whole remaining shortfall:
embed6 falls 0.1413 and raw7 falls 0.2500. File and rank are literally the
tree's split axes, so RED #7 as written is partly an axis-alignment test. The
bar stays where it was pre-registered; the rotation control is recorded beside
it. Moving a bar after seeing the result is the defect this project hunts.
The loss relocated downstream. Untrained arm, gbt, under the new encoding:
raw7 +0.8901 at the encoder's own input, hid +0.8424 (−0.0477), enc9
+0.4583 (−0.3842), read7 +0.2353 (−0.2230). The MLP story now lives at
the 9-dim projection and the read, an order of magnitude above the residual at
the widest layer.
Sixteen plies of context contribute nothing at the widest tap. raw7 pushed
through a random Linear(7,1024)/GELU/Linear(1024,1024)/GELU — the hid tap's
exact shape, one observation, no context — scores +0.8424222625154502 against
the bed's +0.8424222612206883, matching to nine significant figures.
The collapse is priced, and raising nu does not prevent it. First run of
covariance_sweep on the chess walk bed: at nu = 1, 25 and 100 the effective
rank falls from 7.6544 and every arm would refuse at step 20; nu governs only
the partial recovery afterwards (erank_last 1.0042 / 1.8198 / 2.5374). The
ordinal encoding makes it fire earlier, at step 13, with the smallest
per-coordinate sd at 1.8131e-02 — eighteen times above its 1e-3 floor.
Purely dimensional. frozen_random remains the honest arm. One contrast worth
chasing: on the PGN corpus at hidden 256 the trained arm ran all 1,500 steps
without firing (erank_min 1.6108, erank_last 8.2523) — two things differ, so it
is a lead, not a conclusion.
The width-sweep crash is diagnosed and is not what it said — see P-14 in
MISTAKES.md. A float32 overflow reported as a moved gate; the largest genuine
imaginary part is exactly 0.0 at every width tested, and the shipped mask never
fires.
Next: attack the 9-dim projection with the encoding pinned to ordinal.
P3 CORRECTION and the projection's acquittal, 2026-09-14 (producer ratio 313/313)#
Two claims recorded above are wrong and are corrected here.
"Training never helps" was measured at a collapsed width. The collapse is a HIDDEN-WIDTH threshold, not a property of the corpus or the objective:
| hidden | outcome | erank_min |
|---|---|---|
| 128 | survives 1,500 steps | 1.5369977235209193 |
| 256 | survives 1,500 steps | 1.9238083034849818 |
| 512 | refuses at step 45 | sd 5.7238e-02 |
| 1024 | refuses at step 13 | sd 1.8131e-02, erank 1.4530 of 9 |
Only hidden varies across that sweep, so the corpus is exonerated. The
non-firing contrast recorded earlier differs from the bed in three places,
not two: corpus, hidden width and observation encoding. At hidden 256, where
the fit is a real 1,500 steps rather than thirteen and a traceback, training
helps: enc9 +0.6279 against its untrained +0.5663, read7 +0.3422 against
+0.1919, encoder max|dw| 0.5756, no refusal. Every future trained number on this
bed is taken at hidden 256.
The 1024 to 9 projection is an innocent bystander. All four candidates are
killed, each by its own measurement. Width: raw7 zero-padded to nine columns
scores +0.8901038547930719, bit-identical to raw7 and above the bar, so nine
coordinates carry the whole label. Information: enc9 reconstructs the six
ordinal board coordinates at R2 0.9752 to 0.9916 with zero float32 collisions,
and those reconstructions read +0.7105 against +0.4583 taken off enc9
directly. Training: above. Context: the encoder never had any, since the
1024-wide tap is bit-identical under fifteen different context plies, max
abs difference 0.0.
What is left is the basis. An orthogonal rotation costs embed6 -0.1413 and
raw7 -0.2500 but enc9 only -0.0044, because enc9 is already generic and
has nothing left to rotate away. Sixteen random 1024 to 9 draws of the same
hid average +0.5439 and +0.5740 by family; the shipped draw at +0.4583 sits
below all sixteen. The width sweep climbs monotonically (+0.4583 at 9, +0.7221
at 32, +0.8276 at 256) and never reaches the bar. That is not a capacity curve,
since capacity saturates at 7, but the count of axis-aligned directions a tree
can use.
Where the loss is irreversible: the read. It reconstructs the six board
coordinates at R2 0.9182, 0.9105, 0.1487, 0.1329, 0.7213, 0.7414, and
coordinates 2 and 3 are the white queen's file and rank, the piece the committor
turns on. Re-reading from those reconstructions recovers nothing (+0.2596)
against +0.7105 by the same route from enc9. Mechanism: the read is a 16-ply
mean, with five of its seven columns explained by the plain unweighted mean
at R2 0.9962 to 0.9980 and adding ply 15 changing nothing, and a mean blurs the
queen because she moves 0.6923 squares per ply against the white king's 0.0810.
This bed cannot reward an aggregating encoder, and that is measured. q at
ply 15 is a function of the ply-15 position, so the walk is Markov and the
prefix is conditionally independent of the label. CausalEncoder at matched
parameter count, varying only which positions a query may see: aggregation
costs 0.0810 at enc9, prefix +0.2692 against self-span +0.3503. The 16-ply
mean alone scores +0.2978 against ply 15's +0.8851, and concatenating them costs
0.0377. Moving the label to ply 19, the horizon the family actually predicts,
does not rescue it. P1's PASS on the i.i.d. EMA bed is not in question; its sign
flips on this target, which is exactly the limit its own entry names.
Next round does not widen the latent and does not give the encoder context, both measured dead ends here. It attacks the read's blur, on a target that is not Markov in the ply-15 position.
Limits: one bed, one endgame, one reader family, skill stated for the gradient-boosted reader only. The draw ensemble is eight seeds per family, so "below all sixteen" is an order statistic, not a p-value. The recovery route uses label-free board coordinates the encoder cannot access, so it is a ceiling on what the representation carries rather than a shippable probe. The two RED tests binding this round live in the session scratchpad and are not in the tree. The pre-registered bar was not edited and remains RED.