#!/usr/bin/env bash
# retune-cuda.sh — single-knob retune for a running Linux/CUDA byron-miner rig.
#
# Counterpart to retune-mac.sh, scoped to Linux + NVIDIA. Adjusts ONE launch
# parameter on a running rig (Runpod-style /workspace/byron-pool-rig+supervise.sh
# OR vanilla ~/byron+tmux) without changing binaries, /dl pins, pool code, or
# solver code. Single-variable contract — to change multiple knobs, re-run with
# each one in turn.
#
# Usage (run on the target rig):
#   bash retune-cuda.sh [--dry-run] --<knob> <value>
#
# Supported knobs (v1 — one per invocation):
#   --batch-size       N    positive integer (CLI default 128; flagship 512)
#   --prepare-workers  N    positive integer (flagship 16; sm_86 8)
#   --solver-threads   N    positive integer (flagship 8; sm_86 4)
#   --gpu-inputs       0|1  exact 0 or 1
#   --slice-seconds    S    positive decimal — 5, 5.0, 1.5, 0.5 (reject .5, 0, 0.0, neg, sci)
#   --slice-nonces     N    positive integer — RUNTIME-ONLY (no miner.env / tune.conf key)
#
# Modes:
#   --dry-run     Print intended file edits, before/after cmdline (redacted),
#                 and restart plan. No writes, no signals.
#
# NOT supported in v1:
#   --solver-backend  Refused even with operator override. For backend changes
#                     (one-result <-> continuous) use /dl/apply-continuous.sh —
#                     that script handles the binary swap + sha verify path.
#
# Safety: NEVER stops btxd, tailscale, tailscaled, supervise.sh, boot.sh, or
# the BTX node. Only byron-miner and byron-solve/byron-solve2 are stopped on
# layout A (vanilla); only byron-miner on layout B (supervise.sh respawns).
#
# Rollback: every run that writes files generates rollback-retune-cuda-<TS>.sh
# next to the rig directory. The path is printed at both success and failure.
# Previous rollback scripts are NEVER overwritten — each retune gets its own
# timestamped script and backups.
#
# Exit codes: 0 success or successful dry-run; 1 any error.

set -euo pipefail

# ----- pretty output --------------------------------------------------------
if [ -t 2 ]; then
  RED=$'\033[31m'; GREEN=$'\033[32m'; YELLOW=$'\033[33m'; CYAN=$'\033[36m'; RESET=$'\033[0m'
else
  RED=""; GREEN=""; YELLOW=""; CYAN=""; RESET=""
fi
err()  { printf '%serror:%s %s\n' "$RED"    "$RESET" "$*" >&2; exit 1; }
ok()   { printf '%sok:%s    %s\n' "$GREEN"  "$RESET" "$*" >&2; }
warn() { printf '%swarn:%s  %s\n' "$YELLOW" "$RESET" "$*" >&2; }
info() { printf '       %s\n' "$*" >&2; }
note() { printf '%snote:%s  %s\n' "$CYAN"   "$RESET" "$*" >&2; }

TS=$(date -u +%Y%m%dT%H%M%SZ)
SCRIPT_NAME="retune-cuda.sh"

# ----- usage ---------------------------------------------------------------
usage() { cat <<EOF
$SCRIPT_NAME — single-knob retune for a running Linux/CUDA byron-miner rig.

Usage:
  bash $SCRIPT_NAME [--dry-run] --<knob> <value>

Supported knobs (v1, one per invocation):
  --batch-size       N    positive integer
  --prepare-workers  N    positive integer
  --solver-threads   N    positive integer
  --gpu-inputs       0|1
  --slice-seconds    S    positive decimal (5, 5.0, 1.5, 0.5 — reject .5, 0, neg, sci)
  --slice-nonces     N    positive integer (RUNTIME-ONLY; no env-file key)

Modes:
  --dry-run     Print intended edits + redacted before/after cmdline. No writes,
                no signals.

NOT supported in v1 (refused with hard error):
  --solver-backend  Use /dl/apply-continuous.sh for backend/binary changes.

Refuses on:
  - non-Linux host (Darwin, etc.)
  - missing nvidia-smi
  - 0 or >1 running byron-miner processes
  - target flag absent from current cmdline (can't tune what isn't set)
  - target flag present more than once (ambiguous)
  - --solver-backend (use apply-continuous.sh)

Generates rollback-retune-cuda-<TS>.sh next to the rig directory (never
overwrites existing rollbacks). Backups are also timestamped.

Examples:
  bash $SCRIPT_NAME --dry-run --batch-size 512
  bash $SCRIPT_NAME --solver-threads 8
  bash $SCRIPT_NAME --gpu-inputs 1
EOF
}

