ocean_bt_config_t Derived Type

type, public :: ocean_bt_config_t


Inherited by

type~~ocean_bt_config_t~~InheritedByGraph type~ocean_bt_config_t ocean_bt_config_t type~ocean_config_t ocean_config_t type~ocean_config_t->type~ocean_bt_config_t bt type~config_t config_t type~config_t->type~ocean_config_t ocean type~ocean_handle_t ocean_handle_t type~ocean_handle_t->type~config_t cfg

Components

Type Visibility Attributes Name Initial
logical, public :: auto_n_inner = .false.

If .true., derive n_inner at setup from the gravity-wave CFL, evaluated per WET cell (local depth with local cell size, MOM6 set_dtbt; land never limits it). Default .false..

logical, public :: bc_pgf_forcing = .true.

MOM6 split (BT_force + eta_PF): the barotropic substep is forced by the depth mean of the FULL slow PGF — baroclinic part included — minus only the free-surface term that PGF itself carries at the η it was evaluated on (none for the surface-relative MONT/FV_LITE/FV_WRIGHT forms), which the substep’s own -g_bt·∇η replaces. .false. restores the legacy split, which subtracted the WHOLE depth-mean PGF and so never let the barotropic mode feel the baroclinic bottom-pressure gradient (JEBAR; ~0 Sv through Drake Passage on the global 1° case against MOM6’s ~160 Sv).

real(kind=wp), public :: bebt = 0.1_wp

BT continuity-flux velocity projection weight (MOM6 BEBT). Default 0.1 = MOM6’s own default for that parameter: damps the barotropic gravity waves at |λ|² = 1 − b·a² per substep (a = c·dt_bt·k_eff), which is what damps the barotropic grid-scale mode under pred_corr. 0.0 = pure forward-backward Euler (neutral, the pre-2026-09-22 default). The FB stability limit tightens to a ≤ 2/√(1+2·bebt) (MOM6 set_dtbt’s 1+2·BEBT factor).

integer, public :: bt_halo = BT_HALO_AUTO_SENTINEL

Wide-halo march-in width. -1 (default) = AUTO, which resolves to 0: the march-in is OPT-IN. A decomposed run with the march-in is not bit-identical to the serial run over variable bathymetry or with open boundaries (measured by test_ocean_decomp_bitid_mpi; flat-bottom closed / periodic / spherical cases are), so the default keeps every decomposition bit-reproducible. 8 (BT_HALO_AUTO_WIDTH) is the recommended explicit width. 0 = explicit off (per-substep grouped exchange, v1 bit-identical). Even positive value: widen the BT ghost band to nghost + bt_halo and fire one grouped exchange every bt_halo/2 substeps. Odd values are rounded DOWN to even with a warning. Fail-loud exclusions (apply when bt_halo is set EXPLICITLY > 0; AUTO instead silently resolves to 0): wet/dry enable, use_cont_type, upstream_h_face, tides enable, psurf enable, porous enable, cavity_dyn enable, supergrid/tripolar grid_config. The set of record is bt_halo_auto_exclusion; keep it and the validate_config checks in lockstep.

logical, public :: bt_rem_from_visc_rem = .false.

PR-2 (bt-rem-from-av-rem): bt_rem_u/v built from the SAME viscous remnant the layered momentum solve uses, instead of the linear-piston substep_drag law or the static 1.0 no-op. Matches MOM6’s barotropic solver: av_rem = Σ_k frhat_k·visc_rem_k (the visc_rem depth mean, frhat_k the face layer fraction derive_bt_from_layers already uses), then bt_rem = mask·av_rem**(1/n_inner) (zero where mask·av_rem <= 0), built ONCE per barotropic call — after the visc_rem producer, before the inner substeps — into the existing bt_work%bt_rem_u/v multiplier the substep loop already reads. This is the fix for the bbl_glue day-253 instability (the barotropic solver was seeing weak explicit drag while the layers were strongly glued): the fast mode now feels the SAME friction. Default .false. ⇒ no answer change. Self-sufficient (D1 follow-up) — the visc_rem producer runs whenever this is on, decoupled from the retired correction_visc_rem; mutually exclusive with substep_drag (bed drag would be double-counted — once inside visc_rem via the glue/implicit_drag fold, once again via the linear piston) and with bt_halo > 0 (the wide-halo BT clone’s metrics_w/halo-widened arrays carry no av_rem/visc_rem ghost width yet — same posture as porous). Composes with wave_drag (multiplied in after).

real(kind=wp), public :: cfl_bt_safety = 0.65_wp

Safety fraction on the shallow-water CFL bound when auto_n_inner = .true. (typical 0.65-0.7).

logical, public :: cont_corr_bounds = .false.

When .true. (and use_cont_type), the η-correction bound uses the BT_cont face-by-face flux limits. Default .false..

logical, public :: correction_bc_pgf = .false.

Adds a per-layer baroclinic-PGF retro-correction for the η change during the BT substep. Requires pgf%form = "fv_mom6".

logical, public :: correction_h_weighted = .false.

RETIRED (2026-10-02) — setting it .true. is a fail-loud validate_config error. It distributed the barotropic increment by h_face(k)/⟨h⟩_h, which is not energy-conserving: beyond the barotropic ΔKE it adds a positive-definite source ½Δ²·H·(κ−1), κ = Σh³Σh/(Σh²)², plus a shear feedback that grew the stretched-z_fixed 1-degree Southern Ocean to a non-finite state on day 16. MOM6 has no h-weighted fold (its barotropic acceleration is the same in every layer). The key stays registered only so the refusal can say why; drag-aware weighting is correction_visc_rem.

