Under B-7 (clamp default-disabled), per-transition rewards in
bellman_target_projection / bellman_fused_select_project can exceed the
adaptive atom support [V_MIN_eff, V_MAX_eff]; each t_z overshoot is
silently clamped before being mapped onto the discrete support. B-9
publishes the per-step saturation rate + pre-clamp t_z extremes so we
can decide if the atom span has become a bottleneck — without
re-attempting the reverted Fix F atom-widening (pearl_c51_v_max_freeze
_required_for_surfer).
Changes:
- 4 new ISV slots (726-729): top/bot saturation rate + max/min pre-proj.
- bellman_target_projection.cu: both entry points (bellman_target_projection
AND bellman_fused_select_project per feedback_no_partial_refactor) gain
4 new [B] f32 pointer params; thread-0 sequential reduction over
Q_N_ATOMS=21 (odd count rules out symmetric tree-reduce — matches the
kernel's existing softmax max/sum pattern at lines 157/171).
- New cross-batch reducer cuda/rl_bellman_target_saturation_reduce.cu:
single block, grid-stride gather + power-of-2 tree reduce, no atomicAdd
per feedback_no_atomicadd.
- dqn.rs: load reducer cubin, add saturation_reduce_fn handle,
launch_saturation_reduce method, 4 scratch pointer params on both
bellman methods.
- integrated.rs: allocate 4 [B] f32 scratch buffers; pass through both
fused_select_and_project_bellman call sites + launch reducer after each.
4 new bootstrap entries (213 → 217 fixed-size array).
- build.rs: register new kernel.
- 4 new diag leaves under risk_stack.atom_calibration.target_*. Comment
distinguishes them from popart.max_abs_reward_ema (different signal:
Bellman target = r + γ·atom_value, can exceed reward by γ·V_MAX_eff).
- EXPECTED_LEAVES 653 → 657.
- tests/c51_atom_saturation_diagnostic.rs: GPU-oracle test asserts
4 invariants over 249 rows — rates in [0,1], max≥min, rate>0 ⇒
overshoot exists, top+bot ≤ 1.
Validation:
- 200+50 b=16 fold-1 smoke clean. Locally V_MAX_eff adapts to ~19.3
(atom support is ISV-driven via rl_atom_support_update), so saturation
is 0% in the smoke; both diag leaves emit + invariants hold.
- popart_disaggregation_invariants: still passes (249 rows, identity).
- eval_diag_emission: train = eval = 657 leaves.
- Determinism preserved: kernel adds shared-mem reduction over per-thread
t_z values that were already computed; target_dist output unchanged.
Spec: docs/superpowers/specs/2026-06-01-b9-c51-atom-saturation-diagnostic.md
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>