continuity_tracer_step_split Subroutine

public subroutine continuity_tracer_step_split(grid, metrics, this, ms, dt, uhbt, vhbt, bc, mle, mle_fold_active, tracer_mode, h_min, visc_rem_u, visc_rem_v, u_cor, v_cor)

Production entry point for the directionally-split continuity + tracer step. Interleaves the two so the CWC discrete theorem holds in the split form:

  1. zonal_flux — Φx from h^n
  2. tracer_advect_zonal — hTr ← hTr - dt·∂(Φx·T)/∂x at h^n
  3. apply_zonal — h ← h^n - dt·∂Φx/∂x (= h^*)
  4. meridional_flux — Φy from h^*
  5. tracer_advect_meridional — hTr ← hTr - dt·∂(Φy·T)/∂y at h^*
  6. apply_meridional — h ← h^* - dt·∂Φy/∂y (= h^{n+1})

Uniform T preserved: after step 2, hTr = (h - dt·div_x)·T; after step 3, h = h - dt·div_x, so hTr/h = T still. After step 5, hTr = (h^* - dt·div_y)·T = h^{n+1}·T. After step 6, hTr/h = T. Same CWC theorem as the unsplit form, lifted per direction.

flux_h_layer ends the step holding the total horizontal divergence (sum of x and y substeps) — that’s what the vertical-advection kernel consumes for w_interface.

Optional uhbt, vhbt: time-mean barotropic-substep transports. When supplied, the per-layer mass fluxes are renormalised so Σ_k Φx_k = uhbt and Σ_k Φy_k = vhbt, making the slow continuity advance h_layer consistently with the fast loop’s η_end — MOM6’s split-explicit pattern. The same constrained fluxes feed tracer advection, so per-column T = hTr/h stays uniform under the constraint.

Arguments

Type IntentOptional Attributes Name
type(hgrid_t), intent(in) :: grid
type(ocean_metrics_t), intent(in) :: metrics
type(continuity_t), intent(inout) :: this
type(multilayer_state_t), intent(inout) :: ms
real(kind=wp), intent(in) :: dt
real(kind=wp), intent(in), optional :: uhbt(:,:)
real(kind=wp), intent(in), optional :: vhbt(:,:)
type(ocean_bc_state_t), intent(in), optional :: bc

When present, per-edge OBC tags gate the wall-zero step inside the flux kernels. OBC_WALL keeps the Phase 3 closure; OBC_OPEN (and other non-wall tags) leaves the computed mass flux at the wall face for the downstream transport. Absent ⇒ closed-wall everywhere.

type(ocean_mle_t), intent(in), optional :: mle

Fox-Kemper mixed-layer-eddy transports (B5). When present and enabled, mle%uhml/vhml are folded into the per-layer mass fluxes AFTER each direction’s flux fill and BEFORE the matching tracer advect + divergence — so the augmented flux transports both h and tracers (conservative; velocity untouched). Absent / disabled ⇒ bit-identical no-op.

logical, intent(in), optional :: mle_fold_active

Gates the Fox-Kemper fold to the THERMO cadence. Absent or .true. ⇒ the fold applies (bit-identical default — the case at dt_therm_ratio = 1, where every step is a thermo step). .false. skips the fold so the stale FK transports (computed once per thermo interval) are NOT re-applied on the intervening non-thermo outer steps when dt_therm_ratio > 1.

integer, intent(in), optional :: tracer_mode

Phase 2 (6b) windowed-advection mode. TR_MODE_ADVECT (default, absent) ⇒ the historical fused path: advance h AND advect tracers each call (bit-identical to pre-6b). TR_MODE_ACCUMULATE ⇒ advance h, accumulate 0.5·mass_flux·dt into this%uhtr/vhtr (one += per RK2 stage, weight 0.5 baked in — closes the reconstruction against the RK2-averaged h), and SKIP the per-step tracer advect so hTr stays frozen until the boundary drain.

real(kind=wp), intent(in), optional :: h_min

Phase-1 Lagrangian minimum-thickness floor (m). When > 0, passed to continuity_apply_zonal/_meridional to clamp h_new >= h_min. Absent or 0 ⇒ off ⇒ bit-identical.

real(kind=wp), intent(in), optional :: visc_rem_u(:,:,:)

Per-layer viscous remnant gamma_k on east / north faces. Forwarded to the flux renormalisers, where it weights the barotropic increment (MOM6 u_cor = u + du*visc_rem). Absent => gamma == 1, bit-identical.

real(kind=wp), intent(in), optional :: visc_rem_v(:,:,:)

Per-layer viscous remnant gamma_k on east / north faces. Forwarded to the flux renormalisers, where it weights the barotropic increment (MOM6 u_cor = u + du*visc_rem). Absent => gamma == 1, bit-identical.

real(kind=wp), intent(inout), optional :: u_cor(:,:,:)

MOM6 u_cor/v_cor destinations — the step TIME-MEAN velocity (u_av/v_av), never the prognostic. Absent => flux-only.

real(kind=wp), intent(inout), optional :: v_cor(:,:,:)

MOM6 u_cor/v_cor destinations — the step TIME-MEAN velocity (u_av/v_av), never the prognostic. Absent => flux-only.


Calls