logical, public :: correction_visc_rem = .false.

RETIRED (2026-10, D1 follow-up) — setting .true. is a fail-loud validate_config error. This used to weight the BT-corrector fold by visc_rem(k)/⟨visc_rem⟩_h instead of uniformly, but MOM6’s barotropic solver gives every layer the SAME u_accel_bt via accel_layer_u — no visc_rem weight — folded into up BEFORE vertvisc distributes it via the SAME implicit friction the BBL glue uses, so MOM6 never damps it twice. This fold did, and the SECOND, unbounded visc_rem_k/⟨visc_rem⟩_h ratio is what NaNs the 1-degree Southern Ocean z* open-step case under bbl_glue at step ~40 (see validate_config’s refusal message for the measured isolation). visc_rem_chain is the replacement and uses the UNIFORM fold, matching MOM6. The key stays registered only so the refusal can say why; the underlying kernel dispatch (apply_bt_correction’s use_visc_rem) and its direct unit tests are untouched.

logical, public :: forcing_visc_rem = .false.

MOM6 wt_u parity for the BT FORCING assembly: weight each layer’s contribution to F_bt_u/v (and to the PGF-projection subtraction, which must use the same weights) by h_face·visc_rem(k) instead of h_face. Layers the implicit friction will immediately damp — grounded sliver stacks under the vdiff BBL glue — then contribute nothing to the fast loop, which otherwise integrates the spurious grounded-layer PGF’s depth-mean ballistically (PGF_BUG.md §9). Self-sufficient (D1 follow-up) — the visc_rem producer runs whenever this is on, decoupled from the retired correction_visc_rem.

character(len=16), public :: frhat_scheme = "hybrid"

rdb_barotropic_coupling::frhat_h_face_step’s per-layer face-thickness closure for EVERY barotropic depth mean that reads a layer’s face thickness: derive_bt_from_layers, face_depth_mean_u/v, face_depth_mean_rem_u/v (forcing_visc_rem’s wt_u), apply_bt_correction’s open/visc_rem folds, and — through face_depth_mean_u/v — compute_bt_rem_from_visc_rem’s av_rem.

"hybrid" — DEFAULT since PR-4 (the flip, 2026-10), TOGETHER with visc_rem_chain/hvel_mom6/bbl_glue: MOM6’s btcalc HVEL_SCHEME=HYBRID (the MOM6 default) — arithmetic mean above the shallower of the two abutting columns’ bed depths, harmonic mean below it (with a linear blend across the transition), so a thin partial-bed layer’s weight is suppressed next to a thick abutting layer instead of inflated — closing the docs/visc_rem_bt_rem_plan.md Section 7 gap now that av_rem feeds the barotropic fast loop by default too. Does NOT change derive_bt_from_layers’s use_upstream_h_face branch (a roundabout-only alternative with no MOM6 frhat counterpart) or apply_bt_correction’s uniform fold (no depth mean involved there at all — matches MOM6 accel_layer_u).

"arithmetic" — the pre-port plain two-abutting-cell mean, h_face(k) = 0.5*(h_L(k)+h_R(k)). .false. on all four of hvel_mom6/bbl_glue/visc_rem_chain/frhat_scheme together restores the pre-flip behaviour bit for bit.

integer, public :: n_inner = 0

Mode-split barotropic substeps per outer step. 0 (default) routes the unsplit ocean_dyn_step; >= 1 routes the split solver with that many fast substeps (production ~30-100). Overwritten when auto_n_inner = .true..

real(kind=wp), public :: pc_be = 0.6_wp

pred_corr predictor fraction (MOM6 BE, 0.6 in the control run): the predictor’s provisional velocity advances to BE·dt; only the corrector takes the full step. Unused under ssp_rk2.

logical, public :: renorm_visc_rem = .false.

MOM6 continuity-inversion parity (SPEC §4 S2b): pass visc_rem_u/v into the slow-continuity transport-matching renormaliser, switching it to the γ-weighted du + Jacobian form (u_cor = u + du·γ_k) — a heavily-frictioned layer receives a smaller share of the barotropic correction than an undamped one, and the same weighting lands in the mass fluxes. Behaviour change when on (the flux expression tree gains the γ factors). Self-sufficient (D1 follow-up) — the visc_rem producer runs whenever this is on, decoupled from the retired correction_visc_rem.

logical, public :: rescale_strong_drag = .false.

MOM6 RESCALE_STRONG_DRAG: under strong_drag, bt_rem**n_inner /= av_rem exactly (the rational form is only an approximation), so the barotropic-correction Δu is rescaled by min(bt_rem**n_inner/av_rem, 1.0) before it is folded into the layers, keeping the correction consistent with the TRUE depth-mean remnant. Requires strong_drag = .true.; inert (and refused) otherwise.

character(len=16), public :: split_scheme = "pred_corr"

Outer time-integration scheme for the split-explicit ocean path (SPEC §4 S3/S4). Both schemes are supported and both are under test; they differ in what they cost you.

