rdb_ocean_api.F90 Source File

Ocean C ABI — lifecycle (P1) + accessors and the error ring (P2).

bind(c) entry points for create / step / destroy, introspection (time, step count, grid shape, scalar diagnostics), the error ring, and the P2 state-array getters/setters. See the “P2 — accessors and the error ring” section below the P1 lifecycle code for the D<->H contract these follow (docs/ocean_python_api_plan.md S3, 06_python_surface_design.md D3/D4).

Every entry point returns integer(c_int) status — never error stop (a library cannot abort its host process). Status codes reuse rdb_ocean_status (config/setup/IC-seed failures reported by read_config_from_string / validate_config / the configure_ocean_* chain / ocean_state_seed_from_cfg via their optional ierr, P0) plus three handle-specific codes added here.

Setup mirrors benchmarks/bench_ocean.F90 exactly — grid init -> ocean_state_t%init_from_config -> vcoord params -> seed IC -> register tracers -> surface-flux slot -> the configure_ocean_* chain -> resolve n_inner -> ocean_state_enter_data + sf device mapping. That is the proven init ordering (also used by driver_run_ocean); this module does not re-derive it.

Single live ocean handle, enforced. Several modules hold process-global topology/device state keyed to “the one running ocean simulation” (rdb_ocean_halo, rdb_ice_ocean_coupler, rdb_ocean_data_input, rdb_profiler, rdb_ocean_diag_fills) — a second concurrent create would silently corrupt the first rather than merely waste memory. g_handle_live below is the guard; it is ocean-specific POLICY, which is why it lives here and not in the generic rdb_handle.

Never exposed: comm_env_finalize (irreversible, calls MPI_Finalize). comm_env_init IS called, on every create – see build_pending_handle.

Failure contract (P0.1 review F9): rdb_ocean_create_from_string calls handle_destroy on EVERY non-OCEAN_STATUS_OK branch below, before returning the status code — never partially and never retries. handle_destroy deallocates the WHOLE ocean_handle_t (cfg + state pointer target together, rdb_handle.F90), so a configure_ocean_* step that writes a knob (e.g. flips enable) before validating it and failing partway through leaves nothing observable: the entire handle carrying that half-configured cfg/ state is thrown away, not reused. This is why individual configure_ocean_* procedures are not each required to be transactional/rollback-safe on their own — the ONE rollback boundary is “destroy the whole handle” at the create() level, and it must stay that way: adding a retry-with-the-same-handle path would revive the half-configured-state hazard the review flagged.


This file depends on

sourcefile~~rdb_ocean_api.f90~~EfferentGraph sourcefile~rdb_ocean_api.f90 rdb_ocean_api.F90 sourcefile~rdb_comm_env.f90 rdb_comm_env.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_comm_env.f90 sourcefile~rdb_config.f90 rdb_config.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_config.f90 sourcefile~rdb_constants.f90 rdb_constants.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_error_ring.f90 rdb_error_ring.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_handle.f90 rdb_handle.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_handle.f90 sourcefile~rdb_ocean_bathymetry_inject.f90 rdb_ocean_bathymetry_inject.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_bathymetry_inject.f90 sourcefile~rdb_ocean_diag_derived.f90 rdb_ocean_diag_derived.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_diag_derived.f90 sourcefile~rdb_ocean_diag_fills.f90 rdb_ocean_diag_fills.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_dyn.f90 rdb_ocean_dyn.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_dyn.f90 sourcefile~rdb_ocean_engine.f90 rdb_ocean_engine.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_engine.f90 sourcefile~rdb_ocean_eos_compute.f90 rdb_ocean_eos_compute.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_eos_compute.f90 sourcefile~rdb_ocean_fold_apply.f90 rdb_ocean_fold_apply.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_ocean_halo_width.f90 rdb_ocean_halo_width.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_halo_width.f90 sourcefile~rdb_ocean_periodic.f90 rdb_ocean_periodic.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ocean_status.f90 rdb_ocean_status.F90 sourcefile~rdb_ocean_api.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_comm_env.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_config.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_config.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_coriolis_adv.f90 rdb_coriolis_adv.F90 sourcefile~rdb_config.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_eos.f90 rdb_eos.F90 sourcefile~rdb_config.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_enthalpy.f90 rdb_ice_enthalpy.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_init.f90 rdb_ice_init.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ice_init.f90 sourcefile~rdb_nml_schema.f90 rdb_nml_schema.F90 sourcefile~rdb_config.f90->sourcefile~rdb_nml_schema.f90 sourcefile~rdb_ocean_bottom_drag.f90 rdb_ocean_bottom_drag.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_boundary_types.f90 rdb_ocean_boundary_types.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_cavity.f90 rdb_ocean_cavity.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_cavity.f90 sourcefile~rdb_ocean_cavity_melt.f90 rdb_ocean_cavity_melt.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_cavity_melt.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90 rdb_ocean_horizontal_viscosity.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_lateral_mix.f90 rdb_ocean_lateral_mix.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_pressure_force.f90 rdb_ocean_pressure_force.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_pseudo_salt.f90 rdb_ocean_pseudo_salt.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_pseudo_salt.f90 sourcefile~rdb_ocean_surface_flux.f90 rdb_ocean_surface_flux.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_tidal_mixing.f90 rdb_ocean_tidal_mixing.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_tidal_mixing.f90 sourcefile~rdb_ocean_top_drag.f90 rdb_ocean_top_drag.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_ocean_vmix.f90 rdb_ocean_vmix.F90 sourcefile~rdb_config.f90->sourcefile~rdb_ocean_vmix.f90 sourcefile~rdb_recon_weno.f90 rdb_recon_weno.F90 sourcefile~rdb_config.f90->sourcefile~rdb_recon_weno.f90 sourcefile~rdb_vcoord.f90 rdb_vcoord.F90 sourcefile~rdb_config.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_handle.f90->sourcefile~rdb_config.f90 sourcefile~rdb_handle.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_handle.f90->sourcefile~rdb_ocean_engine.f90 sourcefile~rdb_grid.f90 rdb_grid.F90 sourcefile~rdb_handle.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_state.f90 rdb_ocean_state.F90 sourcefile~rdb_handle.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_bathymetry_inject.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bathymetry_inject.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_bathymetry_inject.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_bathymetry_inject.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_diag.f90 rdb_ocean_diag.F90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_ocean_diag.f90 sourcefile~rdb_ocean_diag_derived.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_column.f90 rdb_ice_column.F90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ocean_diag.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ocean_pseudo_salt.f90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_vcoord.f90 rdb_ocean_vcoord.F90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_remap_column.f90 rdb_remap_column.F90 sourcefile~rdb_ocean_diag_fills.f90->sourcefile~rdb_remap_column.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_eos_compute.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_barotropic_coupling.f90 rdb_barotropic_coupling.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_barotropic_coupling.f90 sourcefile~rdb_barotropic_state.f90 rdb_barotropic_state.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_barotropic_state.f90 sourcefile~rdb_barotropic_substep.f90 rdb_barotropic_substep.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_barotropic_substep.f90 sourcefile~rdb_barotropic_workstate.f90 rdb_barotropic_workstate.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_continuity.f90 rdb_continuity.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_continuity.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_efp.f90 rdb_efp.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_mem_report.f90 rdb_mem_report.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_multilayer_state.f90 rdb_multilayer_state.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_bt_budget_probe.f90 rdb_ocean_bt_budget_probe.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_bt_budget_probe.f90 sourcefile~rdb_ocean_bt_wide.f90 rdb_ocean_bt_wide.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_bt_wide.f90 sourcefile~rdb_ocean_cavity_flux.f90 rdb_ocean_cavity_flux.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_cavity_flux.f90 sourcefile~rdb_ocean_chksum.f90 rdb_ocean_chksum.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_chksum.f90 sourcefile~rdb_ocean_console_stats.f90 rdb_ocean_console_stats.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_console_stats.f90 sourcefile~rdb_ocean_epbl.f90 rdb_ocean_epbl.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_epbl.f90 sourcefile~rdb_ocean_geothermal.f90 rdb_ocean_geothermal.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_geothermal.f90 sourcefile~rdb_ocean_ghost_poison.f90 rdb_ocean_ghost_poison.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_ghost_poison.f90 sourcefile~rdb_ocean_gm.f90 rdb_ocean_gm.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_gm.f90 sourcefile~rdb_ocean_halo.f90 rdb_ocean_halo.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ocean_halo_state.f90 rdb_ocean_halo_state.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ocean_hdiff_tracer.f90 rdb_ocean_hdiff_tracer.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_hdiff_tracer.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_ideal_age.f90 rdb_ocean_ideal_age.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_ideal_age.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90 rdb_ocean_isopycnal_slopes.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_isopycnal_slopes.f90 sourcefile~rdb_ocean_kappa_shear.f90 rdb_ocean_kappa_shear.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_kappa_shear.f90 sourcefile~rdb_ocean_ke_probe.f90 rdb_ocean_ke_probe.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_ke_probe.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_meke.f90 rdb_ocean_meke.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_meke.f90 sourcefile~rdb_ocean_metrics.f90 rdb_ocean_metrics.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_min_thickness.f90 rdb_ocean_min_thickness.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_min_thickness.f90 sourcefile~rdb_ocean_mle.f90 rdb_ocean_mle.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_mle.f90 sourcefile~rdb_ocean_obc_baroclinic.f90 rdb_ocean_obc_baroclinic.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_obc_baroclinic.f90 sourcefile~rdb_ocean_p_surf.f90 rdb_ocean_p_surf.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_p_surf.f90 sourcefile~rdb_ocean_porous.f90 rdb_ocean_porous.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_porous.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_redi.f90 rdb_ocean_redi.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_redi.f90 sourcefile~rdb_ocean_remap.f90 rdb_ocean_remap.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_remap.f90 sourcefile~rdb_ocean_sponge.f90 rdb_ocean_sponge.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_sponge.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_surface_stress.f90 rdb_ocean_surface_stress.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_tidal_mixing.f90 sourcefile~rdb_ocean_tides.f90 rdb_ocean_tides.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_tides.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_ocean_varmix.f90 rdb_ocean_varmix.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_varmix.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_ocean_vdiff.f90 rdb_ocean_vdiff.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_vdiff.f90 sourcefile~rdb_ocean_vertical_advection.f90 rdb_ocean_vertical_advection.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_vertical_advection.f90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_vmix.f90 sourcefile~rdb_ocean_wave_speed.f90 rdb_ocean_wave_speed.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_ocean_wave_speed.f90 sourcefile~rdb_profiler.f90 rdb_profiler.F90 sourcefile~rdb_ocean_dyn.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag_derived.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag_fills.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_dyn.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_decomp.f90 rdb_decomp.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_halo.f90 rdb_halo.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ice_atm_forcing.f90 rdb_ice_atm_forcing.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_atm_forcing.f90 sourcefile~rdb_ice_basal_flux.f90 rdb_ice_basal_flux.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_basal_flux.f90 sourcefile~rdb_ice_evp.f90 rdb_ice_evp.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_evp.f90 sourcefile~rdb_ice_frazil.f90 rdb_ice_frazil.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_frazil.f90 sourcefile~rdb_ice_frazil_uptake.f90 rdb_ice_frazil_uptake.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_frazil_uptake.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_init.f90 sourcefile~rdb_ice_itd.f90 rdb_ice_itd.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_itd.f90 sourcefile~rdb_ice_ocean_coupler.f90 rdb_ice_ocean_coupler.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_ocean_coupler.f90 sourcefile~rdb_ice_snow.f90 rdb_ice_snow.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_snow.f90 sourcefile~rdb_ice_thermo_driver.f90 rdb_ice_thermo_driver.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_thermo_driver.f90 sourcefile~rdb_ice_transport.f90 rdb_ice_transport.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ice_transport.f90 sourcefile~rdb_io_netcdf.f90 rdb_io_netcdf.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_boundary_data.f90 rdb_ocean_boundary_data.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_boundary_data.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_cavity_flux.f90 sourcefile~rdb_ocean_data_forcing.f90 rdb_ocean_data_forcing.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_data_forcing.f90 sourcefile~rdb_ocean_data_input.f90 rdb_ocean_data_input.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_data_input.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag.f90 sourcefile~rdb_ocean_diag_netcdf.f90 rdb_ocean_diag_netcdf.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_diag_netcdf.f90 sourcefile~rdb_ocean_fold_exchange.f90 rdb_ocean_fold_exchange.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_fold_exchange.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_geothermal.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_setup.f90 rdb_ocean_setup.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_setup.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_sponge.f90 sourcefile~rdb_ocean_stability_audit.f90 rdb_ocean_stability_audit.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_stability_audit.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_state.f90 rdb_state.F90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_state.f90 sourcefile~rdb_ocean_engine.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_ocean_eos_compute.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_eos_compute.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_fold.f90 rdb_ocean_fold.F90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_fold_apply.f90->sourcefile~rdb_ocean_fold_exchange.f90 sourcefile~rdb_ocean_halo_width.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_ocean_halo_width.f90->sourcefile~rdb_recon_weno.f90 sourcefile~rdb_ocean_periodic.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_periodic.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_periodic.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_periodic.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_barotropic_coupling.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_barotropic_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_ocean_fold_exchange.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_bt_cont_type.f90 rdb_bt_cont_type.F90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_bt_cont_type.f90 sourcefile~rdb_ocean_halo_counters.f90 rdb_ocean_halo_counters.F90 sourcefile~rdb_barotropic_substep.f90->sourcefile~rdb_ocean_halo_counters.f90 sourcefile~rdb_barotropic_workstate.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_barotropic_workstate.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_barotropic_workstate.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_barotropic_state.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_fold_exchange.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_gm.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_ocean_mle.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_continuity.f90->sourcefile~rdb_recon_weno.f90 sourcefile~rdb_scratch_3d.f90 rdb_scratch_3d.F90 sourcefile~rdb_continuity.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_tracer.f90 rdb_tracer.F90 sourcefile~rdb_continuity.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_barotropic_state.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_ocean_porous.f90 sourcefile~rdb_coriolis_adv.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_decomp.f90->sourcefile~rdb_config.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_eos.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_grid.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_halo.f90->sourcefile~rdb_comm_env.f90 sourcefile~rdb_halo.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_halo.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_halo.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_ice_atm_forcing.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_state.f90 rdb_ice_state.F90 sourcefile~rdb_ice_atm_forcing.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ice_basal_flux.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_mass.f90 rdb_ice_mass.F90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_mass.f90 sourcefile~rdb_ice_optics.f90 rdb_ice_optics.F90 sourcefile~rdb_ice_column.f90->sourcefile~rdb_ice_optics.f90 sourcefile~rdb_ice_enthalpy.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ice_evp.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_frazil.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_frazil.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_frazil.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_frazil.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_frazil.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_ice_mass.f90 sourcefile~rdb_ice_frazil_uptake.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ice_init.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_itd.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_itd.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_itd.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_itd.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ice_ocean_coupler.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_snow.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_snow.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_snow.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_snow.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_thermo_driver.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_continuity.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ice_itd.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ice_transport.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_io_netcdf.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_io_netcdf.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_mem_report.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_multilayer_state.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_nml_schema.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_nml_schema.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_bottom_drag.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_boundary_data.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_boundary_data.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_tide_astro.f90 rdb_ocean_tide_astro.F90 sourcefile~rdb_ocean_boundary_types.f90->sourcefile~rdb_ocean_tide_astro.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_bt_budget_probe.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_barotropic_substep.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_bt_wide.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_ocean_cavity.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_cavity.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_ocean_cavity_melt.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_cavity_flux.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_cavity_melt.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_cavity_melt.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_chksum.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_chksum.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_ocean_chksum.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_chksum.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ocean_chksum.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_console_stats.f90 rdb_console_stats.F90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_console_stats.f90 sourcefile~rdb_ocean_console_stats.f90->sourcefile~rdb_ocean_halo_counters.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_data_input.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_data_forcing.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_data_input.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_diag_mask.f90 rdb_ocean_diag_mask.F90 sourcefile~rdb_ocean_diag.f90->sourcefile~rdb_ocean_diag_mask.f90 sourcefile~rdb_ocean_diag_netcdf.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag_netcdf.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_diag_netcdf.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_diag_netcdf.f90->sourcefile~rdb_ocean_diag.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_epbl.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_fold.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_comm_env.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_fold_plan.f90 rdb_ocean_fold_plan.F90 sourcefile~rdb_ocean_fold_exchange.f90->sourcefile~rdb_ocean_fold_plan.f90 sourcefile~rdb_ocean_geothermal.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_geothermal.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_geothermal.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_geothermal.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_ghost_poison.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_ghost_poison.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_ocean_ghost_poison.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_ghost_poison.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_ghost_poison.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_ocean_isopycnal_slopes.f90 sourcefile~rdb_ocean_gm.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_comm_env.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_halo.f90->sourcefile~rdb_ocean_halo_counters.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ocean_halo_state.f90->sourcefile~rdb_ocean_halo_counters.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_hdiff_tracer.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_horizontal_viscosity.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_ideal_age.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_ideal_age.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_ideal_age.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_isopycnal_slopes.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_massless.f90 rdb_massless.F90 sourcefile~rdb_ocean_kappa_shear.f90->sourcefile~rdb_massless.f90 sourcefile~rdb_ocean_ke_probe.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_ke_probe.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_ocean_ke_probe.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_ke_probe.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_ke_probe.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_lateral_mix.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_ocean_gm.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_ocean_varmix.f90 sourcefile~rdb_ocean_meke.f90->sourcefile~rdb_ocean_wave_speed.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_bipolar.f90 rdb_ocean_bipolar.F90 sourcefile~rdb_ocean_metrics.f90->sourcefile~rdb_ocean_bipolar.f90 sourcefile~rdb_ocean_min_thickness.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_min_thickness.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_min_thickness.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_min_thickness.f90->sourcefile~rdb_remap_column.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_ocean_epbl.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_mle.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_obc_baroclinic.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_obc_baroclinic.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_ocean_obc_baroclinic.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_obc_baroclinic.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_obc_baroclinic.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_p_surf.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_p_surf.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_p_surf.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_porous.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90 rdb_ocean_pgf_reconstruct.F90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_ocean_pgf_reconstruct.f90 sourcefile~rdb_ocean_pressure_force.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_pseudo_salt.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_redi.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_remap_column.f90 sourcefile~rdb_ocean_remap.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_dyn.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_fold_apply.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_halo.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_cavity.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_cavity_melt.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_epbl.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_fold_exchange.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_geothermal.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_halo.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_halo_state.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_p_surf.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_porous.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_sponge.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_state.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_tides.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_vdiff.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_vmix.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_recon_weno.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_ocean_setup.f90->sourcefile~rdb_ocean_tide_astro.f90 sourcefile~rdb_ocean_sponge.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_sponge.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_sponge.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_sponge.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_sponge.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_stability_audit.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_comm_env.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_bathymetry_inject.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_dyn.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_periodic.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_barotropic_state.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_continuity.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_coriolis_adv.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_bottom_drag.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_boundary_types.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_cavity.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_cavity_flux.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_data_forcing.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_data_input.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_diag.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_epbl.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_fold.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_gm.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_hdiff_tracer.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_horizontal_viscosity.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_isopycnal_slopes.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_kappa_shear.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_lateral_mix.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_meke.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_mle.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_p_surf.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_pressure_force.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_pseudo_salt.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_redi.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_sponge.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_tidal_mixing.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_tides.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_top_drag.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_varmix.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_vcoord.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_vdiff.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_vertical_advection.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_vmix.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_wave_speed.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_profiler.f90 sourcefile~rdb_bathymetry.f90 rdb_bathymetry.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_bathymetry.f90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ice_state.f90 sourcefile~rdb_ocean_obc.f90 rdb_ocean_obc.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_obc.f90 sourcefile~rdb_ocean_restart.f90 rdb_ocean_restart.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_restart.f90 sourcefile~rdb_ocean_restart_io.f90 rdb_ocean_restart_io.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_restart_io.f90 sourcefile~rdb_ocean_z_init.f90 rdb_ocean_z_init.F90 sourcefile~rdb_ocean_state.f90->sourcefile~rdb_ocean_z_init.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_surface_flux.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_surface_stress.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_tidal_mixing.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_tides.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_tides.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_tides.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_tides.f90->sourcefile~rdb_ocean_tide_astro.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_top_drag.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_ocean_isopycnal_slopes.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_ocean_varmix.f90->sourcefile~rdb_ocean_wave_speed.f90 sourcefile~rdb_ocean_vcoord.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_vcoord.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_vcoord.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_vcoord.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_vcoord.f90->sourcefile~rdb_vcoord.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_vdiff.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_scratch_3d.f90 sourcefile~rdb_ocean_vertical_advection.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_ocean_surface_flux.f90 sourcefile~rdb_ocean_vmix.f90->sourcefile~rdb_ocean_surface_stress.f90 sourcefile~rdb_ocean_wave_speed.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_wave_speed.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_wave_speed.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_wave_speed.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_ocean_wave_speed.f90->sourcefile~rdb_ocean_metrics.f90 sourcefile~rdb_recon_weno.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_recon_weno.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_remap_column.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_state.f90->sourcefile~rdb_config.f90 sourcefile~rdb_state.f90->sourcefile~rdb_tracer.f90 sourcefile~rdb_vcoord.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_bathymetry.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_bathymetry.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_bathymetry.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_bathymetry.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_bt_cont_type.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_bt_cont_type.f90->sourcefile~rdb_barotropic_workstate.f90 sourcefile~rdb_console_stats.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_console_stats.f90->sourcefile~rdb_efp.f90 sourcefile~rdb_ice_mass.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_mass.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_optics.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_optics.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_ice_column.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_ice_enthalpy.f90 sourcefile~rdb_ice_state.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_massless.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_bipolar.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag_mask.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_diag_mask.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_diag_mask.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_obc.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_obc.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_obc.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_pgf_reconstruct.f90->sourcefile~rdb_eos.f90 sourcefile~rdb_ocean_restart.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_decomp.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_restart_io.f90->sourcefile~rdb_ocean_restart.f90 sourcefile~rdb_ocean_tide_astro.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_config.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_error_ring.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_ocean_status.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_io_netcdf.f90 sourcefile~rdb_ocean_z_init.f90->sourcefile~rdb_multilayer_state.f90 sourcefile~rdb_scratch_3d.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_scratch_3d.f90->sourcefile~rdb_mem_report.f90 sourcefile~rdb_tracer.f90->sourcefile~rdb_constants.f90 sourcefile~rdb_tracer.f90->sourcefile~rdb_grid.f90 sourcefile~rdb_tracer.f90->sourcefile~rdb_mem_report.f90