# ----- arg parsing ---------------------------------------------------------
DRY_RUN=0
KNOB=""
VALUE=""
while [ $# -gt 0 ]; do
  case "$1" in
    --dry-run) DRY_RUN=1; shift ;;
    -h|--help) usage; exit 0 ;;
    --solver-backend)
      err "--solver-backend not supported by $SCRIPT_NAME v1. For backend/binary changes use /dl/apply-continuous.sh — it handles sha-verified binary swap and per-layout persistence. See https://170-64-206-184.sslip.io/dashboard#/optimization" ;;
    --batch-size|--prepare-workers|--solver-threads|--gpu-inputs|--slice-seconds|--slice-nonces)
      [ $# -ge 2 ] || err "$1 needs a value (e.g. $1 512)"
      [ -z "$KNOB" ] || err "single-knob contract — pass one knob per invocation (got '$KNOB' then '$1')"
      KNOB="$1"; VALUE="$2"; shift 2 ;;
    *) err "unknown argument: $1 (see --help)" ;;
  esac
done

[ -n "$KNOB" ] || { usage; echo; err "no knob specified — see --help"; }

# ----- value validation ----------------------------------------------------
validate_positive_int() {
  case "$1" in
    ''|*[!0-9]*) err "$KNOB: '$1' is not a non-negative integer" ;;
    0|0[0-9]*)   err "$KNOB: '$1' must be a positive integer (got 0 or leading-zero)" ;;
  esac
}
validate_gpu_inputs() {
  case "$1" in
    0|1) : ;;
    *)   err "$KNOB: must be exactly 0 or 1 (got '$1')" ;;
  esac
}
# Positive decimal: matches 5, 5.0, 1.5, 0.5, 123, 123.456
# Rejects 0, 0.0, .5, -1, 1e3, 1.0e3, empty, anything with non-numeric/non-dot
validate_positive_decimal() {
  local v="$1"
  case "$v" in
    ''|.|.*|*.) err "$KNOB: '$v' must be a positive decimal with leading digit (e.g. 5 or 0.5)" ;;
    *e*|*E*)    err "$KNOB: scientific notation not allowed ('$v')" ;;
    -*)         err "$KNOB: negative not allowed ('$v')" ;;
  esac
  # Single optional dot; only digits otherwise
  if ! printf '%s' "$v" | grep -qE '^[0-9]+(\.[0-9]+)?$'; then
    err "$KNOB: '$v' is not a valid positive decimal"
  fi
  # Reject 0 and 0.0 (and 00, 0.00, etc.)
  case "$v" in
    0|0.0|0.00|0.000) err "$KNOB: must be > 0 (got '$v')" ;;
  esac
  # Reject implicit zero like "0." or "00.5" (leading-zero non-zero is fine: 0.5)
  if printf '%s' "$v" | grep -qE '^0[0-9]'; then
    err "$KNOB: leading-zero integers not allowed ('$v')"
  fi
}

case "$KNOB" in
  --batch-size|--prepare-workers|--solver-threads|--slice-nonces) validate_positive_int  "$VALUE" ;;
  --gpu-inputs)    validate_gpu_inputs    "$VALUE" ;;
  --slice-seconds) validate_positive_decimal "$VALUE" ;;
esac

# ----- platform preflight (BEFORE the success banner) ---------------------
# On any refusal here we want only the error line, not a misleading "ok:"
# banner suggesting the retune is going to proceed. The banner moves below.
UNAME="${RETUNE_CUDA_TEST_UNAME:-$(uname -s 2>/dev/null || echo unknown)}"
case "$UNAME" in
  Linux) : ;;
  Darwin) err "macOS not supported — use /dl/retune-mac.sh for Mac rigs" ;;
  *)      err "unsupported OS '$UNAME' — this script is Linux/CUDA only" ;;
