Atomic introduction of `compute_sp18_hold_opportunity_cost` device function
in `trade_physics.cuh` + migration of the 3 placeholder-commented per-bar
Hold sites in `experience_kernels.cu` + CPU/GPU oracle tests + test-wrapper
cubin manifest entry — single atomic commit per `feedback_no_partial_refactor`.
Closes the SP18 INTERIM STATE introduced by Phase 1 (3c318953a). The reward
function is now structurally complete: no placeholder sites, no orphaned
consumers. Wire-up audit fingerprint stable; production-call count for the
new device fn = 3 (segment_complete + per-bar positioned-Hold + per-bar
flat-Hold), test-wrapper count = 1, definition count = 1.
Sites:
- trade_physics.cuh: new device fn after `compute_lump_sum_opp_cost`.
Returns 0 for `dir_idx != DIR_HOLD`. Sign convention DD5(b) MIRRORED:
positive next_log_return → negative Hold reward (opp cost paid for
sitting out a winner); negative next_log_return → positive Hold reward
(correctly avoided a loser). Asymmetric-capped via the existing
`compute_asymmetric_capped_pnl` primitive against ISV[483]/[484]; output
multiplied by `shaping_scale` (validation = 0 → returns 0). No new
infrastructure beyond existing primitives.
- experience_kernels.cu Site 1 (segment_complete ~3143): unconditional
add into `base_reward` BEFORE the SP12 asymmetric cap; preserves SP12
cap interaction.
- experience_kernels.cu Site 2 (per-bar positioned-Hold ~3641):
unconditional add into `r_micro` (rc[3]) — same component the deleted
SP13/SP16 subtraction wrote to, preserving SP11 reward-subsystem
controller signal attribution. `vol_proxy` recomputed locally from
`features[bar_idx * market_dim + 9]` (per-bar branches are outside
the segment_complete `vol_proxy` scope).
- experience_kernels.cu Site 3 (per-bar flat-Hold ~3760): unconditional
add into `r_opp_cost` (rc[4]) — same component the deleted subtraction
wrote to. `vol_proxy` recomputed locally with the same pattern.
Each site reads ISV[HOLD_REWARD_POS_CAP_INDEX=483] /
ISV[HOLD_REWARD_NEG_CAP_INDEX=484] with sentinel-or-out-of-bounds guard
falling through to SENTINEL_HOLD_REWARD_POS/NEG_CAP=+5/-10 macros. Bit-
identical to a fixed +5/-10 cap during cold-start and Pre-Phase-3 (Phase 3
lands the producer kernel `hold_reward_cap_update_kernel`).
Tests:
- T1 CPU oracle (sp18_hold_reward_oracle_tests.rs): 6 cases covering
positive/negative log-return, dir-gate (early-exit for non-HOLD),
positive saturation (clamps at +pos_cap), negative saturation (clamps
at -neg_cap), shaping_scale=0 validation mode (returns 0).
- T2 GPU oracle (sp18_hold_reward_oracle_tests.rs::gpu): same 6 cases via
`sp18_hold_opp_test_kernel.cu` wrapper; bit-equality to CPU at ε=1e-5.
- sp18_hold_opp_test_kernel.cu: single-thread single-block test wrapper
mirroring `sp12_reward_math_test_kernel.cu` structure. Mapped-pinned
f32 input/output per `feedback_no_htod_htoh_only_mapped_pinned`;
__threadfence_system() for PCIe visibility before host read.
- build.rs: cubin manifest entry for sp18_hold_opp_test_kernel.cu.
Validation:
- SQLX_OFFLINE=true CUDA_COMPUTE_CAP=86 cargo check --workspace clean.
- cargo test -p ml --lib sp18: 4 SP18 lib tests pass (slot layouts,
fold-reset entries-present).
- cargo test -p ml --lib state_reset_registry: 5 tests pass (including
every_fold_and_soft_reset_entry_has_dispatch_arm).
- cargo test -p ml --test sp18_hold_reward_oracle_tests --features cuda
-- --include-ignored: 7 tests pass (2 CPU + 5 GPU on local RTX 3050 Ti),
including the new compute_sp18_hold_opportunity_cost_cpu_oracle and
compute_sp18_hold_opportunity_cost_gpu_oracle.
- bash scripts/audit_sp18_consumers.sh --check: exit 0 (post-Phase-2
fingerprint snapshotted in docs/sp18-wireup-audit.md).
- Wire-up audit: `grep -rn "compute_sp18_hold_opportunity_cost" crates/ml/`
shows 1 device-fn definition + 3 production call sites + 1 test-wrapper
kernel + CPU/GPU oracle tests. No orphans.
Audit fingerprint diff (Phase 1 → Phase 2):
- `Slot 380 (HOLD_COST_INDEX) consumers`: experience_kernels.cu hit count
1 → 0 (placeholder-comment reference to HOLD_COST_INDEX removed; new
Phase 2 calls reference HOLD_REWARD_POS/NEG_CAP_INDEX=483/484 instead);
doc references in dqn-wire-up-audit.md grew by 2 lines (Phase 2
close-out narrative). Total section count 42 → 43.
- All other sections unchanged — Phase 2 introduces forward-tracking
references on the SP18-bound side (slots 483/484, new device fn), not
the deleted-chain side.
Discipline:
- feedback_no_partial_refactor: device fn + 3 call sites + tests + cubin
manifest in same commit; INTERIM STATE LIFTED in same commit.
- feedback_no_atomicadd: device fn is per-thread sample-local register
arithmetic; test wrapper is single-thread single-block, no shared mem.
- feedback_no_htod_htoh_only_mapped_pinned: GPU oracle test uses
MappedF32Buffer.
- feedback_isv_for_adaptive_bounds: pos/neg caps read from ISV[483]/[484]
with sentinel fallback; Phase 3 producer `hold_reward_cap_update_kernel`
drives them adaptively.
- feedback_wire_everything_up: device fn + 3 call sites + test wrapper +
CPU/GPU oracle tests + cubin manifest all in same commit.
- pearl_one_unbounded_signal_per_reward: only `vol_normalized_return` is
unbounded; everything else in [neg_cap, pos_cap] × shaping_scale.
- pearl_first_observation_bootstrap: sentinels (5.0/-10.0) match SP14
P0-A REWARD_POS/NEG_CAP_ADAPTIVE for bit-identical cold-start.
Plan: docs/superpowers/plans/2026-05-08-sp18-reward-shape-hold-attractor.md
Phase 2 Tasks 2.1-2.5.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>