proc~~continuity_tracer_step_split~~CallsGraph proc~continuity_tracer_step_split continuity_tracer_step_split interface~ocean_fold_north_v_face ocean_fold_north_v_face proc~continuity_tracer_step_split->interface~ocean_fold_north_v_face interface~ocean_halo_centre ocean_halo_centre proc~continuity_tracer_step_split->interface~ocean_halo_centre proc~accumulate_flux_x accumulate_flux_x proc~continuity_tracer_step_split->proc~accumulate_flux_x proc~accumulate_flux_y accumulate_flux_y proc~continuity_tracer_step_split->proc~accumulate_flux_y proc~continuity_apply_meridional continuity_apply_meridional proc~continuity_tracer_step_split->proc~continuity_apply_meridional proc~continuity_apply_zonal continuity_apply_zonal proc~continuity_tracer_step_split->proc~continuity_apply_zonal proc~continuity_meridional_flux continuity_meridional_flux proc~continuity_tracer_step_split->proc~continuity_meridional_flux proc~continuity_zonal_flux continuity_zonal_flux proc~continuity_tracer_step_split->proc~continuity_zonal_flux proc~drain_copy_3d drain_copy_3d proc~continuity_tracer_step_split->proc~drain_copy_3d proc~drain_rescale_htr drain_rescale_hTr proc~continuity_tracer_step_split->proc~drain_rescale_htr proc~drain_rescale_htr_budget drain_rescale_hTr_budget proc~continuity_tracer_step_split->proc~drain_rescale_htr_budget proc~mle_fold_x mle_fold_x proc~continuity_tracer_step_split->proc~mle_fold_x proc~mle_fold_y mle_fold_y proc~continuity_tracer_step_split->proc~mle_fold_y proc~ocean_bc_outer_face_tag ocean_bc_outer_face_tag proc~continuity_tracer_step_split->proc~ocean_bc_outer_face_tag proc~ocean_fold_wrap_centre_3d_state ocean_fold_wrap_centre_3d_state proc~continuity_tracer_step_split->proc~ocean_fold_wrap_centre_3d_state proc~ocean_halo_is_decomposed_x ocean_halo_is_decomposed_x proc~continuity_tracer_step_split->proc~ocean_halo_is_decomposed_x proc~ocean_halo_is_decomposed_y ocean_halo_is_decomposed_y proc~continuity_tracer_step_split->proc~ocean_halo_is_decomposed_y proc~ocean_periodic_wrap_centre_3d ocean_periodic_wrap_centre_3d proc~continuity_tracer_step_split->proc~ocean_periodic_wrap_centre_3d proc~pd_limit_meridional_impl pd_limit_meridional_impl proc~continuity_tracer_step_split->proc~pd_limit_meridional_impl proc~pd_limit_zonal_impl pd_limit_zonal_impl proc~continuity_tracer_step_split->proc~pd_limit_zonal_impl proc~profiler_start profiler_start proc~continuity_tracer_step_split->proc~profiler_start proc~profiler_stop profiler_stop proc~continuity_tracer_step_split->proc~profiler_stop proc~tracer_advect_meridional tracer_advect_meridional proc~continuity_tracer_step_split->proc~tracer_advect_meridional proc~tracer_advect_zonal tracer_advect_zonal proc~continuity_tracer_step_split->proc~tracer_advect_zonal proc~fold_v_2d fold_v_2d interface~ocean_fold_north_v_face->proc~fold_v_2d proc~fold_v_3d fold_v_3d interface~ocean_fold_north_v_face->proc~fold_v_3d proc~ocean_halo_centre_2d ocean_halo_centre_2d interface~ocean_halo_centre->proc~ocean_halo_centre_2d proc~ocean_halo_centre_3d ocean_halo_centre_3d interface~ocean_halo_centre->proc~ocean_halo_centre_3d proc~continuity_meridional_flux->proc~ocean_bc_outer_face_tag local local proc~continuity_meridional_flux->local proc~ppm_cell_limiter ppm_cell_limiter proc~continuity_meridional_flux->proc~ppm_cell_limiter proc~ppm_limit_pos ppm_limit_pos proc~continuity_meridional_flux->proc~ppm_limit_pos proc~ppm_limited_slope ppm_limited_slope proc~continuity_meridional_flux->proc~ppm_limited_slope proc~ppm_mirror_h ppm_mirror_h proc~continuity_meridional_flux->proc~ppm_mirror_h proc~renormalise_meridional_flux_to_vhbt renormalise_meridional_flux_to_vhbt proc~continuity_meridional_flux->proc~renormalise_meridional_flux_to_vhbt proc~volcfl_face volcfl_face proc~continuity_meridional_flux->proc~volcfl_face proc~continuity_zonal_flux->proc~ocean_bc_outer_face_tag proc~continuity_zonal_flux->local proc~continuity_zonal_flux->proc~ppm_cell_limiter proc~continuity_zonal_flux->proc~ppm_limit_pos proc~continuity_zonal_flux->proc~ppm_limited_slope proc~continuity_zonal_flux->proc~ppm_mirror_h proc~renormalise_zonal_flux_to_uhbt renormalise_zonal_flux_to_uhbt proc~continuity_zonal_flux->proc~renormalise_zonal_flux_to_uhbt proc~continuity_zonal_flux->proc~volcfl_face proc~drain_rescale_htr_budget->local interface~fold_north_centre fold_north_centre proc~ocean_fold_wrap_centre_3d_state->interface~fold_north_centre interface~ocean_fold_pack ocean_fold_pack proc~ocean_fold_wrap_centre_3d_state->interface~ocean_fold_pack interface~ocean_fold_unpack ocean_fold_unpack proc~ocean_fold_wrap_centre_3d_state->interface~ocean_fold_unpack proc~n_tracers n_tracers proc~ocean_fold_wrap_centre_3d_state->proc~n_tracers proc~ocean_fold_begin ocean_fold_begin proc~ocean_fold_wrap_centre_3d_state->proc~ocean_fold_begin proc~ocean_fold_end ocean_fold_end proc~ocean_fold_wrap_centre_3d_state->proc~ocean_fold_end proc~ocean_fold_exchange ocean_fold_exchange proc~ocean_fold_wrap_centre_3d_state->proc~ocean_fold_exchange proc~ocean_fold_is_distributed ocean_fold_is_distributed proc~ocean_fold_wrap_centre_3d_state->proc~ocean_fold_is_distributed proc~pd_limit_meridional_impl->local reduce reduce proc~pd_limit_meridional_impl->reduce proc~pd_limit_zonal_impl->local proc~pd_limit_zonal_impl->reduce proc~find_or_create_region find_or_create_region proc~profiler_start->proc~find_or_create_region proc~get_wall_time get_wall_time proc~profiler_start->proc~get_wall_time proc~nvtx_range_push nvtx_range_push proc~profiler_start->proc~nvtx_range_push proc~profiler_stop->proc~get_wall_time proc~nvtx_range_pop nvtx_range_pop proc~profiler_stop->proc~nvtx_range_pop proc~tracer_advect_meridional_one_impl tracer_advect_meridional_one_impl proc~tracer_advect_meridional->proc~tracer_advect_meridional_one_impl proc~tracer_advect_zonal_one_impl tracer_advect_zonal_one_impl proc~tracer_advect_zonal->proc~tracer_advect_zonal_one_impl proc~fold_north_centre_2d fold_north_centre_2d interface~fold_north_centre->proc~fold_north_centre_2d proc~fold_north_centre_3d fold_north_centre_3d interface~fold_north_centre->proc~fold_north_centre_3d proc~ocean_fold_pack_2d ocean_fold_pack_2d interface~ocean_fold_pack->proc~ocean_fold_pack_2d proc~ocean_fold_pack_3d ocean_fold_pack_3d interface~ocean_fold_pack->proc~ocean_fold_pack_3d proc~ocean_fold_unpack_2d ocean_fold_unpack_2d interface~ocean_fold_unpack->proc~ocean_fold_unpack_2d proc~ocean_fold_unpack_3d ocean_fold_unpack_3d interface~ocean_fold_unpack->proc~ocean_fold_unpack_3d proc~fold_v_2d->interface~ocean_fold_pack proc~fold_v_2d->interface~ocean_fold_unpack proc~fold_v_2d->proc~ocean_fold_begin proc~fold_v_2d->proc~ocean_fold_end proc~fold_v_2d->proc~ocean_fold_exchange proc~fold_v_2d->proc~ocean_fold_is_distributed interface~fold_north_v_face fold_north_v_face proc~fold_v_2d->interface~fold_north_v_face proc~fold_stagger_nrows fold_stagger_nrows proc~fold_v_2d->proc~fold_stagger_nrows proc~fold_v_3d->interface~ocean_fold_pack proc~fold_v_3d->interface~ocean_fold_unpack proc~fold_v_3d->proc~ocean_fold_begin proc~fold_v_3d->proc~ocean_fold_end proc~fold_v_3d->proc~ocean_fold_exchange proc~fold_v_3d->proc~ocean_fold_is_distributed proc~fold_v_3d->interface~fold_north_v_face proc~fold_v_3d->proc~fold_stagger_nrows proc~grow_buffers grow_buffers proc~ocean_fold_begin->proc~grow_buffers to_string to_string proc~ocean_fold_begin->to_string warning warning proc~ocean_fold_begin->warning comm_irecv_real_sp_array_n comm_irecv_real_sp_array_n proc~ocean_fold_exchange->comm_irecv_real_sp_array_n comm_isend_real_sp_array_n comm_isend_real_sp_array_n proc~ocean_fold_exchange->comm_isend_real_sp_array_n proc~comm_env_compute_comm comm_env_compute_comm proc~ocean_fold_exchange->proc~comm_env_compute_comm waitall waitall proc~ocean_fold_exchange->waitall proc~ocean_halo_centre_2d_impl ocean_halo_centre_2d_impl proc~ocean_halo_centre_2d->proc~ocean_halo_centre_2d_impl proc~oh_count_centre_2d oh_count_centre_2d proc~ocean_halo_centre_2d->proc~oh_count_centre_2d proc~ocean_halo_centre_3d->proc~ocean_periodic_wrap_centre_3d proc~ocean_halo_centre_3d->comm_irecv_real_sp_array_n proc~ocean_halo_centre_3d->comm_isend_real_sp_array_n proc~ocean_halo_centre_3d->proc~comm_env_compute_comm proc~ew_rank_east ew_rank_east proc~ocean_halo_centre_3d->proc~ew_rank_east proc~ew_rank_west ew_rank_west proc~ocean_halo_centre_3d->proc~ew_rank_west proc~needs_flags needs_flags proc~ocean_halo_centre_3d->proc~needs_flags proc~ns_rank_north ns_rank_north proc~ocean_halo_centre_3d->proc~ns_rank_north proc~ns_rank_south ns_rank_south proc~ocean_halo_centre_3d->proc~ns_rank_south proc~ocean_halo_buffers_ensure_nz ocean_halo_buffers_ensure_nz proc~ocean_halo_centre_3d->proc~ocean_halo_buffers_ensure_nz proc~oh_count_centre_3d oh_count_centre_3d proc~ocean_halo_centre_3d->proc~oh_count_centre_3d proc~oh_count_msgs oh_count_msgs proc~ocean_halo_centre_3d->proc~oh_count_msgs proc~ocean_halo_centre_3d->waitall proc~renormalise_meridional_flux_to_vhbt->local proc~renormalise_zonal_flux_to_uhbt->local proc~tracer_advect_meridional_one_impl->local proc~tracer_advect_meridional_one_impl->proc~ppm_cell_limiter proc~tracer_advect_meridional_one_impl->proc~ppm_limited_slope proc~tracer_advect_meridional_one_impl->proc~ppm_mirror_h proc~tracer_advect_zonal_one_impl->local proc~tracer_advect_zonal_one_impl->proc~ppm_cell_limiter proc~tracer_advect_zonal_one_impl->proc~ppm_limited_slope proc~tracer_advect_zonal_one_impl->proc~ppm_mirror_h proc~fold_north_v_face_2d fold_north_v_face_2d interface~fold_north_v_face->proc~fold_north_v_face_2d proc~fold_north_v_face_3d fold_north_v_face_3d interface~fold_north_v_face->proc~fold_north_v_face_3d comm_world comm_world proc~comm_env_compute_comm->comm_world proc~decomp_rank_from_coords decomp_rank_from_coords proc~ew_rank_east->proc~decomp_rank_from_coords proc~ew_rank_west->proc~decomp_rank_from_coords proc~ns_rank_north->proc~decomp_rank_from_coords proc~ns_rank_south->proc~decomp_rank_from_coords proc~ocean_fold_pack_2d->proc~ocean_fold_pack_3d proc~ocean_fold_pack_3d->proc~fold_stagger_nrows proc~fold_stagger_family fold_stagger_family proc~ocean_fold_pack_3d->proc~fold_stagger_family proc~ocean_fold_unpack_2d->proc~ocean_fold_unpack_3d proc~ocean_fold_unpack_3d->proc~fold_stagger_nrows proc~ocean_fold_unpack_3d->proc~fold_stagger_family proc~ocean_halo_buffers_ensure_nz->to_string proc~ocean_halo_buffers_ensure_nz->warning proc~ocean_halo_centre_2d_impl->comm_irecv_real_sp_array_n proc~ocean_halo_centre_2d_impl->comm_isend_real_sp_array_n proc~ocean_halo_centre_2d_impl->proc~comm_env_compute_comm proc~ocean_halo_centre_2d_impl->proc~ew_rank_east proc~ocean_halo_centre_2d_impl->proc~ew_rank_west proc~ocean_halo_centre_2d_impl->proc~needs_flags proc~ocean_halo_centre_2d_impl->proc~ns_rank_north proc~ocean_halo_centre_2d_impl->proc~ns_rank_south proc~ocean_halo_centre_2d_impl->proc~oh_count_msgs proc~ocean_halo_centre_2d_impl->waitall proc~ocean_periodic_wrap_centre_2d ocean_periodic_wrap_centre_2d proc~ocean_halo_centre_2d_impl->proc~ocean_periodic_wrap_centre_2d proc~fold_north_v_face_2d->local proc~fold_north_v_face_3d->local