esac
if ! command -v nvidia-smi >/dev/null 2>&1; then
  if [ -z "${RETUNE_CUDA_TEST_SKIP_NVIDIA_SMI:-}" ]; then
    err "nvidia-smi not found — CUDA toolkit / NVIDIA driver missing on this host. Refusing."
  fi
fi
[ -d /proc ] || err "/proc not available — this script depends on /proc for cmdline + env introspection"

# Validation + platform checks passed — now safe to announce intent.
ok "$SCRIPT_NAME: $KNOB -> $VALUE  (dry-run=$DRY_RUN)"

# ----- find the single byron-miner ----------------------------------------
PIDS="$(pgrep -x byron-miner || true)"
COUNT=$(printf '%s\n' "$PIDS" | sed '/^$/d' | wc -l)
if [ "$COUNT" -ne 1 ]; then
  if [ "$COUNT" -eq 0 ]; then
    err "no byron-miner process found — start it first via join-pool.sh or your supervisor, then re-run"
  fi
  warn "expected exactly one byron-miner; found $COUNT. Process listing:"
  ps -eo pid,args | grep '[b]yron-miner' >&2 || true
  err "ambiguous miner state — pkill any orphans and re-run"
fi
PID="$PIDS"
ok "byron-miner pid=$PID (exactly one)"

# ----- read cmdline + env from /proc --------------------------------------
PROC_ROOT="${RETUNE_CUDA_TEST_PROC_ROOT:-/proc}"
CMDLINE_FILE="$PROC_ROOT/$PID/cmdline"
ENVIRON_FILE="$PROC_ROOT/$PID/environ"
[ -r "$CMDLINE_FILE" ] || err "cannot read $CMDLINE_FILE — run with sudo if needed"

ARGV=()
while IFS= read -r -d '' arg; do ARGV+=("$arg"); done < "$CMDLINE_FILE"
[ "${#ARGV[@]}" -gt 0 ] || err "$CMDLINE_FILE is empty (process gone?)"