"pred_corr" (DEFAULT since 2026-09-14) = the MOM6 predictor-corrector. Off-centred predictor at pc_be·dt, slow tendencies (Coriolis-advection, horizontal viscosity) evaluated on the u_av/h_av step time-means, ONE prognostic update in the corrector, forward-backward gravity-wave pairing — neutrally stable to ω·dt = 2, which lifts the internal-wave dt ceiling and removes the resting-state energy growth described below. It preserves the Eady benchmark’s genuine baroclinic mode and makes it slightly stronger (max|v| ×2074 over 60 days vs ×1480). The per-step cost is close to ssp_rk2’s: coriolis_coast (48²×4, 20 simulated days, one V100, quiescent machine) runs 70.3 s under pred_corr against 65.9 s under ssp_rk2 — about 7 % slower, not a different order.

validate_config refuses pred_corr FAIL-LOUD, never silently, outside its v1 envelope: eulerian_z, &ocean_wetdry_nml enable, dt_tracer_advect_ratio > 1. Six shipped namelists pin ssp_rk2 for those reasons.

"ssp_rk2" (EXPERIMENTAL) = two identical stages + SSP average. Still fully supported and fully tested — experimental is a label on the ANSWER, not a deprecation of the code path, and the stability suite runs an ssp_rk2 twin of every case whose namelist does not pin a scheme. It has the widest envelope: every vcoord including eulerian_z, wet/dry, and dt_tracer_advect_ratio > 1 — and it is the only scheme wired through the windowed tracer-advection path, which is why some namelists must pin it.

What it costs you, measured. The two-stage average amplifies an internal gravity wave by √(1 + (ω·dt)⁴/4) per step, so it MANUFACTURES energy from a motionless stratified state. On validation_examples/ocean/eady/resting_stratified_channel.nml (flat bed, periodic, stably stratified, at rest, seeded with ±0.5 mK of noise, NO energy source of any kind) En reaches 2.992E-05 m²/s² by day 25 — 7.7 mm/s of current out of nothing — still climbing on a 2.5-day e-folding, where pred_corr on the identical file holds 1.739E-09 (17 000× less, 83-day e-folding). The cause is the OUTER time splitting and nothing else: the Coriolis form, the ALE remap and the PGF form were each substituted and each moved the answer by < 0.1 % — all three are EXONERATED; removing the stratification dropped En 119×; removing the lateral viscosity RAISED it (viscosity damps the mode, it is not its source).

How to read that number. The error is a (ω·dt)⁴ noise floor, set by how hard dt is pushed against the internal-wave period. A forced, energetic, viscous run sits decades above the floor and never notices it; a quiescent, weakly-damped or long spin-up run does not, and there the manufactured energy is the signal. The suite carries this as a scoped XFAIL on resting_stratified_channel__ssp_rk2, not as institutional memory.

logical, public :: strong_drag = .false.

MOM6 BT_STRONG_DRAG (default .false.): replace the plain power form with the rational approximation bt_rem = mask·n_inner·av_rem/(1 + (n_inner-1)·av_rem), which damps LESS aggressively per substep for a given av_rem — recommended only if the plain av_rem**(1/n_inner) form still leaves the acceptance-gate run unstable (D3, a measured deviation to report, not a default). Requires bt_rem_from_visc_rem = .true.; inert otherwise (checked in validate_config).

logical, public :: substep_drag = .false.

Multiplies the per-face BT velocity update by a damping factor every inner step.

logical, public :: substep_zeta_ke = .true.

When .true. (default, bit-identical) the BT fast loop integrates its LIVE relative vorticity + KE gradient ((ζ_bt+f)·v − ∇KE_bt) every substep. .false. = MOM6 parity: the fast loop carries planetary Coriolis only (f·v_at_u, live velocities); ζ_bt-advection and ∇KE stay FROZEN inside the depth-mean forcing F_bt_*_fast, exactly like MOM6’s q = f/D weights (btstep_find_Cor — planetary only, no ζ, no KE term). The subtract_fast_cor_ref reference reduces to its f·v̄ part so the τ=0 cancellation stays exact. Motivation: the live nonlinear terms host an exponential ~4Δx wall-trapped BT mode on shelf rims (wall-biased KE stencil: wall faces contribute zero KE, so −∇KE points into walls and corners) that the bound_kh-clamped viscosity (λ·dt ≤ bound_coef/8) cannot damp at large dt — the 1024²×100 dt=600 SE-corner column evacuation (h-guard step 106).

logical, public :: upstream_h_face = .false.

When .true., the BT chain uses per-face upstream-PPM column-sum thickness instead of centred h_face. Default .false..

logical, public :: use_cont_type = .false.

When .true., the BT substep uses a piecewise-cubic flux-bounded closure instead of uh = u·h_face. Default .false..

logical, public :: visc_rem_chain = .true.