Source Code

!! Ocean C ABI — lifecycle (P1) + accessors and the error ring (P2).
!!
!! `bind(c)` entry points for create / step / destroy, introspection (time,
!! step count, grid shape, scalar diagnostics), the error ring, and the P2
!! state-array getters/setters. See the "P2 — accessors and the error ring"
!! section below the P1 lifecycle code for the D<->H contract these follow
!! (`docs/ocean_python_api_plan.md` S3, `06_python_surface_design.md` D3/D4).
!!
!! Every entry point returns `integer(c_int)` status — never `error stop`
!! (a library cannot abort its host process). Status codes reuse
!! `rdb_ocean_status` (config/setup/IC-seed failures reported by
!! `read_config_from_string` / `validate_config` / the `configure_ocean_*`
!! chain / `ocean_state_seed_from_cfg` via their optional `ierr`, P0) plus
!! three handle-specific codes added here.
!!
!! Setup mirrors `benchmarks/bench_ocean.F90` exactly — grid init ->
!! `ocean_state_t%init_from_config` -> vcoord params -> seed IC -> register
!! tracers -> surface-flux slot -> the `configure_ocean_*` chain -> resolve
!! `n_inner` -> `ocean_state_enter_data` + `sf` device mapping. That is the
!! proven init ordering (also used by `driver_run_ocean`); this module does
!! not re-derive it.
!!
!! Single live ocean handle, enforced. Several modules hold process-global
!! topology/device state keyed to "the one running ocean simulation"
!! (`rdb_ocean_halo`, `rdb_ice_ocean_coupler`, `rdb_ocean_data_input`,
!! `rdb_profiler`, `rdb_ocean_diag_fills`) — a second concurrent create would
!! silently corrupt the first rather than merely waste memory. `g_handle_live`
!! below is the guard; it is ocean-specific POLICY, which is why it lives
!! here and not in the generic `rdb_handle`.
!!
!! Never exposed: `comm_env_finalize` (irreversible, calls `MPI_Finalize`).
!! `comm_env_init` IS called, on every create -- see `build_pending_handle`.
!!
!! Failure contract (P0.1 review F9): `rdb_ocean_create_from_string`
!! calls `handle_destroy` on EVERY non-`OCEAN_STATUS_OK` branch below,
!! before returning the status code — never partially and never retries.
!! `handle_destroy` deallocates the WHOLE `ocean_handle_t` (`cfg` +
!! `state` pointer target together, `rdb_handle.F90`), so a
!! `configure_ocean_*` step that writes a knob (e.g. flips `enable`)
!! before validating it and failing partway through leaves nothing
!! observable: the entire handle carrying that half-configured `cfg`/
!! `state` is thrown away, not reused. This is why individual
!! `configure_ocean_*` procedures are not each required to be
!! transactional/rollback-safe on their own — the ONE rollback boundary
!! is "destroy the whole handle" at the `create()` level, and it must
!! stay that way: adding a retry-with-the-same-handle path would revive
!! the half-configured-state hazard the review flagged.
module rdb_ocean_api
   use, intrinsic :: iso_c_binding, only: c_char, c_double, c_int, c_loc, c_null_ptr, c_ptr
   use, intrinsic :: iso_fortran_env, only: output_unit
   use rdb_constants, only: wp
   use rdb_handle, only: ocean_handle_t, handle_create, handle_destroy, handle_check, &
                         HANDLE_OK
   use rdb_config, only: read_config_from_string, validate_config
   use rdb_ocean_engine, only: engine_setup, engine_enter_data, engine_step, &
                               engine_step_ice, engine_step_finalize, engine_exit_data, &
                               engine_teardown
   use rdb_ocean_dyn, only: ocean_dyn_flush_tracer_window
   use rdb_comm_env, only: comm_env_init
   use rdb_ocean_status, only: OCEAN_STATUS_OK, OCEAN_STATUS_ERR_CONFIG_VALIDATE, &
                               OCEAN_STATUS_ERR_SETUP, OCEAN_STATUS_ERR_BAD_HANDLE, &
                               OCEAN_STATUS_ERR_ALREADY_EXISTS, &
                               OCEAN_STATUS_ERR_NOT_INITIALISED, &
                               OCEAN_STATUS_ERR_NOT_FOUND, OCEAN_STATUS_ERR_BAD_SHAPE, &
                               OCEAN_STATUS_ERR_NOT_PENDING
   use rdb_error_ring, only: fail, error_ring_push, error_ring_get, error_ring_count, &
                             error_ring_clear, ERROR_RING_MSG_LEN
   use rdb_ocean_eos_compute, only: ocean_eos_compute
   ! NetCDF-free (deliberately NOT `rdb_bathymetry`, which requires
   ! RDB_ENABLE_NETCDF=ON purely because it also contains the NetCDF
   ! reader — see rdb_ocean_bathymetry_inject's module docstring). Renamed
   ! on import so the existing rdb_ocean_set_bathymetry call sites below
   ! (P2) are untouched.
   use rdb_ocean_bathymetry_inject, only: fill_bathymetry_ghosts_array => bathymetry_fill_ghosts_array
   use rdb_ocean_periodic, only: ocean_periodic_wrap_centre_2d
   use rdb_ocean_fold_apply, only: ocean_fold_wrap_eta_2d
   ! ---- P2.5: pre-create geometry injection ----
   use rdb_ocean_halo_width, only: required_halo
   ! ---- P7: diagnostics discoverability + in-memory access ----
   use rdb_ocean_diag_derived, only: derived_catalog_size, derived_catalog_name
   use rdb_ocean_diag_fills, only: canonical_diag_catalog_size, canonical_diag_catalog_name
   implicit none
   private

   public :: rdb_ocean_create_from_string
   public :: rdb_ocean_step
   public :: rdb_ocean_destroy
   ! ---- P2.5: pre-create geometry injection ----
   public :: rdb_ocean_create_pending
   public :: rdb_ocean_create_finalize
   public :: rdb_ocean_stage_bathymetry
   public :: rdb_ocean_stage_metrics
   public :: rdb_ocean_stage_topology
   public :: rdb_ocean_required_halo
   public :: rdb_ocean_get_time
   public :: rdb_ocean_get_step_count
   public :: rdb_ocean_get_grid_info
   public :: rdb_ocean_get_total_mass
   public :: rdb_working_precision
   ! ---- P2: error ring ----
   public :: rdb_ocean_last_error
   public :: rdb_flush_logs
   ! ---- P2: D<->H refresh ----
   public :: rdb_ocean_refresh_host
   ! ---- P2: getters ----
   public :: rdb_ocean_get_h_layer_ptr
   public :: rdb_ocean_get_u_face_x_layer_ptr
   public :: rdb_ocean_get_v_face_y_layer_ptr
   public :: rdb_ocean_get_hu_ptr
   public :: rdb_ocean_get_hv_ptr
   public :: rdb_ocean_get_w_interface_ptr
   public :: rdb_ocean_get_rho_layer_ptr
   public :: rdb_ocean_get_b_ptr
   public :: rdb_ocean_get_bt_eta_ptr
   public :: rdb_ocean_get_tau_x_ptr
   public :: rdb_ocean_get_tau_y_ptr
   public :: rdb_ocean_get_q_heat_ptr
   public :: rdb_ocean_get_q_salt_ptr
   public :: rdb_ocean_get_kv_ptr
   public :: rdb_ocean_get_kt_ptr
   public :: rdb_ocean_get_ks_ptr
   public :: rdb_ocean_get_wet_t_ptr
   ! ---- P2: tracers by name ----
   public :: rdb_ocean_get_tracer_count
   public :: rdb_ocean_list_tracers
   public :: rdb_ocean_get_tracer_ptr
   ! ---- P2: narrow setters ----
   public :: rdb_ocean_set_tracer
   public :: rdb_ocean_set_h
   public :: rdb_ocean_set_u
   public :: rdb_ocean_set_v
   public :: rdb_ocean_set_bathymetry
   public :: rdb_ocean_set_wind
   public :: rdb_ocean_set_heat_flux
   public :: rdb_ocean_set_salt_flux
   ! ---- P2: scalar diagnostics ----
   public :: rdb_ocean_get_kinetic_energy
   ! ---- P7: diagnostics discoverability ----
   public :: rdb_ocean_derived_catalog_size
   public :: rdb_ocean_derived_catalog_name
   public :: rdb_ocean_canonical_catalog_size
   public :: rdb_ocean_canonical_catalog_name
   public :: rdb_ocean_get_diag_count
   public :: rdb_ocean_list_diags
   ! ---- P7: in-memory diagnostic access ----
   public :: rdb_ocean_get_diagnostic_ptr

   logical, save :: g_handle_live = .false.
      !! True from a successful create() until destroy(). Guards the single-
      !! live-ocean-handle invariant (see module header). Module-`save`
      !! state is the correct tool here — it mirrors the process-global
      !! singletons it is protecting against, not a design smell.

   real(wp), parameter :: RHO0_DIAG = 1025.0_wp
      !! Reference density used ONLY to give the total-mass diagnostic
      !! (`rdb_ocean_get_total_mass`) units of kg. The solver itself is
      !! Boussinesq (continuity conserves volume/thickness, not mass), so
      !! this constant does not feed back into the dynamics anywhere — it
      !! exists purely so the diagnostic is dimensionally a mass rather than
      !! a bare volume, and cancels out of any before/after conservation
      !! check a caller does with it.

contains

   ! ================================================================
   ! Lifecycle
   ! ================================================================

   function rdb_ocean_create_from_string(nml_text, nml_len, handle_out) &
      result(status) bind(c, name="rdb_ocean_create_from_string")
      !! Build a config from an in-memory namelist string (no filesystem
      !! touch), set up the ocean dyn-core exactly as `bench_ocean` /
      !! `driver_run_ocean` do, map it onto the device, and hand back an
      !! opaque handle. On ANY failure, `handle_out` is `c_null_ptr` and the
      !! partially-built handle (if one was allocated) is freed — never a
      !! half-initialised handle escaping to the caller.
      integer(c_int), intent(in), value :: nml_len
      character(kind=c_char), intent(in) :: nml_text(nml_len)
      type(c_ptr), intent(out) :: handle_out
      integer(c_int) :: status

      type(c_ptr) :: c_handle
      type(ocean_handle_t), pointer :: h
      character(len=:), allocatable :: text

      handle_out = c_null_ptr
      call error_ring_clear()

      if (g_handle_live) then
         call error_ring_push("rdb_ocean_create_from_string: a live ocean handle "// &
                              "already exists in this process (multi-instance is not "// &
                              "supported) — destroy it before creating another")
         status = int(OCEAN_STATUS_ERR_ALREADY_EXISTS, c_int)
         return
      end if

      call c_to_f_string(nml_text, nml_len, text)

      call build_pending_handle(text, c_handle, h, status)
      if (status /= int(OCEAN_STATUS_OK, c_int)) return   ! already destroyed
      g_handle_live = .true.

      ! ---- Setup (P2.4) — driver_run_ocean's exact 21-stage sequence,
      ! via the shared engine. Single-rank: compute_rank=0, compute_size=1
      ! (defaults); no restart on the C ABI yet (a future phase's concern,
      ! not silently dropped — `engine_setup`'s `restart_file` is simply
      ! not passed here). This is where the API used to run its OWN
      ! independent 9-stage subset (configure_ocean_metrics/_forcing/
      ! _drag/_vmix/_lateral/_pgf/_bt/_bt_split/_land_mask only) — every
      ! other stage (bc/diag/hdiff/porous/p_surf/sponge/tides/tracers/
      ! wave_drag/wetdry/dataovr/halo_init) is now reachable through here
      ! too. No geometry staged on this single-call path (P2.5's
      ! rdb_ocean_create_pending + rdb_ocean_stage_* do that) — so
      ! engine_setup falls through to its ordinary namelist-driven path,
      ! byte-identical to before P2.5. ----
      call complete_ocean_create(c_handle, h, status)
      handle_out = c_handle   ! c_null_ptr iff complete_ocean_create failed
   end function rdb_ocean_create_from_string

   subroutine build_pending_handle(text, c_handle, h, status)
      !! Shared prefix of `rdb_ocean_create_from_string` AND
      !! `rdb_ocean_create_pending` (P2.5): allocate a handle, parse +
      !! validate the config, check `dt_fixed`. Does NOT touch
      !! `g_handle_live` — callers set it themselves once they know which
      !! of the two flows they are in (immediate `complete_ocean_create` vs
      !! staying pending for geometry injection).
      !!
      !! On success: `c_handle`/`h` are a valid, freshly allocated handle
      !! (`h%is_pending`/`h%is_initialised` both still `.false.`).
      !! On failure: the handle has ALREADY been destroyed (matches the F9
      !! "destroy the whole handle on any failure" contract) — `c_handle`
      !! is left as returned by `handle_create`/`handle_destroy` (the
      !! caller must not touch it further) and `h` is null.
      character(len=*), intent(in) :: text
      type(c_ptr), intent(out) :: c_handle
      type(ocean_handle_t), pointer, intent(out) :: h
      integer(c_int), intent(out) :: status

      integer :: ierr, hstat

      ! Bring the comm env up before ANYTHING touches it.  The API is
      ! single-rank by design (compute_rank=0, compute_size=1), but
      ! "single-rank" is not "no MPI": in an RDB_ENABLE_MPI=ON build the
      ! setup path reaches `comm_world()`, and reaching it before MPI is
      ! initialised is not a soft failure --
      !
      !   *** The MPI_Comm_f2c() function was called before MPI_INIT was
      !   *** invoked.  This is disallowed by the MPI standard.
      !
      ! -- the process aborts.  Both create entry points funnel through
      ! here, so this is the one place that needs it, and `comm_env_init`
      ! is idempotent, so paying it per create costs nothing.
      !
      ! NOT a fix for being embedded in a host that has ALREADY called
      ! MPI_Init (mpi4py, a C driver of its own).  `pic_mpi_init` calls
      ! `MPI_Init_thread` unconditionally and pic-mpi exposes no
      ! `MPI_Initialized` wrapper to guard on, so that case would still
      ! double-initialise.  Closing it needs an upstream wrapper: the
      ! `no-mpi-in-rdb` rule means this file cannot ask MPI directly.
      call comm_env_init()

      c_handle = handle_create()
      hstat = handle_check(c_handle, h)
      if (hstat /= HANDLE_OK) then
         ! Unreachable in practice (we just created it), but never trust a
         ! resolve blindly — fail loud rather than dereference a null h.
         call handle_destroy(c_handle)
         h => null()
         status = int(OCEAN_STATUS_ERR_BAD_HANDLE, c_int)
         return
      end if

      call read_config_from_string(text, h%cfg, ierr=ierr)
      if (ierr /= OCEAN_STATUS_OK) then
         status = int(ierr, c_int)
         call handle_destroy(c_handle)
         h => null()
         return
      end if

      call validate_config(h%cfg, ierr=ierr)
      if (ierr /= OCEAN_STATUS_OK) then
         status = int(ierr, c_int)
         call handle_destroy(c_handle)
         h => null()
         return
      end if

      if (h%cfg%dt_fixed <= 0.0_wp) then
         status = int(OCEAN_STATUS_ERR_CONFIG_VALIDATE, c_int)
         call handle_destroy(c_handle)
         h => null()
         return
      end if

      status = int(OCEAN_STATUS_OK, c_int)
   end subroutine build_pending_handle

   subroutine complete_ocean_create(c_handle, h, status)
      !! Shared tail of `rdb_ocean_create_from_string` AND
      !! `rdb_ocean_create_finalize` (P2.5): `engine_setup` ->
      !! resolved-`n_inner` check -> `engine_enter_data` -> finalize handle
      !! bookkeeping. Consumes whatever geometry `h%engine%staged_*` fields
      !! carry (empty/unset for the single-call path — byte-identical to
      !! before P2.5). On any failure, destroys the WHOLE handle (F9) and
      !! clears the single-live-handle guard; `c_handle` is `c_null_ptr` and
      !! `h` is null on return in that case, matching `rdb_ocean_destroy`'s
      !! out-null convention.
      type(c_ptr), intent(inout) :: c_handle
      type(ocean_handle_t), pointer, intent(inout) :: h
      integer(c_int), intent(out) :: status

      integer :: ierr

      call engine_setup(h%engine, h%cfg, ierr)
      if (ierr /= OCEAN_STATUS_OK) then
         status = int(ierr, c_int)
         call handle_destroy(c_handle)
         g_handle_live = .false.
         h => null()
         return
      end if

      if (h%engine%n_inner < 1) then
         call error_ring_push("rdb_ocean_create: barotropic substep count "// &
                              "n_inner is unresolved (< 1) — set &ocean_bt_nml auto_n_inner "// &
                              "= .true. or n_inner explicitly")
         status = int(OCEAN_STATUS_ERR_SETUP, c_int)
         call handle_destroy(c_handle)
         g_handle_live = .false.
         h => null()
         return
      end if
      h%n_inner = h%engine%n_inner

      ! ---- Device placement ----
      call engine_enter_data(h%engine, h%cfg)
      ! `h%grid`/`h%state` are views onto the engine (see `ocean_handle_t`'s
      ! docstring) so every existing P2 accessor body below (`h%grid%...`,
      ! `h%state%...`) keeps working unchanged.
      h%grid = h%engine%grid
      h%state => h%engine%state
      h%device_mapped = .true.

      h%is_initialised = .true.
      h%is_pending = .false.
      status = int(OCEAN_STATUS_OK, c_int)
   end subroutine complete_ocean_create

   function rdb_ocean_create_pending(nml_text, nml_len, handle_out) &
      result(status) bind(c, name="rdb_ocean_create_pending")
      !! P2.5 phase 1 of 2: build + validate a config and allocate a
      !! handle EXACTLY like `rdb_ocean_create_from_string`, but stop
      !! there — does NOT run `engine_setup` or map the device. Claims the
      !! single-live-handle guard immediately (a second concurrent
      !! create/create_pending is refused from this point on, even though
      !! `engine_setup` has not yet touched any process-global state — the
      !! invariant is "one handle mid-create at a time", not merely "one
      !! finished one").
      !!
      !! Between this call and `rdb_ocean_create_finalize`, the
      !! `rdb_ocean_stage_*` entry points below inject geometry
      !! (bathymetry, grid metrics, topology) that `engine_setup` consumes
      !! — exactly the window passive-tracer registration already needed
      !! (`registry_locked` closes at `enter_data`, `rdb_multilayer_state.F90`).
      !! A caller with no geometry to inject has no reason to use this
      !! entry point — `rdb_ocean_create_from_string` remains the
      !! normal one-call path and is completely unaffected by this phase.
      integer(c_int), intent(in), value :: nml_len
      character(kind=c_char), intent(in) :: nml_text(nml_len)
      type(c_ptr), intent(out) :: handle_out
      integer(c_int) :: status

      type(c_ptr) :: c_handle
      type(ocean_handle_t), pointer :: h
      character(len=:), allocatable :: text

      handle_out = c_null_ptr
      call error_ring_clear()

      if (g_handle_live) then
         call error_ring_push("rdb_ocean_create_pending: a live ocean handle "// &
                              "already exists in this process (multi-instance is not "// &
                              "supported) — destroy it before creating another")
         status = int(OCEAN_STATUS_ERR_ALREADY_EXISTS, c_int)
         return
      end if

      call c_to_f_string(nml_text, nml_len, text)

      call build_pending_handle(text, c_handle, h, status)
      if (status /= int(OCEAN_STATUS_OK, c_int)) return   ! already destroyed

      h%is_pending = .true.
      g_handle_live = .true.
      handle_out = c_handle
   end function rdb_ocean_create_pending

   function rdb_ocean_create_finalize(c_handle) result(status) &
      bind(c, name="rdb_ocean_create_finalize")
      !! P2.5 phase 2 of 2: complete a handle started by
      !! `rdb_ocean_create_pending` (optionally staged with
      !! `rdb_ocean_stage_*` geometry in between) — runs `engine_setup`
      !! (consuming any staged geometry) through device mapping, exactly
      !! like the tail of `rdb_ocean_create_from_string`.
      !!
      !! On success, `c_handle` is unchanged (same pointer value, now fully
      !! initialised) — call the ordinary P2 accessors/`rdb_ocean_step`
      !! on it from here. On failure, the WHOLE handle is destroyed (F9)
      !! and `c_handle` is set to `c_null_ptr` — same out-null convention
      !! as `rdb_ocean_destroy`, since (unlike `create_from_string`,
      !! whose caller only ever sees a handle on success) a caller here
      !! already holds a handle value going in.
      type(c_ptr), intent(inout) :: c_handle
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: hstat

      call error_ring_clear()
      hstat = handle_check(c_handle, h)
      if (hstat /= HANDLE_OK) then
         status = int(OCEAN_STATUS_ERR_BAD_HANDLE, c_int)
         return
      end if
      if (.not. h%is_pending) then
         call error_ring_push("rdb_ocean_create_finalize: handle is not in the "// &
                              "pending (staging) window — call rdb_ocean_create_pending "// &
                              "first (this handle may already have been finalised)")
         status = int(OCEAN_STATUS_ERR_NOT_PENDING, c_int)
         return
      end if

      call complete_ocean_create(c_handle, h, status)
   end function rdb_ocean_create_finalize

   function rdb_ocean_stage_bathymetry(c_handle, b_data, nx_p, ny_p, convention) &
      result(status) bind(c, name="rdb_ocean_stage_bathymetry")
      !! P2.5: stage an interior-sized `(nx_p, ny_p)` bathymetry array on
      !! the PENDING handle `c_handle` (`rdb_ocean_create_pending`).
      !! Consumed by `engine_setup` inside `rdb_ocean_create_finalize` —
      !! see `rdb_ocean_bathymetry_inject` for the sign-normalisation +
      !! wet-fraction validation the array goes through THERE (not here:
      !! shape can't be checked against `nx_phys`/`ny_phys` until the grid
      !! exists, which `engine_setup` builds).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p
      real(c_double), intent(in) :: b_data(nx_p, ny_p)
      integer(c_int), intent(in), value :: convention
         !! `BATHY_CONVENTION_DEPTH_POSITIVE_DOWN` (1) or
         !! `_HEIGHT_POSITIVE_UP` (2) — REQUIRED, no default (D6.2: sign is
         !! the single most dangerous argument in this API). Any other
         !! value is rejected at `create_finalize` time (not here).
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      status = resolve_ocean_pending(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      if (allocated(h%engine%staged_bathymetry)) deallocate (h%engine%staged_bathymetry)
      allocate (h%engine%staged_bathymetry(nx_p, ny_p), source=real(b_data, wp))
      h%engine%staged_bathymetry_convention = int(convention)
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_stage_bathymetry

   function rdb_ocean_stage_metrics(c_handle, x, y, dx, dy, area, nxp, nyp, nx, ny) &
      result(status) bind(c, name="rdb_ocean_stage_metrics")
      !! P2.5: stage in-memory MOM6-style supergrid arrays on the PENDING
      !! handle `c_handle` — `metrics_assemble_from_supergrid_arrays`'s
      !! exact layout (`x`/`y`: `(nxp,nyp)` degrees; `dx`: `(nx,nyp)` m;
      !! `dy`: `(nxp,ny)` m; `area`: `(nx,ny)` m^2, where
      !! `nxp=2*nx_phys+1`, `nyp=2*ny_phys+1`, `nx=2*nx_phys`,
      !! `ny=2*ny_phys`). Consumed inside `rdb_ocean_create_finalize`,
      !! bypassing `cfg%ocean%grid%grid_config` entirely — exports the
      !! SAME assembler the NetCDF supergrid reader and the analytic
      !! tripolar generator already use, so this and a mosaic-file grid
      !! produce identical metrics for the identical arrays. Shape is
      !! validated at `create_finalize` time (against the grid, which does
      !! not exist yet here).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nxp, nyp, nx, ny
      real(c_double), intent(in) :: x(nxp, nyp), y(nxp, nyp)
      real(c_double), intent(in) :: dx(nx, nyp), dy(nxp, ny)
      real(c_double), intent(in) :: area(nx, ny)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      status = resolve_ocean_pending(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      if (allocated(h%engine%staged_metrics_x)) then
         deallocate (h%engine%staged_metrics_x, h%engine%staged_metrics_y, &
                     h%engine%staged_metrics_dx, h%engine%staged_metrics_dy, &
                     h%engine%staged_metrics_area)
      end if
      allocate (h%engine%staged_metrics_x(nxp, nyp), source=real(x, wp))
      allocate (h%engine%staged_metrics_y(nxp, nyp), source=real(y, wp))
      allocate (h%engine%staged_metrics_dx(nx, nyp), source=real(dx, wp))
      allocate (h%engine%staged_metrics_dy(nxp, ny), source=real(dy, wp))
      allocate (h%engine%staged_metrics_area(nx, ny), source=real(area, wp))
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_stage_metrics

   function rdb_ocean_stage_topology(c_handle, periodic_x, periodic_y) &
      result(status) bind(c, name="rdb_ocean_stage_topology")
      !! P2.5: stage Oceananigans-style grid topology (per-dimension
      !! periodicity — "the grid owns periodicity", not the per-edge
      !! `&ocean_bc_nml` tags) on the PENDING handle `c_handle`. Consumed
      !! inside `rdb_ocean_create_finalize` via
      !! `ocean_bc_state_set_topology`, run AFTER the namelist edge tags
      !! are parsed and OVERRIDING whatever they derived for
      !! `periodic_x`/`periodic_y` (an axis NOT marked periodic here keeps
      !! whatever physical BC the namelist gave it).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: periodic_x, periodic_y
         !! Nonzero = periodic on that axis.
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      status = resolve_ocean_pending(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      h%engine%staged_periodic_x = (periodic_x /= 0_c_int)
      h%engine%staged_periodic_y = (periodic_y /= 0_c_int)
      h%engine%has_staged_topology = .true.
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_stage_topology

   function rdb_ocean_required_halo(pv_adv_scheme, pv_adv_scheme_len, &
                                    tracer_recon, tracer_recon_len, &
                                    periodic, tripolar_fold, decomposed, &
                                    kappa_shear_at_vertex) result(ng) &
      bind(c, name="rdb_ocean_required_halo")
      !! Stateless (no handle required — like `rdb_working_precision`):
      !! the minimum `nghost` for a given scheme/topology selection,
      !! folding the six scattered per-scheme/per-topology minimums behind
      !! `rdb_ocean_halo_width::required_halo` so a Python caller sizing
      !! a grid's halo before `create()` agrees with what `engine_setup`
      !! will itself enforce. A zero-length scheme string means "unset"
      !! (baseline 2), matching the Fortran function's
      !! absent-optional-argument behaviour.
      integer(c_int), intent(in), value :: pv_adv_scheme_len
      character(kind=c_char), intent(in) :: pv_adv_scheme(pv_adv_scheme_len)
      integer(c_int), intent(in), value :: tracer_recon_len
      character(kind=c_char), intent(in) :: tracer_recon(tracer_recon_len)
      integer(c_int), intent(in), value :: periodic, tripolar_fold, decomposed
      integer(c_int), intent(in), value :: kappa_shear_at_vertex
      integer(c_int) :: ng

      character(len=:), allocatable :: pv_s, tr_s

      call c_to_f_string(pv_adv_scheme, pv_adv_scheme_len, pv_s)
      call c_to_f_string(tracer_recon, tracer_recon_len, tr_s)
      ng = int(required_halo(pv_adv_scheme=pv_s, tracer_recon=tr_s, &
                             periodic=(periodic /= 0_c_int), &
                             tripolar_fold=(tripolar_fold /= 0_c_int), &
                             decomposed=(decomposed /= 0_c_int), &
                             kappa_shear_at_vertex=(kappa_shear_at_vertex /= 0_c_int)), c_int)
   end function rdb_ocean_required_halo

   function rdb_ocean_step(c_handle, n_steps) result(status) &
      bind(c, name="rdb_ocean_step")
      !! Advance `n_steps` fixed-`dt` (`cfg%dt_fixed`) outer steps via the
      !! shared `engine_step` / `engine_step_ice` / `engine_step_finalize`
      !! sequence (P2.4 + P2.4b) — the SAME calls `driver_run_ocean`'s time
      !! loop makes: the dyn-core advance, then sea-ice per-step physics
      !! (`engine_step_ice` — a no-op when `&ocean_ice_nml enable = .false.`,
      !! so this is bit-identical to before P2.4b for every non-ice config),
      !! then surface-flux-component assembly / the diag step. `n_steps <=
      !! 0` is a successful no-op (mirrors an empty range, not an error).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: n_steps
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: i
      real(wp) :: dt
      integer :: step_ierr

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (n_steps <= 0_c_int) return

      dt = h%cfg%dt_fixed
      do i = 1, int(n_steps)
         call engine_step(h%engine, dt, h%t_current, ierr=step_ierr)
         if (step_ierr /= OCEAN_STATUS_OK) then
            status = int(step_ierr, c_int)
            return
         end if
         call engine_step_ice(h%engine, h%cfg, dt, h%t_current, ierr=step_ierr)
         if (step_ierr /= OCEAN_STATUS_OK) then
            status = int(step_ierr, c_int)
            return
         end if
         call engine_step_finalize(h%engine, dt, h%t_current, ierr=step_ierr)
         if (step_ierr /= OCEAN_STATUS_OK) then
            status = int(step_ierr, c_int)
            return
         end if
         h%t_current = h%t_current + dt
      end do
      ! P2 D<->H contract (docs/ocean_python_api_plan.md S3): a step only
      ! CLEARS the lazy-sync flag, never syncs — the next accessor that
      ! needs a live host view calls rdb_ocean_refresh_host itself.
      h%host_is_current = .false.
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_step

   function rdb_ocean_destroy(c_handle) result(status) &
      bind(c, name="rdb_ocean_destroy")
      !! Idempotent: a null/already-destroyed/garbage handle is a no-op
      !! success (so a Python `__del__` can call this blind). On a live
      !! handle: unwind device residency (`engine_exit_data`) then release
      !! the god-state's host-side allocations + the process-global ocean-
      !! halo module state (`engine_teardown`), then free the handle itself
      !! and clear the single-live-handle guard.
      type(c_ptr), intent(inout) :: c_handle
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: hstat

      hstat = handle_check(c_handle, h)
      if (hstat /= HANDLE_OK) then
         ! Already destroyed, never created, or garbage: idempotent no-op.
         c_handle = c_null_ptr
         status = int(OCEAN_STATUS_OK, c_int)
         return
      end if

      if (h%device_mapped) then
         call engine_exit_data(h%engine)
         h%device_mapped = .false.
      end if
      ! P2.5: a handle that never got past rdb_ocean_create_pending (no
      ! finalize call, abandoned mid-staging) never ran engine_setup —
      ! engine%state was never init_from_config'd, ocean_halo_init/
      ! engine%geo%init never ran, so there is nothing for engine_teardown
      ! to tear down. Guard on engine%is_setup (set only at the tail of
      ! engine_setup) rather than calling it unconditionally.
      if (h%engine%is_setup) call engine_teardown(h%engine)
      h%state => null()

      call handle_destroy(c_handle)
      g_handle_live = .false.
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_destroy

   ! ================================================================
   ! Queries
   ! ================================================================

   function rdb_ocean_get_time(c_handle, t_out) result(status) &
      bind(c, name="rdb_ocean_get_time")
      !! Current simulation time (seconds).
      type(c_ptr), intent(in), value :: c_handle
      real(c_double), intent(out) :: t_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      t_out = 0.0_c_double
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      t_out = real(h%t_current, c_double)
   end function rdb_ocean_get_time

   function rdb_ocean_get_step_count(c_handle, step_out) result(status) &
      bind(c, name="rdb_ocean_get_step_count")
      !! Outer-step counter. Reads `dyn%outer_step_count` directly (the
      !! kernel's own bookkeeping) rather than keeping a second counter on
      !! the handle, so there is exactly one source of truth.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(out) :: step_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      step_out = 0_c_int
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      step_out = int(h%state%dyn%outer_step_count, c_int)
   end function rdb_ocean_get_step_count

   function rdb_ocean_get_grid_info(c_handle, nx, ny, nz, nghost) result(status) &
      bind(c, name="rdb_ocean_get_grid_info")
      !! Physical (interior, ghost-excluded) grid shape + ghost width.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(out) :: nx, ny, nz, nghost
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      nx = 0
      ny = 0
      nz = 0
      nghost = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      nx = int(h%grid%nx_phys, c_int)
      ny = int(h%grid%ny_phys, c_int)
      nz = int(h%state%multilayer%nz_ml, c_int)
      nghost = int(h%grid%nghost, c_int)
   end function rdb_ocean_get_grid_info

   function rdb_ocean_get_total_mass(c_handle, m_out) result(status) &
      bind(c, name="rdb_ocean_get_total_mass")
      !! One scalar diagnostic — total water mass over the physical domain
      !! (`sum(h_layer * areaT) * RHO0_DIAG`) — so a caller can prove the
      !! solver actually advanced (and, in a closed quiescent/wall basin,
      !! that it is conserving mass) without any state-array accessor (P2).
      !! `!$acc update self` on the leaf array via `associate` (never the
      !! aggregate `ocean_state_t`/`multilayer_state_t`) before summing, per
      !! the D<->H contract — inert on a host build, load-bearing on GPU.
      type(c_ptr), intent(in), value :: c_handle
      real(c_double), intent(out) :: m_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp) :: total

      m_out = 0.0_c_double
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      associate (hlayer => h%state%multilayer%h_layer, ng => h%grid%nghost, &
                 nxp => h%grid%nx_phys, nyp => h%grid%ny_phys)
         !$acc update self(hlayer)
         if (handle_has_area(h)) then
            ! Curvilinear (spherical / supergrid / tripolar) cells differ
            ! in area, and `grid%dx*grid%dy` is a placeholder there (1 m^2
            ! on a supergrid), so weight each column by its own `areaT`.
            total = cell_area_weighted_sum(hlayer, h%state%metrics%areaT, ng, nxp, nyp)
         else
            total = sum(hlayer(ng + 1:ng + nxp, ng + 1:ng + nyp, :))*h%grid%dx*h%grid%dy
         end if
      end associate

      m_out = real(total*RHO0_DIAG, c_double)
   end function rdb_ocean_get_total_mass

   function handle_has_area(h) result(has_area)
      !! True when the handle's metrics carry a full ghosted `areaT` (every
      !! grid the engine builds does); anything else falls back to
      !! `grid%dx*grid%dy`.
      type(ocean_handle_t), pointer, intent(in) :: h
      logical :: has_area
      has_area = .false.
      if (.not. allocated(h%state%metrics%areaT)) return
      has_area = size(h%state%metrics%areaT, 1) == size(h%state%multilayer%h_layer, 1) &
                 .and. size(h%state%metrics%areaT, 2) == size(h%state%multilayer%h_layer, 2)
   end function handle_has_area

   pure function cell_area_weighted_sum(f, area, ng, nxp, nyp) result(total)
      !! `sum_k sum_ij f(i,j,k)*area(i,j)` over the physical interior.
      ! assumed-shape-ok: host-side scalar diagnostic, not a device kernel.
      real(wp), intent(in) :: f(:, :, :), area(:, :)
      integer, intent(in) :: ng, nxp, nyp
      real(wp) :: total
      integer :: i, j, k
      total = 0.0_wp
      do k = 1, size(f, 3)
         do j = ng + 1, ng + nyp
            do i = ng + 1, ng + nxp
               total = total + f(i, j, k)*area(i, j)
            end do
         end do
      end do
   end function cell_area_weighted_sum

   function rdb_working_precision() result(bytes) &
      bind(c, name="rdb_working_precision")
      !! Bytes per `wp` (4 or 8) — so a Python caller resolves float32 vs
      !! float64 at load time instead of guessing. No handle needed: this
      !! is a build-time constant.
      integer(c_int) :: bytes

      bytes = int(storage_size(1.0_wp)/8, c_int)
   end function rdb_working_precision

   ! ================================================================
   ! P2 — the error ring
   ! ================================================================

   function rdb_ocean_last_error(idx, buf, cap) result(len_out) &
      bind(c, name="rdb_ocean_last_error")
      !! Read the error ring at ring-relative index `idx` (0 = most
      !! recent push). Copies up to `cap` bytes of the trimmed message into
      !! `buf`, NUL-terminating if room remains, and returns the FULL
      !! trimmed message length — a `snprintf`-style contract: a returned
      !! length >= `cap` means the copy was truncated. Returns 0 (buf
      !! untouched) if `idx` is out of `[0, count)` or `cap <= 0`.
      !!
      !! The ring is the ONLY channel a caller should trust for the
      !! SPECIFIC reason behind a coarse status code (D4.3,
      !! `06_python_surface_design.md`) — never scrape stdout.
      integer(c_int), intent(in), value :: idx
      integer(c_int), intent(in), value :: cap
      character(kind=c_char), intent(out) :: buf(cap)
      integer(c_int) :: len_out

      character(len=ERROR_RING_MSG_LEN) :: msg
      integer :: n, ncopy, i

      len_out = 0_c_int
      if (idx < 0_c_int .or. idx >= int(error_ring_count(), c_int) .or. cap <= 0_c_int) return

      msg = error_ring_get(int(idx))
      n = len_trim(msg)
      len_out = int(n, c_int)
      ncopy = min(n, int(cap))
      do i = 1, ncopy
         buf(i) = msg(i:i)
      end do
      if (ncopy < cap) buf(ncopy + 1) = achar(0)
   end function rdb_ocean_last_error

   subroutine rdb_flush_logs() bind(c, name="rdb_flush_logs")
      !! Flush the buffered log stream (unit 6 / stdout — what
      !! `pic_logger`'s `global_logger` writes to; it exposes no flush of
      !! its own). Call this in a `finally` around a C entry point if the
      !! human-readable log needs to be ordered relative to Python's own
      !! stdout (D4.4) — the ring (above) is still the only channel to
      !! trust for the SPECIFIC failure reason.
      flush (output_unit)
   end subroutine rdb_flush_logs

   ! ================================================================
   ! P2 — D<->H refresh
   ! ================================================================

   function rdb_ocean_refresh_host(c_handle) result(status) &
      bind(c, name="rdb_ocean_refresh_host")
      !! Lazy device->host refresh of every P2-exposed leaf array, gated on
      !! `host_is_current` (a second call with nothing new on device is a
      !! cheap flag check, not a re-copy). `!$acc update self` runs on
      !! LEAF component names only, via `associate` — never the aggregate
      !! `ocean_state_t`/sub-state derived type (the documented
      !! `rdb_ocean_dyn.F90:2949` segfault: an aggregate D->H copy
      !! overwrites the host allocatable descriptors with DEVICE
      !! addresses). Inert on a host-only build (no `!$acc` support) —
      !! see CLAUDE.md's GPU-verification caveat.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      call ocean_handle_refresh_host(h)
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_refresh_host

   ! ================================================================
   ! P2 — getters: (ptr, extents..., generation)
   !
   ! None of these refresh the host copy themselves — per the D3.4 lazy-
   ! sync design, the pointer + `generation` (== `dyn%outer_step_count` at
   ! issue) are cheap to hand back every call; the CALLER compares
   ! `generation` against what it last saw and, on a mismatch, calls
   ! `rdb_ocean_refresh_host` itself before dereferencing. This is what
   ! keeps "pay only when you touch it" true: a getter call alone never
   ! triggers a D->H copy.
   !
   ! `mass_flux_x/y_layer` and `flux_h_layer` are deliberately NOT exposed
   ! — they are `!$acc enter data create(...)`-mapped (rdb_multilayer_state
   ! .F90), so there is no valid host copy at any point in the run
   ! (docs/ocean_python_api_plan.md S3).
   ! ================================================================

   function rdb_ocean_get_h_layer_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_h_layer_ptr")
      !! Layer thickness (m), cell-centred, FULL extent (ghosts included):
      !! `multilayer%h_layer`, shape `(nx_total, ny_total, nz_ml)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%h_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_h_layer_ptr

   function rdb_ocean_get_u_face_x_layer_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_u_face_x_layer_ptr")
      !! West-face x-velocity (m/s): `multilayer%u_face_x_layer`, shape
      !! `(nx_total+1, ny_total, nz_ml)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%u_face_x_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_u_face_x_layer_ptr

   function rdb_ocean_get_v_face_y_layer_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_v_face_y_layer_ptr")
      !! South-face y-velocity (m/s): `multilayer%v_face_y_layer`, shape
      !! `(nx_total, ny_total+1, nz_ml)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%v_face_y_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_v_face_y_layer_ptr

   function rdb_ocean_get_hu_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_hu_ptr")
      !! West-face x transport (h*u, m^2/s): `multilayer%hu_face_x_layer`,
      !! same shape/stagger as `u_face_x_layer`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%hu_face_x_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_hu_ptr

   function rdb_ocean_get_hv_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_hv_ptr")
      !! South-face y transport (h*v, m^2/s): `multilayer%hv_face_y_layer`,
      !! same shape/stagger as `v_face_y_layer`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%hv_face_y_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_hv_ptr

   function rdb_ocean_get_w_interface_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_w_interface_ptr")
      !! Vertical velocity at layer interfaces (m/s):
      !! `multilayer%w_interface`, shape `(nx_total, ny_total, nz_ml+1)`,
      !! k=1 bed .. k=nz_ml+1 surface.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%w_interface
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_w_interface_ptr

   function rdb_ocean_get_rho_layer_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_rho_layer_ptr")
      !! In-situ density (kg/m^3): `multilayer%rho_layer`, same shape as
      !! `h_layer`. Filled by the EOS each thermo step (and by every P2
      !! setter that touches T/S/h).
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%multilayer%rho_layer
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_rho_layer_ptr

   function rdb_ocean_get_b_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_b_ptr")
      !! Bathymetry (m, POSITIVE DOWN — `eta = sum(h) - b`):
      !! `barotropic%b`, shape `(nx_total, ny_total)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%barotropic%b
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_b_ptr

   function rdb_ocean_get_bt_eta_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_bt_eta_ptr")
      !! Sea-surface height (m): `dyn%bt_work%bt_eta`, shape
      !! `(nx_total, ny_total)`. Diagnostic (`sum_k(h_layer) - bt_H_ref`),
      !! not independently settable — write `h` or `b` instead.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%dyn%bt_work%bt_eta
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_bt_eta_ptr

   function rdb_ocean_get_tau_x_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_tau_x_ptr")
      !! East-face wind stress (N/m^2): `surface_stress%tau_x`, shape
      !! `(nx_total+1, ny_total)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%surface_stress%tau_x
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_tau_x_ptr

   function rdb_ocean_get_tau_y_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_tau_y_ptr")
      !! North-face wind stress (N/m^2): `surface_stress%tau_y`, shape
      !! `(nx_total, ny_total+1)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%surface_stress%tau_y
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_tau_y_ptr

   function rdb_ocean_get_q_heat_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_q_heat_ptr")
      !! Net surface heat flux (W/m^2): `h%state%surface_flux%Q_heat`. P2
      !! called this the "STANDALONE `sf` slot" (a separate, minimally-
      !! seeded object from `ocean_state%surface_flux`) because the P1
      !! step call never ran `ocean_surface_flux_assemble`. P2.4 unifies
      !! setup+step across all three callers via the shared engine
      !! (`rdb_ocean_engine`), so `rdb_ocean_step` now DOES run the
      !! assembler (via `engine_step_finalize`) against this SAME field —
      !! a write here is live-consumed the same way the driver's is.
      !! Shape `(nx_total, ny_total)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%surface_flux%Q_heat
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_q_heat_ptr

   function rdb_ocean_get_q_salt_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_q_salt_ptr")
      !! Net surface salt flux: `h%state%surface_flux%Q_salt` — see
      !! `rdb_ocean_get_q_heat_ptr` for the P2.4 unification note.
      !! Shape `(nx_total, ny_total)`.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%surface_flux%Q_salt
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_q_salt_ptr

   function rdb_ocean_get_kv_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_kv_ptr")
      !! Vertical viscosity (m^2/s): `vmix%kv`, shape
      !! `(nx_total, ny_total, nz_ml+1)` (layer INTERFACES).
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%vmix%kv
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_kv_ptr

   function rdb_ocean_get_kt_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_kt_ptr")
      !! Vertical heat diffusivity (m^2/s): `vmix%kt`, shape
      !! `(nx_total, ny_total, nz_ml+1)` (layer INTERFACES).
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%vmix%kt
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_kt_ptr

   function rdb_ocean_get_ks_ptr(c_handle, ptr, nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_ks_ptr")
      !! Vertical salt (+ every passive tracer) diffusivity (m^2/s):
      !! `vmix%ks`, shape `(nx_total, ny_total, nz_ml+1)` (layer
      !! INTERFACES). `ks ≡ kt` unless `&ocean_ddiff_nml` double diffusion
      !! is enabled.
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%vmix%ks
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_ks_ptr

   function rdb_ocean_get_wet_t_ptr(c_handle, ptr, nx, ny, gen) result(status) &
      bind(c, name="rdb_ocean_get_wet_t_ptr")
      !! Wet mask at T points (1 = wet, 0 = land): `metrics%wet_T`, shape
      !! `(nx_total, ny_total)`. Read-only (no setter — land masking is a
      !! configure-time / bathymetry concern).
      type(c_ptr), intent(in), value :: c_handle
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :)

      ptr = c_null_ptr
      nx = 0
      ny = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      tmp => h%state%metrics%wet_T
      call fill_getter_2d(ptr, nx, ny, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_wet_t_ptr

   ! ================================================================
   ! P2 — tracers by name (not index; budget attribution is
   ! `tracer_t%budget_id`, not an index comparison, and the recovered
   ! code had none)
   ! ================================================================

   function rdb_ocean_get_tracer_count(c_handle, count_out) result(status) &
      bind(c, name="rdb_ocean_get_tracer_count")
      !! Number of registered tracers (S, T, + any passive tracers) —
      !! bounds for `rdb_ocean_list_tracers`'s index argument.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(out) :: count_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      count_out = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (allocated(h%state%multilayer%tracers)) then
         count_out = int(size(h%state%multilayer%tracers), c_int)
      end if
   end function rdb_ocean_get_tracer_count

   function rdb_ocean_list_tracers(c_handle, idx, buf, cap) result(len_out) &
      bind(c, name="rdb_ocean_list_tracers")
      !! Name of the tracer at registry index `idx` (0-based). Same
      !! `snprintf`-style contract as `rdb_ocean_last_error`: copies up
      !! to `cap` bytes, NUL-terminates if room remains, returns the FULL
      !! trimmed name length. Returns 0 (buf untouched) on a bad handle,
      !! an out-of-range `idx`, or `cap <= 0`.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: idx
      integer(c_int), intent(in), value :: cap
      character(kind=c_char), intent(out) :: buf(cap)
      integer(c_int) :: len_out

      type(ocean_handle_t), pointer :: h
      integer :: hstat, n, ncopy, i
      character(len=32) :: name

      len_out = 0_c_int
      hstat = resolve_ocean(c_handle, h)
      if (hstat /= OCEAN_STATUS_OK) return
      if (.not. allocated(h%state%multilayer%tracers)) return
      if (idx < 0_c_int .or. idx >= int(size(h%state%multilayer%tracers), c_int)) return
      if (cap <= 0_c_int) return

      name = h%state%multilayer%tracers(int(idx) + 1)%name
      n = len_trim(name)
      len_out = int(n, c_int)
      ncopy = min(n, int(cap))
      do i = 1, ncopy
         buf(i) = name(i:i)
      end do
      if (ncopy < cap) buf(ncopy + 1) = achar(0)
   end function rdb_ocean_list_tracers

   function rdb_ocean_get_tracer_ptr(c_handle, name, name_len, ptr, nx, ny, nz, gen) &
      result(status) bind(c, name="rdb_ocean_get_tracer_ptr")
      !! Raw `h*Tr` store (NOT concentration — D3.2) for the tracer named
      !! `name`, by NAME (never index — the recovered code had no tracer
      !! accessors, and index comparison is already the wrong idiom in the
      !! Fortran itself). `OCEAN_STATUS_ERR_NOT_FOUND` if no registered
      !! tracer matches. Shape `(nx_total, ny_total, nz_ml)`.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: name_len
      character(kind=c_char), intent(in) :: name(name_len)
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)
      character(len=:), allocatable :: fname
      integer :: it, found, ierr_local

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      call c_to_f_string(name, name_len, fname)
      found = tracer_index_by_name(h, fname)
      if (found == 0) then
         call fail("rdb_ocean_get_tracer_ptr: no tracer named '"//fname//"'", &
                   ierr_local, OCEAN_STATUS_ERR_NOT_FOUND)
         status = int(ierr_local, c_int)
         return
      end if
      tmp => h%state%multilayer%tracers(found)%hTr
      call fill_getter_3d(ptr, nx, ny, nz, gen, tmp, h%state%dyn%outer_step_count)
   end function rdb_ocean_get_tracer_ptr

   ! ================================================================
   ! P2 — narrow setters. NARROW, NEVER BROAD (D3.5): each pushes only the
   ! device array(s) it just mutated, so calling one mid-run with no
   ! intervening read/refresh cannot rewind live device state to a stale
   ! host snapshot — the exact regression `ocean_setter_no_clobber` guards.
   ! No hTr -> Tr division happens here (D3.2) — the tracer setter takes
   ! concentration and forms `hTr = h_layer * value` on the physical
   ! interior using the CURRENT thickness (refreshed first); every other
   ! setter is a plain overwrite of its own array.
   ! ================================================================

   function rdb_ocean_set_h(c_handle, h_data, nx_p, ny_p, nz_p) result(status) &
      bind(c, name="rdb_ocean_set_h")
      !! Overwrite `h_layer` on the physical interior (k=1 bed .. k=nz
      !! surface), narrow-push it, then recompute `rho_layer` from the new
      !! thickness against whatever T/S currently sit on device (pulling
      !! the recomputed density back to host so the host stays
      !! authoritative). No other prognostic slot is touched.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p, nz_p
      real(c_double), intent(in) :: h_data(nx_p, ny_p, nz_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, i, j, k, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (.not. shape_matches_interior(h, nx_p, ny_p, nz_p)) then
         call fail("rdb_ocean_set_h: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (hl => h%state%multilayer%h_layer)
         do k = 1, nz_p
            do j = 1, ny_p
               do i = 1, nx_p
                  hl(ng + i, ng + j, k) = real(h_data(i, j, k), wp)
               end do
            end do
         end do
         !$acc update device(hl)
      end associate
      call ocean_eos_compute(h%state%eos, h%state%multilayer)
      associate (rl => h%state%multilayer%rho_layer)
         !$acc update self(rl)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_h

   function rdb_ocean_set_u(c_handle, u_data, nx_p, ny_p, nz_p) result(status) &
      bind(c, name="rdb_ocean_set_u")
      !! Overwrite `u_face_x_layer` on the physical interior faces
      !! (`nx_p+1` west faces bracketing `nx_p` physical columns) and
      !! narrow-push it. Does NOT re-derive `hu_face_x_layer` (a separate
      !! prognostic transport slot) — that stays whatever it was until the
      !! next step recomputes it.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p, nz_p
      real(c_double), intent(in) :: u_data(nx_p + 1, ny_p, nz_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (.not. shape_matches_interior(h, nx_p, ny_p, nz_p)) then
         call fail("rdb_ocean_set_u: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (uf => h%state%multilayer%u_face_x_layer)
         uf(ng + 1:ng + nx_p + 1, ng + 1:ng + ny_p, :) = real(u_data, wp)
         !$acc update device(uf)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_u

   function rdb_ocean_set_v(c_handle, v_data, nx_p, ny_p, nz_p) result(status) &
      bind(c, name="rdb_ocean_set_v")
      !! Overwrite `v_face_y_layer` on the physical interior faces
      !! (`ny_p+1` south faces bracketing `ny_p` physical rows) and
      !! narrow-push it. See `rdb_ocean_set_u` for the
      !! `hv_face_y_layer` caveat (not re-derived).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p, nz_p
      real(c_double), intent(in) :: v_data(nx_p, ny_p + 1, nz_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (.not. shape_matches_interior(h, nx_p, ny_p, nz_p)) then
         call fail("rdb_ocean_set_v: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (vf => h%state%multilayer%v_face_y_layer)
         vf(ng + 1:ng + nx_p, ng + 1:ng + ny_p + 1, :) = real(v_data, wp)
         !$acc update device(vf)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_v

   function rdb_ocean_set_bathymetry(c_handle, b_data, nx_p, ny_p) result(status) &
      bind(c, name="rdb_ocean_set_bathymetry")
      !! Overwrite bathymetry `b` (m, POSITIVE DOWN) on the physical
      !! interior, fill ghosts, re-wrap the periodic/fold seam, re-derive
      !! `bt_H_ref = b` (the mode-split contract) and re-wrap IT too, then
      !! narrow-push `b` (+ `bt_H_ref`) — mirrors the init-time sequence at
      !! `rdb_driver.F90` exactly, so a mid-run perturbation sees the same
      !! seam/BT-reference treatment the setup path does. No prognostic
      !! slot is touched.
      !!
      !! CAVEAT (mid-run, not a re-seed): does NOT re-derive the ALE
      !! `z_ref` table (ZSTAR_FULL) or the static land mask + C-grid face
      !! metrics — for those, drive the case from a namelist/restart
      !! instead (matches the recovered precedent's documented limit).
      !!
      !! REFUSED under an ice-shelf cavity (`&ocean_cavity_dyn_nml
      !! enable`). There the mode-split contract is `bt_H_ref = b −
      !! z_draft`, so the `bt_H_ref = b` re-derivation below would silently
      !! delete the ice load from the datum — and even re-deriving it
      !! correctly would not be enough: a new bed changes which columns
      !! GROUND, and this entry point deliberately does not rebuild the wet
      !! mask or the land-masked metrics. Re-seed from a namelist instead.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p
      real(c_double), intent(in) :: b_data(nx_p, ny_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local
      logical :: wrap_seam

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (nx_p /= h%grid%nx_phys .or. ny_p /= h%grid%ny_phys) then
         call fail("rdb_ocean_set_bathymetry: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      if (h%state%metrics%use_cavity) then
         call fail("rdb_ocean_set_bathymetry: refused with &ocean_cavity_dyn_nml "// &
                   "enable — the datum is bt_H_ref = b - z_draft, and a mid-run bed "// &
                   "swap would both drop the ice load from it and change which "// &
                   "columns ground (this entry point does not rebuild the wet mask). "// &
                   "Re-seed from a namelist instead.", &
                   ierr_local, OCEAN_STATUS_ERR_SETUP)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      ! Single-rank by construction: the API builds its engine through
      ! `engine_setup` with no compute rank/size (one rank, px = py = 1), so
      ! the local periodic wrap and the fold below hold the whole grid (the
      ! fold dispatcher takes its local-kernel branch).  Audited for the
      ! distributed fold (plan site A1): no px > 1 path reaches this.
      wrap_seam = h%state%bc%periodic_x .or. h%state%bc%periodic_y .or. h%state%bc%north_fold

      associate (b => h%state%barotropic%b)
         b(ng + 1:ng + nx_p, ng + 1:ng + ny_p) = real(b_data, wp)
         call fill_bathymetry_ghosts_array(b, h%grid)
         if (wrap_seam) then
            call ocean_periodic_wrap_centre_2d(b, h%grid%nx_total, h%grid%ny_total, &
                                               h%grid%nx_phys, h%grid%ny_phys, h%grid%nghost, &
                                               h%state%bc%periodic_x, h%state%bc%periodic_y)
            call ocean_fold_wrap_eta_2d(h%grid, h%state%bc, b)
         end if
         !$acc update device(b)
      end associate
      ! The isopycnal slopes' bed datum is a copy of `b` (no-op when the
      ! slot is off); refresh it from the re-wrapped field.
      if (h%state%slopes%is_init) then
         call h%state%slopes%set_bathymetry(h%state%barotropic%b)
         !$acc update device(h%state%slopes%bathy)
      end if

      if (h%state%dyn%n_inner >= 1) then
         associate (bref => h%state%dyn%bt_work%bt_H_ref)
            bref = h%state%barotropic%b
            if (wrap_seam) then
               call ocean_periodic_wrap_centre_2d(bref, h%grid%nx_total, h%grid%ny_total, &
                                                  h%grid%nx_phys, h%grid%ny_phys, h%grid%nghost, &
                                                  h%state%bc%periodic_x, h%state%bc%periodic_y)
               call ocean_fold_wrap_eta_2d(h%grid, h%state%bc, bref)
            end if
            !$acc update device(bref)
         end associate
      end if
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_bathymetry

   function rdb_ocean_set_wind(c_handle, taux_data, tauy_data, nx_p, ny_p) result(status) &
      bind(c, name="rdb_ocean_set_wind")
      !! Overwrite the C-grid surface wind stress (N/m^2) mid-run:
      !! `taux_data` is `(nx_p+1, ny_p)` (east faces), `tauy_data` is
      !! `(nx_p, ny_p+1)` (north faces) over the physical interior; ghost
      !! faces are left untouched (0 from init — wall faces see no
      !! spurious stress). Narrow-pushes `tau_x`/`tau_y` only. Settable
      !! repeatedly for a time-varying wind schedule.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p
      real(c_double), intent(in) :: taux_data(nx_p + 1, ny_p)
      real(c_double), intent(in) :: tauy_data(nx_p, ny_p + 1)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (nx_p /= h%grid%nx_phys .or. ny_p /= h%grid%ny_phys) then
         call fail("rdb_ocean_set_wind: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (tx => h%state%surface_stress%tau_x, ty => h%state%surface_stress%tau_y)
         tx(ng + 1:ng + nx_p + 1, ng + 1:ng + ny_p) = real(taux_data, wp)
         ty(ng + 1:ng + nx_p, ng + 1:ng + ny_p + 1) = real(tauy_data, wp)
         !$acc update device(tx, ty)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_wind

   function rdb_ocean_set_heat_flux(c_handle, q_data, nx_p, ny_p) result(status) &
      bind(c, name="rdb_ocean_set_heat_flux")
      !! Overwrite net surface heat flux `h%state%surface_flux%Q_heat`
      !! (W/m^2) on the physical interior, narrow-push. See
      !! `rdb_ocean_get_q_heat_ptr` for the P2.4 unification note.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p
      real(c_double), intent(in) :: q_data(nx_p, ny_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (nx_p /= h%grid%nx_phys .or. ny_p /= h%grid%ny_phys) then
         call fail("rdb_ocean_set_heat_flux: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (qh => h%state%surface_flux%Q_heat)
         qh(ng + 1:ng + nx_p, ng + 1:ng + ny_p) = real(q_data, wp)
         !$acc update device(qh)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_heat_flux

   function rdb_ocean_set_salt_flux(c_handle, q_data, nx_p, ny_p) result(status) &
      bind(c, name="rdb_ocean_set_salt_flux")
      !! Overwrite net surface salt flux `h%state%surface_flux%Q_salt` on
      !! the physical interior, narrow-push. See
      !! `rdb_ocean_get_q_heat_ptr` for the P2.4 unification note.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: nx_p, ny_p
      real(c_double), intent(in) :: q_data(nx_p, ny_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      integer :: ng, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (nx_p /= h%grid%nx_phys .or. ny_p /= h%grid%ny_phys) then
         call fail("rdb_ocean_set_salt_flux: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      ng = h%grid%nghost
      associate (qs => h%state%surface_flux%Q_salt)
         qs(ng + 1:ng + nx_p, ng + 1:ng + ny_p) = real(q_data, wp)
         !$acc update device(qs)
      end associate
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_salt_flux

   function rdb_ocean_set_tracer(c_handle, name, name_len, data, nx_p, ny_p, nz_p) &
      result(status) bind(c, name="rdb_ocean_set_tracer")
      !! Set the tracer named `name` to `data` (its own units — degC for
      !! temperature, PSU for salinity) on the physical interior. Verbatim
      !! the recovered `ocean_set_tracer_impl` sequence, generalised from
      !! hardcoded S/T to any registered tracer BY NAME: flush the
      !! windowed-advection accumulator -> refresh host (need the LIVE
      !! `h_layer` to form `hTr = h_layer*value`) -> mutate `hTr` on the
      !! physical interior -> narrow-push `hTr` -> recompute `rho_layer`
      !! (pulled back to host so it stays authoritative).
      !! `OCEAN_STATUS_ERR_NOT_FOUND` if no registered tracer matches.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: name_len
      character(kind=c_char), intent(in) :: name(name_len)
      integer(c_int), intent(in), value :: nx_p, ny_p, nz_p
      real(c_double), intent(in) :: data(nx_p, ny_p, nz_p)
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      character(len=:), allocatable :: fname
      integer :: ng, i, j, k, it, ierr_local

      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      if (.not. shape_matches_interior(h, nx_p, ny_p, nz_p)) then
         call fail("rdb_ocean_set_tracer: shape mismatch against the physical interior", &
                   ierr_local, OCEAN_STATUS_ERR_BAD_SHAPE)
         status = int(ierr_local, c_int)
         return
      end if

      call c_to_f_string(name, name_len, fname)
      it = tracer_index_by_name(h, fname)
      if (it == 0) then
         call fail("rdb_ocean_set_tracer: no tracer named '"//fname//"'", &
                   ierr_local, OCEAN_STATUS_ERR_NOT_FOUND)
         status = int(ierr_local, c_int)
         return
      end if

      ! Drain any open windowed tracer-advection accumulation BEFORE
      ! reading/overwriting the tracer, so pending transport (accumulated
      ! against the OLD field) is never later applied to the NEW one.
      ! Hard no-op at dt_tracer_advect_ratio<=1 (default; bit-identical).
      call ocean_dyn_flush_tracer_window(h%grid, h%state%metrics, h%state%dyn, &
                                         h%state%continuity, h%state%multilayer, &
                                         bc=h%state%bc)
      call ocean_handle_refresh_host(h)

      ng = h%grid%nghost
      associate (hl => h%state%multilayer%h_layer, htr => h%state%multilayer%tracers(it)%hTr)
         do k = 1, nz_p
            do j = 1, ny_p
               do i = 1, nx_p
                  htr(ng + i, ng + j, k) = hl(ng + i, ng + j, k)*real(data(i, j, k), wp)
               end do
            end do
         end do
         !$acc update device(htr)
      end associate
      call ocean_eos_compute(h%state%eos, h%state%multilayer)
      associate (rl => h%state%multilayer%rho_layer)
         !$acc update self(rl)
      end associate
      h%host_is_current = .true.
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_set_tracer

   ! ================================================================
   ! P2 — scalar diagnostics
   ! ================================================================

   function rdb_ocean_get_kinetic_energy(c_handle, ke_out) result(status) &
      bind(c, name="rdb_ocean_get_kinetic_energy")
      !! Total kinetic energy (J, up to the Boussinesq reference-density
      !! factor — matches `rdb_ocean_get_total_mass`'s convention of
      !! leaving `rho0` out) over the physical interior:
      !! `sum(0.5 * h_layer * (u_centre^2 + v_centre^2) * areaT)`, faces
      !! averaged to centres — same formula as
      !! `rdb_ocean_budgets::budget_total_ke`, computed inline (weighted by
      !! the metrics' `areaT`, so it is right on spherical / supergrid /
      !! tripolar grids too; `grid%dx*grid%dy` is only the fallback). Unlike
      !! the raw-pointer getters above, this refreshes the host itself —
      !! it hands back a NUMBER, not a pointer a caller could otherwise
      !! defer syncing for.
      type(c_ptr), intent(in), value :: c_handle
      real(c_double), intent(out) :: ke_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp) :: total, uc, vc
      integer :: i, j, k, ng, nxp, nyp
      logical :: use_area

      ke_out = 0.0_c_double
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      call ocean_handle_refresh_host(h)
      use_area = handle_has_area(h)
      ng = h%grid%nghost
      nxp = h%grid%nx_phys
      nyp = h%grid%ny_phys
      total = 0.0_wp
      associate (ms => h%state%multilayer)
         do k = 1, ms%nz_ml
            do j = 1, nyp
               do i = 1, nxp
                  uc = 0.5_wp*(ms%u_face_x_layer(ng + i, ng + j, k) + &
                               ms%u_face_x_layer(ng + i + 1, ng + j, k))
                  vc = 0.5_wp*(ms%v_face_y_layer(ng + i, ng + j, k) + &
                               ms%v_face_y_layer(ng + i, ng + j + 1, k))
                  if (use_area) then
                     total = total + ms%h_layer(ng + i, ng + j, k)*0.5_wp*(uc*uc + vc*vc)* &
                             h%state%metrics%areaT(ng + i, ng + j)
                  else
                     total = total + ms%h_layer(ng + i, ng + j, k)*0.5_wp*(uc*uc + vc*vc)
                  end if
               end do
            end do
         end do
      end associate
      if (use_area) then
         ke_out = real(total, c_double)
      else
         ke_out = real(total*h%grid%dx*h%grid%dy, c_double)
      end if
      status = int(OCEAN_STATUS_OK, c_int)
   end function rdb_ocean_get_kinetic_energy

   ! ================================================================
   ! P7 — diagnostics discoverability. Two STATIC catalogs (no handle
   ! needed — a Python user should be able to ask "what CAN I request"
   ! before ever calling create()) plus the LIVE registered set on one
   ! handle ("what IS this instance actually emitting"). Mirrors the
   ! `rdb_ocean_list_tracers` name-by-index / snprintf-style contract
   ! throughout: copies up to `cap` bytes, NUL-terminates if room
   ! remains, returns the FULL trimmed name length; 0 (buf untouched) on
   ! an out-of-range index or `cap <= 0`.
   ! ================================================================

   function rdb_ocean_derived_catalog_size() result(n) &
      bind(c, name="rdb_ocean_derived_catalog_size")
      !! Number of names in the DERIVED diagnostic catalog (vorticity_z,
      !! ke_total, mld_density, ... — `rdb_ocean_diag_derived`'s static
      !! table, opt-in via `&ocean_diag_nml diags`). No handle required:
      !! this is build-time information, reachable before `create()`.
      integer(c_int) :: n
      n = int(derived_catalog_size(), c_int)
   end function rdb_ocean_derived_catalog_size

   function rdb_ocean_derived_catalog_name(idx, buf, cap) result(len_out) &
      bind(c, name="rdb_ocean_derived_catalog_name")
      !! Name of derived-catalog entry `idx` (0-based, < the size above).
      integer(c_int), intent(in), value :: idx
      integer(c_int), intent(in), value :: cap
      character(kind=c_char), intent(out) :: buf(cap)
      integer(c_int) :: len_out

      character(len=64) :: name
      integer :: n, ncopy, i

      len_out = 0_c_int
      if (idx < 0_c_int .or. idx >= int(derived_catalog_size(), c_int)) return
      if (cap <= 0_c_int) return

      name = derived_catalog_name(int(idx) + 1)
      n = len_trim(name)
      len_out = int(n, c_int)
      ncopy = min(n, int(cap))
      do i = 1, ncopy
         buf(i) = name(i:i)
      end do
      if (ncopy < cap) buf(ncopy + 1) = achar(0)
   end function rdb_ocean_derived_catalog_name

   function rdb_ocean_canonical_catalog_size() result(n) &
      bind(c, name="rdb_ocean_canonical_catalog_size")
      !! Number of names in the CANONICAL diagnostic catalog (SSH,
      !! temperature, salinity, u, v, KE, ... — the set
      !! `register_default_diags` MAY register at setup; some entries
      !! are gated by another namelist group, e.g. `temperature` needs
      !! `&ocean_thermo_nml enable_thermodynamics`). No handle required.
      !! Use `rdb_ocean_get_diag_count`/`rdb_ocean_list_diags` on a
      !! live handle to see what actually registered.
      integer(c_int) :: n
      n = int(canonical_diag_catalog_size(), c_int)
   end function rdb_ocean_canonical_catalog_size

   function rdb_ocean_canonical_catalog_name(idx, buf, cap) result(len_out) &
      bind(c, name="rdb_ocean_canonical_catalog_name")
      !! Name of canonical-catalog entry `idx` (0-based, < the size above).
      integer(c_int), intent(in), value :: idx
      integer(c_int), intent(in), value :: cap
      character(kind=c_char), intent(out) :: buf(cap)
      integer(c_int) :: len_out

      character(len=16) :: name
      integer :: n, ncopy, i

      len_out = 0_c_int
      if (idx < 0_c_int .or. idx >= int(canonical_diag_catalog_size(), c_int)) return
      if (cap <= 0_c_int) return

      name = canonical_diag_catalog_name(int(idx) + 1)
      n = len_trim(name)
      len_out = int(n, c_int)
      ncopy = min(n, int(cap))
      do i = 1, ncopy
         buf(i) = name(i:i)
      end do
      if (ncopy < cap) buf(ncopy + 1) = achar(0)
   end function rdb_ocean_canonical_catalog_name

   function rdb_ocean_get_diag_count(c_handle, count_out) result(status) &
      bind(c, name="rdb_ocean_get_diag_count")
      !! Number of diagnostics REGISTERED on this live instance right
      !! now (canonical + derived + anything the `&ocean_diag_nml diags`
      !! token list added) — bounds for `rdb_ocean_list_diags`'s
      !! index argument. This is the "selected" set, as opposed to the
      !! two static "available" catalogs above.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(out) :: count_out
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h

      count_out = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return
      count_out = int(h%state%diag%nvars, c_int)
   end function rdb_ocean_get_diag_count

   function rdb_ocean_list_diags(c_handle, idx, buf, cap) result(len_out) &
      bind(c, name="rdb_ocean_list_diags")
      !! Name of the registered diagnostic at index `idx` (0-based).
      !! Same snprintf-style contract as `rdb_ocean_list_tracers`.
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: idx
      integer(c_int), intent(in), value :: cap
      character(kind=c_char), intent(out) :: buf(cap)
      integer(c_int) :: len_out

      type(ocean_handle_t), pointer :: h
      integer :: hstat, n, ncopy, i
      character(len=64) :: name

      len_out = 0_c_int
      hstat = resolve_ocean(c_handle, h)
      if (hstat /= OCEAN_STATUS_OK) return
      if (idx < 0_c_int .or. idx >= int(h%state%diag%nvars, c_int)) return
      if (cap <= 0_c_int) return

      name = h%state%diag%vars(int(idx) + 1)%name
      n = len_trim(name)
      len_out = int(n, c_int)
      ncopy = min(n, int(cap))
      do i = 1, ncopy
         buf(i) = name(i:i)
      end do
      if (ncopy < cap) buf(ncopy + 1) = achar(0)
   end function rdb_ocean_list_diags

   ! ================================================================
   ! P7 — in-memory diagnostic access. Returns the diag manager's OWN
   ! `output_buffer` for a registered diagnostic, by name — no NetCDF
   ! round-trip. `output_buffer` is pulled host-ward unconditionally at
   ! every cadence fire (`ocean_diag_step`, the `!$acc update self
   ! ... if_present` right before `v%fire_count` increments), so unlike
   ! the P2 state getters this needs no separate `refresh_host` call:
   ! by the time `rdb_ocean_step` returns, every diagnostic that
   ! fired this call is already host-current. `fire_count` (not
   ! `dyn%outer_step_count`) is the generation — a diagnostic on a
   ! multi-hour cadence should not look "stale" to Python between fires.
   ! ================================================================

   function rdb_ocean_get_diagnostic_ptr(c_handle, name, name_len, ptr, &
                                         nx, ny, nz, gen) result(status) &
      bind(c, name="rdb_ocean_get_diagnostic_ptr")
      !! Raw `output_buffer` for the diagnostic named `name` (LAYER vgrid:
      !! `(nx_total, ny_total, nz_ml)` for a layered var, `(nx_total,
      !! ny_total, 1)` for a 2D var; a non-LAYER `output_vgrid` reports
      !! the remapped shape, e.g. `nz` z-levels). `OCEAN_STATUS_ERR_NOT_FOUND`
      !! if `name` is not currently REGISTERED on this instance (it may
      !! still be a legal name on one of the two static catalogs, just
      !! gated off or not selected — see `rdb_ocean_get_diag_count`/
      !! `rdb_ocean_list_diags` to discover what IS registered).
      type(c_ptr), intent(in), value :: c_handle
      integer(c_int), intent(in), value :: name_len
      character(kind=c_char), intent(in) :: name(name_len)
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      integer(c_int) :: status

      type(ocean_handle_t), pointer :: h
      real(wp), pointer :: tmp(:, :, :)
      character(len=:), allocatable :: fname
      integer :: iv, found, ierr_local

      ptr = c_null_ptr
      nx = 0
      ny = 0
      nz = 0
      gen = 0
      status = resolve_ocean(c_handle, h)
      if (status /= OCEAN_STATUS_OK) return

      call c_to_f_string(name, name_len, fname)
      found = 0
      do iv = 1, h%state%diag%nvars
         if (trim(h%state%diag%vars(iv)%name) == fname) then
            found = iv
            exit
         end if
      end do
      if (found == 0) then
         call fail("rdb_ocean_get_diagnostic_ptr: no diagnostic named '"//fname// &
                   "' is currently registered (see rdb_ocean_list_diags)", &
                   ierr_local, OCEAN_STATUS_ERR_NOT_FOUND)
         status = int(ierr_local, c_int)
         return
      end if
      if (.not. allocated(h%state%diag%vars(found)%output_buffer)) then
         call fail("rdb_ocean_get_diagnostic_ptr: '"//fname// &
                   "' has no output_buffer allocated", ierr_local, OCEAN_STATUS_ERR_NOT_FOUND)
         status = int(ierr_local, c_int)
         return
      end if
      tmp => h%state%diag%vars(found)%output_buffer
      ptr = c_loc(tmp(1, 1, 1))
      nx = int(size(tmp, 1), c_int)
      ny = int(size(tmp, 2), c_int)
      nz = int(size(tmp, 3), c_int)
      gen = int(h%state%diag%vars(found)%fire_count, c_int)
   end function rdb_ocean_get_diagnostic_ptr

   ! ================================================================
   ! Internal helpers
   ! ================================================================

   function resolve_ocean(c_handle, h) result(status)
      !! Resolve + verify a handle is a live, fully-initialised ocean
      !! simulation. Every query/step entry point funnels through this so
      !! the bad-handle and not-yet-initialised cases are reported with one
      !! consistent status code each, in one place.
      type(c_ptr), intent(in) :: c_handle
      type(ocean_handle_t), pointer, intent(out) :: h
      integer(c_int) :: status

      integer :: hstat

      hstat = handle_check(c_handle, h)
      if (hstat /= HANDLE_OK) then
         h => null()
         status = int(OCEAN_STATUS_ERR_BAD_HANDLE, c_int)
         return
      end if
      if (.not. h%is_initialised) then
         h => null()
         status = int(OCEAN_STATUS_ERR_NOT_INITIALISED, c_int)
         return
      end if
      status = int(OCEAN_STATUS_OK, c_int)
   end function resolve_ocean

   function resolve_ocean_pending(c_handle, h) result(status)
      !! P2.5: resolve + verify a handle is a live, PENDING (not yet
      !! finalised) ocean simulation — the window every
      !! `rdb_ocean_stage_*` geometry-injection call requires. Mirrors
      !! `resolve_ocean`'s shape, checking `is_pending` instead of
      !! `is_initialised`.
      type(c_ptr), intent(in) :: c_handle
      type(ocean_handle_t), pointer, intent(out) :: h
      integer(c_int) :: status

      integer :: hstat

      hstat = handle_check(c_handle, h)
      if (hstat /= HANDLE_OK) then
         h => null()
         status = int(OCEAN_STATUS_ERR_BAD_HANDLE, c_int)
         return
      end if
      if (.not. h%is_pending) then
         h => null()
         status = int(OCEAN_STATUS_ERR_NOT_PENDING, c_int)
         return
      end if
      status = int(OCEAN_STATUS_OK, c_int)
   end function resolve_ocean_pending

   subroutine c_to_f_string(c_str, c_len, f_str)
      !! Convert a `bind(c)` `character(kind=c_char)` buffer + explicit
      !! length into a Fortran allocatable string. No null-termination
      !! assumption — `c_len` is authoritative (matches the recovered
      !! precedent's convention).
      integer(c_int), intent(in) :: c_len
      character(kind=c_char), intent(in) :: c_str(c_len)
      character(len=:), allocatable, intent(out) :: f_str

      integer :: i

      allocate (character(len=c_len) :: f_str)
      do i = 1, c_len
         f_str(i:i) = c_str(i)
      end do
   end subroutine c_to_f_string

   subroutine fill_getter_3d(ptr, nx, ny, nz, gen, arr, step)
      !! Shared tail of every 3D P2 getter: base-address `c_loc`, actual
      !! array extents (never assumed from grid metadata — read straight
      !! off the pointer), and the generation stamp. `arr` must already be
      !! pointer-associated with a live, non-empty state array.
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, nz, gen
      real(wp), pointer, intent(in) :: arr(:, :, :)
      integer, intent(in) :: step

      ptr = c_loc(arr(1, 1, 1))
      nx = int(size(arr, 1), c_int)
      ny = int(size(arr, 2), c_int)
      nz = int(size(arr, 3), c_int)
      gen = int(step, c_int)
   end subroutine fill_getter_3d

   subroutine fill_getter_2d(ptr, nx, ny, gen, arr, step)
      !! 2D counterpart of `fill_getter_3d`.
      type(c_ptr), intent(out) :: ptr
      integer(c_int), intent(out) :: nx, ny, gen
      real(wp), pointer, intent(in) :: arr(:, :)
      integer, intent(in) :: step

      ptr = c_loc(arr(1, 1))
      nx = int(size(arr, 1), c_int)
      ny = int(size(arr, 2), c_int)
      gen = int(step, c_int)
   end subroutine fill_getter_2d

   subroutine ocean_handle_refresh_host(h)
      !! Lazy D->H refresh of every P2-exposed leaf array, gated on
      !! `h%host_is_current`. `!$acc update self` on LEAF component names
      !! only, via `associate` — never the aggregate `ocean_state_t`/
      !! sub-state derived type (`rdb_ocean_dyn.F90:2949`). Inert on a
      !! host-only build.
      type(ocean_handle_t), pointer, intent(inout) :: h

      integer :: it

      if (h%host_is_current) return

      associate (ms => h%state%multilayer, bt => h%state%barotropic, &
                 bw => h%state%dyn%bt_work, ss => h%state%surface_stress, &
                 vm => h%state%vmix, mt => h%state%metrics, sf => h%state%surface_flux)
         !$acc update self(ms%h_layer, ms%u_face_x_layer, ms%v_face_y_layer, &
         !$acc&            ms%hu_face_x_layer, ms%hv_face_y_layer, &
         !$acc&            ms%w_interface, ms%rho_layer)
         if (allocated(ms%tracers)) then
            do it = 1, size(ms%tracers)
               !$acc update self(ms%tracers(it)%hTr)
            end do
         end if
         !$acc update self(bt%b, bw%bt_eta)
         !$acc update self(ss%tau_x, ss%tau_y)
         !$acc update self(sf%Q_heat, sf%Q_salt)
         !$acc update self(vm%kv, vm%kt, vm%ks)
         !$acc update self(mt%wet_T)
      end associate
      h%host_is_current = .true.
   end subroutine ocean_handle_refresh_host

   pure function tracer_index_by_name(h, name) result(idx)
      !! Registry index (1-based) of the tracer named `name`, or 0 if none
      !! matches. Name comparison is `trim`-both-sides (registry names are
      !! fixed-length `character(len=32)`).
      type(ocean_handle_t), pointer, intent(in) :: h
      character(len=*), intent(in) :: name
      integer :: idx

      integer :: it

      idx = 0
      if (.not. allocated(h%state%multilayer%tracers)) return
      do it = 1, size(h%state%multilayer%tracers)
         if (trim(h%state%multilayer%tracers(it)%name) == trim(name)) then
            idx = it
            return
         end if
      end do
   end function tracer_index_by_name

   pure function shape_matches_interior(h, nx_p, ny_p, nz_p) result(ok)
      !! True iff `(nx_p, ny_p, nz_p)` matches the live handle's physical
      !! interior shape (`grid%nx_phys`, `grid%ny_phys`,
      !! `multilayer%nz_ml`) — the shape every P2 3D setter's caller-
      !! supplied array must have.
      type(ocean_handle_t), pointer, intent(in) :: h
      integer(c_int), intent(in) :: nx_p, ny_p, nz_p
      logical :: ok

      ok = (nx_p == h%grid%nx_phys) .and. (ny_p == h%grid%ny_phys) .and. &
           (nz_p == h%state%multilayer%nz_ml)
   end function shape_matches_interior

end module rdb_ocean_api