# Locate target flag (must appear exactly once, with a value after it)
HITS=0
HIT_IDX=-1
for ((i=0; i<${#ARGV[@]}; i++)); do
  if [ "${ARGV[i]}" = "$KNOB" ]; then
    HITS=$((HITS+1))
    HIT_IDX=$i
  fi
done
if [ "$HITS" -eq 0 ]; then
  err "$KNOB not present in current cmdline — can't tune what isn't set already. Re-run join-pool.sh with the desired knob to add it."
fi
if [ "$HITS" -gt 1 ]; then
  err "$KNOB present $HITS times in current cmdline (ambiguous) — investigate manually before retuning"
fi
if [ "$HIT_IDX" -ge $((${#ARGV[@]}-1)) ]; then
  err "$KNOB is the last argv token with no value following it"
fi

OLD_VALUE="${ARGV[HIT_IDX+1]}"
if [ "$OLD_VALUE" = "$VALUE" ]; then
  warn "$KNOB is already $VALUE — nothing to do"
  exit 0
fi

# Build new argv
NEW_ARGV=("${ARGV[@]}")
NEW_ARGV[HIT_IDX+1]="$VALUE"

# Quoted strings for display + relaunch
OLD_CMD_QUOTED=$(printf '%q ' "${ARGV[@]}")
NEW_CMD_QUOTED=$(printf '%q ' "${NEW_ARGV[@]}")

# Redact btx1... addresses in display
redact_addr_in_str() {
  printf '%s' "$1" | sed -E 's/(btx1[a-z0-9]{6})[a-z0-9]{40,}([a-z0-9]{6})/\1****\2/g'
}
OLD_CMD_REDACTED=$(redact_addr_in_str "$OLD_CMD_QUOTED")
NEW_CMD_REDACTED=$(redact_addr_in_str "$NEW_CMD_QUOTED")

# ----- capture BTX_* env from /proc/PID/environ ---------------------------
ENV_EXPORTS=""
ENV_COUNT=0
if [ -r "$ENVIRON_FILE" ]; then
  while IFS= read -r -d '' kv; do
    case "$kv" in
      BTX_MATMUL_*=*|BTX_MINER_*=*)
        ENV_EXPORTS+="export $(printf '%q' "${kv%%=*}")=$(printf '%q' "${kv#*=}")"$'\n'
        ENV_COUNT=$((ENV_COUNT+1))
        ;;
    esac
  done < "$ENVIRON_FILE"
  ok "captured $ENV_COUNT BTX_* env keys from running process"
else
  warn "$ENVIRON_FILE not readable — env vars will not be preserved across relaunch (relaunched miner will inherit current shell env only)"
fi

# ----- layout detection ----------------------------------------------------
# Layout B (Runpod): /workspace/byron-pool-rig + supervise.sh respawn
# Layout A (vanilla): ~/byron + tmux session 'byron'
RIGDIR=""
LAYOUT=""
LOG_PATH=""
PERSIST_FILE=""

# supervise.sh process is the strongest signal
SUPERVISE_PID="$(pgrep -fx 'bash[[:space:]]*/workspace/byron-pool-rig/supervise.sh' 2>/dev/null || pgrep -f 'supervise\.sh' 2>/dev/null || true)"
SUPERVISE_PID=$(printf '%s\n' "$SUPERVISE_PID" | head -n1)

if [ -n "$SUPERVISE_PID" ] && [ -f /workspace/byron-pool-rig/miner.env ]; then
  LAYOUT="B"
  RIGDIR="/workspace/byron-pool-rig"
  LOG_PATH="$RIGDIR/miner.log"
  PERSIST_FILE="$RIGDIR/miner.env"
elif [ -f "${HOME:-/root}/byron/byron-miner" ] && [ -x "${HOME:-/root}/byron/byron-miner" ]; then
  LAYOUT="A"
  RIGDIR="${HOME}/byron"
  LOG_PATH="${HOME}/byron-miner.log"
  PERSIST_FILE="${HOME}/byron/tune.conf"
else
  # Fall back to deriving from the running cmdline's miner binary path
  MINER_DIR="$(dirname "${ARGV[0]}")"
  if [ -f "$MINER_DIR/byron-miner" ]; then
    LAYOUT="A"
    RIGDIR="$MINER_DIR"
    LOG_PATH="$HOME/byron-miner.log"
    PERSIST_FILE="$MINER_DIR/tune.conf"
    warn "layout auto-detection fell through to cmdline-derived path ($RIGDIR) — confirm this is correct before proceeding"
  else
    err "cannot detect rig layout (neither /workspace/byron-pool-rig nor ~/byron found) — refusing"
  fi
fi

case "$LAYOUT" in
  A) ok "layout A detected (vanilla install.sh / ~/byron + tmux); rig dir: $RIGDIR" ;;
  B) ok "layout B detected (Runpod-style supervise.sh); rig dir: $RIGDIR; supervise pid: $SUPERVISE_PID" ;;
esac

# ----- map CLI flag -> persistence-file key per layout --------------------
# Layout B miner.env keys: BATCH, PREPARE_WORKERS, SOLVER_THREADS, GPU_INPUTS, SLICE
# Layout A tune.conf keys:  BATCH_SIZE, PREPARE_WORKERS, SOLVER_THREADS, GPU_INPUTS, SLICE_SECONDS
# Neither layout persists --slice-nonces.
PERSIST_KEY=""
RUNTIME_ONLY=0
case "$LAYOUT:$KNOB" in
  B:--batch-size)       PERSIST_KEY="BATCH" ;;
  B:--prepare-workers)  PERSIST_KEY="PREPARE_WORKERS" ;;
  B:--solver-threads)   PERSIST_KEY="SOLVER_THREADS" ;;
  B:--gpu-inputs)       PERSIST_KEY="GPU_INPUTS" ;;
  B:--slice-seconds)    PERSIST_KEY="SLICE" ;;
  B:--slice-nonces)     RUNTIME_ONLY=1 ;;
  A:--batch-size)       PERSIST_KEY="BATCH_SIZE" ;;
  A:--prepare-workers)  PERSIST_KEY="PREPARE_WORKERS" ;;
  A:--solver-threads)   PERSIST_KEY="SOLVER_THREADS" ;;
  A:--gpu-inputs)       PERSIST_KEY="GPU_INPUTS" ;;
  A:--slice-seconds)    PERSIST_KEY="SLICE_SECONDS" ;;
  A:--slice-nonces)     RUNTIME_ONLY=1 ;;