Called by

proc~~continuity_tracer_step_split~~CalledByGraph proc~continuity_tracer_step_split continuity_tracer_step_split proc~run_continuity_chain run_continuity_chain proc~run_continuity_chain->proc~continuity_tracer_step_split proc~run_stage run_stage proc~run_stage->proc~continuity_tracer_step_split proc~ocean_dyn_step ocean_dyn_step proc~ocean_dyn_step->proc~run_stage proc~run_stage_split run_stage_split proc~run_stage_split->proc~run_continuity_chain proc~engine_step engine_step proc~engine_step->proc~ocean_dyn_step proc~ocean_dyn_step_split ocean_dyn_step_split proc~engine_step->proc~ocean_dyn_step_split proc~ocean_dyn_step_split->proc~run_stage_split proc~driver_run_ocean driver_run_ocean proc~driver_run_ocean->proc~engine_step proc~rdb_ocean_step rdb_ocean_step proc~rdb_ocean_step->proc~engine_step proc~driver_run driver_run proc~driver_run->proc~driver_run_ocean

Variables

Type Visibility Attributes Name Initial
integer, private :: bc_e_tag
integer, private :: bc_n_tag
integer, private :: bc_s_tag
integer, private :: bc_w_tag
logical, private :: do_mle_fold
logical, private :: fold_wall
real(kind=wp), private :: h_min_use
integer, private :: ii
integer, private :: it
integer, private :: it_cw
integer, private :: jj
integer, private :: kk
integer, private :: mode
integer, private :: nghost
integer, private :: nx
integer, private :: nx_phys
integer, private :: ny
integer, private :: ny_phys
integer, private :: nz
logical, private :: per_x
logical, private :: per_y

