"""
    reach_linear(A_cl, B_w, x0_center, x0_halfwidth, w_lo, w_hi, tspan, dt)

Hand-rolled box-zonotope reach tube for `dx/dt = A_cl*x + B_w*w` with
`x(0)` in an axis-aligned box (center, halfwidths) and `w` in a scalar
interval.  Julia port of `reachability/reach_linear.m`.

Returns `(T, R_lo, R_hi, center)`:
- `T      :: Vector{Float64}` — time grid
- `R_lo   :: Matrix{Float64}` — `n × M` lower bounds
- `R_hi   :: Matrix{Float64}` — `n × M` upper bounds
- `center :: Matrix{Float64}` — `n × M` nominal (w_mid-driven) trajectory

Mathematical setup:
    x(t) = e^(A·t)·x(0) + integral_0^t e^(A·(t-s))·B_w·w(s) ds
Per step of size dt:
    A_step = expm(A_cl * dt)
    G_step = integral_0^dt e^(A_cl·s)·B_w ds  (via Van Loan augmented trick)
Halfwidths propagate using elementwise-|·| matrix: a box under linear
map M becomes a box with halfwidth |M|·h (sound over-approximation).
"""
function reach_linear(A_cl, B_w, x0_center, x0_halfwidth, w_lo, w_hi, tspan, dt)
    n = size(A_cl, 1)
    @assert size(A_cl, 2) == n "A_cl must be square"
    x0_center    = vec(x0_center)
    x0_halfwidth = vec(x0_halfwidth)
    B_w_col      = vec(B_w)

    w_mid  = 0.5 * (w_lo + w_hi)
    w_half = 0.5 * (w_hi - w_lo)

    T = collect(tspan[1]:dt:tspan[2])
    M = length(T)

    R_lo  = zeros(n, M)
    R_hi  = zeros(n, M)
    centr = zeros(n, M)

    A_step  = exp(A_cl * dt)
    A_abs   = abs.(A_step)

    # Van Loan augmented matrix: upper-right block of exp([A B_w; 0 0]*dt)
    # equals integral_0^dt exp(A*s)*B_w ds.
    aug = [A_cl B_w_col; zeros(1, n+1)]
    aug_exp = exp(aug * dt)
    G_step  = aug_exp[1:n, n+1]
    G_abs   = abs.(G_step)

    x_center_now  = copy(x0_center)
    halfwidth_now = copy(x0_halfwidth)
    S_center = zeros(n)
    S_half   = zeros(n)

    centr[:, 1] = x_center_now .+ S_center
    R_lo[:, 1]  = centr[:, 1] .- halfwidth_now .- S_half
    R_hi[:, 1]  = centr[:, 1] .+ halfwidth_now .+ S_half

    for k in 2:M
        x_center_now  = A_step * x_center_now
        halfwidth_now = A_abs   * halfwidth_now

        S_center = A_step * S_center + G_step * w_mid
        S_half   = A_abs   * S_half   + G_abs   * w_half

        centr[:, k] = x_center_now .+ S_center
        R_lo[:, k]  = centr[:, k] .- halfwidth_now .- S_half
        R_hi[:, k]  = centr[:, k] .+ halfwidth_now .+ S_half
    end

    return T, R_lo, R_hi, centr
end