esac

if [ "$RUNTIME_ONLY" = "1" ]; then
  warn "$KNOB is RUNTIME-ONLY — the value will apply to the relaunched miner but will NOT persist across container reboot / supervise.sh respawn / next join-pool.sh run. Neither miner.env nor tune.conf carries this key."
fi

# ----- compute persistence-file edit (if applicable) ----------------------
PERSIST_DIFF=""
PERSIST_NEW=""
if [ "$RUNTIME_ONLY" = "0" ] && [ -n "$PERSIST_FILE" ] && [ -f "$PERSIST_FILE" ]; then
  if grep -qE "^${PERSIST_KEY}=" "$PERSIST_FILE"; then
    # Replace the value, keep the line shape (preserves comments after #)
    PERSIST_NEW="$(awk -v k="$PERSIST_KEY" -v v="$VALUE" '
      BEGIN { re = "^" k "=" }
      $0 ~ re {
        # Preserve trailing comment if any
        c = ""
        if (match($0, /[ \t]+#/)) { c = substr($0, RSTART) }
        print k "=" v c
        next
      }
      { print }
    ' "$PERSIST_FILE")"
    PERSIST_DIFF=$(diff -u --label "$PERSIST_FILE (current)" --label "$PERSIST_FILE (new)" \
      <(printf '%s\n' "$(cat "$PERSIST_FILE")") <(printf '%s\n' "$PERSIST_NEW") || true)
  else
    warn "$PERSIST_KEY not present in $PERSIST_FILE — change will apply to running cmdline but not persist via $PERSIST_FILE. (Operator: add a $PERSIST_KEY= line manually if persistence is needed.)"
  fi
elif [ "$RUNTIME_ONLY" = "0" ] && [ -n "$PERSIST_FILE" ]; then
  warn "$PERSIST_FILE does not exist — persistence file will not be updated. (This is unusual; investigate before proceeding.)"
fi

# ----- restart plan summary -----------------------------------------------
case "$LAYOUT" in
  B) RESTART_PLAN="pkill -x byron-miner (supervise.sh respawns within ~30 s, sourcing the edited $PERSIST_FILE)" ;;
  A) RESTART_PLAN="tmux kill-session -t byron + pkill byron-miner/solve + relaunch tmux session 'byron' with new cmdline (captured BTX_* env re-exported)" ;;
esac

# ----- dry-run gate --------------------------------------------------------
if [ "$DRY_RUN" = "1" ]; then
  printf '\n%sDRY-RUN%s — no files written, no signals sent.\n\n' "$YELLOW" "$RESET" >&2
  info "Layout:           $LAYOUT  ($RIGDIR)"
  info "Detected PID:     $PID"
  info "Persistence file: ${PERSIST_FILE:-<none>}"
  info "Persistence key:  ${PERSIST_KEY:-<none>${RUNTIME_ONLY:+ (RUNTIME-ONLY)}}"
  printf '\n%scurrent cmdline (redacted):%s\n  %s\n\n' "$CYAN" "$RESET" "$OLD_CMD_REDACTED" >&2
  printf '%sproposed cmdline (redacted):%s\n  %s\n\n' "$CYAN" "$RESET" "$NEW_CMD_REDACTED" >&2
  printf '%schange:%s %s  %s -> %s\n\n' "$CYAN" "$RESET" "$KNOB" "$OLD_VALUE" "$VALUE" >&2
  if [ -n "$PERSIST_DIFF" ]; then
    printf '%spersistence-file diff:%s\n' "$CYAN" "$RESET" >&2
    printf '%s\n\n' "$PERSIST_DIFF" >&2
  fi
  info "Would generate: $RIGDIR/rollback-retune-cuda-${TS}.sh"
  info "Would back up:  ${PERSIST_FILE:+$PERSIST_FILE.bak-pre-retune-${TS}}"
  info "Restart plan:   $RESTART_PLAN"
  info "Smoke check:    wait <=30 s for 'solver daemon ready' in $LOG_PATH"
  ok "dry-run complete"
  exit 0
