# Horizon Rebase: N_HORIZONS 5 → 3 with HORIZONS = [10, 100, 1000]

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Reduce N_HORIZONS from 5 to 3 and shift HORIZONS from `[30, 100, 300, 1000, 6000]` to `[10, 100, 1000]` to match empirically-derived market autocorrelation elbows on ES MBP-10. Eliminates 3 horizons that sit below the input ACF noise floor (per C1 investigation) and resolves the chronic h6000 collapse (h6000 AUC plateaued at 0.50-0.74 even when other horizons hit 0.74+; ρ(30, 6000) = 0.064 in labels — no signal).

**Architecture:** Atomic refactor of N_HORIZONS = 3 across ml-alpha (lib + tests + examples + CUDA kernels), ml-backtesting (locally-constant N_HORIZONS), and Argo workflow scripts (per-horizon log strings). BUCKET_DIM_K shifts from compile-time `[25,25,25,25,28]` to initial-hint `[43,43,42]`; runtime values remain ISV-driven via the existing τ-assignment kernel at Phase 1→2 transition (no new controller required — existing Controller A's bucket-assignment IS the ISV step). MAX_BUCKET_DIM raised from 28 to 96 to accommodate non-uniform τ distributions in 3 buckets.

**Tech Stack:** Rust + CUDA. Build via `SQLX_OFFLINE=true cargo check`. Tests via `cargo test -p <crate>`. Smoke validation via Argo Workflows on Scaleway L40S.

---

## Current state (in-progress at start of plan)

Branch: `ml-alpha-phase-a` HEAD `0384f7674` (D1 signed-EMA fix) + `fe5d7a1ad` (D2 EMA bandwidth rename).

Working tree has uncommitted partial work from a prior session:
- `crates/ml-alpha/src/heads.rs` — constants changed (`N_HORIZONS=3`, `HORIZONS=[10,100,1000]`)
- `crates/ml-alpha/src/cfc/bucket_routing.rs` — `MAX_BUCKET_DIM=96`, `BUCKET_DIM_K=[43,43,42]`, `BUCKET_CHANNEL_OFFSET=[0,43,86,128]`
- `crates/ml-alpha/tests/perception_overfit.rs` — `[1.0; 5]` → `[1.0; N_HORIZONS]`, `[f32; 5]` → `[f32; N_HORIZONS]` (replace_all done; cascade type errors may remain)
- `crates/ml-alpha/examples/alpha_train.rs` — log line `w_h30..w_h6000` → `w_h10..w_h1000` (partial; 4 hardcoded array literals at lines 465, 525, 534 + summary at 678 still pending)

`cargo check -p ml-alpha` (library only) passes. `cargo check -p ml-alpha --all-targets` fails on tests/example.

## File structure

| File | Type | Action |
|---|---|---|
| `crates/ml-alpha/src/heads.rs` | source | ✓ constants changed (committed-pending) |
| `crates/ml-alpha/src/cfc/bucket_routing.rs` | source | ✓ constants changed (committed-pending) |
| `crates/ml-alpha/cuda/horizon_lambda.cu` | kernel | `#define N_HORIZONS_LAMBDA 5` → `3` (line 36); verify no other hardcoded `5` in body |
| `crates/ml-alpha/cuda/bce_loss_multi_horizon.cu` | kernel | `#define BCS_N_HORIZONS 5` → `3` (line 35); verify warp accumulator + shared mem sizes |
| `crates/ml-alpha/cuda/output_smoothness.cu` | kernel | Audit for hardcoded 5; should be parametric via N_HORIZONS uniform |
| `crates/ml-alpha/cuda/smoothness_lambda_controller.cu` | kernel | **CONFIRMED**: `SLC_N_HORIZONS = 5` (line 30) AND `TARGET_K_RATIO[]` (lines 45-51) uses old-horizon formula `sqrt(30/{30,100,300,1000,6000})`. Must change to `sqrt(10/{10,100,1000})` for new HORIZONS = [10,100,1000]. The kernel currently reads only slots [0..3] under N_HORIZONS=3, anchoring h1000's smoothness target to OLD h300 ratio (functional bug). |
| `crates/ml-alpha/cuda/multi_horizon_heads.cu` | kernel | Audit for `HORIZON_H` define vs hardcoded |
| `crates/ml-alpha/cuda/heads_block_diagonal_fwd.cu` | kernel | Audit |
| `crates/ml-alpha/cuda/cfc_step_per_branch.cu` | kernel | Audit |
| `crates/ml-alpha/cuda/bucket_transition_kernels.cu` | kernel | Audit for MAX_BUCKET_DIM dependencies (raised from 28 to 96) |
| `crates/ml-alpha/examples/alpha_train.rs` | binary | Replace 4 hardcoded `seq.labels[0..5]` array literals with `std::array::from_fn`; fix summary struct |
| `crates/ml-alpha/tests/perception_overfit.rs` | test | Verify after replace_all; may still have Vec<&[[f32; 5]]> cascading types |
| `crates/ml-alpha/tests/smoothness_lambda_controller_invariants.rs` | test | Semantic rewrite: 5-element fixtures `[0.5, 0.15, 0.05, 0.015, 0.0025]` → 3-element. Rename `excess_at_h6000_lifts_lambda_proportionally` → `excess_at_h1000_lifts_lambda_proportionally`. Re-derive expected values |
| `crates/ml-alpha/tests/output_smoothness_grad_finite_diff.rs` | test | ✓ `[0.1, 0.3, 1.0, 3.0, 10.0]` → `[0.1, 1.0, 10.0]` (committed-pending) |
| `crates/ml-alpha/tests/bucket_transition_kernels.rs` | test | May reference 28-element bucket sizes; audit for MAX_BUCKET_DIM=96 |
| `crates/ml-alpha/tests/perception_forward_golden.rs` | test | Golden values invalidated; regenerate golden |
| `crates/ml-alpha/tests/forward_step_golden.rs` | test | Golden values invalidated; regenerate golden |
| `crates/ml-alpha/tests/heads_block_diagonal.rs` | test | Audit MAX_BUCKET_DIM dependencies |
| `crates/ml-alpha/tests/heads_bit_equiv.rs` | test | Audit (uses N_HORIZONS, should be OK) |
| `crates/ml-alpha/tests/backward_finite_diff.rs` | test | Audit (uses N_HORIZONS, should be OK) |
| `crates/ml-backtesting/src/harness.rs:36` | source | Local `const N_HORIZONS: usize = 5` → `3` |
| `crates/ml-backtesting/src/artifacts.rs` | source | Audit (likely uses harness's N_HORIZONS) |
| `crates/ml-backtesting/src/lob/mod.rs` | source | Audit |
| `crates/ml-backtesting/src/policy/mod.rs` | source | Audit |
| `crates/ml-backtesting/src/sim/mod.rs` | source | Audit |
| `crates/ml-backtesting/cuda/decision_policy.cu` | kernel | Audit `N_HORIZONS` define source |
| `crates/ml-backtesting/tests/decision_floor_coldstart.rs` | test | Audit |
| `crates/ml-backtesting/tests/stop_controller.rs` | test | Audit (recently modified by D1) |
| `crates/ml-backtesting/tests/trainer_parity.rs` | test | Audit |
| `infra/k8s/argo/alpha-perception-template.yaml` | yaml | Audit script body for `h30/h100/h300/h1000/h6000` log strings; update or generalize |
| `infra/k8s/argo/alpha-cv-template.yaml` | yaml | Audit |

---

## Task breakdown

### Task 1: Verify partial in-flight changes are coherent

**Files:**
- Read: `crates/ml-alpha/src/heads.rs`
- Read: `crates/ml-alpha/src/cfc/bucket_routing.rs`
- Read: `crates/ml-alpha/tests/perception_overfit.rs`
- Read: `crates/ml-alpha/examples/alpha_train.rs`

- [ ] **Step 1: Run cargo check on library**

```
SQLX_OFFLINE=true cargo check -p ml-alpha
```
Expected: PASS (constants already changed, library should compile).

- [ ] **Step 2: Run cargo check on all targets to scope remaining errors**

```
SQLX_OFFLINE=true cargo check -p ml-alpha --all-targets 2>&1 | grep "error\[" | sort -u | head -30
```
Expected: error list confirms scope per the File structure table above.

- [ ] **Step 3: If library doesn't compile, STOP and investigate.** Library compile failure means the in-flight changes have a fundamental error — must be diagnosed before proceeding.

### Task 2a (INSERTED 2026-05-22): Migrate loader.rs to N_HORIZONS

**Discovered during execution**: `crates/ml-alpha/src/data/loader.rs` has 5 hardcoded `[T; 5]` array types that must migrate FIRST before alpha_train.rs can compile. Also: `ml-backtesting/src/sim/mod.rs:1318` (`broadcast_alpha(&[f32; 5])`) and `ml-backtesting/src/harness.rs:509` (`[&str; 5]` log labels).

**Files:**
- Modify: `crates/ml-alpha/src/data/loader.rs:118, 159, 170, 231, 364`
- Modify: `crates/ml-backtesting/src/sim/mod.rs:1318` (and any callers)
- Modify: `crates/ml-backtesting/src/harness.rs:509` (5-string log array)

- [ ] **Step 1: Migrate loader struct fields**

Line 118:
```rust
pub horizons: [usize; 5],
```
→ Replace with:
```rust
pub horizons: [usize; crate::heads::N_HORIZONS],
```

Lines 159, 170: same pattern with `[Vec<f32>; 5]` → `[Vec<f32>; crate::heads::N_HORIZONS]`.

Lines 231, 364: `Default::default()` already returns the correct type once the field type changes; no edit needed if it inferred the type.

- [ ] **Step 2: Migrate ml-backtesting sim broadcast_alpha**

Line 1318:
```rust
pub fn broadcast_alpha(&mut self, probs: &[f32; 5]) -> Result<()> {
```
→ Replace with `[f32; N_HORIZONS]` (using ml-backtesting's local N_HORIZONS constant from harness.rs).

Audit callers — they may construct `[f32; 5]` literals that cascade.

- [ ] **Step 3: Migrate harness log labels**

Line 509:
```rust
let horizons: [&str; 5] = ["30", "100", "300", "1000", "6000"];
```
→ Replace with:
```rust
let horizons: [&str; N_HORIZONS] = ["10", "100", "1000"];
```

- [ ] **Step 4: cargo check both crates**

```
SQLX_OFFLINE=true cargo check -p ml-alpha -p ml-backtesting
```
Expected: PASS.

- [ ] **Step 5: Commit checkpoint** (no test changes yet, library + sim only)

### Task 2: Fix alpha_train.rs hardcoded 5-arrays

**Files:**
- Modify: `crates/ml-alpha/examples/alpha_train.rs:465-470, 525-540, 678`

- [ ] **Step 1: Replace 5-element array literals with `std::array::from_fn`**

Lines 465-470 currently:
```rust
let row = [
    seq.labels[0][k], seq.labels[1][k], seq.labels[2][k],
    seq.labels[3][k], seq.labels[4][k],
];
```
Replace with:
```rust
let row: [f32; N_HORIZONS] = std::array::from_fn(|h| seq.labels[h][k]);
```

- [ ] **Step 2: Apply same pattern to lines 525-528 (`row`) and 534-537 (`last_labels`)**

Use `std::array::from_fn` consistently.

- [ ] **Step 3: Audit log lines for per-horizon AUC**

Lines emitting `auc_h30=..., auc_h6000=...` need to become `auc_h10=..., auc_h1000=...`. Search for `auc_h` in alpha_train.rs and update all 5-named fields to 3-named.

- [ ] **Step 4: cargo check -p ml-alpha --example alpha_train**

```
SQLX_OFFLINE=true cargo check -p ml-alpha --example alpha_train
```
Expected: PASS.

- [ ] **Step 5: Commit checkpoint**

```
git add crates/ml-alpha/src/heads.rs crates/ml-alpha/src/cfc/bucket_routing.rs crates/ml-alpha/examples/alpha_train.rs
git commit -m "refactor(per-horizon): N_HORIZONS 5→3, HORIZONS=[10,100,1000] — Phase 1 (lib+example)"
```

### Task 3: Fix perception_overfit.rs cascading types

**Files:**
- Modify: `crates/ml-alpha/tests/perception_overfit.rs`

- [ ] **Step 1: Re-run cargo check on this test**

```
SQLX_OFFLINE=true cargo check -p ml-alpha --test perception_overfit 2>&1 | grep "error\[" | head -20
```

- [ ] **Step 2: Fix any remaining `Vec<[f32; 5]>` or `Vec<&[[f32; 5]]>` type signatures**

Replace `[f32; 5]` → `[f32; N_HORIZONS]`, ensure import of N_HORIZONS at top.

- [ ] **Step 3: cargo build the test**

```
SQLX_OFFLINE=true cargo build -p ml-alpha --tests --test perception_overfit
```
Expected: PASS.

### Task 4: Semantic rewrite of smoothness_lambda_controller_invariants.rs

**Files:**
- Modify: `crates/ml-alpha/tests/smoothness_lambda_controller_invariants.rs`

This test encodes specific h6000 behavior that no longer exists. The test fixtures must be re-derived for the [10, 100, 1000] horizon set.

- [ ] **Step 1: Rewrite the 3 hardcoded 5-element arrays**

Line 201:
```rust
let raw = [0.5_f32, 0.15, 0.05, 0.015, 0.0025];
```
Re-derive for 3 horizons preserving geometric decay matching horizon ratios. For HORIZONS=[10, 100, 1000] (geometric ratio 10):
```rust
let raw = [0.5_f32, 0.05, 0.005];
```

Line 219:
```rust
let ema = [0.5_f32, 0.15, 0.05, 0.015, 0.0025];
```
Replace with:
```rust
let ema = [0.5_f32, 0.05, 0.005];
```

Line 233 (excess test):
```rust
let ema = [0.5_f32, 0.15, 0.05, 0.015, 0.025];  // h6000 = 10× target
```
Re-derive for h1000 = 10× target. With expected h1000 target = ema[h10] × 10/1000 = 0.005:
```rust
let ema = [0.5_f32, 0.05, 0.05];  // h1000 = 0.05 = 10× target (0.005)
```

- [ ] **Step 2: Rename the test function from `excess_at_h6000_lifts_lambda_proportionally` to `excess_at_h1000_lifts_lambda_proportionally`**

- [ ] **Step 3: Update assertion indices (h6000 was index 4; h1000 is now index 2)**

In the test body, replace `lambda[4]` (h6000) with `lambda[2]` (h1000). Update the comments referencing `h6000` to `h1000`.

- [ ] **Step 4: Update assertions on h300/h1000 (indices 2, 3) — those horizons no longer exist**

The test asserted `lambda[1], lambda[2], lambda[3]` all at base. With 3 horizons, only `lambda[1]` (h100) needs to stay at base; the `lambda[2]` is now h1000 (the excess case).

- [ ] **Step 5: cargo test the test**

```
SQLX_OFFLINE=true cargo test -p ml-alpha --test smoothness_lambda_controller_invariants
```
Expected: 3+ tests pass.

### Task 5: CUDA kernel `#define` updates

**Files:**
- Modify: `crates/ml-alpha/cuda/horizon_lambda.cu:36`
- Modify: `crates/ml-alpha/cuda/bce_loss_multi_horizon.cu:35`

- [ ] **Step 1: Update horizon_lambda.cu**

Line 36:
```cuda
#define N_HORIZONS_LAMBDA 5
```
Replace with:
```cuda
#define N_HORIZONS_LAMBDA 3
```

- [ ] **Step 2: Update bce_loss_multi_horizon.cu**

Line 35:
```cuda
#define BCS_N_HORIZONS 5
```
Replace with:
```cuda
#define BCS_N_HORIZONS 3
```

Verify the comment on line 28 ("Hard-coded N_HORIZONS_BCE = 5 → per-warp 5-element accumulator stays in registers") is updated to reflect 3.

- [ ] **Step 3: Audit other kernels for hardcoded 5**

```
grep -nE "^\s*for\s*\(\s*int\s+h\s*=\s*0;\s*h\s*<\s*5\b|HORIZONS\s*=\s*5\b" crates/ml-alpha/cuda/*.cu
```
Expected: no hits (all loops should iterate to `N_HORIZONS` constant from cfc/bucket_routing.rs equivalent at the kernel level).

- [ ] **Step 4: cargo build (compiles cubins)**

```
SQLX_OFFLINE=true cargo build -p ml-alpha
```
Expected: PASS. Build script recompiles cubins.

### Task 6: ml-backtesting N_HORIZONS update

**Files:**
- Modify: `crates/ml-backtesting/src/harness.rs:36`
- Audit: `crates/ml-backtesting/src/{artifacts,lob,policy,sim}/**`
- Audit: `crates/ml-backtesting/cuda/decision_policy.cu`
- Audit: `crates/ml-backtesting/tests/*.rs`

- [ ] **Step 1: Update the local constant**

Line 36:
```rust
const N_HORIZONS: usize = 5;
```
Replace with:
```rust
const N_HORIZONS: usize = 3;
```

- [ ] **Step 2: Audit for hardcoded 5 in ml-backtesting**

```
grep -rn "; 5\]\|, 5\b\|\[f32; 5\]\|\[0\.0; 5\]\|\[1\.0; 5\]" crates/ml-backtesting/ --include="*.rs"
```
Update any matches to use the local `N_HORIZONS`.

- [ ] **Step 3: Check decision_policy.cu for N_HORIZONS source**

```
grep -n "N_HORIZONS\|#define.*HORIZONS" crates/ml-backtesting/cuda/decision_policy.cu | head -5
```
If `N_HORIZONS` is defined via `lob_state.cuh`, update there. If hardcoded, replace with 3.

- [ ] **Step 4: cargo check -p ml-backtesting**

```
SQLX_OFFLINE=true cargo check -p ml-backtesting --all-targets
```
Expected: PASS (or list of remaining errors to fix iteratively).

### Task 7: Argo workflow templates audit

**Files:**
- Audit: `infra/k8s/argo/alpha-perception-template.yaml`
- Audit: `infra/k8s/argo/alpha-cv-template.yaml`

- [ ] **Step 1: Find per-horizon log-name references in the workflow script body**

```
grep -nE "h30|h100|h300|h1000|h6000|auc_h|final_smooth_loss" infra/k8s/argo/alpha-*.yaml
```
Update any explicit `auc_h6000=` or similar logging-name string to match the new horizons.

- [ ] **Step 2: Audit summary.json parsing if any**

If the workflow parses `alpha_train_summary.json` and reads a 5-element array, update to read 3 elements.

- [ ] **Step 3: NO kubectl apply yet** — the YAML changes are committed but not deployed until validation completes.

### Task 8: Audit + fix remaining test files

**Files:**
- Audit: `crates/ml-alpha/tests/bucket_transition_kernels.rs`
- Audit: `crates/ml-alpha/tests/perception_forward_golden.rs`
- Audit: `crates/ml-alpha/tests/forward_step_golden.rs`
- Audit: `crates/ml-alpha/tests/heads_block_diagonal.rs`
- Audit: `crates/ml-alpha/tests/heads_bit_equiv.rs`
- Audit: `crates/ml-alpha/tests/backward_finite_diff.rs`
- Audit: `crates/ml-alpha/tests/cfc_step_per_branch.rs`
- Audit: `crates/ml-alpha/tests/forward_graph_capture.rs`

- [ ] **Step 1: cargo check all tests**

```
SQLX_OFFLINE=true cargo check -p ml-alpha --tests 2>&1 | grep "error" | head -30
```
Iterate through any remaining type errors. Most should be 5-array literals or golden values.

- [ ] **Step 2: Golden value invalidation**

`perception_forward_golden.rs` + `forward_step_golden.rs` have golden output values that depend on N_HORIZONS. These golden tests will FAIL on real values even when types match.

Two options:
(a) **Regenerate goldens** — run the forward pass on the new architecture, capture outputs, replace golden constants. Requires running the test once to capture, then committing the new values.
(b) **Mark `#[ignore]` with rationale** — temporarily skip until the architecture stabilizes. Per `feedback_no_hiding`, this is NOT preferred — but acceptable IF the test is followed by a `// REGENERATE after N_HORIZONS rebase` comment AND a separate task to actually regenerate before the next merge.

Recommend (a) — regenerate. The new golden values are the new ground truth.

- [ ] **Step 3: cargo test (local CPU-side tests only — skip CUDA on RTX 3050)**

```
SQLX_OFFLINE=true cargo test -p ml-alpha --lib
```
Expected: 33 tests pass (unchanged from baseline).

### Task 9: Final local validation

- [ ] **Step 1: Full workspace check**

```
SQLX_OFFLINE=true cargo check --workspace --all-targets
```
Expected: clean.

- [ ] **Step 2: ml-alpha + ml-backtesting lib tests**

```
SQLX_OFFLINE=true cargo test -p ml-alpha --lib
SQLX_OFFLINE=true cargo test -p ml-backtesting --lib
```
Expected: both pass.

- [ ] **Step 3: RTX 3050 synthetic-overfit smoke**

```
SQLX_OFFLINE=true cargo test -p ml-alpha --test perception_overfit --release stacked_trainer -- --test-threads=1 --nocapture
```
Expected: 4 tests pass, loss shrinks from ~2.3 → ~0 within budget. Confirms the new 3-horizon architecture trains end-to-end on local GPU.

- [ ] **Step 4: Atomic commit if not already done**

If Tasks 2-8 each committed separately, fine. If they accumulated, commit now:
```
git add -A
git commit -m "refactor(per-horizon): N_HORIZONS 5→3, HORIZONS=[10,100,1000] — full propagation"
```

### Task 10: Push + dispatch Argo Smoke 1

- [ ] **Step 1: git push**

```
git push origin ml-alpha-phase-a
```

- [ ] **Step 2: Apply updated workflow template**

```
kubectl apply -f infra/k8s/argo/alpha-perception-template.yaml
```
(Per `feedback_argo_template_must_apply.md` — `argo submit --from=wftmpl/<name>` reads cluster CRD, not on-disk YAML. Must apply first.)

- [ ] **Step 3: Dispatch Argo Smoke 1**

```
./scripts/argo-alpha-perception.sh --watch
```
~25-30 min wallclock on L40S.

**Falsification gate**: best `auc_h1000` (the new longest horizon) should reach **≥ 0.65** by epoch 4. If it collapses to chance (~0.50) as h6000 did with the heads_w1 mask, the 3-horizon architecture has its own failure mode and the plan is wrong.

### Task 11: Dispatch Argo Smoke 2 (anti-calibration validation)

- [ ] **Step 1: Wait for Smoke 1 checkpoint**

Path: `/feature-cache/alpha-perception-runs/<sha>/trunk_best_h1000.bin`
(Note: file name changes from `trunk_best_h6000.bin` to `trunk_best_h1000.bin` because the best-checkpoint metric is now the longest-horizon AUC.)

- [ ] **Step 2: Update sweep YAML to point at new checkpoint**

Update `config/ml/sweep_smoke_perhoriz_cfc.yaml` (or new equivalent) `checkpoint:` field to the new SHA's path.

- [ ] **Step 3: Dispatch Argo lob-backtest-sweep**

```
./scripts/argo-lob-sweep.sh --config config/ml/sweep_smoke_perhoriz_cfc.yaml --watch
```

**Validation gates**:
- `outcome_by_entry_conv` table: NO LONGER monotonically anti-calibrated. High-conviction mean_pnl_pu should be ≥ mid-conviction mean_pnl_pu (or both close to zero — D1 fix may suppress high-conviction trades entirely when signed EMA → 0).
- CRT.diag per-horizon: 3 horizons reported (h10, h100, h1000) instead of 5. mean_run_len ratio h1000/h10 — informational only (the per-horizon-mean_run_len-differentiation thesis is no longer the architectural goal; if it shows up naturally, bonus).

### Task 12: Memory pearls

- [ ] **Step 1: Write pearls capturing this session's findings**

Five new pearls to add (each as a single file under `/home/jgrusewski/.claude/projects/-home-jgrusewski-Work-foxhunt/memory/`):

1. `pearl_input_acf_grounds_horizon_choice.md` — derive HORIZONS from empirical ES MBP-10 autocorrelation, not from intuition. C1 finding: hardcoded [30,100,300,1000,6000] had 3 horizons below noise floor.

2. `pearl_binarization_preserves_smoothness.md` — D3 finding: the "BCE binarization destroys cross-horizon smoothness" intuition is empirically wrong. Sign-of-log-return preserves ~88% of available cross-horizon Pearson correlation under bivariate Gaussian assumption. If smoothness matters, it must come from somewhere other than the output formulation.

3. `pearl_conviction_must_match_action_signal.md` — D1 finding: reporting `EMA(|x|)` while sizing via `sign(instantaneous) × EMA(|x|)` produces anti-calibrated outputs. Conviction reporting must use the same smoothed signal the action uses.

4. `pearl_block_diagonal_mask_starves_long_horizon.md` — the heads_w1 block-diagonal mask catastrophically regressed h6000 AUC (0.73 → 0.54). Long-horizon prediction NEEDS cross-channel feature integration; structurally restricting it to bucket-assigned channels removes the integrative capacity.

5. `pearl_loader_ema_bandwidth_assumes_snapshot_rate.md` — D2 finding: EMA constants derived from "9s @ 250ms" assumed 250ms/snapshot, but empirical median Δt is 74µs. The actual half-lives were 3000× shorter than documented. ANY constant derived from a wall-clock target needs the actual event rate as input.

- [ ] **Step 2: Update MEMORY.md index**

Add 5 one-line entries to the index, ordered under appropriate sections.

## Validation summary

After Task 11 completes, the success criteria are:

| Gate | Pass condition |
|---|---|
| Library compile | `cargo check --workspace --all-targets` clean |
| Library tests | `cargo test -p ml-alpha --lib` 33 passed |
| Local GPU smoke | perception_overfit 4/4 pass, loss → ~0 |
| Argo Smoke 1 | `auc_h1000 ≥ 0.65` at best epoch |
| Argo Smoke 2 anti-calibration | high-conviction `mean_pnl_pu ≥ mid-conviction mean_pnl_pu` |
| Argo Smoke 2 differentiation | informational only (not a gate) |

## Risks

| Risk | Likelihood | Mitigation |
|---|---|---|
| MAX_BUCKET_DIM=96 wastes GPU memory (was 28) | Medium | Profile after smoke; if memory-bound, drop to 64 or implement runtime sizing |
| h1000 collapses to chance like h6000 did with mask | Medium | Falsification gate at Task 10 Step 3. If hit, the 3-horizon architecture itself is wrong; pivot to architecture C or D from earlier brainstorm. |
| Anti-calibration persists despite D1 fix | Medium | Suggests deeper issue than EMA mechanism; consider Candidate B (hold-duration prediction) as next architecture step |
| Golden value regeneration introduces subtle bug | Low | Diff old vs new golden; sanity-check distributional properties |
| Old checkpoints can't load into new model | Certain | Accepted — clean break, per `feedback_no_legacy_aliases` |
| Smoothness controller test re-derivation has bugs | Medium | Test invariants (boundedness, monotonicity) not specific values per `pearl_tests_must_prove_not_lock_observations` |

## Execution

After plan saved: dispatch to a coder subagent in the current worktree with this plan as the brief, OR use the subagent-driven-development skill for fresh-subagent-per-task execution with review checkpoints.
