# SP18 Combined-Scope Wire-Up Audit > Audit captured during Phase 1 Task 1.1 of `docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md`. > > **Branch**: `feat/sp18-combined` @ `0b737ec30` (Phase 0 baseline, pre-Phase-1 atomic deletion). > > **Audit script**: `scripts/audit_sp18_consumers.sh`. Re-run after every commit that touches the SP13/SP16 chain (D-leg) or the TD(λ) `q_next` site (B-leg). Pre-commit hook (Task 1.2) enforces snapshot-vs-current diff via `--check`. ## Outcome of Phase 1 Task 1.1 (audit-first, halt-on-drift) The pre-dispatch consumer-audit run surfaced **multiple consumers NOT in the plan's locked checklist** (spec §"Architecture Changes"). Per task input — *"If your audit surfaces ANY additional consumer, halt and report — do NOT proceed with deletion ad-hoc"* — Phase 1 Tasks 1.3-1.5 (atomic deletion) are **HALTED** pending plan / spec amendment. ### Plan's locked D-leg checklist (8 entries) | # | Site | Action | |---|------|--------| | 1 | `experience_kernels.cu:3162-3166, 3637-3643, 3710-3716` (3 cost sites) | DELETE — replace with placeholder comment per Task 1.5 | | 2 | `training_loop.rs:564` (launcher call) + `:631-667` (hold_cost_scale_diag emit) + `:4171-4226` (host controller block) | DELETE launcher + diag emit + controller; RETAIN slot-382 min_hold_temp_diag emit at `:567-617` | | 3 | `crates/ml/src/cuda_pipeline/hold_cost_scale_update_kernel.cu` | DELETE entire file | | 4 | `crates/ml/src/cuda_pipeline/sp14_isv_slots.rs` slots [461..468) (`HOLD_COST_SCALE_INDEX` + `HCS_*` Welford) + their `MIN`/`MAX`/`SENTINEL` constants | DELETE constants + the layout-locked tests for them | | 5 | `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs` `launch_hold_cost_scale_update` method (line 15837) + `HOLD_COST_SCALE_UPDATE_CUBIN` static (line 2257) + struct field `hold_cost_scale_update_ops` (line 7853) + constructor wiring (lines 24433-24434, 25296) | DELETE all | | 6 | `crates/ml/src/trainers/dqn/state_reset_registry.rs` 7 entries (`sp16_phase2_hold_cost_scale_adaptive` + `sp16_t3_hcs_*` × 6) | DELETE entries | | 7 | `crates/ml/build.rs:881` cubin manifest entry `hold_cost_scale_update_kernel.cu` | DELETE | | 8 | `crates/ml/src/cuda_pipeline/state_layout.cuh:214` (`ISV_HOLD_COST_SCALE_IDX`) + `:228-233` (`ISV_HCS_*` mirror) | DELETE | ### Additional consumers surfaced by audit (NOT in plan checklist) The following consumers must be added to the atomic-deletion commit. They were missed by the plan's spec §"Architecture Changes" and would have left the deletion partial — per `feedback_no_partial_refactor.md` this would block compile or, worse, leave dangling references that compile but break test/integration paths. | # | Site | Type | Resolution | |---|------|------|------------| | **A1** | `crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs:1240-1323` — `HoldCostScaleUpdateOps` struct + `impl` (kernel-load + launcher) | **PRODUCER WRAPPER** (Rust-side). The plan said the launcher is in `gpu_dqn_trainer.rs` but actually only the trainer's *thin call site* `launch_hold_cost_scale_update` is there; the real `HoldCostScaleUpdateOps::new`/`launch` impl lives in `gpu_aux_trunk.rs`. | DELETE the struct + impl block (84 lines). DELETE the `HOLD_COST_SCALE_UPDATE_CUBIN` import at `gpu_aux_trunk.rs:56` (transitively unused after the struct is gone). | | **A2** | `crates/ml/tests/sp14_oracle_tests.rs:2174-2920` — 11 GPU oracle tests + the doc-block at the head | **TESTS** (75 occurrences of HOLD_COST_SCALE / HCS_TARGET / hold_cost_scale_update across this file). Tests: `sp16_phase2_hold_cost_scale_climbs_with_overrun`, `sp16_phase2_hold_cost_scale_at_target_is_one`, `sp16_phase2_hold_cost_scale_under_target_is_one`, `sp16_phase2_hold_cost_scale_bounds_clamp`, `sp16_phase2_hold_cost_scale_pearl_a_bootstrap`, `sp16_phase3_alpha_high_during_signal_jump`, `sp16_phase3_alpha_low_in_steady_state`, `sp16_phase3_pearl_a_bootstrap_first_obs`, `sp16_phase3_alpha_naturally_bounded`, `sp16_phase3_no_hardcoded_alpha`, plus the helper `recover_alpha`/`compute_target_scale`/`run_synthetic_sequence` infrastructure (~750 lines). All exercise the now-deleted `hold_cost_scale_update` kernel via `include_bytes!(.../hold_cost_scale_update_kernel.cubin)` — without the kernel they fail to compile. | DELETE the 2 test modules (`sp16_phase2_hold_cost_scale_gpu` + `sp16_t3_hold_cost_scale_alpha_gpu`) and the standalone `sp16_phase3_no_hardcoded_alpha` test. ~750 lines from `sp14_oracle_tests.rs:2174-2920`. | | **A3** | `crates/ml/src/trainers/dqn/trainer/training_loop.rs:8971-9043` — 7 dispatch arms in `reset_named_state` (`"sp16_phase2_hold_cost_scale_adaptive"` + `"sp16_t3_hcs_*"` × 6) | **DISPATCH ARMS**. Required atomically with the registry-entry deletion (Task 1.4 expanded scope) — the contract test `every_fold_and_soft_reset_entry_has_dispatch_arm` (state_reset_registry.rs:2120-2280) fails if the registry has an entry but no dispatch arm OR vice versa. | DELETE the 7 match arms (slot 461 + HCS slots 462-467) atomically with the registry entries. | | **A4** | `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs:23200-23216` — constructor block writing `HOLD_COST_BASE` to `HOLD_COST_INDEX=380` + `HOLD_RATE_TARGET_DEFAULT` to slot 381 | **CONSTRUCTOR WRITE** of slot 380 (controller output anchor). Per DD7c, slot 380 is RETAINED as part of the observation chain — but it now has no producer. The constructor-write of `HOLD_COST_BASE` keeps it at 0.005 cold-start. After deletion of the host controller, slot 380 becomes a **read-only diagnostic slot** (constructor-init only, no per-step rewrites). | KEEP the constructor-write (DD7c retains slot 380). UPDATE the comment block at lines 23201-23210 to reflect the post-SP18-Phase-1 reality: "After SP18 Phase 1, slot 380 is no longer rewritten by a host-side controller (deleted per DD7=c). The constructor-init is now its only assignment — it serves as a read-only diagnostic anchor surfacing the static base cost. Phase 2 lands the structural Hold opportunity-cost reward at the 3 deleted cost-subtraction sites; that reward does NOT consume slot 380." | | **A5** | `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs:617, 2245-2256` — doc-block prose referring to deleted machinery | **DOC PROSE**. References `launch_hold_cost_scale_update`, `gpu_aux_trunk::HoldCostScaleUpdateOps`, `isv[461]`, etc. | UPDATE comments to remove deleted-machinery references; replace with "(deleted SP18 Phase 1, DD7=c — see docs/sp18-wireup-audit.md)" pointer. No code change. | | **A6** | `crates/ml/src/cuda_pipeline/sp13_isv_slots.rs:73-77` — `HOLD_COST_BASE`, `HOLD_COST_CONTROLLER_GAIN`, `HOLD_COST_FLOOR_RATIO`, `HOLD_COST_CEIL_RATIO` constants | **HOST-CONTROLLER CONSTANTS**. Used ONLY by the host controller block at `training_loop.rs:4213-4223` we're deleting. Per the SP13 v3 P0a comment block at `sp13_isv_slots.rs:50-72`, `HOLD_COST_BASE` is also used by the constructor-write at `gpu_dqn_trainer.rs:23215`. | KEEP `HOLD_COST_BASE` (consumed by constructor-init at A4 — slot 380 retained). DELETE `HOLD_COST_CONTROLLER_GAIN`, `HOLD_COST_FLOOR_RATIO`, `HOLD_COST_CEIL_RATIO` (sole consumer is the deleted controller block). | | **A7** | `crates/ml/src/cuda_pipeline/gpu_experience_collector.rs:5579-5585` — comment doc-ref to deleted controller | **COMMENT DOC-REF**. References `training_loop.rs:3604` (stale line — now `:4209`) and the priced-cost flow. | UPDATE prose to reflect post-Phase-1 reality. | | **A8** | `crates/ml/src/cuda_pipeline/sp5_isv_slots.rs:325-327` — comment doc-ref describing the controller chain | **COMMENT DOC-REF**. Documents slot 380/381/382. Slots 381 + 382 retained per DD7c; slot 380 retained but no longer controller-written. | UPDATE prose to reflect post-Phase-1 reality. | | **A9** | `crates/ml/src/cuda_pipeline/state_reset_registry.rs:1138-1271` — registry entries already marked RETIRED in PP.4 (commit c66e15f6d) for slots 461 + 462-467 | **REGISTRY ENTRIES**. Already marked RETIRED in Pre-Phase PP.4. Phase 1 promotes RETIRED-with-sentinel-0.0-write to FULL DELETION. | DELETE the 7 entries (slot 461 + HCS slots 462-467) from the registry; layout fingerprint must be updated atomically — the fingerprint seed string covers slot allocations [461..468) which become RESERVED-gap (SP14-C.1 pattern). Slots themselves stay allocated (ISV_TOTAL_DIM unchanged) to preserve checkpoint compatibility. | | **A10** | `crates/ml/src/trainers/dqn/state_reset_registry.rs:2256-2308` — `lock_sp18_v2_pp4_retired_chain` test which asserts the 7 retired entries STILL EXIST | **CONTRACT TEST**. PP.4 added a lock test asserting the entries exist with sentinel 0.0. After full deletion, this test must be deleted (the entries no longer exist, so asserting their existence panics). | DELETE the `lock_sp18_v2_pp4_retired_chain` test (or rewrite to assert the slots are NOT registered). | ### Out-of-scope mentions (do NOT delete; verify only) These show up in the audit grep but per DD7c are RETAINED for HEALTH_DIAG continuity OR are independent modules: | Site | Reason RETAINED | |------|----------------| | `crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu` | Producer of slot 382 (observation chain). DD7c retains entire observation chain. | | `crates/ml/src/cuda_pipeline/min_hold_temperature_update_kernel.cu` | Independent SP16-P1 producer of slot 460 (different chain). Reads same slots 381+382 but writes different output. RETAINED. | | `crates/ml/src/cuda_pipeline/sp14_isv_slots.rs` MHT_* slots [468..474) | SP16-P1 (MIN_HOLD_TEMPERATURE) Welford accumulators — distinct chain. RETAINED. | | `crates/ml/src/cuda_pipeline/sp13_isv_slots.rs:43` `HOLD_COST_INDEX = 380` | Slot constant for the retained observation chain. RETAINED. | | `crates/ml/src/cuda_pipeline/state_layout.cuh:200-202` `ISV_HOLD_COST_IDX/ISV_HOLD_RATE_TARGET_IDX/ISV_HOLD_RATE_OBSERVED_EMA_IDX` (slots 380/381/382) | Mirror constants for retained observation chain. RETAINED. | | `crates/ml/src/trainers/dqn/state_reset_registry.rs:939` `sp13_hold_rate_observed_ema` registry entry for slot 382 | FoldReset entry for retained observation chain. RETAINED. | | `crates/ml/tests/sp13_phase0_oracle_tests.rs` references to `hold_rate_observer_kernel` | Tests for retained observer kernel. RETAINED. | | `docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md` and related plan docs | Historical spec/plan archaeology. NOT modified. | ## Path forward Two options: 1. **EXPAND deletion scope to cover all 10 additional consumers (A1-A10) atomically in the same Phase 1 commit.** The plan's spec §"Architecture Changes" was incomplete; this audit makes the scope explicit. The atomic commit grows from ~8 sites to ~18 sites but stays within the same conceptual change ("delete SP13/SP16 cost-application chain"). Per `feedback_no_partial_refactor`. 2. **Halt and request human review.** The plan author may have intended these consumers to be discovered iteratively (the plan says "trust the script, not the plan author"). But the script's purpose is to *surface* missed consumers BEFORE the deletion, exactly so the deletion stays atomic. Halting honours that intent; expanding the scope ad-hoc honours the deletion's atomicity. **Recommended**: Option 1, with this audit doc serving as the spec-amendment record. The 10 additional consumers all flow from the same chain — none introduce new architectural concerns. They just expand the byte-count of the atomic-deletion commit. ## Status **Phase 1 Tasks 1.3-1.6 LANDED (2026-05-09)** — atomic deletion of all 18 sites (8 plan + 10 audit-expanded A1-A10) committed in a single atomic commit per `feedback_no_partial_refactor`. The branch was placed in INTERIM STATE: 3 reward sites (`experience_kernels.cu` segment_complete + per-bar positioned-Hold + per-bar flat-Hold) were placeholder-commented but contained no Hold cost. **Phase 2 Tasks 2.1-2.5 LANDED (2026-05-09)** — atomic introduction of the `compute_sp18_hold_opportunity_cost` device function + 3-site migration + CPU/GPU oracle tests in a single commit per `feedback_no_partial_refactor`. **INTERIM STATE LIFTED.** L40S dispatch remains FORBIDDEN pending Phase 3 cap producer + Phase 4-5 B-leg, but the reward function is now structurally complete (no placeholder sites, no orphaned consumers). Per DD7c, observation chain preserved: slot 380 (HOLD_COST_INDEX, constructor- init-only diagnostic at HOLD_COST_BASE=0.005), slot 381 (HOLD_RATE_TARGET_INDEX, constructor-init at 0.20), slot 382 (HOLD_RATE_OBSERVED_EMA_INDEX, FoldReset chain still wired). MHT_* slots [468..474) preserved (MIN_HOLD_TEMPERATURE chain RETAINED). Sites deleted (atomic commit): - 3 reward subtraction sites in experience_kernels.cu (placeholder comments) - hold_cost_scale_update_kernel.cu (file deleted) - HoldCostScaleUpdateOps in gpu_aux_trunk.rs (struct + impl, ~120 lines) - launch_hold_cost_scale_update + cubin static + struct field + constructor + field-init in gpu_dqn_trainer.rs (5 sites) - HOLD_COST_SCALE_INDEX, HCS_* slots, sentinel/bounds, layout-locked tests in sp14_isv_slots.rs - HOLD_COST_CONTROLLER_GAIN/FLOOR/CEIL constants in sp13_isv_slots.rs - ISV_HOLD_COST_SCALE_IDX + ISV_HCS_* mirror constants in state_layout.cuh - build.rs cubin manifest entry - 7 fold-reset registry entries (slot 461 + HCS_* 462..468) - 7 dispatch arms in training_loop.rs - Host controller block in training_loop.rs (lines ~4171-4226) - launcher call site + hold_cost_scale_diag emit block in training_loop.rs - 11 GPU oracle tests + helpers in sp14_oracle_tests.rs (~750 lines) - lock_sp18_v2_pp4_retired_chain test (deleted, no inverse-contract replacement — pointless ceremony per user call) - Layout fingerprint seed updated (HOLD_COST_SCALE + HCS_* removed; range [461..468) is RESERVED gap; ISV_TOTAL_DIM stays at 507 per checkpoint compatibility — RESERVED-gap pattern from SP14-C.1) **Validation: cargo check --workspace clean; cargo test -p ml --lib passes (state_reset_registry::every_fold_and_soft_reset_entry_has_dispatch_arm, sp16_t3_wiener_welford_slot_layout_locked, sp18_combined_slot_layout_locked all OK).** ### Phase 2 sites introduced (atomic commit) - `crates/ml/src/cuda_pipeline/trade_physics.cuh` — new device function `compute_sp18_hold_opportunity_cost` after `compute_lump_sum_opp_cost`. Single forward-only function; reuses `compute_asymmetric_capped_pnl` for the bilateral cap. No new infrastructure beyond existing primitives. - `crates/ml/src/cuda_pipeline/experience_kernels.cu` — 3 placeholder comments replaced atomically: - Site 1 (segment_complete branch ~3143): unconditional add into `base_reward` BEFORE the SP12 asymmetric cap; preserves the SP12 cap interaction and cap participation per the original SP13 P0a comment contract. - Site 2 (per-bar positioned-Hold ~3641): unconditional add into `r_micro` (rc[3]) — same component the deleted subtraction wrote to pre-SP18, preserving SP11 reward-subsystem controller signal attribution. `vol_proxy` recomputed locally from `features[bar_idx * market_dim + 9]` (the per-bar branches are outside the segment_complete `vol_proxy` scope). - Site 3 (per-bar flat-Hold ~3760): unconditional add into `r_opp_cost` (rc[4]) — same component the deleted subtraction wrote to pre-SP18. `vol_proxy` recomputed locally as in Site 2. - `crates/ml/src/cuda_pipeline/sp18_hold_opp_test_kernel.cu` — new GPU oracle test wrapper (single-thread single-block). Wraps the device fn for the GPU oracle test; mapped-pinned f32 input/output per `feedback_no_htod_htoh_only_mapped_pinned`. - `crates/ml/build.rs` — added `sp18_hold_opp_test_kernel.cu` to the cubin manifest. - `crates/ml/tests/sp18_hold_reward_oracle_tests.rs` — added T1 (CPU oracle, 6 cases covering positive/negative/saturation/dir-gate/ shaping_scale=0) + T2 (GPU oracle parity with CPU at ε=1e-5) per spec §"Behavioral Test Predictions". ### Phase 2 design notes - **`vol_proxy` scoping**: the per-bar Hold sites (Sites 2-3) are outside the segment_complete `vol_proxy` scope (declared at `experience_kernels.cu:3124`). Each per-bar site recomputes `vol_proxy` locally from the same `features[bar_idx * market_dim + 9]` ATR-norm input the segment_complete branch and the B.2/D.4c bonus blocks consume. The `vol_proxy_pb1`/`vol_proxy_pb2` locals mirror the segment_complete computation modulo the `× sqrt(hold_time)` term (which is 1 for per-bar / hold_time=1 — matches the device fn's contract). - **ISV cap fallback**: each call site reads `ISV[483]` (POS_CAP) and `ISV[484]` (NEG_CAP) with the same sentinel-or-out-of-bounds guard used at Site 1: if the slot value is within ε of the sentinel OR outside `[POS_CAP_MIN_BOUND, POS_CAP_MAX_BOUND]`, the call falls through to the macro defaults (+5/-10). This is bit-identical to a fixed +5/-10 cap during cold-start and Pre-Phase-3 (when the producer kernel `hold_reward_cap_update_kernel` has not yet landed). - **Reward-component slot routing**: Sites 1/2/3 write to slots 0/3/4 (popart/micro/opp_cost) per the SP15 Wave 2 reward-component layout. Routing matches the deleted SP13/SP16 subtraction destinations exactly, so the SP11 reward-subsystem controller's component-weight estimator sees the same input distribution shape (modulo the structural-counterfactual vs reactive-cost magnitude shift, which is precisely the change the controller is meant to learn). - **`shaping_scale` gating**: the device fn multiplies the capped output by `shaping_scale`. Validation runs at `shaping_scale=0` → the device fn returns 0.0 → no Hold opp cost in pure backtest, matching the SP12 lump-sum `compute_lump_sum_opp_cost` contract. ### Phase 3 sites introduced (atomic commit, 2026-05-09) D-leg adaptive HOLD_REWARD_POS/NEG_CAP producer kernel + launcher + per-epoch wiring + GPU oracle tests. Lifts the Phase 2 caps from "sentinel-driven cold-start" (5.0/-10.0 fixed) to "p99(|step_ret|) over Long/Short trade closes × 1.5 safety factor" with Wiener-optimal α blend per `pearl_wiener_optimal_adaptive_alpha`. The Phase 2 consumer (`compute_sp18_hold_opportunity_cost`) sees the producer-driven slots [483]/[484] from epoch 1 onward; the sentinel branch in the consumer remains as the cold-start fallback path (zero-trade-close epoch ⇒ producer early-returns ⇒ slots stay at sentinel ⇒ consumer falls through to the +5/-10 macro defaults — bit-identical to pre-Phase-3 behavior). Sites introduced (single atomic commit per `feedback_no_partial_refactor`): - **Kernel**: `crates/ml/src/cuda_pipeline/hold_reward_cap_update_kernel.cu` (NEW, ~310 lines). Single-block 256-thread producer; mirrors the SP14 P0-A `reward_cap_update_kernel` structural template with three differences: 1. Filter: `(is_close && step_ret != 0)` — both winners AND losers (the consumer needs the magnitude scale of *all* Long/Short closes, not just winners). Position-side cap filters `(is_close && step_ret > 0)`. 2. Welford-derived Wiener-α (slots [487..493)) replaces fixed α=0.01. Floor at WELFORD_ALPHA_MIN=0.4 per `pearl_wiener_alpha_floor_for_nonstationary` — the policy-realised Long/Short close magnitude distribution is intrinsically non-stationary as the policy adapts, sharpening tails. 3. Bounds [HOLD_REWARD_POS_CAP_MIN_BOUND=0.5, HOLD_REWARD_POS_CAP_MAX_BOUND=50.0] (vs. position-side [1.0, 50.0]). - **Cubin manifest**: `crates/ml/build.rs` — added kernel entry. Build-rs cubin static `HOLD_REWARD_CAP_UPDATE_CUBIN` exposed in `gpu_dqn_trainer.rs:2190`. - **Producer wrapper**: `crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs` — new `HoldRewardCapUpdateOps` struct (mirrors `RewardCapUpdateOps`) with `new`/`launch` impl; pre-loads `CudaFunction` at construction per `pearl_no_host_branches_in_captured_graph.md`. - **Trainer wiring**: `crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs`: - Struct field `hold_reward_cap_update_ops` at line 7828. - Constructor instantiation at line 24409. - Field-init in struct literal at line 25293. - Method `launch_hold_reward_cap_update` placed right after `launch_reward_cap_update`. - **Per-epoch wire-up**: `crates/ml/src/trainers/dqn/trainer/training_loop.rs` line 511 — `fused.trainer().launch_hold_reward_cap_update(step_ret_ptr, trade_close_ptr, total_samples)` placed right after `launch_reward_cap_update` so both cap producers share the same source buffers (read-only; independent ISV slot pairs at [452]/[453] and [483]/[484]). - **HEALTH_DIAG**: `training_loop.rs` (post `td_error_pre` emit) — new line `HEALTH_DIAG[N]: hold_reward_cap [pos={:.4} neg={:.4} fire_rate={:.4}]`. Reads ISV[483]/[484]/[486] via `read_isv_signal_at`; slot 486 (HOLD_OPP_COST_FIRE_RATE_EMA) is the engagement-canary diag for Phase 8 to populate. - **GPU oracle tests**: `crates/ml/tests/sp18_hold_reward_oracle_tests.rs` — three new tests in the `mod gpu` block: - `hold_reward_cap_producer_drives_slots` — T5 from spec: 100 simulated Long/Short closes with jittered ~N(0, 0.01); verifies ISV[483] is bilaterally clamped to MIN_BOUND=0.5 (small p99 ⇒ target_cap << bound), ISV[484] = -2 × ISV[483], filler outliers (close=0) filtered out. - `hold_reward_cap_pearl_a_first_observation_replace` — Pearl-A bootstrap: 4 large closes with `step_ret = [+10, -20, +30, -40]`, target_cap = max(p99, max) × 1.5 ≈ 76.5, clamped to MAX_BOUND=50, Pearl-A REPLACE on sentinel writes 50.0 directly (NOT blended with sentinel 5.0). - `hold_reward_cap_no_closes_preserves_isv` — no closes (trade_close=0 ∀i): every ISV slot (POS, NEG, all 6 Welford) preserved bit-exactly. Verifies the cold-start fallback path. - **Audit script**: `scripts/audit_sp18_consumers.sh --check` exits 0 (no fingerprint drift in tracked sections — SP18 D-leg/B-leg legacy references unchanged; the new Phase 3 producer is in its own symbol-namespace not tracked by the audit's snapshot, which is by design — the audit tracks the DELETED SP13/SP16 legacy chain). **Validation: cargo check --workspace clean (1m 12s); 3 GPU oracle tests pass on local RTX 3050 Ti (1.93s wallclock).** ### Phase 3 design notes - **Why Wiener-α (vs. fixed α=0.01 on the position-side cap)**: the position-side cap drifts slowly because the underlying alpha distribution is approximately stationary over training. The Hold-side cap distribution is intrinsically NON-stationary as the policy adapts (sizing tightens, trail shifts). Welford-derived α tracks that non-stationarity; the 0.4 floor prevents the canonical "α decays to 0 once policy converges" trap (SP16 T3 regression at train-multi-seed-b5gmp sha 641aa0dfd Fold 1: α=0.248 at HD4 → Hold% 52.6% at HD5 — see `pearl_wiener_alpha_floor_for_nonstationary`). - **Why no separate Long/Short flag buffer**: `step_ret_per_sample[i]` is non-zero ONLY on close events from positions that had non-zero pre_sign — i.e. Long or Short positions. Hold/Flat positions don't generate non-zero step_ret per `experience_kernels.cu:2855`. Filtering by `(is_close && step_ret != 0)` is the cheapest correct extraction of the Long/Short close distribution; no extra dir_idx_per_sample buffer needed. - **Cold-start fallback behavior**: when `total_count == 0` (no closes this epoch — Hold-heavy phase), the kernel early-returns at the guard in Phase 2 of its body. ALL slots (POS, NEG, all 6 Welford) remain unchanged. The Phase 2 consumer (`compute_sp18_hold_opportunity_cost`) sees the cold-start sentinels (5.0/-10.0) and routes through its own `is_sentinel` branch to the macro defaults +5/-10 — bit-identical to pre-Phase-3. - **Coexistence with `reward_cap_update_kernel`**: both producers read the same `step_ret_per_sample` + `trade_close_per_sample` buffers (read-only; no shared state). They drive independent ISV slot pairs and use different filters. Per-epoch boundary launches are sequential (position-side first, Hold-side second) so the two_kernel sequence is graph-capturable (single stream, no host branches). The redundant block-tree-reduce on the same input buffers is a micro-cost (≤ 2 × 64K samples × 4 floats per epoch = cheap relative to per-step training cost). ## B-leg verification (Phase 1.1 sub-objective) Per the plan's locked B-leg checklist: - **`td_lambda_kernel` launch sites**: 1 site at `gpu_experience_collector.rs:4154` (per the plan). Verified. - **`q_next` self-bootstrap**: line `~4143` confirmed (`let q_next = rewards`). Phase 5 will replace. - **PER priority recomputation**: 5 sites in `crates/ml-dqn/src/gpu_replay_buffer.rs`, `crates/ml-dqn/src/replay_buffer_type.rs`, `crates/ml/src/trainers/dqn/config.rs`, `crates/ml/src/trainers/dqn/fused_training.rs`, `crates/ml/tests/gpu_per_integration_test.rs`. NONE store `q_next` — confirms B-DD4 (no PER migration needed). - **C51 loss target-Q origin**: `c51_loss_kernel.cu` `block_bellman_project_f` already uses target-net Q at loss time — no migration needed. - **`target_params_buf`**: 17 references in `gpu_dqn_trainer.rs`, Polyak-tracked, ready for B-leg consumption. - **PopArt slot 63**: catalogued; B-DD11 reset is the only PopArt change in scope. B-leg pre-conditions verified ✓ — no additional B-leg consumers surfaced (Phase 4-5 atomic-refactor scope is unchanged). ## Captured audit fingerprint (locked snapshot — pre-commit hook `--check` diffs against this) The fingerprint below is a per-section, per-file hit count summary. It is short enough to inline in markdown, sortable for human review, and stable enough to diff cleanly. Catches both new-consumer drift (a file appears in a section where it didn't before) and expanded-consumer drift (a file's hit count changes). It does NOT catch line-by-line content drift within a single file — that's checked by the per-section grep output diffed in PR review. To regenerate: ``` scripts/audit_sp18_consumers.sh --fingerprint # update this section between BEGIN/END markers, then: git add docs/sp18-wireup-audit.md # the pre-commit hook check_sp18_consumer_audit fails the next commit if # scripts/audit_sp18_consumers.sh --check reports drift vs this snapshot. ``` After Phase 1's atomic deletion lands, this fingerprint will shrink dramatically (slot 461 + HCS_* + hold_cost_scale_update sections drop to zero hits). That re-capture is committed as part of Task 1.6 (close-out). ``` === D-leg: Slot 380 (HOLD_COST_INDEX) consumers === total_hits=39 crates/ml/build.rs 1 crates/ml/src/cuda_pipeline/state_layout.cuh 1 crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu 1 crates/ml/src/cuda_pipeline/sp5_isv_slots.rs 1 crates/ml/src/cuda_pipeline/sp13_isv_slots.rs 1 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs 2 crates/ml/src/trainers/dqn/trainer/training_loop.rs 3 crates/ml/src/trainers/dqn/state_reset_registry.rs 1 docs/sp18-wireup-audit.md 5 docs/dqn-wire-up-audit.md 10 docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md 4 docs/superpowers/plans/2026-05-04-sp13-redefine-success-for-predictive-skill.md 5 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 4 === D-leg: Slot 461 (HOLD_COST_SCALE_INDEX) consumers === total_hits=28 docs/sp18-wireup-audit.md 6 docs/dqn-wire-up-audit.md 9 docs/superpowers/plans/2026-05-08-sp16-hold-collapse-structural-fix.md 10 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 3 === D-leg: Slots [462..468) (HCS_* Welford) consumers === total_hits=9 docs/dqn-wire-up-audit.md 8 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 1 === D-leg: hold_cost_scale_update_kernel references === total_hits=58 docs/sp18-wireup-audit.md 11 docs/dqn-wire-up-audit.md 22 docs/superpowers/specs/2026-05-08-sp18-reward-shape-hold-attractor-design.md 4 docs/superpowers/plans/2026-05-08-sp16-hold-collapse-structural-fix.md 9 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 12 === D-leg: hold_rate_observer_kernel (RETAINED — diag chain stays per DD7=c) === total_hits=160 crates/ml/tests/sp13_phase0_oracle_tests.rs 19 crates/ml/tests/sp14_oracle_tests.rs 20 crates/ml/build.rs 5 crates/ml/src/cuda_pipeline/min_hold_temperature_update_kernel.cu 6 crates/ml/src/cuda_pipeline/state_layout.cuh 1 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs 12 crates/ml/src/cuda_pipeline/hold_rate_observer_kernel.cu 4 crates/ml/src/cuda_pipeline/sp5_isv_slots.rs 3 crates/ml/src/cuda_pipeline/sp13_isv_slots.rs 2 crates/ml/src/cuda_pipeline/gpu_dqn_trainer.rs 8 crates/ml/src/cuda_pipeline/gpu_aux_trunk.rs 5 crates/ml/src/cuda_pipeline/sp14_isv_slots.rs 5 crates/ml/src/trainers/dqn/trainer/training_loop.rs 13 crates/ml/src/trainers/dqn/state_reset_registry.rs 3 docs/sp18-wireup-audit.md 7 docs/isv-slots.md 1 docs/dqn-wire-up-audit.md 11 docs/superpowers/specs/2026-05-04-sp13-redefine-success-for-predictive-skill.md 6 docs/superpowers/specs/2026-05-08-sp18-reward-shape-hold-attractor-design.md 1 docs/superpowers/plans/2026-05-05-sp14-aux-q-wire-earned-gradient-flow.md 5 docs/superpowers/plans/2026-05-04-sp13-redefine-success-for-predictive-skill.md 20 docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md 3 === D-leg: Per-bar Hold-cost subtraction sites (3 lines we change in Phase 1.5) === total_hits= === D-leg: state_layout.cuh mirror constants (HOLD_COST + HCS) === total_hits=1 crates/ml/src/cuda_pipeline/state_layout.cuh 1 === D-leg: state_reset_registry slot 461 entries === total_hits= === D-leg: Cubin manifest references in build.rs === total_hits=2 crates/ml/build.rs 2 === B-leg: td_lambda_kernel launch sites === total_hits=20 crates/ml/tests/sp18_td_lambda_q_next_oracle_tests.rs 8 crates/ml/src/cuda_pipeline/gpu_experience_collector.rs 11 crates/ml/src/cuda_pipeline/nstep_kernel.cu 1 === B-leg: q_next argument origin (the line ~4143 site) === total_hits= === B-leg: rewards_out consumers (downstream of TD(λ) output) === total_hits= === B-leg: PER priority computation sites (verify B-DD4: NO migration needed) === total_hits=18 crates/ml-dqn/src/gpu_replay_buffer.rs 7 crates/ml-dqn/src/replay_buffer_type.rs 3 crates/ml/tests/gpu_per_integration_test.rs 3 crates/ml/src/trainers/dqn/config.rs 4 crates/ml/src/trainers/dqn/fused_training.rs 1 === B-leg: Replay buffer schema — q_next storage check (verify NO migration) === total_hits= === B-leg: c51_loss_kernel target-Q origin (verify already correct at loss time) === total_hits= === B-leg: target_params_buf consumers (verify Polyak-tracked, available at collection time) === total_hits= === B-leg: PopArt slot 63 references (verify B-DD11 reset is the only PopArt change) === total_hits=20 crates/ml/tests/sp18_hold_reward_oracle_tests.rs 17 crates/ml/tests/sp11_producer_unit_tests.rs 3 === END === total_hits= ``` ## Phase 4 close-out — B-leg target-net forward additive infrastructure (2026-05-09) Phase 4 lands the **additive dead-code** state for the B-leg target-Q DDQN bootstrap that Phase 5 will swap into `td_lambda_kernel`'s `q_next` argument at `gpu_experience_collector.rs:~4313` (post-Task-4.1 hoist). **Scope landed in this commit:** 1. **Task 4.1** — `next_states` build-site relocated. The `build_next_states_f32` invocation now precedes the TD(λ) launch block (no behavioral change — `next_states` had no observer between its pre-Phase-4 build site and the bottom-of-fn batch assembly). Pure ordering hoist; verified via the audit grep `next_states\b` flagging 0 post-TD(λ) consumers. 2. **Task 4.2** — `compute_q_next_target_bootstrap` skeleton method added on `GpuExperienceCollector` with full plan-aligned signature: ```rust pub fn compute_q_next_target_bootstrap( &mut self, next_states: &CudaSlice, target_params_ptr: u64, online_params_ptr: u64, batch_size: usize, ) -> Result, MLError> ``` Body is a hard-error early-return per `feedback_no_stubs` — no production caller exists in Phase 4 (the caller is wired in Phase 5 Task 5.1, replacing the self-bootstrap clone at line ~4313). Tasks 4.3-4.5 fill in the body atomically before Phase 5 wires the production call site. Returning Err instead of `todo!()` satisfies Invariant 9 (no deferred-work markers); any accidental pre-Phase-5 call site hard-errors with a clear pointer to the open work. **Phase 4 introspection tests** (no GPU required, all pass): | Test | File | Purpose | |---|---|---| | `compute_q_next_target_bootstrap_method_exists` | `crates/ml/tests/sp18_td_lambda_q_next_oracle_tests.rs` | Asserts method signature exists with documented parameters | | `next_states_built_before_td_lambda` | same | Asserts Task 4.1 ordering invariant via offset-comparison | | `td_lambda_still_consumes_self_bootstrap_q_next_in_phase4` | same | Atomic-refactor guard: Phase 4 STILL uses `q_next = rewards_out` clone — Phase 5 Task 5.1 swaps | **Atomic-refactor invariant (HARD — `feedback_no_partial_refactor`):** Phase 4 leaves `compute_q_next_target_bootstrap` as **dead code** (no production caller, only introspection tests). Phase 5.1 makes it live in lockstep with the `q_next` argument swap at the TD(λ) launch: > **NO L40S DISPATCH between the Phase 4 close-out commit and the > Phase 5 Task 5.1 commit** that replaces > `let q_next = dtod_clone_f32_native(&self.stream, &self.rewards_out, ...)` > with `let q_next = self.compute_q_next_target_bootstrap(&next_states, ...)`. **Tasks 4.3-4.5 remaining (deferred to follow-up subagent dispatch):** - **Task 4.3** — implement online-net forward on `next_states` for DDQN per-branch argmax (mirror of `submit_forward_ops_ddqn` infra: VSN+BN+trunk+branch-head chain via `cublas_forward.forward_online_raw`, output: `[B, 4]` i32 per-branch argmax actions). - **Task 4.4** — implement target-net forward via `cublas_forward.forward_target_raw`, then `compute_expected_q` with target's distributional outputs to produce per-action `E[Q]`; gather per-branch argmax `Q_target[b, a*_online_branch]` and resolve final scalar form (B-DD8.a: dir-branch only, OR sum across 4 branches). GPU oracle tests T8 + T11 (`#[ignore = "requires GPU"]`). - **Task 4.5** — replace `todo!()` body; close-out grep audit asserts `todo!` absent in `compute_q_next_target_bootstrap`; Phase 4 ends in additive-dead-code state with full body wired. These tasks each require substantial new infrastructure on the collector (target-net trunk forward chain duplicating `gpu_dqn_trainer.rs` Pass 2's VSN+BN+OFI+trunk+branch-head sequence) and are decomposed in the plan §"Phase 4" as 3 separate TDD cycles with GPU oracle test gates between each. Atomic-refactor rule binds the entire chain Task 4.5 → Task 5.1: NO L40S DISPATCH between. **Sub-task verification status:** ```bash SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo check -p ml # → finished `dev` profile [unoptimized + debuginfo] target(s); clean SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 \ cargo test -p ml --test sp18_td_lambda_q_next_oracle_tests # → 3 passed; 0 failed; 0 ignored ``` **Audit script post-Phase-4 fingerprint diff (vs Phase 1 close-out):** - `td_lambda_kernel launch sites` count rose from 9 → 19 due to the new `sp18_td_lambda_q_next_oracle_tests.rs` introspection test file (7 hits via test docstrings + assertions + the build-site ordering check); the source `gpu_experience_collector.rs` count rose by 3 (additive comments referencing `td_lambda_kernel` per the Phase-4 wiring narrative). Fingerprint regenerated above. - `compute_q_next_target_bootstrap` introduced — 1 producer site (the new method in `gpu_experience_collector.rs`) + 1 introspection test referencing it. No production caller (Phase 4 = additive dead code by design).