fi

# ============== APPLY MODE ================================================
ok "applying $KNOB $OLD_VALUE -> $VALUE  (TS=$TS)"

# 1) Backup persistence file if we're going to edit it
PERSIST_BAK=""
if [ -n "$PERSIST_NEW" ] && [ -n "$PERSIST_FILE" ]; then
  PERSIST_BAK="${PERSIST_FILE}.bak-pre-retune-${TS}"
  if [ -e "$PERSIST_BAK" ]; then
    err "backup target already exists: $PERSIST_BAK (collision on TS=$TS — wait 1 s and retry)"
  fi
  cp -a "$PERSIST_FILE" "$PERSIST_BAK"
  ok "backed up $PERSIST_FILE -> $PERSIST_BAK"
fi

# 2) Generate launch-previous-retune-<TS>.sh (for rollback's relaunch on layout A,
#    and informational on layout B)
LAUNCH_PREV="${RIGDIR}/launch-previous-retune-${TS}.sh"
if [ -e "$LAUNCH_PREV" ]; then
  err "launch-previous target already exists: $LAUNCH_PREV (collision on TS=$TS — wait 1 s and retry)"
fi
{
  printf '#!/usr/bin/env bash\n'
  printf '# Generated by %s at %s. Pre-retune launch reference.\n' "$SCRIPT_NAME" "$TS"
  printf 'set -uo pipefail\n'
  printf '%s' "$ENV_EXPORTS"
  printf '%s' "$OLD_CMD_QUOTED"
  printf '\\\n  2>&1 | tee -a %s\n' "$(printf '%q' "$LOG_PATH")"
} > "$LAUNCH_PREV"
chmod +x "$LAUNCH_PREV"

# 3) Generate rollback-retune-cuda-<TS>.sh
ROLLBACK="${RIGDIR}/rollback-retune-cuda-${TS}.sh"
if [ -e "$ROLLBACK" ]; then
  err "rollback target already exists: $ROLLBACK (collision on TS=$TS — wait 1 s and retry)"
fi
{
  printf '#!/usr/bin/env bash\n'
  printf '# Rollback for the %s -> %s retune applied at %s.\n' "$KNOB" "$VALUE" "$TS"
  printf '# Restores %s from backup (if any) and relaunches the miner.\n' "$PERSIST_FILE"
  printf '# Does NOT touch btxd, tailscale, supervise.sh, boot.sh, or wallets.\n'
  printf 'set -euo pipefail\n'
  if [ -n "$PERSIST_BAK" ]; then
    printf 'PERSIST_FILE=%q\n' "$PERSIST_FILE"
    printf 'PERSIST_BAK=%q\n'  "$PERSIST_BAK"
    printf '[ -f "$PERSIST_BAK" ] || { echo "missing backup: $PERSIST_BAK" >&2; exit 1; }\n'
    printf 'cp -a "$PERSIST_BAK" "$PERSIST_FILE"\n'
    printf 'echo "restored: $PERSIST_FILE"\n'
  fi
  if [ "$LAYOUT" = "A" ]; then
    printf 'LAUNCH_PREV=%q\n' "$LAUNCH_PREV"
    printf 'tmux kill-session -t byron 2>/dev/null || true\n'
    printf 'pkill -x byron-miner   2>/dev/null || true\n'
    printf 'pkill -x byron-solve   2>/dev/null || true\n'
    printf 'pkill -x byron-solve2  2>/dev/null || true\n'
    printf 'sleep 2\n'
    printf 'if command -v tmux >/dev/null 2>&1; then\n'
    printf '  tmux new -d -s byron "bash $LAUNCH_PREV"\n'
    printf '  echo "tmux session byron relaunched via $LAUNCH_PREV"\n'
    printf 'else\n'
    printf '  nohup bash "$LAUNCH_PREV" >/dev/null 2>&1 &\n'
    printf '  echo "byron-miner relaunched (pid $!) via $LAUNCH_PREV"\n'
    printf 'fi\n'
  else
    # Layout B — supervise.sh respawns from miner.env
    printf 'pkill -x byron-miner  2>/dev/null || true\n'
    printf 'pkill -x byron-solve2 2>/dev/null || true\n'
    printf 'echo "byron-miner stopped; supervise.sh will respawn from restored $PERSIST_FILE within ~30 s"\n'
  fi
} > "$ROLLBACK"
chmod +x "$ROLLBACK"
ok "generated rollback: $ROLLBACK"