PR-3 (visc_rem audit + unification, D1 — revised 2026-10 once MOM6 settled the BT-correction fold question): ONE switch for exactly MOM6’s vertvisc_remnant/av_rem/bt_rem set — DEFAULT ON since PR-4 (the flip, 2026-10), TOGETHER with &ocean_vdiff_nml hvel_mom6/bbl_glue — MOM6 gives the barotropic solver the same friction the layered vertical solve applies; without it the BBL glue’s bed piston never reaches the fast mode (NaN at day 253 on the 1-degree Southern Ocean closed-face case under glue-only). .false. restores the pre-PR-4 linear-piston substep_drag behaviour for bt_rem (mutually exclusive with this switch, D2 below) — equivalent to switching on the visc_rem PRODUCER (decoupled from the retired correction_visc_rem, runs whenever any consumer below needs it) plus forcing_visc_rem (MOM6 wt_u), renorm_visc_rem (MOM6 continuity u_cor = u + du*visc_rem, which IS how MOM6 ties visc_rem to a velocity correction) and bt_rem_from_visc_rem (MOM6’s barotropic av_rem/bt_rem viscous-remnant depth mean) all at once. The barotropic-correction fold (apply_bt_correction) stays UNIFORM under this switch — MOM6’s accel_layer_u never weights it by visc_rem, so neither does this chain; see correction_visc_rem’s docstring for why that fold is retired, not folded in here. Same caveat as before: without &ocean_vdiff_nml implicit_drag=.true. or bbl_glue=.true., visc_rem stays ≡ 1 and the whole chain is a legal, warned, no-op. The three *_visc_rem consumer knobs (forcing_visc_rem/renorm_visc_rem/bt_rem_from_visc_rem) stay individually settable (never retired) for the existing fine-grained tests, each an equivalent SUBSET of this switch, never a superset, and each now SELF-SUFFICIENT (no longer “requires” a separate producer knob — the producer runs whenever any one of them is on). strong_drag/ rescale_strong_drag stay separate namelist keys per D1 (MOM6 has its own BT_STRONG_DRAG/RESCALE_STRONG_DRAG parameters for them) and require this switch (or bt_rem_from_visc_rem) on, same as before. accel_visc_rem (&ocean_vdiff_nml) is likewise NOT part of this chain and is RETIRED (see its own docstring) — no MOM6 state-update equivalent. Default .false. ⇒ no answer change (PR-4 is the default flip). D2: mutually exclusive with substep_drag (checked via bt_rem_from_visc_rem’s existing requirement, which this switch satisfies identically to setting it directly).

logical, public :: wave_drag = .false.

Master switch for the barotropic linear (Rayleigh) wave drag — the bulk energy sink for the barotropic tide (Egbert & Ray 2001; Jayne & St Laurent 2001), MOM6 BT_LINEAR_WAVE_DRAG. Default .false. => bit-identical. Composes multiplicatively with substep_drag inside bt_rem_u/v.

character(len=256), public :: wave_drag_file = ""

Reserved for PR-14 (MOM6 BT_WAVE_DRAG_FILE); unused today.

character(len=32), public :: wave_drag_form = "uniform"

r_H filler: “uniform” (global scalar), “roughness_proxy” (resolved-bathymetry-variance proxy for Jayne & St Laurent <h^2>), or “file” (reserved for PR-14’s NetCDF map reader — fail-loud not-implemented today).

real(kind=wp), public :: wave_drag_h2_max = 2.5e4_wp

Ceiling on the resolved-bathymetry <h^2> proxy (m^2; ⇔ h_rms <= 158 m) for wave_drag_form = "roughness_proxy".

real(kind=wp), public :: wave_drag_kappa = 6.2832e-4_wp

Topographic wavenumber kappa (1/m) for wave_drag_form = "roughness_proxy". Same default as &ocean_tidal_mixing_nml kappa_itides — keep them equal.

real(kind=wp), public :: wave_drag_n_bot = 1.0e-3_wp

Reference near-bottom buoyancy frequency N_bot (1/s) for wave_drag_form = "roughness_proxy".

real(kind=wp), public :: wave_drag_r_uniform = 0.0_wp

Piston velocity r_H (m/s) for wave_drag_form = "uniform".

real(kind=wp), public :: wave_drag_scale = 1.0_wp

Global tuning multiplier on r_H (MOM6 BT_WAVE_DRAG_SCALE).

character(len=64), public :: wave_drag_var = "rH"

Reserved for PR-14 (MOM6 BT_WAVE_DRAG_VAR); unused today.


