"""
    pke_th_rhs!(dx, x, t, plant, Q_sg, u)

In-place ODE RHS for the coupled PKE + thermal-hydraulics model.
Matches ../plant-model/pke_th_rhs.m term-for-term.

State x = [n, C1..C6, T_f, T_c, T_cold].

Arguments:
- `dx`  : 10-element mutable output
- `x`   : 10-element state
- `t`   : time [s]
- `plant`: parameter NamedTuple from `pke_params`
- `Q_sg`: callable `Q_sg(t)` returning SG heat removal [W]
- `u`   : scalar external reactivity [dk/k]
"""
function pke_th_rhs!(dx, x, t, plant, Q_sg, u)
    n      = x[1]
    T_f    = x[8]
    T_c    = x[9]
    T_cold = x[10]
    T_hot  = 2 * T_c - T_cold

    Qsg_t = Q_sg(t)

    rho = u +
          plant.alpha_f * (T_f - plant.T_f0) +
          plant.alpha_c * (T_c - plant.T_c0)

    # Neutronics
    dx[1] = (rho - plant.beta) / plant.Lambda * n +
            plant.lambda_i[1]*x[2] + plant.lambda_i[2]*x[3] +
            plant.lambda_i[3]*x[4] + plant.lambda_i[4]*x[5] +
            plant.lambda_i[5]*x[6] + plant.lambda_i[6]*x[7]

    # Precursors
    for i in 1:6
        dx[1+i] = (plant.beta_i[i] / plant.Lambda) * n - plant.lambda_i[i] * x[1+i]
    end

    # Thermal-hydraulics
    dx[8]  = (plant.P0 * n - plant.hA * (T_f - T_c)) / (plant.M_f * plant.c_f)
    dx[9]  = (plant.hA * (T_f - T_c) - 2 * plant.W * plant.c_c * (T_c - T_cold)) /
             (plant.M_c * plant.c_c)
    dx[10] = (plant.W * plant.c_c * (T_hot - T_cold) - Qsg_t) /
             (plant.M_sg * plant.c_c)

    return nothing
end

"""
Convenience: allocating version (returns dx as a new vector).  Useful in
scripts and tests; for hot-loop reachability use the in-place version.
"""
function pke_th_rhs(x, t, plant, Q_sg, u)
    dx = similar(x)
    pke_th_rhs!(dx, x, t, plant, Q_sg, u)
    return dx
end