Source Code

   subroutine continuity_tracer_step_split(grid, metrics, this, ms, dt, uhbt, vhbt, bc, mle, mle_fold_active, &
                                           tracer_mode, h_min, visc_rem_u, visc_rem_v, u_cor, v_cor)
      !! Production entry point for the directionally-split
      !! continuity + tracer step.  Interleaves the two so the
      !! CWC discrete theorem holds in the split form:
      !!
      !!   1. zonal_flux        — Φx from h^n
      !!   2. tracer_advect_zonal — hTr ← hTr - dt·∂(Φx·T)/∂x at h^n
      !!   3. apply_zonal       — h ← h^n - dt·∂Φx/∂x  (= h^*)
      !!   4. meridional_flux   — Φy from h^*
      !!   5. tracer_advect_meridional — hTr ← hTr - dt·∂(Φy·T)/∂y at h^*
      !!   6. apply_meridional  — h ← h^* - dt·∂Φy/∂y  (= h^{n+1})
      !!
      !! Uniform T preserved: after step 2, hTr = (h - dt·div_x)·T;
      !! after step 3, h = h - dt·div_x, so hTr/h = T still.  After
      !! step 5, hTr = (h^* - dt·div_y)·T = h^{n+1}·T.  After step 6,
      !! hTr/h = T.  Same CWC theorem as the unsplit form, lifted
      !! per direction.
      !!
      !! `flux_h_layer` ends the step holding the total horizontal
      !! divergence (sum of x and y substeps) — that's what the
      !! vertical-advection kernel consumes for w_interface.
      !!
      !! Optional `uhbt, vhbt`: time-mean barotropic-substep transports.  When
      !! supplied, the per-layer mass fluxes are renormalised so
      !! `Σ_k Φx_k = uhbt` and `Σ_k Φy_k = vhbt`, making the slow
      !! continuity advance `h_layer` consistently with the fast
      !! loop's `η_end` — MOM6's split-explicit pattern.  The same
      !! constrained fluxes feed tracer advection, so per-column
      !! `T = hTr/h` stays uniform under the constraint.
      type(hgrid_t), intent(in) :: grid
      type(ocean_metrics_t), intent(in) :: metrics
      type(continuity_t), intent(inout) :: this
      type(multilayer_state_t), intent(inout) :: ms
      real(wp), intent(in) :: dt
      real(wp), intent(in), optional :: uhbt(:, :)
      real(wp), intent(in), optional :: vhbt(:, :)
      type(ocean_bc_state_t), intent(in), optional :: bc
         !! When present, per-edge OBC tags gate the wall-zero step
         !! inside the flux kernels.  OBC_WALL keeps the Phase 3
         !! closure; OBC_OPEN (and other non-wall tags) leaves the
         !! computed mass flux at the wall face for the downstream
         !! transport.  Absent ⇒ closed-wall everywhere.
      type(ocean_mle_t), intent(in), optional :: mle
         !! Fox-Kemper mixed-layer-eddy transports (B5).  When present
         !! and enabled, `mle%uhml`/`vhml` are folded into the per-layer
         !! mass fluxes AFTER each direction's flux fill and BEFORE the
         !! matching tracer advect + divergence — so the augmented flux
         !! transports both h and tracers (conservative; velocity
         !! untouched).  Absent / disabled ⇒ bit-identical no-op.
      logical, intent(in), optional :: mle_fold_active
         !! Gates the Fox-Kemper fold to the THERMO cadence.  Absent or
         !! `.true.` ⇒ the fold applies (bit-identical default — the case
         !! at `dt_therm_ratio = 1`, where every step is a thermo step).
         !! `.false.` skips the fold so the stale FK transports (computed
         !! once per thermo interval) are NOT re-applied on the
         !! intervening non-thermo outer steps when `dt_therm_ratio > 1`.
      ! assumed-shape-ok: pure passthroughs to the renormaliser.
      real(wp), intent(in), optional :: visc_rem_u(:, :, :), visc_rem_v(:, :, :)
         !! Per-layer viscous remnant gamma_k on east / north faces.  Forwarded
         !! to the flux renormalisers, where it weights the barotropic
         !! increment (MOM6 `u_cor = u + du*visc_rem`).  Absent => gamma == 1,
         !! bit-identical.
      ! assumed-shape-ok: pure passthroughs.
      real(wp), intent(inout), optional :: u_cor(:, :, :), v_cor(:, :, :)
         !! MOM6 `u_cor`/`v_cor` destinations — the step TIME-MEAN velocity
         !! (`u_av`/`v_av`), never the prognostic.  Absent => flux-only.
      integer, intent(in), optional :: tracer_mode
         !! Phase 2 (6b) windowed-advection mode.  `TR_MODE_ADVECT`
         !! (default, absent) ⇒ the historical fused path: advance h AND
         !! advect tracers each call (bit-identical to pre-6b).
         !! `TR_MODE_ACCUMULATE` ⇒ advance h, accumulate
         !! `0.5·mass_flux·dt` into `this%uhtr/vhtr` (one += per RK2
         !! stage, weight 0.5 baked in — closes the reconstruction against
         !! the RK2-averaged h), and SKIP the per-step tracer advect so
         !! `hTr` stays frozen until the boundary drain.
      real(wp), intent(in), optional :: h_min
         !! Phase-1 Lagrangian minimum-thickness floor (m). When > 0, passed
         !! to `continuity_apply_zonal`/`_meridional` to clamp h_new >= h_min.
         !! Absent or 0 ⇒ off ⇒ bit-identical.

      integer :: it
      logical :: per_x, per_y, do_mle_fold, fold_wall
      integer :: nx, ny, nz, nx_phys, ny_phys, nghost
      integer :: mode
      integer :: ii, jj, kk
      integer :: it_cw
      integer :: bc_w_tag, bc_e_tag, bc_s_tag, bc_n_tag
      real(wp) :: h_min_use

      mode = TR_MODE_ADVECT
      if (present(tracer_mode)) mode = tracer_mode

      h_min_use = 0.0_wp
      if (present(h_min)) h_min_use = h_min

      ! P2 positive-definite limiter: reset the per-call limited-face counter
      ! (accumulated across the zonal + meridional passes below).  Host scalar.
      this%n_limited_step = 0

      per_x = .false.
      per_y = .false.
      if (present(bc)) then
         per_x = bc%periodic_x .and. .not. ocean_halo_is_decomposed_x()
         per_y = bc%periodic_y .and. .not. ocean_halo_is_decomposed_y()
      end if
      ! Fox-Kemper fold defaults ON (bit-identical for callers that do not
      ! pass the gate); the dyn step passes `is_thermo_step()` to suppress
      ! the fold on non-thermo steps when dt_therm_ratio > 1.
      do_mle_fold = .true.
      if (present(mle_fold_active)) do_mle_fold = mle_fold_active
      ! Whether the MLE bolus fold actually contributes this call.  When it
      ! does, the augmented flux must be re-closed at no-normal-flow WALL
      ! faces (the fold adds bolus transport at every face, including the
      ! physical wall the resolved flux already zeroed — otherwise the bolus
      ! bleeds tracer mass into the ghost halo across the wall).  GM is no
      ! longer folded: it is its own operator (`continuity_gm_apply`).
      fold_wall = .false.
      if (present(mle)) fold_wall = fold_wall .or. mle%enable
      fold_wall = fold_wall .and. do_mle_fold
      nx = grid%nx_total
      ny = grid%ny_total
      nz = ms%nz_ml
      nx_phys = grid%nx_phys
      ny_phys = grid%ny_phys
      nghost = grid%nghost

      ! Windowed-advect concentration hold (step 1 of 2).  In
      ! TR_MODE_ACCUMULATE the horizontal tracer advect is deferred to the
      ! end-of-window drain, so `hTr` must not move — but `h_layer` does,
      ! every stage.  Snapshot the pre-continuity thickness so the paired
      ! rescale at the bottom of this routine can hold `T = hTr/h_layer`
      ! fixed instead of holding `hTr` fixed.  `hprev_work` is idle here:
      ! the drain is the only other consumer and it runs at outer-step end.
      if (mode == TR_MODE_ACCUMULATE) then
         ! First accumulate stage of a window: latch the window-start
         ! thickness the drain will use to undo the hold exactly.
         if (.not. this%hTr_holds_conc) then
            call drain_copy_3d(nx, ny, nz, ms%h_layer, this%h_win_start)
         end if
         call drain_copy_3d(nx, ny, nz, ms%h_layer, this%hprev_work)
      end if

      if (present(uhbt) .and. present(bc)) then
         call continuity_zonal_flux(grid, metrics, this, ms, dt, uhbt=uhbt, bc=bc, &
                                    visc_rem=visc_rem_u, u_cor=u_cor)
      else if (present(uhbt)) then
         call continuity_zonal_flux(grid, metrics, this, ms, dt, uhbt=uhbt, &
                                    visc_rem=visc_rem_u, u_cor=u_cor)
      else if (present(bc)) then
         call continuity_zonal_flux(grid, metrics, this, ms, dt, bc=bc)
      else
         call continuity_zonal_flux(grid, metrics, this, ms, dt)
      end if
      ! FK MLE fold (B5): add uhml into the zonal mass flux so it advects
      ! both h and tracers and enters the divergence.  No-op if absent.
      ! NOTE: this fold runs every dynamics call; `mle_compute_transports`
      ! runs only at thermo cadence.  When dt_therm_ratio > 1 the stale
      ! uhml/vhml are re-folded on non-thermo steps (over-applies FK at
      ! dynamics cadence — known limitation, safe/conservative via
      ! sum_k a(k) = 0; to be gated when sub-thermo cadence is exercised).
      if (present(mle) .and. do_mle_fold) then
         call mle_fold_x(mle, ms%mass_flux_x_layer, nx + 1, ny, nz)
      end if
      ! No-normal-flow wall closure for the bolus fold (mirrors the
      ! resolved-flux wall zeroing in `continuity_zonal_flux`): the MLE
      ! fold above adds `uhml` at the physical WALL faces, which the
      ! resolved flux had zeroed.  Without re-zeroing, the bolus transports
      ! tracer mass across the wall into the ghost halo and the
      ! physical-domain `sum(hTr)` drifts.  BC-aware: periodic / open edges
      ! keep the folded transport.
      if (fold_wall) then
         ! Re-close the physical walls after the MLE fold.  An MPI seam
         ! (`has_*` false) is not a wall: zeroing it cut every decomposed
         ! Fox-Kemper run's transport at the rank seams (the tile edge
         ! is an interior face the neighbour computes identically).
         bc_w_tag = OBC_WALL
         bc_e_tag = OBC_WALL
         if (present(bc)) then
            bc_w_tag = ocean_bc_outer_face_tag(bc%west%bc_type)
            bc_e_tag = ocean_bc_outer_face_tag(bc%east%bc_type)
            if (.not. bc%has_west) bc_w_tag = OBC_PERIODIC
            if (.not. bc%has_east) bc_e_tag = OBC_PERIODIC
         end if
         do concurrent(kk=1:nz, jj=1:ny)
            if (bc_w_tag == OBC_WALL) ms%mass_flux_x_layer(nghost + 1, jj, kk) = 0.0_wp
            if (bc_e_tag == OBC_WALL) ms%mass_flux_x_layer(nghost + nx_phys + 1, jj, kk) = 0.0_wp
         end do
      end if
      ! P2 positive-definite outflux limiter (zonal): scale the OUTGOING
      ! east-face mass fluxes so no donor drains below h_lim.  Applied to the
      ! FOLDED total (after the MLE bolus fold + wall closure) so the
      ! h-apply, tracer advect, uhtr accumulation, and the corrector's
      ! `use_state_fluxes` reads all consume the SAME limited flux (D3).
      ! Off ⇒ skipped ⇒ bit-identical.
      if (this%positive_definite) then
         ! v1.1: forward u_cor so the limiter re-scales the captured
         ! transport-matched velocity (the u_av family) by the same θ as
         ! the flux — optional-forwarding propagates absence.  Keeps u_av
         ! consistent with the LIMITED fluxes the pred_corr corrector's
         ! use_state_fluxes CorAdCalc transports with (the flux↔velocity
         ! match is load-bearing).
         call pd_limit_zonal_impl(nx, ny, nz, dt, this%h_lim, metrics%iareaT, &
                                  ms%h_layer, ms%mass_flux_x_layer, &
                                  this%pd_theta%data, this%n_limited_step, &
                                  u_cor=u_cor)
      end if
      if (mode == TR_MODE_ACCUMULATE) then
         ! Windowed mode: accumulate this stage's zonal mass flux (with
         ! the RK2 0.5 weight baked in) and SKIP the tracer advect so
         ! hTr stays frozen.  hprev = h^{n+1} + div(uhtr) then closes
         ! the reconstruction against the RK2-averaged h.
         call accumulate_flux_x(nx + 1, ny, nz, dt, ms%mass_flux_x_layer, this%uhtr)
      else if (mode /= TR_MODE_NONE .and. present(bc)) then
         call tracer_advect_zonal(grid, metrics, this, ms, dt, bc=bc)
      else if (mode /= TR_MODE_NONE) then
         call tracer_advect_zonal(grid, metrics, this, ms, dt)
      end if
      if (h_min_use > 0.0_wp) then
         call continuity_apply_zonal(grid, metrics, ms, dt, h_min=h_min_use)
      else
         call continuity_apply_zonal(grid, metrics, ms, dt)
      end if

      ! Mid-Lie-split MPI seam exchange (O3 correctness fix): after the zonal
      ! apply updates h_layer (and hTr in ADVECT mode) the meridional flux
      ! reconstruction reads h ghost columns that the NEIGHBOUR rank's zonal
      ! apply has updated — but those ghosts were never re-exchanged.  Without
      ! this exchange an x-rank seam leaks ~3e-9/day global mass.
      ! D0-unconditional: single-rank non-periodic => no-op, single-rank
      ! periodic => local wrap, multi-rank => messages.  Must run BEFORE the
      ! periodic wrap below so both exchanges see the same post-zonal state.
      !
      ! NOTE: this exchange is also inside the "ocean_continuity" compute region
      ! opened by the dyn caller (rdb_ocean_dyn.F90); ocean_comms_ml here isolates
      ! the comm share, so sums of compute+comms slightly over-close by this term.
      ! This is a known, accepted double-attribution — no stop/restart of the
      ! outer region from inside this module.
      call profiler_start("ocean_comms_ml")
      call ocean_halo_centre(ms%h_layer, nz)
      if (allocated(ms%tracers)) then
         do it = 1, size(ms%tracers)
            if (.not. allocated(ms%tracers(it)%hTr)) cycle
            call ocean_halo_centre(ms%tracers(it)%hTr, nz)
         end do
      end if
      call profiler_stop("ocean_comms_ml")

      ! Mid-Lie-split ghost wrap (design §1.5): re-wrap h_layer (and
      ! tracers) after the zonal apply so the meridional reconstruction
      ! reads current ghost values.  Both per_x and per_y checked: even
      ! for periodic-x only, per_y ghosts can inherit stale values
      ! accumulated under the zonal update.  Two cheap DC kernels, no
      ! correctness traps.
      if (per_x .or. per_y) then
         ! Batched async wrap (queue 1): h_layer + every tracer issued without
         ! per-call sync, then synced ONCE below — pipelines the tiny ghost-slab
         ! launches (otherwise launch-latency-bound).  Wait before the fold,
         ! which reads these wrapped ghosts.
         call ocean_periodic_wrap_centre_3d(ms%h_layer, nx, ny, nz, &
                                            nx_phys, ny_phys, nghost, per_x, per_y, no_wait=.true.)
         if (allocated(ms%tracers)) then
            do it = 1, size(ms%tracers)
               if (.not. allocated(ms%tracers(it)%hTr)) cycle
               call ocean_periodic_wrap_centre_3d(ms%tracers(it)%hTr, nx, ny, nz, &
                                                  nx_phys, ny_phys, nghost, per_x, per_y, no_wait=.true.)
            end do
         end if
         !$acc wait(1)
      end if
      ! Mid-Lie-split north fold (Appendix A): re-fold the centre fields
      ! (h_layer + tracers) AFTER the periodic wrap so the meridional
      ! reconstruction reads fold-consistent north ghosts.  No-op when not
      ! folding.
      if (present(bc)) call ocean_fold_wrap_centre_3d_state(grid, bc, ms)

      if (present(vhbt) .and. present(bc)) then
         call continuity_meridional_flux(grid, metrics, this, ms, dt, vhbt=vhbt, bc=bc, &
                                         visc_rem=visc_rem_v, v_cor=v_cor)
      else if (present(vhbt)) then
         call continuity_meridional_flux(grid, metrics, this, ms, dt, vhbt=vhbt, &
                                         visc_rem=visc_rem_v, v_cor=v_cor)
      else if (present(bc)) then
         call continuity_meridional_flux(grid, metrics, this, ms, dt, bc=bc)
      else
         call continuity_meridional_flux(grid, metrics, this, ms, dt)
      end if
      ! FK MLE fold (B5): add vhml into the meridional mass flux.
      ! Same thermo-cadence / dynamics-fold limitation as the zonal fold above.
      if (present(mle) .and. do_mle_fold) then
         call mle_fold_y(mle, ms%mass_flux_y_layer, nx, ny + 1, nz)
      end if
      ! No-normal-flow wall closure for the meridional bolus fold (see the
      ! zonal block above for the rationale).
      if (fold_wall) then
         ! MPI seams are not walls (see the zonal twin above).
         bc_s_tag = OBC_WALL
         bc_n_tag = OBC_WALL
         if (present(bc)) then
            bc_s_tag = ocean_bc_outer_face_tag(bc%south%bc_type)
            bc_n_tag = ocean_bc_outer_face_tag(bc%north%bc_type)
            if (.not. bc%has_south) bc_s_tag = OBC_PERIODIC
            if (.not. bc%has_north) bc_n_tag = OBC_PERIODIC
         end if
         do concurrent(kk=1:nz, ii=1:nx)
            if (bc_s_tag == OBC_WALL) ms%mass_flux_y_layer(ii, nghost + 1, kk) = 0.0_wp
            if (bc_n_tag == OBC_WALL) ms%mass_flux_y_layer(ii, nghost + ny_phys + 1, kk) = 0.0_wp
         end do
      end if
      ! P2 positive-definite outflux limiter (meridional): mirror of the zonal
      ! pass, on the post-zonal-apply h* availability.  Same D3 single-source
      ! scaling of the folded total.  Off ⇒ skipped ⇒ bit-identical.
      if (this%positive_definite) then
         ! v1.1: forward v_cor — see the zonal twin.
         call pd_limit_meridional_impl(nx, ny, nz, dt, this%h_lim, metrics%iareaT, &
                                       ms%h_layer, ms%mass_flux_y_layer, &
                                       this%pd_theta%data, this%n_limited_step, &
                                       v_cor=v_cor)
      end if
      ! Tripolar fold-line flux projection.  The fold-line row (north face
      ! of the last T-row, `rdb_ocean_fold` header) stores ONE physical face
      ! twice; its two flux slots were computed independently (PPM
      ! reconstruction, vhbt renormalisation, MLE bolus, limiter) and
      ! agree only up to rounding.  Project the FINAL flux antisymmetric
      ! (and refill the rows above it) before it touches h / hTr / the
      ! accumulated transport, so the cross-fold exchange telescopes: what
      ! leaves cell (i,nj) through its north face is exactly what enters
      ! cell (ni+1-i,nj).  No-op when not folding.  px > 1: no halo
      ! precedes this point, so the owner-routed exchange is what makes it
      ! exact (every value comes from the rank that owns the mirror face).
      if (present(bc)) then
         if (bc%north_fold) call ocean_fold_north_v_face(ms%mass_flux_y_layer, nx, ny + 1, nz, &
                                                         nx_phys, ny_phys, nghost)
      end if
      if (mode == TR_MODE_ACCUMULATE) then
         call accumulate_flux_y(nx, ny + 1, nz, dt, ms%mass_flux_y_layer, this%vhtr)
      else if (mode /= TR_MODE_NONE .and. present(bc)) then
         call tracer_advect_meridional(grid, metrics, this, ms, dt, bc=bc)
      else if (mode /= TR_MODE_NONE) then
         call tracer_advect_meridional(grid, metrics, this, ms, dt)
      end if
      if (h_min_use > 0.0_wp) then
         call continuity_apply_meridional(grid, metrics, ms, dt, h_min=h_min_use)
      else
         call continuity_apply_meridional(grid, metrics, ms, dt)
      end if
      ! Windowed-advect concentration hold (step 2 of 2).  Both applies have
      ! advanced `h_layer`; re-weight the frozen tracer content onto it so the
      ! concentration every downstream consumer reads (`ocean_eos_compute` →
      ! ρ → PGF, vdiff, hdiff, vertical advect, diagnostics) is EXACTLY the
      ! window-start value, as it is in MOM6 (whose prognostic `Tr%t` is a
      ! concentration and is therefore thickness-invariant for free).
      ! Without this, `T` drifts by the full window thickness divergence and
      ! the resulting grid-scale buoyancy error closes an exponentially
      ! growing EOS→PGF→divergence loop.  Composes with `rk2_average`:
      ! `hTr0 = T·h^n` and `hTr = T·h^(2)` average to `T·h^(n+1)`, so `T` is
      ! still exactly `T`.  `continuity_tracer_drain` converts back before it
      ! spends the accumulated transports.
      if (mode == TR_MODE_ACCUMULATE .and. allocated(ms%tracers)) then
         do it_cw = 1, size(ms%tracers)
            if (.not. allocated(ms%tracers(it_cw)%hTr)) cycle
            if (.not. ms%tracers(it_cw)%do_horizontal_advection) cycle
            ! Budget dispatch (mirrors `tracer_advect_zonal`): heat/salt get
            ! the hold's own content change recorded so the console budget
            ! closes at EVERY report, not only on window boundaries.  Tracers
            ! without a budget slot take the budget-free twin.
            select case (ms%tracers(it_cw)%budget_id)
            case (TRACER_BUDGET_HEAT)
               call drain_rescale_hTr_budget(nx, ny, nz, ms%h_layer, this%hprev_work, &
                                             DRAIN_BUDGET_IN_STAGE_WEIGHT, &
                                             ms%tracers(it_cw)%hTr, ms%heat_budget_horiz_adv)
            case (TRACER_BUDGET_SALT)
               call drain_rescale_hTr_budget(nx, ny, nz, ms%h_layer, this%hprev_work, &
                                             DRAIN_BUDGET_IN_STAGE_WEIGHT, &
                                             ms%tracers(it_cw)%hTr, ms%salt_budget_horiz_adv)
            case default
               call drain_rescale_hTr(nx, ny, nz, ms%h_layer, this%hprev_work, &
                                      ms%tracers(it_cw)%hTr)
            end select
         end do
         this%hTr_holds_conc = .true.
      end if

      ! P3: fold this call's limited-face count into the running total (one
      ! add per split call, mirroring dyn%ntrunc_total).  n_limited_step is 0
      ! when positive_definite is off, so this is a no-op there.
      this%n_limited_total = this%n_limited_total + this%n_limited_step
   end subroutine continuity_tracer_step_split