# 4) Apply persistence-file edit atomically (if any)
if [ -n "$PERSIST_NEW" ] && [ -n "$PERSIST_FILE" ]; then
  TMP="${PERSIST_FILE}.tmp-retune-${TS}.$$"
  printf '%s\n' "$PERSIST_NEW" > "$TMP"
  mv "$TMP" "$PERSIST_FILE"
  ok "edited $PERSIST_FILE: $PERSIST_KEY=$VALUE"
fi

# 5) Log cursor + stop miner (layout-specific)
PRE_LINES=$(wc -l < "$LOG_PATH" 2>/dev/null || echo 0)
ok "log cursor PRE_LINES=$PRE_LINES (new lines start at $((PRE_LINES+1)))"

case "$LAYOUT" in
  A)
    ok "stopping byron-miner + byron-solve(2) (NOT btxd, NOT tailscale, NOT supervise.sh)"
    tmux kill-session -t byron 2>/dev/null || true
    pkill -x byron-miner  2>/dev/null || true
    pkill -x byron-solve  2>/dev/null || true
    pkill -x byron-solve2 2>/dev/null || true
    sleep 2
    ;;
  B)
    ok "stopping byron-miner only (supervise.sh will respawn from edited $PERSIST_FILE within ~30 s)"
    pkill -x byron-miner  2>/dev/null || true
    pkill -x byron-solve2 2>/dev/null || true
    ;;
esac

# 6) Relaunch (layout A only — layout B is handled by supervise.sh)
if [ "$LAYOUT" = "A" ]; then
  LAUNCH_NEW="${RIGDIR}/launch-retune-${TS}.sh"
  if [ -e "$LAUNCH_NEW" ]; then
    err "launch-retune target already exists: $LAUNCH_NEW (TS collision)"
  fi
  {
    printf '#!/usr/bin/env bash\n'
    printf '# Generated by %s at %s. Post-retune launch (%s=%s).\n' "$SCRIPT_NAME" "$TS" "$KNOB" "$VALUE"
    printf 'set -uo pipefail\n'
    printf '%s' "$ENV_EXPORTS"
    printf '%s' "$NEW_CMD_QUOTED"
    printf '\\\n  2>&1 | tee -a %s\n' "$(printf '%q' "$LOG_PATH")"
  } > "$LAUNCH_NEW"
  chmod +x "$LAUNCH_NEW"

  if command -v tmux >/dev/null 2>&1; then
    tmux new -d -s byron "bash $LAUNCH_NEW"
    ok "tmux session byron started via $LAUNCH_NEW"
  else
    nohup bash "$LAUNCH_NEW" >/dev/null 2>&1 &
    ok "byron-miner started (pid $!) via $LAUNCH_NEW; logs: tail -f $LOG_PATH"
  fi
fi

# 7) Smoke check — wait <=30 s for 'solver daemon ready' in the new log lines
ok "waiting up to 30 s for 'solver daemon ready' in $LOG_PATH ..."
READY=0
for _ in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
  sleep 2
  if tail -n +$((PRE_LINES + 1)) "$LOG_PATH" 2>/dev/null | grep -q "solver daemon ready"; then
    READY=1
    break
  fi
done

if [ "$READY" = "1" ]; then
  ok "solver daemon ready — retune complete ($KNOB: $OLD_VALUE -> $VALUE)"
  printf '\n  Rollback (if needed):\n    bash %s\n\n' "$ROLLBACK" >&2
  info "Watch the dashboard for ~5 min. If $KNOB=$VALUE makes things worse, run the rollback command above."
  exit 0
else
  warn "solver did NOT signal ready within 30 s — auto-rolling back"
  bash "$ROLLBACK" >&2 || warn "rollback script exited non-zero — investigate manually"
  err "retune failed and was rolled back; investigate via: tail -n 80 $LOG_PATH"
fi
