# SP9 — Kelly Cold-Start Warmup Floor: Fully ISV-Driven

**Date:** 2026-05-03
**Status:** Draft (user review pending)
**Authors:** session-1777830566225
**Related pearls:**
- `pearl_controller_anchors_isv_driven.md` (anchors must be ISV-driven, no regime-encoded constants)
- `pearl_cold_start_exit_signal_or.md` (cold-start exits need OR'd signals across statistical / behavioral / temporal axes)
- `pearl_kelly_cap_signal_driven_floors.md` (existing Kelly cross-fold-persistent storage at ISV[280..286])

## 1. Overview

Replace the Kelly cap's hardcoded `warmup_floor` and binary cold-start gate
with a fully signal-driven adaptive floor whose value AND release condition
are both derived from observed system signals via the ISV bus. No
regime-encoded constants survive except Invariant-1 numerical anchors
(clamp ranges, EPS_DIV).

This is the third link in the Fix 33→Fix 36→SP9 chain: each fix unblocks
the next-level controller. SP7 fixed loss-balance saturation; Fix 34 woke
IQN-aux branches; Fix 35 fixed CQL formula direction; Fix 36 made the cap
ISV-driven; SP9 now does the same for the Kelly cold-start floor that
unmasked itself when training intent rose to Full=0.47 but eval pinned to
Quarter=1.00 (T10 wsnc6 ep1-3, 2026-05-03).

## 2. Pathology summary

T10 train-multi-seed-wsnc6 ep1-3 evidence (commit `e0dbae3c9`):

```
ep1: intent_dist [iq=0.30 ih=0.27 if=0.43]   eval_dist [eq=0.92 eh=0.07 ef=0.01]   kelly_f=0.000
ep2: intent_dist [iq=0.48 ih=0.25 if=0.27]   eval_dist [eq=1.00 eh=0.00 ef=0.00]   kelly_f=0.000
ep3: intent_dist [iq=0.28 ih=0.25 if=0.47]   eval_dist [eq=1.00 eh=0.00 ef=0.00]   kelly_f=0.000

val: trade_count=1 in 214,654 bars, active_frac=0.0, dir_entropy=0.0, sharpe=0.0
```

Training-side intent diverges sharply from eval-side realization. The Kelly
cap forces eval magnitude to Quarter (smallest) because `kelly_f=0` and the
existing warmup_floor's release condition (single-axis statistical) creates
a chicken-and-egg loop: cap closed → no trades → no Kelly samples → cap
stays closed.

## 3. Architecture

### 3.1 Final Kelly cap formula

```
final_kelly_f = max(measured_kelly_f, warmup_floor)
```

`measured_kelly_f` is the existing computed value at ISV[KELLY_F_SMOOTH_INDEX=280].

`warmup_floor` is new — a signal-driven slot that decays as the model matures.

### 3.2 Warmup floor formula

```
warmup_floor = base_floor * (1 - combined_confidence)

base_floor = clamp(q_var_mag / max(q_var_mag_ema, EPS_DIV),  KELLY_FLOOR_MIN_RATIO,  KELLY_FLOOR_MAX_RATIO)

combined_confidence = max(statistical_conf, behavioral_conf, temporal_conf)

statistical_conf = clamp(kelly_sample_count / sample_count_target, 0, 1)
behavioral_conf  = clamp(1 - (intent_eval_divergence / divergence_target), 0, 1)
temporal_conf    = clamp(epoch_idx_in_fold / temporal_target, 0, 1)
```

Where:
- `q_var_mag = ISV[FLATNESS_BASE+1]` — already produced by `pearl_2_budget_kernel`
- `q_var_mag_ema` — new ISV slot, EMA-tracked via Pearl D
- `kelly_sample_count = ISV[KELLY_SAMPLE_COUNT_INDEX=283]` — already exists
- `intent_eval_divergence` — new ISV slot, computed from `intent_dist_f / eval_dist_f` ratio
- `epoch_idx_in_fold = ISV[EPOCH_IDX_INDEX=39]` — already exists
- `sample_count_target`, `divergence_target`, `temporal_target` — new EMA-tracked ISV slots, themselves Pearl D-driven from observation history

### 3.3 OR semantics (per pearl_cold_start_exit_signal_or)

Each `*_conf` is a `[0, 1]` confidence. `combined_confidence = max(...)` ensures
ANY signal firing exits cold-start. This is OR semantics:
- All three cold (early training) → confidence ≈ 0 → warmup_floor = base_floor
- Any one hot → confidence rises → warmup_floor decays → measured Kelly dominates
- Robust to all three failure modes from the pearl

### 3.4 Signal-driven base_floor (per pearl_controller_anchors_isv_driven)

`base_floor` itself is signal-driven from q_var_mag self-relative ratio.
When mag-Q variance is above its own historical EMA (model is learning to
distinguish magnitudes), base_floor opens up. When below, base_floor stays
conservative. This eliminates the `0.5` half-Kelly hardcoded constant in
favor of a self-relative measurement.

`KELLY_FLOOR_MIN_RATIO=0.25` and `KELLY_FLOOR_MAX_RATIO=1.0` are the only
remaining constants — they are Invariant-1 step-rate-limiters (prevent
explosion / underflow), not regime-encoded.

## 4. ISV slot allocation

New slots (after SP8's ending at SP5_SLOT_END=330):

| Slot | Purpose | Reset semantics |
|---|---|---|
| `KELLY_WARMUP_FLOOR_INDEX = 330` | Final adaptive floor value | FoldReset (sentinel 0, Pearl A bootstrap) |
| `Q_VAR_MAG_EMA_INDEX = 331` | EMA-tracked baseline for self-relative ratio | FoldReset |
| `INTENT_EVAL_DIVERGENCE_INDEX = 332` | intent_dist_f / eval_dist_f ratio | FoldReset |
| `KELLY_SAMPLE_COUNT_TARGET_INDEX = 333` | EMA-tracked sample-sufficiency threshold | FoldReset |
| `KELLY_DIVERGENCE_TARGET_INDEX = 334` | EMA-tracked behavioral-gap threshold | FoldReset |
| `KELLY_TEMPORAL_TARGET_INDEX = 335` | EMA-tracked time-since-reset threshold | FoldReset |
| `EVAL_DIST_Q_INDEX = 336` | Eval-side Quarter mag fraction | FoldReset |
| `EVAL_DIST_H_INDEX = 337` | Eval-side Half mag fraction | FoldReset |
| `EVAL_DIST_F_INDEX = 338` | Eval-side Full mag fraction | FoldReset |

Bump `SP5_SLOT_END = 339` and `ISV_TOTAL_DIM = 339`.

**Why eval_dist must move to ISV (not just be referenced):** the existing
`read_eval_intent_magnitude_distribution()` at `gpu_backtest_evaluator.rs:1353`
performs a DtoH readback of `[f32; 3]`, violating both
`feedback_no_cpu_compute_strict.md` and `feedback_no_htod_htoh_only_mapped_pinned.md`.
SP9 migrates the eval-side mag distribution to GPU-resident ISV storage,
removing the DtoH readback — a separate `feedback_no_cpu_compute_strict`
win regardless of the divergence computation's needs. The CPU consumer
at `metrics.rs:908` (HEALTH_DIAG emit) migrates to read from ISV via
mapped-pinned.

## 5. Producer kernels

### 5.1 `kelly_warmup_floor_compute_kernel.cu`

Single block, single thread. Reads:
- `q_var_mag = ISV[FLATNESS_BASE+1]`
- `q_var_mag_ema = ISV[Q_VAR_MAG_EMA_INDEX]`
- `kelly_sample_count = ISV[KELLY_SAMPLE_COUNT_INDEX]`
- `sample_count_target = ISV[KELLY_SAMPLE_COUNT_TARGET_INDEX]`
- `intent_eval_divergence = ISV[INTENT_EVAL_DIVERGENCE_INDEX]`
- `divergence_target = ISV[KELLY_DIVERGENCE_TARGET_INDEX]`
- `epoch_idx = ISV[EPOCH_IDX_INDEX]`
- `temporal_target = ISV[KELLY_TEMPORAL_TARGET_INDEX]`

Writes scratch buffer for Pearls A+D smoothing into `ISV[KELLY_WARMUP_FLOOR_INDEX]`.

### 5.2 `intent_eval_divergence_compute_kernel.cu`

Reads from monitoring summary (`action_counts[12]` + per-magnitude bins).
Computes:
```
intent_f = action_counts[Full bins] / total_action_counts  (training)
eval_f   = consume_validation_loss eval_dist_f             (val window)
divergence = max(intent_f, EPS_DIV) / max(eval_f, EPS_DIV)
```

Single block, single thread. Writes scratch for Pearl A+D into ISV[INTENT_EVAL_DIVERGENCE_INDEX].

### 5.3 EMA-target updaters (3 single-block kernels)

For each of `sample_count_target`, `divergence_target`, `temporal_target`:
- Pearl D Wiener-α-derived EMA from observed history
- Sentinel-bootstrap (Pearl A) at fold start
- Updates ISV slot directly via apply_pearls_ad chain

### 5.4 `eval_intent_dist_compute_kernel.cu`

GPU producer for `EVAL_DIST_{Q,H,F}_INDEX`. Reads from `intent_mag_buf`
(already GPU-resident in `gpu_backtest_evaluator`). Single block, 3 threads
or 1 thread + 3 reductions. Computes:
```
total = intent_mag_buf[Quarter] + intent_mag_buf[Half] + intent_mag_buf[Full]
EVAL_DIST_Q = intent_mag_buf[Quarter] / max(total, EPS_DIV)
EVAL_DIST_H = intent_mag_buf[Half]    / max(total, EPS_DIV)
EVAL_DIST_F = intent_mag_buf[Full]    / max(total, EPS_DIV)
```

Writes scratch for Pearl A+D smoothing into ISV. Replaces the existing
DtoH `read_eval_intent_magnitude_distribution()` call site at
`metrics.rs:908` — that consumer migrates to read from ISV via
mapped-pinned `read_volatile`. The DtoH function and its caller chain are
deleted in the same commit per `feedback_no_partial_refactor.md`.

## 6. Consumer integration

Kelly cap call site (need to locate via grep) currently does:
```
final_cap = max(measured_kelly_f, hardcoded_warmup_floor)
```

Replace with:
```
warmup_floor = isv[KELLY_WARMUP_FLOOR_INDEX];
final_cap = max(measured_kelly_f, warmup_floor);
```

If the call site is on host: must be moved to GPU per `feedback_no_cpu_compute_strict`. If GPU: just add the ISV read.

## 7. Tests

- Unit test: producer kernel correctness on synthetic q_var/sample/divergence inputs
- Contract test: `every_fold_and_soft_reset_entry_has_dispatch_arm` includes 6 new slots
- Layout fingerprint test: ISV_TOTAL_DIM=336 reflected in fingerprint string
- L40S 5-epoch smoke validation: post-fix run shows kelly_f rising from 0 → measured value over epochs as confidence accumulates

## 8. Risks and open questions

- **Risk 1**: `q_var_mag_ema` update could lag if EMA-α is too slow, causing base_floor to under-react to model maturity. Mitigation: Pearl D Wiener-optimal α derived from variance ratio.
- **Risk 2**: `intent_eval_divergence` requires both training-side and val-side action distributions in the same step. Validation runs only periodically; need to handle the case where val_dist hasn't been updated yet. Mitigation: use stale val_dist (Pearl A sentinel-bootstrap from first observation) until next val window.
- **Risk 3**: Cold-start defeating itself if any one signal is structurally always-zero (e.g., no validation runs in fold 0). Mitigation: temporal axis is the safety net — `epoch_idx_in_fold > temporal_target` always fires eventually.
- **Open question**: Does the Kelly cap apply only at eval, or also at training? Answer determines whether intent_eval_divergence uses training intent_dist or eval intent_dist. Confirm via grep before implementation.

## 9. Implementation phases (atomic per feedback_no_partial_refactor)

Single commit:
1. Allocate 9 new ISV slots in `sp5_isv_slots.rs` (6 for SP9 controller +
   3 for EVAL_DIST migration)
2. Bump `SP5_SLOT_END = 339` and `ISV_TOTAL_DIM = 339`
3. Add 6 new producer kernels:
   - `kelly_warmup_floor_compute_kernel.cu`
   - `intent_eval_divergence_compute_kernel.cu`
   - `eval_intent_dist_compute_kernel.cu` (GPU migration of DtoH readback)
   - 3× EMA target updaters (sample_count / divergence / temporal)
4. Register in `build.rs`
5. Rust launchers in `gpu_dqn_trainer.rs`
6. Wire launches into `training_loop.rs` at appropriate points
7. Modify Kelly cap consumer to read warmup_floor from ISV
8. **Delete** `read_eval_intent_magnitude_distribution()` DtoH function
   and migrate `metrics.rs:908` consumer to ISV mapped-pinned read
9. State reset registry: 9 FoldReset entries + dispatch arms
10. Contract test `every_fold_and_soft_reset_entry_has_dispatch_arm`
    must pass with 9 new entries
11. Audit doc entry (Fix 37) including the eval_dist GPU migration as
    a paired DtoH-elimination win
12. HEALTH_DIAG line for `kelly_warmup_floor`, `intent_eval_divergence`,
    and the 3 EMA targets (visibility, mandatory per `feedback_no_hiding`)

Total estimated: ~1000-1200 LOC across 10 files. ~2-2.5 hours focused
work; subagent-feasible with isolated worktree.

## 10. Validation criteria

After SP9 lands, expected behavior on T10 50-epoch run:
- ep1: warmup_floor ≈ 0.5 × q_var_ratio (mostly cold-start), kelly_f effective = floor
- ep3+: confidence rises (statistical or behavioral or temporal axis fires), floor decays, measured kelly_f dominates
- val_active_frac stays > 0.20 across folds
- intent_dist_f and eval_dist_f converge (gap closes)
- sharpe positive across all folds at convergence