Source Code

   type :: ocean_bt_config_t
      integer :: n_inner = 0
         !! Mode-split barotropic substeps per outer step.  `0` (default)
         !! routes the unsplit `ocean_dyn_step`; `>= 1` routes the split
         !! solver with that many fast substeps (production ~30-100).
         !! Overwritten when `auto_n_inner = .true.`.
      logical :: auto_n_inner = .false.
         !! If `.true.`, derive `n_inner` at setup from the gravity-wave
         !! CFL, evaluated per WET cell (local depth with local cell size,
         !! MOM6 `set_dtbt`; land never limits it).  Default `.false.`.
      real(wp) :: cfl_bt_safety = 0.65_wp
         !! Safety fraction on the shallow-water CFL bound when
         !! `auto_n_inner = .true.` (typical 0.65-0.7).
      real(wp) :: bebt = 0.1_wp
         !! BT continuity-flux velocity projection weight (MOM6 `BEBT`).
         !! Default `0.1` = MOM6's own default for that parameter: damps
         !! the barotropic gravity waves at
         !! `|λ|² = 1 − b·a²` per substep (`a = c·dt_bt·k_eff`), which is
         !! what damps the barotropic grid-scale mode under `pred_corr`.
         !! `0.0` = pure forward-backward Euler (neutral, the pre-2026-09-22
         !! default).  The FB stability limit tightens to
         !! `a ≤ 2/√(1+2·bebt)` (MOM6 `set_dtbt`'s `1+2·BEBT` factor).
      logical :: use_cont_type = .false.
         !! When `.true.`, the BT substep uses a piecewise-cubic
         !! flux-bounded closure instead of `uh = u·h_face`.  Default `.false.`.
      logical :: cont_corr_bounds = .false.
         !! When `.true.` (and `use_cont_type`), the η-correction bound
         !! uses the BT_cont face-by-face flux limits.  Default `.false.`.
      logical :: upstream_h_face = .false.
         !! When `.true.`, the BT chain uses per-face upstream-PPM
         !! column-sum thickness instead of centred `h_face`.  Default `.false.`.
      logical :: correction_h_weighted = .false.
         !! RETIRED (2026-10-02) — setting it `.true.` is a fail-loud
         !! `validate_config` error.  It distributed the barotropic
         !! increment by `h_face(k)/⟨h⟩_h`, which is not energy-conserving:
         !! beyond the barotropic `ΔKE` it adds a positive-definite source
         !! `½Δ²·H·(κ−1)`, `κ = Σh³Σh/(Σh²)²`, plus a shear feedback that
         !! grew the stretched-`z_fixed` 1-degree Southern Ocean to a
         !! non-finite state on day 16.  MOM6 has no h-weighted fold (its
         !! barotropic acceleration is the same in every layer).  The key
         !! stays registered only so the refusal can say why; drag-aware
         !! weighting is `correction_visc_rem`.
      logical :: correction_visc_rem = .false.
         !! RETIRED (2026-10, D1 follow-up) — setting `.true.` is a
         !! fail-loud `validate_config` error.  This used to weight the
         !! BT-corrector fold by `visc_rem(k)/⟨visc_rem⟩_h` instead of
         !! uniformly, but MOM6's barotropic solver gives every layer the
         !! SAME `u_accel_bt` via `accel_layer_u` — no `visc_rem` weight —
         !! folded into `up` BEFORE
         !! `vertvisc` distributes it via the SAME implicit friction the
         !! BBL glue uses, so MOM6 never damps it twice.  This fold did,
         !! and the SECOND, unbounded `visc_rem_k/⟨visc_rem⟩_h` ratio is
         !! what NaNs the 1-degree Southern Ocean z* open-step case under
         !! `bbl_glue` at step ~40 (see `validate_config`'s refusal
         !! message for the measured isolation). `visc_rem_chain` is the
         !! replacement and uses the UNIFORM fold, matching MOM6. The key
         !! stays registered only so the refusal can say why; the
         !! underlying kernel dispatch (`apply_bt_correction`'s
         !! `use_visc_rem`) and its direct unit tests are untouched.
      logical :: visc_rem_chain = .true.
         !! PR-3 (visc_rem audit + unification, D1 — revised 2026-10 once
         !! MOM6 settled the BT-correction fold question): ONE switch for
         !! exactly MOM6's `vertvisc_remnant`/`av_rem`/`bt_rem` set —
         !! DEFAULT ON since PR-4 (the flip, 2026-10), TOGETHER with
         !! `&ocean_vdiff_nml hvel_mom6`/`bbl_glue` — MOM6 gives the
         !! barotropic solver the same friction the layered vertical
         !! solve applies; without it the BBL glue's bed piston never
         !! reaches the fast mode (NaN at day 253 on the 1-degree
         !! Southern Ocean closed-face case under glue-only). `.false.`
         !! restores the pre-PR-4 linear-piston `substep_drag` behaviour
         !! for `bt_rem` (mutually exclusive with this switch, D2 below) —
         !! equivalent to switching on the visc_rem PRODUCER (decoupled
         !! from the retired `correction_visc_rem`, runs whenever any
         !! consumer below needs it) plus `forcing_visc_rem` (MOM6
         !! `wt_u`), `renorm_visc_rem` (MOM6 continuity `u_cor = u +
         !! du*visc_rem`, which IS how MOM6 ties `visc_rem` to a velocity
         !! correction) and `bt_rem_from_visc_rem` (MOM6's barotropic
         !! `av_rem`/`bt_rem` viscous-remnant depth mean) all at once.  The
         !! barotropic-correction fold (`apply_bt_correction`) stays
         !! UNIFORM under this switch — MOM6's `accel_layer_u` never
         !! weights it by `visc_rem`, so neither does this chain; see
         !! `correction_visc_rem`'s docstring for why that fold is
         !! retired, not folded in here.  Same caveat as before: without
         !! `&ocean_vdiff_nml implicit_drag=.true.` or `bbl_glue=.true.`,
         !! `visc_rem` stays ≡ 1 and the whole chain is a legal, warned,
         !! no-op.  The three `*_visc_rem` consumer knobs
         !! (`forcing_visc_rem`/`renorm_visc_rem`/`bt_rem_from_visc_rem`)
         !! stay individually settable (never retired) for the existing
         !! fine-grained tests, each an equivalent SUBSET of this switch,
         !! never a superset, and each now SELF-SUFFICIENT (no longer
         !! "requires" a separate producer knob — the producer runs
         !! whenever any one of them is on).  `strong_drag`/
         !! `rescale_strong_drag` stay separate namelist keys per D1
         !! (MOM6 has its own `BT_STRONG_DRAG`/`RESCALE_STRONG_DRAG`
         !! parameters for them) and require this switch (or
         !! `bt_rem_from_visc_rem`) on, same as before. `accel_visc_rem`
         !! (`&ocean_vdiff_nml`) is likewise NOT part of this chain and is
         !! RETIRED (see its own docstring) — no MOM6 state-update
         !! equivalent. Default `.false.` ⇒ no answer change (PR-4 is the
         !! default flip).  D2: mutually exclusive with `substep_drag`
         !! (checked via `bt_rem_from_visc_rem`'s existing requirement,
         !! which this switch satisfies identically to setting it
         !! directly).
      character(len=16) :: split_scheme = "pred_corr"
         !! Outer time-integration scheme for the split-explicit ocean path
         !! (SPEC §4 S3/S4).  Both schemes are supported and both are under
         !! test; they differ in what they cost you.
         !!
         !! `"pred_corr"` (DEFAULT since 2026-09-14) = the MOM6
         !! predictor-corrector.  Off-centred predictor at `pc_be·dt`, slow
         !! tendencies (Coriolis-advection, horizontal viscosity) evaluated
         !! on the `u_av`/`h_av` step time-means, ONE prognostic update in
         !! the corrector, forward-backward gravity-wave pairing — neutrally
         !! stable to `ω·dt = 2`, which lifts the internal-wave `dt` ceiling
         !! and removes the resting-state energy growth described below.  It
         !! preserves the Eady benchmark's genuine baroclinic mode and makes
         !! it slightly stronger (max|v| ×2074 over 60 days vs ×1480).  The
         !! per-step cost is close to ssp_rk2's: `coriolis_coast` (48²×4, 20
         !! simulated days, one V100, quiescent machine) runs 70.3 s under
         !! pred_corr against 65.9 s under ssp_rk2 — about 7 % slower, not a
         !! different order.
         !!
         !! `validate_config` refuses `pred_corr` FAIL-LOUD, never silently,
         !! outside its v1 envelope: `eulerian_z`, `&ocean_wetdry_nml
         !! enable`, `dt_tracer_advect_ratio > 1`.  Six shipped namelists
         !! pin `ssp_rk2` for those reasons.
         !!
         !! `"ssp_rk2"` (**EXPERIMENTAL**) = two identical stages + SSP
         !! average.  Still fully supported and fully tested — experimental
         !! is a label on the ANSWER, not a deprecation of the code path,
         !! and the stability suite runs an `ssp_rk2` twin of every case
         !! whose namelist does not pin a scheme.  It has the widest
         !! envelope: every vcoord including `eulerian_z`, wet/dry, and
         !! `dt_tracer_advect_ratio > 1` — and it is the only scheme wired
         !! through the windowed tracer-advection path, which is why some
         !! namelists must pin it.
         !!
         !! **What it costs you, measured.**  The two-stage average
         !! amplifies an internal gravity wave by `√(1 + (ω·dt)⁴/4)` per
         !! step, so it MANUFACTURES energy from a motionless stratified
         !! state.  On
         !! `validation_examples/ocean/eady/resting_stratified_channel.nml`
         !! (flat bed, periodic, stably stratified, at rest, seeded with
         !! ±0.5 mK of noise, NO energy source of any kind) En reaches
         !! **2.992E-05 m²/s² by day 25** — 7.7 mm/s of current out of
         !! nothing — still climbing on a 2.5-day e-folding, where
         !! `pred_corr` on the identical file holds **1.739E-09** (17 000×
         !! less, 83-day e-folding).  The cause is the OUTER time splitting
         !! and nothing else: the Coriolis form, the ALE remap and the PGF
         !! form were each substituted and each moved the answer by < 0.1 %
         !! — all three are EXONERATED; removing the stratification dropped
         !! En 119×; removing the lateral viscosity RAISED it (viscosity
         !! damps the mode, it is not its source).
         !!
         !! **How to read that number.**  The error is a `(ω·dt)⁴` noise
         !! floor, set by how hard `dt` is pushed against the internal-wave
         !! period.  A forced, energetic, viscous run sits decades above the
         !! floor and never notices it; a quiescent, weakly-damped or long
         !! spin-up run does not, and there the manufactured energy is the
         !! signal.  The suite carries this as a scoped XFAIL on
         !! `resting_stratified_channel__ssp_rk2`, not as institutional
         !! memory.
      real(wp) :: pc_be = 0.6_wp
         !! pred_corr predictor fraction (MOM6 `BE`, 0.6 in the control run):
         !! the predictor's provisional velocity advances to `BE·dt`; only
         !! the corrector takes the full step.  Unused under ssp_rk2.
      logical :: renorm_visc_rem = .false.
         !! MOM6 continuity-inversion parity (SPEC §4 S2b): pass
         !! `visc_rem_u/v` into the slow-continuity transport-matching
         !! renormaliser, switching it to the γ-weighted `du` + Jacobian
         !! form (`u_cor = u + du·γ_k`)
         !! — a heavily-frictioned layer receives a smaller share of the
         !! barotropic correction than an undamped one, and the same
         !! weighting lands in the mass fluxes.  Behaviour change when
         !! on (the flux expression tree gains the γ factors).
         !! Self-sufficient (D1 follow-up) — the visc_rem producer runs
         !! whenever this is on, decoupled from the retired
         !! `correction_visc_rem`.
      logical :: forcing_visc_rem = .false.
         !! MOM6 `wt_u` parity for the BT FORCING assembly:
         !! weight each layer's
         !! contribution to `F_bt_u/v` (and to the PGF-projection
         !! subtraction, which must use the same weights) by
         !! `h_face·visc_rem(k)` instead of `h_face`.  Layers the implicit
         !! friction will immediately damp — grounded sliver stacks under
         !! the vdiff BBL glue — then contribute nothing to the fast loop,
         !! which otherwise integrates the spurious grounded-layer PGF's
         !! depth-mean ballistically (PGF_BUG.md §9).
         !! Self-sufficient (D1 follow-up) — the visc_rem producer runs
         !! whenever this is on, decoupled from the retired
         !! `correction_visc_rem`.
      logical :: bt_rem_from_visc_rem = .false.
         !! PR-2 (bt-rem-from-av-rem): `bt_rem_u/v` built from the SAME
         !! viscous remnant the layered momentum solve uses, instead of
         !! the linear-piston `substep_drag` law or the static `1.0`
         !! no-op. Matches MOM6's barotropic solver:
         !! `av_rem = Σ_k frhat_k·visc_rem_k` (the visc_rem depth mean,
         !! `frhat_k` the face layer fraction `derive_bt_from_layers`
         !! already uses), then `bt_rem = mask·av_rem**(1/n_inner)`
         !! (zero where `mask·av_rem <= 0`), built ONCE per barotropic
         !! call — after the visc_rem producer, before the inner
         !! substeps — into the existing `bt_work%bt_rem_u/v` multiplier
         !! the substep loop already reads.  This is the fix for the
         !! bbl_glue day-253 instability (the barotropic solver was
         !! seeing weak explicit drag while the layers were strongly
         !! glued): the fast mode now feels the SAME friction.  Default
         !! `.false.` ⇒ no answer change.  Self-sufficient (D1
         !! follow-up) — the visc_rem producer runs whenever this is on,
         !! decoupled from the retired `correction_visc_rem`; mutually
         !! exclusive with
         !! `substep_drag` (bed drag would be double-counted — once
         !! inside visc_rem via the glue/implicit_drag fold, once again
         !! via the linear piston) and with `bt_halo > 0` (the wide-halo
         !! BT clone's `metrics_w`/halo-widened arrays carry no
         !! `av_rem`/`visc_rem` ghost width yet — same posture as
         !! porous). Composes with `wave_drag` (multiplied in after).
      logical :: strong_drag = .false.
         !! MOM6 `BT_STRONG_DRAG` (default `.false.`):
         !! replace the plain power form with the rational approximation
         !! `bt_rem = mask·n_inner·av_rem/(1 + (n_inner-1)·av_rem)`, which
         !! damps LESS aggressively per substep for a given `av_rem` —
         !! recommended only if the plain `av_rem**(1/n_inner)` form still
         !! leaves the acceptance-gate run unstable (D3, a measured
         !! deviation to report, not a default).  Requires
         !! `bt_rem_from_visc_rem = .true.`; inert otherwise (checked in
         !! `validate_config`).
      logical :: rescale_strong_drag = .false.
         !! MOM6 `RESCALE_STRONG_DRAG`: under `strong_drag`,
         !! `bt_rem**n_inner /= av_rem` exactly (the rational form is only
         !! an approximation), so the barotropic-correction `Δu` is
         !! rescaled by `min(bt_rem**n_inner/av_rem, 1.0)` before it is
         !! folded into the layers, keeping the correction consistent
         !! with the TRUE depth-mean remnant.  Requires `strong_drag =
         !! .true.`; inert (and refused) otherwise.
      character(len=16) :: frhat_scheme = "hybrid"
         !! `rdb_barotropic_coupling::frhat_h_face_step`'s per-layer
         !! face-thickness closure for EVERY barotropic depth mean that
         !! reads a layer's face thickness: `derive_bt_from_layers`,
         !! `face_depth_mean_u/v`, `face_depth_mean_rem_u/v`
         !! (`forcing_visc_rem`'s `wt_u`), `apply_bt_correction`'s
         !! open/visc_rem folds, and — through `face_depth_mean_u/v` —
         !! `compute_bt_rem_from_visc_rem`'s `av_rem`.
         !!
         !! `"hybrid"` — DEFAULT since PR-4 (the flip, 2026-10), TOGETHER
         !! with `visc_rem_chain`/`hvel_mom6`/`bbl_glue`: MOM6's `btcalc`
         !! `HVEL_SCHEME=HYBRID` (the MOM6 default) — arithmetic mean above the
         !! shallower of the two abutting columns' bed depths, harmonic
         !! mean below it (with a linear blend across the transition), so
         !! a thin partial-bed layer's weight is suppressed next to a
         !! thick abutting layer instead of inflated — closing the
         !! `docs/visc_rem_bt_rem_plan.md` Section 7 gap now that `av_rem`
         !! feeds the barotropic fast loop by default too. Does NOT change
         !! `derive_bt_from_layers`'s `use_upstream_h_face` branch (a
         !! roundabout-only alternative with no MOM6 frhat counterpart) or
         !! `apply_bt_correction`'s uniform fold (no depth mean involved
         !! there at all — matches MOM6 `accel_layer_u`).
         !!
         !! `"arithmetic"` — the pre-port plain two-abutting-cell mean,
         !! `h_face(k) = 0.5*(h_L(k)+h_R(k))`. `.false.` on all four of
         !! `hvel_mom6`/`bbl_glue`/`visc_rem_chain`/`frhat_scheme` together
         !! restores the pre-flip behaviour bit for bit.
      logical :: correction_bc_pgf = .false.
         !! Adds a per-layer baroclinic-PGF retro-correction for the η
         !! change during the BT substep.  Requires `pgf%form = "fv_mom6"`.
      logical :: bc_pgf_forcing = .true.
         !! MOM6 split (`BT_force` + `eta_PF`): the barotropic substep is
         !! forced by the depth mean of the FULL slow PGF — baroclinic
         !! part included — minus only the free-surface term that PGF
         !! itself carries at the η it was evaluated on (none for the
         !! surface-relative MONT/FV_LITE/FV_WRIGHT forms), which the
         !! substep's own `-g_bt·∇η` replaces.  `.false.` restores the legacy split, which
         !! subtracted the WHOLE depth-mean PGF and so never let the
         !! barotropic mode feel the baroclinic bottom-pressure gradient
         !! (JEBAR; ~0 Sv through Drake Passage on the global 1° case
         !! against MOM6's ~160 Sv).
      logical :: substep_drag = .false.
         !! Multiplies the per-face BT velocity update by a damping factor
         !! every inner step.
      logical :: substep_zeta_ke = .true.
         !! When `.true.` (default, bit-identical) the BT fast loop
         !! integrates its LIVE relative vorticity + KE gradient
         !! (`(ζ_bt+f)·v − ∇KE_bt`) every substep.  `.false.` = MOM6
         !! parity: the fast loop carries planetary Coriolis only
         !! (`f·v_at_u`, live velocities); ζ_bt-advection and ∇KE stay
         !! FROZEN inside the depth-mean forcing `F_bt_*_fast`, exactly
         !! like MOM6's `q = f/D` weights (`btstep_find_Cor` — planetary
         !! only, no ζ, no KE term).  The
         !! `subtract_fast_cor_ref` reference reduces to its `f·v̄` part
         !! so the τ=0 cancellation stays exact.  Motivation: the live
         !! nonlinear terms host an exponential ~4Δx wall-trapped BT
         !! mode on shelf rims (wall-biased KE stencil: wall faces
         !! contribute zero KE, so −∇KE points into walls and corners)
         !! that the `bound_kh`-clamped viscosity (λ·dt ≤ bound_coef/8)
         !! cannot damp at large dt — the 1024²×100 dt=600 SE-corner
         !! column evacuation (h-guard step 106).
      logical :: wave_drag = .false.
         !! Master switch for the barotropic linear (Rayleigh) wave drag
         !! — the bulk energy sink for the barotropic tide (Egbert & Ray
         !! 2001; Jayne & St Laurent 2001), MOM6 `BT_LINEAR_WAVE_DRAG`.
         !! Default `.false.` => bit-identical.  Composes multiplicatively
         !! with `substep_drag` inside `bt_rem_u/v`.
      character(len=32) :: wave_drag_form = "uniform"
         !! `r_H` filler: "uniform" (global scalar), "roughness_proxy"
         !! (resolved-bathymetry-variance proxy for Jayne & St Laurent
         !! `<h^2>`), or "file" (reserved for PR-14's NetCDF map reader —
         !! fail-loud not-implemented today).
      real(wp) :: wave_drag_scale = 1.0_wp
         !! Global tuning multiplier on `r_H` (MOM6 `BT_WAVE_DRAG_SCALE`).
      real(wp) :: wave_drag_r_uniform = 0.0_wp
         !! Piston velocity `r_H` (m/s) for `wave_drag_form = "uniform"`.
      real(wp) :: wave_drag_kappa = 6.2832e-4_wp
         !! Topographic wavenumber `kappa` (1/m) for
         !! `wave_drag_form = "roughness_proxy"`.  Same default as
         !! `&ocean_tidal_mixing_nml kappa_itides` — keep them equal.
      real(wp) :: wave_drag_n_bot = 1.0e-3_wp
         !! Reference near-bottom buoyancy frequency `N_bot` (1/s) for
         !! `wave_drag_form = "roughness_proxy"`.
      real(wp) :: wave_drag_h2_max = 2.5e4_wp
         !! Ceiling on the resolved-bathymetry `<h^2>` proxy (m^2; ⇔
         !! h_rms <= 158 m) for `wave_drag_form = "roughness_proxy"`.
      character(len=256) :: wave_drag_file = ""
         !! Reserved for PR-14 (MOM6 `BT_WAVE_DRAG_FILE`); unused today.
      character(len=64) :: wave_drag_var = "rH"
         !! Reserved for PR-14 (MOM6 `BT_WAVE_DRAG_VAR`); unused today.
      integer :: bt_halo = BT_HALO_AUTO_SENTINEL
         !! Wide-halo march-in width.  `-1` (default) = AUTO, which resolves
         !! to 0: the march-in is OPT-IN.  A decomposed run with the march-in
         !! is not bit-identical to the serial run over variable bathymetry or
         !! with open boundaries (measured by `test_ocean_decomp_bitid_mpi`;
         !! flat-bottom closed / periodic / spherical cases are), so the
         !! default keeps every decomposition bit-reproducible.  `8`
         !! (`BT_HALO_AUTO_WIDTH`) is the recommended explicit width.
         !! `0` = explicit off (per-substep grouped exchange, v1 bit-identical).
         !! Even positive value: widen the BT ghost band to `nghost + bt_halo`
         !! and fire one grouped exchange every `bt_halo/2` substeps.  Odd
         !! values are rounded DOWN to even with a warning.  Fail-loud
         !! exclusions (apply when bt_halo is set EXPLICITLY > 0; AUTO instead
         !! silently resolves to 0): wet/dry enable, use_cont_type,
         !! upstream_h_face, tides enable, psurf enable, porous enable,
         !! cavity_dyn enable, supergrid/tripolar grid_config.  The set of record is
         !! `bt_halo_auto_exclusion`; keep it and the `validate_config`
         !! checks in lockstep.
   end type ocean_bt_config_t